Files
waggle-os/docs/ux-refactor/gap-cards/S08-mcp-hub.md
Oleg Maslov 0c3e2ead3b
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled
moving
2026-09-02 10:10:29 +02:00

21 KiB

Gap Card — S08 · MCP Hub

UX-refactor planning artifact. Execution model is the LOCKED in-place incremental refactor of apps/web + targeted backend extensions. Mockup is directional; PRD acceptance criteria win. Every claim below cites a real file. PRD = docs/Waggle_OS_UX_Refactor_Master_Handoff_Package/Waggle_OS_UX_Refactor_PRD.md.


1. Screen & purpose

MCP Hub — the power-user capability-extension surface for Model Context Protocol servers (PRD §12.8, PRD line 566-579). Belongs to the Extend IA layer (PRD §10.4; blueprint line 74, 81: route group /mcps). Explicit blueprint directive: "Do not bury Connectors/MCPs inside Settings" (blueprint line 79).

Purpose (PRD §12.8): "Enable power-user extension through Model Context Protocol servers." MCPs are auditable capability providers for power users and agents (blueprint line 140). The governing acceptance criterion: "MCPs are powerful but always visible, scoped, auditable, and reversible." (PRD line 579).

Primary user journey — J11 / Journey 9 "Install MCP" (PRD lines 736-743; blueprint line 206-208): power user opens MCP Hub → selects e.g. Postgres MCP → reviews risk and scope → approves installation → MCP becomes available to selected workspace/agent → install audit is recorded.

Mockup (directional only — screen_08_mcp_hub.png): top toolbar with tabs (Installed / Available / Marketplace / Custom / Remote Registry), search, "Add MCP Server" CTA; left an "Installed MCPs" table (name, status pill, connected-to, last-used, scope, actions) over an "Available MCPs" category-filtered grid; right rail with an "MCP Overview" donut (counts), "MCP Health", "Recent Activity" (audit/log feed), and a "Custom MCP" add affordance.


2. Required states (PRD / Blueprint)

Tabs (PRD §12.8 line 572): Installed · Available · Marketplace · Custom · Remote Registry.

MCP object fields (PRD §12.8 line 573): name, description, version, status, connected to, last used, locality (local/remote), risk, permissions, logs. Target schema Extension/MCP (blueprint line 554, PRD §15.2 ExtensionType): id, type, name, source, version, endpoint, capabilities, riskLevel, approvalClass, status, health, installedAt, approvedBy.

Actions (PRD §12.8 line 574): install · start/stop · test · scope · view logs · revoke · add custom MCP. Risky MCPs require approval + audit trail (PRD line 575; blueprint line 161 "elevated connectors/MCPs … require approval").

Extension lifecycle states (PRD §14.7 lines 906-914): Available · Installed · Update available · Installing · Failed install · Risk approval required · Disabled/revoked. Plus per-server runtime status (blueprint line 334-335): installed; available; running; stopped; error; risk-approval-needed.

Connector/MCP UI states (PRD §14.5 / blueprint line 467): Loading, empty, populated, error.

Scope (acceptance-critical): an MCP must be scopeable to workspace and/or agent (PRD line 742; blueprint line 207 "scope to workspace/agent"; line 601 "Connector and MCP installs are visible, permissioned, health-checked and auditable"; line 600 "a workspace agent cannot run with hidden memory/tool/MCP access").

Offline degradation (blueprint line 516): offline mode degrades MCPs gracefully while keeping local workspace/memory available.


3. Current state in repo

