要评估影响,优先看四件事:有没有把应用状态放在协议会话里、有没有依赖长连接做 elicitation 或 sampling、有没有硬匹配 ​​-32002​​、有没有在用 ​​logging/setLevel​​。

7 月 28 日,MCP 官方发布了 2026-07-28 规范替代 2025-11-25,这是它自诞生以来改动最大的一次修订。initialize/initialized 握手删了,Mcp-Session-Id 头也删了,协议层不再有会话这个概念。

图片

Claude 给的官方数据是 MCP 近期月度 SDK 下载量突破 4 亿,Anthropic 的 connectors 目录收录了 950 多个 MCP Server。势头依旧非常猛。

背景

2025-11-25 及更早版本里,一次 Streamable HTTP 交互要先握手:客户端发 initialize,服务端回能力集,客户端再发 notifications/initialized。之后服务端下发一个 Mcp-Session-Id,每个请求都得带上:

POST /mcp HTTP/1.1
Mcp-Session-Id: 1868a90c-3a3f-4f5b

{"jsonrpc":"2.0","id":2,"method":"tools/call",
 "params":{"name":"search","arguments":{"q":"otters"}}}

    这个 ID 只在签发它的那台机器上有效。想水平扩容,要么在负载均衡上开 sticky session,要么全集群共享一份会话存储,很多团队是两样都做。

    代价还不止扩容。连接一断状态就没了,客户端得重新握手。中间的网关、限流器想知道这个请求在干什么,只能把 JSON-RPC body 解开看。

    无状态的协议内核

    SEP-2575 删掉握手,SEP-2567 删掉协议级会话和 Mcp-Session-Id。现在一次工具调用长这样:

    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"}}}}

    协议版本、客户端身份和能力都放进了 _meta,键名是 io.modelcontextprotocol/protocolVersion/clientInfo/clientCapabilities。版本对不上返回 UnsupportedProtocolVersionError

    一次工具调用现在是完全自包含的,普通的轮询负载均衡就够用,不需要共享存储,也不需要会话亲和。

    有状态与无状态的请求路径对比

    有状态与无状态的请求路径对比

    图上看右边几条线的走向。上半只有一条能走通,打到实例 B 就失败,因为会话状态只在 A 上;下半三条都通,右下角灰掉的共享会话存储也就不需要了。

    另有一处容易漏:SEP-2575 同时移除了 SSE 流的可恢复能力,Last-Event-ID 不再适用。响应流断了在途请求就丢了,客户端要用新 ID 重发。

    server/discover

    客户端想提前知道服务端支持什么,可以调新增的 server/discover,它返回协议版本、能力集和身份信息。服务端 MUST 实现,客户端 MAY 调用,跳过它直接发工具调用完全合法。

    显式的状态句柄

    协议不管状态了,不等于应用不能有状态。官方的做法是由工具签发一个句柄,让模型作为普通参数在工具之间传:

    {"jsonrpc":"2.0","id":2,"method":"tools/call",
     "params":{"name":"add_item",
     "arguments":{"basket_id":"bsk_a1b2c3","sku":"shoes"}}}

    这里的 basket_id 由上一步建购物车的工具返回。官方的判断是这种写法比放在传输层里的会话状态更好用,模型能看到这个句柄,也就能自己决定后续哪些调用要带上它。

    多轮请求 MRTR

    工具执行到一半需要用户确认,或者需要客户端的模型生成一段内容,以前靠 elicitation/createsampling/createMessageroots/list 这类服务端反向请求解决,实现上要求服务端一直保持一条 SSE 流不断开。

    无状态之后没有常驻连接,这套做法不成立了。SEP-2322 的替代方案是 Multi Round-Trip Requests,缩写 MRTR。服务端把要问的东西放在返回值里,tools/callprompts/getresources/read 都可以返回 resultType 为 input_required 的结果:

    {
      "resultType": "input_required",
      "inputRequests": {
        "confirm": {
          "method": "elicitation/create",
          "params": {"mode": "form", "message": "Delete 3 files?",
            "requestedSchema": {"type": "object",
              "properties": {"confirm": {"type": "boolean"}},
              "required": ["confirm"]}}
        }
      },
      "requestState": "eyJzdGVwIjoxLCJmaWxlcyI6WyJhIl19"
    }

      客户端收集完回答,用同样的键放进 inputResponses,连同原样的 requestState 重发一次原始调用。

      旧的长连接方式与 MRTR 的时序对比

      旧的长连接方式与 MRTR 的时序对比

      左边那根粗竖线是关键,整个过程服务端都得保持这条流,断一次这次调用就废了。右边拆成两次普通请求,中间状态全在报文里,第二次由哪台机器处理都行。

      顺带说下 resultType,这是 RC 之后才补的字段,现在每个 result 都必须带,普通结果是 complete,老版本服务端不带这个字段时按 complete 处理。

      配套的 SEP-2260 规定服务端只能在处理某个客户端请求的过程中发起请求,用户看到的每次弹窗都能追溯到主动动作。Supabase 就是靠 MRTR 才做上 elicitation 的,删数据前可以先问一句。

      路由头 Mcp-Method 与 Mcp-Name

      SEP-2243 要求 Streamable HTTP 的 POST 请求必须带两个标准头:Mcp-Method 标明方法,Mcp-Name 标明具体的工具、资源或 prompt 名。

      网关拿到这两个头就能路由、限流、计量,不用再解 body。头和 body 对不上直接拒(HTTP 400,-32020)。同一个 SEP 还支持用 x-mcp-header 把工具参数映射成自定义 HTTP 头。

      列表结果的缓存

      tools/listprompts/listresources/listresources/readresources/templates/list 的返回值多带两个字段(SEP-2549):

      • ttlMs:新鲜度提示,单位毫秒
      • cacheScopepublic 或 private,决定共享的中间层能不能缓存

      语义照着 HTTP Cache-Control 抄的,和原有的 listChanged 通知并存。会话没了以后列表端点不再随连接变化,客户端避免每次调用都重拉工具目录,靠的就是这两个字段。

      链路追踪

      SEP-414 把 W3C Trace Context 的 traceparenttracestatebaggage 固定在了 _meta 里。之前几个 SDK 各写各的,键名不统一就串不起来;统一之后,一条 trace 能从宿主应用一路连到下游服务。

      扩展框架

      SEP-2133 把扩展正式化了。每个扩展有一个反向 DNS 风格的 ID,通过 ClientCapabilities 和 ServerCapabilities 里新增的 extensions 映射协商,代码放在独立仓库,按自己的节奏发版。

      新能力不用再写进核心规范,也就不会因此触发一次带破坏性改动的版本升级。

      核心协议与扩展框架的分层关系

      核心协议与扩展框架的分层关系

      图上看中间那条虚线,也就是 extensions 的协商动作。上面一层动一下就是主版本变更,下面三个扩展各有各的发版节奏。两端只有都声明了某个扩展它才生效,没声明就当它不存在。

      Tasks 扩展

      Tasks 在 2025-11-25 里是核心协议的实验特性,生产环境跑下来问题不少,这次按 SEP-2663 移到了 io.modelcontextprotocol/tasks 扩展,同时做了一轮重构:

      • 阻塞式的 tasks/result 换成 tasks/get 轮询
      • 新增 tasks/update,客户端可以往运行中的任务里送输入
      • 删掉 tasks/list
      • 服务端可以主动返回 task handle

      变更通知也从 HTTP GET 端点改到了统一的 subscriptions/listen 流。

      MCP Apps 扩展

      MCP Apps 走的是 SEP-1865,今年 1 月就已经是 Final,是第一个官方扩展。它让 Server 下发可交互的 HTML 界面,宿主在沙箱 iframe 里渲染,工具要提前声明 UI 模板,宿主才能预取和做安全审查。

      界面上触发的动作,走的是和直接调用工具相同的审计与授权路径。

      授权的收紧

      六个 SEP,方向是向生产环境里的 OAuth 2.0 和 OIDC 部署形态对齐。其中几条值得单独说:

      • 授权服务器 SHOULD 在响应里带 iss,客户端换 token 前 MUST 校验(SEP-2468,对应 RFC 9207),防的是 authorization server mix-up 攻击
      • 注册时 MUST 指定 application_type(SEP-837)。桌面端和 CLI 客户端之前常报 redirect_uri 错误,就是因为授权服务器默认按 web 类型拒掉了 localhost 回调
      • 客户端凭据绑定到签发它的 issuer(SEP-2352),持久化时要按 issuer 分开存
      • Dynamic Client Registration 正式废弃,方向是 Client ID Metadata Documents,兼容期内继续可用

      错误码的变化

      以前基本什么都是 HTTP 200 加一个放在 body 里的错误。现在传输层失败返回真实的 HTTP 状态码,应用层结果才留在 body 里:

      情况

      2025-* 版本

      2026-07-28

      未知方法

      HTTP 200 + body 内错误

      HTTP 404

      协议版本不支持

      不统一

      HTTP 400,-32022

      头绑定字段缺失或不匹配

      不统一

      HTTP 400,-32020

      缺少必需的客户端能力

      -32021

      还有一处容易踩:资源不存在的错误码从 MCP 自定义的 -32002 改成了 JSON-RPC 标准的 -32602(SEP-2164),客户端里硬匹配过的要检查一遍。

      logging/setLevel 不再可用,改用每请求的 io.modelcontextprotocol/logLevel 字段。

      废弃的能力

      MCP 第一次有了正式的特性生命周期策略(SEP-2596),定义 Active、Deprecated、Removed 三种状态。核心保证是最短窗口:从标记 Deprecated 的那个版本发布算起,至少保留十二个月才有资格移除;出安全事故可以加速,但也要留够九十天。

      这次按这个策略废弃的是 Roots(改用工具参数、资源 URI 或服务端配置)、Sampling(改为直接对接 LLM 提供方 API)、Logging(stdio 用 stderr,观测用 OpenTelemetry),以及旧的 HTTP+SSE 传输。四项都还能正常工作至少一年,官方的态度是新实现别再依赖。

      SDK 支持与升级方式

      升级是 opt-in 的,版本按请求选。像 Amazon Bedrock AgentCore Gateway 就支持一个网关同时声明 2025-11-25 和 2026-07-28,请求带哪个版本头就走哪套行为,两边可以各自按节奏迁移。

      四个 Tier 1 SDK 在发布当天全部支持 2026-07-28,Rust SDK 是 beta:

      • Python mcp v2,FastMCP 更名 MCPServer,升级后一个端点同时应答两个协议版本
      • TypeScript v2 拆包成 @modelcontextprotocol/server 和 /client,ESM only,要求 Node.js 20+
      • C# 2.0.0 系列,v1.x API 继续可用
      • Go 从 v1.7.0 起支持,模块路径不变

      Go 这边有个必须知道的开关,Streamable HTTP transport 只有在 Stateless 打开时才接受 2026-07-28的请求:

      handler := mcp.NewStreamableHTTPHandler(func(r *http.Request) *mcp.Server {
          return server
      }, &mcp.StreamableHTTPOptions{Stateless: true})

      不开这个开关,客户端会协商降级到 2025-11-25。TypeScript 也是类似的显式选择,Python 和 C# 相反,升级后默认就走新版本。

      总结

      这次改的是 MCP 的传输层和生命周期,不是往上加特性。会话和握手去掉之后,MCP Server 就是一个普通的 HTTPS 端点,可以部署到 serverless 和边缘节点。

      要评估影响,优先看四件事:有没有把应用状态放在协议会话里、有没有依赖长连接做 elicitation 或 sampling、有没有硬匹配 -32002、有没有在用 logging/setLevel

      7 月 28 日只是规范文本的发布日,跑在 2025-11-25 上的实现不受影响,废弃项还有至少十二个月,节奏可以按自己的节奏来定。

      新写的 Server 直接按无状态来就好。

      更多推荐