mirror of
https://github.com/mudler/LocalAI.git
synced 2026-07-08 23:37:43 -04:00
* Revert "fix: Add timeout-based wait for model deletion completion (#8756)"
This reverts commit 9e1b0d0c82.
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
* feat: add mcp prompts and resources
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
* feat(ui): add client-side MCP
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
* feat(ui): allow to authenticate MCP servers
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
* feat(ui): add MCP Apps
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
* chore: update AGENTS
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
* chore: allow to collapse navbar, save state in storage
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
* feat(ui): add MCP button also to home page
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
* fix(chat): populate string content
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
---------
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
121 lines
5.7 KiB
Markdown
121 lines
5.7 KiB
Markdown
# Testing MCP Apps (Interactive Tool UIs)
|
|
|
|
MCP Apps is an extension to MCP where tools declare interactive HTML UIs via `_meta.ui.resourceUri`. When the LLM calls such a tool, the UI renders the app in a sandboxed iframe inline in the chat. The app communicates bidirectionally with the host via `postMessage` (JSON-RPC) and can call server tools, send messages, and update model context.
|
|
|
|
Spec: https://modelcontextprotocol.io/extensions/apps/overview
|
|
|
|
## Quick Start: Run a Test MCP App Server
|
|
|
|
The `@modelcontextprotocol/server-basic-react` npm package is a ready-to-use test server that exposes a `get-time` tool with an interactive React clock UI. It requires Node >= 20, so run it in Docker:
|
|
|
|
```bash
|
|
docker run -d --name mcp-app-test -p 3001:3001 node:22-slim \
|
|
sh -c 'npx -y @modelcontextprotocol/server-basic-react'
|
|
```
|
|
|
|
Wait ~10 seconds for it to start, then verify:
|
|
|
|
```bash
|
|
# Check it's running
|
|
docker logs mcp-app-test
|
|
# Expected: "MCP server listening on http://localhost:3001/mcp"
|
|
|
|
# Verify MCP protocol works
|
|
curl -s -X POST http://localhost:3001/mcp \
|
|
-H 'Content-Type: application/json' \
|
|
-H 'Accept: application/json, text/event-stream' \
|
|
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}'
|
|
|
|
# List tools — should show get-time with _meta.ui.resourceUri
|
|
curl -s -X POST http://localhost:3001/mcp \
|
|
-H 'Content-Type: application/json' \
|
|
-H 'Accept: application/json, text/event-stream' \
|
|
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
|
|
```
|
|
|
|
The `tools/list` response should contain:
|
|
```json
|
|
{
|
|
"name": "get-time",
|
|
"_meta": {
|
|
"ui": { "resourceUri": "ui://get-time/mcp-app.html" }
|
|
}
|
|
}
|
|
```
|
|
|
|
## Testing in LocalAI's UI
|
|
|
|
1. Make sure LocalAI is running (e.g. `http://localhost:8080`)
|
|
2. Build the React UI: `cd core/http/react-ui && npm install && npm run build`
|
|
3. Open the Chat page in your browser
|
|
4. Click **"Client MCP"** in the chat header
|
|
5. Add a new client MCP server:
|
|
- **URL**: `http://localhost:3001/mcp`
|
|
- **Use CORS proxy**: enabled (default) — required because the browser can't hit `localhost:3001` directly due to CORS; LocalAI's proxy at `/api/cors-proxy` handles it
|
|
6. The server should connect and discover the `get-time` tool
|
|
7. Select a model and send: **"What time is it?"**
|
|
8. The LLM should call the `get-time` tool
|
|
9. The tool result should render the interactive React clock app in an iframe as a standalone chat message (not inside the collapsed activity group)
|
|
|
|
## What to Verify
|
|
|
|
- [ ] Tool appears in the connected tools list (not filtered — `get-time` is callable by the LLM)
|
|
- [ ] The iframe renders as a standalone chat message with a puzzle-piece icon
|
|
- [ ] The app loads and is interactive (clock UI, buttons work)
|
|
- [ ] No "Reconnect to MCP server" overlay (connection is live)
|
|
- [ ] Console logs show bidirectional communication:
|
|
- `tools/call` messages from app to host (app calling server tools)
|
|
- `ui/message` notifications (app sending messages)
|
|
- [ ] After the app renders, the LLM continues and produces a text response with the time
|
|
- [ ] Non-UI tools continue to work normally (text-only results)
|
|
- [ ] Page reload shows the HTML statically with a reconnect overlay until you reconnect
|
|
|
|
## Console Log Patterns
|
|
|
|
Healthy bidirectional communication looks like:
|
|
|
|
```
|
|
Parsed message { jsonrpc: "2.0", id: N, result: {...} } // Bridge init
|
|
get-time result: { content: [...] } // Tool result received
|
|
Calling get-time tool... // App calls tool
|
|
Sending message { method: "tools/call", ... } // App -> host -> server
|
|
Parsed message { jsonrpc: "2.0", id: N, result: {...} } // Server response
|
|
Sending message text to Host: ... // App sends message
|
|
Sending message { method: "ui/message", ... } // Message notification
|
|
Message accepted // Host acknowledged
|
|
```
|
|
|
|
Benign warnings to ignore:
|
|
- `Source map error: ... about:srcdoc` — browser devtools can't find source maps for srcdoc iframes
|
|
- `Ignoring message from unknown source` — duplicate postMessage from iframe navigation
|
|
- `notifications/cancelled` — app cleaning up previous requests
|
|
|
|
## Architecture Notes
|
|
|
|
- **No server-side changes needed** — the MCP App protocol runs entirely in the browser
|
|
- `PostMessageTransport` wraps `window.postMessage` between host and `srcdoc` iframe
|
|
- `AppBridge` (from `@modelcontextprotocol/ext-apps`) auto-forwards `tools/call`, `resources/read`, `resources/list` from the app to the MCP server via the host's `Client`
|
|
- The iframe uses `sandbox="allow-scripts allow-forms"` (no `allow-same-origin`) — opaque origin, no access to host cookies/DOM/localStorage
|
|
- App-only tools (`_meta.ui.visibility: "app-only"`) are filtered from the LLM's tool list but remain callable by the app iframe
|
|
|
|
## Key Files
|
|
|
|
- `core/http/react-ui/src/components/MCPAppFrame.jsx` — iframe + AppBridge component
|
|
- `core/http/react-ui/src/hooks/useMCPClient.js` — MCP client hook with app UI helpers (`hasAppUI`, `getAppResource`, `getClientForTool`, `getToolDefinition`)
|
|
- `core/http/react-ui/src/hooks/useChat.js` — agentic loop, attaches `appUI` to tool_result messages
|
|
- `core/http/react-ui/src/pages/Chat.jsx` — renders MCPAppFrame as standalone chat messages
|
|
|
|
## Other Test Servers
|
|
|
|
The `@modelcontextprotocol/ext-apps` repo has many example servers:
|
|
- `@modelcontextprotocol/server-basic-react` — simple clock (React)
|
|
- More examples at https://github.com/modelcontextprotocol/ext-apps/tree/main/examples
|
|
|
|
All examples support both stdio and HTTP transport. Run without `--stdio` for HTTP mode on port 3001.
|
|
|
|
## Cleanup
|
|
|
|
```bash
|
|
docker rm -f mcp-app-test
|
|
```
|