@@ -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,149 @@ 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" )],
574+ structuredContent = None ,
575+ isError = False
576+ )
577+
578+ result = await run_tool_test (tools , call_tool_handler , test_callback , validate_output = False )
579+
580+ # Verify server didn't return an error - it returned content without structured output
581+ assert result is not None
582+ assert not result .isError
583+ assert len (result .content ) == 1
584+ assert result .content [0 ].text == "No structured output"
585+ assert result .structuredContent is None
586+
587+
588+ @pytest .mark .anyio
589+ async def test_validate_output_true_with_invalid_schema ():
590+ """Test that when validate_output=True (default), invalid output schema is validated."""
591+ tools = [
592+ Tool (
593+ name = "tool_with_schema" ,
594+ description = "Tool with output schema" ,
595+ inputSchema = {
596+ "type" : "object" ,
597+ "properties" : {},
598+ },
599+ outputSchema = {
600+ "type" : "object" ,
601+ "properties" : {
602+ "required_field" : {"type" : "string" },
603+ },
604+ "required" : ["required_field" ],
605+ },
606+ )
607+ ]
608+
609+ async def call_tool_handler (name : str , arguments : dict [str , Any ]) -> dict [str , Any ]:
610+ if name == "tool_with_schema" :
611+ # Missing required field, validation is enabled (default)
612+ return {"other_field" : "value" }
613+ else : # pragma: no cover
614+ raise ValueError (f"Unknown tool: { name } " )
615+
616+ async def test_callback (client_session : ClientSession ) -> CallToolResult :
617+ return await client_session .call_tool ("tool_with_schema" , {})
618+
619+ result = await run_tool_test (tools , call_tool_handler , test_callback )
620+
621+ # Verify error - output validation is enabled by default
622+ assert result is not None
623+ assert result .isError
624+ assert "Output validation error:" in result .content [0 ].text
0 commit comments