把 MCP server 的会话删掉:无状态规范迁移清单
我自建的 MCP server 之前把授权后的上下文存在进程内存里,用一个 Mcp-Session-Id 挂住。平时看不出问题,一到滚动发布就出事:请求被负载均衡甩到刚起来的实例上,会话没跟过来,客户端收到一个未知会话的报错,只能重开一轮。为了让这个设计站住,我得配 sticky session、给反向代理拉长超时,还要保住那条常开的 SSE 流。Cloudflare 把这段历史说得很直白:远程 MCP 只是把本地那套有状态连接原样搬到了 Web 基建上,于是守规矩的服务端要管会话粘性、流保持和消息重放。[2]
这周我把它拆了。2026-07-28 这版规范把 MCP 改成了无状态:initialize/initialized 握手和 Mcp-Session-Id 都退休,协议会话从核心请求路径里消失,每个请求自带协议版本、客户端身份和能力。[1][4] 结果很实际,任何请求都能落在普通轮询负载均衡后面的任意实例上,不需要共享存储。[1]
状态没有消失,只是换了位置#
规范的说法很具体:需要跨请求携带状态时,由工具签发一个显式 handle,让模型当普通参数传回来。[4] 官方博客给的理由比我想的更有说服力,模型能看见这个 handle,也能在工具之间传递它,这比藏在传输层里的会话状态更好用。[1] 我按这个思路把「当前项目」「上次构建号」这类东西全改成了返回值里的字段,服务端不再记任何一次调用的上下文。
配套变化有几个必须记住。server/discover 现在是必须实现的 RPC,用来公布支持的版本、能力和身份;客户端可以调也可以不调,但版本对不上时,服务端必须回 UnsupportedProtocolVersionError 并列出自己支持的版本。[4][8]
需要用户输入的场景不再能靠服务端主动拉取,得走多轮往返:服务端返回 resultType 为 input_required 的结果和它需要的输入,客户端收集到答案后带 inputResponses 重试原来那次调用。[3]
规范明说旧的 elicitation/create、sampling/createMessage、roots/list 这条服务端发起请求的老路不再支持,属于破坏性变更。[3]
另外 resultType 现在所有结果都要带,老服务端漏了这个字段时客户端按 complete 处理。[4][7]
快速上手:三个 curl 探针加一条双轨路#
改造前先证明现状。下面这条请求不带握手、不带会话头,直接调工具,参数结构照规范的 Streamable HTTP 示例写:
curl -sS https://mcp.example.com/mcp \
-H 'Content-Type: application/json' \
-H 'MCP-Protocol-Version: 2026-07-28' \
-H 'Mcp-Method: tools/call' \
-H 'Mcp-Name: get_weather' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_weather","arguments":{"location":"Shanghai"},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"probe","version":"1.0.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}'
能拿到结果只是第一步。还要确认三件事:响应里没有 Mcp-Session-Id;把同一条请求分别打到两个不同实例都成功;故意让 Mcp-Name 和 body 里的 name 不一致,服务端应当回 400 Bad Request 和 -32020 的 HeaderMismatch。[6] 这条最容易被跳过。头部校验没上线的服务端在规范眼里是不合规的,而网关和限流器正是靠 Mcp-Method、Mcp-Name 这两个头部工作,不必解析 JSON body。[6][2]
改造和上线之间要留一条双轨路。Cloudflare 的迁移指南按现状分了四种路径,最常见的两种是:不依赖会话的服务端直接搬到新工厂函数接 createMcpHandler;真有会话依赖的,先加一条无状态路由,旧的 createLegacyMcpHandler 只留在临时通道上,把依赖替换完再下线它。[5] 他们的端点在一个过渡期内同时接受新协议和 2025 版 Streamable HTTP 客户端,客户端基本不用改配置就能重连。[2]
Python 侧的两个坑值得提前知道。SDK v2 里 FastMCP 改名成 MCPServer,字段从 camelCase 换成 snake_case,pip install mcp 现在装的是 2.x,没迁完的项目先给依赖加 <2 上限。[9] 同步 def 工具现在跑在工作线程上,HTTP 客户端换成 httpx2 后用系统信任库校验 TLS,一个没有系统 CA 的精简容器可能突然握不上手。[9][10]
无状态之后,重试变便宜了#
这是我改完最大的感受。以前重试要担心会话还在不在,现在同一条请求打到哪个实例都行,客户端超时重发是安全的。代价是工具自己要能扛住重放:写操作要么幂等,要么用一个由客户端传入的幂等键。规范把这类标识符的用法讲得很清楚,跨请求的状态必须由客户端在每次请求里显式带上。[7] 顺带一个运维上的变化,SSE 流恢复和消息重放被删掉了,Last-Event-ID 和事件 ID 都不再存在。[4] 如果你的客户端原来依赖断线续传,得改成整体重发请求。
列表类响应也开始带缓存提示:tools/list、prompts/list、resources/list 和 resources/read 会返回 ttlMs 和 cacheScope,工具目录同时改成确定性顺序,客户端缓存和上游 prompt cache 都能稳住。[1][6] 把工具目录的顺序固定下来、别每次注册都变,上游 prompt cache 的命中率立刻正常了。
弃用清单要记在脑子里。Roots、Sampling、Logging、动态客户端注册(DCR)和旧的 HTTP+SSE 传输在这一版标记为弃用,规范承诺至少十二个月的下线窗口。[1][4]
ping、logging/setLevel、notifications/roots/list_changed 则是直接移除,日志级别改成每请求在 _meta 里用 io.modelcontextprotocol/logLevel 传。[4]
鉴权侧也收紧了,客户端必须校验 RFC 9207 的 iss 参数,DCR 让位给客户端 ID 元数据文档。[1][2]
验收清单#
按这六条过一遍,全绿再切流量:
- 不握手、不带会话头的一次工具调用能成功返回。
- 杀掉任意实例后,同一条请求打到另一个实例仍然成功,不需要 sticky session。
- 头部与 body 不一致时返回
400和-32020。 - 需要用户确认的工具第一次返回
resultType为input_required,带inputResponses重试后能走到complete。 tools/list带ttlMs和cacheScope,且两次调用的顺序完全一致。- 请求一个不支持的版本,返回
-32022,并在supported里列出可用版本。[4][8]
说句实话:这次迁移的收益不在功能上,而在发布流程上。以前每次动 MCP server 都得先想会话怎么办,现在它就只是一个普通的 HTTP 服务。
Sources#
[1] https://blog.modelcontextprotocol.io/posts/2026-07-28 — The 2026-07-28 Specification (MCP Blog) [2] https://blog.cloudflare.com/mcp-v2 — The next generation of MCP (Cloudflare Blog) [3] https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr — MCP spec 2026-07-28 — Multi Round-Trip Requests [4] https://modelcontextprotocol.io/specification/2026-07-28/changelog — MCP spec 2026-07-28 — Key Changes [5] https://developers.cloudflare.com/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2 — Cloudflare Agents — Migrate to MCP SDK v2 [6] https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http — MCP spec 2026-07-28 — Streamable HTTP transport [7] https://modelcontextprotocol.io/specification/2026-07-28/basic/index — MCP spec 2026-07-28 — Base protocol overview [8] https://modelcontextprotocol.io/specification/2026-07-28/basic/versioning — MCP spec 2026-07-28 — Versioning and compatibility [9] https://py.sdk.modelcontextprotocol.io/migration — MCP Python SDK — Migration Guide v1 to v2 [10] https://pydantic.dev/articles/mcp-python-sdk-v2-beta — Try the MCP Python SDK v2 beta today (Pydantic)