Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions src/mcp/server/lowlevel/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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)
Expand All @@ -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:
Expand Down
Loading