Skip to content

Getting Started

Requirements

The local machine needs Linux, OpenSSH, rsync, and the standard false utility. A source installation additionally needs CPython 3.13 or 3.14 with venv, Bash, and GNU Make. The remote account needs Linux, /bin/sh, rsync, GNU coreutils and findutils, setsid, and /bin/kill. The optional sudo_exec tool additionally requires sudo and /bin/bash.

Standalone Linux executable

Each GitHub Release contains remote-ssh-mcp-linux-amd64 and remote-ssh-mcp-linux-arm64, built and smoke-tested natively on Ubuntu 26.04 runners, plus SHA256SUMS.txt. Download all three files, run sha256sum --check --ignore-missing SHA256SUMS.txt, and install only the binary matching the local architecture on a compatible Linux system. Keep it in a dedicated writable directory: that directory is the standalone server's local file boundary and owns its private .remote-ssh-mcp/ state. The binary still invokes the host's OpenSSH and rsync programs; it does not bundle or replace them.

Source checkout

Clone the repository, enter its root, prepare the explicit runtime, and check the launcher:

cd /path/to/remote-ssh-mcp-server
make runtime-venv
export PATH="$PWD:$PATH"
remote-ssh-mcp --help

make runtime-venv resolves the exact published SSH library from PyPI and installs every third-party dependency as a hash-locked binary package. It then installs this checkout without dependencies. The target records the installed lock inside venv-runtime/, verifies package metadata with pip check, and removes pip from the completed runtime. Development tools are not installed there.

Every environment lives beneath .venvs/<machine-user-key>/. Make and the launcher select this directory automatically from an application-specific hash of /etc/machine-id and the local UID, so each machine prepares its own environments in a checkout shared across machines, such as a network drive. Run the same make runtime-venv command once on each machine; do not activate a venv manually. A missing or invalid machine ID fails instead of selecting a shared directory. Old root-level venv-* environments are ignored and left untouched; remove them manually when no longer needed.

The launcher does not run pip and does not repair a missing or stale environment. It verifies the recorded runtime lock, the recorded project version, the installed MCP distribution, and the installed SSH distribution, then executes the installed MCP module with that environment's bin/python -I -m remote_ssh_mcp. It imports the installed SSH distribution; the caller's current directory and inherited PYTHONPATH do not select application code. Both the validation probe and server entry point use Python isolated mode.

For a source checkout, the active virtual environment must live in this machine's .venvs/<machine-user-key>/ directory of the project, and the project must contain its launcher, version, and runtime-lock files. The server uses that verified venv owner as the local file boundary. It refuses a global Python environment or a venv detached from the project instead of deriving a boundary from the installed package under site-packages. It also rejects a project .version that does not match the active MCP package.

MCP Client Setup

For Codex, copy the example into a trusted project's .codex/config.toml, ensure its command resolves to this project's launcher, and enable the server:

Keep the server name remote_machine if you want the supplied tool approval policy to match unchanged. Its 180-second startup deadline applies only to MCP process initialization, which performs no SSH authentication. The separate 150-second tool deadline covers connect; the server itself limits master startup to 120 seconds by default.

For a personal Claude Code registration scoped to the current project:

claude mcp add --transport stdio --scope local remote_machine -- \
  remote-ssh-mcp
claude mcp get remote_machine

For a shared registration, adapt the server example as .mcp.json or use the same command with --scope project. Merge the permissions example into the appropriate Claude settings file. Its names assume the server remains remote_machine.

The server starts without SSH sessions. Connect with a trusted OpenSSH alias:

{"ssh_alias":"production-app"}

or with a direct authority:

{"host":"host.example","user":"deploy","port":2222}

port is optional in direct mode and defaults to 22.

Each successful connect returns a new session_id and a one-time secret session_key. Pass both to every remote operation of that session; the key is never shown again, and session_list omits it. One agent can hold sessions to several servers, and several agents can share one server process, but each remote server can have only one session at a time. Only connect may authenticate. Call disconnect with the session_id to reopen a server, recover from transport loss, or drop a session whose key was lost.

Some stdio clients remove graphical-session and SSH-agent variables. On Linux, the connection library may query logind for the current UID's runtime directory and read the active user systemd manager environment. Recovery is restricted to DISPLAY, WAYLAND_DISPLAY, XAUTHORITY, XDG_RUNTIME_DIR, DBUS_SESSION_BUS_ADDRESS, SSH_AUTH_SOCK, SSH_ASKPASS, and SSH_ASKPASS_REQUIRE; an existing non-empty value always wins. The recovered mapping belongs only to the initial OpenSSH master subprocess. It is never passed to commands, transfers, or remote payloads. Unsupported platforms, missing systemd tools, invalid runtime state, or an unavailable user manager make recovery a safe no-op.

PIN and passphrase entry remains entirely in native OpenSSH and its normal system prompt. The MCP protocol accepts no credential or PIN.

Options

-h, --help                 show command help and exit
--version                  show the installed server version and exit
--connect-timeout SECONDS  SSH master deadline, 0.1..900 (default 120)
--command-timeout SECONDS  command deadline, 0.1..86400 (default 120)
--max-output-bytes BYTES   per-stream capture, 1024..67108864 (default 1048576)
--max-transfers COUNT      concurrent transfers per session, 1..16 (default 2)
--max-sessions COUNT       simultaneously open SSH sessions, 1..32 (default 8)
--log-level LEVEL          DEBUG, INFO, WARNING, or ERROR (default INFO)

The Codex example's 150-second per-tool deadline is longer than both server defaults. If you deliberately raise an individual command timeout above that value, raise the client's tool deadline as well or the client will cancel the call first.

Local upload, download, and spool paths are relative to the selected local root, not the caller's current working directory. That root is the source project for the launcher and the executable's directory for a standalone binary. See MCP tools for the complete operation contract and Security before granting write or sudo approval.

Troubleshooting

  • session_not_found: call connect first, or use session_list to find the current session ID.
  • invalid_session_key: pass the key returned by that session's connect. If it is lost, disconnect the session by ID and connect again.
  • already_connected: the server already has a session, whose ID is in the message; disconnect it before reopening or retrying after master loss.
  • session_limit_reached: disconnect an unused session or raise --max-sessions.
  • connection_start_failed: run ordinary OpenSSH with the same target and resolve host-key, token, proxy, or network errors locally.
  • An error saying no usable interactive system prompt is available means the bounded OpenSSH diagnostic matched a missing askpass route and session recovery did not find one. Start a normal graphical user session and import its environment into the user systemd manager; do not copy display or socket paths into shared MCP configuration.
  • connection_lost: disconnect that session, then reconnect only when another authentication is intended.
  • sudo_password_required: add an appropriately narrow NOPASSWD rule or avoid sudo_exec; cached authentication is deliberately ignored.
  • After a failed transfer, inspect the structured error and bounded stderr tail, fix permissions or free space, and start the same source/destination pair to resume its deterministic partial.