UI-MCP-Generative-UI
README.md
# UI-MCP-Generative-UI
An MCP server that renders **charts as generative UI** — the model sends data, and
an interactive chart appears inline in the chat rather than a wall of numbers.
Built with [mcp-use](https://docs.mcp-use.com/v2/typescript/getting-started/welcome)
v2 (MCP Apps). Point Claude or ChatGPT at the server's URL and the chart renders
inside the conversation.
| | |
|---|---|
|  |  |
|  | |
## Requirements
- Node.js **>= 22.22.2** (the framework's floor; also what `npm test` needs for
`--experimental-strip-types`)
## Quick start
```bash
npm install
npm run dev
```
- MCP endpoint: `http://localhost:3000/mcp`
- Inspector (try the tools by hand): `http://localhost:3000/mcp/inspector`
In the Inspector, open `render-chart` and paste a spec into the `spec` field:
```json
{
"title": "Revenue by quarter",
"subtitle": "FY2026, in millions of USD",
"chartType": "bar",
"categories": ["Q1", "Q2", "Q3", "Q4"],
"series": [
{ "name": "Product", "values": [12.4, 15.1, 14.2, 19.8] },
{ "name": "Services", "values": [8.1, 9.4, 11.7, 12.2] }
],
"valuePrefix": "$",
"valueSuffix": "M",
"decimals": 1
}
```
## Tools
| Tool | What it does |
|---|---|
| `render-chart` | Draws a `bar`, `line`, `area`, or `donut` chart and shows it inline. Returns a readable summary as text plus the spec as `structuredContent`. |
| `analyze-series` | Summary statistics for a list of numbers: total, mean, median, min/max, first/last, change, percent change, and direction. No view — plain text and structured output. |
`render-chart` rejects a spec whose series don't line up with the category axis,
with a message telling the model how to fix it, rather than drawing a chart that
misrepresents the data.
### What the view does
- **Four chart forms.** Grouped bars, multi-series lines, filled areas, and a
donut (parts of one whole — it uses the first series only).
- **Hover tooltips.** A cursor-anchored card with every series value at the
hovered category; the hit target is the full plot height, not the mark.
- **Table view.** A toggle in the header renders the same data as an accessible
table — this is also the relief for the palette's low-contrast slots in light
mode, where color alone shouldn't have to carry the value.
- **Click to pin** a category, so a highlight survives the pointer leaving.
- **In-view tool calls.** The *Analyze* button calls `analyze-series` from inside
the view and shows the result in the footer.
- **Host-driven highlight.** The view registers a `highlight-category` tool, so
the model can say "highlight Q3" and the chart responds.
- **Streams as it arrives.** While the tool input is still being written, the
chart draws whatever has landed so far; missing points are gaps, not zeros.
- **Light and dark**, each with its own validated palette, following the host's theme.
## Connecting Claude or ChatGPT
Both hosts need a **public HTTPS URL** ending in `/mcp`.
For a quick trial without deploying anything, mcp-use can open a tunnel:
```bash
npm run dev -- --tunnel
```
Then add the printed HTTPS URL as a custom MCP connector — in Claude under
Settings → Connectors, in ChatGPT under Settings → Connectors (developer mode).
The exact menu wording moves around between releases; look for "custom connector"
or "add MCP server" and paste the URL.
> **This server ships with no authentication.** Anyone with the URL can call it.
> That is fine for a tunnel you close afterwards; before putting it on a stable
> public address, add an OAuth provider — mcp-use has one-line adapters under
> `mcp-use/oauth/*`, wired through the `oauth` option in `index.ts`.
## Self-hosting
```bash
npm run build # bundles index.ts + views into .mcp-use/build
npm run start # serves that build
```
`start` honours `PORT` and `HOST` (defaults `3000` and `127.0.0.1`). Behind a
reverse proxy or in a container you'll usually want:
```bash
PORT=8080 HOST=0.0.0.0 npm run start
```
Binding `0.0.0.0` without host validation is only safe behind an edge that routes
your own domains; if the process is directly reachable, set `allowedHosts` on the
`MCPServer` options. The server logs a warning about this on startup.
## Project layout
```
index.ts server + tool definitions
views/chart/
view.tsx the React view bound to render-chart
chart.ts palette, spec normalization, scales, formatting
chart.test.ts unit tests for the above
view.css design tokens for light and dark
public/icon.svg server icon
```
## Development
```bash
npm run typecheck # types + regenerates view type bindings
npm test # unit tests for the chart logic
npm run build # production bundle
```
`mcp-env.d.ts` types the React hooks from the tools exported by `index.ts`, so a
tool consumed by a view must be assigned to an exported `const`.
### Adding a tool
Add it in `index.ts` with `server.tool({ ... }, handler)`. To bind a view, give
the tool an `outputSchema`, return matching `structuredContent`, add
`view: { name: "<dir>" }`, and create `views/<dir>/view.tsx`. Run
`npm run typecheck` afterwards to refresh the generated types.
### About the chart colors
The categorical palette is fixed and ordered — slots are assigned in sequence and
never cycled into invented hues. Both the light and dark ramps were checked for
lightness banding, chroma, colorblind separation, and contrast against their own
surface. Labels drawn *on* a filled mark pick black or white per fill, so every
slot clears 4.5:1; `chart.test.ts` asserts that so a future palette edit can't
quietly regress it.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues