跳转到内容
本站为社区维护的非官方中文翻译,与 Cloudflare, Inc. 无关联。本站不提供账户登录,请勿在任何非官方站点输入您的 Cloudflare 账户信息。官方文档:developers.cloudflare.com

迁移到 MCP SDK v2

最后更新 查看 MarkdownAgent 设置

本指南介绍 Agents SDK v0.20.0 中的 MCP SDK v2 ↗ 升级。说明如何将服务器迁移到 @modelcontextprotocol/server,仅在需要有会话特性时使用临时旧版通道,以及如何更新 MCP 客户端。

选择服务器路径

使用下表选择迁移路径:

当前服务器 迁移路径
无会话依赖的 SDK v1 服务器 将服务器定义移到 SDK v2 工厂函数,并把工厂函数传给 createMcpHandler。
依赖有会话特性的 SDK v1 服务器 添加 SDK v2 路由。仅在替换这些依赖期间,在临时旧版通道上保留 createLegacyMcpHandler。
不使用旧版有状态特性的 McpAgent 直接迁移到 SDK v2 工厂函数与 createMcpHandler。
使用旧版有状态特性的 McpAgent 设计无状态等价方案,同时提供无状态与旧版通道,然后排空旧版通道。

Agents SDK v0.20.0 弃用以下 API:

  • 将 SDK v1 服务器传给 createMcpHandler。请将服务器移到 SDK v2 工厂函数。仅将 createLegacyMcpHandler 作为有会话行为的临时桥接。该重载计划在下一个主版本中移除。
  • McpAgent。已弃用且功能冻结。请尽早迁移。尚未宣布移除版本。
  • MCPClientManager.callTool(params, resultSchema, options) 与 withX402Client(...).callTool(confirm, params, resultSchema, options)。请改用 callTool(params, options) 或 callTool(confirm, params, options)。尚未宣布移除版本。

experimental_createMcpHandler 此前已弃用,仍计划在下一个主版本中移除。请将其 SDK v1 服务器移到 SDK v2 工厂函数。仅在迁移期间于临时有会话通道上使用 createLegacyMcpHandler。

安装 MCP 包

仅安装应用实际导入的 MCP 包世代。保持 v2 版本精确固定。

无状态服务器:

npm i agents @modelcontextprotocol/[email protected] zod

临时旧版通道:

npm i agents @modelcontextprotocol/[email protected] zod

连接 MCP 服务器的 Agent:

npm i agents @modelcontextprotocol/[email protected]

@modelcontextprotocol/server 和 @modelcontextprotocol/sdk 是 agents 的可选对等依赖,因此包管理器不会自动安装它们。使用时请将它们添加到自己的 package.json。@modelcontextprotocol/client 是必需的对等依赖,因为每个 Agent 都包含 MCP 客户端。请随支持这些版本的 Agents 发行版更新 MCP 的具体版本。

如果现有旧版服务器导入了 @modelcontextprotocol/sdk,却没有在 package.json 中声明它,那么它此前只是因为 agents 将该包作为依赖安装才得以运行。升级前请显式添加此依赖。

即使只从 agents/mcp 导入客户端辅助函数,该入口也会加载 @modelcontextprotocol/sdk。请改为从 agents/mcp/client 导入 RPCClientTransport、RPC_DO_PREFIX 和 McpClientOptions。agents/x402 也需要安装 @modelcontextprotocol/sdk:它在运行时不会加载该包,但其类型引用 MCP SDK v1。

判断是否需要临时旧版通道

不要仅因为服务器当前导入 SDK v1 就继续保留它。除非端点依赖以下有会话特性之一,否则请直接迁移到 SDK v2 工厂函数:

  • 协议会话或传入的 WorkerTransport
  • 传输存储或事件重放
  • 独立 GET 流
  • 推送的 elicitation、sampling 或 roots 请求
  • 使用 HTTP DELETE 的会话删除

若端点使用上述任一特性,请先部署无状态路由。仅在替换有会话依赖期间保留 SDK v1 路由。使用 isLegacyRequest() 路由请求,如 同时运行无状态与旧版通道 所示。

