diff --git a/src/mcp/server/fastmcp/utilities/func_metadata.py b/src/mcp/server/fastmcp/utilities/func_metadata.py index 241100d31..2438a2a54 100644 --- a/src/mcp/server/fastmcp/utilities/func_metadata.py +++ b/src/mcp/server/fastmcp/utilities/func_metadata.py @@ -112,7 +112,7 @@ def convert_result(self, result: Any) -> Any: the structured output. """ if isinstance(result, CallToolResult): - if self.output_schema is not None: + if self.output_schema is not None and not result.isError: assert self.output_model is not None, "Output model must be set if output schema is defined" self.output_model.model_validate(result.structuredContent) return result diff --git a/tests/server/fastmcp/test_func_metadata.py b/tests/server/fastmcp/test_func_metadata.py index 61e524290..facb9a3d2 100644 --- a/tests/server/fastmcp/test_func_metadata.py +++ b/tests/server/fastmcp/test_func_metadata.py @@ -878,6 +878,20 @@ def func_returning_annotated_tool_call_result() -> Annotated[CallToolResult, Per meta.convert_result(func_returning_annotated_tool_call_result()) +def test_tool_call_result_annotated_error_skips_structured_validation(): + class PersonClass(BaseModel): + name: str + + def func_returning_annotated_tool_call_result_error() -> Annotated[CallToolResult, PersonClass]: # pragma: no cover + return CallToolResult(content=[], isError=True) + + meta = func_metadata(func_returning_annotated_tool_call_result_error) + result = func_returning_annotated_tool_call_result_error() + + assert meta.output_schema is not None + assert meta.convert_result(result) is result + + def test_tool_call_result_in_optional_is_rejected(): """Test that Optional[CallToolResult] raises InvalidSignature"""