MCP 2026-07-28 大版本迁移指南:Breaking Changes 全解析与代码适配
2026 年 7 月 28 日,Model Context Protocol 将迎来协议诞生以来最大规模的一次修订。这不是一次可以忽视的小版本升级——核心协议层从有状态转为无状态,Session ID 从传输层彻底移除,OAuth 授权链路加严,三个核心功能进入 12 个月弃用倒计时。本文将逐项拆解每一条 Breaking Change,配合可直接运行的代码示例,帮你完成从旧版到新版的全量适配。
一、为什么这次更新不能跳过?
先说结论:现有 Server 在 7 月 28 日之后仍可运行,但你会失去生态入场券。
根据官方生命周期策略(SEP-2596),任何功能从标记弃用到实际移除,至少保留 12 个月窗口。也就是说,你基于 2025-11-25 规范写的 Server 不会在 7 月 28 日突然挂掉。但现实是——Claude 连接器目录、MCP Directory、各主流客户端的兼容性矩阵,都会在新版发布后以 2026-07-28 作为准入标准。晚迁移一天,你的 Server 在用户发现链路里的曝光就少一天。
更关键的是,这次改动的性质不同于以往的功能叠加。它是协议层的结构性重构,涉及的改动相互耦合——无状态化影响 Session 管理、请求路由、长时任务、服务端推送,几乎贯穿了远程 Server 的每一个环节。跳过这次更新,下一次迁移时你会面对多版本的累积债务。
二、Breaking Changes 逐项解析与代码适配
2.1 Session ID 移除:你的状态去哪了?(SEP-2567)
影响范围:所有依赖 Mcp-Session-Id 的代码。
旧版协议中,客户端首次请求后,服务端返回一个 Mcp-Session-Id,此后所有请求必须携带该 Header。这意味着请求被"钉"在生成该 Session 的实例上——如果你想水平扩展,就需要 sticky routing 或共享 Session 存储。
新版彻底移除了这个机制。每个请求都是自包含的,可以落在任意实例上。
旧版 Server(2025-11-25):
import { McpServer } from "@modelcontextprotocol/sdk";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { randomUUID } from "node:crypto";
const server = new McpServer({
name: "my-doc-server",
version: "1.0.0",
});
// 旧版:transport 层生成并管理 session
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
});
// 旧版:状态挂在 session 上
const sessionStore = new Map<string, { document: unknown }>();
server.setRequestHandler(
CallToolRequestSchema,
async (request) => {
const sessionId = transport.sessionId; // 依赖 session ID
const doc = sessionStore.get(sessionId);
// ...处理逻辑
}
);
新版 Server(2026-07-28):
import { McpServer } from "@modelcontextprotocol/sdk";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
const server = new McpServer({
name: "my-doc-server",
version: "1.0.0",
});
// 新版:无需 session 生成器
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
// 新版:状态通过显式句柄传递,不依赖传输层
server.tool(
"create_document",
"Create a new document and return a handle",
{},
async () => {
const documentId = crypto.randomUUID();
// 将文档存到外部存储(数据库、Redis 等),而非内存 session
await storeDocument(documentId, { content: "", pages: [] });
return {
content: [
{
type: "text",
text: JSON.stringify({ document_id: documentId }),
},
],
};
}
);
server.tool(
"append_page",
"Append a page to an existing document",
{
document_id: z.string().describe("The document handle from create_document"),
page_content: z.string().describe("Content of the new page"),
},
async ({ document_id, page_content }) => {
// document_id 作为普通参数传入,不依赖任何 session
const doc = await loadDocument(document_id);
doc.pages.push(page_content);
await storeDocument(document_id, doc);
return {
content: [
{
type: "text",
text: `Page appended. Document now has ${doc.pages.length} pages.`,
},
],
};
}
);
实操要点: 运行 grep -rn "Mcp-Session-Id\|sessionIdGenerator\|sessionStore" src/ 找出所有读取 session 的代码,逐一改为显式参数传递。
2.2 initialize 握手移除:协议版本和能力怎么传递?(SEP-2575)
影响范围:所有在 initialize 阶段缓存能力的代码。
旧版协议要求客户端先发送 initialize 请求,双方交换协议版本和能力声明,此后才能调用工具。新版将这个信息移到了每个请求的 _meta 字段中——每次请求自带身份。
旧版请求流程:
// 第一步:握手
POST /mcp HTTP/1.1
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {},
"clientInfo": { "name": "my-app", "version": "1.0" }
}
}
// 第二步:才能调用工具,且必须携带 Session ID
POST /mcp HTTP/1.1
Mcp-Session-Id: 1868a90c-3a3f-4f5b
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": { "name": "search", "arguments": { "q": "otters" } }
}
新版请求(单次、自包含):
POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search
Content-Type: application/json
{
"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 替代握手时的能力交换:
// 新版:客户端需要了解服务端能力时,主动调用 server/discover
const discoverResponse = await client.request(
{ method: "server/discover" },
ServerDiscoverResultSchema
);
// 返回服务端支持的能力、扩展等信息
console.log(discoverResult.capabilities);
实操要点: 运行 grep -rn "onInitialize\|InitializeRequestSchema\|initialized" src/,将所有在握手阶段缓存的数据改为从请求 _meta 中按需读取。
2.3 新增必需路由头:别让网关吃掉你的请求(SEP-2243)
影响范围:所有经过 WAF、反向代理、CDN 的远程部署。
新版要求 Streamable HTTP 传输必须携带 Mcp-Method 和 Mcp-Name 两个 Header,用于让负载均衡器在不解析请求体的情况下完成路由。如果 Header 与 Body 中的 method/name 不一致,服务端会直接拒绝请求。
这个改动本身由 SDK 处理,但你必须确认基础设施不会过滤 Mcp-* 前缀的 Header。
# 检查清单:从客户端到服务端的完整链路
# 1. CDN(如 Cloudflare)—— 自定义 Header 是否放行
# 2. WAF —— 是否有 "未知 Header 拒绝" 规则
# 3. Nginx/HAProxy —— proxy_pass_config 是否透传
# 4. Ingress Controller —— 是否有 header 限制注解
Nginx 配置示例(确保透传):
location /mcp {
proxy_pass http://upstream_mcp;
# 必须透传 Mcp-* 前缀的 Header
proxy_pass_request_headers on;
# 如果使用了 header 过滤,显式放行
proxy_set_header Mcp-Method $http_mcp_method;
proxy_set_header Mcp-Name $http_mcp_name;
proxy_set_header MCP-Protocol-Version $http_mcp_protocol_version;
}
2.4 错误码 -32002 变为 -32602:一个容易忽略的测试陷阱(SEP-2164)
影响范围:所有硬编码 -32002 的服务端和客户端代码。
"资源未找到"的 JSON-RPC 错误码从 MCP 自定义的 -32002 改为标准 JSON-RPC 的 -32602(Invalid Params)。
// 旧版
throw new McpError(-32002, "Resource not found");
// 新版
throw new McpError(-32602, "Resource not found");
这个改动在测试套件中的"杀伤力"远大于生产代码。 如果你写了类似 expect(error.code).toBe(-32002) 的断言,测试会全部飘红。
# 一键排查
grep -rn "32002" src/ test/
2.5 多轮往返请求替代长连接 SSE:工具中的交互式流程怎么办?(SEP-2260, SEP-2322)
影响范围:所有通过 SSE 长连接向客户端推送消息的工具。
新版规定,服务端只能在处理客户端请求的过程中向客户端发请求,禁止通过持久 SSE 通道主动推送。需要用户输入的工具改为返回 InputRequiredResult,客户端收集答案后携带 requestState 重新发起请求。
新版多轮请求模式:
// 服务端:工具需要确认时返回 InputRequiredResult
server.tool(
"delete_files",
"Delete files matching a pattern",
{ pattern: z.string() },
async ({ pattern }) => {
const files = await glob(pattern);
// 不再通过 SSE 推送确认请求,而是返回 InputRequiredResult
return {
resultType: "inputRequired",
inputRequests: {
confirm: {
type: "elicitation",
message: `确定要删除这 ${files.length} 个文件吗?`,
schema: { type: "boolean" },
},
},
requestState: Buffer.from(
JSON.stringify({ step: "confirm", files })
).toString("base64"),
};
}
);
// 客户端收到后,收集用户输入并重新发起请求
// POST /mcp
// {
// "method": "tools/call",
// "params": {
// "name": "delete_files",
// "inputResponses": { "confirm": true },
// "requestState": "eyJzdGVwIjoiY29uZmlybSIsImZpbGVzIjpbXX0="
// }
// }
关键点: requestState 是 Base64 编码的服务端上下文,客户端原样回传。任何实例都可以接手处理,因为所有状态都在 payload 中。
三、MCP Apps:工具终于可以有 UI 了
这是本次更新最令人期待的新特性(SEP-1865)。以前工具只能返回文本或结构化数据,现在可以声明并返回交互式 HTML 界面,由 Host 在沙箱 iframe 中渲染。
MCP Apps 配置示例:
// 声明工具的 UI 模板
server.tool(
"visualize_data",
"Generate an interactive chart from data",
{
data: z.array(z.object({
label: z.string(),
value: z.number(),
})),
chart_type: z.enum(["bar", "line", "pie"]),
},
async ({ data, chart_type }) => {
return {
content: [
{
type: "text",
text: `Generated ${chart_type} chart with ${data.length} data points`,
},
{
type: "resource",
resource: {
uri: "mcp-app://visualize/render",
name: "Interactive Chart",
mimeType: "text/html",
text: generateChartHTML(data, chart_type),
},
},
],
// 声明该工具的 UI 模板(Host 可预取和缓存)
_meta: {
appTemplates: {
"mcp-app://visualize/render": {
templateUri: "https://your-server.com/templates/chart.html",
cacheScope: "server",
},
},
},
};
}
);
工作流程:
1. Server 声明工具可用的 UI 模板,Host 可预取、缓存并进行安全审查
2. 工具被调用时,返回带有 mcp-app:// URI 的资源
3. Host 在沙箱 iframe 中渲染该 UI
4. UI 通过 postMessage 与 Host 通信,所有操作走同一 JSON-RPC 审计链路
四、弃用清单:12 个月倒计时(SEP-2577)
以下三个功能在新版中标记为弃用,12 个月后(约 2027 年 7 月)可能被移除:
| 弃用功能 | 替代方案 | 排查命令 |
|---|---|---|
| Roots(客户端声明文件系统根目录) | 通过工具参数显式传递 | grep -rn "ListRootsRequest\|roots" src/ |
| Sampling(服务端请求客户端调用 LLM) | 服务端直接调用 LLM Provider API | grep -rn "createMessage\|sampling" src/ |
| Logging(MCP 协议级日志方法) | stdio 传输用 stderr;结构化可观测性用 OpenTelemetry |
grep -rn "sendLoggingMessage\|setLoggingLevel" src/ |
注意: 弃用不等于移除。现有代码在 7 月 28 日后仍然可以工作。但新项目不应再使用这些功能,已有项目建议尽早迁移。
五、完整迁移实操步骤
以下是推荐的三阶段迁移路径:
阶段一:处理硬性 Breaking Changes(7 月 28 日前)
# 步骤 1:排查 Session 依赖
grep -rn "Mcp-Session-Id\|sessionIdGenerator\|sessionStore" src/
# 步骤 2:排查 initialize 握手依赖
grep -rn "onInitialize\|InitializeRequestSchema\|initialized" src/
# 步骤 3:排查硬编码错误码
grep -rn "32002" src/ test/
# 步骤 4:升级 SDK 到支持 2026-07-28 的版本
npm install @modelcontextprotocol/sdk@latest
# 步骤 5:验证基础设施 Header 透传
curl -X POST https://your-server.com/mcp \
-H "Content-Type: application/json" \
-H "Mcp-Method: tools/call" \
-H "Mcp-Name: ping" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"ping","arguments":{}}}'
阶段二:适配新特性(7 月 28 日后逐步推进)
- 为静态工具列表添加
ttlMs和cacheScope,让客户端可以缓存 - 将需要确认的交互式流程改为
InputRequiredResult多轮模式 - 如使用实验性 Tasks API,迁移到 Tasks Extension
阶段三:清理弃用功能(12 个月内)
- 将 Sampling 调用替换为直接 LLM API 调用
- 将 Roots 替换为显式工具参数
- 将协议级 Logging 替换为 stderr 或 OpenTelemetry
六、常见问题 FAQ
Q1:我现有的 MCP Server 在 7 月 28 日之后还能用吗?
能用。 根据 SEP-2596 生命周期策略,已发布的功能在弃用后至少保留 12 个月。但主流客户端(Claude、Cursor 等)会优先协商 2026-07-28,旧版 Server 可能降级到兼容模式运行,部分新特性(如 MCP Apps、缓存控制)将不可用。
Q2:无状态化是否意味着我的应用也必须无状态?
不是。 协议层无状态不等于应用层无状态。你仍然可以维护状态——只不过状态需要通过工具返回的显式句柄(如 document_id、session_token)在请求间传递,而不是依赖传输层的 Mcp-Session-Id。这种"显式句柄"模式实际上比隐式 Session 更灵活,因为模型可以跨工具组合和传递这些句柄。
Q3:Tasks API 怎么办?我基于 2025-11-25 的实验性 Tasks 写了代码。
需要迁移。 Tasks 已从核心协议移入 Extension(SEP-2663),生命周期发生了变化:tools/call 返回任务句柄,通过 tasks/get、tasks/update、tasks/cancel 驱动,tasks/list 已被移除。这不是简单的搬移,而是重新设计。如果你还没开始用 Tasks,建议等 Extension 正式稳定后再接入。
Q4:我的 Server 跑在 Serverless 平台上(Vercel、Cloudflare Workers),迁移难度大吗?
反而更简单。 Serverless 本身就是"每次请求一个新实例"的模型,与无状态协议天然契合。你主要需要确认:(1) 没有在内存中跨请求保存状态;(2) 平台的网关/CDN 不拦截 Mcp-* Header。
Q5:MCP Apps 对现有工具的兼容性如何?
完全向后兼容。 MCP Apps 是纯增量特性。如果你不声明 appTemplates 和 mcp-app:// 资源,工具的行为与之前完全一致。你可以按需为特定工具添加 UI 能力,不需要一次性改造所有工具。
Q6:授权方面有什么需要注意的?
客户端现在会按照 RFC 9207 校验 iss 参数。服务端侧,你需要:(1) 发布准确的 RFC 9728 protected-resource metadata,确保 authServerUrls 稳定;(2) 保持 issuer 不变,因为 issuer 变更会强制所有客户端重新注册;(3) 校验 audience-bound token,而非接受授权服务器曾经签发的任何 token。
七、迁移检查清单速查
- [ ] 移除所有 Session 键控状态,改为显式句柄参数传递
- [ ] 移除
initialize/initialized握手逻辑,从请求_meta读取协议版本和客户端信息 - [ ] 全链路确认
Mcp-*Header 透传(CDN → WAF → Proxy → LB → Server) - [ ] 排查并替换硬编码的
-32002错误码 - [ ] 将长连接 SSE 推送流程改为
InputRequiredResult多轮往返模式 - [ ] 如使用实验性 Tasks API,制定迁移到 Extension 版本的计划
- [ ] 审计授权配置:
authServerUrls、issuer 稳定性、token audience 校验 - [ ] 排查 Roots、Sampling、Logging 的使用,制定替换时间表
- [ ] 在验证窗口内完成 SDK 升级,跑通完整测试套件
距离 7 月 28 日正式发布还有不到两周。RC 已于 5 月 21 日锁定,变更集不会再变——现在迁移,目标明确、节奏可控。等正式版发布后再动手,你就是被别人的截止日期推着走了。