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

# MCP Transports

> Learn about the different transport options for connecting to MCP servers with the Agents SDK.

This guide explains the different transport options for connecting to MCP servers with the Agents SDK.

<Note>
  For a primer on MCP Servers and how they are implemented in the Agents SDK with `McpAgent`, see [Creating MCP Servers](/mcp/creating-servers).
</Note>

## Streamable HTTP Transport (Recommended)

The **Streamable HTTP** transport is the recommended way to connect to MCP servers.

### How it works

When a client connects to your MCP server:

<Steps>
  <Step title="HTTP Request">
    The client makes an HTTP request to your Worker with a JSON-RPC message in the body
  </Step>

  <Step title="WebSocket Upgrade">
    Your Worker upgrades the connection to a WebSocket
  </Step>

  <Step title="Durable Object Connection">
    The WebSocket connects to your `McpAgent` Durable Object which manages connection state
  </Step>

  <Step title="Bidirectional Messages">
    JSON-RPC messages flow bidirectionally over the WebSocket
  </Step>

  <Step title="Streaming Response">
    Your Worker streams responses back to the client using Server-Sent Events (SSE)
  </Step>
</Steps>

This is all handled automatically by the `McpAgent.serve()` method:

```typescript theme={null}
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

export class MyMCP extends McpAgent {
  server = new McpServer({ name: "Demo", version: "1.0.0" });

  async init() {
    // Define your tools, resources, prompts
  }
}

// Serve with Streamable HTTP transport
export default MyMCP.serve("/mcp");
```

The `serve()` method returns a Worker with a `fetch` handler that:

* Handles CORS preflight requests
* Manages WebSocket upgrades
* Routes messages to your Durable Object

### Connection from clients

Clients connect using the `streamable-http` transport:

```typescript theme={null}
await agent.addMcpServer("my-server", "https://your-worker.workers.dev/mcp");
```

## SSE Transport (Deprecated)

We also support the legacy **SSE (Server-Sent Events)** transport, but it is deprecated in favor of Streamable HTTP.

<Warning>
  SSE transport is deprecated. Use Streamable HTTP for new implementations.
</Warning>

If you need SSE transport for compatibility:

```typescript theme={null}
// Server
export default MyMCP.serveSSE("/sse");

// Client
await agent.addMcpServer("my-server", url, callbackHost);
```

## RPC Transport (Experimental)

The **RPC transport** is a custom transport 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.

### Why use RPC transport?

* **Faster**: No network overhead - direct function calls
* **Simpler**: No HTTP endpoints, no connection management
* **Internal only**: Perfect for agents calling MCP servers within the same Worker

<Warning>
  RPC transport does not support authentication. Use HTTP/SSE for external connections that require OAuth.
</Warning>

### Connecting an Agent to an McpAgent via RPC

The RPC transport uses Durable Object bindings to connect your `Agent` (MCP client) directly to your `McpAgent` (MCP server).

<Steps>
  <Step title="Define your MCP server">
    Create your `McpAgent` with the tools you want to expose:

    ```typescript theme={null}
    import { McpAgent } from "agents/mcp";
    import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
    import { z } from "zod";

    type State = { counter: number };

    export class MyMCP extends McpAgent<Env, State> {
      server = new McpServer({ name: "MyMCP", version: "1.0.0" });
      initialState: State = { 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}`
                }
              ]
            };
          }
        );
      }
    }
    ```
  </Step>

  <Step title="Connect your Agent to the MCP server">
    In your `Agent`, call `addMcpServer()` with the Durable Object binding in `onStart()`:

    ```typescript theme={null}
    import { AIChatAgent } from "agents/ai-chat-agent";

    export class Chat extends AIChatAgent<Env> {
      async onStart(): Promise<void> {
        // 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 });
      }
    }
    ```

    <Note>
      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.

      **Deduplication:** For RPC transport, 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 `addMcpServer` in `onStart()` without worrying about creating multiple connections on restart.
    </Note>
  </Step>

  <Step title="Configure Durable Object bindings">
    In your `wrangler.jsonc`, define bindings for both Durable Objects:

    ```jsonc theme={null}
    {
      "durable_objects": {
        "bindings": [
          { "name": "Chat", "class_name": "Chat" },
          { "name": "MyMCP", "class_name": "MyMCP" }
        ]
      },
      "migrations": [
        {
          "new_sqlite_classes": ["MyMCP", "Chat"],
          "tag": "v1"
        }
      ]
    }
    ```
  </Step>

  <Step title="Set up your Worker fetch handler">
    Route requests to your Chat agent:

    ```typescript theme={null}
    import { routeAgentRequest } from "agents";

    export default {
      async fetch(request: Request, env: Env, ctx: ExecutionContext) {
        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 });
      }
    };
    ```
  </Step>
</Steps>

### Passing props from client to server

Since RPC transport does not have an OAuth flow, you can pass user context (like userId, role, etc.) directly as props:

```typescript theme={null}
await this.addMcpServer("my-mcp", this.env.MyMCP, {
  props: { userId: "user-123", role: "admin" }
});
```

Your `McpAgent` can then access these props:

```typescript theme={null}
export class MyMCP extends McpAgent<
  Env,
  State,
  { userId?: string; role?: string }
