← Создание агентов на Cloudflare / agents / model-context-protocol / protocol
Инструменты
MCP-инструменты: функции, которые MCP-сервер предоставляет клиентам для вызова. LLM может вызвать инструмент, чтобы найти данные, выполнить вычисление или обратиться к API. MCP-сервер выполняет инструмент и возвращает его результат.
Используйте @modelcontextprotocol/server для не сохраняющего состояние createMcpHandler сервер. McpAgent устарел и не получает новых функций. Существующие McpAgent routes должны продолжать использовать @modelcontextprotocol/sdk только пока они мигрируют.
Определение инструментов
Используйте server.registerTool() чтобы зарегистрировать инструмент на не сохраняющем состояние McpServer экземпляр. У каждого инструмента есть имя, описание и схема входных данных, заданная с помощью библиотеки схем, например Zod ↗ или Valibot ↗, и функцию-обработчик.
import { McpServer } from "@modelcontextprotocol/server";
import { z } from "zod";
function createServer() {
const server = new McpServer({ name: "Math", version: "1.0.0" });
server.registerTool(
"add",
{
description: "Add two numbers together",
inputSchema: { a: z.number(), b: z.number() },
},
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }],
}),
);
return server;
}import { McpServer } from "@modelcontextprotocol/server";
import { z } from "zod";
function createServer() {
const server = new McpServer({ name: "Math", version: "1.0.0" });
server.registerTool(
"add",
{
description: "Add two numbers together",
inputSchema: { a: z.number(), b: z.number() },
},
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }],
}),
);
return server;
}Обработчик инструмента получает провалидированный ввод и должен вернуть объект с content массив. Каждый элемент содержимого имеет type (обычно "text") и соответствующие данные.
Результаты инструмента
Результаты инструмента возвращаются в виде массива частей содержимого. Самый распространённый тип: text, но вы также можете возвращать изображения и встроенные ресурсы.
server.registerTool(
"lookup",
{
description: "Look up a user by ID",
inputSchema: { 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) }],
};
},
);server.registerTool(
"lookup",
{
description: "Look up a user by ID",
inputSchema: { 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) }],
};
},
);Задайте isError: true чтобы сообщить, что вызов инструмента завершился ошибкой. LLM получает сообщение об ошибке и может решить, как действовать дальше.
Описания инструментов
description параметр критически важен: именно его читает LLM, чтобы решить, стоит ли и когда вызывать ваш инструмент. Пишите описания, которые:
- Определенный о том, что делает инструмент: формулировка «Get the current weather for a city» лучше, чем «Weather tool»
- Ясность входных данных: «Требует названия города в виде строки» помогает LLM правильно сформировать вызов
- Честность в отношении ограничений: «Поддерживает только города США» не позволяет LLM вызывать его с неподдерживаемыми входными данными
Валидация ввода с помощью Zod
Входные данные инструмента задаются схемами Zod и автоматически проверяются перед запуском обработчика. Используйте .describe() метод, чтобы дать LLM контекст о каждом параметре.
server.registerTool(
"search",
{
description: "Search for documents by query",
inputSchema: {
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) }],
};
},
);server.registerTool(
"search",
{
description: "Search for documents by query",
inputSchema: {
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) }],
};
},
);Использование инструментов с createMcpHandler
Для серверов MCP без сохранения состояния определяйте инструменты внутри фабричной функции и передавайте сервер в createMcpHandler:
import { createMcpHandler } from "agents/mcp/server";
import { McpServer } from "@modelcontextprotocol/server";
import { z } from "zod";
function createServer() {
const server = new McpServer({ name: "My Tools", version: "1.0.0" });
server.registerTool(
"ping",
{ description: "Check if the server is alive", inputSchema: {} },
async () => ({
content: [{ type: "text", text: "pong" }],
}),
);
return server;
}
export default {
fetch(request, env, ctx) {
return createMcpHandler(createServer)(request, env, ctx);
},
};import { createMcpHandler } from "agents/mcp/server";
import { McpServer } from "@modelcontextprotocol/server";
import { z } from "zod";
function createServer() {
const server = new McpServer({ name: "My Tools", version: "1.0.0" });
server.registerTool(
"ping",
{ description: "Check if the server is alive", inputSchema: {} },
async () => ({
content: [{ type: "text", text: "pong" }],
}),
);
return server;
}
export default {
fetch(request, env, ctx) {
return createMcpHandler(createServer)(request, env, ctx);
},
} satisfies ExportedHandler;Использование инструментов с McpAgent
Этот раздел применяется только к существующим устаревшим маршрутам во время миграции. Определите их инструменты в init() метод объекта McpAgent. Инструменты получают доступ к экземпляру агента через this, чтобы они могли читать и записывать состояние.
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}` }],
};
},
);
}
}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}` }],
};
},
);
}
}