Mingyu's Library主页

深度学习文档 · 2026-09-14

Deep Dive · 2026-09-14

MCP 无状态协议核心
与水平扩展部署

MCP's Stateless Protocol Core
and Horizontal Scaling

2026-07-28 版规范把 MCP 从「有状态的双向协议」改成了「无状态的请求/响应协议」。这不是加了个功能,是把协议的形状改了——远程 MCP 服务终于能挂在一台普通的轮询负载均衡后面。这份文档讲清楚:改了什么、为什么改、怎么迁、会踩什么坑。

The 2026-07-28 specification turned MCP from a stateful, bidirectional protocol into a stateless request/response one. This isn't a new feature — it changes the shape of the protocol, so a remote MCP server can finally sit behind a plain round-robin load balancer. This document covers what changed, why, how to migrate, and what bites.

调研时间:2026-09-14 · 规范版本:2026-07-28 · 阅读约 18 分钟

Researched: 2026-09-14 · Spec revision: 2026-07-28 · ~18 min read

第 1 节SECTION 130 秒速览The 30-second version

如果你只有半分钟:MCP 以前要求客户端和服务器先握手、拿一个会话 ID、然后在这条会话上来回对话。这意味着后续每个请求都必须回到「记得这个会话」的那台服务器上。2026-07-28 版规范把这套全删了——握手没了,会话 ID 没了,每个请求自带全部必要信息,因此可以落到任意一台健康的实例上。

If you only have thirty seconds: MCP used to require a handshake, a session ID, and a conversation carried on that session. That meant every follow-up request had to land on the one server that remembered the session. The 2026-07-28 revision removed all of it — no handshake, no session ID, every request carries what it needs, so it can land on any healthy instance.

2026-07-28
规范版本号(MCP 用日期做版本)
Spec revision (MCP versions by date)
6 SEPs
协同实现无状态的提案数量
Proposals working together for statelessness
SEP-2322
MRTR——本次的头号特性
MRTR — the headline feature
12 个月months
被弃用能力的最短保留期
Minimum grace period for deprecated features

数据来源:MCP 官方博客SEP-2322(检索于 2026-09-14)

Sources: MCP official blog, SEP-2322 (retrieved 2026-09-14)

之前:有状态 客户端 网关粘性会话 实例 A ✓ 实例 B ✗ 实例 C ✗ 会话只活在实例 A 的内存里 之后:无状态 客户端 负载均衡轮询即可 实例 A ✓ 实例 B ✓ 实例 C ✓ 请求自带全部上下文,谁接都一样 代价在哪里? · 服务端不再「记得」你 → 每个请求都要带协议版本、客户端信息、capabilities · SSE 断流不再可恢复 → 传输中的请求直接丢失,必须用新 ID 重发 · 因此:任何不可逆的工具(转账、删除、部署)都必须自己实现幂等 · 需要「问用户一句」的场景 → 改用 MRTR 带状态重试(见第 4 节)
图 1:同一套远程 MCP 服务,在有状态与无状态两版规范下的部署形态差异。下半部分是这笔交易的代价。
BEFORE: STATEFUL Client Gatewaysticky sessions Pod A ✓ Pod B ✗ Pod C ✗ session lives only in Pod A's memory AFTER: STATELESS Client Load balancerplain round-robin Pod A ✓ Pod B ✓ Pod C ✓ request carries its own context; any pod will do What does it cost? · The server no longer remembers you → every request must carry protocol version, client info, capabilities · SSE streams are no longer resumable → an in-flight request is lost and must be re-issued with a new ID · Therefore: any irreversible tool (transfer, delete, deploy) must implement its own idempotency · Anything that needs to ask the user → move to MRTR's stateful retry (Section 4)
Figure 1: The same remote MCP service deployed under the stateful and stateless revisions. The lower half is what the trade costs you.

第 2 节SECTION 2它在解决什么问题What problem it solves

先说痛点:MCP 最初是照着「本地」设计的

Start with the pain: MCP was designed for "local" first

MCP(Model Context Protocol,模型上下文协议)最初要解决的问题很朴素:让一个 AI 助手能调用你电脑上的工具——读文件、查数据库、跑命令。在这个场景里,客户端和服务器就在同一台机器上,一对一,进程活着会话就活着。于是协议自然地做成了有状态的:先 initialize 握手协商能力,拿一个 Mcp-Session-Id,之后所有请求都挂在这个会话上。

