← Создание агентов на Cloudflare / agents / model-context-protocol / guides
Создайте MCP-сервер типа найди и выполни
Используйте openApiMcpServer() чтобы опубликовать большой сервис OpenAPI через два инструмента Model Context Protocol (MCP):
searchвыполняет написанный моделью код на основе документа OpenAPI.executeдобавляет предоставленный хостомcodemode.request()функцию.
Документ OpenAPI остаётся вне контекста модели, если только код поиска не возвращает его часть. Аутентификация остаётся на стороне хостового Worker.
Предварительные требования
Вам понадобится проект Cloudflare Workers, документ OpenAPI 3.x и метод на стороне хоста для аутентификации API-запросов.
openApiMcpServer() в настоящее время возвращает сервер SDK v1. Обслуживайте его через явный устаревший createLegacyMcpHandler API.
Публикация сервиса
-
Установите Code Mode и зависимости MCP:
npm i @cloudflare/codemode agents @modelcontextprotocol/sdk zod -
Добавьте привязку Worker Loader и
nodejs_compatфлаг совместимости:{ "$schema": "./node_modules/wrangler/config-schema.json", "name": "openapi-codemode-mcp", "main": "src/server.ts", // Set this to today's date "compatibility_date": "2026-08-28", "compatibility_flags": [ "nodejs_compat" ], "worker_loaders": [ { "binding": "LOADER" } ] }name = "openapi-codemode-mcp" main = "src/server.ts" # Set this to today's date compatibility_date = "2026-08-28" compatibility_flags = ["nodejs_compat"] [[worker_loaders]] binding = "LOADER" -
Загрузите документ OpenAPI на хосте. Создайте MCP-сервер с аутентифицированным
requestфункция:src/server.jsimport { DynamicWorkerExecutor } from "@cloudflare/codemode"; import { openApiMcpServer } from "@cloudflare/codemode/mcp"; import { createLegacyMcpHandler } from "agents/mcp"; const SPEC_URL = "https://api.example.com/openapi.json"; const API_ORIGIN = "https://api.example.com"; let specCache; async function loadSpec() { if (specCache) return specCache; const response = await fetch(SPEC_URL); if (!response.ok) { throw new Error(`OpenAPI request failed: ${response.status}`); } specCache = await response.json(); return specCache; } export default { async fetch(request, env, ctx) { const authorization = request.headers.get("Authorization"); if (!authorization?.startsWith("Bearer ")) { return new Response("Bearer token required", { status: 401 }); } const server = openApiMcpServer({ spec: await loadSpec(), executor: new DynamicWorkerExecutor({ loader: env.LOADER }), name: "example-api", version: "1.0.0", request: async (options) => { if (!options.path.startsWith("/")) { throw new Error("API path must start with a slash"); } const url = new URL(`${API_ORIGIN}${options.path}`); for (const [key, value] of Object.entries(options.query ?? {})) { if (value !== undefined) { url.searchParams.set(key, String(value)); } } const headers = { Authorization: authorization }; if (options.contentType) { headers["Content-Type"] = options.contentType; } else if (options.body !== undefined) { headers["Content-Type"] = "application/json"; } const response = await fetch(url, { method: options.method, headers, body: options.body === undefined ? undefined : options.rawBody ? options.body : JSON.stringify(options.body), }); if (!response.ok) { throw new Error(`API request failed: ${response.status}`); } if (response.status === 204) return null; const responseType = response.headers.get("Content-Type") ?? ""; return responseType.includes("application/json") ? await response.json() : await response.text(); }, }); return createLegacyMcpHandler(server, { route: "/mcp" })(request, env, ctx); }, };src/server.tsimport { DynamicWorkerExecutor } from "@cloudflare/codemode"; import { openApiMcpServer } from "@cloudflare/codemode/mcp"; import { createLegacyMcpHandler } from "agents/mcp"; const SPEC_URL = "https://api.example.com/openapi.json"; const API_ORIGIN = "https://api.example.com"; let specCache: Record<string, unknown> | undefined; async function loadSpec(): Promise<Record<string, unknown>> { if (specCache) return specCache; const response = await fetch(SPEC_URL); if (!response.ok) { throw new Error(`OpenAPI request failed: ${response.status}`); } specCache = (await response.json()) as Record<string, unknown>; return specCache; } export default { async fetch(request, env, ctx): Promise<Response> { const authorization = request.headers.get("Authorization"); if (!authorization?.startsWith("Bearer ")) { return new Response("Bearer token required", { status: 401 }); } const server = openApiMcpServer({ spec: await loadSpec(), executor: new DynamicWorkerExecutor({ loader: env.LOADER }), name: "example-api", version: "1.0.0", request: async (options) => { if (!options.path.startsWith("/")) { throw new Error("API path must start with a slash"); } const url = new URL(`${API_ORIGIN}${options.path}`); for (const [key, value] of Object.entries(options.query ?? {})) { if (value !== undefined) { url.searchParams.set(key, String(value)); } } const headers: Record<string, string> = { Authorization: authorization }; if (options.contentType) { headers["Content-Type"] = options.contentType; } else if (options.body !== undefined) { headers["Content-Type"] = "application/json"; } const response = await fetch(url, { method: options.method, headers, body: options.body === undefined ? undefined : options.rawBody ? (options.body as string) : JSON.stringify(options.body), }); if (!response.ok) { throw new Error(`API request failed: ${response.status}`); } if (response.status === 204) return null; const responseType = response.headers.get("Content-Type") ?? ""; return responseType.includes("application/json") ? await response.json() : await response.text(); }, }); return createLegacyMcpHandler(server, { route: "/mcp" })( request, env, ctx, ); }, } satisfies ExportedHandler<Env>; -
Разверните Worker:
npx wrangler deploy -
В клиенте MCP подключитесь к
https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev/mcp. Включите bearer-токен, требуемый вашим Worker. -
Выведите список инструментов MCP. Убедитесь, что сервер предоставляет
searchиexecute.
Выполните поиск в документе OpenAPI
Вызовите search перед execute. Код search может проверять документ, не выполняя запросов к API:
async () => {
const spec = await codemode.spec();
return Object.entries(spec.paths)
.filter(([path]) => path.includes("/orders"))
.map(([path, operations]) => ({
path,
methods: Object.keys(operations),
}));
};Локальный OpenAPI $ref значения разрешаются внутри песочницы, когда код вызывает codemode.spec(). Внешние ссылки остаются неразрешёнными.
Вызов API
execute инструмент включает те же codemode.spec() метод и предоставленный хостом codemode.request() метод:
async () => {
const response = await codemode.request({
method: "GET",
path: "/orders",
query: { status: "processing", limit: 20 },
});
return response.items.map(({ id, status }) => ({ id, status }));
};Host callback получает method, path, необязательный query, необязательный body, необязательный contentType, и необязательный rawBody полей. Точные типы см. в openApiMcpServer() API.
search и execute инструменты используют фиксированные примеры сниппетов. Необязательный description добавляется к execute описания инструмента. Эта функция не использует {{types}} или {{example}} плейсхолдеры, поддерживаемые codeMcpServer().
Защита API
Пример считывает bearer token до создания MCP-сервера. Его callback запроса добавляет этот токен к исходящим запросам. Токен никогда не попадает в sandbox.
openApiMcpServer() не обеспечивает устойчивое подтверждение для каждого запроса внутри execute. Применяйте авторизацию и любое необходимое подтверждение для каждой операции в колбэке хоста до применения побочных эффектов. Проверяйте пути вместо того, чтобы принимать произвольные origins.
Не включайте секреты в документ OpenAPI или в результаты API. То и другое доступно коду, написанному моделью.
DynamicWorkerExecutor блокирует прямой внешний fetch() и connect() вызовы по умолчанию. Сгенерированный код обращается к сервису только через колбэк запроса хоста.
Ограничение результатов
Пусть код, написанный моделью, выбирает, преобразует, агрегирует или пагинирует данные перед их возвратом. Издатель ограничивает конечные ответы MCP примерно 6000 расчётными токенами и помечает усечённые ответы --- TRUNCATED ---.
Усечение не уменьшает уже выполненную работу API. Возвращайте только точечные идентификаторы, поля статуса, счётчики и ошибки, необходимые модели для следующего решения.