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

# Connecting to MCP Servers

> Connect your agent to external MCP servers to use their tools, resources, and prompts through a standardized protocol.

Connect your agent to external MCP (Model Context Protocol) servers to use their tools, resources, and prompts. This enables your agent to interact with GitHub, Slack, databases, and other services through a standardized protocol.

## Overview

The MCP client capability lets your agent:

* **Connect to external MCP servers** - GitHub, Slack, databases, AI services
* **Use their tools** - Call functions exposed by MCP servers
* **Access resources** - Read data from MCP servers
* **Use prompts** - Leverage pre-built prompt templates

<Note>
  This page covers connecting to MCP servers as a client. To create your own MCP server, see [Creating MCP Servers](/mcp/creating-servers).
</Note>

## Quick Start

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

export class MyAgent extends Agent {
  async onRequest(request: Request) {
    // Add an MCP server
    const result = await this.addMcpServer(
      "github",
      "https://mcp.github.com/mcp"
    );

    if (result.state === "authenticating") {
      // Server requires OAuth - redirect user to authorize
      return Response.redirect(result.authUrl);
    }

    // Server is ready - tools are now available
    const state = this.getMcpServers();
    console.log(`Connected! ${state.tools.length} tools available`);

    return new Response("MCP server connected");
  }
}
```

## Adding MCP Servers

Use `addMcpServer()` to connect to an MCP server:

```typescript theme={null}
const result = await this.addMcpServer(name, url, options?);
```

### Basic Usage

```typescript theme={null}
// Simple connection
await this.addMcpServer("notion", "https://mcp.notion.so/mcp");

// With explicit callback host
await this.addMcpServer("github", "https://mcp.github.com/mcp", {
  callbackHost: "https://my-worker.workers.dev"
});
```

### Transport Options

MCP supports multiple transport types:

<Tabs>
  <Tab title="Streamable HTTP (Recommended)">
    ```typescript theme={null}
    await this.addMcpServer("server", "https://mcp.example.com/mcp", {
      transport: {
        type: "streamable-http"
      }
    });
    ```

    HTTP with streaming - recommended default.
  </Tab>

  <Tab title="SSE (Legacy)">
    ```typescript theme={null}
    await this.addMcpServer("server", "https://mcp.example.com/mcp", {
      transport: {
        type: "sse"
      }
    });
    ```

    Server-Sent Events - legacy/compatibility transport.
  </Tab>

  <Tab title="Auto-detect">
    ```typescript theme={null}
    await this.addMcpServer("server", "https://mcp.example.com/mcp", {
      transport: {
        type: "auto"
      }
    });
    ```

    Auto-detect based on server response.
  </Tab>
</Tabs>

### Custom Headers

For servers behind authentication (like Cloudflare Access) or using bearer tokens:

```typescript theme={null}
await this.addMcpServer("internal", "https://internal-mcp.example.com/mcp", {
  transport: {
    headers: {
      Authorization: "Bearer my-token",
      "CF-Access-Client-Id": "...",
      "CF-Access-Client-Secret": "..."
    }
  }
});
```

### Retry Options

Configure retry behavior for connection and reconnection attempts:

```typescript theme={null}
await this.addMcpServer("github", "https://mcp.github.com/mcp", {
  retry: {
    maxAttempts: 5,
    baseDelayMs: 1000,
    maxDelayMs: 10000
  }
});
```

These options are persisted and used when reconnecting after hibernation or after OAuth completion. Default: 3 attempts, 500ms base delay, 5s max delay.

### URL Security

MCP server URLs are validated before connection to prevent Server-Side Request Forgery (SSRF). The following URL targets are blocked:

* Private/internal IP ranges (RFC 1918: `10.x`, `172.16-31.x`, `192.168.x`)
* Loopback addresses (`127.x`, `::1`)
* Link-local addresses (`169.254.x`, `fe80::`)
* Cloud metadata endpoints (`169.254.169.254`)

<Warning>
  If you need to connect to an internal MCP server, use the [RPC transport](/mcp/transports#rpc-transport) with a Durable Object binding instead of HTTP.
</Warning>

### Return Value

`addMcpServer()` returns the connection state:

```typescript theme={null}
type AddMcpServerResult =
  | { id: string; state: "ready" }
  | { id: string; state: "authenticating"; authUrl: string };
