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
访问服务器

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

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选