graphite-editor-mcp
Render, validate, and inspect Graphite documents headlessly via MCP, producing reproducible image assets with full provenance.
Render a document to PNG/SVG/JPG/GIF from a template (
duotone-image,pattern-background,solid-background,text-on-background) or inline legacy.graphiteJSON, with required raster dimensions, optional output naming, and validated artifact metadata (sha256, size, dimensions, quarantine info).Batch-render 2–20 variants of one template sequentially via
graphite_render_variants, with per-item error reporting and outputs named<templateId>-<index>.<format>.Validate a document without rendering via
graphite_validate_doc: structural checks (JSON parse, network interface, nodes, export references) and optional engine compile check; invalid docs returnvalid:falsewith reasons instead of errors.Explore the node registry with
graphite_list_nodes,graphite_search_nodes, andgraphite_describe_nodeto discover identifiers and input contracts for authoring custom documents.Support animated GIF output with frame/duration modes, and deterministic byte-identical renders for the same spec.
Provides programmatic rendering and document validation for the Graphite node-graph graphics engine, allowing agents to author documents as data (templates or inline .graphite JSON), render them to PNG, SVG, JPG, or GIF via a pinned CLI, list available node identifiers, and validate document structure and compilability.
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., "@graphite-editor-mcpRender a 800x600 PNG of a procedural gradient using the 'gradient' template."
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.
graphite-editor-mcp
A programmatic graphics engine boundary for AI agents. Deterministic, node-graph-based, agent-drivable: agents author documents as data, a headless CLI renders them, assets are reproducible and versionable. The MCP server is the stable boundary that makes this usable by agents and automation: it wraps data-driving, never GUI-driving, and it exists to be boring — every call returns a validated artifact or a precise error. Print/DTP finishing is deliberately out of scope; Graphite owns procedural/generative asset production.
What this is — and is not
It is: an MCP server (stdio) exposing six tools over the pinned graphene-cli binary. Agents author documents as data — either template + parameters or inline legacy .graphite JSON — and receive validated, hashed artifacts with full provenance. Nothing here drives a GUI.
It is not: a print pipeline. graphene-cli writes .png, .jpg, .jpeg, .gif and .svg only — PDF is explicitly rejected upstream (headless print chains compose downstream, e.g. HTML/CSS → WeasyPrint → Ghostscript, with Graphite supplying the art). It is also not an upstream Graphite fork: the engine is pinned at a verified commit; the only source change is a small, versioned patch layer applied deterministically at build time (see docs/ENGINE-PIN.md).
Related MCP server: starlight-creator-mcp
Architecture (one paragraph)
Tools → builder/templates → runner → pinned CLI: each MCP tool validates its inputs, produces a complete legacy .graphite document (via the template factories in src/templates.ts or an inline document materialized under tmp/mcp-inline/), and hands a render spec to GraphiteRunner (src/runner.ts), which spawns the pinned graphene-cli binary, captures its output tails, enforces a per-render timeout, and — because every successful export writes a valid artifact and THEN dies with SIGSEGV — validates the artifact unconditionally (src/validator.ts: magic bytes + structure + size floors) and quarantines the abnormal exit instead of trusting exit codes. Failures are typed (src/errors.ts) with stable codes and precise diagnostics; the MCP layer turns every failure into an isError result carrying the code.
Quickstart
Prerequisites:
Node ≥ 22 (see
package.jsonengines).Rust toolchain (
cargoon PATH, or at~/.cargo/bin) — for building the pinned engine.git — for the shallow engine fetch.
git clone https://github.com/ktheunlikedoll/graphite-editor-mcp.git
cd graphite-editor-mcp
npm install
bash scripts/fetch-engine.sh # shallow-fetch the pinned upstream SHA into engine/ and build graphene-cli
npm run build # tsc → dist/src/*.js
npm test # optional; the real-CLI test tiers run when the binary is presentscripts/fetch-engine.sh shallow-fetches the exact pinned upstream commit into engine/Graphite/ (gitignored — never committed) and builds the debug graphene-cli. It is idempotent: if engine/Graphite is already at the pinned commit, it skips the fetch. First build takes a while. Reference build facts — commit SHA, binary sha256, toolchain — are recorded in docs/ENGINE-PIN.md.
The CLI path: GRAPHITE_CLI_PATH
Resolution order (src/paths.ts):
GRAPHITE_CLI_PATHenv var — used exclusively when set. A broken value fails precisely on that value; it never silently falls back.Default:
<projectRoot>/engine/Graphite/target/debug/graphene-cli(the pinned debug build).Otherwise:
CLI_NOT_FOUNDerror listing exactly what was searched.
Tool contracts
Four tools, stdio transport, official MCP SDK, zod-validated inputs. Every result is one JSON text block. Every failure is an isError: true result whose text is a JSON error envelope:
{
"code": "SPEC_INVALID",
"message": "output format \"png\" requires an explicit size: provide width AND height with BOTH dimensions (got width=800, height=undefined). Rationale: graphene-cli silently falls back to a 1x1 default viewport when a dimension is missing, so Graphite rejects incomplete sizes outright. (SVG is the only format that may omit size.)",
"name": "SpecInvalidError"
}Error codes
Code | Class | When |
|
| No usable binary (missing, not a file, not executable). Carries |
|
| The engine refused the document (compile failed) or it cannot be read. Carries |
|
| The CLI failed in a way that is NOT the SIGSEGV-after-write case. Carries |
|
| The CLI produced (or claimed to produce) an artifact that failed validation. Carries |
|
| Render exceeded its time budget (default 120 s) and was killed. Carries |
|
| Malformed request before/without any process run: unknown template id, bad params, incomplete raster size, unsupported GIF timing, bad |
| — | Any non-Graphite error; message only, never a stack trace. |
graphite_render
Renders a document to a validated artifact.
Input | Type | Notes |
|
| One of the four template ids. Either |
|
| Template parameters; defaults to the template's |
|
| Inline legacy |
|
| Required. |
|
| Both required together for raster formats (png/jpg/gif); a width-only request is rejected with |
|
| Base name WITHOUT extension — the format extension is appended ( |
|
| Required for |
Output path: <projectRoot>/exports/<name> (directory created on demand; the runner deletes stale artifacts of the same path before rendering so validation is always about THIS render).
Successful result (real output, template-driven duotone with default params):
{
"artifactPath": "<project-root>/exports/readme-example.png",
"format": "png",
"byteSize": 1154138,
"sha256": "93ef9b199f1e166cb95d26ba584f7038745a701ff18c9195e4ffc3eb23060b75",
"width": 1000,
"height": 500,
"validation": {
"valid": true,
"format": "png",
"byteSize": 1154138,
"path": "<project-root>/exports/readme-example.png",
"width": 1000,
"height": 500
},
"quarantine": {
"exitCode": null,
"signal": "SIGSEGV",
"quarantinedAfterWrite": true,
"stderrTail": ""
},
"renderMs": 1502,
"exitCode": null,
"signal": "SIGSEGV",
"command": "graphene-cli export …/tmp/mcp-inline/render-….graphite --output …/exports/readme-example.png --width 1000 --height 500",
"documentPath": "<project-root>/tmp/mcp-inline/render-….graphite",
"templateId": "duotone-image",
"params": { "width": 1000, "height": 500, "seed": 0, "scale": 35, "dark": { "red": 0.02, "green": 0.02, "blue": 0.12, "alpha": 1 }, "light": { "red": 1, "green": 0.3, "blue": 0.03, "alpha": 1 }, "reverse": false }
}templateId/params echo only on template-driven renders (echoing the parameters actually used — defaults included). The render is NOT compiled separately first: the export itself surfaces compile errors (DOC_INVALID), so every call spawns exactly one CLI process.
graphite_list_nodes
No required inputs; optional filter (case-sensitive substring). Returns { "count": <n>, "nodes": ["…", …] } from graphene-cli list-node-identifiers — 324 identifiers on the pinned build. The unfiltered list is cached in memory after the first success (process lifetime; the pin cannot change under a running server).
{
"count": 1,
"nodes": ["raster_nodes::adjustments::GradientMapNode"]
}graphite_search_nodes
Keyword/category search over the full node registry. Inputs: optional query (case-insensitive substring across identifier, display name, and description) and optional category (exact match, e.g. "Gradient", "Math: Arithmetic"). Returns { count, results: [{ identifier, displayName, category, description }], hint } — pair with graphite_describe_node for the input contract. Backed by graphene-cli dump-node-metadata (JSONL, cached process-lifetime).
graphite_describe_node
Full metadata for one node: required identifier (as returned by search). Returns { identifier, displayName, category, description, fields: [{ name, description, type?, default?, softRange?, hardRange?, step?, unit?, hidden? }] } — the authoritative contract for wiring that node into a custom document. Unknown identifiers return a SPEC_INVALID envelope that points at search.
graphite_validate_doc
Structural validation of a document WITHOUT rendering. Pass either document (inline object or JSON string) or documentPath (existing file) — never both. Optional compileCheck: true adds the engine's own compile verdict (default false — fast and offline). Validation results are data: an invalid document returns valid: false with precise reasons; it is not an error result.
{
"valid": false,
"structural": {
"checks": {
"parsed": true, "hasNetworkInterface": true, "hasNetwork": true,
"hasNodesArray": true, "hasExportsArray": true,
"nodeEntriesWellFormed": true, "exportsResolveToNodes": false
},
"nodeCount": 4,
"reasons": ["export references node id 999999, which does not exist in network.nodes"]
}
}With compileCheck: true on a structurally-valid document:
{
"valid": true,
"structural": { "checks": { "parsed": true, "hasNetworkInterface": true, "hasNetwork": true, "hasNodesArray": true, "hasExportsArray": true, "nodeEntriesWellFormed": true, "exportsResolveToNodes": true }, "nodeCount": 4, "reasons": [] },
"compile": { "ok": true, "stderrTail": "" },
"documentPath": "<project-root>/tmp/mcp-inline/validate-….graphite"
}Compile runs only after structural validation passes; when structural validation fails, compile is reported as { "ok": false, "reason": "not run: structural validation failed" } (no wasted spawn). documentPath echoes the on-disk path (the materialized copy for inline documents). A failed engine compile sets compile.ok: false with the engine's stderrTail.
graphite_render_variants
Sequential batch renderer (v1 doctrine: never parallel — one GPU-heavy child at a time).
Input | Type | Notes |
|
| Required. |
|
| 2..20 full parameter objects; each validated independently. |
|
| Required. |
|
| Required — the render size for every variant (pass the same values inside each variant's params). |
|
| Default |
Outputs: <templateId>-<index>.<format> (index = position in variants, 0-based) under the output dir. Each variant goes through the full render + validate pipeline; a failing variant is reported per-item and does NOT abort the batch — the call is isError only when every variant fails.
Result: { "count": 3, "results": [ …same shape as graphite_render results… ] }; a failed item looks like { "templateId": "duotone-image", "params": { … }, "error": { "code": "SPEC_INVALID", "message": "…" } }.
Motion (animated GIF, verified)
Time is data. Wire graphene_core::animation::AnimationTimeNode → math_nodes::MathNode (expression, e.g. A * 45) → any node input — wrapper rotation, translation, gradient stops — and export GIF:
{ "output": { "format": "gif", "width": 800, "height": 500,
"gif": { "mode": "frames", "fps": 12, "frames": 24 } } }Headless export injects frame_index / fps as animation time per frame (verified frame-exact), so any time-driven parameter animates. Authoring rules proven in builds:
Seamless loops: total rotation per loop must equal a multiple of the shape's rotational symmetry (e.g. 90° = 12 spoke pitches on a 48-spoke star); per-frame motion must NOT equal a symmetry multiple, or the animation aliases into stillness (wagon-wheel). Verify both numerically from the GIF frames.
Never wire
RealTimeNode(wall-clock, non-deterministic) into agent-authored documents;QuantizeAnimationTimeNodegives stop-motion stepping.Per-instance variation:
core_types::vector::ReadIndexNode(loop level 0) → MathNode → a transform input inside the repeated content;RepeatNodestacks copies without it,RepeatRadialNodeat radius 0 produces spokes from a center point.Output container is GIF only — narrative timelines and audio belong to a video engine, not this boundary.
See docs/demo/ for rendered examples (static banner, kinetic loop).
Authoring notes (hard-won, verified)
Facts upstream does not document, established by pixel-level evidence during builds:
Colors are sRGB floats (hex ÷ 255), passed through to output bytes in both PNG and GIF — the CLI applies no gamma conversion at the boundary. Pass
#1B1B1Bas{ "red": 0.106, "green": 0.106, "blue": 0.106 }.A scalar fed into a Vec2 input broadcasts to both components — compose explicitly with
math_nodes::CombineVec2Node(X + Y inputs) when the components differ.vector_nodes::generator_nodes::LineNodedraws from the local origin to itsLine Tovalue; its primary input is unit-typed (passNone, not a position).Shapes render centered on their origin after transform translation; text anchors top-left-ish. Position with transform wrappers (
TransformNode), not fill transforms, when composing.The GIF path skips the SIGSEGV quarantine? No — the quarantine applies to every export; GIFs are validated like PNGs.
graphite_search_nodes/graphite_describe_nodeare the intended entry point for authoring beyond the four seed templates: search the 324-node registry, read the exact input contract (types, defaults, ranges, units), then author inline JSON against it.
Demos
Rendered by this server (see docs/demo/):
Artifact | What it demonstrates |
| hand-authored 19-node custom document: multi-layer stack (noise → gradient-map field, positioned vector rules, vector text) |
| headless motion: radial star rotating through a line field, seamless loop, AnimationTime-driven |
Templates
Id | Produces | Params | Provenance |
| Full-bleed solid color (EmptyImage route) or two-color linear gradient (proven Rectangle→Fill vector route) |
| working demo gradient route |
| Procedural noise-pattern background (NoisePatternNode, clip disabled → full-bleed) |
| working demo — full 16-input serialization |
| Fallback-font text (TextNode → TextToVector → Fill) over a solid background |
| executed TextNode chain with proven placement transform |
| Procedural noise mapped through a two-color gradient ramp (GradientMap duotone) |
| demo-proven NoisePattern → GradientMap chain |
Default parameters per template live in src/templates.ts (DEFAULT_PARAMS) and are exactly the committed sample outputs in templates/*.graphite. Agents never hand-write wrapper JSON.
The SIGSEGV quarantine — why every render reports quarantinedAfterWrite
Ground truth on the pinned engine build: every successful graphene-cli export writes a complete, valid artifact and THEN dies with SIGSEGV (exit 139) — reproducibly (likely the detached GPU-poll thread in graphene-cli/src/main.rs).
The runner therefore never trusts exit codes: it validates the artifact unconditionally (existence, non-trivial size, magic bytes + structure). When the CLI crashed after writing a valid artifact, the render succeeds and carries quarantine metadata { exitCode, signal, quarantinedAfterWrite: true, stderrTail }. This is normal and expected — a successful tool result with quarantine.quarantinedAfterWrite: true is the pinned engine behaving exactly as measured. If a future engine build stops crashing, the field flips to false (exit 0 + valid artifact). A crash that left a broken artifact is NOT quarantined: it is an ARTIFACT_INVALID error.
Determinism
Same spec rendered twice → byte-identical output. Evidence: the Phase-3 suite asserts pixel-identical renders for all four templates (tests/templates-e2e.test.ts, determinism: … pixel-identical), and every tool result carries the artifact sha256, so any two renders of the same document can be compared by hash. Assets are reproducible and diffable — the point of the whole boundary.
Engine pin & support statement
Pinned engine: upstream Graphite (
GraphiteEditor/Graphite) at commitd7ae6029e0c1818d81a13b0389ef1808496a715b, fetched shallowly and built byscripts/fetch-engine.sh. Build facts and the reference binary sha256:docs/ENGINE-PIN.md.Why a pin: upstream is a fast-churning alpha. The pin exists for reproducibility — the node registry (324 identifiers on this build), document serialization shapes, and rendered output are only stable against one exact engine commit. Deterministic tooling requires a deterministic engine.
Sanctioned patch layer: the PIN itself never changes — upstream source stays at the pinned commit — and
scripts/fetch-engine.shdeterministically re-applies this project's versioned patches (docs/engine-patches/*.patch, currently0001: thedump-node-metadataregistry command poweringgraphite_search_nodes/graphite_describe_node) after checkout. The patch is written to be upstreamable verbatim.Upgrading is a deliberate re-pin: choose a new upstream commit, update the pinned SHA in
scripts/fetch-engine.shanddocs/ENGINE-PIN.md, rebuild, and re-run the full test suite (including the real-CLI e2e tiers) before relying on the new build. Node-registry content and serialization shapes can change between upstream versions.Support boundary: bugs in this repository (the MCP server, builder, runner, validator) belong here; questions about engine behavior belong upstream at
GraphiteEditor/Graphite. The SIGSEGV-after-write quirk is an upstream behavior, absorbed by the quarantine contract above.
Built on Graphite
This project is an independent tooling boundary built on the Graphite editor — the free, open-source, node-based graphics editor by the Graphite Foundation.
Project & web app: graphite.art · Code: GraphiteEditor/Graphite
Engine license: dual MIT / Apache-2.0 — this wrapper complies with both.
Relationship: not affiliated with or endorsed by the Graphite project. We pin an exact upstream commit (see the engine-pin section) and contribute findings upstream where useful.
Demo provenance:
docs/demo/artifacts are rendered by this server from original documents.docs/fractal-proof/(internal) derives from upstream'sdemo-artwork/marbled-mandelbrot.graphite(Apache-2.0) with a local node-path fix.
Integration
Register the server with your MCP client (Claude Desktop, Cursor, or any client supporting the Model Context Protocol). Give it the absolute path to the built entrypoint:
{
"mcpServers": {
"graphite-editor-mcp": {
"command": "node",
"args": ["/absolute/path/to/graphite-editor-mcp/dist/src/index.js"]
}
}
}To explore the server interactively:
npx @modelcontextprotocol/inspector node dist/src/index.jsA scripted non-interactive stdio handshake lives at scripts/stdio-probe.mjs (node scripts/stdio-probe.mjs dist/src/index.js graphite_list_nodes '{"filter":"GradientMap"}').
Limitations
No PDF / no print pipeline — the CLI rejects PDF.
Fallback-font text only — the text template uses the engine's embedded fallback font. The raw-
TextNoderesource failure is fully decoded and the authoring-shape fix is execution-verified; seedocs/font-resource-investigation.md— embedded brand fonts would need the.gddresource route (future work, not this build).Sequential renders in v1 — one graphene-cli child at a time;
graphite_render_variantsruns its variants in a plain loop.Debug-binary latency — the pinned debug build spends ~1–1.5 s per spawn (startup + render); a release build (
cargo build --release -p graphene-cli, then pointGRAPHITE_CLI_PATHat it) cuts that substantially.Inline documents are materialized and kept under
tmp/mcp-inline/(small JSON files; gitignored) so error messages always point at a real document on disk.graphite_list_nodescaches the registry per process lifetime.Strictly a wrapper: no upstream source modifications, no GUI driving.
Development
npm run typecheck # tsc --noEmit
npm run build # tsc -p tsconfig.json → dist/src/*.js
npm test # vitest run — 122 tests, 5 filesTest layout (tests/):
runner.test.ts/validator.test.ts— process-runner and artifact-validation units against synthetic fake CLIs intests/synthetic/*.mjs(crash-after-write, clean exits, hangs, garbage writes) — the real binary is never required.builder.test.ts/ fixtures — builder + template round-trips against committed proven documents intests/fixtures/.templates-e2e.test.ts— real-CLI renders of all four templates, parametric pixel checks, determinism. Skipped as a whole when the binary is absent (describe.skipIf).mcp-e2e.test.ts— the MCP surface over the SDK'sInMemoryTransport: a protocol tier (no binary needed: tool advertisement,SPEC_INVALIDenvelopes, structural validation) and a real-CLI tier (template/inline renders, list-nodes, compile check, variants batch).
Engine pin: docs/ENGINE-PIN.md records the pinned commit, the reproducible scripts/fetch-engine.sh, and the reference binary sha256; the clone (engine/) is gitignored and never modified.
Built by The Native Creative.
Licensed under the MIT License — Copyright (c) 2026 The Native Creative.
Available Tools
4 toolsgraphite_list_nodesGraphite node registryA
List every node identifier the pinned graphene-cli understands (exits cleanly; cached in memory after the first call). Optional filter: case-sensitive substring match.
| Name | Required | Description | Default |
|---|---|---|---|
| filter | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the burden. It discloses that the command exits cleanly and is cached in memory after the first call, and explains the filter behavior (case-sensitive substring). However, it does not mention return format, error handling, or permission requirements. These gaps are moderate for a read-only list operation.
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?
A single, front-loaded sentence states the core purpose first, then adds behavioral details and parameter info without redundancy. Every clause earns its place.
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?
For a simple list tool with one optional parameter and no output schema, the description covers purpose, parameter behavior, and some operational characteristics. It lacks return type details, but these are not essential for a basic enumeration tool. Overall it is adequately complete.
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 coverage is 0%, so the description must explain the parameter. It does so effectively: 'Optional filter: case-sensitive substring match' clarifies the parameter's name, optionality, and matching semantics. This adds substantial value 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 description clearly states the verb 'List' and the resource 'every node identifier the pinned graphene-cli understands'. It distinguishes itself from sibling tools (render, validate, render_variants) by describing a listing operation, 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?
No explicit guidance on when to use this tool versus alternatives. It does not mention when not to use it or suggest a sibling for other needs. Usage is only implied by the description's content, not stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
graphite_renderGraphite renderA
Render a Graphite document to a validated artifact. Pass EITHER { templateId, params? } (params default to the template defaults; known templates: duotone-image, pattern-background, solid-background, text-on-background) OR { document } (inline legacy .graphite JSON, materialized under tmp/mcp-inline/). output: { format: png|svg|jpg|gif, width?, height?, outputName? } — raster formats require BOTH width and height; SVG may omit size; outputName is a base name (extension appended automatically). Artifacts land in /exports/. Every success reports sha256, artifact validation, and quarantine metadata: the pinned CLI always exits SIGSEGV after writing, so quarantinedAfterWrite: true is normal and expected.
| Name | Required | Description | Default |
|---|---|---|---|
| output | Yes | ||
| params | No | ||
| document | No | ||
| templateId | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full transparency burden and succeeds: it warns that the pinned CLI exits SIGSEGV after writing, that quarantinedAfterWrite: true is normal, that artifacts land in <projectRoot>/exports/, and that outputName gets an extension appended automatically. This is exactly the kind of non-obvious behavior an agent needs.
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 long but every clause carries operational weight, and it is front-loaded with the core purpose and the either/or input decision. The SIGSEGV warning is unusual but necessary, not filler.
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 no annotations and no output schema, the description covers the full invocation path: inputs, output format constraints, artifact location, and success metadata (sha256, validation, quarantine). It is sufficient for an agent to call the tool correctly, with only minor gaps around error behavior and gif sub-options.
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 prose must compensate. It does: templateId vs document, params defaulting behavior, known templates, output format enum, raster requiring both width/height, and outputName semantics are all explained. The only substantive gap is the gif animation object (mode/fps/frames/duration), which is left to the raw 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 identifies a specific operation: 'Render a Graphite document to a validated artifact,' which clearly separates it from list/validate operations. It does not explicitly mention graphite_render_variants, so sibling differentiation is mostly implicit rather than stated.
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 explicit input routing: pass either templateId/params or an inline document, lists known template IDs, and states output size rules. This is clear when-to-use context, but it never tells the agent when to prefer graphite_render_variants or graphite_validate_doc, so exclusions are absent.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
graphite_render_variantsGraphite batch rendererA
Render 2..20 parametric variants of ONE template sequentially (one GPU-heavy child at a time). Each variant is a full params object; artifacts land as -. under outputDir (default /exports/). A failing variant is reported per-item and does NOT abort the batch; the call is an error only when every variant fails.
| Name | Required | Description | Default |
|---|---|---|---|
| width | Yes | ||
| format | Yes | ||
| height | Yes | ||
| variants | Yes | ||
| outputDir | No | ||
| templateId | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does so richly: it discloses sequential execution, GPU-heavy resource behavior, per-item failure isolation, the fact that the call only errors if all variants fail, artifact naming, and outputDir defaulting. This is well beyond a mere 'renders variants' statement.
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?
Three sentences, each earning its place: the first states the core operation and constraints, the second covers artifact output, and the third covers failure semantics. No wasted words or repetition of schema 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?
For a tool with no annotations and no output schema, the description provides a strong operational contract: limits, ordering, failure behavior, output location, and error condition. The exact per-item report structure is not specified, but the essential information needed to call and interpret the outcome is present.
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 adds meaning for variants ('full params object'), templateId/index artifact naming, format in the filename, and outputDir default. Width and height are left implicit, but their purpose is reasonably inferable from the render context.
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 ('render') and resource ('parametric variants of ONE template') and clearly marks this as a batch operation distinct from the single-render sibling. The '2..20' range and 'ONE template' scope prevent confusion with graphite_render, graphite_list_nodes, and graphite_validate_doc.
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 conveys when to use the tool: when rendering multiple parametric variants of a single template sequentially. It does not explicitly name the sibling alternatives or state when not to use it, but the batch/one-template framing gives clear context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
graphite_validate_docGraphite document validatorA
Structurally validate a Graphite document WITHOUT rendering: JSON parse, network_interface shape, node-entry tuples, and export references. Pass EITHER { document } (inline) OR { documentPath }. Optional compileCheck: true adds the engine's own compile verdict (default false). Validation results are data: an invalid document returns valid: false with precise reasons instead of an error.
| Name | Required | Description | Default |
|---|---|---|---|
| document | No | ||
| compileCheck | No | ||
| documentPath | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden, and it delivers: it discloses that validation does not render, that compileCheck adds the engine's compile verdict with a default of false, and that invalid documents return valid: false with precise reasons instead of throwing an error. This gives the agent a strong model of observable behavior.
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?
Three sentences, front-loaded with the core purpose and the key distinction of not rendering. Every clause adds information about behavior, input, or results, with no filler.
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 no output schema and no annotations, the description still covers what is validated, how inputs are provided, the optional flag, and how results are returned. An agent has enough to call the tool correctly and interpret its outcome.
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 coverage is 0%, so the description must compensate. It clarifies the mutually exclusive choice between document and documentPath, explains that document is inline, and defines compileCheck's optional role and default. This is exactly the meaning the bare schema cannot convey.
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 uses a specific verb ('validate') and names its resource ('a Graphite document'), then enumerates the exact validation dimensions: JSON parse, network_interface shape, node-entry tuples, and export references. The phrase 'WITHOUT rendering' differentiates it from sibling rendering tools.
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?
It clearly indicates the tool is for structural validation, not rendering, and explains the two input modes: inline document or documentPath. It does not explicitly name the sibling alternatives or say 'use this instead of graphite_render', but the rendering exclusion and sibling list make the intended context clear.
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.
4 tool updates
v0.1.0- First observed
graphite_list_nodes - First observed
graphite_render - First observed
graphite_render_variants - First observed
graphite_validate_doc
TDQS
Scored across 4 tools
Each tool has a clearly distinct role: single rendering, batch variant rendering, node introspection, and structural validation. render and render_variants are related but the descriptions make the batch/parametric purpose unambiguous.
All tools follow the same snake_case graphite_ prefix and a clear verb_noun pattern: render, list_nodes, validate_doc, render_variants. The naming is uniform and predictable.
Four tools fit the narrow rendering/validation scope well and each tool earns its place. The count is within the ideal 3-15 range and does not feel padded or insufficient.
The surface covers the main workflows: render, render variants, validate, and inspect known nodes. Minor gaps exist, such as no template-listing tool or document editing capability, but the stated domain appears rendering-focused.
Maintenance
Related MCP Connectors
Build and run visual creative-production workflows from your AI agent.
Deterministic image rendering for agents: JSON template in, on-brand PNG out.
Generate reproducible image, video, and audio assets with leading models and your own provider keys.
Design, save, and run outcome-aligned AI workflows and verifiers, with reliable image output.
Related MCP Servers
- AlicenseAqualityDmaintenanceA deterministic video rendering engine that enables AI agents to create programmable, reproducible videos via MCP protocol.1419 npm2MIT
- AlicenseNot gradedqualityCmaintenanceEnables agentic creators to generate images, videos, and audio, run taste-based scoring, and publish through human-gated signed manifests using their own keys and local provenance.MIT
- AlicenseAqualityCmaintenanceEnables AI agents to generate game-ready 3D assets from reference images with PBR textures, and to retexture meshes the user already owns, while recording full provenance for every generated file.2010MIT
- AlicenseNot gradedqualityAmaintenanceEnables AI assistants to create Material Maker node graphs from natural language, validate them against the node catalog, and render them headlessly to PBR texture maps and editable .ptex files.5MIT