feat: add MCP server support with global library and per-project toggles
All checks were successful
Build App / build-macos (push) Successful in 2m20s
Build App / build-windows (push) Successful in 3m21s
Build App / build-linux (push) Successful in 5m8s
Build Container / build-container (push) Successful in 1m4s
Sync Release to GitHub / sync-release (release) Successful in 2s

Add Model Context Protocol (MCP) server configuration support. Users can
define MCP servers globally (new sidebar tab) and enable them per-project.
Enabled servers are injected into containers as MCP_SERVERS_JSON env var
and merged into ~/.claude.json by the entrypoint.

Backend: McpServer model, McpStore (JSON + atomic writes), 4 CRUD commands,
container injection with fingerprint-based recreation detection.
Frontend: MCP sidebar tab, McpPanel/McpServerCard components, useMcpServers
hook, per-project MCP checkboxes in ProjectCard config.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 08:57:12 -08:00
parent 2ddc705925
commit 625d48a6ed
22 changed files with 839 additions and 23 deletions

View File

@@ -1,6 +1,7 @@
import { useShallow } from "zustand/react/shallow";
import { useAppState } from "../../store/appState";
import ProjectList from "../projects/ProjectList";
import McpPanel from "../mcp/McpPanel";
import SettingsPanel from "../settings/SettingsPanel";
export default function Sidebar() {
@@ -8,35 +9,37 @@ export default function Sidebar() {
useShallow(s => ({ sidebarView: s.sidebarView, setSidebarView: s.setSidebarView }))
);
const tabCls = (view: typeof sidebarView) =>
`flex-1 px-3 py-2 text-sm font-medium transition-colors ${
sidebarView === view
? "text-[var(--accent)] border-b-2 border-[var(--accent)]"
: "text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
}`;
return (
<div className="flex flex-col h-full w-[25%] min-w-56 max-w-80 bg-[var(--bg-secondary)] border border-[var(--border-color)] rounded-lg overflow-hidden">
{/* Nav tabs */}
<div className="flex border-b border-[var(--border-color)]">
<button
onClick={() => setSidebarView("projects")}
className={`flex-1 px-3 py-2 text-sm font-medium transition-colors ${
sidebarView === "projects"
? "text-[var(--accent)] border-b-2 border-[var(--accent)]"
: "text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
}`}
>
<button onClick={() => setSidebarView("projects")} className={tabCls("projects")}>
Projects
</button>
<button
onClick={() => setSidebarView("settings")}
className={`flex-1 px-3 py-2 text-sm font-medium transition-colors ${
sidebarView === "settings"
? "text-[var(--accent)] border-b-2 border-[var(--accent)]"
: "text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
}`}
>
<button onClick={() => setSidebarView("mcp")} className={tabCls("mcp")}>
MCP
</button>
<button onClick={() => setSidebarView("settings")} className={tabCls("settings")}>
Settings
</button>
</div>
{/* Content */}
<div className="flex-1 overflow-y-auto overflow-x-hidden p-1 min-w-0">
{sidebarView === "projects" ? <ProjectList /> : <SettingsPanel />}
{sidebarView === "projects" ? (
<ProjectList />
) : sidebarView === "mcp" ? (
<McpPanel />
) : (
<SettingsPanel />
)}
</div>
</div>
);