Initial commit: CadQuery MCP Server

- server.py: FastMCP-Server mit render_stl, preview_png, list_models
- README.md: Ausführliche Installationsanleitung (DE)
- .gitignore: Schließt .venv, STL/PNG-Ausgaben und Cache aus

Stack: cadquery 2.7.0, mcp 1.26.0, cq-editor 0.6.2, Python 3.13

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nova
2026-03-13 11:22:06 +01:00
commit 57466b4ba2
3 changed files with 507 additions and 0 deletions
+123
View File
@@ -0,0 +1,123 @@
"""
CadQuery MCP Server
Ermöglicht Claude, CadQuery-Python-Code auszuführen und STL-Dateien zu erzeugen.
"""
import os
import sys
import tempfile
import base64
import traceback
from pathlib import Path
from mcp.server.fastmcp import FastMCP
import cadquery as cq
from cadquery import exporters
OUTPUT_DIR = Path(os.environ.get("CADQUERY_OUTPUT_DIR", Path.home() / "3d-models"))
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
mcp = FastMCP("cadquery")
def _run_cadquery(code: str) -> cq.Workplane:
"""Führt CadQuery-Code aus und gibt das Ergebnis zurück."""
captured = {}
def show_object(obj, name="result", options=None):
captured["result"] = obj
def show(obj, name="result", options=None):
captured["result"] = obj
namespace = {
"cq": cq,
"cadquery": cq,
"show_object": show_object,
"show": show,
}
exec(code, namespace) # noqa: S102
if "result" in captured:
return captured["result"]
# Letzte Variable suchen, die ein Workplane oder Shape ist
for val in reversed(list(namespace.values())):
if isinstance(val, (cq.Workplane, cq.Shape, cq.Assembly)):
return val
raise ValueError(
"Kein CadQuery-Objekt gefunden. "
"Bitte 'show_object(result)' am Ende des Codes aufrufen "
"oder das Ergebnis in eine Variable namens 'result' speichern."
)
@mcp.tool()
def render_stl(code: str, filename: str = "model.stl") -> str:
"""
Rendert CadQuery-Python-Code als STL-Datei für den 3D-Drucker.
Args:
code: Gültiger CadQuery-Python-Code
filename: Dateiname für die STL-Ausgabe (z.B. "halter.stl")
Returns:
Pfad zur erzeugten STL-Datei oder Fehlermeldung
"""
if not filename.endswith(".stl"):
filename += ".stl"
output_path = OUTPUT_DIR / filename
try:
result = _run_cadquery(code)
exporters.export(result, str(output_path))
size_kb = output_path.stat().st_size // 1024
return f"STL erfolgreich erstellt: {output_path}\nDateigröße: {size_kb} KB"
except Exception as e:
return f"Fehler beim Rendern:\n{traceback.format_exc()}\nBitte CadQuery-Code prüfen."
@mcp.tool()
def preview_png(code: str, filename: str = "preview.png") -> str:
"""
Rendert eine PNG-Vorschau des CadQuery-Modells.
Args:
code: Gültiger CadQuery-Python-Code
filename: Dateiname für die PNG-Ausgabe
Returns:
Base64-kodiertes PNG-Bild oder Fehlermeldung
"""
if not filename.endswith(".png"):
filename += ".png"
svg_path = OUTPUT_DIR / filename.replace(".png", ".svg")
output_path = OUTPUT_DIR / filename
try:
result = _run_cadquery(code)
exporters.export(result, str(svg_path))
size_bytes = svg_path.stat().st_size
return f"SVG-Vorschau erstellt: {svg_path} ({size_bytes} Bytes)\n(PNG-Konvertierung erfordert zusätzliche Abhängigkeiten)"
except Exception as e:
return f"Fehler beim Rendern der Vorschau:\n{traceback.format_exc()}"
@mcp.tool()
def list_models() -> str:
"""Listet alle bisher erstellten 3D-Modelle im Output-Verzeichnis."""
files = list(OUTPUT_DIR.glob("*.stl"))
if not files:
return f"Keine STL-Dateien in {OUTPUT_DIR}"
lines = [f"Modelle in {OUTPUT_DIR}:"]
for f in sorted(files):
size_kb = f.stat().st_size // 1024
lines.append(f" {f.name} ({size_kb} KB)")
return "\n".join(lines)
if __name__ == "__main__":
mcp.run()