Skip to content

Commit e1fcabb

Browse files
committed
feat(mcp): interactive uninstall picker + rename claude-code client 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`.
1 parent d637eb0 commit e1fcabb

12 files changed

Lines changed: 120 additions & 37 deletions

File tree

packages/cli-core/src/cli-program.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,10 @@ export function createProgram() {
472472
.description("Manage the Clerk remote MCP server connection for AI editors and CLIs")
473473
.setExamples([
474474
{ command: "clerk mcp install", description: "Install into all detected MCP clients" },
475-
{ command: "clerk mcp install --client cursor", description: "Install into Cursor only" },
475+
{
476+
command: "clerk mcp install --client claude",
477+
description: "Install into Claude Code only",
478+
},
476479
{ command: "clerk mcp list", description: "Show registered Clerk entries" },
477480
{ command: "clerk mcp uninstall", description: "Remove the Clerk entry from all clients" },
478481
]);
@@ -498,7 +501,7 @@ export function createProgram() {
498501
},
499502
{ command: "clerk mcp install --all", description: "Install into every detected client" },
500503
{
501-
command: "clerk mcp install --client cursor --client vscode",
504+
command: "clerk mcp install --client claude --client vscode",
502505
description: "Install into specific clients",
503506
},
504507
])
@@ -515,16 +518,27 @@ export function createProgram() {
515518
.command("uninstall")
516519
.description("Remove the Clerk MCP entry from supported clients")
517520
.addOption(
518-
createOption("--client <id>", "MCP client to target (repeatable). Default: all clients.")
521+
createOption(
522+
"--client <id>",
523+
"MCP client to target (repeatable). Default in human mode: pick from installed; in agent mode: all clients.",
524+
)
519525
.choices([...MCP_CLIENT_IDS])
520526
.argParser(collectOptionValues)
521527
.default([] as string[]),
522528
)
529+
.option("--all", "Remove from every client without prompting")
523530
.option("--name <name>", 'Entry name to remove (default: "clerk")')
524531
.option("--json", "Output as JSON")
525532
.setExamples([
526-
{ command: "clerk mcp uninstall", description: "Remove from every client" },
527-
{ command: "clerk mcp uninstall --client cursor", description: "Remove from Cursor only" },
533+
{
534+
command: "clerk mcp uninstall",
535+
description: "Pick which installed clients to remove from",
536+
},
537+
{ command: "clerk mcp uninstall --all", description: "Remove from every client" },
538+
{
539+
command: "clerk mcp uninstall --client claude",
540+
description: "Remove from Claude Code only",
541+
},
528542
])
529543
.action((options) => mcpHandlers.uninstall(options));
530544

packages/cli-core/src/commands/mcp/README.md

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ All entries are written to each client's **user-global** config, so the server
2323
is available in every project (no per-project approval, no dependence on which
2424
directory you run the CLI from).
2525

26-
| ID | Client | Scope | Config file |
27-
| ------------- | ------------------------ | ----- | --------------------------------------- |
28-
| `claude-code` | Claude Code | user | `~/.claude.json` (`mcpServers`) |
29-
| `cursor` | Cursor | user | `~/.cursor/mcp.json` |
30-
| `vscode` | VS Code (Copilot) | user | VS Code user `mcp.json` (per-OS, below) |
31-
| `windsurf` | Windsurf | user | `~/.codeium/windsurf/mcp_config.json` |
32-
| `gemini` | Gemini Code Assist / CLI | user | `~/.gemini/settings.json` |
26+
| ID | Client | Scope | Config file |
27+
| ---------- | ------------------------ | ----- | --------------------------------------- |
28+
| `claude` | Claude Code | user | `~/.claude.json` (`mcpServers`) |
29+
| `cursor` | Cursor | user | `~/.cursor/mcp.json` |
30+
| `vscode` | VS Code (Copilot) | user | VS Code user `mcp.json` (per-OS, below) |
31+
| `windsurf` | Windsurf | user | `~/.codeium/windsurf/mcp_config.json` |
32+
| `gemini` | Gemini Code Assist / CLI | user | `~/.gemini/settings.json` |
3333

3434
VS Code's user config dir is OS-specific: `~/Library/Application Support/Code/User/mcp.json`
3535
(macOS), `%APPDATA%\Code\User\mcp.json` (Windows), `$XDG_CONFIG_HOME/Code/User/mcp.json`
@@ -68,9 +68,13 @@ named `clerk` or pointing at any `*.clerk.com` host).
6868

6969
### `clerk mcp uninstall`
7070

71-
Remove the named entry from each client. Throws `mcp_not_installed` (exit
72-
code 1) when nothing was removed. Removing the entry doesn't drop a live editor
73-
session, so (in human mode) it prints a next step to reload each affected editor.
71+
Remove the named entry. In human mode with no `--client`/`--all`, it prompts
72+
with a multiselect of the clients that **currently have the entry**, so you
73+
choose exactly which to remove from. `--all` removes from every client without
74+
prompting; agent mode targets all clients; `--client <id>` (repeatable) targets
75+
specific clients. Throws `mcp_not_installed` (exit code 1) when nothing matched.
76+
Removing the entry doesn't drop a live editor session, so (in human mode) it
77+
prints a next step to reload each affected editor.
7478

7579
> **Reachability:** there is no `mcp doctor` subcommand. Server health is part
7680
> of `clerk doctor`, which probes the configured MCP URL via the `initialize`

packages/cli-core/src/commands/mcp/clients/claude-code.ts renamed to packages/cli-core/src/commands/mcp/clients/claude.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
import { hasStringProp, makeJsonClient } from "./make-json-client.ts";
99
import { pathExists, userPath } from "./paths.ts";
1010

11-
export const claudeCodeClient = makeJsonClient({
12-
id: "claude-code",
11+
export const claudeClient = makeJsonClient({
12+
id: "claude",
1313
displayName: "Claude Code",
1414
scope: "user",
1515
activation: "Restart Claude Code, then run `/mcp` to connect (sign in if prompted).",

packages/cli-core/src/commands/mcp/clients/clients.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ let mockHome = realOs.tmpdir();
1111
mock.module("node:os", () => ({ ...realOs, homedir: () => mockHome }));
1212
afterAll(() => mock.restore());
1313

14-
const { claudeCodeClient } = await import("./claude-code.ts");
14+
const { claudeClient } = await import("./claude.ts");
1515
const { cursorClient } = await import("./cursor.ts");
1616
const { vscodeClient } = await import("./vscode.ts");
1717
const { windsurfClient } = await import("./windsurf.ts");
@@ -26,8 +26,8 @@ const URL = "https://mcp.clerk.com/mcp";
2626
// specific user-global config file and encodes the server its own way.
2727
const cases = [
2828
{
29-
name: "claude-code",
30-
client: claudeCodeClient,
29+
name: "claude",
30+
client: claudeClient,
3131
expectedPath: () => join(mockHome, ".claude.json"),
3232
topKey: "mcpServers",
3333
shape: { type: "http", url: URL },

packages/cli-core/src/commands/mcp/clients/registry.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
* human-mode multiselect picker.
44
*/
55

6-
import { claudeCodeClient } from "./claude-code.ts";
6+
import { claudeClient } from "./claude.ts";
77
import { cursorClient } from "./cursor.ts";
88
import { geminiClient } from "./gemini.ts";
99
import type { ClientId, McpClient } from "./types.ts";
1010
import { vscodeClient } from "./vscode.ts";
1111
import { windsurfClient } from "./windsurf.ts";
1212

1313
export const CLIENTS: readonly McpClient[] = [
14-
claudeCodeClient,
14+
claudeClient,
1515
cursorClient,
1616
vscodeClient,
1717
windsurfClient,

packages/cli-core/src/commands/mcp/clients/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* the `clerk` server entry in its own config file format.
77
*/
88

9-
export type ClientId = "claude-code" | "cursor" | "vscode" | "windsurf" | "gemini";
9+
export type ClientId = "claude" | "cursor" | "vscode" | "windsurf" | "gemini";
1010

1111
/** Where the client config file lives relative to the user / project. */
1212
export type Scope = "project" | "user";

packages/cli-core/src/commands/mcp/clients/user-scope.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const { checkMcp } = await import("../../doctor/check-mcp.ts");
3030
const captured = useCaptureLog();
3131

3232
const URL = "https://mcp.clerk.com/mcp";
33-
const ALL_CLIENT_IDS = ["claude-code", "cursor", "vscode", "windsurf", "gemini"];
33+
const ALL_CLIENT_IDS = ["claude", "cursor", "vscode", "windsurf", "gemini"];
3434

3535
describe("user-scope MCP clients (homedir redirected to a tmpdir)", () => {
3636
beforeEach(async () => {

packages/cli-core/src/commands/mcp/install.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,11 +200,11 @@ describe("mcp install", () => {
200200
await mkdir(join(cwd, ".cursor"), { recursive: true });
201201
await writeFile(join(cwd, ".cursor", "mcp.json"), "{ not json");
202202

203-
await mcpInstall({ client: ["cursor", "claude-code"], url: URL_A });
203+
await mcpInstall({ client: ["cursor", "claude"], url: URL_A });
204204

205205
const payload = JSON.parse(captured.out) as { results: { client: string; status: string }[] };
206206
expect(payload.results).toEqual([
207-
expect.objectContaining({ client: "claude-code", status: "added" }),
207+
expect.objectContaining({ client: "claude", status: "added" }),
208208
]);
209209
expect(captured.err).toContain("Cursor"); // per-client warning for the failure
210210
});

packages/cli-core/src/commands/mcp/install.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ import type { McpClient, UpsertResult } from "./clients/types.ts";
2424

2525
async function chooseClients(options: McpOptions, cwd: string): Promise<McpClient[]> {
2626
if (options.client?.length || options.all || isAgent()) return targetClients(options, cwd);
27-
return pickClients(await detectInstalledClients(cwd));
27+
return pickClients(await detectInstalledClients(cwd), "Select MCP clients to install into:", {
28+
autoSelectSingle: true,
29+
});
2830
}
2931

3032
function statusLabel(status: UpsertResult["status"]): string {

packages/cli-core/src/commands/mcp/shared.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,18 @@ export function resolveClients(ids: readonly string[]): McpClient[] {
6363
});
6464
}
6565

66-
export async function pickClients(detected: McpClient[]): Promise<McpClient[]> {
66+
export async function pickClients(
67+
detected: McpClient[],
68+
message: string,
69+
options: { autoSelectSingle?: boolean } = {},
70+
): Promise<McpClient[]> {
6771
if (detected.length === 0) return [];
68-
if (detected.length === 1) return detected;
72+
if (detected.length === 1 && options.autoSelectSingle) return detected;
6973
// Imported lazily (like `doctor` does) so the prompt layer stays off the
7074
// module-load path for non-interactive callers and tests.
7175
const { multiselect } = await import("../../lib/prompts.ts");
7276
const selected = await multiselect<ClientId>({
73-
message: "Select MCP clients to install into:",
77+
message,
7478
options: detected.map((c) => ({ value: c.id, label: `${c.displayName} (${c.scope})` })),
7579
initialValues: detected.map((c) => c.id),
7680
required: true,

0 commit comments

Comments
 (0)