ssh-mcp

command module
v0.0.0-...-f1d5e3d Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 30, 2026 License: MIT Imports: 7 Imported by: 0

README

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

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

Or build from source:

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

Usage

Stdio transport (default)
./ssh-mcp
HTTP transport
./ssh-mcp --enable-http
# Listens on localhost:8081/mcp
Both transports
./ssh-mcp --enable-http
# Stdio + HTTP on localhost:8081/mcp
HTTP only (no stdio)
./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):

./ssh-mcp --host-allowlist "192.168.1.*" --host-allowlist "10.0.0.*"

Allow hosts by CIDR range:

./ssh-mcp --host-allowlist "10.0.0.0/8" --host-allowlist "192.168.0.0/16"

Block dangerous commands (multiple flags):

./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):

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:

./ssh-mcp --local-base-dir /tmp/ssh-workspace

Enable HTTP transport with bearer token authentication:

./ssh-mcp --enable-http --http-token "my-secret-token"

Limit concurrent connections and file size:

./ssh-mcp --max-connections 5 --max-file-size 10485760

Limit output size to prevent LLM context overflow:

./ssh-mcp --max-output-size 65536

Limit concurrent SSH tunnels:

./ssh-mcp --max-tunnels 5

Disable specific tools (multiple flags):

./ssh-mcp --disable-tools ssh_execute --disable-tools ssh_edit_file

Disable tools via environment variable:

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_*):

{
  "host": "example.com"
}

Full format — user, password, port inline:

{
  "host": "admin:secret@example.com:2222"
}

Explicit parameters (override inline values):

{
  "host": "example.com",
  "port": 2222,
  "user": "admin",
  "key_path": "~/.ssh/id_ed25519"
}

SSH config alias (resolved automatically from ~/.ssh/config):

{
  "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.

{
  "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.

{
  "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:

{
  "session_id": "admin@example.com:22",
  "local_path": "/tmp/config.yaml",
  "remote_path": "~/config.yaml"
}

Upload a directory (recursive):

{
  "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:

{
  "session_id": "admin@example.com:22",
  "remote_path": "~/app.log",
  "local_path": "/tmp/app.log"
}

Download a directory (recursive):

{
  "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:

{
  "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:

{
  "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:

{
  "session_id": "admin@example.com:22",
  "remote_path": "~/app.log"
}

Read with offset and limit (pagination):

{
  "session_id": "admin@example.com:22",
  "remote_path": "/var/log/syslog",
  "offset": 100,
  "limit": 50
}

Read with max file size limit:

{
  "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.

{
  "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:

{
  "terminal_id": "term-1",
  "text": "ls -la\n",
  "wait_ms": 300
}

Send a special key:

{
  "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.

{
  "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.

{
  "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:

{
  "session_id": "admin@example.com:22",
  "remote_addr": "localhost:5432",
  "local_port": 0
}

Specific local port:

{
  "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.

{
  "session_id": "admin@example.com:22"
}
ssh_tunnel_close

Close an active tunnel.

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

Codex Configuration

Add to Codex using the CLI:

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

Verify the saved configuration:

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:

connect failed: auth config: no authentication methods available

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

ssh-add -l

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

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

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

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

Or manually edit ~/.claude.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/Application Support/Claude/claude_desktop_config.json):

Stdio transport
{
  "mcpServers": {
    "ssh": {
      "command": "/path/to/ssh-mcp",
      "args": ["--enable-sudo", "--no-verify-host-key"]
    }
  }
}
HTTP transport

Start the server manually:

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

Then configure Claude Desktop to use the HTTP endpoint at http://localhost:8081/mcp.

Security

  • HTTP transport is localhost-only — the HTTP server binds to localhost (hardcoded, not configurable)
  • HTTP authentication — optional bearer token authentication for HTTP transport (--http-token); constant-time comparison
  • HTTP server hardeningReadHeaderTimeout and IdleTimeout set to prevent slowloris-style attacks
  • Host key verification — enabled by default using ~/.ssh/known_hosts; fails with a clear error if the file is missing (no silent downgrade to insecure mode)
  • Sudo disabled by default — must be explicitly enabled with --enable-sudo
  • Interactive terminals disabled by default — PTY sessions bypass the command filter; must be explicitly enabled with --enable-terminal
  • SSH tunnels disabled by default — tunnel creation must be explicitly enabled with --enable-tunnels
  • Host filtering — allowlist/denylist with regex and CIDR support; denylist takes priority; regex patterns are auto-anchored for full-string matching; CIDR patterns (e.g., 10.0.0.0/8) match by IP range; case-insensitive host matching
  • Command filtering — allowlist/denylist with regex support; denylist takes priority; patterns are auto-anchored; filter runs on the original command (before cd/sudo prepend); error messages do not expose filter patterns
  • Local path restriction--local-base-dir restricts all local file operations (upload/download) to a specific directory
  • Path traversal protection — rejects paths with .. path segments or null bytes (both local and remote); segment-based check allows names like foo..bar
  • Filename validation — rejects filenames longer than 255 characters, containing control characters (including DEL and Unicode Cc), or path separators
  • Rate limiting — per-host token bucket rate limiter with automatic stale entry cleanup; optionally applies to SFTP file operations (--rate-limit-file-ops)
  • Connection pool limits--max-connections caps the number of concurrent SSH connections
  • File size limits--max-file-size caps remote file read operations to prevent memory exhaustion
  • Output truncation--max-output-size limits per-stream output size in execute and terminal tools to prevent LLM context overflow; UTF-8-safe truncation avoids splitting multi-byte characters
  • Tunnel pool limits--max-tunnels caps the number of concurrent SSH tunnels
  • No credential persistence — passwords are not stored in the connection pool; only the SSH client config (with key-based auth methods) is retained for auto-reconnect
  • Remote path expansion~ expands to user's home directory on remote server

Development

# Build
go build ./...

# Run unit tests
go test ./internal/...

# Run E2E tests (requires Docker)
go test -v -timeout 120s ./e2e/...

# Run all tests
go test ./...

# Vet
go vet ./...

License

MIT License. See LICENSE for details.

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL