# MCP servers

> Install stdio/http/sse MCP servers from the registry marketplace, claudette-mcp-servers store schema, harness sync to Cursor/Gemini/Codex/OpenCode, and runtime loading into Claude Agent SDK.

- Repository: Parcha-ai/build
- GitHub: https://github.com/Parcha-ai/build
- Human docs: https://grok-wiki.com/public/docs/parcha-ai-build-bea5702b371b
- Complete Markdown: https://grok-wiki.com/public/docs/parcha-ai-build-bea5702b371b/llms-full.txt

## Source Files

- `src/main/services/mcp.service.ts`
- `src/main/ipc/mcp.ipc.ts`
- `src/renderer/components/extensions/MCPMarketplace.tsx`
- `src/renderer/components/extensions/MCPInstallDialog.tsx`
- `scripts/verify-mcp-harness-sync.ts`
- `scripts/verify-mcp-runtime-matrix.js`

---

---
title: "MCP servers"
description: "Install stdio/http/sse MCP servers from the registry marketplace, claudette-mcp-servers store schema, harness sync to Cursor/Gemini/Codex/OpenCode, and runtime loading into Claude Agent SDK."
---

Build stores MCP server definitions in the `claudette-mcp-servers` electron-store, installs them from the official MCP Registry marketplace or raw JSON, normalizes remote HTTP/SSE endpoints through a pinned `mcp-remote@0.1.38` stdio wrapper, syncs merged configs into Cursor/Gemini/Codex/OpenCode harness files, and injects the normalized map into the Claude Agent SDK `mcpServers` option on every agent turn.

## Architecture

```mermaid
flowchart TB
  subgraph UI["Renderer"]
    MCPMarketplace["MCPMarketplace"]
    MCPInstallDialog["MCPInstallDialog"]
    ExtensionsExplorer["ExtensionsExplorer"]
  end

  subgraph Main["Main process"]
    McpIpc["mcp.ipc.ts"]
    McpService["mcp.service.ts"]
    ClaudeService["claude.service.ts"]
    HarnessServices["cursor / gemini / codex / opencode services"]
    SshService["ssh.service.ts"]
    StdioBridge["mcp-stdio-bridge.service.ts"]
  end

  subgraph Storage["electron-store"]
    McpStore["claudette-mcp-servers"]
    HarnessSyncStore["claudette-mcp-harness-sync"]
  end

  subgraph External["External"]
    Registry["registry.modelcontextprotocol.io"]
    McpRemote["npx mcp-remote@0.1.38"]
    McpAuth["~/.mcp-auth"]
  end

  subgraph HarnessFiles["Harness config files"]
    CursorJson["~/.cursor/mcp.json"]
    GeminiJson["~/.gemini/settings.json"]
    CodexToml["~/.codex/config.toml"]
    OpenCodeJson["~/.config/opencode/build-mcp.json"]
    ClaudeJson["~/.claude/config.json SSH only"]
  end

  MCPMarketplace --> McpIpc
  MCPInstallDialog --> McpIpc
  ExtensionsExplorer --> McpIpc
  McpIpc --> McpService
  McpService --> McpStore
  McpService --> HarnessSyncStore
  McpService --> Registry
  McpService --> HarnessFiles
  McpService --> McpRemote
  ClaudeService --> McpService
  HarnessServices --> McpService
  HarnessServices --> SshService
  SshService --> McpService
  SshService --> StdioBridge
  SshService --> McpAuth
  SshService --> ClaudeJson
  ClaudeService --> StdioBridge
```

<Note>
Build’s MCP catalog is separate from Claude Code’s `~/.claude/config.json`. Local Claude harness turns receive MCP servers directly through the Agent SDK; SSH sessions additionally merge into remote `~/.claude/config.json`.
</Note>

## Storage schema

### `claudette-mcp-servers`

Each key is a server ID (marketplace installs use the registry ID’s last path segment, e.g. `ai.exa/exa` → `exa`). Values follow `MCPServerConfig`:

