DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

MCP 把 null 当成缺失

MCP 返回显式 null,旧客户端却按缺失结果报错。区分字段不存在与 null,核对 Python SDK 2.3.0 的输出 schema 验证和发送端边界。

工具声明的 outputSchema 允许 null,服务端也明确返回了 "structuredContent": null,Python MCP 客户端却报:

Tool nullable has an output schema but did not return structured content

先核对结果中这个成员究竟有没有出现。旧 Python SDK 2.x 会把显式 JSON null 与未提供 structuredContent 混为一谈,在执行输出 schema 验证之前就抛出“未返回结构化内容”。已核对的正式版 2.3.0 修正了这一判断。

区分 None 与字段缺失

下面两个结果形状含义不同。这里只展示工具结果对象,不是完整 JSON-RPC 响应:

{"content": [], "structuredContent": null}
{"content": []}

Pydantic 解析后,两者的 structured_content 属性都可能为 None。前者明确提供了值,后者没有提供字段;模型的 model_fields_set 可以区分这两种情况。

因此,检查时应保留脱敏后的实际响应形状、工具的 outputSchema 和客户端版本。只打印 result.structured_content,再看它是不是 None,不足以确认服务端漏了结果。

这一问题针对声明了输出 schema 的工具。没有输出 schema 的结果,以及工具明确返回的 isError 结果,不应套用同样的输出验证结论。

null 是否有效,仍由 schema 决定

2026-07-28 协议的结构化工具结果允许任意 JSON 值,包括 null,但仍须符合工具声明的输出 schema。例如 {"type": "null"} 允许 null,{"type": "object"} 则不允许。

所以修复不能把所有 null 都当作成功,也不能为了消除异常,随意给业务 schema 加上可空类型。如果工具承诺返回对象,null 应继续作为不符合契约的结果被拒绝。

旧客户端已能验证 0、false 和空字符串;这里的问题是把 None 用作字段缺失判断,不能泛化成所有假值都被忽略。

选择修正字段存在性判断的客户端

2.3.0 的验证路径仅在值为 None、且 structured_content 没有被设置时认定结果缺失。明确提供的 null 会进入已有的 schema 验证:schema 允许则返回,不允许则报结构化内容不合法。

旧部署应选择包含此修复、且与应用兼容的客户端发行版。不要关闭输出校验,也不要把原始字符串直接当成已经通过校验的结果。应用自建 result resolver 时,也要保留显式值与字段省略的区别。

还要检查实际发送方。已核对的 2.3.0 Python SDK 服务端仍会在出站序列化时省略 null 的 structuredContent;客户端修复没有同时改变这项行为。若线上收到的字段本来就不存在,仅升级客户端不会凭空得到一个显式 null。服务端需要按自己的协议契约产生结果,而不是用文本内容冒充缺失的结构化成员。

用三种结果验收

在获准的测试环境中,对允许 null 的 schema,明确返回 null 应能通过;对只允许 object 的 schema,同样的 null 应报 schema 不匹配;完全省略 structuredContent 时,即使 schema 允许 null,也仍应报缺失。

这三种结果要分别确认,不能只看其中一个调用不再报错。若升级后出现 Invalid structured content returned by tool,应检查结果是否真的符合 schema;错误从“字段缺失”变成“类型不符合”,可能正说明 null 已进入正确的验证路径。