工具
MCP 工具是 MCP 服务器 暴露出来供客户端调用的函数。当 LLM 决定要执行某个动作时 —— 查询数据、运行计算、调用 API —— 它就会调用一个工具。MCP 服务器执行工具并返回结果。
工具使用 @modelcontextprotocol/sdk 包来定义。Agents SDK 负责传输和生命周期;无论你使用 createMcpHandler 还是 McpAgent,工具的定义方式都一样。
定义工具
使用 server.tool() 在 McpServer 实例上注册工具。每个工具都有名字、描述(LLM 用它来决定何时调用)、用 Zod ↗ 定义的输入 schema,以及一个处理函数。
JavaScript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
function createServer() {
const server = new McpServer({ name: "Math", version: "1.0.0" });
server.tool(
"add",
"Add two numbers together",
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }],
}),
);
return server;
}
Explain Code
TypeScript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
function createServer() {
const server = new McpServer({ name: "Math", version: "1.0.0" });
server.tool(
"add",
"Add two numbers together",
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }],
}),
);
return server;
}
Explain Code
工具的处理函数会拿到经过校验的输入,并必须返回一个带 content 数组的对象。每个 content 元素都有一个 type(通常是 "text")和对应的数据。
工具结果
工具的结果以 content 数组形式返回。最常见的类型是 text,你也可以返回图片和嵌入资源(embedded resource)。
JavaScript
server.tool(
"lookup",
"Look up a user by ID",
{ userId: z.string() },
async ({ userId }) => {
const user = await db.getUser(userId);
if (!user) {
return {
isError: true,
content: [{ type: "text", text: `User ${userId} not found` }],
};
}
return {
content: [{ type: "text", text: JSON.stringify(user, null, 2) }],
};
},
);
Explain Code
TypeScript
server.tool(
"lookup",
"Look up a user by ID",
{ userId: z.string() },
async ({ userId }) => {
const user = await db.getUser(userId);
if (!user) {
return {
isError: true,
content: [{ type: "text", text: `User ${userId} not found` }],
};
}
return {
content: [{ type: "text", text: JSON.stringify(user, null, 2) }],
};
},
);
Explain Code
把 isError 设为 true 表示工具调用失败。LLM 会拿到错误信息,自行决定下一步怎么做。
工具描述
description 参数至关重要 —— 这是 LLM 用来判断是否以及何时调用你工具的依据。写描述时要做到:
- 具体说明工具做什么:“Get the current weather for a city” 比 “Weather tool” 好得多
- 明确输入要求:“Requires a city name as a string” 能帮助 LLM 正确地构造调用
- 诚实说明限制:“Only supports US cities” 可以避免 LLM 用不支持的输入调用它
用 Zod 校验输入
工具的输入定义为 Zod schema,在 handler 运行之前会被自动校验。使用 Zod 的 .describe() 方法,为每个参数提供给 LLM 的上下文。
JavaScript
server.tool(
"search",
"Search for documents by query",
{
query: z.string().describe("The search query"),
limit: z
.number()
.min(1)
.max(100)
.default(10)
.describe("Maximum number of results to return"),
category: z
.enum(["docs", "blog", "api"])
.optional()
.describe("Filter by content category"),
},
async ({ query, limit, category }) => {
const results = await searchIndex(query, { limit, category });
return {
content: [{ type: "text", text: JSON.stringify(results) }],
};
},
);
Explain Code
TypeScript
server.tool(
"search",
"Search for documents by query",
{
query: z.string().describe("The search query"),
limit: z
.number()
.min(1)
.max(100)
.default(10)
.describe("Maximum number of results to return"),
category: z
.enum(["docs", "blog", "api"])
.optional()
.describe("Filter by content category"),
},
async ({ query, limit, category }) => {
const results = await searchIndex(query, { limit, category });
return {
content: [{ type: "text", text: JSON.stringify(results) }],
};
},
);
Explain Code
配合 createMcpHandler 使用工具
对于无状态的 MCP 服务器,在工厂函数里定义工具,然后把 server 传给 createMcpHandler:
JavaScript
import { createMcpHandler } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
function createServer() {
const server = new McpServer({ name: "My Tools", version: "1.0.0" });
server.tool("ping", "Check if the server is alive", {}, async () => ({
content: [{ type: "text", text: "pong" }],
}));
return server;
}
export default {
fetch: (request, env, ctx) => {
const server = createServer();
return createMcpHandler(server)(request, env, ctx);
},
};
Explain Code
TypeScript
import { createMcpHandler } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
function createServer() {
const server = new McpServer({ name: "My Tools", version: "1.0.0" });
server.tool("ping", "Check if the server is alive", {}, async () => ({
content: [{ type: "text", text: "pong" }],
}));
return server;
}
export default {
fetch: (request: Request, env: Env, ctx: ExecutionContext) => {
const server = createServer();
return createMcpHandler(server)(request, env, ctx);
},
} satisfies ExportedHandler<Env>;
Explain Code
配合 McpAgent 使用工具
对于有状态的 MCP 服务器,在 McpAgent 的 init() 方法中定义工具。工具可以通过 this 访问 agent 实例,意味着它们能够读写状态。
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: "Stateful Tools", version: "1.0.0" });
async init() {
this.server.tool(
"incrementCounter",
"Increment and return a counter",
{},
async () => {
const count = (this.state?.count ?? 0) + 1;
this.setState({ count });
return {
content: [{ type: "text", text: `Counter: ${count}` }],
};
},
);
}
}
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: "Stateful Tools", version: "1.0.0" });
async init() {
this.server.tool(
"incrementCounter",
"Increment and return a counter",
{},
async () => {
const count = (this.state?.count ?? 0) + 1;
this.setState({ count });
return {
content: [{ type: "text", text: `Counter: ${count}` }],
};
},
);
}
}
Explain Code
下一步
Build a remote MCP server 在 Cloudflare 上部署 MCP 服务器的逐步教程。
createMcpHandler API 无状态 MCP 服务器的参考。
McpAgent API 有状态 MCP 服务器的参考。
MCP authorization 为你的 MCP 服务器添加 OAuth 认证。