openApiMcpServer() を使うと、大規模な OpenAPI サービスを、次の 2 つの Model Context Protocol (MCP) ツールとして公開できます。
searchは、モデルが書いたコードを OpenAPI ドキュメントに対して実行します。executeは、ホストが提供するcodemode.request()関数を追加します。
OpenAPI ドキュメントは、search コードが一部を返さない限り、モデルのコンテキストには入りません。認証はホスト Worker 側に残ります。
Cloudflare Workers プロジェクト、OpenAPI 3.x ドキュメント、API リクエストを認証するホスト側の手段が必要です。
openApiMcpServer() は現時点で SDK v1 サーバーを返します。明示的なレガシー API である createLegacyMcpHandler 経由で提供してください。
-
Code Mode と MCP の依存関係をインストールします。
npm i @cloudflare/codemode agents @modelcontextprotocol/sdk zodyarn add @cloudflare/codemode agents @modelcontextprotocol/sdk zodpnpm add @cloudflare/codemode agents @modelcontextprotocol/sdk zodbun add @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-09-20", "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-09-20" compatibility_flags = ["nodejs_compat"] [[worker_loaders]] binding = "LOADER" -
ホスト側で OpenAPI ドキュメントを読み込みます。認証済みの
request関数で MCP サーバーを作成します。src/server.jsjs 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.tsts 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>; -
Worker をデプロイします。
npx wrangler deployyarn wrangler deploypnpm wrangler deploy -
MCP クライアントで
https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev/mcpに接続します。Worker が要求する bearer トークンを含めます。 -
MCP ツールを一覧します。サーバーが
searchとexecuteを公開していることを確認します。
execute の前に search を呼び出します。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),
}));
};コードが codemode.spec() を呼ぶと、ローカルの OpenAPI $ref はサンドボックス内で解決されます。外部参照は未解決のままです。
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 }));
};ホストのコールバックは method、path、省略可能な query、省略可能な body、省略可能な contentType、省略可能な rawBody を受け取ります。正確な型は openApiMcpServer() API を参照してください。
search と execute ツールは、固定のサンプルスニペットを使います。省略可能な description は、execute ツールの説明に追記されます。この関数は、codeMcpServer() が対応する {{types}} や {{example}} プレースホルダーは使いません。
この例では、MCP サーバーを作成する前に bearer トークンを読み取ります。リクエストコールバックは、そのトークンを送信リクエストに付けます。トークンはサンドボックスに入りません。
openApiMcpServer() は、execute 内の各リクエストに対する耐久的な承認は提供しません。副作用を適用する前に、ホストのコールバックで認可と、必要な操作ごとの承認を強制してください。任意の origin を受け入れるのではなく、パスを検証してください。
シークレットを OpenAPI ドキュメントや API 結果に含めないでください。どちらも、モデルが書いたコードから参照できます。
DynamicWorkerExecutor は、既定で外部への直接 fetch() と connect() をブロックします。生成コードは、ホストのリクエストコールバック経由でのみサービスに到達します。
モデルが書いたコードで、返す前にデータの選択、マップ、集計、ページネーションを行ってください。公開側は最終的な MCP 応答を、推定トークン約 6,000 に制限し、切り詰められた応答には --- TRUNCATED --- を付けます。
切り詰めは、すでに実行した API 作業を減らしません。モデルの次の判断に必要な識別子、ステータスフィールド、件数、エラーに絞って返してください。