@@ -145,7 +145,7 @@ async def test_prepared_toolset_user_error_add_new_tools():
145
145
@base_toolset .tool
146
146
def add (a : int , b : int ) -> int :
147
147
"""Add two numbers"""
148
- return a + b
148
+ return a + b # pragma: no cover
149
149
150
150
async def prepare_add_new_tool (ctx : RunContext [None ], tool_defs : list [ToolDefinition ]) -> list [ToolDefinition ]:
151
151
# Try to add a new tool that wasn't in the original set
@@ -175,7 +175,7 @@ async def test_prepared_toolset_user_error_change_tool_names():
175
175
@base_toolset .tool
176
176
def add (a : int , b : int ) -> int :
177
177
"""Add two numbers"""
178
- return a + b
178
+ return a + b # pragma: no cover
179
179
180
180
async def prepare_change_names (ctx : RunContext [None ], tool_defs : list [ToolDefinition ]) -> list [ToolDefinition ]:
181
181
# Try to change the name of an existing tool
@@ -193,126 +193,6 @@ async def prepare_change_names(ctx: RunContext[None], tool_defs: list[ToolDefini
193
193
await prepared_toolset .prepare_for_run (context )
194
194
195
195
196
- async def test_prepared_toolset_allows_removing_tools ():
197
- """Test that PreparedToolset allows removing tools from the original set."""
198
- context = build_run_context (None )
199
- base_toolset = FunctionToolset [None ]()
200
-
201
- @base_toolset .tool
202
- def add (a : int , b : int ) -> int :
203
- """Add two numbers"""
204
- return a + b
205
-
206
- @base_toolset .tool
207
- def subtract (a : int , b : int ) -> int :
208
- """Subtract two numbers"""
209
- return a - b
210
-
211
- @base_toolset .tool
212
- def multiply (a : int , b : int ) -> int :
213
- """Multiply two numbers"""
214
- return a * b
215
-
216
- async def prepare_remove_tools (ctx : RunContext [None ], tool_defs : list [ToolDefinition ]) -> list [ToolDefinition ]:
217
- # Remove the 'subtract' tool, keep 'add' and 'multiply'
218
- return [tool_def for tool_def in tool_defs if tool_def .name != 'subtract' ]
219
-
220
- prepared_toolset = PreparedToolset (base_toolset , prepare_remove_tools )
221
-
222
- # This should not raise an error
223
- run_toolset = await prepared_toolset .prepare_for_run (context )
224
-
225
- # Verify that only 'add' and 'multiply' tools are available
226
- assert set (run_toolset .tool_names ) == {'add' , 'multiply' }
227
- assert len (run_toolset .tool_defs ) == 2
228
-
229
- # Verify that the tools still work
230
- assert await run_toolset .call_tool (ToolCallPart (tool_name = 'add' , args = {'a' : 5 , 'b' : 3 }), context ) == 8
231
- assert await run_toolset .call_tool (ToolCallPart (tool_name = 'multiply' , args = {'a' : 4 , 'b' : 2 }), context ) == 8
232
-
233
-
234
- async def test_prefixed_toolset_tool_defs ():
235
- """Test that PrefixedToolset correctly prefixes tool definitions."""
236
- base_toolset = FunctionToolset [None ]()
237
-
238
- @base_toolset .tool
239
- def add (a : int , b : int ) -> int :
240
- """Add two numbers"""
241
- return a + b
242
-
243
- @base_toolset .tool
244
- def subtract (a : int , b : int ) -> int :
245
- """Subtract two numbers"""
246
- return a - b
247
-
248
- prefixed_toolset = PrefixedToolset (base_toolset , 'math' )
249
-
250
- # Check that tool names are prefixed
251
- assert prefixed_toolset .tool_names == ['math_add' , 'math_subtract' ]
252
-
253
- # Check that tool definitions have prefixed names
254
- tool_defs = prefixed_toolset .tool_defs
255
- assert len (tool_defs ) == 2
256
-
257
- add_def = next (td for td in tool_defs if td .name == 'math_add' )
258
- subtract_def = next (td for td in tool_defs if td .name == 'math_subtract' )
259
-
260
- assert add_def .name == 'math_add'
261
- assert add_def .description == 'Add two numbers'
262
- assert subtract_def .name == 'math_subtract'
263
- assert subtract_def .description == 'Subtract two numbers'
264
-
265
-
266
- async def test_prefixed_toolsetcall_tools ():
267
- """Test that PrefixedToolset correctly calls tools with prefixed names."""
268
- context = build_run_context (None )
269
- base_toolset = FunctionToolset [None ]()
270
-
271
- @base_toolset .tool
272
- def add (a : int , b : int ) -> int :
273
- """Add two numbers"""
274
- return a + b
275
-
276
- @base_toolset .tool
277
- def multiply (a : int , b : int ) -> int :
278
- """Multiply two numbers"""
279
- return a * b
280
-
281
- prefixed_toolset = PrefixedToolset (base_toolset , 'calc' )
282
-
283
- # Test calling tools with prefixed names
284
- result = await prefixed_toolset .call_tool (ToolCallPart (tool_name = 'calc_add' , args = {'a' : 5 , 'b' : 3 }), context )
285
- assert result == 8
286
-
287
- result = await prefixed_toolset .call_tool (ToolCallPart (tool_name = 'calc_multiply' , args = {'a' : 4 , 'b' : 2 }), context )
288
- assert result == 8
289
-
290
-
291
- async def test_prefixed_toolset_prepare_for_run ():
292
- """Test that PrefixedToolset correctly prepares for run with prefixed tools."""
293
- context = build_run_context (None )
294
- base_toolset = FunctionToolset [None ]()
295
-
296
- @base_toolset .tool
297
- def add (a : int , b : int ) -> int :
298
- """Add two numbers"""
299
- return a + b
300
-
301
- prefixed_toolset = PrefixedToolset (base_toolset , 'test' )
302
-
303
- # Prepare for run
304
- run_toolset = await prefixed_toolset .prepare_for_run (context )
305
-
306
- # Verify that the run toolset has prefixed tools
307
- assert run_toolset .tool_names == ['test_add' ]
308
- assert len (run_toolset .tool_defs ) == 1
309
- assert run_toolset .tool_defs [0 ].name == 'test_add'
310
-
311
- # Verify that the tool still works
312
- result = await run_toolset .call_tool (ToolCallPart (tool_name = 'test_add' , args = {'a' : 10 , 'b' : 5 }), context )
313
- assert result == 15
314
-
315
-
316
196
async def test_prefixed_toolset_error_invalid_prefix ():
317
197
"""Test that PrefixedToolset raises ValueError for tool names that don't start with the prefix."""
318
198
context = build_run_context (None )
@@ -321,46 +201,14 @@ async def test_prefixed_toolset_error_invalid_prefix():
321
201
@base_toolset .tool
322
202
def add (a : int , b : int ) -> int :
323
203
"""Add two numbers"""
324
- return a + b
204
+ return a + b # pragma: no cover
325
205
326
206
prefixed_toolset = PrefixedToolset (base_toolset , 'math' )
327
207
328
208
# Test calling with wrong prefix
329
209
with pytest .raises (ValueError , match = "Tool name 'wrong_add' does not start with prefix 'math_'" ):
330
210
await prefixed_toolset .call_tool (ToolCallPart (tool_name = 'wrong_add' , args = {'a' : 1 , 'b' : 2 }), context )
331
211
332
- # Test calling with no prefix
333
- with pytest .raises (ValueError , match = "Tool name 'add' does not start with prefix 'math_'" ):
334
- await prefixed_toolset .call_tool (ToolCallPart (tool_name = 'add' , args = {'a' : 1 , 'b' : 2 }), context )
335
-
336
- # Test calling with partial prefix
337
- with pytest .raises (ValueError , match = "Tool name 'mat_add' does not start with prefix 'math_'" ):
338
- await prefixed_toolset .call_tool (ToolCallPart (tool_name = 'mat_add' , args = {'a' : 1 , 'b' : 2 }), context )
339
-
340
-
341
- async def test_prefixed_toolset_empty_prefix ():
342
- """Test that PrefixedToolset works correctly with an empty prefix."""
343
- context = build_run_context (None )
344
- base_toolset = FunctionToolset [None ]()
345
-
346
- @base_toolset .tool
347
- def add (a : int , b : int ) -> int :
348
- """Add two numbers"""
349
- return a + b
350
-
351
- prefixed_toolset = PrefixedToolset (base_toolset , '' )
352
-
353
- # Check that tool names have empty prefix (just underscore)
354
- assert prefixed_toolset .tool_names == ['_add' ]
355
-
356
- # Test calling the tool
357
- result = await prefixed_toolset .call_tool (ToolCallPart (tool_name = '_add' , args = {'a' : 3 , 'b' : 4 }), context )
358
- assert result == 7
359
-
360
- # Test error for wrong name
361
- with pytest .raises (ValueError , match = "Tool name 'add' does not start with prefix '_'" ):
362
- await prefixed_toolset .call_tool (ToolCallPart (tool_name = 'add' , args = {'a' : 1 , 'b' : 2 }), context )
363
-
364
212
365
213
async def test_comprehensive_toolset_composition ():
366
214
"""Test that all toolsets can be composed together and work correctly."""
@@ -381,12 +229,12 @@ def add(a: int, b: int) -> int:
381
229
@math_toolset .tool
382
230
def subtract (a : int , b : int ) -> int :
383
231
"""Subtract two numbers"""
384
- return a - b
232
+ return a - b # pragma: no cover
385
233
386
234
@math_toolset .tool
387
235
def multiply (a : int , b : int ) -> int :
388
236
"""Multiply two numbers"""
389
- return a * b
237
+ return a * b # pragma: no cover
390
238
391
239
# Create second FunctionToolset with string operations
392
240
string_toolset = FunctionToolset [TestDeps ]()
@@ -399,31 +247,20 @@ def concat(s1: str, s2: str) -> str:
399
247
@string_toolset .tool
400
248
def uppercase (text : str ) -> str :
401
249
"""Convert text to uppercase"""
402
- return text .upper ()
250
+ return text .upper () # pragma: no cover
403
251
404
252
@string_toolset .tool
405
253
def reverse (text : str ) -> str :
406
254
"""Reverse a string"""
407
- return text [::- 1 ]
255
+ return text [::- 1 ] # pragma: no cover
408
256
409
257
# Create third FunctionToolset with advanced operations
410
258
advanced_toolset = FunctionToolset [TestDeps ]()
411
259
412
260
@advanced_toolset .tool
413
261
def power (base : int , exponent : int ) -> int :
414
262
"""Calculate base raised to the power of exponent"""
415
- return base ** exponent
416
-
417
- @advanced_toolset .tool
418
- def factorial (n : int ) -> int :
419
- """Calculate factorial of n"""
420
-
421
- def _fact (x : int ) -> int :
422
- if x <= 1 :
423
- return 1
424
- return x * _fact (x - 1 )
425
-
426
- return _fact (n )
263
+ return base ** exponent # pragma: no cover
427
264
428
265
# Step 1: Prefix each FunctionToolset individually
429
266
prefixed_math = PrefixedToolset (math_toolset , 'math' )
@@ -501,16 +338,6 @@ async def prepare_add_context(ctx: RunContext[TestDeps], tool_defs: list[ToolDef
501
338
'type' : 'object' ,
502
339
},
503
340
),
504
- ToolDefinition (
505
- name = 'adv_factorial' ,
506
- description = 'Calculate factorial of n (role: user)' ,
507
- parameters_json_schema = {
508
- 'additionalProperties' : False ,
509
- 'properties' : {'n' : {'type' : 'integer' }},
510
- 'required' : ['n' ],
511
- 'type' : 'object' ,
512
- },
513
- ),
514
341
]
515
342
)
516
343
# Call a tool and check result
@@ -593,16 +420,6 @@ async def prepare_add_context(ctx: RunContext[TestDeps], tool_defs: list[ToolDef
593
420
'type' : 'object' ,
594
421
},
595
422
),
596
- ToolDefinition (
597
- name = 'adv_factorial' ,
598
- description = 'Calculate factorial of n (role: admin)' ,
599
- parameters_json_schema = {
600
- 'additionalProperties' : False ,
601
- 'properties' : {'n' : {'type' : 'integer' }},
602
- 'required' : ['n' ],
603
- 'type' : 'object' ,
604
- },
605
- ),
606
423
]
607
424
)
608
425
result = await admin_final_toolset .call_tool (
@@ -656,3 +473,22 @@ async def prepare_add_context(ctx: RunContext[TestDeps], tool_defs: list[ToolDef
656
473
toolset_once = await prepared_toolset .prepare_for_run (ctx2 )
657
474
toolset_twice = await (await prepared_toolset .prepare_for_run (ctx1 )).prepare_for_run (ctx2 )
658
475
assert toolset_once == toolset_twice
476
+
477
+
478
+ async def test_context_manager ():
479
+ try :
480
+ from pydantic_ai .mcp import MCPServerStdio
481
+ except ImportError :
482
+ return
483
+
484
+ server1 = MCPServerStdio ('python' , ['-m' , 'tests.mcp_server' ])
485
+ server2 = MCPServerStdio ('python' , ['-m' , 'tests.mcp_server' ])
486
+ toolset = CombinedToolset ([server1 , PrefixedToolset (server2 , 'prefix' )])
487
+
488
+ async with toolset :
489
+ assert server1 .is_running
490
+ assert server2 .is_running
491
+
492
+ async with toolset :
493
+ assert server1 .is_running
494
+ assert server2 .is_running
0 commit comments