Example: Petstore server
A complete, runnable Code Mode MCP server for the Swagger Petstore API, from wiring to a live tools/call.
Example: Petstore server
A complete worker that exposes the Swagger Petstore API to an agent as search + execute, served over MCP. Full source: examples/petstore.
The worker
import { exports } from "cloudflare:workers";
import {
McpServer,
WebStandardStreamableHTTPServerTransport,
} from "@modelcontextprotocol/server";
import {
createGate,
processSpec,
registerCodemodeTools,
type ToolRegistrar,
type WorkerLoaderLike,
} from "codemode-workers";
interface Env {
LOADER: WorkerLoaderLike;
PETSTORE_API_KEY?: string; // optional; injected as the api_key header when set
}
const API_BASE = "https://petstore3.swagger.io/api/v3";
const SPEC_URL = "https://petstore3.swagger.io/api/v3/openapi.json";
export const Gate = createGate({ allowedHosts: [new URL(API_BASE).hostname] });
// Fetch + reduce the spec once per isolate, not on every search call.
let catalog: Promise<unknown> | undefined;
function getCatalog(): Promise<unknown> {
return (catalog ??= fetchCatalog());
}
async function fetchCatalog(): Promise<unknown> {
return processSpec(
(await (await fetch(SPEC_URL)).json()) as Record<string, unknown>,
);
}
function buildServer(env: Env): McpServer {
const server = new McpServer({ name: "petstore-codemode", version: "0.1.0" });
registerCodemodeTools(server as unknown as ToolRegistrar, {
loader: env.LOADER,
catalog: {
get: getCatalog,
description: "Swagger Petstore OpenAPI catalog: spec.paths[path][method].",
},
api: {
baseUrl: API_BASE,
outbound: () =>
(exports as Record<string, (options: unknown) => unknown>).Gate?.({
props: env.PETSTORE_API_KEY
? { headers: { api_key: env.PETSTORE_API_KEY } }
: {},
}),
description: "Swagger Petstore v3.",
},
timeoutMs: 10_000,
});
return server;
}
export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext,
): Promise<Response> {
if (new URL(request.url).pathname !== "/mcp") {
return new Response("POST JSON-RPC to /mcp.", { status: 404 });
}
// Stateless Streamable HTTP: a fresh server + transport per request.
const server = buildServer(env);
const transport = new WebStandardStreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
await server.connect(transport);
const response = await transport.handleRequest(request);
ctx.waitUntil(transport.close());
return response;
},
};
A few things worth noting:
buildServerruns per request and the transport is stateless (sessionIdGenerator: undefined), so there is nothing to keep alive between calls.- The catalog is fetched and reduced once per isolate, then reused.
api_keyis injected only when the secret is set, so the public read endpoints work with no key at all.timeoutMsbounds how long agent code can run.- The
/mcpendpoint has no auth. That is fine for the public Petstore, but put auth in front before you set a real key or deploy publicly.
wrangler.jsonc
{
"name": "petstore-codemode",
"main": "worker.ts",
"compatibility_date": "2026-01-12",
"compatibility_flags": ["nodejs_compat"],
"worker_loaders": [{ "binding": "LOADER" }],
"services": [
{
"binding": "GATE_SELF",
"service": "petstore-codemode",
"entrypoint": "Gate",
},
],
}
LOADER runs agent code in fresh isolates. The GATE_SELF service binding, pointing back at this same worker, is what lets exports.Gate resolve as the execute isolate’s outbound.
Run it
cd examples/petstore
wrangler dev
List the tools with the MCP Inspector:
npx @modelcontextprotocol/inspector --cli http://localhost:8787/mcp --method tools/list
# -> search, execute
A live session
First the agent uses search to find an endpoint. This runs over the baked-in catalog with no network:
// tools/call search
async () =>
Object.entries(spec.paths)
.flatMap(([p, m]) =>
Object.keys(m)
.filter((x) => x === "get")
.map((x) => x.toUpperCase() + " " + p),
)
.slice(0, 6);
[
"GET /pet/findByStatus",
"GET /pet/findByTags",
"GET /pet/{petId}",
"GET /store/inventory",
"GET /store/order/{orderId}",
"GET /user/login"
]
Then execute calls the live API through the gate, which injects the key (if any) and allows only the Petstore host:
// tools/call execute
async () => {
const r = await api.request({
method: "GET",
path: "/pet/findByStatus",
query: { status: "available" },
});
return { status: r.status, count: r.data.length };
};
{ "status": 200, "count": 0 }
The agent never saw a token and could not have reached any host other than petstore3.swagger.io.
Adapt it to your API
Change four things:
API_BASEandSPEC_URLto your API and its OpenAPI spec.allowedHostsincreateGateto your API’s host.- The
props.headersinoutboundto your auth scheme (Authorization: Bearer ...,api_key, and so on). - The catalog
descriptionto hint the agent at your spec’s shape.
The sandboxing, the gate, and the transport stay the same. See examples/urantia for a second worker built the same way.