From 58889e60d39aa78db4b82799f4a1bd18ec12a542 Mon Sep 17 00:00:00 2001 From: SAY-5 Date: Tue, 14 Apr 2026 23:07:38 -0700 Subject: [PATCH] fix(server): allow tools with outputSchema to surface errors via unstructured content Per #2429, a low-level tool with an `outputSchema` cannot return a tool-execution failure (e.g. "Resource not found") via unstructured `TextContent` because the call_tool decorator unconditionally validates the result against `outputSchema` and replaces the original error message with a hard "outputSchema defined but no structured output returned" error. Mirror the TypeScript SDK fix (modelcontextprotocol/typescript-sdk#655): when an outputSchema-bound tool returns no structured content, treat the unstructured payload as a tool-error result (`isError=True`) instead of replacing it. Tools that *do* return structured output keep their existing validation contract, and the explicit `CallToolResult`-returning path is unchanged. Github-Issue:#2429 --- src/mcp/server/lowlevel/server.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/mcp/server/lowlevel/server.py b/src/mcp/server/lowlevel/server.py index 2dd1a8277..b6dc913e6 100644 --- a/src/mcp/server/lowlevel/server.py +++ b/src/mcp/server/lowlevel/server.py @@ -537,6 +537,8 @@ async def handler(req: types.CallToolRequest): # output normalization unstructured_content: UnstructuredContent maybe_structured_content: StructuredContent | None + is_error = False + if isinstance(results, types.CallToolResult): return types.ServerResult(results) elif isinstance(results, types.CreateTaskResult): @@ -556,12 +558,18 @@ async def handler(req: types.CallToolRequest): else: # pragma: no cover return self._make_error_result(f"Unexpected return type from tool: {type(results).__name__}") - # output validation + # output validation: a tool with an outputSchema must still + # be able to surface a tool-execution failure via unstructured + # content. If structured output is missing for such a tool, + # treat the unstructured payload as a tool-error result + # (isError=true) instead of replacing it with a hard + # "outputSchema defined but no structured output returned" + # error that obscures the original failure message. This + # mirrors the TypeScript SDK fix in + # modelcontextprotocol/typescript-sdk#655. if tool and tool.outputSchema is not None: if maybe_structured_content is None: - return self._make_error_result( - "Output validation error: outputSchema defined but no structured output returned" - ) + is_error = True else: try: jsonschema.validate(instance=maybe_structured_content, schema=tool.outputSchema) @@ -573,7 +581,7 @@ async def handler(req: types.CallToolRequest): types.CallToolResult( content=list(unstructured_content), structuredContent=maybe_structured_content, - isError=False, + isError=is_error, ) ) except UrlElicitationRequiredError: