Elements

Elements · Tool use

Server panel

Which servers are connected, what each one brought, and which is still waiting on you.

Servers2 of 3 connected
streamable-http· connected
list_issuescreate_prget_diff
fig. 01

Installation

npx shadcn@latest add "@assistant-ui/elements-mcp-server-panel"
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.

A compact list of MCP servers, each row collapsed to a name and a status dot until you open it for its transport and tool list. With a runtime the servers come from your MCP connections; standalone you pass the list in.

Getting started

@assistant-ui/react-mcp owns MCP connection state. Mount its manager once, then read servers and their tools from it anywhere in the tree.

Mount the MCP manager

app/providers.tsx
"use client";

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

function McpManagerMount({
  connectors,
  children,
}: {
  connectors: MCPConnector[];
  children: React.ReactNode;
}) {
  const aui = useAui();
  const config = AuiConfig({ mcp: McpManagerResource({ connectors }) });
  return (
    <AuiProvider extends={aui} config={config}>
      {children}
    </AuiProvider>
  );
}

Each connector in connectors becomes one server the manager connects, tracks, and lists tools for. Custom servers a user adds through McpAddFormPrimitive join the same list without another mount.

List servers and their tools

components/assistant-ui/elements/mcp-servers.tsx
"use client";

import { useState } from "react";
import { McpManagerPrimitive, McpServerPrimitive } from "@assistant-ui/react-mcp";
import { useAuiState } from "@assistant-ui/store";
import { PlugIcon } from "lucide-react";

function ServerRow() {
  const [open, setOpen] = useState(false);
  const name = useAuiState((s) => s.mcpServer.name);
  return (
    <McpServerPrimitive.Root>
      <button type="button" onClick={() => setOpen((v) => !v)}>
        <PlugIcon className="size-3.5" />
        {name}
      </button>
      {open && (
        <McpServerPrimitive.Tools>
          {(tool) => <span key={tool.name}>{tool.name}</span>}
        </McpServerPrimitive.Tools>
      )}
    </McpServerPrimitive.Root>
  );
}

export function McpServerList() {
  return (
    <McpManagerPrimitive.Root>
      <McpManagerPrimitive.Connectors>{() => <ServerRow />}</McpManagerPrimitive.Connectors>
      <McpManagerPrimitive.CustomServers>{() => <ServerRow />}</McpManagerPrimitive.CustomServers>
    </McpManagerPrimitive.Root>
  );
}

McpServerPrimitive.Root scopes the row so useAuiState((s) => s.mcpServer...) and McpServerPrimitive.Tools resolve to that one server. Each row tracks its own open state here, so more than one can be expanded at a time; the MCP config dialog ships a fuller version of this same list, with connect and authorize actions, inside a dialog shell.

Anatomy

<div data-slot="mcp-server-panel">
  <div>{/* "n of m connected" */}</div>
  <button aria-expanded>
    {/* chevron, plug icon, name, tool count, status dot */}
  </button>
  {/* expanded: transport, status label, optional Authorize action, tool chips */}
</div>

The header count only tallies servers whose status is "connected". The status dot is emerald for connected, a pulsing dim dot for connecting, amber for needs-auth, and red for failed, each paired with screen-reader-only text. The Authorize pill only renders on a needs-auth row when onAuthorize is passed; without it the row expands with no way to act on it.

Examples

Map connection states onto four dots

McpServerPrimitive.Status (and the s.mcpServer.connectionState selector behind it) carries six states: connected, connecting, authRequired, authPending, error, disconnected. The panel's own McpServerStatus type only has four. Fold the extra two into their nearest dot before handing the value to the panel:

import type { MCPConnectionState } from "@assistant-ui/react-mcp";
import type { McpServerStatus } from "@/components/assistant-ui/elements/mcp-server-panel";

function toPanelStatus(state: MCPConnectionState): McpServerStatus {
  switch (state) {
    case "connected":
      return "connected";
    case "connecting":
    case "authPending":
      return "connecting";
    case "authRequired":
      return "needs-auth";
    case "error":
    case "disconnected":
      return "failed";
  }
}

Wire the authorize action

McpServerPrimitive.OAuthLink starts the same OAuth flow the config dialog uses. Render it as the row's authorize action once connectionState is authRequired:

<McpServerPrimitive.OAuthLink className="rounded-full bg-amber-500/15 px-2 py-0.5 text-[11px] font-medium text-amber-700">
  Authorize
</McpServerPrimitive.OAuthLink>

Restyle the panel

Both lanes take className on the root. The panel uses the shared paper surface for its background, mono for the counts and status text, and field alongside mono for the tool chips, so retargeting those tokens in surfaces.tsx restyles every element built on them.

API reference

McpManagerPrimitive and McpServerPrimitive

PartRendersNotes
McpManagerPrimitive.RootdivProvides the manager scope; wrap the list in it.
McpManagerPrimitive.Connectorsrender propIterates app-declared connectors, one { server } per call.
McpManagerPrimitive.CustomServersrender propIterates user-added custom servers the same way.
McpServerPrimitive.RootdivScopes useAuiState((s) => s.mcpServer...) and the sub-parts below to one server.
McpServerPrimitive.NametextThe server's display name.
McpServerPrimitive.Statusspandata-state set to the connection state; renders the state as text by default.
McpServerPrimitive.Toolsrender propIterates the server's tools, one MCPToolInfo per call. Renders nothing while the list is empty.
McpServerPrimitive.ConnectButton / .DisconnectButton / .OAuthLink / .RemoveButtonbuttonCall connect(), disconnect(), start OAuth, or remove() on the scoped server.

Server state

SelectorTypeDescription
s.mcpServer.namestringDisplay name.
s.mcpServer.connectionState"connected" | "connecting" | "authRequired" | "authPending" | "error" | "disconnected"Full connection state, six values.
s.mcpServer.toolsMCPToolInfo[]{ name, description?, inputSchema } for each tool the server listed.
s.mcpServer.lastError{ message: string } | nullSet when a connect or list-tools call failed.
useMcpServerTool()MCPToolInfoThe current tool inside McpServerPrimitive.Tools; throws outside it.