Skip to main content
Glama
m1bsvonmibenstein

SSH MCP Server

README.md
# SSH MCP Server

An [MCP](https://modelcontextprotocol.io) server that lets an AI assistant (Claude Code, Claude Desktop, etc.) run commands and transfer files over SSH — **without ever seeing your real connection details**. The assistant only works with aliases like `prod` or `server1`; hostnames, usernames, keys, and passwords stay on your machine.

Built on the official `@modelcontextprotocol/sdk` and `ssh2`, with an optional passthrough mode that shells out to your system `ssh`/`scp` and reads `~/.ssh/config`.

## Why

- **Credentials never reach the model.** Real host/user/IP live in your local config (or `~/.ssh/config`) and are scrubbed from all output before the assistant sees it.
- **Alias-only surface.** The assistant calls `ssh_exec("prod", "...")`; it has no idea what `prod` resolves to.
- **Real `scp`/`rsync`/`ProxyJump` support** via passthrough mode when you want it.

## Tools

| Tool | Description |
|------|-------------|
| `ssh_exec` | Run a command on a server. Returns stdout/stderr/exit code. |
| `ssh_upload` | Upload a file, a glob (`logs/*.gz`), or a whole directory (`recursive=true`). `preserve=true` keeps mode+timestamps (`scp -p`). |
| `ssh_download` | Download a file, a glob, or a whole directory (`recursive=true`). Same `preserve` option. |
| `ssh_list` | List a remote directory. |
| `ssh_list_servers` | List configured aliases and their descriptions. Never exposes hosts. |

## Install

```bash
git clone <this-repo> sshmcp
cd sshmcp
npm install
```

Requires Node 18+. Passthrough mode additionally needs the OpenSSH client (`ssh`/`scp`) on your PATH — built into Windows 10/11, macOS, and Linux.

## Configure your servers

Create `~/.claude/ssh-servers.json` (or anywhere, and point `SSH_CONFIG_PATH` at it). Each entry is an alias. Pick **one** auth method per server.

### A. SSH key (recommended)
```json
{
  "server1": {
    "description": "Main deploy box",
    "host": "10.0.0.5",
    "port": 22,
    "username": "deploy",
    "privateKeyPath": "~/.ssh/id_ed25519"
  }
}
```

### B. Password from an environment variable
```json
{
  "box": {
    "host": "10.0.0.6",
    "username": "admin",
    "passwordEnv": "BOX_PASS"
  }
}
```
Then export `BOX_PASS` before the server starts.

### C. Direct password (local/testing only)
```json
{
  "box": { "host": "10.0.0.6", "username": "admin", "password": "secret" }
}
```

### D. SSH-config passthrough (most secure, full scp/rsync)
```json
{
  "prod": { "description": "Main prod", "sshConfigHost": "prod" }
}
```
When an entry has `sshConfigHost`, the tools shell out to the system `ssh`/`scp` using that `Host` alias from `~/.ssh/config`. HostName, User, IdentityFile, ProxyJump, etc. all come from there and **never appear in this file** — no credentials live in `ssh-servers.json` at all. Real hostnames/users/IPs are scrubbed from output.

- `"sshConfigHost": true` reuses the JSON alias as the Host name.
- `"sshConfigHost": "prod"` maps to a differently-named Host in `~/.ssh/config`.
- Requires key/agent auth (ssh config holds no passwords).

Methods can be mixed freely across servers in one file. See `ssh-servers.example.json`.

### ProxyJump (e.g. reaching Proxmox VMs / hosts behind a bastion)

Add `proxyJump` to reach a target that isn't directly reachable — such as a VM on a Proxmox host's internal network. It works in both modes:

- **Passthrough:** `proxyJump` is an ssh host/alias passed to `scp -J`/`ssh -J`.
  ```json
  "vm-web": { "sshConfigHost": "vm-web", "proxyJump": "pmx" }
  ```
  (Equivalent to putting `ProxyJump pmx` in the Host block in `~/.ssh/config`, which also just works.)

- **Native (ssh2):** `proxyJump` names another configured **native** alias to tunnel through. Chains are supported (a jump may have its own jump).
  ```json
  "vm-web": { "host": "10.10.0.50", "username": "root", "privateKeyPath": "~/.ssh/vm_key", "proxyJump": "server1" }
  ```

The jump host's real address is scrubbed from output alongside the target's.

## Register with Claude Code

```bash
claude mcp add ssh --scope user -- node /absolute/path/to/sshmcp/ssh-server.js
```

> MCP servers are **not** configured in `settings.json` — use `claude mcp add` (writes `~/.claude.json`) or a project `.mcp.json`.

Set a custom config path with the `SSH_CONFIG_PATH` env var if you don't use `~/.claude/ssh-servers.json`.

After adding (or editing the server), **fully restart Claude Code** — MCP tools load at session startup, so a running session won't pick up changes.

## Usage examples

```
# run a command
ssh_exec(serverAlias="prod", command="systemctl status nginx")

# upload a build, preserving permissions and timestamps
ssh_upload(serverAlias="prod", localPath="./dist", remotePath="/var/www/app", recursive=true, preserve=true)

# pull all logs matching a glob
ssh_download(serverAlias="prod", remotePath="/var/log/*.log", localPath="./logs")
```

## Security model

- The config file holds credentials and is `.gitignore`d. `chmod 600 ~/.claude/ssh-servers.json`.
- Connection errors are sanitized — host/IP/username are replaced with the alias before returning.
- Passthrough mode resolves the real host/user via `ssh -G` **server-side only** to scrub them from output; that data never enters the assistant's context.
- Prefer keys or `passwordEnv` over inline passwords.

## Notes & limitations

- **Windows + `preserve`:** timestamps are preserved reliably in both directions; Unix permission bits are limited by what Windows (NTFS) can represent — Windows has no execute bit, so a `755` source becomes `666` when Windows is one end. Linux↔Linux (server-to-server style) is unaffected.
- **Glob** matches a single directory level; use `recursive=true` for whole trees.
- **Symlinks** are skipped during recursive directory walks to avoid cycles.
- Passwords are not supported in passthrough mode (ssh config holds none); give those hosts a key to use mode D.

## License

MIT

TDQS

A3.8/5.0

Scored across 5 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: download, execute, list files, list servers, and upload. No overlap or ambiguity.

Naming Consistency5/5

All tools follow a consistent 'ssh_verb_noun' pattern using snake_case, with descriptive verbs like download, exec, list, and upload.

Tool Count5/5

5 tools is well-scoped for an SSH server, covering core operations (execution, file transfer, listing) without being excessive or insufficient.

Completeness4/5

The set covers essential SSH operations (exec, list, download, upload) but lacks directory creation/deletion or file renaming, which are minor gaps.

Maintenance

ActivityMaintained
ResponsivenessSyncing