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