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