Skip to main content
Glama

ssh-mcp — persistent SSH sessions for coding agents

MCP server (stdio) keeping one long-lived local-OpenSSH process per session; tools behave like their local equivalents on the remote machine: ssh_exec, ssh_view, ssh_create, ssh_edit, ssh_grep, ssh_glob + lifecycle (ssh_connect, ssh_disconnect, ssh_list_sessions), bootstrap (ssh_setup_key) and capability probe (ssh_capabilities). Zero re-auth after first connect — stateful shell (cd/env) persists across calls. See PLAN.md for the settled design; comments in every file cite it by line.

Target use

Built for coding agents doing file operations from a Windows client against Unix file servers — the motivating case being project files under a Samba/CIFS network drive whose SMB mount is slow or flaky (UNC paths, stale caches, long round-trips). ssh-mcp bypasses SMB entirely: one persistent SSH session per server means view/create/edit/grep/glob/exec run at native speed with POSIX paths and state (cd, env) persisting across every agent call — no remount or drive letter involved.

Related MCP server: codex-serverops-mcp

Warning & disclaimer — use entirely at your own risk

  • ssh_exec runs arbitrary commands on the real server with no undo, rollback, or confirmation layer between agent and filesystem; a bad agent command can overwrite/delete what it likes. Verify targets (ssh_view, dry-run ls) before destructive ops.

  • Multi-session mixups edit the wrong host: every result echoes [session=...] — always pass session_name when more than one session is alive, and re-check which server you're on before writes.

  • The password= fallback keeps plaintext credentials in a local persisted spec while sessions run; require explicit consent and rotate passwords on production file servers (details under Honest limits).

  • MIT licensed: the software is provided "as is"; all damages, costs or losses from using it are fully on the user, not the author — see LICENSE for the exact terms.

Build & run

pnpm install                      # needs pnpm + recent Node (developed/tested on v24)
pnpm build          # tsc -> dist/
node --test "dist/tests/*.test.js"   # unit tests (no network); integration auto-skips without SSH_IT_* env

Prerequisites: the local machine must have an OpenSSH ssh binary on PATH (Windows OpenSSH client verified working); remote hosts need POSIX shells (bash/sh) — Windows-remote is unsupported by design.

Wire into opencode / Claude Desktop (stdio MCP)

Build once first (pnpm build), then point the harness at the compiled entrypoint using absolute paths — MCP servers are launched with an arbitrary cwd and a different PATH, so relative/PATH-resolved commands break. Get the absolute node path with node -e "console.log(process.execPath)".

opencode (opencode.json) — <REPO> = this repo's absolute root:

{ "mcp": { "ssh-jmcp": { "type": "local", "command": ["<ABS node.exe>", "<REPO>/dist/src/server/index.js"] } } }

Claude Desktop (claude_desktop_config.json):

{ "mcpServers": { "ssh-jmcp": { "command": "<ABS node.exe>", "args": ["<REPO>/dist/src/server/index.js"] } } }

Usage flow

  1. Password host? ssh_setup_key(host, port, user, password) — one-time bootstrap; later connects use the generated key automatically (no password in any later call or persisted spec). If that account can't write ~/.ssh/authorized_keys (infra perms), fall back to ssh_connect ... password= instead — see deviation note below. On servers where .ssh is permanently read-only (file-server setups like ours), skip bootstrap entirely: it can never succeed, use password= directly every connect.

  2. ssh_connect(host, port, user[, identity, name, password]) → session name (user@host default); idempotent for an alive name.

  3. Then exec/view/create/edit/grep/glob freely — pass session_name whenever more than one session is alive (every result starts with [session=...]).

Remote paths are POSIX: use /, never \ (builders reject backslashes with that hint); relative paths resolve against the session's current remote cwd.

Integration test on a real host

