SSH MCP Server
An MCP (Model Context Protocol) server that lets Claude execute commands on remote servers over SSH. Supports ~/.ssh/config, private keys, password auth, and SSH agent forwarding.
Tools
| Tool | Description | |------|-------------| | ssh_connect | Open an SSH connection to a remote host | | ssh_execute | Run a command on a connected host | | ssh_disconnect | Close a connection | | ssh_list_connections | Show all connections and their status |
Setup
git clone <repo-url> && cd ssh-mcp-server
npm install
npm run build
Claude Code
claude mcp add ssh node /absolute/path/to/ssh-mcp-server/build/index.js
Claude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"ssh": {
"command": "node",
"args": ["/absolute/path/to/ssh-mcp-server/build/index.js"]
}
}
}
Authentication
Auth is resolved in this order — the first match wins:
- Explicit password —
passwordparam - Explicit private key —
privateKeyPathparam (supportspassphrase) - SSH config IdentityFile — reads
~/.ssh/config, tries each key in order - SSH agent — uses
SSH_AUTH_SOCK(default when nothing else is specified)
Using ~/.ssh/config
The server reads ~/.ssh/config on every connect call, so edits are picked up without restarting. All standard directives are supported:
Host myserver
HostName 10.0.1.50
User deploy
Port 2222
IdentityFile ~/.ssh/id_ed25519
Then just connect with host: "myserver" — the alias, hostname, user, port, and key are all resolved automatically.
Tool Reference
ssh_connect
| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | host | string | yes | Hostname, IP, or SSH config alias | | username | string | no | Override SSH config user | | port | number | no | Override SSH config port (default 22) | | password | string | no | Password auth | | privateKeyPath | string | no | Path to private key (e.g. ~/.ssh/id_ed25519) | | passphrase | string | no | Passphrase for encrypted private key | | useAgent | boolean | no | Use SSH agent (default: true if no other auth given) | | connectTimeout | number | no | Timeout in ms for TCP + handshake (default 20000) | | connectionId | string | no | Custom ID for this connection (default: host value) |
Returns a connection ID used by the other tools.
ssh_execute
| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | connectionId | string | yes | Connection ID from ssh_connect | | command | string | yes | Shell command to run | | cwd | string | no | Working directory (wraps as cd <cwd> && <command>) | | timeout | number | no | Command timeout in ms (default 30000) | | errOnNonZero | boolean | no | Mark non-zero exit as MCP error (default false) |
Returns stdout, stderr, and exit code. Output is capped at 1MB per stream.
errOnNonZero defaults to false because many commands like grep (no match = exit 1) and diff (differences = exit 1) use non-zero exits for normal results.
ssh_disconnect
| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | connectionId | string | yes | Connection ID to close |
ssh_list_connections
No parameters. Returns all connections with host, port, user, and status (connected/dead).
Broken Pipe Handling
- Keepalive packets are sent every 10 seconds to detect dead connections early
- If a connection drops,
ssh_executereturns a clear error telling you to reconnect - An outer timeout wraps the entire TCP + SSH handshake so connections to firewalled/black-hole IPs don't hang forever
- All connections are cleaned up on
SIGTERM/SIGINT
Example Session
1. ssh_connect({ host: "myserver" })
→ Connected to 10.0.1.50:2222 as deploy. Connection ID: "myserver"
2. ssh_execute({ connectionId: "myserver", command: "uname -a" })
→ Linux prod-01 5.15.0 #1 SMP x86_64 GNU/Linux
→ Exit code: 0
3. ssh_execute({ connectionId: "myserver", command: "ls", cwd: "/var/log" })
→ syslog auth.log kern.log ...
→ Exit code: 0
4. ssh_list_connections()
→ - myserver: 10.0.1.50:2222 as deploy [connected]
5. ssh_disconnect({ connectionId: "myserver" })
→ Disconnected "myserver".
Security Notes
- Host key verification is not enforced (TOFU — trust on first use). The server accepts any host key.
- Passwords and passphrases are passed as tool parameters and are not persisted, but may appear in MCP client logs depending on your client.
- The server runs with the permissions of the user who started it.
License
MIT






