Skip to main content
mcp-use provides comprehensive React integration through McpClientProvider and the useMcp hook. The provider-based approach is recommended for managing multiple MCP server connections in your application.

Features

  • Multi-server management: Connect to multiple MCP servers simultaneously
  • Automatic proxy fallback: Smart fallback to proxy when direct connection fails (FastMCP/CORS)
  • OAuth support: Complete OAuth flow with token management
  • Notification management: Track and handle server notifications per server
  • Sampling/Elicitation: Built-in handling for interactive server requests
  • Type safety: Full TypeScript support with automatic type inference
Recommended Approach: Use McpClientProvider to manage multiple servers with automatic proxy fallback, notification management, and persistence support. It provides a superior developer experience compared to standalone useMcp().

Quick Start with Provider

import { McpClientProvider, useMcpClient, useMcpServer } from "mcp-use/react";

// 1. Wrap your app with the provider
function App() {
  return (
    <McpClientProvider
      defaultAutoProxyFallback={true} // Enable automatic proxy fallback
    >
      <MyComponent />
    </McpClientProvider>
  );
}

// 2. Add servers dynamically
function MyComponent() {
  const { addServer, servers } = useMcpClient();

  useEffect(() => {
    addServer("linear", {
      url: "https://mcp.linear.app/mcp",
      name: "Linear",
    });

    addServer("my-server", {
      url: "http://localhost:3000/mcp",
      name: "My Server",
      headers: { Authorization: "Bearer YOUR_API_KEY" },
    });
  }, [addServer]);

  return (
    <div>
      <h2>Connected Servers ({servers.length})</h2>
      {servers.map((server) => (
        <ServerCard key={server.id} server={server} />
      ))}
    </div>
  );
}

// 3. Use individual servers
function ServerCard({ server }) {
  if (server.state !== "ready") {
    return (
      <div>
        {server.name}: {server.state}...
      </div>
    );
  }

  return (
    <div>
      <h3>{server.serverInfo?.name || server.name}</h3>
      <p>Tools: {server.tools.length}</p>
      <button onClick={() => server.callTool("my-tool", {})}>Call Tool</button>
    </div>
  );
}

Automatic Proxy Fallback

The provider includes intelligent automatic proxy fallback for FastMCP and CORS-restricted servers:
<McpClientProvider
  defaultAutoProxyFallback={true} // Enabled by default
  // Uses https://inspector.mcp-use.com/inspector/api/proxy by default
>
  <MyApp />
</McpClientProvider>
How it works:
  1. Tries direct connection first
  2. Detects FastMCP or CORS errors automatically
  3. Retries with proxy seamlessly
  4. Success! Connection established through proxy
Custom proxy configuration:
<McpClientProvider
  defaultAutoProxyFallback={{
    enabled: true,
    proxyAddress: "http://localhost:3005/inspector/api/proxy",
  }}
>
  <MyApp />
</McpClientProvider>
Per-server override:
// Disable automatic fallback for a specific server
addServer("my-server", {
  url: "http://localhost:3000/mcp",
  autoProxyFallback: false, // Override provider default
});

// Or use a different proxy for one server
addServer("special-server", {
  url: "https://api.example.com/mcp",
  proxyConfig: {
    proxyAddress: "https://my-custom-proxy.com/api/proxy",
  },
});

Connection States

Each server manages its connection state:
function ServerStatus({ serverId }) {
  const server = useMcpServer(serverId);

  if (!server) return null;

  switch (server.state) {
    case "discovering":
      return <Spinner>Connecting...</Spinner>;

    case "authenticating":
      return <div>Authenticating... Check for popup window</div>;

    case "pending_auth":
      return (
        <button onClick={server.authenticate}>Click to Authenticate</button>
      );

    case "ready":
      return <div>✅ Connected ({server.tools.length} tools available)</div>;

    case "failed":
      return (
        <div>
Connection failed: {server.error}
          <button onClick={server.retry}>Retry</button>
        </div>
      );
  }
}

Provider Configuration

McpClientProvider Props

interface McpClientProviderProps {
  // Default proxy configuration for all servers (can be overridden per-server)
  defaultProxyConfig?: {
    proxyAddress?: string;
    headers?: Record<string, string>;
  };

  // Enable automatic proxy fallback (default: true)
  // When a server fails with FastMCP or CORS errors, automatically retries with proxy
  defaultAutoProxyFallback?:
    | boolean
    | {
        enabled?: boolean;
        proxyAddress?: string; // Default: https://inspector.mcp-use.com/inspector/api/proxy
      };

