Skip to content
This repository was archived by the owner on May 26, 2026. It is now read-only.

feat(kora): KR-MCP-3 — MCP-picker UI frontend shell (stub)#106

Merged
rafe-walker merged 1 commit into
feature/phase2-upgradesfrom
feat/kora-KR-MCP-3
May 22, 2026
Merged

feat(kora): KR-MCP-3 — MCP-picker UI frontend shell (stub)#106
rafe-walker merged 1 commit into
feature/phase2-upgradesfrom
feat/kora-KR-MCP-3

Conversation

@rafe-walker

Copy link
Copy Markdown
Owner

Summary

Operator-facing view of EXTERNAL MCPs Kora consumes (Kora-as-MCP-client). Distinct concept from the existing `/mcp` surface, which is Kora-as-MCP-server admin (per-tool gating for tools Kora EXPOSES).

Surface Existing New (this PR)
Concept Kora-as-MCP-SERVER admin Kora-as-MCP-CLIENT picker
Endpoint `/api/mcp/servers` family `/api/mcp/clients/list`
Page `MCPPage` at `/mcp` `MCPClientsPanel` at `/mcp-clients`
Nav icon `Plug` `Cable` (visually distinct at a glance)

Branch base: `feature/phase2-upgrades` per bucket §0. Same stub-then-real pattern as KR-HB-PANEL; CC#1's KR-MCP-1 ST2 swaps the body using this PR's pinned payload shape. Bucket spec: `kora_docs/17_cc_bucket_prompts/KR-MCP-3_mcp_picker_ui_stub.md`.

SECURITY contract (bucket §5 hard-rule)

`auth_token_env` carries the env-var NAME (e.g. `KORA_MCP_GITHUB_TOKEN`); `auth_token_present` is a bool. Token VALUES never appear in the response shape, never render in the UI (no tooltips, no copy buttons, no dev-console). Tokens live in Doppler; operator rotates via `doppler secrets set `.

Two test guards catch any future drift:

  1. Regex-pin `auth_token_env` to UPPER_SNAKE env-var name shape (rejects values that look like actual tokens)
  2. Walk response keys; reject any token-VALUE-shaped name (`token` / `secret` / `access_token` / `api_key` / `password` / `bearer` / bare `auth_token` without `_env`/`_present` suffix)

Backend

`GET /api/mcp/clients/list` — stub returns 2 clients matching bucket §3 verbatim:

Both `configured_but_unconnected` + `auth_token_present: false`. `stub: true` flag drives FE banner.

Frontend

`pages/MCPClientsPanel.tsx`:

  • Aggregate strip: total + connected/configured/error counts + italic reminder "Token values never displayed — managed in Doppler"
  • Per-client row: status icon + name + transport badge (Terminal/Network icon for stdio/streamable_http) + status pill + tools_count when connected + KeyRound icon with green check / red x for auth presence (NEVER the value)
  • Expandable detail: endpoint (truncated, full on hover) + `auth_token_env` name + presence indicator + italic Doppler rotation hint + `allowed_tools_regex` + `tools_count`
  • STUB banner with KR-MCP-1 ST2 flip-in note
  • Defensive empty-state for `clients: []`

`lib/api.ts` — `MCPClientTransport` + `MCPClientStatus` type aliases + `MCPClient` + `MCPClientsListResponse` interfaces + `getMCPClients` client. Tokens explicitly absent from the TS shape (`auth_token_env: string` + `auth_token_present: boolean` — type-level guarantee).

`App.tsx` — `/mcp-clients` route + nav entry (Cable icon) between `/heartbeat` and `/boot-status`. Cable distinct from MCPPage's Plug so operator distinguishes "Kora as MCP CLIENT" (Cable) from "Kora as MCP SERVER" (Plug) at nav-glance.

`DashboardPage.tsx` — new MCP Clients card on row 2 (6th card now; grid bumped from `lg:grid-cols-5` to `lg:grid-cols-3` for cleaner 2-row visual symmetry on desktop). Card body aggregates total + connected/errors pills; headline tone goes destructive when any error/unhealthy client exists. `ALL_SOURCES` extended → footer stubbed count adds 1.

