- services/lucy_autonomie.py: Stufe je Aktionsart (nur lesen, erst fragen, selbst handeln), Start „mehr Freiheit“;
Meldung danach (POST /api/lucy/gehandelt)
- services/rueckfragen.py: nummerierte Rückfragen, nur Knöpfe der Oberfläche (ERLAUBT), „ja“ führt genau die
gespeicherte Aktion aus, 24 h gültig, hinfällig wenn der Hinweis weg ist, Nachprüfung danach
- notify.sh -k: Antworttasten (Antworttastatur über den Direktweg, ohne Eingriff in Hermes)
- services/ermittlung.py: Wächter meldet rote Hinweise an Lucy; Knopf wird Rückfrage, Lucy sieht über den
api_server nach und schickt Ursache und Weg als eine Nachricht
- services/wochenbericht.py + mc2-wochenbericht.timer (So 07:30): feste Sätze, Absatz vom Modell, offene
App-Updates als Rückfragen
- MCP: Werkzeuge fragen die Freiheiten (auf_bitte), neu: freiheiten, rueckfragen, rueckfrage_beantworten,
sicherheitsupdates_einspielen, snapshot_zurueckspielen, snapshots, geraet_logs, dienst_logs
- Ausführer-Aktion journal (nur lesend), POST /api/homelab/ziele/{id}/sicherheitsupdates
- Läufe des Homelabs mit flock (Oberfläche und Steward schreiben beide)
- Skill homelab-orchestrator, SOUL-Abschnitt als Kopie in deploy/nur-box
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
137 lines
4.6 KiB
Python
137 lines
4.6 KiB
Python
#!/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 <venv>/bin/python --args <dieses Skript>
|
|
"""
|
|
|
|
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()
|