  // Initial servers (auto-connected on mount)
  mcpServers?: Record<string, McpServerOptions>;

  // Persistence
  storageProvider?: StorageProvider;

  // Debugging
  enableRpcLogging?: boolean;

  // Callbacks
  onServerAdded?: (id: string, server: McpServer) => void;
  onServerRemoved?: (id: string) => void;
  onServerStateChange?: (id: string, state: string) => void;
  onSamplingRequest?: (request, serverId, serverName, approve, reject) => void;
  onElicitationRequest?: (
    request,
    serverId,
    serverName,
    approve,
    reject
  ) => void;
}

Server Options

interface McpServerOptions {
  // Connection
  url?: string; // MCP server URL
  name?: string; // Display name for the server
  enabled?: boolean; // Enable/disable connection (default: true)
  headers?: Record<string, string>; // Auth headers
  transportType?: "auto" | "http" | "sse"; // Transport preference (default: 'auto')
  timeout?: number; // Connection timeout (ms, default: 30000)

  // Proxy (overrides provider defaults)
  proxyConfig?: {
    proxyAddress?: string;
    headers?: Record<string, string>;
  };
  autoProxyFallback?: boolean | { enabled?: boolean; proxyAddress?: string };

  // OAuth
  preventAutoAuth?: boolean; // Prevent automatic OAuth popup (default: true)
  useRedirectFlow?: boolean; // Use redirect instead of popup (default: false)
  callbackUrl?: string; // OAuth callback URL
  clientInfo?: {
    name: string;
    version: string;
    description?: string;
    icons?: Array<{ src: string }>;
    websiteUrl?: string;
  };

  // Reconnection
  autoRetry?: boolean | number; // Auto-retry on failure
  autoReconnect?: boolean | number; // Auto-reconnect on drop (default: 3000ms)

  // Advanced
  wrapTransport?: (transport: any, serverId: string) => any;
  onNotification?: (notification: Notification) => void;
  onSampling?: (params) => Promise<CreateMessageResult>;
  onElicitation?: (params) => Promise<ElicitResult>;
}

Authentication

Bearer Token Authentication

function MyApp() {
  const { addServer } = useMcpClient();

  useEffect(() => {
    addServer("authenticated-server", {
      url: "http://localhost:3000/mcp",
      name: "My Server",
      headers: {
        Authorization: "Bearer YOUR_API_KEY",
      },
    });
  }, [addServer]);

  return <ServerList />;
}

OAuth Authentication

Manual OAuth Trigger (Default) By default, you need to explicitly trigger OAuth when a server requires authentication:
function ServerCard({ serverId }) {
  const server = useMcpServer(serverId);

  if (server.state === "pending_auth") {
    return (
      <button onClick={server.authenticate}>Sign in to {server.name}</button>
    );
  }

  if (server.state === "authenticating") {
    return <div>Authenticating...</div>;
  }

  // ... rest of component
}
Automatic OAuth (Legacy) To enable automatic OAuth popup when authentication is required, set preventAutoAuth: false:
function MyApp() {
  const { addServer } = useMcpClient();

  useEffect(() => {
    addServer("linear", {
      url: "https://mcp.linear.app/mcp",
      name: "Linear",
      preventAutoAuth: false, // Auto-trigger OAuth popup
    });
  }, [addServer]);

  return <ServerList />;
}
Redirect Flow (for mobile or popup-blocked environments)
addServer("linear", {
  url: "https://mcp.linear.app/mcp",
  name: "Linear",
  useRedirectFlow: true, // Use redirect instead of popup
  // preventAutoAuth: true is the default
});

OAuth Callback Page

Create an OAuth callback route to handle OAuth redirects:
// app/oauth/callback/page.tsx (Next.js App Router)
// or pages/oauth/callback.tsx (Next.js Pages Router)
import { onMcpAuthorization } from "mcp-use/auth";
// Also available from: import { onMcpAuthorization } from 'mcp-use/react'
import { useEffect } from "react";

export default function OAuthCallback() {
  useEffect(() => {
    // The function handles everything internally:
    // - Displays success/error UI directly in this callback window
    // - Posts message to opener window for popup flow
    // - Automatically redirects for redirect flow
    // - No need for error handling - it's handled internally
    onMcpAuthorization();
  }, []);

  // Simple loading state while processing
  return <div>Processing authentication...</div>;
}
The onMcpAuthorization() function handles all success and error cases internally. For popup flow, it posts a message to the opener window (which useMcp listens for automatically). For redirect flow, it handles the navigation. You don’t need custom error handling in your callback component.