MCP (the Model Context Protocol) started with a modest problem: let an AI assistant call tools on your own machine — read files, query a database, run commands. In that setting the client and server share a machine, one-to-one, and the session lives as long as the process does. So the protocol was naturally stateful: an initialize handshake to negotiate capabilities, an Mcp-Session-Id, and every later request hanging off that session.

问题出在 MCP 火了之后。人们开始把 MCP 服务部署到云上,让成千上万个 agent 远程调用。这时「有状态」就从设计优点变成了运维负担。

The trouble started once MCP took off. People began deploying MCP servers in the cloud for thousands of agents to call remotely. At that point "stateful" flipped from a design virtue into an operations tax.

打个比方An analogy

有状态的远程服务像银行柜台的叫号:你在 3 号窗口办了一半,资料都摊在 3 号窗口的桌上。你去上个厕所回来,只能回 3 号窗口——别的窗口不知道你办到哪儿了。如果 3 号窗口下班了(实例重启/扩缩容),你得从头再来。

A stateful remote service is like a bank teller window: you're halfway through at window 3, and your paperwork is spread across window 3's desk. Step away and come back, and you must return to window 3 — no other window knows where you left off. If window 3 closes (a pod restarts or scales down), you start over.

无状态则像自助机:你每次把整张表格递进去,哪台机器都能受理。代价是——你每次都得递整张表格。

Stateless is like a self-service kiosk: you hand over the whole form each time, and any kiosk can process it. The cost is that you hand over the whole form each time.

有状态在云上具体贵在哪

Where statefulness actually costs you in the cloud

一句话概括本次改动的动机:让远程 MCP 服务能像一个普通的 HTTP 微服务那样部署和扩容。官方博客把 2026-07-28 描述为「自协议发布以来最大的一次修订」。

The motivation in one line: make a remote MCP server deployable and scalable like any ordinary HTTP microservice. The official blog calls 2026-07-28 the largest revision since the protocol launched.

来源:MCP 官方博客:2026-07-28 规范Google Developers Blog

Sources: MCP blog: the 2026-07-28 specification, Google Developers Blog

第 3 节SECTION 3六项核心变化Six core changes

官方说明里提到,无状态不是靠单个改动实现的,而是六个 SEP(Specification Enhancement Proposal,规范增强提案)协同的结果。下面逐条拆开讲——每条都先说「改了什么」,再说「对你意味着什么」。

The official write-up notes that statelessness isn't one change but six SEPs (Specification Enhancement Proposals) working together. Below, one at a time — what changed, then what it means for you.

① 握手和会话 ID 被删掉了

① The handshake and session ID are gone

规范移除了 initialize / initialized 握手,也移除了 Mcp-Session-Id。取而代之:每个请求都自己携带协议版本、客户端信息与 capabilities

The spec removes the initialize / initialized handshake and removes Mcp-Session-Id. Instead, each request carries its own protocol version, client information, and capabilities.

对你意味着什么:每个请求体大了一点(多带了几十到几百字节的元信息),换来的是「任意实例都能处理」。这是一笔明确的带宽换弹性的交易。

What it means for you: each request body gets slightly bigger (tens to hundreds of bytes of extra metadata) in exchange for "any pod can handle it." It's an explicit bandwidth-for-elasticity trade.

② 两个新 header,让网关不用拆包

② Two new headers, so gateways don't need to open the box

Streamable HTTP 传输现在要求带两个 header:

The Streamable HTTP transport now requires two headers:

Mcp-Method
正在执行的 JSON-RPC 方法名,比如 tools/calltools/list
The JSON-RPC method being executed, e.g. tools/call or tools/list.
Mcp-Name
被调用的具体工具、prompt 或 resource 的名字。
The specific tool, prompt, or resource name being invoked.

对你意味着什么:这是本次改动里最容易立刻吃到红利的一条。你可以在 Nginx / Envoy / API 网关上直接按 header 做路由和限流——比如把重的 tools/call 路到大内存节点、把轻的 tools/list 路到小节点;或者给某个特别贵的工具单独设限流,不用写任何自定义的请求体解析插件。

