1
+ import json
1
2
import logging
2
3
from typing import Any
3
4
4
5
import pytest
6
+ from inline_snapshot import snapshot
5
7
from mcp .types import Tool as MCPTool
6
- from pydantic import BaseModel
8
+ from pydantic import BaseModel , TypeAdapter
7
9
8
- from agents import FunctionTool , RunContextWrapper
10
+ from agents import Agent , FunctionTool , RunContextWrapper
9
11
from agents .exceptions import AgentsException , ModelBehaviorError
10
12
from agents .mcp import MCPServer , MCPUtil
11
13
@@ -18,7 +20,16 @@ class Foo(BaseModel):
18
20
19
21
20
22
class Bar (BaseModel ):
21
- qux : str
23
+ qux : dict [str , str ]
24
+
25
+
26
+ Baz = TypeAdapter (dict [str , str ])
27
+
28
+
29
+ def _convertible_schema () -> dict [str , Any ]:
30
+ schema = Foo .model_json_schema ()
31
+ schema ["additionalProperties" ] = False
32
+ return schema
22
33
23
34
24
35
@pytest .mark .asyncio
@@ -47,7 +58,7 @@ async def test_get_all_function_tools():
47
58
server3 .add_tool (names [4 ], schemas [4 ])
48
59
49
60
servers : list [MCPServer ] = [server1 , server2 , server3 ]
50
- tools = await MCPUtil .get_all_function_tools (servers )
61
+ tools = await MCPUtil .get_all_function_tools (servers , convert_schemas_to_strict = False )
51
62
assert len (tools ) == 5
52
63
assert all (tool .name in names for tool in tools )
53
64
@@ -56,6 +67,11 @@ async def test_get_all_function_tools():
56
67
assert tool .params_json_schema == schemas [idx ]
57
68
assert tool .name == names [idx ]
58
69
70
+ # Also make sure it works with strict schemas
71
+ tools = await MCPUtil .get_all_function_tools (servers , convert_schemas_to_strict = True )
72
+ assert len (tools ) == 5
73
+ assert all (tool .name in names for tool in tools )
74
+
59
75
60
76
@pytest .mark .asyncio
61
77
async def test_invoke_mcp_tool ():
@@ -107,3 +123,141 @@ async def test_mcp_invocation_crash_causes_error(caplog: pytest.LogCaptureFixtur
107
123
await MCPUtil .invoke_mcp_tool (server , tool , ctx , "" )
108
124
109
125
assert "Error invoking MCP tool test_tool_1" in caplog .text
126
+
127
+
128
+ @pytest .mark .asyncio
129
+ async def test_agent_convert_schemas_true ():
130
+ """Test that setting convert_schemas_to_strict to True converts non-strict schemas to strict.
131
+ - 'foo' tool is already strict and remains strict.
132
+ - 'bar' tool is non-strict and becomes strict (additionalProperties set to False, etc).
133
+ """
134
+ strict_schema = Foo .model_json_schema ()
135
+ non_strict_schema = Baz .json_schema ()
136
+ possible_to_convert_schema = _convertible_schema ()
137
+
138
+ server = FakeMCPServer ()
139
+ server .add_tool ("foo" , strict_schema )
140
+ server .add_tool ("bar" , non_strict_schema )
141
+ server .add_tool ("baz" , possible_to_convert_schema )
142
+ agent = Agent (
143
+ name = "test_agent" , mcp_servers = [server ], mcp_config = {"convert_schemas_to_strict" : True }
144
+ )
145
+ tools = await agent .get_mcp_tools ()
146
+
147
+ foo_tool = next (tool for tool in tools if tool .name == "foo" )
148
+ assert isinstance (foo_tool , FunctionTool )
149
+ bar_tool = next (tool for tool in tools if tool .name == "bar" )
150
+ assert isinstance (bar_tool , FunctionTool )
151
+ baz_tool = next (tool for tool in tools if tool .name == "baz" )
152
+ assert isinstance (baz_tool , FunctionTool )
153
+
154
+ # Checks that additionalProperties is set to False
155
+ assert foo_tool .params_json_schema == snapshot (
156
+ {
157
+ "properties" : {
158
+ "bar" : {"title" : "Bar" , "type" : "string" },
159
+ "baz" : {"title" : "Baz" , "type" : "integer" },
160
+ },
161
+ "required" : ["bar" , "baz" ],
162
+ "title" : "Foo" ,
163
+ "type" : "object" ,
164
+ "additionalProperties" : False ,
165
+ }
166
+ )
167
+ assert foo_tool .strict_json_schema is True , "foo_tool should be strict"
168
+
169
+ # Checks that additionalProperties is set to False
170
+ assert bar_tool .params_json_schema == snapshot (
171
+ {
172
+ "type" : "object" ,
173
+ "additionalProperties" : {"type" : "string" },
174
+ }
175
+ )
176
+ assert bar_tool .strict_json_schema is False , "bar_tool should not be strict"
177
+
178
+ # Checks that additionalProperties is set to False
179
+ assert baz_tool .params_json_schema == snapshot (
180
+ {
181
+ "properties" : {
182
+ "bar" : {"title" : "Bar" , "type" : "string" },
183
+ "baz" : {"title" : "Baz" , "type" : "integer" },
184
+ },
185
+ "required" : ["bar" , "baz" ],
186
+ "title" : "Foo" ,
187
+ "type" : "object" ,
188
+ "additionalProperties" : False ,
189
+ }
190
+ )
191
+ assert baz_tool .strict_json_schema is True , "baz_tool should be strict"
192
+
193
+
194
+ @pytest .mark .asyncio
195
+ async def test_agent_convert_schemas_false ():
196
+ """Test that setting convert_schemas_to_strict to False leaves tool schemas as non-strict.
197
+ - 'foo' tool remains strict.
198
+ - 'bar' tool remains non-strict (additionalProperties remains True).
199
+ """
200
+ strict_schema = Foo .model_json_schema ()
201
+ non_strict_schema = Baz .json_schema ()
202
+ possible_to_convert_schema = _convertible_schema ()
203
+
204
+ server = FakeMCPServer ()
205
+ server .add_tool ("foo" , strict_schema )
206
+ server .add_tool ("bar" , non_strict_schema )
207
+ server .add_tool ("baz" , possible_to_convert_schema )
208
+
209
+ agent = Agent (
210
+ name = "test_agent" , mcp_servers = [server ], mcp_config = {"convert_schemas_to_strict" : False }
211
+ )
212
+ tools = await agent .get_mcp_tools ()
213
+
214
+ foo_tool = next (tool for tool in tools if tool .name == "foo" )
215
+ assert isinstance (foo_tool , FunctionTool )
216
+ bar_tool = next (tool for tool in tools if tool .name == "bar" )
217
+ assert isinstance (bar_tool , FunctionTool )
218
+ baz_tool = next (tool for tool in tools if tool .name == "baz" )
219
+ assert isinstance (baz_tool , FunctionTool )
220
+
221
+ assert foo_tool .params_json_schema == strict_schema
222
+ assert foo_tool .strict_json_schema is False , "Shouldn't be converted unless specified"
223
+
224
+ assert bar_tool .params_json_schema == non_strict_schema
225
+ assert bar_tool .strict_json_schema is False
226
+
227
+ assert baz_tool .params_json_schema == possible_to_convert_schema
228
+ assert baz_tool .strict_json_schema is False , "Shouldn't be converted unless specified"
229
+
230
+
231
+ @pytest .mark .asyncio
232
+ async def test_agent_convert_schemas_unset ():
233
+ """Test that leaving convert_schemas_to_strict unset (defaulting to False) leaves tool schemas
234
+ as non-strict.
235
+ - 'foo' tool remains strict.
236
+ - 'bar' tool remains non-strict.
237
+ """
238
+ strict_schema = Foo .model_json_schema ()
239
+ non_strict_schema = Baz .json_schema ()
240
+ possible_to_convert_schema = _convertible_schema ()
241
+
242
+ server = FakeMCPServer ()
243
+ server .add_tool ("foo" , strict_schema )
244
+ server .add_tool ("bar" , non_strict_schema )
245
+ server .add_tool ("baz" , possible_to_convert_schema )
246
+ agent = Agent (name = "test_agent" , mcp_servers = [server ])
247
+ tools = await agent .get_mcp_tools ()
248
+
249
+ foo_tool = next (tool for tool in tools if tool .name == "foo" )
250
+ assert isinstance (foo_tool , FunctionTool )
251
+ bar_tool = next (tool for tool in tools if tool .name == "bar" )
252
+ assert isinstance (bar_tool , FunctionTool )
253
+ baz_tool = next (tool for tool in tools if tool .name == "baz" )
254
+ assert isinstance (baz_tool , FunctionTool )
255
+
256
+ assert foo_tool .params_json_schema == strict_schema
257
+ assert foo_tool .strict_json_schema is False , "Shouldn't be converted unless specified"
258
+
259
+ assert bar_tool .params_json_schema == non_strict_schema
260
+ assert bar_tool .strict_json_schema is False
261
+
262
+ assert baz_tool .params_json_schema == possible_to_convert_schema
263
+ assert baz_tool .strict_json_schema is False , "Shouldn't be converted unless specified"
0 commit comments