export SSH_IT_HOST=<host> SSH_IT_PORT=22 SSH_IT_USER=<user>   # + optional SSH_IT_IDENTITY or SSH_IT_PASSWORD (accepted fallback, see limits)
pnpm test                                                     # connect -> scratch tree roundtrip -> cleanup + disconnect
# PowerShell: $env:SSH_IT_HOST="<host>"; $env:SSH_IT_PORT="22"; ... — the vars are plain env, any shell works.

Runs only when those env vars are set; skips silently otherwise. The test touches only /tmp/ssh_it_mcp* on the target (create/edit/read/cleanup of that scratch dir) — require explicit user consent before pointing it at a non-test host.

Honest limits (PLAN.md §6, surfaced in tool descriptions too)

  • No interactive remote programs — anything reading stdin (vim, REPLs, nested ssh) desyncs the session pipe; use non-interactive invocations (sed -i, batch flags).

  • Hard command timeout kills the whole session process (shortcut: upgrade = per-command subshell backgrounding); remote orphan children of killed sessions may linger. Remote reconnects with a [session reconnected — shell state lost] warning.

  • stdout+stderr combined (not separated streams); find glob is basename approximation; grep engine ladder picked once at connect from caps — rg > ag > ugrep > GNU grep (--exclude-dir=.svn/.git) > portable retry (§9 extended probe list also covers sed/patch/diff/stat/wc/python3 for future tiers). Modern grep form skips VCS dirs for file-server tree speed; portable/busybox retry omits GNU-only flags.

  • ssh_edit rewrites whole files via heredoc (shortcut: upgrade = remote patch for >2 MB files — §9 tiers pending); write-back is atomic now (heredoc into <path>.<tok>.tmp then same-fs mv -f, partial file never observable mid-write), race-guard prefers O(1) stat mtime+size tuple when GNU stat probed (sha256 chain stays the floor for other hosts); size preflight refuses >2 MB files before full read (max_bytes override).

  • Windows local client is fine (argv spawn, no PTY); CRLF content from a Windows agent stays CRLF on the remote file.

  • ssh_setup_key uses SSH_ASKPASS_REQUIRE=force — OpenSSH builds lacking it can't use this flow; the structured error tells you to add the printed .pub manually. Askpass path verified working on Windows OpenSSH (2026-10-20 LAN test host: password auth reached remote, stty stderr noise harmless); pubkey append success depends on the account's ~/.ssh perms — where it is permanently read-only, bootstrap can never succeed; use the password= fallback rather than retrying. Bootstrap only creates/extends (mkdir -p, chmod 700 ~/.ssh, pubkey append) — it never overwrites or deletes remote files.

  • Accepted deviation (user request 2026-10-20): ssh_connect password= runtime fallback for hosts where authorized_keys can't be written — BatchMode is omitted and the password is fed via a temp askpass script + SSH_ASKPASS_REQUIRE=force with no tty. Plaintext password lives in that session's persisted spec + a temp askpass script while the session runs (deleted on process exit; orphaned if the server itself is SIGKILLed). Enable only with explicit user consent outside test hosts; prefer keys wherever possible; rotate any test-host password when done.

  • Session specs persist at {homedir}/.config/ssh-mcp/sessions.json, shared by any running ssh-mcp server: two harnesses both holding servers for the same target race on reconnect (last connect wins) — keep one instance per host/user pair.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables agent-style remote development over SSH with file operations and command execution, exposing native tools for searching, editing, and transferring files on remote machines.
      2
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      A Windows-first MCP server that maintains persistent stateful SSH sessions to Linux servers, enabling Codex to run commands, manage files, and use sudo without exposing credentials.
      39 PyPI
      1
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables coding agents to treat a remote Linux host over SSH as a local working tree, exposing editor-semantics operations, background job management, artifact transfer, and guard hooks through MCP.
      1
      MIT
    • A
      license
      A
      quality
      C
      maintenance
      Enables AI assistants to run shell commands, read, write, edit, search, and stream files on remote SSH servers and Docker containers, including nested multi-layer chains, without installing any runtime on the target hosts. It auto-injects a self-contained Go helper with adaptive zstd compression for fast transfers and process control.
      9
      MIT