What it means for you: this is the easiest win in the whole revision. You can route and rate-limit straight off the headers in Nginx / Envoy / your API gateway — send heavy tools/call traffic to big-memory nodes and light tools/list to small ones, or rate-limit one expensive tool specifically, all without a custom body-parsing plugin.

③ 列表结果可以缓存了(SEP-2549)

③ List results are now cacheable (SEP-2549)

tools/listprompts/listresources/list 以及 resources/read 的返回值现在可以带上 ttlMscacheScope 两个字段,语义参照 HTTP 的 Cache-Control;同时规范要求返回结果顺序确定

tools/list, prompts/list, resources/list, and resources/read results can now carry ttlMs and cacheScope, modeled on HTTP Cache-Control, and the spec requires deterministic ordering of results.

为什么「顺序确定」很重要?因为工具清单通常被放在 LLM 上下文的最前面,是 prompt 缓存前缀的一部分。清单顺序每次抖动一下,缓存前缀就变了,整段缓存全部作废。确定性排序 + ttlMs 一起,直接落到你的 token 账单上。

Why does deterministic ordering matter? The tool list usually sits at the very front of the LLM's context, inside the prompt-cache prefix. If the order jitters between calls, the prefix changes and the whole cached segment is invalidated. Deterministic ordering plus ttlMs shows up directly on your token bill.

④ 服务端主动推送改用 subscriptions/listen

④ Server-initiated messages move to subscriptions/listen

过去服务端要主动给客户端发消息(比如「工具列表变了」),靠的是一条独立的 HTTP GET 长连接流。现在这条独立 GET 流subscriptions/listen 取代

Previously, server-to-client messages (e.g. "the tool list changed") rode a standalone HTTP GET stream. That standalone GET stream is replaced by subscriptions/listen.

⑤ SSE 的可恢复性被移除了 ⚠️

⑤ SSE resumability is removed ⚠️

这是最容易被忽略、后果最实在的一条。SSE 流的可恢复机制(Last-Event-ID 与事件 ID)被移除了。如果响应流断了,那个传输中的请求就丢了,客户端必须用一个新的请求 ID 重新发一次。

This is the easiest one to miss and the one with the most concrete consequences. SSE stream resumability (Last-Event-ID and event IDs) is removed. If a response stream breaks, the in-flight request is lost, and the client must re-issue it as a new request with a new ID.

推论:任何不可逆的工具都必须自己实现幂等。「创建订单」「发起转账」「触发部署」——如果它可能被重发一次,而你的服务端没有幂等键,你就会看到重复执行。规范把这件事从「最佳实践」变成了「不做就会出事」。

The corollary: every irreversible tool must implement its own idempotency. "Create order," "initiate transfer," "trigger deploy" — if it can be re-issued and your server has no idempotency key, you will see duplicate execution. The spec moved this from best practice to non-negotiable.

⑥ 授权:DCR 弃用,改用 CIMD

⑥ Authorization: DCR deprecated in favor of CIMD

动态客户端注册(Dynamic Client Registration, DCR)被正式弃用,改推 CIMD(Client ID Metadata Documents,客户端 ID 元数据文档)。DCR 出于兼容仍然可用。

Dynamic Client Registration (DCR) is formally deprecated in favor of CIMD (Client ID Metadata Documents). DCR still works for backward compatibility.

两者的差别一句话说清:DCR 是「客户端先去授权服务器登记一次,拿一个 client_id」;CIMD 是「客户端的 client_id 本身就是一个它自己托管的 URL,授权服务器需要时去这个 URL 现取元数据」。

The difference in one line: with DCR a client registers with the authorization server once and receives a client_id; with CIMD the client_id is a URL the client hosts itself, and the authorization server resolves it on demand.

对你意味着什么:换授权服务器不再需要重新注册。对同时接多个 MCP 服务、每个服务背后是不同 IdP 的 agent 来说,这消掉了一大堆注册状态。

What it means for you: switching authorization servers no longer requires re-registration. For an agent talking to many MCP servers each behind a different IdP, that erases a pile of registration state.

顺带:什么被弃用了,还能用多久

Also: what's deprecated, and for how long

