MCP 变成无状态:2026-07-28 规范拆掉了 session,你的服务器要改什么

MCP 2026-07-28 规范删掉了 initialize 握手和 Mcp-Session-Id,每个请求自带上下文。逐条梳理无状态核心、server/discover、头部路由、MRTR、可缓存 list、三个官方扩展、授权收紧和弃用清单,以及迁移的真实代价。

阅读时长: 10 分钟
共 4831字
作者: longlikun

2026 年 7 月 28 日,Model Context Protocol 发布第五个规范版本。改动最大的一条是:协议层的 session 没了。

这不是一次加特性的小版本。传输层重做,握手删除,三个老特性进入弃用倒计时。Anthropic 同日发了 Claude 侧的公告,说支持会陆续上线,但没给日期。


一、被拆掉的:initialize 握手和 Mcp-Session-Id

旧模型的流程是:客户端 POST 一个 initialize,服务端铸一个 Mcp-Session-Id 返回,之后每个请求都带这个 header。服务端在内存里保存这段会话的协议版本、客户端能力、订阅状态。

生产环境里的后果很具体。多副本部署时,第一个请求经负载均衡打到 Pod A,A 在本地内存记下会话并返回 session ID;下一个请求打到 Pod B,B 完全不认识这个 ID,返回 404,而且没有任何有用的诊断信息。

绕过方式只有两种:负载均衡开 sticky session(牺牲弹性伸缩和滚动发布),或者把会话状态外置到 Redis(多一套要运维的组件)。New Relic 那篇文章的说法是——一个用来简化 AI 集成的协议,悄悄变成了基础设施更复杂的理由。

新规范把 initialize / notifications/initialized 握手和 Mcp-Session-Id header 一起删掉。每个请求自带上下文,放在 _meta 里:

  • io.modelcontextprotocol/protocolVersion — 协议版本
  • io.modelcontextprotocol/clientCapabilities — 客户端能力
  • io.modelcontextprotocol/clientInfo — 客户端身份(SHOULD)

服务端也 SHOULD 在每个结果的 _meta 里回 io.modelcontextprotocol/serverInfo。版本对不上返回 UnsupportedProtocolVersionError

一个请求现在长这样:

POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search

{"jsonrpc":"2.0","id":1,"method":"tools/call",
 "params":{"name":"search","arguments":{"q":"otters"},
 "_meta":{"io.modelcontextprotocol/clientInfo":{"name":"my-app","version":"1.0"}}}}

结果是任何请求可以落在任何实例上。轮询负载均衡、自动扩缩容、Serverless 容器、跨区域部署——这些原本需要额外适配的部署形态,现在默认就能跑,不用预置会话存储,也不用在发版时排空连接。

代价写在明面上:能力协商信息从「每会话一次」变成「每请求一次」,payload 会变大一点。

顺带删掉的还有 pinglogging/setLevelnotifications/roots/list_changed。日志级别改成按请求在 _meta 里带 io.modelcontextprotocol/logLevel,并且服务端 MUST NOT 对没带这个字段的请求发 notifications/message


二、server/discover:版本协商的新入口

握手没了,版本协商还得有。新规范加了 server/discover,服务端 MUST 实现,用来声明自己支持的协议版本、能力和身份。

客户端 MAY 在其他任何请求之前调它做前置版本选择,也可以在 STDIO 上把它当作向后兼容探测——老服务器不认识这个方法,客户端据此回退。

它和 initialize 的本质区别:initialize 建立一段有状态会话,server/discover 只是一次可缓存的元数据查询。


三、头部路由:Mcp-MethodMcp-Name

Streamable HTTP 的 POST 请求现在必须带两个标准头:

  • Mcp-Methodtools/callresources/readprompts/get 之类
  • Mcp-Name — 具体的工具名或资源名

受益方不是应用代码,是中间层。网关、WAF、API 管理平台不用解 JSON-RPC body 就知道这个请求要干什么,于是可以做按工具限流、把不同资源路由到不同后端、在边缘层做授权判断。

同一个 SEP 还加了 x-mcp-header,允许把工具参数映射成自定义 HTTP 头。头部和 body 对不上会返回 HeaderMismatchError


四、MRTR:服务端不再回头找客户端

旧协议里,服务端处理一次工具调用的中途可以反过来向客户端发请求:sampling/createMessage 要一次 LLM 补全,elicitation/create 问用户一个问题,roots/list 查客户端暴露了哪些目录。这套东西需要一条双向的、开着的流。