> {
  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}` }]
      };
    });
  }
}
```

The props are:

* **Type-safe**: TypeScript extracts the Props type from your McpAgent generic
* **Persistent**: Stored in Durable Object storage via `updateProps()`
* **Available immediately**: Set before any tool calls are made

This is useful for:

* User authentication context
* Tenant/organization IDs
* Feature flags or permissions
* Any per-connection configuration

### How RPC transport works under the hood

When you call `addMcpServer()` with a Durable Object binding, the SDK:

1. Creates an `RPCClientTransport` that wraps the DO stub
2. Calls `handleMcpMessage()` on the `McpAgent` for each JSON-RPC message
3. The `McpAgent` routes messages through its `RPCServerTransport` to the MCP server
4. Responses flow back synchronously through the RPC call

This happens entirely within your Worker's execution context using Cloudflare's RPC mechanism - no HTTP, no WebSockets, no public internet.

The RPC transport fully supports:

* JSON-RPC 2.0 validation (via the MCP SDK's schema)
* Batch requests
* Notifications (messages without `id` field)
* Automatic reconnection after Durable Object hibernation (when called from `onStart()`)

### Configuring RPC Transport Server Timeout

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

```typescript theme={null}
export class MyMCP extends McpAgent<Env, State> {
  server = new McpServer({ name: "MyMCP", version: "1.0.0" });

  protected 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

| Transport           | Use when                              | Pros                                     | Cons                            |
| ------------------- | ------------------------------------- | ---------------------------------------- | ------------------------------- |
| **Streamable HTTP** | External MCP servers, production apps | Standard protocol, secure, supports auth | Slight network overhead         |
| **RPC**             | Internal agents                       | Fastest, simplest setup                  | No auth, Service Bindings only  |
| **SSE**             | Legacy compatibility                  | Backwards compatible                     | Deprecated, use Streamable HTTP |

## Examples

<CardGroup cols={3}>
  <Card title="Streamable HTTP" icon="globe" href="https://github.com/cloudflare/agents/tree/main/examples/mcp">
    See the MCP example
  </Card>

  <Card title="RPC Transport" icon="bolt" href="https://github.com/cloudflare/agents/tree/main/examples/mcp-rpc-transport">
    See the RPC transport example
  </Card>

  <Card title="MCP Client" icon="plug" href="https://github.com/cloudflare/agents/tree/main/examples/mcp-client">
    See the MCP client example
  </Card>
</CardGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Creating Servers" icon="server" href="/mcp/creating-servers">
    Build your own MCP server with the Agents SDK
  </Card>

  <Card title="Connecting Clients" icon="plug" href="/mcp/connecting-clients">
    Connect your agent to external MCP servers
  </Card>

  <Card title="Securing Servers" icon="shield" href="/mcp/securing-servers">
    Implement OAuth and security best practices
  </Card>

  <Card title="API Reference" icon="book" href="https://github.com/cloudflare/agents">
    View the full API documentation
  </Card>
</CardGroup>
