|
| 1 | +# EP-2046: Chat UI support for MCP UI widgets (MCP Apps) |
| 2 | + |
| 3 | +* Issue: [#2046](https://github.com/kagent-dev/kagent/issues/2046) |
| 4 | + |
| 5 | +## Background |
| 6 | + |
| 7 | +The Model Context Protocol is gaining an "Apps"/UI extension |
| 8 | +(`@modelcontextprotocol/ext-apps`, rendered via `@mcp-ui/client`) that lets an MCP |
| 9 | +server attach an interactive HTML/UI **resource** to a tool. When an agent calls |
| 10 | +such a tool, the client can render a live widget instead of (or in addition to) raw |
| 11 | +tool-call JSON. |
| 12 | + |
| 13 | +kagent's chat today renders tool calls as collapsible JSON. This EP makes the chat |
| 14 | +MCP-App–aware: when a tool call maps to an MCP app resource, the chat renders the |
| 15 | +app inline in a sandboxed frame and brokers messages between the app and the chat |
| 16 | +(send a message on the user's behalf, surface "visible" tool calls, proxy resource |
| 17 | +reads and tool calls back to the originating MCP server). |
| 18 | + |
| 19 | +## Motivation |
| 20 | + |
| 21 | +- Let MCP servers deliver rich, interactive results (forms, boards, charts, live |
| 22 | + progress) directly in the kagent chat. |
| 23 | +- Provide the in-chat rendering half of the kagent plugin story (the sidebar/plugin |
| 24 | + half is EP-2047; the first consumer is the Kanban task-progress widget, EP-2048). |
| 25 | + |
| 26 | +### Goals |
| 27 | + |
| 28 | +- Discover MCP app resources per MCP server and associate them with tool calls. |
| 29 | +- Render the app via a sandboxed renderer inside chat messages / tool-call display. |
| 30 | +- Broker host↔app messaging: `sendMessage`, visible tool calls, and proxying of |
| 31 | + resource reads and tool calls to the backend MCP server. |
| 32 | +- Backend endpoints to list an MCP server's tools, read its resources, and call its |
| 33 | + tools on behalf of the UI. |
| 34 | + |
| 35 | +### Non-Goals |
| 36 | + |
| 37 | +- The sidebar plugin/registration mechanism (EP-2047). |
| 38 | +- Shipping a specific MCP app (the Kanban task-progress app is EP-2048). |
| 39 | +- File-upload / artifact handling — note the chat files carry adjacent |
| 40 | + file-upload/minimap code (see "Adjacent code" below); that feature is tracked |
| 41 | + separately and is **not** part of this EP's scope. |
| 42 | + |
| 43 | +## Implementation Details |
| 44 | + |
| 45 | +### Backend |
| 46 | + |
| 47 | +- **`go/adk/pkg/mcp/registry.go`** — `CreateToolsets` now also returns the set of |
| 48 | + **MCP-app–capable tool names** (tools whose MCP server advertises a UI resource), |
| 49 | + so the agent can treat their results specially. |
| 50 | +- **`go/adk/pkg/agent/mcp_apps.go`** — `MakeMCPAppModelResultCallback`: for |
| 51 | + MCP-app tools, keep the rich tool payload in chat history for UI rendering while |
| 52 | + compacting what is sent back to the model (avoids redundant polling/tool churn). |
| 53 | + Wired in `agent.go` only when `len(mcpAppToolNames) > 0`. |
| 54 | +- **`go/core/internal/httpserver/handlers/mcpapps.go`** — `MCPAppsHandler` with |
| 55 | + `HandleListTools`, `HandleCallTool`, `HandleReadResource`, exposed under |
| 56 | + `/api/mcp-apps/{namespace}/{name}/...`. (Only the MCP-apps hunks of the shared |
| 57 | + `server.go`/`handlers.go` are included here; the plugins hunks belong to EP-2047.) |
| 58 | + |
| 59 | +### UI (`ui/src`) |
| 60 | + |
| 61 | +- **`components/mcp-apps/McpAppRenderer.tsx`** — renders an MCP app resource via |
| 62 | + `@mcp-ui/client` in a sandbox, wiring its `onUIAction`/resource-read/tool-call |
| 63 | + callbacks to the backend; `McpAppsInspector.tsx` is a standalone inspector view |
| 64 | + (surfaced at `app/apps/[appName]/page.tsx`, reachable by clicking an MCP app |
| 65 | + listed alongside a server's tools in `components/mcp/McpServersView.tsx`). |
| 66 | +- **`components/chat/ChatMcpAppsContext.tsx`** — context that maps a tool name to its |
| 67 | + MCP app (`getMcpAppForTool`) and brokers `sendMessage` / `McpAppVisibleToolCall` |
| 68 | + between an app and the chat. |
| 69 | +- **`components/chat/ChatLayoutUI.tsx`** — mounts `ChatMcpAppsProvider` around the |
| 70 | + chat subtree so the MCP-app context is active for every chat session (without this |
| 71 | + mount, tool calls never resolve to apps and no widget renders). |
| 72 | +- **`components/chat/ChatInterface.tsx`, `ChatMessage.tsx`, `ToolCallDisplay.tsx`, |
| 73 | + `components/ToolDisplay.tsx`** — render the app for MCP-app tool calls and forward |
| 74 | + app actions. |
| 75 | +- **`app/actions/mcp-apps.ts`** + **`app/api/mcp-apps/.../{resources,tools/.../call}`** |
| 76 | + — server actions / BFF routes calling the backend MCP-apps endpoints. |
| 77 | +- **`public/sandbox_proxy.html`** — sandbox proxy document for the app iframe. |
| 78 | + |
| 79 | +### New dependencies (`ui/package.json`) |
| 80 | + |
| 81 | +- `@mcp-ui/client` `^7.1.1` |
| 82 | +- `@modelcontextprotocol/ext-apps` `^1.7.1` |
| 83 | +- `@modelcontextprotocol/sdk` `^1.29.0` |
| 84 | + |
| 85 | +The lockfile (`ui/package-lock.json`) and the generated `ui/public/mockServiceWorker.js` |
| 86 | +(MSW worker, bumped `2.14.2` → `2.14.6`) are regenerated as a side effect of resolving |
| 87 | +the new dependency tree. |
| 88 | + |
| 89 | +### Adjacent code |
| 90 | + |
| 91 | +Per the agreed split, the chat files (`ChatInterface.tsx`, `ChatMessage.tsx`, |
| 92 | +`messageHandlers.ts`) are taken whole and therefore also carry the chat |
| 93 | +**file-upload** (`lib/fileUpload.ts`, `chat/FileAttachment.tsx`) and **minimap** |
| 94 | +(`chat/ChatMinimap.tsx`) UI that was developed alongside MCP apps. These are |
| 95 | +included so the chat compiles, but are not the subject of this EP; the file-upload |
| 96 | +backend (artifact extraction, `save_artifact`) is intentionally **excluded**. |
| 97 | + |
| 98 | +## Test Plan |
| 99 | + |
| 100 | +- **Unit (Go):** `registry_test.go` (MCP-app tool-name detection) and |
| 101 | + `mcp_apps_test.go` (model-result callback). `go build ./adk/... ./core/...` and |
| 102 | + test compilation pass. |
| 103 | +- **Unit (UI):** `getMcpAppForTool` mapping (`ChatMcpAppsContext.test.tsx`); mcp-apps |
| 104 | + server actions (`actions/__tests__/mcp-apps.test.ts`); and a regression test |
| 105 | + (`chat/__tests__/ChatLayoutUI.test.tsx`) asserting `ChatLayoutUI` mounts |
| 106 | + `ChatMcpAppsProvider` around the chat so widgets can render. |
| 107 | +- **Manual / e2e:** point the chat at an MCP server exposing a UI resource; confirm |
| 108 | + the widget renders inline, `sendMessage` posts to the chat, and resource/tool-call |
| 109 | + proxying reaches the server. The Kanban task-progress widget (EP-2048) is the |
| 110 | + reference end-to-end case. |
| 111 | + |
| 112 | +## Alternatives |
| 113 | + |
| 114 | +- **Render apps only in a side panel (not inline in chat):** loses the |
| 115 | + tool-call→widget association and the conversational flow. |
| 116 | +- **Trust the model with full tool payloads:** causes token bloat and tool churn; |
| 117 | + hence the model-result compaction callback. |
| 118 | + |
| 119 | +## Open Questions |
| 120 | + |
| 121 | +- Should MCP-app rendering be opt-in per MCP server (a `spec` flag) rather than |
| 122 | + inferred from advertised UI resources? |
| 123 | +- How should multiple apps in a single conversation share/scope state? |
0 commit comments