Calling Tools

function ToolExecutor({ serverId }: { serverId: string }) {
  const server = useMcpServer(serverId);
  const [result, setResult] = useState(null);

  if (!server || server.state !== "ready") {
    return <div>Server not ready...</div>;
  }

  const handleSendEmail = async () => {
    try {
      const result = await server.callTool("send-email", {
        to: "user@example.com",
        subject: "Hello",
        body: "Test message",
      });
      setResult(result);
    } catch (error) {
      console.error("Tool call failed:", error);
    }
  };

  return (
    <div>
      <h3>{server.serverInfo?.name} Tools</h3>
      <button onClick={handleSendEmail}>Send Email</button>
      {result && <pre>{JSON.stringify(result, null, 2)}</pre>}

      <h4>Available Tools:</h4>
      <ul>
        {server.tools.map((tool) => (
          <li key={tool.name}>
            {tool.name}: {tool.description}
          </li>
        ))}
      </ul>
    </div>
  );
}

Reading Resources

function ResourceViewer({ serverId, uri }: { serverId: string; uri: string }) {
  const server = useMcpServer(serverId);
  const [content, setContent] = useState("");

  useEffect(() => {
    if (server?.state === "ready") {
      server.readResource(uri).then((resource) => {
        setContent(resource.contents[0].text || "");
      });
    }
  }, [server?.state, uri]);

  if (!server) return null;

  return (
    <div>
      <h3>
        {server.name} - Resource: {uri}
      </h3>
      <pre>{content}</pre>
    </div>
  );
}

Managing Multiple Servers

function ServerManager() {
  const { servers, addServer, removeServer } = useMcpClient();

  const handleAddLinear = () => {
    addServer("linear", {
      url: "https://mcp.linear.app/mcp",
      name: "Linear",
    });
  };

  const handleAddLocal = () => {
    addServer("local", {
      url: "http://localhost:3000/mcp",
      name: "Local Server",
      headers: { Authorization: "Bearer key" },
    });
  };

  return (
    <div>
      <button onClick={handleAddLinear}>Add Linear</button>
      <button onClick={handleAddLocal}>Add Local Server</button>

      <h3>Connected Servers ({servers.length})</h3>
      {servers.map((server) => (
        <div key={server.id}>
          <h4>{server.serverInfo?.name || server.name}</h4>
          <p>State: {server.state}</p>
          <p>Tools: {server.tools.length}</p>
          <p>Resources: {server.resources.length}</p>
          <p>Notifications: {server.unreadNotificationCount} unread</p>
          <button onClick={() => removeServer(server.id)}>Remove</button>
        </div>
      ))}
    </div>
  );
}

Persistence

Save server configurations to localStorage:
import { McpClientProvider, LocalStorageProvider } from "mcp-use/react";

function App() {
  return (
    <McpClientProvider
      storageProvider={new LocalStorageProvider("my-app-servers")}
      defaultAutoProxyFallback={true}
    >
      <MyApp />
    </McpClientProvider>
  );
}
Servers added via addServer() are automatically saved and restored on page reload. Custom Storage Provider:
class CustomStorageProvider implements StorageProvider {
  async getServers(): Promise<Record<string, McpServerOptions>> {
    // Load from your backend, IndexedDB, etc.
    return {};
  }

  async setServers(servers: Record<string, McpServerOptions>): Promise<void> {
    // Save to your backend, IndexedDB, etc.
  }
}

Notification Management

Each server maintains its own notification history:
function NotificationPanel({ serverId }: { serverId: string }) {
  const server = useMcpServer(serverId);

  if (!server) return null;

  return (
    <div>
      <h3>Notifications ({server.unreadNotificationCount} unread)</h3>
      <button onClick={server.markAllNotificationsRead}>Mark All Read</button>
      <button onClick={server.clearNotifications}>Clear All</button>

      <ul>
        {server.notifications.map((notification) => (
          <li
            key={notification.id}
            style={{ fontWeight: notification.read ? "normal" : "bold" }}
            onClick={() => server.markNotificationRead(notification.id)}
          >
            {notification.method}
            <pre>{JSON.stringify(notification.params, null, 2)}</pre>
          </li>
        ))}
      </ul>
    </div>
  );
}

Sampling & Elicitation

