INTEGRITY Документация

Создайте MCP-сервер типа найди и выполни

Используйте openApiMcpServer() чтобы опубликовать большой сервис OpenAPI через два инструмента Model Context Protocol (MCP):

Документ OpenAPI остаётся вне контекста модели, если только код поиска не возвращает его часть. Аутентификация остаётся на стороне хостового Worker.

Предварительные требования

Вам понадобится проект Cloudflare Workers, документ OpenAPI 3.x и метод на стороне хоста для аутентификации API-запросов.

openApiMcpServer() в настоящее время возвращает сервер SDK v1. Обслуживайте его через явный устаревший createLegacyMcpHandler API.

Публикация сервиса

  1. Установите Code Mode и зависимости MCP:

    npm i @cloudflare/codemode agents @modelcontextprotocol/sdk zod
  2. Добавьте привязку 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"
  3. Загрузите документ OpenAPI на хосте. Создайте MCP-сервер с аутентифицированным request функция:

    src/server.js
    import { 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.ts
    import { 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>;
  4. Разверните Worker:

    npx wrangler deploy
  5. В клиенте MCP подключитесь к https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev/mcp. Включите bearer-токен, требуемый вашим Worker.

  6. Выведите список инструментов 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. Возвращайте только точечные идентификаторы, поля статуса, счётчики и ошибки, необходимые модели для следующего решения.