McpClient·续3
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 ↗︎ 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:
import { Agent, DurableObjectOAuthClientProvider } from "agents";
export class MyAgent extends Agent {
createMcpOAuthProvider(callbackUrl) {
return new DurableObjectOAuthClientProvider(
myCustomStorage, // any DurableObjectStorage-compatible adapter
this.name,
callbackUrl,
);
}
}
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
// 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);
}
}
// 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
// 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();
// 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 returned false.
Lifecycle methods
this.mcp.registerServer()
Register a server without immediately connecting.
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.
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.
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.
// 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 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.
async closeConnection(id: string): Promise<void>
this.mcp.closeAllConnections()
Close all active server connections while preserving registrations.
async closeAllConnections(): Promise<void>
this.mcp.listTools()
Get raw MCP tool records without converting their schemas to Zod.
listTools(filter?: MCPServerFilter): Array<Tool & { serverId: string }>
Use this method for catalog discovery and inspection. Pass an MCPServerFilter to limit the returned tools to specific connections.
this.mcp.getAITools()
Get all discovered MCP tools in a format compatible with the AI SDK.
getAITools(filter?: MCPServerFilter): ToolSet
Tools are automatically namespaced by server ID to prevent conflicts when multiple MCP servers expose tools with the same name.
getAITools() reuses converted schemas for the current catalog on each live connection. It converts schemas again after discovery replaces the catalog or the live connection changes. Each call returns fresh tool records and execute functions. Use this.mcp.listTools() when you only need the raw catalog.
Pass an MCPServerFilter to scope the returned tools to a subset of connected servers:
// 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" });
// 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:
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:
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;
}
}
}
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
Build your own MCP server.
Client SDK
Connect from browsers with onMcpUpdate.
Store and sync state
Learn about agent persistence.
Was this helpful?
YesNo
On this page
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/agents/model-context-protocol/apis/client-api/#page","headline":"McpClient","description":"Connect Agents to external MCP servers to use their tools, resources, and prompts over the Model Context Protocol.","url":"https://developers.cloudflare.com/agents/model-context-protocol/apis/client-api/","inLanguage":"en","image":"https://developers.cloudflare.com/agents/model-context-protocol/apis/client-api/og.png?v=d2c89cb40edacc61","dateModified":"2026-10-21","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"},"keywords":["MCP"]}
description: Create stateless or legacy MCP server handlers for Cloudflare Workers with the Agents SDK. title: MCP handler APIs image: https://developers.cloudflare.com/agents/model-context-protocol/apis/handler-api/og.png?v=8656f1af1f0b68d0
Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/agents/llms.txt
Use this file to discover all available pages before exploring further.
Docs