MCP 把握手删了,我觉得这是协议在认错
MCP 新规范的 changelog 里,主要改动一共列了九条。前五条里,有四条的第一个动词是「移除」。
移除协议级 session。移除 initialize 握手。移除 ping。移除断线重连。
一个协议,在自己有史以来最大的一次版本更新里,主要工作是删自己。
好家伙。
7 月 28 号,MCP 的 2026-07-28 规范正式落地。MCP 全称 Model Context Protocol,简单说就是让大模型能调用外部工具的那套通信标准,你在 Claude、Cursor 里挂的那些「连接器」「插件」,底下跑的多半就是它。上一版规范还是 2025-11-25,中间隔了八个月。官方博客给这次的定调是发布以来最大的一次修订,主线只有一句话,把 MCP 变成一个无状态协议。
这个词听着挺技术,但它砍掉的东西一点都不抽象。
先说最要命的那条。以前你的 MCP 服务端跟客户端建立连接,第一步是握手,客户端喊一声 initialize,服务端回一声我准备好了,双方交换协议版本和能力清单,然后服务端发一个 Mcp-Session-Id 头给你,之后所有请求都带着这个 id 过来。服务端就靠这个 id 认人,知道这一串请求是同一个会话,可以在内存里给你留一块地方,存缓存、存工作流进度、存这次对话的配置。
现在这一整套,没了。
Session 没了,Mcp-Session-Id 头没了,握手也没了。每个请求自己带着协议版本和客户端能力过来,塞在 _meta 字段里。客户端每次都得报一遍自己是谁,服务端每次都在返回结果里报一遍自己是谁。版本对不上,直接返回一个 UnsupportedProtocolVersionError 打回去。

说真的,我第一眼扫到这条的时候是有点懵的。握手这东西,在网络协议里几乎是本能反应,TCP 要三次握手,TLS 要握手,你随便打开一个 RFC 都能看见它。一个 2024 年才诞生的新协议,把自己的握手删了。
但把 changelog 从头到尾扫完,我改主意了。这次改得对。而且我觉得它删的不只是几行代码,它删的是自己最初那个心智模型。
MCP 刚出来那会儿,绝大多数 server 是跑在你本地的。一个 stdio 进程,Claude Desktop 起一个子进程,管道连着,一对一,从头连到尾。在这种场景下,session 是免费的。进程本身就是会话,你往一个全局变量里塞东西,它自然就活到进程退出。握手也很自然,两个进程刚认识,互相通报一下家门,天经地义。
问题是 agent 后来上云了。
现在的 MCP server 更多是一个 HTTP 服务,挂在负载均衡后面,几十个实例横着扩。这时候 session 立刻从免费变成了最贵的那部分。你得做 sticky routing,也就是让同一个会话的请求永远打到同一台机器上;做不到就得搞一个共享的 session store,Redis 一挂全线崩;有些网关为了认出 Mcp-Session-Id 还得做深度包检测,把 HTTP body 拆开看。
于是就出现了一个特别拧巴的局面。你写的是一个云原生的服务,但因为协议要求你记住客户端,你被迫把它做成了一个有黏性的、扩不动的东西。
新规范的解法是把状态显式化。服务端如果真的需要跨调用记住点什么,就自己铸造一个 handle 给客户端,比如一个 basket_id,然后让模型在后续调用里把这个 id 当成普通的工具参数传回来。状态不再是「服务端偷偷记着你」,而是「你自己带着凭证来」。