对于不使用 McpAgent 的 SDK v1 端点,仅在该临时旧版分支上使用 createLegacyMcpHandler。客户端完成迁移且现有会话排空后将其移除。

将无状态服务器迁移到 SDK v2

无状态 createMcpHandler 接受工厂函数。工厂函数返回来自 @modelcontextprotocol/server 的 McpServer 或 Server。

  1. 按照上游 TypeScript SDK v2 迁移指南 ↗ 了解服务器注册变更。

  2. 从 @modelcontextprotocol/server 导入服务器。

  3. 将服务器构造与注册移入工厂函数。

  4. 保留 Worker 对象的 default export。在其 fetch() 方法中,将工厂函数传给 createMcpHandler,并像以前一样调用返回的可调用对象。仅在较低层请求集成时使用处理程序的 fetch(request, options?) 方法。

  5. 不要将 Agents 处理程序返回的可调用对象作为 default export。Wrangler 会把函数形式的 default export 解释为 WorkerEntrypoint 类。

  6. 移除 SDK v1 的传输与会话选项。

  7. 用无状态客户端与旧版客户端测试该端点。

src/index.jsjs
import { McpServer } from "@modelcontextprotocol/server";
import { createMcpHandler } from "agents/mcp/server";
import { z } from "zod";

function createServer() {
	const server = new McpServer({
		name: "example-server",
		version: "1.0.0",
	});

	server.registerTool(
		"hello",
		{
			description: "Return a greeting",
			inputSchema: { name: z.string().optional() },
		},
		async ({ name }) => ({
			content: [{ type: "text", text: `Hello, ${name ?? "World"}!` }],
		}),
	);

	return server;
}

export default {
	fetch(request, env, ctx) {
		return createMcpHandler(createServer)(request, env, ctx);
	},
};
src/index.tsts
import { McpServer } from "@modelcontextprotocol/server";
import { createMcpHandler } from "agents/mcp/server";
import { z } from "zod";

function createServer() {
    const server = new McpServer({
        name: "example-server",
        version: "1.0.0",
    });

    server.registerTool(
        "hello",
        {
            description: "Return a greeting",
            inputSchema: { name: z.string().optional() },
        },
        async ({ name }) => ({
            content: [{ type: "text", text: `Hello, ${name ?? "World"}!` }],
        }),
    );

    return server;
}

export default {
    fetch(request, env, ctx) {
        return createMcpHandler(createServer)(request, env, ctx);
    },
} satisfies ExportedHandler;

Worker 入口点仍是对象。仅处理程序调用方式发生变化:

// SDK v1: pass a fresh constructed server.
return createMcpHandler(createServer())(request, env, ctx);

// SDK v2: pass the factory itself.
return createMcpHandler(createServer)(request, env, ctx);

不要简化为 export default createMcpHandler(createServer)。Agents 处理程序可在另一个处理程序内组合调用,但 Wrangler 会把任何函数 default export 当作 WorkerEntrypoint 类。

SDK v2 处理程序为每个 MCP 请求创建一台服务器。并发 Worker 请求不会共享已连接的服务器实例。

无状态服务器的处理程序选项

Agents 包装器添加 route、corsOptions、allowedHostnames、allowedOriginHostnames 与 authContext。它还会将受支持的 SDK v2 选项透传给上游处理程序。

常见选项包括:

选项 行为
route 设置精确请求路径。默认为 /mcp。
legacy 默认使用旧版兼容。无状态专用端点请设为 "reject"。
responseMode 选择自动、JSON 或 SSE 响应处理。
allowedHostnames 将 Host 头限制为特定主机名。
allowedOriginHostnames 限制浏览器 Origin,或在受信任中间件已校验时接受 "*"。
corsOptions 控制 CORS 响应头。设为 false 可移除它们。
onerror 报告处理程序错误而不改变响应。
maxSubscriptions, keepAliveMs 配置 subscriptions/listen 投递。