新规范用 Multi Round-Trip Requests(MRTR)替代:服务端返回一个 InputRequiredResultresultType: "input_required"),inputRequests 字段说明还需要什么信息,同时带一个不透明的 requestState。客户端把答案放进 inputResponses,重新发起原来那个请求。

配套改动是所有 result 现在都有必填的 resultType 字段,取值 "complete""input_required"。老服务器返回的结果没有这个字段,客户端 MUST 当作 "complete" 处理。

notifications/elicitation/complete 和 URL 模式 elicitation 的 elicitationId 一起被删——客户端重试原请求就能拿到结果,服务端主动通知这条路不再需要;要跨重试关联同一次交互,服务端自己在 requestState 里编码标识。


五、真需要跨调用状态怎么办

无状态是协议层的事,不是应用层的。浏览器会话、文件编辑上下文这类跨调用状态照样存在,只是换了承载方式:服务端在工具返回值里给出一个自己铸造的 handle,Agent 在后续调用里把它当普通参数传回来。

副作用比性能更值得说。状态从传输层的隐式会话变成了 payload 里的显式参数:对模型可见,在日志里可审计,出问题时能直接从请求体看出当时的上下文是什么。


六、订阅收敛成一条 subscriptions/listen

HTTP GET 端点和 resources/subscribe / resources/unsubscribe 被替换成单一的 subscriptions/listen——一条长连的 POST 响应流,客户端按类型显式 opt-in:toolsListChangedpromptsListChangedresourcesListChangedresourceSubscriptions。服务端确认订阅,并给推送的通知打上 io.modelcontextprotocol/subscriptionId

请求作用域的通知(notifications/progressnotifications/message)不走这条流,仍然走它们所属请求的响应流。

同时被删的是 SSE 的断流恢复:Last-Event-ID header 和 SSE event ID 都没了。响应流断了就是断了,在途请求丢失,客户端 MUST 用新的 request ID 重发。这是无状态的直接后果——恢复重放要求服务端记住发过什么,而这正是要拆掉的东西。


七、list 结果可以缓存了

tools/listprompts/listresources/listresources/readresources/templates/list 的返回值现在必须带两个字段(新的 CacheableResult 接口):

  • ttlMs — 新鲜度提示,毫秒
  • cacheScope"public""private",控制共享中间层能不能缓存

这两个字段和已有的 listChanged 通知互补,不是替代关系。

规范另外建议 tools/list 以确定性顺序返回工具。理由不是洁癖:工具列表进的是 LLM 的 prompt,顺序不稳定会打掉 prompt cache 命中率。

这几条和协议层去 session 是同一个方向——list 端点不再随连接变化,所以才可能缓存。


八、三个扩展转正

新规范立了正式的扩展框架,ClientCapabilitiesServerCapabilities 加了 extensions 字段。三个扩展从实验状态转正:

Tasksio.modelcontextprotocol/tasks):从核心协议挪进扩展。tools/call 可以返回 task handle 而不是立即结果,客户端用 tasks/get 轮询、tasks/update 回传输入、tasks/cancel 取消。阻塞式的 tasks/result 被移除,tasks/list 也删了;服务端现在可以不经每请求 opt-in 就主动返回 task handle。适用场景是文件处理、代码生成、批处理这类长任务——过程中不用挂着一条 socket。

MCP Apps:服务端下发交互式 HTML 界面,在沙箱 iframe 里渲染。工具需要提前声明 UI 模板供 host 做安全审查,所有用户操作仍走标准 JSON-RPC 路径,因此可审计。

Enterprise Managed Authorization:组织侧集中管理 MCP 服务器访问,基于 Identity Assertion JWT Authorization Grants,带完整审计链路。


九、授权收紧

  • 授权服务器 SHOULD 在授权响应里带 iss 参数(RFC 9207),客户端 MUST 在兑换 authorization code 之前校验它与记录的 issuer 一致
  • 客户端动态注册时 MUST 指定合适的 application_type,避免 OIDC 的 redirect URI 冲突(桌面 / CLI 应用的 localhost 回调)
  • 客户端凭据绑定到签发它的授权服务器:MUST 按 issuer 标识存储,MUST NOT 跨授权服务器复用,授权服务器变了必须重新注册
  • OAuth 2.0 动态客户端注册(RFC 7591)被弃用,改用 Client ID Metadata Documents(CIMD);DCR 仅作为对不支持 CIMD 的授权服务器的兼容手段保留

十、可观测性交给 OpenTelemetry

