Recursive tool return type publishes an outputSchema with no root type, failing tools/list on 2025-11-25 sessions

当 MCP 工具的返回类型自引用(递归 BaseModel,以及 #3331 之后并入的递归 TypedDict)时,Pydantic 生成一个只含 $defs 和 $ref 、缺少根 "type": "object" 的 outputSchema。这与 2026-07-28 会话兼容,但在 202

快速结论:当 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。

解决步骤

  1. 先确认问题属于版本边界问题:同一服务端用现代客户端(2026-07-28)list_tools() 应成功,仅 legacy 会话失败。
  2. 在协议层(tools/list 结果序列化处)做归一化,保持现代会话 schema 不变,仅对 2025-11-25 协商会话处理;门控条件为“协商版本为 legacy 且根缺少 type”。
  3. 对根形如 {"$defs": ..., "$ref": "#/$defs/Node"}、且该 $def 显式为 object 的 schema,返回一份副本并注入同级 "type": "object",即 {"type": "object", "$ref": "#/$defs/Node", "$defs": {...}}。可优先尝试这一形态:draft-07 校验器忽略 $ref 的兄弟关键字,目标自身的 type: "object" 生效;2020-12 校验器会处理兄弟关键字,二者均能通过根 shape 检查。
  4. 不要为递归非对象 schema(例如 RootModel[list[...]])标注 type: "object";此类情况改用仅限 legacy 路径的深度受限 $ref 展开作为回退。
  5. 保留低层 server 的按版本严格校验;将归一化放在统一序列化边界,使 BaseModel 与 TypedDict 返回路径同时受益。
  6. 若已具备相应测试环境,新增回归测试:现代会话 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

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 32091

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注