flint-chart-connector
A deployable remote MCP server that packages Microsoft's Flint chart compiler for chat hosts, enabling users to render, compile, and explore charts from natural language descriptions across Claude and Codex.
README
Flint charts for Claude and Codex
Flint is Microsoft Research's chart compiler. You hand it a compact semantic spec (chart type, which field goes on which channel, and what each field means), and it derives the scales, axes, tick formats, colour schemes, label rotation, legends and layout, emitting native Vega-Lite, ECharts or Chart.js. It works well with models, because semantic types are much easier to infer correctly than a full chart config.
This repo packages Flint as a remote MCP server you deploy yourself, tuned for chat hosts. It's verified working in Claude (web, desktop, mobile, Code) and OpenAI Codex.
There is no shared instance to join, so you'll want to deploy your own. It takes about a minute and sits comfortably inside the free tier.
Deploy it
Or from a clone:
git clone https://github.com/jmilinovich/flint-chart-connector
cd flint-chart-connector
npm install
npx vercel deploy --prod
Either way you end up with a URL. Your connector endpoint is that URL plus /mcp:
https://<your-deployment>.vercel.app/mcp
Open the root URL in a browser and the landing page shows you that endpoint, already filled in, with a copy button.
Nothing here is Vercel-specific except vercel.json. It's a plain Node HTTP handler
(src/http.js), so any host that runs Node 20+ will do. npm run dev serves it on
localhost:8787.
Connect it
Claude web, desktop, mobile. Settings → Connectors → Add custom connector, paste the
/mcp URL. No authentication. Claude connects from Anthropic's cloud rather than from your
machine, which is why the endpoint has to be publicly reachable, and why this is hosted
rather than run locally.
Claude Code.
claude mcp add --transport http flint https://<your-deployment>.vercel.app/mcp
Codex.
codex mcp add flint --url https://<your-deployment>.vercel.app/mcp
or in ~/.codex/config.toml:
[mcp_servers.flint]
url = "https://<your-deployment>.vercel.app/mcp"
Then ask for a chart: "chart my last six months of revenue by segment."
Or run it locally instead
For stdio hosts, meaning Claude Code and Claude Desktop, Microsoft's own server runs locally with no deployment at all:
claude mcp add flint -- npx -y flint-chart-mcp
That's the better option if local stdio is all you need. Deploy this one when you want a URL, for Claude web and mobile or for Codex, or when you want the tuned surface described below.
Tools
| Tool | Use |
|---|---|
render_chart |
Show a chart. Live and editable in Claude, a PNG everywhere else. The default. |
compile_chart |
Backend-native Vega-Lite / ECharts / Chart.js JSON, for artifacts and embedding. |
list_chart_types |
Chart types with the channels and properties each accepts. |
What's different from upstream
The charts are unmodified. This calls flint-chart and flint-chart-mcp/render straight
off npm, and the interactive UI is upstream's own prebuilt flint-app.html. What changes
is the surface presented to the host.
One render_chart that is both interactive and static. Upstream ships five tools,
including a separate create_chart_view for the interactive path, and tells the model to
prefer it. On a host without MCP Apps that means announcing a chart and then displaying
nothing. Here a single tool is always registered as an MCP App and always returns a
rendered PNG. Claude draws the live widget with Flint's editing panel, while Codex and
everything else get the image, so neither ends up without a chart.
The catalogs live in the schema. Flint's 44 semantic types and its chart-type list are
declared as enums, read out of the installed package at import time so they cannot drift.
This matters because an unrecognised semantic type is ignored silently, with no warning,
and you quietly lose behaviour. { month: "YearMonth" } gives you a temporal axis, while a
plausible-looking "Month-Year" gives you unordered categories and says nothing. Both
hosts preserve the enums: Claude as JSON Schema, Codex as TypeScript string-literal unions.
Guidance lives in the server instructions, not in an MCP resource and not only in per-parameter descriptions. Neither host auto-reads resources, and Codex drops parameter descriptions entirely (see the gotchas below). Upstream ships a 567-line authoring skill as a resource that a chat host will never load.
No local file reads. This is a remote server, so its disk is not yours. Data is inline rows only.
/view is a static page that renders a spec client-side with tooltips and PNG/SVG export,
and render_chart links to it. The spec is raw-deflated into the URL fragment, which
browsers never send to a server.
Layout
api/mcp.js Vercel function → src/http.js
src/http.js Stateless Streamable HTTP transport
src/server.js Tools, schemas, instructions
src/catalog.js Semantic types + chart types, read from the installed flint-chart
src/app-ui.js Locates upstream's prebuilt MCP App UI bundle
src/share.js Spec → URL-fragment packing for the viewer
public/ Landing page, viewer, vendored Vega bundles
Development
npm install
npm run dev # http://localhost:8787/mcp
npm test # 19 tests, no network
npm run vendor # refresh public/vendor after bumping flint-chart
Gotchas
Never gate MCP App registration on client capability over a stateless transport.
Claude declares io.modelcontextprotocol/ui only in initialize and never repeats it, so
on a stateless server every later request looks incapable:
initialize uiDeclared=true
tools/list uiDeclared=false <-- registers the plain tool
tools/call uiDeclared=false
The app tool then never gets advertised and the widget never renders, however well the host
supports it. Register unconditionally instead, since the _meta is inert on hosts that
ignore it.
Always return the image too. Skipping the raster on app hosts to avoid a duplicate view sounds efficient right until the widget doesn't render, at which point the answer has a link and no chart. Claude renders the widget and ignores the image content, so the duplicate never materialises anyway, and Codex has no widget at all and needs the image.
Codex rewrites your schema as a TypeScript signature. Enums survive as string-literal
unions, which is what you want. Per-parameter description text does not survive at all,
though the server instructions and the tool description both do. Any guidance that has
to reach the model belongs in the instructions rather than in a parameter description.
GET /mcp must not return JSON. A GET carrying Accept: text/event-stream is a client
opening the optional server-to-client stream. A stateless server has nothing to push and
should answer 405. Returning 200 application/json instead can put the official MCP
TypeScript SDK client into a reconnect loop. Plain GETs still get a health blob.
framework: null in vercel.json is load-bearing. Left to auto-detect, Vercel reads a
bare package.json as its Node server preset, bundles the repo root into one function, and
every route including static files 500s into a lambda with no entrypoint (Invalid export found).
includeFiles is load-bearing too. The render path needs @resvg/resvg-js for
rasterising, @napi-rs/canvas for text metrics so label widths match the browser, and
flint-chart-mcp/assets/fonts for Liberation Sans. The font directory is resolved at
runtime by walking up from the module, which dependency tracing cannot see. Without it you
get charts with mismeasured labels.
Locking it down
The endpoint is open by default, which is what keeps setup to one paste. Anyone who learns
your URL can render charts on your account. Set FLINT_MCP_SECRET and the URL becomes the
credential:
vercel env add FLINT_MCP_SECRET production
Then use https://…/mcp?k=<secret> as the connector URL. Authorization: Bearer <secret>
works too, which suits Claude Code and Codex. The query form exists because some connector
dialogs accept nothing but a URL.
Data handling
Rows are compiled and rasterised in memory and returned as an image. Nothing is written to disk or a database, and the server reads no local files and fetches no URLs. The viewer is a static page whose spec rides in the URL fragment.
Flint is MIT-licensed, by Microsoft Research with IDEAS Lab, Renmin University of China. This connector is an independent wrapper and is not affiliated with Microsoft.
推荐服务器
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 模型以安全和受控的方式获取实时的网络信息。