Feat: Hermes PC-Zugriff via MCP (executor + mcp_pc)

- mcp/mcp_pc.py: MCP-Server der PC-Tools an den Executor proxied
  (pc_shell, pc_screenshot, pc_type, pc_key, pc_open, pc_status)
- client/hermes-pc/executor.py: FastAPI auf Port 7777 (Daemon-Reg entfernt)
- client/hermes-pc/start.bat, install.bat, requirements.txt
- client/hermes-voice/agent_client.py, main.py: agent_client integration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-06-26 23:32:18 +02:00
parent a64b30c429
commit 483eb0b2fb
7 changed files with 307 additions and 2 deletions
+114
View File
@@ -0,0 +1,114 @@
#!/usr/bin/env python3
"""
Mission Control 2.0 — PC Control MCP Server.
Stdio-MCP-Server. Läuft als Subprocess von Hermes Gateway (AI Box).
Proxied Befehle an den Hermes PC Executor (Windows PC, Port 7777).
Env: PC_EXECUTOR_URL (default http://192.168.178.XXX:7777)
"""
import os
import httpx
from mcp.server.fastmcp import FastMCP
PC_URL = os.environ.get("PC_EXECUTOR_URL", "").rstrip("/")
mcp = FastMCP("hermes-pc-control")
def _pc(path: str, data: dict | None = None) -> dict:
if not PC_URL:
return {"error": "PC_EXECUTOR_URL nicht gesetzt. Setze die Env-Variable mit der IP des Windows PCs."}
try:
with httpx.Client(timeout=90) as c:
if data is None:
r = c.get(f"{PC_URL}{path}")
else:
r = c.post(f"{PC_URL}{path}", json=data)
r.raise_for_status()
return r.json()
except httpx.ConnectError:
return {"error": f"PC Executor nicht erreichbar ({PC_URL}). Läuft executor.py auf dem Windows PC?"}
except Exception as e:
return {"error": str(e)}
@mcp.tool()
def pc_shell(command: str) -> str:
"""Führt einen PowerShell-Befehl auf dem Windows-PC des Nutzers aus.
Nutze dies um Dateien zu lesen/schreiben, Programme zu starten oder Systeminfos abzufragen.
Beispiele: 'Get-Date', 'ls C:\\Users', 'notepad.exe test.txt'
Gibt stdout, stderr und returncode zurück."""
result = _pc("/shell", {"command": command})
if "error" in result:
return f"FEHLER: {result['error']}"
parts = []
if result.get("stdout"):
parts.append(f"stdout:\n{result['stdout']}")
if result.get("stderr"):
parts.append(f"stderr:\n{result['stderr']}")
parts.append(f"returncode: {result.get('returncode', '?')}")
return "\n".join(parts)
@mcp.tool()
def pc_screenshot() -> str:
"""Macht einen Screenshot des Windows-PC-Bildschirms.
Nutze dies um zu sehen was der Nutzer gerade auf dem Bildschirm hat.
Gibt einen base64-kodierten JPEG-String zurück."""
result = _pc("/screenshot", {})
if "error" in result:
return f"FEHLER: {result['error']}"
b64 = result.get("screenshot", "")
if b64:
return f"[Screenshot aufgenommen, {len(b64)} Zeichen base64-JPEG]"
return "Kein Screenshot-Daten erhalten."
@mcp.tool()
def pc_type(text: str) -> str:
"""Tippt Text in das aktive Fenster auf dem Windows-PC.
Der Nutzer muss das Zielfenster vorher aktiviert haben.
Nützlich um Text in Editoren, Terminals oder Formulare einzugeben."""
result = _pc("/type", {"text": text})
if "error" in result:
return f"FEHLER: {result['error']}"
return "Text getippt." if result.get("ok") else str(result)
@mcp.tool()
def pc_key(keys: str) -> str:
"""Drückt eine Tastenkombination auf dem Windows-PC.
Format: Tasten mit '+' verbinden. Beispiele: 'ctrl+s', 'alt+F4', 'ctrl+shift+esc', 'win+d'
Nützlich um Tastenkürzel auszuführen."""
result = _pc("/key", {"keys": keys})
if "error" in result:
return f"FEHLER: {result['error']}"
return f"Tastenkombination '{keys}' gedrückt." if result.get("ok") else str(result)
@mcp.tool()
def pc_open(target: str) -> str:
"""Öffnet eine URL im Browser oder eine Datei/Programm auf dem Windows-PC.
Für URLs: 'https://...' → öffnet im Standard-Browser.
Für Dateien/Programme: 'C:\\Pfad\\zur\\Datei.txt' oder 'notepad.exe'"""
result = _pc("/open", {"target": target})
if "error" in result:
return f"FEHLER: {result['error']}"
return f"'{target}' geöffnet." if result.get("ok") else str(result)
@mcp.tool()
def pc_status() -> str:
"""Prüft ob der Windows-PC Executor erreichbar ist und gibt Hostname zurück.
Rufe dies auf um zu prüfen ob PC-Steuerung möglich ist, bevor du andere pc_* Tools nutzt."""
result = _pc("/health")
if "error" in result:
return f"PC Executor NICHT erreichbar: {result['error']}"
return f"PC Executor erreichbar. Hostname: {result.get('host', '?')}, Status: {result.get('status', '?')}"
if __name__ == "__main__":
mcp.run()