```

* **`ready`** - Server connected and tools discovered
* **`authenticating`** - Server requires OAuth; redirect user to `authUrl`

## OAuth Authentication

Many MCP servers require OAuth authentication. The agent handles the OAuth flow automatically.

### How It Works

```mermaid theme={null}
sequenceDiagram
    participant Client
    participant Agent
    participant MCPServer

    Client->>Agent: addMcpServer(name, url)
    Agent->>MCPServer: Connect
    MCPServer-->>Agent: Requires OAuth
    Agent-->>Client: state: authenticating, authUrl
    Client->>MCPServer: User authorizes
    MCPServer->>Agent: Callback with code
    Agent->>MCPServer: Exchange for token
    Agent-->>Client: onMcpUpdate (ready)
```

### Handling OAuth in Your Agent

```typescript theme={null}
async onRequest(request: Request) {
  const result = await this.addMcpServer("github", "https://mcp.github.com/mcp");

  if (result.state === "authenticating") {
    // Option 1: Redirect the user
    return Response.redirect(result.authUrl);

    // Option 2: Return the URL for client-side redirect
    return Response.json({
      status: "needs_auth",
      authUrl: result.authUrl
    });
  }

  return Response.json({ status: "connected", id: result.id });
}
```

### OAuth Callback

The callback URL is automatically constructed:

```
https://{host}/{agentsPrefix}/{agent-name}/{instance-name}/callback
```

For example: `https://my-worker.workers.dev/agents/my-agent/default/callback`

OAuth tokens are securely stored in SQLite and persist across agent restarts.

### Custom Callback Handling

For custom OAuth completion behavior:

```typescript theme={null}
// In your agent constructor or onStart
this.mcp.configureOAuthCallback({
  // Redirect after successful auth
  successRedirect: "https://myapp.com/success",

  // Redirect on error
  errorRedirect: "https://myapp.com/error",

  // Or use a custom handler
  customHandler: (result) => {
    return new Response(
      JSON.stringify({
        success: result.authSuccess,
        serverId: result.serverId,
        error: result.authError
      }),
      {
        headers: { "Content-Type": "application/json" }
      }
    );
  }
});
```

### Custom OAuth Provider

By default, agents use dynamic client registration to authenticate with MCP servers. If you need to use a different OAuth strategy — such as pre-registered client credentials, mTLS-based authentication, or other mechanisms — override the `createMcpOAuthProvider` method in your agent subclass:

```typescript theme={null}
import { Agent } from "agents";
import type { AgentMcpOAuthProvider } from "agents";

class MyAgent extends Agent {
  createMcpOAuthProvider(callbackUrl: string): AgentMcpOAuthProvider {
    return new MyCustomOAuthProvider(this.ctx.storage, this.name, callbackUrl);
  }
}
```

Your custom class must implement the `AgentMcpOAuthProvider` interface, which extends the MCP SDK's `OAuthClientProvider` with additional properties (`authUrl`, `clientId`, `serverId`) and methods (`checkState`, `consumeState`, `deleteCodeVerifier`) used by the agent's MCP connection lifecycle.

#### Custom storage backend

The most common customization is using a different storage backend while keeping the built-in OAuth logic (CSRF state, PKCE, nonce generation, token management). Import `DurableObjectOAuthClientProvider` and pass your own storage adapter:

```typescript theme={null}
import { Agent, DurableObjectOAuthClientProvider } from "agents";
import type { AgentMcpOAuthProvider } from "agents";

class MyAgent extends Agent {
  createMcpOAuthProvider(callbackUrl: string): AgentMcpOAuthProvider {
    return new DurableObjectOAuthClientProvider(
      myCustomStorage, // any DurableObjectStorage-compatible adapter
      this.name,
      callbackUrl
    );
  }
}
```

## Using MCP Capabilities

Once connected, access the server's capabilities:

### Getting Available Tools

```typescript theme={null}
const state = this.getMcpServers();

// All tools from all connected servers
for (const tool of state.tools) {
  console.log(`Tool: ${tool.name}`);
  console.log(`  From server: ${tool.serverId}`);
  console.log(`  Description: ${tool.description}`);
}
```

### Resources and Prompts

```typescript theme={null}
const state = this.getMcpServers();

// Available resources
for (const resource of state.resources) {
  console.log(`Resource: ${resource.name} (${resource.uri})`);
}

// Available prompts
for (const prompt of state.prompts) {
  console.log(`Prompt: ${prompt.name}`);
}
```

### Server Status

```typescript theme={null}
const state = this.getMcpServers();

for (const [id, server] of Object.entries(state.servers)) {
  console.log(`${server.name}: ${server.state}`);
  // state: "ready" | "authenticating" | "connecting" | "connected" | "discovering" | "failed"
}
```

