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:

  • Mit Hilfe von Access Control Lists (kurz ACL) ist es möglich, einzelnen Nutzern (oder auch Gruppen) gezielt Rechte an einzelnen Dateien zu geben oder zu entziehen. ACLs ergänzen damit die normale Rechteverwaltung von Linux.

    Datum · Autor

  • Squid ist ein Open-Source Proxyserver. Das heißt Daten, die aus dem Internet angefordert werden, speichert Squid lokal in einem Cache und lädt diese bei Bedarf aus dem Zwischenspeicher nach. Dadurch ist der Datentransfer effizienter, weil weniger Netzbelastung durch große Datenmengen erzeugt wird. Aber man kann durch den Einsatz von Filtern auch die Sicherheit des Netzes erhöhen.

    Datum · Autor

  • Bitfehler sind auf Festplatten nicht zu vermeiden. Btrfs kann diese anzeigen und unter Umständen beheben.

    Datum · Autor

PRTG Map