reliable-ssh-mcp
Enables structured command execution, file operations, and fleet management on Linux/OpenSSH hosts, including interpreter-aware script execution and target-native line endings.
Allows probing remote hosts to verify NVIDIA GPU presence and details through probe_identity.
Provides managed tmux session tools for starting, listing, monitoring, reading logs, sending input to, and stopping long-running remote jobs.
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., "@reliable-ssh-mcpRun nvidia-smi on all production hosts and summarize any failures"
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.
reliable-ssh-mcp
English | 简体中文
Execution policy can now be inspected with get_execution_policy and reloaded
from disk with reload_config. See exploration and training
for interpreter selection, supported reload fields and the initial restart requirement.
reliable-ssh-mcp is a local STDIO MCP server for running structured commands and fleet operations on Linux/OpenSSH and Windows/OpenSSH or pinned-key Windows/Plink hosts. It avoids sending user-controlled command data through unnecessary shell quoting layers.
Version 0.9.1 hardens read-only command profiles, adds structured MCP outputs,
keeps runtime versions aligned with the package, and fixes CLI help and audit
decisions. It builds on the health-aware connection pool and cross-platform
features introduced in 0.9.0. See the changelog.
Architecture decisions are documented in docs/adr.
Recommended reading order
This README keeps capability documentation grouped by topic so experienced users can find a reference quickly. First-time users should read it in this order instead of following the file strictly from top to bottom:
Read the introduction and Use structured tools instead of shell strings to understand the safety model.
Read Start the server and begin with one SSH alias.
Read Client roots and publication safety before uploading or downloading.
Read Cross-platform scripts and line endings before sending scripts.
Read Run long experiments in managed tmux sessions for durable jobs.
Read Use the fleet server for multi-host operations, then the bastion and unified-registry sections only when managing multiple machines.
Read password, inventory and Windows-persistence sections only when those deployment modes apply.
Contributors should finish with Run the checks, the parser-safe inspection guide, and the changelog.
Version history is maintained in CHANGELOG.md; the Chinese version is CHANGELOG.zh-CN.md.
Related MCP server: SSH MCP Server
Use structured tools instead of shell strings
The server exposes structured execution, file, connection, and session tools:
probe_identityverifies the hostname, IP addresses, operating system, and NVIDIA GPUs.probe_identity.execution_contextreports the actual remote platform, shells, path separators, Python executable, and native line endings.exec_argvruns one program with an exact argument array andshell=False.run_scriptdetects the verified target interpreter and executes a temporary Bash/Sh or PowerShell script with safe target encoding and line endings.run_bash_scriptsends a complete script through stdin and enablesset -Eeuo pipefailby default.stat_pathreturns file metadata without parsinglsoutput.read_filereads UTF-8 or Base64 content withoutcator heredocs.write_filewrites UTF-8 or Base64 content atomically by default.connection_statusreports persistent pool PIDs, handshakes, requests, heartbeats, and reconnects when pooling is enabled.list_local_rootsreports the named local directories available to transfer tools.start_remote_sessionstarts a detached tmux job with a durable combined stdout/stderr log.list_remote_sessions,remote_session_status, andread_remote_sessionobserve running or completed jobs.send_remote_session_inputandstop_remote_sessioncontrol a managed session.read_remote_logreads the newest log bytes or continues from a returned offset.
It also exposes path-based transfer tools whose local side is restricted to named roots:
upload_filecopies a relative file from a selected root without putting its content in model context.download_filestages a remote file beside its destination, then publishes it atomically without overwriting an existing file.
Local tool parameters never accept absolute paths. Select local_root: "project"
or another name returned by list_local_roots, then pass a relative
local_path. A bare --local-root . is shorthand for
--local-root project=.. Repeat the option to allow more than one directory:
node .\src\index.js `
--ssh-target example-gpu `
--local-root project=. `
--local-root 'shared=${RELIABLE_SSH_SHARED_ROOT}'Relative roots are resolved from the MCP process working directory. This lets a
project-scoped client launch the same published package in different projects
without changing the package or hard-coding a developer's drive path. When no
--local-root option is supplied, RELIABLE_SSH_LOCAL_ROOTS may contain a JSON
object such as
{"project":".","shared":"${RELIABLE_SSH_SHARED_ROOT}"}.
Each execution result separates stdout, stderr, exit_code, timed_out, duration_ms, and truncation metadata. Text written to stderr does not make a successful process fail when its exit code is zero.
Tools return both human-readable JSON text and an equivalent
structuredContent.data object. Errors use structuredContent.error, with a
stable policy code and a non-executable next_step explanation when available.
Security model and read-only policy
MCP annotations are hints for clients; server-side policy remains authoritative.
In Fleet readonly mode, listing a name in readOnlyPrograms is necessary but
not sufficient. The executable must also have a built-in positive argv profile,
and every supplied argument must match that profile. Programs without a profile
fail with READONLY_PROFILE_UNAVAILABLE; unsupported arguments fail with
READONLY_PROFILE_DENIED. Neither triggers a request to bypass policy. Complex
tools such as find, sed,
journalctl, and ip are not supported in readonly mode. Supporting one
requires a dedicated read-only profile or tool that has been code-reviewed and
tested. A reviewed template may be used under restricted policy, but templates
never bypass the read-only argv profile.
For a read-only Git status, the safety flags are global arguments and therefore must appear before the subcommand:
git --no-pager --no-optional-locks status --short --branchThe policy allowlist reduces mistakes but is not an operating-system sandbox. Use a least-privilege SSH account, remote filesystem permissions, containers or service-level isolation where untrusted prompts or models are in scope.
The service does not automatically:
turn user approval into a bypass for local-root, credential, or host identity checks;
replay ordinary commands, writes, uploads, or training launches after failure;
switch to another route when a configured route fails;
read local files outside effective named roots;
accept unknown SSH host keys or scan undeclared networks.
Cross-platform scripts and line endings
Agents should call probe_identity before nontrivial work and follow its
execution_context instead of guessing from the client machine. Structured
exec_argv never parses shell syntax, while script tools must match a shell
reported by the target. Prefer run_script for multiline shell work. The
legacy run_bash_script remains available for compatibility but is only valid
when execution_context.shells.bash is present.
UTF-8 write_file, upload_file, and fleet upload_directory use
line_endings: "auto" by default. Auto mode converts POSIX and shell scripts
to LF, Windows batch files to CRLF, and other confirmed UTF-8 text to the
target platform's native convention. Files containing NUL bytes, invalid UTF-8,
unknown binary-like extensions, or files larger than the normalization limit
are preserved. Base64 writes are always preserved.
Set line_endings explicitly when needed:
auto: safe target-aware conversion, the default;preserve: byte-for-byte transfer;lf: force LF for confirmed text;crlf: force CRLF for confirmed text.
Downloads remain byte-preserving so retrieving a remote project never silently rewrites its contents.
Use --audit-log to append redacted JSONL events. Audit records contain metadata and hashes, not stdout, stderr, stdin, script bodies, or file contents.
Use the fleet server for multi-host operations
Create a local fleet configuration from the public example, then replace the documentation addresses and paths with your own values:
Copy-Item -LiteralPath .\config\fleet.example.json -Destination .\config\fleet.jsonThe real config/fleet.json is intentionally ignored because it normally contains private hostnames, IP addresses, and local paths. Start the fleet server with:
node .\src\index.js --fleet-config .\config\fleet.jsonThe fleet server adds:
multiple named servers with explicit
ProxyJump, host-key aliases, and identity gates;per-server tool groups and
readonly,restricted, orunrestrictedpolicy modes;argv program allowlists and parameterized command templates;
persistent multi-connection pools backed by long-running OpenSSH/Python sessions;
managed local port forwards, named forwards, and SOCKS5 proxies;
zero-context SFTP file transfer;
background tar.gz directory upload/download with stage progress and cancellation;
separate redacted JSONL audit logs per server.
allowlisted LAN discovery through named bastions and explicit CIDR/port boundaries;
SSH host-key inspection plus fingerprint-confirmed ephemeral onboarding;
named server groups;
durable tmux sessions for training and other long-running programs.
The fleet exposes 44 tools. Use a fixed single-server registration when a task needs the smaller target-specific surface.
Run long experiments in managed tmux sessions
exec_argv remains synchronous: it waits for the process, applies a timeout,
and kills the process group after a timeout. SSH keepalive and the persistent
connection pool keep transport sessions healthy, but they do not detach a
training process from SSH.
Use start_remote_session for long-running work. The tool safely quotes the
exact program argv, starts it inside a detached tmux session, captures combined
stdout/stderr in the pane, and appends the same output to a durable log under:
~/.local/state/reliable-ssh-mcp/sessions/<session>/output.logThe job continues when the originating SSH or MCP connection closes. Use:
list_remote_sessionsafter reconnecting or restarting MCP;remote_session_statusfor running/completed state, exit code, and log size;read_remote_sessionfor recent pane output;read_remote_logwithnext_offset_bytesfor incremental training-log polling;send_remote_session_inputfor literal interactive input;stop_remote_sessionfor an explicit stop.
Linux targets must have tmux, bash, and tee installed. The MCP reports a
clear dependency error and does not fall back to an SSH-bound process when they
are missing.
Discover and onboard LAN targets through a bastion
Discovery is disabled until a private fleet configuration declares a bastion. The bastion references an already configured server, and every permitted CIDR, TCP port, and scan-size limit is explicit:
{
"bastions": {
"lab_lan": {
"server": "jump_server",
"allowedCidrs": ["192.168.10.0/24"],
"allowedPorts": [22],
"maxHosts": 256,
"defaultUser": "research",
"onboardDefaults": {
"mode": "restricted",
"allowPrograms": ["df", "hostname", "nvidia-smi", "python3"]
}
}
},
"serverGroups": {
"gpu_nodes": []
}
}The controlled workflow is:
list_bastionsshows the permitted boundaries.discover_lan_hostsscans exactly one allowlisted CIDR and port, with at most 256 hosts.inspect_lan_host_keycollects the discovered SSH public keys and fingerprints.Verify a fingerprint through an independent trusted channel.
onboard_discovered_hostaccepts that exact fingerprint, creates a strict temporary known-hosts file, and adds the target to the running fleet throughProxyJump.
Onboarded servers are intentionally ephemeral: they disappear when the MCP process exits. Add a verified target to the private fleet JSON when it should become permanent.
For project-scoped Codex registrations, set the MCP working directory to the
project and pass --local-root project=. together with --fleet-config.
Command-line roots override roots in every fleet server for that MCP process,
so one private fleet file can be reused without redirecting downloads to the
directory of another project.
The fleet JSON format also accepts named roots inside defaults or an
individual server object:
{
"defaults": {
"localRoots": {
"project": ".",
"shared": "${RELIABLE_SSH_SHARED_ROOT}"
}
}
}The checked-in example contains only "project": ".". Put real server
aliases, IP addresses, key paths, shared-directory paths, and audit locations
only in an ignored user configuration or environment variables.
Single-server instances can also reuse authenticated connections:
node .\src\index.js --ssh-target server --pool-size 1 --keepalive-interval 30 --heartbeat-interval 60OpenSSH receives protocol keepalive options. The persistent Python runner uses a separately configured idle ping heartbeat, without launching hostname or GPU subprocesses. Busy sessions skip application heartbeats. A 30-second protocol keepalive and 60-second application heartbeat are conservative starting values.
One verified connection is sufficient to serve a request; spare connections are filled in the background. Idle connections without a successful response for 60 seconds are pinged before use. Identity cache entries expire after five minutes and are invalidated when pool generation changes or no recently responsive connection remains. Every new connection verifies the configured identity before serving user operations.
Only built-in probe_identity and ping operations may retry once after a transport failure. User commands, file writes, and training launches are never automatically replayed; identity mismatch and explicit controller closure stop retries. No route is switched automatically. Connection status reports response freshness, classified errors, heartbeat counts, and implementation: health-pool-v2.
Source and transport-configuration changes require restarting/reconnecting the MCP server process. reload_config reloads execution policy only; closing SSH connections does not load new JavaScript or transport settings. Do not force-restart a shared MCP process with in-flight operations.
Run the checks
Run the complete fixed verification pipeline before committing or publishing:
npm run verifyFor an individual check, run:
npm test
npm run checkFor parser-safe numbered source inspection:
npm run inspect -- lines --file src/connection-pool.js --start 279 --end 340Complex regular expressions belong in a project-relative JSON query file, not in a PowerShell command. See the code-inspection guide.
Run an end-to-end check against one configured SSH alias:
npm run smoke -- --ssh-target example-gpu --expected-hostname gpu-host.example --expected-ip 192.0.2.10 --expected-gpu "RTX 4090" --local-root project=.The smoke test verifies the MCP handshake, tool list, host identity, special-character argv handling, stderr handling, Bash stdin execution, atomic file writing, file reading, stat metadata, and cleanup.
For a production host where no remote mutation is acceptable, run the dedicated read-only smoke test. It checks schemas, identity, path metadata, and an optional profiled command without attempting any remote write or delete tool:
npm run smoke:readonly -- --fleet-config .\config\fleet.json --server readonly_hostStart the server
node .\src\index.js `
--ssh-target example-gpu `
--expected-hostname gpu-host.example `
--expected-ip 192.0.2.10 `
--expected-gpu "RTX 4090" `
--local-root project=. `
--audit-log 'D:\path\to\logs\reliable-ssh-example.jsonl'The target must be an OpenSSH alias that already handles authentication, host-key verification, and any ProxyJump route.
For OpenSSH aliases with an identity gate, the server runs ssh -G before the first identity probe. By default the effective HostName must match expected-ip. NAT hosts can set expected-route-host to the public SSH route while retaining the private interface address in expected-ip for remote identity verification.
For a password-authenticated Windows host, the fixed server can use the installed PuTTY Plink client without exposing the password in process arguments:
node .\src\index.js `
--ssh-target administrator@192.0.2.20 `
--ssh-flavor plink `
--ssh-command C:\path\to\plink.exe `
--password-file C:\path\to\secrets\server.password `
--host-key "ssh-ed25519 <pinned-public-key>" `
--remote-python python `
--pool-size 1 `
--keepalive-interval 30 `
--heartbeat-interval 60Do not commit the password file. The Plink mode requires a pinned host key and runs with -batch, so an unexpected host key cannot be accepted silently.
Client roots and publication safety
Version 0.6.0 supports the MCP client's Roots capability as a portable fallback.
--client-roots fallback is the default: explicitly configured startup or
environment roots win, and the client is queried only when none were configured.
Use --client-roots merge to add client-provided file:// roots beside explicit
roots, or --client-roots disabled to ignore the capability. Non-file URIs are
ignored. Every effective root remains inspectable through list_local_roots, and
all transfer paths must still be relative to one named root.
The npm package is publishable and uses a files allowlist. The private
config/fleet.json is not included; only config/fleet.example.json is packed.
Always inspect npm pack --dry-run before publishing.
Interactive password input through MCP
For a server already configured with sshFlavor: "plink" and a verified hostKey, call
provide_connection_password with the authorized login password (and server for fleet),
then call probe_identity. Supplying a password does not itself verify login and never
changes the remote account password. A wrong password fails; do not replay mutations.
The agent may automatically enter a password that the user has supplied for an authorized connection. Do not ask the user to type it manually merely because normal commands use batch mode. Never infer a password, disable host-key verification, or use a password to bypass an operation policy. If bootstrapping through an authorized, trusted bastion using a managed interactive session, first read the session and confirm the password prompt; send the password only then, never to an ordinary shell prompt.
The Plink tool writes a process-scoped temporary password file with user-only permissions
(Windows ACL restricted before writing; Unix directory 0700/file 0600). Plink reads it
using -pwfile with -batch and the pinned -hostkey. Neither command argv nor this
service's audit log includes the password or its hash. MCP clients may retain tool inputs:
this is not a guarantee about third-party client history. The temporary file is plaintext
inside the protected directory, not an encrypted credential vault.
clear_connection_password removes that temporary file, closes idle command sessions,
and restores any startup passwordFile setting. Changes are rejected while operations
are active. Normal process exit removes session files; forced termination or power loss
can leave a protected temporary directory. Restarting requires supplying the password again.
This feature does not integrate a persistent keyring library and does not change OpenSSH
authentication, existing SSH aliases, or operating-system passwords. Plink targets must
use their own supported host/session configuration; OpenSSH ProxyJump aliases are not
automatically translated. Reload the MCP service after upgrading to expose the new tools.
Run the full fleet verification
npm run smoke:fleet -- .\config\fleet.jsonThe fleet smoke test uses the servers in your local fleet configuration and verifies persistent connections, explicit ProxyJump, identity gates, allowlisted argv, templates, TCP forwarding, SOCKS5, background archive transfer, progress, content integrity, cleanup, and connection shutdown.
Per-server inventory and operating guidance
Each fleet server supports optional serverInfo (an inline object) or
serverInfoFile (a JSON file relative to the fleet configuration directory).
Use only one; inventory is never inherited from fleet defaults because it describes
an individual host. Multiple connection routes to one host can share one file.
Single-server instances accept --server-info-file <path>; relative paths resolve
against the launch working directory.
{
"description": "Research GPU server",
"cpu": "Intel Core i9",
"memoryGb": 64,
"storageTb": 4,
"gpus": [{"model": "RTX 4090", "count": 1, "memoryGbPerGpu": 48}],
"os": "Ubuntu",
"usageGuidance": "Use the data directory only after verifying its backing disk is mounted.",
"notes": "Example inventory; replace with measured hardware and exact paths.",
"source": "Operator-provided example"
}All fields are optional. verifiedAt accepts an ISO 8601 timestamp with a timezone.
Capacity values must be positive numbers; GPU counts must be positive integers.
memoryGb and memoryGbPerGpu express customary nominal RAM/VRAM capacities;
storageTb is nominal physical storage in decimal TB, not free space. Put exact
measurements, disk layout and unit caveats in notes.
list_servers includes the complete server_info object for every host.
get_server_info({server: "name"}) returns one fleet host's saved inventory;
a single-server instance uses get_server_info({}). Neither requires an SSH
connection. Missing inventory is null; credentials are never included.
Startup instructions intentionally do not embed per-server inventory or
usageGuidance. After selecting a host, call get_server_info to load those
details on demand. This keeps tool discovery compact and prevents one server's
guidance from being repeated across unrelated contexts. Guidance does not change
execution policy, grant permissions or enforce filesystem restrictions.
Files are loaded at startup. Restart/reconnect the affected MCP instances after editing code or inventory. Hardware metadata is a dated snapshot; use remote read-only tools to confirm mounts, available memory and disk space before a job.
Unified server registry
Use registry version 2 to share identity, inventory and policy across named connection routes. A scoped independent process loads the same registry with --server <name> --route <route>. See shared registry guide. Existing version 1 configurations remain supported.
Windows persistent connections
The pooled runner starts with a short Python command and sends its source through
SSH standard input. The bootstrap reads the exact UTF-8 byte count before accepting
newline-delimited JSON requests. This avoids sending the entire daemon as a long
command line to the Windows SSH shell. Python starts with -X utf8 so the runner's
JSON and Unicode file paths use UTF-8.
For registry version 2, put remotePython in the selected route, alongside
sshTarget. Use a verified Python executable path. A Windows host may have stale
inherited USERPROFILE or COMPUTERNAME variables; use probe_identity and actual
file-path checks when selecting directories. Keep Windows-only program allowlists
and omit Linux templates such as df or systemctl.
Reload the MCP process after code changes; closing pooled SSH connections alone
does not reload its JavaScript. Validate through the reloaded MCP with
probe_identity, exec_argv, and a Unicode-path write_file/read_file round trip.
Windows PowerShell 5 scripts should explicitly select UTF-8 output when returning
non-ASCII command output; Python's UTF-8 mode does not change other programs' code
pages. This bootstrap fix does not make Linux-only templates or tmux available on
Windows, and does not change Plink transfer/tunnel restrictions.
This server cannot be deployed
Maintenance
Related MCP Connectors
MCP server for mandates, delegation, policy-gated execution, credential grants, and audit.
The MCP server for Azure DevOps, bringing the power of Azure DevOps directly to your agents.
MCP server with quote and live cryptocurrency price tools, local and cloud-deployed transports.
Run commands and read/write files on your servers over Termalin's keyless tunnels (hosted MCP).
Related MCP Servers
- AlicenseBqualityAmaintenanceA server that enables secure interaction with remote SSH hosts through standardized MCP interface, providing functions like listing hosts, executing commands, and transferring files using native SSH tools.733598MIT
- AlicenseNot gradedqualityDmaintenanceAn MCP server that gives AI agents SSH capabilities to execute commands, transfer files, and inspect remote systems through a preconfigured host list.46MIT
- FlicenseNot gradedqualityDmaintenanceMCP server for infrastructure discovery and remote management, enabling SSH command execution, file transfer, log tailing, and machine/service inventory with a companion web dashboard.2-
- AlicenseNot gradedqualityCmaintenanceSSH-based MCP server that enables remote execution of SSH commands, file transfers, and secure server management via the MCP protocol.ISC