feat(mcp): first-class MCP tab — catalog, GUI auth/probe/logs, per-tool gating

A Cursor-style MCP manager inside Capabilities, plus the backend it needs.

- Server list with brand/favicon avatars + live status dot and a capability
  summary (N tools, M prompts, K resources); Servers | Catalog views.
- Catalog: one-click install of Nous-approved servers with required-env prompts.
- GUI OAuth: Authenticate opens the system browser from the TTY-less backend and
  verifies a token actually lands; header/API-key servers are never pushed down
  OAuth; a dirty mcp.json can't drop a freshly-persisted auth field.
- Full-width mcp.json editor (ecosystem document format) + pinned stdio/agent
  LogTail; probes cached 5m and keyed by (profile, config) so revisiting never
  respawns the fleet or shows a stale probe.
- Whole-map persistence (PUT /api/mcp/servers) so deletes/toggles actually stick
  (the generic /api/config deep-merge could not remove keys).
- perf: MCP probe/auth no longer hold the global skills lock, so a slow stdio
  spawn can't stall every other request into a 15s timeout.
- per-tool include/exclude gating (lib/mcp-tool-filter) mirroring the CLI loader.
This commit is contained in:
Brooklyn Nicholson 2026-07-03 05:08:28 -05:00
parent 7e6d60aadc
commit 16aa09aca5
12 changed files with 1959 additions and 36 deletions

View file

@ -490,6 +490,27 @@ class TestEnvVarInterpolation:
assert _interpolate_env_vars(True) is True
assert _interpolate_env_vars(None) is None
def test_interpolate_cursor_env_prefix(self, monkeypatch):
"""Cursor-style ${env:VAR} resolves the same secret as ${VAR}."""
monkeypatch.setenv("MY_KEY", "secret123")
from tools.mcp_tool import _interpolate_env_vars
assert _interpolate_env_vars("Bearer ${env:MY_KEY}") == "Bearer secret123"
def test_interpolate_cursor_env_prefix_missing(self, monkeypatch):
"""An unset ${env:VAR} keeps its literal placeholder, like ${VAR}."""
monkeypatch.delenv("MISSING_VAR", raising=False)
from tools.mcp_tool import _interpolate_env_vars
assert _interpolate_env_vars("Bearer ${env:MISSING_VAR}") == "Bearer ${env:MISSING_VAR}"
def test_env_ref_name_strips_prefix(self):
from tools.mcp_tool import _env_ref_name
assert _env_ref_name("env:API_KEY") == "API_KEY"
assert _env_ref_name("API_KEY") == "API_KEY"
assert _env_ref_name(" env:API_KEY ") == "API_KEY"
# ---------------------------------------------------------------------------
# Tests: probe-path env resolution (#37792)