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:
- Tries direct connection first
- Detects FastMCP or CORS errors automatically
- Retries with proxy seamlessly
- 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
authProvider?: OAuthClientProvider; // Optional external OAuth provider (headless/custom runtimes)
clientInfo?: {
name: string;
version: string;
description?: string;
icons?: Array<{ src: string }>;
websiteUrl?: string;
};
// Reconnection & Health Checks
autoRetry?: boolean | number; // Auto-retry on initial failure
autoReconnect?: boolean | number | { // Auto-reconnect on drop (default: 3000ms)
enabled?: boolean; // Enable/disable (default: true)
initialDelay?: number; // Delay before reconnect in ms (default: 3000)
healthCheckInterval?: number | false; // Health check polling in ms, or false to disable (default: 10000)
healthCheckTimeout?: number; // Time before connection considered dead in ms (default: 30000)
};
reconnectionOptions?: ReconnectionOptions; // SDK-level transport reconnection
// 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.
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). For elicitation, you can use helpers from mcp-use (e.g. acceptWithDefaults, validate) inside your callback; see 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>;
Reconnection & Health Checks
When autoReconnect is enabled (the default), the hook monitors connection health by sending periodic HEAD requests to the server. If the server becomes unreachable, it automatically reconnects.
How it works
- After a successful connection, a health check timer starts
- Every 10 seconds (default), a HEAD request is sent to the server URL
- If no successful response is received for 30 seconds (default), the connection is considered broken
- The hook transitions to
"discovering" state and reconnects after a configurable delay
Configuration
autoReconnect accepts three forms:
// Boolean: enable with defaults (3s reconnect delay, 10s health check interval)
useMcp({ url: '...', autoReconnect: true })
// Number: custom reconnect delay in ms
useMcp({ url: '...', autoReconnect: 5000 })
// Object: full control over reconnection and health checks
useMcp({
url: '...',
autoReconnect: {
enabled: true,
initialDelay: 5000, // Wait 5s before reconnecting
healthCheckInterval: 30000, // Poll every 30s instead of 10s
healthCheckTimeout: 60000, // Wait 60s before declaring dead
},
})
Disabling health checks
You can disable health check polling entirely while still reconnecting on transport-level failures:
useMcp({
url: '...',
autoReconnect: {
healthCheckInterval: false, // No HEAD request polling
},
})
SDK-level reconnection options
The reconnectionOptions prop controls the underlying StreamableHTTPClientTransport retry behavior, separate from the health check system:
import type { ReconnectionOptions } from 'mcp-use/react'
useMcp({
url: '...',
reconnectionOptions: {
initialReconnectionDelay: 2000, // Start with 2s delay (default: 1000)
maxReconnectionDelay: 60000, // Cap at 60s (default: 30000)
reconnectionDelayGrowFactor: 2, // Double each retry (default: 1.5)
maxRetries: 5, // Retry up to 5 times (default: 2)
},
})
autoReconnect controls the application-level health monitoring (HEAD request polling and reconnect triggers).
reconnectionOptions controls the transport-level retry behavior within the MCP SDK when the SSE stream drops.
Both can be used together for robust connection handling.
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
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.
complete(params)
Request autocomplete suggestions for prompt arguments or resource template URIs:
// Complete a prompt argument
const result = await server.complete({
ref: { type: 'ref/prompt', name: 'code-review' },
argument: { name: 'language', value: 'py' }
});
console.log('Suggestions:', result.completion.values);
// Output: ['python', 'pytorch', ...]
// Complete a resource template URI variable
const result = await server.complete({
ref: { type: 'ref/resource', uri: 'file:///{path}' },
argument: { name: 'path', value: '/home' }
});
See Completions for detailed usage and examples.
refreshResourceTemplates()
Manually refresh the resource templates list from the server:
await server.refreshResourceTemplates();
console.log('Updated templates:', server.resourceTemplates);
The resourceTemplates state is automatically populated on connection and updated when notifications are received. Use this method to force a refresh when needed.
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>
);
}
With elicitation (same pattern as the Node client):
import { useMcp } from "mcp-use/react";
import { acceptWithDefaults } from "mcp-use";
const mcp = useMcp({
url: "http://localhost:3000/mcp",
onElicitation: async (params) => acceptWithDefaults(params),
});
For most applications, use McpClientProvider instead of standalone
useMcp. The provider offers better multi-server support, automatic proxy
fallback, and notification management.
Next Steps