Skip to main content
Glama
README.md
# ssh-bridge-mcp

An MCP server that lets any MCP-capable AI client (Claude Code, Claude
Desktop, or a client talking to it over HTTP) run commands on a remote
machine over SSH — **without installing anything on the remote machine**.
The only requirement on the remote end is a working `sshd`, same as any
normal SSH login.

This exists to bridge the gap between "AI agent wants to help with my
remote server" and "that server doesn't have Claude Code / agent tooling
installed and I don't want to install it there."

## How it works

The server runs **locally**, wherever your AI client runs (your laptop,
typically). It holds one or more live SSH connections in memory and
exposes them as MCP tools: `ssh_connect`, `ssh_exec`, `ssh_read_file`,
`ssh_write_file`, `ssh_list_sessions`, `ssh_disconnect`. The AI calls these
tools; this process is the one that actually opens the socket and
authenticates to the remote host — the model itself never touches your
credentials.

```
 Your AI client  <-- MCP (stdio) -->  ssh-bridge-mcp  <-- SSH -->  remote host
 (Claude Code /                       (runs on your
  Claude Desktop)                      local machine)
```

## Install

```bash
git clone <this repo>
cd ssh-bridge-mcp
python3 -m venv venv
./venv/bin/pip install -e .
```

## Configure credentials (do this, not inline passwords)

Set these as **environment variables on the MCP server process**, not as
arguments in a chat message — that way the password never enters the
model's context or any conversation log.