这个思路一点都不新。Web 后端十几年前就走过一模一样的路,从服务端 session 走到 token,从有状态走到无状态 REST,理由完全一致,因为要横着扩。MCP 只是把这段路又走了一遍,用了八个月。
我是真的觉得这里有个信号,值得所有做 agent 基础设施的人停一下。凡是把状态藏在服务端记忆里的设计,在 agent 时代都得重新估一遍价。 不是说有状态一定错,是说 agent 的调用模式天生就是碎的,一个任务可能横跨几十分钟、几十次工具调用、中间还会重试和分叉,你以为的一次「会话」在基础设施眼里根本不成立。
当然,我也不想装作当初那批人蠢。坦率讲,2024 年设计 MCP 的时候,本地 stdio 就是主流场景,照着那个场景设计出 session 和握手,是完全合理的工程判断。你不能要求一个协议在第一版就预言到两年后 agent 会长成什么样。能在两年后承认这个抽象过时了,并且愿意付出破坏性升级的代价去改,这比一开始就猜对更难。
好,接下来说点实际的。你手上如果真有一个跑着的 MCP server,这次要动哪儿。
第一件事,去 grep 一遍 Mcp-Session-Id。任何把状态挂在这个 id 上的代码,缓存也好,工作流进度也好,每会话配置也好,现在都没有 session 可挂了。这些东西得改成前面说的显式 handle,作为工具参数进出。这是工作量最大的一块,没有捷径。
第二件事,tasks/list 直接被删了。原因也很直白,「列出当前会话的所有任务」这个语义,在没有会话之后无从定义。整个 Tasks 特性还从核心协议里搬了出去,变成一个官方扩展,阻塞式的 tasks/result 换成了 tasks/get 轮询,另外加了个 tasks/update 让客户端能往任务里塞输入。如果你的前端有个界面是靠 tasks/list 渲染任务列表的,那块 UI 现在没数据源了。
第三件事,也是我觉得最容易被漏掉的一条。SSE 流的可恢复性和消息重发被移除了,Last-Event-ID 头和 event id 一起没了。翻译成人话,响应流断了就是断了,在途的那个请求直接丢失,客户端必须用一个全新的 request id 把整个请求重发一遍。
看出问题在哪了吗。
以前断线能续,重发是补差量。现在重发是从头再来一次。于是你的工具就必须是幂等的,也就是同一个请求执行一次和执行三次,结果得一样。如果你有个工具叫「创建订单」「发送邮件」「扣款」,而它内部没做幂等键,那么在新规范下,一次网络抖动就可能给用户发两封邮件。

