MCP 一夜之间转向无状态:会话 ID 没了,我维护的三个 MCP 服务器连夜等着改
背景与热点吐槽
昨天(2026 年 7 月 28 日)MCP 官方博客甩出新版规范,我早上刷到的时候咖啡都差点洒了:初始化握手没了,会话 ID 没了,整个协议从双向有状态改成了无状态请求/响应。这是 MCP 诞生以来最大的一次架构变更,不是加个字段那种小修小补,是把地基换了。
作为一个手上维护着三个内部 MCP 服务器(一个查工单、一个连数据库、一个调 CI)的牛马,我的第一反应是:又要迁移?我上个季度刚把 SSE 传输改成 Streamable HTTP,屁股还没坐热。但冷静下来读完规范,我得说句公道话:这次变更是对的,而且早该来了。旧的有状态设计在生产环境里就是个刺儿头——会话粘滞(sticky session)、共享存储、扩容时的会话漂移,每一个都是运维的噩梦。官方给了一年弃用期,四大主流语言 SDK 已经同步更新,现在动手不算晚,但也别拖到明年 deadline 前一周。
技术原理拆解
这次变更的核心可以概括为三件事:
1. 无状态请求/响应模式
旧协议要求客户端先走 initialize 握手,服务器分配会话 ID,后续所有请求都绑定这个会话。这意味着 MCP 服务器天然是有状态的:要么内存里存会话(单机瓶颈),要么外置到 Redis(多一个依赖),负载均衡器还得配会话亲和。
新规范直接砍掉握手和会话 ID,每个请求自包含(self-contained)。服务器拿到请求就能处理,处理完就忘。这带来的直接红利是:MCP 服务器可以像普通无状态 HTTP 服务一样水平扩展,扔到任意负载均衡器后面都能跑,Kubernetes 上加副本就是改个 replicas 数字的事,不需要共享存储。
2. Multi Round-Trip Requests(多轮往返请求)
有人会问:没了会话,那些需要中途用户交互的场景怎么办?比如工具执行到一半需要用户确认(elicitation)。新规范的答案是 Multi Round-Trip Requests:服务器在响应中声明"我还需要更多输入",客户端携带完整上下文再次发起请求。状态不再存在服务器上,而是在请求/响应之间传递——这个思路和 OAuth 的 state 参数、HTTP 的 cookie-less 认证一脉相承,把状态管理的责任推给了持有上下文的一方(客户端)。
3. HTTP 标头承载方法与工具名
旧协议里,网关想知道一个请求要调哪个工具,必须解析 JSON-RPC body。新规范强制把方法名和工具名放进 HTTP 标头,网关层不拆包就能做路由、限流、鉴权。这对平台团队是重大利好:你可以在 API 网关上按工具粒度配置策略,比如"数据库写工具只允许内网调用",而不用在每个 MCP 服务器里重复实现。
另外授权规范也收紧了,动态客户端注册(DCR)被弃用。DCR 在实践中一直是个安全灰色地带——任意客户端可以自注册拿凭证,审计困难。收紧之后接入流程会麻烦一点,但可控性明显提升。
代码示例或实操演示
拿我那个查工单的 MCP 服务器举例,迁移前后的对比(示意代码,以官方 SDK 实际 API 为准):
旧版:有状态,握手 + 会话 ID
// 客户端必须先握手
const session = await client.initialize({ capabilities: {...} });
// 后续请求携带会话 ID
await client.callTool("query_ticket", { id: "T-1024" }, {
headers: { "Mcp-Session-Id": session.id }
});
新版:无状态,单次自包含请求
// 无需握手,直接调用,能力协商信息随请求携带
const result = await client.callTool("query_ticket", { id: "T-1024" });
// SDK 自动在标头中写入方法与工具名,网关可直接路由
网关层现在可以这样按工具做精细化路由(Nginx 示意):
# 按标头中的工具名分流,写操作走独立的高权限后端
map $http_mcp_tool_name $mcp_backend {
default mcp_readonly_pool;
"db_write" mcp_privileged_pool;
}
server {
location /mcp {
proxy_pass http://$mcp_backend;
}
}
服务端最爽的变化是:原来为了存会话引入的 Redis 直接下线了。无状态之后,我的部署清单少了一个组件、一组告警、一个夜里可能把我叫醒的东西。
牛马实践建议
结合我这两天的评估,给同样要迁移的兄弟们几条实在建议:
先盘点状态依赖,再动代码。 grep 一下你的 MCP 服务器里所有读写会话上下文的地方。如果只是缓存了客户端能力声明,直接删;如果存了业务中间态(比如多步工具调用的临时结果),要改造成 Multi Round-Trip 模式或外置到数据库,这部分才是迁移的真正工作量。
利用一年弃用期做双栈过渡。 SDK 新版本同时支持新旧传输,建议先升级 SDK 保持旧行为,验证无回归后再逐个客户端切到无状态模式。不要 big bang 式一刀切。
趁机把网关策略建起来。 标头路由是白送的架构红利。至少做两件事:按工具名配限流(防止 Agent 疯狂重试打爆下游)、按工具名分级鉴权(读写分离)。
认真读安全公告。 无状态不等于更安全,安全社区(如 backslash.security)已经指出新规范引入了新攻击面:Multi Round-Trip 的上下文由客户端携带,服务器必须校验回传上下文的完整性(签名或 MAC),否则就是把篡改的口子开给了客户端。别裸传状态。
DCR 弃用要提前和接入方打招呼。 如果你的平台之前靠动态客户端注册给内部团队发凭证,现在就要设计替代的准入流程,这种跨团队的事拖不得。
总结与展望
说实话,"又要学新东西"的怨气归怨气,但这次 MCP 的方向我是举双手赞成的。无状态化是分布式系统被验证了二十年的正确答案,MCP 早期为了交互体验选择有状态设计,如今在规模化部署的现实面前回归正统,是协议走向成熟的标志。
往后看,标头路由 + 无状态意味着 MCP 服务器会越来越像普通微服务,现有的服务网格、可观测性、灰度发布那一套基建可以直接复用——MCP 正在从"AI 圈的自定义协议"变成"云原生生态的一等公民"。对我们后端来说,这反而降低了长期维护成本。
一年弃用期看着长,过起来飞快。建议本季度完成评估,下季度完成迁移,别学我当年拖 Python 2 升级拖到官方停止支持。卷是卷了点,但这波卷得值。
FAQ
Q1:旧版有状态 MCP 服务器还能用多久?必须马上迁移吗?
官方给了一年弃用期(至 2027 年 7 月左右),旧传输方式在此期间仍可工作。不必恐慌性迁移,但建议本季度完成状态依赖盘点,利用 SDK 双栈支持做渐进式切换,避免拖到弃用期末端被动应对。
Q2:没有了会话 ID,需要多步交互的工具(如中途让用户确认)怎么实现?
使用新规范的 Multi Round-Trip Requests:服务器在响应中声明需要额外输入,客户端携带完整上下文重新发起请求。状态在请求/响应之间传递而非存在服务器上。注意服务器必须对回传上下文做完整性校验(如签名),防止客户端篡改。
Q3:无状态化之后,原来存在 Redis 里的会话数据怎么办?
分两类处理:客户端能力声明等协商信息现在随每个请求携带,可直接删除;真正的业务中间态(多步调用的临时结果)要么改造为 Multi Round-Trip 模式由客户端携带,要么下沉到业务数据库,不再作为协议层会话存在。
Q4:强制 HTTP 标头传递方法与工具名,对现有网关有什么实际好处?
网关无需解析 JSON-RPC 请求体即可获知调用目标,可以直接在 Nginx/API 网关层实现按工具粒度的路由、限流和分级鉴权,例如让写操作工具走独立的高权限后端池,读操作走普通池,安全策略从各服务器下沉到统一网关。
Q5:动态客户端注册(DCR)被弃用后,客户端接入怎么办?
需要改为预注册或平台化的准入流程:由管理方提前为客户端签发凭证,而非允许客户端运行时自注册。这增加了一点接入摩擦,但显著改善了凭证审计与可控性。建议尽早设计替代流程并通知所有接入方。