assistant-ui React MCP
Always consult assistant-ui.com/llms.txt for the latest API.
Let end users add, authenticate, and manage MCP servers from the browser with @assistant-ui/react-mcp. The connected servers' tools are merged into the chat runtime automatically.
Contents
- References | Routes vs tools | Mount the manager | Drop-in dialog | Compose from primitives | OAuth connect flow | Custom storage | Imperative API | Common Gotchas | Related Skills
References
- ./references/setup.md -- McpManagerResource, defineConnector, storage, useAui({ mcp })
- ./references/ui.md -- McpManagerPrimitive / McpServerPrimitive / McpConfigDialog
- ./references/oauth.md -- OAuth connect flow and auth modes
Routes vs tools
This skill is for user-managed MCP servers: the end user picks and authenticates servers at runtime in the browser. Each server's tools are namespaced as serverId__toolName and exposed to the chat runtime with no extra wiring.
For developer-defined tools (frontend makeAssistantTool, backend AI SDK tool(), custom tool-call UI), use the tools skill instead. The two compose: a chat built with useChatRuntime can use both app tools and user-connected MCP tools at once.
Mount the manager
McpManagerResource({ connectors }) builds the mcp scope; pass it to useAui and wrap the app in AuiProvider. Connectors are presets declared with defineConnector.
"use client";
import { AuiProvider, useAui } from "@assistant-ui/react";
import { McpManagerResource, defineConnector } from "@assistant-ui/react-mcp";
const connectors = [
defineConnector({
id: "linear",
name: "Linear",
url: "https://mcp.linear.app",
auth: { type: "oauth", scopes: ["read"] },
icon: "/icons/linear.svg",
}),
defineConnector({
id: "weather",
name: "Weather",
url: "https://mcp.example.com/weather",
auth: { type: "none" },
}),
];
export function Providers({ children }: { children: React.ReactNode }) {
const aui = useAui({ mcp: McpManagerResource({ connectors }) });
return <AuiProvider value={aui}>{children}</AuiProvider>;
}
Defaults: storage is McpLocalStorage(), oauthRedirectUri is ${window.location.origin}/mcp/callback, and autoConnect is true.
Drop-in dialog
McpConfigDialog is the shadcn dialog that lists connectors and custom servers with inline auth controls and an add form. Drop it anywhere under the provider.
import { McpConfigDialog } from "@/components/assistant-ui/mcp-config";
export default function Page() {
return (
<header className="flex items-center justify-between">
<h1>My app</h1>
<McpConfigDialog />
</header>
);
}
Pass children to override the default trigger: <McpConfigDialog><Button>Servers</Button></McpConfigDialog>.
Compose from primitives
McpManagerPrimitive iterates servers; nested McpServerPrimitive.* reads each item's scope automatically. Conditional buttons render only when the connection state matches.
"use client";
import { McpManagerPrimitive, McpServerPrimitive } from "@assistant-ui/react-mcp";
const ServerCard = () => (
<McpServerPrimitive.Root>
<McpServerPrimitive.Icon />
<McpServerPrimitive.Name />
<McpServerPrimitive.Status />
<McpServerPrimitive.ConnectButton>Connect</McpServerPrimitive.ConnectButton>
<McpServerPrimitive.DisconnectButton>Disconnect</McpServerPrimitive.DisconnectButton>
<McpServerPrimitive.OAuthLink>Authorize</McpServerPrimitive.OAuthLink>
<McpServerPrimitive.RemoveButton>Remove</McpServerPrimitive.RemoveButton>
<McpServerPrimitive.Error />
</McpServerPrimitive.Root>
);
export default function McpPage() {
return (
<McpManagerPrimitive.Root>
<h2>Connectors</h2>
<McpManagerPrimitive.Connectors>{() => <ServerCard />}</McpManagerPrimitive.Connectors>
<h2>Your servers</h2>
<McpManagerPrimitive.CustomServers>{() => <ServerCard />}</McpManagerPrimitive.CustomServers>
<McpManagerPrimitive.AddCustomTrigger>Add custom server</McpManagerPrimitive.AddCustomTrigger>
</McpManagerPrimitive.Root>
);
}
RemoveButton is hidden on connectors (presets cannot be removed). Omit AddCustomTrigger and CustomServers to disable user-added servers. Build the add form with McpAddFormPrimitive (Root, NameField, UrlField, AuthSelect, AuthFields, Error, Submit, Cancel); see ui.md.
OAuth connect flow
auth is one of { type: "none" }, { type: "bearer", token? }, or { type: "oauth", scopes?, ... }. For OAuth, add a callback route at the configured oauthRedirectUri and render McpOAuthCallback inside the same provider so it can finish the handshake.
"use client";
import { McpOAuthCallback } from "@assistant-ui/react-mcp";
import { useRouter } from "next/navigation";
import { Providers } from "../../providers";
export default function Callback() {
const router = useRouter();
return (
<Providers>
<McpOAuthCallback onComplete={() => router.replace("/mcp")} />
</Providers>
);
}
See oauth.md for the full auth-mode shapes and connection-state values (connected, connecting, authRequired, authPending, error, disconnected).
Custom storage
Replace the default McpLocalStorage() to persist custom servers and auth state on a backend (use McpMemoryStorage() for SSR/tests).
import { McpManagerResource, McpCustomStorage } from "@assistant-ui/react-mcp";
const aui = useAui({
mcp: McpManagerResource({
connectors,
storage: McpCustomStorage({
loadCustomServers: async () => fetch("/api/mcp/servers").then((r) => r.json()),
saveCustomServers: async (records) =>
fetch("/api/mcp/servers", { method: "PUT", body: JSON.stringify(records) }),
loadAuthState: async (id) =>
fetch(`/api/mcp/auth/${id}`).then((r) => (r.ok ? r.json() : null)),
saveAuthState: async (id, state) =>
fetch(`/api/mcp/auth/${id}`, { method: "PUT", body: JSON.stringify(state) }),
clearAuthState: async (id) => fetch(`/api/mcp/auth/${id}`, { method: "DELETE" }),
}),
}),
});
Imperative API
Inside event handlers, drive the manager through useAui().mcp().
const aui = useAui();
await aui.mcp().addCustomServer({ name, url, auth: { type: "bearer", token } });
await aui.mcp().server({ id }).connect();
await aui.mcp().server({ id }).callTool("echo", { text: "hi" });
Read reactive state with useAuiState, scoped under s.mcp (manager) and s.mcpServer (current item inside a McpServerPrimitive subtree):
const isHydrated = useAuiState((s) => s.mcp.isHydrated);
const connectionState = useAuiState((s) => s.mcpServer.connectionState);
Common Gotchas
Tools not appearing in chat
- The server must reach the
connectedstate; checkMcpServerPrimitive.Statusors.mcpServer.connectionState. - Tool names are prefixed
serverId__toolName; reference that exact name in tool UI.
OAuth never completes
- The callback route path must match
oauthRedirectUri(default/mcp/callback). McpOAuthCallbackmust be rendered inside the same provider as the manager.
Servers not persisting / SSR errors
- Default
McpLocalStorage()needs the browser; useMcpMemoryStorage()orMcpCustomStorage(...)on the server.
Custom server cannot be removed
RemoveButtonhides on connector presets by design; only user-added servers are removable.
Transport
- Only StreamableHTTP is supported; resources, prompts, sampling, and auto-reconnect are not yet wired.
Related Skills
- tools -- developer-defined frontend/backend tools and custom tool-call UI (
makeAssistantTool, AI SDKtool(),makeAssistantToolUI); the complement to user-managed MCP servers. - setup -- scaffold with the
mcptemplate (npx assistant-ui@latest create -t mcp) and pick a runtime. - runtime -- the chat runtime (
useChatRuntime) that MCP tools are merged into.

