---
title: Build a search and execute MCP server
description: Create Code Mode search and execute MCP tools from an OpenAPI document while keeping credentials in the host Worker.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/agents/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Build a search and execute MCP server

Use `openApiMcpServer()` to publish a large OpenAPI service through two Model Context Protocol (MCP) tools:

* `search` runs model-written code against the OpenAPI document.
* `execute` adds a host-provided `codemode.request()` function.

The OpenAPI document stays outside the model context unless search code returns part of it. Authentication remains in the host Worker.

Warning

Code Mode is experimental and may have breaking changes. Use caution in production.

## Prerequisites

You need a Cloudflare Workers project, an OpenAPI 3.x document, and a host-side method for authenticating API requests.

## Publish the service

1. Install Code Mode and the MCP dependencies:  
 npm  yarn  pnpm  bun  
```  
npm i @cloudflare/codemode agents @modelcontextprotocol/sdk zod  
```  
```  
yarn add @cloudflare/codemode agents @modelcontextprotocol/sdk zod  
```  
```  
pnpm add @cloudflare/codemode agents @modelcontextprotocol/sdk zod  
```  
```  
bun add @cloudflare/codemode agents @modelcontextprotocol/sdk zod  
```
2. Add a Worker Loader binding and the `nodejs_compat` compatibility flag:

  * [  wrangler.jsonc ](#tab-panel-5865)
  * [  wrangler.toml ](#tab-panel-5866)  
JSONC  
```  
{  "$schema": "./node_modules/wrangler/config-schema.json",  "name": "openapi-codemode-mcp",  "main": "src/server.ts",  // Set this to today's date  "compatibility_date": "2026-06-24",  "compatibility_flags": [    "nodejs_compat"  ],  "worker_loaders": [    {      "binding": "LOADER"    }  ]}  
```  
TOML  
```  
name = "openapi-codemode-mcp"main = "src/server.ts"# Set this to today's datecompatibility_date = "2026-06-24"compatibility_flags = ["nodejs_compat"]  
[[worker_loaders]]binding = "LOADER"  
```
3. Load the OpenAPI document on the host. Create the MCP server with an authenticated `request` function:

  * [  JavaScript ](#tab-panel-5867)
  * [  TypeScript ](#tab-panel-5868)  
src/server.js  
```  
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.ts  
```  
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>;  
```
4. Deploy the Worker:  
 npm  yarn  pnpm  
```  
npx wrangler deploy  
```  
```  
yarn wrangler deploy  
```  
```  
pnpm wrangler deploy  
```
5. In an MCP client, connect to `https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev/mcp`. Include the bearer token required by your Worker.
6. List the MCP tools. Verify that the server exposes `search` and `execute`.

## Search the OpenAPI document

Call `search` before `execute`. Search code can inspect the document without making API requests:

JavaScript

```
async () => {  const spec = await codemode.spec();  return Object.entries(spec.paths)    .filter(([path]) => path.includes("/orders"))    .map(([path, operations]) => ({      path,      methods: Object.keys(operations),    }));};
```

Local OpenAPI `$ref` values resolve inside the sandbox when code calls `codemode.spec()`. External references remain unresolved.

## Call the API

The `execute` tool includes the same `codemode.spec()` method and the host-provided `codemode.request()` method:

JavaScript

```
async () => {  const response = await codemode.request({    method: "GET",    path: "/orders",    query: { status: "processing", limit: 20 },  });
  return response.items.map(({ id, status }) => ({ id, status }));};
```

The host callback receives `method`, `path`, optional `query`, optional `body`, optional `contentType`, and optional `rawBody` fields. For exact types, refer to the [openApiMcpServer() API](https://developers.cloudflare.com/agents/tools/codemode/api-reference/#openapimcpserver).

The `search` and `execute` tools use fixed example snippets. An optional `description` is appended to the `execute` tool description. This function does not use the `{{types}}` or `{{example}}` placeholders supported by [codeMcpServer()](https://developers.cloudflare.com/agents/model-context-protocol/guides/build-codemode-mcp-server/).

## Protect the API

The example reads the bearer token before creating the MCP server. Its request callback adds that token to outbound requests. The token never enters the sandbox.

`openApiMcpServer()` does not provide durable approval for each request inside `execute`. Enforce authorization and any required per-operation approval in the host callback before applying side effects. Validate paths instead of accepting arbitrary origins.

Do not include secrets in the OpenAPI document or API results. Both are available to model-written code.

`DynamicWorkerExecutor` blocks direct external `fetch()` and `connect()` calls by default. Generated code reaches the service only through the host request callback.

## Limit results

Have model-written code select, map, aggregate, or paginate data before returning. The publisher limits final MCP responses to approximately 6,000 estimated tokens and marks truncated responses with `--- TRUNCATED ---`.

Truncation does not reduce API work already performed. Return focused identifiers, status fields, counts, and errors that support the model's next decision.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/agents/model-context-protocol/guides/build-codemode-openapi-mcp-server/#page","headline":"Build a search and execute MCP server · Cloudflare Agents docs","description":"Create Code Mode search and execute MCP tools from an OpenAPI document while keeping credentials in the host Worker.","url":"https://developers.cloudflare.com/agents/model-context-protocol/guides/build-codemode-openapi-mcp-server/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-06-24","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"},"keywords":["AI","MCP"]}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/agents/","name":"Agents"}},{"@type":"ListItem","position":3,"item":{"@id":"/agents/model-context-protocol/","name":"Model Context Protocol (MCP)"}},{"@type":"ListItem","position":4,"item":{"@id":"/agents/model-context-protocol/guides/","name":"Guides"}},{"@type":"ListItem","position":5,"item":{"@id":"/agents/model-context-protocol/guides/build-codemode-openapi-mcp-server/","name":"Build a search and execute MCP server"}}]}
```
