MLog

属于我们的双语博客实验场

返回文章列表
技术#MCP#AI Agent#协议设计#后端架构

MCP 协议最大改版:无状态化到底改变了什么

发布于: 2026年7月12日阅读时长: 10 min

MCP 2026-07-28 规范将协议从有状态彻底重构为无状态——Session 消失、握手取消、Extensions 成为一等公民。这篇文章聊聊这次改版的实际影响,以及你的 MCP Server 需要改什么。

上个周末我在重构一个 MCP Server。原先跑在单机上一直好好的,但一放到负载均衡后面就各种诡异问题——session 过期、sticky session 不稳定、共享 session store 的引入让整个架构重了三倍。我跟同事吐槽这事,他说:"等等,MCP 马上要大改版了,session 整个没了。"

我去看了 2026-07-28 规范的 Release Candidate,确实。这不是一次小修小补,而是 MCP 自发布以来最大的一次架构变更。核心思路一句话就能概括:把协议层的状态全部干掉,让 MCP 变成真正的无状态协议。 最终规范将在 7 月 28 日正式发布,但 RC 已经锁定了所有变更,现在就可以动手评估迁移了。

Session 没了,然后呢?

在 2025-11-25 版本中,MCP 的典型调用流程是这样的:客户端先发一个 initialize 请求建立 session,服务器返回一个 Mcp-Session-Id,之后所有请求都得带上这个 header。这意味着负载均衡器必须把同一个 session 的所有请求路由到同一台服务器实例上——sticky session 成了刚需。如果你想做水平扩展,要么引入共享 session store(Redis 之类),要么接受单点瓶颈。

新版本直接把整个 session 概念删了。initialize 握手取消,Mcp-Session-Id 取消,客户端信息、协议版本、capabilities 这些原本在握手阶段交换的东西,现在全塞进每个请求的 _meta 字段里随身携带。

效果立竿见影:任意请求落到任意服务器实例都能被独立处理。你的 MCP Server 从"需要 sticky session + 共享存储"降级为"一个普通的轮询负载均衡就够了"。

// 旧:必须先 initialize,然后每次带 session ID
POST /mcp
Mcp-Session-Id: 1868a90c-3a3f-4f5b
{"jsonrpc":"2.0","method":"tools/call",...}

// 新:一个自包含请求,扔给谁都能处理
POST /mcp
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search
{"jsonrpc":"2.0","method":"tools/call","params":{...,"_meta":{...}}}

这还带来了一个附带好处:Mcp-MethodMcp-Name 被提升为必需的 HTTP header。网关和负载均衡器可以直接按 header 做路由、限流、审计,不用再解析 JSON body。运维友好度直接拉满。

状态去哪了?显式句柄模式

协议层不管理状态了,不代表你的应用就不能有状态。官方推荐的替代方案叫"显式句柄模式"(explicit handle pattern),思路很简单:服务器从 tool 调用中生成一个显式的 ID(比如 basket_idbrowser_id),模型把这个 ID 当作普通参数在后续调用中传递。

// 步骤 1: 创建购物车,服务器返回 basket_id
tools/call: create_basket
→ { "basket_id": "basket_abc123" }

// 步骤 2: 添加商品,模型把 basket_id 当参数传回去
tools/call: add_item
→ { "basket_id": "basket_abc123", "item": "...", "quantity": 1 }

我一开始觉得这只是"把 session 的活丢给应用层"的偷懒做法。但仔细想了下,这个模式其实更强——状态对模型是可见的。模型可以跨 tool 组合句柄,可以在多步之间灵活传递,甚至可以推理和判断下一步该用哪个句柄。而原来藏在 transport 元数据里的 session 状态,模型根本感知不到。

Extensions 成为一等公民

Extensions 在 2025-11-25 版本就存在了,但那会儿没有正式的治理流程,更像是个占位符。新版通过 SEP-2133 给 Extensions 定了规矩:反向 DNS 命名、独立的 ext-* 仓库、独立版本号、从实验到正式的晋升路径。这意味着 MCP 的进化不再需要每次改核心规范,新能力可以通过 Extension 机制独立发布和迭代。

这个版本引入了两个正式 Extension。

MCP Apps:服务器可以返回交互式 HTML 界面,host 在沙盒 iframe 里渲染。工具可以提前声明 UI 模板让 host 预取、缓存和安全审查。UI 触发的操作走的是跟普通 tool 调用完全相同的 JSON-RPC 通道——意味着所有 UI 操作都会经过同样的审计和授权路径。简单说,你的 MCP tool 不再只能返回 markdown 文本,它可以返回一个可排序的表格、一个表单、一个数据可视化面板。

Tasks:耗时任务从实验性功能正式升级为 Extension。旧版 Tasks 依赖 SSE 长连接来驱动生命周期,新版则完全适配了无状态模型:tools/call 返回 task handle,客户端通过 tasks/gettasks/updatetasks/cancel 来驱动。tasks/list 被移除了——在无 session 的情况下,没法安全地限定列表的范围。

Breaking Changes:你的代码要改什么

这次改版有 breaking changes,而且不是可选的。以下是必须处理的:

变更 影响 动作
Mcp-Session-Id 移除 所有读这个 header 的代码失效 改为显式参数传递
initialize 握手取消 capabilities 协商逻辑失效 改为 _meta 随请求携带 + server/discover
Streamable HTTP 新增必需 header 没带 Mcp-Method / Mcp-Name 的请求会被拒绝 在所有请求中加上这两个 header
Tools list 缓存策略 不能再假设客户端会实时拉取 根据数据新鲜度声明合理的 ttlMs
W3C Trace Context 标准化 分布式追踪的 key 名被锁定 按新规范更新 traceparent 等字段

还有一个值得注意的:inputSchemaoutputSchema 升级到了完整的 JSON Schema 2020-12。现在支持 oneOfanyOfallOf$ref$defs 等组合和引用能力。outputSchema 不再限制为 object 类型,structuredContent 可以是任意 JSON 值。这对定义复杂 tool 参数的人来说是个不小的提升。

另外三个功能被标记为弃用:Roots、Sampling、Logging。弃用不是移除——它们在当前版本和未来 12 个月内都能正常工作,但你得开始规划替代方案了。

迁移时间线

  • 2026-05-21:RC 发布,变更锁定
  • 2026-07-28:正式规范发布,Tier 1 SDK 预期在此前完成适配
  • 2026.07-08:主流 SDK 陆续更新
  • 2027+:旧版本支持逐步下线,12 个月弃用窗口结束

如果你现在就开始评估迁移,时间完全够。建议优先做三件事:

  1. 把 session 依赖的代码全部找出来,按显式句柄模式重写
  2. 检查所有 Streamable HTTP 端点,确保处理好 Mcp-MethodMcp-Name header
  3. 如果用了实验性 Tasks API,对照新 lifecycle 做适配

最后

这次改版给我的感觉是:MCP 从"能跑就行"进入了"能规模化部署"的阶段。无状态化、Extensions 机制、弃用策略,每一项都在解决实际生产中的痛点。协议层的复杂度被大量削减,换来的是部署简单性和扩展灵活性。

对自建 MCP Server 的团队来说,迁移成本不算低,但值得。一个能用普通 HTTP 基础设施水平扩展的 MCP Server,跟一个需要 sticky session 和共享存储的 MCP Server,运维成本差了一个数量级。

我喜欢这次改版的一个细节:Mcp-Method header 的设计。这个改动很小,但意味着你的 MCP 流量可以用现成的 API 网关工具来治理,不需要自建任何专用中间件。这就是好的协议设计——用已有的轮子,而不是造新轮子。