CallToolResult structuredContent is not alias-normalized to match outputSchema

Offen
#3,467 2 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

Bewertung

Schwierigkeit
3/5
Geschätzter Aufwand
1-2 Tage
Anfängerfreundlichkeit
76/100
Issue-Typ
Bug
Klarheit
Klar beschrieben
Aktivitätsstatus
Aktiv
Tech-Stack
python
Bereich
api

Rechercherichtung

Beginne in mcp/server/mcpserver/utilities/func_metadata.py bei FuncMetadata.convert_result und vergleiche den CallToolResult-Zweig mit dem im Issue beschriebenen Pfad für einfache Rückgabewerte. Führe zuerst das bereitgestellte Alias-Beispiel aus; fertig, wenn CallToolResult.structured_content nach der Konvertierung die outputSchema-Aliase verwendet und der Rest des Ergebnisses erhalten bleibt.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Beschreibung

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.

Vorherrschende Sprache
Python
Sterne
24.3k
Forks
4k
Ø Merge
1 T. 19 Min.
Gemergte PRs (30 T.)
29

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
  3. Forken Sie das Repository und arbeiten Sie in einem Branch.
  4. Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.

Mehr aus modelcontextprotocol/python-sdk

Alle Issues in modelcontextprotocol/python-sdk

Ähnliche Issues

Weitere Issues zu Python

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.