能力状态说明 CapabilityStatusNotes
initialize / Mcp-Session-Id 已移除协议层不再有会话概念 RemovedNo session concept at the protocol layer
SSE 可恢复(Last-Event-ID)已移除断流即丢,需重发 SSE resumability (Last-Event-ID)RemovedBreak = loss; re-issue required
Dynamic Client Registration 已弃用改用 CIMD,兼容期内继续可用 DeprecatedUse CIMD; still works during the grace period
Roots / Sampling / Logging 已弃用官方承诺至少继续可用 12 个月 DeprecatedOfficially guaranteed to keep working for at least 12 months
Tasks 移出实验性核心进入 io.modelcontextprotocol/tasks 扩展,含轮询式 tasks/get 与新的 tasks/update Moved out of the experimental coreNow the io.modelcontextprotocol/tasks extension, with poll-based tasks/get and a new tasks/update

来源:MCP 官方博客Appwrite 技术解读Stacktree 破坏性变更清单

Sources: MCP official blog, Appwrite breakdown, Stacktree breaking-changes list

第 4 节SECTION 4MRTR:没有长连接的「人在回路」MRTR: human-in-the-loop without a stream

前面留了一个问题没答:如果协议没有会话、服务端也不能主动推消息,那「工具执行到一半需要问用户一句」怎么办?——比如一个部署工具要确认「真的要发到生产环境吗?」。这正是 MRTR(Multi Round-Trip Requests,多轮往返请求) 要解决的,它是 SEP-2322,也是本次修订的头号特性。

One question is still open: if the protocol has no session and the server can't push, what happens when a tool needs to ask the user something mid-execution? A deploy tool confirming "really ship to production?", say. That's what MRTR (Multi Round-Trip Requests) is for. It is SEP-2322, and it's the headline feature of this revision.

MRTR 是什么

What MRTR is

一句话:服务端不再「反向发起一个请求去问客户端」,而是先返回一个「还没做完,我需要这些输入」的结果,客户端补齐后重试原来那次工具调用

In one line: instead of the server initiating a reverse request to ask the client, the server returns an "incomplete — I need these inputs" result, and the client fills them in and retries the original tool call.

具体机制:服务端返回一个 InputRequiredResult(resultType: "input_required"),里面有两样东西——一个 inputRequests 映射(我要问什么),和一个不透明的 requestState 数据块(我做到哪儿了)。客户端拿到答案后,把 requestState 原样带回去重发。

The mechanism: the server returns an InputRequiredResult (resultType: "input_required") containing an inputRequests map (what I need to ask) and an opaque requestState blob (where I got to). Once the client has the answers, it re-sends with requestState attached verbatim.

为什么这很关键:requestState 是可序列化的,所以「我做到哪儿了」这件事被装进了请求里,而不是留在某台服务器的内存里。于是重试可以落到任意一台实例上接着做。这就是 MRTR 成为无状态化关键拼图的原因。

Why this is the keystone: requestState is serializable, so "where I got to" travels inside the request instead of sitting in one server's memory. The retry can therefore be picked up by any instance. That's why MRTR is the piece that makes statelessness work.

客户端 / Agent 服务端(任意实例) ① tools/call deploy(env=prod) 落到实例 A ② resultType: "input_required" inputRequests: {confirm: "确认发到 prod?"} + requestState(不透明) 客户端向用户提问并拿到回答 ③ 重试同一次调用 + requestState + 答案 这次落到实例 C 也没问题 —— 状态在请求里 全程没有一条长连接被持有
图 2:MRTR 的三步往返。第 ③ 步是重点——重试可以落到和第 ① 步完全不同的服务器实例上。
Client / Agent Server (any instance) ① tools/call deploy(env=prod) lands on Pod A ② resultType: "input_required" inputRequests: {confirm: "Ship to prod?"} + opaque requestState Client asks the user and collects the answer ③ retry the same call + requestState + answers Pod C can serve this one — the state rides in the request No long-lived connection is held at any point
Figure 2: MRTR's three-step round trip. Step ③ is the point — the retry can land on a completely different server instance than step ①.

安全提醒:requestState 是服务端交给客户端保管、之后原样收回的状态。规范要求它是完整性受保护的(integrity-protected)——也就是说服务端必须能验证它没被篡改(签名或加密)。如果你自己实现服务端,千万不要把未签名的明文状态丢出去:那等于把「我做到哪一步、有什么权限」交给客户端随便改。

