Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions dashboard/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,8 @@ export const en = {
specPlaceholder: "spec — e.g. fs=npx -y @modelcontextprotocol/...",
saved: "saved",
savedRestart: "saved — restart carboncode code to bridge this server",
removed: "removed — restart to drop the live bridge",
removed: "removed + disconnected",
removedRestart: "removed — restart to drop the live bridge",
removeConfirm: "Remove MCP spec from config?\n\n{spec}",
noServers: "No MCP servers in this session.",
tools: "tools",
Expand Down Expand Up @@ -495,7 +496,7 @@ export const en = {
marketplaceEnvHint:
"Set these in your shell before next `carboncode code` so the bridged server can authenticate.",
marketplaceRestartHint:
"Spec written to ~/.carboncode/config.json. Restart `carboncode code` to bridge the server (live hot-reload is on the roadmap).",
"Saved in ~/.carboncode/config.json but not bridged in this session. Restart `carboncode code` if live reload is unavailable or failed.",
},
memory: {
loading: "loading memory…",
Expand Down
5 changes: 3 additions & 2 deletions dashboard/src/i18n/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,8 @@ export const zhCN = {
specPlaceholder: "规格 — 例如 fs=npx -y @modelcontextprotocol/...",
saved: "已保存",
savedRestart: "已保存 — 重启 carboncode code 以桥接此服务器",
removed: "已移除 — 重启以断开实时桥接",
removed: "已移除并断开桥接",
removedRestart: "已移除 — 重启以断开实时桥接",
removeConfirm: "从配置中移除 MCP 规格?\n\n{spec}",
noServers: "此会话中无 MCP 服务器。",
tools: "个工具",
Expand Down Expand Up @@ -470,7 +471,7 @@ export const zhCN = {
marketplaceEnvHint:
"下次启动 `carboncode code` 之前在 shell 里设好,桥接的服务器才能正常鉴权。",
marketplaceRestartHint:
"已写入 ~/.carboncode/config.json。重启 `carboncode code` 后服务器才会真正桥接(热重载在路线图上)。",
"已写入 ~/.carboncode/config.json,但当前会话尚未桥接。若实时重载不可用或失败,请重启 `carboncode code`。",
},
memory: {
loading: "加载记忆…",
Expand Down
8 changes: 8 additions & 0 deletions dashboard/src/lib/mcp-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,11 @@ export function mcpSpecCommand(spec: unknown): string {
const eq = text.indexOf("=");
return eq > 0 ? text.slice(eq + 1) : text;
}

export function mcpMutationNeedsRestart(result: { requiresRestart?: boolean }): boolean {
return result.requiresRestart === true;
}

export function shouldShowMcpRestartHint(installed: boolean, bridged: boolean): boolean {
return installed && !bridged;
}
23 changes: 18 additions & 5 deletions dashboard/src/panels/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { api } from "../lib/api.js";
import { fmtNum } from "../lib/format.js";
import { html } from "../lib/html.js";
import {
normalizeMcpSpec,
mcpMutationNeedsRestart,
mcpSpecCommand as specCommand,
mcpSpecLabel as specLabel,
normalizeMcpSpec,
shouldShowMcpRestartHint,
} from "../lib/mcp-spec.js";

interface McpServer {
Expand Down Expand Up @@ -216,7 +218,7 @@ export function McpPanel() {
method: "POST",
body: { spec: newSpec.trim() },
});
setInfo(r.requiresRestart ? t("mcp.savedRestart") : t("mcp.saved"));
setInfo(mcpMutationNeedsRestart(r) ? t("mcp.savedRestart") : t("mcp.saved"));
setTimeout(() => setInfo(null), 4000);
setNewSpec("");
await load();
Expand All @@ -232,8 +234,13 @@ export function McpPanel() {
if (!confirm(t("mcp.removeConfirm", { spec }))) return;
setBusy(true);
try {
await api("/mcp/specs", { method: "DELETE", body: { spec } });
setInfo(t("mcp.removed"));
const r = await api<{ requiresRestart?: boolean }>("/mcp/specs", {
method: "DELETE",
body: { spec },
});
setInfo(
mcpMutationNeedsRestart(r) ? t("mcp.removedRestart") : t("mcp.removed"),
);
setTimeout(() => setInfo(null), 4000);
await load();
} catch (err) {
Expand Down Expand Up @@ -396,6 +403,10 @@ export function McpPanel() {
const spec = specForEntry(openRegistry);
return spec && (specs ?? []).includes(spec) ? spec : null;
})(),
installedBridged: (() => {
const spec = specForEntry(openRegistry);
return !!spec && data.servers.some((server) => server.spec === spec);
})(),
onInstall: () => installFromRegistry(openRegistry),
onUninstall: (spec: string) => removeSpec(spec),
onClose: () => setOpenRegistry(null),
Expand Down Expand Up @@ -619,6 +630,7 @@ interface RegistryDetailArgs {
entry: RegistryEntryDto;
busy: boolean;
installedSpec: string | null;
installedBridged: boolean;
onInstall: () => void;
onUninstall: (spec: string) => void;
onClose: () => void;
Expand All @@ -628,6 +640,7 @@ function renderRegistryDetail({
entry,
busy,
installedSpec,
installedBridged,
onInstall,
onUninstall,
onClose,
Expand Down Expand Up @@ -713,7 +726,7 @@ function renderRegistryDetail({
}

${
installed
shouldShowMcpRestartHint(installed, installedBridged)
? html`<div class="card accent-warn">
<div class="card-b" style="font-size:12.5px;line-height:1.6">
${t("mcp.marketplaceRestartHint")}
Expand Down
22 changes: 21 additions & 1 deletion tests/dashboard-mcp-spec.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { describe, expect, it } from "vitest";
import { mcpSpecCommand, mcpSpecLabel, normalizeMcpSpec } from "../dashboard/src/lib/mcp-spec.js";
import {
mcpMutationNeedsRestart,
mcpSpecCommand,
mcpSpecLabel,
normalizeMcpSpec,
shouldShowMcpRestartHint,
} from "../dashboard/src/lib/mcp-spec.js";

describe("dashboard MCP spec display helpers", () => {
it("keeps legacy string specs unchanged", () => {
Expand Down Expand Up @@ -34,3 +40,17 @@ describe("dashboard MCP spec display helpers", () => {
expect(mcpSpecCommand(spec)).toBe("");
});
});

describe("dashboard MCP hot-reload feedback", () => {
it("only requests a restart when the API explicitly reports one", () => {
expect(mcpMutationNeedsRestart({ requiresRestart: true })).toBe(true);
expect(mcpMutationNeedsRestart({ requiresRestart: false })).toBe(false);
expect(mcpMutationNeedsRestart({})).toBe(false);
});

it("only shows the marketplace restart hint for installed, unbridged servers", () => {
expect(shouldShowMcpRestartHint(true, false)).toBe(true);
expect(shouldShowMcpRestartHint(true, true)).toBe(false);
expect(shouldShowMcpRestartHint(false, false)).toBe(false);
});
});
Loading