Grotti 3.5.1: MCP-Anweisung für Clients, Queue-Strip nur bei Ablauf, Start wartet auf printing.

This commit is contained in:
Dominic Gundel committed 2026-10-20 21:39:22 +02:00
1 parent f034c5e626
commit 5166ed6530
5 files changed
+60 -16

No files matched your search

+52 -13
View File
@@ -57,6 +57,51 @@ def _write_guard():
raise RuntimeError("MCP läuft im Read-only-Modus (GROTTI_MCP_READ_ONLY).")
# Wird beim Connect an den Client gegeben (Claude, Cursor, …).
INSTRUCTIONS = """\
Grotti steuert eine 3D-Drucker-Farm (Anycubic Kobra, Klipper/Moonraker).
Antworte dem Nutzer auf Deutsch. Handle nur über diese Tools.
Identität
- Drucker über das Feld host (IP). Den Namen nur zur Anzeige nutzen. Hosts nie raten: erst list_printers oder get_printer.
- Dateien über die numerische id aus list_files (Feld id), nicht über den Dateinamen allein.
- Vor jedem Schreibzugriff den aktuellen Stand lesen.
Lesen
- Farm: list_printers (live=true). Ein Drucker: get_printer(host).
- Läuft: list_running. Warteschlange: list_queue. Ereignisse: list_events.
- Bibliothek: list_files. Spulen: list_spools. Filament-Fehlmenge: bedarf_lesen.
- Kundenaufträge: auftraege_lesen. Verschleißteile: wartung_lesen.
Zustand (Feld state)
- printing: druckt. paused: Pause. standby: frei. complete: fertig, Platte oft noch belegt.
- error: Fehler, nicht einfach neu starten. offline: nicht erreichbar.
- progress ist 0…1. filament_empty oder pause_reason bei Pause heißt Filament leer.
Dann dem Nutzer sagen, die neue Spule in Grotti dem Drucker zuzuweisen, und erst danach resume_printer.
Eine Filament-Pause nicht blind fortsetzen.
Schreiben
confirm=true nur setzen, wenn der Nutzer diese Aktion in diesem Zug klar will. Nicht vorauseilend bestätigen.
- enqueue_when_free: Datei in die Warteschlange. host leer = Grotti wählt den nächsten Drucker mit passender Sorte. host gesetzt = dieser Drucker, sobald er frei ist und die Platte klar ist. Das ist der normale Weg.
- enqueue_print: sofort senden und starten. Nur wenn der Nutzer einen Sofortstart auf einem genannten freien Drucker will (state standby, Platte frei). Große Dateien brauchen oft etwa eine Minute bis zum Start.
- Den Material-Match macht Grotti (PLA Meta ist nicht Matte und nicht Silk). Eine falsche Maschine nicht erzwingen. Eine Ablehnung wörtlich weitergeben.
- bed_leveling=true nur auf ausdrücklichen Wunsch (Bettnetz / Auto-Level).
- pause_printer und resume_printer während des Drucks, ohne confirm.
- cancel_printer bricht den Druck ab und braucht confirm=true. Bei Unklarheit erst nachfragen.
- clear_bed ist nur die Quittung „Platte ist leer“ und schiebt nichts physisch. Erst aufrufen, wenn der Nutzer sagt, das Teil ist herunter. Ohne diese Quittung startet der nächste „wenn frei“-Job nicht.
- steer_temp (Düse und Bett in °C), steer_speed (Prozent), steer_fan (0–100): nur diese Werte. Kein freier G-code.
Grenzen
- Keine G-code-Konsole, kein Firmware-Restart, kein E-Stop, keine Benutzer- oder Token-Verwaltung. Diese Lücken nicht umgehen.
- Im Read-only-Modus (GROTTI_MCP_READ_ONLY) schlagen Schreib-Tools fehl. Das sagen und nicht wiederholen.
- Gesperrte Drucker und ausgeschaltete Automatik starten keine Warteschlangen-Jobs. Den Fehler nennen.
Antworten
- Kurz und konkret: Druckername, host, Datei, Zustand, was du getan hast.
- Den Fehlertext von Grotti zitieren. Keinen Workaround außerhalb der Tools erfinden.
"""
def main():
try:
from mcp.server.fastmcp import FastMCP
@@ -67,23 +112,17 @@ def main():
)
sys.exit(1)
server = FastMCP(
"grotti",
instructions=(
"Grotti steuert die 3D-Drucker-Farm. Destruktive Aktionen brauchen "
"confirm=true. Kein freier G-code, kein Firmware-Restart."
),
)
server = FastMCP("grotti", instructions=INSTRUCTIONS)
@server.tool()
def list_printers(live: bool = True) -> str:
"""Druckerliste mit optionalem Live-Zustand."""
"""Alle Drucker. live=true liefert Zustand, Temps, Fortschritt, Dateiname. Host ist die IP."""
q = "?live=1" if live else "?live=0"
return json.dumps(_api("GET", "/drucker" + q), ensure_ascii=False, indent=2)
@server.tool()
def get_printer(host: str) -> str:
"""Einen Drucker anhand Host/IP aus der Live-Liste."""
"""Einen Drucker anhand host (IP) aus der Live-Liste. Host vorher über list_printers holen."""
rows = _api("GET", "/drucker?live=1") or []
for row in rows:
if row.get("host") == host:
@@ -115,7 +154,7 @@ def main():
@server.tool()
def list_files(limit: int = 50) -> str:
"""Dateien in der Bibliothek."""
"""Dateien in der Bibliothek. Die numerische id ist die file_id fürs Einreihen und Starten."""
rows = _api("GET", "/dateien") or []
return json.dumps(rows[: max(1, min(limit, 200))], ensure_ascii=False, indent=2)
@@ -181,7 +220,7 @@ def main():
@server.tool()
def clear_bed(host: str, confirm: bool = False) -> str:
"""Platte als geräumt quittieren (kein physisches Schieben)."""
"""Quittung, dass der Nutzer die Platte geleert hat. Schiebt nichts. confirm muss true sein. Danach kann die Warteschlange den nächsten Job starten."""
_write_guard()
if not confirm:
raise RuntimeError("Quittierung braucht confirm=true.")
@@ -201,7 +240,7 @@ def main():
confirm: bool = False,
bed_leveling: bool = False,
) -> str:
"""Datei in die Warteschlange („wenn frei“). confirm muss true sein."""
"""Datei in die Warteschlange. host leer: Grotti wählt den nächsten Drucker mit passender Sorte. host gesetzt: dieser Drucker, sobald frei und Platte klar. confirm muss true sein. Normaler Weg zum Drucken."""
_write_guard()
if not confirm:
raise RuntimeError("Einreihen braucht confirm=true.")
@@ -220,7 +259,7 @@ def main():
confirm: bool = False,
bed_leveling: bool = False,
) -> str:
"""Sofort senden und starten. confirm muss true sein."""
"""Sofort an diesen host senden und starten. Nur bei freiem Drucker (standby, Platte frei) und klarem Sofortstart-Wunsch. confirm muss true sein."""
_write_guard()
if not confirm:
raise RuntimeError("Start braucht confirm=true.")
+1 -1
View File
@@ -1,3 +1,3 @@
"""Version von Grotti (SemVer). Bei jeder Änderung CHANGELOG.md mitführen."""
__version__ = "3.5.0"
__version__ = "3.5.1"