Neuster Artikel:

Datum

Ziel: Ein lokales LLM in Unsloth Studio ruft kontrollierte MCP-Befehle auf. Der MCP-Server führt diese Befehle über SSH in der Rocky-Linux-VM aus.

Dieses Beispiel ist auf das rudimentärste begrenzt und überlässt dem LLM vollen root Zugriff auf die VM.
Ein praktisches Beispiel ist am Ende des Artikels eingefügt.

Vorbereitungen

Benötigt wird:
  • eine Linux-VM, auf die von lokal per SSH (Key) zugegriffen werden kann
  • ein Linuxbenutzer mit root-Rechten (ohne Passwort)
  • Unsloth Studio (mit heruntergeladenem LLM, ich verwende unsloth/Qwen3.8-27B-GGUF:UD-Q5_K_M)

Der MCP-Server läuft unter Windows und benutzt den dort vorhandenen OpenSSH-Client. Alle folgenden Befehle werden in PowerShell ausgeführt.

Python-Umgebung unter Windows

Schritt 1 – Projektordner erstellen


New-Item -ItemType Directory -Path "C:\VM-MCP" -Force
Set-Location "C:\VM-MCP"

Schritt 2 – Virtuelle Python-Umgebung


py --version
py -m venv .venv
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
.\.venv\Scripts\Activate.ps1

Nach erfolgreicher Aktivierung beginnt die PowerShell-Zeile mit (.venv).
(.venv) PS C:\VM-MCP>

Schritt 3 – MCP-SDK installieren


python -m pip install --upgrade pip
pip install "mcp[cli]" pyyaml
python -c "import mcp; print('MCP-Paket erfolgreich geladen')"
MCP-Server einrichten

Speicherort des Servers:
C:\VM-MCP\server.py
Das folgende Skript verwendet absolute Windows-Pfade. Dadurch ist es unabhängig davon, ob Unsloth die Datei C:\Users\<Benutzer>\.ssh\config lädt.
Die Befehle werden direkt mit root-Rechten entsprechend umgesetzt.


import logging
import shlex
import socket
import sys
import paramiko
from mcp.server import MCPServer
logging.getLogger("paramiko").setLevel(logging.WARNING)
mcp = MCPServer("Rocky Linux VM Root")
# ============================================================
# SSH-Konfiguration
# ============================================================
SSH_HOST = "127.0.0.1"
SSH_PORT = 2222
SSH_USER = "r"
SSH_KEY = r"C:\Users\r\.ssh\rocky_mcp_ed25519"
SSH_KNOWN_HOSTS = r"C:\Users\r\.ssh\known_hosts"
SSH_TIMEOUT = 600
MAX_SSH_TIMEOUT = 1800
# ============================================================
# Interne SSH-Ausführung
# ============================================================
def run_ssh(command: str, timeout: int = SSH_TIMEOUT) -> str:
    """Führt einen Linux-Befehl über eine geprüfte SSH-Verbindung aus."""
    client = paramiko.SSHClient()
    try:
        client.load_host_keys(SSH_KNOWN_HOSTS)
        client.connect(
            SSH_HOST,
            port=SSH_PORT,
            username=SSH_USER,
            key_filename=SSH_KEY,
            timeout=10,
            banner_timeout=10,
            auth_timeout=10,
            allow_agent=False,
            look_for_keys=False,
        )
        _, stdout, stderr = client.exec_command(
            command,
            timeout=timeout,
        )
        stdout_text = stdout.read().decode("utf-8", errors="replace")
        stderr_text = stderr.read().decode("utf-8", errors="replace")
        exit_code = stdout.channel.recv_exit_status()
    except (TimeoutError, socket.timeout):
        return "FEHLER: Zeitüberschreitung bei der SSH-Ausführung."
    except FileNotFoundError as exc:
        return f"FEHLER: SSH-Datei wurde nicht gefunden.\n{exc}"
    except paramiko.AuthenticationException:
        return "FEHLER: SSH-Authentifizierung fehlgeschlagen."
    except paramiko.BadHostKeyException as exc:
        return f"FEHLER: SSH-Hostschlüssel stimmt nicht überein: {exc}"
    except (PermissionError, paramiko.SSHException) as exc:
        return f"FEHLER: SSH-Verbindung fehlgeschlagen.\n{exc}"
    except Exception as exc:
        return f"FEHLER: {type(exc).__name__}: {exc}"
    finally:
        client.close()
    output_parts = []
    if stdout_text.strip():
        output_parts.append(stdout_text.strip())
    if stderr_text.strip():
        output_parts.append("STDERR:\n" + stderr_text.strip())
    if not output_parts:
        output_parts.append("(Keine Ausgabe)")
    output_parts.append(f"Exit-Code: {exit_code}")
    return "\n\n".join(output_parts)
