@@ -21,13 +21,15 @@ async def run_tool_test(
2121 tools : list [Tool ],
2222 call_tool_handler : Callable [[str , dict [str , Any ]], Awaitable [Any ]],
2323 test_callback : Callable [[ClientSession ], Awaitable [CallToolResult ]],
24+ validate_output : bool = True ,
2425) -> CallToolResult | None :
2526 """Helper to run a tool test with minimal boilerplate.
2627
2728 Args:
2829 tools: List of tools to register
2930 call_tool_handler: Handler function for tool calls
3031 test_callback: Async function that performs the test using the client session
32+ validate_output: Whether to enable output validation (default: True)
3133
3234 Returns:
3335 The result of the tool call
@@ -40,7 +42,7 @@ async def run_tool_test(
4042 async def list_tools ():
4143 return tools
4244
43- @server .call_tool ()
45+ @server .call_tool (validate_output = validate_output )
4446 async def call_tool (name : str , arguments : dict [str , Any ]):
4547 return await call_tool_handler (name , arguments )
4648
@@ -474,3 +476,147 @@ async def test_callback(client_session: ClientSession) -> CallToolResult:
474476 assert result .content [0 ].type == "text"
475477 assert "Output validation error:" in result .content [0 ].text
476478 assert "'five' is not of type 'integer'" in result .content [0 ].text
479+
480+
481+ @pytest .mark .anyio
482+ async def test_validate_output_false_returns_invalid_schema ():
483+ """Test that when validate_output=False, server returns invalid output without error."""
484+ tools = [
485+ Tool (
486+ name = "tool_with_schema" ,
487+ description = "Tool with output schema" ,
488+ inputSchema = {
489+ "type" : "object" ,
490+ "properties" : {},
491+ },
492+ outputSchema = {
493+ "type" : "object" ,
494+ "properties" : {
495+ "required_field" : {"type" : "string" },
496+ },
497+ "required" : ["required_field" ],
498+ },
499+ )
500+ ]
501+
502+ async def call_tool_handler (name : str , arguments : dict [str , Any ]) -> dict [str , Any ]:
503+ if name == "tool_with_schema" :
504+ # Missing required field, but server validation is disabled
505+ return {"other_field" : "value" }
506+ else : # pragma: no cover
507+ raise ValueError (f"Unknown tool: { name } " )
508+
509+ async def test_callback (client_session : ClientSession ) -> CallToolResult :
510+ # Note: Even though server validation is disabled, client validation will still fail
511+ # This test verifies that the server doesn't return an error response
512+ try :
513+ return await client_session .call_tool ("tool_with_schema" , {})
514+ except RuntimeError as e :
515+ # Client validation failed, but that's expected
516+ # The important thing is that the server didn't return an error response
517+ # We can verify this by checking the error message
518+ assert "Invalid structured content" in str (e )
519+ # Return a mock result to indicate server didn't error
520+ return CallToolResult (
521+ content = [TextContent (type = "text" , text = "Server returned result" )],
522+ structuredContent = {"other_field" : "value" },
523+ isError = False ,
524+ )
525+
526+ result = await run_tool_test (tools , call_tool_handler , test_callback , validate_output = False )
527+
528+ # Verify server didn't return an error - it returned the invalid output
529+ assert result is not None
530+ assert not result .isError
531+ assert result .structuredContent == {"other_field" : "value" }
532+
533+
534+ @pytest .mark .anyio
535+ async def test_validate_output_false_returns_no_structured_output ():
536+ """Test that when validate_output=False, server returns without structured output without error."""
537+ tools = [
538+ Tool (
539+ name = "tool_with_schema" ,
540+ description = "Tool with output schema" ,
541+ inputSchema = {
542+ "type" : "object" ,
543+ "properties" : {},
544+ },
545+ outputSchema = {
546+ "type" : "object" ,
547+ "properties" : {
548+ "result" : {"type" : "string" },
549+ },
550+ "required" : ["result" ],
551+ },
552+ )
553+ ]
554+
555+ async def call_tool_handler (name : str , arguments : dict [str , Any ]) -> list [TextContent ]:
556+ if name == "tool_with_schema" :
557+ # Returns only content, no structured output, but server validation is disabled
558+ return [TextContent (type = "text" , text = "No structured output" )]
559+ else : # pragma: no cover
560+ raise ValueError (f"Unknown tool: { name } " )
561+
562+ async def test_callback (client_session : ClientSession ) -> CallToolResult :
563+ # Note: Even though server validation is disabled, client validation will still fail
564+ # This test verifies that the server doesn't return an error response
565+ try :
566+ return await client_session .call_tool ("tool_with_schema" , {})
567+ except RuntimeError as e :
568+ # Client validation failed, but that's expected
569+ # The important thing is that the server didn't return an error response
570+ assert "has an output schema but did not return structured content" in str (e )
571+ # Return a mock result to indicate server didn't error
572+ return CallToolResult (
573+ content = [TextContent (type = "text" , text = "No structured output" )], structuredContent = None , isError = False
574+ )
575+
576+ result = await run_tool_test (tools , call_tool_handler , test_callback , validate_output = False )
577+
578+ # Verify server didn't return an error - it returned content without structured output
579+ assert result is not None
580+ assert not result .isError
581+ assert len (result .content ) == 1
582+ assert result .content [0 ].text == "No structured output"
583+ assert result .structuredContent is None
584+
585+
586+ @pytest .mark .anyio
587+ async def test_validate_output_true_with_invalid_schema ():
588+ """Test that when validate_output=True (default), invalid output schema is validated."""
589+ tools = [
590+ Tool (
591+ name = "tool_with_schema" ,
592+ description = "Tool with output schema" ,
593+ inputSchema = {
594+ "type" : "object" ,
595+ "properties" : {},
596+ },
597+ outputSchema = {
598+ "type" : "object" ,
599+ "properties" : {
600+ "required_field" : {"type" : "string" },
601+ },
602+ "required" : ["required_field" ],
603+ },
604+ )
605+ ]
606+
607+ async def call_tool_handler (name : str , arguments : dict [str , Any ]) -> dict [str , Any ]:
608+ if name == "tool_with_schema" :
609+ # Missing required field, validation is enabled (default)
610+ return {"other_field" : "value" }
611+ else : # pragma: no cover
612+ raise ValueError (f"Unknown tool: { name } " )
613+
614+ async def test_callback (client_session : ClientSession ) -> CallToolResult :
615+ return await client_session .call_tool ("tool_with_schema" , {})
616+
617+ result = await run_tool_test (tools , call_tool_handler , test_callback )
618+
619+ # Verify error - output validation is enabled by default
620+ assert result is not None
621+ assert result .isError
622+ assert "Output validation error:" in result .content [0 ].text
0 commit comments