#!/usr/bin/env python3 """ Homelab Orchestrator — MCP-Server für Lucy (Hermes), seit 25.09.2026. Stdio-MCP-Server, von Hermes Gateway als Unterprozess gestartet (auf der KI-Box, mit dem venv von MC2). Gibt Lucy die Knöpfe des Orchestrators: Lage lesen, Protokolle lesen, Updates und Sicherheitsupdates starten, „Alle aktualisieren“, Dienste neu starten, Snapshots zurückspielen, Kernel aufräumen, Host neu starten, Modell-Radar — und die Rückfragen an den Commander. Die Logik steht in orchestrator_werkzeuge.py (ohne MCP testbar); alles geht über die REST-API von MC2 mit denselben Leitplanken wie die Knöpfe der Oberfläche. Handeln: auf ausdrückliche Bitte des Commanders (auf_bitte=true) oder nach Lucys Freiheiten je Aktionsart (seit dem Abend des 25.09.2026, Einstellungen → Lucy) — selbst mit Meldung danach, als Rückfrage oder gar nicht. Grund und Stufe jeder Aktion werden protokolliert. FastMCP liest die Beschreibung beim Anmelden, deshalb steht sie in description= (orchestrator_werkzeuge.BESCHREIBUNG). Neue Werkzeuge sieht Hermes erst nach einem Neustart des Gateways. Einrichten auf der Box (einmal): hermes mcp add homelab-orchestrator --command /bin/python --args """ import orchestrator_werkzeuge as w from mcp.server.fastmcp import FastMCP mcp = FastMCP("homelab-orchestrator") B = w.BESCHREIBUNG # --- Lesen ---------------------------------------------------------------------------------------------------- @mcp.tool(description=B["lage"]) def lage() -> str: return w.lage() @mcp.tool(description=B["updates"]) def updates() -> str: return w.updates() @mcp.tool(description=B["speicher"]) def speicher() -> str: return w.speicher() @mcp.tool(description=B["protokoll"]) def protokoll(anzahl: int = 15, ziel: str | None = None) -> str: return w.protokoll(anzahl, ziel) @mcp.tool(description=B["radar"]) def radar() -> str: return w.radar() @mcp.tool(description=B["freiheiten"]) def freiheiten() -> str: return w.freiheiten() @mcp.tool(description=B["rueckfragen"]) def rueckfragen() -> str: return w.rueckfragen() @mcp.tool(description=B["geraet_logs"]) def geraet_logs(ziel: str, zeilen: int = 80) -> str: return w.geraet_logs(ziel, zeilen) @mcp.tool(description=B["dienst_logs"]) def dienst_logs(dienst: str, zeilen: int = 80) -> str: return w.dienst_logs(dienst, zeilen) @mcp.tool(description=B["snapshots"]) def snapshots(ziel: str) -> str: return w.snapshots(ziel) # --- Handeln -------------------------------------------------------------------------------------------------- @mcp.tool(description=B["update_starten"]) def update_starten(ziel: str, baustein: str, grund: str, auf_bitte: bool = False) -> str: return w.update_starten(ziel, baustein, grund, auf_bitte) @mcp.tool(description=B["sicherheitsupdates_einspielen"]) def sicherheitsupdates_einspielen(ziel: str, grund: str, auf_bitte: bool = False) -> str: return w.sicherheitsupdates_einspielen(ziel, grund, auf_bitte) @mcp.tool(description=B["alle_aktualisieren"]) def alle_aktualisieren(bereich: str, grund: str, auf_bitte: bool = False) -> str: return w.alle_aktualisieren(bereich, grund, auf_bitte) @mcp.tool(description=B["nach_updates_suchen"]) def nach_updates_suchen(grund: str, auf_bitte: bool = False) -> str: return w.nach_updates_suchen(grund, auf_bitte) @mcp.tool(description=B["dienst_neustarten"]) def dienst_neustarten(dienst: str, grund: str, auf_bitte: bool = False) -> str: return w.dienst_neustarten(dienst, grund, auf_bitte) @mcp.tool(description=B["snapshot_zurueckspielen"]) def snapshot_zurueckspielen(ziel: str, snapshot: str, grund: str, auf_bitte: bool = False) -> str: return w.snapshot_zurueckspielen(ziel, snapshot, grund, auf_bitte) @mcp.tool(description=B["kernel_aufraeumen"]) def kernel_aufraeumen(grund: str, auf_bitte: bool = False) -> str: return w.kernel_aufraeumen(grund, auf_bitte) @mcp.tool(description=B["host_neustarten"]) def host_neustarten(grund: str, auf_bitte: bool = False) -> str: return w.host_neustarten(grund, auf_bitte) @mcp.tool(description=B["radar_testen"]) def radar_testen(kandidat: str, grund: str, auf_bitte: bool = False) -> str: return w.radar_testen(kandidat, grund, auf_bitte) @mcp.tool(description=B["radar_suchen"]) def radar_suchen(grund: str, auf_bitte: bool = False) -> str: return w.radar_suchen(grund, auf_bitte) @mcp.tool(description=B["rueckfrage_beantworten"]) def rueckfrage_beantworten(antwort: str, nr: int | None = None) -> str: return w.rueckfrage_beantworten(antwort, nr) if __name__ == "__main__": mcp.run()