Security note: requestState is state the server hands to the client and takes back verbatim. The spec requires it to be integrity-protected — the server must be able to verify it wasn't tampered with (signed or encrypted). If you implement a server, never emit unsigned plaintext state: that hands "where I am and what I'm allowed to do" to the client to edit at will.

换个说法:MRTR 和你熟悉的什么东西像? Put another way: what does MRTR resemble?

HTTP 的 302 重定向 + cookie,但方向反过来:服务端说「我还不能给你结果,你先去拿到这几样东西,然后带着我给你的这张便条再来找我」。那张便条(requestState)对客户端是不可读的黑盒,但服务端一看就知道上次办到哪儿了。

也像 OAuth 的 state 参数——同样是「把服务端的上下文寄存在客户端身上走一圈再拿回来」,同样必须防篡改。如果你实现过 OAuth 回调,MRTR 的心智模型可以直接搬过来。

It resembles an HTTP 302 plus a cookie, but reversed: the server says "I can't give you a result yet — go fetch these things, then come back carrying this note I'm giving you." The note (requestState) is an opaque blob to the client, but the server reads it and knows exactly where it left off.

It also resembles OAuth's state parameter — the same "park the server's context on the client for a round trip and take it back," with the same tamper-proofing requirement. If you've implemented an OAuth callback, that mental model transfers directly.

SDK 支持情况

SDK support

官方博客称本次更新伴随「Tier 1 SDK 的更新」。已确认的具体例子:C# SDK v2.0 已包含 MRTR 支持,并会检查客户端协商的是 2026-07-28 还是旧的有状态版本 2025-11-25,据此走不同路径。

The official blog says the release came with "updated Tier 1 SDKs." One confirmed concrete example: the C# SDK v2.0 ships MRTR support and checks whether the client negotiated 2026-07-28 or the older stateful revision 2025-11-25, branching accordingly.

⚠️ 置信度说明:本次调研只确认到 C# SDK 的具体 MRTR 实现细节。其他语言 SDK(Python / TypeScript / Java / Go 等)的支持进度未能逐一核实,请以各 SDK 仓库的 README 与 release notes 为准。

⚠️ Confidence note: this research only confirmed MRTR implementation details for the C# SDK. Support status for the other language SDKs (Python / TypeScript / Java / Go and so on) was not individually verified — check each SDK's README and release notes.

来源:SEP-2322 规范PR #2322C# SDK MRTR 文档AAIF:requestState 设计

Sources: SEP-2322, PR #2322, C# SDK MRTR docs, AAIF: designing requestState

第 5 节SECTION 5怎么部署How to deploy it

这一节讲无状态带来的实际部署形态变化。核心只有一句话:你之前为 MCP 专门加的那些组件,现在大多可以删掉。

This section covers what statelessness changes in practice. The core of it: most of the components you added specifically for MCP can now be deleted.

Agent / MCP 客户端 每个请求自带:协议版本 · 客户端信息 · capabilities 网关 / 负载均衡 —— 只读 header,不拆包 Mcp-Method: tools/call Mcp-Name: deploy_service → 按此路由 / 限流 实例池:轻量操作 tools/list · 可缓存 · 小规格 实例池:重操作 tools/call · 大内存 · 可独立扩容 已可删除的组件 粘性路由 · 共享会话存储 · 长连接池 业务层仍可有状态(数据库、缓存)——无状态说的是「协议层」,不是「你的应用没状态」
图 3:基于 header 的分层部署。最下面那行是最常见的误解澄清。
Agent / MCP client every request carries: protocol version · client info · capabilities Gateway / load balancer — reads headers only, never opens the body Mcp-Method: tools/call Mcp-Name: deploy_service → route / rate-limit on these Pool: light operations tools/list · cacheable · small nodes Pool: heavy operations tools/call · big memory · scales alone Components you can delete sticky routing · session store · connection pool Your business layer can still be stateful (DBs, caches) — "stateless" describes the protocol, not your application
Figure 3: Header-based tiered deployment. The bottom row clears up the most common misreading.

网关侧:一个可以直接抄的路由思路

Gateway side: a routing approach you can copy

下面是思路示意(不是从官方文档抄来的配置,请按你自己的网关语法调整)。要点在于:所有判断都只依赖 header,不碰 body。

