MCP server state
ai-ui specs/ai-ui/mcp-server-state.kmd
Connection state visibility for every MCP server attached to a Koder client. Status chip (color-coded) + capabilities drawer + auto-reconnect + error-clear-on-success. Required for any debuggable MCP-aware product.
Quando esta spec se aplica
Triggers primários
- Display MCP server connection state in any UI
Todos os triggers
- Attach/detach MCP server in any Koder client
- Debug MCP connection issues in a product
- Implement MCP-aware Settings panel
Corpo da especificação
Spec — MCP server state
Histórico: Cursor #149363 e Claude Code #36308 — error state visualmente persistente após reconnect. Lição: state visual MUST clear automaticamente quando connection succeeds.
Princípios
- Always-visible status — todo MCP server attached é representado por um chip persistente em algum surface (header, sidebar, settings panel).
- Self-healing visual — error state NUNCA persiste após reconnect succeed; transitions são automáticas.
- Capabilities transparency — user vê quais tools/resources/prompts o server expõe.
- Manual override — restart, disable, remove sempre disponível.
R1 — Status chip
Compact representation: dot (8×8dp) + server name + count badge.
🟢 my-server [12 tools]
🟡 my-server [reconnecting…]
🔴 my-server [error: timeout]
⚪ my-server [disabled]
4 states (per color-roles.kmd):
| State | Dot color | Meaning | Triggers |
|---|---|---|---|
| connected | success (green) | Server reachable, capabilities loaded | Successful initialize + at least one tools/list response |
| connecting | warning (yellow/amber) | Initial handshake OR reconnect in progress | Right after attach OR after error + within retry backoff |
| error | error (red) | Connection failed; retries exhausted OR fatal protocol error | Timeout / TLS error / protocol mismatch / server rejected |
| disabled | text-muted (gray) | User-disabled OR auto-disabled (too many failures) | Manual toggle OR 5 consecutive errors |
State transitions:
[new attach] → connecting → connected (T1 success)
↘ error (T1 fail) → connecting (auto-retry) → connected (T2 success — CLEAR error!)
↘ error (T2 fail) ...
[manual disable] → disabled
[manual enable] → connecting
[5 consec errors] → disabled (auto)
Critical regression contract (R4.1): error → connecting → connected transition MUST clear the visual error state immediately upon connected. NO stale red dot. NO "still showing error after success".
R2 — Tooltip (hover / long-press)
Mouse hover (desktop) ou long-press (mobile) reveal:
my-server
status: connected
capabilities: 12 tools, 3 resources, 5 prompts
last sync: 2s ago
ping: 45ms
Error state inclui error message + last attempt timestamp + next retry ETA.
R3 — Drawer (expanded view)
Tap chip → opens drawer (side-panel desktop, bottom-sheet mobile):
┌───────────────────────────────────────────┐
│ my-server [status_chip] │
│ https://my-server.example.com │
├───────────────────────────────────────────┤
│ Capabilities │
│ ▼ Tools (12) │
│ • search · readOnly · low risk │
│ • fetch_url · readOnly · low risk │
│ • write_file · destructive · high risk │
│ ... │
│ ▼ Resources (3) │
│ ... │
│ ▼ Prompts (5) │
│ ... │
├───────────────────────────────────────────┤
│ Settings │
│ [Auto-reconnect: ON ] │
│ [Keep-alive ping: 30s] │
├───────────────────────────────────────────┤
│ [ Restart ] [ Disable ] [ Remove ] │
└───────────────────────────────────────────┘
R3.1 — Capability lists
- Tools: name + annotation tags (readOnly / destructive / idempotent) + risk tier (cross-link
mcp-permission-prompt.kmdR2). - Resources: URI + MIME + size.
- Prompts: name + description + argument schema preview.
R3.2 — Settings
- Auto-reconnect (default ON): exponential backoff (1s, 2s, 4s, 8s, 16s, then 30s ceiling).
- Keep-alive ping (default 30s): interval para
pingrequest; failed ping triggers reconnect. - Per-tool permission overrides: shortcut to permission store (cross-link
mcp-permission-prompt.kmdR4).
R3.3 — Actions
- Restart: graceful disconnect + new attach. UI mostra connecting → connected (T1).
- Disable: stop reconnect attempts; state → disabled. Persists across sessions.
- Remove: full uninstall from registry; permission entries auto-purged (cross-link
policies/identity-data-retention.kmdR5).
R4 — Error state lifecycle (lição Cursor + Claude Code bugs)
R4.1 — Auto-clear contract (NORMATIVE)
Quando state transitions de connecting → connected:
- Visual dot MUST update synchronously (next render frame).
- Error message MUST be removed from tooltip and drawer.
- Last error timestamp MUST be cleared.
- Capability lists MUST be reloaded from
tools/list+resources/list+prompts/list(server may have changed). - NO race condition where dot stays red while internal state is green.
R4.2 — Auto-disable threshold
After 5 consecutive connection errors within 10 minutes: state → disabled (not error). User MUST manually re-enable. Prevents zombie retry loops draining battery / quota.
Counter resets after one connected state.
R4.3 — Error categorization
| Error category | Recoverable? | Display |
|---|---|---|
| Network timeout | Yes (auto-retry) | "Connection timeout · retrying in 8s" |
| TLS/certificate | No | "TLS error · check server config" |
| Protocol mismatch | No | "Protocol version unsupported" |
| Server rejected (401/403) | No (user action needed) | "Authentication failed · check credentials" |
| Server unreachable (DNS/refused) | Yes (auto-retry) | "Server unreachable · retrying" |
| Rate limit (429) | Yes (delayed retry) | "Rate limited · retrying in 30s" |
R5 — Multi-tenant scoping
Server registry e state per (koder_user_id, workspace_id):
- User A em workspace 1 attach
my-server→ visible só pra user A em workspace 1. - Cross-workspace switch: state chip atualiza automaticamente.
- Shared workspace (multi-user): server visible para todos members; state global; permission decisions per-user (cross-link
mcp-permission-prompt.kmdR4).
Storage: kdb-kv table mcp_servers:<koder_user_id>:<workspace_id>:<server_id>.
R6 — Surface bindings
| Surface | API |
|---|---|
| Flutter | KoderMCPServerChip (compact) + KoderMCPServerDrawer (expanded) em koder_kit/lib/src/ai/ |
| Web | <koder-mcp-server-chip> + <koder-mcp-server-drawer> |
| Compose Android | KoderMCPServerChip em koder-design-compose (futuro) |
| SwiftUI iOS | idem em koder-design-swift (futuro) |
| CLI / TUI | Status line: MCP: 🟢 server1 🟡 server2 🔴 server3; koder mcp status lists detalhe |
R7 — Acessibilidade
- Chip é
role="status" aria-live="polite"; state change announced. - Dot tem
aria-label="connected"etc. - Drawer é
role="dialog"quando aberto. - Keyboard: Tab to chip, Enter to expand drawer, ESC to close, arrows nav lista.
- Reduced-motion: state transitions sem animação.
- Color contrast: dot color MUST atender AAA contrast em ambos themes (light/dark) per
themes/color-roles.kmd.
R8 — i18n
| Key | en-US | pt-BR |
|---|---|---|
mcp.server.state.connected | "Connected" | "Conectado" |
mcp.server.state.connecting | "Connecting…" | "Conectando…" |
mcp.server.state.error | "Error" | "Erro" |
mcp.server.state.disabled | "Disabled" | "Desativado" |
mcp.server.action.restart | "Restart" | "Reiniciar" |
mcp.server.action.disable | "Disable" | "Desativar" |
mcp.server.action.remove | "Remove" | "Remover" |
T-suite
- T1 Initial attach: state shows connecting → connected; capability count populates from tools/resources/prompts list.
- T2 Network outage: connected → error (timeout); chip color red; tooltip mostra error message.
- T3 Auto-retry: error → connecting → connected; R4.1 regression: error message + red dot CLEAR immediately upon connected.
- T4 Manual restart: tap restart → connecting → connected.
- T5 Disable: tap disable → state disabled; reconnect loop stops; capability lists hidden but cached.
- T6 Re-enable: tap enable → connecting → connected; lists reload from server (may have changed).
- T7 Auto-disable after 5 errors: simulate 5 timeouts within 10min → state → disabled (not error); counter resets after successful connect.
- T8 Multi-tenant: user A attach server X → user B no workspace 2 NÃO vê server X.
- N1 Stale state regression (Cursor #149363): connection succeeds while old error visible → assert visual updates within 1 frame.
- N2 TLS error não dispara auto-retry (R4.3); display permanent error message.
Cross-link
- Companion:
mcp-tool-invocation.kmd(consumer),mcp-permission-prompt.kmd(trust state) - Policies:
multi-tenant-by-default.kmd(R5 storage scoping) - Color tokens:
themes/color-roles.kmd - Backend:
services/ai/mcp/,services/ai/mcp-registry/ - Historical incidents: Cursor #149363, Claude Code #36308
- MCP normative: https://modelcontextprotocol.io/specification/2025-11-25
Referências
meta/docs/stack/specs/ai-ui/mcp-tool-invocation.kmdmeta/docs/stack/specs/ai-ui/mcp-permission-prompt.kmdmeta/docs/stack/policies/multi-tenant-by-default.kmd