McpAgent
当你在 Cloudflare 上构建 MCP Server 时,你扩展来自 Agents SDK 的 McpAgent class ↗:
JavaScript
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) }],
}),
);
}
}
Explain Code
TypeScript
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) }],
}),
);
}
}
Explain Code
这意味着你的 MCP 服务器的每个实例都有自己的持久化状态,由 Durable Object 支持,带有自己的 SQL database。
你的 MCP 服务器不一定要是 Agent。你可以构建无状态的 MCP 服务器,使用 @modelcontextprotocol/sdk 包向你的 MCP 服务器添加 tools。
但如果你希望你的 MCP 服务器:
- 记住先前的工具调用以及它提供的响应
- 向 MCP 客户端提供游戏,记住游戏棋盘状态、之前的移动和分数
- 缓存先前外部 API 调用的状态,以便后续工具调用可以重用
- 做 Agent 能做的任何事情,但允许 MCP 客户端与之通信
你可以使用下面的 API 来实现。
API 概览
| 属性/方法 | 描述 |
|---|---|
| state | 当前状态对象(已持久化) |
| initialState | 实例启动时的默认状态 |
| setState(state) | 更新并持久化状态 |
| onStateChanged(state) | 状态更改时调用 |
| sql | 在嵌入式数据库上执行 SQL 查询 |
| server | 用于注册工具的 McpServer 实例 |
| props | 来自 OAuth 认证的用户身份和 token |
| elicitInput(options, context) | 从用户请求结构化输入 |
| McpAgent.serve(path, options) | 创建 Worker handler 的静态方法 |
使用 McpAgent.serve() 部署
McpAgent.serve() 静态方法创建一个将请求路由到你的 MCP 服务器的 Worker handler:
JavaScript
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) }],
}));
}
}
// Export the Worker handler
export default MyMCP.serve("/mcp");
Explain Code
TypeScript
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) }],
}));
}
}
// Export the Worker handler
export default MyMCP.serve("/mcp");
Explain Code
这是部署 MCP 服务器最简单的方式 — 大约 15 行代码。serve() 方法自动处理 Streamable HTTP 传输。
使用 OAuth 认证
使用 OAuth Provider Library ↗ 时,将你的 MCP 服务器传递给 apiHandlers:
JavaScript
import { OAuthProvider } from "@cloudflare/workers-oauth-provider";
export default new OAuthProvider({
apiHandlers: { "/mcp": MyMCP.serve("/mcp") },
authorizeEndpoint: "/authorize",
tokenEndpoint: "/token",
clientRegistrationEndpoint: "/register",
defaultHandler: AuthHandler,
});
TypeScript
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 和数据驻留合规性,指定一个司法管辖区以确保你的 MCP 服务器实例在特定区域中运行:
JavaScript
// EU jurisdiction for GDPR compliance
export default MyMCP.serve("/mcp", { jurisdiction: "eu" });
TypeScript
// EU jurisdiction for GDPR compliance
export default MyMCP.serve("/mcp", { jurisdiction: "eu" });
使用 OAuth:
JavaScript
export default new OAuthProvider({
apiHandlers: {
"/mcp": MyMCP.serve("/mcp", { jurisdiction: "eu" }),
},
// ... other OAuth config
});
TypeScript
export default new OAuthProvider({
apiHandlers: {
"/mcp": MyMCP.serve("/mcp", { jurisdiction: "eu" }),
},
// ... other OAuth config
});
当你指定 jurisdiction: "eu" 时:
- 所有 MCP 会话数据保留在 EU 内
- 由你的工具处理的用户数据保留在 EU 内
- 存储在 Durable Object 中的状态保留在 EU 内
可用的司法管辖区包括 "eu"(European Union)和 "fedramp"(FedRAMP 合规位置)。有关更多选项,请参阅 Durable Objects data location。
休眠支持
McpAgent 实例自动支持 WebSockets Hibernation,允许有状态的 MCP 服务器在不活动期间休眠,同时保留其状态。这意味着你的 agent 仅在主动处理请求时消耗计算资源,在保持完整上下文和会话历史的同时优化成本。
休眠默认启用,无需额外配置。
认证和授权
McpAgent 类与 OAuth Provider Library ↗ 无缝集成,用于认证和授权。
当用户向你的 MCP 服务器进行身份验证时,他们的身份信息和 token 通过 props 参数提供,允许你:
- 访问用户特定的数据
- 在执行操作之前检查用户权限
- 基于用户属性自定义响应
- 使用认证 token 代表用户向外部服务发出请求
状态同步 API
McpAgent 类提供对 Agent state APIs 的完全访问:
- state — 当前持久化状态
- initialState — 实例启动时的默认状态
- setState — 更新并持久化状态
- onStateChanged — 响应状态更改
- sql — 在嵌入式数据库上执行 SQL 查询
会话结束后状态重置
目前,每个客户端会话由 McpAgent 类的一个实例支持。这是自动处理的,如入门指南中所示。这意味着当同一客户端重新连接时,他们将启动新会话,状态将被重置。
例如,以下代码实现了一个 MCP 服务器,它记住一个计数器值,并在调用 add 工具时更新计数器:
JavaScript
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 });
}
}
Explain Code
TypeScript
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 });
}
}
Explain Code
Elicitation(人工参与回路)
MCP 服务器可以在工具执行期间使用 elicitation 请求额外的用户输入。MCP 客户端(如 Claude Desktop)基于你的 JSON Schema 渲染表单并返回用户的响应。
何时使用 elicitation
- 请求最初工具调用中没有的结构化输入
- 在继续之前确认高风险操作
- 在执行过程中收集额外的上下文或偏好
elicitInput(options, context)
在工具执行期间从用户请求结构化输入。
参数:
| 参数 | 类型 | 描述 |
|---|---|---|
| options.message | string | 解释需要什么输入的消息 |
| options.requestedSchema | JSON Schema | 定义预期输入结构的 schema |
| context.relatedRequestId | string | 来自工具处理器的 extra.requestId |
返回: Promise<{ action: "accept" | "decline", content?: object }>
JavaScript
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
export class CounterMCP extends McpAgent {
server = new McpServer({
name: "counter-server",
version: "1.0.0",
});
initialState = { counter: 0 };
async init() {
this.server.tool(
"increase-counter",
"Increase the counter by a user-specified amount",
{ confirm: z.boolean().describe("Do you want to increase the counter?") },
async ({ confirm }, extra) => {
if (!confirm) {
return { content: [{ type: "text", text: "Cancelled." }] };
}
// Request additional input from the user
const userInput = await this.server.server.elicitInput(
{
message: "By how much do you want to increase the counter?",
requestedSchema: {
type: "object",
properties: {
amount: {
type: "number",
title: "Amount",
description: "The amount to increase the counter by",
},
},
required: ["amount"],
},
},
{ relatedRequestId: extra.requestId },
);
// Check if user accepted or cancelled
if (userInput.action !== "accept" || !userInput.content) {
return { content: [{ type: "text", text: "Cancelled." }] };
}
// Use the input
const amount = Number(userInput.content.amount);
this.setState({
...this.state,
counter: this.state.counter + amount,
});
return {
content: [
{
type: "text",
text: `Counter increased by ${amount}, now at ${this.state.counter}`,
},
],
};
},
);
}
}
Explain Code
TypeScript
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
type State = { counter: number };
export class CounterMCP extends McpAgent<Env, State, {}> {
server = new McpServer({
name: "counter-server",
version: "1.0.0",
});
initialState: State = { counter: 0 };
async init() {
this.server.tool(
"increase-counter",
"Increase the counter by a user-specified amount",
{ confirm: z.boolean().describe("Do you want to increase the counter?") },
async ({ confirm }, extra) => {
if (!confirm) {
return { content: [{ type: "text", text: "Cancelled." }] };
}
// Request additional input from the user
const userInput = await this.server.server.elicitInput(
{
message: "By how much do you want to increase the counter?",
requestedSchema: {
type: "object",
properties: {
amount: {
type: "number",
title: "Amount",
description: "The amount to increase the counter by",
},
},
required: ["amount"],
},
},
{ relatedRequestId: extra.requestId },
);
// Check if user accepted or cancelled
if (userInput.action !== "accept" || !userInput.content) {
return { content: [{ type: "text", text: "Cancelled." }] };
}
// Use the input
const amount = Number(userInput.content.amount);
this.setState({
...this.state,
counter: this.state.counter + amount,
});
return {
content: [
{
type: "text",
text: `Counter increased by ${amount}, now at ${this.state.counter}`,
},
],
};
},
);
}
}
Explain Code
表单的 JSON Schema
requestedSchema 定义显示给用户的表单结构:
TypeScript
const schema = {
type: "object",
properties: {
// Text input
name: {
type: "string",
title: "Name",
description: "Enter your name",
},
// Number input
amount: {
type: "number",
title: "Amount",
minimum: 1,
maximum: 100,
},
// Boolean (checkbox)
confirm: {
type: "boolean",
title: "I confirm this action",
},
// Enum (dropdown)
priority: {
type: "string",
enum: ["low", "medium", "high"],
title: "Priority",
},
},
required: ["name", "amount"],
};
Explain Code
处理响应
JavaScript
const result = await this.server.server.elicitInput(
{ message: "Confirm action", requestedSchema: schema },
{ relatedRequestId: extra.requestId },
);
switch (result.action) {
case "accept":
// User submitted the form
const { name, amount } = result.content;
// Process the input...
break;
case "decline":
// User cancelled
return { content: [{ type: "text", text: "Operation cancelled." }] };
}
Explain Code
TypeScript
const result = await this.server.server.elicitInput(
{ message: "Confirm action", requestedSchema: schema },
{ relatedRequestId: extra.requestId },
);
switch (result.action) {
case "accept":
// User submitted the form
const { name, amount } = result.content as { name: string; amount: number };
// Process the input...
break;
case "decline":
// User cancelled
return { content: [{ type: "text", text: "Operation cancelled." }] };
}
Explain Code
MCP 客户端支持
Elicitation 需要 MCP 客户端支持。并非所有 MCP 客户端都实现 elicitation 能力。检查客户端文档以了解兼容性。
有关更多人工参与回路模式,包括基于工作流的批准,请参阅 Human-in-the-loop patterns。
下一步
Build a Remote MCP server Cloudflare 上 MCP 服务器入门。
MCP Tools 设计并向你的 MCP 服务器添加工具。
Authorization 设置 OAuth 认证。
Securing MCP servers 生产环境的安全最佳实践。
createMcpHandler 构建无状态的 MCP 服务器。