modelcontextprotocol / modelcontextprotocol/python-sdk
Generated output schema uses Pydantic's validation shape while structured output uses its serialization shape
还没有人认领这个 Issue。
- 主要语言
- Python
- 星标
- 24.3k
- 派生
- 4k
- 平均合并
- 1 天 1 小时
- 30 天内合并 PR
- 31
描述
Initial Checks
- I confirm that I'm using the latest version of MCP Python SDK.
- I confirm that I searched existing issues and pull requests before opening this issue.
Description
For a Pydantic return model whose validation and serialization shapes differ, the generated outputSchema describes the validation shape while structuredContent uses the serialization shape. The SDK therefore publishes an output schema that rejects its own generated structured result.
This is related to, but not a duplicate of, #1073 / #1099. That change aligned ordinary field aliases by serializing structured output with aliases. Split validation_alias / serialization_alias values still expose different validation and serialization shapes, and serialization-only fields such as computed_field reveal the same underlying mismatch.
Example Code
from __future__ import annotations
import asyncio
import json
from jsonschema import Draft202012Validator
from pydantic import BaseModel, ConfigDict, Field, computed_field
try:
from mcp.server.mcpserver.tools.base import Tool
except ImportError: # MCP Python SDK 1.x
from mcp.server.fastmcp.tools.base import Tool
class AliasOutput(BaseModel):
model_config = ConfigDict(extra="forbid", populate_by_name=True)
value: int = Field(validation_alias="wireIn", serialization_alias="wireOut")
def alias_output() -> AliasOutput:
return AliasOutput(value=1)
class ComputedOutput(BaseModel):
model_config = ConfigDict(extra="forbid")
value: int
@computed_field
@property
def doubled(self) -> int:
return self.value * 2
def computed_output() -> ComputedOutput:
return ComputedOutput(value=1)
async def check(function: object) -> None:
tool = Tool.from_function(function)
converted = await tool.run({}, None, convert_result=True)
structured = converted[1] if isinstance(converted, tuple) else converted.structured_content
errors = [
error.message
for error in Draft202012Validator(tool.output_schema).iter_errors(structured)
]
print(function.__name__)
print("schema:", json.dumps(tool.output_schema, sort_keys=True))
print("structured:", json.dumps(structured, sort_keys=True))
print("schema_errors:", errors)
async def main() -> None:
await check(alias_output)
await check(computed_output)
asyncio.run(main())
Observed validator messages:
alias_output
schema: ... "wireIn" ...
structured: {"wireOut": 1}
schema_errors: ["Additional properties are not allowed ('wireOut' was unexpected)", "'wireIn' is a required property"]
computed_output
schema: ... "value" ...
structured: {"doubled": 2, "value": 1}
schema_errors: ["Additional properties are not allowed ('doubled' was unexpected)"]
Expected behavior
The generated outputSchema should describe the serialized structured output. Generating the output model schema in Pydantic serialization mode makes both witnesses conform: the alias schema uses wireOut, and the computed-field schema includes doubled.
Python & MCP Python SDK
- Python: 3.13.13
- MCP Python SDK: 1.28.1 (latest stable)
- Also reproduced on current
main:2713b53b127afc094dc97d6067df9f69b647661c(2.0.0b2) - Pydantic: 2.13.4
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
调研方向
从 MCP 服务器工具的实现中的 Tool.from_function 开始,使用针对各个 SDK 版本显示的导入路径,并跟踪 output_schema 和 structured_content 是如何生成的。使用 Draft202012Validator 重现 alias_output 和 computed_output 示例。当生成的 schema 接受两个序列化的结构化结果(包括 wireOut 和 doubled)时,即表示完成。
由索引模型根据 Issue 内容生成。
评估
- 技术栈
- python
- 领域
- api, backend
- Issue 类型
- 缺陷
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 活跃度
- 冷清
- 描述清晰度
- 描述清楚
- 新手友好度
- 52/100