在 Cloudflare 上构建 MCP 服务器时,需扩展 Agents SDK 中的 McpAgent 类 ↗:
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
export class MyMCP extends McpAgent {
server = new McpServer({ name: "Demo", version: "1.0.0" });
async init() {
this.server.tool(
"add",
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }],
}),
);
}
}import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
export class MyMCP extends McpAgent {
server = new McpServer({ name: "Demo", version: "1.0.0" });
async init() {
this.server.tool(
"add",
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }],
}),
);
}
}这意味着 MCP 服务器的每个实例都有由 Durable Object 支持的持久状态,以及独立的 SQL 数据库。
MCP 服务器不一定是 Agent。你可以构建无状态 MCP 服务器,仅使用 @modelcontextprotocol/sdk 包向服务器添加 工具。
但若希望 MCP 服务器:
- 记住先前的工具调用及其响应
- 向 MCP 客户端提供游戏,并记住棋盘状态、先前走法与得分
- 缓存先前外部 API 调用的状态,供后续工具调用复用
- 执行 Agent 能做的一切,但允许 MCP 客户端与之通信
可使用下方 API 实现。
| 属性/方法 | 描述 |
|---|---|
state |
当前状态对象(已持久化) |
initialState |
实例启动时的默认状态 |
setState(state) |
更新并持久化状态 |
onStateChanged(state) |
状态变更时调用 |
sql |
在嵌入式数据库上执行 SQL 查询 |
server |
用于注册工具的 McpServer 实例 |
props |
OAuth 身份验证中的用户身份与令牌 |
elicitInput(options, context) |
向用户请求结构化输入 |
McpAgent.serve(path, options) |
创建 Worker 处理函数的静态方法 |
McpAgent.serve() 静态方法创建 Worker 处理程序,将请求路由到你的 MCP server:
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
export class MyMCP extends McpAgent {
server = new McpServer({ name: "my-server", version: "1.0.0" });
async init() {
this.server.tool("square", { n: z.number() }, async ({ n }) => ({
content: [{ type: "text", text: String(n * n) }],
}));
}
}
// 导出 Worker 处理程序
export default MyMCP.serve("/mcp");import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
export class MyMCP extends McpAgent {
server = new McpServer({ name: "my-server", version: "1.0.0" });
async init() {
this.server.tool("square", { n: z.number() }, async ({ n }) => ({
content: [{ type: "text", text: String(n * n) }],
}));
}
}
// 导出 Worker 处理程序
export default MyMCP.serve("/mcp");这是部署 MCP server 的最简方式——约 15 行代码。serve() 方法自动处理 Streamable HTTP 传输。
使用 OAuth Provider Library ↗ 时,将 MCP 服务器传入 apiHandlers:
import { OAuthProvider } from "@cloudflare/workers-oauth-provider";
export default new OAuthProvider({
apiHandlers: { "/mcp": MyMCP.serve("/mcp") },
authorizeEndpoint: "/authorize",
tokenEndpoint: "/token",
clientRegistrationEndpoint: "/register",
defaultHandler: AuthHandler,
});import { OAuthProvider } from "@cloudflare/workers-oauth-provider";
export default new OAuthProvider({
apiHandlers: { "/mcp": MyMCP.serve("/mcp") },
authorizeEndpoint: "/authorize",
tokenEndpoint: "/token",
clientRegistrationEndpoint: "/register",
defaultHandler: AuthHandler,
});为符合 GDPR 与数据驻留要求,可指定 jurisdiction 以确保 MCP server 实例在特定区域运行:
// EU jurisdiction,符合 GDPR
export default MyMCP.serve("/mcp", { jurisdiction: "eu" });// EU jurisdiction,符合 GDPR
export default MyMCP.serve("/mcp", { jurisdiction: "eu" });配合 OAuth:
export default new OAuthProvider({
apiHandlers: {
"/mcp": MyMCP.serve("/mcp", { jurisdiction: "eu" }),
},
// ... 其他 OAuth 配置
});export default new OAuthProvider({
apiHandlers: {
"/mcp": MyMCP.serve("/mcp", { jurisdiction: "eu" }),
},
// ... 其他 OAuth 配置
});指定 jurisdiction: "eu" 时:
- 所有 MCP 会话数据保留在 EU 内
- tool 处理的用户数据保留在 EU 内
- Durable Object 中存储的 state 保留在 EU 内
可用 jurisdiction 包括 "eu"(欧盟)与 "fedramp"(FedRAMP 合规位置)。更多选项请参阅 Durable Objects 数据位置。
McpAgent 实例自动支持 WebSockets Hibernation,使有状态 MCP 服务器在非活跃期休眠同时保留状态。这意味着 Agent 仅在主动处理请求时消耗计算资源,在保持完整上下文与对话历史的同时优化成本。
休眠默认启用,无需额外配置。
McpAgent 的 Streamable HTTP 传输可度过 Cloudflare 边缘约 5 分钟的空闲流看门狗,因此在连接不稳定时进行中的工具调用不会丢失:
- GET(独立监听流) — 配置
EventStore时,空闲断开通过客户端以Last-Event-ID头重连恢复(无需保活)。无EventStore时使用注释帧保活(: keepalive,每 25 秒)保持长连接监听器存活。 - POST(工具响应流) — 始终保活,进行中的工具调用可度过空闲看门狗。配置
EventStore时 POST 流还可通过Last-Event-ID恢复;重连客户端重放遗漏事件直至最终响应。每个 POST 流在写入关闭帧时清除其事件。
DurableObjectEventStore 从 agents/mcp 导出,供在 Agent 或 Durable Object 内嵌 transport 的有状态 WorkerTransport 调用方使用:
import { DurableObjectEventStore } from "agents/mcp";
const eventStore = new DurableObjectEventStore(this.ctx.storage);import { DurableObjectEventStore } from "agents/mcp";
const eventStore = new DurableObjectEventStore(this.ctx.storage);传输配置请参阅 MCP Transport。
McpAgent 类与 OAuth Provider Library ↗ 无缝集成,可用于身份验证与授权。
用户向 MCP server 完成身份验证后,其身份信息与 token 通过 props 参数可用,你可:
- 访问用户特定数据
- 在操作前检查用户权限
- 根据用户属性定制响应
- 使用身份验证 token 代表用户向外部服务发起请求
McpAgent 类提供对 Agent state API 的完整访问:
state— 当前持久化状态initialState— 实例启动时的默认状态setState— 更新并持久化状态onStateChanged— 响应状态变更sql— 在嵌入式数据库上执行 SQL 查询
例如,以下代码实现记住计数器值、并在调用 add 工具时更新计数器的 MCP 服务器:
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
export class MyMCP extends McpAgent {
server = new McpServer({
name: "Demo",
version: "1.0.0",
});
initialState = {
counter: 1,
};
async init() {
this.server.resource(`counter`, `mcp://resource/counter`, (uri) => {
return {
contents: [{ uri: uri.href, text: String(this.state.counter) }],
};
});
this.server.tool(
"add",
"Add to the counter, stored in the MCP",
{ a: z.number() },
async ({ a }) => {
this.setState({ ...this.state, counter: this.state.counter + a });
return {
content: [
{
type: "text",
text: String(`Added ${a}, total is now ${this.state.counter}`),
},
],
};
},
);
}
onStateChanged(state) {
console.log({ stateUpdate: state });
}
}import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
type State = { counter: number };
export class MyMCP extends McpAgent<Env, State, {}> {
server = new McpServer({
name: "Demo",
version: "1.0.0",
});
initialState: State = {
counter: 1,
};
async init() {
this.server.resource(`counter`, `mcp://resource/counter`, (uri) => {
return {
contents: [{ uri: uri.href, text: String(this.state.counter) }],
};
});
this.server.tool(
"add",
"Add to the counter, stored in the MCP",
{ a: z.number() },
async ({ a }) => {
this.setState({ ...this.state, counter: this.state.counter + a });
return {
content: [
{
type: "text",
text: String(`Added ${a}, total is now ${this.state.counter}`),
},
],
};
},
);
}
onStateChanged(state: State) {
console.log({ stateUpdate: state });
}
}MCP elicitation ↗ 允许 server 在处理其他请求(如 tool call)时向用户请求输入。当前稳定 MCP 规范定义两种模式:
- Form 模式 通过客户端收集结构化、非敏感数据。
- URL 模式 将用户引导至带外交互,如第三方授权或支付。
server 发送前,客户端必须声明支持对应模式。
在工具处理函数中调用 this.server.server.elicitInput()。将 extra.requestId 作为 relatedRequestId 传入,使响应返回原始工具调用的流:
const result = await this.server.server.elicitInput(
{
mode: "form",
message: "By how much do you want to increase the counter?",
requestedSchema: {
type: "object",
properties: {
amount: {
type: "number",
title: "Amount",
minimum: 1,
maximum: 100,
},
},
required: ["amount"],
},
},
{ relatedRequestId: extra.requestId },
);
if (result.action !== "accept" || !result.content) {
return { content: [{ type: "text", text: "Counter unchanged." }] };
}
const amount = Number(result.content.amount);const result = await this.server.server.elicitInput(
{
mode: "form",
message: "By how much do you want to increase the counter?",
requestedSchema: {
type: "object",
properties: {
amount: {
type: "number",
title: "Amount",
minimum: 1,
maximum: 100,
},
},
required: ["amount"],
},
},
{ relatedRequestId: extra.requestId },
);
if (result.action !== "accept" || !result.content) {
return { content: [{ type: "text", text: "Counter unchanged." }] };
}
const amount = Number(result.content.amount);为向后兼容,表单请求可省略 mode: "form"。schema 支持带基本类型字段的扁平对象。请勿使用表单模式请求密码、API 密钥、访问令牌、支付凭证或其他机密。
URL 模式用于必须在 MCP 客户端外完成的交互。请求包含 message、URL 与唯一 elicitationId:
const elicitationId = crypto.randomUUID();
const result = await this.server.server.elicitInput(
{
mode: "url",
message: "Connect your account to continue.",
url: `https://example.com/connect?elicitationId=${elicitationId}`,
elicitationId,
},
{ relatedRequestId: extra.requestId },
);
if (result.action !== "accept") {
return { content: [{ type: "text", text: "Connection cancelled." }] };
}
return {
content: [
{
type: "text",
text: "Connection page opened. Complete it in your browser.",
},
],
};const elicitationId = crypto.randomUUID();
const result = await this.server.server.elicitInput(
{
mode: "url",
message: "Connect your account to continue.",
url: `https://example.com/connect?elicitationId=${elicitationId}`,
elicitationId,
},
{ relatedRequestId: extra.requestId },
);
if (result.action !== "accept") {
return { content: [{ type: "text", text: "Connection cancelled." }] };
}
return {
content: [
{
type: "text",
text: "Connection page opened. Complete it in your browser.",
},
],
};URL 模式下,accept 表示用户同意打开 URL,不表示带外交互已完成。server 之后可发送 notifications/elicitation/complete,使用相同 elicitationId。
请勿在 url 中放置机密、个人信息或预认证受保护资源 URL。生产 server 应使用 HTTPS。将每个请求绑定到已认证用户,并验证同一用户完成带外流程。
两种模式均返回三种 action 之一:
| 操作 | 含义 |
|---|---|
accept |
用户提交了表单或同意打开 URL。 |
decline |
用户明确拒绝请求。 |
cancel |
用户关闭请求但未明确选择。 |
已接受的表单响应包含与 requestedSchema 匹配的 content。URL 响应省略 content。decline 与 cancel 响应通常也省略。
switch (result.action) {
case "accept":
// form 模式下,验证并处理 result.content。
break;
case "decline":
return { content: [{ type: "text", text: "Request declined." }] };
case "cancel":
return { content: [{ type: "text", text: "Request dismissed." }] };
}switch (result.action) {
case "accept":
// form 模式下,验证并处理 result.content。
break;
case "decline":
return { content: [{ type: "text", text: "Request declined." }] };
case "cancel":
return { content: [{ type: "text", text: "Request dismissed." }] };
}更多人机协同模式请参阅 Human-in-the-loop 模式。