A tiny Python proxy that lets Claude Desktop on Windows reach an MCP server running on a remote machine over SSH.
It exists to work around a hard Windows-only blocker: Microsoft's
C:\Windows\System32\OpenSSH\ssh.exe cannot launch inside an Electron
grandchild process tree. Even ssh -V returns exit 255 with no stderr,
which means the standard MCP stdio configuration
silently fails on Claude Desktop with MCP server: Server disconnected.
This project replaces ssh.exe with paramiko,
a pure-Python SSH client. paramiko runs entirely inside the proxy's own
Python process, so it has none of the console / handle-inheritance / sandbox
constraints that kill ssh.exe in this context.
- Tested on Windows 11 + Claude Desktop
- Used to expose a remote MCP server hosted on Debian 12 over SSH
- Drop-in compatible with the usual ssh-style argument list
Claude Desktop (Electron)
│ stdio
▼
python.exe ──► mcp_ssh_proxy.py
│ paramiko (pure Python SSH)
▼
remote MCP server (any language)
launched via `exec_command`
The proxy:
- Reads ssh-style argv (
-i KEY -p PORT user@host remote-cmd …). - Opens a paramiko SSH connection — no
ssh.exe, no subprocess. - Calls
channel.exec_command(remote_cmd)to spawn the MCP server on the remote host. - Pumps raw bytes between Claude Desktop's stdio and the SSH channel using
os.read/os.write(zero Python-level buffering).
- Windows 10/11
- Python 3.9+ on
PATH(any distribution: python.org, Microsoft Store, …) - Claude Desktop with MCP support
Run from PowerShell:
iwr -useb https://raw.githubusercontent.com/KondrashovDenis/claude-ssh-mcp/main/install.ps1 | iexThis will:
pip install paramiko- Copy
mcp_ssh_proxy.pyto%LOCALAPPDATA%\Programs\claude-ssh-mcp\ - Print the JSON snippet to paste into your
claude_desktop_config.json
pip install paramiko
mkdir "$env:LOCALAPPDATA\Programs\claude-ssh-mcp"
# Save mcp_ssh_proxy.py there.Edit %APPDATA%\Claude\claude_desktop_config.json. Add (or merge with) a
mcpServers block:
{
"mcpServers": {
"myserver": {
"command": "python",
"args": [
"C:\\Users\\YOU\\AppData\\Local\\Programs\\claude-ssh-mcp\\mcp_ssh_proxy.py",
"-i", "C:\\Users\\YOU\\.ssh\\my_key",
"-p", "22",
"user@host.example.com",
"python3", "/remote/path/to/your/mcp_server.py"
]
}
}
}Replace myserver, the key path, port, host and remote command with your
own. Restart Claude Desktop completely (close from system tray) and the
server should attach within a few seconds.
A copy of this snippet is also in config.example.json.
The proxy parses an ssh-compatible subset of arguments. Anything else is
silently ignored, so you can keep an existing ssh ... command line:
| Flag | Behaviour |
|---|---|
-i KEYFILE |
private key path |
-p PORT |
remote port (default 22) |
-l USER |
remote username |
user@host |
username + hostname |
-T, -v, -vvv, -q, -N, -f, -n, -x, -X, -Y, -A, -a |
ignored |
-o KEY=VALUE |
ignored (BatchMode, StrictHostKeyChecking, etc. don't apply to paramiko) |
| everything after host | remote command, joined with spaces and run via exec_command |
The proxy writes a small log to %LOCALAPPDATA%\claude-ssh-mcp.log:
[15:48:18] === proxy start (paramiko) ===
[15:48:18] argv=['-i', 'C:\\Users\\you\\.ssh\\key', '-p', '2233', 'user@host', 'python3', '/srv/server.py']
[15:48:18] parsed host=host port=2233 user=user key=C:\Users\you\.ssh\key cmd='python3 /srv/server.py'
[15:48:18] connecting to user@host:2233 key=C:\Users\you\.ssh\key
[15:48:19] connected
[15:48:19] channel opened, command exec'd
[15:48:19] client->ssh -> 147B
[15:48:19] ssh->client <- 185B
Remote MCP server's stderr is also captured under the [remote-stderr]
prefix in the same file.
- The proxy uses
AutoAddPolicyfor host keys. It accepts whatever public key the server presents on first contact (and on every contact). MCP context cannot prompt for confirmation, so this is the best we can do inside Claude Desktop. If your threat model needs strict host-key pinning, hard-code the expected key inside the script instead of usingAutoAddPolicy. look_for_keys=Falseandallow_agent=Falseare set explicitly so the proxy never authenticates with anything other than the-ikey path you give it.- The private key path is read from disk on every connection. Make sure the key file's NTFS ACL allows only your user to read it.
You can, but it adds a WSL boot to every Claude session and forces you to
keep your key inside WSL. paramiko is one pip install and runs in the
same Python process as the proxy, so there is no extra subsystem in the
chain.
MIT — see LICENSE.
{ "mcpServers": { "remote": { "command": "ssh", "args": ["user@host", "python3", "/remote/server.py"] } } }