Files
Thorsten 77eb5379d2 Initial commit: OpenSCAD MCP server for Claude Code
MCP server that lets Claude generate and render OpenSCAD models to STL files.
Includes setup documentation and installation guide.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 16:00:24 +01:00

110 lines
3.3 KiB
Python

"""
OpenSCAD MCP Server
Ermöglicht Claude, OpenSCAD-Code zu rendern und STL-Dateien zu erzeugen.
"""
import subprocess
import tempfile
import os
import base64
from pathlib import Path
from mcp.server.fastmcp import FastMCP
# Pfad zur OpenSCAD-Executable (Windows-Standard)
OPENSCAD_PATH = os.environ.get(
"OPENSCAD_PATH",
r"C:\Program Files\OpenSCAD\openscad.exe"
)
# Output-Verzeichnis für STL-Dateien
OUTPUT_DIR = Path(os.environ.get("OPENSCAD_OUTPUT_DIR", Path.home() / "3d-models"))
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
mcp = FastMCP("openscad")
def _run_openscad(scad_code: str, output_path: Path, extra_args: list = None) -> tuple[bool, str]:
"""Führt OpenSCAD mit dem gegebenen Code aus. Gibt (success, output) zurück."""
with tempfile.NamedTemporaryFile(suffix=".scad", mode="w", delete=False, encoding="utf-8") as f:
f.write(scad_code)
scad_file = f.name
try:
cmd = [OPENSCAD_PATH, "-o", str(output_path)] + (extra_args or []) + [scad_file]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
output = (result.stdout + result.stderr).strip()
success = result.returncode == 0 and output_path.exists()
return success, output
finally:
os.unlink(scad_file)
@mcp.tool()
def render_stl(code: str, filename: str = "model.stl") -> str:
"""
Rendert OpenSCAD-Code als STL-Datei für den 3D-Drucker.
Args:
code: Gültiger OpenSCAD-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
success, output = _run_openscad(code, output_path)
if success:
size_kb = output_path.stat().st_size // 1024
return f"STL erfolgreich erstellt: {output_path}\nDateigröße: {size_kb} KB\nOpenSCAD-Output: {output or '(kein Output)'}"
else:
return f"Fehler beim Rendern:\n{output}\n\nBitte OpenSCAD-Code prüfen."
@mcp.tool()
def preview_png(code: str, filename: str = "preview.png") -> str:
"""
Rendert eine PNG-Vorschau des OpenSCAD-Modells.
Args:
code: Gültiger OpenSCAD-Code
filename: Dateiname für die PNG-Ausgabe
Returns:
Base64-kodiertes PNG-Bild oder Fehlermeldung
"""
if not filename.endswith(".png"):
filename += ".png"
output_path = OUTPUT_DIR / filename
# --camera=0,0,0,55,0,25,140 = isometrische Ansicht
extra = ["--camera=0,0,0,55,0,25,140", "--imgsize=800,600", "--colorscheme=DeepOcean"]
success, output = _run_openscad(code, output_path, extra_args=extra)
if success:
with open(output_path, "rb") as f:
img_b64 = base64.b64encode(f.read()).decode()
return f"data:image/png;base64,{img_b64}"
else:
return f"Fehler beim Rendern der Vorschau:\n{output}"
@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()