使用 openApiMcpServer() 通过两个 Model Context Protocol (MCP) tool 发布大型 OpenAPI 服务:
search针对 OpenAPI 文档运行模型编写的代码。execute添加宿主提供的codemode.request()函数。
OpenAPI 文档保持在模型上下文之外,除非搜索代码返回其中一部分。认证保留在宿主 Worker。
需要 Cloudflare Workers 项目、OpenAPI 3.x 文档,以及宿主侧认证 API 请求的方法。
-
安装 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-08-17", "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-17" compatibility_flags = ["nodejs_compat"] [[worker_loaders]] binding = "LOADER" -
在 host 加载 OpenAPI 文档。用已认证的
request函数创建 MCP 服务器:src/server.jsjs import { DynamicWorkerExecutor } from "@cloudflare/codemode"; import { openApiMcpServer } from "@cloudflare/codemode/mcp"; import { createMcpHandler } 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.status === 204) return null; const responseType = response.headers.get("Content-Type") ?? ""; const result = responseType.includes("application/json") ? await response.json() : await response.text(); if (!response.ok) { throw new Error(`API request failed: ${response.status}`); } return result; }, }); return createMcpHandler(server, { route: "/mcp" })(request, env, ctx); }, };src/server.tsts import { DynamicWorkerExecutor } from "@cloudflare/codemode"; import { openApiMcpServer } from "@cloudflare/codemode/mcp"; import { createMcpHandler } 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.status === 204) return null; const responseType = response.headers.get("Content-Type") ?? ""; const result = responseType.includes("application/json") ? await response.json() : await response.text(); if (!response.ok) { throw new Error(`API request failed: ${response.status}`); } return result; }, }); return createMcpHandler(server, { route: "/mcp" })( request, env, ctx, ); }, } satisfies ExportedHandler<Env>; -
部署 Worker:
npx wrangler deployyarn wrangler deploypnpm wrangler deploy -
在 MCP client 中连接到
https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev/mcp。包含 Worker 所需的 bearer token。 -
列出 MCP tool。验证服务器暴露
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 tool 包含相同的 codemode.spec() 方法与 host 提供的 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 回调接收 method、path、可选 query、可选 body、可选 contentType 与可选 rawBody 字段。精确类型请参阅 openApiMcpServer() API。
search 与 execute tool 使用固定示例 snippet。可选 description 追加到 execute tool 描述。此函数不使用 codeMcpServer() 支持的 {{types}} 或 {{example}} 占位符。
示例在创建 MCP 服务器前读取 bearer token。其 request 回调将该 token 加入出站请求。Token 永不进入沙箱。
openApiMcpServer() 不为 execute 内每次请求提供持久审批。在应用副作用前于宿主回调强制执行授权及任何所需的按操作审批。验证路径而非接受任意来源。
不要在 OpenAPI 文档或 API 结果中包含 secret。两者对模型编写的代码可用。
DynamicWorkerExecutor 默认阻止直接外部 fetch() 与 connect()。生成的代码仅通过 host request 回调访问服务。
让模型编写的代码在返回前 select、map、aggregate 或 paginate 数据。发布者将最终 MCP 响应限制在约 6,000 估计 token,截断响应标记 --- TRUNCATED ---。
截断不减少已执行的 API 工作。返回支持模型下一决策的聚焦标识符、状态字段、计数与错误。