Skip to content
Cloudflare Docs

Transport

The Model Context Protocol (MCP) specification defines two standard transport mechanisms for communication between clients and servers:

  1. stdio — Communication over standard in and standard out, designed for local MCP connections.
  2. Streamable HTTP — The standard transport method for remote MCP connections, introduced in March 2025. It uses a single HTTP endpoint for bidirectional messaging.

MCP servers built with the Agents SDK use createMcpHandler to handle Streamable HTTP transport.

Implementing remote MCP transport

Use createMcpHandler to create an MCP server that handles Streamable HTTP transport. This is the recommended approach for new MCP servers.

Get started quickly

You can use the "Deploy to Cloudflare" button to create a remote MCP server.

Deploy to Workers

Remote MCP server (without authentication)

Create an MCP server using createMcpHandler. View the complete example on GitHub.

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

MCP server with authentication

If your MCP server implements authentication & authorization using the Workers OAuth Provider library, use createMcpHandler with the apiRoute and apiHandler properties. View the complete example on GitHub.

JavaScript
export default new OAuthProvider({
apiRoute: "/mcp",
apiHandler: {
fetch: (request, env, ctx) => {
// Create a new server instance per request
const server = createServer();
return createMcpHandler(server)(request, env, ctx);
},
},
// ... other OAuth configuration
});

Stateful MCP servers

If your MCP server needs to maintain state across requests, use createMcpHandler with a WorkerTransport inside an Agent class. This allows you to persist session state in Durable Object storage and use advanced MCP features like elicitation and sampling.

See Stateful MCP Servers for implementation details.

RPC transport

The RPC transport is designed for internal applications where your MCP server and agent are both running on Cloudflare — they can even run in the same Worker. It sends JSON-RPC messages directly over Cloudflare's RPC bindings without going over the public internet.

  • Faster — no network overhead, direct function calls between Durable Objects
  • Simpler — no HTTP endpoints, no connection management
  • Internal only — perfect for agents calling MCP servers within the same Worker

RPC transport does not support authentication. Use Streamable HTTP for external connections that require OAuth.

Connecting an Agent to an McpAgent via RPC

1. Define your MCP server

Create your McpAgent with the tools you want to expose:

JavaScript
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
export class MyMCP extends McpAgent {
server = new McpServer({ name: "MyMCP", version: "1.0.0" });
initialState = { counter: 0 };
async init() {
this.server.tool(
"add",
"Add to the counter",
{ amount: z.number() },
async ({ amount }) => {
this.setState({ counter: this.state.counter + amount });
return {
content: [
{
type: "text",
text: `Added ${amount}, total is now ${this.state.counter}`,
},
],
};
},
);
}
}

2. Connect your Agent to the MCP server

In your Agent, call addMcpServer() with the Durable Object binding in onStart():

JavaScript
import { AIChatAgent } from "@cloudflare/ai-chat";
export class Chat extends AIChatAgent {
async onStart() {
// Pass the DO namespace binding directly
await this.addMcpServer("my-mcp", this.env.MyMCP);
}
async onChatMessage(onFinish) {
const allTools = this.mcp.getAITools();
const result = streamText({
model,
tools: allTools,
// ...
});
return createUIMessageStreamResponse({ stream: result });
}
}

RPC connections are automatically restored after Durable Object hibernation, just like HTTP connections. The binding name and props are persisted to storage so the connection can be re-established without any extra code.

If addMcpServer is called with a name that already has an active connection, the existing connection is returned instead of creating a duplicate. This makes it safe to call in onStart().

3. Configure Durable Object bindings

In your wrangler.jsonc, define bindings for both Durable Objects:

{
"durable_objects": {
"bindings": [
{ "name": "Chat", "class_name": "Chat" },
{ "name": "MyMCP", "class_name": "MyMCP" }
]
},
"migrations": [
{
"new_sqlite_classes": ["MyMCP", "Chat"],
"tag": "v1"
}
]
}

4. Set up your Worker fetch handler

Route requests to your Chat agent:

JavaScript
import { routeAgentRequest } from "agents";
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
// Optionally expose the MCP server via HTTP as well
if (url.pathname.startsWith("/mcp")) {
return MyMCP.serve("/mcp").fetch(request, env, ctx);
}
const response = await routeAgentRequest(request, env);
if (response) return response;
return new Response("Not found", { status: 404 });
},
};

Passing props to the MCP server

Since RPC transport does not have an OAuth flow, you can pass user context directly as props:

JavaScript
await this.addMcpServer("my-mcp", this.env.MyMCP, {
props: { userId: "user-123", role: "admin" },
});

Your McpAgent can then access these props:

JavaScript
export class MyMCP extends McpAgent {
async init() {
this.server.tool("whoami", "Get current user info", {}, async () => {
const userId = this.props?.userId || "anonymous";
const role = this.props?.role || "guest";
return {
content: [{ type: "text", text: `User ID: ${userId}, Role: ${role}` }],
};
});
}
}

Props are type-safe (TypeScript extracts the Props type from your McpAgent generic), persistent (stored in Durable Object storage), and available immediately before any tool calls are made.

Configuring RPC transport server timeout

The RPC transport has a configurable timeout for waiting for tool responses. By default, the server waits 60 seconds for a tool handler to respond. You can customize this by overriding getRpcTransportOptions() in your McpAgent:

JavaScript
export class MyMCP extends McpAgent {
server = new McpServer({ name: "MyMCP", version: "1.0.0" });
getRpcTransportOptions() {
return { timeout: 120000 }; // 2 minutes
}
async init() {
this.server.tool(
"long-running-task",
"A tool that takes a while",
{ input: z.string() },
async ({ input }) => {
await longRunningOperation(input);
return {
content: [{ type: "text", text: "Task completed" }],
};
},
);
}
}

Choosing a transport

TransportUse whenProsCons
Streamable HTTPExternal MCP servers, production appsStandard protocol, secure, supports authSlight network overhead
RPCInternal agents on CloudflareFastest, simplest setupNo auth, Durable Object bindings only
SSELegacy compatibilityBackwards compatibleDeprecated, use Streamable HTTP

Migrating from McpAgent

If you have an existing MCP server using the McpAgent class:

  • Not using state? Replace your McpAgent class with McpServer from @modelcontextprotocol/sdk and use createMcpHandler(server) in a Worker fetch handler.
  • Using state? Use createMcpHandler with a WorkerTransport inside an Agent class. See Stateful MCP Servers for details.
  • Need SSE support? Continue using McpAgent with serveSSE() for legacy client compatibility. See the McpAgent API reference.

Testing with MCP clients

You can test your MCP server using an MCP client that supports remote connections, or use mcp-remote, an adapter that lets MCP clients that only support local connections work with remote MCP servers.

Follow this guide for instructions on how to connect to your remote MCP server to Claude Desktop, Cursor, Windsurf, and other MCP clients.