You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
CallToolResult structuredContent is not alias-normalized to match outputSchema #3467
FuncMetadata.convert_result treats a plain model return and a CallToolResult return differently for Pydantic field aliases.
Plain returns: validate_python(..., by_alias=True, by_name=True) then model_dump(..., by_alias=True) / dump_python(..., by_alias=True), so structuredContent keys match outputSchema.
CallToolResult path: validate_python(result.structured_content) only, then returns the result unchanged.
Because validation accepts Python field names when populate_by_name / by_name is in play, a tool can pass validation while emitting wire keys that do not match the published outputSchema (which uses aliases). Clients that validate structuredContent against outputSchema then reject a server-produced payload.
Related but not the same as #1073 / #1099 (plain-return alias dump) or #3100 / #3118 (schema validation vs serialization mode). This is specifically the CallToolResult short-circuit skipping alias normalization.
Verified on current main tip 08a3bc8eaf5bb69a6cb05ac708a86f5325977c20.
Expected: after validating CallToolResult.structured_content, normalize it the same way as the plain-return path (by_alias dump) before returning.
Example Code
from __future__ importannotationsimportjsonfromtypingimportAnnotatedfrompydanticimportBaseModel, ConfigDict, Fieldfrommcp_typesimportCallToolResult, TextContentfrommcp.server.mcpserver.utilities.func_metadataimportfunc_metadataclassAliasOut(BaseModel):
model_config=ConfigDict(populate_by_name=True)
field_first: str=Field(alias="first")
field_second: str=Field(alias="second")
defplain() ->AliasOut:
returnAliasOut(field_first="a", field_second="b")
defvia_call_tool_result() ->Annotated[CallToolResult, AliasOut]:
# Python field names — accepted by validate when populate_by_name is onreturnCallToolResult(
content=[TextContent(text="ok")],
structured_content={"field_first": "a", "field_second": "b"},
)
plain_meta=func_metadata(plain)
ctr_meta=func_metadata(via_call_tool_result)
print("outputSchema keys:", sorted(plain_meta.output_schema["properties"]))
print("plain:", json.dumps(plain_meta.convert_result(plain()).structured_content, sort_keys=True))
print(
"CallToolResult:",
json.dumps(ctr_meta.convert_result(via_call_tool_result()).structured_content, sort_keys=True),
)
MCP Python SDK: current main @ 08a3bc8eaf5bb69a6cb05ac708a86f5325977c20 (2.1.2.dev16+08a3bc8)
Pydantic: 2.12.5 (venv) / also reproduced with system 2.13.5 for imports
AI disclosure
This issue was drafted with AI assistance (Cursor / Grok). I verified the reproduction on current main and reviewed the convert_result paths in func_metadata.py before filing. I would like to open a fix PR for this issue.
Hi! I would like to take this issue. I have read #3467 and the auto-closed #3468 (which was closed by the assignment bot, not on merit), and I can implement the alias normalization on the CallToolResult path mirroring the plain-return path (validate_python + by_alias dump), with a regression test using the AliasOut shape from the issue. Could a maintainer please assign #3467 to me? Once assigned I will open the PR. Thanks!
Activity
feiiiiii5 commentedon Sep 13, 2026
Hi! I would like to take this issue. I have read #3467 and the auto-closed #3468 (which was closed by the assignment bot, not on merit), and I can implement the alias normalization on the
CallToolResultpath mirroring the plain-return path (validate_python+by_aliasdump), with a regression test using theAliasOutshape from the issue. Could a maintainer please assign #3467 to me? Once assigned I will open the PR. Thanks!Vish12345678673 commentedon Sep 18, 2026
I reviewed this issue and confirmed the distinction between the plain return path and the CallToolResult path in FuncMetadata.convert_result().
I can reproduce the alias mismatch described here and would like to work on the fix and regression coverage.
I have not started a PR yet and will wait for maintainer confirmation before proceeding.