### Integration with AI SDK

To use MCP tools with the Vercel AI SDK, use `this.mcp.getAITools()` which converts MCP tools to AI SDK format:

```typescript theme={null}
import { generateText } from "ai";

async function chat(prompt: string) {
  const response = await generateText({
    model: openai("gpt-4"),
    prompt,
    tools: this.mcp.getAITools() // Converts MCP tools to AI SDK format
  });

  return response;
}
```

<Note>
  `getMcpServers().tools` returns raw MCP `Tool` objects for inspection. Use `this.mcp.getAITools()` when passing tools to the AI SDK.
</Note>

## Managing Servers

### Removing a Server

```typescript theme={null}
await this.removeMcpServer(serverId);
```

This disconnects from the server and removes it from storage.

### Persistence

MCP servers persist across agent restarts:

* Server configuration stored in SQLite
* OAuth tokens stored securely
* Connections restored automatically when agent wakes

### Listing All Servers

```typescript theme={null}
const state = this.getMcpServers();

for (const [id, server] of Object.entries(state.servers)) {
  console.log(`${id}: ${server.name} (${server.server_url})`);
}
```

## Client-Side Integration

Connected clients receive real-time MCP updates via WebSocket:

```typescript theme={null}
import { useAgent } from "agents/react";

function Dashboard() {
  const [tools, setTools] = useState([]);
  const [servers, setServers] = useState({});

  const agent = useAgent({
    agent: "MyAgent",
    onMcpUpdate: (mcpState) => {
      setTools(mcpState.tools);
      setServers(mcpState.servers);
    }
  });

  return (
    <div>
      <h2>Connected Servers</h2>
      {Object.entries(servers).map(([id, server]) => (
        <div key={id}>
          {server.name}: {server.connectionState}
        </div>
      ))}

      <h2>Available Tools ({tools.length})</h2>
      {tools.map(tool => (
        <div key={`${tool.serverId}-${tool.name}`}>
          {tool.name}
        </div>
      ))}
    </div>
  );
}
```

## Advanced: MCPClientManager

For fine-grained control, use `this.mcp` directly:

### Step-by-Step Connection

```typescript theme={null}
// 1. Register the server (saves to storage and creates in-memory connection)
const id = "my-server";
await this.mcp.registerServer(id, {
  url: "https://mcp.example.com/mcp",
  name: "My Server",
  callbackUrl: "https://my-worker.workers.dev/agents/my-agent/default/callback",
  transport: { type: "auto" }
});

// 2. Connect (initializes transport, handles OAuth if needed)
const connectResult = await this.mcp.connectToServer(id);

if (connectResult.state === "failed") {
  console.error("Connection failed:", connectResult.error);
  return;
}

if (connectResult.state === "authenticating") {
  console.log("OAuth required:", connectResult.authUrl);
  return;
}

// 3. Discover capabilities (transitions from "connected" to "ready")
if (connectResult.state === "connected") {
  const discoverResult = await this.mcp.discoverIfConnected(id);

  if (!discoverResult?.success) {
    console.error("Discovery failed:", discoverResult?.error);
  }
}
```

### Event Subscription

```typescript theme={null}
// Listen for state changes (onServerStateChanged is an Event<void>)
const disposable = this.mcp.onServerStateChanged(() => {
  console.log("MCP server state changed");
  this.broadcastMcpServers(); // Notify connected clients
});

// Clean up the subscription when no longer needed
// disposable.dispose();
```

### Waiting for Connections

After hibernation or when connections are being restored in the background, MCP tools may not be immediately available. Use `waitForConnections()` to wait until all in-flight connection and discovery operations have settled:

```typescript theme={null}
// Wait indefinitely for all connections to be ready
await this.mcp.waitForConnections();

// Wait with a timeout (in milliseconds)
await this.mcp.waitForConnections({ timeout: 10_000 });
```

This is useful when you need to call `this.mcp.getAITools()` immediately after the agent wakes from hibernation. Without waiting, tools from servers that are still reconnecting will be missing.

<Note>
  `AIChatAgent` handles this automatically via the `waitForMcpConnections` property (defaults to `{ timeout: 10_000 }`). You only need `waitForConnections()` directly when using `Agent` with MCP, or when you want finer control inside `onChatMessage`.
</Note>

### Error Recovery

```typescript theme={null}
async retryConnection(serverId: string) {
  const result = await this.mcp.connectToServer(serverId);

  if (result.state === "connected") {
    await this.mcp.discoverIfConnected(serverId);
  } else if (result.state === "failed") {
    console.error("Reconnection failed:", result.error);
  }
}
```

