协议升级深度实践:MCP无状态化迁移清单(网关、任务与鉴权)
调研日期:2026-07-30
本文目标:把 Tasks 扩展和鉴权变化,转成一份可以排期、灰度和验收的生产迁移清单。
一、先分清:哪些是规范事实,哪些是工程决策
| 层面 | 已核验事实 | 本文的工程建议 |
| 传输 |
| 网关按 |
| 状态 | 协议无状态不等于业务无状态;服务可返回显式句柄供后续工具调用携带 | 为 |
| 长任务 | Tasks 从实验性核心能力转为扩展,生命周期围绕 | 把任务状态放进受控存储;不要试图用网关内存模拟全局 |
| 鉴权 | 授权规范加强了 | 身份提供方、MCP Client、Gateway 三处共同做 issuer / audience / scope 校验 |
| 可观测性 | W3C Trace Context 在 | 外来 |
二、真正改变的是“状态放在哪里”
旧实现常见的隐含假设是:一个客户端先 initialize,随后每次工具调用都带上同一个 Mcp-Session-Id;反向代理必须做粘性会话,或让所有实例共用会话存储。
在无状态传输模型下,推荐把调用链拆成下面这样:
Host / Agent
│ MCP-Protocol-Version + 调用元数据 + 显式业务句柄
▼
Agent Gateway(身份、策略、限流、Trace)
│ Mcp-Method / Mcp-Name 路由
▼
任一 MCP Server 实例
│
├─ 立即结果
└─ { task_id / run_id / approval_id } ← 需要跨调用保留的状态
例如“导出一份报表”不应依赖某台 Server 内存里的 session,而应由 reports.create 返回一个受权限约束的 report_id;后续 reports.get 带回该 ID。这样横向扩容、重试、故障转移和审计回放都有明确边界。
不要把显式句柄等同于裸 ID。 一个可进入生产的句柄至少应能在服务端关联到:tenant、subject、tool、expires_at、trace_id 和状态版本。客户端只看到不可猜测的标识,服务端才负责真正的授权判断。
三、迁移前先做四张清单
3.1 协议与 SDK 清单
为每个 Host、MCP Client、Server 和 Gateway 记录:支持的协议版本、使用的传输方式、是否还依赖 Mcp-Session-Id、是否使用实验性 Tasks API。这里的目的不是收集版本号,而是找出谁会在切换日把新版请求误发给旧实现。
3.2 状态清单
逐个扫描工具:有没有把购物车、审批、浏览器上下文、分页游标或任务结果藏在进程内存或 session 里?为每类状态确定显式 handle、生命周期、所有者与回收策略。
3.3 网关清单
确认反向代理、WAF、限流器和审计采集器能透传并读取新版关键 Header:
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: reports.create
网关应校验 Header 与 JSON-RPC 请求体是否一致,不能只相信其中一侧;否则按工具名分流的策略可能被绕过或产生错误审计。
3.4 身份与权限清单
把旧的“一个 MCP Server 一个长期 API Key”列为迁移风险项。至少核验:授权响应的 iss、客户端登记的 application_type、凭据是否只绑定到对应 issuer、scope 升级是否需要重新批准,以及工具句柄是否会跨租户泄漏。
四、可运行示例:先在网关边缘拒绝 Header / Body 不一致
下面是一个 Node.js 20+ 的最小接入检查器。它不是 MCP Server,也不替代 OAuth 验证;用途是在流量回放或灰度环境中,先验证新版请求的协议版本、方法名、工具名和客户端元数据是否自洽。真实网关通过检查后还应把请求转发给后端服务。
// mcp-edge-check.mjs
import http from "node:http";
function reject(res, message) {
res.writeHead(400, { "content-type": "application/json" });
res.end(JSON.stringify({ error: message }));
}
async function readJson(req) {
let raw = "";
for await (const chunk of req) raw += chunk;
return JSON.parse(raw || "{}");
}
http.createServer(async (req, res) => {
if (req.method !== "POST" || req.url !== "/mcp") {
res.writeHead(404).end();
return;
}
let payload;
try {
payload = await readJson(req);
} catch {
reject(res, "invalid JSON");
return;
}
const version = req.headers["mcp-protocol-version"];
const headerMethod = req.headers["mcp-method"];
const headerName = req.headers["mcp-name"];
const bodyMethod = payload?.method;
const bodyName = payload?.params?.name;
const meta = payload?.params?._meta ?? {};
if (version !== "2026-07-28") return reject(res, "unexpected protocol version");
if (payload?.jsonrpc !== "2.0") return reject(res, "JSON-RPC 2.0 required");
if (!headerMethod || headerMethod !== bodyMethod) {
return reject(res, "Mcp-Method does not match JSON-RPC method");
}
if (bodyMethod === "tools/call" && (!headerName || headerName !== bodyName)) {
return reject(res, "Mcp-Name does not match params.name");
}
if (!meta["io.modelcontextprotocol/clientInfo"]) {
return reject(res, "clientInfo missing from _meta");
}
// 这里只记录关联信息;不要把外来 traceparent 当成认证信息。
console.log({
method: bodyMethod,
tool: bodyName ?? null,
traceparent: meta.traceparent ?? null,
});
res.writeHead(204).end();
}).listen(8181, () => console.log("edge check: http://127.0.0.1:8181/mcp"));
启动后用下面的请求验证一条合规的 tools/call:
node mcp-edge-check.mjs
curl -i http://127.0.0.1:8181/mcp \
-H "Content-Type: application/json" \
-H "MCP-Protocol-Version: 2026-07-28" \
-H "Mcp-Method: tools/call" \
-H "Mcp-Name: search" \
--data '{
"jsonrpc":"2.0",
"id":1,
"method":"tools/call",
"params":{
"name":"search",
"arguments":{"q":"MCP migration"},
"_meta":{
"io.modelcontextprotocol/clientInfo":{"name":"migration-test","version":"1.0"},
"traceparent":"00-0123456789abcdef0123456789abcdef-0123456789abcdef-01"
}
}
}'
把 Mcp-Name 改成别的值,应收到 400。这正是灰度期值得自动化的检查:路由依据和审计依据必须指向同一个工具调用。
五、按这个顺序上线,回滚才有抓手
- 先双栈观测,再切流量。 Server / Gateway 先接受旧版与
2026-07-28,记录每个版本的成功率、429/4xx、工具耗时和会话依赖。 - 把隐式 session 状态改成显式资源。 为每个 handle 增加归属与过期校验,并针对重试设计幂等键。
- 迁移 Tasks。 不再假设可以全局枚举
tasks/list;由业务侧保存可见 task 关联,再用tasks/get、tasks/update、tasks/cancel驱动生命周期。 - 把授权单独做回归。 分别测试 issuer 不匹配、缺少
iss、错误 redirect、scope step-up、过期 refresh token 和跨租户 handle;不要只测“能登录”。 - 开启路径级策略。
Mcp-Method/Mcp-Name可用于路由和限流,但 L3/L4 工具仍要回到上一篇的 Gateway 策略和人工审批。 - 完成可回滚验收。 一旦新版错误率、授权拒绝率或任务中断率异常,按 Host / Client 开关退回旧协议路径;不要在回滚时删除已生成的业务 handle。
六、三个最容易翻车的点
1)“无状态”后仍偷偷依赖本机内存
单实例压测会掩盖问题;一加第二台实例,task_id、审批上下文或分页游标就丢。对所有跨调用数据使用显式 ID 和服务端受控存储,才是真正的无状态传输。
2)把 Trace 当作身份
traceparent 的价值是串起调用链,而不是证明谁有权限。安全决策仍须来自已验证的工作负载身份、租户和 scope;Trace 最多作为关联字段。
3)只改 Server,不测 Client 与反向代理
新版 Header 被 CDN 丢弃、WAF 不认识 Mcp-Method、旧 Client 仍发送 Session ID,都会让“SDK 单测通过”变成线上失败。验收必须覆盖 Host → Gateway → Server → 下游工具整链。
结语
其最大价值不是让 MCP 少一次握手,而是让协议层不再替业务藏状态。对 Agent 系统而言,这为普通负载均衡、精细网关策略、可关联追踪和可控长任务打下了更干净的边界。
如果你已经完成了 Agent Gateway 的身份、权限和审计设计,下一步不是立刻全量升级,而是拿一个低风险 MCP Server 跑完本文四张清单、边缘一致性检查和双栈回滚演练。把“能调用”升级成“可扩、可审、可退”,协议升级才真正产生生产价值。
更多推荐

所有评论(0)