MCP connect

Pro

Consent to connect an MCP server: where it runs and who runs it, what it says it can do (its tools, by name and description), the permissions it asks for, and how to disconnect later. Nothing is ticked for the person; a permission the server doesn't describe says so instead of being hidden; a list the server hasn't given is said to be unknown, not shown as empty. Connect and Cancel look the same, so neither is the easy way out.

Markdown

Preview

Install

With the Candor CLI

First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/mcp-connect

Brings in: button, locale, locked, utils · npm: lucide-react@^1.67.0

Usage

TSX · The example above
"use client"

import { McpConnect, type McpScope, type McpServerTool } from "@/components/mcp-connect"

const tools: McpServerTool[] = [
  { name: "search_issues", title: "Search issues", description: "Finds issues by text, label or assignee.", readOnly: true },
  { name: "create_issue", title: "Create issue", description: "Opens a new issue in a team you choose." },
  { name: "update_issue", description: "Changes an issue's status, assignee or labels.", destructive: true },
  { name: "sync_cycles" },
]

const scopes: McpScope[] = [
  { id: "issues:read", label: "Read issues", description: "Titles, descriptions, comments and labels in every team you can see.", required: true },
  { id: "issues:write", label: "Create and change issues", description: "Opens issues and edits their fields. It can't delete them." },
  // The server didn't say what this one allows: the card says so rather than hiding it.
  { id: "admin.workspace" },
]

export default function Example() {
  return (
    <McpConnect
      className="w-full max-w-xl"
      server={{ name: "Tracker", url: "https://mcp.tracker.example/v1", owner: "Tracker Inc. (verified by your workspace admin)" }}
      tools={tools}
      scopes={scopes}
      disconnect="Settings › Connections › Tracker › Disconnect. Its access stops at once; issues it already created stay."
      onConnect={() => {}}
      onCancel={() => {}}
    />
  )
}

<McpConnect>

PropTypeDefaultDescription
serverrequiredMcpServerInfo—The server: its name, where it runs and who runs it, as your app knows them.
toolsMcpServerTool[] | undefined—The tools it lists. Leave out when it hasn't listed them; `[]` when it lists none.
scopesMcpScope[] | undefined—The permissions it asks for. Leave out when it hasn't said; `[]` when it asks for none.
disconnectrequiredstring—How to disconnect later, e.g. "Settings › Connections › Linear › Disconnect. Its access stops at once."
acknowledgementstring | undefined—A sentence the person ticks themselves before connecting. Optional.
titlestring | undefined—The heading. Defaults to "Connect {name}?".
onConnectrequired(scopes: string[]) => void—Called with the ids of the permissions the person ticked, in the order given.
onCancelrequired() => void—Called when the person cancels.
busyboolean | undefinedfalseDisables Connect and says "Connecting" while the connection is made.
errorstring | undefined—Why the last attempt failed, said above the buttons.
toolLimitnumber | undefined6How many tools to show before "Show all".
classNamestring | undefined—Extra classes for the outer element.

Strings

The fixed words, in English by default. Change any of them under mcpConnect in <LocaleProvider strings={{ … }}>; the table is exported as mcpConnectStrings.

KeyEnglish
titleConnect {name}?
whereWhere your data goes
remoteA server at
addressFull address
localRuns on this computer
whereUnknownWhere this server runs isn’t known.
ownerRun by
ownerUnknownWho runs it isn’t known.
toolsWhat it says it can do
toolCount{ one: "{count} tool", other: "{count} tools" }
toolsUnknownThe server hasn’t listed what it can do.
noToolsIt lists no tools.
notDescribedNot described by the server
saysReadOnlySays it only reads
saysDestructiveSays it can change or delete
hintsNoteWhat a tool says about itself is the server’s own description; nothing has checked it.
showAllShow all {count}
showFewerShow fewer
scopesWhat it asks for
scopesHintNothing is granted unless you tick it.
scopesUnknownThe server hasn’t said what access it asks for.
noScopesIt asks for no extra access.
neededNeeded to connect
disconnectTo disconnect later:
connectConnect
connectingConnecting
cancelCancel
tickNeeded{ one: "Tick the permission it needs to connect first.", other: "Tick the {count} permissions it needs to connect first." }
tickAcknowledgementTick the acknowledgement first.

Types

McpServerTool
One tool the server lists, as it describes itself (MCP's `tools/list`).
{ /** The tool's name, as the agent calls it. */ name: string /** A readable name, when the server gives one. */ title?: string | undefined /** What the server says the tool does. Without one, the card says "Not described by the server". */ description?: string | undefined /** The server's own hint that the tool only reads (MCP's `readOnlyHint`). Shown as the server's claim. */ readOnly?: boolean
McpScope
One permission (OAuth scope or the like) the server asks for.
{ /** The scope as the server names it ("repo:read"); what `onConnect` sends back. */ id: string /** A plain name for it, when your app has one. Without one, the id is shown. */ label?: string | undefined /** What it allows, in a sentence. Without one, the card says "Not described by the server". */ description?: string | undefined /** The server can't connect without it: Connect stays locked unti
McpServerInfo
The server, as your app knows it (not as the server describes itself).
{ /** The name to show, e.g. "Linear". */ name: string /** Where a remote server is: its full URL. Its host is shown first. */ url?: string | undefined /** How a local server is started, e.g. `npx @example/mcp-server`. */ command?: string | undefined /** Who runs it, as your app knows it. Left out, the card says it isn't known. */ owner?: string | undefined }