Skip to main content
Glama
perhamm

ssh-mcp-server

by perhamm

ssh-mcp-server

CI npm

MCP-Server über SSH: Der Agent führt Befehle auf entfernten Maschinen aus, während Schlüssel, Passwörter und sudo auf unserer Seite bleiben.

Ein Fork von classfang/ssh-mcp-server unter ISC.

Deutsch | English

Was ist das?

ssh-mcp-server ist eine Brücke zwischen einem MCP-Client (Claude Code, Cursor, Cline) und SSH. Der Agent ruft Werkzeuge auf, der Server verbindet sich mit der Maschine und gibt die Ausgabe zurück. Das Modell sieht weder den privaten Schlüssel noch das Passwort noch das sudo-Passwort: All das wird aus der lokalen Konfiguration und den Umgebungsvariablen des Prozesses gelesen.

Ein einzelner Server bedient beliebig viele Hosts. Der Host wird spontan über einen Alias aus ~/.ssh/config gewählt, daher muss nicht jede Maschine in die Konfiguration des MCP-Clients eingetragen werden.

Related MCP server: ssh-mcp-server

Was in diesem Fork hinzugefügt wurde

Funktion

Wozu

Hosts aus ~/.ssh/config dynamisch

Ein MCP für die gesamte Flotte. Der Alias wird in connectionName übergeben, die Verbindung wird beim ersten Zugriff aufgebaut

ProxyJump

Ein Host hinter einem Bastion ist über den Alias erreichbar, die ProxyJump-Kette wird aus der SSH-Konfiguration ausgelesen

sudo aus einer Umgebungsvariable

Der Agent fragt sudo: true an, der Server setzt das Passwort ein und entfernt es aus der Ausgabe

Verbotener Kern

Eine Liste von Operationen, die niemals ausgeführt werden: weder unter sudo, noch in irgendeinem Profil, noch über SFTP

Guard-Profile

Fertiger Satz von Verboten safe und Whitelist readonly, versioniert und aktualisierbar

Tunnel

SOCKS5 (Analogon zu ssh -D) und Portweiterleitung (Analogon zu ssh -L) auf einen beliebigen lokalen Port

Host-Key-Prüfung

known_hosts wird standardmäßig abgeglichen, ein Schlüssel, der nicht in der Liste steht, führt zur Verweigerung der Verbindung

Moderne Kryptografie

Ed25519 zuerst in der Liste, ohne SHA-1, CBC und DSA

Audit-Log

Jeder Aufruf wird in JSONL mit Rotation und gzip-Archiven geschrieben

Kein Datei-Upload

upload wird standardmäßig nicht veröffentlicht: Eine Datei, die die Guards nicht lesen, ist ein Weg, Code auf den Host zu bringen

Werkzeuge

Werkzeug

Was es tut

execute-command

Führt einen Befehl aus, unterstützt sudo und einen beliebigen connectionName

download

Holt eine Datei vom Server

list-servers

Zeigt konfigurierte Verbindungen, deren Status und das aktive Guard-Profil

list-ssh-hosts

Zeigt Aliase aus der SSH-Konfiguration, die als connectionName verfügbar sind

open-tunnel

Startet einen SOCKS5-Proxy oder eine Portweiterleitung über die Verbindung

close-tunnel

Schließt einen Tunnel

list-tunnels

Zeigt offene Tunnel und Verbindungszähler

list-ssh-hosts erscheint nur mit dem Flag --ssh-config-hosts, Tunnel-Werkzeuge werden durch das Flag --disable-tunnels entfernt. Das Werkzeug upload ist nicht in der Liste: Es wird nur mit dem Flag --enable-upload veröffentlicht.

Host-Fakten ohne überflüssige Befehle

Beim Verbinden erfasst der Server einmal den Zustand der Maschine: Name, Adressen, Betriebssystem, Kernel, Uptime, Festplatte, Speicher, Anzahl der Prozesse. Alle Abfragen sind mit Markern zu einem einzigen Befehl zusammengefügt, sodass es ein einziger SSH-Zugriff ist und nicht sechs.

Der Server hält das Ergebnis im Cache und gibt es in list-servers aus:

[connected] prod-1 | deploy@10.0.0.5:22 | hostname=prod-1 | os=Linux | updated=2026-08-19T18:14:23Z