Below is an illustrative sketch (not copied from official docs — adapt it to your gateway's syntax). The point is that every decision reads headers only and never touches the body.

# Nginx 思路示意 / Nginx sketch
map $http_mcp_method $mcp_pool {
    "tools/call"     heavy_pool;
    "tools/list"     light_pool;
    "prompts/list"   light_pool;
    "resources/list" light_pool;
    default          light_pool;
}

# 给特别贵的单个工具单独限流
# rate-limit one expensive tool specifically
map $http_mcp_name $mcp_limit_key {
    "deploy_service" $binary_remote_addr;
    default          "";
}
limit_req_zone $mcp_limit_key zone=expensive:10m rate=5r/m;

server {
    location /mcp {
        limit_req zone=expensive burst=2 nodelay;
        proxy_pass http://$mcp_pool;
        # 不再需要 ip_hash / sticky / session affinity
        # no ip_hash / sticky / session affinity needed anymore
    }
}

服务端侧:三件必做的事

Server side: three things you must do

  1. 给不可逆工具加幂等键
    因为断流重发会带新的请求 ID,你不能靠请求 ID 去重。约定一个业务层幂等键(比如客户端生成的操作 UUID,放在工具参数里),服务端据此判重。这一条是必做,不是优化。
  2. 给 requestState 签名
    用 HMAC 或加密,带上过期时间。服务端收回时先验签再解析。所有实例共享同一把密钥(这是无状态部署里少数仍需共享的东西,但它是配置,不是会话存储)。
  3. 给列表接口设合理的 ttlMs
    工具清单如果基本不变,给个几分钟到几小时的 ttlMs;并保证排序是确定的(按名字排,别按 map 遍历顺序)。这一条直接省 token。
  1. Add idempotency keys to irreversible tools
    A re-issued request carries a new request ID, so you can't dedupe on that. Agree on a business-level idempotency key (a client-generated operation UUID in the tool arguments, say) and dedupe on it server-side. This is mandatory, not an optimization.
  2. Sign your requestState
    HMAC or encrypt it, with an expiry. Verify before parsing on the way back. All instances share one key — one of the few things still shared in a stateless deployment, but it's configuration, not a session store.
  3. Set a sensible ttlMs on list endpoints
    If your tool list rarely changes, give it a ttlMs of minutes to hours, and make the ordering deterministic (sort by name; don't emit map iteration order). This one directly saves tokens.

第 6 节SECTION 6迁移清单与会踩的坑Migration checklist and pitfalls

迁移清单

Migration checklist

检查项为什么怎么改 CheckWhyWhat to do
代码里有没有依赖 Last-Event-ID 或 SSE 消息重放 该机制已被移除 删掉断流恢复逻辑,改成「失败即用新 ID 重发」 Any reliance on Last-Event-ID or SSE replay The mechanism is gone Delete resume logic; re-issue with a new ID on failure
有没有不可逆工具 重发会导致重复执行 加业务层幂等键(必做) Any irreversible tools Re-issues cause duplicate execution Add a business-level idempotency key (mandatory)
取消(cancellation)逻辑 每种传输的取消语义需分别实现 按传输逐一实现并测试 Cancellation logic Cancellation semantics differ per transport Implement and test per transport
服务端主动发起的 sampling / elicitation 不能再靠反向请求 迁到 MRTR,状态放 requestState Server-initiated sampling / elicitation Reverse requests are no longer available Move to MRTR with state in requestState
变更通知(list changed 等) 独立 GET 流已被取代 改用 subscriptions/listen Change notifications (list changed, etc.) The standalone GET stream is replaced Move to subscriptions/listen
授权用的是 DCR 已弃用(兼容期内仍可用) 规划迁到 CIMD,不急但要排期 Authorization uses DCR Deprecated (still works during the grace period) Plan a move to CIMD — not urgent, but schedule it
网关的粘性会话配置 不再需要 确认客户端都已升级后再关掉 Gateway sticky-session config No longer needed Turn it off only after all clients have upgraded

四个高频坑

Four common traps

坑 1:以为「无状态」= 我的应用不用管状态了。不是。无状态说的是协议层没有会话。你的业务状态(用户数据、任务进度、缓存)该放数据库还得放数据库。真正变了的是:状态不再隐式地绑在某条连接上,而要显式地放进请求里或持久化到共享存储。

Trap 1: reading "stateless" as "my application no longer has state." It doesn't mean that. Statelessness applies to the protocol layer. Your business state (user data, job progress, caches) still belongs in a database. What actually changed is that state is no longer implicitly bound to a connection — it must be explicitly placed in the request or persisted to shared storage.

坑 2:太早关掉粘性路由。客户端和服务端的升级不同步是常态。老客户端还在用 2025-11-25 的有状态玩法时,你把网关的会话亲和性关了,它们会以各种奇怪的方式失败。正确顺序是:服务端先双版本兼容 → 观察流量里旧版本占比降到零 → 再关粘性。

Trap 2: dropping sticky routing too early. Client and server upgrades are never in lockstep. If old clients are still speaking the stateful 2025-11-25 revision when you disable session affinity, they fail in creative ways. The right order: make the server support both revisions → watch the old-revision share of traffic fall to zero → then drop stickiness.

坑 3:把 requestState 当成明文塞回去。前面说过,但值得再说一次:这是把服务端内部状态交给客户端保管。不签名 = 允许客户端伪造「我已经通过确认了」。

Trap 3: sending requestState back as plaintext. Said earlier, but worth repeating: this hands internal server state to the client. Unsigned means the client can forge "I already passed the confirmation."

坑 4:忽略了每请求开销的累加。每个请求都带协议版本、客户端信息、capabilities,单看很小,但在高 QPS 的 agent 场景下会累加成可观的带宽和序列化开销。如果你的 agent 在一个任务里要打几百次工具调用,这一项值得测一下。

Trap 4: ignoring the accumulated per-request overhead. Protocol version, client info, and capabilities on every request look tiny individually, but at agent-scale QPS they add up to real bandwidth and serialization cost. If your agent makes hundreds of tool calls per task, measure this.

来源:MCPJam 迁移指南Stacktree 变更清单Equixly 安全视角。坑 1/2/4 属于社区经验与工程推论,非官方明文规定。

Sources: MCPJam migration guide, Stacktree change list, Equixly security analysis. Traps 1/2/4 are community experience and engineering inference, not explicit spec text.

第 7 节SECTION 7对比与选型:我现在该不该迁?Comparison: should I migrate now?

你的情况建议理由 Your situationRecommendationWhy
本地 stdio MCP 服务(个人用、Claude Code 里挂的那种) 不急 无状态解决的是远程扩容问题,本地一对一场景没有这个痛点。等 SDK 升级时顺带跟上即可。 Local stdio MCP server (personal, wired into Claude Code) No rush Statelessness solves remote scaling; a local one-to-one setup doesn't have that pain. Follow along when your SDK bumps.
远程 MCP 服务,单实例,流量小 可以等 收益主要在多实例扩容。但建议现在就先把幂等做了,那是无论如何都该有的。 Remote MCP server, single instance, low traffic Can wait The payoff is in multi-instance scaling. But do the idempotency work now — you want it regardless.
远程 MCP 服务,已经在跑 Redis 会话存储 + 粘性路由 值得迁 直接省掉两个运维组件,这是最大受益群体。 Remote MCP server already running a Redis session store + sticky routing Worth migrating You delete two operational components outright. This is the group that benefits most.
想上 serverless / 函数计算 必须迁 有状态 MCP 和 serverless 是根本冲突的——无状态是前置条件。 Want to run on serverless / FaaS Must migrate Stateful MCP fundamentally conflicts with serverless. Statelessness is the precondition.
重度依赖 elicitation / 人工确认 先评估 SDK 迁移量集中在 MRTR 改造上;先确认你用的语言 SDK 支持到位了再动。 Heavily reliant on elicitation / human confirmation Check SDK support first The bulk of the work is the MRTR rewrite; confirm your language's SDK is ready before starting.

一个不分情况都成立的建议:无论你迁不迁,现在就给不可逆工具加幂等。有状态时代靠「连接不断就不会重发」这个假设活着的代码,本来就是脆的——网络抖动、客户端重试、用户手快点两下,都会破掉这个假设。无状态只是把这件事从「偶尔出事」变成了「一定出事」。

One recommendation that holds regardless: migrate or not, add idempotency to your irreversible tools now. Code that survived the stateful era by assuming "the connection won't break so nothing gets re-sent" was always fragile — network jitter, client retries, or a user double-clicking all break that assumption. Statelessness just moves this from "occasionally bites" to "definitely bites."

第 8 节SECTION 8术语表Glossary

MCP (Model Context Protocol)
模型上下文协议。让 AI 模型以统一方式调用外部工具与数据源的开放协议。版本号用发布日期表示,如 2026-07-28
An open protocol that lets AI models call external tools and data sources in a uniform way. Revisions are named by release date, e.g. 2026-07-28.
SEP (Specification Enhancement Proposal)
规范增强提案。MCP 的变更提案流程,类似 Python 的 PEP。本次无状态化由六个 SEP 协同完成。
MCP's change-proposal process, analogous to Python's PEP. Six SEPs together delivered this statelessness work.
MRTR (Multi Round-Trip Requests)
多轮往返请求,SEP-2322。让一次工具调用可以「中途要输入 → 客户端补齐 → 带状态重试」,从而无需长连接。
SEP-2322. Lets a single tool call go "needs input mid-way → client fills it in → retry with state attached," removing the need for a held-open stream.
requestState
MRTR 中服务端交给客户端、之后原样收回的不透明状态块。必须做完整性保护(签名/加密)。
The opaque state blob a server hands the client and takes back verbatim in MRTR. Must be integrity-protected (signed or encrypted).
CIMD (Client ID Metadata Documents)
客户端 ID 元数据文档。用「客户端自己托管的 URL」当 client_id,授权服务器按需解析,取代动态客户端注册(DCR)。
Uses a URL the client hosts itself as the client_id, resolved on demand by the authorization server. Replaces Dynamic Client Registration (DCR).
Streamable HTTP
MCP 的 HTTP 传输方式。本次要求它必须携带 Mcp-MethodMcp-Name 两个 header。
MCP's HTTP transport. This revision requires it to carry the Mcp-Method and Mcp-Name headers.
粘性会话(Sticky session)
Sticky session
负载均衡策略:把同一会话的所有请求固定路由到同一台后端实例。无状态之后不再需要。
A load-balancing policy that pins all requests in a session to one backend instance. No longer needed once the protocol is stateless.
幂等(Idempotency)
Idempotency
同一个操作执行一次和执行多次,结果一样。实现方式通常是给操作一个唯一键,服务端见过就不再执行。
Performing an operation once and performing it many times produce the same result. Usually implemented by giving each operation a unique key the server dedupes on.

第 9 节SECTION 9学习资源Resources

按「先读哪个」排序:

Ordered by what to read first:

  1. MCP 官方博客:The 2026-07-28 Specification —— 一手来源,先读这个。
  2. SEP-2322: Multi Round-Trip Requests —— MRTR 的正式定义。要实现服务端的必读。
  3. 2026 MCP Roadmap —— 了解接下来还要变什么,避免刚迁完又变。
  4. MCPJam:迁移指南 / Stacktree:每个破坏性变更 + 修法 —— 动手迁移时的对照表。
  5. Google Developers Blog / Microsoft(Azure App Service) —— 两家云厂商各自的规模化部署视角。
  6. Equixly:无状态 MCP 的安全影响 —— 专讲 requestState 与授权面的风险。
  7. C# SDK:MRTR 概念文档 —— 目前找到的最完整的 SDK 侧实现说明(其他语言请查各自仓库)。
  8. MCP 规范版本时间线 —— 想搞清楚各版本之间怎么演变的话看这个。
  1. MCP official blog: The 2026-07-28 Specification — the primary source. Read this first.
  2. SEP-2322: Multi Round-Trip Requests — the formal definition of MRTR. Required reading if you're implementing a server.
  3. The 2026 MCP Roadmap — what's changing next, so you don't finish a migration just in time for another one.
  4. MCPJam: migration guide / Stacktree: every breaking change, with fixes — the reference tables to keep open while migrating.
  5. Google Developers Blog / Microsoft (Azure App Service) — two cloud vendors' takes on scaling it.
  6. Equixly: security implications of stateless MCP — focused on requestState and the authorization surface.
  7. C# SDK: MRTR concepts — the most complete SDK-side write-up found (check other languages' own repos).
  8. MCP specification version timeline — for understanding how the revisions evolved.