SSH MCP Server
officielExécutez des commandes, déplacez des fichiers, recherchez des journaux et auditez des machines via SSH depuis votre agent.
Que pouvez-vous faire avec SSH MCP ?
- Exécuter des commandes avec des garde-fous de sécurité — Demandez à votre assistant d’exécuter des commandes uniques ou groupées via
ssh_exec, avec une protection contre les commandes destructrices qui bloque les opérations irréversibles avant qu’elles n’atteignent le serveur. - Lire, écrire et lister des fichiers distants — Utilisez
ssh_file_read,ssh_file_writeetssh_file_listpour inspecter ou modifier des fichiers, avec des écritures atomiques et une vérification facultative SHA-256. - Rechercher dans les journaux et vérifier l’état du serveur — Interrogez
ssh_log_searchoussh_log_tailsur des fichiers et conteneurs, ou obtenez un instantané structuré de l’état avecssh_snapshotetssh_audit_baseline. - Transférer des fichiers avec vérification d’intégrité — Téléversez ou téléchargez des fichiers et répertoires via
ssh_uploadetssh_download, avec repli automatique surscphérité pour les appareils plus anciens. - Gérer des tâches de fond de longue durée — Détachez les opérations lentes avec
ssh_execet suivez-les viassh_job_status,ssh_job_outputetssh_job_kill, en survivant aux déconnexions.
Documentation
SSH MCP Server — Outils serveur distant pour agents IA
|
|
Un serveur MCP SSH — un outil polyvalent qui vous fait gagner du temps et des tokens, à vous et à votre agent IA, pour le débogage, le développement et la maintenance de serveurs. Exécutez des commandes, déplacez des fichiers, lisez des journaux et auditez des machines via SSH — un VPS cloud, une machine physique, ou le routeur BusyBox qui dort dans votre placard. |
Il utilise le client OpenSSH déjà présent sur votre machine : vos clés, votre ~/.ssh/config, vos hôtes de rebond, votre forwarding d'agent. Rien d'intégré, rien à compiler, aucune liaison native.
Fonctionne avec Claude Code, Codex CLI, Cline, opencode, Gemini CLI, Qwen Code, Hermes et d'autres clients MCP.
Installation · Outils · Configuration · Sécurité · Feuille de route · Documentation · Journal des modifications
Installation en 30 secondes
Aucune installation globale requise. npx télécharge le paquet lors de la première utilisation :
npx -y @hypnosis/ssh-mcp-server
Ajoutez-le à votre client MCP — Claude Code, par exemple — pour chaque projet :
claude mcp add ssh -s user \
-e SSH_PROFILES_FILE="$HOME/.claude/ssh-profiles.json" \
-- npx -y @hypnosis/ssh-mcp-server
Ou écrivez-le à la main — le même serveur dans la forme de configuration que partagent la plupart des clients :
{
"mcpServers": {
"ssh": {
"command": "npx",
"args": ["-y", "@hypnosis/ssh-mcp-server"],
"env": {
"SSH_PROFILES_FILE": "~/.claude/ssh-profiles.json"
}
}
}
}
Créez ensuite ~/.claude/ssh-profiles.json avec au moins une machine :
{
"profiles": {
"production": {
"host": "server.example.com",
"username": "admin",
"privateKeyPath": "~/.ssh/your_private_key"
}
}
}
C'est suffisant pour se connecter.
Codex, opencode, Qwen Code et d'autres clients sont couverts dans Configurer le serveur MCP SSH.
Installation comme plugin
Certains clients — Claude Code, par exemple — peuvent prendre l'ensemble comme plugin à la place :
/plugin marketplace add hypnosis/ssh-mcp-server
/plugin install ssh-mcp-server@ssh-mcp-server
Le plugin lit ~/.claude/ssh-profiles.json sauf si SSH_PROFILES_FILE indique le contraire, donc
créez ce fichier d'abord et le serveur démarre avec vos machines déjà chargées.
Prérequis
Node.js 18+ et un client ssh système sur PATH. Sur Windows, utilisez un profil basé sur une clé ;
les profils avec mot de passe et phrase secrète ne sont actuellement pas disponibles.
Vous préférez une version épinglée, un travail hors ligne, ou une vérification de registre en moins à chaque lancement :
npm install -g @hypnosis/ssh-mcp-server, puis utilisez ssh-mcp-server comme commande au lieu
de npx.
À qui cela s'adresse
- DevOps et SRE qui veulent des audits plus rapides, des vérifications d'incidents et un travail serveur de routine.
- Codeurs par vibes et créateurs indépendants qui livrent avec un assistant IA et exécutent ce qu'ils construisent sur leurs propres serveurs.
- Administrateurs système et ingénieurs plateforme qui veulent des outils structurés au lieu d'un shell brut sans restriction.
- Développeurs et petites équipes gérant leur propre VPS sans équipe d'exploitation dédiée.
- Propriétaires de homelab, NAS et routeurs dont le matériel utile a survécu à ses protocoles modernes.
Pourquoi un serveur MCP SSH plutôt qu'un shell brut
Moins de tokens, coûts IA réduits
Un shell brut donne à un agent IA un déluge : commandes répétées, tableaux ASCII et vidages de journaux. Cela brûle des tokens pour transformer ce bruit en image du serveur — votre argent.
Débogage serveur plus rapide
Des outils spécialisés regroupent les vérifications de routine, plafonnent les sorties bruyantes et renvoient la partie qui compte. L'agent passe moins de temps à traduire la sortie du terminal et arrive plus vite à la correction.
Moins de suppositions, moins d'erreurs IA
Les réponses structurées indiquent ce qui a été trouvé, ce qui n'a pas pu être mesuré et ce qui a été tronqué. Cela laisse moins de place à l'agent pour combler les lacunes avec une hallucination — et vous donne moins de mauvaises corrections, des déploiements plus calmes et un code plus fiable.
Compatibilité SSH : serveurs modernes, équipement hérité et Windows
Utilisez votre configuration OpenSSH existante
Aucune implémentation SSH intégrée, aucune liaison native, aucune recompilation par plateforme. Les commandes utilisent le
client ssh système, donc vos clés, votre ~/.ssh/config, vos hôtes de rebond et votre forwarding d'agent
continuent de fonctionner exactement comme dans un terminal. Lorsque c'est pris en charge, une connexion
multiplexée partagée par destination signifie que vous vous authentifiez une fois, pas une fois par commande.
Support SSH pour serveurs hérités, routeurs et NAS
Envoyez un fichier à un routeur avec un scp moderne et vous obtenez ceci :
scp app.conf router:/etc/
# scp: subsystem request failed on channel 0
Rien n'est cassé — un scp actuel parle le nouveau protocole, et le routeur ne le sait pas.
Dans un terminal, vous allez maintenant lire un fil de forum et revenir avec un drapeau supplémentaire. Ici, vous ne faites
rien : le transfert est tenté, le refus est reconnu, l'ancien protocole est utilisé à la place,
et cette machine est mémorisée pour que le prochain fichier y aille directement.
Solutions de repli pour clients SSH plus anciens et outils manquants
Le vieil équipement obtient un repli, pas une impasse. Lorsqu'une fonctionnalité moderne manque, le serveur prend la voie plus ancienne quand c'est possible :
| Votre machine | Ce que vous obtenez |
|---|---|
| Un routeur ou NAS trop petit pour le transfert de fichiers moderne | Le fichier arrive quand même — l'ancien protocole est utilisé automatiquement |
| Un serveur de dix ans | Le flux de travail fonctionne toujours ; il ouvre simplement une nouvelle connexion par commande au lieu d'en réutiliser une |
| Une image allégée sans moyen de hacher un fichier | Le téléversement dit « impossible de vérifier » au lieu de prétendre une correspondance que personne n'a vérifiée |
| Une machine où un outil n'est simplement pas installé | La réponse dit « non mesuré » — jamais un zéro qui se lit comme « rien là » |
Conçu pour le Model Context Protocol
Construit sur le SDK MCP officiel, TypeScript partout, plus de 2500 tests unitaires ainsi qu'une suite en direct qui s'exécute contre de vrais conteneurs plutôt que des simulations.
SSH brut vs serveur MCP SSH : le même travail, des deux manières
Vérification de santé du serveur SSH
Situation : Un déploiement vient de sortir. Le serveur semble lent, et vous ne savez pas si le disque, la mémoire, les services, les conteneurs ou les erreurs sont en cause.
Question : « Cette machine est-elle saine ? »
SSH brut
$ uptime
10:42:17 up 18 days, 3:21, 2 users, load average: 0.42, 0.31, 0.28
$ df -hT
Filesystem Type Size Used Avail Use% Mounted on
/dev/sda1 ext4 40G 35G 5.0G 87% /
overlay overlay 40G 35G 5.0G 87% /var/lib/docker/overlay2/...
$ free -h
total used free shared buff/cache available
Mem: 7.7Gi 4.9Gi 612Mi 121Mi 2.2Gi 2.5Gi
$ systemctl --failed
UNIT LOAD ACTIVE SUB DESCRIPTION
● api-worker.service loaded failed failed API background worker
$ docker ps -a
CONTAINER ID IMAGE STATUS PORTS
8e14d0b41c2a api:latest Up 3 minutes 0.0.0.0:8080->8080/tcp
65b894af2430 worker:latest Exited (1) 2 minutes ago
$ ss -tulpn
Netid State Local Address:Port Process
tcp LISTEN 0.0.0.0:22 users:(("sshd",pid=842,fd=3))
tcp LISTEN 0.0.0.0:8080 users:(("docker-proxy",pid=1942,fd=4))
$ journalctl -p err --since -1h | tail -50
Aug 20 10:39:14 prod api-worker[22104]: database connection timed out
Aug 20 10:39:14 prod systemd[1]: api-worker.service: Failed with result 'exit-code'.
C'est encore un résultat abrégé. Une vérification complète nécessite plus de commandes pour le CPU, les états
des services, les nombres de conteneurs et les erreurs récentes, chacune avec son propre format de sortie. Pire, une machine
sans ss peut sembler avoir zéro écouteur quand la vérification de port n'a jamais tourné.
Résultat MCP structuré
ssh_snapshot({ "profile": "production" })
{
"disk_pct": 87,
"mem_pct": 64,
"cpu_pct": 12,
"load": "0.42 0.31 0.28",
"containers": 7,
"ports": 14,
"services_running": 3,
"recent_errors": 21,
"unavailable": []
}
Ce que l'agent gagne
| SSH brut | MCP structuré | Votre gain |
|---|---|---|
| Plusieurs commandes et tableaux ASCII | Champs nommés dans un seul résultat | Un appel, champs nommés et moins d'allers-retours |
| Un outil manquant peut ressembler à une sortie vide | unavailable nomme ce qui n'a pas été mesuré | Moins de suppositions et moins de mauvaises corrections |
| Vous triez disques, services et erreurs | Les signaux de problème sont déjà remontés | Débogage plus rapide |
Un résultat ssh_audit_baseline complet peut être plus long qu'une poignée de sorties de commandes brutes —
environ 1 077 tokens contre 765 dans notre mesure en laboratoire. L'économie vient du flux de travail
complet, pas du fait de raccourcir une réponse.
Dans une vraie session de dépannage, les outils spécialisés ont réduit 49 appels de commandes séparés à 4 appels MCP. Chaque appel supplémentaire démarre un autre tour de modèle avec la conversation accumulée. La mise en cache des invites peut réduire le coût des entrées répétées, mais les nouvelles commandes et leur sortie consomment toujours du contexte. Moins d'allers-retours signifient moins de tokens sur la session, moins d'analyses répétées et un chemin plus rapide vers la réponse.
Besoin de l'image complète plutôt que du pouls ? ssh_audit_baseline regroupe le système, le disque,
la mémoire, les ports, sshd, les unités en échec, Docker, le pare-feu et les mises à jour. Les résultats arrivent en
CRITIQUE / AVERTISSEMENT / OK ; les sections non mesurées sont nommées au lieu de se lire silencieusement comme zéro.
Recherche de journal sur serveur Linux
Situation : L'API expire, mais le même message peut être dans nginx, syslog, journald ou un journal d'application que vous ne pouvez pas lire avec votre utilisateur normal.
Question : « D'où vient cette erreur ? »
SSH brut
$ grep -i "timeout" /var/log/nginx/error.log
2026/08/20 10:38:54 [error] upstream timed out while reading response header
$ grep -i "timeout" /var/log/syslog
Aug 20 10:39:14 prod api-worker[22104]: database connection timed out
$ grep -i "timeout" /var/log/app/*.log 2>/dev/null
$ journalctl -u api --since "1 hour ago" | grep -i timeout
Aug 20 10:39:14 prod api[22104]: database connection timed out after 30000ms
La troisième commande semble propre, mais 2>/dev/null a aussi masqué une erreur de permission. « Rien
ne correspond » et « rien n'a été lu » semblent maintenant identiques. Un journal chargé peut aussi renvoyer des milliers de
lignes et pousser le reste de l'incident hors du contexte de l'agent.
Résultat MCP structuré
ssh_log_search({ "profile": "production",
"path": ["/var/log/nginx/error.log", "/var/log/syslog", "/var/log/app/*.log"],
"query": "timeout", "context": 2, "since": "1h" })
{
"matches": 34,
"lines": [
{ "file": "/var/log/nginx/error.log", "line": 4821,
"text": "upstream timed out while reading response header", "context": false },
{ "file": "/var/log/nginx/error.log", "line": 4822,
"text": "client closed connection", "context": true }
],
"files_searched": 6,
"files_unreadable": ["/var/log/app/private"],
"files_skipped": 12,
"files_undated": [],
"limited": false,
"truncated": false
}
Ce que l'agent gagne
| SSH brut | MCP structuré | Votre gain |
|---|---|---|
| Quatre recherches et quatre sorties | Une recherche sur fichiers et globs | Moins de tokens et d'allers-retours |
| Les erreurs de permission peuvent disparaître | files_unreadable nomme chaque chemin manqué | Pas de fausse conclusion « journaux propres » |
| La sortie peut croître sans plafond utile | limited et truncated exposent chaque coupure | Décisions plus sûres à partir de résultats partiels |
since utilise l'horloge du serveur, namesOnly: true renvoie uniquement les chemins correspondants, et
ssh_log_tail lit les N dernières lignes de plusieurs journaux en un seul appel.
Modifications de configuration distantes sûres
Situation : Vous devez remplacer une configuration nginx sur un serveur en production. Une connexion interrompue, un mauvais mode ou une copie non vérifiée pourrait laisser le service avec un fichier cassé.
Question : « Puis-je remplacer cette configuration sans laisser un fichier partiel ? »
SSH brut
$ sudo sh -c 'cat > /etc/nginx/conf.d/api.conf' <<'EOF'
server {
listen 80;
location / { proxy_pass http://127.0.0.1:8080; }
}
EOF
$ echo $?
0
Un code de sortie zéro dit que le shell a terminé. Cela ne prouve pas quels octets ont atterri, et >
a tronqué l'ancien fichier avant que le premier octet du nouveau n'arrive. Si la connexion tombe
en pleine écriture, le service reste avec une configuration partielle.
Résultat MCP structuré
ssh_file_write({ "profile": "production",
"files": [{ "path": "/etc/nginx/conf.d/api.conf",
"content": "server {\n listen 80;\n location / { proxy_pass http://127.0.0.1:8080; }\n}\n",
"mode": "644", "sudo": true, "verify": true }] })
{
"files": [{ "path": "/etc/nginx/conf.d/api.conf", "written": true,
"verified": "verified", "reason": null, "bytes": 79 }]
}
Ce que l'agent gagne
| SSH brut | MCP structuré | Votre gain |
|---|---|---|
| La cible est tronquée avant que la copie ne se termine | Un fichier temporaire complet la remplace avec un seul renommage | Pas de configuration à moitié écrite |
| Code de sortie uniquement | Octets et résultat de vérification sont nommés | Vous savez ce qui a réellement atterri |
| Les permissions vivent dans le texte du shell | sudo, mode et verify sont des champs par fichier | Propriété prévisible et moins d'erreurs de citation |
verified a trois résultats honnêtes : verified, unavailable quand le serveur n'a pas d'outil de hachage,
et skipped quand la vérification n'a pas été demandée. Pour les lectures, ssh_file_read accepte une
liste de chemins ; ssh_file_list gère les globs, la récursion, les tailles et les modes.
Exécuter des commandes SSH par lots avec sudo
Situation : Un déploiement est prêt, mais la syntaxe nginx, l'état des services et les erreurs récentes doivent tous être vérifiés avant que le trafic ne bouge. Un échec de vérification ne doit pas disparaître dans un vidage combiné.
Question : « Chaque vérification préalable a-t-elle réussi ? »
SSH brut
$ ssh admin@server.example.com 'sudo nginx -t'
nginx: configuration file /etc/nginx/nginx.conf test is successful
$ ssh admin@server.example.com 'sudo systemctl is-active nginx'
active
$ ssh admin@server.example.com 'sudo tail -5 /var/log/nginx/error.log'
2026/08/20 10:38:54 [error] upstream timed out while reading response header
Trois connexions renvoient trois sorties sans rapport. Si les commandes sont jointes avec ;, le
shell ne rapporte que le dernier code de sortie ; si elles sont jointes avec &&, les vérifications ultérieures disparaissent
après le premier échec.
Résultat MCP structuré
ssh_exec({ "profile": "production",
"command": ["nginx -t", "systemctl is-active nginx",
"tail -5 /var/log/nginx/error.log"],
"sudo": true })
{
"commands": [
{ "command": "nginx -t", "exit_code": 0, "truncated": false, "clipped_bytes": 0,
"stdout": "", "stderr": "nginx: configuration file /etc/nginx/nginx.conf test is successful\n" },
{ "command": "systemctl is-active nginx", "exit_code": 0, "truncated": false,
"clipped_bytes": 0, "stdout": "active\n", "stderr": "" },
{ "command": "tail -5 /var/log/nginx/error.log", "exit_code": 0, "truncated": false,
"clipped_bytes": 0, "stdout": "2026/08/21 09:14:02 [error] upstream timed out\n", "stderr": "" }
],
"job_id": null
}
Ce que l'agent gagne
| SSH brut | MCP structuré | Votre gain |
|---|---|---|
| Trois appels et sorties sans rapport | Une liste de commandes ordonnée | Moins d'allers-retours |
| Un shell combiné peut masquer le statut intermédiaire | Chaque commande garde son propre exit_code | Aucun échec de vérification manqué |
sudo et les citations sont répétés dans le texte de commande | sudo s'applique à tout le lot | Moins d'erreurs de citation |
La protection contre les commandes destructrices vérifie la liste complète avant que la première commande ne s'exécute. Si une
entrée est refusée, chaque autre entrée est marquée comme non exécutée et rien n'est envoyé au serveur.
Chaque commande porte ses propres stdout et stderr. Une commande qui s'est exécutée sans rien afficher
a une chaîne vide ; une commande qui n'a jamais tourné n'a pas du tout ce champ, donc les deux ne peuvent pas être
confondus. Une sortie de plus de 128 Ko par commande conserve les deux extrémités — le début pour les tableaux, la fin pour
les journaux — avec une jointure au milieu indiquant la quantité, et clipped_bytes précise combien a été coupé.
La coupure se fait sur des limites d'octets et recule jusqu'au bord d'un caractère, donc une
réponse tronquée ne porte jamais de marque de remplacement.
sudo atteint le serveur sans terminal : la réponse du profil est transmise à sudo sur
l'entrée standard. Le secret utilisé provient de sudoPassword lorsque le profil en nomme un et
de password sinon — un profil qui se connecte par clé n'a aucun mot de passe de connexion, et
là où une machine sépare les deux, celui de connexion est la mauvaise réponse. Quand il n'y a rien
avec quoi répondre, la réponse le dit et nomme les solutions, au lieu de laisser les propres conseils de sudo
sur -S et les assistants askpass. Une commande qui lit sa propre entrée standard ne reçoit jamais le
mot de passe, qui se retrouverait sinon mélangé aux données.
Exécuter des tâches SSH de longue durée
Situation : Une sauvegarde ou une migration durera plus longtemps que la session de l'agent. La connexion peut se fermer, mais vous aurez encore besoin de son état, de sa sortie et de son code de sortie plus tard.
Question : « Cette tâche survivra-t-elle à la conversation ? »
SSH brut
$ ssh admin@server.example.com 'pg_dump app | gzip > /srv/backups/app.sql.gz'
client_loop: send disconnect: Broken pipe
Le terminal a disparu. Vous devez maintenant vous reconnecter, trouver le processus, inspecter le fichier cible et deviner si la sauvegarde s'est terminée ou s'est arrêtée à mi-chemin.
Résultat MCP structuré
ssh_exec({ "profile": "production",
"command": "pg_dump app | gzip > /srv/backups/app.sql.gz",
"detach": true })
{
"commands": [{
"command": "pg_dump app | gzip > /srv/backups/app.sql.gz",
"exit_code": null,
"truncated": false,
"timed_out": false,
"blocked": false,
"blocked_reason": null,
"not_run": false,
"warning": null
}],
"job_id": "mst0f2q1-9ab3c4d5"
}
Ce que l'agent gagne
| SSH brut | MCP structuré | Votre gain |
|---|---|---|
| La tâche est liée à une seule session SSH | La tâche distante a un identifiant persistant | Déconnexions et redémarrages sûrs |
| Se reconnecter signifie chercher processus et fichiers | Le statut et le code de sortie ont des états nommés | Pas de devinette sur la fin |
| Relire la sortie répète l'ancien texte | La sortie continue depuis un décalage d'octets | Moins de jetons utilisés sur les longues tâches |
L'état de la tâche vit sur le disque distant, pas dans la mémoire de ce serveur. ssh_job_status distingue
running, finished et lost ; ssh_job_output continue depuis le dernier décalage d'octets ; et
ssh_job_kill signale tout le groupe de processus au lieu de seulement son shell.
Transférer des fichiers vers des routeurs et NAS hérités
Situation : Un client OpenSSH actuel essaie SFTP, mais le routeur ou le NAS ne comprend que le protocole scp classique. Le fichier doit quand même arriver intact et remplacer sa cible en toute sécurité.
Question : « Cet ancien appareil peut-il encore recevoir un fichier vérifié ? »
SSH brut
$ scp app.conf operator@router:/etc/app.conf
subsystem request failed on channel 0
scp: Connection closed
L'étape suivante habituelle est de se souvenir du drapeau hérité, de réessayer la copie puis d'exécuter une commande de hachage séparée — si l'appareil dispose d'un outil de hachage.
Résultat MCP structuré
ssh_upload({ "profile": "router", "local_path": "./app.conf",
"remote_path": "/etc/app.conf", "sudo": true,
"mode": "644", "owner": "root:root", "verify": true })
{
"files": [{
"path": "/etc/app.conf",
"written": true,
"verified": "verified",
"reason": null,
"bytes": 1284
}]
}
Ce que l'agent gagne
| SSH brut | MCP structuré | Votre gain |
|---|---|---|
| Le mode SFTP moderne s'arrête à la première erreur | Le repli scp classique est automatique et mémorisé | Le vieux matériel fonctionne encore |
| Une copie réussie ne prouve pas l'intégrité | La vérification SHA-256 a un résultat nommé | La corruption n'est pas prise pour un succès |
| Le remplacement direct peut laisser une cible partielle | Un fichier temporaire est déplacé en place après le transfert | Le fichier de travail survit aux interruptions |
Si l'appareil n'a ni sha256sum ni openssl, le résultat dit unavailable et nomme
la raison au lieu de signaler une fausse correspondance. Les répertoires entiers utilisent recursive: true et
vérifient leurs hachages en un seul lot.
Protection contre les commandes destructrices pour les agents IA
La garde s'exécute localement, avant qu'une commande n'atteigne SSH. Elle sépare les opérations qui peuvent être récupérées de celles qui détruisent le conteneur contenant les données, et elle vérifie l'ordre des commandes dans les chaînes et les lots.
Arrêter une chaîne destructrice avant qu'elle ne commence
Une séquence sûre de sauvegarde-et-remplacement :
cp -r /srv/app /srv/app.bak && mv /srv/app /srv/app-old && rm -rf /srv/app
Les mêmes opérations dans le mauvais ordre :
rm -rf /srv/app && cp -r /srv/app /srv/app.bak && mv /srv/app /srv/app-old
# REFUSED before the first command runs
Le shell supprimerait le répertoire et ne découvrirait qu'ensuite que la source de sauvegarde a disparu.
La garde voit que les étapes ultérieures lisent une cible déjà détruite par une étape antérieure, donc
tout l'appel reste sur votre machine. La même vérification détecte
dropdb app && pg_dump app > backup.sql.
Refuser la perte irréversible, avertir des changements récupérables
| Refusé — le conteneur lui-même | Seulement averti — son contenu |
|---|---|
DROP DATABASE, dropdb | DROP TABLE, TRUNCATE, DELETE FROM |
docker volume rm, docker compose down -v | docker rm -f <name> |
crontab -r | modifier une tâche |
mkfs, wipefs -a, lvremove, zfs destroy | chmod 777 |
reboot, shutdown, halt | git reset --hard |
docker compose down -v est refusé parce que -v supprime les volumes Docker nommés, y compris un volume
de base de données. Sans -v, arrêter les services n'est pas traité comme la même action irréversible.
La suppression récursive de la racine du système de fichiers, d'un répertoire personnel ou d'arbres système tels que /etc,
/var et /usr est également refusée, y compris lorsqu'un lien symbolique y mène. Une cible non résolue
telle que rm -rf "$DIR"/* est aussi refusée : « impossible de vérifier » n'est pas traité comme « sûr ».
Nommer ce que vous arrêtez
Une commande qui trouve sa cible au lieu de la nommer n'est pas envoyée. Le serveur l'expand et répond avec ce qui se cache derrière la cible :
docker kill $(docker ps -q --filter ancestor=web)
# BLOCKED — would stop:
# edge — web:latest, Up 34 days, 0.0.0.0:8443->8443/tcp
Pour un processus, la réponse ajoute les signes qu'il est en cours d'utilisation : depuis combien de temps il tourne,
sur quels ports il accepte les connexions, combien de connexions il porte. Les cibles nommées
ne coûtent rien de plus et passent en silence — docker kill web-1, kill 4871,
systemctl stop app.
Pour continuer, nommez ce qui est arrêté. Les noms sont vérifiés par rapport à ce que la commande atteint réellement, donc un masque qui a dérivé vers autre chose est refusé plutôt que confirmé :
docker kill $(docker ps -q --filter ancestor=web) # CONFIRMED-KILL: edge
Un motif sur les lignes de commande est un cas particulier. Il correspond à la commande même qui le porte, donc le shell qui l'exécute est signalé avant la cible et la réponse s'interrompt au milieu. Une telle frappe n'est pas confirmée mais réécrite — par numéro, ou avec un caractère écrit comme une classe pour que le motif cesse de se correspondre à lui-même :
pkill -f relay
# BLOCKED — two ways through:
# kill 4871
# pkill -f '[r]elay' # CONFIRMED-KILL: 4871
Trois résultats restent distincts : cibles trouvées, l'expansion n'a rien atteint, et rien avec quoi demander — pas de moteur sur la machine, une réponse tronquée, une connexion qui a échoué. Les deux derniers sont aussi des refus : ne pas savoir n'est pas une raison de procéder.
Confirmer une commande destructrice intentionnelle
Rien n'est interdit définitivement. Ajoutez # CONFIRMED-DESTRUCTIVE à une commande examinée et elle
est autorisée. Quand la garde refuse une entrée dans un lot, le lot complet s'arrête avant
l'exécution, donc le serveur n'est jamais laissé après une opération à moitié exécutée.
La garde fonctionne dans un seul appel. Elle ne peut pas relier une suppression dans une invocation avec une lecture dans la suivante, ni raisonner sur des outils qu'elle ne reconnaît pas. C'est une ceinture de sécurité, pas un moteur de politique : les opérations récupérables restent votre décision. Les restrictions de chemins et les règles de citation sont documentées dans docs/security.md.
Outils
18 outils SSH MCP pour les opérations serveur. Paramètres complets et exemples dans docs/tools.md.
| Outil | Ce qu'il fait |
|---|---|
ssh_exec | Exécuter une commande ou un lot, avec la garde anti-destructrice et détachement optionnel |
ssh_file_read | Lire un ou plusieurs fichiers, texte ou binaire |
ssh_file_write | Écrire des fichiers avec renommage atomique et vérification SHA-256 optionnelle |
ssh_file_list | Lister un répertoire, avec glob et récursion optionnels |
ssh_upload | Téléverser un fichier ou répertoire via SSH, binaire-sûr avec vérifications d'intégrité ; un répertoire remplace la cible ou fusionne dedans |
ssh_download | Télécharger un fichier ou répertoire via SSH, binaire-sûr avec vérifications d'intégrité |
ssh_job_status | État d'une tâche en arrière-plan : en cours, terminée, ou perdue |
ssh_job_output | Lire la sortie accumulée depuis un décalage d'octets |
ssh_job_list | Lister les tâches, en balayant celles terminées au-delà de leur TTL |
ssh_job_kill | Signaler tout le groupe de processus d'une tâche |
ssh_log_tail | Dernières N lignes d'un ou plusieurs journaux, glob pris en charge ; un conteneur par nom |
ssh_log_search | Recherche par motif dans les journaux, ou à travers le journal d'un conteneur |
ssh_snapshot | Instantané de santé ponctuel : services, ressources, Docker, réseau, erreurs |
ssh_monitor | Contrôle du transport : statistiques, rechargement, test, liste, fermeture |
ssh_audit_baseline | Système, disque, mémoire, réseau, ssh, services, Docker, pare-feu, mises à jour |
ssh_tls_check | Expiration de certificat, SAN, chaîne et hook de renouvellement pour un domaine |
ssh_disk_breakdown | Où le disque est parti : du top-N, Docker, journald, caches |
ssh_service_status | systemctl status plus une queue journalctl pour une unité |
Annotations de sécurité des outils MCP
Les annotations MCP standard indiquent aux clients quels outils sont en lecture seule, destructeurs, idempotents ou monde-ouvert. Voir le tableau complet.
Exécuter des commandes SSH et gérer des fichiers distants
Commandes, lectures et écritures de fichiers, listages de répertoires — le travail ordinaire sur une machine, chaque réponse déjà analysée.
Surveiller les tâches SSH de longue durée
Le travail lent est détaché et suivi au lieu d'être attendu : chaque regard dit jusqu'où il est allé.
Rechercher dans les journaux et vérifier la santé du serveur
Journaux de fichiers et de conteneurs, et une image ponctuelle de la machine, avec sortie plafonnée pour qu'une queue ne dévore pas la fenêtre de contexte.
Téléverser et télécharger des fichiers via SSH
Transferts binaires-sûrs avec vérifications d'intégrité. Détails dans docs/transfer.md.
Pour les binaires et gros fichiers, utilisez
ssh_upload/ssh_download— les morceaux base64 et les heredocs ne sont ni binaires-sûrs ni atomiques.
Auditer des serveurs Linux via SSH
En lecture seule et regroupés en un seul aller-retour. Détails dans docs/audit.md.
Mode de compatibilité SSH Windows
Windows utilise le mode de compatibilité automatiquement. Quand le multiplexage de connexion est indisponible, le serveur passe à une connexion par commande. Les mêmes outils restent disponibles via SSH par clé — aucune configuration séparée ni implémentation spécifique à Windows.
La garde anti-commandes destructrices est couverte dans Protection contre les commandes destructrices pour les agents IA.
Configurer le serveur SSH MCP
Exécutez le paquet depuis Installation en 30 secondes d'abord, puis créez un fichier de profil.
Créer des profils de connexion SSH
Placez-le où vous voulez — à côté de la propre configuration de votre agent est le choix habituel. Les exemples ci-dessous utilisent ~/.claude/ssh-profiles.json ; pour d'autres agents, changez le répertoire (~/.codex/, ~/.qwen/, ~/.config/opencode/) :
{
"profiles": {
"production": {
"host": "server.example.com",
"username": "admin",
"port": 22,
"privateKeyPath": "~/.ssh/your_private_key"
}
}
}
Choisir un profil SSH explicitement
Il n'y a pas de profil de repli : chacun est une machine différente, et une commande envoyée à la mauvaise machine n'est pas quelque chose qu'un message d'erreur peut défaire ensuite. Demandez sans nom et la réponse liste les noms parmi lesquels choisir :
ssh_exec({ command: "uptime" })
→ No profile specified. Name one explicitly: production
Un profil que le serveur ne peut pas utiliser pour SSH — pas de host, pas de username, ou mode: "local" — est ignoré sans plainte, et les champs qu'il ne reconnaît pas sont laissés tranquilles, donc le fichier peut être partagé avec d'autres outils. Un profil avec un champ cassé est un cas différent : il est nommé avec le champ et la valeur, et ses voisins sains continuent de fonctionner.
Chaque profil prend optionnellement un bloc pathSecurity qui autorise ou bloque les chemins que les outils de fichiers peuvent toucher — voir docs/security.md.
Un profil qui se connecte par clé mais a besoin de sudo côté distant prend un sudoPassword — le secret auquel sudo répond, qui sur beaucoup de machines n'est pas le mot de passe de connexion. Gardez-le dans le fichier de secrets plutôt qu'ici.
Garder les mots de passe SSH et phrases secrètes hors des profils
Privilégiez les clés. Si un mot de passe ou une phrase secrète pour une clé chiffrée est inévitable, conservez-le dans un fichier de secrets séparé, jamais dans le profil lui-même :
{
"secretsFile": "~/.config/ssh-mcp/secrets.json",
"profiles": {
"production": {
"host": "server.example.com",
"username": "admin"
}
}
}
Le fichier de secrets est indexé par nom de profil — voir secrets.json.example :
{
"production": { "password": "..." },
"buildbox": { "sudoPassword": "..." }
}
sudoPassword est la réponse à sudo sur cette machine. Un profil qui se connecte par clé n'a pas de mot de passe de connexion à fournir, et lorsque les deux diffèrent, celui de connexion est la mauvaise réponse ; sans lui, password est utilisé.
Le fichier de secrets doit être lisible uniquement par vous (chmod 600). Les chemins relatifs sont résolus à partir du fichier de profils ; les secrets restent hors de argv et sont masqués dans les journaux. Voir sécurité des identifiants.
Configurer Claude Code, Codex et d'autres clients MCP
Choisissez le client que vous utilisez et pointez-le vers le même fichier de profils.
Claude Code
Une seule commande ; -s user rend le serveur disponible dans chaque projet :
claude mcp add ssh -s user \
-e SSH_PROFILES_FILE="$HOME/.claude/ssh-profiles.json" \
-- npx -y @hypnosis/ssh-mcp-server
Codex CLI
codex mcp add ssh \
--env SSH_PROFILES_FILE="$HOME/.codex/ssh-profiles.json" \
-- npx -y @hypnosis/ssh-mcp-server
opencode
Placez-le dans ~/.config/opencode/opencode.json :
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"ssh": {
"type": "local",
"command": ["npx", "-y", "@hypnosis/ssh-mcp-server"],
"enabled": true,
"environment": {
"SSH_PROFILES_FILE": "~/.config/opencode/ssh-profiles.json"
}
}
}
}
Qwen Code
Une seule commande, comme les autres :
qwen mcp add ssh \
-e SSH_PROFILES_FILE="$HOME/.qwen/ssh-profiles.json" \
npx -y @hypnosis/ssh-mcp-server
Autres clients MCP
Gemini CLI, Hermes, Cline, un plugin d'éditeur ou votre propre agent fonctionnent de la même manière. Tout ce dont ils ont besoin, c'est d'une commande à exécuter et d'une variable d'environnement.
Redémarrer votre client MCP
Redémarrez le client, puis exécutez ssh_monitor({ action: "list" }) pour confirmer que le profil est chargé.
Configuration du serveur SSH MCP
| Variable | Rôle | Défaut |
|---|---|---|
SSH_PROFILES_FILE | Chemin vers le JSON des profils — obligatoire | — |
SSH_MCP_LOG_LEVEL | debug, info, warn, error | info |
LOG_LEVEL | Repli, utilisé uniquement lorsque SSH_MCP_LOG_LEVEL n'est pas défini | info |
SSH_MCP_LOG_TIMESTAMP | Horodatage dans les lignes de journal | true |
SSH_MCP_CONTROL_PERSIST | Secondes pendant lesquelles une connexion partagée reste active après la dernière commande ; 0 la ferme immédiatement | 600 |
SSH_MCP_CONTROL_DIR | Où vivent les sockets de contrôle | ~/.ssh/ssh-mcp |
SSH_MCP_PROFILES_CACHE_TTL | Durée de vie du cache de profils, ms | 60000 |
SSH_MCP_PROFILES_WATCH | Recharger le fichier de profils lorsqu'il change | true |
La connexion partagée survit volontairement à ce processus : la fermer à la sortie couperait le canal qu'une autre fenêtre sur la même machine utilise.
Limites du serveur SSH MCP
Chaque limite vous indique le moyen de la contourner. Un outil qui ne peut pas faire quelque chose le dit et nomme ssh_exec, qui exécute des commandes directement sur la machine — un pilote de journal non pris en charge, un utilitaire que la machine ne possède pas, un moteur que ce serveur ne parle pas. Vous n'avez pas besoin de savoir à l'avance où s'arrêtent les outils : le refus le dit, au moment où cela compte.
Trois refus restent délibérément silencieux à propos du shell, car là, c'est la mauvaise réponse : un chemin que votre profil interdit (contourner votre propre règle n'est pas une solution), un appel malformé (la correction est dans l'appel), et un refus de ssh_exec lui-même.
- Annulation : un appel annulé arrête désormais aussi la commande sur le serveur, envoyé comme un second appel sur la même connexion. Lorsque le serveur n'a pas de
/proc, la commande est trouvée viapsà la place. FreeBSD n'est pas vérifié : un comportement correct n'y est pas garanti. Les transferts de fichiers etssh_snapshotne prennent pas du tout l'annulation. - Écritures atomiques : BSD et macOS ne peuvent pas pré-vérifier les renommages entre systèmes de fichiers.
Feuille de route du serveur SSH MCP
-
Test complet contre des hôtes SSH macOS
-
Test de compatibilité de bout en bout sur Windows
-
Audits multi-hôtes — comparer la santé de plusieurs profils SSH en un seul appel
-
Importer des profils depuis le
~/.ssh/configexistant -
Transferts reprenables pour les gros fichiers et les connexions instables
-
Chronologie des opérations à distance — commandes, transferts et décisions de garde dans une seule piste d'audit
-
Playbooks de dépannage SSH prêts à l'emploi
-
Journaux de conteneurs sans passer par le shell— FAIT :ssh_log_tailetssh_log_searchprennent un nom de conteneur, demandent à docker où il écrit et lisent ce fichier avec le même mécanisme que tout autre journal -
Un refus qui vous laisse bloqué— FAIT : chaque limite nomme désormaisssh_execcomme voie de sortie, donc atteindre le bord d'un outil coûte une phrase au lieu d'un jeu de devinettes -
Des réponses qui atteignent le modèle— FAIT : la sortie de commande, les lignes de journal correspondantes, les noms de machines et les sections d'instantané voyagent dans les champs, pas seulement dans le texte -
Schémas d'outils MCP plus petits— FAIT : la liste d'outils a été allégée de 10 %, et un travail détaché affiche désormais les dernières lignes qu'il a écrites au lieu d'être interrogé à l'aveugle -
Travail long sous root— FAIT : un travail détaché s'exécute avecsudoet est suivi en tant que root, et un profil à clé seule répond àsudoavec son propresudoPassword
Développer et tester le serveur SSH MCP
npm install
npm run build # tsc
npx tsc --noEmit # types, plus dead declarations
npm run test:unit # unit tests
npm run lab:up # start the two test containers
npm run test:live # live suite against those containers
La suite en conditions réelles s'exécute contre de vrais conteneurs — un BusyBox, un coreutils — car les deux divergent silencieusement, et une maquette est d'accord avec celui qui l'a écrite. Voir docs/architecture.md pour la structure.
Vous aimez SSH MCP Server ? ⭐
Si vous aimez l'outil, donnez-lui une étoile sur GitHub — cela aide plus de personnes à découvrir le projet.
Contribuer au serveur SSH MCP
Les problèmes et les demandes de tirage sont les bienvenus sur github.com/hypnosis/ssh-mcp-server.
Licence
MIT — voir LICENSE.