fix(cc-config): MCP scope was inverted — expose user/local, not user/project
readMcpServers only ever parses ~/.claude.json (top-level = user scope, projects[<root>] = the panel's "projectScoped" bucket, which the CLI itself calls `local`). The previous user/project scope choice was the exact inverse: `--scope project` writes .mcp.json, which readMcpServers never reads (a successful add would vanish), and every "projectScoped" row was actually `local`, so Remove sent a scope the CLI could never match. - cc-mcp-cli.js: MCP_SCOPES -> ["user", "local"]; drop the local mutationQueue/serialize in favor of cc-plugin-cli.js's exported one, so a plugin install and an MCP add can't spawn `claude` concurrently; drop the String(v) coercion in env/header flags (a wrong-typed value is now a 400, not a written "[object Object]"); assertEmbeddedValue allows empty strings (a legitimate empty stdio arg was previously a 400). - cc-plugin-cli.js: export `serialize` for reuse. - CcConfig.tsx: CcMcpScope narrowed to user/local; the projectScoped bucket's McpCard now sends scope="local"; rows sourced from ~/.claude/settings.json (no CLI scope addresses them) no longer render a Remove button. Cleaned up i18n keys my own earlier change made dead (explain.mcp.addCmd/remove/removeButton) and gave the scope <select> its own short option labels instead of reusing full-sentence section headings.
This commit is contained in:
@@ -1903,8 +1903,8 @@ function McpPanel({
|
||||
aria-label={t("mcp.scope")}
|
||||
className="h-8 bg-surface-1 border border-border rounded px-2 text-[12px] text-fg-primary focus:outline-none focus:ring-1 focus:ring-accent/40"
|
||||
>
|
||||
<option value="user">{t("mcp.userScope")}</option>
|
||||
<option value="project">{t("mcp.projectScope")}</option>
|
||||
<option value="user">{t("mcp.scopeUser")}</option>
|
||||
<option value="local">{t("mcp.scopeLocal")}</option>
|
||||
</select>
|
||||
<div className="flex rounded-md border border-border overflow-hidden">
|
||||
{(["stdio", "http"] as const).map((k) => (
|
||||
@@ -1994,7 +1994,7 @@ function McpPanel({
|
||||
<McpCard
|
||||
key={`p:${s.name}:${s.source}`}
|
||||
server={s}
|
||||
scope="project"
|
||||
scope="local"
|
||||
run={run}
|
||||
busy={busy}
|
||||
t={t}
|
||||
@@ -2022,6 +2022,10 @@ function McpCard({
|
||||
}) {
|
||||
const [confirmRemove, setConfirmRemove] = useState(false);
|
||||
const busyId = `${scope}:${server.name}`;
|
||||
// Entries from ~/.claude/settings.json have no CLI scope that addresses
|
||||
// them (`claude mcp remove` only ever touches ~/.claude.json / .mcp.json)
|
||||
// — a Remove button here would look live and never work.
|
||||
const removable = server.source !== "~/.claude/settings.json";
|
||||
return (
|
||||
<div className="rounded-lg border border-border bg-surface-2 px-4 py-3">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
@@ -2063,29 +2067,33 @@ function McpCard({
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<ActionButton
|
||||
icon={Trash2}
|
||||
danger
|
||||
disabled={busy === busyId}
|
||||
onClick={() => {
|
||||
if (!confirmRemove) {
|
||||
setConfirmRemove(true);
|
||||
return;
|
||||
}
|
||||
setConfirmRemove(false);
|
||||
void run(
|
||||
busyId,
|
||||
() => api.ccConfig.mcpRemove(server.name, scope),
|
||||
t("manage.mcpRemoved", { name: server.name })
|
||||
);
|
||||
}}
|
||||
>
|
||||
{busy === busyId
|
||||
? t("manage.working")
|
||||
: confirmRemove
|
||||
? t("manage.confirmRemove")
|
||||
: t("manage.remove")}
|
||||
</ActionButton>
|
||||
{removable ? (
|
||||
<ActionButton
|
||||
icon={Trash2}
|
||||
danger
|
||||
disabled={busy === busyId}
|
||||
onClick={() => {
|
||||
if (!confirmRemove) {
|
||||
setConfirmRemove(true);
|
||||
return;
|
||||
}
|
||||
setConfirmRemove(false);
|
||||
void run(
|
||||
busyId,
|
||||
() => api.ccConfig.mcpRemove(server.name, scope),
|
||||
t("manage.mcpRemoved", { name: server.name })
|
||||
);
|
||||
}}
|
||||
>
|
||||
{busy === busyId
|
||||
? t("manage.working")
|
||||
: confirmRemove
|
||||
? t("manage.confirmRemove")
|
||||
: t("manage.remove")}
|
||||
</ActionButton>
|
||||
) : (
|
||||
<p className="text-[10px] text-fg-muted">{t("mcp.notRemovable")}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user