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).
3.1 KiB
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 wrapsqueryRowsoutput with{"__renderer": "...", "data": [...]}for 12 tools. - Registry (
web/src/lib/tool-renderers.ts): match/dispatch system that maps tool names +__rendererhints 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 collapsedToolCallGroupalongside unmatched tools. - ToolCallGroup (
web/src/lib/components/ToolCallGroup.svelte): acceptsunmatchedprop, 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.tsweb/src/lib/renderers/index.tsweb/src/lib/renderers/EntityCard.svelte+entity-card.tsweb/src/lib/renderers/HealthSummary.svelte+health-summary.tsweb/src/lib/renderers/LXCList.svelte+lxc-list.tsweb/src/lib/renderers/EntityTable.svelte+entity-table.tsweb/src/lib/renderers/KnowledgeResults.svelte+knowledge-results.tsweb/src/lib/renderers/BlastRadius.svelte+blast-radius.tsweb/src/lib/renderers/ChangeLog.svelte+change-log.tsweb/src/lib/renderers/FleetSnapshot.svelte+fleet-snapshot.tsweb/src/lib/renderers/MetricChart.svelte+metric-chart.ts
Modified (5):
internal/mcp/server.go—annotateJSONResult()+ 12 tool annotationsinternal/mcp/server_test.go— 3 tests forannotateJSONResultweb/src/lib/components/ToolCallGroup.svelte—unmatched/bodyToolsweb/src/pages/Chat.svelte— inline dispatch + 5-card limitweb/src/main.ts— deferred renderer import