| Field | Type | When used |
|-------|------|-----------|
| `type` | `stdio` \| `http` \| `sse` | Transport hint; remote installs set `http` or `sse` from registry `transport_type` |
| `command` | string | Stdio launcher (`npx`, `node`, …) |
| `args` | string[] | Stdio arguments; marketplace npm installs use `['-y', '<package>']` |
| `env` | `Record<string, string>` | Stdio environment variables (registry package env vars) |
| `url` | string | HTTP/SSE remote endpoint |
| `headers` | `Record<string, string>` | Remote auth headers (registry remote headers) |
| `alwaysLoad` | boolean | Optional; preserved through normalization |
| `tools` | `Record<string, unknown>[]` | Optional tool metadata; preserved through normalization |

On read, stored configs are normalized: any `mcp-remote` package arg is pinned to `mcp-remote@0.1.38`, and `http://` localhost URLs gain `--allow-http` when missing.

### `claudette-mcp-harness-sync`

Tracks harness merge state:

| Key | Purpose |
|-----|---------|
| `managedServerIds` | Server IDs last written by Build sync (sorted) |
| `removedServerIds` | IDs Build removed; used to delete stale entries from harness files on next sync |

Install marks a server as installed (removes it from `removedServerIds`). Uninstall appends the ID to `removedServerIds`.

## Transport types and normalization

### Stdio (native)

A local process with `command` + `args`. These are the servers that cannot run on a remote SSH host without bridging.

`isNativeStdioServer()` returns true when the config has a `command`, no `url`, and args do not include `mcp-remote` / `mcp-remote@…`.

### HTTP and SSE (remote)

Registry remotes install with `type: 'http'` or `type: 'sse'` and a `url`. Optional `headers` hold auth values from the install dialog.

### `mcp-remote` wrapper (default for Claude and harnesses)

For HTTP/SSE endpoints, Build normally wraps remotes as stdio:

```text
npx -y mcp-remote@0.1.38 <url> [--allow-http] [--header "Name: ${BUILD_MCP_...}"]
```

Header values become `BUILD_MCP_<SERVER>_<HEADER>` environment variables so OAuth tokens in `~/.mcp-auth` can be shared across harnesses.

`normalizeMcpServerForClaude()` applies this wrapping unless `preferNativeRemoteTransports: true` is passed (then SSE/HTTP types pass through with `url` and `headers` intact).

### Localhost reverse tunnels

Configs referencing `http://localhost` or `127.0.0.1` ports are detected via `getLocalhostMcpPorts()`. SSH setup creates reverse tunnels so remote harnesses reach the local service.

## Registry marketplace

| Constant | Value |
|----------|-------|
| API | `https://registry.modelcontextprotocol.io/v0/servers` |
| Cache TTL | 5 minutes |
| Max pages | 20 (pagination via `metadata.nextCursor`) |
| User-Agent | `Claudette/1.0` |

`fetchMarketplaceServers()` paginates, transforms entries to `MarketplaceMCPServer`, sorts by name, and falls back to stale cache on fetch errors.

`MarketplaceMCPServer` fields include `id`, `name`, `description`, `packages`, `remotes`, `authFields`, `requiresAuth`, `repositoryUrl`, `websiteUrl`, `icon`, `keywords`, `isLatest`, `publishedAt`.

Auth fields are extracted from required/secret package environment variables and remote headers, deduplicated by key.

### Install from marketplace

`installServer()` chooses:

1. **npm package** (`registry_name === 'npm'`): stdio `npx -y <package.name>` with `env` from auth dialog values.
2. **First remote**: `type` from `transport_type` (`sse` vs `http`), `url`, optional `headers`.
3. Otherwise: `{ success: false, error: 'No installation method available for this server' }`.

After store write, `prewarmMcpRemoteAuth()` may spawn `npx` with the wrapped args to complete OAuth; successful prewarm triggers harness + SSH resync via IPC.

## UI entry points