There is NO dedicated MCP Hub app today. MCPs surface in three thin, mostly-static places:

  1. Static catalog (discovery only) — apps/web/src/components/os/apps/connectors/McpCatalog.tsx + McpServerCard.tsx + mcp-registry.ts (a re-export shim of @waggle/shared). Rendered as the "MCP Servers" tab inside ConnectorsApp.tsx (apps/web/src/components/os/apps/ConnectorsApp.tsx:197-205,348-349). The catalog data is MCP_CATALOG / MCP_CATEGORIES / CATEGORY_EMOJI in packages/shared/src/mcp-catalog.ts (McpServer interface at :17; MCP_CATALOG at :53; 14 categories at :30). Per the backend-map (05f §12, lines 391-401), this catalog is static and not connected at runtime — a discovery/install-command directory.

    • The only "install" UX is copy-a-shell-command-then-restart (McpServerCard.tsx:30-37,98-138 copies server.installCmd; tooltip text :111-116: "Copy the command … Run it in your terminal … Restart Waggle"). There is no in-app install, start/stop, test, scope, revoke, logs, risk, or status — none of the PRD §12.8 actions/fields exist in the UI.
  2. Live runtime status (read-only) — GET /api/capabilities/status (packages/server/src/local/routes/capabilities.ts:35-53,103-110) returns mcpServers:[{name,state,healthy,tools}] from the real runtime. But the runtime is empty by default (see #3 below), so this list is always [] on a stock install.

  3. The MCP runtime engine (rich, but unwired) — packages/agent/src/mcp/mcp-runtime.ts. McpRuntime has the full lifecycle the PRD needs: addServer(config) (:308), removeServer(name) (:327), startAll/stopAll, per-instance start()/stop()/callTool() (McpServerInstance :102,154,178), getServerStates() (:362), getHealthy() (:370), isServerHealthy(name) (:399), and per-workspace scoping via McpServerConfig.workspaceId (:13) + getToolsForWorkspace(workspaceId) (:385). CRITICAL GAP: the runtime is instantiated empty — const mcpRuntime = new McpRuntime() with comment "empty by default" (packages/server/src/local/index.ts:911), and nothing ever calls addServer() (grep mcpRuntime.addServer / .mcp.json / loadMcpConfig → 0 matches in packages/server/src). So there is no persistence layer that loads installed MCP configs at boot, and no HTTP route to install/ start/stop/test/remove a server.

Install-audit substrate is present (AuditCapabilityType includes 'mcp'packages/core/src/install-audit.ts:22; InstallAuditStore.record/getRecent/getByCapability exist) but has no read HTTP route (substrate-types inventory §d: writes only; GET /api/audit/installs exists in skills.ts but is a generic recent-installs feed). MCP installs are not currently audited because there is no MCP install path to audit.

Adapter: no getMcps/installMcp/testMcp methods in apps/web/src/lib/adapter.ts (grep → 0 matches).

Disposition: create-new (with substrate reuse). A net-new MCPHub app (MCPHubApp.tsx) promoted out of ConnectorsApp's MCP tab, **plus net-new /api/mcps/* backend routes wiring the existing McpRuntime

  • a new persisted config store + install-audit**. The static catalog component (McpCatalog/McpServerCard) is reused as the "Available"/"Marketplace" tab content. This is the single largest backend gap on the Extend layer — the engine exists but is entirely unwired to UI or persistence.

4. Frontend work

New IA placement: add an mcp-hub (or reuse the dead marketplace-adjacent slot) AppId in apps/web/src/lib/dock-tiers.ts, register chrome in Desktop.tsx appConfig + a renderAppContent switch case (per frontend inventory §b lines 123-129), and add a dock entry under the Extend zone-parent (dock-tiers.ts zone model). MCP Hub is power-user/PRO-tier-gated (PRD personas: power user/developer needs MCPs — PRD line 151).

Components to CREATE (apps/web/src/components/os/apps/):

  • MCPHubApp.tsx — shell with the 5 PRD tabs (Installed / Available / Marketplace / Custom / Remote Registry). Mirror ConnectorsApp.tsx's sidebar-tab + filter layout (:183-216) so it's visually consistent with the Connector Hub it splits from.
  • mcp/InstalledMcpTable.tsx — table of running/installed servers: name, status pill (running/stopped/ error/installing — maps McpServerState mcp-runtime.ts:16), connected-to, last-used, locality, scope chip, row actions (start/stop, test, scope, view logs, revoke). Drives PRD §12.8 line 573-574.
  • mcp/AddCustomMcpForm.tsx — Custom tab: { name, command, args[], env{}, workspaceId? }POST /api/mcps (matches McpServerConfig mcp-runtime.ts:8-14). Reuse injection-scanner patterns server-side.
  • mcp/McpDetailPanel.tsx (right rail) — MCP Overview counts, MCP Health, permissions/scope editor, logs viewer, and the risk/approval banner ("Risk approval required" state). Reuse the approvals surface pattern from ApprovalsApp.tsx / inline-chat approval (same backend /api/approval/*).
  • mcp/McpScopeDialog.tsx — scope-to-workspace/agent picker (writes workspaceId onto the server config; later, mcpIds[] onto workspace/agent per §6).

Components to REUSE (keep-promote):

  • McpCatalog.tsx + McpServerCard.tsx + mcp-registry.ts → become the Available + Marketplace + Remote Registry tab bodies. Today McpServerCard only shows a copy-command strip (McpServerCard.tsx:98-138) — rework it to add a real Install button (calls the new installMcp adapter method) while keeping copy-command as the offline fallback.
  • recommendConnectors from @waggle/shared (already used McpCatalog.tsx:15,69) for "Recommended" tile.
  • LockedFeature.tsx for tier gating; ContextMenu.tsx for row actions; shadcn table, badge, dialog, tabs, tooltip primitives (components/ui/*, frontend inventory §e).

Adapter methods to ADD (apps/web/src/lib/adapter.ts — the single sidecar gateway, §c lines 162-265): getMcps(), installMcp(payload), addCustomMcp(config), startMcp(id), stopMcp(id), testMcp(id), scopeMcp(id, scope), revokeMcp(id), getMcpLogs(id) — one per PRD §16.9 / §12.8 action.

New hook: hooks/useMcps.ts (mirrors the useConnectors-style pattern) returning { installed, available, custom, install, start, stop, test, scope, revoke, refresh }. Wire SSE/poll over /api/capabilities/status or a new /api/mcps for live status (cache-invalidate on "connector sync" / install completion per blueprint line 514).


5. Backend work (PRD §16.9 endpoints — all MCP rows)

All net-new routes live in a new packages/server/src/local/routes/mcps.ts, registered in local/index.ts, wiring the already-built McpRuntime (server.agentState.mcpRuntime, decorated local/index.ts:1361). No new SQLite table is strictly required — but a persisted config source IS (see migration flag).

PRD §16.9 endpoint Status Extend vs net-new + substrate it touches
GET /api/mcps PARTIAL → build net-new route over existing data No /api/mcps exists (grep-confirmed in backend-routes inventory, line 422 & §16.9 line 522). The data is derivable today: live runtime status from McpRuntime.getServerStates()/getHealthy()/getAllTools() (already surfaced in capabilities.ts:35-53), enriched with catalog metadata from MCP_CATALOG (@waggle/shared). Net-new thin route in mcps.ts that joins runtime state + persisted config + catalog into the Extension/MCP shape (§2). Touches: McpRuntime (agent), MCP_CATALOG (shared), new config store.
POST /api/mcps/install PARTIAL → net-new, route through existing install path No /api/mcps/install (inventory §16.9 line 523). Closest install paths are marketplace (POST /api/marketplace/install — SecurityGate + audit, marketplace.ts:224-319) and plugin install (POST /api/plugins/install). Net-new MCP install that: (a) persists an McpServerConfig, (b) calls mcpRuntime.addServer(config) + start() (mcp-runtime.ts:308,102), (c) runs the SecurityGate + writes an install_audit row with capability_type:'mcp' (install-audit.ts:22) — satisfying the "install audit is recorded" acceptance step (PRD line 743). Risky servers → return risk approval required state instead of starting. Tier: PRO (match marketplace).
POST /api/mcps/:id/test MISSING No MCP test/health route (inventory §16.9 line 524). Closest analog is GET /api/connectors/:id/health. Net-new: resolve the server, start() if needed, assert isHealthy() (mcp-runtime.ts:94,399) and/or do a tools/list round-trip, return health + discovered tools. Touches McpRuntime.
POST /api/mcps/:id/revoke MISSING No MCP revoke/uninstall by id (inventory §16.9 line 525). Closest: DELETE /api/plugins/:name. Net-new: mcpRuntime.removeServer(name) (mcp-runtime.ts:327) + delete persisted config + write install_audit action:'rejected'/'revoked'. Touches McpRuntime + config store + install-audit.
GET /api/marketplace PARTIAL Listing is GET /api/marketplace/search (marketplace.ts); the Marketplace tab reuses it. Alias of /search (inventory §16.9 line 526). Not MCP-specific work.
POST /api/marketplace/install EXISTS marketplace.ts (Tier PRO, SecurityGate) — reused by the Marketplace tab.

Additional routes implied by PRD §12.8 actions not in §16.9 list (net-new):

  • POST /api/mcps (add custom MCP) — blueprint API line 530 lists GET/POST /mcps. Persists config + addServer + audit. Net-new.
  • POST /api/mcps/:id/start and POST /api/mcps/:id/stop — PRD §12.8 "start/stop" action. Map to McpServerInstance.start()/stop() (mcp-runtime.ts:102,154). Net-new.
  • PATCH /api/mcps/:id/permissions (scope/permissions) — blueprint API line 530 (PATCH /mcps/:id/permissions). Writes workspaceId/scope onto the persisted config (runtime honors it via getToolsForWorkspace, mcp-runtime.ts:385). Net-new.
  • GET /api/mcps/:id/logs — PRD §12.8 "view logs". No log capture exists in McpServerInstance today (stderr is piped :113 but not retained). Net-new + small runtime change to buffer stderr/state-change events for retrieval.
  • GET /api/extend/audit (or extend /api/audit/installs) — surface the install-audit trail for the MCP Hub right-rail "Recent Activity". InstallAuditStore.getRecent() exists but has no MCP-scoped HTTP read (substrate-types §d gap #1). Net-new (implied addition to §16.9, flagged in substrate inventory).

Migration / persistence flag (IMPORTANT):

  • No .mind SQLite migration is required for the MCP entity itself — installed MCP configs can persist as a JSON file (mirrors workspace.json / .mcp.json convention; note repo root already has an untracked .mcp.json per git status) read at boot to populate mcpRuntime.addServer(). This persistence layer is entirely net-new (today mcpRuntime is empty and never populated — local/index.ts:911). This is the load-bearing backend gap: without it, "Installed" MCPs do not survive restart.
  • Install-audit DDL drift (latent, flag for the plan): AuditRiskLevel TS includes 'critical' (install-audit.ts:16) but both CHECK constraints allow only low|medium|high (install-audit.ts:65, schema.ts:130). Any MCP install recording riskLevel:'critical' would throw a CHECK violation — map CRITICAL → 'high' + approvalClass:'blocked' as marketplace.ts already does (marketplace.ts:224-319), or fix the DDL.

6. Shared types needed (PRD §15 vs lib/types.ts)

  • ExtensionType (PRD §15.2 line 953: 'skill'|'connector'|'mcp'|'model'|'template'|'external_tool') — MISSING in apps/web/src/lib/types.ts (substrate-types §e: none of the §15.2 unions exist in FE). Add it.
  • Extension / Mcp interface (blueprint line 554: id, type, name, source, version, endpoint, capabilities, riskLevel, approvalClass, status, health, installedAt, approvedBy) — MISSING. No Extension/Mcp type anywhere in FE. Add to lib/types.ts (and ideally packages/shared/src/types.ts so the sidecar route and FE share it). The existing McpServer (@waggle/shared mcp-catalog.ts:17: id,name,description,author,category,url,installCmd,capabilities,official?,logo?) is the catalog/discovery shape — distinct from the installed-instance Mcp shape; keep both, the Hub joins them by id.
  • Runtime types already exist server-side and should be the source of truth for the route contract: McpServerConfig, McpServerState, McpToolInfo (packages/agent/src/mcp/mcp-runtime.ts:8-22). Mirror McpServerState ('starting'|'ready'|'error'|'stopped') into the FE status union (map to PRD §14.7 states).
  • Reuse from install-audit: AuditCapabilityType, AuditRiskLevel, AuditApprovalClass (packages/core/src/install-audit.ts:15-22) for the riskLevel/approvalClass/audit fields — do not invent parallel enums.
  • mcpIds[] on Workspace/Agent (PRD §15.3 WorkspaceConfigV2.mcpIds, §15.5 Agent mcpIds) — MISSING from both WorkspaceConfig (workspace-manager.ts, substrate-types §a) and AgentDef (packages/shared/src/types.ts:36-47, §e). Needed for the "scope to workspace/agent" action. Additive optional field (no DB migration — workspace.json is JSON).

7. Dependencies (screens / phases first)

  • Connector Hub (S07) — MCP Hub is split OUT of ConnectorsApp.tsx's MCP tab; do the Connector Hub promotion first (or jointly) so the shared sidebar-tab/filter/health/state patterns are settled and the MCP tab can be cleanly extracted. Same Extend-layer dock placement work.
  • Extend layer / dock-zone IA — requires the new AppId + dock-zone wiring (frontend inventory §b/§d: consolidate on AppId, add Extend zone entries). Blocks any new Extend app from being reachable.
  • Install-audit read route (§d gap) — shared with Skills (S?) and Connectors governance; build once, reuse across the Extend layer.
  • Approvals surface — risk-approval flow reuses existing /api/approval/* + ApprovalsApp patterns; no new approval substrate, but the wiring depends on that surface staying stable.
  • Marketplace — the Marketplace tab reuses GET /api/marketplace/search + POST /api/marketplace/install (already exist); ensure marketplace MCP packages are tagged so they route to the MCP install path.
  • Phase hint: later phase. Backend wiring (persisted config store + runtime population + /api/mcps/*) is the prerequisite for any non-static UI; the static catalog already ships, so this is a depth upgrade, not a day-1 blocker. Sequence after Home/Workspace/Memory core screens.

8. Effort: XL

Largest Extend-layer gap: the runtime engine exists but is completely unwired (empty McpRuntime, no persistence, no HTTP surface, copy-command-only UI). Requires a net-new persisted MCP-config store + boot-time runtime population + ~8 net-new sidecar routes (install/start/stop/test/scope/revoke/logs + custom add) + SecurityGate/audit integration + a full new app shell with 5 tabs, installed-table, detail/logs panel, scope dialog, risk-approval flow, and adapter/hook layer — while preserving the static catalog as the Available/Marketplace tabs. (Frontend-only would be M; the backend wiring + persistence + governance is what pushes it to XL.)


9. Open questions

  1. Persistence location/format for installed MCP configs — JSON file (.mcp.json at dataDir, mirroring workspace.json; root already has an untracked .mcp.json) vs a new install_audit-adjacent table? Recommendation: JSON file (no migration), but confirm the dataDir path + multi-workspace scoping model.
  2. Scope model — is an MCP scoped by writing workspaceId onto its single config (1 server : 1 workspace, per current McpServerConfig.workspaceId), or by an mcpIds[] membership array on each workspace/agent (N:N)? PRD §15.3/§15.5 imply mcpIds[] (N:N); the runtime today only supports the single-workspaceId field (mcp-runtime.ts:13,385). N:N needs a runtime change.
  3. Logs captureMcpServerInstance pipes stderr but does not retain it (mcp-runtime.ts:113). Add a ring-buffer of stderr + stateChange events for GET /api/mcps/:id/logs, or defer logs to a later phase?
  4. "Remote Registry" tab semantics — is this the Composio gateway (already referenced in McpCatalog.tsx:106,294-301), the official modelcontextprotocol/servers registry, or a remote (HTTP/SSE-transport) MCP class distinct from local stdio? The runtime today is stdio-only (mcp-runtime.ts:108-115); remote-transport MCPs would need a new transport in McpServerInstance.
  5. Install execution surface — does in-app "Install" run npx … (spawn a child process to install the package) in the sidecar, or only register config + rely on a globally-installed binary? Security review needed (spawning installers vs the current copy-to-terminal model). Tauri IPC/CSP implications.
  6. Tier gate — confirm MCP Hub is PRO+ (matches marketplace install gate) vs power-user-density-tier (UserTier) only. Three tier vocabularies to reconcile (frontend inventory §f).