Skip to content

cli: write Cursor, OpenCode, VS Code and Gemini CLI configs through add-mcp - #15

Closed
chenchenSimular wants to merge 2 commits into
mainfrom
chenchen/add-mcp-config-writer
Closed

chenchenSimular wants to merge 2 commits into
mainfrom
chenchen/add-mcp-config-writer

Conversation

@chenchenSimular

@chenchenSimular chenchenSimular commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Closes #14

Why

npx @simular-ai/box connect carried its own writer for every client's MCP config. add-mcp (Apache-2.0, npm add-mcp) already tracks where 25 clients keep their servers and in what shape, on macOS, Linux and Windows. This PR moves the clients where add-mcp's output is equivalent or better onto it, behind our own interface, and keeps box-specific behavior in our layer.

Spike: add-mcp 2.6.1 vs our writers

Run against our test fixtures (cli/test/fixtures/*) in a temp HOME with upsertServer(agent, 'box', {type: 'http', url}) / removeServer, then against the real clients.

Client Path (add-mcp vs ours) Entry add-mcp writes Format preservation Verdict
Cursor same (~/.cursor/mcp.json) {url} (same) comments kept; the mcpServers map is re-indented as a whole moved to add-mcp
VS Code same (Code/User/mcp.json under Application Support / %APPDATA% / XDG) {type: "http", url} (same) kept moved to add-mcp
OpenCode ~/.config/opencode/opencode.jsonc or .json, whichever exists (new file: .jsonc; ours: .json). Ignores XDG_CONFIG_HOME and OPENCODE_CONFIG {type: "remote", url, enabled: true} (same); writes mcp.servers.box when the file uses OpenCode's newer layout (ours did not) comments kept; mcp map re-indented moved to add-mcp; our layer stops with manual steps when XDG_CONFIG_HOME points away from ~/.config
Gemini CLI ~/.gemini/settings.json {type: "http", url}; gemini mcp list shows box: … (http) comments kept new client (install matrix added)
Codex same ($CODEX_HOME/config.toml) adds type = "http" re-serializes the whole file: all comments lost, CRLF becomes LF, and our table's own settings (startup_timeout_sec) and subtables ([mcp_servers.box.tools.*] approvals) are dropped kept ours
Hermes $HERMES_HOME, %LOCALAPPDATA%\hermes, plus Hermes profiles (active_profile), which ours does not follow {url} only: no auth: oauth, so hermes mcp login box refuses it ("not configured for OAuth"); no key mode comments kept kept ours (profiles: follow-up)
Claude Code writes ~/.claude.json directly {type: "http", url} n/a kept ours: we install the plugin (server + box skill) through claude plugin install
OpenClaw not supported by add-mcp kept ours
Claude Desktop refuses remote servers (same as us) kept ours (prints connector steps)

Behavior add-mcp does not have, all of it kept in our layer (src/clients/add-mcp-client.js):

add-mcp 2.6.1 Our layer
A server named box that is not ours (e.g. box.com) overwrites it stops, --name to install next to it; remove leaves it
File that is not valid JSON parses leniently, writes into it, reports success refuses before add-mcp runs; file untouched
Backup none *.box-backup-<time> of the previous content, only when the file changed
Atomic write no (writeFileSync in place) the old text is backed up before add-mcp runs and restored if add-mcp fails part-way or the result doesn't read back as asked
Old box-control entries removed when they point at box
Headers / keys writes header values literally into the config never passes a key to add-mcp; key mode stays Hermes/OpenClaw-only, .env 0600

What changed

  • cli/src/config-writer.js: our interface (inspect, apply). add-mcp runs in a child process (add-mcp-worker.js) with the CLI context's environment, because it resolves HOME and env overrides once at import. Unit tests keep their temp HOME, and the paths add-mcp writes are the ones the rest of the CLI reads. Cost: about 40 ms per call, two calls per client on connect.
  • cli/src/clients/add-mcp-client.js replaces json-client.js; cursor.js, vscode.js, opencode.js use it; gemini-cli.js is new.
  • formats.js: the JSON upsert/remove writers are gone (TOML, YAML, .env and the strict JSON parser stay).
  • cli.js: a config that can't be written (EACCES/EPERM, "Access is denied") gets a clear message; Codex adds the Codex app steps (Settings → MCP servers → Add server → Streamable HTTP → URL → Save → restart → Authenticate).
  • codex.js: with no codex on PATH (the Codex app alone), the sign-in hint points at the app's Settings instead of codex mcp login.
  • Install matrix: gemini-cli (@google/gemini-cli@0.66.0) on Linux, macOS and Windows.

Deleted vs kept: json-client.js (87 lines) and the JSON writers in formats.js (~45 lines) are gone; the new wrapper, worker and adapter are ~210 lines, so line count goes up. What we no longer maintain is the per-client path and shape knowledge for the moved clients; adding a client add-mcp already knows is now a ~15-line file (gemini-cli.js).

Supply chain

  • add-mcp pinned exactly at 2.6.1 (published 2026-11-01). It adds 6 packages: @clack/prompts@0.9.1 (+ @clack/core), @iarna/toml, chalk, commander, plus jsonc-parser and yaml we already had. No install scripts anywhere in the tree; npm audit --omit=dev: 0 vulnerabilities.
  • npx cold start: 20 packages / 3.7 MB installed instead of 14 / 2.5 MB. Packed CLI 29.7 kB.
  • License: Apache-2.0 with no NOTICE file. We depend on it from npm and don't copy its code, so nothing to add to our package.

Tests

  • Unit: 40 pass locally (macOS). New: OpenCode empty mcp.servers, restore + no stray backup when add-mcp fails, Gemini CLI round trip, OpenCode XDG stop, EACCES on Codex's config prints the app steps, Codex hint without codex on PATH. Tests that hard-coded VS Code's Linux path now ask the client for its location.
  • Install matrix run locally on macOS: gemini-cli, opencode, codex OK. The full Linux/macOS/Windows matrix runs in CI on this PR.

Review

Independent review (simular-code-review + secure-coding invariant review): no blocking findings. Fixed in the second commit:

  • A failed or killed add-mcp run could leave a damaged config with no backup (add-mcp writes in place). The backup now comes first, and any failure restores the file.
  • OpenCode with an empty mcp.servers map (newer layout) failed connect. The key choice now mirrors add-mcp's.
  • The worker no longer inherits BOX_TOKEN / MCP_BOX_API_KEY.

Not changed (judgment calls): add-mcp's own dependencies are caret ranges, so npx users resolve a floating tree, as with our existing deps (option: ship an npm-shrinkwrap.json). The child-process worker exists only because add-mcp reads HOME at import; an upstream "resolve per call" option would remove it.

Not done (follow-ups)

  • Hermes profiles (active_profile), which add-mcp follows and we don't.
  • Windsurf, Zed, Goose, Copilot CLI and others add-mcp covers: not exposed, because the install matrix can't verify them headlessly yet.
  • Upstream (add-mcp): honor XDG_CONFIG_HOME for OpenCode, keep auth: oauth for Hermes, format-preserving Codex TOML edits, OpenClaw, and a "replace only if the URL matches" option. Then Codex and Hermes can move too.

🤖 Generated with Claude Code

chenchenSimular and others added 2 commits November 4, 2026 11:14
…dd-mcp

add-mcp (Apache-2.0, pinned at 2.6.1) now owns where these clients keep
their MCP servers and in what shape, behind our own interface
(src/config-writer.js). It runs in a child process with the CLI context's
environment, because it resolves paths from HOME and env once at import.

Our layer keeps what is specific to box: the "only replace a box entry
whose URL is ours" guard, refusing files we cannot parse, box-control
migration, backups, and reading the result back (restoring the file if
it is not what we asked for). Codex and Hermes keep their own writers:
add-mcp drops Codex comments and per-server settings, and writes Hermes
entries without auth: oauth, so `hermes mcp login` refuses them.

Also:
- Gemini CLI is a new client, verified by the install matrix.
- OpenCode stops with manual steps when XDG_CONFIG_HOME points away from
  ~/.config, where add-mcp would write a file OpenCode never reads.
- A config that cannot be written (EACCES/EPERM) gets a clear message;
  for Codex it prints the Codex app's Settings steps.
- The Codex sign-in hint no longer assumes `codex` is on PATH.

Refs #14

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From review of #15:
- add-mcp writes in place, so a crashed or timed-out worker, a full disk
  or a failed box-control removal could leave a damaged config with no
  backup. The previous text is now saved before add-mcp runs and put
  back on any failure; a backup is kept only when the file changed.
- OpenCode: a file with an empty `mcp.servers` map (the newer layout)
  failed connect, because we expected box under `mcp`. Mirror add-mcp's
  choice of key.
- The worker no longer inherits BOX_TOKEN / MCP_BOX_API_KEY.

Refs #14

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chenchenSimular

Copy link
Copy Markdown
Contributor Author

Decision: we won't adopt add-mcp. We keep our own writers and will revisit if upstream fixes the issues below.

Spike result (add-mcp 2.6.1, our fixtures plus the real-client matrix):

Client add-mcp
Cursor, VS Code, OpenCode Equivalent output (OpenCode also handles the mcp.servers layout; it ignores XDG_CONFIG_HOME)
Codex Worse: it re-serializes the whole TOML, so comments, CRLF, our table's settings and tools.* subtables are lost (upstream neon-solutions/add-mcp#122, open since 09-23, no reply)
Hermes Worse: no auth: oauth, so hermes mcp login box refuses the entry
Claude Code Not a fit: we install the plugin (server + box skill), not a bare ~/.claude.json entry
OpenClaw Not supported

Gaps we would have had to wrap for every client: it overwrites a foreign box server, writes into invalid JSON and reports success, keeps no backup, and writes in place rather than atomically. With those wrappers, a child-process worker (add-mcp reads HOME at import) and our own Codex/Hermes/OpenClaw/Claude Code writers still in place, net code went up, not down.

Upstream correctness issues sit unanswered: #121 (Zed path), #122 (TOML), #130 (Claude Code CLAUDE_CONFIG_DIR), all with no reply. #50 (Claude Desktop MSIX) has a fix in PR #114, open and unmerged since 09-07.

What carries over without add-mcp, in a new PR: Gemini CLI as a client (on our own writer), clear EACCES/EPERM errors with the Codex app steps, and a Codex sign-in hint that doesn't assume codex is on PATH. Closing this PR; the branch stays for reference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cli: delegate per-client MCP config writing to add-mcp

1 participant