This project ships a subscribe-first Model Context Protocol (MCP) server that lets any MCP-capable LLM host drive remote shells, async commands, SFTP transfers, rsync v32 wire-compat sync (downgrades to v31 against legacy servers), TCP port forwards, and local UART/TTY/COM ports — over a single, lock-free, hexagonal Rust core. This server is maintained by @farchanjo.
This server enables any MCP host (Claude Desktop, Claude Code, Cline, IDE plugins, mcp-inspector, custom agents) to interact with SSH targets through automation. MCP is a standardized protocol for communication between AI systems and external tools or data sources.
The server provides tools to open persistent SSH sessions, run async commands, drive interactive PTYs, transfer files via SFTP (with resume + verify), mirror directory trees through a built-in rsync wire-protocol v32 client (downgrades to v31 against legacy servers via negotiation) (byte-identical against rsync 3.2.7), bind local TCP forwards, and talk to local serial ports — all streamed back to the model as MCP notifications/resources/updated push events the moment bytes arrive. No polling loops, no empty payloads, no wasted tokens.
Architecture is hexagonal (ADR 0002) with lock-free hot paths (DashMap, ArcSwap, Atomic*, tokio::sync::broadcast, mpsc — zero Mutex on RunningCommand / RunningShell / RunningTransfer / SessionRef / ForwardHandle / lane state). Every long-lived resource carries a CAS state machine (Owned → Observed → Releasing → Closed) plus per-session refcount cascade (ADR 0003). Each resources/subscribe mints a SubId (UUIDv7) with its own mpsc::channel, lag policy, filter pipeline, replay buffer, and stats (ADR 0004).
Full module map and design rationale: docs/ARCHITECTURE.md.
This server has been tested against the following Rust versions: >=1.95.0 (Rust 2024 edition, resolver 3).
| OS | Server runtime | SSH/SFTP target | Serial transport |
|---|---|---|---|
| Linux x86_64 / aarch64 | tier 1 | tier 1 | tier 1 |
| macOS arm64 / x86_64 | tier 1 | tier 1 | tier 1 |
| Windows x86_64 | tier 2 (HTTP + stdio binaries build) | tier 1 | tier 2 (COM* ports) |
This server depends on the following Rust crates (top-level — full transitive set in Cargo.lock):
rmcp1.6 — MCP protocolrussh— SSH/SFTP clientaxum0.8 — HTTP transporttokio— async runtimedashmap,arc-swap— lock-free statetracing,serde,thiserror,backon,uuid(UUIDv7)
| Requirement | When required | Notes |
|---|---|---|
| OpenSSH server (or compatible) | always | tested against OpenSSH 7.6+ (Linux, macOS, BSD); MaxSessions budget respected |
| SFTP subsystem | for ssh_upload / ssh_download / ssh_rsync transport=Sftp |
enabled by default on OpenSSH |
rsync ≥ 3.2.0 on remote |
for ssh_rsync transport=Wire |
falls back to Sftp automatically when missing |
Local serial device (/dev/ttyUSB*, /dev/tty.usbserial*, COM*) |
for serial_* tools |
no SSH needed |
| Name | Description |
|---|---|
ssh-mcp |
HTTP transport (axum 0.8 + rmcp StreamableHttpService). Default bind 0.0.0.0:8000, path /. Tracks sessions through Mcp-Session-Id header. |
ssh-mcp-stdio |
Stdio MCP transport (rmcp::transport::io::stdio()). For Claude Desktop / Cline / mcp-inspector / IDE plugins. |
ssh-mcp-tail |
NDJSON daemon over stdin/stdout. Three subcommands (run, shell, daemon). Unix-pipeline composable. Reference: docs/DAEMON.md. |
39 tools (38 without the port_forward feature) split across three semantic axes. Catalogue and per-tool schemas: docs/API.md.
| Axis | Count | Description |
|---|---|---|
ssh_* |
24 | Operations over SSH — connect, exec (async + sync), shell PTY, upload/download, rsync, port forward, agent management. See docs/API.md → Connection / Execute / Shell / SFTP / Rsync / Forward. |
sub_* |
9 | Subscription lane management — sub_open, sub_close, sub_pause/resume/filter/replay/list/stats, sub_stats_all. Cross-resource. See docs/API.md → Subscription administration. |
serial_* |
6 | Local UART / TTY / COM ports — no SSH. serial_open/close/write/press/scan/active. See docs/API.md → Serial. |
7 push-capable resource schemes. Subscribe with sub_open uri=<scheme>://<id>/<channel> (or legacy resources/subscribe). Full contract: docs/RESOURCES.md.
| Scheme | Channel | Producer |
|---|---|---|
shell://<SHELL_ID>/output |
PTY stdout | russh shell channel |
command://<COMMAND_ID>/output |
merged stdout/stderr | russh exec channel |
transfer://<TRANSFER_ID>/progress |
bytes/sec + ETA | SFTP upload/download |
rsync://<RSYNC_ID>/progress |
per-file events + SyncCompleted |
WireRsyncTransport / SftpRsyncTransport |
forward://<FORWARD_ID>/events |
TCP forward lifecycle | port_forward feature |
session://<SESSION_ID>/health |
keepalive + RTT | session reaper |
serial://<SERIAL_ID>/output |
UART RX | local serial port |
This server is tested using a layered local + CI gate. To learn more about testing, refer to CI.md.
Quick numbers — 1986 lib tests + 134 integration tests across 9 binaries + 27 loom invariants + 8 e2e VM tests (gated e2e-vm) + 21 Python integration tests for the v7.0 ssh_rsync MCP surface (scripts/test_v7_rsync_*.py).
git clone https://github.com/farchanjo/ssh-mcp.git
cd ssh-mcp
cargo build --release
sudo install -m 0755 target/release/ssh-mcp{,-stdio,-tail} /usr/local/bin/Three binaries — pick the transport that matches your host:
# Local hosts (Claude Desktop, Cline, mcp-inspector, IDE plugins)
/usr/local/bin/ssh-mcp-stdio
# HTTP hosts (browser- or service-based)
/usr/local/bin/ssh-mcp --bind 0.0.0.0:8000 --path /
# Hosts without resources/subscribe; Unix pipelines (jq, vector, fluent-bit)
/usr/local/bin/ssh-mcp-tail daemonSkip TCP forwarding via cargo build --release --no-default-features (38 tools, smaller binary).
You can also wire ssh-mcp into an MCP host config file (Claude Desktop, Cline, etc.):
{
"mcpServers": {
"ssh": {
"type": "stdio",
"command": "/usr/local/bin/ssh-mcp-stdio"
}
}
}To upgrade to the latest available version, pull and rebuild:
git -C ssh-mcp pull --ff-only
cargo build --release
sudo install -m 0755 target/release/ssh-mcp{,-stdio,-tail} /usr/local/bin/You can also pin a specific version, for example, if you need to downgrade when something is broken in the latest version (please report an issue in this repository). Use the following syntax where X.Y.Z is any released tag:
git -C ssh-mcp checkout vX.Y.Z
cargo build --releaseSee docs/OPERATIONS.md for production deployment, systemd units, Docker, and observability wiring.
For support and questions about this server:
- Issues: Report bugs or request features via GitHub Issues
- Discussions: Open a thread under GitHub Discussions
- Security: Send security reports to the maintainer listed in MAINTAINERS (do not open a public issue for embargoed disclosures)
See the changelog.
- MCP Documentation
- docs/ARCHITECTURE.md — hexagonal layer map, lifecycle adapter, channel mux
- docs/API.md — every MCP tool, schema, response shape
- docs/RESOURCES.md — push resource contracts and cursor semantics
- docs/CONFIGURATION.md — full env-var matrix (40 settings)
- docs/LLM_GUIDE.md — push-first prompts, error handbook, hint escalation
- docs/DAEMON.md —
ssh-mcp-tailNDJSON wire format - docs/DEVELOPMENT.md — lock-free invariants, contributor narrative
- docs/MIGRATION.md — host migration guide across versions
- docs/OPERATIONS.md — production runbook
- docs/adr/ — 11 ADRs (rmcp adoption, hexagonal, lifecycle, channel mux, LLM UX, backpressure, error taxonomy, NDJSON daemon, serial, SFTP resume, rsync hybrid)
- CONTRIBUTING.md — contributor guide
- CODE_OF_CONDUCT.md — community code of conduct
- REVIEW_CHECKLIST.md — PR review checklist
- MAINTAINERS — current maintainers
MIT License.
See LICENSE for the full text. Third-party portions of the rsync wire transport are derived from OpenBSD openrsync and retain the original ISC license — see LICENSES/openrsync-ISC.txt.