Raw JSON:
[{"name":"prod-1","connected":true,"guards":"guards=safe ruleset=2026.08.19 ...",
  "status":{"reachable":true,"osVersion":"Ubuntu 24.04.1 LTS","kernelVersion":"6.8.0-51-generic",
  "uptime":"12 days","diskSpace":{"free":"9.8G","total":"229.6G"},
  "memory":{"free":"5.6G","total":"15.5G"},"processes":{"running":214}}}]

Das heißt, uname -a, df -h, free -h und uptime müssen nicht abgefragt werden, die Antworten sind bereits da. Der Agent ruft list-servers einmal auf und liest den Status daraus.

Die Abfragen werden einzeln durch die Guards geleitet. Mit einer Whitelist bleiben im Status nur die erlaubten Felder. Ein unvollständiger Status bedeutet nicht, dass der Host nicht erreichbar ist.

Ein erfolgreicher Befehl ohne Ausgabe gibt [exit code] 0 zurück, keine leere Zeichenkette. Eine leere Antwort liest das Modell als unklaren Ausgang und prüft über echo $? nach – das ist ein zusätzlicher Zugriff auf den Server und zusätzliche Token.

Schnellstart: ein Server für die gesamte Flotte

Konfiguration des MCP-Clients:

{
  "mcpServers": {
    "ssh": {
      "command": "npx",
      "args": [
        "-y",
        "@perhamm/ssh-mcp-server",
        "--ssh-config-hosts",
        "--guards-profile", "safe"
      ],
      "env": {
        "SSH_MCP_SUDO_PASSWORD": "..."
      }
    }
  }
}

Danach arbeitet der Agent so:

  1. Ruft list-ssh-hosts auf und findet den gewünschten Alias, z. B. r-ulybka-prod-master. Bei großen Konfigurationen wird die Liste gekürzt, daher übergibt der Agent filter: eine Teilzeichenkette oder ein Muster wie r-ulybka-*.

  2. Ruft execute-command mit connectionName: "r-ulybka-prod-master" auf.

  3. Der Server liest den Alias aus ~/.ssh/config, übernimmt daraus HostName, User, Port, IdentityFile und ProxyJump, baut die Verbindung auf und führt den Befehl aus.

Der Schlüssel verlässt dabei die Maschine nicht: Der Server liest die Datei selbst, in den Dialog gelangt nur der Pfad aus der SSH-Konfiguration. Wenn IdentityFile nicht angegeben ist, wird der ssh-agent aus SSH_AUTH_SOCK verwendet. Ein Alias ohne HostName verbindet sich unter seinem eigenen Namen, wie es ssh tut.

Erreichbar ist nur der Alias, der in der Konfiguration als eigener Host-Block deklariert ist. Ein Host *-Block liefert Standardwerte, macht aber keinen beliebigen Namen zu einem erreichbaren Host.

Die Liste der Aliase lässt sich eingrenzen:

"args": [
  "-y", "@perhamm/ssh-mcp-server",
  "--ssh-config-hosts",
  "--allowed-hosts", "r-ulybka-*,*-stage-*",
  "--ssh-config-file", "/home/user/.ssh/config_work"
]

Die Muster unterstützen * und ?. Passt ein Alias auf kein Muster, wird keine Verbindung aufgebaut und der Agent erhält den Fehler SSH_HOST_NOT_ALLOWED.

Sicherheitsmodus und Guards

Guards sind ein versionierter Regelsatz, der jeden Befehl vor dem Senden an den Server prüft. Die Regeln liegen in guards/default-guards.json und werden zusammen mit dem Repository aktualisiert.

Profile

Profil

Verhalten

off

Die Profilregeln sind deaktiviert, nur der verbotene Kern ist aktiv. Standardwert

safe

Zusätzlich Verbot destruktiver Befehle, alles andere ist erlaubt

readonly

Erlaubt nur Lesen und Diagnose, erbt alle Verbote von safe

"args": ["-y", "@perhamm/ssh-mcp-server", "--ssh-config-hosts", "--guards-profile", "safe"]

Was safe zusätzlich zum Kern abfängt: shutdown und reboot, Zurücksetzen der Firewall, Stoppen von sshd und kubelet, kubectl delete, helm uninstall, docker system prune, Entfernen von Paketen, DROP DATABASE, curl | sh, git push --force, Bereinigen von Logs, Entladen von Kernelmodulen, interaktive Editoren. Die vollständige Liste mit Begründungen steht im JSON.

