Sales Explorer
by tpiros
README.md
# Sales Explorer — a minimal MCP App
A tiny, dependency-light demo of an **MCP App**: an interactive UI that an MCP
server ships into an AI host (Claude, etc.), rendered in a sandboxed iframe right
in the conversation.
The whole point is one moment: **you click a bar and the chart drills down with
no model round-trip** — and the assistant still knows what you're looking at.
## The three beats this demonstrates
1. **UI in the chat, not text.** Ask "show me sales by region" → a bar chart
renders inline instead of a table of numbers.
2. **Bidirectional, model-free interaction.** Click a region → the iframe calls
the `get_sales` tool *itself* and re-renders. No new prompt, no model turn.
3. **The model stays in the loop.** After drilling in, the app tells the host
what you're viewing, so you can then say "email this to my team" and it knows
what "this" is.
## How it works (the two files that matter)
- `server.ts` — registers a **tool** (`get_sales`) whose `_meta.ui.resourceUri`
points at a **UI resource** (`ui://sales-explorer/mcp-app.html`). That one line
of metadata is what turns a normal tool into an MCP App.
- `src/mcp-app.ts` — the iframe app. It renders the chart from the result the
host pushes in (`app.ontoolresult`), and on a bar click calls
`app.callServerTool({ name: "get_sales", arguments: { region } })`.
The UI is bundled by Vite into a **single self-contained HTML file**
(`dist/mcp-app.html`) so the server can return it as one resource with no
external asset requests.
## Run it
Requires Node ≥ 22.18 (run TypeScript directly — no `tsx`/`ts-node`).
```bash
npm install
npm run dev # builds the UI on change + serves MCP over HTTP on :3001
```
- HTTP endpoint: `http://localhost:3001/mcp` (Streamable HTTP, stateless)
- stdio: `npm run start:stdio`
Then point an MCP-Apps-capable host at it (Claude Desktop, MCPJam, VS Code
Copilot, …) and ask: *"show me sales by region."*
## Verify without a host (curl)
```bash
npm start & # build UI + serve
# list tools — note the _meta.ui.resourceUri on get_sales
curl -s -H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-X POST http://localhost:3001/mcp \
--data '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
# the drill-down the iframe performs on a click
curl -s -H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-X POST http://localhost:3001/mcp \
--data '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_sales","arguments":{"region":"EMEA"}}}'
```
## Project layout
```
server.ts # MCP server: get_sales tool + ui:// resource
main.ts # transport (Streamable HTTP / stdio)
mcp-app.html # UI shell (bundled into dist/mcp-app.html)
src/mcp-app.ts # the iframe app: render chart, click -> callServerTool
src/styles.css # chart styling (uses host theme variables when provided)
vite.config.ts # single-file bundle config
```
Built on `@modelcontextprotocol/ext-apps` + `@modelcontextprotocol/sdk`.
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues