Spec Driven Development
MCP Server that facilitates spec-driven development workflows, not just Vibe Coding.
Descripción del creador (original en inglés)
Policy-gated, audited SSH for Linux and Windows hosts: roles, approvals, and an audit log.
Estos tres nunca se combinan en una sola puntuación: las opiniones, las mediciones y la popularidad son cosas distintas.
Todavía no escribimos un resumen sencillo para este. Abajo están los datos que leímos del registro.
Elige la app de IA que usas y copia la configuración.
SSH_MCP_PASSWORD, SSH_MCP_KEY, SSH_MCP_PASSPHRASE, SSH_MCP_SUDO_PASSWORDLos valores marcados como secretos son llaves de tu cuenta. Nunca los compartas ni los pegues en un chat.
Necesitas
¿Qué app usas?
Elige tu app arriba. La recordamos, así que la próxima vez el botón dirá “Instalar en …”.
Abre Claude Desktop → Settings → Developer → Edit Config y pega esto en claude_desktop_config.json
{
"mcpServers": {
"ssh": {
"command": "npx",
"args": [
"-y",
"ssh-mcp"
],
"env": {
"SSH_MCP_PASSWORD": "your-value",
"SSH_MCP_KEY": "your-value",
"SSH_MCP_PASSPHRASE": "your-value",
"SSH_MCP_SUDO_PASSWORD": "your-value"
}
}
}
}Ejecuta esto en tu terminal
claude mcp add ssh --env SSH_MCP_PASSWORD=your-value --env SSH_MCP_KEY=your-value --env SSH_MCP_PASSPHRASE=your-value --env SSH_MCP_SUDO_PASSWORD=your-value -- npx -y ssh-mcpCompleta los valores vacíos antes de ejecutarlo.
Pégalo en .cursor/mcp.json de tu proyecto, o en ~/.cursor/mcp.json
{
"mcpServers": {
"ssh": {
"command": "npx",
"args": [
"-y",
"ssh-mcp"
],
"env": {
"SSH_MCP_PASSWORD": "your-value",
"SSH_MCP_KEY": "your-value",
"SSH_MCP_PASSPHRASE": "your-value",
"SSH_MCP_SUDO_PASSWORD": "your-value"
}
}
}
}Tu navegador te pide permiso para abrir la app; luego la app muestra la configuración y te pide confirmar. Los valores de las claves quedan vacíos para que los completes tú.
Pégalo en .vscode/mcp.json de tu proyecto
{
"servers": {
"ssh": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"ssh-mcp"
],
"env": {
"SSH_MCP_PASSWORD": "your-value",
"SSH_MCP_KEY": "your-value",
"SSH_MCP_PASSPHRASE": "your-value",
"SSH_MCP_SUDO_PASSWORD": "your-value"
}
}
}
}Tu navegador te pide permiso para abrir la app; luego la app muestra la configuración y te pide confirmar. Los valores de las claves quedan vacíos para que los completes tú.
Este se ejecuta en tu computadora, y ChatGPT solo puede conectarse a servidores en internet. Elige otra app.
Tu elección se guarda en una cookie en este dispositivo; no necesitas cuenta.
SSH_MCP_KEY envEsta configuración se genera a partir de la ficha del creador en el registro. Si el README dice otra cosa, sigue el README.
Se ejecuta en tu computadora; la lista de herramientas aparece en tu app de IA después de instalarlo.
Revisamos información que cualquiera puede ver; no hemos leído el código. Las revisiones que no aplican no se cuentan. · Revisado el 6 nov 2026
El nombre en el registro se verificó con la cuenta de GitHub tufantunc, dueña del repo
Todavía no hemos leído este repo en GitHub
Todavía no hemos leído este repo en GitHub
Este se ejecuta en tu computadora; no hay endpoint en internet
Se ejecuta en tu computadora y usa ajustes locales en su lugar
Se ejecuta en tu computadora; solo podemos leer listas de herramientas de endpoints en internet
Comparado con todos los servidores activos de esta categoría en el registro. Las estrellas solo cuentan cuando quien lo publica es dueño del repo.
Superó 1 de 1 revisiones básicas aplicables. No hemos leído el código. Antes de agregar una clave o un token, revisa qué acceso pide el servidor y usa una clave que tenga solo los permisos que necesitas.
Debes completar: SSH_MCP_PASSWORD, SSH_MCP_PASSPHRASE, SSH_MCP_SUDO_PASSWORD
Se ejecuta en tu computadora. Tu app de IA lo inicia cuando hace falta, y los datos no pasan por los servidores del creador (a menos que el propio servidor llame a un servicio en línea).
El repo de GitHub cambió por última vez –, y la última versión (2.23.1) llegó al registro hoy.
Las apps compatibles con MCP, como Claude Desktop, Claude Code, Cursor y VS Code. También necesitas Node.js en tu computadora.
En la misma categoría: Spec Driven Development, Remote Desktop Commander, topo^alpin
Servidores de la misma categoría hechos por otros creadores, ordenados por estrellas.
Cómo leer una tarjeta:★ Estrellas de GitHubiTres pilares: reseñas · revisiones · usoiActualizado = fecha en que se publicó la última versión en el registroi
MCP Server that facilitates spec-driven development workflows, not just Vibe Coding.
Remote Desktop Commander: use files, terminal and processes on your own computers from any AI app.
Avalanche bulletins and mountain weather forecasts for any place in the Alps.
Copiado tal cual del repo del creador. PopNx MCP no lo ha revisado ni lo respalda. Se quitaron HTML, imágenes y scripts. Los enlaces llevan fuera de este sitio.
SSH MCP Server is a security-first Model Context Protocol server that gives LLM agents controlled SSH access to remote hosts — with command classification, policy-based authorization, human-in-the-loop approval, and full audit logging.
The risk this server exists to manage. Giving an LLM shell access on a remote host puts private data, untrusted input and network egress in one place — Simon Willison's "lethal trifecta". Prompt injection has no general fix, so ssh-mcp assumes any command may be attacker-influenced: it classifies before executing, authorizes against a role × host-group matrix, gates destructive work behind approval, and records the decision either way. That narrows the blast radius; it does not remove the risk. Two things stay yours: never point it at a root account, and never set `auto` approval on a production profile. SECURITY.md has the full threat model.
npm install -g ssh-mcp
Without a config the server still starts, so a client or directory can complete the MCP handshake and read tools/list — but every tool call is refused until you configure it, with a message naming the path below. Nothing runs on a host until this step is done. The one exception is reload-config: write the file, then call it with action: "reload" (or restart the server) to configure a server that is already running — see Reloading.
Create the config file at the path for your platform:
| Platform | Path |
|---|---|
| Linux | ~/.config/ssh-mcp/config.toml (or $XDG_CONFIG_HOME/ssh-mcp/config.toml) |
| macOS | ~/Library/Application Support/ssh-mcp/config.toml |
| Windows | %APPDATA%\ssh-mcp\config.toml |
[defaults]
defaultProfile = "dev" approvalMode = "ask-destructive"
[[profiles]] name = "dev" host = "192.168.1.100" port = 22 user = "deploy" # NOT root! auth = "key" keyRef = "~/.ssh/id_ed25519" role = "admin" approvalPolicy = "auto" # dev is permissive
chmod 700 ~/.config/ssh-mcp && chmod 600 ~/.config/ssh-mcp/config.toml
The config decides which hosts, roles and policy rules this server honours, so it checks that nobody but you can read it — and treats the two platforms differently, because the question has a much clearer answer on one of them.
Linux and macOS: enforced. The mode check above, on the file *and* the directory — which is why chmod 700 is in that command, since mkdir -p under the default umask leaves the directory 0755. The server refuses to start otherwise. "Only the owner" is unambiguous here and chmod is a one-line fix.
Windows: split by what the ACL actually allows. There are no mode bits, so the ACL is read instead — and read exposure and write exposure are not treated alike, because Windows is much clearer about one of them than the other.
| The ACL lets another account… | Default |
|---|---|
| only read the config | reported, and the server starts |
| change the config | refused |
| nothing (no ACL at all) | refused — that is full control for everyone |
| …and if the ACL could not be read | refused, except when icacls is absent or the check timed out |
A config under %APPDATA% inherits access for you, SYSTEM and Administrators and needs nothing done to it. One created elsewhere does not: a file under C:\ inherits *read* for every local account and *modify* for every authenticated one. The message names the two icacls commands that fix it either way.
Read exposure is reported rather than refused because that is where Windows is genuinely muddier than POSIX, and refusing over it blocked a config at the documented location (#138). Write exposure is refused because it is not muddy at all: another account being able to rewrite the file that decides which hosts, roles and approval policy this server honours is an authorization bypass, not a disclosure.
Two flags move the whole thing: --strictConfigAcl refuses everything the check objects to, read-only grants included; --allowUncheckedConfigAcl reports everything and refuses nothing. Neither combination leaves you without an exit, which is the lesson of #138.
| Status | Meaning |
|---|---|
0 | Clean shutdown |
1 | A defect in the server — printed with a stack trace; please report it |
2 | How it was invoked or configured — printed as a message, no stack |
A supervisor that treats any non-zero status as a failure needs no change. One that matched on 1 to detect a startup problem should match on 2 as well.
Starting with nothing configured is not an exit-2 condition, as of the release that added introspection without a config: the server starts so it can be described, and refuses each tool call instead, except reload-config. A supervisor that used a non-zero exit to catch an unconfigured deployment should watch for starting unconfigured on stderr, or read configured from GET /health when running the HTTP transport.
export SSH_MCP_PASSWORD="your-password" # if using auth=password
export SSH_AUTH_SOCK="$SSH_AUTH_SOCK" # already set if agent running
Claude Code:
claude mcp add --transport stdio ssh-mcp -- ssh-mcp
Claude Desktop / Cursor / Windsurf:
{
"mcpServers": { "ssh-mcp": { "command": "ssh-mcp", "env": { "SSH_MCP_PASSWORD": "your-password" } } } }
Never pass passwords as CLI arguments — they're visible via ps aux. Use env vars, config files, SSH agent, or OS keychain.
| Tool | Purpose | readOnly | destructive |
|---|---|---|---|
list-connections | Discover available hosts and connection status | ✅ | — |
list-sessions | List active sessions per host | ✅ | — |
open-session | Create a named interactive (stateful) or background session | — | — |
close-session | Close a session. A background session's command is signalled (INT/TERM/KILL) before its channel is dropped | — | ✅ |
read-session-output | Read output from background sessions (e.g., tail -f) | ✅ | — |
read-command | Execute allowlisted read-only commands (ls, cat, grep, ...) | ✅ | — |
run-command | Execute arbitrary commands (destructive/privileged need approval, unless approvalPolicy = "auto") | — | — |
privileged-command | Execute with sudo (needs approval, unless approvalPolicy = "auto") | — | ✅ |
sftp-upload | Upload a file via SFTP (replaces the destination unconditionally) | — | ✅ |
sftp-download | Download a file via SFTP | ✅ | — |
sftp-list | List a remote directory, bounded in entries and bytes | ✅ | — |
sftp-upload-file | Stream a local file to the remote host, never through model context | — | ✅ |
sftp-download-file | Stream a remote file to local disk, never through model context | — | ✅ |
signal-process | Send INT/TERM/KILL to a remote PID | — | ✅ |
reload-config | Show the config file's status, or re-read and apply it without a restart (see Reloading) | — | ✅ |
sftp-upload/sftp-download move file *contents* through the model's context: the text is an argument on the way out and a response on the way back. That is what you want for a config snippet and exactly what you do not want for a 200 MB tarball or anything binary.
The two also differ on what they do to an existing destination, and the approved string now says which is which. sftp-upload replaces unconditionally and spells --overwrite every time, because that is what it always does; sftp-upload-file refuses unless you pass overwrite: true, and only then carries the flag. Since sftp-upload takes its content as an argument rather than naming a local file, its string also carries --bytes= --sha256=<32 hex> — without that, two uploads to the same path are the same string, which means one approval covers both and an auditor cannot tell which set of bytes landed. The digest is 128 bits rather than a short prefix because that claim has to hold against a caller who picks both payloads, not only against an accidental repeat.
[policy].denylist patterns are tested against that whole string and, for every SFTP tool, against the remote path on its own — as given, lexically normalized, and read the way Windows reads it (see "Policy Engine"). A rule written for the path, such as authorized_keys$, therefore does not depend on where the string puts it or on how the file is spelled. A rule anchored on the *whole* string (^sftp:upload /root/.*$) still does: that layout is ours to change, so anchor on the path instead.
sftp-upload-file and sftp-download-file stream between the remote host and local disk instead. Neither the bytes nor a base64 encoding of them ever reaches the model — the response is a byte count and two paths.
The two transfer tools are off until you configure `defaults.transferRoot`, and refuse with an explanation until then. (sftp-list has no local side and needs none of this.) That directory is the whole of their local reach:
[defaults]
transferRoot = "/srv/ssh-mcp-transfers" transferMaxBytes = 268435456 # 256MB per transfer transferTimeoutMs = 300000 # 5min with no bytes moving
Checked on every call, and refused rather than degraded if any of it fails: the directory must be 0700 and owned by the account running this server, with no group- or world-writable parent, and must not overlap the ssh-mcp installation, the config directory, the audit log directory, or ~/.ssh. Caller paths are confined to it, symlinks are refused rather than followed, and a download is staged as a sibling .part file and published atomically, so a failed transfer never leaves a half-written file at the destination.
transferTimeoutMs is an idle budget, not a total one: it bounds one metadata round-trip, or one stretch of the copy with no bytes moving, and is re-armed on progress. A slow but live transfer of a large file survives it; a stalled channel still fails within one window. That is why the byte cap above does not have to be divided by it — a total budget would have made a 256MB cap mean "only if the link sustains 900 KB/s".
Not available on Windows, where the transfer root cannot yet be verified private; the two transfer tools refuse there rather than writing into a directory other accounts may be able to read.
sftp-upload-file takes an optional mode (1–511; setuid, setgid and the sticky bit are refused). Omitted, a new remote file is published 0600; an overwritten one inherits the replaced file's permission bits — its permission bits only, not its setuid or setgid.
What a transfer is authorized for includes
Solo se muestra el principio; lee el resto en el repo