# MCP server (/docs/mcp)

`@microcharts/mcp` is a [Model Context Protocol](https://modelcontextprotocol.io) server that gives an assistant three
tools: find a chart for a question, get its exact props, render it to SVG. It calls the real library, so what comes back
is the mark the component draws.

It runs on your machine over stdio. Nothing is hosted, there is no key, and no data leaves the process your client
spawns.

## Add it to your client

The package ships a `microcharts-mcp` binary, so every client below spawns the same thing — `npx -y @microcharts/mcp`.
There is no install step of your own: `npx` fetches it on first run.

Open yours for its command, its config format, and where the file lives.

### Command-line agents

#### Claude Code

**Command line**

```bash
claude mcp add microcharts -- npx -y @microcharts/mcp
```

Everything after `--` is the command Claude Code spawns. Add `--scope user` to make it available in every project.

**Config file** — `.mcp.json`

```json
{
  "mcpServers": {
    "microcharts": {
      "command": "npx",
      "args": ["-y", "@microcharts/mcp"]
    }
  }
}
```

Project scope — check this file in and everyone on the repo gets the server.

Docs: https://code.claude.com/docs/en/mcp

#### Codex CLI

**Command line**

```bash
codex mcp add microcharts -- npx -y @microcharts/mcp
```

**Config file** — `~/.codex/config.toml`

```toml
[mcp_servers.microcharts]
command = "npx"
args = ["-y", "@microcharts/mcp"]
```

The ChatGPT desktop app and the Codex IDE extension read this same file, so one entry covers all three.

Docs: https://developers.openai.com/codex/mcp

#### Gemini CLI

**Command line**

```bash
gemini mcp add --scope user microcharts npx -y @microcharts/mcp
```

Scope defaults to the current project; `--scope user` writes the home config instead.

**Config file** — `~/.gemini/settings.json`

```json
{
  "mcpServers": {
    "microcharts": {
      "command": "npx",
      "args": ["-y", "@microcharts/mcp"]
    }
  }
}
```

Docs: https://google-gemini.github.io/gemini-cli/docs/tools/mcp-server.html

#### GitHub Copilot CLI

**In the app**

```text
/mcp add

Name     microcharts
Type     Local (STDIO)
Command  npx -y @microcharts/mcp
Tools    *
```

Tab moves between fields, Ctrl+S saves. The server is usable without restarting.

**Config file** — `~/.copilot/mcp-config.json`

```json
{
  "mcpServers": {
    "microcharts": {
      "type": "local",
      "command": "npx",
      "args": ["-y", "@microcharts/mcp"],
      "tools": ["*"]
    }
  }
}
```

Docs: https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers

#### opencode

**Config file** — `opencode.json`

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "microcharts": {
      "type": "local",
      "command": ["npx", "-y", "@microcharts/mcp"],
      "enabled": true
    }
  }
}
```

opencode takes the command as one array rather than command plus args.

Docs: https://opencode.ai/docs/mcp-servers/

#### Amp

**Command line**

```bash
amp mcp add microcharts -- npx -y @microcharts/mcp
```

**Config file** — `~/.config/amp/settings.json`

```json
{
  "amp.mcpServers": {
    "microcharts": {
      "command": "npx",
      "args": ["-y", "@microcharts/mcp"]
    }
  }
}
```

The VS Code extension reads the same key from its own settings.

Docs: https://ampcode.com/manual#mcp

#### Goose

**In the app**

```text
goose configure

> Add Extension
> Command-line Extension

Name     microcharts
Command  npx -y @microcharts/mcp
```

**Config file** — `~/.config/goose/config.yaml`

```yaml
extensions:
  microcharts:
    type: stdio
    name: microcharts
    enabled: true
    cmd: npx
    args: ["-y", "@microcharts/mcp"]
    envs: {}
