- Python 93.9%
- Dockerfile 3.1%
- Shell 3%
| __pycache__ | ||
| docs | ||
| help | ||
| .env.example | ||
| .gitignore | ||
| build-and-push.sh | ||
| cadlib.py | ||
| cq_runner.py | ||
| cqkit.py | ||
| docker-compose.yml | ||
| Dockerfile | ||
| documents.py | ||
| entrypoint.sh | ||
| README.md | ||
| start_mcp.py | ||
freecad-mcp
Docker-Image: FreeCAD 1.1 (headless unter Xvfb) mit dem MCP-Server des Addons
ghbalf/freecad-ai. Agenten (Claude Code, Hermes, opencode)
steuern damit FreeCAD über MCP (Streamable HTTP, /mcp). Gedacht als Backend des Iris-Plugins
freecad-bridge.
Image: forgejo.lewerken.duckdns.org/iris/freecad-mcp
Betrieb
cp .env.example .env # MCP_AUTH_TOKEN setzen (openssl rand -hex 24)
docker compose up -d # zieht das Image aus der Forgejo-Registry
MCP-Endpunkt: http://<host>:3100/mcp mit Authorization: Bearer <MCP_AUTH_TOKEN>.
Dateien (Exporte, Screenshots, .FCStd) liegen im Volume ./workspace, die freecad-ai-Config in ./config.
Der Server braucht nach dem Start etwa 60 Sekunden.
Bauen und veröffentlichen
Auf einem amd64-Docker-Host (der FreeCAD-AppImage ist x86_64):
FORGEJO_TOKEN=... ./build-and-push.sh --version v0.1.0
Build-Argumente im Dockerfile: FREECAD_VERSION, FREECAD_AI_REF (Commit des freecad-ai-Repos, gepinnt).
Aufbau und Besonderheiten
Dockerfile— Ubuntu 24.04, FreeCAD-AppImage entpackt (kein FUSE), freecad-ai auf festen Commit gepinnt.start_mcp.py— startet den MCP-Server per Timer erst, wenn FreeCADs Event-Loop läuft. Ohne das ist der Viewport-Ausschnitt beicapture_viewport/set_viewfalsch (View-Widget entsteht vor dem Fenster-Layout).entrypoint.sh— Xvfb + FreeCAD, plus Watchdog: alle 30 s ein echter Tool-Call (list_documents); nach 3 Fehlschlägen beendet sich der Container,restart: unless-stoppedstartet ihn neu.documents.py— jedes Tool bekommt den Pflichtparameterdocument: pro Aufruf wird das Dokument gewählt (bei Bedarf angelegt oder aus/workspace/documents/<name>.FCStdgeöffnet) und nach jedem schreibenden Tool gespeichert. Mehrere Chats arbeiten so parallel in getrennten Modellen, ein Neustart verliert nichts.switch_documententfällt,list_documentszeigt zusätzlich die gespeicherten Dokumente. Bei Ansichts-Tools wird das 3D-Fenster des Dokuments nach vorne geholt (verdeckte Fenster liefern leere/angeschnittene Screenshots),capture_viewportzieht vorherfitAllnach. Dateipfade in Ausgaben nennen den Nutzerpfad/workspaces/freecad/….import_model(eigenes Tool indocuments.py): lädt STEP/IGES/BREP/STL/OBJ ins Dokument und meldet die Gesamtmaße. Upstream hat keinen Import; überexecute_code(Sandbox ohne GUI) scheitern die üblichen Wege. Container-LocaleC.UTF-8ist nötig, sonst findet Python Pfade mit Umlauten („Anhänge“) nicht.create_enclosure_around(eigenes Tool): Gehäuse (Wanne mit offener Seite) rechnerisch exakt um die Bounding-Box eines Objekts, in dessen Koordinaten; Öffnung an der kürzesten Seite; echte Überschneidungsprüfung. Ersetzt das unzuverlässige Nachbauen durch das Modell.run_script(eigenes Tool) undcadlib.py(cad.*): FreeCAD-Python im aktiven Dokument, mit Bausteinen für Druckteile (Gehäuse mit Deckel/Lippe, Lüftungsschlitze, Fasen, Schraubdome, Löcher). Zeitlimit 60 s, bei Fehler/Zeitüberschreitung werden alle Änderungen des Skripts zurückgenommen. Der Watchdog wartet vier Fehlproben ab (länger als das Skript-Limit).- Upstream-Fehler im Executor behoben (
documents.py,_patch_executor):QtMainThreadToolExecutorweckte mitwakeAll()alle wartenden Aufrufe; parallele Aufrufe (z. B. Watchdog-Probe während eines Skripts) bekamenNonezurück ("'NoneType' object has no attribute 'success'") und lösten Hermes' Schutzschalter aus. Jetzt wartet jeder Aufruf auf sein eigenes Ereignis. run_cadquery+cad_help(allgemeiner Konstruktionsweg): CadQuery läuft in eigenem venv/opt/cq(Python 3.12, getrennt von FreeCADs Python); das Skript (cq_runner.py) kenntshow_object,resultundload(label)für übergebene Dokumentobjekte, die Ergebnisse gehen als BREP an FreeCAD und erscheinen alsPart::Feature(gleichnamige werden aktualisiert). Zeitlimit 50 s, bei Fehlern bleibt das Dokument unverändert.cad_helpliefert getestete Rezepte aushelp/*.md(alle Codeblöcke dort werden vor jedem Release gegen das Image ausgeführt).- Watchdog: Ein laufendes Werkzeug schreibt
/tmp/mcp_busy; solange es < 150 s arbeitet, zählt der Watchdog keine Fehlproben. - Dokumentnamen:
list_documentsnennt den Wert fürdocument(FreeCADs interner Name kann abweichen: führender_,_statt-); UUID-artige Namen werden auf die Schreibweise mit Bindestrichen abgebildet.execute_code/run_macro/switch_documentsind ausgeblendet —run_scriptersetzt sie (execute_codescheiterte im GUI-losen Sandbox-Prozess an jedem Dokumentzugriff). - Oberfläche per VNC: Xvfb (fest
:99, 1280×800) +x11vncauf Port 5900, nur mitVNC_PASSWORD(ohne Variable kein Zugang). Der Port wird nicht veröffentlicht; der Iris-Server leitet ihn per WebSocket an einen noVNC-Client weiter. FreeCADs Hauptfenster füllt den Bildschirm (start_mcp.py). - Mounts (vom Plugin gesetzt): alle Workspaces schreibgeschützt unter
/workspaces,/workspaces/freecadbeschreibbar. - Schema-Patch (Dockerfile):
pad_sketch.lengthakzeptiert Zahl und String. Upstream deklariert nurstring, Hermes lehnt dannlength: 8ab. init: truein der Compose-Datei ist nötig:xvfb-runhängt als PID 1 (Xvfb meldet "bereit" per SIGUSR1).
Sicherheit
Das Token ist Pflicht (MCP_AUTH_TOKEN). execute_code und run_macro laufen im Normalmodus in einem
Sandbox-Subprozess; der "Dangerous Mode" von freecad-ai wird nicht aktiviert. Den Port nicht ungeschützt
ins Internet stellen.
Lizenz
FreeCAD und freecad-ai stehen unter LGPL-2.1.