# Ssh Mcp

> SSH MCP server

- **Type:** MCP server
- **Install:** `agentstack add mcp-n0madic-ssh-mcp`
- **Verified:** Pending review
- **Seller:** [n0madic](https://agentstack.voostack.com/s/n0madic)
- **Installs:** 0
- **Category:** [Integrations](https://agentstack.voostack.com/c/integrations)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [n0madic](https://github.com/n0madic)
- **Source:** https://github.com/n0madic/ssh-mcp

## Install

```sh
agentstack add mcp-n0madic-ssh-mcp
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# SSH MCP Server

A Model Context Protocol (MCP) server that provides AI agents with secure SSH access to remote hosts. Supports connection pooling, key-based and password authentication, sudo, file operations via SFTP, SSH tunnels (local port forwarding), command filtering, rate limiting, output truncation, and graceful shutdown.

## Features

- **SSH Connection Pool** — reuses connections, auto-reconnect on failure, idle cleanup, auto-detection of remote OS and shell
- **Authentication** — explicit `key_path` first, then ssh-agent, then auto-discovered `~/.ssh/id_*` keys (when no agent), then password; automatic `~/.ssh/config` alias resolution
- **Command Execution** — with sudo support, working directory, timeout, graceful kill (SIGTERM → SIGKILL), ANSI stripping
- **SFTP File Operations** — upload/download files and directories, read files with line offset/limit, edit files (replace/patch/create), file info with directory listing, `~` path expansion
- **Interactive PTY Terminals** — buffered PTY sessions for interactive programs (vim, htop, REPL), dialogs, and real-time output (opt-in with `--enable-terminal`)
- **SSH Tunnels** — local port forwarding (localhost:port → remote:port via SSH) for accessing remote services like databases, APIs, and web servers (opt-in with `--enable-tunnels`)
- **Output Truncation** — configurable per-stream output size limit (`--max-output-size`) to prevent LLM context overflow
- **Security** — host/command allowlist/denylist (regex + CIDR), per-host rate limiting, path traversal protection, filename length validation
- **Transports** — stdio (default) and Streamable HTTP (`localhost` only)
- **Graceful Shutdown** — closes all tunnels, SSH connections, and terminal sessions on SIGINT/SIGTERM

## Installation

```bash
go install github.com/n0madic/ssh-mcp@latest
```

Or build from source:

```bash
git clone https://github.com/n0madic/ssh-mcp.git
cd ssh-mcp
go build -o ssh-mcp .
```

## Usage

### Stdio transport (default)

```bash
./ssh-mcp
```

### HTTP transport

```bash
./ssh-mcp --enable-http
# Listens on localhost:8081/mcp
```

### Both transports

```bash
./ssh-mcp --enable-http
# Stdio + HTTP on localhost:8081/mcp
```

### HTTP only (no stdio)

```bash
./ssh-mcp --enable-http --disable-stdio
```

## CLI Flags

| Flag | Env Var | Default | Description |
|------|---------|---------|-------------|
| `--enable-http` | `MCP_SSH_ENABLE_HTTP` | `false` | Enable HTTP transport |
| `--http-port` | `MCP_SSH_HTTP_PORT` | `8081` | HTTP transport port |
| `--disable-stdio` | `MCP_SSH_DISABLE_STDIO` | `false` | Disable stdio transport |
| `--no-verify-host-key` | `MCP_SSH_NO_VERIFY_HOST_KEY` | `false` | Disable host key verification |
| `--known-hosts` | `MCP_SSH_KNOWN_HOSTS` | `~/.ssh/known_hosts` | Path to known_hosts file |
| `--ssh-config` | `MCP_SSH_CONFIG` | `~/.ssh/config` | Path to SSH config file |
| `--enable-sudo` | `MCP_SSH_ENABLE_SUDO` | `false` | Allow sudo execution |
| `--command-timeout` | `MCP_SSH_COMMAND_TIMEOUT` | `60s` | Command execution timeout |
| `--host-allowlist` | `MCP_SSH_HOST_ALLOWLIST` | _(empty)_ | Host allowlist (can be specified multiple times) |
| `--host-denylist` | `MCP_SSH_HOST_DENYLIST` | _(empty)_ | Host denylist (can be specified multiple times) |
| `--command-allowlist` | `MCP_SSH_COMMAND_ALLOWLIST` | _(empty)_ | Command allowlist regex (can be specified multiple times) |
| `--command-denylist` | `MCP_SSH_COMMAND_DENYLIST` | _(empty)_ | Command denylist regex (can be specified multiple times) |
| `--rate-limit` | `MCP_SSH_RATE_LIMIT` | `60` | Rate limit (requests per minute per host) |
| `--rate-limit-file-ops` | `MCP_SSH_RATE_LIMIT_FILE_OPS` | `false` | Apply rate limiting to SFTP file operations |
| `--local-base-dir` | `MCP_SSH_LOCAL_BASE_DIR` | _(empty)_ | Restrict local file operations to this directory |
| `--max-file-size` | `MCP_SSH_MAX_FILE_SIZE` | `0` | Maximum file size for read operations (0=unlimited) |
| `--max-connections` | `MCP_SSH_MAX_CONNECTIONS` | `0` | Maximum concurrent SSH connections (0=unlimited) |
| `--http-token` | `MCP_SSH_HTTP_TOKEN` | _(empty)_ | Bearer token for HTTP transport authentication |
| `--disable-tools` | `MCP_SSH_DISABLE_TOOLS` | _(empty)_ | Disable specific tools (can be specified multiple times) |
| `--enable-terminal` | `MCP_SSH_ENABLE_TERMINAL` | `false` | Allow interactive PTY terminal sessions (`ssh_open_terminal`) |
| `--max-terminals` | `MCP_SSH_MAX_TERMINALS` | `0` | Maximum concurrent PTY terminal sessions (0=unlimited) |
| `--max-output-size` | `MCP_SSH_MAX_OUTPUT_SIZE` | `0` | Maximum output size per stream in bytes for execute/terminal results (0=unlimited) |
| `--enable-tunnels` | `MCP_SSH_ENABLE_TUNNELS` | `false` | Allow SSH tunnel creation (`ssh_tunnel_create`) |
| `--max-tunnels` | `MCP_SSH_MAX_TUNNELS` | `0` | Maximum concurrent SSH tunnels (0=unlimited) |
| `--version` | — | — | Show version and exit |

**Priority:** CLI flags > environment variables > defaults.

### Examples

**Allow only specific hosts (regex patterns):**
```bash
./ssh-mcp --host-allowlist "192.168.1.*" --host-allowlist "10.0.0.*"
```

**Allow hosts by CIDR range:**
```bash
./ssh-mcp --host-allowlist "10.0.0.0/8" --host-allowlist "192.168.0.0/16"
```

**Block dangerous commands (multiple flags):**
```bash
./ssh-mcp --command-denylist "rm\s+-rf.*" --command-denylist "shutdown.*" --command-denylist "reboot.*"
```

> **Note:** Command/host filter patterns are auto-anchored with `^` and `$` for full-string matching. Use `.*` for substring matching (e.g., `rm\s+-rf.*` matches `rm -rf /` but `rm` alone won't match `format`). Host patterns also support CIDR notation (e.g., `10.0.0.0/8`) — CIDR patterns are detected automatically and match by IP range instead of regex.

**Using environment variables (comma-separated):**
```bash
export MCP_SSH_HOST_ALLOWLIST="host1.example.com,host2.example.com,host3.example.com"
export MCP_SSH_COMMAND_DENYLIST="rm\s+-rf.*,shutdown.*,reboot.*"
export MCP_SSH_ENABLE_SUDO=true
./ssh-mcp
```

**Restrict local file operations to a directory:**
```bash
./ssh-mcp --local-base-dir /tmp/ssh-workspace
```

**Enable HTTP transport with bearer token authentication:**
```bash
./ssh-mcp --enable-http --http-token "my-secret-token"
```

**Limit concurrent connections and file size:**
```bash
./ssh-mcp --max-connections 5 --max-file-size 10485760
```

**Limit output size to prevent LLM context overflow:**
```bash
./ssh-mcp --max-output-size 65536
```

**Limit concurrent SSH tunnels:**
```bash
./ssh-mcp --max-tunnels 5
```

**Disable specific tools (multiple flags):**
```bash
./ssh-mcp --disable-tools ssh_execute --disable-tools ssh_edit_file
```

**Disable tools via environment variable:**
```bash
export MCP_SSH_DISABLE_TOOLS="ssh_execute,ssh_edit_file"
./ssh-mcp
```

## MCP Tools

### ssh_connect

Connect to a remote host via SSH.

**Minimal (key auth from ssh-agent or `~/.ssh/id_*`):**
```json
{
  "host": "example.com"
}
```

**Full format — user, password, port inline:**
```json
{
  "host": "admin:secret@example.com:2222"
}
```

**Explicit parameters (override inline values):**
```json
{
  "host": "example.com",
  "port": 2222,
  "user": "admin",
  "key_path": "~/.ssh/id_ed25519"
}
```

**SSH config alias (resolved automatically from `~/.ssh/config`):**
```json
{
  "host": "my-server"
}
```

SSH config aliases are resolved automatically — no extra flags needed. Explicit parameters (port, user, key_path) override values from the config.

Returns `session_id` for use with other tools. Also auto-detects remote OS, architecture, and shell.

### ssh_execute

Execute a command on a remote host. On timeout, sends SIGTERM first (5s grace period) then SIGKILL, and returns partial stdout/stderr with a `[TIMEOUT]` marker in stderr.

```json
{
  "session_id": "admin@example.com:22",
  "command": "ls -la /var/log",
  "timeout": 30,
  "sudo": true,
  "sudo_password": "secret",
  "working_dir": "/home/admin"
}
```

### ssh_disconnect

Disconnect an SSH session.

```json
{
  "session_id": "admin@example.com:22"
}
```

### ssh_list_sessions

List all active SSH sessions with their connection details, statistics, active terminal sessions, and active tunnels (no parameters required).

### ssh_upload

Upload a local file or directory to a remote host via SFTP. Automatically detects whether the local path is a file or directory. Preserves file permissions and directory structure. Supports `~` for remote home directory.

**Upload a file:**
```json
{
  "session_id": "admin@example.com:22",
  "local_path": "/tmp/config.yaml",
  "remote_path": "~/config.yaml"
}
```

**Upload a directory (recursive):**
```json
{
  "session_id": "admin@example.com:22",
  "local_path": "/tmp/myapp",
  "remote_path": "/opt/myapp"
}
```

### ssh_download

Download a file or directory from a remote host via SFTP. Automatically detects whether the remote path is a file or directory. Preserves file permissions and directory structure. Supports `~` for remote home directory.

**Download a file:**
```json
{
  "session_id": "admin@example.com:22",
  "remote_path": "~/app.log",
  "local_path": "/tmp/app.log"
}
```

**Download a directory (recursive):**
```json
{
  "session_id": "admin@example.com:22",
  "remote_path": "/opt/myapp",
  "local_path": "/tmp/myapp-backup"
}
```

### ssh_edit_file

Edit a file on a remote host. Two modes:

**Replace mode** (default) — full content replacement or new file creation:
```json
{
  "session_id": "admin@example.com:22",
  "remote_path": "/etc/myapp/config.yaml",
  "mode": "replace",
  "content": "new file content here",
  "backup": true
}
```

**Patch mode** — find and replace:
```json
{
  "session_id": "admin@example.com:22",
  "remote_path": "/etc/myapp/config.yaml",
  "mode": "patch",
  "old_string": "old_value",
  "new_string": "new_value",
  "backup": true
}
```

### ssh_read_file

Read a file from a remote host with optional line offset and limit. Returns content with line numbers (like `cat -n`). Supports `~` for home directory.

**Read entire file:**
```json
{
  "session_id": "admin@example.com:22",
  "remote_path": "~/app.log"
}
```

**Read with offset and limit (pagination):**
```json
{
  "session_id": "admin@example.com:22",
  "remote_path": "/var/log/syslog",
  "offset": 100,
  "limit": 50
}
```

**Read with max file size limit:**
```json
{
  "session_id": "admin@example.com:22",
  "remote_path": "~/large-file.csv",
  "max_size": 1048576
}
```

Returns file content with line numbers, total line count, file size, and which lines are shown.

---

## Interactive PTY Terminal Tools

These four tools provide buffered PTY access for interactive programs. Requires `--enable-terminal`.

**Typical workflow:**

```
ssh_open_terminal   →  opens shell, returns terminal_id + initial prompt
ssh_send_input      →  write text or keystrokes, get new output
ssh_read_output     →  poll for new output without writing
ssh_close_terminal  →  close the session
```

Active terminals are also listed in `ssh_list_sessions` output.

### ssh_open_terminal

Open a PTY terminal session. Requires `--enable-terminal` flag on the server.

```json
{
  "session_id": "admin@example.com:22",
  "cols": 120,
  "rows": 50,
  "term_type": "xterm-256color",
  "wait_ms": 500,
  "protect_exit": true
}
```

Returns `terminal_id` and the initial shell output (prompt). `cols`/`rows`/`term_type`/`wait_ms`/`protect_exit` are optional.

`protect_exit` (default true) overrides the shell's `exit` command with a no-op function so an accidental `exit` from the agent cannot kill the session. Use `ssh_close_terminal` to terminate the session explicitly. Subshells (sudo, python, ssh into another host) are unaffected. Automatically disabled for Windows hosts.

### ssh_send_input

Send text or a special key to a terminal and read new output.

**Send a command:**
```json
{
  "terminal_id": "term-1",
  "text": "ls -la\n",
  "wait_ms": 300
}
```

**Send a special key:**
```json
{
  "terminal_id": "term-1",
  "special_key": "CTRL_C"
}
```

Supported special keys: `CTRL_C`, `CTRL_D`, `CTRL_Z`, `ESC`, `TAB`, `BACKSPACE`, `ENTER`, `ARROW_UP`, `ARROW_DOWN`, `ARROW_LEFT`, `ARROW_RIGHT`.

Use `\n` for newline and `\r` for carriage return in `text`. Exactly one of `text` or `special_key` must be provided — setting both is an error.

### ssh_read_output

Read buffered output since the last read without writing anything.

```json
{
  "terminal_id": "term-1",
  "wait_ms": 1000,
  "limit": 100
}
```

- `wait_ms` — wait up to N milliseconds for new data (default 0 = return immediately).
- `limit` — maximum number of complete lines to return per call (default 0 = return everything). Remaining lines stay in the buffer for subsequent calls. Response includes `lines` (count returned) and `has_more` (true if more lines are buffered); the text response also appends a marker line when more output is pending.

### ssh_close_terminal

Close a PTY terminal session.

```json
{
  "terminal_id": "term-1"
}
```

---

## SSH Tunnel Tools

These three tools provide local port forwarding through SSH connections. Useful for accessing remote databases, web servers, or APIs that aren't directly reachable. Requires `--enable-tunnels`.

**Typical workflow:**

```
ssh_tunnel_create  →  binds local port, forwards to remote address
ssh_tunnel_list    →  show active tunnels
ssh_tunnel_close   →  stop forwarding and release the port
```

Active tunnels are also listed in `ssh_list_sessions` output. Tunnels are automatically closed when the parent SSH session is disconnected.

### ssh_tunnel_create

Create a local port forwarding tunnel.

**Auto-assign local port:**
```json
{
  "session_id": "admin@example.com:22",
  "remote_addr": "localhost:5432",
  "local_port": 0
}
```

**Specific local port:**
```json
{
  "session_id": "admin@example.com:22",
  "remote_addr": "10.0.0.5:3306",
  "local_port": 13306
}
```

Returns `tunnel_id`, the bound local address and port, and the remote address.

### ssh_tunnel_list

List all active tunnels. Optionally filter by session ID.

```json
{
  "session_id": "admin@example.com:22"
}
```

### ssh_tunnel_close

Close an active tunnel.

```json
{
  "tunnel_id": "admin@example.com:22-1"
}
```

---

## Codex Configuration

Add to Codex using the CLI:

```bash
codex mcp add ssh \
  --env SSH_AUTH_SOCK="$SSH_AUTH_SOCK" \
  -- /path/to/ssh-mcp --enable-terminal
```

Verify the saved configuration:

```bash
codex mcp get ssh
```

For passphrase-protected SSH keys, `ssh-mcp` must be able to use your
ssh-agent. It does not prompt for private-key passphrases itself. If Codex starts
`ssh-mcp` without a working `SSH_AUTH_SOCK`, `ssh_connect` can fail before any
network connection attempt with:

```text
connect failed: auth config: no authentication methods available
```

Before adding the MCP server, make sure the key is loaded:

```bash
ssh-add -l
```

On macOS, load the key into the agent and store the passphrase in Keychain:

```bash
/usr/bin/ssh-add --apple-use-keychain ~/.ssh/id_rsa
```

If `SSH_AUTH_SOCK` changes after reboot or after restarting your terminal, update
the Codex MCP entry by running `codex mcp add` again with the current value.
Existing Codex sessions may keep an already-started `ssh-mcp` process with the
old environment; restart Codex or remove the stale `ssh-mcp` process after
changing the config.

Manual equivalent in `~/.codex/config.toml`:

```toml
[mcp_servers.ssh]
command = "/path/to/ssh-mcp"
args = ["--enable-terminal"]

[mcp_servers.ssh.env]
SSH_AUTH_SOCK = "/var/run/com.apple.launchd.XYZ/Listeners"
```

## Claude Code Configuration

Add to Claude Code using the CLI:

```bash
claude mcp add --transport stdio --scope user ssh -- /path/to/ssh-mcp --no-verify-host-key
```

Or manually edit `~/.claude.json`:

```json
{
  "mcpServers": {
    "ssh": {
      "type": "stdio",
      "command": "/path/to/ssh-mcp",
      "args": ["--no-verify-host-key"]
    }
  }
}
```

## Claude Desktop Configuration

Add to your Claude Desktop config (`~/Library

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [n0madic](https://github.com/n0madic)
- **Source:** [n0madic/ssh-mcp](https://github.com/n0madic/ssh-mcp)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: flagged — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-n0madic-ssh-mcp
- Seller: https://agentstack.voostack.com/s/n0madic
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