```

Goose calls the executable `cmd`, not `command`.

Docs: https://block.github.io/goose/docs/getting-started/using-extensions/

### Editors and IDEs

#### VS Code

**Config file** — `.vscode/mcp.json`

```json
{
  "servers": {
    "microcharts": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@microcharts/mcp"]
    }
  }
}
```

VS Code names the object `servers`, not `mcpServers`. For every workspace, run **MCP: Open User Configuration** instead.

**Command line**

```bash
code --add-mcp '{"name":"microcharts","command":"npx","args":["-y","@microcharts/mcp"]}'
```

Docs: https://code.visualstudio.com/docs/copilot/customization/mcp-servers

#### Cursor

**Config file** — `.cursor/mcp.json`

```json
{
  "mcpServers": {
    "microcharts": {
      "command": "npx",
      "args": ["-y", "@microcharts/mcp"]
    }
  }
}
```

`~/.cursor/mcp.json` is the same file for every project.

Docs: https://cursor.com/docs/context/mcp

#### Windsurf

**Config file** — `~/.codeium/windsurf/mcp_config.json`

```json
{
  "mcpServers": {
    "microcharts": {
      "command": "npx",
      "args": ["-y", "@microcharts/mcp"]
    }
  }
}
```

Cascade panel → the MCP icon → **Configure** opens this file.

Docs: https://docs.windsurf.com/windsurf/cascade/mcp

#### Zed

**Config file** — `settings.json`

```json
{
  "context_servers": {
    "microcharts": {
      "command": "npx",
      "args": ["-y", "@microcharts/mcp"]
    }
  }
}
```

Zed calls MCP servers context servers. Settings → AI → MCP Servers → **Add Local Server** writes the same entry.

Docs: https://zed.dev/docs/ai/mcp

#### JetBrains IDEs

**In the app**

```text
Settings | Tools | AI Assistant | Model Context Protocol (MCP)

> + > As JSON > paste the block below > Apply
```

**Config file**

```json
{
  "mcpServers": {
    "microcharts": {
      "command": "npx",
      "args": ["-y", "@microcharts/mcp"]
    }
  }
}
```

Covers IntelliJ IDEA, WebStorm, PyCharm, and the rest of the family — AI Assistant and Junie share the server list.

Docs: https://www.jetbrains.com/help/ai-assistant/configure-an-mcp-server.html

#### Kiro

**Config file** — `.kiro/settings/mcp.json`

```json
{
  "mcpServers": {
    "microcharts": {
      "command": "npx",
      "args": ["-y", "@microcharts/mcp"],
      "disabled": false,
      "autoApprove": ["find_microchart", "get_microchart", "render_microchart"]
    }
  }
}
```

Workspace config wins over `~/.kiro/settings/mcp.json`. Kiro reloads on save — no restart. Drop `autoApprove` to confirm each call by hand.

Docs: https://kiro.dev/docs/mcp/configuration/

#### Antigravity

**Config file** — `~/.gemini/config/mcp_config.json`

```json
{
  "mcpServers": {
    "microcharts": {
      "command": "npx",
      "args": ["-y", "@microcharts/mcp"]
    }
  }
}
```

`.agents/mcp_config.json` scopes the same block to one workspace.

Docs: https://antigravity.google/docs/mcp

#### Visual Studio

**Config file** — `.mcp.json`

```json
{
  "servers": {
    "microcharts": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@microcharts/mcp"]
    }
  }
}
```

`%USERPROFILE%\.mcp.json` for every solution, `<SOLUTIONDIR>\.mcp.json` for one. Saving the file restarts the agent's servers.

Docs: https://learn.microsoft.com/en-us/visualstudio/ide/mcp-servers

#### Cline

**Config file** — `cline_mcp_settings.json`

```json
{
  "mcpServers": {
    "microcharts": {
      "command": "npx",
      "args": ["-y", "@microcharts/mcp"],
      "disabled": false,
      "autoApprove": []
    }
  }
}
```

MCP Servers icon → **Configure** → **Configure MCP Servers** opens it. The CLI reads `~/.cline/mcp.json`.

Docs: https://docs.cline.bot/mcp/configuring-mcp-servers

#### Roo Code

**Config file** — `.roo/mcp.json`

```json
{
  "mcpServers": {
    "microcharts": {
      "command": "npx",
      "args": ["-y", "@microcharts/mcp"]
    }
  }
}
```

Project config wins over the global `mcp_settings.json`.

Docs: https://docs.roocode.com/features/mcp/using-mcp-in-roo

#### Continue

**Config file** — `.continue/mcpServers/microcharts.yaml`

```yaml
name: microcharts
version: 0.0.1
schema: v1
mcpServers:
  - name: microcharts
    type: stdio
    command: npx
    args:
      - "-y"
      - "@microcharts/mcp"