这条不会报错,不会有告警,它就是安安静静地在你线上跑出脏数据。我个人认为这是这次改版里最阴的一个坑。
第四件事,Streamable HTTP 的 POST 请求现在强制要带 Mcp-Method 和 Mcp-Name 两个头。好处是网关终于能在不拆 body 的情况下按方法路由流量了,代价是你那些自己手搓的客户端集成得补上这俩头,不补就是静默失败。
第五件事,跟省钱有关,我建议优先做。tools/list、prompts/list、resources/list 这几个列表接口的返回值现在必须带 ttlMs 和 cacheScope 两个字段。ttlMs 是新鲜度提示,单位毫秒,告诉客户端这份工具清单能缓存多久;cacheScope 只有 public 和 private 两个值,管的是中间层能不能替你缓存。同时规范建议 tools/list 返回的工具顺序要是确定的。
顺序为什么重要,很多朋友可能没意识到。工具清单是要拼进 prompt 里喂给模型的,模型这边有 prompt 缓存,前缀一模一样才能命中。你要是每次返回的工具顺序随机抖一下,前缀就变了,缓存全部作废,token 账单直接翻上去。就这么一个排序问题,能吃掉一笔真金白银。
第六件事,错误码改号了。resource not found 从 -32002 改成了 -32602,对齐 JSON-RPC 里 Invalid Params 的语义。另外规范给错误码划了地盘,-32000 到 -32019 归实现自己用,-32020 到 -32099 归规范。如果你代码里哪儿硬编码了 -32002 去判断,这次会静默地判错。
说完要动的,说说被废弃的三个。Roots、Sampling、Logging,正式进入 Deprecated 状态,至少能用到 2027 年 5 月,但新代码别再写了。Roots 用工具参数或者 resource URI 替代,Logging 直接写 stderr 或者上 OpenTelemetry。
这三个里,Sampling 被废最有意思。
Sampling 当初的设想挺漂亮,服务端可以反过来找客户端借模型用,我这个 server 需要 LLM 做一步推理,我不自己接 API,我让宿主帮我跑。听起来很优雅,省得每个 server 作者都去申请一份 API key。
但它把一堆账搅成了糊涂账。这次推理谁付钱,token 算谁头上,服务端能不能通过精心构造的 prompt 把用户的上下文套出来,宿主要不要给用户弹个确认框。理想很美,落地一地鸡毛,实际实现它的客户端本来就没几个。现在官方的建议很干脆,你自己去对接 LLM 厂商的 API。
顺着这个逻辑,服务端主动发起请求这件事整个被重构了。以前 roots/list、sampling/createMessage、elicitation/create 这几个都是服务端反向敲客户端的门,现在统一换成一个叫 MRTR 的模式,全称 Multi Round-Trip Requests,多轮往返请求。服务端不再主动敲门,而是在返回结果里说一句「我信息不够,需要你补这些」,resultType 字段填 input_required,把要补的东西放在 inputRequests 里。客户端拿到之后,带着答案重试原来那个请求。
双向 RPC 被拍平成了一问一答。所有结果现在都必须带 resultType 字段,普通结果填 complete,中间态填 input_required;老服务端返回的结果没这个字段,客户端一律当 complete 处理。
厉害了,这一手其实挺狠的。它等于宣布,MCP 不再是一个双向对等的协议,它就是个请求响应协议,服务端永远处于被动。设计空间是变窄了,但每一个请求都变成了自包含的、可以打到任意一台机器上的东西。有点子牛逼。
聊到这儿我想说个可能有点跑偏的观察。这次改版里被讨论最少、但我觉得最值钱的东西,其实不在技术条款里,在治理那一节。
规范正式采纳了一套特性生命周期政策,把特性分成 Active、Deprecated、Removed 三种状态,规定从宣布废弃到最早可以移除,中间至少隔十二个月,并且专门维护一份废弃特性的注册表,你随时能查到现在哪些东西正在倒计时。SEP 流程也正式化了,改成基于 PR 的工作流,提案是 seps 目录下的 markdown 文件,编号跟着 PR 号走,每个提案要有 sponsor 负责。扩展机制也升成了一等公民,用反向 DNS 命名,独立于规范单独发版本,各有各的仓库和维护者。
一个协议开始认真定义「我该怎么删东西」的规矩,说明它准备活很久。
这跟前面那些破坏性改动放在一起看,其实是一句话的两面。这次的破坏是一次性还债,把两年前那个针对本地进程的旧抽象一次砍干净;砍完之后立规矩,是在跟所有实现者承诺,以后不会再这么砍你了。官方博客里那句话说得很明白,未来的版本可以在不破坏核心能力的前提下演进。
坦白讲我也不知道这个承诺能守多久。协议这东西,说要稳定的多,真稳定的少。
不过时间线倒是给得挺厚道。规范的 RC 是 5 月 21 号就锁定的,7 月 28 号才最终发布,中间留了整整十周给各家 SDK 维护者验证。也就是说这事儿不是突然袭击,是提前两个多月贴的公告。真被打个措手不及的,大概率是那种写完 server 就再没看过官方博客的。
愚钝如我,也是这两天翻 changelog 才反应过来这次动静有多大。
万青有句词,是谁来自山川湖海,却囿于昼夜厨房与爱。技术协议大概也有类似的处境,诞生的时候都想着自己要连接一切,跑起来之后才发现,真正卡住自己的往往是当初为了省事随手做的一个小决定。比如在一个本地进程里,顺手记住了客户端是谁。
回到开头那句话。九条主要改动,四条在删自己。
一个协议肯这么删自己,通常不是因为它想删,是因为它发现不删走不下去了。这次 MCP 承认的事情其实挺朴素,两年前照着「一个客户端连一个本地进程」画出来的图纸,撑不住 agent 上云之后的样子。
而我们这些在上面搭东西的人,值得顺着这个思路往自己的代码里看一眼。你那套跑得好好的 agent 基础设施里,有多少地方也在偷偷记着「这次会话是谁」。
有几个是真的记得住的。
参考资料
- MCP 2026-07-28 规范发布公告 https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/
- 官方 changelog 完整改动清单 https://modelcontextprotocol.io/specification/2026-07-28/changelog
- 特性生命周期与废弃政策 https://modelcontextprotocol.io/community/feature-lifecycle
- MRTR 多轮往返请求模式 https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr
- Streamable HTTP 传输规范 https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http
- 第三方迁移分析 https://www.digitalapplied.com/blog/mcp-2026-07-28-spec-stateless-migration-guide