Handle interactive server requests (AI sampling, form elicitation):
function SamplingHandler() {
  const { servers } = useMcpClient();

  return (
    <div>
      {servers.map((server) => (
        <div key={server.id}>
          {server.pendingSamplingRequests.map((request) => (
            <div key={request.id}>
              <h4>{server.name} needs AI assistance</h4>
              <pre>{JSON.stringify(request.request.params, null, 2)}</pre>
              <button
                onClick={() =>
                  server.approveSampling(request.id, {
                    content: [{ type: "text", text: "AI response here" }],
                    model: "gpt-4",
                    role: "assistant",
                  })
                }
              >
                Approve
              </button>
              <button onClick={() => server.rejectSampling(request.id)}>
                Reject
              </button>
            </div>
          ))}
        </div>
      ))}
    </div>
  );
}

Error Handling

function ServerMonitor() {
  const { servers } = useMcpClient();

  return (
    <div>
      {servers.map((server) => (
        <div key={server.id}>
          <h3>{server.name}</h3>

          {server.state === "failed" && (
            <div className="error">
              <p>❌ {server.error}</p>
              <button onClick={server.retry}>Retry Connection</button>

              {/* Common error guidance */}
              {server.error?.includes("401") && (
                <p>💡 Add Authorization header in server configuration</p>
              )}
              {server.error?.includes("CORS") && (
                <p>💡 CORS error - proxy fallback will retry automatically</p>
              )}
              {server.error?.includes("FastMCP") && (
                <p>
                  💡 FastMCP error - proxy fallback will retry automatically
                </p>
              )}
            </div>
          )}

          {server.state === "ready" && (
            <div className="success">
Connected - {server.tools.length} tools available
            </div>
          )}
        </div>
      ))}
    </div>
  );
}

// Provider-level error handling
<McpClientProvider
  defaultAutoProxyFallback={true}
  onServerStateChange={(id, state) => {
    console.log(`Server ${id} state changed to: ${state}`);
  }}
  onServerAdded={(id, server) => {
    console.log(`Server ${id} added:`, server);
  }}
>
  <MyApp />
</McpClientProvider>;

API Reference

Provider Hooks

useMcpClient()

Access the multi-server client context:
const {
  servers,
  addServer,
  removeServer,
  updateServer,
  getServer,
  storageLoaded,
} = useMcpClient();

// Add a server
addServer("my-server", {
  url: "http://localhost:3000/mcp",
  name: "My Server",
  headers: { Authorization: "Bearer key" },
});

// Update a server's configuration (disconnects and reconnects)
await updateServer("my-server", {
  headers: { Authorization: "Bearer new-key" },
});

// Remove a server
removeServer("my-server");

// Get a specific server
const server = getServer("my-server");

useMcpServer(id)

Access a specific server’s state and methods:
const server = useMcpServer("my-server");

// Server properties
server.id; // "my-server"
server.name; // "My Server"
server.state; // "ready" | "discovering" | "failed" | ...
server.tools; // Tool[]
server.resources; // Resource[]
server.prompts; // Prompt[]
server.serverInfo; // { name, version, ... }
server.error; // Error message if failed
server.notifications; // McpNotification[]
server.unreadNotificationCount; // number

// Server methods
await server.callTool("tool-name", { args });
await server.readResource("uri");
await server.listResources();
await server.listPrompts();
await server.getPrompt("prompt-name", { args });
server.retry();
server.disconnect();
server.authenticate();
server.clearStorage();
server.markNotificationRead(notificationId);
server.markAllNotificationsRead();
server.clearNotifications();

Server Methods

callTool(name, args, options?)

const result = await server.callTool("send-email", {
  to: "user@example.com",
  subject: "Hello",
  body: "Test",
});

// With timeout
const result = await server.callTool(
  "long-task",
  { data: "..." },
  {
    timeout: 300000, // 5 minutes
    resetTimeoutOnProgress: true,
  }
);

readResource(uri), listResources(), listPrompts(), getPrompt()

Same API as standalone useMcp, but accessed per-server.

Standalone useMcp Hook

For simple single-server applications, you can use useMcp directly without the provider:
import { useMcp } from "mcp-use/react";

function SimpleApp() {
  const mcp = useMcp({
    url: "http://localhost:3000/mcp",
    headers: { Authorization: "Bearer key" },
    autoProxyFallback: true, // Enable automatic proxy fallback
  });

  if (mcp.state !== "ready") return <div>Connecting...</div>;

  return (
    <div>
      <h2>Tools ({mcp.tools.length})</h2>
      {mcp.tools.map((tool) => (
        <div key={tool.name}>{tool.name}</div>
      ))}
    </div>
  );
}
For most applications, use McpClientProvider instead of standalone useMcp. The provider offers better multi-server support, automatic proxy fallback, and notification management.

Next Steps