MCP 协议断裂式升级

2026-09-08 16:170 条评论121 次阅读

MCP 协议断裂式升级

2026 年 7 月 28 日,MCP 协议发布了一次断裂式升级。新版规范把协议从"有会话的握手协议"改成了"无状态的纯 HTTP 协议":initialize 握手被移除,Mcp-Session-Id 被移除,ping 被移除,Sampling、Roots、Logging 三个特性进入弃用倒计时。

如果你手里的 MCP Server 是照着 2025 年的教程写的,那么新版客户端连上来,第一步就会撞墙。没有渐进路径,只能升级。

这个时间点,我正好在做一个把企业 REST API 批量翻译成 MCP Server 的开源平台。协议一变,平台的地基跟着变了。怎么选,成了立项第一天就必须回答的问题。

一、这次升级,到底改了什么

协议文档给出了从 2025-11-25 到 2026-07-28 的完整变更日志,改动集中在几条主线。

官网文档地址:https://modelcontextprotocol.io/specification/2026-07-28/changelog

1. 协议无状态化,砍掉握手

这是最根本的一条。旧版协议里,客户端要先 initialize 协商版本,服务端返回 session ID,后续请求都带着它。新版直接移除了这套机制(SEP-2575、SEP-2567):

旧形态 新形态
initialize / notifications/initialized 握手 移除,每次请求在 _meta 里携带协议版本与客户端能力
Mcp-Session-Id 头 移除,任何请求可以落在集群任意节点
服务端能力靠握手时宣告 新增 server/discover,服务端自报版本、能力与身份,客户端可先探测再干活

工程上的含义很直接:MCP Server 从"需要会话亲和"变成了"可以跑在普通轮询负载均衡后面"的设备。横向扩展从需要专门设计,变成了天然能力。

2. list 类响应可以缓存了

新版给 tools/list、prompts/list、resources/list 等响应统一加了缓存提示(SEP-2549):返回 ttlMs 与 cacheScope,告诉客户端这份清单多久内不用重新拉。规范还建议 tools/list 按确定性顺序返回工具,配合客户端缓存能提升 LLM 提示词缓存的命中率。

这条对工具量大的 Server 是实打实的利好。一个挂了上千个工具的 Server,客户端每次会话都全量拉一遍工具清单,token 开销很可观。有了缓存提示,清单拉一次能复用很久。

3. 请求头路由,网关不再需要懂协议

新版要求 Streamable HTTP 的 POST 请求必须带 Mcp-Method、Mcp-Name 头(SEP-2243)。网关层只读 HTTP 头就能把请求分到对应处理逻辑,不需要解析 JSON-RPC 请求体。

4. 弃用大扫除,给了 12 个月窗口

Roots、Sampling、Logging 三个特性进入弃用状态,新实现不应该再支持。HTTP+SSE 传输方式也被正式弃用,统一迁到 Streamable HTTP。规范还确立了特性生命周期策略:Active、Deprecated、Removed 三态,弃用窗口最短 12 个月。

5. 错误码划了规范保留区

JSON-RPC 的 server-error 区间被重新划分:-32020 到 -32099 保留给 MCP 规范使用。几个错误码随之重编号,其中 UnsupportedProtocolVersion 变成了 -32022。

6. 其他几条

多轮往返请求(MRTR)模式引入,服务端需要补充信息时返回 resultType: "input_required",客户端带齐信息后重试原请求。实验性的 tasks 能力移出核心协议,成为官方扩展。SSE 流不再支持断点续传,断了就重发。

二、变化背后,是一个明确的信号

把这些改动放在一起看,方向很清楚:MCP 在把自己打磨成"标准 HTTP 基础设施就能托管"的协议。

无状态化意味着服务端不需要共享会话存储,部署形态向普通 Web 服务看齐。请求头路由意味着网关不需要理解 MCP 语义,流量治理可以交给现成的网关设施。

缓存提示意味着工具清单这种高频大响应有了流量出口。弃用窗口意味着生态在主动收缩复杂度,逼着新实现走干净的路。

