Skip to content
Cloudflare Docs

createMcpHandler — API Reference

The createMcpHandler function creates a fetch handler to serve your MCP server. You can use it as a lightweight alternative to the McpAgent class.

It uses an implementation of the MCP Transport interface, WorkerTransport, built on top of web standards, which conforms to the streamable-http transport specification.

TypeScript
import { createMcpHandler, type CreateMcpHandlerOptions } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
function createMcpHandler(
server: McpServer,
options?: CreateMcpHandlerOptions,
): (request: Request, env: unknown, ctx: ExecutionContext) => Promise<Response>;

Parameters

  • server — An instance of McpServer from the @modelcontextprotocol/sdk package
  • options — Optional configuration object (see CreateMcpHandlerOptions)

Returns

A Worker fetch handler function with the signature (request: Request, env: unknown, ctx: ExecutionContext) => Promise<Response>.

CreateMcpHandlerOptions

Configuration options for creating an MCP handler.

TypeScript
interface CreateMcpHandlerOptions extends WorkerTransportOptions {
/**
* The route path that this MCP handler should respond to.
* If specified, the handler will only process requests that match this route.
* @default "/mcp"
*/
route?: string;
/**
* An optional auth context to use for handling MCP requests.
* If not provided, the handler will look for props in the execution context.
*/
authContext?: McpAuthContext;
/**
* An optional transport to use for handling MCP requests.
* If not provided, a WorkerTransport will be created with the provided WorkerTransportOptions.
*/
transport?: WorkerTransport;
// Inherited from WorkerTransportOptions:
sessionIdGenerator?: () => string;
enableJsonResponse?: boolean;
onsessioninitialized?: (sessionId: string) => void;
corsOptions?: CORSOptions;
storage?: MCPStorageApi;
}

Options

route

The URL path where the MCP handler responds. Requests to other paths return a 404 response.

Default: "/mcp"

JavaScript
const handler = createMcpHandler(server, {
route: "/api/mcp", // Only respond to requests at /api/mcp
});

authContext

An authentication context object that will be available to MCP tools via getMcpAuthContext().

When using the OAuthProvider from @cloudflare/workers-oauth-provider, the auth context is automatically populated with information from the OAuth flow. You typically don't need to set this manually.

transport

A custom WorkerTransport instance. If not provided, a new transport is created on every request.

JavaScript
import { createMcpHandler, WorkerTransport } from "agents/mcp";
const transport = new WorkerTransport({
sessionIdGenerator: () => "my-session-id",
storage: {
get: () => myStorage.get("transport-state"),
set: (state) => myStorage.put("transport-state", state),
},
});
const handler = createMcpHandler(server, { transport });

Stateless MCP Servers

Many MCP Servers are stateless, meaning they don't maintain any session state between requests. The createMcpHandler function is a lightweight alternative to the McpAgent class that can be used to serve an MCP server straight from a Worker. View the complete example on GitHub.

JavaScript
import { createMcpHandler } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
const server = new McpServer({
name: "Hello MCP Server",
version: "1.0.0",
});
server.tool(
"hello",
"Returns a greeting message",
{ name: z.string().optional() },
async ({ name }) => {
return {
content: [
{
text: `Hello, ${name ?? "World"}!`,
type: "text",
},
],
};
},
);
export default {
fetch: async (request, env, ctx) => {
const handler = createMcpHandler(server);
return handler(request, env, ctx);
},
};

Each request to this MCP server creates a new session. The server doesn't maintain state between requests. This is the simplest way to implement an MCP server.

Stateful MCP Servers

For stateful MCP servers that need to maintain session state across multiple requests, you can use the createMcpHandler function with a WorkerTransport instance directly in an Agent. This is useful if you want to make use of advanced client features like elicitation and sampling.

Provide a custom WorkerTransport with persistent storage. View the complete example on GitHub.

JavaScript
import { Agent } from "agents";
import { createMcpHandler, WorkerTransport } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const STATE_KEY = "mcp-transport-state";
export class MyStatefulMcpAgent extends Agent {
server = new McpServer({
name: "Stateful MCP Server",
version: "1.0.0",
});
transport = new WorkerTransport({
sessionIdGenerator: () => this.name,
storage: {
get: () => {
return this.ctx.storage.kv.get(STATE_KEY);
},
set: (state) => {
this.ctx.storage.kv.put(STATE_KEY, state);
},
},
});
async onMcpRequest(request) {
return createMcpHandler(this.server, {
transport: this.transport,
})(request, this.env, {});
}
}

In this case we are defining the sessionIdGenerator to return the Agent name as the session ID. To make sure we route to the correct Agent we can use getAgentByName in the Worker handler:

JavaScript
import { getAgentByName } from "agents";
export default {
async fetch(request, env, ctx) {
// Extract session ID from header or generate a new one
const sessionId =
request.headers.get("mcp-session-id") ?? crypto.randomUUID();
// Get the Agent instance by name/session ID
const agent = await getAgentByName(env.MyStatefulMcpAgent, sessionId);
// Route the MCP request to the agent
return await agent.onMcpRequest(request);
},
};