| Variable | Purpose |
|---|---|
| `SSH_BRIDGE_PASSWORD` | Password used by `ssh_connect` when no key is configured |
| `SSH_BRIDGE_KEY_PATH` | Path to a private key, if you use key auth instead |
| `SSH_BRIDGE_KEY_PASSPHRASE` | Passphrase for that key, if any |
| `SSH_BRIDGE_ALLOWED_HOSTS` | Comma-separated allowlist of hosts this server may connect to (recommended once you're not just testing) |
| `SSH_BRIDGE_DENY_PATTERNS` | Comma-separated glob patterns of commands to always block (ships with a small default denylist — `rm -rf /*`, fork bombs, etc.) |
| `SSH_BRIDGE_STRICT_HOST_KEY` | Set to `1` to require the host key already be in `known_hosts` instead of auto-trusting on first connect |
| `SSH_BRIDGE_MAX_OUTPUT_CHARS` | Truncate command output beyond this length (default 20000) |
| `SSH_BRIDGE_TRANSPORT` | `stdio` (default, local process) or `streamable-http` (network, for claude.ai web) |
| `SSH_BRIDGE_HTTP_TOKEN` | **Required** in HTTP mode. Bearer token clients must send; server refuses to start without it |
| `SSH_BRIDGE_HTTP_HOST` / `SSH_BRIDGE_HTTP_PORT` | Bind address/port in HTTP mode (default `127.0.0.1:8000`) |

You can still pass `password` / `key_path` directly to the `ssh_connect`
tool call for one-off use, but prefer the environment variable — anything
passed as a tool argument is visible to the model and typically ends up in
the conversation transcript.

## Wire it up

**Claude Code** — add to `.mcp.json` in your project (or `~/.claude.json`
for a global config):

```json
{
  "mcpServers": {
    "ssh-bridge": {
      "command": "/absolute/path/to/ssh-bridge-mcp/venv/bin/python3",
      "args": ["-m", "ssh_bridge_mcp.server"],
      "env": {
        "SSH_BRIDGE_PASSWORD": "your-password-here",
        "SSH_BRIDGE_ALLOWED_HOSTS": "your-server.example.com"
      }
    }
  }
}
```

**Claude Desktop** — same shape, in Settings → Developer → Edit Config
(`claude_desktop_config.json`), under `mcpServers`.

**This chat (claude.ai web)** — claude.ai runs in the cloud and can't spawn
a local process on your machine, so stdio won't reach it. Run the server in
`streamable-http` mode instead and expose it through a tunnel you control
(Tailscale Funnel, Cloudflare Tunnel, etc.) — never bind it directly to the
open internet. Then add it as a remote MCP connector pointed at your
tunnel's URL:

```bash
export SSH_BRIDGE_TRANSPORT=streamable-http
export SSH_BRIDGE_HTTP_TOKEN=$(python3 -c "import secrets; print(secrets.token_urlsafe(32))")
export SSH_BRIDGE_PASSWORD=your-ssh-password
./venv/bin/python3 -m ssh_bridge_mcp.server
```

This mode **requires** `SSH_BRIDGE_HTTP_TOKEN` — the server refuses to
start without one — and every request must send
`Authorization: Bearer <token>` or it gets a 401 before any tool runs.
Treat that token like a password: whoever holds it can open SSH sessions
through this server. Save it somewhere like a password manager, not in the
tunnel's public config.

Be deliberate about running this mode at all: it turns "a local script that
can SSH into my server" into "a network-reachable service that can SSH into
my server." For most use, driving this from Claude Code or Claude Desktop
locally over stdio (no network exposure at all) is the simpler and safer
path — reach for HTTP mode only when you specifically need claude.ai web
or another remote client to reach it.

## Usage (once configured)

Just talk to your AI client naturally:

> "Connect to 10.0.0.5 as deploy and check disk usage."

It will call `ssh_connect`, then `ssh_exec("df -h")`, and report back.
Sessions persist across multiple tool calls in a conversation (until you
disconnect or the server process restarts), so you don't re-authenticate
for every command.

## Security notes

- **Passwords over SSH are weaker than keys.** This works with password
  auth because that's what was asked for, but consider switching the
  remote host to key-only auth when you get a chance.
- **The default host-key policy auto-trusts new hosts** (like a fresh
  `ssh` the first time you connect). Set `SSH_BRIDGE_STRICT_HOST_KEY=1`
  once you've connected once and trust `known_hosts`.
- **`ssh_exec` runs one command per call**, not a persistent shell — `cd`
  and exported variables from one call don't carry to the next. Chain with
  `&&` or write a script remotely with `ssh_write_file` and execute that.
- **The deny-pattern list is a seatbelt, not a sandbox.** It blocks a
  handful of obviously catastrophic patterns by string match; it is not a
  security boundary against a determined or adversarial actor. Don't rely
  on it to make it safe to point this at a host you don't trust the AI
  (or whoever else can reach this MCP server) to administer.
- **Anyone who can call this server's tools can act as the SSH user it
  authenticates as.** Scope the remote account's permissions accordingly
  (a limited deploy user beats using `root`).

## Extending

The tool surface here is intentionally minimal. Natural next additions:
port forwarding, directory listing/glob over SFTP, binary file transfer,
`sudo` handling, multiplexed persistent shells (via `invoke_shell`) for
interactive/long-running commands, and structured host inventory (an
allowlist with per-host default users instead of one global env config).

## License

MIT — see `LICENSE`.

TDQS

A4/5.0

Scored across 6 tools

Disambiguation5/5

Each tool maps to a distinct operation: session creation, command execution, file read, file write, session listing, and session teardown. There is no meaningful overlap between executing commands and transferring files via SFTP. An agent can reliably choose the right tool based on the intended action.

Naming Consistency5/5

All tools share the ssh_ prefix and follow a consistent verb-based naming pattern: connect, exec, read_file, write_file, list_sessions, disconnect. The snake_case convention is uniform and predictable.

Tool Count5/5

Six tools is well-scoped for an SSH bridge server. Each tool covers an essential part of the session lifecycle or remote interaction without unnecessary redundancy.

Completeness4/5

The core SSH workflow is well covered: connect, execute commands, read/write text files, list sessions, and disconnect. Minor gaps such as file deletion, directory listing, or binary transfer are absent, but they do not hinder typical bridge usage.

Maintenance

ActivityMaintained
ResponsivenessNo issues