Skip to main content
Glama
quiloos39

SSH MCP Server

by quiloos39
README.md
# 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

```bash
git clone <repo-url> && cd ssh-mcp-server
npm install
npm run build
```

### Claude Code

```bash
claude mcp add ssh node /absolute/path/to/ssh-mcp-server/build/index.js
```

### Claude Desktop

Add to `claude_desktop_config.json`:

```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:

1. **Explicit password** — `password` param
2. **Explicit private key** — `privateKeyPath` param (supports `passphrase`)
3. **SSH config IdentityFile** — reads `~/.ssh/config`, tries each key in order
4. **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_execute` returns 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