mcp-ssh-live
Click on "Install 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., "@mcp-ssh-livetail the nginx access log on production"
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.
mcp-ssh-live
Interactive, streaming SSH tool for LLM agents via MCP (Model Context Protocol).
Lets LLM agents (Claude, Cursor, Zed) spawn long-running remote commands and watch their output arrive line-by-line in chat — instead of blocking for hours waiting for a ssh_exec to return.
┌─────────────┐ JSON-RPC/stdio ┌──────────────────┐ SSH ┌─────────────┐
│ Zed / │ ────────────────── │ mcp-ssh-live │ ───────────────── │ your remote │
│ Claude / │ │ (Python) │ │ server(s) │
│ Cursor │ │ │ │ │
└─────────────┘ └──────────────────┘ └─────────────┘
^ ^
| |
ring buffers SFTP + exec
+ reader threadsWhy this exists
Other SSH-MCP servers (tufantunc/ssh-mcp, classfang/ssh-mcp-server, AiondaDotCom/mcp-ssh, …) are all request/response: send a command, wait for it to finish, get the full output. Long jobs — parsers, builds, deploys, log tails — are unusable. The LLM either blocks past the MCP client's 60 s timeout, or falls back to nohup … & + tail -n 10 log polling that loses lines and has no kill-signal story.
mcp-ssh-live splits the one SSH primitive into spawn + tail + signal + stdin, so an agent can:
Start
python parser.pyon a remote box, get ajob_idin <100 ms.Poll
ssh_tail(job_id, since_line_no, wait_ms=5000)in a loop and stream output into the chat live.Send SIGTERM / SIGKILL / SIGINT via
ssh_signalwhen the user wants to stop.Feed
sudopasswords or REPL input viassh_send_stdin.Upload / download files via SFTP (
ssh_upload/ssh_download) — binary-safe, with sha256 on both sides.Manage jobs across multiple hosts simultaneously.
Related MCP server: SSH MCP Server
Features
10 MCP tools covering synchronous exec, streaming spawn+tail, signals, stdin, SFTP, host/job registry management.
OpenSSH-compatible signal delivery. Captures the remote PID at spawn time, falls back to
kill -SIG <pid>on a fresh exec channel when paramiko's in-channelsend_signalis ignored by the server (which is the common case).Ring-buffered output (default 10 000 lines per stream) with condition-variable-backed blocking
wait_mssossh_tailis near-live without busy-polling.Multi-host: one process manages several SSH targets; each tool call can pick a host by alias.
Auto-reconnect on transient network / sshd hiccups (configurable retries + delay).
Graceful shutdown: TERM → 5 s grace → KILL for every running remote process when the server exits.
Opt-in disk log mirror (
--log-dir) — every streamed line also written to<log_dir>/<job_id>.logso full output survives ring-buffer eviction and server restarts.Secrets never leak into MCP responses: passwords live in env vars, only variable names appear in
ssh_list_hosts.
Quick start
1. Install
pipx install mcp-ssh-liveOr, for a project-local editable install:
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e .Requires Python 3.10+.
2. Try it from the command line
# Start the MCP server — no SSH credentials needed at startup
mcp-ssh-liveThe server starts empty and waits for an MCP client to connect. Credentials are provided at runtime by the agent via ssh_connect (see step 4). If you prefer to pre-configure a host at startup, you can still pass --host, --user, --password-env flags — see Configuration.
3. Register with an MCP client
Zed
Edit ~/.config/zed/settings.json (or .zed/settings.json for per-project).
Windows (use the Python executable directly — Zed doesn't pick up PATH the same way as a terminal):
Find your Python path: open a terminal and run
where python.
{
"context_servers": {
"ssh-live": {
"enabled": true,
"command": "C:/path/to/python.exe",
"args": ["-m", "mcp_ssh_live"],
"env": {
"FASTMCP_DISABLE_VERSION_CHECK": "1",
"PYTHONUNBUFFERED": "1"
},
"settings": {}
}
},
"agent": {
"always_allow_tool_actions": true,
"always_allowed_tools": {
"ssh_connect": true, "ssh_disconnect": true,
"ssh_exec": true, "ssh_spawn": true, "ssh_tail": true,
"ssh_signal": true, "ssh_send_stdin": true,
"ssh_list_jobs": true, "ssh_remove_job": true,
"ssh_upload": true, "ssh_download": true, "ssh_list_hosts": true
}
}
}macOS / Linux (if installed via pipx — binary is in PATH):
{
"context_servers": {
"ssh-live": {
"source": "custom",
"enabled": true,
"command": "mcp-ssh-live",
"args": [],
"env": {
"FASTMCP_DISABLE_VERSION_CHECK": "1",
"PYTHONUNBUFFERED": "1"
}
}
},
"agent": {
"always_allow_tool_actions": true,
"always_allowed_tools": {
"ssh_connect": true, "ssh_disconnect": true,
"ssh_exec": true, "ssh_spawn": true, "ssh_tail": true,
"ssh_signal": true, "ssh_send_stdin": true,
"ssh_list_jobs": true, "ssh_remove_job": true,
"ssh_upload": true, "ssh_download": true, "ssh_list_hosts": true
}
}
}Save the file. No restart needed in most cases, but if the indicator stays red — close and reopen Zed.
3b. Enable in the Agent Panel
Open the Agent Panel (right sidebar).
Click
···(top-right of the panel) → Settings → MCP Servers.Find
ssh-liveand toggle it ON.The indicator next to
ssh-liveshould turn green within a few seconds.
If it stays red — check
Zed.log: Command Palette →zed: open logand look forssh-live. The most common fix: thecommandpath in settings is wrong. Runwhere python(Windows) orwhich python(macOS/Linux) to get the correct path.
There's a full config template at .zed/settings.json.example.
Claude Desktop
~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"ssh-live": {
"command": "mcp-ssh-live",
"args": [],
"env": { "FASTMCP_DISABLE_VERSION_CHECK": "1" }
}
}
}Cursor
~/.cursor/mcp.json — same shape as Claude Desktop.
4. Ask your agent
Once registered, just tell the agent your SSH details in chat. It calls ssh_connect(...) automatically — credentials never touch the config file.
Stream a live log:
Connect to 1.2.3.4 as root, password is
···. Runtail -f /var/log/syslogfor 30 seconds, then stop.
The agent picks ssh_spawn → loops ssh_tail(wait_ms=5000) → ssh_signal("TERM") → ssh_remove_job, streaming lines into the chat the whole time.
Upload a local file to the server:
Connect to 1.2.3.4 as root, password is
···. UploadC:/Users/me/builds/app-1.0.zipto/opt/app/releases/app-1.0.zipon the server.
The agent calls ssh_connect(...) then ssh_upload(local_path="C:/Users/me/builds/app-1.0.zip", remote_path="/opt/app/releases/app-1.0.zip") — binary-safe, atomic, with sha256 verification printed in the reply.
Key auth instead of password:
Connect to 1.2.3.4 as deploy using key
~/.ssh/id_ed25519. Upload./dist/bundle.jsto/var/www/html/bundle.js.
Multiple servers at once:
Connect to prod at 1.2.3.4 and stage at 10.0.0.5 (both as root, password
···). Deploy./release.tar.gzto/srv/app/on both.
Staged deploy — save credentials once, deploy step by step:
First, register both servers by alias so you don't repeat credentials every time:
Save SSH connection to 1.2.3.4 as
deploy_test— userdeploy, password···. Save SSH connection to 5.6.7.8 asdeploy_production— userdeploy, password···.
The agent calls ssh_connect(host="1.2.3.4", user="deploy", password="...", alias="deploy_test") and the same for deploy_production. Both aliases stay active for the rest of the session.
Then deploy to test first:
Upload everything from the local
./deploydirectory to/srv/app/ondeploy_test. Run/srv/app/healthcheck.shafterwards and show me the output.
The agent calls ssh_upload for each file in ./deploy, then ssh_exec(command="/srv/app/healthcheck.sh", host="deploy_test") and streams the result.
If the output looks good, deploy to production:
Looks good. Now upload the same
./deploydirectory to/srv/app/ondeploy_production.
The agent reuses the saved deploy_production alias — no need to re-enter credentials — and repeats the upload.
Tools
Tool | What it does |
| Add and open an SSH connection at runtime. Returns an |
| Close a connection and remove it from the registry. |
| Run a short command synchronously; returns full stdout/stderr/exit_status. Timeout triggers SIGTERM → SIGKILL. |
| Start a background job, return |
| Stream new lines incrementally. Blocks up to |
| Send TERM/KILL/INT/… via paramiko + |
| Write to a running job's stdin — sudo passwords, REPL input. |
| List every job the server knows about: id, cmd, label, status, exit_status, line counts. |
| Drop a job from the registry. Running jobs need |
| Run a command via |
| Check status and tail output of a persistent job. |
| SFTP upload, atomic |
| SFTP download, atomic |
| Enumerate configured hosts (alias, address, auth method, connected, active_jobs, is_default). Never leaks passwords. |
Full JSON schemas and examples: SPEC.md.
Configuration
Credential-free mode (recommended)
Start the server with no SSH arguments. The agent receives credentials from the user in chat and connects via ssh_connect:
mcp-ssh-liveNo credentials in any config file. The agent connects on demand:
> "Connect to 1.2.3.4 as root, password is …"
Agent → ssh_connect(host="1.2.3.4", user="root", password="…")
Agent → ssh_exec(command="hostname")Pre-configured hosts (optional)
If you prefer hosts to be available immediately without an ssh_connect call, pass them via CLI flags or a TOML file. Credentials go into env vars — never on the command line.
export SSH_PASSWORD='your-password'
mcp-ssh-live \
--host prod=1.2.3.4:22 \
--host stage=10.0.0.5 \
--user root \
--password-env SSH_PASSWORD \
--default-host prod \
--insecure-auto-add \
--log-dir ~/.cache/mcp-ssh-live/logs \
--log-level INFORun mcp-ssh-live --help for the full flag list.
TOML config file
~/.config/mcp-ssh-live/config.toml:
default_host = "prod"
[hosts.prod]
host = "1.2.3.4"
port = 22
user = "root"
password_env = "PROD_PASS"
[hosts.stage]
host = "10.0.0.5"
user = "deploy"
key = "~/.ssh/stage_key"
[limits]
ring_buffer_lines = 10000
max_jobs_per_host = 50
reap_finished_after_sec = 600
reconnect_retries = 3
reconnect_delay_sec = 2.0
[server]
insecure_auto_add = false
known_hosts = "~/.ssh/known_hosts"
log_dir = "~/.cache/mcp-ssh-live/logs"Full reference: docs/CONFIG.md.
Auth
Password:
--password-env SSH_PASSWORD— name of the env var. Never pass passwords on the command line (visible inps).Key:
--key ~/.ssh/id_ed25519. Optional passphrase via--key-passphrase-env NAME.Agent: omit both — paramiko will try the SSH agent and default key locations.
Common gotchas
"I ran python parser.py and see no output for minutes"
Python (and many other programs) block-buffer stdout when stdin is not a TTY. The output is fine — it's sitting in a buffer on the remote side. Three fixes, pick one:
# Option 1: pty=True
ssh_spawn(command="python parser.py", pty=True)
# Option 2: unbuffered Python
ssh_spawn(command="python -u parser.py")
# Option 3: env var
ssh_spawn(command="python parser.py", env={"PYTHONUNBUFFERED": "1"})For non-Python tools: stdbuf -oL <cmd> or unbuffer <cmd> (from expect).
"ssh_signal returns sent=True but the job keeps running"
OpenSSH's sshd typically ignores channel.send_signal (the RFC 4254 in-channel way). mcp-ssh-live falls back to kill -<SIG> <pid> on a fresh exec channel, using the PID captured from the PID=<n> wrapper at spawn time. The response tells you which path worked:
{"sent_via_channel": false, "sent_via_pid": true, ...}If both are false, the spawn wrapper couldn't capture a PID (extremely rare — only happens if capture_pid=False was forced or the remote shell is very non-standard).
"Host key verification failed"
By default mcp-ssh-live respects ~/.ssh/known_hosts and refuses unknown keys. For the first connection either:
SSH into the host once by hand so the key is trusted, or
Pass
--insecure-auto-add(INSECURE — vulnerable to MITM on first connect), orPoint at a custom file via
--known-hosts /path/to/file.
"Tool calls hang for 60 seconds then error out"
If you see csp request task for "initialize" took over 60s in Zed's log: FastMCP tries to fetch its latest version from PyPI on startup. On air-gapped networks or slow DNS, this can time out. Fix with the env var:
FASTMCP_DISABLE_VERSION_CHECK=1More in docs/TROUBLESHOOTING.md.
Security
Passwords pass through env vars only, never command-line arguments (which would show up in
ps).ssh_list_hostsnever returns the password itself — only the env var name and the key file path.Known-hosts is enforced by default.
Sandboxing is at the MCP client level: whichever client you use (Zed, Claude Desktop, Cursor) is the thing asking the user to approve each tool call. Once approved,
mcp-ssh-liveruns whatever the LLM supplied verbatim — there is no command-level sanitization. This is the whole point of the tool.The LLM gets a shell on your server. If that bothers you, use a dedicated restricted user, jailed with
ForceCommand/ rbash / containers.
Documentation
Quick start — install, register, run in 4 steps (this file).
SPEC.md— full technical specification, all tool contracts with JSON shapes.docs/CONFIG.md— complete CLI + TOML reference.docs/USAGE.md— real-world examples per tool.docs/TROUBLESHOOTING.md— common failure modes with fixes.docs/DEVELOPMENT.md— how to hack on the server itself.CHANGELOG.md— release history.
Project status
v0.1.0 — phases 1-5 complete.
Phase | Feature | Status |
1 | Skeleton + | ✅ |
2 | Jobs + streaming tail | ✅ |
3 | Signals + stdin | ✅ |
4 | SFTP + multi-host | ✅ |
5 | Auto-reconnect + graceful shutdown + disk log mirror | ✅ |
6 | Docs + CI + PyPI publish | in progress |
68 unit tests. Acceptance-tested end-to-end against a real Ubuntu 24.04 sshd: streaming, signals, SFTP round-trip, disk mirror, multi-host.
Contributing
Design feedback on SPEC.md and docs/DEVELOPMENT.md is always welcome. Before opening a code PR:
pip install -e ".[dev]"pytest— all 68 tests must pass.ruff check .andblack --check .for style.If the change touches a tool schema, update
SPEC.mdfirst and explain why in the PR.
License
MIT. See LICENSE.
Related projects
Reviewed before building this; all of them are request/response only, which is why this project exists:
Project | Stars | Language | Streaming? |
392 | TS | ❌ | |
332 | TS | ❌ | |
159 | JS | ❌ | |
63 | JS | ❌ |
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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