Skip to main content
Glama
2667741708

reliable-ssh-mcp

by 2667741708

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.

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:

  1. Read the introduction and Use structured tools instead of shell strings to understand the safety model.

  2. Read Start the server and begin with one SSH alias.

  3. Read Client roots and publication safety before uploading or downloading.

  4. Read Cross-platform scripts and line endings before sending scripts.

  5. Read Run long experiments in managed tmux sessions for durable jobs.

  6. Read Use the fleet server for multi-host operations, then the bastion and unified-registry sections only when managing multiple machines.

  7. Read password, inventory and Windows-persistence sections only when those deployment modes apply.

  8. 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_identity verifies the hostname, IP addresses, operating system, and NVIDIA GPUs.

  • probe_identity.execution_context reports the actual remote platform, shells, path separators, Python executable, and native line endings.

  • exec_argv runs one program with an exact argument array and shell=False.

  • run_script detects the verified target interpreter and executes a temporary Bash/Sh or PowerShell script with safe target encoding and line endings.

  • run_bash_script sends a complete script through stdin and enables set -Eeuo pipefail by default.

  • stat_path returns file metadata without parsing ls output.

  • read_file reads UTF-8 or Base64 content without cat or heredocs.

  • write_file writes UTF-8 or Base64 content atomically by default.

  • connection_status reports persistent pool PIDs, handshakes, requests, heartbeats, and reconnects when pooling is enabled.

  • list_local_roots reports the named local directories available to transfer tools.

  • start_remote_session starts a detached tmux job with a durable combined stdout/stderr log.

  • list_remote_sessions, remote_session_status, and read_remote_session observe running or completed jobs.

  • send_remote_session_input and stop_remote_session control a managed session.

  • read_remote_log reads 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_file copies a relative file from a selected root without putting its content in model context.

  • download_file stages 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 --branch

The 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.json

The 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.json

The fleet server adds:

  • multiple named servers with explicit ProxyJump, host-key aliases, and identity gates;

  • per-server tool groups and readonly, restricted, or unrestricted policy 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.log

The job continues when the originating SSH or MCP connection closes. Use:

  • list_remote_sessions after reconnecting or restarting MCP;

  • remote_session_status for running/completed state, exit code, and log size;

  • read_remote_session for recent pane output;

  • read_remote_log with next_offset_bytes for incremental training-log polling;

  • send_remote_session_input for literal interactive input;

  • stop_remote_session for 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:

  1. list_bastions shows the permitted boundaries.

  2. discover_lan_hosts scans exactly one allowlisted CIDR and port, with at most 256 hosts.

  3. inspect_lan_host_key collects the discovered SSH public keys and fingerprints.

  4. Verify a fingerprint through an independent trusted channel.

  5. onboard_discovered_host accepts that exact fingerprint, creates a strict temporary known-hosts file, and adds the target to the running fleet through ProxyJump.

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 60

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

For an individual check, run:

npm test
npm run check

For parser-safe numbered source inspection:

npm run inspect -- lines --file src/connection-pool.js --start 279 --end 340

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

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

Do 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.json

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

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    A
    maintenance
    A 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.
    7
    335
    98
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    An MCP server that gives AI agents SSH capabilities to execute commands, transfer files, and inspect remote systems through a preconfigured host list.
    46
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    MCP 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
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    SSH-based MCP server that enables remote execution of SSH commands, file transfers, and secure server management via the MCP protocol.
    ISC