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).
This commit is contained in:
66
plans/done/2026-07-13-mcp-tool-apps-custom-chat-renderers.md
Normal file
66
plans/done/2026-07-13-mcp-tool-apps-custom-chat-renderers.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# 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.go` — `annotateJSONResult()` + 12 tool annotations
|
||||
- `internal/mcp/server_test.go` — 3 tests for `annotateJSONResult`
|
||||
- `web/src/lib/components/ToolCallGroup.svelte` — `unmatched`/`bodyTools`
|
||||
- `web/src/pages/Chat.svelte` — inline dispatch + 5-card limit
|
||||
- `web/src/main.ts` — deferred renderer import
|
||||
@@ -14,7 +14,6 @@ went sideways, open an investigation.
|
||||
| 2026-07-08 | [Liveness, drift, and UX cohesion](2026-07-08-liveness-drift-and-ux-cohesion.md) | In Progress — Phase 5 deferred |
|
||||
| 2026-07-10 | [General gated execution: unlimited actions, gated by risk](2026-07-10-general-gated-execution.md) | In Progress — enum retirement + auto-act revival still open |
|
||||
| 2026-07-11 | [Nomos agent code review: gaps and improvement plan](2026-07-11-nomos-agent-code-review.md) | In Progress — only C1 (unauthenticated nomos gateway) still open, deferred |
|
||||
| 2026-07-11 | [Nomos agent code review: gaps and improvement plan](2026-07-11-nomos-agent-code-review.md) | In Progress — only C1 (unauthenticated nomos gateway) still open, deferred |
|
||||
|
||||
## Done
|
||||
|
||||
@@ -45,6 +44,7 @@ See [`done/`](done/) for executed plans:
|
||||
| 2026-07-11 | [UI review: information architecture, usability, and best practices](done/2026-07-11-ui-review-ia-usability.md) |
|
||||
| 2026-07-11 | [Task completion safety net: every live task is stuck "Running"](done/2026-07-11-task-completion-safety-net.md) |
|
||||
| 2026-07-12 | [Wails desktop application](done/2026-07-12-wails-desktop-app.md) |
|
||||
| 2026-07-13 | [MCP tool apps: custom in-chat renderers](done/2026-07-13-mcp-tool-apps-custom-chat-renderers.md) |
|
||||
|
||||
## Conventions
|
||||
|
||||
|
||||
Reference in New Issue
Block a user