mcp-remote-ssh
This server provides AI agents with full remote SSH control: persistent sessions, command execution, interactive shells, file transfer, port forwarding, and secret-safe environment variable handling.
Connect & manage sessions:
ssh_connect(password/key auth),ssh_list_sessions,ssh_close_sessionExecute commands:
ssh_execute(structured stdout/stderr/exit code),ssh_sudo_executeInteractive persistent shell: open, send, read, send control keys, wait for patterns/stabilization
SFTP file operations: upload, download, read, write, list remote directories
Port forwarding: create, list, and close SSH tunnels (local → remote)
Secret-safe env injection: load a local
.envfile, inject variables into sessions, and automatically redact secret values from all tool outputsSession transcripts: start/stop recording, retrieve transcript (text/JSONL), save to local file
Security features: secret redaction across execute/shell/read outputs, shell builtins to avoid process tree exposure, stdin-based exec injection
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-remote-ssh ssh to staging host and execute 'uptime'"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-remote-ssh
MCP server giving AI agents full SSH access -- persistent sessions, structured command output, SFTP file transfer, port forwarding, session transcript recording, and secret-safe environment variable injection with automatic output redaction.
Why this exists
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.
Related MCP server: ssh-mcp
Secret-Safe Environment Variables
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}How it works
┌─────────┐ ┌──────────────────────────────┐ ┌─────────────┐
│ 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 │
└──────────────────────────────┘Local file read -- the env file lives on your machine, never on the remote host
Shell injection via builtins -- uses
read -r VAR <<< 'value' && export VAR(no process tree exposure)Stdin-based exec injection --
ssh_executefeeds secrets via stdin to a bash wrapper, so they never appear in/proc/*/cmdlineAutomatic redaction -- every tool response (
ssh_execute,ssh_shell_send,ssh_shell_read,ssh_read_remote_file) is scrubbed before reaching the LLMLongest-first matching -- prevents partial-match corruption (e.g.,
abc123is replaced beforeabc)
Security properties
Threat | Mitigated? | How |
Secret in LLM context window | Yes | Output redaction replaces values with |
Secret in remote process tree (shell) | Yes | Shell builtins ( |
Secret in remote process tree (exec) | Yes | Secrets fed via stdin, never in |
LLM tries | N/A | File is local-only, doesn't exist on remote |
LLM tries | Yes | Output is redacted |
Encoded/transformed secret (base64) | No | Only literal matches are redacted |
MITM on first SSH connection | Accepted |
|
Host key policy
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:
Pro: Zero-friction connections to newly provisioned machines
Con: Vulnerable to MITM on the very first connection to an unknown host
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.
Env file format
Standard .env format:
# Comments are ignored
API_TOKEN=your-secret-token
DB_PASSWORD="quoted values work"
SECRET_KEY='single quotes too'
export ALSO_WORKS=yesSession transcripts
Recording is off by default. Enable it per session when you need an audit log of what the agent actually ran — useful for bug reproductions and test campaigns.
# Start recording on connect
ssh_connect(host="lab.example.com", username="root", password="...", record=True)
# Or toggle later
ssh_start_recording(session_id="a1b2c3d4")
ssh_execute(session_id="a1b2c3d4", command="uname -a")
ssh_get_transcript(session_id="a1b2c3d4")
→ {"recording": true, "total_entries": 2, "transcript": "[12:01:02] --- connect: lab.example.com ---\n[12:01:05] $ uname -a\nLinux ...\n[exit 0]"}
ssh_save_transcript(session_id="a1b2c3d4", path="/tmp/lab-session.log")
ssh_stop_recording(session_id="a1b2c3d4")Execute/sudo output and shell send/read I/O are secret-redacted before they are recorded
Closing the session discards the in-memory transcript —
ssh_save_transcriptorssh_get_transcriptfirst
Installation
uvx mcp-remote-ssh # or: pip install mcp-remote-sshConfiguration
{
"mcpServers": {
"remote-ssh": {
"command": "uvx",
"args": ["mcp-remote-ssh"]
}
}
}Tools (24)
Connection
Tool | Description |
| Connect with password, key, or agent auth. Optional |
| List active sessions |
| Close a session and release resources |
Execution
Tool | Description |
| Run a command, returns |
| Run with sudo elevation |
Interactive Shell
Tool | Description |
| Open persistent shell (preserves cwd, env, processes) |
| Send text (with optional Enter) |
| Read current output buffer |
| Send Ctrl+C, Ctrl+D, etc. |
| Wait for a pattern or output to stabilize |
Secrets Management
Tool | Description |
| Load secrets from a local env file; values never returned to the LLM |
| Clear redaction registry (values become visible again) |
Transcripts
Tool | Description |
| Start recording execute/sudo/shell I/O for this session |
| Stop recording; transcript stays available until the session is closed |
| Return the transcript as text or JSONL ( |
| Write the transcript to a local file on the MCP host |
SFTP
Tool | Description |
| Upload local file to remote host |
| Download remote file to local machine |
| Read a remote text file |
| Write/append to a remote file |
| List directory with metadata |
Port Forwarding
Tool | Description |
| Create SSH tunnel (local -> remote) |
| List active tunnels |
| Close a tunnel |
Quick start
ssh_connect(host="server.example.com", username="admin", password="secret", record=True)
→ {"session_id": "a1b2c3d4", "connected": true, "recording": 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)Design
Built on Paramiko (SSH) + FastMCP (MCP protocol).
ssh_executeusesexec_command()for clean structured output with real exit codesWhen secrets are loaded,
ssh_executefeeds exports via stdin to abashwrapper, thenexecs the actual command -- secrets never appear in the process treessh_shell_*usesinvoke_shell()for persistent interactive sessionsAll blocking Paramiko calls run in
run_in_executorto stay asyncShell keeps a 500KB rolling buffer for
shell_readpollingSecret redaction uses longest-first string replacement across all output paths
Session transcripts are in-memory, off by default, and discarded when the session is closed
License
MIT
Available Tools
18 toolsssh_close_forwardA
Close a specific port forward.
Args: session_id: The session ID returned by ssh_connect. forward_id: The forward ID returned by ssh_forward_port.
Returns: Confirmation message.
Inputs: session_id (required), forward_id (required). Returns: result
ssh_close_sessionA
Close an SSH session and release all its resources (shell, SFTP, port forwards). WARNING: this kills any running processes in the session.
Args: session_id: The session ID returned by ssh_connect.
Returns: Confirmation message.
Inputs: session_id (required). Returns: result
ssh_connectA
Connect to a remote host via SSH. Returns a session_id for use with all other tools. Supports password and key-based authentication.
Args: host: Hostname or IP address of the remote server. username: SSH username (default: root). password: Password for authentication. Leave empty for key-based auth. key_path: Path to SSH private key file. Leave empty for password auth. port: SSH port (default: 22). timeout: Connection timeout in seconds (default: 60).
Returns: Session info dict with session_id, host, and connection status.
Inputs: host (required), username, password, key_path, +2 more. Returns: object
ssh_download_fileA
Download a file from the remote host to the local machine via SFTP.
Args: session_id: The session ID returned by ssh_connect. remote_path: Path to the file on the remote host. local_path: Destination path on the local machine.
Returns: Confirmation message with file size.
Inputs: session_id (required), remote_path (required), local_path (required). Returns: result
ssh_executeA
Execute a command on the remote host and return structured output. Each call runs in an independent exec channel -- no state is shared between calls (use ssh_shell_* tools for persistent state).
Args: session_id: The session ID returned by ssh_connect. command: Shell command to execute. timeout: Maximum seconds to wait for the command to finish (default: 120).
Returns: Dict with stdout, stderr, and exit_code.
Inputs: session_id (required), command (required), timeout. Returns: object
ssh_forward_portA
Create an SSH port forward (local -> remote). Connections to 127.0.0.1:local_port will be tunneled through SSH to remote_host:remote_port.
If local_port is 0, a random available port is chosen.
Args: session_id: The session ID returned by ssh_connect. remote_port: Port on the remote side to forward to. local_port: Local port to listen on (0 = auto-assign). remote_host: Host on the remote side (default: localhost, i.e. the SSH server itself).
Returns: Dict with forward_id, local_port, remote_host, and remote_port.
Inputs: session_id (required), remote_port (required), local_port, remote_host. Returns: object
ssh_list_forwardsA
List all active port forwards for an SSH session.
Args: session_id: The session ID returned by ssh_connect.
Returns: List of forward info dicts.
Inputs: session_id (required). Returns: result
ssh_list_remote_dirA
List files and directories at a path on the remote host via SFTP.
Args: session_id: The session ID returned by ssh_connect. remote_path: Directory path on the remote host (default: current directory).
Returns: List of dicts with name, size, modified timestamp, and is_dir flag.
Inputs: session_id (required), remote_path. Returns: result
ssh_list_sessionsA
List all active SSH sessions with their connection status and details.
Returns: List of session info dicts.
Inputs: no named parameters. Returns: result
ssh_read_remote_fileA
Read a text file on the remote host and return its contents. For large files, use max_bytes to limit the amount read.
Args: session_id: The session ID returned by ssh_connect. remote_path: Path to the file on the remote host. max_bytes: Maximum bytes to read (default: 1MB). Set to 0 for no limit.
Returns: File contents as text.
Inputs: session_id (required), remote_path (required), max_bytes. Returns: result
ssh_shell_openA
Open a persistent interactive shell on the SSH session. The shell preserves working directory, environment variables, and running processes across multiple send/read calls. Ideal for screen/tmux, long builds, etc.
If a shell is already open, this is a no-op (returns existing shell info).
Args: session_id: The session ID returned by ssh_connect. term: Terminal type (default: xterm). width: Terminal width in columns (default: 200). height: Terminal height in rows (default: 50).
Returns: Confirmation that the shell is open.
Inputs: session_id (required), term, width, height. Returns: result
ssh_shell_readA
Read the current content of the interactive shell buffer. Use this to poll for output from long-running commands without sending anything.
Args: session_id: The session ID returned by ssh_connect. lines: Number of tail lines to return (default: 100).
Returns: Recent shell output (tail of buffer).
Inputs: session_id (required), lines. Returns: result
ssh_shell_sendA
Send text to the interactive shell. By default appends Enter (newline) and waits briefly to capture output.
Args: session_id: The session ID returned by ssh_connect. data: Text to send to the shell. press_enter: Whether to append a newline after the text (default: True). wait: Seconds to wait for output after sending (default: 1.0). read_lines: Number of tail lines to return from the shell buffer (default: 100).
Returns: Recent shell output (tail of buffer).
Inputs: session_id (required), data (required), press_enter, wait, +1 more. Returns: result
ssh_shell_send_controlA
Send a control character to the interactive shell. Common keys: "c" for Ctrl+C (interrupt), "d" for Ctrl+D (EOF), "z" for Ctrl+Z (suspend), "l" for Ctrl+L (clear screen), "a" for Ctrl+A (screen prefix).
Args: session_id: The session ID returned by ssh_connect. key: Single letter for the control key (e.g. "c" sends Ctrl+C).
Returns: Confirmation and recent shell output.
Inputs: session_id (required), key (required). Returns: result
ssh_shell_waitA
Wait for the shell output to contain a specific pattern, or for the output to stabilize (no new output for two poll intervals). Useful for waiting on long-running commands to complete.
Args: session_id: The session ID returned by ssh_connect. pattern: Text pattern to wait for (e.g. a shell prompt like "$ " or "# "). If empty, waits for output to stabilize. timeout: Maximum seconds to wait (default: 300). poll_interval: Seconds between polls (default: 2.0). lines: Number of tail lines to return (default: 100).
Returns: Shell output when the pattern is found or output stabilizes.
Inputs: session_id (required), pattern, timeout, poll_interval, +1 more. Returns: result
ssh_sudo_executeA
Execute a command with sudo on the remote host. If the user already has passwordless sudo, leave sudo_password empty.
Args: session_id: The session ID returned by ssh_connect. command: Shell command to execute under sudo. sudo_password: Password for sudo prompt (empty for passwordless sudo). timeout: Maximum seconds to wait (default: 120).
Returns: Dict with stdout, stderr, and exit_code.
Inputs: session_id (required), command (required), sudo_password, timeout. Returns: object
ssh_upload_fileB
Upload a local file to the remote host via SFTP.
Args: session_id: The session ID returned by ssh_connect. local_path: Path to the file on the local machine. remote_path: Destination path on the remote host.
Returns: Confirmation message with file size.
Inputs: session_id (required), local_path (required), remote_path (required). Returns: result
ssh_write_remote_fileA
Write text content to a file on the remote host via SFTP.
Args: session_id: The session ID returned by ssh_connect. remote_path: Path to the file on the remote host. content: Text content to write. append: If True, append to existing file instead of overwriting (default: False).
Returns: Confirmation message with bytes written.
Inputs: session_id (required), remote_path (required), content (required), append. Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 18 tools
Each tool targets a distinct operation: connection, execution, file transfer, shell interaction, port forwarding, and session management. No overlaps; even similar tools like ssh_execute and ssh_sudo_execute are clearly differentiated by the sudo aspect.
All tools follow the pattern `ssh_<verb>_<noun>`, with shell tools using `ssh_shell_<verb>` as a consistent sub-pattern. No mixed conventions or atypical naming.
18 tools cover the full scope of SSH remote management: connection, stateless and persistent execution, file operations, port forwarding, and session control. Each tool feels justified and the count is appropriate for the domain.
Core workflows are well covered (connect, execute, file transfer, shell, port forwarding). Minor gaps exist: no direct file delete/rename or directory creation tool, but these can be achieved via command execution. Overall, the surface is nearly complete.
Maintenance
Related MCP Connectors
MCP server for building and testing AI agents with multi-model experimentation and insights.
MCP server connecting AI agents to 100+ apps (Gmail, Slack, Notion, GitHub) via one-click OAuth.
MCP server that lets your AI assistant build and deploy apps, hosting each one at a live URL.
Remote MCP server exposing your Node Command fleet (files, shell, processes, browser) to AI hosts.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceAn MCP server that gives AI assistants full SSH/SFTP remote operations — session management, command execution, interactive shells, file transfers, port forwarding, and system diagnostics.2MIT
- AlicenseAqualityCmaintenanceAn MCP server that gives AI agents SSH access to remote machines through your local OpenSSH client, enabling remote command execution, file transfer, persistent shell sessions, and port forwarding.17319 PyPI40MIT
- AlicenseNot gradedqualityDmaintenanceMCP server that enables AI agents to run fully interactive SSH sessions (via tmux) and execute commands like a human operator, with persistent sessions and multiple concurrent connections.6MIT
- AlicenseAqualityDmaintenanceSSH MCP is a Model Context Protocol tool for managing and interacting with multiple virtual machines over SSH. It simplifies executing commands on remote servers using the standard SSH config file format.427 PyPI4MIT