mcp-a2ui-vega

mcp-a2ui-vega

Enables creating and editing live, interactive dashboards with Vega-Lite charts via A2UI in MCP hosts, with dataset binding and reusable widgets.

Category
Visit Server

README

mcp-a2ui-vega

An MCP App whose UI is A2UI, and whose charts are Vega-Lite.

Ask Claude for the ticket sales dashboard and it appears in the conversation: metrics, charts, a table of the latest orders, a place to drop a CSV. Then ask for changes — make the sales one a line chart and show today's sales in green, add a heatmap of when people buy — and the dashboard is edited in place, not redrawn from scratch. Drop a CSV into it, or append rows from a script, and every chart moves on its own.

The dashboard is not a picture the model produced. It is a component tree the agent composed from a typed catalog, drawn by Google's own A2UI renderer (@a2ui/react), bound to a dataset that pushes changes to it.

<p align="center"><img src="docs/dashboard.png" alt="The ticket sales dashboard: metrics, sales over time, breakdowns by channel, show and seating section, the latest orders, and CSV intake" width="900"></p>

What it is made of

Piece What it does
packages/catalog The A2UI catalog — component APIs, functions, and the JSON-Schema document generated from them. The contract between agent and renderer.
packages/renderer The MCP App view: React + @a2ui/react, a Vega-Lite implementation of the catalog, and the MCP Apps bridge.
packages/server A Cloudflare Worker: the MCP server, the dataset store, the saved-widget library, and the ui:// resource.
data The dataset, grounded in the real Broadway weekly grosses.
skills/a2ui-dashboards The skill that teaches an agent to compose and recompose these dashboards well.
tools Dataset builder, live-append feed, host harness, end-to-end test.

No API keys. There is no model in this repo. The agent is whichever MCP host connects; the server stores rows and composes JSON; the renderer is deterministic. The only credentials involved are the Cloudflare ones you need to deploy the Worker.

What this is, in A2UI's own terms

Google's A2UI and MCP Apps post names three ways the two protocols compose. This repo is Pattern 3: A2UI inside MCP Apps — the MCP App bundle carries its own A2UI renderer, which is what lets a host that has never heard of A2UI (Claude, today) show agent-composed UI anyway.

It is A2UI proper, not an imitation of it. The renderer is @a2ui/react driving @a2ui/web_core's MessageProcessor — Google's packages, unmodified. The wire format is A2UI v0.9 messages: createSurface, updateComponents, updateDataModel. The catalog extends A2UI's own basic catalog rather than replacing it, so Column, Card and ChoicePicker are theirs and VegaChart is ours, under one catalog id.

It also implements Pattern 1: A2UI over MCP as a second path — the same dashboard is served as application/a2ui+json at a2ui://dashboard/ticket_sales, so a host with its own A2UI renderer (Flutter, Angular, a native client) can draw it with no iframe at all. That payload is the portable artifact; the MCP App is how everyone else gets to see it.

How a dashboard stays live

agent ──render_dashboard──▶ server ──A2UI messages in _meta──▶ view

                     ┌──────── change stream (SSE) ────────┐
server ──────────────┘  "ticket_sales moved"               ▼
   ▲                                                      view
   └── get_dataset_rows, through the host's tool proxy ──── │
                                                            │
                                          updateDataModel ──┘  every chart,
                                                               tile and table
                                                               re-renders

Four decisions do most of the work:

Rows never travel through the model. render_dashboard returns a layout and a row count. The view fetches rows itself with get_dataset_rows, a tool whose _meta.ui.visibility is ["app"] so it never appears in the agent's tool list. Twelve thousand orders belong in a chart, not in a context window.

A dashboard is components, not an image. Changing one chart is one update_dashboard naming one id. The user's filters, sort order and scroll position survive, because nothing else was touched.

An update must render in a view that has never seen the dashboard. A host is free to open a fresh view per tool result rather than routing it into the running one, and a payload of pure updateComponents has nothing to update there — A2UI rejects it with surface not found, and the user gets a blank panel where they asked for a change. So the server remembers the composed tree, and every update travels in two forms: _meta['a2ui/messages'] rebuilds the whole surface from nothing, _meta['a2ui/patch'] carries only the delta. The view applies whichever fits what it already holds, so the server never has to guess which view it is talking to.

The server says when, the host still carries what. MCP has no server→view channel, so the view holds open a change stream straight to the Worker — the one thing the app resource permits in csp.connectDomains. What comes down it is a notification, never data: the rows are still fetched through the host's tool proxy, so every byte of data remains auditable. An idle dashboard makes no requests at all, and a change reaches it in about a second.

