Are you the author? Sign in to claim
MCP server for remote SSH operations -- persistent sessions, structured command output, SFTP file transfer, port forward
MCP server giving AI agents full SSH access -- persistent sessions, structured command output, SFTP file transfer, port forwarding, and secret-safe environment variable injection with automatic output redaction.
Every other SSH MCP server is missing something: no password auth, no persistent sessions, no SFTP, no port forwarding, or no structured exit codes. This one has all of them -- plus the only MCP-level secret management that prevents AI agents from ever seeing your credentials.
The problem: When an AI agent needs to use API tokens, passwords, or keys on a remote server, the standard approach exposes secrets in the LLM's context window. The agent either reads the secret file (now it's in the conversation) or runs echo $TOKEN and sees the value in the output.
The solution: ssh_load_env_file reads secrets from a local file on your machine, injects them into the remote SSH session, and registers them for automatic output redaction. The AI agent can use the variables freely -- every tool response is scrubbed before it reaches the LLM.
# Agent calls this -- file is read from YOUR machine, not the remote host
ssh_load_env_file(session_id="abc", file_path="~/.secrets/prod.env")
→ "Loaded 3 variables from local:~/.secrets/prod.env: API_TOKEN, DB_PASS, SECRET_KEY"
# Agent tries to echo the value -- redacted automatically
ssh_execute(session_id="abc", command="echo $API_TOKEN")
→ {"stdout": "***\n", "exit_code": 0}
# Agent dumps the environment -- all secret values scrubbed
ssh_execute(session_id="abc", command="env | grep API_TOKEN")
→ {"stdout": "API_TOKEN=***\n", "exit_code": 0}
# Agent reads a file containing a secret -- also redacted
ssh_read_remote_file(session_id="abc", remote_path="/etc/app/config")
→ "db_password=***\ndb_host=localhost\n"
# Normal commands work perfectly -- no over-redaction
ssh_execute(session_id="abc", command="uname -a")
→ {"stdout": "Linux server 6.1.0 ...", "exit_code": 0}
┌─────────┐ ┌──────────────────────────────┐ ┌─────────────┐
│ LLM │ ←─JSON─ │ MCP Server (your machine) │ ──SSH─→ │ Remote Host │
│ (Agent) │ │ │ │ │
└─────────┘ │ 1. Reads ~/.secrets/prod.env│ └─────────────┘
│ 2. Parses KEY=VALUE pairs │
│ 3. Stores values in memory │
│ 4. Injects into SSH session │
│ 5. Redacts ALL tool output │
└──────────────────────────────┘
read -r VAR <<< 'value' && export VAR (no process tree exposure)ssh_execute feeds secrets via stdin to a bash wrapper, so they never appear in /proc/*/cmdlinessh_execute, ssh_shell_send, ssh_shell_read, ssh_read_remote_file) is scrubbed before reaching the LLMabc123 is replaced before abc)| Threat | Mitigated? | How |
|---|---|---|
| Secret in LLM context window | Yes | Output redaction replaces values with *** |
| Secret in remote process tree (shell) | Yes | Shell builtins (read/export) don't fork |
| Secret in remote process tree (exec) | Yes | Secrets fed via stdin, never in /proc/*/cmdline |
LLM tries cat on the env file | N/A | File is local-only, doesn't exist on remote |
LLM tries echo $VAR | Yes | Output is redacted |
| Encoded/transformed secret (base64) | No | Only literal matches are redacted |
| MITM on first SSH connection | Accepted | AutoAddPolicy used — see note below |
This server uses Paramiko's AutoAddPolicy — unknown host keys are accepted without prompting. This is intentional for QE/lab environments where hosts are ephemeral (Beaker, cloud instances, CI machines). The trade-off:
If you operate on untrusted networks, consider wrapping connections through a VPN or SSH bastion with pre-distributed host keys. A host_key_policy parameter may be added in a future release for strict environments.
Standard .env format:
# Comments are ignored
API_TOKEN=your-secret-token
DB_PASSWORD="quoted values work"
SECRET_KEY='single quotes too'
export ALSO_WORKS=yes
uvx mcp-remote-ssh # or: pip install mcp-remote-ssh
{
"mcpServers": {
"remote-ssh": {
"command": "uvx",
"args": ["mcp-remote-ssh"]
}
}
}
| Tool | Description |
|---|---|
ssh_connect | Connect with password, key, or agent auth. Returns session_id |
ssh_list_sessions | List active sessions |
ssh_close_session | Close a session and release resources |
| Tool | Description |
|---|---|
ssh_execute | Run a command, returns {stdout, stderr, exit_code} |
ssh_sudo_execute | Run with sudo elevation |
| Tool | Description |
|---|---|
ssh_shell_open | Open persistent shell (preserves cwd, env, processes) |
ssh_shell_send | Send text (with optional Enter) |
ssh_shell_read | Read current output buffer |
ssh_shell_send_control | Send Ctrl+C, Ctrl+D, etc. |
ssh_shell_wait | Wait for a pattern or output to stabilize |
| Tool | Description |
|---|---|
ssh_load_env_file | Load secrets from a local env file; values never returned to the LLM |
ssh_clear_secrets | Clear redaction registry (values become visible again) |
| Tool | Description |
|---|---|
ssh_upload_file | Upload local file to remote host |
ssh_download_file | Download remote file to local machine |
ssh_read_remote_file | Read a remote text file |
ssh_write_remote_file | Write/append to a remote file |
ssh_list_remote_dir | List directory with metadata |
| Tool | Description |
|---|---|
ssh_forward_port | Create SSH tunnel (local -> remote) |
ssh_list_forwards | List active tunnels |
ssh_close_forward | Close a tunnel |
ssh_connect(host="server.example.com", username="admin", password="secret")
→ {"session_id": "a1b2c3d4", "connected": true}
ssh_load_env_file(session_id="a1b2c3d4", file_path="~/.secrets/prod.env")
→ "Loaded 2 variables: API_TOKEN, DB_PASS"
ssh_execute(session_id="a1b2c3d4", command="curl -H \"Authorization: Bearer $API_TOKEN\" https://api.example.com")
→ {"stdout": "{\"status\": \"ok\"}", "exit_code": 0} # token used but never visible
ssh_shell_open(session_id="a1b2c3d4")
ssh_shell_send(session_id="a1b2c3d4", data="cd /opt && make -j$(nproc)")
ssh_shell_wait(session_id="a1b2c3d4", pattern="$ ", timeout=600)
ssh_upload_file(session_id="a1b2c3d4", local_path="config.yaml", remote_path="/etc/app/config.yaml")
ssh_forward_port(session_id="a1b2c3d4", remote_port=5432, local_port=15432)
Built on Paramiko (SSH) + FastMCP (MCP protocol).
ssh_execute uses exec_command() for clean structured output with real exit codesssh_execute feeds exports via stdin to a bash wrapper, then execs the actual command -- secrets never appear in the process treessh_shell_* uses invoke_shell() for persistent interactive sessionsrun_in_executor to stay asyncshell_read pollingMIT
Run Claude Code as an MCP server so any agent can delegate coding tasks to it
Browser automation using accessibility snapshots instead of screenshots
Google's universal MCP server supporting PostgreSQL, MySQL, MongoDB, Redis, and 10+ databases
Official GitHub integration for repos, issues, PRs, and CI/CD workflows