无状态处理程序拒绝以下 SDK v1 选项:

  • transport
  • storage
  • sessionIdGenerator
  • onsessioninitialized 与 onsessionclosed
  • enableJsonResponse
  • eventStore
  • allowedHosts 与 allowedOrigins
  • enableDnsRebindingProtection
  • retryInterval

请用 responseMode: "json" 替代 enableJsonResponse: true。JSON 模式会丢弃最终结果之前发出的通知。

Workers 上的 Origin 校验

Workers 包装器会校验每一个存在的 Origin。格式错误、不透明以及非 HTTP Origin 会以 403 拒绝。

默认允许列表包含 localhost 类 Origin 以及端点的 workers.dev 主机名。具体的 corsOptions.origin 也会自动加入其主机名。处理程序会对 localhost 与 workers.dev 端点应用匹配的 Host 检查。

对于使用通配 CORS 的自定义域,请显式配置 Host 与 Origin 限制:

export default {
	fetch(request, env, ctx) {
		return createMcpHandler(createServer, {
			allowedHostnames: ["mcp.example.com"],
			allowedOriginHostnames: ["app.example.com"],
			corsOptions: { origin: "https://app.example.com" },
		})(request, env, ctx);
	},
};
export default {
    fetch(request, env, ctx) {
        return createMcpHandler(createServer, {
            allowedHostnames: ["mcp.example.com"],
            allowedOriginHostnames: ["app.example.com"],
            corsOptions: { origin: "https://app.example.com" },
        })(request, env, ctx);
    },
} satisfies ExportedHandler;

仅在受信任中间件于调用处理程序之前校验 Origin 时,才设置 allowedOriginHostnames: "*"。该值会关闭处理程序的 Origin 检查。MCP HTTP 服务器必须校验浏览器 Origin。

CORS 头并不认证请求。请用 OAuth 或其他身份验证层保护端点。

处理程序不会从 request.url 推断受信任的 Host 允许列表。若部署接受任意 Host 值,请在调用处理程序之前校验它们。对于 Cloudflare Workers 之外的本地服务器,请遵循上游 SDK 的 Host 与 Origin 校验指引。

理解与旧版客户端的兼容性

默认的 legacy: "stateless" 设置支持普通旧版工具、资源与提示词。该通道使用 SDK v2 的 web-standard 传输。它不导入 WorkerTransport,也不是完整的有会话传输。

回退有以下限制:

  • 每个 POST 都会获得新的服务器与传输。
  • HTTP GET 与 DELETE 返回 405。
  • 不持久化 MCP 会话 ID 或协议会话状态。
  • 推送的 sampling、elicitation 与 roots 请求会立即失败。
  • 独立流、事件重放与会话删除不可用。
  • 已发布的实验性 tasks 不通过此回退支持。

迁移这些特性期间,将受影响的旧版客户端路由到临时的 createLegacyMcpHandler 或 McpAgent 通道。

迁移 McpAgent 服务器

迁移期间,McpAgent 仍是 SDK v1 服务器。不要将旧版路由内的服务器导入改为 @modelcontextprotocol/server。

无旧版有状态特性时直接迁移

若服务器不依赖 MCP 会话状态、RPC、推送的服务器到客户端请求、独立流或事件重放,请将其工具移到 SDK v2 工厂函数,并用 createMcpHandler 提供服务。

为有状态特性规划无状态等价方案

若服务器使用旧版有状态特性,请在设计并部署无状态等价方案期间保留现有 McpAgent 路由:

旧版路径上的有状态特性 无状态路径上的设计
以 MCP 会话为键的应用数据 将数据存储在显式应用边界之后,例如 Durable Object、D1、KV 或 R2。使用经过身份验证、由服务器签发的句柄寻址,而不是 MCP 会话 ID。
多步交互状态 随 input_required 返回完整性受保护的 requestState。将其绑定到已认证用户、原始方法与参数,以及过期时间。
推送的 elicitation、sampling 或 roots 请求 返回 inputRequired(...)。客户端完成嵌入式请求后重试原始操作。
独立的列表变更流 通过 subscriptions/listen 发布变更。流结束后客户端重新打开订阅。
会话重放或传输恢复 使每个无状态请求可独立恢复。将业务进度持久化到应用存储,而不是 MCP 传输。
Agent 到 McpAgent 的 RPC 通过 Streamable HTTP 使用 URL 将 Agent 连接到 createMcpHandler 服务器。无状态服务器没有 RPC 传输方式。

