Skip to main content
Glama
kakalition

apollo-charting

by kakalition

apollo-charting

CI License: MIT Python 3.10+ MCP

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..5 token palette, light and dark themes.

  • Deterministic output — animations off, fixed viewport, exact width × scale by height × scale pixels, optional transparency.

  • Discoverable — palettes, themes, demo specs, and the spec schema are read-only MCP resources, plus a make-chart prompt.

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_chart fails fast with a dependency_missing error while validate_spec and chart_component keep working.

Related MCP server: infographic-mcp

Install

git clone https://github.com/kakalition/apollo-charting
cd apollo-charting
./install.sh

install.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 need sudo).

  • --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)

--transport stdio

stdin/stdout

Streamable HTTP

--transport streamable-http

http://HOST:PORT/mcp

HTTP + SSE

--transport sse

http://HOST:PORT/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 8000

Explore it interactively with the MCP Inspector:

uv run mcp dev src/apollo_charting/server.py

Tools

Tool

Arguments

Returns

render_chart

spec (required), out, home, theme, scale, transparent, force, keep_html

JSON metadata (path, bytes, sha256, width, height, scale, family, theme, background, home, html, warnings) and an inline image/png.

validate_spec

spec (required), strict

JSON mirroring render.py check: valid, family, series, points, dimensions, palette, theme, layout, warnings; with strict, a Node/bundle/Playwright/Chromium report and a renderable boolean.

chart_component

spec (required)

The equivalent recharts + @/components/ui/chart JSX as text.

Defaults for render_chart:

  • out — <output_dir>/<slug>.png, named from spec.title (or chart).

  • home — $CHARTING_HOME, else ~/.local/share/charting.

  • theme, scale — fall back to the spec, then the built-in defaults.

  • transparent — off; the spec's background decides otherwise.

  • force — false; an existing out raises a conflict error.

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

charting://palettes

Every palette with its colors.

charting://palette/{name}

One palette's colors.

charting://themes

The light and dark token maps.

charting://theme/{name}

One theme's tokens.

charting://demos

Names of the bundled example specs.

charting://demo/{name}

A full demo spec (bar, line, area, pie, donut, radar, radial).

charting://schema

A concise reference for every spec key.

Prompt

Name

Purpose

make-chart

Gives the model the spec contract (families, the x.key requirement, palette names, scale semantics, and the dependency_missing hint) and instructs it to compose a spec and call render_chart.

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"
}
  • chart is required: bar, line, area, pie, donut, radar, or radial.

  • Cartesian families need x.key; pie/donut/radial need name_key + value_key; radar needs x.key (or axis_key) and a series.

  • palette is a name (default, neutral, blue, emerald, amber, rose, slate), an array of up to five colors, or a chart-1..chart-5 override map.

  • theme is light or dark; width/height are 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

invalid_spec

The spec failed validation (unknown family, missing x.key, bad scale, unknown palette, …).

dependency_missing

Node, the esbuild bundle, or Chromium is missing; the message includes the exact install command.

conflict

The output file exists and force was not set.

render_failed

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

CHARTING_HOME

~/.local/share/charting

CHARTING_GRID

true

CHARTING_THEME

light

CHARTING_LEGEND

bottom

CHARTING_PALETTE

default

CHARTING_FONT

system

CHARTING_WIDTH

720

CHARTING_BACKGROUND

transparent

CHARTING_HEIGHT

420

CHARTING_OUTPUT_DIR

<home>/output

CHARTING_SCALE

2

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) ──▶ PNG

The 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 end

tests/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
└── LICENSE

License

MIT. See LICENSE.

Available Tools

3 tools
chart_componentC

Return the equivalent shadcn/Recharts JSX for a chart spec.

ParametersJSON Schema
NameRequiredDescriptionDefault
specYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

C2.9/5.0
Behavior2/5

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.

Conciseness4/5

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.

Completeness2/5

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.

Parameters2/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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/.

ParametersJSON Schema
NameRequiredDescriptionDefault
outNo
homeNo
specYes
forceNo
scaleNo
themeNo
keep_htmlNo
transparentNo

TDQS

A4.1/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines2/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
specYes
strictNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.9/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

  1. 3 tool updatesv1.0.0
    • First observedchart_component
    • First observedrender_chart
    • First observedvalidate_spec

TDQS

A3.7/5.0

Scored across 3 tools

Disambiguation5/5

Each tool has a distinct role: rendering, validation, and generating component code. No overlap or ambiguity between them.

Naming Consistency5/5

All names follow snake_case with a verb-noun or clear-purpose pattern (render_chart, validate_spec, chart_component). Consistent and predictable.

Tool Count4/5

Three tools is small but well-scoped for the stated purpose of chart rendering and conversion. Not overly thin, and each tool is essential.

Completeness4/5

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

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    A 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.
    -
  • A
    license
    A
    quality
    A
    maintenance
    MCP 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.
    8
    25 npm
    MIT