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

feat(kora): KR-MCP-CLIENTS-HEALTH-DISPLAY — render last_check_at + last_error#117

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

feat(kora): KR-MCP-CLIENTS-HEALTH-DISPLAY — render last_check_at + last_error#117
rafe-walker merged 1 commit into
feature/phase2-upgradesfrom
feat/kora-KR-MCP-CLIENTS-HEALTH-DISPLAY

Conversation

@rafe-walker

Copy link
Copy Markdown
Owner

Summary

CC#1's KR-MCP-CONSUMPTION ST2 (PR #113, 94e572d6) added last_check_at + last_error to the /api/mcp/clients/list payload and the MCPClient TS interface. The cockpit's MCPClientsPanel.tsx didn't yet render either field — this PR closes that loop.

Bucket: rafe-walker/kora-docs17_cc_bucket_prompts/KR-MCP-CLIENTS-HEALTH-DISPLAY.md.

What's in here

  • Per-row collapsed view:
    • Clock icon + "checked Xs/Xm/Xh/Xd ago" next to the status pill (italic + muted "never checked" when null).
    • TimerOff "stale" chip when last_check_at is older than 10 min (2x the default 5-min KORA_MCP_HEALTH_CHECK_INTERVAL_SEC cadence — signals "heartbeat scheduler stopped firing" rather than tripping on every normal probe).
    • Red error line under the row when last_error is non-null (truncated to 80 chars, full text in title= tooltip).
  • Per-row expanded view: new "Last Check" section with absolute timestamp + relative phrase + stale chip + full last_error in a <pre> for multi-line readability.
  • Aggregate strip: errors count now unions status=error|unhealthy with last_error !== null (catches the connected-but-flapping case). New stale (>10m) count for endpoints whose heartbeat is silent.
  • Helpers factored to web/src/lib/mcpHealth.ts so the threshold (STALE_CHECK_THRESHOLD_MS) + truncation cap (ERROR_TRUNCATE_LEN) live as named exports the source-pin tests can reference symbolically.

3-layer security contract

This iteration's specific risk: last_error is operator-readable text sourced from an MCP catalog probe (subprocess stderr / HTTP body) — i.e., untrusted input from a misbehaving MCP server.

  1. Plain-text rendering via React's default child escaping — <script> tags in last_error render as text, never execute.
  2. NO dangerouslySetInnerHTML anywhere in the panel. Backend test pins this via a comment-stripped grep so the warning comment in the source doesn't self-trigger, AND so a future "rich error rendering" edit gets caught.
  3. TS type for last_error is plain string | null — no pre-parsed _html companion field exists on MCPClient.

Test plan

  • tests/kora_cli/test_mcp_clients_health_display.py — 16 source-pin tests covering: file existence, dangerouslySetInnerHTML ban (with comment-stripping), last_error as JSX-child text node, stale threshold = 10 min, truncation cap = 80, helper exports + panel-import chain (so source-pins stay load-bearing), aggregate errors filter includes last_error, cron-regression sanity.
  • pnpm tsc --noEmit clean.
  • pnpm build clean.
  • Manual smoke: load /mcp-clients, confirm relative timestamps, simulate stale by mocking the snapshot, confirm last_error renders as text not HTML.

Notes

  • Pre-existing failures in test_web_server_mcp_clients.py (13 failed + 5 errors) confirmed present on bare feature/phase2-upgradesNOT introduced by this PR. Likely fallout from PR feat(kora): KR-MCP-CONSUMPTION ST2 — active health-check + payload extension #113's status-derivation changes; flagging for a separate cleanup bucket.
  • No FE test framework in repo (no vitest / jest / jsdom in web/package.json). Per the established CC#2 pattern (HB-PANEL, MCP-3, WEBHOOK-EVENTS, AGENT-ACTIVITY), FE invariants pin via Python source-grep tests + tsc/vite for type/compile correctness. Standing up a real component test runner is out of scope at <150 LOC.

🤖 Generated with Claude Code

…st_error

CC#1's KR-MCP-CONSUMPTION ST2 (PR #113 94e572d) added last_check_at
+ last_error to the /api/mcp/clients/list payload and the MCPClient
TS interface. The cockpit's MCPClientsPanel.tsx didn't yet render
either field; this PR closes that loop.

