OpenApiConnector を使うと、耐久性のある Code Mode ランタイム内で OpenAPI サービスを公開できます。コネクターは、OpenAPI ドキュメントの各オペレーションからサンドボックスメソッドを 1 つ導出します。
モデルは codemode.search() でメソッドを発見し、codemode.describe() で絞った入力型を取得できます。OpenAPI ドキュメント全体をモデルのコンテキストに入れる必要はありません。
このページは、エージェントが OpenAPI サービスを利用する場合です。search と execute 経由で外部 MCP クライアントへ OpenAPI サービスを公開するには、search と execute の MCP サーバーを構築する を参照してください。
耐久性のある Code Mode ランタイム を設定したプロジェクトが必要です。ランタイムのセットアップで、このガイドが使う Worker Loader バインディングと CodemodeRuntime のエクスポートが用意されます。
-
プロジェクトに OpenAPI ドキュメントを追加します。各オペレーションに一意の
operationIdを付け、安定したサンドボックスメソッド名にします。src/orders-openapi.jsjs export const ordersOpenApiSpec = { openapi: "3.1.0", info: { title: "Orders API", version: "1.0.0" }, paths: { "/orders/{orderId}": { get: { operationId: "get_order", summary: "Get an order by ID.", parameters: [ { name: "orderId", in: "path", required: true, schema: { type: "string" }, }, ], }, }, "/orders": { post: { operationId: "create_order", summary: "Create an order.", requestBody: { required: true, content: { "application/json": { schema: { type: "object", properties: { productId: { type: "string" }, quantity: { type: "integer" }, }, required: ["productId", "quantity"], }, }, }, }, }, }, }, };src/orders-openapi.tsts export const ordersOpenApiSpec = { openapi: "3.1.0", info: { title: "Orders API", version: "1.0.0" }, paths: { "/orders/{orderId}": { get: { operationId: "get_order", summary: "Get an order by ID.", parameters: [ { name: "orderId", in: "path", required: true, schema: { type: "string" }, }, ], }, }, "/orders": { post: { operationId: "create_order", summary: "Create an order.", requestBody: { required: true, content: { "application/json": { schema: { type: "object", properties: { productId: { type: "string" }, quantity: { type: "integer" }, }, required: ["productId", "quantity"], }, }, }, }, }, }, }, } as const; -
コネクターを作成します。ドキュメントを返す
spec()と、ホスト側で認証付きリクエストを行うrequest()を実装します。src/orders-connector.jsjs import { OpenApiConnector } from "@cloudflare/codemode"; import { ordersOpenApiSpec } from "./orders-openapi"; const API_ORIGIN = "https://api.example.com"; export class OrdersConnector extends OpenApiConnector { name() { return "orders"; } instructions() { return "Use for reading and creating orders."; } spec() { return ordersOpenApiSpec; } async request(options) { if (!options.path.startsWith("/")) { throw new Error("Orders API path must start with a slash"); } const url = new URL(options.path, API_ORIGIN); for (const [key, value] of Object.entries(options.params ?? {})) { if (value !== undefined) { url.searchParams.set(key, String(value)); } } const response = await fetch(url, { method: options.method ?? "GET", headers: { ...(options.body !== undefined ? { "Content-Type": "application/json" } : {}), ...options.headers, Authorization: `Bearer ${this.env.ORDERS_API_TOKEN}`, }, body: options.body === undefined ? undefined : JSON.stringify(options.body), }); if (!response.ok) { throw new Error(`Orders API request failed: ${response.status}`); } if (response.status === 204) return null; return response.json(); } tool(name, tool) { if (name === "create_order") { return { ...tool, requiresApproval: true }; } return tool; } }src/orders-connector.tsts import { OpenApiConnector, type ConnectorTool, type OpenApiRequestOptions, } from "@cloudflare/codemode"; import { ordersOpenApiSpec } from "./orders-openapi"; const API_ORIGIN = "https://api.example.com"; export class OrdersConnector extends OpenApiConnector<Env> { override name() { return "orders"; } protected override instructions() { return "Use for reading and creating orders."; } protected override spec() { return ordersOpenApiSpec; } protected override async request(options: OpenApiRequestOptions) { if (!options.path.startsWith("/")) { throw new Error("Orders API path must start with a slash"); } const url = new URL(options.path, API_ORIGIN); for (const [key, value] of Object.entries(options.params ?? {})) { if (value !== undefined) { url.searchParams.set(key, String(value)); } } const response = await fetch(url, { method: options.method ?? "GET", headers: { ...(options.body !== undefined ? { "Content-Type": "application/json" } : {}), ...options.headers, Authorization: `Bearer ${this.env.ORDERS_API_TOKEN}`, }, body: options.body === undefined ? undefined : JSON.stringify(options.body), }); if (!response.ok) { throw new Error(`Orders API request failed: ${response.status}`); } if (response.status === 204) return null; return response.json(); } protected override tool(name: string, tool: ConnectorTool): ConnectorTool { if (name === "create_order") { return { ...tool, requiresApproval: true }; } return tool; } }認証情報はホスト Worker に残ります。モデルが書いたコードが受け取るのはコネクターメソッドとその結果であり、
ORDERS_API_TOKENではありません。tool()フックは導出したオペレーションを装飾します。この例では、create_orderの実行前に承認を求めます。フックでリプレイやロールバックの振る舞いを追加することもできます。 -
コネクターをインポートし、ランタイムに追加します。
src/server.jsjs import { AIChatAgent } from "@cloudflare/ai-chat"; import { createCodemodeRuntime, DynamicWorkerExecutor, } from "@cloudflare/codemode"; import { OrdersConnector } from "./orders-connector"; export class Chat extends AIChatAgent { #runtime() { return createCodemodeRuntime({ ctx: this.ctx, executor: new DynamicWorkerExecutor({ loader: this.env.LOADER }), connectors: [new OrdersConnector(this.ctx, this.env)], }); } async onChatMessage() { const tools = { codemode: this.#runtime().tool() }; // Pass tools to your model call. } }src/server.tsts import { AIChatAgent } from "@cloudflare/ai-chat"; import { createCodemodeRuntime, DynamicWorkerExecutor, } from "@cloudflare/codemode"; import { OrdersConnector } from "./orders-connector"; export class Chat extends AIChatAgent<Env> { #runtime() { return createCodemodeRuntime({ ctx: this.ctx, executor: new DynamicWorkerExecutor({ loader: this.env.LOADER }), connectors: [new OrdersConnector(this.ctx, this.env)], }); } async onChatMessage() { const tools = { codemode: this.#runtime().tool() }; // Pass tools to your model call. } } -
モデルにオペレーションを発見させ、生成されたコネクターメソッドを呼び出させます。
async () => { const matches = await codemode.search("get an order by ID"); const docs = await codemode.describe(matches.results[0].path); const order = await orders.get_order({ orderId: "order-123" }); return { docs, order }; };
OpenApiConnector は、サニタイズした operationId をメソッド名に使います。オペレーションに operationId がない場合は、HTTP メソッドとパスから名前を導出します。メソッド名を安定させ、衝突を避けるために、一意のオペレーション ID を定義します。
生成される各メソッドは、オブジェクトを 1 つ受け取ります。
- パス、クエリ、ヘッダーのパラメーターはトップレベルのフィールドになります。
- JSON リクエストボディは
bodyの下に置かれます。 - 必須の OpenAPI パラメーターは、必須の TypeScript フィールドになります。
- 入力スキーマ内のローカル
$refは、型生成前に解決されます。
コネクターはパスパラメーターを代入し、正規化した { path, method, params, body, headers } オブジェクトを request() へ渡します。
現在のコネクターは入力型を導出しますが、OpenAPI のレスポンススキーマからレスポンス型は導出しません。そのため、生成メソッドの戻り値は unknown です。別のコネクター実装で、より具体的な宣言を付ける場合を除きます。
すべての OpenAPI コネクターは、低レベルの request() サンドボックスメソッドも公開します。OpenAPI ドキュメントに、モデルが必要とするオペレーションが無いときに使います。
const result = await orders.request({
path: "/orders",
method: "GET",
params: { status: "processing" },
});使えるときは、導出されたオペレーションメソッドを優先します。発見しやすい説明と、生成された入力型が付きます。
exposeSpec() のデフォルト戻り値は false です。モデルが書いたコードが生の OpenAPI ドキュメントにアクセスする必要があるときだけ、true を返すようオーバーライドします。大きなドキュメントは、大きな結果と耐久ログエントリを生みます。