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:

  • Als RAM-Disk wird ein virtuelles Laufwerk bezeichnet, welches aus einem Teil des im System befindlichen Arbeitsspeichers (RAM) besteht. Dabei wird statisch oder auch dynamisch Speicherplatz im RAM belegt, mit einem Dateisystem formatiert und schließlich eingehängt.

    Datum · Autor

  • Der Pi-hole ist eine Lösung zum Blockieren von Werbung und Trackern für das gesamte Netzwerk. Das bedeutet, dass der Pi-hole Werbung und Tracker für alle Geräte im Netzwerk blockieren kann.

    Datum · Autor

  • LXC können die LuCI Funktionen erweitern oder wenn gewünscht, übernehmen.

    Datum · Autor

PRTG Map