对 Server 作者来说,这个版本最值得注意的一点是:版本差异不再是"平滑兼容",而是"硬断裂"。旧客户端连新服务端会收到明确的 -32022,新客户端连旧服务端同样失败。

三、我做的决定:只实现 2026-07-28

我的平台叫 MCP Bridge,目标是把企业存量 REST API(Swagger / OpenAPI 2.0 与 3.x)批量翻译成可治理、可托管、可多集群发布的 MCP Server。立项那天,我面对的第一道选择题就是:新旧两版协议都还有存量客户端,要不要都支持?

我查了一圈市面上的方案。单文件转换器和单实例代理已经不少,但它们基本都停在"开发者本地跑一个进程"的形态,而且大多还在追旧协议。真正缺的,是跟上新协议、并且能扛住企业治理需求的东西。

在"兼容旧版"和"只做新版"之间,我最终选了后者,并且是没有任何过渡期的后者。这个决定写在项目的架构决策记录里(ADR-0001),理由可以摊开讲。

双协议入口的方案被否了。 分流点必须解析请求体才能判断版本,这直接抵消了请求头路由的价值,网关又变回"必须理解 MCP 语义"的设备。更麻烦的是要维护两套并行的错误语义与会话语义,测试矩阵翻倍。

双栈加弃用公告的方案也被否了。 它是最容易被接受的方案,也最容易变成永久债务。一旦有生产客户端跑在兼容层上,摘除动作就有了阻力,"摘除日"通常不会到来。

协议在 2026 年 7 月发布正式版,我 9 月立项,没有"用户来不及升级"的历史包袱。兼容层只会是我自己背上去的。

所以平台的协议策略一句话讲完:全平台只实现并只应答 MCP 2026-07-28,对 legacy 客户端显式拒绝,不做双栈、不做兼容转换、不预留适配器。

拒绝不是冷冰冰的。平台的数据面(Executor)在进入任何业务逻辑之前,先跑一道协议守卫,识别五种 legacy 形态:

特征 对应旧协议
请求带 initialize / notifications/initialized 旧握手
请求带 Mcp-Session-Id 头 旧会话
版本头不等于 2026-07-28 旧版本声明
请求体 params.protocolVersion 不是 2026-07-28 旧版本声明
_meta.sessionId 存在 会话残留

命中任意一条,返回固定的 -32022,错误体的 data 里带齐上下文:

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32022,
    "message": "Unsupported Protocol Version: 本平台仅支持 MCP 2026-07-28",
    "data": {
      "supportedProtocolVersion": "2026-07-28",
      "detectedProtocolVersion": "2025-11-25",
      "reason": "initialize 握手已在 2026-07-28 规范中移除",
      "upgradeUrl": "https://modelcontextprotocol.io/specification/2026-07-28",
      "legacySupported": false
    }
  }
}

legacySupported: false 是给客户端的机器可读承诺,五种形态各有可区分的 reason。不然用户只会看到"连不上",然后花一整天抓包。现在客户端可以把整段错误直接显示给用户,告诉他该升级了。

这个决定的回报,恰好踩在新版协议的设计意图上:Executor 完全无状态,可以跑在普通轮询负载均衡后面,任意节点服务任意请求,不需要会话存储。Redis 只承载三类应用层共享状态(上游令牌缓存、刷新锁、失效广播),不承载路由。Redis 挂了,端点仍然能服务,只是退化成每节点各自换令牌。

四、平台长什么样

协议地基定了,上面的治理链路才能谈。平台的完整形态是控制面加数据面两个平面:

数据面

控制面

节点令牌轮询拉快照

节点令牌轮询拉快照

Vue3 管理控制台

mcp-manager
注册解析 / base+overlay / 集群 / 发布
RBAC / 审计

PostgreSQL
Flyway 迁移

mcp-executor 节点 1

mcp-executor 节点 2

Redis
仅应用层共享状态

上游 REST API A

上游 REST API B

MCP 客户端
2026-07-28