readonly verlangt zusätzlich, dass jeder Teil des Befehls in die Whitelist fällt: ls, cat, grep, find, ps, ss, df, journalctl, systemctl status, kubectl get/describe/logs, docker ps/logs und Ähnliches. sudo ist in diesem Profil vollständig verboten, zusammen mit su, doas und pkexec.

Verbotener Kern

Einige Operationen werden niemals ausgeführt: weder im Profil off, noch unter sudo, noch über eine eigene Guard-Datei, noch über SFTP an Befehlen vorbei. Die Liste lebt im Block forbidden.

Kategorie

Was gesperrt ist

Benutzerkonten

useradd, usermod, userdel, groupadd, passwd, chpasswd, chage, vipw sowie Schreiben in /etc/passwd, /etc/shadow, /etc/group

sudo

Schreiben in /etc/sudoers und /etc/sudoers.d, visudo

Zeitpläne

crontab außer crontab -l, Schreiben in /etc/cron*, /var/spool/cron, /etc/anacrontab, die Befehle at und batch

systemd

Schreiben von Units und Timern in /etc/systemd, /lib/systemd, /usr/lib/systemd, systemctl edit, systemd-run

SSH

Bearbeiten von /etc/ssh/*, ~/.ssh/*, authorized_keys, sshd_config sowie ssh-keygen, ssh-copy-id, ssh-add

Interpreter

python, perl, ruby, node, php, lua, Rscript und das Starten eines Skripts als Datei: bash /tmp/x.sh, sh -s, source. Guards lesen keinen fremden Code, daher ist ein solcher Start vollständig gesperrt

Massenlöschung

rm -r eines Verzeichnisses der ersten Ebene oder eines Systemunterverzeichnisses, rm -r mit Maske, find -delete, Löschen über xargs rm, das Flag --no-preserve-root

Datenträger und Geheimnisse

mkfs, wipefs, dd of=/dev/, Schreiben in /dev/sd*, Fork-Bombe, Lesen von /etc/shadow und privaten Schlüsseln

Die normale Arbeit bleibt dabei erhalten: crontab -l, cat /etc/ssh/sshd_config, systemctl restart nginx, rm -rf /var/lib/myapp/cache/tmp kommen durch. Auch ein zerlegtes bash -c "..." funktioniert: Sein Inhalt wird mit denselben Regeln geprüft.

Der Kern sperrt auch die Datei-Werkzeuge. download holt weder /etc/shadow noch den Inhalt von ~/.ssh, und allowedRemotePaths erlaubt hier nichts zurück. Auch die lokale Seite ist geschützt: download legt keine Datei in unser eigenes ~/.ssh ab.

Der Datei-Upload ist vollständig deaktiviert. Das Werkzeug upload wird nicht veröffentlicht, solange nicht --enable-upload übergeben wird, und das Profil readonly lehnt den Upload auch mit diesem Flag ab.

Wenn der Server gerade zum Anlegen von Benutzern oder zum Bearbeiten von Cron benötigt wird, muss der Kern bewusst im Fork angepasst werden: Es gibt kein Flag, das ihn aufhebt.

Warum das nicht mit einem Semikolon umgangen wird

Der Befehl wird anhand von ;, |, &&, ||, &, Zeilenumbrüchen und Substitutionen $(...) in Teile zerlegt, und jeder Teil wird einzeln geprüft. Anführungszeichen werden beim Zerlegen berücksichtigt. So kommt ls; rm -rf / in keinem Profil durch, obwohl die gesamte Zeile mit dem erlaubten ls beginnt.

Wrapper werden vor der Prüfung entfernt: sudo, env, timeout 5, nohup und Zuweisungen wie LC_ALL=C verstecken den Befehl nicht vor den Regeln. Ein Skript innerhalb von bash -c "..." wird separat zerlegt und mit denselben Regeln geprüft. Die Befehlslänge ist auf 5000 Zeichen begrenzt.

Guards verhindern Fehler des Agenten, keinen absichtlichen Umgehungsversuch. Einen Interpreter mit beliebigem Code darin, wie python -c, können die Regeln nicht zerlegen. Wo eine Umgehung nicht zulässig ist, schränken wir die Rechte des SSH-Benutzers selbst ein.

Aktualisierung der Regeln

Drei Möglichkeiten, die Regeln aktuell zu halten:

  1. Wir mergen den Upstream in unseren Fork. Die Regeln werden über das Feld version versioniert, die Version ist in list-servers und im Ablehnungstext sichtbar.

  2. Wir halten eine eigene Datei und geben sie über --guards-file /etc/ssh-mcp/guards.json an. Die Regeln daraus werden zu den eingebauten hinzugefügt, die Version wird 2026.08.19+local-1.

  3. Wir aktualisieren die Datei nach Zeitplan:

node scripts/update-guards.js https://example.com/guards.json /etc/ssh-mcp/guards.json

Das Skript prüft das JSON und kompiliert jeden regulären Ausdruck, und erst danach ersetzt es die Datei. Eine beschädigte Übertragung zerstört nicht den funktionierenden Regelsatz.

Format der eigenen Datei:

{
  "version": "local-1",
  "profiles": {
    "safe": {
      "deny": [
        { "id": "no-ansible", "pattern": "^ansible-playbook\\b", "reason": "выкат идёт из CI" }
      ]
    }
  }
}

Das Feld scope: "command" zwingt die Regel, den gesamten Befehl zu prüfen und nicht nur Teile. So funktionieren die Regeln zu curl | sh und SQL. In den Block forbidden der eigenen Datei können eigene Verbote ergänzt werden, aber die eingebauten lassen sich daraus nicht entfernen: Die Listen werden addiert.

Die alten --whitelist und --blacklist sind weiterhin vorhanden und werden vor den Guards geprüft.

sudo ohne Passwort im Dialog

Das sudo-Passwort liegt in der Umgebungsvariable des Serverprozesses. Der Agent übergibt sudo: true, sieht das Passwort selbst aber weder in den Aufrufargumenten noch in der Ausgabe.

{
  "mcpServers": {
    "ssh": {
      "command": "npx",
      "args": ["-y", "@perhamm/ssh-mcp-server", "--ssh-config-hosts", "--guards-profile", "safe"],
      "env": {
        "SSH_MCP_SUDO_PASSWORD": "..."
      }
    }
  }
}

Tool-Aufruf:

{
  "tool": "execute-command",
  "params": {
    "cmdString": "systemctl restart nginx",
    "connectionName": "r-ulybka-prod-master",
    "sudo": true
  }
}

Der Befehl geht als sudo -S -k -p '' -u root -- /bin/sh -c '<Befehl>' an den Server, das Passwort wird in den stdin-Kanal geschrieben. In der Kommandozeile steht es nicht, daher landet es weder in ps noch in der History. Für solche Befehle wird das Pseudoterminal deaktiviert, sonst würde das tty die Eingabe zurück in die Ausgabe spiegeln. Sicherheitshalber wird das Passwort aus der Ausgabe und aus Fehlertexten entfernt.

Name der Variable und des Zielbenutzers werden über die Flags --sudo-password-env und --sudo-user geändert. Ist die Variable leer, schlägt der Aufruf bereits vor der Verbindung mit SUDO_PASSWORD_MISSING fehl. Das Profil readonly verbietet sudo grundsätzlich.

Im Modus shell wird das Passwort als separate Zeile direkt hinter dem Befehl angehängt, da sudo denselben stdin liest wie die Shell selbst. Das Flag -k stellt sicher, dass die Passwortabfrage immer erfolgt und die Zeile nicht als Befehl ausgeführt wird. Für sudo ist der Modus exec besser.

Tunnel

open-tunnel startet einen lokalen Listener und leitet den Datenverkehr über die SSH-Verbindung. Nützlich, wenn es keinen direkten Zugriff auf die Cluster-Dienste gibt, aber auf den Knoten schon.

SOCKS5 auf Port 8777:

{
  "tool": "open-tunnel",
  "params": {
    "type": "socks5",
    "localPort": 8777,
    "connectionName": "r-ulybka-prod-master"
  }
}

Danach läuft jeder Client über den Proxy, Namen werden auf der entfernten Seite aufgelöst:

curl --socks5-hostname 127.0.0.1:8777 http://prometheus.monitoring.svc:9090/api/v1/query?query=up
kubectl --request-timeout=30s ... # через HTTPS_PROXY=socks5h://127.0.0.1:8777

Weiterleitung eines einzelnen Ports, Analog zu ssh -L:

{
  "tool": "open-tunnel",
  "params": {
    "type": "local",
    "localPort": 15432,
    "remoteHost": "pg-master.internal",
    "remotePort": 5432
  }
}

Wenn localPort nicht angegeben ist, wählt das System den Port und gibt ihn in der Antwort zurück. Tunnel leben bis close-tunnel, bis zum Abbruch der SSH-Verbindung oder bis zum Stopp des Servers.

Einschränkungen werden über Flags gesetzt:

Flag

Standardwert

Funktion

--tunnel-bind-address

127.0.0.1

Adresse, auf der Tunnel lauschen

--allowed-tunnel-ports

keine Einschränkung

Liste der Ports, die belegt werden dürfen

--max-tunnels

8

Wie viele Tunnel gleichzeitig gehalten werden

--disable-tunnels

aus

Entfernt die Tunnel-Tools aus der Liste

Der Listener wird standardmäßig auf Loopback gestartet. Eine Adresse weiter als Loopback öffnet den Proxy in Ihr Netzwerk, daher ändern wir sie bewusst.

Prüfung der Host-Keys

Der Serverschlüssel wird bei jeder Verbindung mit known_hosts abgeglichen, einschließlich der Zwischenhosts der ProxyJump-Kette. Standardmäßig gilt der Modus strict: Ein Host, der nicht in known_hosts steht, bedeutet Ablehnung.

Modus

Verhalten

strict

Standard. Verbindung nur zu Hosts aus known_hosts

accept-new

Unbekannter Host wird bei der ersten Verbindung eingetragen, Schlüsselabweichung bleibt Ablehnung

off

Keine Prüfung, Verhalten des Upstreams

Geprüft werden ~/.ssh/known_hosts, ~/.ssh/known_hosts2 und /etc/ssh/ssh_known_hosts, und für einen Alias mit UserKnownHostsFile die Datei, die in der SSH-Konfiguration angegeben ist. Eine eigene Liste wird über das Flag --known-hosts-file gesetzt. Verstanden werden gehashte Einträge, Muster, die Form [host]:port und das Label @revoked.

Die Ablehnung kommt mit dem Code SSH_HOST_KEY_REJECTED und einem Text mit dem Fingerprint:

Host key of prod.example.com is not in known_hosts (~/.ssh/known_hosts): ssh-ed25519 SHA256:xxxx.
Verify that fingerprint, add the host to known_hosts, or start the server with --host-key-checking accept-new.

Eine Schlüsselabweichung wird in keinem Modus und niemals akzeptiert: Der Server weigert sich, sich zu verbinden, und schreibt, dass der Host entweder neu erstellt wurde oder jemand in der Mitte steht.

Für die erste Bekanntschaft mit dem Bestand ist es praktisch, einmal mit --host-key-checking accept-new durchzugehen und danach wieder strict zu setzen.

Audit-Log

Jeder Aufruf wird als JSON-Zeile geschrieben: Befehl, Verbindung, sudo-Flag, Guard-Urteil, Dauer, Ausgabeumfang. Der Inhalt der Ausgabe gelangt nicht ins Log, das sudo-Passwort wird entfernt.

{"time":"2026-08-19T08:12:44.101Z","pid":8123,"event":"command","result":"blocked","connection":"r-ulybka-prod-master","command":"useradd deploy","sudo":true,"code":"COMMAND_VALIDATION_FAILED","reason":"Blocked by the forbidden core ..."}
{"time":"2026-08-19T08:12:51.880Z","pid":8123,"event":"command","result":"ok","connection":"r-ulybka-prod-master","command":"systemctl status nginx","sudo":false,"durationMs":412,"bytes":1840}

Geschrieben werden die Ereignisse connect, command, download, upload, tunnel-open, tunnel-close, host-key.

Standardmäßig liegt die Datei unter $XDG_STATE_HOME/ssh-mcp-server/audit.jsonl, also üblicherweise ~/.local/state/ssh-mcp-server/audit.jsonl, mit Rechten 0600.

Flag

Standard

Funktion

--audit-log <path>

XDG-State-Verzeichnis

Pfad zum Log, der Wert off deaktiviert das Schreiben

--audit-max-size <bytes>

10485760

Größe, nach der die Datei rotiert wird. 0 deaktiviert die eingebaute Rotation

--audit-keep <count>

10

Wie viele gzip-Archive gehalten werden

Die Rotation ist eingebaut: Bei Erreichen des Limits wandert die aktuelle Datei nach audit.jsonl.1.gz, alte Archive werden verschoben, alles außerhalb von --audit-keep wird gelöscht. Zehn Archive à 10 MiB sind in unkomprimierter Form etwa hundert Megabyte und nach gzip deutlich weniger.

Wenn die Logs bereits von logrotate verwaltet werden, setzen wir --audit-max-size 0 und konfigurieren die Rotation im Modus copytruncate.

Ein Schreibfehler bringt den Befehl nicht zu Fall: Der Server schreibt einmal darüber nach stderr und arbeitet weiter.

Verbindungsarten

Unten Szenarien vom Einfachen zum Komplexen. In args sind jedes Flag und sein Wert zwei separate Array-Elemente: "--host", "192.168.1.1", nicht "--host 192.168.1.1".

Login und Passwort

{
  "mcpServers": {
    "ssh-mcp-server": {
      "command": "npx",
      "args": [
        "-y",
        "@perhamm/ssh-mcp-server",
        "--host", "192.168.1.1",
        "--port", "22",
        "--username", "root",
        "--password", "pwd123456"
      ]
    }
  }
}

Privater Schlüssel

"args": [
  "-y", "@perhamm/ssh-mcp-server",
  "--host", "192.168.1.1",
  "--username", "root",
  "--privateKey", "~/.ssh/id_rsa",
  "--passphrase", "pwd123456"
]

Das Passwort für den Schlüssel muss nicht in die Konfiguration geschrieben werden, sondern kann in die Variable SSH_MCP_PASSPHRASE gelegt werden.

Ein Alias aus ~/.ssh/config

"args": ["-y", "@perhamm/ssh-mcp-server", "--host", "myserver"]

Der Server liest HostName, Port, User, IdentityFile und ProxyJump aus dem Block Host myserver, einschließlich der Direktiven Include und Muster. Flags der Kommandozeile haben Vorrang: --port 2222 überschreibt den Port aus der Konfiguration.

Bastion und ProxyJump

Wenn der Alias ein ProxyJump hat, wird die Kette automatisch aufgebaut:

Host r-ulybka-prod-master
    HostName 10.20.30.40
    User ops
    ProxyJump bastion
    IdentityFile ~/.ssh/prod_key

Jeder nächste Hop verbindet sich über den Kanal des vorherigen, wie es ssh -J tut. Die Kette kann auch manuell angegeben werden: --proxy-jump "bastion,gateway:2222". Die Tiefe der Kette ist auf fünf Hops begrenzt.

Proxy

"args": [
  "-y", "@perhamm/ssh-mcp-server",
  "--host", "192.168.1.1",
  "--username", "root",
  "--password", "pwd123456",
  "--proxy", "socks5://user:pwd@proxy-host:1080"
]

Unterstützt werden socks://, socks5://, http:// und https://. HTTP und HTTPS laufen über die Methode CONNECT mit Basic-Authentifizierung, Standardport 80 und 443. Für SOCKS5 ist der Port Pflicht. Das alte Flag --socksProxy funktioniert, akzeptiert aber nur SOCKS. --proxy und --proxy-jump werden nicht zusammen verwendet.

Jump-Host mit interaktiver Shell

transportMode ist standardmäßig exec. Wir wechseln auf shell, wenn nach erfolgreichem Login Befehle nicht ausgeführt werden oder das Gerät nur eine interaktive Sitzung ausgibt:

"args": [
  "-y", "@perhamm/ssh-mcp-server",
  "--host", "bastion.example.com",
  "--username", "ops",
  "--password", "pwd123456",
  "--transport-mode", "shell",
  "--shell-ready-timeout", "15000"
]

Im Modus shell laufen Befehle nacheinander über eine permanente Sitzung, und upload und download funktionieren nicht: SFTP ist dort deaktiviert.

Zwei-Faktor-Authentifizierung

Das Flag --try-keyboard aktiviert keyboard-interactive. Passwort und Schlüssel werden automatisch eingesetzt, der Code aus dem zweiten Faktor wird aus der Variable SSH_MCP_2FA_CODE gelesen.

Mehrere Verbindungen in einem Server

Neben den Aliasen aus der SSH-Konfiguration bleibt die alte Methode: eine Datei mit der Beschreibung der Verbindungen.

[
  {
    "name": "dev",
    "host": "1.2.3.4",
    "port": 22,
    "username": "alice",
    "privateKey": "~/.ssh/dev_key",
    "guardProfile": "safe",
    "commandTimeoutMs": 120000
  },
  {
    "name": "prod",
    "host": "5.6.7.8",
    "port": 22,
    "username": "bob",
    "privateKey": "~/.ssh/prod_key",
    "guardProfile": "readonly",
    "allowedRemotePaths": ["/var/log", "/tmp"]
  }
]
"args": ["-y", "@perhamm/ssh-mcp-server", "--config-file", "/abs/path/ssh-config.json"]

Auch das Objektformat, bei dem der Schlüssel der Verbindungsname ist, wird unterstützt. Die Verbindung wird über den Parameter connectionName gewählt, ohne ihn wird die erste genommen.

Einschränkungen für Befehle und Pfade

Weiße und schwarze Listen

"args": [
  "-y", "@perhamm/ssh-mcp-server",
  "--host", "192.168.1.1",
  "--username", "root",
  "--privateKey", "~/.ssh/id_rsa",
  "--whitelist", "^ls( .*)?,^cat .*,^df.*",
  "--blacklist", "^rm .*,^shutdown.*"
]

Muster sind reguläre Ausdrücke, durch Kommas getrennt. Der Befehl wird zuerst gegen die weiße Liste geprüft, dann gegen die schwarze, dann gegen das Guard-Profil, und muss alle drei Prüfungen bestehen.

Befehlsvorlage

--command-template umhüllt jeden Befehl. <quotedCommand> setzt den Befehl als maskiertes Argument ein, <command> fügt ihn unverändert ein. Die Vorlage wird nach der Substitution des Arbeitsverzeichnisses angewendet.

su root -c <quotedCommand>
docker exec -i mycontainer sh -c <quotedCommand>

Pfade für Dateioperationen

--allowed-local-paths erweitert die Liste der lokalen Verzeichnisse, die für upload und download verfügbar sind (standardmäßig nur das aktuelle Verzeichnis). --allowed-remote-paths begrenzt die entfernten Pfade, dort schreiben wir absolute POSIX-Pfade durch Kommas getrennt. Ohne dieses Flag sieht SFTP das gesamte Dateisystem des Hosts, wovor der Server beim Start warnt.

Timeouts und Ausgabelimit

Parameter

Standard

Was begrenzt wird

timeout im Tool-Aufruf

keine

Einen Befehl, überschreibt die Verbindungseinstellungen

commandTimeoutMs

30000

Befehl im Modus exec

shellCommandTimeoutMs

30000

Befehl im Modus shell

connectionTimeoutMs

30000

Verbindungsaufbau und Handshake

sftpTimeoutMs

300000

SFTP-Operationen

maxOutputBytes

10485760

Erfasste Ausgabe eines Befehls

keepaliveIntervalMs

10000

Keepalive-Intervall

Bei Überschreitung des Ausgabelimits wird der Befehl abgebrochen und das Tool gibt OUTPUT_LIMIT_EXCEEDED zusammen mit dem bereits gesammelten Teil zurück. Fehler kommen als Struktur aus code, message und retriable.

Flags der Kommandozeile

  --config-file <path>             Файл с описанием соединений
  --ssh-config-file <path>         Путь к SSH-конфигу (по умолчанию ~/.ssh/config)
  --ssh <config>                   Соединение как JSON или пары key=value
  -h, --host <host>                Хост или алиас из SSH-конфига
  -p, --port <port>                Порт
  -u, --username <name>            Пользователь
  -w, --password <password>        Пароль
  -k, --privateKey <path>          Путь к приватному ключу
  -P, --passphrase <passphrase>    Пароль от ключа
  -a, --agent <path>               Сокет ssh-agent
  -W, --whitelist <patterns>       Белый список команд, через запятую
  -B, --blacklist <patterns>       Чёрный список команд, через запятую
  --proxy <url>                    Прокси SOCKS5, HTTP или HTTPS
  -s, --socksProxy <url>           Старый флаг только для SOCKS5
  --allowed-local-paths <paths>    Локальные каталоги для upload и download
  --allowed-remote-paths <paths>   Удалённые каталоги для SFTP
  --transport-mode <mode>          exec или shell (по умолчанию exec)
  --shell-ready-timeout <ms>       Таймаут готовности shell (по умолчанию 10000)
  --command-template <template>    Шаблон с <command> или <quotedCommand>
  --pty                            Псевдотерминал для exec (по умолчанию включён)
  --try-keyboard                   Keyboard-interactive для 2FA
  --pre-connect                    Подключиться ко всем хостам при старте
  --ssh-config-hosts               Разрешить хосты из SSH-конфига на лету
  --allowed-hosts <patterns>       Шаблоны разрешённых алиасов, через запятую
  --proxy-jump <chain>             Цепочка ProxyJump, через запятую
  --guards-profile <name>          off, safe или readonly (по умолчанию off)
  --guards-file <path>             Свой набор правил поверх встроенного
  --sudo-password-env <var>        Переменная с паролем sudo
  --sudo-user <user>               Пользователь для sudo (по умолчанию root)
  --host-key-checking <mode>       strict, accept-new или off (по умолчанию strict)
  --known-hosts-file <paths>       Свои файлы known_hosts, через запятую
  --host-key-algorithms <list>     Алгоритмы хост-ключа, через запятую
  --enable-upload                  Опубликовать инструмент upload (по умолчанию выключен)
  --audit-log <path|off>           Путь к аудит-логу (по умолчанию каталог состояния XDG)
  --audit-max-size <bytes>         Порог ротации, 0 отключает (по умолчанию 10485760)
  --audit-keep <count>             Сколько архивов держим (по умолчанию 10)
  --disable-tunnels                Убрать туннельные инструменты
  --tunnel-bind-address <addr>     Адрес для туннелей (по умолчанию 127.0.0.1)
  --allowed-tunnel-ports <ports>   Разрешённые порты туннелей, через запятую
  --max-tunnels <count>            Лимит одновременных туннелей (по умолчанию 8)
  --version, -v                    Версия пакета
  --help                           Справка

Sicherheit

  • Für Produktion aktivieren wir --guards-profile safe, für die Bereitschaftsanalyse von Vorfällen passt readonly. Mit off bleibt nur der verbotene Kern: Alles andere wird ausgeführt, wovor der Server auch im Log warnt.

  • Der Schlüssel, sein Passwort und das sudo-Passwort werden aus Dateien und Umgebungsvariablen gelesen. In der MCP-Client-Konfiguration speichern wir den Pfad zum Schlüssel, nicht den Schlüssel selbst.

  • Tunnel lauschen auf Loopback. SOCKS5 hat keine Authentifizierung, daher öffnet ein Proxy auf 0.0.0.0 das interne Netzwerk für alle, die den Port erreichen, und der Server schreibt beim Start eine Warnung darüber.

  • Ohne --allowed-remote-paths werden über SFTP beliebige Pfade auf dem Host gelesen und geschrieben, einschließlich ~/.ssh/authorized_keys.

  • Der Host-Key wird im Modus strict mit known_hosts abgeglichen. Die Prüfung über --host-key-checking off zu deaktivieren, lohnt sich nur im Labor.

  • Es gibt keine Einschränkungen der Aufruffrequenz.

Entwicklung

npm install
npm run build
npm test

Die Tests laufen mit dem eingebauten Node.js-Runner und liegen in test/.

Upstream und Lizenz

Das Projekt ist aus classfang/ssh-mcp-server (Autor junki.cn) entstanden, Lizenz ISC. Das Copyright des Upstreams ist in LICENSE erhalten, dort befindet sich auch der Link zum Quellrepository.

Der Guard-Satz ist teilweise nach Ideen von tufantunc/ssh-mcp (MIT) zusammengestellt.

Das Paket in NPM: @perhamm/ssh-mcp-server.

F
license - not found
Not graded
quality - not tested
A
maintenance

Maintenance

Maintainers
Response time
0dRelease cycle
6Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    B
    quality
    F
    maintenance
    An MCP server that allows AI models to execute system commands on local machines or remote hosts via SSH, supporting persistent sessions and environment variables.
    1
    32
    28
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    An MCP server that enables remote SSH command execution and bidirectional file transfers through a standardized interface. It allows AI assistants to securely manage remote servers while keeping credentials isolated and applying command-level security controls.
    ISC
  • A
    license
    A
    quality
    B
    maintenance
    MCP server for managing multiple SSH servers via AI assistants, offering tools for remote command execution, file operations, and system monitoring.
    11
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    An MCP server that gives AI agents SSH capabilities to execute commands, transfer files, and inspect remote systems through a preconfigured host list.
    43
    MIT

View all related MCP servers

Related MCP Connectors

  • MCP server for AI agents to plan, verify, and deploy Cloudflare-native apps.

  • MCP server for secureFlows: token-free URL builders and integration-linting tools for AI agents.

  • MCP server connecting AI agents to non-custodial staking data across 130+ networks.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/perhamm/ssh-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server