Per-row surface (collapsed):
  * "checked Xs/Xm/Xh/Xd ago" relative-time label next to the
    status pill (italic + muted "never checked" when null)
  * Stale chip when last_check_at is older than 10 min — that's
    2x the default 5-min KORA_MCP_HEALTH_CHECK_INTERVAL_SEC cadence,
    so the chip signals "heartbeat scheduler stopped firing" rather
    than tripping on every normal probe cycle
  * Red error line under the row (truncated to 80 chars, full text
    in the tooltip) when last_error is non-null

Per-row surface (expanded detail view):
  * New "Last Check" section: absolute last_check_at timestamp +
    relative phrase + stale chip
  * Full last_error rendered in a <pre> for multi-line readability

Aggregate strip:
  * "errors" count now unions status=error/unhealthy with
    last_error!==null (catches the connected-but-flapping case)
  * New "stale (>10m)" count for endpoints whose heartbeat is silent

3-layer security contract (continuing the panel-by-panel pattern,
this iteration's specific risk = last_error being untrusted text
from a misbehaving MCP probe):
  1. Plain-text rendering via React's default child escaping —
     <script> tags in last_error render as text, never execute.
  2. NO dangerouslySetInnerHTML anywhere in the panel. Backend
     test pins this via a comment-stripped grep that catches a
     future edit switching to "rich error rendering".
  3. TS type for last_error is plain `string | null` — no
     pre-parsed `_html` companion field exists on MCPClient.

Helpers factored to web/src/lib/mcpHealth.ts so the stale threshold
(10 * 60 * 1000 ms) + error truncation cap (80 chars) live as named
constants the source-pin tests can reference symbolically.

Tests:
  * tests/kora_cli/test_mcp_clients_health_display.py — 16 tests:
    source-file existence, dangerouslySetInnerHTML ban (with
    comment-stripping so the warning comment in the source doesn't
    self-trigger), last_error as JSX-child text node, stale
    threshold = 10min, truncation cap = 80, helper exports +
    panel-import-from-helpers chain (so source-pins stay
    load-bearing), aggregate errors filter includes last_error,
    cron-regression sanity.
  * tsc --noEmit + vite build both clean.
  * Pre-existing test_web_server_mcp_clients.py failures (13 + 5
    errors) confirmed present on bare feature/phase2-upgrades —
    NOT introduced by this PR; flagged for separate cleanup.

Spec note (no test framework): repo has no vitest/jest. Per the
established CC#2 pattern, FE invariants pin via Python source-grep
tests + tsc/vite for type/compile correctness. Adding a real
component test runner is out of scope at <150 LOC.

Refs: rafe-walker/kora-docs 17_cc_bucket_prompts/KR-MCP-CLIENTS-HEALTH-DISPLAY.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rafe-walker rafe-walker merged commit ec57d47 into feature/phase2-upgrades May 22, 2026
@rafe-walker rafe-walker deleted the feat/kora-KR-MCP-CLIENTS-HEALTH-DISPLAY branch May 22, 2026 16:11
rafe-walker added a commit that referenced this pull request May 22, 2026
…tigation (#125)

Two-part cleanup.

Part A (tsc drift): HeartbeatPanel.tsx + DashboardPage.tsx now handle all 4 fields KR-FEAT-HEARTBEAT ST2 (#118) added: unknown status arm (muted probe-pending pill + CircleDashed icon), nullable last_check_at (never checked — matching #117 convention), error field rendered as destructive <pre> in expanded view (plain text, no dangerouslySetInnerHTML), cache_warming Probes warming up… banner + dashboard headline-tone suppression. tsc -b clean.

Part B (mcp_clients tests): investigation only, STOPPED per spec rule. Root cause is NOT stale assertions — all 13 failures + 5 errors collapse on ModuleNotFoundError: No module named slowapi from the unconditional import chain web_server → listeners/__init__ → listeners/webhooks → slowapi. Verified by installing --extra web: 19/19 mcp_clients tests pass.

This is task NousResearch#265 (slowapi placement). Recommended 1-line fix: move slowapi to runtime deps. Will dispatch as standalone KR-SLOWAPI-DEP-FIX bucket.

3 files, +267/-14 panel + dashboard + 11 new source-pin tests. 260/260 admin-panel tests pass across 22 suites (with --extra web installed). tsc -b + vite build both clean.
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