```

Continue takes one block file per server, and `mcpServers` is a list rather than an object.

Docs: https://docs.continue.dev/customize/deep-dives/mcp

#### Trae

**In the app**

```text
Settings > MCP > Add > Add Manually
```

**Config file**

```json
{
  "mcpServers": {
    "microcharts": {
      "command": "npx",
      "args": ["-y", "@microcharts/mcp"]
    }
  }
}
```

Docs: https://docs.trae.ai/ide/model-context-protocol

### Desktop and chat apps

#### Claude Desktop

**Config file** — `claude_desktop_config.json`

```json
{
  "mcpServers": {
    "microcharts": {
      "command": "npx",
      "args": ["-y", "@microcharts/mcp"]
    }
  }
}
```

Settings → Developer → **Edit Config** opens it: `~/Library/Application Support/Claude/` on macOS, `%APPDATA%\Claude\` on Windows. Quit and reopen Claude to load it.

Docs: https://modelcontextprotocol.io/docs/develop/connect-local-servers

#### ChatGPT desktop

**In the app**

```text
Settings > MCP servers > Add server

Name     microcharts
Transport STDIO
Command  npx -y @microcharts/mcp
```

Save, then **Restart**.

**Config file** — `~/.codex/config.toml`

```toml
[mcp_servers.microcharts]
command = "npx"
args = ["-y", "@microcharts/mcp"]
```

ChatGPT on the web reads no local file and spawns no local process — it reaches remote connectors only.

Docs: https://developers.openai.com/codex/mcp

#### LM Studio

**Config file** — `~/.lmstudio/mcp.json`

```json
{
  "mcpServers": {
    "microcharts": {
      "command": "npx",
      "args": ["-y", "@microcharts/mcp"]
    }
  }
}
```

The **Program** tab → **Install** → **Edit mcp.json** opens it in the app.

Docs: https://lmstudio.ai/docs/app/plugins/mcp

#### Warp

**In the app**

```text
Settings > AI > MCP servers > + Add

