inkscape-mcp-server
# inkscape-mcp-server
[](https://github.com/code-and-crypto/inkscape-mcp-server/actions/workflows/ci.yml)
[](https://www.python.org/)
[](LICENSE)
[](pyproject.toml)
A standard-library MCP server for editing, analysing and exporting SVG files with
Inkscape 1.2+. Every Inkscape-backed operation is checked against its output file
rather than its exit code.

## Quick start
You need Python 3.9 or newer and Inkscape 1.2 or newer.
```bash
git clone https://github.com/code-and-crypto/inkscape-mcp-server
cd inkscape-mcp-server
python server.py --check
```
`--check` runs a self-test and prints what it found:
```
self-test: 6/6 passed
PASS tool schemas resolve: 23 tools, all parameters known
PASS inkscape png export
PASS boolean union via object-to-path chain
PASS python optimiser
PASS validator
PASS analyser
```
Then register it with your MCP client:
```json
{
"mcpServers": {
"inkscape-mcp-server": {
"type": "stdio",
"command": "python",
"args": ["/path/to/inkscape-mcp-server/server.py"],
"env": { "INKSCAPE_BIN": "/path/to/inkscape" }
}
}
}
```
`INKSCAPE_BIN` is optional. Without it the server checks `PATH` and the usual install
locations, and `server_info` reports what it found. There is also a pip route:
```bash
pip install git+https://github.com/code-and-crypto/inkscape-mcp-server
inkscape-mcp-server --check
```
## Example
The files below are in [`examples/`](examples/), and the output is what the server
actually returned. Start with two overlapping shapes and an unused gradient in
`<defs>`:
```xml
<svg xmlns="http://www.w3.org/2000/svg" width="240" height="240" viewBox="0 0 240 240">
<defs>
<linearGradient id="unused">…</linearGradient>
</defs>
<g id="empty"/>
<circle id="left" cx="95.00000001" cy="120" r="62" style="fill:#3d5afe"/>
<rect id="right" x="105.0000000" y="58" width="124" height="124" rx="14" style="fill:#00bfa5"/>
</svg>
```
Merge the two shapes:
```json
{ "name": "path_boolean",
"arguments": { "input_path": "examples/badge.svg",
"output_path": "examples/badge_union.svg",
"operation": "union",
"object_ids": ["left", "right"] } }
```
```json
{ "success": true, "message": "union applied" }
```
`path-union` does nothing to a `<rect>` or a `<circle>`, because Inkscape's boolean
operations work on paths. The tool inserts `object-to-path` into the chain first, so
the call above merges the shapes instead of silently returning the original file.
Then clean it up:
```json
{ "name": "svg_optimize",
"arguments": { "input_path": "examples/badge_union.svg",
"output_path": "examples/badge_final.svg" } }
```
```json
{
"success": true,
"message": "941 -> 475 bytes (466 saved, 49.5% smaller)",
"bytes_before": 941, "bytes_after": 475, "percent_saved": 49.52,
"steps": {
"unused_defs_removed": 1,
"unused_def_ids": ["unused"],
"empty_containers_removed": 2,
"numbers_rounded_on_attrs": 1
}
}
```
The result is one path, with the unused gradient and the empty group gone:
```xml
<svg xmlns="http://www.w3.org/2000/svg" width="240" height="240" viewBox="0 0 240 240">
<path id="left" style="fill:#3d5afe" d="M 95 58 A 62 62 0 0 0 33 120 …"/>
</svg>
```
The banner at the top of this page was produced and checked with the same tools.
## Why output verification matters
Inkscape returns exit code `0` after rejecting an action:
```bash
$ inkscape drawing.svg --actions="file-vacuum-defs;export-filename:out.svg;export-do"
InkscapeApplication::parse_actions: could not find action for: file-vacuum-defs
$ echo $?
0
```
It also returns `0` after writing nothing at all. A wrapper that reads the exit code
therefore reports success for work that did not happen.
Every Inkscape call here goes through one function that scans stderr for rejected
actions, then checks that the expected output file exists, is not empty, and has a
fresh modification time. The last check matters because a leftover file from an
earlier run otherwise makes a failed export look successful.
Ten behaviours of Inkscape 1.4.2 were confirmed this way, each with a reproduction, in
[docs/inkscape-findings.md](docs/inkscape-findings.md).
## What it can do
| | |
|---|---|
| 157 bundled Inkscape extensions, headlessly | Pattern along Path, Envelope, Perspective, gears, QR, Hershey text |
| 212 preset filters | the Filters menu: bevels, metal, jelly, glass, shadows |
| 8 live path effects | defaults read from Inkscape's `lpe-*.cpp` |
| 348-action registry with scope | reports which 130 actions are GUI-only instead of failing silently |
| Rendered PNG returned to the caller | a blank canvas is visible rather than hidden in a payload |
| SVG optimisation, validation, analysis, animation | in Python, since Inkscape exposes no headless action for these |
Offset and inset, fillet, chamfer, powerstroke, taper, radial repeat, tiled clones as
real `<use>` links, mesh gradients, multipage documents, node-level path editing and
layer management are all reachable, none of which Inkscape exposes to a headless
caller directly.
## Tools
Named `<group>_<action>`, which mirrors the source layout.
| Group | Tools |
|---|---|
| **path** | `path_boolean` `path_convert` `path_geometry` `path_effect` |
| **object** | `object_transform` `object_style` `object_arrange` `object_clone` `object_filter` `object_compose` `object_query` |
| **document** | `document_edit` (layers, gradients, pages, nodes, fonts, canvas) |
| **svg** | `svg_optimize` `svg_validate` `svg_analyze` `svg_animate` |
| **export** | `export_file` `export_batch` `export_preview` |
| **escape hatches** | `action_run` `action_find` `extension_run` |
| **server** | `server_info` |
Parameters for each are in [docs/tool-reference.md](docs/tool-reference.md).
## Error messages
The caller is usually a model that will act on the message, so a failure names the
cause and the call that fixes it:
```
'p1' is <rect> with no 'd'. Run path_convert kind='object-to-path' on it first.
Inkscape's own inset/outset refuses non-paths too.
```
```
'fit-canvas-to-drawing' is in the action registry extracted from the Inkscape source,
but this Inkscape build does not expose it at runtime. Use action_find to search for
a working equivalent.
```
## Tested against
Inkscape 1.4.2 (f4327f4) on Windows 11 and Ubuntu, Python 3.9 through 3.13.
CI runs the self-test on Linux, macOS and Windows, then again with Inkscape installed
on Linux and Windows, then builds the wheel and installs it into a clean environment.
Known limitations:
- macOS has no Inkscape-backed CI job, so only the pure-Python half is verified there.
- Behaviour is verified against Inkscape 1.4.2. Other versions expose different
actions, which is the subject of
[docs/inkscape-findings.md](docs/inkscape-findings.md).
- Every tool transforms an existing file. There is no way to create a document from
scratch yet ([#3](https://github.com/code-and-crypto/inkscape-mcp-server/issues/3)).
## Documentation
| | |
|---|---|
| [Verified Inkscape findings](docs/inkscape-findings.md) | Ten behaviours, each with a reproduction |
| [Tool reference](docs/tool-reference.md) | All 23 tools and their parameters |
| [Architecture](docs/architecture.md) | The layers, and how to replace one |
| [Contributing](CONTRIBUTING.md) | Adding tools, conventions, testing |
| [Changelog](CHANGELOG.md) | What changed in each version |
| [Security](SECURITY.md) | Reporting a vulnerability |
Planned work is tracked in
[Issues](https://github.com/code-and-crypto/inkscape-mcp-server/issues).
## License
MIT. See [LICENSE](LICENSE).
TDQS
Scored across 23 tools
Most tools are cleanly separated by subsystem (paths, objects, SVG document, export, raw actions), and the prefix helps agents route. A few adjacent tools could be confused—object_clone vs object_compose's clone operation and svg_validate vs svg_analyze's quality checks—but the descriptions draw enough of a boundary.
The set is uniformly lowercase snake_case with a domain prefix (object_, path_, svg_, export_, action_), which reads predictably. It is not a strict verb_noun pattern throughout—path_geometry, server_info, and path_effect are noun/adjective entries—so it falls just short of perfect consistency.
At 23 tools the surface is in the heavy range for an MCP server, and an agent must digest a large catalog before acting. The breadth is defensible because Inkscape is a huge domain and each tool packages many underlying operations, but several aggregator tools could have been split or trimmed.
Core workflows—querying, editing, styling, path operations, document structure, optimization, export—are well covered, and action_run/extension_run provide escape hatches for the long tail of Inkscape actions. Obvious lifecycle gaps remain (no dedicated document load/save or object creation/deletion tool), but they can often be worked around through raw actions or existing file context.