Files
oikos/plans/done/2026-07-13-mcp-tool-apps-custom-chat-renderers.md
dtoro 8b50753746
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
feat(chat): MCP tool apps — custom inline renderers for 12 tools
12 of 33 MCP tools now render as rich inline cards instead of raw JSON:
EntityCard, HealthSummary, LXCList, EntityTable, KnowledgeResults,
BlastRadius, ChangeLog, FleetSnapshot, MetricChart.

Architecture:
- Server: annotateJSONResult() wraps queryRows with __renderer hints
- Registry: match/dispatch system maps tool names to Svelte components
- Chat: inline dispatch with 5-card limit, overflow to collapsed group
- ToolCallGroup: unmatched prop, hides when all matched, ARIA labels

Tests: 3 new Go tests for annotateJSONResult (wrap, no-op, multi-row).
2026-07-13 22:46:56 +02:00

3.1 KiB

2026-07-13 — MCP tool apps: custom in-chat renderers

Status: Done — implemented 2026-07-13.

What was built

12 of 33 MCP tools now render as rich inline cards in the chat instead of raw JSON inside a collapsed component. The remaining 21 tools stay collapsed.

Architecture

  • Server (internal/mcp/server.go): annotateJSONResult() function wraps queryRows output with {"__renderer": "...", "data": [...]} for 12 tools.
  • Registry (web/src/lib/tool-renderers.ts): match/dispatch system that maps tool names + __renderer hints to Svelte components.
  • Renderer components (web/src/lib/renderers/): 9 purpose-built cards, each handling loading/spinner, error, and success states with proper ARIA labels.
  • Chat dispatch (web/src/pages/Chat.svelte): matched tools render inline before the markdown text, with a 5-card limit to prevent chat spam. Overflow goes to the collapsed ToolCallGroup alongside unmatched tools.
  • ToolCallGroup (web/src/lib/components/ToolCallGroup.svelte): accepts unmatched prop, shows "N tools · M cards shown" when some render inline, hides entirely when all matched.

Renderers

Component Tools matched Visual
EntityCard get_entity, whoami, explain Slug, type badge, health dot, key attrs
HealthSummary get_health_summary Stacked health bar (healthy/degraded/down)
LXCList list_lxcs Compact table: name, ID, IP, health
EntityTable list_entities Auto-column table from query results
KnowledgeResults search_knowledge, get_entity_knowledge Title, snippet, source, slug
BlastRadius get_blast_radius Entities grouped by hop distance
ChangeLog get_change_history, get_agent_activity Timeline with status dots
FleetSnapshot get_state_snapshot Health + type counts in a grid
MetricChart query_metrics Bucketed time/avg/min/max table

Tests

internal/mcp/server_test.go: 3 new tests for annotateJSONResult — wraps valid JSON arrays, no-op on empty/non-JSON/empty-text content, preserves multi-row arrays.

Files changed

New (18):

  • web/src/lib/tool-renderers.ts
  • web/src/lib/renderers/index.ts
  • web/src/lib/renderers/EntityCard.svelte + entity-card.ts
  • web/src/lib/renderers/HealthSummary.svelte + health-summary.ts
  • web/src/lib/renderers/LXCList.svelte + lxc-list.ts
  • web/src/lib/renderers/EntityTable.svelte + entity-table.ts
  • web/src/lib/renderers/KnowledgeResults.svelte + knowledge-results.ts
  • web/src/lib/renderers/BlastRadius.svelte + blast-radius.ts
  • web/src/lib/renderers/ChangeLog.svelte + change-log.ts
  • web/src/lib/renderers/FleetSnapshot.svelte + fleet-snapshot.ts
  • web/src/lib/renderers/MetricChart.svelte + metric-chart.ts

Modified (5):

  • internal/mcp/server.goannotateJSONResult() + 12 tool annotations
  • internal/mcp/server_test.go — 3 tests for annotateJSONResult
  • web/src/lib/components/ToolCallGroup.svelteunmatched/bodyTools
  • web/src/pages/Chat.svelte — inline dispatch + 5-card limit
  • web/src/main.ts — deferred renderer import