SSH MCP Server
oficialEjecuta comandos, mueve archivos, busca registros y audita máquinas a través de SSH desde tu agente.
¿Qué puedes hacer con SSH MCP?
- Ejecutar comandos con salvaguardas de seguridad — Pídele a tu asistente que ejecute comandos individuales o por lotes mediante
ssh_exec, con protección contra comandos destructivos que bloquea operaciones irreversibles antes de que lleguen al servidor. - Leer, escribir y listar archivos remotos — Usa
ssh_file_read,ssh_file_writeyssh_file_listpara inspeccionar o modificar archivos, con escrituras atómicas y verificación opcional SHA-256. - Buscar registros y verificar el estado del servidor — Consulta
ssh_log_searchossh_log_tailen archivos y contenedores, u obtén una instantánea estructurada del estado conssh_snapshotyssh_audit_baseline. - Transferir archivos con verificaciones de integridad — Sube o descarga archivos y directorios mediante
ssh_uploadyssh_download, con respaldo automático descpheredado para dispositivos más antiguos. - Gestionar trabajos en segundo plano de larga duración — Desacopla operaciones lentas con
ssh_execy rastréalas mediantessh_job_status,ssh_job_outputyssh_job_kill, sobreviviendo a desconexiones.
Documentación
SSH MCP Server — Herramientas de servidor remoto para agentes de IA
|
|
Un servidor MCP SSH: una multiherramienta que te ahorra a ti y a tu agente de IA tiempo y tokens en depuración, desarrollo y mantenimiento de servidores. Ejecuta comandos, mueve archivos, lee registros y audita máquinas a través de SSH: un VPS en la nube, una máquina física o el router BusyBox que tienes en el armario. |
Utiliza el cliente OpenSSH que ya está en tu máquina: tus claves, tu ~/.ssh/config, tus hosts de salto, tu reenvío de agente. Nada incluido, nada que compilar, sin enlaces nativos.
Funciona con Claude Code, Codex CLI, Cline, opencode, Gemini CLI, Qwen Code, Hermes y otros clientes MCP.
Instalar · Herramientas · Configuración · Seguridad · Hoja de ruta · Documentación · Registro de cambios
Instalar en 30 segundos
No se requiere instalación global. npx descarga el paquete en el primer uso:
npx -y @hypnosis/ssh-mcp-server
Añádelo a tu cliente MCP — Claude Code, por ejemplo — para cada proyecto:
claude mcp add ssh -s user \
-e SSH_PROFILES_FILE="$HOME/.claude/ssh-profiles.json" \
-- npx -y @hypnosis/ssh-mcp-server
O escríbelo a mano: el mismo servidor con la forma de configuración que comparten la mayoría de los clientes:
{
"mcpServers": {
"ssh": {
"command": "npx",
"args": ["-y", "@hypnosis/ssh-mcp-server"],
"env": {
"SSH_PROFILES_FILE": "~/.claude/ssh-profiles.json"
}
}
}
}
Luego crea ~/.claude/ssh-profiles.json con al menos una máquina:
{
"profiles": {
"production": {
"host": "server.example.com",
"username": "admin",
"privateKeyPath": "~/.ssh/your_private_key"
}
}
}
Eso es suficiente para conectarse.
Codex, opencode, Qwen Code y otros clientes están cubiertos en Configurar el servidor MCP SSH.
Instalar como plugin
Algunos clientes — Claude Code, por ejemplo — pueden tomar todo el conjunto como un plugin en su lugar:
/plugin marketplace add hypnosis/ssh-mcp-server
/plugin install ssh-mcp-server@ssh-mcp-server
El plugin lee ~/.claude/ssh-profiles.json a menos que SSH_PROFILES_FILE indique lo contrario, así que
crea ese archivo primero y el servidor se iniciará con tus máquinas ya cargadas.
Requisitos
Node.js 18+ y un cliente ssh del sistema en PATH. En Windows, usa un perfil basado en claves;
los perfiles de contraseña y frase de contraseña no están disponibles actualmente.
¿Prefieres una versión fija, trabajo sin conexión o una comprobación de registro menos por inicio?
npm install -g @hypnosis/ssh-mcp-server, luego usa ssh-mcp-server como comando en lugar
de npx.
Para quién es esto
- DevOps y SRE que quieren auditorías más rápidas, comprobaciones de incidentes y trabajo rutinario de servidores.
- Codificadores de vibraciones y creadores independientes que publican con un asistente de IA y ejecutan lo que construyen en sus propios servidores.
- Administradores de sistemas e ingenieros de plataformas que quieren herramientas estructuradas en lugar de un shell sin restricciones.
- Desarrolladores y equipos pequeños que gestionan su propio VPS sin un equipo de operaciones dedicado.
- Propietarios de homelab, NAS y routers cuyo hardware útil ha superado sus protocolos modernos.
Por qué un servidor MCP SSH en lugar de un shell sin procesar
Menos tokens, menores costos de IA
Un shell sin procesar le da a un agente de IA un torrente: comandos repetidos, tablas ASCII y volcados de registros. Quema tokens convirtiendo ese ruido en una imagen del servidor: tu dinero.
Depuración de servidores más rápida
Las herramientas diseñadas específicamente agrupan comprobaciones rutinarias, limitan la salida ruidosa y devuelven la parte que importa. El agente dedica menos tiempo a traducir la salida del terminal y llega antes a la solución.
Menos conjeturas, menos errores de IA
Las respuestas estructuradas indican qué se encontró, qué no se pudo medir y qué se truncó. Eso deja al agente menos margen para rellenar vacíos con una alucinación, y te da menos arreglos malos, despliegues más tranquilos y código más confiable.
Compatibilidad SSH: servidores modernos, equipos heredados y Windows
Usa tu configuración OpenSSH existente
Sin implementación SSH incluida, sin enlaces nativos, sin recompilación por plataforma. Los comandos usan el
cliente ssh del sistema, así que tus claves, tu ~/.ssh/config, tus hosts de salto y tu reenvío de agente
siguen funcionando exactamente como en un terminal. Cuando se admite, una única conexión
multiplexada compartida por destino significa que te autenticas una vez, no una vez por comando.
Soporte SSH para servidores heredados, routers y dispositivos NAS
Envía un archivo a un router con un scp moderno y obtienes esto:
scp app.conf router:/etc/
# scp: subsystem request failed on channel 0
Nada está roto: un scp actual habla el nuevo protocolo, y el router no lo sabe.
En un terminal ahora vas a leer un hilo de foro y vuelves con una bandera extra. Aquí no haces
nada: se intenta la transferencia, se reconoce la negativa, se usa el protocolo antiguo en su lugar,
y esa máquina se recuerda para que el siguiente archivo vaya directamente allí.
Alternativas para clientes SSH antiguos y herramientas faltantes
El equipo antiguo recibe una alternativa, no un callejón sin salida. Cuando falta una función moderna, el servidor toma el camino más antiguo cuando puede:
| Tu máquina | Lo que obtienes |
|---|---|
| Un router o NAS demasiado pequeño para transferencia de archivos moderna | El archivo sigue llegando: el protocolo antiguo se usa automáticamente |
| Un servidor de hace diez años | El flujo de trabajo sigue funcionando; solo abre una conexión nueva por comando en lugar de reutilizar una |
| Una imagen reducida sin forma de calcular hash de un archivo | La subida dice "no se pudo verificar" en lugar de afirmar una coincidencia que nadie comprobó |
| Una máquina donde una herramienta simplemente no está instalada | La respuesta dice "no medido": nunca un cero que se lea como "no hay nada" |
Construido para el Protocolo de Contexto de Modelos
Construido sobre el SDK MCP oficial, TypeScript en todo el código, más de 2500 pruebas unitarias más una suite en vivo que se ejecuta contra contenedores reales en lugar de simulaciones.
SSH sin procesar vs un servidor MCP SSH: el mismo trabajo, de ambas maneras
Verificación de salud del servidor SSH
Situación: Acaba de salir un despliegue. El servidor se siente lento y no sabes si el disco, la memoria, los servicios, los contenedores o los errores son los culpables.
Pregunta: "¿Está saludable esta máquina?"
SSH sin procesar
$ 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'.
Eso sigue siendo un resultado abreviado. Una verificación completa necesita más comandos para CPU, estados de
servicios, conteos de contenedores y errores recientes, cada uno con su propio formato de salida. Peor aún, una máquina
sin ss puede parecer que tiene cero oyentes cuando la verificación de puertos nunca se ejecutó.
Resultado MCP estructurado
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": []
}
Lo que gana el agente
| SSH sin procesar | MCP estructurado | Tu ganancia |
|---|---|---|
| Varios comandos y tablas ASCII | Campos con nombre en un solo resultado | Una llamada, campos con nombre y menos viajes de ida y vuelta |
| Una herramienta faltante puede parecer salida vacía | unavailable nombra lo que no se midió | Menos conjeturas y menos arreglos malos |
| Tú ordenas discos, servicios y errores | Las señales del problema ya están en la superficie | Depuración más rápida |
Un resultado completo de ssh_audit_baseline puede ser más largo que un puñado de salidas de comandos sin procesar:
alrededor de 1,077 tokens frente a 765 en nuestra medición de laboratorio. El ahorro viene del flujo de trabajo
completo, no de hacer una respuesta más corta.
En una sesión real de resolución de problemas, las herramientas diseñadas específicamente redujeron 49 llamadas de comandos separadas a 4 llamadas MCP. Cada llamada adicional inicia otro turno del modelo con la conversación acumulada. El almacenamiento en caché de indicaciones puede reducir el costo de la entrada repetida, pero los comandos nuevos y su salida aún consumen contexto. Menos viajes de ida y vuelta significan menos tokens en toda la sesión, menos análisis repetido y un camino más rápido hacia la respuesta.
¿Necesitas el panorama completo en lugar del pulso? ssh_audit_baseline agrupa sistema, disco,
memoria, puertos, sshd, unidades fallidas, Docker, firewall y actualizaciones. Los hallazgos llegan como
CRÍTICO / ADVERTENCIA / OK; las secciones no medidas se nombran en lugar de leerse silenciosamente como cero.
Búsqueda de registros en servidor Linux
Situación: La API está agotando el tiempo de espera, pero el mismo mensaje puede estar en nginx, syslog, journald o un registro de aplicación que no puedes leer con tu usuario normal.
Pregunta: "¿De dónde vino ese error?"
SSH sin procesar
$ 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
El tercer comando parece limpio, pero 2>/dev/null también ocultó un error de permisos. "Nada
coincidió" y "nada se leyó" ahora se ven idénticos. Un registro ocupado también puede devolver miles de
líneas y sacar el resto del incidente del contexto del agente.
Resultado MCP estructurado
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
}
Lo que gana el agente
| SSH sin procesar | MCP estructurado | Tu ganancia |
|---|---|---|
| Cuatro búsquedas y cuatro salidas | Una búsqueda en archivos y globs | Menos tokens y viajes de ida y vuelta |
| Los errores de permisos pueden desaparecer | files_unreadable nombra cada ruta omitida | Sin conclusión falsa de "registros limpios" |
| La salida puede crecer sin un límite útil | limited y truncated exponen cada corte | Decisiones más seguras con resultados parciales |
since usa el reloj del servidor, namesOnly: true devuelve solo las rutas coincidentes, y
ssh_log_tail lee las últimas N líneas de varios registros en una sola llamada.
Ediciones seguras de configuración remota
Situación: Necesitas reemplazar una configuración de nginx en un servidor en vivo. Una conexión caída, un modo incorrecto o una copia sin verificar podrían dejar el servicio con un archivo roto.
Pregunta: "¿Puedo reemplazar esta configuración sin dejar un archivo parcial?"
SSH sin procesar
$ 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
El código de salida cero dice que el shell terminó. No prueba qué bytes llegaron, y >
truncó el archivo antiguo antes de que llegara el primer byte del nuevo. Si la conexión se cae
a mitad de la escritura, el servicio queda con una configuración parcial.
Resultado MCP estructurado
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 }]
}
Lo que gana el agente
| SSH sin procesar | MCP estructurado | Tu ganancia |
|---|---|---|
| El destino se trunca antes de que la copia se complete | Un archivo temporal completo lo reemplaza con un solo renombrado | Sin configuración a medio escribir |
| Solo código de salida | Los bytes y el resultado de la verificación se nombran | Sabes qué llegó realmente |
| Los permisos viven dentro del texto del shell | sudo, mode y verify son campos por archivo | Propiedad predecible y menos errores de comillas |
verified tiene tres resultados honestos: verified, unavailable cuando el servidor no tiene herramienta
de hash, y skipped cuando no se solicitó verificación. Para lecturas, ssh_file_read acepta una
lista de rutas; ssh_file_list maneja globs, recursión, tamaños y modos.
Ejecutar comandos SSH por lotes con sudo
Situación: Un despliegue está listo, pero la sintaxis de nginx, el estado del servicio y los errores recientes deben verificarse antes de que el tráfico se mueva. Una verificación fallida no debería desaparecer dentro de un volcado combinado.
Pregunta: "¿Pasaron todas las verificaciones previas al despliegue?"
SSH sin procesar
$ 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
Tres conexiones devuelven tres salidas no relacionadas. Si los comandos se unen con ;, el
shell informa solo el último código de salida; si se unen con &&, las verificaciones posteriores desaparecen
después del primer fallo.
Resultado MCP estructurado
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
}
Lo que gana el agente
| SSH sin procesar | MCP estructurado | Tu ganancia |
|---|---|---|
| Tres llamadas y salidas no relacionadas | Una lista de comandos ordenada | Menos viajes de ida y vuelta |
| Un shell combinado puede ocultar el estado intermedio | Cada comando mantiene su propio exit_code | Sin verificación fallida pasada por alto |
sudo y las comillas se repiten en el texto del comando | sudo se aplica a todo el lote | Menos errores de comillas |
El guardián de comandos destructivos verifica la lista completa antes de que se ejecute el primer comando. Si una
entrada es rechazada, cada otra entrada se marca como no ejecutada y nada se envía al servidor.
Cada comando lleva su propio stdout y stderr. Un comando que se ejecutó y no imprimió nada
tiene una cadena vacía; un comando que nunca se ejecutó no tiene ese campo en absoluto, por lo que no se pueden
confundir. La salida de más de 128 KB por comando conserva ambos extremos — el inicio para tablas, el final para
registros — con una costura en medio que indica la cantidad, y clipped_bytes dice cuánto se recortó.
El recorte ocurre en límites de bytes y retrocede hasta el borde de un carácter, por lo que una
respuesta truncada nunca lleva una marca de reemplazo.
sudo llega al servidor sin una terminal: la respuesta del perfil se entrega a sudo en
la entrada estándar. Qué secreto es ese proviene de sudoPassword cuando el perfil nombra uno y
de password en caso contrario — un perfil que inicia sesión por clave no tiene contraseña de inicio de sesión en absoluto, y
donde una máquina mantiene los dos separados, la de inicio de sesión es la respuesta incorrecta. Cuando no hay nada
con qué responder, la respuesta lo dice y nombra las salidas, en lugar de dejar el propio consejo de sudo
sobre -S y los ayudantes de askpass. Un comando que lee su propia entrada estándar nunca recibe la
contraseña, que de otro modo terminaría mezclada con los datos.
Ejecutar trabajos SSH de larga duración
Situación: Una copia de seguridad o migración durará más que la sesión del agente. La conexión puede cerrarse, pero aún necesitas su estado, salida y código de salida más tarde.
Pregunta: "¿Sobrevivirá este trabajo a la conversación?"
SSH sin procesar
$ ssh admin@server.example.com 'pg_dump app | gzip > /srv/backups/app.sql.gz'
client_loop: send disconnect: Broken pipe
La terminal ha desaparecido. Ahora tienes que reconectarte, encontrar el proceso, inspeccionar el archivo de destino y adivinar si la copia de seguridad terminó o se detuvo a mitad de camino.
Resultado MCP estructurado
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"
}
Lo que el agente gana
| SSH sin procesar | MCP estructurado | Tu ganancia |
|---|---|---|
| El trabajo está vinculado a una sesión SSH | El trabajo remoto tiene un id persistente | Desconexiones y reinicios seguros |
| Reconectarse significa buscar procesos y archivos | El estado y el código de salida tienen estados nombrados | Sin adivinar si terminó |
| Leer la salida de nuevo repite texto antiguo | La salida continúa desde un desplazamiento de bytes | Menor uso de tokens en trabajos largos |
El estado del trabajo vive en el disco remoto, no en la memoria de este servidor. ssh_job_status distingue
running, finished y lost; ssh_job_output continúa desde el último desplazamiento de bytes; y
ssh_job_kill señala a todo el grupo de procesos en lugar de solo a su shell.
Transferir archivos a routers y NAS heredados
Situación: Un cliente OpenSSH actual intenta SFTP, pero el router o NAS solo entiende el protocolo scp clásico. El archivo debe llegar intacto y reemplazar su destino de forma segura.
Pregunta: "¿Puede este dispositivo antiguo seguir recibiendo un archivo verificado?"
SSH sin procesar
$ scp app.conf operator@router:/etc/app.conf
subsystem request failed on channel 0
scp: Connection closed
El siguiente paso habitual es recordar la bandera heredada, reintentar la copia y luego ejecutar un comando hash por separado—si el dispositivo tiene una herramienta hash en absoluto.
Resultado MCP estructurado
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
}]
}
Lo que el agente gana
| SSH sin procesar | MCP estructurado | Tu ganancia |
|---|---|---|
| El modo SFTP moderno se detiene en el primer error | La alternativa scp clásica es automática y recordada | El equipo antiguo sigue funcionando |
| Una copia exitosa no prueba integridad | La verificación SHA-256 tiene un resultado nombrado | La corrupción no se confunde con éxito |
| El reemplazo directo puede dejar un destino parcial | Un archivo temporal se mueve a su lugar después de la transferencia | El archivo de trabajo sobrevive a interrupciones |
Si el dispositivo no tiene ni sha256sum ni openssl, el resultado dice unavailable y nombra
la razón en lugar de informar una coincidencia falsa. Los directorios completos usan recursive: true y
verifican sus hashes en un solo lote.
Protección contra comandos destructivos para agentes de IA
La protección se ejecuta localmente, antes de que un comando llegue a SSH. Separa las operaciones que pueden recuperarse de las que destruyen el contenedor que alberga los datos, y verifica el orden de los comandos dentro de cadenas y lotes.
Detener una cadena destructiva antes de que comience
Una secuencia segura de copia de seguridad y reemplazo:
cp -r /srv/app /srv/app.bak && mv /srv/app /srv/app-old && rm -rf /srv/app
Las mismas operaciones en el orden incorrecto:
rm -rf /srv/app && cp -r /srv/app /srv/app.bak && mv /srv/app /srv/app-old
# REFUSED before the first command runs
El shell eliminaría el directorio y solo entonces descubriría que la fuente de la copia de seguridad ha desaparecido.
La protección ve que los pasos posteriores leen un destino ya destruido por un paso anterior, por lo que
toda la llamada permanece en tu máquina. La misma verificación detecta
dropdb app && pg_dump app > backup.sql.
Rechazar pérdida irreversible, advertir sobre cambios recuperables
| Rechazado — el contenedor en sí | Solo advertido — su contenido |
|---|---|
DROP DATABASE, dropdb | DROP TABLE, TRUNCATE, DELETE FROM |
docker volume rm, docker compose down -v | docker rm -f <name> |
crontab -r | editar un trabajo |
mkfs, wipefs -a, lvremove, zfs destroy | chmod 777 |
reboot, shutdown, halt | git reset --hard |
docker compose down -v se rechaza porque -v elimina volúmenes Docker nombrados, incluido un volumen
de base de datos. Sin -v, detener los servicios no se trata como la misma acción irreversible.
La eliminación recursiva de la raíz del sistema de archivos, un directorio de inicio o árboles del sistema como /etc,
/var y /usr también se rechaza, incluso cuando un enlace simbólico lleva allí. Un destino no resuelto
como rm -rf "$DIR"/* también se rechaza: "no se pudo verificar" no se trata como "seguro".
Nombrar lo que detienes
Un comando que encuentra su destino en lugar de nombrarlo no se envía. El servidor lo expande y responde con lo que hay detrás del destino:
docker kill $(docker ps -q --filter ancestor=web)
# BLOCKED — would stop:
# edge — web:latest, Up 34 days, 0.0.0.0:8443->8443/tcp
Para un proceso, la respuesta añade las señales de que está en uso: cuánto tiempo ha estado ejecutándose,
qué puertos acepta conexiones, cuántas conexiones está manejando. Los destinos nombrados
no cuestan nada extra y pasan en silencio — docker kill web-1, kill 4871,
systemctl stop app.
Para continuar, nombra lo que se está deteniendo. Los nombres se verifican contra lo que el comando realmente alcanza, por lo que una máscara que se ha desviado hacia otra cosa se rechaza en lugar de confirmarse:
docker kill $(docker ps -q --filter ancestor=web) # CONFIRMED-KILL: edge
Un patrón sobre líneas de comando es un caso aparte. Coincide con el propio comando que lo lleva, por lo que el shell que lo ejecuta recibe la señal antes que el destino y la respuesta se interrumpe a la mitad. Tal ataque no se confirma sino que se reescribe — por número, o con un carácter escrito como una clase para que el patrón deje de coincidir consigo mismo:
pkill -f relay
# BLOCKED — two ways through:
# kill 4871
# pkill -f '[r]elay' # CONFIRMED-KILL: 4871
Tres resultados permanecen separados: destinos encontrados, la expansión no llegó a nada, y nada con qué preguntar — sin motor en la máquina, una respuesta truncada, una conexión que falló. Los dos últimos también son rechazos: no saber no es una razón para proceder.
Confirmar un comando destructivo intencional
Nada está prohibido permanentemente. Añade # CONFIRMED-DESTRUCTIVE a un comando revisado y se
permite su paso. Cuando la protección rechaza una entrada en un lote, el lote completo se detiene
antes de la ejecución, por lo que el servidor nunca queda después de una operación a medio ejecutar.
La protección funciona dentro de una sola llamada. No puede conectar una eliminación en una invocación con una lectura en la siguiente, ni razonar sobre herramientas que no reconoce. Es un cinturón de seguridad, no un motor de políticas: las operaciones recuperables siguen siendo tu decisión. Las restricciones de rutas y las reglas de comillas están documentadas en docs/security.md.
Herramientas
18 herramientas MCP SSH para operaciones de servidor. Parámetros completos y ejemplos viven en docs/tools.md.
| Herramienta | Qué hace |
|---|---|
ssh_exec | Ejecutar un comando o un lote, con la protección de comandos destructivos y desconexión opcional |
ssh_file_read | Leer uno o varios archivos, texto o binario |
ssh_file_write | Escribir archivos con renombrado atómico y verificación SHA-256 opcional |
ssh_file_list | Listar un directorio, con glob y recursión opcionales |
ssh_upload | Subir un archivo o directorio por SSH, seguro para binarios con verificaciones de integridad; un directorio reemplaza el destino o se fusiona en él |
ssh_download | Descargar un archivo o directorio por SSH, seguro para binarios con verificaciones de integridad |
ssh_job_status | Estado de un trabajo en segundo plano: ejecutándose, terminado o perdido |
ssh_job_output | Leer la salida acumulada desde un desplazamiento de bytes |
ssh_job_list | Listar trabajos, eliminando los terminados más allá de su TTL |
ssh_job_kill | Señalar a todo el grupo de procesos de un trabajo |
ssh_log_tail | Últimas N líneas de uno o varios registros, con soporte de glob; un contenedor por nombre |
ssh_log_search | Búsqueda de patrones en registros, o a través del registro de un contenedor |
ssh_snapshot | Instantánea de salud de una sola vez: servicios, recursos, Docker, red, errores |
ssh_monitor | Control de transporte: estadísticas, recarga, prueba, lista, cierre |
ssh_audit_baseline | Sistema, disco, memoria, red, ssh, servicios, Docker, firewall, actualizaciones |
ssh_tls_check | Caducidad de certificados, SAN, cadena y enlace de renovación para un dominio |
ssh_disk_breakdown | A dónde fue el disco: du top-N, Docker, journald, cachés |
ssh_service_status | systemctl status más una cola journalctl para una unidad |
Anotaciones de seguridad de herramientas MCP
Las anotaciones MCP estándar indican a los clientes qué herramientas son de solo lectura, destructivas, idempotentes o de mundo abierto. Consulta la tabla completa.
Ejecutar comandos SSH y gestionar archivos remotos
Comandos, lecturas y escrituras de archivos, listados de directorios — el trabajo ordinario en una máquina, cada respuesta ya analizada.
Monitorear trabajos SSH de larga duración
El trabajo lento se desconecta y se sigue en lugar de esperarlo: cada mirada dice hasta dónde ha llegado.
Buscar registros y verificar la salud del servidor
Registros de archivos y contenedores, y una imagen de una sola vez de la máquina, con salida limitada para que una cola no consuma la ventana de contexto.
Subir y descargar archivos por SSH
Transferencias seguras para binarios con verificaciones de integridad. Detalles en docs/transfer.md.
Para binarios y archivos grandes usa
ssh_upload/ssh_download— los fragmentos base64 y los heredocs no son seguros para binarios ni atómicos.
Auditar servidores Linux por SSH
Solo lectura y agrupados en un solo viaje de ida y vuelta. Detalles en docs/audit.md.
Modo de compatibilidad SSH de Windows
Windows usa el modo de compatibilidad automáticamente. Cuando la multiplexación de conexiones no está disponible, el servidor cambia a una conexión por comando. Las mismas herramientas siguen estando disponibles a través de SSH basado en claves — sin configuración separada ni implementación específica de Windows.
La protección de comandos destructivos se cubre en Protección contra comandos destructivos para agentes de IA.
Configurar el servidor SSH MCP
Ejecuta el paquete desde Instalar en 30 segundos primero, luego crea un archivo de perfil.
Crear perfiles de conexión SSH
Colócalo donde quieras — junto a la configuración de tu propio agente es la elección habitual. Los ejemplos a continuación usan ~/.claude/ssh-profiles.json; para otros agentes cambia el directorio (~/.codex/, ~/.qwen/, ~/.config/opencode/):
{
"profiles": {
"production": {
"host": "server.example.com",
"username": "admin",
"port": 22,
"privateKeyPath": "~/.ssh/your_private_key"
}
}
}
Elegir un perfil SSH explícitamente
No hay un perfil al que el servidor recurra: cada uno es una máquina diferente, y un comando enviado a la máquina equivocada no es algo que un mensaje de error pueda deshacer después. Pregunta sin un nombre y la respuesta lista los nombres para elegir:
ssh_exec({ command: "uptime" })
→ No profile specified. Name one explicitly: production
Un perfil que el servidor no puede usar para SSH — sin host, sin username, o mode: "local" — se omite sin queja, y los campos que no reconoce se dejan intactos, para que el archivo pueda compartirse con otras herramientas. Un perfil con un campo roto es un caso diferente: se nombra junto con el campo y el valor, y sus vecinos saludables siguen funcionando.
Cada perfil opcionalmente toma un bloque pathSecurity que permite o bloquea las rutas que las herramientas de archivos pueden tocar — consulta docs/security.md.
Un perfil que inicia sesión por clave pero necesita sudo en el lado remoto toma un sudoPassword — el secreto sudo con el que se responde, que en muchas máquinas no es la contraseña de inicio de sesión. Mantenlo en el archivo de secretos en lugar de aquí.
Mantener contraseñas SSH y frases de contraseña fuera de los perfiles
Preferir claves. Si una contraseña o frase de contraseña de clave cifrada es inevitable, mantenla en un archivo de secretos separado, nunca en el propio perfil:
{
"secretsFile": "~/.config/ssh-mcp/secrets.json",
"profiles": {
"production": {
"host": "server.example.com",
"username": "admin"
}
}
}
El archivo de secretos está indexado por nombre de perfil — consulta secrets.json.example:
{
"production": { "password": "..." },
"buildbox": { "sudoPassword": "..." }
}
sudoPassword es lo que sudo responde en esa máquina. Un perfil que inicia sesión con clave no tiene contraseña de inicio de sesión que ofrecer, y cuando ambas difieren, la de inicio de sesión es la respuesta incorrecta; sin ella, se usa password.
El archivo de secretos debe ser legible solo por ti (chmod 600). Las rutas relativas se resuelven desde el archivo de perfiles; los secretos permanecen fuera de argv y se enmascaran en los registros. Consulta seguridad de credenciales.
Configurar Claude Code, Codex y otros clientes MCP
Elige el cliente que uses y apúntalo al mismo archivo de perfiles.
Claude Code
Un comando; -s user hace que el servidor esté disponible en cada proyecto:
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
Ponlo en ~/.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
Un comando, igual que los demás:
qwen mcp add ssh \
-e SSH_PROFILES_FILE="$HOME/.qwen/ssh-profiles.json" \
npx -y @hypnosis/ssh-mcp-server
Otros clientes MCP
Gemini CLI, Hermes, Cline, un plugin de editor o tu propio agente funcionan de la misma manera. Todo lo que necesitan es un comando para ejecutar y una variable de entorno.
Reinicia tu cliente MCP
Reinicia el cliente y luego ejecuta ssh_monitor({ action: "list" }) para confirmar que el perfil se cargó.
Configuración del servidor SSH MCP
| Variable | Qué hace | Predeterminado |
|---|---|---|
SSH_PROFILES_FILE | Ruta al JSON de perfiles — obligatorio | — |
SSH_MCP_LOG_LEVEL | debug, info, warn, error | info |
LOG_LEVEL | Respaldo, usado solo cuando SSH_MCP_LOG_LEVEL no está definido | info |
SSH_MCP_LOG_TIMESTAMP | Marcas de tiempo en las líneas de registro | true |
SSH_MCP_CONTROL_PERSIST | Segundos que una conexión compartida permanece activa después del último comando; 0 la cierra de inmediato | 600 |
SSH_MCP_CONTROL_DIR | Dónde viven los sockets de control | ~/.ssh/ssh-mcp |
SSH_MCP_PROFILES_CACHE_TTL | TTL de caché de perfiles, ms | 60000 |
SSH_MCP_PROFILES_WATCH | Recargar el archivo de perfiles cuando cambia | true |
La conexión compartida sobrevive a este proceso a propósito: cerrarla al salir cortaría el canal que otra ventana en la misma máquina está usando.
Limitaciones del servidor SSH MCP
Cada límite te dice cómo sortearlo. Una herramienta que no puede hacer algo lo dice y nombra ssh_exec, que ejecuta comandos directamente en la máquina — un controlador de registros no compatible, una utilidad que la máquina no tiene, un motor que este servidor no habla. No tienes que saber de antemano dónde terminan las herramientas: la negativa lo dice, en el momento en que importa.
Tres negativas permanecen deliberadamente en silencio sobre el shell, porque ahí es la respuesta incorrecta: una ruta que tu perfil prohíbe (sortear tu propia regla no es una solución), una llamada mal formada (la solución está en la llamada) y una negativa del propio ssh_exec.
- Cancelación: una llamada cancelada ahora también detiene el comando en el servidor, enviada como una segunda llamada por la misma conexión. Donde el servidor no tiene
/proc, el comando se encuentra a través depsen su lugar. FreeBSD no está verificado: el comportamiento correcto allí no está garantizado. Las transferencias de archivos yssh_snapshotno aceptan cancelación en absoluto. - Escrituras atómicas: BSD y macOS no pueden verificar previamente los cambios de nombre entre sistemas de archivos.
Hoja de ruta del servidor SSH MCP
-
Ejecución completa de pruebas contra hosts SSH de macOS
-
Ejecución de compatibilidad de extremo a extremo en Windows
-
Auditorías multi-host — comparar el estado de salud en varios perfiles SSH en una sola llamada
-
Importar perfiles desde el
~/.ssh/configexistente -
Transferencias reanudables para archivos grandes y conexiones inestables
-
Línea de tiempo de operaciones remotas — comandos, transferencias y decisiones de protección en un solo rastro de auditoría
-
Manuales de solución de problemas SSH listos para usar
-
Registros de contenedores sin recurrir al shell— HECHO:ssh_log_tailyssh_log_searchaceptan un nombre de contenedor, preguntan a docker dónde escribe y leen ese archivo con el mismo mecanismo que cualquier otro registro -
Una negativa que te deja atascado— HECHO: cada límite ahora nombrassh_execcomo la vía de salida, así que llegar al borde de una herramienta cuesta una frase en lugar de un juego de adivinanzas -
Respuestas que llegan al modelo— HECHO: la salida de comandos, las líneas de registro coincidentes, los nombres de máquinas y las secciones de instantáneas viajan en los campos, no solo en el texto -
Esquemas de herramientas MCP más pequeños— HECHO: la lista de herramientas se redujo un 10%, y un trabajo separado ahora muestra las últimas líneas que escribió en lugar de ser consultado a ciegas -
Trabajo largo bajo root— HECHO: un trabajo separado se ejecuta consudoy se sigue como root, y un perfil solo de clave respondesudocon su propiosudoPassword
Desarrollar y probar el servidor 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 vivo se ejecuta contra contenedores reales — uno BusyBox, uno coreutils — porque los dos discrepan silenciosamente, y un simulacro está de acuerdo con quien lo escribió. Consulta docs/architecture.md para la estructura.
¿Te gusta SSH MCP Server? ⭐
Si te gusta la herramienta, dale una estrella en GitHub — ayuda a que más personas descubran el proyecto.
Contribuir al servidor SSH MCP
Los problemas y las solicitudes de extracción son bienvenidos en github.com/hypnosis/ssh-mcp-server.
Licencia
MIT — consulta LICENSE.