MCP server state
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.
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
- *lways-visible status*— todo MCP server attached é representado por um chip persistente em algum surface (header, sidebar, settings panel).
- *elf-healing visual*— error state NUNCA persiste após reconnect succeed; transitions são automáticas.
- *apabilities transparency*— user vê quais toolsresourcesprompts o server expõe.
- *anual 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 |
|---|---|---|---|
| *onnected* | success (green) |
Server reachable, capabilities loaded | Successful initialize + at least one tools/list response |
| *onnecting* | warning (yellow/amber) |
Initial handshake OR reconnect in progress | Right after attach OR after error + within retry backoff |
| *rror* | error (red) |
Connection failed; retries exhausted OR fatal protocol error | Timeout / TLS error / protocol mismatch / server rejected |
| *isabled* | text-muted (gray) |
User |
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)*ritical 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: 45msError state inclui error message + last attempt timestamp + next retry ETA.
R3 — Drawer (expanded view)
Tap chip → opens drawer (sidepanel desktop, bottomsheet 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 `mcppermission-prompt.kmd` R2). - Resources: URI + MIME + size.
- Prompts: name + description + argument schema preview.
R3.2 — Settings
- *uto-reconnect*(default ON): exponential backoff (1s, 2s, 4s, 8s, 16s, then 30s ceiling).
- *eep-alive ping*(default 30s): interval para
pingrequest; failed ping triggers reconnect. - *er
tool permission overrides* shortcut to permission store (crosslinkmcp-permission-prompt.kmdR4).
R3.3 — Actions
- *estart* graceful disconnect + new attach. UI mostra connecting → connected (T1).
- *isable* stop reconnect attempts; state → disabled. Persists across sessions.
- *emove* full uninstall from registry; permission entries auto
purged (crosslinkpolicies/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 * 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 peruser (crosslink `mcppermission-prompt.kmd` R4).
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 (lightdark) per `themescolor-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
- *1*Initial attach: state shows connecting → connected; capability count populates from toolsresourcesprompts list.
- *2*Network outage: connected → error (timeout); chip color red; tooltip mostra error message.
- *3*Auto-retry: error → connecting → connected; *4.1 regression: error message + red dot CLEAR immediately upon connected*
- *4*Manual restart: tap restart → connecting → connected.
- *5*Disable: tap disable → state disabled; reconnect loop stops; capability lists hidden but cached.
- *6*Re-enable: tap enable → connecting → connected; lists reload from server (may have changed).
- *7*Auto-disable after 5 errors: simulate 5 timeouts within 10min → state → disabled (not error); counter resets after successful connect.
- *8*Multi-tenant: user A attach server X → user B no workspace 2 NÃO vê server X.
- *1*Stale state regression (Cursor #149363): connection succeeds while old error visible → assert visual updates within 1 frame.
- *2*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