Local MCP for LLM Chart Authoring
The ggaction package includes a local, read-only MCP server that helps a
language model choose current actions and exact call shapes without preloading
the complete documentation. It runs over standard input/output on the user’s
machine. It does not require a hosted server, account, or authentication.
The server does not execute chart code, render output, read request-selected files, make network requests, or collect telemetry.
Measured impact
A fixed 576-run evaluation compared public-documentation browsing with MCP-first authoring plus bounded fallback across Terra, Luna, and Nano. Each model-condition cell contains 48 observations from the same 24 tasks and two repetitions.
Across all three models, MCP with bounded fallback raised strict task success from 19.4% to 85.4%, reduced tokens per task from 13,200 to 6,052, and reduced model calls per task from 4.49 to 2.63. Provider failures remain failures in these totals rather than being removed after the run.
These results describe one fixed task set and its model and provider conditions; they are evidence for the authoring route, not a performance guarantee for every request. See the compact benchmark record for the reviewable aggregate data and the complete evaluation record for the condition definitions, paired comparisons, cost boundaries, provider failure analysis, and limitations.
Install and launch
Install ggaction in the project where the MCP client will run:
npm install ggaction
Launch the installed executable from that project:
npx --no-install ggaction-mcp
For an MCP client configuration, prefer the absolute path to the project-local
executable so the selected ggaction version is reproducible:
{
"mcpServers": {
"ggaction": {
"command": "/absolute/path/to/project/node_modules/.bin/ggaction-mcp"
}
}
}
The exact configuration container varies by MCP client, but the command always starts the same local stdio process. Node.js 20 or later is required.
One-tool workflow
The server exposes exactly one model-visible tool:
search_ggaction({ query })
Send only the exact user request in one query, including chart or mark type, transforms, encodings, guides, layout, and output format when they matter. Do not append dataset contents, code scaffolding, or evaluator instructions:
scatter plot with a color legend at bottom as svg
The result is a bounded JSON task packet with:
matchedConstraints— recognized parts of the requestactionPlan— actions and runtime operations in execution orderexactCalls— short calls with current option namesauthoring— exact package imports,chart()initialization, reusable non-duplicated Canvas/data prerequisites, immutable reassignment steps with closed target and derived-data handoffs, and the selected renderer callunsupported— terminal requirements outside the current contractunresolved— conflicting or underspecified decisions, each with the exact bounded documentation resource needed to continuecandidates— at most three exact resource identities
The MCP response and the deterministic direct adapter use the same serialized task packet. A packet is never silently truncated; it fails if it exceeds its 6,144-byte hard ceiling.
authoring.prerequisites gives the exact signatures and calls for the common
Canvas and data setup that is not already present in actionPlan. Supply every
name listed in a prerequisite’s bindings array—currently the caller-owned
values array. If the request explicitly asks to create Canvas or data, the
corresponding call appears once in authoring.steps and is omitted from
authoring.prerequisites. Keep every returned
program = ... assignment because each action returns a new immutable
ChartProgram. Run authoring.steps in order after those prerequisites:
import { chart } from "ggaction";
import { renderToSVG } from "ggaction/svg";
let program = chart()
program = program.createCanvas({
width: 800,
height: 600,
margin: { top: 140, right: 220, bottom: 120, left: 260 }
})
program = program.createData({ values })
program = program.createScatterPlot({
x: { field: "x", fieldType: "quantitative" },
y: { field: "y", fieldType: "quantitative" }
})
const output = renderToSVG(program)
The resolver closes only deterministic runtime dependencies. It can add a
required area owner for density, pass a derived dataset to its consumer, bind a
unique compatible scale, or name a stable mark target. It does not invent a
missing chart, label positions, legend channel, or composition children; those
remain explicit unresolved decisions.
Unsupported output and a missing supported renderer are separate decisions.
For example, render JPEG reports terminal unsupported.jpg and open
renderer.format, but recommends only the bounded renderer-choice section.
The unsupported entry itself requires no documentation read. If the same
request already names SVG, PNG, PDF, or Browser Canvas, it reports only the
terminal unsupported.jpg decision.
Read-only resources
MCP resource discovery provides a small overview, bounded task recipes, and templates for one exact action card. These resources are for selective reads, not a replacement full-documentation preload.
Documentation fallback is stricter. A docs section is readable only when its
exact URI appears in an unresolved[].resources list from the latest
search_ggaction result. unsupported entries do not unlock documentation. A
later result without that URI removes access again. This keeps the default flow
compact:
complete request
→ search_ggaction
→ use the task packet when resolved
→ read only the recommended bounded section when unresolved
→ clarify and search again
What the server does not provide
- Hosted or HTTP transport
- Authentication, accounts, or telemetry
- Chart execution or renderer tools
- Arbitrary filesystem, network, shell, or code access
- A generic tool that returns the complete documentation bundle
Use the complete action reference manually when a human needs the full contract. For normal model-assisted authoring, start with the one search call and add documentation only for explicit gaps.
Related
Getting Started · Chart Recipes · Rendering · Supported Features