modelcontextprotocol / modelcontextprotocol/python-sdk

CallToolResult structuredContent is not alias-normalized to match outputSchema

Ouverte
#3,467 2 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

v1 v2
Langage dominant
Python
Étoiles
24.3k
Forks
4k
Merge moyen
1 j 1 h
PR mergées (30 j)
31

Description

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.

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Commencez dans mcp/server/mcpserver/utilities/func_metadata.py, au niveau de FuncMetadata.convert_result, et comparez la branche CallToolResult avec le chemin de retour simple décrit dans l’issue. Exécutez d’abord l’exemple d’alias fourni ; c’est terminé lorsque CallToolResult.structured_content utilise les alias de outputSchema après la conversion tout en préservant le reste du résultat.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
python
Domaine
api
Type d'issue
Bug
Difficulté
3/5
Temps estimé
1-2 jours
Activité
Active
Clarté
Clairement spécifiée
Accessibilité débutants
76/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.