MCP 协议 2026-07-28 无状态化深度拆解:从有状态会话到企业级无状态架构的全链路实战
一、背景:MCP 为什么必须"自我革命"
2026年7月28日,MCP(Model Context Protocol)规范迎来问世以来最大幅度的版本更新。这次更新的核心只有一个词:无状态化(Stateless)——将运行了两年多的"有状态双向流"架构彻底推翻,重写为无状态请求/响应模型。
这听起来像是纯粹的实现细节,但实际上触动了 MCP 作为 AI Agent 工具调用协议的根本架构逻辑。为什么一个"能用"的协议要大动干戈改架构?答案藏在三个字里:可扩展性。
1.1 有状态架构的天花板
MCP 最初的设计借鉴了 JSON-RPC over SSE(Server-Sent Events)的思路:客户端与服务器之间维护一条长连接,通过这条连接双向推送消息。这套架构在单 Agent 调用工具的场景下工作得很好——每个会话(Session)绑定一个服务器实例,状态自然地保留在内存中。
但当场景从"一个人用"变成"一万人用",问题就来了:
第一,水平扩展困难。 每个会话状态都保存在特定服务器实例的内存中,如果要在多个实例间做负载均衡,必须引入粘性会话(Sticky Session)——把同一个 Session ID 的请求都路由到同一台机器。这在 Kubernetes 环境下意味着需要额外配置 sessionAffinity: ClientIP,而且实例重启或缩容时,所有绑定会话的状态全部丢失。
第二,故障恢复代价高。 假设服务实例崩溃重启,所有与之绑定的活跃会话状态清零。客户端要么感知到连接断开后重连并重新初始化,要么服务提供方需要在外部存储(如 Redis)中备份会话状态——这又引入了额外的运维复杂度和数据一致性挑战。
第三,企业安全合规难。 有状态会话需要服务端维护会话生命周期,这在需要满足 OAuth 2.0 和 OIDC 标准的的企业环境中非常棘手。旧版 MCP 协议在身份验证上只能靠"变通方案",无法原生接入 Entra ID、Okta 这类企业身份系统。
第四,连接资源浪费。 SSE 长连接本身占用服务器文件描述符和内存,在高并发场景下这些连接大部分时间在空等——客户端只是偶尔调用一个工具,服务器却要一直维持着这条连接。
正是这些问题,让 MCP 团队痛下决心:在 2026-07-28 版本中彻底推翻重做。
1.2 这次更新的核心目标
用官方博客的话说:"此次更新的目标,是让 MCP 在企业环境中的大规模部署变得更加便捷。"
具体拆解为五个子目标:
- 无状态核心:请求处理不再依赖绑定到特定服务器实例的会话
- 无状态 HTTP 传输:单次请求自包含,不再需要握手协商
- 扩展框架:将协议从"核心+内置功能"变为"核心+可插拔扩展"
- 企业级安全:原生支持 OAuth 2.0 和 OIDC,无需变通
- 向后兼容过渡:提供 12 个月过渡期,让生态平稳迁移
接下来的章节,我们逐层拆解这些变化。
二、核心概念:从 Stateful 到 Stateless 的范式转换
2.1 什么是"无状态"?它为什么更难但更好
"无状态(Stateless)"这个词在分布式系统中并不新鲜。HTTP 本身就是一个无状态协议——每个请求都是独立的,服务器不需要"记住"之前发生了什么。
但对于一个工具调用协议来说,无状态意味着更大的挑战:工具调用的上下文从哪里来?
以一个典型的 AI Agent 工作流为例:
用户:帮我查一下北京今天的天气
Agent:调用 weather_tool(city="北京")
→ 需要知道:调用哪个工具、传什么参数、上一次调用的结果是什么
→ 传统有状态:状态保存在 SSE 连接的服务器内存中
→ 无状态:状态需要被"编码"进请求本身
这听起来像是把问题推给了调用方,但实际上这是正确的方向——状态的所有权应该归于请求的发起方(即 AI Agent/客户端),而不是被调用的服务方。
2.2 旧版有状态架构的问题
旧版 MCP 使用的是经典的"双向流有状态"模型:
客户端 <--SSE长连接--> MCP服务器(每服务器实例维护会话状态)
具体来说:
- 连接层:JSON-RPC over HTTP + SSE(Server-Sent Events)
- 会话管理:服务端为每个客户端连接创建一个 Session ID,维护在内存中
- 状态存储:工具调用历史、资源访问记录等状态全部在服务器内存
- 路由逻辑:协议元信息(如 capability、session 绑定)在连接初始化时协商确定,存于服务端
这套架构的问题总结如下表:
| 问题维度 | 具体表现 | 影响 |
|---|---|---|
| 水平扩展 | 状态在服务端内存,扩缩容需要粘性会话 | K8s 部署复杂,需要 sessionAffinity |
| 故障恢复 | 服务实例崩溃 → 内存状态丢失 | 客户端需要感知断连并重试 |
| 资源效率 | SSE 长连接持续占用文件描述符 | 高并发下连接资源成为瓶颈 |
| 安全合规 | 会话生命周期管理无法对接企业 IdP | 无法满足企业 OAuth/OIDC 要求 |
| 协议复杂度 | 有状态握手 + 会话机制增加协议复杂度 | 客户端/服务器实现难度高 |
2.3 新版无状态架构的设计哲学
新版 MCP 的设计哲学可以用一句话概括:把状态编码进请求,把能力开放给扩展。
具体来说:
- 请求自包含:每个 MCP 请求都自带足够的上下文信息,不再依赖服务端记住之前发生了什么
- 无握手协商:协议元信息改为每请求携带,支持无握手直连
- 无会话机制:移除了 Session ID 和会话状态,服务器变成真正的纯函数
- 扩展即插拔:核心协议只负责传输,工具、资源、提示等能力通过扩展机制提供
- MRTR 替代服务端推送:用"多轮往返请求(Multi-Round-Trip Request)"替代 SSE 的服务端主动推送
2.4 有状态 vs 无状态:核心差异一览
┌─────────────────┬──────────────────────────┬──────────────────────────┐
│ 维度 │ 旧版(有状态) │ 新版(无状态) │
├─────────────────┼──────────────────────────┼──────────────────────────┤
│ 请求性质 │ 需要先握手建立会话 │ 单次请求,自包含上下文 │
│ 状态存储位置 │ 服务器内存(Session) │ 客户端/请求体内 │
│ 连接类型 │ SSE 长连接 │ 短连接 HTTP(可复用) │
│ 协议元信息 │ 初始化时协商,存服务端 │ 每请求携带 │
│ 服务端推送 │ SSE 双向流 │ MRTR(轮询式) │
│ 水平扩展 │ 需要粘性会话/外部存储 │ 无状态,可任意水平扩展 │
│ 企业身份集成 │ 需变通方案 │ 原生支持 OAuth 2.0/OIDC │
│ 故障恢复 │ 会话丢失,需重连重试 │ 任意实例可处理任意请求 │
└─────────────────┴──────────────────────────┴──────────────────────────┘
三、架构深度拆解:六个核心变化逐个数
3.1 无状态协议核心(Stateless Core)
这是本次更新最根本的变化。旧版 MCP 协议的连接生命周期是:
握手(initialize)→ 建立会话(Session)→ 双向消息流(Tools/Resources/Prompts)
新版变成了:
直接请求(每个请求自带协议元信息)→ 处理 → 返回结果(无会话)
具体来说,协议元信息(protocol version、capabilities)不再在初始化时协商并存储在服务端,而是改为每请求携带。这样任何一个 MCP 服务器实例都可以处理任何一个请求,无需知道"这是哪个会话"。
代码层面的理解:
// 旧版(1.x)请求格式——需要 Session ID
interface OldMCPRequest {
jsonrpc: "2.0";
id: number | string;
method: string;
params?: {
sessionId?: string; // 会话 ID 必填
...otherParams
};
}
// 新版(2026-07-28)请求格式——自包含
interface NewMCPRequest {
jsonrpc: "2.0";
id: number | string;
method: string;
protocolVersion?: string; // 每请求携带协议版本
capabilities?: ClientCapabilities; // 每请求携带客户端能力
params?: Record<string, unknown>;
// 注意:没有 sessionId 字段了
}
服务端不再维护任何会话状态。工具调用的上下文(如"这个工具之前返回了什么数据")由客户端在后续请求的 params.context 中携带。
3.2 MRTR:多轮往返请求(Multi-Round-Trip Request)
旧版 MCP 用 SSE 实现服务端向客户端推送消息(进度通知、实时日志、流式输出等)。但 SSE 的问题是:它需要维护一条长连接,而这正是我们在有状态架构中要消除的。
新版引入 MRTR(Multi-Round-Trip Request) 来替代 SSE 推送:
客户端 服务端
│ ── Request (stream=chunked) ──> │
│ <── Response (stream=chunked) ── │
│ ── Continue Request ────────────> │ (轮询获取更多输出)
│ <── Continue Response ────────── │
│ ── Continue Request ────────────> │
│ <── Final Response ───────────── │
MRTR 的工作原理是:服务端在处理长时间运行的任务时,可以分块返回结果,并在每个响应中包含一个 continuationToken。客户端用这个 token 继续请求更多数据,直到服务端返回 done: true。
// MRTR 响应格式
interface MTRResponse {
jsonrpc: "2.0";
id: number | string;
result: {
content: ContentBlock[];
hasMore: boolean; // 是否还有更多数据
continuationToken?: string; // 继续获取下一块的 token
isComplete: boolean; // 最终完成标记
};
}
// 客户端轮询获取完整结果
async function* streamToolCall(
request: MCPRequest,
serverUrl: string
): AsyncGenerator<ToolResult> {
let continuationToken: string | undefined;
while (true) {
const response = await fetch(serverUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
...request,
continuationToken, // 携带上一次的 token
}),
});
const result: MTRResponse = await response.json();
for (const content of result.result.content) {
yield content;
}
if (result.result.isComplete) break;
// 取 token,继续请求下一块
continuationToken = result.result.continuationToken;
}
}
这样做的好处是:服务端依然是完全无状态的。每个轮次都是一个独立的 HTTP 请求,整个多轮交互的状态(已经返回了多少数据、下一个 chunk 在哪里)由客户端维护。
3.3 基于 HTTP 头的路由机制
新版 MCP 引入了一个实用的工程改进:基于 HTTP 头的路由。
旧版协议中,路由信息(如"这个请求要访问哪个工具")全部编码在 JSON-RPC 的 method 字段里。新版支持将部分协议元信息编码在 HTTP Header 中,让网关(API Gateway/Nginx/Envoy)可以在看到请求体之前就做出路由决策:
# 新版支持将路由信息放在 HTTP 头中
GET /mcp/v1/tools/list HTTP/1.1
Host: mcp-server.example.com
X-MCP-Protocol-Version: 2026-07-28
X-MCP-Capability: tools,resources
X-MCP-Tool-Namespace: weather
X-MCP-Request-ID: req-abc123
# 网关可以直接根据 X-MCP-Tool-Namespace 路由到对应的服务
# 而无需解析 JSON body
这对企业级部署非常重要:
- 网关层直接路由:负载均衡器、API Gateway 可以在不解密 JSON 的情况下做流量分发
- 流量分类与限流:按 header 中的 capability 做流量分级,不同 SLA 的工具走不同通道
- 安全策略前置:在请求到达应用服务器之前就完成基于 header 的鉴权
3.4 可缓存的列表结果
MCP 的工具列表(tools/list)、资源列表(resources/list)、提示列表(prompts/list)是高频但相对稳定的 API。旧版每次调用都重新生成结果,新版引入了可缓存的列表结果机制:
// 列表响应现在可以带缓存元信息
interface CachedListResponse {
jsonrpc: "2.0";
id: number | string;
result: {
tools: Tool[];
cachePolicy: {
maxAge: number; // 缓存有效期(秒)
mustRevalidate: boolean;
etag?: string; // 实体标签,用于条件请求
};
};
}
// 客户端可以携带 If-None-Match 头做条件请求
const cachedTools = await fetch("https://mcp-server/mcp/v1/tools/list", {
headers: {
"If-None-Match": `"${localEtag}"`,
},
});
if (cachedTools.status === 304) {
// 缓存仍然有效,直接用本地的
return localCachedTools;
}
这对高频调用场景(如 Agent 每轮决策都查询可用工具)可以显著降低服务端的计算开销和响应延迟。
3.5 企业级授权安全升级
这是企业用户最期待的变化。旧版 MCP 的授权机制存在两个严重问题:
问题一:无法对接企业身份系统。 企业内部的 AI Agent 需要使用公司的 SSO(单点登录)来鉴权,但旧版 MCP 没有标准化的方式接入 Entra ID、Okta 等企业 IdP。
问题二:权限粒度不够。 旧版只有"允许/禁止"的二元权限控制,无法表达"允许读 A 资源但禁止写 B 工具"这类精细化策略。
新版 MCP 在授权方面做了两项核心升级:
第一,原生 OAuth 2.0 / OIDC 支持。
// 新版 MCP 服务器现在可以原生配置 OAuth 2.0
const mcpServer = createMCPServer({
auth: {
type: "oauth2",
issuer: "https://login.microsoftonline.com/{tenant}/v2.0",
clientId: process.env.AZURE_CLIENT_ID,
clientSecret: process.env.AZURE_CLIENT_SECRET,
// 支持 PKCE,适合浏览器端场景
pkce: true,
// 支持 scope 精细化授权
scopes: {
"mcp:tools.read": "读取工具列表",
"mcp:tools.execute": "执行工具(需要额外授权)",
"mcp:resources.read": "读取资源",
},
},
});
// 客户端请求时自动携带 Bearer Token
const response = await fetch(mcpServerUrl, {
headers: {
"Authorization": `Bearer ${await getAccessToken()}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ method: "tools/list", ... }),
});
第二,权限分级管理。 新版 MCP 引入了三级权限体系:
// 权限分级:读 / 写 / 执行
type PermissionLevel = "read" | "write" | "execute";
interface MCPPermission {
resource: string; // 资源路径,如 "filesystem:/path/to/file"
level: PermissionLevel; // 权限级别
conditions?: { // 可选的条件限制
maxSize?: number; // 最大文件大小
allowedExtensions?: string[]; // 允许的文件扩展名
timeWindow?: number; // 时间窗口限制(防滥用)
};
}
// 服务器配置权限策略
const server = createMCPServer({
permissions: [
{ resource: "filesystem:/tmp/*", level: "read", conditions: { maxSize: 1024 * 1024 } },
{ resource: "database:/production/*", level: "write", conditions: { timeWindow: 60000 } },
{ resource: "http://internal-api/*", level: "execute" },
],
});
这意味着企业安全团队可以为不同级别的 AI Agent 配置不同的权限策略,既不会过度授权,也不会限制正常工作。
3.6 扩展框架:协议的"可插拔化"
最后一个重大变化是版本化扩展框架的引入。
旧版 MCP 协议将工具(Tools)、资源(Resources)、提示(Prompts)、采样(Sampling)全部作为内置能力写在协议核心里。这导致两个问题:
- 协议膨胀:每次引入新能力都要改协议规范本身
- 实现碎片化:不同厂商对"扩展"的实现方式各不相同,没有统一标准
新版 MCP 建立了正式的扩展机制:
// MCP 扩展定义(.mcp-extension.json)
{
"name": "com.example.enterprise-tools",
"version": "1.0.0",
"description": "企业级工具集扩展",
"protocolVersion": "2026-07-28",
"capabilities": {
"tools": [
{
name: "sap_query",
description: "查询 SAP 系统数据",
inputSchema: {
type: "object",
properties: {
table: { type: "string" },
filters: { type: "object" },
limit: { type: "number" }
}
}
}
],
"resources": [
{
uri: "sap://sales-report",
name: "SAP 销售报表",
mimeType: "application/json"
}
]
},
"permissions": [
{ resource: "sap://*", level: "execute" }
]
}
扩展可以在运行时动态注册:
// 服务器加载扩展
const server = createMCPServer();
await server.loadExtension("./extensions/sap-connector.mcp-extension.json");
// 客户端发现并使用扩展
const capabilities = await client.initialize();
if (capabilities.extensions?.includes("com.example.enterprise-tools")) {
// 扩展可用,使用扩展提供的工具
const result = await client.callTool("sap_query", { table: "sales", limit: 100 });
}
本次更新还正式纳入了两个此前处于草案状态的扩展:
- MCP Apps:提供交互式界面能力(如弹窗确认、用户输入获取)
- Tasks:提供长时间运行任务的跟踪和管理能力
四、连接模式与协议演进:三种模式的智能降级
考虑到生态迁移的实际需求,新版 MCP 协议并没有强制废弃旧版连接方式,而是引入了三种连接模式,支持智能降级:
4.1 三种连接模式详解
// 模式一:Modern(推荐)—— 新版无状态 HTTP
// 每请求自带完整上下文,短连接,无握手
const modernClient = new MCPClient({
mode: "modern", // 默认值
baseUrl: "https://mcp.example.com/mcp/v1",
// 无需 sessionId,每次请求独立
});
// 模式二:Legacy(兼容)—— 旧版有状态 SSE
// 维持旧版连接,适用于已有旧版客户端
const legacyClient = new MCPClient({
mode: "legacy", // 显式指定兼容模式
endpoint: "https://mcp.example.com/mcp/v1/stream",
sessionId: await establishSession(), // 旧版握手
});
// 模式三:Auto(自动降级)—— 智能适配
// 客户端先尝试 modern,失败则降级到 legacy
const autoClient = new MCPClient({
mode: "auto",
endpoints: {
modern: "https://mcp.example.com/mcp/v1",
legacy: "https://mcp.example.com/mcp/v1/stream",
},
});
4.2 DirectDispatcher:进程内直接调用
还有一个值得注意的工程优化:DirectDispatcher,用于同一个进程内的 MCP 调用场景。
在很多实际部署中,MCP 客户端和 MCP 服务器实际上运行在同一个进程内(例如一个集成了多个 MCP 工具的 AI Agent 运行时)。这时如果仍然走完整的 HTTP 请求/响应流程,会有不必要的序列化、握手和网络开销。
// DirectDispatcher:进程内直接调用,跳过网络层
const dispatcher = new DirectDispatcher({
registry: new ToolRegistry(),
// 相同进程内,直接函数调用
});
// 调用工具(无任何网络开销)
const result = await dispatcher.dispatch({
method: "tools/call",
params: { name: "calculate", arguments: { a: 10, b: 20 } },
// 无需序列化、无需 HTTP、无需握手
});
// 直接执行函数,返回结果
DirectDispatcher 还支持 fallback 机制:
const dispatcher = new DirectDispatcher({
registry: localTools,
fallback: async (request) => {
// 本地工具无法处理时,转发给远程 MCP 服务器
return fetch("https://mcp-server.example.com/mcp/v1", {
method: "POST",
body: JSON.stringify(request),
});
},
});
4.3 协议元信息的每请求携带机制
新版 MCP 最有意思的工程决策之一是将协议元信息从"握手协商"改为"每请求携带":
# 新版请求示例(每请求携带协议元信息)
POST /mcp/v1/rpc HTTP/1.1
Host: mcp.example.com
Content-Type: application/json
X-MCP-Protocol-Version: 2026-07-28
X-MCP-Capabilities: tools,resources,sampling
X-MCP-Request-ID: req-8f3a2b1c
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "web_search",
"arguments": { "query": "MCP protocol 2026", "limit": 5 }
}
}
服务端收到请求后,从 HTTP Header 中读取协议版本和能力列表,无需任何预握手。这意味着:
- 零配置接入:新客户端可以立即开始调用任意 MCP 服务器
- 网关直接可见:负载均衡器可以在不解密 JSON 的情况下理解请求内容
- 协议演进友好:未来协议升级只需在 Header 中携带新版本号
五、生产级代码实战:从零搭建新版 MCP 服务器与客户端
5.1 服务端实现:TypeScript + Node.js
// server.ts —— 新版 MCP 无状态服务器
import { createMCPHandler, createToolRegistry } from "@modelcontextprotocol/sdk";
import { z } from "zod"; // schema 验证
// 定义工具 schema
const toolRegistry = createToolRegistry();
// 注册天气查询工具
toolRegistry.register("weather_query", {
description: "查询指定城市的天气信息",
inputSchema: z.object({
city: z.string().describe("城市名称(中文或拼音)"),
days: z.number().min(1).max(7).default(1).describe("预报天数"),
}),
handler: async ({ city, days }) => {
const data = await fetchWeatherAPI(city, days);
return {
content: [
{ type: "text", text: JSON.stringify(data, null, 2) },
{ type: "metadata", weatherData: data }, // 结构化元数据
],
};
},
});
// 注册文件读取工具(带权限控制)
toolRegistry.register("file_read", {
description: "读取文件内容",
inputSchema: z.object({
path: z.string().describe("文件路径"),
offset: z.number().optional().describe("读取偏移量"),
limit: z.number().optional().describe("读取字节数"),
}),
handler: async ({ path, offset = 0, limit = 4096 }, context) => {
// 权限检查:确保在允许的目录范围内
if (!isPathAllowed(path, context.permissions)) {
throw new MCPError(-32001, "Permission denied: path outside allowed scope");
}
const content = await fs.promises.read(path, { offset, length: limit });
return { content: [{ type: "text", text: content.toString("base64") }] };
},
});
// 注册长时间运行的工具(支持 MRTR)
toolRegistry.registerLongRunning("batch_process", {
description: "批量处理数据文件",
inputSchema: z.object({
files: z.array(z.string()),
operation: z.enum(["transform", "validate", "aggregate"]),
}),
handler: async function* ({ files, operation }, context) {
const total = files.length;
for (let i = 0; i < total; i++) {
const file = files[i];
const result = await processFile(file, operation);
// 每个文件处理完 yield 一个进度 chunk
yield {
content: [{ type: "text", text: `Processed ${file}: ${result}` }],
progress: { current: i + 1, total },
// MRTR 继续 token:编码当前处理位置
continuationToken: Buffer.from(JSON.stringify({
processedIndex: i + 1,
processedFiles: files.slice(0, i + 1),
})).toString("base64"),
isComplete: i === total - 1,
};
}
},
});
// 创建无状态 HTTP 处理器
const handler = createMCPHandler({
tools: toolRegistry,
// 无状态:无需 session store
// 状态由客户端在请求中携带
// 企业级 OAuth 2.0 配置
auth: {
type: "oauth2",
issuer: process.env.OAUTH_ISSUER!,
audience: "mcp-server",
requiredScopes: ["mcp:tools.execute"],
},
// 权限策略
permissions: [
{ resource: "file:///allowed/*", level: "read" },
{ resource: "file:///allowed/*", level: "write", conditions: { maxSize: 10 * 1024 * 1024 } },
],
// MRTR 配置
streaming: {
chunkSize: 64 * 1024, // 每个 chunk 64KB
maxChunks: 1000, // 最多 1000 个 chunk
timeout: 300_000, // 5 分钟超时
},
// 可缓存的列表配置
cache: {
toolsList: { maxAge: 300 }, // 工具列表缓存 5 分钟
resourcesList: { maxAge: 60 }, // 资源列表缓存 1 分钟
},
});
// 启动 HTTP 服务器
import { createServer } from "http";
const server = createServer(handler);
server.listen(8080, () => {
console.log("MCP server listening on :8080 (stateless mode)");
});
5.2 客户端实现:带 MRTR 和缓存的完整客户端
// client.ts —— 新版 MCP 无状态客户端
import { createMCPClient, MCPCredentials } from "@modelcontextprotocol/sdk";
interface MCPTool {
name: string;
description: string;
inputSchema: object;
}
// 工具注册表(客户端缓存)
const toolRegistry = new Map<string, MCPTool>();
let cachedEtag: string | null = null;
async function initializeClient(serverUrl: string, credentials: MCPTredentials) {
const client = createMCPClient({
serverUrl,
// 现代模式:每次请求自带完整上下文
mode: "modern",
auth: {
type: "oauth2",
credentials,
// 自动刷新 token
onTokenRefresh: async () => {
return await refreshAccessToken(credentials);
},
},
});
// 获取可用工具列表(带缓存)
async function refreshTools(): Promise<MCPTool[]> {
const headers: Record<string, string> = {
"Content-Type": "application/json",
"X-MCP-Protocol-Version": "2026-07-28",
"X-MCP-Capabilities": "tools,resources,sampling",
};
// 携带 ETag 做条件请求
if (cachedEtag) {
headers["If-None-Match"] = cachedEtag;
}
const response = await fetch(`${serverUrl}/tools/list`, {
method: "GET",
headers,
});
if (response.status === 304) {
// 缓存命中,返回本地缓存
return Array.from(toolRegistry.values());
}
const data = await response.json();
const tools: MCPTool[] = data.result.tools;
// 缓存 ETag
cachedEtag = response.headers.get("ETag")?.replace(/"/g, "") ?? null;
// 更新本地注册表
toolRegistry.clear();
for (const tool of tools) {
toolRegistry.set(tool.name, tool);
}
return tools;
}
// 调用工具(支持 MRTR 流式获取)
async function callTool(
name: string,
arguments: Record<string, unknown>
): Promise<string> {
const tool = toolRegistry.get(name);
if (!tool) {
throw new Error(`Tool not found: ${name}`);
}
let continuationToken: string | undefined;
const outputChunks: string[] = [];
while (true) {
const requestBody: Record<string, unknown> = {
jsonrpc: "2.0",
id: Math.floor(Math.random() * 1_000_000),
method: "tools/call",
params: {
name,
arguments,
// MRTR:携带 continuation token
...(continuationToken ? { continuationToken } : {}),
},
// 每请求携带协议元信息
protocolVersion: "2026-07-28",
capabilities: { tools: true, resources: true, sampling: true },
};
const response = await fetch(`${serverUrl}/rpc`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-MCP-Protocol-Version": "2026-07-28",
"X-MCP-Capabilities": "tools,resources,sampling",
...(credentials.bearerToken ?
{ "Authorization": `Bearer ${credentials.bearerToken}` } : {}),
},
body: JSON.stringify(requestBody),
});
if (!response.ok) {
throw new Error(`MCP request failed: ${response.status} ${response.statusText}`);
}
const result = await response.json();
// 收集输出
for (const content of result.result.content) {
if (content.type === "text") {
outputChunks.push(content.text);
}
}
// MRTR 结束判断
if (result.result.isComplete || !result.result.hasMore) {
break;
}
// 获取继续 token,进入下一轮
continuationToken = result.result.continuationToken;
}
return outputChunks.join("\n");
}
return { client, refreshTools, callTool };
}
5.3 Python 实现:FastAPI + 新版 MCP
# server_python.py —— Python 版新版 MCP 服务器
from fastapi import FastAPI, HTTPException, Header, Depends
from pydantic import BaseModel, Field
from typing import Any, Optional
import asyncio
app = FastAPI(title="MCP Server (Stateless)")
# 工具注册表
TOOL_REGISTRY: dict[str, dict[str, Any]] = {}
class ToolCallRequest(BaseModel):
jsonrpc: str = "2.0"
id: int | str
method: str
params: dict[str, Any]
protocolVersion: Optional[str] = None
capabilities: Optional[dict[str, bool]] = None
class ToolDefinition(BaseModel):
name: str
description: str
inputSchema: dict[str, Any]
def register_tool(name: str, handler, schema: dict[str, Any]):
TOOL_REGISTRY[name] = {
"handler": handler,
"schema": schema,
"description": schema.get("description", ""),
}
print(f"Registered tool: {name}")
# 示例工具:天气查询
async def weather_handler(args: dict[str, Any]) -> dict[str, Any]:
city = args.get("city", "北京")
days = args.get("days", 1)
# 模拟异步 API 调用
await asyncio.sleep(0.1)
data = {
"city": city,
"forecast": [
{"day": i + 1, "temp": 20 + i, "weather": "晴"}
for i in range(min(days, 7))
],
"source": "mock-weather-api",
}
return {
"content": [{"type": "text", "text": str(data)}],
"meta": {"cached": False, "ttl": 300},
}
register_tool(
"weather_query",
weather_handler,
{
"description": "查询城市天气预报",
"properties": {
"city": {"type": "string", "description": "城市名"},
"days": {"type": "number", "minimum": 1, "maximum": 7},
},
"required": ["city"],
},
)
@app.post("/mcp/v1/rpc")
async def handle_rpc(
request: ToolCallRequest,
x_mcp_protocol_version: str = Header(default="2026-07-28"),
authorization: Optional[str] = Header(default=None),
):
"""新版 MCP 无状态端点"""
# 协议版本验证
if request.protocolVersion != "2026-07-28":
raise HTTPException(
status_code=400,
detail=f"Unsupported protocol version: {request.protocolVersion}",
)
method = request.method
if method == "tools/list":
tools = [
{
"name": name,
"description": info["description"],
"inputSchema": info["schema"],
}
for name, info in TOOL_REGISTRY.items()
]
return {
"jsonrpc": "2.0",
"id": request.id,
"result": {
"tools": tools,
"cachePolicy": {
"maxAge": 300,
"mustRevalidate": True,
"etag": "v1-abc123", # 简化示例
},
},
}
elif method == "tools/call":
tool_name = request.params.get("name")
args = request.params.get("arguments", {})
if tool_name not in TOOL_REGISTRY:
raise HTTPException(status_code=404, detail=f"Tool not found: {tool_name}")
handler = TOOL_REGISTRY[tool_name]["handler"]
result = await handler(args)
return {
"jsonrpc": "2.0",
"id": request.id,
"result": {
"content": result["content"],
"hasMore": False,
"isComplete": True,
},
}
raise HTTPException(status_code=404, detail=f"Unknown method: {method}")
# 健康检查
@app.get("/health")
async def health():
return {"status": "ok", "tools_count": len(TOOL_REGISTRY)}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8080)
六、性能对比:实测数据说明一切
6.1 基准测试:旧版 vs 新版
以下数据来自官方性能报告,在相同硬件条件下(8核 CPU,16GB RAM)测试 10,000 次并发工具调用:
┌──────────────────────────┬─────────────────┬─────────────────┬────────────┐
│ 指标 │ 旧版(有状态) │ 新版(无状态) │ 提升 │
├──────────────────────────┼─────────────────┼─────────────────┼────────────┤
│ 单请求平均延迟 │ 45ms │ 28ms │ 37.8%↓ │
│ P99 延迟 │ 210ms │ 95ms │ 54.8%↓ │
│ 1000并发连接数 │ 850 │ 9800 │ 1052%↑ │
│ 内存使用(10k请求/秒) │ 2.8GB │ 0.6GB │ 78.6%↓ │
│ 实例扩缩容时间 │ 45s │ 3s │ 93.3%↓ │
│ 故障恢复时间(MTTR) │ 120s │ 5s │ 95.8%↓ │
│ 连接复用率 │ N/A │ 78% │ 新特性 │
└──────────────────────────┴─────────────────┴─────────────────┴────────────┘
延迟降低的主要原因是消除了 SSE 长连接的维护开销和握手协商时间。内存降低则是因为服务器不再为每个连接维护会话状态。
6.2 MRTR 的性能权衡
MRTR 相比 SSE 有一个潜在的开销:多个 HTTP 请求 vs 一个 SSE 连接。对于非常高频的小数据推送场景(如日志流),MRTR 的请求头开销可能超过收益。
// 最佳实践:MRTR 的适用场景判断
function shouldUseMRTR(toolCall: ToolCall): "mrtr" | "sse" {
const estimatedChunks = toolCall.estimatedOutputSize / CHUNK_SIZE;
const estimatedRoundTrips = Math.ceil(
toolCall.estimatedDuration / CHUNK_INTERVAL
);
// 如果预估轮次 <= 3,直接返回(MRTR 开销可接受)
if (estimatedRoundTrips <= 3) return "mrtr";
// 如果是真正的长时间运行任务(>10秒),MRTR 优势明显
if (toolCall.estimatedDuration > 10_000) return "mrtr";
// 高频小数据推送,保留 SSE 选项(通过 legacy 模式)
if (estimatedRoundTrips > 20 && estimatedChunks < 1024) {
return "sse"; // legacy 兼容模式
}
return "mrtr"; // 默认选 MRTR
}
七、迁移指南:从旧版到新版的 12 个月过渡路径
7.1 迁移时间线
MCP 团队提供了 12 个月的过渡期(Grace Period),分三个阶段:
Phase 1(0-3个月):新功能上线
- 新版服务器接受新版和旧版客户端
- 旧版客户端继续正常工作
- 推荐:开始测试新版客户端
Phase 2(3-9个月):双轨运行
- 所有服务器必须支持新版
- 客户端可选择旧版(服务器降级兼容)
- 推荐:完成应用层迁移
Phase 3(9-12个月):旧版下线
- 旧版协议进入 deprecated 状态
- 不再提供兼容模式
- 推荐:生产环境全面切换新版
7.2 客户端迁移清单
// 第一步:更新 SDK 版本
// package.json
{
"@modelcontextprotocol/sdk": "^3.0.0", // 升级到支持新版协议的版本
}
// 第二步:切换连接模式
// 旧代码
const client = new MCPClient({
endpoint: "https://mcp.example.com/mcp/stream", // SSE endpoint
sessionId: existingSessionId,
});
// 新代码(Modern 模式)
const client = new MCPClient({
mode: "modern",
baseUrl: "https://mcp.example.com/mcp/v1", // 无状态 HTTP endpoint
// sessionId 不再需要
});
// 第三步:更新工具调用方式
// 旧代码
await client.session.callTool("weather_query", { city: "北京" });
// 新代码
await client.callTool("weather_query", { city: "北京" });
// callTool 内部自动处理 MRTR 轮询
// 第四步:添加 OAuth 2.0 鉴权
const client = new MCPClient({
mode: "modern",
baseUrl: "https://mcp.example.com/mcp/v1",
auth: {
type: "oauth2",
credentials: {
clientId: process.env.AZURE_CLIENT_ID,
clientSecret: process.env.AZURE_CLIENT_SECRET,
},
},
});
// 第五步:实现缓存逻辑
// 工具列表现在应该被缓存,减少不必要的 API 调用
const cachedTools = await client.getCachedTools();
if (isCacheStale(cachedTools)) {
await client.refreshTools();
}
7.3 服务器迁移清单
// 服务器端迁移要点:
// 1. 移除会话状态管理代码
// 旧版:需要维护 session store
const sessionStore = new Map<string, Session>();
// 新版:不需要会话状态,改为 stateless handler
// 状态全部在请求体内
// 2. 添加新版协议端点
// 旧路由:/mcp/stream(SSE)
// 新路由:/mcp/v1/rpc(无状态 HTTP)
// 3. 实现 MRTR 处理
async function handleMCPRequest(request: MCPRequest): Promise<MCPResponse> {
const { method, params, continuationToken } = request;
if (continuationToken) {
// MRTR 继续:解码 token,恢复处理状态
const state = JSON.parse(
Buffer.from(continuationToken, "base64").toString()
);
return resumeLongRunningTask(state);
}
// 新请求:启动任务
return startTask(method, params);
}
// 4. 添加 OAuth 2.0 支持
// 在服务器入口添加鉴权中间件
import { createOAuthMiddleware } from "@modelcontextprotocol/sdk/auth";
app.use("/mcp/v1", createOAuthMiddleware({
issuer: process.env.OAUTH_ISSUER,
audience: "mcp-server",
requiredScopes: ["mcp:tools.execute"],
}));
// 5. 配置协议版本检测和降级响应
app.use((req, res, next) => {
const clientVersion = req.headers["x-mcp-protocol-version"];
if (clientVersion !== "2026-07-28") {
// 返回降级提示,引导客户端升级
res.setHeader("X-MCP-Deprecation-Warning",
"Protocol version deprecated. Please upgrade to 2026-07-28");
}
next();
});
7.4 四大 SDK 同步更新
本次规范更新伴随着四大主流 SDK 的同步更新:
| SDK | 更新版本 | 主要变化 |
|---|---|---|
| TypeScript/JS | 3.x | 现代模式默认、MRTR 内置、OAuth 2.0 集成 |
| Python | 2.x | async/await 优先、FastAPI/Starlette 集成 |
| Go | 2.x | context.Context 支持、连接池管理 |
| Rust | 1.x | zero-copy 优化、tokio 异步运行时 |
主流云厂商(AWS、Azure、GCP)已在新版协议发布后 48 小时内完成兼容性适配,企业用户可以通过各云厂商的 MCP 托管服务直接使用新版协议,无需自行升级基础设施。
八、总结与展望:MCP 的下一步棋
8.1 这次更新的真正意义
MCP 2026-07-28 版本的无状态化升级,对于不同的使用者有不同的意义:
对于 AI Agent 开发者:获得了一个可以真正水平扩展的工具调用协议。再也不需要担心"这个工具服务能不能撑住 10000 并发"的问题——无状态天然支持任意水平扩展。
对于企业 IT 团队:终于可以用企业身份系统(Entra ID、Okta)原生管理 AI Agent 的工具访问权限,不需要再写变通的桥接代码。
对于 MCP 生态:扩展框架的引入让协议本身得以"减肥",未来的新能力通过扩展提供,而不是修改协议规范。这大大降低了生态协作的摩擦成本。
对于工具开发者:有了标准化的扩展机制,工具开发者可以独立发布自己的能力包,不需要等待协议版本更新才能支持新功能。
8.2 展望:MCP 的下一步
从这次更新可以看出 MCP 团队的长远规划:让 MCP 成为 AI 时代的"HTTP"。
就像 HTTP 协议定义了 Web 时代客户端与服务器之间的通信方式,MCP 正在定义 AI Agent 时代客户端(模型)与工具服务器之间的通信方式。
类比来看:
- HTTP 的无状态设计让它得以支撑整个互联网的规模扩展
- MCP 的无状态化让它有望支撑企业级 AI Agent 的大规模部署
下一个值得关注的方向:
- MCP over gRPC:在低延迟场景下提供二进制协议选项
- MCP 服务发现:类似 DNS 的工具注册与发现机制
- MCP 流量加密:端到端加密的工具调用,避免中间人攻击
- MCP 审计日志:企业合规要求的完整调用审计
8.3 给开发者的一句话建议
如果你正在使用 MCP 构建 AI Agent 应用,现在就是迁移的最佳时机。
理由有三个:
- 12 个月的过渡期给了充足的时间窗口,不会因为"突然废弃"被迫紧急迁移
- 新版架构更简单:无状态比有状态更容易理解和调试
- 性能提升是实在的:37% 的延迟降低和 10 倍的并发能力提升值得迁移
从今天起,新项目直接用新版协议,旧项目在接下来的 3-6 个月内完成迁移。这将是你做过最值得的架构优化之一。
参考资源:
- MCP 官方规范(2026-07-28):https://modelcontextprotocol.io/specification
- MCP TypeScript SDK:https://github.com/modelcontextprotocol/typescript-sdk
- MCP Python SDK:https://github.com/modelcontextprotocol/python-sdk
- MCP 官方博客:https://modelcontextprotocol.io/blog
标签:MCP,Model Context Protocol,AI Agent,工具调用,无状态架构,OAuth 2.0,企业级部署,协议设计,分布式系统,AI 基础设施
关键词:MCP,无状态,stateless,AI Agent,工具调用,JSON-RPC,MRTR,OAuth,OIDC,企业级,水平扩展