无状态实现就绪后不要立即移除旧版路由。在客户端迁移与现有会话排空期间同时提供两条通道。

同时运行无状态与旧版通道

单个 URL 可将无状态请求路由到 SDK v2,将旧版请求路由到现有有会话服务器。

import { isLegacyRequest } from "@modelcontextprotocol/server";
import { createMcpHandler } from "agents/mcp/server";

const stateless = createMcpHandler(createStatelessServer, {
	route: "/mcp",
	legacy: "reject",
});

const legacy = MyMcpAgent.serve("/mcp");

export default {
	async fetch(request, env, ctx) {
		if (await isLegacyRequest(request)) {
			return legacy.fetch(request, env, ctx);
		}
		return stateless(request, env, ctx);
	},
};
import { isLegacyRequest } from "@modelcontextprotocol/server";
import { createMcpHandler } from "agents/mcp/server";

const stateless = createMcpHandler(createStatelessServer, {
    route: "/mcp",
    legacy: "reject",
});

const legacy = MyMcpAgent.serve("/mcp");

export default {
    async fetch(request: Request, env: Env, ctx: ExecutionContext) {
        if (await isLegacyRequest(request)) {
            return legacy.fetch(request, env, ctx);
        }
        return stateless(request, env, ctx);
    },
} satisfies ExportedHandler<Env>;

在无状态处理程序上保持 legacy: "reject"。否则其旧版兼容通道会在有会话路由收到请求之前消费它们。

在迁移客户端之前先部署两条路由。监控旧版通道并让现有会话排空。仅在没有客户端依赖后再移除旧版路由及其仅用于协议的 Durable Object 绑定。将 Durable Object 迁移配置作为单独的部署步骤处理。

更新 MCP 客户端

Agents 现在在内部使用 @modelcontextprotocol/client。现有 addMcpServer 调用会自动协商协议世代。

无状态路径上的服务器使用 server/discover。对于旧版 Streamable HTTP、SSE 与 RPC 服务器,Agents 会回退到 initialize。

客户端 API 包含这些变更:

  • callTool(params, options) 是首选签名。
  • callTool(params, resultSchema, options) 仍可用但已弃用。
  • MCP 客户端类型现在来自 @modelcontextprotocol/client。
  • 所需的无状态 HTTP 头由 SDK 处理。
  • 列表变更根据协商到的通道使用无状态订阅或旧版通知。

为无状态请求配置 elicitation

无状态路径上的工具、提示词与资源可通过多轮请求(MRTR)返回 input_required。SDK 调用已配置的 elicitation 处理程序并重试原始操作。你原来的 callTool、getPrompt 或 readResource Promise 会保持 pending。

每次重试只包含紧邻上一轮输入的响应,而不是此前所有响应。客户端还会回显最新的不透明 requestState。将后续轮次所需的受信任中间值封入完整性受保护的 requestState;不要期望 inputResponses 跨轮次累积。

两轮工具流程请参阅 无状态 elicitation 示例 ↗。

export class MyAgent extends Agent {
	onStart() {
		this.mcp.configureElicitationHandlers({
			form: async (request, serverId, signal) => {
				return collectInput(request, serverId, signal);
			},
			url: async (request, serverId, signal) => {
				return openExternalFlow(request, serverId, signal);
			},
		});
	}
}
export class MyAgent extends Agent<Env> {
    onStart() {
        this.mcp.configureElicitationHandlers({
            form: async (request, serverId, signal) => {
                return collectInput(request, serverId, signal);
            },
            url: async (request, serverId, signal) => {
                return openExternalFlow(request, serverId, signal);
            },
        });
    }
}