| Surface | Flow |
|---------|------|
| **MCP Marketplace** (`MCPMarketplace.tsx`) | `electronAPI.mcp.getMarketplace()` → install via `MCPInstallDialog` → `electronAPI.mcp.install(serverId, authValues)` |
| **Extensions explorer** (`ExtensionsExplorer.tsx`) | Manual add/edit: stdio (`command`, multiline `args`, `env`) or URL (`url`, `headers`); `electronAPI.mcp.installRaw(name, config)` |
| **Unified marketplace** | Also supports raw install for custom configs |

Installed state matches by full registry ID or trailing segment (`serverId.split('/').pop()`).

## IPC API

| Channel | Handler behavior |
|---------|------------------|
| `mcp:get-servers` | `getActiveServers()` — built-in `claudette-browser` plus installed servers |
| `mcp:get-raw-config` | Raw store entry for one server ID |
| `mcp:get-marketplace` | Registry fetch (cached) |
| `mcp:install-server` | Resolve marketplace entry, `installServer()`, then `syncHarnessesAndSshSessions()` |
| `mcp:install-server-raw` | `installServerRaw()` (requires `command` or `url`), then sync |
| `mcp:uninstall-server` | Delete from store, mark removed, sync |

Preload exposes these as `window.electronAPI.mcp.*`.

After install/uninstall/auth prewarm, IPC runs `syncLocalHarnessConfigs()` and queues SSH sync for connected sessions (30s timeout per session, coalesced if already in flight).

## Harness sync

`syncLocalHarnessConfigs()` writes **sanitized** configs (always `mcp-remote`-wrapped remotes) to:

| Harness | Path | Format |
|---------|------|--------|
| Cursor | `~/.cursor/mcp.json` | JSON `mcpServers` merge |
| Gemini | `~/.gemini/settings.json` | JSON `mcpServers` merge |
| Codex | `~/.codex/config.toml` | TOML `[mcp_servers.<name>]` blocks |
| OpenCode | `~/.config/opencode/build-mcp.json` | JSON merged over `~/.config/opencode/opencode.json` |

Merge behavior:

- **Upsert** all current Build servers into existing harness files.
- **Remove** IDs in `removeServerIds` (managed + removed + current set).
- **Preserve** unrelated harness-only servers (e.g. `customRemoteOnly` in verifier fixtures).

OpenCode launch sets `OPENCODE_CONFIG` to `build-mcp.json` (local and remote).

Cursor SDK path uses `toCursorSdkMcpServers()` to map sanitized configs to `@cursor/sdk` `McpServerConfig`. Cursor CLI adds `--approve-mcps` on launch after sync.

On app `ready`, main calls `mcpService.syncLocalHarnessConfigs()` once.

Each harness service (`cursor`, `cursor-cli`, `gemini`, `codex`, `opencode`) calls sync before streaming: local → `syncLocalHarnessConfigs()`; SSH → `sshService.syncMcpConfigsToRemote()`.

## Claude Agent SDK runtime

On **every** Claude message, `claude.service.ts` builds `mcpServersConfig`:

```text
claudette-browser (if browser history / local session)
+ user MCP from mcpService.getClaudeMcpServersConfig()  [or SSH bridge variant]
+ optional qmd stdio (local only, when enabled)
→ passed as mcpServers to Agent SDK query
```

| Session | User MCP source |
|---------|-----------------|
| Local | `getClaudeMcpServersConfig()` — wrapped remotes, pinned `mcp-remote` |
| SSH | `getClaudeMcpServersConfigForSSH(bridgePorts)` — native stdio replaced with `http://127.0.0.1:{port}/mcp` |

SSH also syncs `getClaudeMcpSyncDataForSSH()` into remote `~/.claude/config.json` before query. `chrome-devtools` stdio is stripped on remote and reattached as a local in-process MCP server.

Built-in **Claudette Browser** (`id: claudette-browser`, `type: sdk`) exposes `browser_snapshot`, `browser_navigate`, `browser_act` — not stored in `claudette-mcp-servers`.

## SSH remote MCP

`syncMcpConfigsToRemote()` sequence:

