Elements

MCP config dialog

A dialog for connectors and custom MCP servers, including authentication and connection state.

MCP servers

Connect servers to expose their tools to this assistant.

Connectors

No connectors configured

Custom servers

fig. 01

Installation

Install manually
npx shadcn@latest add "@assistant-ui/mcp-config"
First time? Set up a runtime

Runtime components read their state from an assistant-ui runtime. Add one to an existing project:

npx assistant-ui@latest init

Then wrap your app in a runtime provider:

import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useChatRuntime, AssistantChatTransport } from "@assistant-ui/ai-sdk";

export default function App() {
  const runtime = useChatRuntime({
    transport: new AssistantChatTransport({ api: "/api/chat" }),
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      {/* your components */}
    </AssistantRuntimeProvider>
  );
}

The installation guide covers new projects, templates, and API routes.

MCP config dialog lists every app-defined connector and user-added custom server in one place, with inline connect, authorize, and remove controls, plus a form for adding a new server. With a runtime it reads and drives live connection state through the MCP manager. There is no standalone form, because connection, authorization, and tool lists are states of a running manager.

Getting started

Mount the manager

McpManagerResource is the entry point: mount it once at the root of your app, with the connectors you want available to every user.

app/assistant.tsx
import { AuiConfig, AuiProvider, useAui } from "@assistant-ui/react";
import { McpManagerResource } from "@assistant-ui/react-mcp";

export function McpProviders({ children }: { children: React.ReactNode }) {
  const aui = useAui();
  const config = AuiConfig({
    mcp: McpManagerResource({
      connectors: [
        { id: "linear", name: "Linear", url: "https://mcp.linear.app/mcp", auth: { type: "oauth" } },
      ],
    }),
  });
  return (
    <AuiProvider extends={aui} config={config}>
      {children}
    </AuiProvider>
  );
}

Render the dialog

McpConfigDialog renders anywhere inside the provider. With no children, it renders its own outlined trigger button; pass an element as children to use your own trigger instead.

components/assistant-ui/elements/thread.aui.tsx
import { McpConfigDialog } from "@/components/assistant-ui/elements/mcp-config.aui";

<McpConfigDialog />

Anatomy

<Dialog>
  <DialogTrigger>{/* plug icon + "MCP servers", or your own children */}</DialogTrigger>
  <DialogContent>
    <section> {/* Connectors */}
      {/* one server card per app-defined connector */}
    </section>
    <section> {/* Custom servers */}
      {/* one server card per user-added server */}
      {/* "Add server" trigger, or the add form once opened */}
    </section>
  </DialogContent>
</Dialog>

Each server card shows an avatar (the server's icon, or a fallback icon), its name, a status badge, action buttons, and, once it has one, an error banner. Which action buttons render depends on connectionState: Connect shows for "disconnected", "error", or "authRequired"; the OAuth link shows whenever the server exposes an authorizationUrl; Disconnect shows for "connected", "connecting", or "authPending". A card in "error" state additionally gets a destructive-tinted border. Each card also carries a visually hidden role="status" region that announces later connection state changes and new connection errors with their message, while the state a card opens with stays silent.

Examples

Custom trigger

Pass any element as children and it becomes the dialog's trigger in place of the default button.

<McpConfigDialog>
  <button type="button">Connectors</button>
</McpConfigDialog>

A server card outside the dialog

Compose McpServerPrimitive.Root directly for a sidebar or settings page that shows one server without the rest of the dialog's chrome.

import { McpServerByIdProvider, McpServerPrimitive } from "@assistant-ui/react-mcp";

<McpServerByIdProvider id="linear">
  <McpServerPrimitive.Root>
    <McpServerPrimitive.Name />
    <McpServerPrimitive.ConnectButton>Connect</McpServerPrimitive.ConnectButton>
  </McpServerPrimitive.Root>
</McpServerByIdProvider>

Custom auth fields

AuthFields renders a labeled bearer token input or OAuth scopes input depending on the selected AuthSelect value by default; pass children to render your own inputs for the current auth type instead. Render them through BearerTokenField and ScopesField so their values reach the submitted auth config. With asChild, your component receives the bound value and onChange and must pass them on to its input.

<McpAddFormPrimitive.AuthFields>
  {({ authType }) =>
    authType === "bearer" ? (
      <McpAddFormPrimitive.BearerTokenField asChild>
        <MyTokenField />
      </McpAddFormPrimitive.BearerTokenField>
    ) : null
  }
</McpAddFormPrimitive.AuthFields>

API reference

McpConfigDialog props

PropTypeDefaultDescription
childrenReactNodeoutlined button with a plug iconCustom trigger element.

McpManagerPrimitive

PartRendersNotes
RootdivSets data-mcp-hydrated once storage has loaded.
ConnectorslistRender-prop, called once per app-defined connector with { server }.
CustomServerslistRender-prop, called once per user-added server with { server }.
AddCustomTriggerbuttonA plain button with no built-in action; show your add-server form from its onClick.

McpServerPrimitive

PartRendersNotes
RootdivSets data-server-id, data-kind, data-connection-state, data-has-error.
NametextThe server's name.
ConnectButtonbuttonRenders null unless connectionState is "disconnected", "error", or "authRequired". Calls aui.mcpServer.connect().
OAuthLinkaRenders null unless the server has an authorizationUrl; opens it in a new tab.
DisconnectButtonbuttonRenders null unless connectionState is "connected", "connecting", or "authPending". Calls aui.mcpServer.disconnect().
RemoveButtonbuttonCalls aui.mcpServer.remove().

Status, Error, Icon, Tools, and ToolName are also available on McpServerPrimitive for building a more detailed server view than the dialog's own card.

McpAddFormPrimitive

PartRendersNotes
RootformOwns the form's state. Takes onSubmitted(id) and onCancel.
NameField, UrlFieldinputBound text inputs. The field that fails validation gets aria-invalid and the error's id in aria-describedby; with asChild, pass aria-describedby to the part, because the slotted child's own value replaces the composed one.
AuthSelectselect"oauth" | "bearer" | "none", defaults to "oauth".
AuthFieldslabeled inputsBearer token or OAuth scopes input with its own label, matching the current AuthSelect value.
BearerTokenField, ScopesFieldinputThe bound inputs AuthFields renders, for use in custom AuthFields children. BearerTokenField marks a missing token the way NameField marks a missing name, with the same asChild rule.
SubmitbuttonDisabled while submitting.
CancelbuttonResets the form and calls onCancel.
ErrordivRenders null without an error; otherwise a role="alert" element that the invalid field's aria-describedby references.

Submitting validates that name is non-empty, that the URL parses as http:/https:, and that a bearer token is present when authType is "bearer"; a failing check sets the form's error instead of calling aui.mcp.addCustomServer. Editing the field an error names clears it.

Manager and server state

Selector / callTypeDescription
s.mcp.isHydratedbooleanWhether persisted custom servers have finished loading.
s.mcp.connectors / s.mcp.customServersMCPServerState[]The two lists the dialog renders as its two sections.
s.mcpServer.connectionStateMCPConnectionState"disconnected" | "authRequired" | "authPending" | "connecting" | "connected" | "error".
s.mcpServer.lastError{ message: string } | nullThe server's most recent connection error.
s.mcpServer.authorizationUrlstring | nullPresent once an OAuth flow is ready to start.
aui.mcp.addCustomServer(input)Promise<string>Adds a custom server; resolves to its id.
aui.mcp.removeServer(id)Promise<void>Removes a custom server by id.
aui.mcpServer.connect() / .disconnect() / .remove()Promise<void>Drive the current server's connection lifecycle.