# ============================================================
# Einziges MCP-Werkzeug
# ============================================================
@mcp.tool()
def run_root_command(command: str, timeout: int = SSH_TIMEOUT) -> str:
    """
    Führt einen beliebigen Linux-Shellbefehl als Root auf der VM aus.
    Verwende dieses Werkzeug für alle Aktionen auf der Rocky-Linux-VM,
    einschließlich Paketverwaltung, Systemupdates, Dienste, Dateien,
    Logs und Diagnose. Der Befehl läuft über sudo -n bash -lc.
    command:
        Vollständiger Linux-Shellbefehl für die VM.
    timeout:
        Maximale Laufzeit in Sekunden, begrenzt auf 1 bis 1800.
    """
    command = str(command).strip()
    if not command:
        return "FEHLER: Der Befehl darf nicht leer sein."
    if "\x00" in command:
        return "FEHLER: Der Befehl enthält ein ungültiges Nullzeichen."
    try:
        timeout = int(timeout)
    except (TypeError, ValueError):
        return "FEHLER: 'timeout' muss eine ganze Zahl sein."
    timeout = max(1, min(timeout, MAX_SSH_TIMEOUT))
    return run_ssh(
        "sudo -n bash -lc " + shlex.quote(command),
        timeout=timeout,
    )
def self_test() -> int:
    """Prüft SSH-Verbindung und Root-Rechte ohne MCP-Client."""
    print(run_root_command("id; hostname; uptime"))
    return 0
if __name__ == "__main__":
    if "--self-test" in sys.argv:
        raise SystemExit(self_test())
    mcp.run(transport="stdio")
MCP-Server lokal prüfen

Set-Location C:\VM-MCP
.\.venv\Scripts\Activate.ps1
python -m py_compile .\server.py
python .\server.py --self-test

Erwartung: Hostname, Benutzer r und uptime erscheinen mit Exit-Code 0. Ein normaler Start wartet ohne sichtbare Ausgabe auf MCP-Nachrichten:
python .\server.py
Beenden mit Strg+C

Verbindung zu Unsloth Studio

In Unsloth Studio den Dialog MCP Servers öffnen und einen lokalen stdio-Server anlegen:
Display name: Rocky Linux
URL or command: C:\VM-MCP\.venv\Scripts\python.exe C:\VM-MCP\server.py
Environment variables leer

Danach Test connection ausführen und bei grünem Test Add server wählen. Das Modell muss ein Instruct-/Chat-Modell mit Tool-Calling-Unterstützung sein.

Erste Prompts

Verbindungstest:
1. das LLM laden und Context Size vergrößern
2. prüfen ob der MCP-Server grün ist

3. der Prompt (Thinking schaltet man am beten erst einmal auf none:

Prüfe über vm_connection_test die Verbindung zu meiner Rocky-Linux-VM und welche Rechte du dort hast

Damit lässt sich die VM komplett administrieren (im Rahmen der Möglichkeiten des LLM):

Autor
Kategorien KI, Automatisierung

Letzte Beiträge:

PRTG Map