快速结论:当 MCP 工具的返回类型自引用(递归 BaseModel,以及 #3331 之后并入的递归 TypedDict)时,Pydantic 生成一个只含 $defs 和 $ref、缺少根 "type": "object" 的 outputSchema。这与 2026-07-28 会话兼容,但在 2025-11-25(legacy 协商)会话上会导致整个 tools/list 序列化校验失败。优先排查协商出的协议版本与递归返回类型的组合。
适用环境:MCP Python SDK 2.x(main),复现于 b2025ab8、0d921927、57394b0;CPython 3.12.13;使用内存内 Client(MCPServer) 路径。Issue 中未确认操作系统、CUDA、显卡或其它依赖版本。
最快修复方案:暂无确认的一步修复方案(Issue 已关闭,需按下方方式在协议层进行版本门控的 schema 归一化,或升级到包含该修复的版本)。
注意事项:归一化只应作用于 2025-11-25 协商路径,现代会话的 {$defs, $ref} 形态应保持原样;如果递归模型的根 $def 实际解析为非对象(如递归 list 或外部为 array 的 union),强行注入 "type": "object" 是错误的,此类情况需退回深度受限的 $ref 展开。
问题场景
在 MCPServer 上注册一个返回自引用 BaseModel 的工具(例如带 children: list["Node"] 的树节点),同一服务端还注册了普通工具(如返回 int)。现代(2026-07-28)客户端可以正常列出并调用;但以 mode="legacy" 连接 2025-11-25 会话的客户端调用 list_tools() 时,服务端在序列化 tools/list 结果时校验失败,客户端收到 MCPError: Handler returned an invalid result,且整个工具列表都不可用,而不只是那一个递归工具。
报错原文
ValidationError: 1 validation error for ListToolsResult
tools.0.outputSchema.type
Field required [type=missing, input_value={'$defs': {'Node': {...}}, '$ref': '#/$defs/Node'}, input_type=dict]
客户端侧表现为:
MCPError: Handler returned an invalid result
服务端日志另有:
handler for 'tools/list' returned an invalid result
原因分析
最可能的原因:Pydantic 对递归模型生成的根 schema 是 {"$defs": {...}, "$ref": "#/$defs/Node"},没有根级 "type"。这在 2026-07-28 的 schema 形态下是合法的;但 2025-11-25 的 Tool.outputSchema 形状要求根上必须有 type: "object",因此服务端在为 legacy 协商会话序列化 tools/list 结果时触发校验错误,异常影响到整个列表结果。故障点位于协议版本序列化边界,而非工具本身调用逻辑(评论反馈:现代会话下递归工具的 list 与 call 均成功)。
环境排查
- 确认 MCP Python SDK 版本线(Issue 指向 2.x
main及特定提交 b2025ab8、0d921927、57394b0)。 - 确认 CPython 版本(复现环境为 3.12.13)。
- 确认客户端协商到的协议版本:2026-07-28 还是 2025-11-25(
mode="legacy")。 - 确认工具返回类型是否自引用(递归
BaseModel,或 #3331 后的递归TypedDict)。 - 确认是否通过低层 server 的按版本严格校验路径,还是
MCPServer的请求感知tools/list路径。 - 获取服务端日志中的
ValidationError,确认报错字段为tools.0.outputSchema.type。
解决步骤
- 先确认问题属于版本边界问题:同一服务端用现代客户端(2026-07-28)
list_tools()应成功,仅 legacy 会话失败。 - 在协议层(
tools/list结果序列化处)做归一化,保持现代会话 schema 不变,仅对 2025-11-25 协商会话处理;门控条件为“协商版本为 legacy 且根缺少type”。 - 对根形如
{"$defs": ..., "$ref": "#/$defs/Node"}、且该$def显式为 object 的 schema,返回一份副本并注入同级"type": "object",即{"type": "object", "$ref": "#/$defs/Node", "$defs": {...}}。可优先尝试这一形态:draft-07 校验器忽略$ref的兄弟关键字,目标自身的type: "object"生效;2020-12 校验器会处理兄弟关键字,二者均能通过根 shape 检查。 - 不要为递归非对象 schema(例如
RootModel[list[...]])标注type: "object";此类情况改用仅限 legacy 路径的深度受限$ref展开作为回退。 - 保留低层 server 的按版本严格校验;将归一化放在统一序列化边界,使
BaseModel与TypedDict返回路径同时受益。 - 若已具备相应测试环境,新增回归测试:现代会话 schema 保持不变、legacy 会话
tools/list成功、递归非对象 schema 不被错误标注。
验证方法
同时用 Client(mcp)(2026-07-28)和 Client(mcp, mode="legacy")(2025-11-25)连接同一服务端:两者 list_tools() 都应成功,且能 call_tool() 递归工具并收到相同的 structuredContent;现代会话的输出 schema 应仍为原始的 {$defs, $ref} 形态,legacy 会话不应再出现 tools.0.outputSchema.type: Field required,客户端也不应再收到 MCPError: Handler returned an invalid result。
参考来源
modelcontextprotocol/python-sdk #3337
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。


