apollo-charting
Generates shadcn/ui-style charts as PNG images and provides the equivalent Recharts/shadcn JSX components, including shadcn/ui token palettes, themes, and chart component conventions.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@apollo-chartingPlease render this chart spec to a PNG"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
apollo-charting
A standalone, stateless Model Context Protocol server that renders shadcn/ui-style charts — bar, line, area, pie, donut, radar, and radial — to PNG from a compact JSON spec, and extracts the equivalent Recharts/shadcn JSX.
It is the charting engine that used to ship as the charting
Agent Skill in
apollo-pack, re-homed as an MCP.
The render core (scripts/_lib.py, scripts/_html.py, scripts/render.py) is
reused as-is; Node/Playwright is shelled out to only for the screenshot.
Spec in, chart out — one JSON object, one PNG (returned inline and written to disk).
Stateless — no database, no render history, no state written; safe to run as many copies as you like.
stdio or HTTP — MCP over stdio by default, with Streamable HTTP (
/mcp) and SSE (/sse) transports built in.Faithful shadcn styling — hairline grid, no axis lines, rounded bars, the
--chart-1..5token palette, light and dark themes.Deterministic output — animations off, fixed viewport, exact
width × scalebyheight × scalepixels, optional transparency.Discoverable — palettes, themes, demo specs, and the spec schema are read-only MCP resources, plus a
make-chartprompt.
Requirements
Python 3.10+ for the MCP server. The Python engine is standard library only; the MCP layer depends on
mcp.Node 20+ with a headless Chromium for
render_chart. Without it,render_chartfails fast with adependency_missingerror whilevalidate_specandchart_componentkeep working.
Related MCP server: infographic-mcp
Install
git clone https://github.com/kakalition/apollo-charting
cd apollo-charting
./install.shinstall.sh is idempotent. It runs uv sync for the server environment, then
npm ci, node scripts/build.mjs, and npx playwright install chromium for
the render dependencies. Flags:
--with-deps— also install the OS packages Chromium needs (Linux; may needsudo).--skip-node— Python environment only.--skip-python— render dependencies only.
The Node step is a one-time setup, after which rendering is fully offline. uv
is the only extra tool you need; install it from https://docs.astral.sh/uv/.
Transports
The server speaks MCP over stdio by default, and can serve the same API over HTTP:
Transport | Flag | Endpoint |
stdio (default) |
| stdin/stdout |
Streamable HTTP |
|
|
HTTP + SSE |
|
|
uv run apollo-charting # stdio
uv run apollo-charting --transport streamable-http --port 8000 # HTTP
uv run apollo-charting --transport sse --port 8000 # SSE--transport, --host (default 127.0.0.1), and --port (default 8000)
also read APOLLO_CHARTING_TRANSPORT, APOLLO_CHARTING_HOST, and
APOLLO_CHARTING_PORT; log verbosity reads APOLLO_CHARTING_LOG_LEVEL
(DEBUG…CRITICAL, default INFO). The HTTP transports bind to loopback by
default; put a reverse proxy (and auth) in front before exposing them beyond
the host.
Use with an MCP client
Point a stdio client at the console script:
{
"mcpServers": {
"apollo-charting": {
"command": "uv",
"args": ["run", "--directory", "/absolute/path/to/apollo-charting", "apollo-charting"]
}
}
}Or run it over HTTP and point a streamable-HTTP client at
http://127.0.0.1:8000/mcp:
uv run apollo-charting --transport streamable-http --port 8000Explore it interactively with the MCP Inspector:
uv run mcp dev src/apollo_charting/server.pyTools
Tool | Arguments | Returns |
|
| JSON metadata (path, bytes, sha256, width, height, scale, family, theme, background, home, html, warnings) and an inline |
|
| JSON mirroring |
|
| The equivalent |
Defaults for render_chart:
out—<output_dir>/<slug>.png, named fromspec.title(orchart).home—$CHARTING_HOME, else~/.local/share/charting.theme,scale— fall back to the spec, then the built-in defaults.transparent— off; the spec'sbackgrounddecides otherwise.force—false; an existingoutraises aconflicterror.
Example
{
"spec": {
"chart": "bar",
"title": "Revenue by month",
"data": [
{"month": "Jan", "desktop": 186, "mobile": 80},
{"month": "Feb", "desktop": 205, "mobile": 120}
],
"x": {"key": "month"},
"series": [{"key": "desktop"}, {"key": "mobile"}],
"palette": "default",
"width": 720,
"height": 420,
"scale": 2
}
}render_chart returns a text block like:
{
"out": "/home/you/.local/share/charting/output/Revenue-by-month.png",
"bytes": 48210,
"sha256": "6f1c…",
"width": 1440,
"height": 840,
"scale": 2,
"family": "bar",
"theme": "light",
"background": "transparent",
"home": "/home/you/.local/share/charting",
"html": null,
"warnings": []
}followed by an image/png content block with the chart.
Resources (read-only)
URI | Content |
| Every palette with its colors. |
| One palette's colors. |
| The |
| One theme's tokens. |
| Names of the bundled example specs. |
| A full demo spec ( |
| A concise reference for every spec key. |
Prompt
Name | Purpose |
| Gives the model the spec contract (families, the |
The chart spec
{
"spec_version": 1,
"chart": "bar",
"title": "Revenue by month",
"data": [
{"month": "Jan", "desktop": 186, "mobile": 80},
{"month": "Feb", "desktop": 205, "mobile": 120}
],
"x": {"key": "month"},
"y": {"hide": false, "format": "number"},
"series": [
{"key": "desktop", "label": "Desktop"},
{"key": "mobile", "label": "Mobile"}
],
"palette": "default",
"legend": "bottom",
"width": 720, "height": 420, "scale": 2,
"background": "transparent"
}chartis required:bar,line,area,pie,donut,radar, orradial.Cartesian families need
x.key; pie/donut/radial needname_key+value_key; radar needsx.key(oraxis_key) and a series.paletteis a name (default,neutral,blue,emerald,amber,rose,slate), an array of up to five colors, or achart-1..chart-5override map.themeislightordark;width/heightare CSS pixels;scale(1–4) multiplies both.
Read charting://schema (or references/schema.md) for
the full key reference, references/charts.md for
per-family options and gallery-variant knobs, and
references/themes.md for the tokens and palettes.
Errors
Tool failures surface as MCP errors whose message starts with the error code:
Code | Meaning |
| The spec failed validation (unknown family, missing |
| Node, the esbuild bundle, or Chromium is missing; the message includes the exact install command. |
| The output file exists and |
| Chromium produced no result or reported an error. |
Configuration
Settings come from the built-in defaults (scripts/_lib.py) and optional
CHARTING_* environment variables. They apply only to fields the spec leaves
unset.
Env var | Default | Env var | Default |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The data root holds only generated artifacts: output/ (the default PNG
destination) and tmp/ (generated HTML, kept only with keep_html). No
database is opened and no render is recorded.
How it works
spec ──▶ _lib.apply_settings ──▶ _lib.validate_spec
──▶ _html.build_html ──▶ Node: screenshot.mjs (Playwright) ──▶ PNGThe Python engine resolves rows/series, tokens, palette, and layout;
screenshot.mjs loads the self-contained HTML into headless Chromium and
captures the chart node, with React + Recharts bundled by esbuild from
scripts/boot/. The MCP layer reuses those modules unchanged and returns the
PNG both inline (base64) and on disk. The database-backed chart library,
settings CRUD, render history, scheduler artifacts, and the pdf-creator
--register integration still exist as CLI code in scripts/, but are not
exposed over MCP.
Development
uv sync
bash tests/smoke.sh # CLI engine end-to-end (self-skips render when Node/Chromium are absent)
uv run python tests/mcp_smoke.py # MCP tools, resources, validation, and a render roundtrip
uv run python tests/transport_smoke.py # stdio + streamable HTTP + SSE, end to endtests/smoke.sh generates its own fixtures and exercises every CLI verb against
a throwaway data root. tests/mcp_smoke.py drives the FastMCP server in-process
and checks the tool trio, the resources, validation, component extraction, error
surfacing, and an end-to-end render. tests/transport_smoke.py runs the server
as a subprocess and, over each of stdio, streamable HTTP, and SSE, initializes,
lists tools, validates a spec, and renders a PNG. The render sections self-skip
when Node, the bundle, or Chromium are missing.
Layout
apollo-charting/
├── install.sh # one-time setup (uv + npm + bundle + Chromium)
├── pyproject.toml # uv project; runtime dep: mcp; dev: mcp[cli]
├── src/apollo_charting/
│ ├── __init__.py # __version__
│ └── server.py # FastMCP server (tools + resources + prompt + transports)
├── scripts/ # the engine and CLI verbs (reused as-is)
│ ├── _lib.py _html.py render.py
│ ├── charts.py init.py settings.py themes.py reports.py
│ ├── screenshot.mjs build.mjs boot/ _chart.html _chart.css
│ └── vendor/ # generated bundle (gitignored)
├── references/ # schema, commands, charts, themes, scheduling
├── tests/
│ ├── smoke.sh # CLI engine end-to-end
│ ├── mcp_smoke.py # MCP-level roundtrip (in-process)
│ └── transport_smoke.py # stdio + HTTP + SSE, end to end
├── .github/workflows/ci.yml
├── package.json package-lock.json
└── LICENSELicense
MIT. See LICENSE.
Available Tools
3 toolschart_componentC
Return the equivalent shadcn/Recharts JSX for a chart spec.
| Name | Required | Description | Default |
|---|---|---|---|
| spec | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It doesn't disclose whether this is a pure transformation (no side effects), whether it validates the spec first, what happens with invalid specs, or whether it returns code as a string vs. a component. The description is too thin to convey behavioral traits.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, concise sentence that front-loads the action and output. It earns its place, though it could add a bit more context without becoming verbose.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool has one nested object parameter with 0% schema coverage, no annotations, and an output schema, the description is too sparse. It doesn't explain what a valid spec looks like, what the output schema contains, or how this relates to the sibling tools. An agent would need to inspect the output schema and guess at the spec format.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, and the description only mentions 'chart spec' without explaining the spec structure, required fields, or format. The schema itself is a generic object with additionalProperties: true, so the description adds minimal meaning beyond the parameter name. It doesn't compensate for the lack of schema documentation.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('Return') and resource ('equivalent shadcn/Recharts JSX for a chart spec'), which clearly distinguishes it from siblings like render_chart and validate_spec. It could be slightly more explicit about the transformation nature, but the core purpose is clear.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies this tool is for converting a chart spec into JSX code, which is distinct from rendering or validating. However, it doesn't explicitly state when to use this over render_chart or validate_spec, nor does it mention any prerequisites or context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
render_chartA
Render a chart spec to a PNG and return metadata plus the inline image.
Args:
spec: The chart spec (schema v1); see the charting://schema resource.
out: Output PNG path. Defaults to <output_dir>/<slug>.png.
home: Data root. Defaults to $CHARTING_HOME or ~/.local/share/charting.
theme: Override the spec theme (light or dark).
scale: Override the pixel scale (1-4).
transparent: Force a transparent background.
force: Overwrite an existing output file.
keep_html: Keep the generated HTML next to the PNG in tmp/.
| Name | Required | Description | Default |
|---|---|---|---|
| out | No | ||
| home | No | ||
| spec | Yes | ||
| force | No | ||
| scale | No | ||
| theme | No | ||
| keep_html | No | ||
| transparent | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the disclosure burden and does substantial work: it states the return shape (metadata plus inline image), default output and data-root paths, and side effects like force-overwriting existing files and keeping generated HTML in tmp/. Minor gaps remain around failure behavior and exact metadata fields, but the main behavioral profile is disclosed.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One purpose sentence is followed by a compact Args list with no filler. Every bullet adds a default, constraint, or side-effect detail, and the main deliverable is front-loaded before parameter details.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema or annotations, so the description must stand alone. It covers every parameter, default, constraint, and side effect, and references charting://schema for the nested spec object. The lack of detailed return-metadata fields is a minor gap compared to the breadth of information supplied.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema has 0% description coverage, but the Args block fully compensates: it documents all eight parameters, explains defaults for out and home, constrains theme to light/dark and scale to 1-4, clarifies force as overwrite behavior, and points spec to the charting://schema resource. This adds meaning far beyond the bare schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence names a concrete action and artifact: 'Render a chart spec to a PNG and return metadata plus the inline image.' This clearly distinguishes render_chart from validate_spec and chart_component, which are not about producing a PNG from a spec.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives no when-to-use guidance, exclusions, or alternatives. An agent must infer usage solely from the purpose statement; there is no hint such as 'use validate_spec to check the spec first' or when chart_component would be more appropriate.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
validate_specA
Validate a chart spec without rendering it.
With strict, also report Node/bundle/Playwright/Chromium availability
and whether the spec is renderable on this host.
| Name | Required | Description | Default |
|---|---|---|---|
| spec | Yes | ||
| strict | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It discloses that strict mode checks Node/bundle/Playwright/Chromium availability and renderability, which is useful. However, it doesn't mention error behavior, side effects (beyond not rendering), or what 'valid' means, leaving gaps in behavioral disclosure for a read-only validation tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two concise sentences, front-loaded with the main purpose and a clear qualifier. Every word earns its place, and the strict-mode nuance is added without bloat.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool has a potentially complex open-ended spec parameter and no annotations, but the description covers the core purpose and the strict-mode behavior. It doesn't describe what a valid spec looks like or what the validation result contains, though an output schema exists (which may cover return structure). For an agent to call it confidently, more detail on expected spec shape and output format would be helpful.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate. It explicitly explains the 'strict' parameter's effect, which adds meaning beyond the schema's boolean type. However, the 'spec' parameter is described only as 'chart spec' without any hint of required fields or structure, leaving the open-ended object poorly documented.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb (validate) and resource (chart spec), and explicitly distinguishes it from rendering by saying 'without rendering it'. This clearly separates it from sibling tools like render_chart, making its purpose unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The phrase 'without rendering it' clearly signals when to use this tool (validation) versus when not to (rendering), and the mention of strict mode adds context for host-availability checks. It doesn't explicitly name alternatives, but the distinction is clear enough for an agent to route correctly.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
3 tool updates
v1.0.0- First observed
chart_component - First observed
render_chart - First observed
validate_spec
TDQS
Scored across 3 tools
Each tool has a distinct role: rendering, validation, and generating component code. No overlap or ambiguity between them.
All names follow snake_case with a verb-noun or clear-purpose pattern (render_chart, validate_spec, chart_component). Consistent and predictable.
Three tools is small but well-scoped for the stated purpose of chart rendering and conversion. Not overly thin, and each tool is essential.
Covers the core workflow: validate a spec, render to PNG, and generate JSX. Minor gaps like listing themes or configuration are not critical for the main use case.
Maintenance
Related MCP Connectors
Create, inspect, manage, and render charts and data visualizations as SVG/PNG or interactive embeds.
Bar-first MCP server for Tabula chart authoring, PNG rendering, and editor handoff.
Render, verify, describe, and safely edit Mermaid diagrams through MCP.
Generate production-ready chart code (Recharts, Chart.js, ECharts, Plotly) from a prompt.
Related MCP Servers
- AlicenseAqualityCmaintenanceEnables AI models to generate interactive visuals including charts, diagrams, UI mockups, and SVG graphics from plain text prompts. This Windows-based MCP server serves as an intermediary to bridge AI tools with visual output capabilities.151 npmMIT
- FlicenseAqualityDmaintenanceRenders infographics from DSL syntax into PNG images using AntV Infographic. Supports various templates, image customization, and multiple transport modes (stdio, HTTP, SSE).11-
- FlicenseNot gradedqualityDmaintenanceA professional chart generation MCP server for Claude Code. Drop a charts-theme.json into any project and Claude can generate beautiful, on-brand charts output as slide-ready PNG and SVG files — fully local, no API keys required.-
- AlicenseAqualityAmaintenanceMCP server that provides vision capabilities to coding agents, enabling them to analyze screenshots, UI mockups, terminal errors, documents, tables, and charts through OpenAI-compatible vision models. Supports local stdio and remote HTTP deployments with structured JSON output and binary upload side channels.825 npmMIT