<Steps>
<Step title="Start stdio bridges">
`mcpStdioBridgeService.startBridgesForSession()` for `getNativeStdioServers()` — local HTTP on `/mcp` per server.
</Step>
<Step title="Sync Claude config">
Merge into `~/.claude/config.json` via `getClaudeMcpSyncDataForSSH()`.
</Step>
<Step title="Sync MCP auth">
SFTP `~/.mcp-auth` to remote; replicate token files across `mcp-remote-*` version dirs.
</Step>
<Step title="Sync harness files">
Remote `~/.cursor/mcp.json`, `~/.gemini/settings.json`, `~/.codex/config.toml`, `~/.config/opencode/build-mcp.json`.
</Step>
<Step title="Reverse tunnels">
Localhost MCP ports and bridge ports tunneled to remote `127.0.0.1:{port}`.
</Step>
</Steps>

Harness MCP sync is cached per host for 5 minutes. MCP auth sync is cached when local `~/.mcp-auth` mtime is unchanged.

## Remote auth prewarm

After install, if the normalized config is an `npx mcp-remote@0.1.38` wrapper, Build spawns a detached prewarm process (default `--auth-timeout 180`, overall timeout up to 190s). Listeners on stdout/stderr detect `Proxy established successfully`, `Local STDIO server running`, or `Authentication completed`. On success, IPC re-runs harness + SSH sync.

## Manual install example

Stdio server:

```json
{
  "type": "stdio",
  "command": "npx",
  "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"],
  "env": { "API_KEY": "..." }
}
```

HTTP remote with headers:

```json
{
  "type": "http",
  "url": "https://mcp.example.com/mcp",
  "headers": { "Authorization": "Bearer ..." }
}
```

Use **Extensions → MCP servers → Add** or `electronAPI.mcp.installRaw(id, config)`.

## Verification

| Script | What it checks |
|--------|----------------|
| `node scripts/verify-mcp-harness-sync.ts` | Normalization, merge helpers, Codex TOML, OpenCode merge, localhost port detection, Cursor SDK mapping (mocked electron-store) |
| `node scripts/verify-mcp-runtime-matrix.js [--skip-remote]` | Local (and optional SSH) harness config files contain pinned `mcp-remote@0.1.38`; CLI `mcp list` / tool listing where CLIs exist; Claude service injects `mcpService.getClaudeMcpServersConfig()` into SDK |

Runtime matrix options: `--remote-host`, `--remote-user`, `--remote-key`, `--fail-on-skips`.

## Troubleshooting

| Symptom | Likely cause |
|---------|----------------|
| Harness does not see new server | Run any harness turn (triggers sync) or restart app (startup sync); check `syncLocalHarnessConfigs()` errors in main log |
| Remote MCP auth fails | `~/.mcp-auth` not synced; complete OAuth via prewarm locally first |
| Localhost MCP unreachable on SSH | Reverse tunnel not established; verify URL uses `localhost` or `127.0.0.1` with explicit port |
| `mcp-remote` version drift | Store auto-migrates to `mcp-remote@0.1.38`; remote auth sync copies tokens across version folders |
| Native stdio missing on remote | Expected — use bridge (`http://127.0.0.1:{port}/mcp`) or remote-capable `mcp-remote` URL |
| Marketplace empty | Registry fetch failed; stale cache may still load; check network and registry status |

<Warning>
Secrets live in electron-store and synced harness files on disk. Treat `claudette-mcp-servers.json` and harness MCP configs as sensitive in backups and shared machines.
</Warning>

## Related pages

<CardGroup>
<Card title="Extensions and commands" href="/extensions-and-commands">
MCP marketplace UI, manual MCP editor, skills, and slash commands in the Extensions panel.
</Card>
<Card title="IPC channels reference" href="/ipc-channels-reference">
Full `mcp:*` channel list and invoke semantics.
</Card>
<Card title="SSH remote sessions" href="/ssh-remote-sessions">
Remote MCP sync, bridges, tunnels, and `~/.mcp-auth` replication.
</Card>
<Card title="Harnesses and models" href="/harnesses-and-models">
Per-harness launch, permission modes, and MCP capability limits.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
MCP harness sync errors, CDP timeouts, and production log locations.
</Card>
</CardGroup>
