Skip to main content

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.

An Agent that acts as an MCP client - dynamically connects to remote MCP servers, handles OAuth authentication, and aggregates tools, prompts, and resources from all connected servers.

What it demonstrates

  • addMcpServer / removeMcpServer - managing MCP server connections from an Agent
  • onMcpUpdate - real-time state updates pushed to the React frontend via WebSocket
  • OAuth popup flow - configureOAuthCallback with a custom handler that closes the popup after auth
  • agentFetch - making HTTP requests to the Agent’s custom endpoints from the client

Server Implementation

src/server.ts
import { Agent, callable, routeAgentRequest } from "agents";

export class MyAgent extends Agent {
  onStart() {
    this.mcp.configureOAuthCallback({
      customHandler: (result) => {
        if (result.authSuccess) {
          return new Response("<script>window.close();</script>", {
            headers: { "content-type": "text/html" },
            status: 200
          });
        }
        const error = result.authError || "Unknown error";
        return new Response(`Authentication Failed: ${error}`, {
          headers: { "content-type": "text/plain" },
          status: 400
        });
      }
    });
  }

  @callable()
  async addServer(name: string, url: string) {
    await this.addMcpServer(name, url, {
      callbackHost: this.env.HOST
    });
  }

  @callable()
  async disconnectServer(serverId: string) {
    await this.removeMcpServer(serverId);
  }
}

export default {
  async fetch(request: Request, env: Env) {
    return (
      (await routeAgentRequest(request, env, { cors: true })) ||
      new Response("Not found", { status: 404 })
    );
  }
} satisfies ExportedHandler<Env>;

Client Implementation

The React frontend uses useAgent with onMcpUpdate to receive real-time server state:
src/client.tsx
import { useAgent } from "agents/react";
import { useState } from "react";
import type { MyAgent } from "./server";

function McpClientApp() {
  const [mcpServers, setMcpServers] = useState<McpServerInfo[]>([]);
  const [connected, setConnected] = useState(false);

  const agent = useAgent<MyAgent>({
    agent: "MyAgent",
    name: sessionId,
    onMcpUpdate: (servers) => {
      // Real-time updates when servers are added/removed/changed
      setMcpServers(servers);
    },
    onOpen: () => setConnected(true),
    onClose: () => setConnected(false)
  });

  const addServer = async (name: string, url: string) => {
    await agent.call("addServer", [name, url]);
  };

  const removeServer = async (serverId: string) => {
    await agent.call("disconnectServer", [serverId]);
  };

  return (
    <div>
      <h1>MCP Servers</h1>
      {mcpServers.map((server) => (
        <div key={server.id}>
          <h2>{server.name}</h2>
          <p>Status: {server.status}</p>
          <h3>Tools</h3>
          <ul>
            {server.tools.map((tool) => (
              <li key={tool.name}>{tool.name}: {tool.description}</li>
            ))}
          </ul>
          <button onClick={() => removeServer(server.id)}>
            Disconnect
          </button>
        </div>
      ))}
      <button onClick={() => addServer("Demo", "http://localhost:5173/mcp")}>
        Add Server
      </button>
    </div>
  );
}

How It Works

1

Agent manages connections

The Agent manages MCP server connections via the built-in mcp property. Each connection maintains state about available tools, prompts, and resources.
2

OAuth configuration

configureOAuthCallback sets up the OAuth flow. When an MCP server requires authentication, a popup window opens. After auth completes, the custom handler closes the popup automatically.
3

Real-time updates

When servers are added, removed, or change state, the Agent broadcasts updates via WebSocket. The client’s onMcpUpdate callback receives the new state.
4

Call server methods

The client uses agent.call() to invoke callable methods on the Agent, like adding or removing servers.

OAuth Flow

For MCP servers that require authentication:
// Server: configure OAuth callback
this.mcp.configureOAuthCallback({
  customHandler: (result) => {
    if (result.authSuccess) {
      // Close the popup window
      return new Response("<script>window.close();</script>", {
        headers: { "content-type": "text/html" }
      });
    }
    // Show error in popup
    return new Response(`Auth failed: ${result.authError}`, {
      status: 400
    });
  }
});

// Client: add server that requires auth
await agent.call("addServer", [
  "GitHub",
  "https://mcp-server.example.com/mcp"
]);
// If auth is required, a popup automatically opens
// After user authorizes, popup closes and connection completes

MCP Server State

The onMcpUpdate callback receives an array of server info:
type McpServerInfo = {
  id: string;
  name: string;
  url: string;
  status: "connecting" | "connected" | "disconnected" | "error";
  error?: string;
  tools: {
    name: string;
    description: string;
    inputSchema: Record<string, unknown>;
  }[];
  prompts: {
    name: string;
    description: string;
  }[];
  resources: {
    uri: string;
    name: string;
    description: string;
  }[];
};

Using MCP Tools in AI Chat

Combine MCP client with AI chat:
import { AIChatAgent } from "@cloudflare/ai-chat";
import { streamText } from "ai";

export class ChatAgent extends AIChatAgent {
  waitForMcpConnections = true;

  async onChatMessage() {
    // Get tools from all connected MCP servers
    const mcpTools = this.mcp.getAITools();

    const result = streamText({
      model,
      tools: {
        // MCP tools are now available to the LLM
        ...mcpTools,
        // Plus any local tools
        myLocalTool: tool({ ... })
      },
      messages: this.messages
    });

    return result.toUIMessageStreamResponse();
  }
}

Running the Example

1

Install dependencies

npm install
2

Configure environment

Copy .env.example to .env:
cp .env.example .env
Set HOST to your callback host (usually http://localhost:5173 for local dev).
3

Start the server

npm run dev
4

Connect to a server

Open http://localhost:5173, enter an MCP server URL, and click Connect.To test with authentication, run the mcp-worker-authenticated example alongside this one.

Testing with Another Example

Run the MCP Server example in another terminal:
cd examples/mcp
npm install && npm run dev
Then in this example, add the server:
  • Name: Demo
  • URL: http://localhost:5174/mcp (note the different port)
You’ll see the server’s tools and resources appear in the UI.

MCP Server

Build a stateful MCP server

MCP Authenticated

MCP server with OAuth authentication

AI Chat

Use MCP tools in AI chat (includes MCP client)

MCP Guide

In-depth guide to Model Context Protocol

Build docs developers (and LLMs) love