MCP server
Add @microcharts/mcp to Claude Desktop, Claude Code, Cursor, or VS Code and your assistant can find the right word-sized chart, read its exact props, and render a finished SVG with real alt text — over stdio, on your machine.
The rest of this site is about a model writing microcharts: a grammar it emits, a catalog it reads. This page is
about a model calling them. @microcharts/mcp is a Model Context Protocol server
that exposes three tools — find a chart for a question, get its exact props, render it to SVG — backed by the real
library, not by a description of it.
It runs on your machine over stdio. Nothing is hosted, there is no key, and no data leaves the process your client spawns.
Emit, read, call
Three ways a model can end up with a correct chart. They stack; none replaces the others.
| Verb | Surface | The model gets |
|---|---|---|
| Emit | The grammar | A fenced block your app parses into a component. |
| Read | /llms.txt, /catalog.json | The true API, so the code it writes compiles. |
| Call | This server | A rendered mark, plus the sentence that describes it. |
Emit is for a surface you control, where you can map a block to a component. Call is for a surface you don't — a chat reply, a terminal, an email draft — where the model needs the finished artwork rather than an instruction to draw it.
Add it to your client
The package ships a microcharts-mcp binary, so every client below spawns the same thing. No install step of your own —
npx fetches it on first run.
In claude_desktop_config.json:
{
"mcpServers": {
"microcharts": {
"command": "npx",
"args": ["-y", "@microcharts/mcp"]
}
}
}claude mcp add microcharts -- npx -y @microcharts/mcpIn .cursor/mcp.json (project) or ~/.cursor/mcp.json (global):
{
"mcpServers": {
"microcharts": {
"command": "npx",
"args": ["-y", "@microcharts/mcp"]
}
}
}In .vscode/mcp.json:
{
"servers": {
"microcharts": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@microcharts/mcp"]
}
}
}Node 20 or newer. The server brings its own copy of @microcharts/react — you do not need microcharts installed to
use it, and it does not touch a project that already has it.
The three tools
They're designed to be called in order, but each stands alone.
find_microchart
You know the question; you don't know which of the 106 chart types answers it. Rank them.
{ "question": "is the error budget burning down?", "limit": 3 }[
{
"slug": "error-budget",
"name": "ErrorBudget",
"tagline": "Are we burning the error budget too fast to survive the window?",
"dataShape": "number[], budget remaining (0–1) per elapsed step; index 0 = 1.0",
"why": "an SLO error budget in a KPI card"
},
{
"slug": "burn-chart",
"name": "BurnChart",
"tagline": "Will we finish on time?",
"dataShape": "{ plan: number[]; actual: number[] }, remaining work per period",
"why": "a sprint burndown in a tab header"
}
]why is not a generated explanation — it's the bestFor phrase that actually matched, quoted back. An optional
dataShape argument filters to the shape you already have.
get_microchart
Everything needed to wire one chart: import paths, its own props plus the
shared grammar, best/avoid guidance, a copy-runnable example — and sample, that
same example as JSON props.
{ "slug": "bullet" }{
"slug": "bullet",
"staticImport": "@microcharts/react/bullet",
"interactiveImport": "@microcharts/react/bullet/interactive",
"dataShape": "value + target + bands",
"encoding": { "channel": "position (measure length vs a target tick)", "precision": "high" },
"bestFor": ["progress to goal", "SLA / budget vs target", "KPI with thresholds"],
"example": { "code": "<Bullet value={72} target={80} bands={[50, 90]} title=\"Quota\" />" },
"sample": { "value": 72, "target": 80, "bands": [50, 90], "title": "Quota" }
}The two example forms exist for two different readers. example.code is JSX, for a human to paste. sample is JSON,
for the model to hand straight to render_microchart — every chart takes its own shape, and guessing between
number[], { label, value }[], and { open, high, low, close }[] is exactly where a model goes wrong.
render_microchart
Render to a self-contained SVG plus its generated alt text — for a surface that cannot run React.
{ "type": "sparkline", "data": [132, 148, 141, 165, 159, 182] }{
"svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"mc-root mc-spark\" …><style>…</style>…</svg>",
"summary": "Trending up 38%. Range 132 to 182. Last value 182.",
"mimeType": "image/svg+xml",
"width": 80,
"height": 20,
"library": "0.8.0"
}That's this, as a real component on this page — the same geometry, the same summary:
The default format: "svg" embeds the stylesheet inside the SVG, so the mark is correct dropped into a surface with no
CSS of its own. Pass format: "bare" when the page already loads @microcharts/react/styles.css.
summary is not written by the tool. It is the chart's own accessible name, read back out of the rendered markup — the
same sentence a screen reader speaks. See Accessibility for how it's generated.
Two resources
Alongside the tools, the server publishes two read-only resources:
microcharts://catalog— every chart type with its props, data shape, encoding, and the@microcharts/reactversion the snapshot was cut from.microcharts://agent-setup— the same paste-and-run setup prompt served at/agent-setup.md, for when the assistant should wire the library into a repo rather than render a one-off.
What it looks like in practice
Render, on a surface that can't run React
You're talking through last quarter's numbers in a desktop assistant. Instead of describing the trend, it calls
render_microchart and puts a real sparkline in the reply — with alt text attached, so the sentence and the mark say
the same thing. The model isn't hoping a client renders its grammar; it has the mark.
Build, in a coding agent
You're adding an SLO panel in Cursor. The agent calls find_microchart("error budget"), gets error-budget and
bullet, calls get_microchart("bullet") for the props and a valid sample, and scaffolds the real component against
the real API — no invented props to correct in review.
Ship, in your own AI app
You're building an assistant with the Vercel AI SDK. The same three capabilities are exported as tools, so your model can produce charts without you writing any wiring:
import { microchartsTools } from "@microcharts/mcp/ai-sdk";import { streamText } from "ai";const result = streamText({ model, tools: microchartsTools, // find_microchart · get_microchart · render_microchart prompt: "Summarise this quarter and show the revenue trend as a chart.",});The ai package is an optional peer on that subpath, so nothing pulls it into the stdio server.
How it behaves
A model mid-reply cannot promise clean numbers, and a tool that guesses is worse than one that refuses. The rule here is that every call ends in one of two places: an honest mark, or a named error. Never a plausible wrong answer.
Messy data still renders
Degenerate input is a documented case, not a failure. The chart renders and the summary says what actually happened — these are the real generated sentences:
| You send | You get back |
|---|---|
[] | A chart and "No data." |
[7] | "Single value 7." |
[5, 5, 5, 5] | "Flat at 5." |
[3, null, 5, null, 9] | Nulls are gaps, never zeros — "Trending up 200%. Range 3 to 9. Last value 9." |
[NaN, 3, Infinity] | Non-finite values are dropped — "Single value 3." |
[-4, -2, -8] | "Trending down 100%. Range -8 to -2. Last value -8." |
The chart never invents a number to fill a hole, and the sentence never claims more than the data supports. Same guarantees as the components — see Accessibility for the full matrix.
Calls it refuses, and what it says
Refusals come back as tool errors with the fix in the message, so the model can correct itself in one turn instead of retrying blind:
| Call | Error |
|---|---|
An unknown type | unknown chart "piechart" |
| A required prop missing | "dot-plot" needs `data`. Data shape: { label, value }[]. Call get_microchart("dot-plot") … |
| A prop of the wrong type | `data` must be number[], got string |
An interactive prop (animate, onActive) | `animate` — interactive-only; this tool renders the static chart |
A function prop (strings, xFormat) | `xFormat` — a function, which cannot cross JSON |
children | `children` — annotations … are React children and have no JSON form |
| More data than a word-sized mark holds | data exceeds 5000 points, or a size limit on the payload or the rendered markup |
| Props that would produce an unrenderable box | "sparkline" produced an invalid -5×20 box — check that `width`/`height` … are positive |
The size limits are deliberate. A microchart is a word-sized mark; a series big enough to produce megabytes of markup is a mistake upstream, and returning it would blow the model's context rather than help it.
What comes back
Every render carries role="img" and the generated accessible name, so the mark is usable the moment it lands.
format: "svg"(default) embeds the stylesheet inside the SVG. The result is genuinely standalone — and because the real stylesheet comes with it, the mark still answersprefers-color-scheme,forced-colors, andprefers-reduced-motionin whatever surface it ends up in. A chart pasted into a dark-themed client is drawn for dark.format: "bare"drops the embedded CSS for a page that already loads@microcharts/react/styles.css. Far smaller per response, and the right choice inside your own app.mimeTypeisimage/svg+xmlfor most charts.DeltaandTokenConfidenceare inline text marks, so they come back astext/htmlwithwidth/heightof0— they size to the font around them, not to a pixel box.libraryis the@microcharts/reactversion that drew it.
Props that do cross a tool call
Anything JSON can express: data, value, domain, title, summary, color, categorical colors, label,
dots, width, height, className, style, id, and positive. Two are worth calling out:
locale—"de-DE"gives"Range 1.200 to 1.800.". Number formatting is fully available.format— theIntl.NumberFormatOptionshalf of the union works:{ "style": "currency", "currency": "EUR" }yields"Range €1,200.00 to €1,800.00.". The callback half does not (see below).
Passing id switches the chart from aria-label to <title>/<desc> + aria-labelledby, exactly as the component
does; summary still comes back correct.
What it can't do
Worth knowing before you reach for it, so you reach for the component instead:
- It renders the static chart. No hover, keyboard roving, touch scrub, or selection, and
animatehas no meaning. Those live on the/interactiveentry —get_microchartreturnsinteractiveImportso an agent can wire it up in code. - No annotations.
Threshold,TargetZone,Marker, andCalloutare React children; JSON has no children. - No function props. Formatter callbacks and event handlers can't cross a tool call. Use
localeand theformatoptions object for numbers, or setsummaryto supply the sentence yourself. - No raster output. You get an SVG or HTML string, never a PNG. Anything that renders SVG will show it; anything that won't needs a converter on your side.
- stdio only. The server is spawned by your client on your machine. There is no hosted endpoint and no HTTP transport, by design — it is also why nothing you chart leaves the process.
- It doesn't touch your project. No files written, no dependencies installed. To wire the library into a repo, use the agent setup prompt instead.
- Chart props are a closed set. Arbitrary
data-*oraria-*attributes are not forwarded — a deliberate library decision, not a transport limit.
Versioning and compatibility
The server carries a snapshot of the catalog and the stylesheet, cut from a specific @microcharts/react release. That
version is stamped on the microcharts://catalog resource and returned on every render as library, so a chart you
generate can always be traced to the code that drew it.
The two version lines move independently: @microcharts/mcp tracks its own changes, and pulls in a compatible library
automatically. Pin the server, not the library, if you need a reproducible render.
Adding a chart
For contributors: nothing about a new chart is written by hand here. The server's catalog is generated from the same chart registry that drives this site, and a test re-derives it from those sources on every run — so a chart that lands in the library without the snapshot being regenerated fails CI rather than shipping a server that has never heard of it. The rendered sample for every stable chart is exercised in that same suite.
The package lives at @microcharts/mcp. If your assistant doesn't
speak MCP, the Quickstart has a paste-and-run prompt that teaches it the library directly, and
AI-native covers the grammar it emits.
BiasStrip
React bias strip chart — bland–Altman offset between two measurement methods. Tiny accessible SVG microchart; RSC-safe static or /interactive.
Formatting & scale
Control how a chart shows numbers and dates, and what value range it's drawn against — one format prop (Intl options or a function) feeds the direct labels, the accessible summary and the interactive readout alike, locale handles separators and language, and domain fixes the scale. Formatting is cached, so it stays cheap across hundreds of charts.