← Создание агентов на Cloudflare / agents / model-context-protocol / apis
API обработчиков MCP
Agents SDK предоставляет два пути обработки запросов на сервере:
| API | Путь импорта | Пакет MCP-сервера | Поведение |
|---|---|---|---|
createMcpHandler |
agents/mcp/server |
@modelcontextprotocol/server |
без сохранения состояния с устаревшим режимом совместимости по умолчанию |
createLegacyMcpHandler |
agents/mcp |
@modelcontextprotocol/sdk |
устаревшие сессии через WorkerTransport |
McpAgent устарел и не получает новых функций. Перенесите существующие McpAgent серверов к обработчику без сохранения состояния. См. руководство по миграции когда функциям с поддержкой сессий требуется поэтапное развёртывание.
Установка зависимостей
Для сервера без сохранения состояния:
npm i agents @modelcontextprotocol/[email protected] zodДля явного устаревшего сервера:
npm i agents @modelcontextprotocol/[email protected] zodИспользуйте именно те версии MCP, которые требует установленная у вас версия Agents.
createMcpHandler
createMcpHandler создаёт вызываемый обработчик запросов MCP без сохранения состояния на основе фабрики сервера MCP SDK v2. Вызывайте его из объекта fetch() экспорт или объедините его внутри другого обработчика.
import {
createMcpHandler,
type CreateMcpHandlerOptions,
type StatelessMcpHandler,
} from "agents/mcp/server";
import type { McpServerFactory } from "@modelcontextprotocol/server";
function createMcpHandler(
factory: McpServerFactory,
options?: CreateMcpHandlerOptions,
): StatelessMcpHandler;Параметры
factoryсоздаёт новыйMcpServerилиServerот@modelcontextprotocol/server. Это может быть синхронным или асинхронным.optionsобъединяет параметры Agents Worker с поддерживаемыми параметрами обработчика upstream SDK v2.
Factory получает следующий контекст запроса:
interface McpRequestContext {
era: "modern" | "legacy";
authInfo?: AuthInfo;
requestInfo?: Request;
}Фабрика без аргументов остаётся действительной.
Пример
import { McpServer } from "@modelcontextprotocol/server";
import { createMcpHandler } from "agents/mcp/server";
import { z } from "zod";
function createServer() {
const server = new McpServer({
name: "hello-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);
},
};import { McpServer } from "@modelcontextprotocol/server";
import { createMcpHandler } from "agents/mcp/server";
import { z } from "zod";
function createServer() {
const server = new McpServer({
name: "hello-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;Передавайте саму фабрику. Не создавайте единственный глобальный экземпляр сервера и не передавайте готовый сервер SDK v2 напрямую.
CreateMcpHandlerOptions
Доступны следующие параметры:
| Опция | Тип | По умолчанию | Описание |
|---|---|---|---|
route |
string |
"/mcp" |
Точный путь, обрабатываемый обёрткой Worker |
corsOptions |
CORSOptions | false |
Wildcard CORS | заголовки ответа CORS, или false чтобы удалить их |
allowedHostnames |
string[] |
Localhost или workers.dev маршрут |
Необязательное ограничение Host для пользовательских доменов |
allowedOriginHostnames |
string[] | "*" |
Localhost, workers.dev, либо конкретный CORS Origin |
ограничение Origin браузера или явное делегирование middleware |
authContext |
McpAuthContext |
Свойства контекста выполнения | Props приложения, возвращаемые getMcpAuthContext() |
legacy |
"stateless" | "reject" |
"stateless" |
совместимость с устаревшими версиями или отклонение запросов без сохранения состояния |
responseMode |
"auto" | "json" | "sse" |
"auto" |
формирование запросов и ответов без сохранения состояния |
onerror |
(error: Error) => void |
None | Отчёты об ошибках вне обычного канала |
maxSubscriptions |
number |
1,024 |
Максимальное количество одновременных потоков прослушивания |
keepAliveMs |
number |
15,000 |
Интервал keepalive для потоков прослушивания |
Параметры транспорта SDK v1 не применяются к этому обработчику. Он отклоняет такие параметры, как transport, storage, sessionIdGenerator, eventStore, а также enableJsonResponse.
Используйте responseMode: "json" вместо enableJsonResponse: true. Режим JSON отбрасывает уведомления, отправленные до финального результата.
Жизненный цикл factory
Handler создаёт один MCP-сервер для каждого запроса. Это соответствует модели черновика протокола, в которой версия, идентичность и возможности передаются с каждым запросом, а не через сессию протокола.
Данные приложения всё равно могут быть долговременными. Храните межзапросные данные за аутентифицированным хендлом в Durable Object, D1, KV или R2, а не привязывайте их к ID сессии MCP.
Elicitation с обработчиком без сохранения состояния
Elicitation через обработчик без сохранения состояния возвращает input_required и завершается через многораундовые запросы (MRTR). При каждой повторной попытке SDK повторно отправляет последний requestState и отправляет ответы только на непосредственно предшествующий раунд ввода. Он не накапливает более ранние inputResponses. Worker не остаётся приостановленным, пока пользователь отвечает.
Используйте inputRequired(...) чтобы запросить ввод. Считайте содержимое принятой формы за этот ход из context.mcpReq.inputResponses с acceptedContent(...). Запечатывайте доверенные промежуточные значения, необходимые для последующих раундов, в защищённые от изменения requestState.
См. пример elicitation без сохранения состояния ↗ для двухраундового потока вызова инструмента. Для push-запросов с сохранением состояния см. Elicitation на устаревших серверах.
Проверка Origin и CORS
Обёртка Workers проверяет каждое присутствующее значение Origin браузера. Она отклоняет некорректные, непрозрачные и не HTTP значения Origin с 403. MCP-клиенты не из браузера и без Origin остаются допустимыми.
Стандартный allowlist включает значения Origin типа localhost, эндпоинта workers.dev hostname и конкретный hostname из corsOptions.origin. Обработчик также применяет аналогичные проверки Host к localhost и workers.dev эндпойнты. Это сохраняет локальную защиту от DNS rebinding без необходимости отдельного списка Origin для типовых маршрутов Workers.
Для пользовательского домена с wildcard-CORS установите allowedHostnames и allowedOriginHostnames явно. Если corsOptions.origin это конкретный URL, обработчик автоматически определяет его имя хоста Origin:
export default {
fetch(request, env, ctx) {
return createMcpHandler(createServer, {
allowedHostnames: ["mcp.example.com"],
corsOptions: {
origin: "https://app.example.com",
},
})(request, env, ctx);
},
};export default {
fetch(request, env, ctx) {
return createMcpHandler(createServer, {
allowedHostnames: ["mcp.example.com"],
corsOptions: {
origin: "https://app.example.com",
},
})(request, env, ctx);
},
} satisfies ExportedHandler;Значения allowlist представляют собой имена хостов без схемы и порта. Сопоставление origin игнорирует схему и порт.
Задайте allowedOriginHostnames: "*" только если доверенный middleware проверяет Origin перед вызовом обработчика. Это значение отключает проверку Origin в обработчике, включая отклонение некорректных и непрозрачных значений Origin. HTTP-серверы MCP обязаны проверять Origin браузера.
Заголовки ответа CORS не являются аутентификацией. Защитите эндпойнт MCP с помощью OAuth или другого уровня аутентификации.
Handler не выводит allowlist значений Host из request.url. Если развёртывание принимает произвольные значения Host, проверяйте их перед вызовом обработчика. Локальным серверам за пределами Cloudflare Workers следует придерживаться рекомендаций upstream SDK по защите от DNS rebinding.
Совместимость с устаревшими клиентами
По умолчанию legacy: "stateless" параметр принимает обычные устаревшие инструменты, промпты и ресурсы. Этот вариант использует транспорт SDK v2 на основе веб-стандартов и не импортирует WorkerTransport.
Этот путь совместимости не обеспечивает полный транспорт сессии:
- Каждый POST создаёт новый сервер и транспорт.
- HTTP GET и DELETE возвращают
405. - Идентификатор сессии MCP не сохраняется.
- Отправленные запросы elicitation, sampling и roots немедленно завершаются ошибкой.
- Автономные потоки, возобновляемость, воспроизведение и удаление сессии недоступны.
- Опубликованные экспериментальные задачи не поддерживаются через этот путь.
Задайте legacy: "reject" для эндпойнта, работающего только без сохранения состояния. Во время миграции направляйте устаревшие клиенты, которым всё ещё нужны сессии протокола, на временный createLegacyMcpHandler или McpAgent дорожка.
Возвращаемое значение
createMcpHandler возвращает StatelessMcpHandler. Он вызываемый и предоставляет средства управления запросами и уведомлениями:
interface StatelessMcpHandler {
(request: Request, env: unknown, ctx: ExecutionContext): Promise<Response>;
fetch(
request: Request,
options?: McpHandlerRequestOptions,
): Promise<Response>;
notify: {
toolsChanged(): void;
promptsChanged(): void;
resourcesChanged(): void;
resourceUpdated(uri: string): void;
};
}
type McpHandlerRequestOptions = {
authInfo?: AuthInfo;
parsedBody?: unknown;
};Вызов обработчика
Вызовите обработчик из объекта Worker fetch() экспорт:
export default {
fetch(request, env, ctx) {
return createMcpHandler(createServer)(request, env, ctx);
},
} satisfies ExportedHandler;Не экспортируйте вызываемый объект напрямую как экспорт Worker по умолчанию. Wrangler считает экспорты функций по умолчанию WorkerEntrypoint классы.
Используйте fetch() когда другой фреймворк или уровень аутентификации уже разобрал или проверил данные запроса:
const response = await handler.fetch(request, {
authInfo,
parsedBody,
});authInfo передаётся фабрике сервера и обработчикам запросов. Обработчик не извлекает его из заголовков запроса и не проверяет токены доступа. parsedBody позволяет избежать повторного разбора тела JSON, которое уже было считано вышестоящим middleware.
Публикация изменений списков и ресурсов
notify методы публикуют типизированные события изменений в подходящие открытые subscriptions/listen потоки:
| Метод | MCP-уведомление |
|---|---|
notify.toolsChanged() |
notifications/tools/list_changed |
notify.promptsChanged() |
notifications/prompts/list_changed |
notify.resourcesChanged() |
notifications/resources/list_changed |
notify.resourceUpdated(uri) |
notifications/resources/updated |
Вызов notifier при отсутствии подходящей открытой подписки ничего не делает.
Используйте единый обработчик для уведомлений
Маршрутизация уведомлений принадлежит экземпляру обработчика. Создание нового обработчика внутри каждого Worker fetch() вызов подходит для обычных инструментов, промптов, ресурсов и MRTR elicitation. Он не может уведомить subscriptions/listen поток, принадлежащий более раннему экземпляру обработчика.
Создайте обработчик один раз в области видимости модуля при использовании notify или subscriptions/listen, затем вызовите его из экспорта объекта Worker:
const handler = createMcpHandler(createServer);
export default {
fetch(request, env, ctx) {
return handler(request, env, ctx);
},
} satisfies ExportedHandler;Уведомления привязаны к isolate. Уведомление, опубликованное в одном Worker isolate, не достигает потока подписки, выполняющегося в другом isolate.
createLegacyMcpHandler
createLegacyMcpHandler обслуживает сервер SDK v1 через WorkerTransport.
import {
createLegacyMcpHandler,
type CreateLegacyMcpHandlerOptions,
type LegacyMcpHandler,
} from "agents/mcp";
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
function createLegacyMcpHandler(
server: McpServer | Server,
options?: CreateLegacyMcpHandlerOptions,
): LegacyMcpHandler;Используйте этот обработчик только как временный мост миграции, если существующий эндпойнт SDK v1 всё ещё требует устаревших сессий, хранилища транспорта, повторного воспроизведения событий или push-запросов от сервера к клиенту.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { createLegacyMcpHandler } from "agents/mcp";
function createServer() {
return new McpServer({ name: "legacy-server", version: "1.0.0" });
}
export default {
async fetch(request, env, ctx) {
return createLegacyMcpHandler(createServer())(request, env, ctx);
},
};import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { createLegacyMcpHandler } from "agents/mcp";
function createServer() {
return new McpServer({ name: "legacy-server", version: "1.0.0" });
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
return createLegacyMcpHandler(createServer())(request, env, ctx);
},
} satisfies ExportedHandler<Env>;Передача сервера SDK v1 в createMcpHandler всё ещё работает, но выдаёт предупреждение об устаревании. Перенесите сервер на фабрику SDK v2 и передайте фабрику в createMcpHandler. Если sessionful-поведение мешает немедленной миграции, используйте createLegacyMcpHandler только на временном устаревшем пути.
experimental_createMcpHandler также устарел. Перенесите его сервер SDK v1 на фабрику SDK v2. Используйте createLegacyMcpHandler только как временный мост для поведения с поддержкой сессий.
CreateLegacyMcpHandlerOptions
CreateLegacyMcpHandlerOptions расширяет WorkerTransportOptions и добавляет следующие поля:
| Опция | Тип | По умолчанию | Описание |
|---|---|---|---|
route |
string |
"/mcp" |
Точный путь, обрабатываемый обработчиком |
authContext |
McpAuthContext |
Свойства контекста выполнения | Props приложения для обработчиков инструментов |
transport |
WorkerTransport |
Новый транспорт | Постоянный или заранее настроенный транспорт |
Типичные WorkerTransportOptions включают:
| Опция | Описание |
|---|---|
sessionIdGenerator |
Создаёт ID сеансов протокола |
enableJsonResponse |
Возвращает JSON вместо SSE, если это поддерживается |
storage |
Сохраняет состояние транспорта через { get, set } адаптер |
eventStore |
Сохраняет события для повторного воспроизведения и восстановления потока |
corsOptions |
Добавляет заголовки ответа CORS и preflight-запросов |
onsessioninitialized, onsessionclosed |
Отслеживать изменения жизненного цикла сессии |
Создавайте новый сервер SDK v1 для каждого запроса, если только вы не предоставляете постоянный транспорт, уже подключённый к этому серверу. Один сервер не может переподключаться к нескольким транспортам.
Контекст аутентификации
Совместимый @cloudflare/workers-oauth-provider предоставляет проверенные стандартные AuthInfo к колбэкам SDK v2 по адресу context.http.authInfo.
Существующий getMcpAuthContext() вспомогательная функция по-прежнему возвращает props приложения:
interface McpAuthContext {
props: Record<string, unknown>;
}import { getMcpAuthContext } from "agents/mcp/server";
server.registerTool(
"whoami",
{ description: "Return the current identity", inputSchema: {} },
async (_args, context) => {
const auth = getMcpAuthContext();
return {
content: [
{
type: "text",
text: JSON.stringify({
clientId: context.http?.authInfo?.clientId,
scopes: context.http?.authInfo?.scopes,
userId: auth?.props.userId,
}),
},
],
};
},
);import { getMcpAuthContext } from "agents/mcp/server";
server.registerTool(
"whoami",
{ description: "Return the current identity", inputSchema: {} },
async (_args, context) => {
const auth = getMcpAuthContext();
return {
content: [
{
type: "text",
text: JSON.stringify({
clientId: context.http?.authInfo?.clientId,
scopes: context.http?.authInfo?.scopes,
userId: auth?.props.userId,
}),
},
],
};
},
);Не записывайте в лог и не возвращайте authInfo.token или authInfo.extra.props.
Переход
См. Мигрировать на MCP SDK v2 перед изменением существующего сервера. Руководство по миграции описывает маршрутизацию между старой и новой версией, серверы с сохранением состояния, изменения на стороне клиента и проверки при развёртывании.