---
title: McpClient
description: Connect Agents to external MCP servers to use their tools, resources, and prompts over the Model Context Protocol.
image: https://edgetunnel-b2h.pages.dev/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://edgetunnel-b2h.pages.dev/agents/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# McpClient

Connect your agent to external [Model Context Protocol (MCP)](https://edgetunnel-b2h.pages.dev/agents/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, refer to [Creating MCP servers](https://edgetunnel-b2h.pages.dev/agents/model-context-protocol/apis/agent-api/).

## Quick start

* [  JavaScript ](#tab-panel-6235)
* [  TypeScript ](#tab-panel-6236)

**JavaScript**

```js
import { Agent } from "agents";


export class MyAgent extends Agent {
  async onRequest(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");
  }
}
```

**TypeScript**

```ts
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");
  }
}
```

Connections persist in the agent's [SQL storage](https://edgetunnel-b2h.pages.dev/agents/runtime/lifecycle/state/), and when an agent connects to an MCP server, all tools from that server become available automatically.

## Adding MCP servers

Use `addMcpServer()` to connect to an MCP server. For non-OAuth servers, no options are needed:

* [  JavaScript ](#tab-panel-6229)
* [  TypeScript ](#tab-panel-6230)

**JavaScript**

```js
// Non-OAuth server — no options required
await this.addMcpServer("notion", "https://mcp.notion.so/mcp");


// OAuth server — callbackHost is auto-derived from the incoming request,
// but you can set it explicitly if needed (e.g. custom domains)
await this.addMcpServer("github", "https://mcp.github.com/mcp", {
  callbackHost: "https://my-worker.workers.dev",
});
```

**TypeScript**

```ts
// Non-OAuth server — no options required
await this.addMcpServer("notion", "https://mcp.notion.so/mcp");


// OAuth server — callbackHost is auto-derived from the incoming request,
// but you can set it explicitly if needed (e.g. custom domains)
await this.addMcpServer("github", "https://mcp.github.com/mcp", {
  callbackHost: "https://my-worker.workers.dev",
});
```

### Stable server IDs

By default, each connection is assigned a generated `nanoid(8)` ID. Pass `id` for connector-style integrations so tools surface as readable keys instead of opaque connection IDs.

* [  JavaScript ](#tab-panel-6227)
* [  TypeScript ](#tab-panel-6228)

**JavaScript**

```js
await this.addMcpServer("GitHub", env.MCP_SESSION, {
  id: "github",
  props: { token: "..." },
});
// tools surface as `tool_github_<name>`
```

**TypeScript**

```ts
await this.addMcpServer("GitHub", env.MCP_SESSION, {
  id: "github",
  props: { token: "..." },
});
// tools surface as `tool_github_<name>`
```

When provided, this `id` replaces the generated value as the server's ID in storage, restore, `listServers()`, `listTools()`, `getAITools()`, and OAuth state. The supplied ID is normalized via the exported `normalizeServerId` helper, so values like `"GitHub MCP!"` become `"github-mcp"` — guaranteeing the ID is safe to embed in AI SDK tool names and storage keys.

Stable IDs are fully additive — no existing code breaks. If you add `{ id: "github" }` to an `addMcpServer` call for a server already registered under an auto-generated ID, the SDK transparently migrates the existing storage row, in-memory connection, and OAuth-related storage keys to the new stable ID. No `removeMcpServer` step is required. `addMcpServer` only throws on a genuinely ambiguous collision: the same stable ID already belongs to a _different_ `(name, url)` server.

### Transport options

MCP supports multiple transport types:

* [  JavaScript ](#tab-panel-6231)
* [  TypeScript ](#tab-panel-6232)

**JavaScript**

```js
await this.addMcpServer("server", "https://mcp.example.com/mcp", {
  transport: {
    type: "streamable-http",
  },
});
```

**TypeScript**

```ts
await this.addMcpServer("server", "https://mcp.example.com/mcp", {
  transport: {
    type: "streamable-http",
  },
});
```

| Transport       | Description                                         |
| --------------- | --------------------------------------------------- |
| auto            | Auto-detect based on server response (default)      |
| streamable-http | HTTP with streaming                                 |
| sse             | Server-Sent Events - legacy/compatibility transport |

### Custom headers

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

* [  JavaScript ](#tab-panel-6233)
* [  TypeScript ](#tab-panel-6234)

**JavaScript**

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

**TypeScript**

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

### 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`)
* Unspecified addresses (`0.0.0.0`, `[::]`)
* Link-local addresses (`169.254.x`, `fe80::`)
* IPv6 unique-local addresses (`fc00::/7`)
* IPv4-mapped IPv6 addresses that resolve to private ranges (for example, `[::ffff:10.0.0.1]`)
* Cloud metadata endpoints (`metadata.google.internal`)

Loopback addresses (`localhost`, `127.x.x.x`, `[::1]`) are **allowed** for local development.

For production connections to internal services, use the [RPC transport](https://edgetunnel-b2h.pages.dev/agents/model-context-protocol/protocol/transport/) with a Durable Object binding instead of HTTP.

### Return value

`addMcpServer()` returns the connection state:

* `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

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

* [  JavaScript ](#tab-panel-6237)
* [  TypeScript ](#tab-panel-6238)

**JavaScript**

```js
class MyAgent extends Agent {
  async onRequest(request) {
    const result = await this.addMcpServer(
      "github",
      "https://mcp.github.com/mcp",
    );


    if (result.state === "authenticating") {
      // Redirect the user to the OAuth authorization page
      return Response.redirect(result.authUrl);
    }


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

**TypeScript**

```ts
class MyAgent extends Agent {
  async onRequest(request: Request) {
    const result = await this.addMcpServer(
      "github",
      "https://mcp.github.com/mcp",
    );


    if (result.state === "authenticating") {
      // Redirect the user to the OAuth authorization page
      return Response.redirect(result.authUrl);
    }


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

### OAuth callback

The callback URL is automatically constructed:

```txt
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.

### Protecting instance names in OAuth callbacks

When using `sendIdentityOnConnect: false` to hide sensitive instance names (like session IDs or user IDs), the default OAuth callback URL would expose the instance name. To prevent this security issue, you must provide a custom `callbackPath`.

* [  JavaScript ](#tab-panel-6259)
* [  TypeScript ](#tab-panel-6260)

**JavaScript**

```js
import { Agent, routeAgentRequest, getAgentByName } from "agents";


export class SecureAgent extends Agent {
  static options = { sendIdentityOnConnect: false };


  async onRequest(request) {
    // callbackPath is required when sendIdentityOnConnect is false
    const result = await this.addMcpServer(
      "github",
      "https://mcp.github.com/mcp",
      {
        callbackPath: "mcp-oauth-callback", // Custom path without instance name
      },
    );


    if (result.state === "authenticating") {
      return Response.redirect(result.authUrl);
    }


    return new Response("Connected!");
  }
}


// Route the custom callback path to the agent
export default {
  async fetch(request, env) {
    const url = new URL(request.url);


    // Route custom MCP OAuth callback to agent instance
    if (url.pathname.startsWith("/mcp-oauth-callback")) {
      // Implement this to extract the instance name from your session/auth mechanism
      const instanceName = await getInstanceNameFromSession(request);


      const agent = await getAgentByName(env.SecureAgent, instanceName);
      return agent.fetch(request);
    }


    // Standard agent routing
    return (
      (await routeAgentRequest(request, env)) ??
      new Response("Not found", { status: 404 })
    );
  },
};
```

**TypeScript**

```ts
import { Agent, routeAgentRequest, getAgentByName } from "agents";


export class SecureAgent extends Agent {
  static options = { sendIdentityOnConnect: false };


  async onRequest(request: Request) {
    // callbackPath is required when sendIdentityOnConnect is false
    const result = await this.addMcpServer(
      "github",
      "https://mcp.github.com/mcp",
      {
        callbackPath: "mcp-oauth-callback", // Custom path without instance name
      },
    );


    if (result.state === "authenticating") {
      return Response.redirect(result.authUrl);
    }


    return new Response("Connected!");
  }
}


// Route the custom callback path to the agent
export default {
  async fetch(request: Request, env: Env) {
    const url = new URL(request.url);


    // Route custom MCP OAuth callback to agent instance
    if (url.pathname.startsWith("/mcp-oauth-callback")) {
      // Implement this to extract the instance name from your session/auth mechanism
      const instanceName = await getInstanceNameFromSession(request);


      const agent = await getAgentByName(env.SecureAgent, instanceName);
      return agent.fetch(request);
    }


    // Standard agent routing
    return (
      (await routeAgentRequest(request, env)) ??
      new Response("Not found", { status: 404 })
    );
  },
} satisfies ExportedHandler<Env>;
```

How callback matching works

OAuth callbacks are matched by the `state` query parameter (format: `{serverId}:{stateValue}`), not by URL path. This means your custom `callbackPath` can be any path you choose, as long as requests to that path are routed to the correct agent instance.

### Custom OAuth callback handling

Configure how OAuth completion is handled. By default, successful authentication redirects to your application origin, while failed authentication displays an HTML error page.

* [  JavaScript ](#tab-panel-6247)
* [  TypeScript ](#tab-panel-6248)

**JavaScript**

```js
export class MyAgent extends Agent {
  onStart() {
    this.mcp.configureOAuthCallback({
      // Redirect after successful auth
      successRedirect: "https://myapp.com/success",


      // Redirect on error with error message in query string
      errorRedirect: "https://myapp.com/error",


      // Or use a custom handler
      customHandler: () => {
        // Close popup window after auth completes
        return new Response("<script>window.close();</script>", {
          headers: { "content-type": "text/html" },
        });
      },
    });
  }
}
```

**TypeScript**

```ts
export class MyAgent extends Agent {
  onStart() {
    this.mcp.configureOAuthCallback({
      // Redirect after successful auth
      successRedirect: "https://myapp.com/success",


      // Redirect on error with error message in query string
      errorRedirect: "https://myapp.com/error",


      // Or use a custom handler
      customHandler: () => {
        // Close popup window after auth completes
        return new Response("<script>window.close();</script>", {
          headers: { "content-type": "text/html" },
        });
      },
    });
  }
}
```

## Using MCP capabilities

Once connected, access the server's capabilities:

### Get available tools

* [  JavaScript ](#tab-panel-6239)
* [  TypeScript ](#tab-panel-6240)

**JavaScript**

```js
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(`  Title: ${tool.title ?? tool.annotations?.title ?? tool.name}`);
  console.log(`  Description: ${tool.description}`);
}
```

**TypeScript**

```ts
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(`  Title: ${tool.title ?? tool.annotations?.title ?? tool.name}`);
  console.log(`  Description: ${tool.description}`);
}
```

#### Integration with AI SDK

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

* [  JavaScript ](#tab-panel-6249)
* [  TypeScript ](#tab-panel-6250)

**JavaScript**

```js
import { generateText } from "ai";
import { createWorkersAI } from "workers-ai-provider";


export class MyAgent extends Agent {
  async onRequest(request) {
    const workersai = createWorkersAI({ binding: this.env.AI });
    const response = await generateText({
      model: workersai("@cf/zai-org/glm-4.7-flash"),
      prompt: "What's the weather in San Francisco?",
      tools: this.mcp.getAITools(),
    });


    return new Response(response.text);
  }
}
```

**TypeScript**

```ts
import { generateText } from "ai";
import { createWorkersAI } from "workers-ai-provider";


export class MyAgent extends Agent<Env> {
  async onRequest(request: Request) {
    const workersai = createWorkersAI({ binding: this.env.AI });
    const response = await generateText({
      model: workersai("@cf/zai-org/glm-4.7-flash"),
      prompt: "What's the weather in San Francisco?",
      tools: this.mcp.getAITools(),
    });


    return new Response(response.text);
  }
}
```

Note

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

### Resources and prompts

* [  JavaScript ](#tab-panel-6241)
* [  TypeScript ](#tab-panel-6242)

**JavaScript**

```js
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}`);
}
```

**TypeScript**

```ts
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}`);
}
```

### Elicitation

[MCP elicitation ↗](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation) lets a server request user input while handling another request, such as a tool call. The current stable MCP specification defines form and URL modes.

Register a handler for each mode your Agent supports in `onStart()`:

* [  JavaScript ](#tab-panel-6257)
* [  TypeScript ](#tab-panel-6258)

**JavaScript**

```js
import { Agent } from "agents";


class MyAgent extends Agent {
  onStart() {
    this.mcp.configureElicitationHandlers({
      form: (request, serverId) =>
        this.forwardElicitationToBrowser(request, serverId),
      url: (request, serverId) =>
        this.forwardElicitationToBrowser(request, serverId),
    });
  }


  forwardElicitationToBrowser(request, serverId) {
    // Forward the request to your UI and resolve after the user responds.
    // A complete implementation appears in Forward elicitation to a UI.
    throw new Error(
      `Implement elicitation for ${serverId}: ${request.params.message}`,
    );
  }
}
```

**TypeScript**

```ts
import { Agent } from "agents";
import type { ElicitRequest, ElicitResult } from "agents/mcp";


class MyAgent extends Agent<Env> {
  onStart() {
    this.mcp.configureElicitationHandlers({
      form: (request, serverId) =>
        this.forwardElicitationToBrowser(request, serverId),
      url: (request, serverId) =>
        this.forwardElicitationToBrowser(request, serverId),
    });
  }


  private forwardElicitationToBrowser(
    request: ElicitRequest,
    serverId: string,
  ): Promise<ElicitResult> {
    // Forward the request to your UI and resolve after the user responds.
    // A complete implementation appears in Forward elicitation to a UI.
    throw new Error(
      `Implement elicitation for ${serverId}: ${request.params.message}`,
    );
  }
}
```

The `serverId` identifies the connection that sent the request. Use it to tell the user which server is asking for input and to apply server-specific policy.

#### Capability negotiation and hibernation

At the MCP `initialize` handshake, a connection advertises only the modes with configured handlers. A form-only handler advertises form mode. A URL-only handler advertises URL mode. A connection without handlers advertises no elicitation capability, which lets the server use its fallback.

The SDK stores the advertised modes with each server registration. A connection restored after Durable Object hibernation can therefore advertise the same modes when it reconnects. Callback functions remain in memory and reattach when `onStart()` runs.

You can explicitly narrow the advertised modes when adding a server:

* [  JavaScript ](#tab-panel-6243)
* [  TypeScript ](#tab-panel-6244)

**JavaScript**

```js
await this.addMcpServer("portal", "https://portal.example.com/mcp", {
  client: {
    capabilities: {
      elicitation: { form: {} },
    },
  },
});
```

**TypeScript**

```ts
await this.addMcpServer("portal", "https://portal.example.com/mcp", {
  client: {
    capabilities: {
      elicitation: { form: {} },
    },
  },
});
```

An explicit `client.capabilities.elicitation` value takes precedence over handler-derived modes and persists with the server registration. Do not advertise a mode without a matching handler. If the server sends that mode, the connection returns an error because it cannot handle the request.

#### Form mode

Form mode collects structured, non-sensitive data in the client. The request includes a restricted JSON Schema in `requestedSchema`. If the user submits the form, return `action: "accept"` with matching `content`:

* [  JavaScript ](#tab-panel-6245)
* [  TypeScript ](#tab-panel-6246)

**JavaScript**

```js
this.mcp.configureElicitationHandlers({
  form: async (request) => {
    const content = await showFormToUser(request.params.requestedSchema);
    return content ? { action: "accept", content } : { action: "cancel" };
  },
});
```

**TypeScript**

```ts
this.mcp.configureElicitationHandlers({
  form: async (request) => {
    const content = await showFormToUser(request.params.requestedSchema);
    return content ? { action: "accept", content } : { action: "cancel" };
  },
});
```

Allow the user to review and edit values before submission. Validate accepted content against `requestedSchema`. Do not use form mode to request passwords, API keys, access tokens, payment credentials, or other secrets.

#### URL mode

URL mode asks the user to open an external page. Use it for out-of-band interactions that may collect secrets, such as third-party authorization or payment. Keep the URL in the dedicated elicitation path and out of model-visible messages and tool-result text.

A URL handler should:

1. Show which MCP server sent the request.
2. Show the request message, target host, and full URL.
3. Ask for consent before opening the URL.
4. Open the page in a browser context the Agent and model cannot inspect.
5. Return `action: "accept"` without `content` after consent.
6. Offer distinct decline and cancel controls.

Do not prefetch the URL or its metadata. Treat the URL as untrusted input. Production servers should send HTTPS URLs.

For URL mode, `accept` means the user consented to open the URL. It does not mean the external interaction finished. A server may later send `notifications/elicitation/complete` with the request `elicitationId`.

#### Response actions

Both modes support three actions:

| Action  | Meaning                                                           |
| ------- | ----------------------------------------------------------------- |
| accept  | The user submitted the form or consented to open the URL.         |
| decline | The user explicitly rejected the request.                         |
| cancel  | The user dismissed the request without making an explicit choice. |

Include `content` only for an accepted form response. Omit it for URL, decline, and cancel responses.

#### Forward elicitation to a UI

A handler returns a promise, but the response often comes from a browser. Broadcast the request to connected clients, then resolve the promise through a `@callable` method:

* [  JavaScript ](#tab-panel-6279)
* [  TypeScript ](#tab-panel-6280)

**JavaScript**

```js
import { Agent, callable } from "agents";


class MyAgent extends Agent {
  pendingElicitations = new Map();


  onStart() {
    this.mcp.configureElicitationHandlers({
      form: (request, serverId) => this.forward(request, serverId),
      url: (request, serverId) => this.forward(request, serverId),
    });
  }


  forward(request, serverId) {
    const id = crypto.randomUUID();


    const result = new Promise((resolve) => {
      const timeout = setTimeout(() => {
        if (this.pendingElicitations.delete(id)) {
          resolve({ action: "cancel" });
        }
      }, 55_000);
      this.pendingElicitations.set(id, { resolve, timeout });
    });


    this.broadcast(
      JSON.stringify({
        type: "mcp-elicitation",
        id,
        serverId,
        params: request.params,
      }),
    );


    return result;
  }


  @callable()
  respondToElicitation(id, result) {
    const pending = this.pendingElicitations.get(id);
    if (!pending) return;


    this.pendingElicitations.delete(id);
    clearTimeout(pending.timeout);
    pending.resolve(result);
  }
}
```

**TypeScript**

```ts
import { Agent, callable } from "agents";
import type { ElicitRequest, ElicitResult } from "agents/mcp";


type PendingResolver = {
  resolve: (result: ElicitResult) => void;
  timeout: ReturnType<typeof setTimeout>;
};


class MyAgent extends Agent<Env> {
  private pendingElicitations = new Map<string, PendingResolver>();


  onStart() {
    this.mcp.configureElicitationHandlers({
      form: (request, serverId) => this.forward(request, serverId),
      url: (request, serverId) => this.forward(request, serverId),
    });
  }


  private forward(
    request: ElicitRequest,
    serverId: string,
  ): Promise<ElicitResult> {
    const id = crypto.randomUUID();


    const result = new Promise<ElicitResult>((resolve) => {
      const timeout = setTimeout(() => {
        if (this.pendingElicitations.delete(id)) {
          resolve({ action: "cancel" });
        }
      }, 55_000);
      this.pendingElicitations.set(id, { resolve, timeout });
    });


    this.broadcast(
      JSON.stringify({
        type: "mcp-elicitation",
        id,
        serverId,
        params: request.params,
      }),
    );


    return result;
  }


  @callable()
  respondToElicitation(id: string, result: ElicitResult) {
    const pending = this.pendingElicitations.get(id);
    if (!pending) return;


    this.pendingElicitations.delete(id);
    clearTimeout(pending.timeout);
    pending.resolve(result);
  }
}
```

The example uses a 55-second timeout because MCP SDK requests default to 60 seconds. If your client call sets a longer request timeout, adjust this timeout to finish first.

Refer to the [mcp-client example ↗](https://github.com/cloudflare/agents/tree/main/examples/mcp-client) for the browser implementation. The [mcp-elicitation example ↗](https://github.com/cloudflare/agents/tree/main/examples/mcp-elicitation) is a server that sends both modes.

To send elicitation requests from an MCP server, refer to [elicitInput](https://edgetunnel-b2h.pages.dev/agents/model-context-protocol/apis/agent-api/#elicitinputoptions-context).

## Managing servers

MCP server registrations persist across Agent restarts. The SDK stores server configuration in SQLite, stores OAuth tokens securely, and restores connections when the Agent wakes.

### List all servers

* [  JavaScript ](#tab-panel-6251)
* [  TypeScript ](#tab-panel-6252)

**JavaScript**

```js
const state = this.getMcpServers();


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

**TypeScript**

```ts
const state = this.getMcpServers();


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

### Get server status

Use the server ID to inspect an individual connection:

* [  JavaScript ](#tab-panel-6255)
* [  TypeScript ](#tab-panel-6256)

**JavaScript**

```js
const state = this.getMcpServers();
const server = state.servers[serverId];


if (server) {
  console.log(`${server.name}: ${server.state}`);
  // state: "ready" | "authenticating" | "connecting" | "connected" | "discovering" | "failed"
}
```

**TypeScript**

```ts
const state = this.getMcpServers();
const server = state.servers[serverId];


if (server) {
  console.log(`${server.name}: ${server.state}`);
  // state: "ready" | "authenticating" | "connecting" | "connected" | "discovering" | "failed"
}
```

### Remove a server

* [  JavaScript ](#tab-panel-6253)
* [  TypeScript ](#tab-panel-6254)

**JavaScript**

```js
await this.removeMcpServer(serverId);
```

**TypeScript**

```ts
await this.removeMcpServer(serverId);
```

This disconnects from the server and removes it from storage.

## Client-side integration

Connected clients receive real-time MCP updates via WebSocket:

* [  JavaScript ](#tab-panel-6271)
* [  TypeScript ](#tab-panel-6272)

**JavaScript**

```js
import { useAgent } from "agents/react";
import { useState } from "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.state}
        </div>
      ))}


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

**TypeScript**

```ts
import { useAgent } from "agents/react";
import { useState } from "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.state}
        </div>
      ))}


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

## API reference

### `addMcpServer()`

Add a connection to an MCP server and make its tools available to your agent.

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.

**TypeScript**

```ts
// HTTP transport (Streamable HTTP, SSE)
async addMcpServer(
  serverName: string,
  url: string,
  options?: {
    id?: string;
    callbackHost?: string;
    callbackPath?: string;
    agentsPrefix?: string;
    client?: ClientOptions;
    transport?: {
      headers?: HeadersInit;
      type?: "sse" | "streamable-http" | "auto";
    };
    retry?: RetryOptions;
  }
): Promise<
  | { id: string; state: "authenticating"; authUrl: string }
  | { id: string; state: "ready" }
>


// RPC transport (Durable Object binding — no HTTP overhead)
async addMcpServer(
  serverName: string,
  binding: DurableObjectNamespace,
  options?: {
    id?: string;
    props?: Record<string, unknown>;
    client?: ClientOptions;
    retry?: RetryOptions;
  }
): Promise<{ id: string; state: "ready" }>
```

#### Parameters (HTTP transport)

* `serverName` (string, required) — Display name for the MCP server
* `url` (string, required) — URL of the MCP server endpoint
* `options` (object, optional) — Connection configuration:  
  * `id` — Optional stable, caller-supplied server ID for connector-style integrations. When provided, it replaces the generated `nanoid(8)` across storage, `listServers()`, `listTools()`, `getAITools()` (so tool keys become readable, for example `tool_github_create_pull_request`), and OAuth state. Refer to [Stable server IDs](#stable-server-ids)
  * `callbackHost` — Host for OAuth callback URL. Only needed for OAuth-authenticated servers. If omitted, automatically derived from the incoming request or WebSocket connection URI — you typically do not need to set this unless you are using a custom domain that differs from the Worker's hostname
  * `callbackPath` — Custom callback URL path that bypasses the default `/agents/{class}/{name}/callback` construction. **Required when `sendIdentityOnConnect` is `false`** to prevent leaking the instance name. When set, the callback URL becomes `{callbackHost}/{callbackPath}`. You must route this path to the agent instance via `getAgentByName`
  * `agentsPrefix` — URL prefix for OAuth callback path. Default: `"agents"`. Ignored when `callbackPath` is provided
  * `client` — MCP client configuration options (passed to `@modelcontextprotocol/sdk` Client constructor). By default, includes `CfWorkerJsonSchemaValidator` for validating tool parameters against JSON schemas
  * `transport` — Transport layer configuration:  
    * `headers` — Custom HTTP headers for authentication
    * `type` — Transport type: `"auto"` (default), `"streamable-http"`, or `"sse"`
  * `retry` — Retry options for connection and reconnection attempts. Persisted and used when restoring connections after hibernation or after OAuth completion. Default: 3 attempts, 500ms base delay, 5s max delay. Refer to [Retries](https://edgetunnel-b2h.pages.dev/agents/runtime/execution/retries/) for details on `RetryOptions`.

#### Parameters (RPC transport)

* `serverName` (string, required) — Display name for the MCP server
* `binding` (`DurableObjectNamespace`, required) — The Durable Object binding for the `McpAgent` class
* `options` (object, optional) — Connection configuration:  
  * `id` — Optional stable, caller-supplied server ID. Refer to [Stable server IDs](#stable-server-ids)
  * `props` — Initialization data passed to the `McpAgent`'s `onStart(props)`. Use this to pass user context, configuration, or other data to the MCP server instance
  * `client` — MCP client configuration options
  * `retry` — Retry options for the connection

RPC transport connects your Agent directly to an `McpAgent` via Durable Object bindings without HTTP overhead. Refer to [MCP Transport](https://edgetunnel-b2h.pages.dev/agents/model-context-protocol/protocol/transport/) for details on configuring RPC transport.

#### Returns

A Promise that resolves to a discriminated union based on connection state:

* When `state` is `"authenticating"`:

  * `id` (string) — Unique identifier for this server connection
  * `state` (`"authenticating"`) — Server is waiting for OAuth authorization
  * `authUrl` (string) — OAuth authorization URL for user authentication
* When `state` is `"ready"`:

  * `id` (string) — Unique identifier for this server connection
  * `state` (`"ready"`) — Server is fully connected and operational

### `removeMcpServer()`

Disconnect from an MCP server and clean up its resources.

**TypeScript**

```ts
async removeMcpServer(id: string): Promise<void>
```

#### Parameters

* `id` (string, required) — Server connection ID returned from `addMcpServer()`

### `getMcpServers()`

Get the current state of all MCP server connections.

**TypeScript**

```ts
getMcpServers(): MCPServersState
```

#### Returns

**TypeScript**

```ts
type MCPServersState = {
  servers: Record<
    string,
    {
      name: string;
      server_url: string;
      auth_url: string | null;
      state:
        | "authenticating"
        | "connecting"
        | "connected"
        | "discovering"
        | "ready"
        | "failed";
      capabilities: ServerCapabilities | null;
      instructions: string | null;
      error: string | null;
    }
  >;
  tools: Array<Tool & { serverId: string }>;
  prompts: Array<Prompt & { serverId: string }>;
  resources: Array<Resource & { serverId: string }>;
  resourceTemplates: Array<ResourceTemplate & { serverId: string }>;
};
```

The `state` field indicates the connection lifecycle:

* `authenticating` — Waiting for OAuth authorization to complete
* `connecting` — Establishing transport connection
* `connected` — Transport connection established
* `discovering` — Discovering server capabilities (tools, resources, prompts)
* `ready` — Fully connected and operational
* `failed` — Connection failed (see `error` field for details)

The `error` field contains an error message when `state` is `"failed"`. Error messages from external OAuth providers are automatically escaped to prevent XSS attacks, making them safe to display directly in your UI.

### `configureOAuthCallback()`

Configure OAuth callback behavior for MCP servers requiring authentication. This method allows you to customize what happens after a user completes OAuth authorization.

**TypeScript**

```ts
this.mcp.configureOAuthCallback(options: {
  successRedirect?: string;
  errorRedirect?: string;
  customHandler?: () => Response | Promise<Response>;
}): void
```

#### Parameters

* `options` (object, required) — OAuth callback configuration:  
  * `successRedirect` (string, optional) — URL to redirect to after successful authentication
  * `errorRedirect` (string, optional) — URL to redirect to after failed authentication. Error message is appended as `?error=<message>` query parameter
  * `customHandler` (function, optional) — Custom handler for complete control over the callback response. Must return a Response

#### Default behavior

When no configuration is provided:

* **Success**: Redirects to your application origin
* **Failure**: Displays an HTML error page with the error message

If OAuth fails, the connection state becomes `"failed"` and the error message is stored in the `server.error` field for display in your UI.

#### Usage

Configure in `onStart()` before any OAuth flows begin:

* [  JavaScript ](#tab-panel-6261)
* [  TypeScript ](#tab-panel-6262)

**JavaScript**

```js
export class MyAgent extends Agent {
  onStart() {
    // Option 1: Simple redirects
    this.mcp.configureOAuthCallback({
      successRedirect: "/dashboard",
      errorRedirect: "/auth-error",
    });


    // Option 2: Custom handler (e.g., for popup windows)
    this.mcp.configureOAuthCallback({
      customHandler: () => {
        return new Response("<script>window.close();</script>", {
          headers: { "content-type": "text/html" },
        });
      },
    });
  }
}
```

**TypeScript**

```ts
export class MyAgent extends Agent {
  onStart() {
    // Option 1: Simple redirects
    this.mcp.configureOAuthCallback({
      successRedirect: "/dashboard",
      errorRedirect: "/auth-error",
    });


    // Option 2: Custom handler (e.g., for popup windows)
    this.mcp.configureOAuthCallback({
      customHandler: () => {
        return new Response("<script>window.close();</script>", {
          headers: { "content-type": "text/html" },
        });
      },
    });
  }
}
```

### `configureElicitationHandlers()`

Configure handlers for server-initiated `elicitation/create` requests. Add a handler for each elicitation mode your Agent supports.

```txt
this.mcp.configureElicitationHandlers(handlers?: {
  form?: (
    request: ElicitRequest,
    serverId: string,
  ) => Promise<ElicitResult>;
  url?: (
    request: ElicitRequest,
    serverId: string,
  ) => Promise<ElicitResult>;
}): void
```

#### Parameters

* `handlers` (object, optional) — Elicitation handlers keyed by mode:  
  * `form` (function, optional) — Handles form-mode requests for structured, non-sensitive input.
  * `url` (function, optional) — Handles URL-mode requests for out-of-band interactions.
* `request` (`ElicitRequest`) — The MCP elicitation request. Inspect `request.params.mode` for the mode-specific fields.
* `serverId` (string) — The ID of the MCP server connection that sent the request.

Each handler returns a promise containing an `ElicitResult`. Return `accept`, `decline`, or `cancel`. Accepted form responses include `content` that matches `requestedSchema`. URL responses omit `content`.

Passing `undefined` clears all configured handlers.

#### Capability behavior

The client advertises only modes with configured handlers during the MCP `initialize` handshake. Handler changes apply immediately to live connections, but servers receive updated advertised modes after those connections reconnect.

The SDK stores the handler-derived modes with each MCP server registration. Restored connections advertise those modes after Durable Object hibernation, and callbacks reattach when `onStart()` runs.

#### Usage

Configure handlers in `onStart()`:

* [  JavaScript ](#tab-panel-6269)
* [  TypeScript ](#tab-panel-6270)

**JavaScript**

```js
import { Agent } from "agents";


export class MyAgent extends Agent {
  onStart() {
    this.mcp.configureElicitationHandlers({
      form: (request, serverId) =>
        this.forwardElicitationToBrowser(request, serverId),
      url: (request, serverId) =>
        this.forwardElicitationToBrowser(request, serverId),
    });
  }


  forwardElicitationToBrowser(request, serverId) {
    // Forward the request to your UI and resolve after the user responds.
    throw new Error(
      `Implement elicitation for ${serverId}: ${request.params.message}`,
    );
  }
}
```

**TypeScript**

```ts
import { Agent } from "agents";
import type { ElicitRequest, ElicitResult } from "agents/mcp";


export class MyAgent extends Agent<Env> {
  onStart() {
    this.mcp.configureElicitationHandlers({
      form: (request, serverId) =>
        this.forwardElicitationToBrowser(request, serverId),
      url: (request, serverId) =>
        this.forwardElicitationToBrowser(request, serverId),
    });
  }


  private forwardElicitationToBrowser(
    request: ElicitRequest,
    serverId: string,
  ): Promise<ElicitResult> {
    // Forward the request to your UI and resolve after the user responds.
    throw new Error(
      `Implement elicitation for ${serverId}: ${request.params.message}`,
    );
  }
}
```

For the complete browser forwarding pattern and mode-specific requirements, refer to [Elicitation](#elicitation).

## Custom OAuth provider

Override the default OAuth provider used when connecting to MCP servers by implementing `createMcpOAuthProvider()` on your Agent class. This enables custom authentication strategies such as pre-registered client credentials or mTLS, beyond the built-in dynamic client registration.

The override is used for both new connections (`addMcpServer`) and restored connections after a Durable Object restart.

* [  JavaScript ](#tab-panel-6273)
* [  TypeScript ](#tab-panel-6274)

**JavaScript**

```js
import { Agent } from "agents";


export class MyAgent extends Agent {
  createMcpOAuthProvider(callbackUrl) {
    const env = this.env;
    return {
      get redirectUrl() {
        return callbackUrl;
      },
      get clientMetadata() {
        return {
          client_id: env.MCP_CLIENT_ID,
          client_secret: env.MCP_CLIENT_SECRET,
          redirect_uris: [callbackUrl],
        };
      },
      clientInformation() {
        return {
          client_id: env.MCP_CLIENT_ID,
          client_secret: env.MCP_CLIENT_SECRET,
        };
      },
    };
  }
}
```

**TypeScript**

```ts
import { Agent } from "agents";
import type { AgentMcpOAuthProvider } from "agents";


export class MyAgent extends Agent<Env> {
  createMcpOAuthProvider(callbackUrl: string): AgentMcpOAuthProvider {
    const env = this.env;
    return {
      get redirectUrl() {
        return callbackUrl;
      },
      get clientMetadata() {
        return {
          client_id: env.MCP_CLIENT_ID,
          client_secret: env.MCP_CLIENT_SECRET,
          redirect_uris: [callbackUrl],
        };
      },
      clientInformation() {
        return {
          client_id: env.MCP_CLIENT_ID,
          client_secret: env.MCP_CLIENT_SECRET,
        };
      },
    };
  }
}
```

If you do not override this method, the agent uses the default provider which performs [OAuth 2.0 Dynamic Client Registration ↗](https://datatracker.ietf.org/doc/html/rfc7591) with the MCP server.

### Custom storage backend

To keep the built-in OAuth logic (CSRF state, PKCE, nonce generation, token management) but route token storage to a different backend, import `DurableObjectOAuthClientProvider` and pass your own storage adapter:

* [  JavaScript ](#tab-panel-6263)
* [  TypeScript ](#tab-panel-6264)

**JavaScript**

```js
import { Agent, DurableObjectOAuthClientProvider } from "agents";


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

**TypeScript**

```ts
import { Agent, DurableObjectOAuthClientProvider } from "agents";
import type { AgentMcpOAuthProvider } from "agents";


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

## Advanced: MCPClientManager

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

### Step-by-step connection

* [  JavaScript ](#tab-panel-6277)
* [  TypeScript ](#tab-panel-6278)

**JavaScript**

```js
// 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);
  }
}
```

**TypeScript**

```ts
// 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

* [  JavaScript ](#tab-panel-6265)
* [  TypeScript ](#tab-panel-6266)

**JavaScript**

```js
// 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();
```

**TypeScript**

```ts
// 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();
```

Note

MCP server list broadcasts (`cf_agent_mcp_servers`) are automatically filtered to exclude connections where [shouldSendProtocolMessages](https://edgetunnel-b2h.pages.dev/agents/runtime/communication/protocol-messages/) returned `false`.

### Lifecycle methods

#### `this.mcp.registerServer()`

Register a server without immediately connecting.

**TypeScript**

```ts
async registerServer(
  id: string,
  options: {
    url: string;
    name: string;
    callbackUrl: string;
    clientOptions?: ClientOptions;
    transportOptions?: TransportOptions;
  }
): Promise<string>
```

#### `this.mcp.connectToServer()`

Establish a connection to a previously registered server.

**TypeScript**

```ts
async connectToServer(id: string): Promise<MCPConnectionResult>


type MCPConnectionResult =
  | { state: "failed"; error: string }
  | { state: "authenticating"; authUrl: string }
  | { state: "connected" }
```

#### `this.mcp.discoverIfConnected()`

Check server capabilities if a connection is active.

**TypeScript**

```ts
async discoverIfConnected(
  serverId: string,
  options?: { timeoutMs?: number }
): Promise<MCPDiscoverResult | undefined>


type MCPDiscoverResult = {
  success: boolean;
  state: MCPConnectionState;
  error?: string;
}
```

#### `this.mcp.waitForConnections()`

Wait for all in-flight MCP connection and discovery operations to settle. This is useful when you need `this.mcp.getAITools()` to return the full set of tools immediately after the agent wakes from hibernation.

**TypeScript**

```ts
// Wait indefinitely
await this.mcp.waitForConnections();


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

Note

`AIChatAgent` calls this automatically via its [waitForMcpConnections](https://edgetunnel-b2h.pages.dev/agents/communication-channels/chat/chat-agents/#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`.

#### `this.mcp.closeConnection()`

Close the connection to a specific server while keeping it registered.

**TypeScript**

```ts
async closeConnection(id: string): Promise<void>
```

#### `this.mcp.closeAllConnections()`

Close all active server connections while preserving registrations.

**TypeScript**

```ts
async closeAllConnections(): Promise<void>
```

#### `this.mcp.getAITools()`

Get all discovered MCP tools in a format compatible with the AI SDK.

**TypeScript**

```ts
getAITools(filter?: MCPServerFilter): ToolSet
```

Tools are automatically namespaced by server ID to prevent conflicts when multiple MCP servers expose tools with the same name.

Pass an `MCPServerFilter` to scope the returned tools to a subset of connected servers:

* [  JavaScript ](#tab-panel-6267)
* [  TypeScript ](#tab-panel-6268)

**JavaScript**

```js
// Tools from a specific server only
const githubTools = this.mcp.getAITools({ serverId: "github" });


// Tools from multiple servers
const tools = this.mcp.getAITools({ serverId: ["github", "notion"] });


// Tools from servers matching a name
const tools = this.mcp.getAITools({ serverName: "GitHub" });


// Only tools from servers that are ready
const tools = this.mcp.getAITools({ state: "ready" });
```

**TypeScript**

```ts
// Tools from a specific server only
const githubTools = this.mcp.getAITools({ serverId: "github" });


// Tools from multiple servers
const tools = this.mcp.getAITools({ serverId: ["github", "notion"] });


// Tools from servers matching a name
const tools = this.mcp.getAITools({ serverName: "GitHub" });


// Only tools from servers that are ready
const tools = this.mcp.getAITools({ state: "ready" });
```

The filter type is available from `agents/mcp/client`:

**TypeScript**

```ts
import type { MCPServerFilter } from "agents/mcp/client";


type MCPServerFilter = {
  serverId?: string | string[];
  serverName?: string | string[];
  state?: MCPConnectionState | MCPConnectionState[];
};
```

All specified filter criteria are AND'd together. The same filter parameter is accepted by `listTools()`, `listPrompts()`, `listResources()`, and `listResourceTemplates()`.

## Error handling

Use error detection utilities to handle connection errors:

* [  JavaScript ](#tab-panel-6275)
* [  TypeScript ](#tab-panel-6276)

**JavaScript**

```js
import { isUnauthorized, isTransportNotImplemented } from "agents";


export class MyAgent extends Agent {
  async onRequest(request) {
    try {
      await this.addMcpServer("Server", "https://mcp.example.com/mcp");
    } catch (error) {
      if (isUnauthorized(error)) {
        return new Response("Authentication required", { status: 401 });
      } else if (isTransportNotImplemented(error)) {
        return new Response("Transport not supported", { status: 400 });
      }
      throw error;
    }
  }
}
```

**TypeScript**

```ts
import { isUnauthorized, isTransportNotImplemented } from "agents";


export class MyAgent extends Agent {
  async onRequest(request: Request) {
    try {
      await this.addMcpServer("Server", "https://mcp.example.com/mcp");
    } catch (error) {
      if (isUnauthorized(error)) {
        return new Response("Authentication required", { status: 401 });
      } else if (isTransportNotImplemented(error)) {
        return new Response("Transport not supported", { status: 400 });
      }
      throw error;
    }
  }
}
```

## Next steps

[ Creating MCP servers ](https://edgetunnel-b2h.pages.dev/agents/model-context-protocol/apis/agent-api/) Build your own MCP server. 

[ Client SDK ](https://edgetunnel-b2h.pages.dev/agents/communication-channels/chat/client-sdk/) Connect from browsers with onMcpUpdate. 

[ Store and sync state ](https://edgetunnel-b2h.pages.dev/agents/runtime/lifecycle/state/) Learn about agent persistence.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://edgetunnel-b2h.pages.dev/agents/model-context-protocol/apis/client-api/#page","headline":"McpClient · Cloudflare Agents docs","description":"Connect Agents to external MCP servers to use their tools, resources, and prompts over the Model Context Protocol.","url":"https://edgetunnel-b2h.pages.dev/agents/model-context-protocol/apis/client-api/","inLanguage":"en","image":"https://edgetunnel-b2h.pages.dev/dev-products-preview.png","dateModified":"2026-07-14","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://edgetunnel-b2h.pages.dev/#website","name":"Cloudflare Docs","url":"https://edgetunnel-b2h.pages.dev/"},"keywords":["MCP"]}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/agents/","name":"Agents"}},{"@type":"ListItem","position":3,"item":{"@id":"/agents/model-context-protocol/","name":"Model Context Protocol (MCP)"}},{"@type":"ListItem","position":4,"item":{"@id":"/agents/model-context-protocol/apis/","name":"APIs"}},{"@type":"ListItem","position":5,"item":{"@id":"/agents/model-context-protocol/apis/client-api/","name":"McpClient"}}]}
```