控制面负责"编译",数据面负责"执行"。两者之间只有一条通道:Executor 用节点令牌轮询拉取发布快照,不直连控制面数据库。因此私有集群可以部署在与控制面网络隔离的环境里,只要它能出站访问控制面 API。

一条 REST 服务从注册到被 AI 客户端调用,走的是完整治理链路:

注册即解析。 支持 URL、上传文件、粘贴内容三种方式收进 Swagger 文档,解析成可治理的结构。每个 operation 自动变成一个 MCP 工具,命名有规约,入参 Schema 从 parameters 和 requestBody 推导。

解析出致命错误时不生成 Server,只留诊断列表。一个不完整的 Server 比一个明确的失败更难处理。

精修不碰原稿。 原始 Swagger 文档只读存储,加 sha256 双保险,读取时重算哈希,不一致直接拒绝。你对工具做的任何精修(改名、改 Schema、启停用)都存在独立的覆盖层里。

将来上游文档升级,按锚点对账。锚点消失的覆盖进挂起区,等人工确认后再决定去留。

发布即快照。 发布会把此刻的生效模型序列化进版本化的发布记录,标记 Server 为已发布只是副作用。发布之后无论原稿和覆盖层怎么改,已发布的端点行为都不变,直到下一次显式发布。

回滚就是用历史快照创建新版本,历史链条完整可审计。

调用即透传。 客户端请求到达 Executor 后,协议守卫、下行鉴权、工具定位、参数映射、上行鉴权、负载均衡、熔断重试,十步走完,把上游 REST 的响应包装成 MCP 工具结果返回。

鉴权是双跳模型,两跳完全隔离:

跳 方向 模式
Auth-D MCP 客户端到 Executor NONE / STATIC_BEARER(OAuth2 未实现前显式拒绝,不给"假装受保护"的假象)
Auth-B Executor 到上游 REST API_KEY / HTTP(Bearer、Basic) / OAuth2 Client Credentials / CUSTOM_HEADER

上行凭据用 AES-256-GCM 加密托管,控制面只回显掩码。下行令牌只存哈希,比对用常量时间,鉴权失败一律回同一句话,不给攻击者枚举的机会。

五、接入一个 AI 助手,只需要一个 URL

发布完成后,任何支持 MCP Streamable HTTP 的客户端都能接入。以 Codex 为例,配置就两样东西:一个 URL,一个令牌。

[mcp_servers.order]
url = "http://your-executor/mcp/order"
bearer_token_env_var = "MCP_BEARER_TOKEN"

或者直接用命令:

codex mcp add order --url http://your-executor/mcp/order

重启 Codex,会话里 /mcp 能看到 Server 处于 connected。之后你对 Codex 说"帮我查一下订单 1024 的状态",它就会调用这个由你部门 REST 接口自动翻译出来的工具,而不是靠幻觉编一个答案。平台侧的审计日志里,能看到这次来自 Codex 的 tools/call 记录。

整个平台 Docker Compose 一条命令起全套(PostgreSQL、Redis、控制面、两个 Executor 节点、管理控制台),项目已开源,MIT 协议。

六、为什么我赌 Modern-only

说回协议。我知道会有人问:企业里旧客户端那么多,你这么激进,不怕没人用吗?

我的判断是,这次断层的方向和以往不同。以往协议升级是叠加新能力,旧客户端最多少用几个功能。这次是删除基础机制,旧形态连不上新世界,新形态也不愿意为旧形态保留负担。

协议自己都给了 12 个月弃用窗口,官方想表达的态度很明确:快速收敛,别留双轨。

与其做一层迟早要拆的兼容垫片,不如把精力全押在正确的协议上,把拒绝做得干净、可解释。等存量客户端陆续升级上来,一个只认 2026-07-28 的 Server,反而成了最不需要操心的那个。

如果你想围观这个决定怎么落地,代码、架构决策记录(ADR)、踩坑记录都在仓库里,欢迎 star 和提 issue。下一篇我会接着写平台里最有意思的一个取舍:覆盖层怎么做到"原稿永不改动",欢迎关注。

项目地址:https://github.com/chengjungao/MCP-Platform