inkscape
Drives the user's live, open Inkscape 1.4 window on macOS so an AI agent and the human can edit the same document simultaneously, with the agent's edits appearing immediately and landing on Inkscape's normal undo stack. Provides reading tools (status, outline, find, inspect, selection, render with object boxes/ids drawn, window screenshots, change diffs), layout tools built on Inkscape's own commands (align, distribute with equal or exact gaps, move by or to millimetre coordinates, resize to an exact visual width/height, styling, attributes, grouping and z-order, undo/redo history), content tools (setting or find/replacing text, inserting SVG/PNG/JPEG files, drawing arbitrary SVG markup in millimetres, and running arbitrary inkex Python code), and export tools (PDF, SVG, EPS, PS or PNG of the page, drawing, selected objects or a region) plus access to any of Inkscape's ~1,070 searchable actions. Every read and edit carries a revision fingerprint with an optional expected_revision guard to prevent clobbering concurrent human edits, and edits are verified as landed.
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., "@inkscape align the three section headers to the left edge of the sidebar"
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.
inkscape-live-mcp
An MCP server that lets an AI agent (built for Claude Code) work in the Inkscape window you have open, at the same time as you. Its edits appear in your window as they happen and sit on Inkscape's normal undo stack, so Cmd+Z takes them back. Your edits are visible to the agent the next time it looks, because it reads the live document, not a saved file.
macOS, Inkscape 1.4.
Why
Shared, live editing. No export–edit–reimport loop. You drag a panel; the agent sees it moved. The agent aligns six headers; you watch them snap into place and can undo it.
Exact layout by numbers. Every coordinate is millimetres from the page's top-left corner, measured on the visual bounding box (stroke and glyph edges included) that Inkscape itself reports. "Align these to the left edge of that panel", "stack them 8 mm apart", "make this exactly 70 mm wide" are one call each, computed, not eyeballed; there is no render–nudge–render loop.
Safe to share a document. Every read returns a revision; an edit given that revision refuses to run if you have changed the drawing since. Every edit reports whether it actually landed.
Related MCP server: inkscape-mcp
Requirements
macOS (tested on macOS 27, Apple Silicon) and Inkscape 1.4 (tested on 1.4.4) in
/Applications.Homebrew for
dbus(the installer runsbrew install dbusif needed).uv (
brew install uv).An MCP client. The installer registers the server with Claude Code; any client that runs stdio servers works.
Install
git clone https://github.com/declanbx/inkscape-live-mcp.git
cd inkscape-live-mcp
./install.shinstall.sh is idempotent (re-run it after pulling). It builds .venv with uv, links extension/ into
Inkscape's user extensions folder, creates ~/Applications/Inkscape (Claude).app, and runs
claude mcp add --scope user inkscape …. Flags: --no-register (skip the Claude Code registration),
--no-app (skip the launcher). For another client, register
<checkout>/.venv/bin/python <checkout>/run_server.py as a stdio server.
Optional, for Claude Code: cp -r skills/inkscape-live ~/.claude/skills/ adds a skill that teaches the
agent the workflow (route by cost, lay out by numbers, share the window safely).
Quick start
Open Inkscape (Claude).app, or drop an
.svgon it. It is the same Inkscape with your usual preferences, started so that the agent can reach it. (A window opened the normal way is not reachable; see How it works.)In Claude Code, ask in plain words: "align the six section headers to the left edge of the Summary panel", "space column 2's boxes 8 mm apart", "what did I change?", "make these the same width as the map" (with objects selected), "replace the title".
Save with Cmd+S as usual. The agent never saves over your file; it can write a copy (
save_copy).
Tools (server name inkscape)
Reading | Layout: Inkscape's own commands, ~0.1–1 s, one undo step each | Content: one undo step per batch | Other |
|
|
|
|
Safety nets on every call
Revision guard. Every read and edit ends with
revision <id>, a fingerprint of every object's id and visual box in paint order, read live from Inkscape. Every editing tool takes an optionalexpected_revision; if the document no longer matches it, the tool refuses, changes nothing, and lists what changed since (moved/added/removed objects).changesreports the current revision and makes it the new baseline;changes(since_revision=…)diffs against any revision read this session.historytakes it too, so an undo never takes back something you did after the agent's edit.Landed verification. After each edit the touched objects' live boxes are compared with what the edit planned: exact positions for moves and gaps, a shared edge for alignment, equal spacing for distribution, new ids present and deleted ids gone for inserts and deletes. The result says
Landed: confirmed,NOT LANDED as planned: …(with what is where), orLanded: unconfirmedfor an edit with no geometric trace, such as a colour.No-op batches add no undo step. The bridge compares the document before and after a batch; if it is byte-identical it hands nothing back, so Inkscape adds no undo entry and your next Cmd+Z still undoes the last real change.
Exports are read back.
exportreopens the file it wrote and checks it against what was asked: the PDF page size in mm (read with macOS Quartz), the PNG's pixel size, the SVG's document size, and whether fonts are embedded or text was turned into outlines (text_to_path=True).Raw actions say what Inkscape did. If Inkscape logs a rejection (
transform-translate abc→ "requires two comma separated numbers"),actionsreturns an error with that reason; otherwise it reports whether the geometry changed, because Inkscape ignores some malformed arguments without a word.Uncertain completion is reported, never retried. If Inkscape does not answer in time (a dialog is open, a huge document), the call returns
UNCERTAIN COMPLETIONinstead of an error or a silent retry: the edit may still apply. The next call waits for Inkscape to finish it before reading or editing.
Cost model
Measured with tests/bench.py on a synthetic A0 document of 50,082 objects (15 MB, one 8 MB embedded
image), Apple Silicon laptop, through the MCP server:
Operation | Time |
status | 0.3 s |
align / move / style (native) | 0.6–0.7 s |
distribute five panels to an exact gap | 1.1 s |
changes (geometry diff) | 0.3 s |
find / outline (tree cached) | 0.25 s |
first outline of an unedited file (read from disk) | 1.6 s |
first outline of an edited document (via the bridge) | 1.7 s |
render the whole page / one panel | 0.8 s / 0.4 s |
bridge edit (text, add_svg, insert, python) | 3.0–3.2 s |
bridge batch that changes nothing | 2.2 s |
undo of a bridge edit | 1.2 s |
export the page as PDF / one panel as PDF (via a headless copy) | 3.4 s / 3.0 s |
export the page as PNG at 150 dpi | 1.1 s |
On a 2,000-object figure everything is under 0.6 s. Bridge edits grow with document size, because
Inkscape serialises the whole document to the extension and reloads its result: on a real 100,000-element
poster with heavily clipped figures one bridge edit took about 16 s. So batch content edits: text
takes a list, add_svg and python take many elements at once.
How it works
Native route.
launch.shstarts a private D-Bus session bus (~/.inkscape-mcp/bus.sock) and opens Inkscape as the unique applicationorg.inkscape.Inkscape.claudeon it (--app-id-tag=claude). Inkscape then publishes its actions overorg.gtk.Actions;inkscape_live_mcp/bus.pycalls them and reads query output (query-all,select-list) from Inkscape's stdout, whichlaunch.shlogs to a file.Bridge route. For what no action can do (text content, inserting markup or files, reparenting, arbitrary edits),
extension/claude_bridge.pyis an effect extension the server triggers over the same bus. It reads a request file, applies the whole batch atomically (if any operation fails, nothing changes), and hands the document back as one undo step.Reading. Boxes come from
query-allon every call. The XML tree is cached and re-read only when the set of objects changes; when the window shows no unsaved changes it is read straight from the file.Choosing an instance.
INKSCAPE_MCP_TAG(defaultclaude) names the instance andINKSCAPE_MCP_STATE(default~/.inkscape-mcp) its state folder, so several instances can run side by side; the test suite uses this to run on its own instance.
Quirks, and how they are handled
Launch constraints silently disable Python extensions when Inkscape is exec'd from a shell, so no edit through the bridge would work.
launch.shtherefore starts Inkscape through LaunchServices (open -n -a … --env … --args --app-id-tag=…).GTK on macOS exports no
/window/Nobjects on D-Bus, only the application and/document/N(confirmed on 1.4.4 by the test suite). Every operation uses application-level actions plus/document/Nfor undo, redo and extensions; window-level actions such as save and zoom are left to you.Open-file limit. Inkscape 1.4.2 on macOS can crash the first time it runs an extension if it inherits a ~1M soft
RLIMIT_NOFILE, common under IDEs and agent hosts (reported in aravindev/inkscape_mcp#33). Launched through LaunchServices, Inkscape does not inherit the caller's limit: from a shell with 1,048,576 the extension sees 2,560 (Inkscape 1.4.4). The test suite asserts it stays at or below 4,096.Wrong boxes for groups that contain a nested
<svg>. Inkscape 1.4 adds the nested viewBox size, in parent units, to the group's box (an inserted 70 mm legend is reported 105.8 mm wide). The server rebuilds those groups' boxes from their children and aligns them with computed moves rather than Inkscape's own Align.Export settings persist inside a running Inkscape, and some cannot be switched back (measured on 1.4.4): once text-to-path is on, setting it off has no effect, and the page/drawing area mode can be changed but never unset. PNG exports therefore pass an explicit area and pixel size every time; PDF, SVG, EPS and PS exports write the live document to a temporary copy, crop the copy to the page, the drawing, the chosen objects or a region, and export it with a fresh headless Inkscape. Your window's export state is never touched, and a region works for PDF too (Inkscape's own vector export ignores one).
Inkscape's own "drawing" area is wrong for documents holding a nested
<svg>(the bug below), soexport(area="drawing")uses the corrected boxes.Some action arguments are ignored silently. A bare or misspelled
object-aligndoes nothing and logs nothing;transform-translate abclogs a reason. See "Raw actions say what Inkscape did".select-by-idadds to the selection: every command clears it first, then restores your selection.export-areacannot be cleared once set, so every render passes an explicit area.A saved copy keeps its source's internal document name, so the front window is matched by file name first.
Two sessions driving one Inkscape would interleave on the bridge files and the stdout log, so every client call holds a cross-process lock (
~/.inkscape-mcp/client.lock).
Limitations
macOS only. On Linux the same D-Bus mechanism works natively; see Related work.
The revision sees geometry, not paint. A colour-only or attribute-only edit you make does not change the revision, and
Landedcannot confirm such an edit.changes(deep=True)re-reads the document and reports text and style differences.The guard is a check, not a lock: an edit you make in the milliseconds between the check and the agent's edit is not caught.
Bridge edits reload the whole document: seconds on large documents (see Cost model).
Tools act on the front window. With several documents open, the front one is identified from the window title; click the window you want if it is ambiguous.
Inkscape 1.4 only. Other versions are untested.
Security
No network access, no API keys, no telemetry. Everything runs on your machine.
python(andactions) run arbitrary code inside Inkscape's Python as you, by design, with the same trust as the agent's own shell access. Run the server only for agents you would give a shell to.The bus is a private
dbus-daemonwhose socket lives in~/.inkscape-mcp(mode 0700, as are its bridge files and logs).
Tests
.venv/bin/python tests/test_units.py # no Inkscape needed
.venv/bin/python tests/e2e.py # 62 checks over MCP on a private Inkscape instance
.venv/bin/python tests/bench.py # the cost table abovee2e.py and bench.py launch their own Inkscape with a fresh tag, a private bus, a temporary state folder
and a throwaway Inkscape profile, check before any edit that the server is driving that instance, and
quit it afterwards (--keep leaves it open). They never touch a window you have open.
Related work
aravindev/inkscape_mcp is the closest prior art: the same mechanism, a running Inkscape driven over D-Bus plus inkex extensions, together with a headless CLI mode and a much broader tool surface, on Linux and Windows (macOS support is an open pull request). This project adds layout by Inkscape-measured visual boxes in millimetres (with the nested-
<svg>box correction), exact align/distribute/resize tools, renders annotated with object ids, a change diff and revision guard for editing alongside a person, edit-landed verification, and macOS support today. The check for actions Inkscape rejected follows its CLI wrapper's stderr scan.Shriinivas/inkmcp: live control of a running Inkscape over D-Bus with an extension and inkex code execution (Linux).
tspspi/mcpinkscape: an offline SVG backend plus an optional native C++ Inkscape extension for live editing with snapshots, polling and revision-conflict protection; the revision guard here follows its
expected_revisionidea.P1oN/inkscape-mcp-server: a native Rust server with guarded, verified edits; the landed check, the "uncertain completion" result and the no-op-adds-no-undo rule here follow its design.
Files
launch.sh (bus + Inkscape) · install.sh · run_server.py (entry point) · inkscape_live_mcp/
(bus.py D-Bus and stdout log, client.py operations, revision guard and landed checks, svgtree.py
tree and box model, server.py MCP tools) · extension/ (claude_bridge.inx/.py) · tests/
(_instance.py private test instance, e2e.py, bench.py, test_units.py, fixtures/) ·
skills/inkscape-live/SKILL.md (a Claude Code skill for using the tools well).
State and logs: ~/.inkscape-mcp/ (inkscape.out.log, inkscape.err.log, bridge/bridge.log, renders).
Troubleshooting
Tools say "not running": open
Inkscape (Claude).app(a normal Inkscape window is not reachable).UNCERTAIN COMPLETIONor a stuck call: callwindowto see Inkscape; a dialog may be open.After updating, re-run
install.shand restart Inkscape (Claude), which reads extensions at startup.
License
MIT, see LICENSE.
Available Tools
24 toolsactionsA
Run any of Inkscape's ~1,070 actions, or search them. run: ["select-all", ["object-align", "left page"], ["transform-rotate", 90]] — returns what Inkscape printed, any complaint it logged (an action it rejected makes this an error), and whether the geometry changed (Inkscape ignores some malformed arguments silently). search: substring over action names and descriptions. Most act on the current selection; use selection(set_ids=…) first. To write files, prefer export.
Inputs: run, search, expected_revision. Returns: result
add_svgA
Draw anything by writing SVG markup: shapes, lines, arrows, text, groups, gradients.
With x_mm/y_mm the markup's coordinates are units ('mm' or 'px') from that page point, so
'' at x_mm=20 is a 40×10 mm bar at 20 mm.
Without them the markup is written as-is in the parent's own user units. Colliding ids are renamed.
Inputs: markup (required), show, x_mm, y_mm, +4 more.
alignA
Align objects exactly. edges: 'left' | 'hcenter' | 'right' | 'top' | 'vcenter' | 'bottom', or one of each ('left top'). to: an object id to align to (it stays put), or first | last | biggest | smallest | page | drawing | selection. to_mm: align that edge to a page coordinate instead. as_group moves the ids as one block. expected_revision: refuse if the document changed since.
Inputs: ids (required), edges (required), to, show, +3 more.
attributesB
Set (or with null, remove) XML attributes: 'inkscape:label', 'width', 'rx', 'xlink:href', 'id'…
Inputs: ids (required), attrs (required), expected_revision. Returns: result
changesA
What the user changed since you last looked or edited (or since since_revision): objects
added, removed, moved or resized (exact, cheap). Reports the current revision and makes it the new
baseline. deep=True also re-reads the document to report text and style edits (slow on posters).
Inputs: deep, since_revision. Returns: result
distributeB
Space objects along x or y. Without gap_mm: Inkscape's equal distribution between the outermost objects (mode gap | centers | left | right | top | bottom). With gap_mm: stack them in their current order with exactly that gap, starting at the first object (or at start_mm).
Inputs: ids (required), axis, mode, show, +3 more.
exportA
Export the live document (unsaved changes included) to PDF, SVG, EPS, PS or PNG; the format comes from the file suffix (or format=). What to export: area='page' (default) or 'drawing' (everything drawn); or ids=[…] for those objects (only=False keeps whatever overlaps them); or region_mm=[x0, y0, x1, y1] (PNG only). dpi: PNG resolution, and the resolution filter effects are rasterised at in PDF/EPS/PS. text_to_path=True turns text into outlines (for printers that cannot take fonts). background: PNG background colour (default transparent). The file is read back afterwards: page size in mm, pixel size, fonts embedded.
Inputs: path (required), dpi, ids, area, +5 more. Returns: result
findA
Find objects by any combination of: text content (case-insensitive substring), inkscape label, id regex, tag (text, g, rect, image, path…), a style substring (e.g. 'fill:#4b80ad'), or a page region [x0, y0, x1, y1] in mm (within = fully inside, overlaps = touches). top_level_only drops matches nested inside another match.
Inputs: tag, text, label, style_has, +5 more. Returns: result
historyA
Undo or redo n steps in the live document (the same stack as the user's Cmd+Z). Pass expected_revision so an undo never takes back something the user did after your edit.
Inputs: n, op, expected_revision. Returns: result
insertC
Place a figure file into the document with its top-left at (x_mm, y_mm), scaled to width_mm or height_mm (aspect kept). .svg is inlined as editable vector (ids namespaced so nothing collides); .png/.jpg are embedded (embed=False links the file instead).
Inputs: path (required), id, show, x_mm, +7 more.
inspectB
Everything about specific objects: tag, label, where it sits in the tree, bounding box, every attribute (embedded images summarised), parsed style, full text, and its children. xml=True adds the raw XML (truncated to max_chars).
Inputs: ids (required), xml, max_chars. Returns: result
launchB
Start Inkscape (Claude) if needed and open path in it (an .svg; Inkscape also opens .pdf/.ai/.eps).
Inputs: path. Returns: result
moveB
Move by (dx_mm, dy_mm) and/or place so the anchor point ('top-left', 'center', 'bottom-right', 'top-center'…) of the ids' joint box lands at (x_mm, y_mm). each=True places every object separately.
Inputs: ids (required), each, show, x_mm, +5 more.
outlineA
The object tree with ids, labels, text snippets and live bounding boxes (mm).
root: start below this id (default: the whole page). depth: levels to expand; deeper content is summarised as counts. refresh: force re-reading the document (normally automatic).
Inputs: root, depth, refresh, max_lines. Returns: result
pythonA
Run Python (inkex) against the live document in one undoable step — anything the other tools
cannot express. In scope: svg (root element), by_id(id), selected (ids), inkex, etree, Transform,
frames (frames.content(el) maps an element's coordinates to page px), to_px('3mm'), qname('inkscape:label'),
unique_id(base), parse_style/style_str. print() output and result = … come back. readonly=True
skips the document reload (fast inspection). timeout_s: give up waiting after this long (the
result is then UNCERTAIN COMPLETION: the code may still finish and apply).
Inputs: code (required), readonly, timeout_s, expected_revision. Returns: result
renderA
Render the LIVE document to an image you can see.
region_mm [x0, y0, x1, y1] or around=[ids] (+margin) choose the area; default is the whole page.
only=True hides everything except the around objects. annotate draws each object's box and id
on the image: pass a list of ids, 'children:' for that object's children, or 'top' for the
main parts in view (the shallowest level showing several objects, wrappers skipped) — the
fastest way to learn which id is which.
Inputs: only, around, annotate, width_px, +3 more.
resizeB
Resize to an exact visual width or height (or by a scale factor), keeping the anchor corner fixed. keep_aspect=False with both width_mm and height_mm stretches (goes through the slower extension).
Inputs: ids (required), each, show, scale, +5 more.
save_copyA
Write the live document (unsaved changes included) to path as Inkscape SVG. Does not change
which file the window is editing; the user saves the real file with Cmd+S.
Inputs: path (required). Returns: result
selectionA
Read what the user currently has selected (ids + boxes), or replace the selection with set_ids (useful to show the user which objects you mean).
Inputs: set_ids. Returns: result
statusA
Is Inkscape (Claude) running; which documents are open; what is frontmost; what the user has selected.
Inputs: no named parameters. Returns: result
structureB
Rearrange objects. op: delete | duplicate | group | ungroup | pop_out (out of its group) |
raise | lower | top | bottom (z-order) | clone | unclone | reparent (move into parent at
index, keeping the visual position). group takes optional new_id and label.
Inputs: op (required), ids (required), index, label, +3 more. Returns: result
styleB
Set CSS style properties on objects, e.g. {"fill": "#4B80AD", "font-size": "11pt", "stroke-width": "0.5", "opacity": "0.8"}. A null value removes the property. Applies to groups too (children inherit unless they override — inspect them if a colour does not take).
Inputs: ids (required), props (required), show, expected_revision.
textC
Change text in ONE undoable batch. edits: [{"id": text id, "text": "new text; \n for new lines"}] keeps the first run's style. replace: [{"old": "…", "new": "…", "ids": optional scope}] swaps a substring inside existing runs, preserving mixed formatting (bold words, superscripts).
Inputs: show, edits, replace, expected_revision.
windowA
Screenshot of the Inkscape window itself (the user's view: zoom, selection handles, any open dialog). Use when something seems stuck or to see what the user is looking at.
Inputs: no named parameters.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 24 tools
Most tools have clearly distinct purposes (e.g., outline vs inspect, find vs selection), but a few pairs overlap: export vs save_copy (both write files), move/align/distribute (positioning), actions vs python (general escape hatches), and style vs attributes (both set properties). Detailed descriptions help disambiguate, making it mostly distinct with a few confusable pairs.
All tool names are lowercase, using underscores only for multi-word names (save_copy, add_svg). The style is consistent, but the set mixes verbs (move, export) and nouns (selection, attributes, text), lacking a uniform verb_noun pattern.
24 tools is slightly above the typical 3-15 range, but the domain (full Inkscape control) is complex and each tool addresses a distinct area. Specialized tools plus general escape hatches (actions, python) are justified, though the set feels heavy.
The surface covers state reading, object manipulation, text/style/attribute editing, export, and general escape hatches. Minor gaps exist (no direct overwrite save, document close/switch), but actions and python provide workarounds.
Maintenance
Related MCP Connectors
Connect AI assistants to your approved computer for files, commands, browser and desktop tasks.
Lets AI agents use a real human as a tool: visual checks, taste, phone calls, unblocking, approvals
Give your AI hands on every computer you own. Remote control of your Windows, Mac and Linux machines from one ChatGPT, Claude or Cursor chat — even from your phone. Ask it to find out why the office server is crawling, update every PC in the house, sort out your mom's laptop from another city, or ship the site from your work machine while you're on a train. It runs commands, reads files and takes a screenshot when it needs to see the screen. You decide what each computer allows, and you can cut any of them off in a second.
Lets AI assistants draw: logos, posters, illustrations and scenes, as editable SVG and PNG.
Related MCP Servers
- AlicenseBqualityBmaintenanceMCP server that lets AI agents drive Inkscape — interactively alongside the GUI or headlessly from the CLI8966 PyPI93MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to control Inkscape for vector graphics editing via MCP tools.98MIT
- AlicenseAqualityBmaintenanceEnables AI agents to inspect, edit, validate, render, and export SVG/Inkscape documents safely with reversible operations and risk-classed tools.8711MIT

Rayzia MCPofficial
AlicenseNot gradedqualityDmaintenanceEnables AI agents to drive a live SVG/vector editor, allowing a full observe-and-act loop on a canvas with real tools, state reading, and PNG rendering.2MIT