Anthropic 发布全新的 MCP 规范:可将 MCP 服务器部署到云上
- 以前 MCP 服务器有个毛病:客户端得先握手拿一个会话 ID,之后每次请求都必须回到同一台机器。加机器扩容没用,serverless 也跑不了。
- 新规范把这条规矩废了:握手和会话 ID 直接删除,每个请求自带身份信息,哪台机器都能接,挂个最普通的负载均衡器就行。
- 服务器中途想问用户一句话(比如「这步会产生费用,确认吗」),现在要跑两趟:先返回「我还差信息」,客户端问完用户,再把原请求重发一遍。
- 状态没消失,只是搬到了明面上,服务器发一个号码牌,让模型当参数传回来。
- 一批东西被删或判了缓刑:握手、会话 ID、SSE 断线续传直接没了;Roots、Sampling、Logging 和老的 HTTP+SSE 传输还能用至少 12 个月。
- 第三方安全分析提醒:协议消掉了会话劫持这类老漏洞,但把安全责任压给了开发者,比如一个请求就能让服务器起个大任务,然后立刻跑掉。
- 最实用的一条:7 月 28 日不是切换开关。你现在的服务器和客户端不会坏,新客户端遇到老服务器会自动退回老握手。
老 MCP 到底卡在哪:会话把服务器钉死在一台机器上
2026 年 7 月 28 日,MCP 官方发布第五版规范 2026-07-28,把协议核心从有状态的双向连接改成无状态的请求/响应,同时启用正式的扩展框架和弃用政策;Anthropic 同天宣布 Claude 全线产品陆续跟进。
MCP 是让 AI 助手连上外部工具和数据的那个协议,Anthropic 2024 年底开源,现在整个行业都在用。四个第一梯队 SDK(TypeScript、Python、Go、C#)加起来每月接近 5 亿次下载,其中 TypeScript 和 Python 各自的累计下载量都过了 10 亿次。
这次改版要解的,是一个非常具体的工程死结:想把一个 MCP 服务器部署到云上给很多人用,很难。
老 MCP 像打电话。你先拨号(initialize 握手),接线员给你一个分机号(Mcp-Session-Id),之后你每说一句话都靠这个分机号认人。想换个接线员?对不起,只有原来那位记得你刚才说了什么。
新 MCP 像寄快递。每个包裹上都贴着完整的寄件人、收件人和内容说明,谁拿到都能处理,不需要先跟谁打招呼。
「打电话」这个设定带来三个连锁问题:
第一,加机器不管用。第二台机器不认识你的分机号,请求打过去直接懵。想扩容,要么让负载均衡器记住每个人该去哪台机器(这叫粘性会话,sticky session),要么让所有机器共用一个 Redis 之类的存储来查会话。
第二,serverless 和边缘节点跑不了。这些环境的设计前提就是「每次调用都是全新的、跑完就忘」,跟「记住会话」天然打架。
第三,中间的网关看不懂你在干嘛。请求内容全在 JSON 请求体里,路上的负载均衡器、限流器、防火墙想按「这次是调哪个工具」来路由或限流,得把 JSON 拆开读,代价高,很多网关根本不干这活。
(得记住谁属于哪台)
闲着
只能它
闲着
都必须回到实例 2
(轮询就行)
任何一个请求
无状态核心具体怎么做到的
那么,新规范是怎么把这个死结解开的?三件事。
握手没了
initialize 和 notifications/initialized 这一对开场仪式被直接删除,Mcp-Session-Id 这个 HTTP 头也删了。以前握手时交换的信息,协议版本、客户端是谁、客户端支持哪些能力,现在改成每个请求自己带,塞在 _meta 字段里。服务器那边也建议在每个返回结果的 _meta 里报一下自己的身份。
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"}}}}
想提前问清楚服务器有什么本事,有个新方法
新增了一个 server/discover,用来查服务器支持哪些协议版本、有什么能力、自己是谁。这里的分工要看清楚:服务器必须实现它,客户端可以不用它。客户端愿意的话可以在干任何事之前先查一次,不查直接发请求也完全合法。这跟老的 initialize 有本质区别,那是必经的开场仪式,这只是一次普通查询。
规范把话说死了:连接不等于会话
服务器不得依赖同一个连接上之前的请求来推断上下文(能力、协议版本、客户端身份都算)。每个请求自己在 _meta 里提供这些信息。
一个打开着的连接,哪怕是本地 stdio 那种一直挂着的进程,不是一次对话。客户端可以在同一根管子里塞进完全不相干的请求,服务器不许把「连接身份」当成「会话身份」用。
顺带一提,像 subscriptions/listen 这种长连接也没破坏无状态:它本身仍然是一次普通的请求/响应,只不过那个「响应」是一条开着不关的通知流。它的状态属于那次请求,不属于底下的连接。
关键一点:状态没有消失,只是从暗处搬到了明处
但这里有个最容易被误读的地方,协议层没有会话了,不代表你的应用必须无状态。
如果你的服务器确实需要跨多次调用记事,官方给的做法是:从工具里发一个明确的「号码牌」(handle)出去,让模型把它当成普通参数,在后续调用里传回来。
- 会话 ID 由传输层维护,模型看不见它
- 状态跟着连接走,连接断了状态跟着没
- 换台机器处理就找不着北
- 出问题不好查,状态不在你的业务代码里
- 服务器从工具返回一个 handle,就是一个普通的返回值
- 模型看得见它,能自己在不同工具之间把它串起来
- 它作为工具参数传递,跟连接无关
- 状态归你的业务层管,能查能调
官方在发布博客里给的理由是这个做法比藏在传输层里的会话状态更好用,原因就是模型能看见这块状态:
我们发现这样比把会话状态藏在传输层里更好,模型能看见这个 handle,能自己在不同工具之间把它串起来。
MCP 官方发布博客,2026-07-28(译文)
这里有个权衡值得说破:状态管理这件事并没有消失,它从「传输层偷偷帮你管」变成了「你自己在业务层明着管」。好处是透明、可排查、能水平扩展;代价是它现在归你的业务层了。
MRTR:服务器想问用户一句话,现在得跑两趟
既然服务器不能再主动开口,那它中途要问用户一句话怎么办?
先说这是个什么场景。一个工具跑到一半,需要用户确认点什么:Supabase 的 MCP 服务器要建个新项目,得先问一句「这会产生费用,确认吗」;或者一条 SQL 会删数据,得先让用户点头。
以前的做法是服务器直接从那条一直开着的双向流里,反向给客户端发一个请求,问一句(elicitation/create)、借用客户端的模型算一下(sampling/createMessage)、或者问文件目录在哪(roots/list)。前提是那条流得一直开着,而这正是无状态化要拆掉的东西。
新做法叫 MRTR(Multi Round-Trip Requests,多轮往返请求):服务器不再发请求,改成「返回一个还没完成的结果」,把想问的问题装在里面;客户端问完用户,带着答案把原请求重发一遍。点下面五步看它怎么走:
(Claude 这类)
"params":{"name":"create_project", …}}
客户端照常发起一次工具调用,请求 ID 是 1。这一步跟以前没区别。
"resultType":"input_required",
"inputRequests":{"confirm":{…}},
"requestState":"一坨签过名的数据"}}
服务器发现信息不够,返回一个结果(注意不是发请求),resultType 标成 input_required,inputRequests 里装着它想问的问题。
requestState 是它塞给客户端保管的一坨不透明数据,用来记住「我刚才做到哪一步」。这样服务器自己不用存任何东西。
客户端把问题呈现给用户:「新建这个项目会产生费用,确认吗?」用户点确认。
这一步服务器完全不参与,它甚至可以在这期间被回收掉、换一台。规范规定客户端不得查看、解析、修改 requestState 里的内容,也不许对它做任何假设,只能原样保管。
"params":{"name":"create_project", …,
"inputResponses":{"confirm":{…}},
"requestState":"原样带回来"}}
客户端把答案装进 inputResponses,把整个原请求重新发一遍,并原样带回 requestState。
请求 ID 必须换新的(这里是 2),规范明确要求,因为这在协议看来是两个互相独立的请求。
服务器这次信息够了,干活,返回最终结果。
整个来回没有任何一步依赖「同一台机器」或者「同一条连接」。
Supabase 的产品负责人说这个模式让他们做成了一件想做很久的事:
支持询问用户这件事在我们路线图上放了一阵子了,但因为 Supabase MCP 是无状态跑的,一直不好做。MRTR 改变了这一点,它让我们的工具在动手之前可以先跟用户确认,比如新建一个项目的费用,或者一条会删数据的查询。
Inian Parameshwaran,Supabase 产品负责人(译文)
代价在两处
第一,原请求要整个重发。服务器可能得把前面的工作重做一遍,除非它把中间结果也编码进 requestState 里。
第二,requestState 是一颗需要小心处理的手雷。它在客户端手上转了一圈才回来,规范对此的要求相当硬:
服务器必须把 requestState 当成攻击者可控的输入。如果它会影响授权、资源访问或业务逻辑,服务器必须用 HMAC 或 AEAD 之类的手段保护它的完整性(相当于打一个别人仿不出的火漆印),验不过的直接拒绝。只有一种情况可以免掉这道保护:被人改了也顶多让这次请求失败,不会有更坏的后果。
防重放这一层规范只给到「应该做」这一档,具体是在这坨受保护的数据里塞进三样东西并逐一核对:是谁(换个人拿来用就拒)、多久过期(超时就拒)、原来是哪个请求(方法名加上关键参数的摘要,对不上就拒)。
规范还补了一句实话:这些措施只能缩小重放的时间窗、防住跨用户跨请求的复用,并不保证「只能用一次」。真需要一次性的场景(比如一次性兑换码),得自己在服务端另外保证。
服务器省掉的那套共享存储,变成了要自己写对的密码学签名。
另外 MRTR 有使用范围:只能用在 prompts/get、resources/read、tools/call 这三个方法上,其他请求上服务器不得返回 input_required。
网关终于看得懂 MCP 了(附一条发布博客没提的安全机制)
状态的问题解决了,那么还剩一个老问题:中间那些网关,一直不知道你在调什么。
新规范给每个 POST 请求加了必填的 HTTP 头。Mcp-Method 每个请求都要带,说这次调的是什么方法;Mcp-Name 只在点名了具体对象时才带,也就是 tools/call、resources/read、prompts/get 这三种请求,说调的是哪个工具、资源或提示词。加上一直都要带的 MCP-Protocol-Version,网关、限流器、防火墙读这几行就够了。
想知道这是调哪个工具,必须把整个 JSON 请求体解析开。对高吞吐的网关来说这笔开销不划算,很多网关干脆不做。
按方法和工具名路由、限流、计量、鉴权,全都在头这一层做完,不用碰请求体。
听着像个纯便利功能,但它配了一条硬性校验,这条在发布博客里没提:
服务器必须核对 Mcp-Method、Mcp-Name 这些头的值跟请求体里对应的值是否一致,不一致就返回 HeaderMismatch 错误(错误码 -32020)。
为什么非做不可:不校验的话,攻击者可以在头里写一个人畜无害的工具名骗过网关放行,请求体里却调另一个危险工具。网关看头放行,服务器看体执行,中间就是一道口子。
规范还专门叮嘱中间层:如果协议版本太老、或者头压根没带,宁可直接拒掉,也别去信一个没经过校验的头。
还有个配套的新东西叫 x-mcp-header:服务器可以在工具的参数定义里标注「这个参数要镜像进 HTTP 头」,头名叫 Mcp-Param-{名字},方便按租户之类的维度路由。这个功能对服务器是可选的,但客户端必须支持。记住它,后面讲安全时它还要出场。
工具清单终于能缓存了(顺带保住上游的 prompt 缓存)
无状态还带来一个顺手的好处:列表结果不再随连接变化,于是能缓存了。
tools/list、prompts/list、resources/list、resources/read、resources/templates/list 这五个方法的返回结果,现在必须带两个字段:
public 还是 private,决定路上的共享代理能不能替大家一起缓存。另外还有一条要求级别稍低、但值钱得多的:服务器应该按确定的顺序返回工具列表。
这条得解释一下才看得出它值钱在哪。工具清单是要拼进模型提示词里的,而提示词的缓存是从头开始逐字匹配的,开头只要变一个字,后面整段缓存就全部失效、重新计费。工具列表如果每次返回的顺序都不一样,上游的缓存就永远打不中。
一个「排序要稳定」的小要求,省下的是真金白银。
什么被删了,什么被判了缓刑
接下来是最该对着自查的部分,你手上的东西哪些会坏。这次 changelog 一共 9 条重大改动、12 条次要改动、4 条弃用,下面两张清单是其中最该盯的。
initialize/notifications/initialized握手Mcp-Session-Id头与协议级会话- HTTP GET 端点、
resources/subscribe与resources/unsubscribe换成统一的subscriptions/listen长连接流,客户端按通知类型逐个订阅 ping、logging/setLevel、notifications/roots/list_changed日志级别改成每个请求在_meta里带logLevel- SSE 断线续传(
Last-Event-ID头和 SSE 事件 ID)流一断,那个进行中的请求就丢了,客户端必须换一个新的请求 ID 整个重发 tasks/result与tasks/list改成tasks/get轮询notifications/elicitation/complete和 URL 模式询问里的elicitationId- 服务器主动发请求这个模式本身全部改走 MRTR
- Roots(告诉服务器该看哪些目录)改用工具参数、资源 URI 或服务器配置来传
- Sampling(服务器借用客户端的模型)改成直接对接大模型厂商的 API
- Logging(协议层日志)改成打到
stderr,或者用 OpenTelemetry - 老的 HTTP+SSE 传输2025-03-26 就软性弃用了,这次正式归入弃用状态,迁到 Streamable HTTP
includeContext的thisServer和allServers两个值不填,或者用none- 动态客户端注册(DCR)改推 CIMD;为兼容还留着,未来某一版会删
错误码也动了
资源找不到的错误码从 -32002 改成了标准的 -32602(参数无效),跟 JSON-RPC 规范对齐。另外三个新错误码重新编了号:-32020 头与体不一致、-32021 缺必需能力、-32022 协议版本不支持。规范顺手划了地盘:-32000 到 -32019 是历史遗留区,这些号是政策出台前各家自己占的,往后不许再往里分配新号,新实现也不该再用;-32020 到 -32099 归规范专用。
如果你的客户端代码里硬编码了 -32002 这个数字,这次要改,但别改过头:规范同时要求客户端仍然接受老版本服务器发来的 -32002,只是自己这边不再发了。
弃用政策本身是这次的新东西
以前弃用全凭默契,这次成文了。规范定义了功能的三种状态和一条最短时限:
提案通过
12 个月
这 12 个月是从「被标记弃用的那一版规范发布」算起,不是从提案定稿算起。除此之外还有两条配套:一是有一个统一的「待删清单」页面,不用自己去翻各版 changelog 拼图;二是第一梯队 SDK 被要求必须在下一个版本里用各语言原生的方式把弃用标出来(TypeScript 的 @deprecated、C# 的 [Obsolete]、Java 的 @Deprecated、Go 的 Deprecated: 注释),做不到会走降级流程。
扩展变成了正式通道
核心删瘦了,那么新能力往哪加?
以前想给 MCP 加能力,只能往核心协议里塞,Tasks 之前就挂在核心里当「实验性功能」。这次立了正式的扩展框架:核心保持精简,新能力走扩展,客户端和服务器各自在能力里声明自己支持哪些扩展。
tasks/get 轮询进度。Tasks 由 AWS 贡献,是首批官方扩展之一;AWS 那边说新规范和它的无状态核心已经在 Amazon Bedrock AgentCore 里可用。Tasks 的机制值得多说两句:服务器决定某个请求要跑很久时,返回一个任务号而不是最终结果,这个任务号是持久的句柄,客户端断线了、重启了,拿同一个号接着轮询就行。任务有五个状态:
深色的三个是终态,到了就不再变。中途要问用户,任务转到 input_required,客户端用新的 tasks/update 回答,不需要第二条连接,也不需要服务器主动发消息。
授权这块收紧了四处
删的加的都讲完了,但还有一块官方单独拎出来说:过去一年里,实现方花时间最多的地方就是授权。这次改了四处。
localhost 回调地址,报 redirect_uri 错误。你要是纳闷过为什么你的命令行客户端 OAuth 流程报
MCP 官方发布博客,谈 application_type 那条改动(译文)redirect_uri错,多半就是这个原因。
第三方安全分析:责任从协议搬到了开发者身上
但以上都是官方口径。规范正式发布前,有第三方把这套新规范当攻击面研究了一遍,Akamai 在 6 月发布了一份分析,结论被 SecurityWeek 报道。
他们的判断是:新规范确实去掉了几类漏洞,但同时开出了几片新的攻击面,而且这些新风险高度依赖开发者怎么实现。
- 会话劫持,没有会话了,也就没得劫持
- 服务器主动弹提示,服务器不能再主动发起请求,只能被动等客户端来问
- 更强的认证标准,就是上一节那四处收紧
- 状态对象和追踪标识如果可预测:劫持别人正在进行的工作流、拿到别的 Agent 的数据、触发跨租户的越权操作
x-mcp-header泄密:开发者要是不小心把 API 密钥、令牌或者个人信息映射进了 HTTP 头,这些秘密就被直接推进了头里,路径上每一个负载均衡器、代理、日志系统都看得见- 协议混淆(Desync):头和请求体不一致,绕过基于头的安全控制,这正是规范里那条强制校验要挡的东西,挡不挡得住看服务器实现方有没有认真实现
- MCP Apps 把浏览器的老问题带了进来:比如存储型跨站脚本(XSS)
- Tasks 是个「打了就跑」的拒绝服务通道:建一个任务对客户端来说很便宜,对服务器很贵。攻击者一个请求就能让服务器起一个吃 CPU、内存、数据库存储的任务,然后立刻断开走人
既然协议转向了无状态模型,还引入了富 UI 应用和异步任务,那么关键的安全边界现在完全取决于开发者怎么实现。
Maxim Zavodchik,Akamai 威胁研究高级总监,据 SecurityWeek 报道(译文)
他们的总结是,这些改动的分量超过了增量改进。它从根本上重塑了安全责任落在谁身上:以前由协议强制保障的安全决策,现在越来越多地交给了 MCP 服务器的开发者和平台运营方。
报道在这里补了一句同样重要的话:MCP 协议本身并没有变得更脆弱,变大的是基于新规范搭起来的那些 MCP 服务器的攻击面。
那我现在要不要动手
那么说了这么多破坏性改动,最后回答那个最实际的问题。答案跟「最大规模破坏性更新」这个印象是相反的。
7 月 28 日不是一个切换开关。现有的客户端和服务器今天不会坏,7 月 28 日也不会坏。那天只是规范正式文本发布的日子,不是把老协议关掉的日子。
具体的兼容路径有三条:
一,新客户端遇到老服务器会自动退让。说 2026-07-28 的客户端碰上只会 2025-11-25 或更老版本的服务器时,会回落到原来的 initialize 握手,新旧照常互通。反过来,老服务器收到不带协议版本头的请求,可以当成 2025-03-26 处理。
二,SDK 的大版本升级是另一码事。Python 和 TypeScript 都出了 v2,这是各自的破坏性大版本,什么时候升由你自己定,跟 7 月 28 日没关系。TypeScript SDK 的 v1.x 在 v2 正式发布后还会继续修 bug 和安全问题至少 6 个月,Python 的 v1.x 分支继续收关键 bug 修复和安全补丁。
三,升级 SDK 本身不一定改变你在网线上说什么,而且四个 SDK 的行为不一样,这点容易踩:
2026-07-28,得在接线的时候显式选。四个第一梯队 SDK(TypeScript、Python、Go、C#)当天就支持新规范,Rust SDK 是 beta 支持。真正要动手改的是下面这几类,对着过一遍:
MCP 删掉了开场握手和会话号,一个请求现在哪台机器都能接
MCP 官方发布第五版规范 2026-07-28,把协议核心改成「说完就忘」,一页带图讲完改了什么、什么会坏。
↓ 一页读完 · 有一张会动的图
MCP 是让 AI 助手连上外部工具和数据的那套协议,Anthropic 2024 年底开源,现在整个行业都在用。用的人多了,一个工程死结就压不住了:想把一个 MCP 服务器部署到云上给很多人用,很难。
下载量与目录数为 MCP 官方与 Anthropic 各自公布,近 20% 是 honeycomb.io 自述,第三方均未复现。
✔ 连接一直开着,服务器能反过来问用户一句话
✘ serverless(按次计费、跑完就回收的云服务)和边缘节点跑不了:那些环境每次调用都是全新的,天生不记事
✘ 路上的网关看不懂:想知道这次调的是哪个工具,得把整个请求拆开读,代价高,多数网关干脆不做
新规范把开场握手和会话号一起删了。每个请求自己带上协议版本、客户端是谁、支持哪些能力,服务器处理完就忘。负载均衡器(把请求分给多台机器的调度员)不用再记谁属于哪台,轮着发就行。
协议层没了会话,应用照样可以记事,做法换了个地方:服务器从工具里发一张明面上的号码牌出去,让模型当成普通参数,在下次调用时传回来。
会话号由传输层维护,模型看不见它。连接断了状态跟着没,换台机器就找不着北,出问题也不好查。
号码牌就是工具的一个返回值。模型看得见,能自己在不同工具之间串起来;它归业务层管,能查能调,跟连接无关。
状态管理这件事并没有消失,它从「传输层偷偷帮你管」挪到了「你自己在业务层明着管」。
服务器不能再主动开口,那它中途要问用户一句话怎么办。新规范给的办法叫 MRTR(多轮往返请求):服务器先返回一个「我还差信息」的半成品结果,客户端问完用户,把原请求带着答案整个重发一遍。
代价在两处:原请求要整个重发,服务器可能得把前面的活重做一遍;那张号码牌在客户端手上转了一圈才回来,规范因此把它定成「攻击者可以随便改的输入」,凡是牵涉授权和权限的,服务器必须给它打上一个仿不出来的签名,验不过直接拒。省掉的共享存储,换成了要自己写对的密码学签名。
这次改动清单一共 9 条重大、12 条次要、4 条弃用。最该对着自查的是这两张单子。
✘ 断线续传:流一断,进行中的请求就丢了,得换个新编号整个重发
✘ 服务器主动发请求这个模式本身,全部改走 MRTR
✘ 资源找不到的错误码从 -32002 改成标准的 -32602,代码里写死过这个数字的要改
✔ 老的 HTTP+SSE 传输,同样 12 个月缓刑,之后迁到 Streamable HTTP
✔ 动态客户端注册(DCR),改推 CIMD,为兼容暂时留着
拿一个会话号。
之后每个请求,
都得回同一台。
白开了
每月下载量合计
, 删
会话号
, 删
边缘节点
也能跑了
它怎么问我确认?
客户端手里
转过一圈。
规范要求当成
攻击者能改的
输入,签名
验不过直接拒。
- × 开场握手和会话号
- × SSE 断线续传,断了整个重发
- × 服务器主动发请求这个模式
- × 错误码 -32002 改成 -32602
Logging 和老的
HTTP+SSE 传输,
缓刑到这时候
我得连夜改吗?
代价是状态和安全都落到了开发者自己手上。
