feat(mcp): add clerk mcp install/list/uninstall + doctor MCP check#307
feat(mcp): add clerk mcp install/list/uninstall + doctor MCP check#307rafa-thayto wants to merge 11 commits into
Conversation
🦋 Changeset detectedLatest commit: 19b2820 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR adds MCP server support to the Clerk CLI: new subcommands (clerk mcp install/list/uninstall), a JSON-backed client factory and five client integrations (Claude Code, Cursor, VS Code, Windsurf, Gemini), per-client config read/write helpers, collection of installed entries, an initialize-handshake probe supporting JSON and SSE with a 10s timeout, a doctor check (checkMcp), environment/profile mcpUrl wiring, CLI wiring, comprehensive tests, and documentation including a changeset and README updates. Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
49da9bd to
173bdb4
Compare
|
!snapshot |
b4bc06c to
4f53e9f
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/cli-core/src/commands/mcp/clients/user-scope.test.ts (1)
2-2: ⚡ Quick winUse Bun file APIs here instead of
node:fsread/write helpers.These tests currently use
readFile/writeFilefromnode:fs/promises, which goes against the repo’s Bun-specific file I/O guideline.Bun.file(...).json()/text()andBun.write(...)would keep this aligned with the rest of the codebase. As per coding guidelines,**/*.{ts,tsx,js,jsx}: PreferBun.fileovernode:fs's readFile/writeFile for file operations.Also applies to: 45-46, 64-66
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/cli-core/src/commands/mcp/clients/user-scope.test.ts` at line 2, Replace node:fs/promises read/write helpers imported as mkdir, mkdtemp, readFile, rm, writeFile in user-scope.test.ts with Bun file APIs: use Bun.file(path).text()/json() for reads and Bun.write/Bun.writeSync (or Bun.file(path).write(...)) for writes, and keep using mkdtemp/mkdir/rm equivalents via Bun or the existing helpers if available; update all call sites that use readFile/writeFile (and any promise-based usages) to the corresponding Bun.file(...).text()/json() and Bun.write(...) patterns so the tests follow the repo’s Bun-specific I/O guideline.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli-core/src/commands/mcp/clients/user-scope.test.ts`:
- Around line 7-12: The top-level module mock of node:os (mock.module("node:os",
...)) is never torn down causing cross-test leakage; add a cleanup via
afterAll(() => mock.restoreModule("node:os")) (or equivalent) to restore the
original module and avoid order-dependent failures, and update file I/O in this
test to use Bun APIs instead of node:fs/promises—replace readFile/writeFile
calls with Bun.file(path).json()/text() for reads and Bun.file(path).write(...)
or Bun.write(...) for writes so the test uses Bun's file operations correctly;
reference mock.module, mock.restoreModule, mockHome and the test file's existing
fs read/write usages when making the changes.
---
Nitpick comments:
In `@packages/cli-core/src/commands/mcp/clients/user-scope.test.ts`:
- Line 2: Replace node:fs/promises read/write helpers imported as mkdir,
mkdtemp, readFile, rm, writeFile in user-scope.test.ts with Bun file APIs: use
Bun.file(path).text()/json() for reads and Bun.write/Bun.writeSync (or
Bun.file(path).write(...)) for writes, and keep using mkdtemp/mkdir/rm
equivalents via Bun or the existing helpers if available; update all call sites
that use readFile/writeFile (and any promise-based usages) to the corresponding
Bun.file(...).text()/json() and Bun.write(...) patterns so the tests follow the
repo’s Bun-specific I/O guideline.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 33d9756c-11d6-436b-8465-7e750f438029
📒 Files selected for processing (34)
.changeset/mcp-install.mdREADME.mdpackages/cli-core/src/cli-program.tspackages/cli-core/src/commands/doctor/README.mdpackages/cli-core/src/commands/doctor/check-mcp.tspackages/cli-core/src/commands/doctor/context.test.tspackages/cli-core/src/commands/doctor/index.tspackages/cli-core/src/commands/mcp/README.mdpackages/cli-core/src/commands/mcp/clients/claude-code.tspackages/cli-core/src/commands/mcp/clients/clients.test.tspackages/cli-core/src/commands/mcp/clients/cursor.tspackages/cli-core/src/commands/mcp/clients/gemini.tspackages/cli-core/src/commands/mcp/clients/json-config.tspackages/cli-core/src/commands/mcp/clients/make-json-client.test.tspackages/cli-core/src/commands/mcp/clients/make-json-client.tspackages/cli-core/src/commands/mcp/clients/paths.tspackages/cli-core/src/commands/mcp/clients/registry.tspackages/cli-core/src/commands/mcp/clients/types.tspackages/cli-core/src/commands/mcp/clients/user-scope.test.tspackages/cli-core/src/commands/mcp/clients/vscode.tspackages/cli-core/src/commands/mcp/clients/windsurf.tspackages/cli-core/src/commands/mcp/collect.tspackages/cli-core/src/commands/mcp/index.tspackages/cli-core/src/commands/mcp/install.test.tspackages/cli-core/src/commands/mcp/install.tspackages/cli-core/src/commands/mcp/list.test.tspackages/cli-core/src/commands/mcp/list.tspackages/cli-core/src/commands/mcp/probe.test.tspackages/cli-core/src/commands/mcp/probe.tspackages/cli-core/src/commands/mcp/shared.tspackages/cli-core/src/commands/mcp/uninstall.test.tspackages/cli-core/src/commands/mcp/uninstall.tspackages/cli-core/src/lib/environment.tspackages/cli-core/src/lib/errors.ts
✅ Files skipped from review due to trivial changes (4)
- .changeset/mcp-install.md
- packages/cli-core/src/commands/doctor/README.md
- README.md
- packages/cli-core/src/commands/mcp/README.md
🚧 Files skipped from review as they are similar to previous changes (26)
- packages/cli-core/src/commands/mcp/list.test.ts
- packages/cli-core/src/commands/mcp/clients/registry.ts
- packages/cli-core/src/commands/mcp/index.ts
- packages/cli-core/src/commands/mcp/clients/claude-code.ts
- packages/cli-core/src/commands/mcp/clients/cursor.ts
- packages/cli-core/src/commands/mcp/list.ts
- packages/cli-core/src/commands/mcp/clients/vscode.ts
- packages/cli-core/src/commands/doctor/check-mcp.ts
- packages/cli-core/src/commands/mcp/clients/gemini.ts
- packages/cli-core/src/commands/mcp/clients/windsurf.ts
- packages/cli-core/src/commands/mcp/clients/types.ts
- packages/cli-core/src/commands/mcp/probe.test.ts
- packages/cli-core/src/lib/errors.ts
- packages/cli-core/src/commands/doctor/index.ts
- packages/cli-core/src/commands/mcp/clients/paths.ts
- packages/cli-core/src/commands/mcp/install.ts
- packages/cli-core/src/lib/environment.ts
- packages/cli-core/src/commands/mcp/collect.ts
- packages/cli-core/src/commands/mcp/uninstall.ts
- packages/cli-core/src/commands/mcp/clients/clients.test.ts
- packages/cli-core/src/commands/mcp/probe.ts
- packages/cli-core/src/commands/mcp/install.test.ts
- packages/cli-core/src/cli-program.ts
- packages/cli-core/src/commands/mcp/clients/json-config.ts
- packages/cli-core/src/commands/mcp/clients/make-json-client.test.ts
- packages/cli-core/src/commands/mcp/uninstall.test.ts
a6e7532 to
bdf8faf
Compare
|
!snapshot |
Snapshot publishednpm install -g clerk@2.0.1-snapshot.bdf8faf
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli-core/src/commands/mcp/clients/clients.test.ts`:
- Around line 65-96: The VS Code test is leaking to the real config because
vscodeUserDir() respects XDG_CONFIG_HOME/APPDATA; update the tests to isolate
that by setting XDG_CONFIG_HOME and APPDATA to a directory under mockHome (or
delete them) before running the VS Code-specific assertions and restore them
after; ensure the expectation for the VS Code path does not call vscodeUserDir()
(i.e. compute the expected path explicitly inside the test instead of reusing
expectedPath()/vscodeUserDir()) so the test verifies the function under test
rather than mirroring the same helper.
In `@packages/cli-core/src/commands/mcp/clients/paths.ts`:
- Around line 26-35: vscodeUserDir currently uses the nullish coalescing
operator (??) with process.env.APPDATA and process.env.XDG_CONFIG_HOME which
treats empty strings as set; update vscodeUserDir to treat empty or
whitespace-only env vars as unset by checking truthiness/trim (e.g. derive local
vars like appData = process.env.APPDATA?.trim() ? process.env.APPDATA :
undefined and xdg = process.env.XDG_CONFIG_HOME?.trim() ?
process.env.XDG_CONFIG_HOME : undefined) and then fall back to join(homedir(),
...) in the switch cases; reference the vscodeUserDir function and its uses of
process.env.APPDATA and process.env.XDG_CONFIG_HOME, and ensure platform(),
homedir(), and join() behavior is preserved.
In `@packages/cli-core/src/lib/environment.ts`:
- Around line 154-165: getMcpUrl currently lets an empty CLERK_MCP_URL ("") win
due to the nullish coalescing operator; treat empty/whitespace-only env values
as unset by trimming and checking truthiness before falling back. Update
getMcpUrl (function name: getMcpUrl) to compute const env =
process.env.CLERK_MCP_URL?.trim(); then return env && env.length ? env :
getCurrentEnv().mcpUrl ?? DEFAULT_MCP_URL so blank overrides no longer produce
an unusable empty string.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 2826916f-5886-46c5-96c7-da4b8e7b1a92
📒 Files selected for processing (23)
.changeset/mcp-install.mdpackages/cli-core/src/cli-program.tspackages/cli-core/src/commands/doctor/check-mcp.tspackages/cli-core/src/commands/mcp/README.mdpackages/cli-core/src/commands/mcp/clients/claude-code.tspackages/cli-core/src/commands/mcp/clients/clients.test.tspackages/cli-core/src/commands/mcp/clients/cursor.tspackages/cli-core/src/commands/mcp/clients/gemini.tspackages/cli-core/src/commands/mcp/clients/json-config.tspackages/cli-core/src/commands/mcp/clients/make-json-client.test.tspackages/cli-core/src/commands/mcp/clients/make-json-client.tspackages/cli-core/src/commands/mcp/clients/paths.tspackages/cli-core/src/commands/mcp/clients/user-scope.test.tspackages/cli-core/src/commands/mcp/clients/vscode.tspackages/cli-core/src/commands/mcp/clients/windsurf.tspackages/cli-core/src/commands/mcp/install.test.tspackages/cli-core/src/commands/mcp/install.tspackages/cli-core/src/commands/mcp/list.test.tspackages/cli-core/src/commands/mcp/list.tspackages/cli-core/src/commands/mcp/shared.tspackages/cli-core/src/commands/mcp/uninstall.test.tspackages/cli-core/src/lib/environment.tspackages/cli-core/src/lib/errors.ts
💤 Files with no reviewable changes (3)
- packages/cli-core/src/commands/mcp/clients/gemini.ts
- packages/cli-core/src/commands/mcp/clients/windsurf.ts
- packages/cli-core/src/cli-program.ts
✅ Files skipped from review due to trivial changes (2)
- .changeset/mcp-install.md
- packages/cli-core/src/commands/mcp/README.md
🚧 Files skipped from review as they are similar to previous changes (12)
- packages/cli-core/src/commands/mcp/clients/cursor.ts
- packages/cli-core/src/lib/errors.ts
- packages/cli-core/src/commands/mcp/clients/vscode.ts
- packages/cli-core/src/commands/mcp/list.ts
- packages/cli-core/src/commands/doctor/check-mcp.ts
- packages/cli-core/src/commands/mcp/uninstall.test.ts
- packages/cli-core/src/commands/mcp/clients/make-json-client.test.ts
- packages/cli-core/src/commands/mcp/clients/user-scope.test.ts
- packages/cli-core/src/commands/mcp/shared.ts
- packages/cli-core/src/commands/mcp/clients/json-config.ts
- packages/cli-core/src/commands/mcp/clients/make-json-client.ts
- packages/cli-core/src/commands/mcp/install.test.ts
b017038 to
a9840ca
Compare
|
!snapshot |
Snapshot publishednpm install -g clerk@2.0.1-snapshot.d637eb0
|
8a62c01 to
42756ac
Compare
Register the Clerk remote MCP server (https://mcp.clerk.com/mcp) in Claude Code, Cursor, VS Code, Windsurf, and Gemini, each with a JSON/agent mode and human-mode 'next steps' guidance (reload + sign-in). clerk doctor gains an MCP reachability check that probes the configured server via the MCP initialize handshake when an entry is installed (warns, never fails). URL resolution: --url > CLERK_MCP_URL > active env profile mcpUrl.
A top-level import of @inquirer/prompts is resolved at module load, which breaks integration tests that mock the module with a partial shape omitting checkbox. Defer it to call-time like doctor/update already do.
context.test.ts replaced the entire config.ts module via mock.module, which is process-lifetime in Bun and omitted getConfigFile/_setConfigDir. When the doctor folder ran in a single `bun test` process, the polluted mock leaked into doctor.test.ts (which needs the real module), crashing with "Export named 'getConfigFile' not found". Swap the config mock for a spyOn on resolveProfile (the only symbol context.ts imports) and restore it in afterAll, so doctor.test.ts gets the real config.ts back.
Add afterAll mock cleanup and replace node:fs/promises readFile/writeFile with Bun.file().json(), Bun.file().text(), and Bun.write() per project standards.
… server Write Claude Code, Cursor, and VS Code entries to each editor's user-global config (~/.claude.json, ~/.cursor/mcp.json, VS Code's per-OS user mcp.json) instead of project-scoped files. Project scope tied "did it install?" to the run directory matching the editor's launch dir plus a trust prompt and restart; user scope makes the server available in every project, regardless of where the CLI is run. Default the MCP URL to the hosted server (https://mcp.clerk.com/mcp) when no profile defines mcpUrl, so install works out of the box on published builds whose injected env profile omits the field. Resolution order: --url > CLERK_MCP_URL > active profile mcpUrl > hosted server. Drop the localhost --url examples from --help (kept in the README for contributors), and tidy redundant docblock titles plus settleClients' loop. Tests migrated to redirect homedir to a tmpdir so user-scoped client writes stay isolated.
…isolation - vscodeUserDir(): use || instead of ?? so empty APPDATA/XDG_CONFIG_HOME fall back to the homedir-based default instead of producing a relative path - getMcpUrl(): trim and check truthiness so CLERK_MCP_URL="" doesn't win - clients.test.ts, user-scope.test.ts: clear XDG_CONFIG_HOME/APPDATA in beforeEach and restore in afterEach to prevent VS Code path leaking to the runner's real config directory (fixes CI test failure)
The #305 Clack migration removed @inquirer/prompts, but the mcp install client picker still imported checkbox from it — undeclared after the rebase and broken on a clean install. Add a multiselect wrapper to lib/prompts.ts (matching the existing confirm/text/password Clack wrappers, with the same cancel→throwUserAbort handling) and route pickClients through it. No behavior change to the picker.
…ninstall Follow the patterns the #305 Clack migration established: wrap install/list/uninstall human flows in withGutter() (guaranteed intro/outro cleanup, paused/failed outros on abort/error), render next steps via outro([...]) instead of a custom printNextSteps helper, and render the list table through the ui.* prompt rail (ui.message) like apps/list. No behavior change to JSON/agent output.
… to `claude` Uninstall: in human mode with no --client/--all, prompt with a multiselect of the clients that actually have the entry, so you choose exactly which to remove from (mirrors the install picker). Add --all to remove from every client without prompting; agent mode still targets all. --client still targets specific clients. Rename the Claude Code client id from `claude-code` to `claude` (shorter `--client claude`); the file and export follow (claude.ts / claudeClient). Display name stays "Claude Code". Help examples now lead with `--client claude`.
…octor - Add Codex MCP client backed by ~/.codex/config.toml (smol-toml + a makeTomlClient codec on the shared makeFileClient factory); uses Codex's native Streamable HTTP transport, no mcp-remote bridge - Rename the VS Code client display to "GitHub Copilot"; accept `--client copilot` as an alias for `vscode` (same config) - uninstall: prompt lists only clients that have the entry, nothing pre-checked (select-to-remove), and warns how to install instead of erroring when nothing is registered - doctor: probe every distinct configured MCP URL (not just the first); lower the probe timeout to 5s - resolveClients dedupes aliases/repeats; remove dead projectPath export
42756ac to
19b2820
Compare
Summary
Adds a
clerk mcpcommand group to register the Clerk remote MCP server (https://mcp.clerk.com/mcp) into supported AI editors, plus an MCP health check inclerk doctor.clerk mcp install— register the server in one or more clients. Interactive multiselect in human mode; all detected clients in agent mode. Flags:--client(repeatable),--all,--url,--name,--force,--json. Conflict policy: same URL →unchanged(no-op); different URL →skippedwith a reason unless--force.clerk mcp list— show every Clerk-flavored entry (namedclerkor pointing at any*.clerk.comhost) across all clients.clerk mcp uninstall— remove the entry. In human mode it prompts with a multiselect of the clients that actually have it, so you pick exactly which to remove from;--allremoves from every client without prompting (agent mode targets all);--clienttargets specific clients. Throwsmcp_not_installed(exit 1) when nothing matched.clerk doctor— gains anMCP servercheck that probes the configured server via the MCPinitializehandshake only when an entry is installed (skips otherwise; warns, never fails — a server outage shouldn't fail the whole doctor run). There is intentionally no separateclerk mcp doctor— one doctor.Supported clients — all user-global scope
Entries are written to each client's user-global config, so the server is available in every project, with no per-project approval and no dependence on the directory the CLI is run from.
--clientidclaude~/.claude.json(mcpServers)cursor~/.cursor/mcp.jsonvscode~/Library/Application Support/Code/User/mcp.json(per-OS)windsurf~/.codeium/windsurf/mcp_config.jsongemini~/.gemini/settings.json(npx mcp-remotestdio bridge)Each editor gets its own descriptor schema (
{type:http,url}/{url}/{serverUrl}/npx mcp-remoteargs).Design notes
--url>CLERK_MCP_URLenv > active env profilemcpUrl(new field) > Clerk's hosted server (default), soclerk mcp installneeds no flags or profile setup.npxonPATH), and removing it doesn't drop a live session (reload). Human-mode output uses the prompt rail — intro/outro gutter, a "Next steps" block, and interactive multiselect pickers for install/uninstall.Promise.allSettled); own-property guards avoid prototype pollution;--urlscheme is validated (http/https only).Test plan
bun run format:check,bun run lint,bun run typecheck— greenbun testfor the feature — 132 mcp + doctor tests passing (per-client encode/extract round-trips, conflict policy incl.--force, prototype-key names, malformed-config handling, the doctorinitializehandshake acrossapplication/json+ SSE transports, hosted-URL fallback when a profile omitsmcpUrl, the uninstall picker selecting a subset,--all, unknown--client, multi-client partial failure, gutter/next-steps output). Tests redirecthomedir()to a tmpdir so user-scoped writes stay isolated.install→list→doctor→uninstallend-to-end against the live server (handshake returnsClerk MCP Server); verified configs land in the user-global paths regardless of cwd; partial-failure path verified with a corrupted client config