处理程序与进行中的调用保留在内存中。休眠、isolate 重启、传输丢失或连接重建会使活跃的交互式调用被拒绝。连接恢复后请重试该操作。

将手动处理的 requestState 视为不可信输入。将其绑定到已认证用户与操作,保护完整性,并设置较短过期时间。

更新自定义 OAuth 提供商

自定义 AgentMcpOAuthProvider 必须实现 v2 OAuthClientProvider 契约:

  • 从 @modelcontextprotocol/client 导入 OAuth 类型。
  • 存储 StoredOAuthClientInformation 与 StoredOAuthTokens。
  • 保留凭据上的 SDK issuer 戳记。
  • 跨浏览器重定向持久化 OAuthDiscoveryState。
  • 在 invalidateCredentials 中接受 "discovery"。
  • 当授权 issuer 不同时,分开保存凭据。

SDK v2 默认校验 OAuth 元数据 issuer。受信任但元数据已知不匹配的旧版服务器可使用 skipIssuerMetadataValidation: true。这会削弱 OAuth mix-up 保护,不应作为通用回退。

查看协议差异

MCP 的无状态模型改变了传输与生命周期:

领域 先前行为 新行为
启动 initialize 握手 无握手。server/discover 对客户端可选
请求元数据 连接范围的协商 每个请求携带版本、客户端能力与身份元数据
会话 可选 Mcp-Session-Id 无协议会话
服务器输入请求 服务器发送 JSON-RPC 请求 服务器返回 input_required。客户端重试原始操作
变更通知 独立 GET 流与列表变更通知 带 SSE 响应的 subscriptions/listen POST
流恢复 Last-Event-ID 可恢复已配置的流 listen 流在失败后重新打开。没有 Last-Event-ID 重放。

自定义传输、代理与网关必须保留草案请求头:

  • MCP-Protocol-Version
  • Mcp-Method
  • 工具、提示词与资源操作的 Mcp-Name
  • 声明的 Mcp-Param-* 工具头

Agents 使用的精确 SDK 版本实现 MCP 2026-07-28 修订。它使 clientInfo 变为可选,并将服务器身份放在结果 _meta 中。请使用高层 SDK API,并随支持各协议修订的 Agents 发行版更新 MCP 包。原始无状态结果必须包含 resultType。

该草案弃用 Roots、Sampling、Logging、旧的 HTTP+SSE 传输以及 Dynamic Client Registration。在弃用窗口内这些类型仍可用于旧版兼容。已发布的实验性 task 方法成为 io.modelcontextprotocol/tasks 扩展。Agents SDK v0.20.0 未添加该扩展。

集成兼容性

这些集成接受 SDK v2 McpServer。请使用 createMcpHandler 提供服务:

这些集成仍会生成或要求 SDK v1 服务器:

在它们生成 SDK v2 服务器之前,请使用 createLegacyMcpHandler 提供其输出,并为每个请求创建一个新服务器。Code Mode MCP 连接器和 withX402Client 同时接受两代客户端。

规划上线

  1. 按有会话依赖对每个端点分类。

  2. 固定 Agents 发行版所需的 MCP SDK 版本。

  3. 将可无状态的每个服务器定义移到 SDK v2 工厂函数。

  4. 在仍需临时旧版通道的端点旁添加无状态处理程序。

  5. 使用 isLegacyRequest() 路由无状态与旧版请求。

  6. 分别测试无状态客户端与旧版客户端。

  7. 通过每个代理与网关测试所需 HTTP 头。

  8. 在干净登录后以及 Durable Object 休眠后测试 OAuth。

  9. 测试取消、多轮输入与传输丢失。

  10. 验证有效 Origin,并以 403 拒绝无效 Origin。

  11. 仅在现有会话排空后移除旧版路由。

Agents v0.20.0 之前发行版存储的 HTTP 会话 ID 不包含协商的协议版本。升级后的客户端会丢弃这些 ID 并重新连接,而不是发送不安全的恢复请求。绑定到旧远程会话的现有进行中工作不会恢复。

API 详情请参阅 createMcpHandler 和 MCP Client。

这篇文档对您有帮助吗?