How to install react-mcp
npx skills add https://github.com/assistant-ui/skills --skill react-mcpFull instructions (SKILL.md)
Source of truth, from assistant-ui/skills.
name: react-mcp description: "Lets end users add, authenticate, and manage MCP servers from the browser in assistant-ui apps with @assistant-ui/react-mcp. Use when building user-managed MCP server UIs: mounting McpManagerResource via useAui({ mcp }), declaring presets with defineConnector, dropping in McpConfigDialog, or composing McpManagerPrimitive (Root, Connectors, CustomServers, AddCustomTrigger), McpServerPrimitive (Root, Name, Icon, Status, ConnectButton, DisconnectButton, OAuthLink, RemoveButton, Error), and McpAddFormPrimitive (NameField, UrlField, AuthSelect, AuthFields, Submit, Cancel). Covers auth modes none/bearer/oauth, the OAuth flow with McpOAuthCallback, connection states, storage via McpLocalStorage/McpMemoryStorage/McpCustomStorage, reading state with useAuiState (s.mcp, s.mcpServer), and imperative addCustomServer/connect/callTool. Distinct from developer-defined backend @ai-sdk/mcp tools in the tools skill. Reach for this when connected-server tools are missing, OAuth never completes, or servers do not persist." license: MIT
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.
Related skills
More from assistant-ui/skills and the wider catalog.
assistant-ui
Overview and router for assistant-ui, the React library for building AI chat interfaces from composable primitives. Use for high-level, cross-cutting, or architecture-overview questions: choosing packages, picking a runtime, or understanding the layered model (RuntimeCore, Runtime, context hooks, primitives) and message model. Covers the `@assistant-ui/react` core plus `@assistant-ui/react-ai-sdk`, `@assistant-ui/react-langgraph`, `assistant-stream`, and `assistant-cloud`; `AssistantRuntimeProvider`; the primitives `ThreadPrimitive`, `MessagePrimitive`, `ComposerPrimitive`; the hooks `useAui`, `useAuiState`, `useAuiEvent`; and runtime selection across `useChatRuntime`, `useExternalStoreRuntime`, `useLangGraphRuntime`, `useLocalRuntime`. For a specific area route to a focused sibling instead: setup, runtime, primitives, tools, streaming, cloud, thread-list, or update. Not for hands-on tasks already owned by those siblings.
streaming
Streaming backends and wire protocols for assistant-ui via the assistant-stream package. Use when building a custom (non-AI-SDK) streaming endpoint with createAssistantStreamResponse or createAssistantStreamController, emitting parts through appendText/appendReasoning/appendSource/appendFile/addToolCallPart and setResponse; choosing between the AI SDK Data Stream format (toUIMessageStreamResponse) and the native Assistant Transport format; encoding or decoding streams with DataStreamEncoder/DataStreamDecoder, AssistantTransportEncoder/AssistantTransportDecoder, PlainTextEncoder, or UIMessageStreamDecoder; or wiring streamed chunks into useLocalRuntime or useChatRuntime. Use specifically for debugging stream wire issues: text-delta, part-start, result events, text/event-stream content-type, SSE format, tool calls not rendering, or partial text not showing. For general non-stream debugging route to the relevant focused skill, not the parent overview.
tools
Registers LLM tools and renders custom tool-call UI in assistant-ui (@assistant-ui/react). Use when adding frontend-only tools with makeAssistantTool / useAssistantTool (browser actions like clipboard, navigation, localStorage, async-generator streaming, AbortSignal), rendering backend AI SDK tool() calls with makeAssistantToolUI / useAssistantToolUI (status.type running/complete/incomplete/requires-action, args, result, artifact via ToolCallMessagePartProps), building generative UI from tool results, or implementing human-in-the-loop and approval flows (addResult, resume with context.human(), respondToApproval for server-side needsApproval gates). Covers registering tool components inside AssistantRuntimeProvider and the case-sensitive toolName matching that connects a tool to its UI. Reach for this when tool UI is not rendering, a tool is not being called, or a result is not showing.
primitives
Builds and customizes assistant-ui chat UI from composable, unstyled @assistant-ui/react primitives that follow Radix-style part composition. Use when assembling or styling a custom Thread, Composer, message rendering, action bar, or branch picker from building blocks: ThreadPrimitive (.Root, .Viewport, .Messages, .Empty, .ScrollToBottom), ComposerPrimitive (.Input, .Send, .Cancel, .Attachments), MessagePrimitive (.Parts/.Content, .Error), ActionBarPrimitive (.Copy, .Edit, .Reload, .Speak, feedback, .ExportMarkdown), BranchPickerPrimitive, AttachmentPrimitive, ThreadListPrimitive, ThreadListItemPrimitive. Covers MessagePrimitive.Parts children render functions for text, image, reasoning, and tool-call parts; conditional rendering with AuiIf (deprecated .If); and gotchas like wrapping in AssistantRuntimeProvider and adding className since primitives ship unstyled. For prebuilt drop-in UI and scaffolding use setup; for multi-thread sidebar behavior use thread-list.
runtime
Guide to the assistant-ui runtime system, single-thread state, and the imperative runtime API in @assistant-ui/react. Use when creating a runtime (useLocalRuntime with a ChatModelAdapter, useExternalStoreRuntime for Redux/Zustand, useRemoteThreadListRuntime), wiring AssistantRuntimeProvider, or reading/mutating thread, message, and composer state and events. Covers the unified hooks useAui, useAuiState, useAuiEvent (composer.send, thread.runStart, thread.runEnd), legacy hooks (useAssistantRuntime, useThreadRuntime, useMessageRuntime, useComposerRuntime, useThread, useThreadMessages), the AssistantRuntime/ThreadRuntime/MessageRuntime/ComposerRuntime hierarchy, thread operations (append, cancelRun, message().edit/reload), capabilities, and types (ThreadMessage, MessagePart, MessageStatus, ChatModelRunResult). Use for provider \"Cannot read property of undefined\" errors or state not updating. For multi-thread list UI and switching between conversations use thread-list instead.
thread-list
Implements multi-thread (conversation history) management in assistant-ui apps: rendering the prebuilt `ThreadList` next to `Thread`, or building a custom sidebar with `ThreadListPrimitive` and `ThreadListItemPrimitive` (Root, New, Items, Trigger, Title, Archive, Unarchive, Delete). Covers thread CRUD through the `useAui()`/`useAuiState()` API: `switchToThread`, `switchToNewThread`, and per item `rename`, `archive`, `unarchive`, `delete`, `generateTitle`, `initialize`, plus reading `s.threads.threadIds`/`archivedThreadIds`/`mainThreadId`. Includes cloud-backed persistence via `useChatRuntime` + `AssistantCloud` and a local `useRemoteThreadListRuntime` + `InMemoryThreadListAdapter` path. Use when a user wants a thread list/sidebar, switching, searching, sorting, drag-and-drop, or renaming/archiving/deleting conversations. For single-thread state, messages, or composer use runtime; for cloud auth/persistence setup use cloud.