> CLI Server (command) > paste the block below > Save
```

**Config file**

```json
{
  "microcharts": {
    "command": "npx",
    "args": ["-y", "@microcharts/mcp"]
  }
}
```

Warp takes the server entry on its own, without an `mcpServers` wrapper.

Docs: https://docs.warp.dev/knowledge-and-collaboration/mcp

A client that isn't listed takes the same two values, whatever it names the fields: the command is `npx`, and the
arguments are `-y` and `@microcharts/mcp`. A client that reaches remote connectors only — ChatGPT on the web, a hosted
agent builder, a browser-only IDE — cannot spawn a local process at all. Point it at [`/llms.txt`](/llms.txt) and
[`/catalog.json`](/catalog.json) instead.

> Node 20 or newer. The server brings its own copy of `@microcharts/react`, so you do not need microcharts installed to use it, and it does not touch a project that already has it.

## 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](/docs/ai#the-grammar)                        | A fenced block your app parses into a component.      |
| **Read** | [`/llms.txt`](/llms.txt), [`/catalog.json`](/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 control — a
chat reply, a terminal, an email draft — where the model needs the finished mark rather than an instruction to draw one.

## The three tools

Call them in order, or one at a time.

### `find_microchart`

Rank the 106 chart types against a question written in plain language.

```json title="call"
{ "question": "is the error budget burning down?", "limit": 3 }
```

```json title="result"
[
  {
    "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 the `bestFor` phrase that matched, quoted back rather than generated. 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](/docs/quickstart#the-shared-grammar), best/avoid guidance, the authored box, its gotchas, a
copy-runnable example, and `sample` — that same example as JSON props.

```json title="call"
{ "slug": "bullet" }
```

```json title="result (abridged)"
{
  "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" },
  "maxWidth": 320,
  "maxHeight": 70,
  "gotchas": ["Its only interaction callback is `onSelect`."],
  "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 serve two 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 where a model goes wrong.

`maxWidth` and `maxHeight` are the authored box in viewBox units. Past it a chart's geometry stops scaling, so a `width`
of 823 on a chart authored to 320 paints a word-sized mark in a room-sized frame; scale with CSS instead. `gotchas`
carries what a prop description cannot: documented caps, inputs the component derives from a raw sample, and how
`format` meets a chart that prints its own sign or unit.

### `render_microchart`

Render to a self-contained SVG plus its generated alt text — for a surface that cannot run React.

```json title="call"
{ "type": "sparkline", "data": [132, 148, 141, 165, 159, 182] }
```

```json title="result (svg abridged)"
{
  "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"
}
```

Here is that same call as a component on this page, with the same geometry and the same summary:

<Sparkline data={[132, 148, 141, 165, 159, 182]} width={160} height={40} title="Weekly revenue" />

The default `format: "svg"` embeds the stylesheet inside the SVG, so the mark renders correctly on a surface with no CSS
of its own. Pass `format: "bare"` when the page already loads `@microcharts/react/styles.css`.

The tool does not write `summary`. It reads the chart's own accessible name back out of the rendered markup, the same
sentence a screen reader speaks. See [Accessibility](/docs/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/react`
  version the snapshot was cut from.
- **`microcharts://agent-setup`** — the same paste-and-run setup prompt served at [`/agent-setup.md`](/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 the alt text attached, so the sentence and the mark
carry the same reading. Nothing on the client side has to understand the grammar.

### 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`, then scaffolds the component against the
real API. There are 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:

```ts
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 rarely has clean numbers. Every call therefore ends in one of two places: a rendered mark, or a named
error. The server never guesses a value to make a call succeed.

### Messy data still renders

Degenerate input is a documented case. The chart renders and the summary reports what the data was. 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."`                              |

A gap is never filled with an invented number, and the sentence never claims more than the data supports. These are the
same guarantees the components make; [Accessibility](/docs/accessibility) has 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 series big enough to produce megabytes of markup is a mistake upstream, and returning
it would fill the model's context instead of helping.

### 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, so the result stands alone. Because the embedded
  CSS is the real stylesheet, the mark still answers `prefers-color-scheme`, `forced-colors`, and
  `prefers-reduced-motion` wherever it lands: 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`. It is much
  smaller per response, and the right choice inside your own app.
- **`mimeType`** is `image/svg+xml` for most charts. `Delta` and `TokenConfidence` are inline **text** marks, so they
  come back as `text/html` with `width`/`height` of `0`: they size to the font around them, not to a pixel box.
- **`library`** is the `@microcharts/react` version 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`** — the `Intl.NumberFormatOptions` half 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`, as the component does;
`summary` still comes back correct.

## What it can't do

Reach for the component instead when you need any of these:

- **It renders the static chart.** No hover, keyboard roving, touch scrub, or selection, and `animate` has no meaning.
  Those live on the [`/interactive` entry](/docs/quickstart#add-interactivity); `get_microchart` returns
  `interactiveImport` so an agent can wire it up in code.
- **No annotations.** `Threshold`, `TargetZone`, `Marker`, and `Callout` are [React children](/docs/annotations); JSON
  has no children.
- **No function props.** Formatter callbacks and event handlers can't cross a tool call. Use `locale` and the `format`
  options object for numbers, or set `summary` to 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.** Your client spawns the server on your machine. There is no hosted endpoint and no HTTP transport, by
  design, which 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](/docs/quickstart#set-up-with-an-ai-agent) instead.
- **Chart props are a closed set.** Arbitrary `data-*` or `aria-*` attributes are not forwarded. That is a library
  decision, not a limit of the transport.

## 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. A chart that lands in
the library without the snapshot being regenerated fails CI instead of shipping a server whose catalog is missing it.
The rendered sample for every stable chart is exercised in that same suite.

---

The package lives at [`@microcharts/mcp`](https://www.npmjs.com/package/@microcharts/mcp), and is listed in the
[Glama MCP registry](https://glama.ai/mcp/servers/ganapativs/microcharts). If your assistant doesn't speak MCP, the
[Quickstart](/docs/quickstart) has a paste-and-run prompt that teaches it the library directly, and
[AI-native](/docs/ai) covers the grammar it emits.
