MCP tool invocation card
ai-ui specs/ai-ui/mcp-tool-invocation.kmd
Visual indicator + arguments preview + result rendering for every MCP tool call executed by a Koder client. Implements the SHOULD-level UI requirement from MCP spec 2025-11-25 (Tools section): "Applications SHOULD provide UI that makes clear which tools are being exposed to the AI model, insert clear visual indicators when tools are invoked, and present confirmation prompts."
When this spec applies
Primary triggers
- Consume MCP tools in any Koder surface
All triggers
- Render any MCP tool invocation in a Koder client UI
- Display tools/call request + response in chat surface
- Implement MCP server consumer in any product (Kortex, Bot, Talk, Kruze, Kode)
Specification body
Spec — MCP tool invocation card
MCP normative source: https://modelcontextprotocol.io/specification/2025-11-25/server/tools. Companion:
mcp-permission-prompt.kmd(consent gate antes do invoke),mcp-server-state.kmd(status do server que hospeda o tool).
Princípios
- Visibility-first — todo tool call MUST aparecer na UI imediatamente, mesmo durante streaming.
- No silent invocations — tool calls não documentados visualmente violam spec MCP.
- Args + result side-by-side — usuário consegue auditar input e output.
- Truncation com escape — outputs grandes truncados mas copy-to-clipboard sempre full.
R1 — Anatomia
┌─────────────────────────────────────────────┐
│ [🔧] tool_name [status_badge] │ ← header (clickable)
│ Tool from <server_origin_chip> │
├─────────────────────────────────────────────┤
│ ▼ Arguments │ ← body (collapsed by default)
│ { "query": "...", "limit": 10 } │
├─────────────────────────────────────────────┤
│ ▼ Result │ ← footer (auto-expand on success)
│ [content rendered per R2] │
└─────────────────────────────────────────────┘
| Slot | Conteúdo | Notas |
|---|---|---|
| Tool icon | tools/list[].icon se MCP server fornecer; fallback ícone genérico 🔧 | Resolution: 24×24dp |
| Tool name | tools/list[].name (snake_case do MCP) | Display em title-small-emphasized (cross-link themes/typography.kmd R9) |
| Server origin chip | Slug do server (de mcp-registry) | Trusted state: chip color do mcp-server-state.kmd R1 |
| Status badge | per R3 | Always visible |
| Arguments | JSON pretty-printed | Toggle: raw JSON ↔ form view (R5) |
| Result | per R2 | Auto-expand on success; collapsed on error |
R2 — Result content types
MCP tools/call retorna content[] array. Cada item tem type ∈
{text, image, resource_link, embedded_resource}. Card MUST
renderizar todos os 4 tipos:
type | Renderer | Notes |
|---|---|---|
text | Markdown render (cross-link code-block.kmd para fenced) | Default fallback |
image | KoderImage widget; mime-detection via header | Max width = card width; tap-to-fullscreen |
resource_link | Chip clickable → opens em new surface ou inline preview | Resolve URI via mcp-registry resource fetcher |
embedded_resource | Inline render of embedded MIME (text/markdown/code/image) | Same renderer pipeline as standalone content |
Multiple content items: render sequencialmente em ordem, separados por divider de 1dp.
R3 — Status states
Badge no header per estado do tools/call:
| Estado | Badge | Color (per themes/color-roles.kmd) |
|---|---|---|
| Pending | "⏳ Waiting" | surface-variant |
| Running | "⚙ Running…" + breathing dot | accent |
| Success | "✓ Done" | success |
| Error | "✗ Error" + retry button | error |
| Cancelled | "⊘ Cancelled" | text-muted |
Transições: pending → running → (success | error | cancelled). Card NUNCA pula direto de pending pra success (sempre passa por running).
R4 — Truncation
Outputs grandes (>2000 chars text, >500KB image):
- Text: collapse com "Show more" link após 30 linhas; full text mantido em DOM mas visualmente clipped.
- Image: thumbnail por default; tap-to-fullscreen overlay.
- Tool call args > 100 lines: collapse com "Expand args".
Copy-to-clipboard SEMPRE full, independente do estado visual.
R5 — Schema-driven form view (opcional)
Quando MCP tools/list[].inputSchema está presente (JSON Schema), card
SHOULD oferecer toggle "Raw JSON" ↔ "Form view":
- Form view renderiza inputs schema-aware (string → text field, number → spinner, enum → segmented control, boolean → switch).
- Required fields marcados com asterisco; help text de
description. - Form é read-only (já invocado); modo edit-and-retry vai em ticket separado #re-invocation (futuro).
R6 — Surface bindings
| Surface | API |
|---|---|
| Flutter | KoderMCPToolCard em engines/sdk/koder_kit/lib/src/ai/mcp_tool_card.dart |
| Web | <koder-mcp-tool-card> web component em engines/sdk/koder_web_kit |
| Compose Android | KoderMCPToolCard em engines/sdk/koder-design-compose (quando shipar) |
| SwiftUI iOS | KoderMCPToolCard em engines/sdk/koder-design-swift (quando shipar) |
| CLI / TUI | Texto: header em 1 linha + args + result; bubbletea expandable em TUI |
Surfaces MUST exportar API consistente (constructor + state callbacks), diferindo apenas em props específicos do framework.
R7 — Multi-tenant + storage
Tool invocation persistida pra conversation history (cross-link #115)
respeita policies/multi-tenant-by-default.kmd:
- Storage key:
(koder_user_id, workspace_id, conversation_id, message_id, tool_call_id). - Cross-tenant lookup retorna 404 (não 403, per multi-tenancy contract).
- Audit log da invocation persistido em
services/foundation/audit/com user + workspace + tool + args hash.
R8 — Acessibilidade
- Card é semanticamente
<article role="region" aria-label="Tool invocation: <name>">. - Status badge anuncia mudança via aria-live="polite".
- Args e result são
<details><summary>em Web; equivalente expansível em Flutter/Compose. - Reduced-motion: skip breathing dot animation; status muda direto.
- Screen reader: anuncia tool name + server origin + status; args só ao expandir.
R9 — Per-preset variation
| Preset | Card style |
|---|---|
material3 / material_expressive | Defaults; shape-corner-medium |
material2 | Filled card, shape-corner-small |
terminal_classic | Bracket-style [tool_name], monospace, no shadow |
brutalist | Sharp corners, 2px border, no gradient |
cyberpunk_neon | Glow outline em accent color, gradient backdrop |
glassmorphism | Backdrop blur + 20% surface tint |
minimalist_mono | Mono fonts, single border, no fill |
T1 — Test contract
Toda surface implementadora MUST passar:
- T1 Mount: render card com estado pending + tool name visível + args collapsed.
- T2 Expand args: tap header → args visible; tap novamente → collapse.
- T3 Status transition: pending → running → success → render result; assert badge atualiza.
- T4 Error + retry: status error → retry button visible → tap → status volta a running.
- T5 Each content type: text, image, resource_link, embedded_resource (4 sub-tests).
- T6 Truncation: text > 2000 chars → "Show more" visible; expand → full text.
- T7 Copy-to-clipboard: copy ação copia full args (não truncado).
- T8 A11y: screen reader announces status change; reduced-motion skips breathing dot.
Cross-link
- Companion:
mcp-permission-prompt.kmd,mcp-server-state.kmd - Consumer pattern:
chat-message-bubble.kmdhospeda tool cards inline - Code rendering:
code-block.kmd(referenciado em R2 text type) - Tokens visuais:
themes/color-roles.kmd,themes/typography.kmdR9 - Backend:
services/ai/mcp/,services/ai/mcp-registry/ - MCP normative: https://modelcontextprotocol.io/specification/2025-11-25/server/tools
References
meta/docs/stack/specs/ai-ui/README.kmdmeta/docs/stack/specs/ai-ui/mcp-permission-prompt.kmdmeta/docs/stack/specs/ai-ui/mcp-server-state.kmdmeta/docs/stack/specs/ai-ui/chat-message-bubble.kmdmeta/docs/stack/policies/multi-tenant-by-default.kmd