Metadata-Version: 2.4
Name: mcp-remote-ssh
Version: 0.3.0
Summary: MCP server for remote SSH operations -- persistent sessions, structured command execution, SFTP file transfer, and port forwarding for AI agents.
Author: Mohammadfaiz Bawa
License-Expression: MIT
Project-URL: Homepage, https://github.com/faizbawa/mcp-remote-ssh
Project-URL: Repository, https://github.com/faizbawa/mcp-remote-ssh
Project-URL: Issues, https://github.com/faizbawa/mcp-remote-ssh/issues
Project-URL: Changelog, https://github.com/faizbawa/mcp-remote-ssh/blob/main/CHANGELOG.md
Keywords: mcp,ssh,remote,sftp,paramiko,ai,model-context-protocol,cursor,claude
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: System :: Networking
Classifier: Topic :: System :: Systems Administration
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: fastmcp<4,>=3.0
Requires-Dist: paramiko>=3.4
Requires-Dist: click>=8.0
Requires-Dist: loguru>=0.7
Dynamic: license-file

# mcp-remote-ssh

[![PyPI](https://img.shields.io/pypi/v/mcp-remote-ssh.svg)](https://pypi.org/project/mcp-remote-ssh/)
[![Python](https://img.shields.io/pypi/pyversions/mcp-remote-ssh.svg)](https://pypi.org/project/mcp-remote-ssh/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

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**.

## 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.

## 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.

```text
# 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  │
                    └──────────────────────────────┘
```

1. **Local file read** -- the env file lives on your machine, never on the remote host
2. **Shell injection via builtins** -- uses `read -r VAR <<< 'value' && export VAR` (no process tree exposure)
3. **Stdin-based exec injection** -- `ssh_execute` feeds secrets via stdin to a bash wrapper, so they never appear in `/proc/*/cmdline`
4. **Automatic redaction** -- every tool response (`ssh_execute`, `ssh_shell_send`, `ssh_shell_read`, `ssh_read_remote_file`) is scrubbed before reaching the LLM
5. **Longest-first matching** -- prevents partial-match corruption (e.g., `abc123` is replaced before `abc`)

### 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 (`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 |

### Env file format

Standard `.env` format:

```bash
# Comments are ignored
API_TOKEN=your-secret-token
DB_PASSWORD="quoted values work"
SECRET_KEY='single quotes too'
export ALSO_WORKS=yes
```

## Installation

```bash
uvx mcp-remote-ssh        # or: pip install mcp-remote-ssh
```

## Configuration

```json
{
  "mcpServers": {
    "remote-ssh": {
      "command": "uvx",
      "args": ["mcp-remote-ssh"]
    }
  }
}
```

## Tools (20)

### Connection

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

### Execution

| Tool | Description |
|------|-------------|
| `ssh_execute` | Run a command, returns `{stdout, stderr, exit_code}` |
| `ssh_sudo_execute` | Run with sudo elevation |

### Interactive Shell

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

### Secrets Management

| 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) |

### SFTP

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

### Port Forwarding

| Tool | Description |
|------|-------------|
| `ssh_forward_port` | Create SSH tunnel (local -> remote) |
| `ssh_list_forwards` | List active tunnels |
| `ssh_close_forward` | Close a tunnel |

## Quick start

```text
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)
```

## Design

Built on **[Paramiko](https://www.paramiko.org/)** (SSH) + **[FastMCP](https://github.com/PrefectHQ/fastmcp)** (MCP protocol).

- `ssh_execute` uses `exec_command()` for clean structured output with real exit codes
- When secrets are loaded, `ssh_execute` feeds exports via stdin to a `bash` wrapper, then `exec`s the actual command -- secrets never appear in the process tree
- `ssh_shell_*` uses `invoke_shell()` for persistent interactive sessions
- All blocking Paramiko calls run in `run_in_executor` to stay async
- Shell keeps a 500KB rolling buffer for `shell_read` polling
- Secret redaction uses longest-first string replacement across all output paths

## License

MIT
