feat(cc-config): add and remove MCP servers through the claude CLI
Mutation goes through `claude mcp add|remove`, never a direct edit of ~/.claude.json — the running CLI rewrites that file concurrently, so a dashboard write would race it. Reads stay on readMcpServers. The plan specified a --url flag; the real CLI (v2.1.266) has none — commandOrUrl is positional, and --header must trail it or the variadic flag swallows the url. Built against the verified shape.
This commit is contained in:
@@ -10,9 +10,11 @@
|
||||
* artifacts (skills, agents, commands, output styles, memory, per-project
|
||||
* auto-memory), the structured keybindings.json editor, settings.json layers
|
||||
* via a per-key merge (`PATCH /settings`, which is also how hooks are edited),
|
||||
* and plugin/marketplace management delegated to the `claude plugin …` CLI.
|
||||
* Every file write is preceded by a timestamped backup. MCP server config
|
||||
* stays read-only — it is written concurrently by the running CLI.
|
||||
* plugin/marketplace management delegated to the `claude plugin …` CLI, and
|
||||
* MCP server add/remove delegated to the `claude mcp …` CLI. Every file
|
||||
* write is preceded by a timestamped backup; the CLI-delegated writes are
|
||||
* not files this server touches directly, so there is nothing to back up —
|
||||
* the CLI owns `~/.claude.json`.
|
||||
*
|
||||
* Also hosts the auth-profile routes (`/profiles*`) that swap which Anthropic
|
||||
* account the local CLI runs as; those carry identity metadata only, never
|
||||
@@ -25,6 +27,7 @@ const { Router } = require("express");
|
||||
const cc = require("../lib/cc-discovery");
|
||||
const ccMutate = require("../lib/cc-mutate");
|
||||
const ccCli = require("../lib/cc-plugin-cli");
|
||||
const ccMcpCli = require("../lib/cc-mcp-cli");
|
||||
const ccProfiles = require("../lib/cc-profiles");
|
||||
const { broadcast } = require("../websocket");
|
||||
|
||||
@@ -329,6 +332,43 @@ router.post("/marketplaces/action", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ── MCP servers (via the `claude` CLI) ─────────────────────────────────
|
||||
//
|
||||
// Same reasoning as the plugin/marketplace routes above: `~/.claude.json` is
|
||||
// rewritten concurrently by the running CLI, so add/remove shell out to
|
||||
// `claude mcp …` rather than editing the file. Reads are unaffected — GET
|
||||
// /mcp above stays on `readMcpServers`; the client refetches it after either
|
||||
// call below succeeds.
|
||||
|
||||
router.post("/mcp/add", async (req, res) => {
|
||||
const { name, scope, transport, command, args, url, headers, env } = req.body || {};
|
||||
try {
|
||||
const result = await ccMcpCli.mcpAdd({
|
||||
name,
|
||||
scope,
|
||||
transport,
|
||||
command,
|
||||
args,
|
||||
url,
|
||||
headers,
|
||||
env,
|
||||
});
|
||||
return cliResult(res, result, { action: "add", type: "mcp", name });
|
||||
} catch (err) {
|
||||
return cliError(res, err);
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/mcp/remove", async (req, res) => {
|
||||
const { name, scope } = req.body || {};
|
||||
try {
|
||||
const result = await ccMcpCli.mcpRemove({ name, scope });
|
||||
return cliResult(res, result, { action: "remove", type: "mcp", name });
|
||||
} catch (err) {
|
||||
return cliError(res, err);
|
||||
}
|
||||
});
|
||||
|
||||
// PATCH /api/cc-config/settings — merge individual keys into one settings
|
||||
// layer. Body: { scope: "user"|"project"|"project-local", set?: {dotted: value},
|
||||
// unset?: ["dotted"] }. Hooks are edited through this same route as
|
||||
|
||||
Reference in New Issue
Block a user