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.
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.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。