Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

search と execute の MCP サーバーを構築する

最終更新 Markdown で表示Agent セットアップ

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 経由で提供してください。

サービスを公開する

  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-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"
  3. ホスト側で 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>;
  4. Worker をデプロイします。

    npx wrangler deploy
  5. MCP クライアントで https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev/mcp に接続します。Worker が要求する bearer トークンを含めます。

  6. MCP ツールを一覧します。サーバーが searchexecute を公開していることを確認します。

OpenAPI ドキュメントを検索する

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 はサンドボックス内で解決されます。外部参照は未解決のままです。

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 }));
};

ホストのコールバックは methodpath、省略可能な query、省略可能な body、省略可能な contentType、省略可能な rawBody を受け取ります。正確な型は openApiMcpServer() API を参照してください。

searchexecute ツールは、固定のサンプルスニペットを使います。省略可能な description は、execute ツールの説明に追記されます。この関数は、codeMcpServer() が対応する {{types}}{{example}} プレースホルダーは使いません。

API を保護する

この例では、MCP サーバーを作成する前に bearer トークンを読み取ります。リクエストコールバックは、そのトークンを送信リクエストに付けます。トークンはサンドボックスに入りません。

openApiMcpServer() は、execute 内の各リクエストに対する耐久的な承認は提供しません。副作用を適用する前に、ホストのコールバックで認可と、必要な操作ごとの承認を強制してください。任意の origin を受け入れるのではなく、パスを検証してください。

シークレットを OpenAPI ドキュメントや API 結果に含めないでください。どちらも、モデルが書いたコードから参照できます。

DynamicWorkerExecutor は、既定で外部への直接 fetch()connect() をブロックします。生成コードは、ホストのリクエストコールバック経由でのみサービスに到達します。

結果を絞り込む

モデルが書いたコードで、返す前にデータの選択、マップ、集計、ページネーションを行ってください。公開側は最終的な MCP 応答を、推定トークン約 6,000 に制限し、切り詰められた応答には --- TRUNCATED --- を付けます。

切り詰めは、すでに実行した API 作業を減らしません。モデルの次の判断に必要な識別子、ステータスフィールド、件数、エラーに絞って返してください。

役に立ちましたか?