Skip to main content
Glama
jmilinovich

flint-chart-connector

by jmilinovich

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

Deploy with Vercel

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.

Related MCP server: Code Box

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.

F
license - not found
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    An MCP server that enables AI assistants to create, update, and publish Datawrapper charts through natural language. It provides tools for data synchronization, visual configuration, and retrieving chart images or editor links.
    Last updated
    8
    MIT
  • F
    license
    -
    quality
    B
    maintenance
    A self-hosted MCP server that lets claude.ai delegate tasks to OpenAI Codex CLI, billed through the ChatGPT subscription, with tools for job submission, status polling, and result retrieval.
    Last updated

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jmilinovich/flint-chart-connector'

If you have feedback or need assistance with the MCP directory API, please join our Discord server