Any chart, including ones the catalog never named

VegaChart takes a whole Vega-Lite spec as a property. A heatmap, a box plot, a faceted small-multiple — none of them are in the catalog, and all of them work, because the catalog's boundary is kinds of component, not kinds of chart.

When the user likes one, save_widget keeps it by name, and render_dashboard({widgets: ["sales_by_hour_heatmap"]}) brings it back in a later conversation — still bound to the live dataset, so it updates like everything else.

The data

data/ticket_sales.csv is one row per ticket order across twelve Broadway shows. The shows, their theatres, house sizes, week-by-week capacity and price levels are real, from the Broadway weekly grosses dataset (Playbill, via TidyTuesday). The individual orders are modelled from those figures, because the source is weekly and aggregate. data/README.md says exactly which parts are which.

npm run data:build                    # rebuild, 90 days ending now
npm run data:append -- --watch 10     # a live feed: new orders every 10s

Point that at a deployment with --url https://your-worker.workers.dev and watch the dashboard move while you are looking at it — within a second of each append, because the server tells it to.

Run it

npm install
npm run data:build          # build the dataset (downloads the source CSV once)
npm run build               # catalog → renderer → single-file app → worker
npm run dev -w @mcp-a2ui-vega/server

Then open http://localhost:8788/app.html for the dashboard on its own, or http://localhost:8788/ for the connection instructions.

Install it in Claude

Deploy first (below) — a custom connector is reached from Anthropic's cloud, not from your machine, so localhost will not do.

Claude Code, straight from this repository:

/plugin marketplace add vpm238/mcp-a2ui-vega
/plugin install a2ui-vega-dashboards@mcp-a2ui-vega

That installs the MCP server and the skill together. The server URL is in .claude-plugin/plugin.json — if you deploy your own Worker, change that one line and /plugin marketplace update mcp-a2ui-vega.

Claude web or desktop: Settings → Connectors → Add custom connector, and paste https://your-worker.workers.dev/mcp. There is no OAuth and no key. Then add the skill: zip the skills/a2ui-dashboards folder — the folder itself must be at the root of the zip — and upload it under Settings → Capabilities → Skills.

The skill is optional either way: the server sends usage instructions in its MCP handshake. It is what makes the follow-ups good — editing one component instead of redrawing everything, and remembering charts you liked.

Then ask for the ticket sales dashboard.

Deploy

The Worker is the only thing that has to be hosted. GitHub Pages gets the standalone demo.

npx wrangler login
npm run deploy -w @mcp-a2ui-vega/server

That creates the KV namespace if it does not exist yet, writes its id into wrangler.toml, bundles the app and the seed data, and deploys.

Or add two repository secrets and push to main — the workflow does all of it, including creating the namespace, and skips the deploy with a warning rather than failing while the secrets are missing:

Secret What it is
CLOUDFLARE_API_TOKEN A token from the Edit Cloudflare Workers template
CLOUDFLARE_ACCOUNT_ID Your account id, from the Workers dashboard

The same workflow publishes the standalone demo to GitHub Pages once Pages is enabled under Settings → Pages with source GitHub Actions. Until it is, the workflow says so in a warning and stays green.

Test

npm test                                       # dataset and catalog checks
npm run dev -w @mcp-a2ui-vega/server           # terminal 1
python3 -m http.server 8479                    # terminal 2, at the repo root
node tools/e2e.mjs                             # a real browser, the real protocol

To run the same suite against a deployment rather than a local worker:

node tools/relay.mjs https://your-worker.workers.dev     # terminal 3
SERVER_URL=http://localhost:8790 node tools/e2e.mjs

The relay exists because a browser behind a restrictive proxy may not reach Cloudflare while Node can; every byte still comes from the real deployment. It buffers responses, so it does not carry the change stream — a view behind it falls back to polling, which is worth exercising too.

tools/e2e.mjs drives tools/harness.html — a hand-written MCP Apps host, ~120 lines, deliberately not sharing code with the app so a protocol mistake cannot pass unnoticed in both. It checks the things a type checker cannot: that the dashboard draws, that recomposing colours one tile without disturbing the rest, that appended rows arrive unasked, that a filter moves the metrics and the table together, and that a saved widget comes back.

tools/push-latency.mjs measures the thing the architecture claims: an idle dashboard makes no requests, and a change reaches it in about half a second with exactly one fetch.

node tools/push-latency.mjs https://your-worker.workers.dev

Licence

MIT.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured