MCP 序列化问题 - Pydantic 排除空值模式
这是一个最小化的复现示例,展示了MCP客户端中Pydantic序列化的一个常见问题:可选字段在序列化时被处理为 null 这些值违反了MCP规范。
问题所在
当使用 Pydantic 与 MCP SDK 类型时,可选字段 None 被序列化为 null JSON中的值。然而,MCP服务器会严格验证可选字段是否应(满足特定条件或要求) 省略 完全地,而不是作为……发送 null。
没有 exclude_none=True: 服务器拒绝了请求
Error: Expected object, received null at path ["params", "capabilities", "roots"]随着 exclude_none=True: 服务器接受了请求 ✓
快速入门
cd /tmp/mcp-repro/client
python3 test_mcp_with_server.py这会运行两个测试:
- 破碎的 - 无MCP SDK类型
exclude_none=True→ 服务器验证失败 ❌ - 固定的 - MCP SDK 类型与
exclude_none=True→ 服务器接受初始化 ✓
文件
client/test_mcp_with_server.py- 使用实际的MCP SDK类型编写的Python客户端,用于测试两种场景server/simple_server.js- 使用官方MCP SDK并进行严格验证的Node.js MCP服务器
《The Fix》的中文译名可以是《补救》或《修正》。具体翻译可能需要根据上下文或该作品的具体内容来确定最贴切的译名。但在这里,“The Fix”被翻译为“补救”是一个比较通用且能传达原文基本含义的译法
在为MCP请求序列化Pydantic模型时,始终使用 exclude_none=True:
# BEFORE (broken)
init_request = InitializeRequest(...)
await self._send_request(init_request.model_dump())
# AFTER (fixed)
init_request = InitializeRequest(...)
await self._send_request(init_request.model_dump(exclude_none=True))为何这很重要
MCP规范要求,当未提供可选字段时,应从JSON中省略这些字段。发送时 null 未设置的可选字段的值违反了这一约定,并在严格的MCP服务器中导致验证错误。
这是MCP客户端实现中的一种常见模式——无论你在哪里对MCP协议消息进行序列化,并且这些消息包含可选字段,都应使用 exclude_none=True 以确保符合规格要求。
示例输出
TEST: BROKEN - Actual MCP SDK without exclude_none=True
Null fields in params: ['meta']
Total params fields: 4
→ Sending: {"method": "initialize", "params": {"meta": null, ...}}
← Received: {"error": {"code": -32603, "message": "[{\"code\": \"invalid_type\", ...}]"}}
Status: ❌ FAILED
TEST: FIXED - Actual MCP SDK with exclude_none=True
Null fields in params: []
Total params fields: 3
→ Sending: {"method": "initialize", "params": {"protocolVersion": "2024-11-05", ...}}
← Received: {"result": {"protocolVersion": "2024-11-05", ...}}
Status: ✓ SUCCESS如何将此作为学习示例
- 运行测试以查看两种情况
- 检查
client/test_mcp_with_server.py了解如何正确构建和序列化MCP请求 - 检查
server/simple_server.js理解MCP服务器验证 - 应用
exclude_none=True在你自己的MCP客户端代码中实现该模式
MCP规范参考
MCP规范可在以下网址获取:https://modelcontextprotocol.io/specification
关键要求:在请求/响应对象中,未提供的可选字段应在JSON中省略,不进行序列化 null。
