Inkscape
Un modèle pilote Inkscape 1.4 par le Hub pour composer une carte de visite sur deux calques. Inkscape est la seule application ici qui n’a rien à démarrer : le Hub lance un processus Python pour chaque script. Cette page donne le texte, le script, les réponses du Hub et la capture.

Sur cette page
Activer Inkscape
Il n’y a ni pont ni fenêtre. Le Hub cherche /usr/bin/python3, /usr/bin/inkscape, et les extensions d’Inkscape dans /usr/share/inkscape/extensions. Toute la préparation tient en une entrée dans ~/.config/libremodular-mcp/config.toml, après libremodular-mcp init :
[apps.inkscape]
enabled = true
Sur une Debian 13 vierge, import inkex demande aussi python3-tinycss2, que le paquet inkscape n’installe pas : sudo apt install inkscape python3-tinycss2.
get_state sur hub a listé Inkscape comme connecté en mode cli, avec la version qu’Inkscape déclarait :
{
"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": []
}Texte donné au modèle
Avant d’écrire, le modèle a appelé get_state sur inkscape. Le state était vide et un avertissement disait no document yet: the working document is created by the first script.
Script écrit
Un modèle, Claude Sonnet 5.5, a écrit ce script à partir du guide et de cet état. Ce n’est pas un script tiré des tests du projet. Deux lignes viennent du guide : le script retire d’abord les enfants de la racine, parce que le document est conservé d’un script au suivant, et il règle cx, cy et r du cercle un par un, parce que, comme le guide l’avertit, les deux arguments de inkex.Circle.new ne font pas un centre.
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()))}))La réponse du Hub
{
"app": "inkscape",
"duration_ms": 151,
"error": null,
"result": {
"elements": 8,
"layers": [
"Background",
"Text"
]
},
"status": "ok",
"stderr": "",
"stdout": "",
"truncated": false
}Le script a duré 151 ms : un processus Python a démarré et s’est terminé. Le script a imprimé son result sur la dernière ligne, après LMM_RESULT: , ce qui est la façon dont un script Inkscape renvoie une valeur. get_state a ensuite décrit le document de travail et renvoyé une capture :
{
"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": []
}L’image produite
La capture est le document de travail exporté par inkscape lui-même, de 559 par 397 pixels pour une carte de 148 par 105 mm. Le texte est dessiné dans la police sans-serif de cette machine ; une autre machine le dessine dans la sienne. Le pixel en haut à gauche de la capture vaut #1C385C et celui qui est dans le cercle vaut #C25438.