## Examples

### MCP Client Demo

The [`examples/mcp-client`](https://github.com/cloudflare/agents/tree/main/examples/mcp-client) example demonstrates:

* Adding and removing MCP servers dynamically
* Custom OAuth callback handling (popup-closing behavior)
* Listing tools from connected servers
* Real-time state updates to the frontend

```typescript theme={null}
// From examples/mcp-client/src/server.ts
export class MyAgent extends Agent {
  onStart() {
    // Custom OAuth callback that closes the popup window
    this.mcp.configureOAuthCallback({
      customHandler: (result) => {
        if (result.authSuccess) {
          return new Response("<script>window.close();</script>", {
            headers: { "content-type": "text/html" }
          });
        }
        // Handle error...
      }
    });
  }

  async onRequest(request: Request) {
    const url = new URL(request.url);

    if (url.pathname.endsWith("add-mcp")) {
      const { name, url } = await request.json();
      await this.addMcpServer(name, url);
      return new Response("Ok");
    }
    // ...
  }
}
```

## API Reference

### addMcpServer()

<CodeGroup>
  ```typescript HTTP Transport theme={null}
  // HTTP transport (Streamable HTTP, SSE)
  async addMcpServer(
    name: string,
    url: string,
    options?: {
      callbackHost?: string;  // only needed for OAuth-authenticated servers
      callbackPath?: string;  // custom callback URL path
      agentsPrefix?: string;
      client?: ClientOptions;
      transport?: {
        headers?: HeadersInit;
        type?: "sse" | "streamable-http" | "auto"; // default: "auto"
      };
      retry?: RetryOptions;
    }
  ): Promise<
    | { id: string; state: "ready" }
    | { id: string; state: "authenticating"; authUrl: string }
  >
  ```

  ```typescript RPC Transport theme={null}
  // RPC transport (Durable Object binding — no HTTP overhead)
  async addMcpServer(
    name: string,
    binding: DurableObjectNamespace,
    options?: {
      props?: Record<string, unknown>; // passed to the McpAgent's onStart(props)
      client?: ClientOptions;
      retry?: RetryOptions;
    }
  ): Promise<{ id: string; state: "ready" }>
  ```
</CodeGroup>

Add and connect to an MCP server. Throws if connection or discovery fails.

For non-OAuth servers, `callbackHost` is not required — you can call `addMcpServer("name", url)` with no options. For RPC transport, pass a `DurableObjectNamespace` binding instead of a URL. See [MCP Transports](/mcp/transports) for details.

<Note>
  Calling `addMcpServer` is idempotent when both the server name **and** URL match an existing active connection — the existing connection is returned without creating a duplicate. This makes it safe to call in `onStart()` without worrying about duplicate connections on restart.

  If you call `addMcpServer` with the same name but a **different** URL, a new connection is created. Both connections remain active and their tools are merged in `getAITools()`. To replace a server, call `removeMcpServer(oldId)` first.

  URLs are normalized before comparison (trailing slashes, default ports, and hostname case are handled), so `https://MCP.Example.com` and `https://mcp.example.com/` are treated as the same URL.
</Note>

### removeMcpServer()

```typescript theme={null}
async removeMcpServer(id: string): Promise<void>
```

Disconnect from and remove an MCP server.

### getMcpServers()

```typescript theme={null}
getMcpServers(): MCPServersState
```

Get the current state of all MCP servers and their capabilities.

### MCPServersState

```typescript theme={null}
type MCPServersState = {
  servers: {
    [id: string]: MCPServer;
  };
  tools: (Tool & { serverId: string })[];
  prompts: (Prompt & { serverId: string })[];
  resources: (Resource & { serverId: string })[];
};
```

### MCPServer

```typescript theme={null}
type MCPServer = {
  name: string;
  server_url: string;
  auth_url: string | null;
  state:
    | "ready"
    | "authenticating"
    | "connecting"
    | "connected"
    | "discovering"
    | "failed";
  error: string | null;
  instructions: string | null;
  capabilities: ServerCapabilities | null;
};
```

## 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="Transports" icon="arrows-repeat" href="/mcp/transports">
    Learn about different MCP transport options
  </Card>

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

  <Card title="Examples" icon="code" href="https://github.com/cloudflare/agents/tree/main/examples/mcp-client">
    View the MCP client example on GitHub
  </Card>
</CardGroup>
