快速结论:当 MCP Python SDK 2.x 的服务器没有配置任何 experimental capabilities 时,`initialize` 路径返回 `capabilities.experimental == {}`,而 `server/discover` 路径返回 `None`(且从 wire 中省略该字段),两条发现路径语义不一致。优先排查客户端代码是否对两条路径统一读取 `experimental`,并按汇总结论改用 `caps.experimental or {}`。
适用环境:Issue 已确认环境为 Windows、Python 3.14.3、MCP Python SDK 2.0.0、mcp-types 2.0.0、Pydantic 2.13.4。仓库标签为 v2 / P2 / spec-2026-07-28。
最快修复方案:升级到包含 #3614 修复的 SDK 版本。修复后,未配置 experimental 能力的服务器在 `initialize` 结果中同样不再输出 `experimental`,两条路径均返回 `None`。读取侧使用 `caps.experimental or {}` 兼容任一表示。
注意事项:维护者选择“统一省略”而非“统一发送 `{}`”,因为 `server/discover` 和其他已核对的 SDK 本就不输出该字段。该修复只覆盖未配置 experimental capabilities 的场景;初始化时传入非空 experimental map 的情形不在本 Issue 范围内(这些值不会存储在 `Server` 上,因而也不会自动出现在 discover 中),如需该行为应另开 Issue。对已有迁移门禁而言,原先针对 `{}` 与 `None` 差异设定的预期 delta 需要在升级后复核并移除或修订。
问题场景
在 MCP Python SDK 2.0.0 中创建一个未配置 experimental capabilities 的 Server(例如 Server("repro", version="0.0.0")),然后分别通过两条公开发现路径读取能力声明:传统的 create_initialization_options().capabilities(即 initialize 握手)和 get_capabilities(protocol_version="2026-07-28")(即 server/discover)。Issue 正文通过最小复现脚本对比两者,同时下游迁移门禁也依赖这个差异作为临时期望值。
报错原文
[v2] MCPServer reports empty experimental capabilities as {} via initialize but None via server/discover
最小复现脚本的实测输出:
legacy {} True
modern None False
原因分析
根据 Issue 中的源码定位,两条路径对“缺失的 experimental map”处理方式不同:
initialize路径把缺失的 experimental map 转换为{},因此该字段存在且为空对象。get_capabilities保留None;类型定义中experimental默认即为None。- 现代 discover handler 调用
get_capabilities时未传入 experimental map,runner 在序列化时按exclude_none省略None字段,因此 wire 上该字段缺失,解析后的 SDK 模型则表现为None。 - 协议 schema 将该字段定义为可选且为对象类型,并未强制规定唯一的 JSON 或 SDK 表示;因此
{}与省略/`None` 都能通过校验。
这是客户端可见的差异:对旧表示使用 .get(...) 可以工作,但对新表示会抛出异常;is not None 之类的判断语义也随之改变。
环境排查
- 确认
mcp版本是否为 2.0.0(或受影响的其他 2.x 版本)。 - 确认
mcp-types版本。 - 确认 Pydantic 版本(Issue 环境为 2.13.4)。
- 确认 Python 版本(Issue 环境为 3.14.3)。
- 确认操作系统(Issue 报告为 Windows)。
- 确认代码走的是
initialize、server/discover,还是两条路径都在使用。 - 确认服务器是否配置过任何 experimental capabilities(未配置才触发本差异)。
解决步骤
- 升级 MCP Python SDK 到包含 #3614 的版本。该修复已由维护者确认合并。
- 升级后重新运行最小复现脚本(或等价检查):创建一个未配置 experimental capabilities 的
Server,分别取create_initialization_options().capabilities与get_capabilities(protocol_version="2026-07-28"),打印experimental的值。 - 按 #3614 的汇总,两条路径现在都应返回
None(且 wire 上省略该字段)。 - 在客户端读取代码中把
caps.experimental or {}作为统一读取方式,兼容任一表示。 - 如果迁移门禁中为该差异设置了临时期望 delta,请在本修复版本上复测后移除或修订该 delta(Issue 正文明确写明会在修复首个 2.x 版本后重新测试)。
- 如果“统一省略”的契约仍不满足你的迁移门禁,按维护者建议另开新 Issue 讨论。
验证方法
在升级后的 SDK 上重跑上述最小复现脚本:未配置 experimental capabilities 的服务器,两条发现路径的 experimental 均应为 None,且不再出现 legacy {} True / modern None False 这样的不一致输出。若客户端已改用 caps.experimental or {} 读取,则在两条路径下都不应再出现 AttributeError 一类的取值异常。
参考来源
modelcontextprotocol/python-sdk #3254
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。
![[enhancement]: Need a download retry feature in Model Manager](https://www.chat-gpts.plus/wp-content/uploads/2026/10/8840-e9568051-768x403.jpg)

