An SSH MCP server written in Rust: manage authentication credentials securely from the command line, then use MCP tools to run remote commands, interactive shells and SFTP file transfers against multiple Linux hosts. SSH credentials and the master password never reach the MCP client/agent.
中文文档见 README.zh-CN.md。
Prebuilt binaries for Windows and Linux (x86_64) are attached to every release. Grab the latest from the latest release:
ssh-mcp-windows-x86_64.exe— Windowsssh-mcp-linux-x86_64— Linux (built on Linux)
# Windows
ssh-mcp-windows-x86_64.exe --version
# Linux
chmod +x ssh-mcp-linux-x86_64
./ssh-mcp-linux-x86_64 --versionEach release includes SHA-256 checksums in its notes. Build from source if you need another platform or architecture.
- Credentials are encrypted with AES-256-GCM (key derived from a master password via Argon2id); credentials can only be added via the CLI, never through MCP
- SSH sensitive information (passwords, master password) is completely isolated from the agent: it lives in a local encrypted vault managed exclusively through the CLI; MCP clients/agents only ever see aliases, session IDs and non-sensitive metadata (username/host/port) and can neither read nor modify credentials
- Multiple concurrent sessions per host, each with a unique
session_id - Dual MCP transports: stdio (default) and Streamable HTTP (actix-web), can run simultaneously
- Remote command execution, interactive PTY shell, SFTP upload/download
- Transparent sudo support: every
ssh_execcommand runs on a PTY and password prompts are answered automatically from the vault, so the password is never exposed to the MCP client or logs - Host key TOFU verification (fingerprint recorded on first connect, rejected on mismatch afterwards)
- Built-in command safety guard: catastrophic commands (e.g.
rm -rf /) are hard-blocked; high-risk commands require human approval viassh-mcp pending/approve/deny - File logging for every operation, with process ID and automatic 10 MB rotation
- Optional HTTP bearer-token authentication (
SSH_MCP_HTTP_TOKEN) - Ready-made integration configs for Codex / Claude Code / Cursor / VS Code / Claude Desktop / Cherry Studio
cargo build --releaseThe binary is produced at target/release/ssh-mcp(.exe).
The credential vault is encrypted with a master password. Provide it via the SSH_MCP_MASTER_PASSWORD environment variable. If unset, it is prompted interactively only when stdin is a terminal; otherwise the process exits with an error (MCP stdio mode always requires the environment variable). An empty value is treated as an error.
ssh-mcp is a single binary. Run ssh-mcp --help and ssh-mcp <command> --help for the built-in help.
| Option | Description |
|---|---|
-h, --help |
Print help |
-V, --version |
Print version |
--data-dir <path> |
Data directory (default ~/.ssh-mcp, %USERPROFILE%\.ssh-mcp on Windows). Valid for every subcommand and may be placed before or after the subcommand. Must point to the same directory as the running server for pending / approve / deny to interact with it. |
| Command | Arguments | Description |
|---|---|---|
add |
<alias> <user@host> <password> [--sudo-password <password>] [--port <port>] |
Add a credential. <alias> is a unique name used by MCP later. <user@host> must be exactly user@host. --sudo-password optionally sets a password used to answer sudo prompts (falls back to the login password). --port defaults to 22. |
list |
— | List stored credentials as alias user@host:port. Passwords are never shown. |
remove |
<alias> |
Remove a credential. |
pending |
— | List commands waiting for human approval (ID, alias, creation time, seconds until expiry) and print the current policy. |
approve |
<id> |
Approve a pending command so the next identical retry is allowed. Requires the master password; records USERNAME as the approver. |
deny |
<id> |
Remove a pending command without approving it. |
serve |
[--http <addr:port>] |
Start the MCP server (this is the default when no subcommand is given). Without --http: stdio only. With --http: serves Streamable HTTP at http://<addr>/mcp and keeps stdio; if stdin is closed the stdio side stops and HTTP continues. |
# Add credentials (passwords never leave the encrypted vault)
ssh-mcp add web root@192.168.1.10 'password'
ssh-mcp add db root@10.0.0.5 'password' --port 2222
ssh-mcp add app deploy@10.0.0.6 'password' --sudo-password 'sudo-password' # optional separate sudo password
# List and remove
ssh-mcp list
ssh-mcp remove web
# Approval workflow
ssh-mcp pending
ssh-mcp approve <id>
ssh-mcp deny <id>
# Server over stdio only (MCP clients spawn this process)
SSH_MCP_MASTER_PASSWORD='...' ssh-mcp
# Server with Streamable HTTP on 127.0.0.1:8787
SSH_MCP_MASTER_PASSWORD='...' ssh-mcp serve --http 127.0.0.1:8787
# --data-dir is global; both placements work
ssh-mcp --data-dir /srv/ssh-mcp serve --http 0.0.0.0:8787
ssh-mcp serve --data-dir /srv/ssh-mcp --http 0.0.0.0:8787Exit code is 0 on success and 1 on error (the error message is printed to stderr).
vault.bin— AES-256-GCM encrypted credential storeknown_hosts— fingerprints of trusted hostspending/— approval queue (one file per pending command)approved/— consumed approvalsssh-mcp.log— operation log (see Logging)
| Variable | Default | Description |
|---|---|---|
SSH_MCP_MASTER_PASSWORD |
interactive prompt (TTY only) | Master password that unlocks the vault. Required whenever stdin is not a terminal (always the case in MCP stdio mode). An empty value is an error. Never logged. |
SSH_MCP_HTTP_TOKEN |
unset (auth disabled) | When set, every HTTP request must send Authorization: Bearer <token> or X-SSH-MCP-Token: <token>; requests without a matching token get 401. Only applies to serve --http. Never logged. |
SSH_MCP_COMMAND_POLICY |
block |
block: hard-block catastrophic commands and require approval for high-risk commands. warn: hard-block only; high-risk commands run with a logged warning. allow: filtering disabled (not recommended). Any unknown value falls back to block. |
SSH_MCP_APPROVAL_TTL |
300 |
Approval validity in seconds. An approval expires after this period, is scoped to the same alias and exact command, and is consumed after one use. |
SSH_MCP_LOG_FILE |
<data_dir>/ssh-mcp.log |
Override the log file path; parent directories are created automatically. |
SSH_MCP_LOG_LEVEL |
info |
trace / debug / info / warn / error. Any unknown value falls back to info. |
USERNAME |
cli |
Audit name recorded when ssh-mcp approve is run (normally set by Windows; falls back to cli when unset). |
All variables are read once at process start.
Local MCP clients (Claude Desktop, Cursor, ...) spawn the process directly:
SSH_MCP_MASTER_PASSWORD='...' ssh-mcpSSH_MCP_MASTER_PASSWORD='...' ssh-mcp serve --http 127.0.0.1:8787The endpoint is http://127.0.0.1:8787/mcp (MCP Streamable HTTP, stateful sessions). With --http, the stdio transport keeps running as well; when stdin is closed the stdio side exits and HTTP continues to serve.
To require a token, set SSH_MCP_HTTP_TOKEN; clients must send Authorization: Bearer <token> (or X-SSH-MCP-Token: <token>):
SSH_MCP_MASTER_PASSWORD='...' SSH_MCP_HTTP_TOKEN='your-token' ssh-mcp serve --http 0.0.0.0:8787Binding 0.0.0.0 exposes the server to the network — always set SSH_MCP_HTTP_TOKEN in that case and make sure the firewall only allows trusted clients.
| Tool | Description |
|---|---|
ssh_connect |
Open an SSH session for an alias, returns a session_id |
ssh_disconnect |
Close a session |
ssh_exec |
Run a command on a PTY, returns stdout/stderr/exit code (configurable timeout); password prompts are answered automatically |
ssh_shell_start |
Open an interactive PTY shell; sudo password prompts are answered automatically |
ssh_shell_write |
Send input to a shell |
ssh_shell_read |
Read shell output (configurable wait in milliseconds) |
ssh_shell_close |
Close a shell |
ssh_upload |
SFTP upload a local file to the remote host |
ssh_download |
SFTP download a remote file to the local host |
ssh_list_sessions |
List all open sessions |
ssh_list_credentials |
List stored credentials (alias, username, host, port); passwords are never returned |
Credentials can only be added/removed via the CLI; MCP can never modify them or read any password. ssh_list_credentials returns non-sensitive metadata only.
On hosts where direct root login is disabled, connect with a regular user and prefix commands with sudo (e.g. ssh_exec → sudo apt-get update). Every ssh_exec command runs on a PTY: the server watches the output for password prompts ([sudo] password for <user>:, Password:, 密码:) and answers them with the credential's password straight over the SSH channel. sudo disables echo while reading the password, so the password never appears in tool output, command history, or logs.
- The SSH login password is reused for sudo by default; if sudo uses a different password, pass it at add time (
ssh-mcp add web root@host 'pw' --sudo-password 'spw') or configureNOPASSWDfor the account. - Password prompts are answered wherever they appear:
x && sudo y,echo x | sudo tee /etc/f,su -c ...and interactive shells (ssh_shell_start) all work. Because commands run on a PTY, stderr is merged into stdout and colored output may include ANSI escape codes. - Hosts with passwordless sudo (
NOPASSWD) are unaffected — no prompt, no answer. - The safety guard still applies: it evaluates the command with the
sudoprefix stripped, sosudo rm -rf /remains hard-blocked.
The server includes a command safety guard (src/guard.rs): regardless of which MCP client calls it, ssh_exec, ssh_shell_start (with a command) and ssh_shell_write are checked before the command is ever sent to the remote host.
- Hard blocked (always denied, cannot be bypassed by approval): recursive force delete on system roots (
rm -rf /,rm -rf /*,rm -rf /etc,rm -rf /boot, ...); writing to disk devices (dd ... of=/dev/sdX,mkfs ... /dev/sdX,> /dev/sdX, ...); fork bombs (:(){ :|:& };:); recursivechmod/chownon system roots. - Requires human approval (default):
rm -rfon any path,rm -ron system directories,mkfs,dd, power operations (shutdown/reboot/halt/poweroff/init 0|6),kill -9 1, moving a system root,curl|wget ... | sh|bash, recursivechmod/chown.
The first attempt of a high-risk command returns an error with an approval ID:
command requires approval: run `ssh-mcp approve <id>` (or `ssh-mcp deny <id>`), then retry the same command
- List the pending queue:
ssh-mcp pending - Approve:
ssh-mcp approve <id>(asks for the master password; must use the same--data-diras the server) - Ask the MCP client to retry the exact same command — it will be allowed
- An approval is valid for 5 minutes by default (
SSH_MCP_APPROVAL_TTL), scoped to the same alias and command, and consumed after one use
To reject: ssh-mcp deny <id>.
The policy and the approval TTL are configured with SSH_MCP_COMMAND_POLICY and SSH_MCP_APPROVAL_TTL (see Environment variables).
Every operation is written to a log file at <data_dir>/ssh-mcp.log (~/.ssh-mcp/ssh-mcp.log by default), including:
- CLI operations: add/remove credentials (passwords are never logged), list, approve/deny/view pending commands
- MCP tool calls: parameters (session ID, command, paths, ...) and success/failure results
- Safety guard: hard blocks, approval creation/approval/consumption
- HTTP requests: method, path, status code, duration
The path and verbosity are configured with SSH_MCP_LOG_FILE and SSH_MCP_LOG_LEVEL (see Environment variables).
The log file rotates to ssh-mcp.log.1 after 10 MB. Logs never contain passwords or the master password; timestamps are UTC. If log initialization fails (e.g. the file is not writable), only a warning is printed and normal operation continues. Every line includes the process ID ([pid=12345]), so when multiple Codex instances each spawn their own server the lines can be attributed to the right process.
Examples below use the Windows ssh-mcp.exe; replace <your-path> with the actual absolute path of your binary:
<your-path>\ssh-mcp.exe
Two integration modes:
- stdio (recommended): the MCP client spawns the process. The client must set the
SSH_MCP_MASTER_PASSWORDenvironment variable for the child process (to unlock the encrypted vault). - HTTP: start the service manually (
ssh-mcp serve --http 127.0.0.1:8787) and configure the URL only. No master password is needed in the client config.
Add via CLI (stdio):
codex mcp add ssh-mcp --env SSH_MCP_MASTER_PASSWORD=your-master-password -- <your-path>\ssh-mcp.exeOr edit ~/.codex/config.toml directly:
[mcp_servers.ssh-mcp]
command = "<your-path>\\ssh-mcp.exe"
enabled = true
[mcp_servers.ssh-mcp.env]
SSH_MCP_MASTER_PASSWORD = "${SSH_MCP_MASTER_PASSWORD}"When using ${SSH_MCP_MASTER_PASSWORD}, set the environment variable in the system (e.g. Windows Settings → Environment Variables) before starting Codex; otherwise the literal string is passed and unlocking fails.
HTTP mode (the token is sent as a header; some Codex versions only accept an environment-variable reference for the bearer token, so you can write the header literally or point to an env var):
codex mcp add ssh-mcp --url http://127.0.0.1:8787/mcp --bearer-token-env-var SSH_MCP_HTTP_TOKEN[mcp_servers.ssh-mcp]
url = "http://127.0.0.1:8787/mcp"
enabled = true
startup_timeout_sec = 3600
tool_timeout_sec = 3600
http_headers = { Authorization = "Bearer your-token" } # literal header, or:
# bearer_token_env_var = "SSH_MCP_HTTP_TOKEN"Verify with codex mcp list and restart Codex for the tools to load. See the official docs: https://developers.openai.com/codex/mcp.
Ask Codex before calling ssh-mcp tools (codex mcp add has no approval flag yet; edit ~/.codex/config.toml manually):
[mcp_servers.ssh-mcp]
command = "<your-path>\\ssh-mcp.exe"
enabled = true
default_tools_approval_mode = "prompt" # auto | prompt | approve
# Force prompts only for specific high-risk tools
[mcp_servers.ssh-mcp.tools.ssh_exec]
approval_mode = "prompt"
[mcp_servers.ssh-mcp.tools.ssh_shell_write]
approval_mode = "prompt"Add via CLI (stdio):
claude mcp add --transport stdio --scope user ssh-mcp --env SSH_MCP_MASTER_PASSWORD=your-master-password -- <your-path>\ssh-mcp.exeOr a project-level .mcp.json (place it in the project root and share it with the repo):
{
"mcpServers": {
"ssh-mcp": {
"type": "stdio",
"command": "<your-path>\\ssh-mcp.exe",
"env": {
"SSH_MCP_MASTER_PASSWORD": "${SSH_MCP_MASTER_PASSWORD}"
}
}
}
}Again, ${SSH_MCP_MASTER_PASSWORD} must be set in the environment before starting Claude Code, otherwise the literal string is passed and unlocking fails.
HTTP mode:
claude mcp add --transport http ssh-mcp http://127.0.0.1:8787/mcpRun /mcp inside Claude Code to check the connection status. See the official docs: https://code.claude.com/docs/en/mcp.
Ask Claude Code before calling ssh-mcp tools (project-level .claude/settings.json):
{
"permissions": {
"ask": [
"MCPTool(ssh-mcp:*)"
]
}
}You can target individual tools instead, e.g. "MCPTool(ssh-mcp:ssh_exec)", "MCPTool(ssh-mcp:ssh_shell_write)"; putting them in deny blocks them entirely.
Project-level .cursor/mcp.json (or global ~/.cursor/mcp.json):
{
"mcpServers": {
"ssh-mcp": {
"type": "stdio",
"command": "<your-path>\\ssh-mcp.exe",
"env": {
"SSH_MCP_MASTER_PASSWORD": "your-master-password"
}
}
}
}Alternatively use the UI: Settings → MCP → Add new MCP server → fill in the command, arguments and environment variables. See the official docs: https://cursor.com/help/customization/mcp.
Workspace .vscode/mcp.json:
{
"servers": {
"ssh-mcp": {
"type": "stdio",
"command": "<your-path>\\ssh-mcp.exe",
"env": {
"SSH_MCP_MASTER_PASSWORD": "${input:ssh-mcp-master-password}"
}
}
},
"inputs": [
{
"type": "promptString",
"id": "ssh-mcp-master-password",
"description": "ssh-mcp master password",
"password": true
}
]
}With ${input:...}, VS Code safely prompts for the master password on first launch instead of storing it in plain text. For HTTP mode:
{
"servers": {
"ssh-mcp": {
"type": "http",
"url": "http://127.0.0.1:8787/mcp"
}
}
}See the official docs: https://code.visualstudio.com/docs/copilot/customization/mcp-servers.
Edit %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"ssh-mcp": {
"command": "<your-path>\\ssh-mcp.exe",
"env": {
"SSH_MCP_MASTER_PASSWORD": "your-master-password"
}
}
}
}Quit and restart Claude Desktop completely after editing.
Settings → MCP servers → Add server:
- Name:
ssh-mcp - Command:
<your-path>\ssh-mcp.exe - Environment variables:
SSH_MCP_MASTER_PASSWORD=your-master-password
You can also choose the Streamable HTTP type and enter http://127.0.0.1:8787/mcp (no master password needed).
- Restart the client or reopen the session after changing the configuration so the tool list reloads.
- In stdio mode the master password is stored in plain text (or referenced from an environment variable) in the client config; do not commit configs containing real passwords to public repositories. If you commit
.mcp.json, prefer the${VAR}form. - Backslashes in Windows paths must be escaped as
\\in JSON/TOML; forward slashes also work, e.g.<your-path>/ssh-mcp.exe. - Client-side approvals (Codex
approval_mode = "prompt", Claude Codepermissions.ask) only constrain a single client; the server-side command safety guard applies to all clients and is the final line of defense.
cargo testIncludes credential vault encryption unit tests and MCP handshake integration tests over both stdio and HTTP transports.