#!/usr/bin/env python3 """ Mission Control 2.0 — Stack-Management MCP Server (für Hermes). Gibt dem Hermes-Agenten Werkzeuge, um den lokalen LLM-Stack SELBST zu steuern: Modelle ansehen/entdecken/eintragen, Routing setzen, Dienste neu starten, System-Status lesen. Spricht die MC-2-REST-API (/api/*). Damit ist Hermes echte Control-Plane des Stacks (Plan: „MC-Mgmt-MCP, damit Hermes den Stack selbst steuert"). Läuft typischerweise lokal auf der Box neben Hermes. Env: MC_URL (default http://127.0.0.1:9000), MC_TOKEN (optional). Install: pip install mcp httpx """ import json import os import httpx from mcp.server.fastmcp import FastMCP MC_URL = os.environ.get("MC_URL", "http://127.0.0.1:9000").rstrip("/") MC_TOKEN = os.environ.get("MC_TOKEN", "") mcp = FastMCP("mission-control-stack") def _h() -> dict: return {"X-MC-Token": MC_TOKEN} if MC_TOKEN else {} def _get(path: str, **params): r = httpx.get(f"{MC_URL}{path}", headers=_h(), params=params, timeout=20) r.raise_for_status() return r.json() def _post(path: str, data: dict): r = httpx.post(f"{MC_URL}{path}", headers=_h(), json=data, timeout=60) r.raise_for_status() return r.json() def _put(path: str, data: dict): r = httpx.put(f"{MC_URL}{path}", headers=_h(), json=data, timeout=20) r.raise_for_status() return r.json() @mcp.tool() def list_models() -> str: """Listet die in llama-swap konfigurierten Modelle (Name, Rolle, Kontext, Fähigkeiten).""" data = _get("/api/models") return "\n".join( f"- {m['name']} (Rolle: {m.get('role') or '—'}, ctx: {m.get('ctx')}, " f"tools: {m['capabilities']['tools']}, MoE: {m['capabilities']['moe']})" for m in data.get("models", []) ) or "Keine Modelle konfiguriert." @mcp.tool() def discover_models() -> str: """Zeigt die aktuell besten Modelle je Kategorie (live von HuggingFace, Hardware-Fit). Nutze dies, bevor du ein neues Modell vorschlägst/einträgst.""" data = _get("/api/discover") out = [f"System-RAM: {data.get('sys_ram_gb')} GB"] for c in data.get("categories", []): rec = c.get("recommended") or "—" out.append(f"\n## {c['title']} (beste Wahl: {rec})") for m in c["models"][:3]: out.append(f" - {m['repo']} · {m['fit']['text']} (~{m['fit']['req_gb']} GB)") return "\n".join(out) @mcp.tool() def system_status() -> str: """Live-Auslastung der Box (CPU/RAM/GPU/Disk).""" s = _get("/api/system/status") g = s.get("gpu") or {} return (f"CPU {s['cpu']['percent']}% · RAM {s['ram']['percent']}% · " f"GPU {g.get('busy_percent', '—')}% · Disk {(s.get('disk') or {}).get('percent', '—')}%") @mcp.tool() def register_model(model_path: str, role: str = "", ctx: int = 8192, jinja: bool = False) -> str: """Trägt ein bereits heruntergeladenes GGUF als llama-swap-Modell ein (cmd + Rollen-Alias). role z.B. fast/heavy/coder/vision/agent. jinja=True für Tool-Calling (Agent-Hirn).""" res = _post("/api/models/register", {"model_path": model_path, "role": role or None, "ctx": ctx, "jinja": jinja}) return f"Eingetragen als '{res.get('model_id')}'." @mcp.tool() def set_route(name: str, target_alias: str) -> str: """Setzt das Gateway-Routing: Gateway-Modellname (fast/heavy/auto/…) → llama-swap-Alias.""" _put("/api/routing/route", {"name": name, "target_alias": target_alias}) return f"Routing gesetzt: {name} → {target_alias}" @mcp.tool() def routing_overview() -> str: """Zeigt das aktuelle Gateway-Routing + Fallbacks.""" return json.dumps(_get("/api/routing"), ensure_ascii=False, indent=2) @mcp.tool() def restart_service(service: str) -> str: """Startet einen erlaubten Dienst neu (mission-control-2 | litellm-gateway | hermes-webui).""" res = _post("/api/system/restart", {"service": service}) return f"Restart {service}: {'ok' if res.get('ok') else 'Fehler — ' + res.get('err', '')}" if __name__ == "__main__": mcp.run()