With persistent storage, the transport preserves:

  • Session ID across reconnections
  • Protocol version negotiation state
  • Initialization status

This allows MCP clients to reconnect and resume their session in the event of a connection loss.

WorkerTransport

The WorkerTransport class implements the MCP Transport interface, handling HTTP request/response cycles, Server-Sent Events (SSE) streaming, session management, and CORS.

TypeScript
class WorkerTransport implements Transport {
sessionId?: string;
started: boolean;
onclose?: () => void;
onerror?: (error: Error) => void;
onmessage?: (message: JSONRPCMessage, extra?: MessageExtraInfo) => void;
constructor(options?: WorkerTransportOptions);
async handleRequest(
request: Request,
parsedBody?: unknown,
): Promise<Response>;
async send(
message: JSONRPCMessage,
options?: TransportSendOptions,
): Promise<void>;
async start(): Promise<void>;
async close(): Promise<void>;
}

Constructor Options

TypeScript
interface WorkerTransportOptions {
/**
* Function that generates a unique session ID.
* Called when a new session is initialized.
*/
sessionIdGenerator?: () => string;
/**
* Enable traditional Request/Response mode, disabling streaming.
* When true, responses are returned as JSON instead of SSE streams.
* @default false
*/
enableJsonResponse?: boolean;
/**
* Callback invoked when a session is initialized.
* Receives the generated or restored session ID.
*/
onsessioninitialized?: (sessionId: string) => void;
/**
* CORS configuration for cross-origin requests.
* Configures Access-Control-* headers.
*/
corsOptions?: CORSOptions;
/**
* Optional storage API for persisting transport state.
* Use this to store session state in Durable Object/Agent storage
* so it survives hibernation/restart.
*/
storage?: MCPStorageApi;
}

sessionIdGenerator

Provides a custom session identifier. This session identifier is used to identify the session in the MCP Client.

JavaScript
const transport = new WorkerTransport({
sessionIdGenerator: () => `user-${Date.now()}-${Math.random()}`,
});

enableJsonResponse

Disables SSE streaming and returns responses as standard JSON.

JavaScript
const transport = new WorkerTransport({
enableJsonResponse: true, // Disable streaming, return JSON responses
});

onsessioninitialized

A callback that fires when a session is initialized, either by creating a new session or restoring from storage.

JavaScript
const transport = new WorkerTransport({
onsessioninitialized: (sessionId) => {
console.log(`MCP session initialized: ${sessionId}`);
},
});

corsOptions

Configure CORS headers for cross-origin requests.

TypeScript
interface CORSOptions {
origin?: string;
methods?: string;
headers?: string;
maxAge?: number;
exposeHeaders?: string;
}
JavaScript
const transport = new WorkerTransport({
corsOptions: {
origin: "https://example.com",
methods: "GET, POST, OPTIONS",
headers: "Content-Type, Authorization",
maxAge: 86400,
},
});

storage

Persist transport state to survive Durable Object hibernation or restarts.

TypeScript
interface MCPStorageApi {
get(): Promise<TransportState | undefined> | TransportState | undefined;
set(state: TransportState): Promise<void> | void;
}
interface TransportState {
sessionId?: string;
initialized: boolean;
protocolVersion?: ProtocolVersion;
}
JavaScript
const transport = new WorkerTransport({
storage: {
get: async () => {
// Retrieve state from Durable Object storage
return await this.ctx.storage.get("mcp-state");
},
set: async (state) => {
// Persist state to Durable Object storage
await this.ctx.storage.put("mcp-state", state);
},
},
});

Authentication Context

When using OAuth authentication with the createMcpHandler, user information is made available to your MCP tools through getMcpAuthContext(). Under the hood this uses AsyncLocalStorage to pass the request to the tool handler, keeping the authentication context available.

TypeScript
interface McpAuthContext {
props: Record<string, unknown>;
}

getMcpAuthContext

Retrieve the current authentication context within an MCP tool handler. This returns user information that was populated by the OAuth provider. Note that if using McpAgent this information is accessable directly on this.props instead.

TypeScript
import { getMcpAuthContext } from "agents/mcp";
function getMcpAuthContext(): McpAuthContext | undefined;
JavaScript
import { getMcpAuthContext } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const server = new McpServer({ name: "Auth Server", version: "1.0.0" });
server.tool("getProfile", "Get the current user's profile", {}, async () => {
// Access user info automatically populated by OAuth provider
const auth = getMcpAuthContext();
const username = auth?.props?.username;
const email = auth?.props?.email;
return {
content: [
{
type: "text",
text: `User: ${username ?? "anonymous"}, Email: ${email ?? "none"}`,
},
],
};
});

Error Handling

The createMcpHandler automatically catches errors and returns JSON-RPC error responses with code -32603 (Internal error).

JavaScript
server.tool("riskyOperation", "An operation that might fail", {}, async () => {
if (Math.random() > 0.5) {
throw new Error("Random failure occurred");
}
return {
content: [{ type: "text", text: "Success!" }],
};
});
// Errors are automatically caught and returned as:
// {
// "jsonrpc": "2.0",
// "error": {
// "code": -32603,
// "message": "Random failure occurred"
// },
// "id": <request_id>
// }