# MCP servers
> Declaring MCP servers in ivar.json, how sync spells them for each provider, and why a hall's servers can hold their own accounts.
Source: https://ivar.run/docs/reference/mcp

import { Callout } from 'fumadocs-ui/components/callout';

A hall declares its MCP servers once, in `ivar.json`, and `ivar sync`
materialises them for every provider configured in the hall. The definitions are
hall-scoped: a harness finds them by walking up from the view directory, so
every session in the hall sees the same servers.

## Declare them in `ivar.json`

A complete v4 hall manifest declaring all three providers (`claude-code`,
`opencode`, and `omp`) and both HTTP endpoints (`linear` and `figma`) alongside
a local tool:

```json title="ivar.json"
{
  "$schema": "https://ivar.run/schema/4.json",
  "name": "acme",
  "providers": {
    "available": ["claude-code", "opencode", "omp"],
    "default": "claude-code"
  },
  "mcp": [
    {
      "name": "linear",
      "type": "http",
      "url": "https://mcp.linear.app/mcp"
    },
    {
      "name": "figma",
      "type": "http",
      "url": "https://mcp.figma.com/mcp"
    },
    {
      "name": "local-tools",
      "type": "local",
      "command": "npx",
      "args": ["-y", "@acme/tools-mcp"],
      "env": {
        "TOOLS_ENDPOINT": "ACME_TOOLS_ENDPOINT"
      }
    }
  ],
  "repos": [],
  "integration": {
    "via": "local",
    "strategy": "squash"
  },
  "version": 4
}
```

### Configuration types

`ivar.json` strictly enforces canonical `http` and `local` definitions:

```json title="Canonical HTTP definition"
{
  "name": "linear",
  "type": "http",
  "url": "https://mcp.linear.app/mcp"
}
```

```json title="Canonical Local definition"
{
  "name": "local-tools",
  "type": "local",
  "command": "npx",
  "args": ["-y", "@acme/tools-mcp"],
  "env": {
    "TOOLS_ENDPOINT": "ACME_TOOLS_ENDPOINT"
  }
}
```

| Field | Description | Applies to |
| --- | --- | --- |
| `name` | The canonical unqualified name of the server in the hall (e.g. `figma`, `linear`). | `http`, `local` |
| `type` | Canonical transport: `http` for remote servers or `local` for spawned processes. Provider spellings like `remote`, `stdio`, and `sse` are rejected. | `http`, `local` |
| `url` | Remote endpoint HTTP/HTTPS URL. | `http` |
| `command` | Executable or binary spawned by the harness. | `local` |
| `args` | Optional argument array passed to the command. | `local` |
| `env` | Optional mapping of environment variables injected into the process (`KEY: ENV_VAR_NAME` reference). | `local` |
| `oauth` | Optional persisted OAuth client and endpoint metadata generated/updated by `ivar mcp auth`. | `http` |

<Callout type="warn" title="The manifest carries no secrets">
  `ivar.json` is committed. `env` holds *references* — variable names resolved
  at runtime — never plain secrets. OAuth credentials live in provider credential
  stores or `.ivar/secrets/mcp.env`.
</Callout>

### Provider translation table

`ivar sync` translates canonical manifest definitions into provider-native
configurations:

| Canonical (`ivar.json`) | Claude Code (`.mcp.json`) | OpenCode (`opencode.json`) | OMP (`mcp.json`) |
| --- | --- | --- | --- |
| `type: "http"` | `type: "http"` | `type: "remote"` | `type: "http"` |
| `type: "local"` | `type: "stdio"` | `type: "local"` | `type: "stdio"` |
| `command`, `args` | `command`, `args` | `command: ["cmd", ...args]` | `command`, `args` |
| `url` | `url` | `url` | `url` |
| `env` | `env` | `environment` | `env` |
| Server name | `<hall>-<name>` | `<hall>-<name>` | `<hall>-<name>` |

## Naming and authentication commands

The name stored in `ivar.json` is canonical and **unqualified** (`figma`, `linear`).
At every provider boundary, `ivar` qualifies the name with the hall name:
`<hall>-<server>` (for example, `acme-figma` or `acme-linear`).