Logging 特性被弃用,建议的迁移路径是 stdio 场景写 stderr,其余场景用 OpenTelemetry。规范同时把 OTel 的 trace context 传播写成 _meta 的约定键:traceparenttracestatebaggage(W3C Trace Context)。

实际意义在于打通链路。MCP 原来那套私有日志通道是孤岛,一次工具调用失败没法和应用其他部分对上,只能手工交叉比对时间戳。改用 W3C Trace Context 之后,Agent 发起调用 → MCP 服务器 → 外部 API → 数据库,这一串在任何 OTel 后端里是一条完整的 trace。

不过规范做的是「弃用私有 Logging,并把 OTel 传播写成 _meta 约定」,而不是把 OTel 定为强制替代品。New Relic 的文章把这一步描述为迁移到 OpenTelemetry,方向没错,落到规范条文上的强度要温和一些。


十一、弃用清单和时间窗

规范这次确立了特性生命周期政策:Active / Deprecated / Removed 三态,弃用窗口最少 12 个月,并维护一份弃用特性登记表。

当前处于 Deprecated 状态的:

特性 建议替代
Roots 用工具参数、resource URI 或服务端配置传目录
Sampling 直接对接 LLM 提供商 API
Logging stdio 写 stderr,或用 OpenTelemetry
HTTP+SSE 传输 Streamable HTTP
OAuth 2.0 DCR(RFC 7591) Client ID Metadata Documents
includeContext"thisServer" / "allServers" 省略该字段或用 "none"

还有一组错误码调整。resource not found 从 -32002 改成 -32602(Invalid Params,向 JSON-RPC 对齐);服务端错误码区间做了划分,-32000-32019 留给实现自定义(现有 SDK 用法沿用),-32020-32099 归规范所有。本版新增的几个跟着重编号:HeaderMismatch -32001-32020MissingRequiredClientCapability -32003-32021UnsupportedProtocolVersion -32004-32022


十二、代价

The Register 的报道里,MCP 共同作者 David Soria Parra 的说法是:自己写了实现的人,「要做到正确会有相当大的工作量」。底层传输机制是整个重做的。

另一句更值得记:协议现在「线上传的东西比以前复杂了一点」。无状态不等于更简单——状态没有消失,只是从服务端内存挪到了每一个请求的 body 里。这是设计哲学的取舍,不是纯粹的简化。

兼容性上,Stacklok 的企业就绪指南提醒:新旧要通,得双方共享一个受支持的协议世代,或者其中一方主动做回退和翻译。新版服务端不会自动兼容老客户端,反之亦然。这一点在企业部署里比在个人项目里麻烦得多。

弃用窗口是「最少 12 个月」,不是确定的日期。这个措辞给了规范方灵活性,也给使用方留下了不确定性。


十三、Claude 那边的进度

Anthropic 在 7 月 28 日同步发了 Claude 侧公告,口径是支持会在 Claude 产品线陆续上线,没有给具体日期,也没有说明 Claude.ai / Claude Code / API / Agent SDK / Desktop 各自的时间表和向后兼容策略。

Tier 1 SDK 已经跟上:TypeScript、Python、Go、C# 支持 2026-07-28,Rust 是 beta。

生态数字:MCP 月度 SDK 下载量超过 4 亿次,年内增长 4 倍;Claude 的连接器目录收录了 950+ MCP 服务器;公开提到的生态伙伴包括 Figma、Intuit、Netlify、PostHog、Xero、Zoom。


该做什么

只跑本地 stdio 的:影响有限。无状态主要针对负载均衡后面的远程服务器,本地工具和桌面集成本来就没有这个问题。但 Roots / Sampling / Logging 的弃用同样适用,12 个月窗口从现在开始算。

有远程服务器、且用了 sticky session 或 Redis 存会话的:这次收益最大,那套外置状态可以准备拆掉。改造成本也最大——传输层是重写的。

自己实现协议而没有用官方 SDK 的:按 Parra 的说法工作量不小,优先考虑迁到 Tier 1 SDK。

用了 sampling / elicitation / roots 的:三条路都要改。sampling 直接调 LLM API,elicitation 走 MRTR,roots 改成工具参数或 resource URI。

所有人:把 _meta 里的 traceparent 接进现有 OTel 链路。成本低,收益是终于能看到一条完整的调用链,而不是一堆对不上的日志片段。


参考:

关于

关注我获取更多资讯

月球基地博客公众号二维码,扫码关注获取更多 AI 与编程资讯
📢 公众号
月球基地博客作者个人微信二维码,扫码交流 AI 与编程话题
💬 个人号
使用 Hugo 构建
主题 StackJimmy 设计