echarts-mcp
# echarts-mcp
A modern, open-source **Apache ECharts MCP server**. It exposes type-safe tools that let AI
agents and chat UIs render rich, deterministic charts server-side — no browser. Emits SVG,
PNG, and self-contained interactive HTML (PNG needs the optional `@napi-rs/canvas` dep).
Built against the **Model Context Protocol v2 SDK** (`@modelcontextprotocol/server@2`) and
**Apache ECharts 6.1.0** (pinned), supporting both **stdio** and **Streamable HTTP** transports.
## Highlights
- **Multi-format rendering** — one call returns any combination of `svg` (raw vector
text, zero native deps), `png` (base64 image, via optional `@napi-rs/canvas`), and
`html` (a self-contained interactive page). Deterministic by default (`animation: false`).
- **Fully stateless** — every tool result is self-contained (raw SVG/PNG/HTML + echoed
option). No files, no session state, horizontally scalable over HTTP.
- **Optional chart hosting** — set `MCP_EXPORT_DIR` + `MCP_EXPORT_BASE_URL` and use
`exportToUrl` to persist an artifact and get a public URL for chat platforms.
- **Type-safe tool surface** — JSON Schema derived from Zod v4 (`Standard Schema`), so
agents get precise, machine-readable input/output contracts.
- **Passthrough fidelity** — `render_echart` accepts *any* ECharts v6 option; it does not
restrict chart types (see `list_chart_types` for discoverability only).
- **Hardened by default** — SSRF deny-by-default for remote images, option-depth/complexity
bounds, render timeout, constant-time auth, and non-root container. See [SECURITY.md](./SECURITY.md).
## Tools
| Tool | Purpose |
| --- | --- |
| `render_echart` | Renders an ECharts option to SVG, PNG, and/or HTML. Returns the artifacts + echoed option. |
| `validate_echart_option` | Dry-run structural + semantic validation (pinned to echarts@6.1.0) without rendering. |
| `list_chart_types` | Lists common series types for agent discoverability (not an allowlist). |
### `render_echart` inputs
| Param | Type | Default | Notes |
| --- | --- | --- | --- |
| `option` | object | *required* | Full ECharts v6 option (passthrough). |
| `formats` | `("svg" \| "png" \| "html")[]` | `["svg"]` | Outputs to produce in one call. |
| `width` / `height` | int | 800 / 500 | Render dimensions in logical pixels (100–4096). |
| `devicePixelRatio` | number | 2 | DPI multiplier for PNG output (1–3). |
| `theme` | string \| object | `"light"` | Built-in name or inline theme JSON object. |
| `background` | string | `"transparent"` | e.g. `"#ffffff"` or `"transparent"`. |
| `animation` | boolean | `false` | Emit animated output when `true`. |
| `exportToUrl` | boolean | `false` | Persist the artifact and return a public URL (needs hosting config). |
The result carries MCP content blocks per requested format — an `image` block for
base64 PNG, a `text` block for raw SVG and/or HTML — plus `structuredContent` with
`formats`, `width`, `height`, `theme`, `background`, `version`, and (conditionally)
`svg`, `pngBytes`, `html`, `url`, and a `warning`.
## Quick start
Requires Node.js 20+ (developed on Node 24) and pnpm.
```bash
pnpm install
pnpm build # compiles src/ -> dist/
pnpm dev # run via tsx (stdio)
pnpm start # run the compiled dist (stdio)
```
Run the server over stdio (the default) from an MCP client by pointing it at `dist/index.js`.
### Install from npm
```bash
npm install -g @ryuk3nd0/echarts-mcp
# then use it from any MCP client:
echarts-mcp
# or ad-hoc without a global install:
npx -y @ryuk3nd0/echarts-mcp
```
### Streamable HTTP
```bash
MCP_TRANSPORT=streamable-http MCP_HTTP_PORT=3030 node dist/index.js
```
The MCP endpoint is then available at `http://localhost:3030/mcp`.
### Configuration
Configuration is resolved in this precedence order (highest first):
1. Environment variables
2. A JSON config file (`MCP_CONFIG` path, or `./config.json` when present)
3. Built-in defaults
| Env var | Config key | Default |
| --- | --- | --- |
| `MCP_TRANSPORT` | `transport` | `stdio` |
| `MCP_HTTP_HOST` | `http.host` | `0.0.0.0` |
| `MCP_HTTP_PORT` | `http.port` | `3030` |
| `MCP_HTTP_PATH` | `http.path` | `/mcp` |
| `MCP_API_TOKEN` | `http.authToken` | `null` (disabled) |
| `MCP_EXPORT_DIR` | `export.outputDir` | `null` (disabled) |
| `MCP_EXPORT_BASE_URL` | `export.baseUrl` | `null` (disabled) |
| `MCP_IMAGE_ALLOW_HOSTS` | `security.imageAllowHosts` | `[]` (deny all remote images) |
See `config.example.json` for the full shape.
### Authentication (Streamable HTTP only)
By default the HTTP endpoint is **open** (intended for trusted localhost). To protect it,
set `MCP_API_TOKEN` (or `http.authToken` in `config.json`). Clients must then send
`Authorization: Bearer <token>`; requests without a matching token receive `401`.
## Development
```bash
pnpm typecheck # tsc --noEmit
pnpm lint # eslint
pnpm test # vitest run
pnpm format # prettier
```
### Enterprise / security suites
```bash
pnpm test:security # adversarial: SSRF guard, bounds, HTML injection, allowlist
pnpm test:stress # concurrency + pathological-input cliffs
pnpm test:leak # standalone --expose-gc heap-plateau check
pnpm test:enterprise # security + stress + leak (release gate)
pnpm audit # dependency audit (release-time, --audit-level=high)
```
## Docker
```bash
docker build -t echarts-mcp .
# stdio
docker run -i --rm echarts-mcp
# streamable HTTP
docker run -p 3030:3030 -e MCP_TRANSPORT=streamable-http echarts-mcp
```
## PNG rendering
PNG output uses the optional `@napi-rs/canvas` dependency (a Rust/Skia binding). It is
installed automatically with `pnpm install`, but if the native binary is unavailable the
SVG and HTML paths still work — requesting `png` without it returns a clear actionable
error. Raster rendering is bounded by an 8K-equivalent pixel budget and a
`maxImageBytes` cap (default 8 MiB); oversized images are suppressed with a `warning`.
## Security
The threat model, implemented controls, and accepted residual risks are documented in
[SECURITY.md](./SECURITY.md). In short: this is a **local-first** server (stdio), with
SSRF denied by default, bounded inputs, and a non-root container. See that file for what is
and isn't covered.
## Roadmap / deferred
- **Geo/map fetches** and custom font registration in the canvas path.
- **Full OAuth 2.1** resource-server auth (optional bearer token today).
- **HTTP-transport hardening** (body/concurrency caps) before any hosted deployment.
## License
Apache-2.0. See [LICENSE](./LICENSE).
## Integrations
Ready-to-paste client config snippets live in `integrations/`:
- `integrations/claude-desktop.json` — Claude Desktop stdio entry.
- `integrations/mcp.json` — generic stdio entry (Claude Code, Cursor, and others).
- `integrations/streamable-http.json` — remote Streamable HTTP endpoint for gateways and hosted LLM tool integrations.
A machine-readable `SKILL.md` at the repo root describes the server for Agent Skills systems.
The same guidance is exposed to MCP clients via the server's `instructions` field.
TDQS
Scored across 3 tools
render_echart, validate_echart_option, and list_chart_types each address a distinct concern: output generation, option validation, and capability discovery. No two tools overlap in purpose, so agent selection should be straightforward.
All tool names use snake_case with a verb-first pattern: render_, validate_, list_. The minor inconsistency is the object noun varying between echart, echart_option, and chart_types, but the overall convention is still predictable.
Three tools cover a narrow, well-defined pipeline: discovering chart types, validating options, and rendering output. This is an appropriate scope with no redundant tools and no obvious missing boilerplate steps.
The core workflow is well covered: list_chart_types aids discovery, validate_echart_option prevents render failures, and render_echart produces several output formats. However, render_echart's description references an exportToUrl tool for chat platforms that is not actually part of this server, creating a minor gap for agents aiming at Slack/Discord/Telegram.