Test plan

  • `tests/kora_cli/test_web_server_mcp_clients.py` — 9/9 green, covers all 6 §4 scenarios + security/contract guards:
    • Both-clients-present pin (github + cloudflare)
    • Per-entry shape + transport/status enum validation
    • SECURITY: auth_token_env regex-pinned to env-var NAME shape
    • SECURITY: walk response keys; reject any token-VALUE-shaped key
    • Stub stays `configured_but_unconnected` (dashboard "0 connected" aggregate guard)
    • Stub stays `auth_token_present: false` (FE red-x indicator)
    • Cron-regression sanity
  • Full admin-panel suite: 168/168 green across 16 test files (was 159/159, +9 new)
  • `npx tsc -b` on `web/` — clean
  • `npx vite build` on `web/` — clean
  • FE component tests — skipped per absent infra (consistent with HB-PANEL feat(kora): KR-HB-PANEL — heartbeat dashboard frontend shell (stub) #103 + 12+ prior CC#2 panels; backend security guards + TS type system + manual smoke cover the contract)
  • Manual smoke: navigate to `/mcp-clients`, verify both rows show red-x auth indicators + configured_but_unconnected status; expand a row and verify endpoint truncates + Doppler rotation hint shows + tokens NEVER appear; verify dashboard `/` shows the new MCP Clients card on row 2 with headline number; click the card and confirm navigation to `/mcp-clients`

Flip-over plan

When CC#1's KR-MCP-1 ST2 ships the real catalog read, the endpoint body swaps to project from the live `kora_mcp/` pool. Payload shape is already pinned; FE renders real data automatically and the STUB banner auto-disappears once `stub:false` flows through.

🤖 Generated with Claude Code

Operator-facing view of EXTERNAL MCPs Kora consumes
(Kora-as-MCP-client). Distinct concept from KR-P2-C ST2's existing
/mcp surface, which is Kora-as-MCP-server admin (per-tool gating
for tools Kora EXPOSES). Routes + types disambiguated:

  /api/mcp/servers      → Kora-as-server admin    (existing, /mcp)
  /api/mcp/clients/list → Kora-as-client picker   (new, /mcp-clients)

Branch base: feature/phase2-upgrades per bucket §0. Same stub-then-
real pattern as KR-HB-PANEL; CC#1's KR-MCP-1 ST2 swaps the body
using the same payload shape this PR pins.

SECURITY contract (bucket §5 + ship-checklist hard-rule):
auth_token_env carries the env-var NAME (e.g. KORA_MCP_GITHUB_TOKEN);
auth_token_present is a bool. Token VALUES never appear in the
response shape, never render in the UI (no tooltips, no copy-to-
clipboard buttons, no dev-console). Tokens live in Doppler;
operator rotates via `doppler secrets set <env>`. Two test-side
guards catch any future drift:
  * regex-pin auth_token_env to UPPER_SNAKE env-var name shape
  * walk the response keys for token-VALUE-shaped names (token,
    secret, access_token, api_key, password, bearer, bare
    auth_token without _env/_present suffix) and assert zero
    matches

§2 K-DG verifications (grep'd vs bucket assumptions):
  * kora_mcp/ pool/registry/routing — confirmed (CC#1 ST1 1c495da)
  * No collision with existing /mcp route — new /mcp-clients route
    + MCPClientsPanel (not MCPPage) avoid namespace overlap
  * Cable icon (lucide-react) distinct from MCPPage's Plug — operator
    visually distinguishes the two MCP surfaces in nav

Backend (kora_cli/web_server.py):
  * GET /api/mcp/clients/list — stub returns 2 clients matching
    bucket §3 verbatim (github stdio, cloudflare streamable_http),
    both in configured_but_unconnected + auth_token_present:false.
    stub:true flag drives FE banner.

Frontend:
  * pages/MCPClientsPanel.tsx —
    - Aggregate summary strip: total + connected/configured/error
      counts + italic reminder "Token values never displayed —
      managed in Doppler"
    - Per-client row: status icon + name + transport badge (with
      Terminal vs Network icon for stdio vs streamable_http) +
      status pill + tools_count when connected + KeyRound icon
      with green check / red x for auth presence (NEVER the value)
    - Expandable detail: endpoint (truncated, full on hover) +
      auth_token_env name + presence indicator + italic Doppler
      rotation hint copy + allowed_tools_regex + tools_count
    - STUB banner with KR-MCP-1 ST2 flip-in note
    - Defensive empty-state for clients: []
  * lib/api.ts — MCPClientTransport + MCPClientStatus type aliases +
    MCPClient + MCPClientsListResponse interfaces + getMCPClients
    client. Tokens explicitly absent from the TS shape (only
    auth_token_env: string + auth_token_present: boolean).
  * App.tsx — /mcp-clients route + nav entry (Cable icon) between
    /heartbeat and /boot-status. Cable distinct from MCPPage's Plug
    so operator distinguishes "Kora as MCP CLIENT" (Cable) from
    "Kora as MCP SERVER" (Plug) at nav-glance.
  * DashboardPage.tsx — new MCP Clients card on row 2 (6th card now;
    grid bumped from lg:grid-cols-5 to lg:grid-cols-3 for cleaner
    2-row visual symmetry on desktop). Card body aggregates total +
    connected/errors pills; headline tone goes destructive when any
    error/unhealthy client exists. ALL_SOURCES extended → footer
    count adds 1 stubbed source.

Tests: tests/kora_cli/test_web_server_mcp_clients.py — 9 tests
covering all 6 §4 scenarios plus security/contract guards:
  * Both-clients-present pin (github + cloudflare)
  * Per-entry shape + transport/status enum validation
  * SECURITY: auth_token_env regex-pinned to env-var NAME shape
  * SECURITY: walk response keys; reject any token-VALUE-shaped key
    (token, secret, access_token, api_key, password, bearer, or
    bare auth_token without _env/_present suffix)
  * Stub stays configured_but_unconnected (dashboard "0 connected"
    aggregate depends on it)
  * Stub stays auth_token_present:false (FE red-x indicator)
  * Cron-regression sanity

168/168 across 16 admin-panel test files (was 159/159, +9 new).
tsc -b + vite build clean.

FE test framework still absent (same as KR-HB-PANEL); component
tests skipped per established pattern. Backend security guards +
type system + manual smoke cover the contract.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant