From bbbd17475fc47fef0a1d1275edae7bed59c31a3c Mon Sep 17 00:00:00 2001
From: esengine <359807859@qq.com>
Date: Sat, 2 May 2026 03:01:25 -0700
Subject: [PATCH] feat(mcp): activate d-keybind in /mcp browser; share disable
helper
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Final stage of #105: wire McpBrowser modal's labelled-but-stub `d`
keybind to the same `mcpDisabled` config persistence the slash
already does. Removes the "(TBD)" label from the modal footer.
- New `src/cli/ui/mcp-disable.ts` exporting `toggleMcpDisabled`,
the readConfig+writeConfig logic that was previously inlined in
the slash handler.
- `src/cli/ui/slash/handlers/mcp.ts` — replaces the inline
read/write block with a single call to `toggleMcpDisabled`.
Unknown-name validation stays in the slash since it has access
to mcpServers + mcpSpecs to build the "Known: …" hint.
- `src/cli/ui/McpBrowser.tsx` — `d` key handler persists
`mcpDisabled` for the selected server and closes the modal.
No need for the unknown-name check in the modal — the user
picked from a live list, the name is always valid.
- Footer label updated.
Refs #105.
---
src/cli/ui/McpBrowser.tsx | 9 ++++++++-
src/cli/ui/mcp-disable.ts | 26 ++++++++++++++++++++++++++
src/cli/ui/slash/handlers/mcp.ts | 21 ++-------------------
3 files changed, 36 insertions(+), 20 deletions(-)
create mode 100644 src/cli/ui/mcp-disable.ts
diff --git a/src/cli/ui/McpBrowser.tsx b/src/cli/ui/McpBrowser.tsx
index 22ef2d4..bc43d07 100644
--- a/src/cli/ui/McpBrowser.tsx
+++ b/src/cli/ui/McpBrowser.tsx
@@ -4,6 +4,7 @@ import { Box, Text } from "ink";
// biome-ignore lint/style/useImportType: tsconfig jsx=react needs React in value scope for JSX compilation
import React, { useState } from "react";
import { useKeystroke } from "./keystroke-context.js";
+import { toggleMcpDisabled } from "./mcp-disable.js";
import { type ApplyAppend, kickOffMcpReconnect } from "./mcp-reconnect-kickoff.js";
import type { McpServerSummary } from "./slash/types.js";
import { COLOR } from "./theme.js";
@@ -41,6 +42,12 @@ export function McpBrowser({
// so the line is visible immediately.
postInfo(kickOffMcpReconnect(target, postInfo, applyAppend));
onClose();
+ } else if (ev.input === "d") {
+ const target = servers[index];
+ if (!target) return;
+ // Persist `mcpDisabled` and close — takes effect on next launch.
+ postInfo(toggleMcpDisabled("disable", target.label));
+ onClose();
}
});
@@ -66,7 +73,7 @@ export function McpBrowser({
)}
- ↑↓ pick · [r] reconnect · [d] disable (TBD) · esc quit
+ ↑↓ pick · [r] reconnect · [d] disable · esc quit
);
diff --git a/src/cli/ui/mcp-disable.ts b/src/cli/ui/mcp-disable.ts
new file mode 100644
index 0000000..bd76760
--- /dev/null
+++ b/src/cli/ui/mcp-disable.ts
@@ -0,0 +1,26 @@
+/** Persists `mcpDisabled` to ~/.reasonix/config.json — shared between `/mcp disable / enable` slash and the McpBrowser `d` keybind. */
+
+import { readConfig, writeConfig } from "../../config.js";
+
+export function toggleMcpDisabled(action: "disable" | "enable", name: string): string {
+ const trimmed = name.trim();
+ if (!trimmed) {
+ return `usage: /mcp ${action} · pick a name shown in /mcp (anonymous servers can't be named-toggled).`;
+ }
+ const cfg = readConfig();
+ const current = new Set(cfg.mcpDisabled ?? []);
+ if (action === "disable") {
+ if (current.has(trimmed)) {
+ return `▸ ${trimmed} is already disabled — restart to apply, or /mcp enable ${trimmed}.`;
+ }
+ current.add(trimmed);
+ writeConfig({ ...cfg, mcpDisabled: [...current].sort() });
+ return `▸ ${trimmed} disabled — takes effect on next launch. /mcp enable ${trimmed} to revert.`;
+ }
+ if (!current.has(trimmed)) {
+ return `▸ ${trimmed} is not disabled.`;
+ }
+ current.delete(trimmed);
+ writeConfig({ ...cfg, mcpDisabled: current.size > 0 ? [...current].sort() : undefined });
+ return `▸ ${trimmed} re-enabled — takes effect on next launch.`;
+}
diff --git a/src/cli/ui/slash/handlers/mcp.ts b/src/cli/ui/slash/handlers/mcp.ts
index e7bf1d0..ba50a32 100644
--- a/src/cli/ui/slash/handlers/mcp.ts
+++ b/src/cli/ui/slash/handlers/mcp.ts
@@ -1,6 +1,6 @@
-import { readConfig, writeConfig } from "../../../../config.js";
import type { CacheFirstLoop } from "../../../../loop.js";
import { applyMcpAppend } from "../../mcp-append.js";
+import { toggleMcpDisabled } from "../../mcp-disable.js";
import { kickOffMcpReconnect } from "../../mcp-reconnect-kickoff.js";
import type { SlashHandler } from "../dispatch.js";
import { appendSection } from "../helpers.js";
@@ -105,24 +105,7 @@ function toggleDisabled(
const list = [...known].sort().join(", ") || "(none)";
return { info: `unknown MCP server "${name}". Known: ${list}.` };
}
- const cfg = readConfig();
- const current = new Set(cfg.mcpDisabled ?? []);
- if (action === "disable") {
- if (current.has(name)) {
- return { info: `▸ ${name} is already disabled — restart to apply, or /mcp enable ${name}.` };
- }
- current.add(name);
- writeConfig({ ...cfg, mcpDisabled: [...current].sort() });
- return {
- info: `▸ ${name} disabled — takes effect on next launch. /mcp enable ${name} to revert.`,
- };
- }
- if (!current.has(name)) {
- return { info: `▸ ${name} is not disabled.` };
- }
- current.delete(name);
- writeConfig({ ...cfg, mcpDisabled: current.size > 0 ? [...current].sort() : undefined });
- return { info: `▸ ${name} re-enabled — takes effect on next launch.` };
+ return { info: toggleMcpDisabled(action, name) };
}
function parseLabelFromSpec(spec: string): string | null {