Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

CallToolResult structuredContent is not alias-normalized to match outputSchema

Open
#3,467 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
76/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
python
Domain
api

Research direction

Start in mcp/server/mcpserver/utilities/func_metadata.py at FuncMetadata.convert_result and compare the CallToolResult branch with the plain-return path described in the issue. Run the provided alias example first; done when CallToolResult.structured_content uses the outputSchema aliases after conversion while preserving the rest of the result.

Written by the indexing model from the issue text.

Description

v1 v2
Initial Checks
Release line

2.x (current stable)

Description

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__ import annotations
import json
from typing import Annotated

from pydantic import BaseModel, ConfigDict, Field
from mcp_types import CallToolResult, TextContent
from mcp.server.mcpserver.utilities.func_metadata import func_metadata


class AliasOut(BaseModel):
    model_config = ConfigDict(populate_by_name=True)
    field_first: str = Field(alias="first")
    field_second: str = Field(alias="second")


def plain() -> AliasOut:
    return AliasOut(field_first="a", field_second="b")


def via_call_tool_result() -> Annotated[CallToolResult, AliasOut]:
    # Python field names — accepted by validate when populate_by_name is on
    return CallToolResult(
        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),
)

Observed on main @ 08a3bc8:

outputSchema keys: ['first', 'second']
plain: {"first": "a", "second": "b"}
CallToolResult: {"field_first": "a", "field_second": "b"}
Python & MCP Python SDK
  • Python: 3.12.10
  • 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.

Dominant language
Python
Stars
24.3k
Forks
4k
Avg merge
1d 11h
Merged PRs (30d)
30

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from modelcontextprotocol/python-sdk

All issues in modelcontextprotocol/python-sdk

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.