Loading...
Loading...
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.
npx skill4agent add assistant-ui/skills react-mcp@assistant-ui/react-mcpserverId__toolNamemakeAssistantTooltool()useChatRuntimeMcpManagerResource({ connectors })mcpuseAuiAuiProviderdefineConnector"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>;
}storageMcpLocalStorage()oauthRedirectUri${window.location.origin}/mcp/callbackautoConnecttrueMcpConfigDialogimport { 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>
);
}children<McpConfigDialog><Button>Servers</Button></McpConfigDialog>McpManagerPrimitiveMcpServerPrimitive.*"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>
);
}RemoveButtonAddCustomTriggerCustomServersMcpAddFormPrimitiveRootNameFieldUrlFieldAuthSelectAuthFieldsErrorSubmitCancelauth{ type: "none" }{ type: "bearer", token? }{ type: "oauth", scopes?, ... }oauthRedirectUriMcpOAuthCallback"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>
);
}connectedconnectingauthRequiredauthPendingerrordisconnectedMcpLocalStorage()McpMemoryStorage()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" }),
}),
}),
});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" });useAuiStates.mcps.mcpServerMcpServerPrimitiveconst isHydrated = useAuiState((s) => s.mcp.isHydrated);
const connectionState = useAuiState((s) => s.mcpServer.connectionState);connectedMcpServerPrimitive.Statuss.mcpServer.connectionStateserverId__toolNameoauthRedirectUri/mcp/callbackMcpOAuthCallbackMcpLocalStorage()McpMemoryStorage()McpCustomStorage(...)RemoveButtonmakeAssistantTooltool()makeAssistantToolUImcpnpx assistant-ui@latest create -t mcpuseChatRuntime