SSH_CONFIG_PATH — Path to a TOML file holding the server definitions. Defaults to ~/.codex/ssh-config.toml. Each server can set MODE to readonly or restricted to constrain what the agent may run.
SSH_ENV_PATH — Path to a .env file declaring servers as SSH_SERVER_<NAME>_HOST, _USER, _KEYPATH and friends. Defaults to a .env next to the package.
Claude Code ไม่มีลิงก์ติดตั้งสำหรับคำสั่งแบบโลคัล ให้คัดลอกคำสั่งด้านล่างแล้วรันในเทอร์มินัล
MCP SSH Manager - SSH Remote Server Management via Model Context Protocol 🚀
A Model Context Protocol (MCP) server that enables Claude Code and OpenAI Codex to manage multiple SSH connections. Execute commands, transfer files, manage databases, create backups, monitor health, and automate DevOps tasks across your servers — directly from your AI assistant.
MCP server for SSH: let Claude Code, Codex and any MCP client run commands, transfer files and manage servers over SSH, with human approval, an encrypted vault and a live audit trail. Desktop app and CLI included.
API key
SSH_CONFIG_PATH, SSH_ENV_PATH
ตรวจสอบล่าสุด
2026-11-13
ดาว
543
เครื่องมือ
—
อัปไทม์
—
🎉 What's New in v3.8.6
🔒 Security release — six advisories fixed. Upgrade. (Released: October 8, 2026)
🔴 Command injection on your own machine through the default on-error hook (GHSA-759m-wfpq-xmx3) — the connection error, whose text a server chooses, was pasted into a shell command. Any SSH server you connected to could run a command on the machine running mcp-ssh-manager. Hook values now reach commands as shell variables, never as command text, and the default hook writes its log without a shell.
🔴 restricted mode only checked how a line started (GHSA-rfxw-26h6-7w42) — ^echo let echo hi; id through. Every command in a list or pipeline must now match your allowlist; command substitution and writes to files are refused.
🔴 cwd reached the shell unquoted (GHSA-37fv-fcpc-j236), past the security policy.
🔴 ssh_db_query could write (GHSA-9w6j-vg8f-hp8g, GHSA-q37w-vhpx-q5q9) — SELECT … INTO OUTFILE, SELECT … INTO a new table, and JavaScript in MongoDB queries. Queries now run in a read-only transaction, and a MongoDB query must be a filter document.
🟠 Host keys were never compared (GHSA-cwg3-pfmm-w8rm) — a changed host key is now refused, as OpenSSH does. A server reinstalled since its key was recorded needs ssh-keygen -R <host> once.
ssh_deploy and ssh_backup_schedule quote every value, and a cron schedule must be a schedule.
Hook settings, aliases, the active profile and the logs now live in ~/.ssh-manager/, readable by you alone, instead of inside the installed package, where every upgrade lost them.
An MCP SSH server is the most dangerous tool you can hand an AI agent: a shell on
machines that matter. This one is built so you decide how far the agent can go —
per server, not globally.
Mode
What the agent can do
unrestricted(default)
Everything. Same behaviour as any other SSH MCP server.
readonly
Mutating tools are refused outright — no deploy, no upload, no sudo, no database import. Read commands still work.
restricted
Every command must match an allow pattern and no deny pattern. Anything else is refused before it reaches the host.
The sudo password never reaches the remote command line. It travels on the
SSH channel's stdin, so it is not visible in ps, in /proc/<pid>/cmdline, or
in an auditd trail — unlike the echo "$pass" | sudo -S pattern common in
this category (#34).
Every database argument is shell-quoted through one centralised helper,
guarded by a 648-combination injection test.
Read-only SQL is enforced, not suggested: ssh_db_query refuses anything
that is not a SELECT.
Vulnerabilities are published, not buried. See SECURITY.md
for the reporting process and the advisories already fixed.
Reproducible installs: the lockfile is committed, CI installs with
npm ci, and a test enforces that every dependency resolves to
registry.npmjs.org with an integrity hash and no unreviewed install scripts.
Previous Releases
v3.8.4 - Secrets stop reaching the log, CodeQL on every commit (August 28, 2026)
🔒 The logger no longer writes secrets in clear text — it writes to ~/.ssh-manager.log and stderr (which your MCP host captures), so one call site handing it a server config would have persisted a production password. Redaction now happens inside the logger. Also: CodeQL on every push, every action pinned by SHA, and a broken example that never parsed. Full changelog →
🔒 The sudo password no longer reaches the remote command line (#34) — it travelled through echo "<password>" | sudo -S, readable in ps and /proc/<pid>/cmdline by every account on the host. It now goes over the SSH channel's stdin. Also: listed in the official MCP Registry as io.github.bvisible/mcp-ssh-manager, releases published from CI with SLSA provenance and a CycloneDX SBOM, and the per-server security modes documented at last. Full changelog →
Committed lockfile (#60 — contributed by @cudatuda) plus npm run test:lockfile guarding it against drift and tampering. CI installs with npm ci; ESLint and the JSDoc typecheck became blocking gates after being purely decorative. Full changelog →
v3.8.0 - Groups in your config, ssh_sync on Windows, tunnel crash fix (August 14, 2026)
👥 New optional group field per server (#56 — contributed by @ice616, requested in #55) — tag a server with group = "production" and it is in that group: ssh_execute_group resolves members straight from your .env/TOML, union'd with any .server-groups.json you already keep. Also: ssh_sync fixed from a Windows host (#59 — contributed by @2836603852), a tunnel on a busy port no longer takes the whole MCP server down, the @modelcontextprotocol/sdk floor raised to ^1.30.0 over three advisories, and JSDoc type-checking added to CI. Full changelog →
🔗 New opt-in FORWARD_AGENT / forward_agent option (#53 — requested by @raphaelbahat in #52) — the equivalent of OpenSSH's ForwardAgent yes, per server: processes on the remote host authenticate to other SSH hosts with the keys in your local ssh-agent, without copying any private key. Requires a running agent and defaults to false. Full changelog →
v3.6.7 - Security: command injection fix in the database helpers (July 11, 2026)
🔒 Every ssh_db_* argument is now shell-quoted (#51 — responsibly disclosed by Ugur Ozer, Aeon AI Risk Management (http://airiskmanagement.ca), see #48) — caller-controlled values (ssh_db_list most notably, which stayed allowed in readonly/restricted modes) were interpolated into shell-evaluated strings, allowing arbitrary command execution on the SSH target. A centralized shellQuote() now wraps every value across all 15 builders, guarded by a 648-combination injection test. Full changelog →
v3.6.6 - SUDO_PASSWORD / DEFAULT_DIR / ssh_sync key auth work again (July 11, 2026)
🔑 camelCase config field reads (#50 — thanks @egoan82) — since the v3.0.0 ConfigLoader refactor, ssh_execute_sudo ignored SUDO_PASSWORD, DEFAULT_DIR was ignored by ssh_execute/ssh_group_execute/ssh_list_servers, and ssh_sync never passed the configured SSH key to rsync. All aligned with the loader's camelCase fields, with a regression test locking the loader output shape. Full changelog →
🔒 Queries are delivered on stdin via a single-quoted heredoc (#44, #45 — thanks @technophile77) — the remote shell no longer parses backticks/$(…) inside queries (which corrupted backtick identifiers and let the "SELECT-only" tool run arbitrary shell commands), and row_count now reflects each engine's real output instead of counting wrapper lines. Full changelog →
🧹 Dead-code removal (−343 lines), zero behavioral change — removed 27 unused exports and 2 duplicate exports; the MCP server and CLI behave identically (command builders/parsers byte-identical, all 37 tools verified end-to-end). A calibrated knip.json plus a blockingknip CI step keep unused code from creeping back. Full changelog →
v3.6.3 - ssh_sync reports the real transfer count (June 18, 2026)
📊 No more false "No files needed to be transferred" (#42 — thanks @MakksSh) — fixed rsync --stats parsing: --stats is always passed now, and rsync 2.x/3.x wording, openrsync's B suffix, and locale separators are all handled. Full changelog →
v3.6.2 - Richer tool descriptions (June 9, 2026)
📝 All 37 tool descriptions rewritten — every MCP tool now documents its real behavior (side effects, destructive vs read-only nature, idempotency, sudo/auth requirements, security-mode gating, parameter semantics) instead of a 4-to-10-word summary. Agents now know the consequences before invoking a tool; no behavioral change — only description strings changed. Full changelog →
🔌 Module-level timers no longer pin the event loop (follow-up to #41) — tunnel-manager.js and session-manager.js registered module-level setIntervals that were never unref()'d, so importing either module kept Node's event loop alive. Both are now unref()'d. Full changelog →
v3.6.0 - Live config hot reload + stdio lifecycle fix (June 9, 2026)
♻️ Configuration hot reload (#40 — thanks @EnjoySR) — add or edit a server in your .env/TOML and the running MCP server picks it up on the next call, no restart. A ServerConfigManager reloads lazily on file-signature change (path + mtime + size); a failed reload keeps the last known-good config; real process.env vars keep top priority. No watcher, no polling.
🔌 No more orphaned stdio processes (#41 — thanks @LegendaryGatz) — a stdio MCP server is torn down by stdin EOF / SIGTERM, not SIGINT; with only a SIGINT handler every session leaked a ~83 MB node process. Shutdown is now idempotent across SIGINT/SIGTERM/SIGHUP/stdin-close, timers are unref()'d, and the process exits ~10 ms after teardown instead of never. Full changelog →
🪟 Healthy Windows sessions no longer reported as Dead (#39 — thanks @username77) — the liveness probe ran echo "ping" and cmd.exe echoed the quotes literally, failing a strict === 'ping' check and needlessly rebuilding live connections. Now uses echo ping parsed by a null-safe isPingAlive(stdout) helper (CRLF/quote/case-normalized), covered by tests/test-ssh-ping.js. Full changelog →
A second authorization layer that filters tool invocations inside the MCP server, complementing the existing client-side autoApprove. Useful when sharing the MCP with a third-party agent, a CI bot, or any client where ssh_execute shouldn't be unconditionally trusted.
🔒 Three modes, opt-in per server (no MODE field = identical to v3.4.x):
unrestricted (default) — strict no-op. evaluatePolicy() early-returns on the first line, zero overhead.
restricted — every command must match at least one ALLOW_PATTERNS regex AND no DENY_PATTERNS regex. DENY wins. With no ALLOW_PATTERNS everything is refused (fail-closed).
📝 Audit log — opt-in JSONL per server (SSH_SERVER_<N>_AUDIT_LOG=/path/to/audit.jsonl). Records ts, server, tool, args, allowed, reason on denial, exitCode/success on execution. Sensitive arg fields (password, passphrase, sudoPassword, token, secret, apikey) are replaced with ***.
🪄 Command aliases expanded BEFORE policy evaluation — a DENY pattern can't be bypassed via an alias.
♻️ Backward-compatible by design — a v3.4.x .env or TOML loads identically. No MODE field → zero behavior change. The interactive wizard (ssh-manager server add) defaults all three new prompts to skip. All 13 pre-existing tests pass unmodified. New tests/test-policy.js adds 26 tests covering modes, DENY > ALLOW precedence, invalid-regex handling, redaction, and the backward-compat fast path. Full reference →
v3.4.1 - Modern OpenSSH 9.x compatibility (May 16, 2026)
🔐 Expanded SSH algorithm list — handshake against OpenSSH 9.x out of the box (#32)
sshpass (optional, for rsync with password authentication)
macOS: brew install hudochenkov/sshpass/sshpass
Linux: apt-get install sshpass
🚀 Quick Start - Claude Code
1. Install MCP SSH Manager
Option A: Install from npm (recommended)
# Install globally from npm
npm install -g mcp-ssh-manager
# Or install locally
npx mcp-ssh-manager
Option B: Install from source
# Clone and install
git clone https://github.com/bvisible/mcp-ssh-manager.git
cd mcp-ssh-manager
npm install
# Install the Bash CLI
cd cli && ./install.sh
# Configure your first server
ssh-manager server add
2. Install to Claude Code
# For personal use (current user only)
claude mcp add ssh-manager node /path/to/mcp-ssh-manager/src/index.js
# For team sharing (creates .mcp.json in project)
claude mcp add ssh-manager --scope project node /path/to/mcp-ssh-manager/src/index.js
# For all your projects
claude mcp add ssh-manager --scope user node /path/to/mcp-ssh-manager/src/index.js
3. Configure Auto-Approval (Optional but Recommended)
To avoid being prompted for approval on every SSH command, add auto-approve configuration:
autoApprove is all-or-nothing per tool: once ssh_execute is approved, anything goes. If you want a second layer that filters what the MCP server actually accepts to run — useful when sharing the MCP with a third-party agent, a CI bot, or a client's server — declare a per-server security mode.
# In your .env — three optional fields. Omit them all to keep v3.4.x behavior exactly.
SSH_SERVER_CLIENT_PROD_HOST=client-prod.example.com
SSH_SERVER_CLIENT_PROD_USER=consultant
SSH_SERVER_CLIENT_PROD_KEYPATH=~/.ssh/consultant_ed25519
SSH_SERVER_CLIENT_PROD_MODE=readonly # unrestricted | readonly | restricted
SSH_SERVER_CLIENT_PROD_AUDIT_LOG=~/.ssh-manager/audit.jsonl # opt-in JSONL audit trail
# For mode=restricted, provide an allowlist of regex (DENY wins over ALLOW):
# SSH_SERVER_CI_ALLOW_PATTERNS="^docker (ps|logs);^kubectl get "
unrestricted (default, no field needed) — identical to pre-v3.5.0 behavior. Zero overhead.
restricted — every ssh_execute command must match at least one ALLOW_PATTERNS regex AND no DENY_PATTERNS regex.
Existing configs are unaffected — no field is mandatory, no behavior changes unless you opt in. See docs/SECURITY_MODES.md for the full reference, recipes, and limitations.
4. Start Using!
In Claude Code, you can now:
"List all my SSH servers"
"Execute 'ls -la' on production server" # Uses default directory if set
"Run 'docker ps' on staging"
"Upload config.json to production:/etc/app/config.json"
"Download logs from staging:/var/log/app.log"
With Default Directories:
If you set /var/www/html as default for production, these commands are equivalent:
"Run 'ls' on production" → executes in /var/www/html
"Run 'ls' on production in /tmp" → executes in /tmp (overrides default)
🚀 Quick Start - OpenAI Codex
1. Install MCP SSH Manager
Same installation as Claude Code (see above), then configure for Codex:
# Set up Codex integration
ssh-manager codex setup
# Migrate existing servers to TOML format (if you have .env servers)
ssh-manager codex migrate
# Test the integration
ssh-manager codex test
2. Manual Configuration (Optional)
If you prefer manual setup, add to ~/.codex/config.toml:
"List my SSH servers"
"Execute 'docker ps' on production"
"Upload file.txt to staging:/tmp/"
"Monitor CPU usage on all servers"
"Download production:/var/log/app.log to ./logs/"
Converting Between Formats
Switch easily between Claude Code (.env) and Codex (TOML):
# Convert .env to TOML (for Codex)
ssh-manager codex convert to-toml
# Convert TOML back to .env (for Claude Code)
ssh-manager codex convert to-env
Both formats can coexist! The system supports both simultaneously.
🛠️ Available MCP Tools
Core Tools
ssh_list_servers
Lists all configured SSH servers with their details.
ssh_execute
Execute commands on remote servers.
Parameters: server (name), command, cwd (optional working directory)
Note: If no cwd is provided, uses the server's default directory if configured
ssh_upload
Upload files to remote servers.
Parameters: server, local_path, remote_path
ssh_download
Download files from remote servers.
Parameters: server, remote_path, local_path
Backup & Restore Tools (v2.1+) 🔄
ssh_backup_create
Create backup of database or files on remote server.
Configuration file: ~/.ssh-manager/profile holds the profile name (switching from your assistant writes it)
Default: Uses default profile if not specified
Available profiles:
default - Basic SSH operations
frappe - Frappe/ERPNext specific
docker - Docker container management
nodejs - Node.js applications
Create custom profiles in profiles/ directory
Environment Variables
Servers are configured in the .env file with this pattern:
# Server configuration pattern
SSH_SERVER_[NAME]_HOST=hostname_or_ip
SSH_SERVER_[NAME]_USER=username
SSH_SERVER_[NAME]_PASSWORD=password # For password auth
SSH_SERVER_[NAME]_KEYPATH=~/.ssh/key # For SSH key auth
SSH_SERVER_[NAME]_PASSPHRASE=key_passphrase # Optional, for passphrase-protected keys
SSH_SERVER_[NAME]_PORT=22 # Optional, defaults to 22
SSH_SERVER_[NAME]_DEFAULT_DIR=/path/to/dir # Optional, default working directory
SSH_SERVER_[NAME]_DESCRIPTION=Description # Optional
SSH_SERVER_[NAME]_GROUP=production # Optional, free-form label for grouping/import-export
SSH_SERVER_[NAME]_PLATFORM=windows # Optional: "linux" (default) or "windows"
SSH_SERVER_[NAME]_PROXYJUMP=bastion # Optional: name of another server to use as jump host
SSH_SERVER_[NAME]_PROXYCOMMAND=command # Optional: custom proxy command (ncat, ssh -W, etc.)
SSH_SERVER_[NAME]_FORWARD_AGENT=true # Optional: forward local ssh-agent to remote (needs SSH_AUTH_SOCK; security risk — see SSH Agent section)
# Example: Linux server
SSH_SERVER_PRODUCTION_HOST=prod.example.com
SSH_SERVER_PRODUCTION_USER=admin
SSH_SERVER_PRODUCTION_PASSWORD=secure_password
SSH_SERVER_PRODUCTION_PORT=22
SSH_SERVER_PRODUCTION_DEFAULT_DIR=/var/www/html
SSH_SERVER_PRODUCTION_DESCRIPTION=Production Server
SSH_SERVER_PRODUCTION_SUDO_PASSWORD=secure_sudo_pass # Optional, for automated deployments
# Example: Windows server (OpenSSH for Windows)
SSH_SERVER_WINHOST_HOST=192.168.1.90
SSH_SERVER_WINHOST_USER=svc-ssh
SSH_SERVER_WINHOST_KEYPATH=~/.ssh/winhost_key
SSH_SERVER_WINHOST_PORT=2222
SSH_SERVER_WINHOST_PLATFORM=windows
SSH_SERVER_WINHOST_DESCRIPTION=Windows host via OpenSSH
# Example: Server behind a bastion/jump host
SSH_SERVER_BASTION_HOST=bastion.example.com
SSH_SERVER_BASTION_USER=jumpuser
SSH_SERVER_BASTION_KEYPATH=~/.ssh/bastion_key
SSH_SERVER_INTERNAL_HOST=10.0.0.5
SSH_SERVER_INTERNAL_USER=admin
SSH_SERVER_INTERNAL_KEYPATH=~/.ssh/internal_key
SSH_SERVER_INTERNAL_PROXYJUMP=bastion
SSH_SERVER_INTERNAL_DESCRIPTION=Private server behind bastion
Server Management Tool
The Python management tool (tools/server_manager.py) provides:
Never commit .env files - Always use .env.example as template
Use SSH keys when possible - More secure than passwords
Limit server access - Use minimal required permissions
Rotate credentials - Update passwords and keys regularly
🔑 Passphrase-Protected SSH Keys
MCP SSH Manager supports passphrase-protected SSH keys in two ways:
Option 1: SSH Agent (recommended)
If your SSH key is loaded into ssh-agent, MCP SSH Manager will use it automatically — no configuration changes needed:
# Add your key to the agent (enter passphrase once)
ssh-add ~/.ssh/your_key
# Verify the key is loaded
ssh-add -l
The server detects the SSH_AUTH_SOCK environment variable and connects to the running agent. This is the same mechanism that regular ssh uses for GUI passphrase prompts.
Option 2: Passphrase in configuration
You can store the passphrase directly in the server config:
Note: SSH Agent is preferred over storing passphrases in config files for better security.
🔗 SSH Agent Forwarding
Enable per-server agent forwarding (the equivalent of OpenSSH's ForwardAgent yes) so processes on the remote host can authenticate to other SSH hosts using the keys in your localssh-agent — e.g. git clone over SSH on a remote server using your local GitHub key, without copying any private key to the server.
It is opt-in per server and defaults to false. It requires a running local agent (SSH_AUTH_SOCK present); when the agent is unavailable the flag is simply ignored.
.env format:
SSH_SERVER_MYSERVER_FORWARD_AGENT=true
TOML format:
[ssh_servers.myserver]
forward_agent = true
⚠️ Security warning: agent forwarding lets any process that can read the forwarded agent socket on the remote host — including anyone with root there — use your loaded keys to impersonate you against other hosts for the life of the connection. Only enable it for servers you trust, mirroring the same caution ssh_config(5) advises for ForwardAgent.
📚 Advanced Usage
ProxyJump / Bastion Host
Connect to servers behind a bastion or jump host. The connection is tunneled through the jump server transparently — all tools (execute, upload, download, sync) work as usual.
# Define the bastion server
SSH_SERVER_BASTION_HOST=bastion.example.com
SSH_SERVER_BASTION_USER=jumpuser
SSH_SERVER_BASTION_KEYPATH=~/.ssh/bastion_key
# Point the target server to the bastion
SSH_SERVER_PRIVATE_HOST=10.0.0.5
SSH_SERVER_PRIVATE_USER=admin
SSH_SERVER_PRIVATE_PROXYJUMP=bastion
Or in TOML:
[ssh_servers.bastion]
host = "bastion.example.com"
user = "jumpuser"
key_path = "~/.ssh/bastion_key"
[ssh_servers.private]
host = "10.0.0.5"
user = "admin"
proxy_jump = "bastion"
Chained jumps are supported: if bastion itself has a proxy_jump, the chain is followed recursively. Circular references are detected and rejected.
ProxyCommand / Custom Proxy
Connect through SOCKS5 proxies or custom proxy commands. The proxy command executes locally and forwards traffic to the remote host.
The proxy command must be a valid command that reads from stdin and writes to stdout, accepting %h and %p placeholders for host and port.
Server Groups
Tag a server with group and it becomes part of that group — no extra file to maintain. The label is free-form and travels with the server definition, so it survives an export to (or import from) another tool.
[ssh_servers.web1]
host = "10.0.0.1"
user = "deploy"
group = "production"
Both servers are then reachable as a group:
Run "uptime" on the production group → ssh_execute_group
List my server groups → ssh_group_manage (action: list)
ssh_list_servers also reports the group of each server, so you can see membership without opening the config.
How it combines with ssh_group_manage: groups you create with ssh_group_manage live in .server-groups.json and carry execution settings (strategy, delay, stop-on-error). Groups implied by the group field carry membership only. When a name exists on both sides, membership is the union — the stored list plus every server tagged with that name — and the stored execution settings apply. Group names are case-insensitive.
A group that exists only through the group field is read-only for ssh_group_manage: to change who belongs to it, edit the servers' group in your .env/TOML. Creating a group of the same name with ssh_group_manage is still allowed and simply adds stored members and settings on top.
Ensure user has necessary permissions on remote server
📚 Usage Examples
Backup & Restore
"Backup production MySQL database before deployment"
"List all backups on production server"
"Restore backup from yesterday"
"Schedule daily database backup at 2 AM"
"Backup website files excluding cache and logs"
The timeout parameter for SSH commands is advisory only
Due to SSH2 library limitations, commands may continue running on the server even after timeout
On Linux/macOS hosts, a system timeout wrapper is used for reliable command termination
Windows hosts: Set PLATFORM=windows in your server config to skip the Linux timeout/sh -c wrapper (which is incompatible with Windows OpenSSH)
SSH Sync (rsync)
Password authentication requires sshpass to be installed
SSH key authentication is recommended for better security and reliability
Windows MCP hosts: pass native local paths such as local:C:\project or local:.\project; ssh_sync converts drive-letter and UNC paths to MSYS2 format before launching rsync. Prefer native paths, since Node checks them on disk using Windows path semantics — a path already written as /c/... or //server/share is passed through to rsync untouched rather than converted twice.
Large file transfers may take time and appear to hang - be patient
Connection Management
Connections are pooled and reused for performance
If a connection becomes stale, it will be automatically reconnected on next use
Force reconnection by using the ssh_connection_status tool with reconnect action
MCP server for SSH: let Claude Code, Codex and any MCP client run commands, transfer files and manage servers over SSH, with human approval, an encrypted vault and a live audit trail. Desktop app and
MCP server for SSH: let Claude Code, Codex and any MCP client run commands, transfer files and manage servers over SSH, with human approval, an encrypted vault and a live audit trail. Desktop app and CLI included.
ตรวจสอบล่าสุดเมื่อ 2026-11-13 MCP server for SSH: let Claude Code, Codex and any MCP client run commands, transfer files and manage servers over SSH, with human approval, an encrypted vault and a live audit trail. Desktop app and CLI included.