12 KiB
VectorCraft MCP server
vectorcraft-cli mcp runs a Model Context Protocol server on stdio
(newline-delimited JSON-RPC 2.0, protocol 2025-06-18; 2025-03-26 and 2024-11-05 also accepted). Agents use it to
draw, inspect and look at VectorCraft documents.
It has two backends:
| Mode | How | What works |
|---|---|---|
| Remote | vectorcraft-cli mcp --connect 127.0.0.1:7979 (the app must run with vectorcraft --control 7979) |
Everything. Tool calls are forwarded over the control protocol, so you watch the app change live. |
| Headless | vectorcraft-cli mcp --headless |
An in-process engine session with a CPU renderer. Everything except the UI-only tools (inspect_ui, type_text, open_panel, screenshot {window:true}). |
With no flag, the server tries 127.0.0.1:7979 and falls back to headless. Logs go to stderr; stdout carries
only protocol messages.
Build and register
cargo build --release -p vectorcraft-cli
claude mcp add vectorcraft -- "$PWD/target/release/vectorcraft-cli" mcp
# or pin a mode:
claude mcp add vectorcraft-headless -- "$PWD/target/release/vectorcraft-cli" mcp --headless
claude mcp add vectorcraft-app -- "$PWD/target/release/vectorcraft-cli" mcp --connect 127.0.0.1:7979
Other clients use the same command in their JSON config:
{"mcpServers": {"vectorcraft": {"command": "/abs/path/target/release/vectorcraft-cli", "args": ["mcp"]}}}
For a live session, start the app first: cargo run --release -p vectorcraft -- --control 7979.
Tools
Coordinates are points in document space: y points down, the origin is the first artboard's top-left, and a new
document is 612 × 792 (US Letter). New objects become the selection. Most commands act on the selection or on
explicit ids.
Paint values (fill, stroke) accept "#rrggbb", "none", [r,g,b] (0..1), {"c","m","y","k"}, {"gray"},
or a full paint.setFill params object ({"gradient": …}, {"swatch": "name"}).
| Tool | Arguments | Notes |
|---|---|---|
list_commands |
{filter?, enabledOnly?} |
The command catalogue: id, label, menu, shortcut, params doc, enablement. |
run_command |
{command, params?} |
Runs any command. Use it for everything without a dedicated tool. |
inspect_document |
{} |
Artboards, layer tree (ids, kinds, bounds, paint), selection, history, tool. |
inspect_ui |
{} |
UI state. Remote mode only. |
select_tool |
{tool} |
selection, directSelection, pen, rectangle, ellipse, polygon, star, lineSegment, … |
pointer_gesture |
{events:[{kind,x,y,mods?}], tool?, mods?} |
kind is one of down, drag, up, move, doubleclick. Events go through the same path as the mouse. |
draw_path |
{points | d, closed?, fill?, stroke?, strokeWidth?} |
points is [[x,y],…] or [{x,y,in?,out?,smooth?},…]. d is SVG path data. |
draw_shape |
{shape, …geometry, fill?, stroke?, strokeWidth?} |
rectangle/ellipse: x,y,width,height (plus radius for corners). polygon: cx,cy,radius,sides. star: cx,cy,radius1,radius2,points. line: x1,y1,x2,y2. |
set_paint |
{fill?, stroke?, strokeWidth?, ids?} |
Applies to the selection (or ids) and becomes the default for new art. |
press_key |
{key, mods?} |
Remote: a real key event. Headless: runs the command or tool bound to that shortcut, or sends the key to the busy tool. |
type_text |
{text} |
Remote only. |
invoke_menu |
{command, params?} |
Invokes a menu item by command id. Includes UI commands such as view.* and window.* in remote mode. |
open_panel |
{panel} |
Remote only. |
screenshot |
{path?, scale?, artboard?, window?} |
Returns MCP image content (image/png, base64) plus a text block. Renders the artboard; window:true captures the app window (remote only). |
open_file |
{path} |
Opens any readable file as a new active document: .vectorcraft/.drawcraft, .svg/.svgz, .pdf/.ai, .ait, PNG, JPEG, GIF, WebP, TIFF, BMP (an image opens as a document of its pixel size). Templates open as a new untitled document. run_command document.formats lists the formats. |
save_file |
{path?} |
Saves in the native .vectorcraft format. |
export |
{path?, format?, scale?, artboard?, range?, selection?, outlineText?, options?} |
svg, pdf, png, jpg, webp or vectorcraft (the list comes from document.formats). When format is omitted, it comes from the path's extension. PDF writes one page per artboard: all of them, or artboard (0-based) / range ("1-3, 5", 1-based); the other formats write one artboard. options carries more format options (e.g. {"quality": 80} for JPEG). selection: true exports the selected objects cropped to their bounds; outlineText: true writes SVG text as paths. Template layers are left out, live effects are kept, and exporting vectorcraft never changes the document's path. Without path the bytes come back as dataBase64. Both backends run the same document.export call. |
add_text |
{text, x?, y?, width?, height?, path?, mode?, pathEffect?, size?, font?, color?} |
Point type at (x, y); area type with width/height; or path + mode (area/onPath) to flow text in or along a path, with pathEffect (rainbow, skew, 3dRibbon, stairStep, gravity). |
apply_effect |
{effect?, params?, ids?} |
Appends a live effect. Without effect, returns the effect catalogue with parameters and defaults. |
pathfinder |
{operation, ids?} |
unite, minusFront, intersect, exclude, divide, trim, merge, crop, outline, minusBack. For a live version, apply the pathfinder.* effect to a group. |
transform |
{ids?, dx?, dy?, rotate?, scale?, scaleX?, scaleY?, reflect?, shear?, origin?, copy?} |
Runs move, rotate, scale, reflect, shear in that order. With copy, the first step duplicates. |
create_graph |
{type?, x, y, width, height, csv? | series?, categories?, rows?} |
Nine graph types: column, stacked column, bar, stacked bar, line, area, scatter, pie and radar. Edit later with graph.setData / graph.setType via run_command. |
text_wrap |
{ids?, offset?, invert?, release?} |
Area type below the objects (same layer) flows around them. |
undo / redo |
{} |
Appearance stacks: an object can carry several fills and strokes (appearance.addFill, appearance.addStroke), indexed
in paint order (0 is painted first, the bottom row of the Appearance panel). paint.setFill, paint.setStroke,
stroke.set, stroke.setAdvanced, paint.editGradient, paint.setGradientGeom and transparency.set take
item to edit one of them; run_command appearance.setActiveItem {"index": n} makes that row the target of later
calls that omit item (as clicking the row in the Appearance panel does) until the selection changes.
inspect_document reports it as paint.appearanceItem. Live effects take the same item to apply to one fill or
stroke instead of the whole object: run_command effect.apply {"effect": "path.offsetPath", "item": 0}, and
effect.remove, effect.setParams (visible toggles one) and effect.duplicate address that item's effects;
apply_effect uses the active item. effect.list reports each object's item effects under applied[].items.
Errors (unknown tool, bad arguments, a disabled or failing command) come back as a normal result with
isError: true and a message. The model can read the message and retry.
Swatches go through run_command: swatch.list lists every swatch with its colour group, kind and colour;
swatch.edit {name, newName?, color?, mode?, global?, spot?} edits one (fills, strokes and text linked to a
global or spot swatch follow its colour and name, as one undo step); swatch.delete {names, unlink?} deletes
swatches and colour groups in one undo step (art using a deleted global swatch keeps its colour, unlinked).
swatch.new {color?, mode?, spot?, global?, group?} saves a colour (into a colour group with group), gradient
or pattern; swatch.newGroup {fromArtwork: true, toGlobal?, includeTints?} makes a colour group of the selected
art's colours (by default as global swatches the art links to).
Resources
| URI | Content |
|---|---|
vectorcraft://document |
document.inspect summary (JSON) |
vectorcraft://document/json |
The complete document model (JSON) |
Examples
Draw, look, export:
{"name":"draw_shape","arguments":{"shape":"rectangle","x":72,"y":72,"width":200,"height":120,"radius":12,"fill":"#1e88e5","stroke":"none"}}
{"name":"draw_shape","arguments":{"shape":"star","cx":400,"cy":300,"radius1":90,"radius2":40,"points":5,"fill":"#ffc107","stroke":"#000000","strokeWidth":2}}
{"name":"draw_path","arguments":{"d":"M72 400 C 150 300 250 500 330 400","stroke":"#e53935","strokeWidth":4,"fill":"none"}}
{"name":"screenshot","arguments":{"scale":0.5}}
{"name":"export","arguments":{"path":"/tmp/art.svg"}}
Long-tail commands:
{"name":"list_commands","arguments":{"filter":"align"}}
{"name":"run_command","arguments":{"command":"select.all"}}
{"name":"run_command","arguments":{"command":"object.align","params":{"align":"left"}}}
{"name":"run_command","arguments":{"command":"object.group"}}
Drive a tool like a mouse:
{"name":"pointer_gesture","arguments":{"tool":"ellipse","events":[
{"kind":"down","x":100,"y":100},{"kind":"drag","x":150,"y":140},{"kind":"up","x":200,"y":180}]}}
Edit a gradient with the Gradient tool's annotator (the bar runs along the vector; stop chips sit 10 px under
it, midpoint diamonds 7 px above): a click on the bar adds a stop, dragging a chip moves it (drag it off the bar to
delete it, hold Alt to copy it), dragging the end handle changes the vector. The selected stop
(gradient.selectStop) takes Delete and ←/→:
{"name":"run_command","arguments":{"command":"paint.setFill","params":{"gradient":{"start":[100,150],"end":[200,150]}}}}
{"name":"pointer_gesture","arguments":{"tool":"gradient","events":[{"kind":"down","x":130,"y":150},{"kind":"up","x":130,"y":150}]}}
{"name":"pointer_gesture","arguments":{"events":[{"kind":"down","x":130,"y":160},{"kind":"drag","x":160,"y":160},{"kind":"up","x":160,"y":160}]}}
{"name":"press_key","arguments":{"key":"Delete"}}
Raw protocol (for debugging):
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"sh","version":"0"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"draw_shape","arguments":{"shape":"ellipse","x":10,"y":10,"width":100,"height":80,"fill":"#ff0000"}}}' \
'{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"inspect_document","arguments":{}}}' \
| target/release/vectorcraft-cli mcp --headless
Headless batch CLI
vectorcraft-cli commands # command catalogue (JSON)
vectorcraft-cli run --in in.svg \
--cmd select.all --cmd paint.setFill --params '{"color":"#ff0000"}' \
--export out.svg --export out.png --scale 2
vectorcraft-cli run --cmd file.new --params '{"width":800,"height":600}' \
--cmd shape.star --params '{"cx":400,"cy":300,"radius1":200,"radius2":90}' \
--export star.vectorcraft
run prints one JSON line per step (open, cmd, export) and exits non-zero on the first failure. --params
applies to the --cmd just before it. run also accepts the host commands file.open, file.save, file.export,
file.exportForScreens and tool.select. run --in, convert and info read every format document.open reads
(vectorcraft-cli --help lists them).