Skip to main content
Glama
pmboxbiz

mcp-ssh-live

by pmboxbiz

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault

No arguments

Capabilities

Features and capabilities supported by this server

CapabilityDetails
tools
{
  "listChanged": true
}
logging
{}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
extensions
{
  "io.modelcontextprotocol/ui": {}
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
ssh_connectA

Add a new SSH host at runtime and open the connection. Call this first before using ssh_exec / ssh_spawn / ssh_upload / ssh_download on a new server.

Returns an alias (e.g. 'h-3f8a1b2c') that you pass as the 'host' argument to all other tools. If you supply your own alias it will be used instead.

Auth: provide exactly one of password or key_path. If neither is given, paramiko tries the SSH agent and default key files (~/.ssh/id_*).

Calling ssh_connect again with the same alias updates the credentials and reconnects.

ssh_disconnectA

Close the SSH connection to a host and remove it from the registry. Any running jobs on that host are terminated (their channels are closed). After this call the alias is no longer valid for other tools.

Returns removed=false (not an error) if the alias wasn't registered — useful for idempotent cleanup.

ssh_run_persistentA

Run a command on the remote host that SURVIVES MCP disconnect, Zed restart, or any interruption to the current session. The process runs via nohup in the background; its stdout and stderr are saved to log files on the remote server.

Returns immediately with the remote PID and log file paths. Check progress later with ssh_persistent_status or manually: ssh_exec('tail -n 50 /tmp/mcp-persistent//out.log') ssh_exec('ps -p -o pid,stat,etime,cmd --no-headers || echo DEAD')

WHEN TO USE THIS (not ssh_spawn):

  • Multi-hour or overnight tasks (training runs, large parsers, database migrations, long builds).

  • Any task where losing the MCP connection would be catastrophic.

  • Tasks you want to start now and check on later.

WHEN TO USE ssh_spawn INSTEAD:

  • You need real-time streaming output in chat.

  • The task takes under ~10 minutes and you will stay connected.

NOTE: logs are NOT auto-deleted. Clean up manually with: ssh_exec('rm -rf /tmp/mcp-persistent/')

ssh_persistent_statusA

Check the status of a persistent job started by ssh_run_persistent. Returns whether the process is still running, the last lines of its stdout and stderr logs, and the exit code (if it finished).

You need the PID and the log paths returned by ssh_run_persistent. Example: ssh_persistent_status(pid=12345, out_log='/tmp/mcp-persistent/abc123/out.log', err_log='/tmp/mcp-persistent/abc123/err.log')

ssh_execA

Run a shell command on the remote host and return its full stdout/stderr/exit status. Blocks until the command finishes or the wall-clock timeout expires (SIGTERM then SIGKILL).

WHEN TO USE ssh_exec:

  • Short commands that finish in under ~60s.

  • Fire-and-forget background tasks that must SURVIVE a disconnect (use nohup or screen so the process keeps running even if the MCP server restarts or Zed closes).

SURVIVING DISCONNECT — nohup pattern: ssh_exec('nohup python -u train.py > /tmp/train.log 2>&1 &')

process keeps running after disconnect; check later with:

ssh_exec('tail -n 50 /tmp/train.log') ssh_exec('ps aux | grep train.py')

SURVIVING DISCONNECT — screen pattern: ssh_exec('screen -dmS myjob bash -c "python train.py > /tmp/out.log 2>&1"')

reattach later: screen -r myjob

WARNING: ssh_spawn ties the remote process to the MCP channel — it dies on disconnect. Use ssh_exec+nohup/screen for tasks that must outlive the current session.

For real-time streaming output of a command you will watch to completion without disconnecting, use ssh_spawn + ssh_tail instead.

ssh_spawnA

Start a shell command on the remote host IN THE BACKGROUND and return a short job_id immediately (<100 ms). Use ssh_tail to stream output line-by-line while the job runs.

WHEN TO USE ssh_spawn:

  • You need real-time output in chat (builds, deploys, log tails).

  • The command takes more than ~60s but you WILL stay connected.

WARNING — PROCESS DIES ON DISCONNECT: ssh_spawn ties the remote process to the MCP server's SSH channel. If the MCP server restarts, Zed closes, or the connection drops — the remote process receives SIGHUP and dies. Do NOT use ssh_spawn for tasks that must survive a disconnect (multi-hour training runs, overnight parsers, etc.).

FOR TASKS THAT MUST SURVIVE DISCONNECT — use ssh_exec with nohup or screen instead: ssh_exec('nohup python -u train.py > /tmp/train.log 2>&1 &') ssh_exec('screen -dmS myjob bash -c "python train.py"') Then check progress with: ssh_exec('tail -n 50 /tmp/train.log') ssh_exec('screen -r myjob')

BUFFERING GOTCHA: many programs block-buffer stdout when stdin is not a TTY, so you see nothing until exit. Fix: pass pty=True, or use 'python -u' / PYTHONUNBUFFERED=1 / 'stdbuf -oL'. pty=True merges stderr into stdout.

ssh_tailA

Stream incremental output from a job started by ssh_spawn. Given a monotonic cursor 'since_line_no', return every buffered line with line_no > cursor (up to max_lines). If wait_ms > 0 and nothing new is available, block up to wait_ms milliseconds for new output. Returns still_running=False and exit_status when the job has finished.

Canonical polling loop (use this): cursor = 0 while True: r = ssh_tail(job_id, since_line_no=cursor, wait_ms=5000, max_lines=500, stream='both') for ln in r['lines']: show_to_user(ln) cursor = r['last_line_no'] if not r['still_running']: break

If r['buffer_truncated'] is True, older output was evicted from the ring buffer before you read it — either live with the gap or increase limits.ring_buffer_lines on the server. This tool does NOT open a new SSH connection per call; all data comes from in-memory buffers maintained by the spawn's reader threads.

ssh_list_jobsA

List every job registered on this mcp-ssh-live server — running, finished-but-not-yet-reaped, and crashed. Returns metadata only (job_id, cmd, host, label, started_at, finished_at, exit_status, status, pid, pty, stdout_lines, stderr_lines); use ssh_tail to fetch the actual output of a specific job.

Filter by host with the 'host' argument (alias or raw address). Pass include_finished=false to see only still-running jobs, which is typical when deciding whether to spawn a new one without hitting the max_jobs_per_host cap.

Finished jobs are kept in the registry for a grace window (reap_finished_after_sec, default 600 s) so you can still fetch their final tail output after they exit.

ssh_remove_jobA

Drop a job from the registry, freeing its slot against max_jobs_per_host and releasing its ring buffers.

Finished / crashed / killed jobs can always be removed. STILL-RUNNING jobs require force=True, which closes the SSH channel (sends EOF to the remote's stdin) but does NOT guarantee the remote process dies — for a hard kill call ssh_signal(KILL) FIRST and then ssh_remove_job.

Returns removed=True if a job was actually dropped, removed=False if the id wasn't present (a no-op, not an error — useful for idempotent cleanup scripts that don't know whether a job is still around).

ssh_signalA

Deliver a POSIX signal (TERM, KILL, INT, HUP, QUIT, USR1, USR2, STOP, CONT, and a few others) to a job started by ssh_spawn. Use TERM (polite, the default) for graceful shutdown; use KILL only when TERM fails to stop the job within a reasonable window.

Delivery strategy: we first try paramiko's in-channel send_signal (often ignored by OpenSSH sshd), then fall back to 'kill - ' on a fresh SSH session using the PID captured at spawn time. Both paths are attempted unless use_pid_fallback=False.

Pass wait_ms > 0 to block up to that many milliseconds waiting for the job to actually exit; the response then includes the real exit_status. Otherwise we return immediately with still_running indicating whether the signal took effect within the fire-and-forget window.

Common patterns:

Polite stop, wait up to 5 s:

ssh_signal(job_id, 'TERM', wait_ms=5000)

Hard kill, don't wait (verify via ssh_tail later):

ssh_signal(job_id, 'KILL')

Interrupt (like Ctrl-C):

ssh_signal(job_id, 'INT')

ssh_send_stdinA

Write bytes to the stdin of a running job (one spawned by ssh_spawn). Used for password prompts (sudo -S -p ''), REPL input (python, node), and any interactive CLI that expects keystrokes.

By default a trailing '\n' is appended so the remote program sees a complete line (this is what 'sudo -S' and most line-based REPLs need). Set newline=False to send raw bytes without the terminator — useful for control characters (e.g. data='\x03' to send ^C to a pty-mode job, or data='\x04' for ^D / EOF).

Set close_stdin=True to close the remote's stdin AFTER the write, which signals EOF to programs like 'cat', 'wc', 'sort'. Leaving it open (default) lets you make more writes to the same job.

IMPORTANT: for password input, pair this with ssh_spawn(pty=True) AND the command 'sudo -S -p ""'. Without pty=True, sudo refuses to read the password from a pipe on most distros. Without the -p "" flag, sudo writes its prompt to stdout, which mixes into ssh_tail output.

Payload size is capped at 64 KiB per call. For larger input use ssh_upload to place a file on disk, then have the command read from it.

ssh_uploadA

Upload a local file to the remote host via SFTP. Binary-safe; preserves arbitrary bytes unlike 'cat > file <<EOF' over ssh_exec.

Writes to <remote_path>.partial first, then renames atomically on success — a crashed upload never leaves a half-written file at the target path. Parent directories are auto-created (mkdir -p). If remote_path points at an existing directory, the local filename is appended (same as scp).

Size cap: max_bytes (default 100 MiB, hard ceiling 1 GiB). Returns a sha256 of the bytes actually transferred so the caller can verify integrity.

Optional mode (octal, e.g. 493 for 0o755) runs chmod on the remote after the transfer. A chmod failure is non-fatal — the bytes are already there; mode_set=null in the response means the chmod was skipped or rejected.

ssh_downloadA

Download a file from the remote host to a local path via SFTP. Binary-safe.

Writes to <local_path>.partial first, then atomically replaces the final path on success. A failed or cancelled download never leaves a truncated file where downstream tooling would pick it up.

Size cap: max_bytes (default 100 MiB, hard ceiling 1 GiB). Remote size is checked via sftp.stat BEFORE the transfer starts; files that exceed the cap fail fast. Caps are enforced streaming-side too, so a misreported remote size can't trick us into filling the local disk.

ssh_list_hostsA

List every SSH host this mcp-ssh-live server knows about, along with its runtime state (connected, active_jobs). Use this to discover available target aliases in a multi-host setup, to confirm that a --host flag or TOML entry landed as expected, and to check the per-host active-jobs count before spawning another job.

The response NEVER includes secrets — only the names of password environment variables and the paths of key files. The actual password / key contents are resolved only at SSH handshake time, not here.

Each entry has: alias, host, port, user, auth ('password_env' | 'key' | 'agent'), password_env (str or null), key (str or null), key_passphrase_env (str or null), connected (bool; whether we currently hold a live SSH transport), active_jobs (int; jobs registered on this host that are still running), is_default (bool).

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/pmboxbiz/mcp-ssh-live'

If you have feedback or need assistance with the MCP directory API, please join our Discord server