# MCP connect

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.

- Registry name: `mcp-connect` (`@candor/mcp-connect`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/mcp-connect

## Install

```bash
npx candor-ui add mcp-connect
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/mcp-connect`.
It brings in: button, locale, locked, utils.
npm packages: lucide-react@^1.67.0.

## Example

```tsx
"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>

Props:

- `server: McpServerInfo`: The server: its name, where it runs and who runs it, as your app knows them.
- `tools?: McpServerTool[] | undefined`: The tools it lists. Leave out when it hasn't listed them; `[]` when it lists none.
- `scopes?: McpScope[] | undefined`: The permissions it asks for. Leave out when it hasn't said; `[]` when it asks for none.
- `disconnect: string`: How to disconnect later, e.g. "Settings › Connections › Linear › Disconnect. Its access stops at once."
- `acknowledgement?: string | undefined`: A sentence the person ticks themselves before connecting. Optional.
- `title?: string | undefined`: The heading. Defaults to "Connect {name}?".
- `onConnect: (scopes: string[]) => void`: Called with the ids of the permissions the person ticked, in the order given.
- `onCancel: () => void`: Called when the person cancels.
- `busy?: boolean | undefined` (default `false`): Disables Connect and says "Connecting" while the connection is made.
- `error?: string | undefined`: Why the last attempt failed, said above the buttons.
- `toolLimit?: number | undefined` (default `6`): How many tools to show before "Show all".
- `className?: string | undefined`

## Strings (`mcpConnect`)

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

- `title`: Connect {name}?
- `where`: Where your data goes
- `remote`: A server at
- `address`: Full address
- `local`: Runs on this computer
- `whereUnknown`: Where this server runs isn’t known.
- `owner`: Run by
- `ownerUnknown`: Who runs it isn’t known.
- `tools`: What it says it can do
- `toolCount`: { one: "{count} tool", other: "{count} tools" }
- `toolsUnknown`: The server hasn’t listed what it can do.
- `noTools`: It lists no tools.
- `notDescribed`: Not described by the server
- `saysReadOnly`: Says it only reads
- `saysDestructive`: Says it can change or delete
- `hintsNote`: What a tool says about itself is the server’s own description; nothing has checked it.
- `showAll`: Show all {count}
- `showFewer`: Show fewer
- `scopes`: What it asks for
- `scopesHint`: Nothing is granted unless you tick it.
- `scopesUnknown`: The server hasn’t said what access it asks for.
- `noScopes`: It asks for no extra access.
- `needed`: Needed to connect
- `disconnect`: To disconnect later:
- `connect`: Connect
- `connecting`: Connecting
- `cancel`: Cancel
- `tickNeeded`: { one: "Tick the permission it needs to connect first.", other: "Tick the {count} permissions it needs to connect first." }
- `tickAcknowledgement`: Tick the acknowledgement first.

## Types

### McpServerTool

One tool the server lists, as it describes itself (MCP's `tools/list`).

```ts
{ /** 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.

```ts
{ /** 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).

```ts
{ /** 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 }
```

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider` (fixed strings) and `TermsProvider` (nouns); entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