Use `ivar mcp auth` with the canonical name:

```sh
ivar mcp auth figma --provider claude-code   # authenticate specific provider
ivar mcp auth linear --provider opencode
ivar mcp auth figma --provider omp
ivar mcp auth figma --all-providers          # authenticate all configured providers

# Inspect credential status
ivar mcp status                              # all servers across all providers
ivar mcp status figma                        # specific server
ivar mcp status --provider claude-code       # specific provider
ivar mcp status --live                       # query provider harnesses live
ivar mcp status --json                       # emit JSON status matrix

Direct provider commands take the **hall-qualified** name only where the provider CLI natively exposes MCP login commands:

```sh
claude mcp login acme-figma                  # Claude Code native login
opencode mcp auth acme-linear                # OpenCode native auth
```

OMP exposes no MCP login command; authentication for OMP always runs through `ivar mcp auth` and persists credentials keyed by profile and endpoint via `omp auth-broker`.

## Provider support matrix

| Service | Provider | OAuth Owner | Preregistration | Credential Owner | Config Path |
| --- | --- | --- | --- | --- | --- |
| **Linear** | Claude Code | Claude Code (`claude mcp login`) | Not needed (public client) | Claude Code keychain | `.mcp.json` |
| **Linear** | OpenCode | OpenCode (`opencode mcp auth`) | Not needed (public client) | OpenCode (`mcp-auth.json`) | `opencode.json` |
| **Linear** | OMP | Ivar (internal OAuth PKCE) | Dynamic registration (`/register`) | `omp auth-broker` | `mcp.json` |
| **Figma** | Claude Code | Claude Code (`claude mcp login`) | Not needed (`Claude Code` allowlisted) | Claude Code keychain | `.mcp.json` |
| **Figma** | OpenCode | Ivar (internal OAuth PKCE) | Yes (`client_name: "Codex"`) | OpenCode (`mcp-auth.json`) | `opencode.json` |
| **Figma** | OMP | Ivar (internal OAuth PKCE) | Yes (`client_name: "Codex"`) | `omp auth-broker` | `mcp.json` |

## Claude Code

Claude Code reads `.mcp.json` at the hall root. `ivar sync` materialises
hall-qualified names. When launching sessions, `ivar`
supplies process-scoped settings `--settings '{"enabledMcpjsonServers":["acme-linear","acme-figma",...]}'`
so declared servers are enabled without modifying global state. Claude Code owns
its OAuth lifecycle directly via `claude mcp login`.

```json title="Generated .mcp.json (Claude Code)"
{
  "mcpServers": {
    "acme-linear": {
      "type": "http",
      "url": "https://mcp.linear.app/mcp"
    },
    "acme-figma": {
      "type": "http",
      "url": "https://mcp.figma.com/mcp"
    },
    "acme-local-tools": {
      "type": "stdio",
      "command": "npx",
      "args": [
        "-y",
        "@acme/tools-mcp"
      ],
      "env": {
        "TOOLS_ENDPOINT": "ACME_TOOLS_ENDPOINT"
      }
    }
  }
}
```

## OpenCode

OpenCode reads `opencode.json` at the hall root. For standard services like
Linear, OpenCode manages OAuth directly via `opencode mcp auth acme-linear`. For
Figma, Ivar performs prerequisite registration and OAuth token acquisition,
storing tokens in OpenCode's credential store (`mcp-auth.json`).

```json title="Generated opencode.json (OpenCode)"
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "acme-linear": {
      "type": "remote",
      "url": "https://mcp.linear.app/mcp"
    },
    "acme-figma": {
      "type": "remote",
      "url": "https://mcp.figma.com/mcp",
      "oauth": {
        "clientId": "<generated_client_id>",
        "clientSecret": "{env:IVAR_MCP_ACME_FIGMA_SECRET}",
        "redirectUri": "http://127.0.0.1:19876/callback"
      }
    },
    "acme-local-tools": {
      "type": "local",
      "command": [
        "npx",
        "-y",
        "@acme/tools-mcp"
      ],
      "environment": {
        "TOOLS_ENDPOINT": "{env:ACME_TOOLS_ENDPOINT}"
      }
    }
  }
}
```

## OMP

OMP reads `mcp.json` at the hall root (or the provider-resolved MCP configuration path).
Because OMP has no built-in `mcp login` CLI command, `ivar mcp auth` always runs
Ivar's internal OAuth PKCE engine for OMP, then imports the resulting tokens
keyed by profile and MCP endpoint directly via `omp auth-broker`.

When persisted OAuth metadata contains a `token_url`, `ivar sync` materialises
the corresponding `auth` block in `mcp.json`.

```json title="Generated mcp.json (OMP with Linear & Figma)"
{
  "mcpServers": {
    "acme-linear": {
      "type": "http",
      "url": "https://mcp.linear.app/mcp",
      "auth": {
        "type": "oauth",
        "clientId": "<generated_client_id>",
        "tokenUrl": "<discovered-token-endpoint>"
      }
    },
    "acme-figma": {
      "type": "http",
      "url": "https://mcp.figma.com/mcp",
      "auth": {
        "type": "oauth",
        "clientId": "<generated_client_id>",
        "clientSecret": "${IVAR_MCP_ACME_FIGMA_SECRET}",
        "tokenUrl": "<discovered-token-endpoint>"
      }
    },
    "acme-local-tools": {
      "type": "stdio",
      "command": "npx",
      "args": [
        "-y",
        "@acme/tools-mcp"
      ],
      "env": {
        "TOOLS_ENDPOINT": "ACME_TOOLS_ENDPOINT"
      }
    }
  }
}
```

## OAuth metadata and secrets

When `ivar mcp auth` discovers endpoints or performs dynamic client
registration, it persists client and endpoint metadata back to `ivar.json` under
the server entry:

```json title="Persisted oauth metadata in ivar.json (Generated)"
{
  "name": "figma",
  "type": "http",
  "url": "https://mcp.figma.com/mcp",
  "oauth": {
    "client_id": "<generated_client_id>",
    "client_secret_env": "IVAR_MCP_ACME_FIGMA_SECRET",
    "token_url": "<discovered-token-endpoint>",
    "resource": "<discovered-resource>"
  }
}
```

| Field | Purpose |
| --- | --- |
| `client_id` | Registered OAuth client identifier. |
| `client_secret_env` | Name of the environment variable holding the client secret (for confidential clients). |
| `token_url` | Discovered OAuth token endpoint URL. When present, enables materialised provider `auth` blocks (such as OMP). |
| `resource` | Optional RFC 8707 / RFC 9728 protected resource indicator URI. |

<Callout type="warn" title="Secrets remain outside version control">
  Ivar writes only non-sensitive metadata (`client_id`, `token_url`, `client_secret_env`) to `ivar.json`. Confidential client secrets are stored in `.ivar/secrets/mcp.env` or the provider's credential vault, never committed in plain text.
</Callout>

## Service nuances: Linear vs. Figma

### Linear
Linear implements standard RFC 8414 OAuth 2.0 authorization server metadata and RFC 7591 dynamic client registration for public clients (`token_endpoint_auth_method: "none"`). No pre-shared client credentials or allowlist workarounds are required. Claude Code and OpenCode authenticate natively, while OMP utilizes Ivar's generic PKCE registration and token installation.

### Figma
Figma restricts Dynamic Client Registration (DCR) to allowlisted `client_name` values (such as `"Claude Code"` and `"Codex"`). Generic or unrecognized client names receive `403 Forbidden`. Furthermore, Figma requires a client secret during token exchange despite advertising public authentication.

To support OpenCode and OMP:
1. `ivar mcp auth` registers a client using an allowlisted client name (`Codex`).
2. Ivar persists the registration metadata in `ivar.json` and saves the secret reference to `.ivar/secrets/mcp.env`.
3. Ivar runs the browser OAuth PKCE exchange and imports credentials directly to the respective harness stores.

<Callout type="warn" title="External DCR policy caveat">
  Figma's dynamic client registration policy is server-controlled and external. Changes to upstream allowlists or registration rules may require updating client identifiers or registration parameters.
</Callout>
