编程 MCP 协议 2026-07-28 无状态化深度拆解:从有状态会话到企业级无状态架构的全链路实战

2026-08-17 22:21:18 +0800 CST views 30

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 在企业环境中的大规模部署变得更加便捷。"

具体拆解为五个子目标:

  1. 无状态核心:请求处理不再依赖绑定到特定服务器实例的会话
  2. 无状态 HTTP 传输:单次请求自包含,不再需要握手协商
  3. 扩展框架:将协议从"核心+内置功能"变为"核心+可插拔扩展"
  4. 企业级安全:原生支持 OAuth 2.0 和 OIDC,无需变通
  5. 向后兼容过渡:提供 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 的设计哲学可以用一句话概括:把状态编码进请求,把能力开放给扩展

具体来说:

  1. 请求自包含:每个 MCP 请求都自带足够的上下文信息,不再依赖服务端记住之前发生了什么
  2. 无握手协商:协议元信息改为每请求携带,支持无握手直连
  3. 无会话机制:移除了 Session ID 和会话状态,服务器变成真正的纯函数
  4. 扩展即插拔:核心协议只负责传输,工具、资源、提示等能力通过扩展机制提供
  5. 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)全部作为内置能力写在协议核心里。这导致两个问题:

  1. 协议膨胀:每次引入新能力都要改协议规范本身
  2. 实现碎片化:不同厂商对"扩展"的实现方式各不相同,没有统一标准

新版 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/JS3.x现代模式默认、MRTR 内置、OAuth 2.0 集成
Python2.xasync/await 优先、FastAPI/Starlette 集成
Go2.xcontext.Context 支持、连接池管理
Rust1.xzero-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 的大规模部署

下一个值得关注的方向:

  1. MCP over gRPC:在低延迟场景下提供二进制协议选项
  2. MCP 服务发现:类似 DNS 的工具注册与发现机制
  3. MCP 流量加密:端到端加密的工具调用,避免中间人攻击
  4. MCP 审计日志:企业合规要求的完整调用审计

8.3 给开发者的一句话建议

如果你正在使用 MCP 构建 AI Agent 应用,现在就是迁移的最佳时机

理由有三个:

  1. 12 个月的过渡期给了充足的时间窗口,不会因为"突然废弃"被迫紧急迁移
  2. 新版架构更简单:无状态比有状态更容易理解和调试
  3. 性能提升是实在的: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,企业级,水平扩展

推荐文章

微信小程序开发资源汇总
2026-05-11 16:11:29 +0800 CST
nginx反向代理
2024-11-18 20:44:14 +0800 CST
设置mysql支持emoji表情
2024-11-17 04:59:45 +0800 CST
程序员茄子在线接单