ssh-mcp
A small, reliable MCP server that gives an AI agent (Claude Code, etc.) persistent SSH access to your servers — register a host once, then run commands by name.
Why
Stock SSH MCP servers reimplement the SSH transport in-process: they drop connections, lose credentials between sessions, and force the agent to fall back to raw ssh -i key user@host shell-outs (which leak creds into command history and break on reconnect).
This server does not reimplement SSH. It wraps the system ssh/scp binaries with OpenSSH ControlMaster multiplexing:
- A background control socket (
ControlPersist=10m) keeps one authenticated connection warm — reconnects are instant and survive MCP restarts. - Credentials live in a registry file (
~/.ssh/ssh_mcp_hosts.json), set once, reused by host name. - The agent calls
ssh_exec(host="myserver", command=...)— no keys or IPs in every call.
Tools
| Tool | Purpose | |---|---| | host_add | Register/overwrite a named host (name, host, user, port?, key?, note?) | | host_list | List registered hosts | | host_remove | Remove a host by name | | ssh_exec | Run one non-interactive command on a host (one-shot) | | sftp_put | Upload a local file via scp | | sftp_get | Download a remote file via scp |
Execution model — one-shot
Each ssh_exec is an independent command. State does not persist between calls:
cwddoes not persist → use absolute paths orcd /path && cmd.- env does not persist → set inline (
VAR=x cmd). - No interactive TUI (
vim,top,less) and no prompts → pass-y/--yes. - Long jobs:
nohup cmd >/tmp/x.log 2>&1 &, then tail the log in a later call.
First connect with a password (auto key-provisioning)
For a host that only accepts a password (a factory-default device, a fresh VPS), let the server mint a dedicated key for it:
host_add(name="cam1", host="10.0.0.180", user="root")— nokey.ssh_exec(host="cam1", command="uname -a")→ fails withPermission deniedplus a hint.- Put the password in a file so it never enters the transcript:
umask 077; printf %s 'THE_PASSWORD' > /tmp/sshpw_cam1
ssh_exec(host="cam1", command="uname -a", password_file="/tmp/sshpw_cam1")
The server generates ~/.ssh/ssh_mcp_cam1_ed25519, installs it via ssh-copy-id, records the key path in the registry, shreds the password file, and runs the command. Every later call authenticates by key — no password needed. One dedicated key per host; the password is read from the file (never argv) and is never logged.
Requires sshpass and ssh-copy-id on PATH for this flow.
Requirements
- Node.js 23.6+ (runs TypeScript directly via type-stripping — no build step)
- OpenSSH client (
ssh,scp) on PATH sshpass+ssh-copy-id(only for password-based auto-provisioning)
Install
pnpm install
Register with your MCP client (e.g. Claude Code ~/.claude.json):
{
"mcpServers": {
"ssh": {
"type": "stdio",
"command": "node",
"args": ["/absolute/path/to/ssh_mcp/src/index.ts"]
}
}
}
Restart the client, then add a host:
host_add(name="myserver", host="1.2.3.4", user="root", key="~/.ssh/id_ed25519")
ssh_exec(host="myserver", command="uname -a")
License
MIT












