# Inkscape

A model drives Inkscape 1.4 through the Hub to lay out a name card on two layers. Inkscape is the one application here with nothing to start: the Hub runs a Python process for each script. This page gives the text, the script, the Hub's answers and the capture.

*A drawing made by a script and open in Inkscape: it is an ordinary SVG, and its XML nodes can be edited by hand.*

## Facts

- Version tried: 1.4
- Mode: `cli`

## Enable Inkscape

There is no bridge and no window. The Hub looks for `/usr/bin/python3`, for `/usr/bin/inkscape`, and for the Inkscape extensions in `/usr/share/inkscape/extensions`. The whole preparation is one entry in `~/.config/libremodular-mcp/config.toml`, after `libremodular-mcp init`:

```toml
[apps.inkscape]
enabled = true
```

On a clean Debian 13, `import inkex` also needs `python3-tinycss2`, which the `inkscape` package does not pull in: `sudo apt install inkscape python3-tinycss2`.

`get_state` on `hub` listed Inkscape as connected in the `cli` mode, with the version Inkscape reported:

**Hub response**

```json
{
  "app": "hub",
  "app_version": "0.1.0",
  "bridge_version": null,
  "captured_at": "2026-10-21T12:47:13.608Z",
  "connected": true,
  "content_origin": "hub",
  "screenshot": null,
  "state": {
    "modules": [
      {
        "app_version": "1.4",
        "bridge_version": null,
        "connected": true,
        "mode": "cli",
        "name": "inkscape"
      }
    ]
  },
  "warnings": []
}
```


## Text given to the model

```text
In Inkscape, lay out a name card, A6 landscape (148 by 105 mm). Dark blue background (#1c385c), a copper circle (#c25438) in the top-left corner, the name "Alice Martin" in white and large, a thin amber line (#f2b344) under it, then the role "Sound engineer" in light grey and smaller. Put the background shapes on a layer named "Background" and the text on a layer named "Text".
```


Before writing, the model called `get_state` on `inkscape`. The `state` was empty and one warning said `no document yet: the working document is created by the first script`.

## Script written

A model, Claude Sonnet 5.5, wrote this script from the guide and from that state. It is not a script taken from the project's tests. Two lines come from the guide: the script removes the children of the root first, because the document is kept from one script to the next, and it sets `cx`, `cy` and `r` of the circle one by one, because, as the guide warns, the two arguments of `inkex.Circle.new` do not make a centre.

**Script written**

```python
import json
import os

import inkex

document = inkex.load_svg(os.environ["LMM_DOCUMENT"])
svg = document.getroot()
for child in list(svg):
    svg.remove(child)
svg.set("width", "148mm")
svg.set("height", "105mm")
svg.set("viewBox", "0 0 148 105")

background = svg.add(inkex.Layer.new("Background"))
panel = inkex.Rectangle.new(0, 0, 148, 105)
panel.style = {"fill": "#1c385c"}
background.add(panel)
disc = inkex.Circle()
disc.set("cx", "20")
disc.set("cy", "20")
disc.set("r", "9")
disc.style = {"fill": "#c25438"}
background.add(disc)
rule = inkex.Rectangle.new(20, 62, 108, 0.8)
rule.style = {"fill": "#f2b344"}
background.add(rule)

texts = svg.add(inkex.Layer.new("Text"))
for content, y, size, colour, weight in [("Alice Martin", 56, 12, "#ffffff", "bold"), ("Sound engineer", 76, 6, "#cfd4da", "normal")]:
    line = inkex.TextElement()
    line.text = content
    line.set("x", "20")
    line.set("y", str(y))
    line.style = {"fill": colour, "font-size": f"{size}px", "font-family": "sans-serif", "font-weight": weight}
    texts.add(line)

document.write(os.environ["LMM_DOCUMENT"])
print("LMM_RESULT: " + json.dumps({"layers": [layer.label for layer in svg.descendants().filter(inkex.Layer)], "elements": len(list(svg.descendants()))}))
```


## The Hub's answer

**Hub response**

```json
{
  "app": "inkscape",
  "duration_ms": 151,
  "error": null,
  "result": {
    "elements": 8,
    "layers": [
      "Background",
      "Text"
    ]
  },
  "status": "ok",
  "stderr": "",
  "stdout": "",
  "truncated": false
}
```


The script ran in 151 ms: one Python process started and ended. The script printed its `result` on the last line, after `LMM_RESULT: `, which is how an Inkscape script hands a value back. `get_state` then described the working document and returned a capture:

**Hub response**

```json
{
  "app": "inkscape",
  "app_version": "1.4",
  "bridge_version": null,
  "captured_at": "2026-10-21T12:47:14.185Z",
  "connected": true,
  "content_origin": "document",
  "screenshot": "$XDG_RUNTIME_DIR/libremodular-mcp/captures/inkscape-20261021T124713832Z.png",
  "state": {
    "element_count": 8,
    "height": "105mm",
    "layers": [
      {
        "id": null,
        "label": "Background"
      },
      {
        "id": null,
        "label": "Text"
      }
    ],
    "width": "148mm"
  },
  "warnings": []
}
```


## The image produced

The capture is the working document exported by `inkscape` itself, 559 by 397 pixels for a card of 148 by 105 mm. The text is drawn in the `sans-serif` font of this machine, so another machine draws it in its own. The pixel at the top left of the capture is `#1C385C` and the one inside the circle is `#C25438`.

![A dark blue card with a copper circle at the top left, the name Alice Martin in white, an amber line and the role Sound engineer in grey.](https://libremodular.davalan.fr/images/applications/inkscape-name-card.png)

*The capture of the working document after the script, as exported by the Hub.*


> **Limit**
>
> Not tried: an Inkscape window open at the same time as the Hub, and a document larger than this one.

[Reference guide in the repository](https://github.com/RonanDavalan/LibreModular-MCP/blob/main/docs/modules/inkscape.md)

