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

这个报错发生在 MCP Python SDK 2.x 中,当工具返回值是自引用(递归)Pydantic 模型时,工具列表 schema 缺少根级 type: "object" ,导致旧协议(2025-11-25)的 tools/list 校验失败。优先确认是否是递归返回类型并升级到包含修复的 SDK

快速结论:这个报错发生在 MCP Python SDK 2.x 中,当工具返回值是自引用(递归)Pydantic 模型时,工具列表 schema 缺少根级 type: "object",导致旧协议(2025-11-25)的 tools/list 校验失败。优先确认是否是递归返回类型并升级到包含修复的 SDK 版本。

适用环境:MCP Python SDK 2.x(main 分支,Issue 验证于 b2025ab80d92192757394b0 等提交),CPython 3.12.13,使用内存 Client(MCPServer) 路径。

最快修复方案:暂无确认的一步修复方案(截至 Issue 关闭时,修复位于外部分支 Parker-Fawcett/fix/3337-recursive-tool-output-schema-object-root,尚未确认合入主分支)。可优先尝试检查是否使用了递归 BaseModelTypedDict 作为工具返回类型,并关注 SDK 后续版本更新。

注意事项:Issue 中提出的修复方案(在 FuncMetadata.model_post_init 阶段将裸 $ref 包装为 {"type": "object", "allOf": [{"$ref": ...}], "$defs": ...})仅在本地分支验证,尚未合并,属于“可能有效的方案”而非官方确认。是否将单个工具的 schema 问题降级为仅影响该工具而不是整个列表失败,仍是未决的设计问题。

问题场景

用户在使用 MCP Python SDK 2.x 创建工具服务器(MCPServer)时,某个工具函数的返回类型为递归/自引用的 Pydantic 模型(如 class Node(BaseModel),且 children: list["Node"])。当客户端以旧协议模式(mode="legacy",对应 2025-11-25 协议版本)连接并调用 tools/list 时,整个工具列表操作失败,客户端收到错误,而不是只影响那个递归工具。

报错原文

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
server logs "handler for 'tools/list' returned an invalid result"

原因分析

可能原因:当工具返回类型是递归/自引用的 Pydantic 模型时,Pydantic 发出的输出 schema 是 {"$defs": {...}, "$ref": "#/$defs/Node"},根位置没有 "type": "object"。这在 2026-07-28 协议版本下没问题,但 2025-11-25 协议版本要求 Tool.outputSchema 根级必须有 type: "object"。因此,在旧协议协商下序列化 tools/list 结果时校验失败,且失败影响的是整个列表,而不是单个工具。

Issue 中还指出,在 main 分支上,递归 BaseModel 返回类型受影响;PR #3331 将 TypedDict 返回原生交给 Pydantic,因此递归 TypedDict 也会出现同样问题(此前手工构建的镜像模型恰好内联了根类型)。

环境排查

  • 确认 MCP Python SDK 版本为 2.x(main 分支,Issue 观测于 b2025ab80d92192757394b0 等提交)。
  • 确认 Python 版本(Issue 验证环境为 CPython 3.12.13)。
  • 检查是否有工具函数的返回类型为递归 BaseModel(如 children: list["Node"])或递归 TypedDict
  • 确认客户端连接方式:是否使用 mode="legacy"(对应 2025-11-25 协议),以及是否可以通过内存 Client(MCPServer) 复现(Issue 中验证了该路径)。

解决步骤

  1. 先定位工具服务器中是否有递归/自引用的返回类型定义,例如:
    class Node(BaseModel):
        name: str
        children: list["Node"] = []
  2. 检查使用的 MCP Python SDK 版本,并查看是否有已发布的修复版本(Issue 关闭于 2026-08-24,建议升级到该日期之后的最新版)。
  3. 如果无法升级,可优先尝试参考 Issue 中验证过的修复思路(尚未合并到主分支,仅供参考):在 schema 生成阶段(FuncMetadata.model_post_init,即 StrictJsonSchema 推导处),当 Pydantic 发出根级是裸 $ref 且目标 $defs 条目是 object 类型时,发布为:
    {"type": "object", "allOf": [{"$ref": "#/$defs/Node"}], "$defs": {…}}
  4. 注意:不要改变非递归、非对象类型的 schema(如 RootModel[list[...]]),只修复原本就损坏的递归对象场景。
  5. 如果不需要旧协议兼容,可以考虑强制客户端使用新协议(不加 mode="legacy"),但这不是根治方案。

验证方法

确认修复有效的方法是:在修复后,同时用 Client(mcp)(新协议)和 Client(mcp, mode="legacy")(旧协议)调用 tools/list,两者都应成功,且两种协议下返回的递归工具 structuredContent 一致。同时确认现代会话(2026-07-28)下非递归工具的 schema 与修复前完全一致,不受影响。建议补充回归测试:递归 BaseModel 返回的精确 schema 单元测试,以及两种协议下 tools/list 都成功的端到端测试。

参考来源

modelcontextprotocol/python-sdk #3337

GamsGo AI

AI 工具推荐

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

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

了解 GamsGo AI

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

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 20141

发表回复

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