{
  "markdown": "<h1 align=\"center\">fast-mcp-ssh</h1>\n<p align=\"center\">SSH, SFTP and persistent shells for AI agents. One Rust binary, no runtime.</p>\n\n<p align=\"center\">\n  <a href=\"https://crates.io/crates/fast-mcp-ssh\"><img src=\"https://img.shields.io/crates/v/fast-mcp-ssh?logo=rust&color=b7410e\" alt=\"crates.io\" /></a>\n  <a href=\"https://github.com/klNuno/fast-mcp-ssh/actions/workflows/ci.yml\"><img src=\"https://github.com/klNuno/fast-mcp-ssh/actions/workflows/ci.yml/badge.svg\" alt=\"CI\" /></a>\n  <a href=\"./LICENSE\"><img src=\"https://img.shields.io/badge/license-MIT-blue\" alt=\"License\" /></a>\n  <img src=\"https://img.shields.io/badge/rust-1.89%2B-b7410e?logo=rust\" alt=\"Rust 1.89+\" />\n  <img src=\"https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20Windows-0078D6\" alt=\"Platform\" />\n</p>\n\nAn MCP server that gives a model real SSH access:\n\n- One connection per host, held open across calls.\n- A PTY shell that remembers `cd` and `export`.\n- SFTP instead of `cat > file`, and host-to-host copies that skip your disk.\n- A screenshot of the remote desktop.\n- Regex guards before anything leaves your machine.\n- An append-only audit log of every call.\n\nAnswers come back as TOON, roughly 40 percent fewer\ntokens than JSON on tabular data.\n\n## Install\n\n```bash\ncargo install fast-mcp-ssh\n```\n\nOr a prebuilt binary from the\n[latest release](https://github.com/klNuno/fast-mcp-ssh/releases/latest),\nchecked against `SHA256SUMS.txt`. Linux and macOS ship x86_64 and aarch64,\nWindows x86_64.\n\nCopy [`hosts.example.toml`](./hosts.example.toml) to `~/.fast-mcp-ssh/hosts.toml`\nand fill in your hosts. Keys go in `~/.fast-mcp-ssh/keys/<name>`; `auth` is\n`key`, `agent` or `password`.\n\n## Wire it up\n\n`.mcp.json`, or `claude_desktop_config.json` for Claude Desktop:\n\n```json\n{\n  \"mcpServers\": {\n    \"ssh\": {\n      \"type\": \"stdio\",\n      \"command\": \"fast-mcp-ssh\"\n    }\n  }\n}\n```\n\nSame block in Claude Code, Cursor, Windsurf, Zed, VS Code Copilot and anything\nelse that speaks MCP over stdio.\n\nIn the [MCP registry](https://registry.modelcontextprotocol.io) it is\n`mcp-name: io.github.klNuno/fast-mcp-ssh`.\n\n## Tools\n\n`host` is optional on every tool once `[defaults] default_host` is set.\n\n| Group | Tools | |\n|---|---|---|\n| Run | `exec` `exec_batch` `sh` `interrupt` | One-shot, parallel fan-out, persistent PTY, Ctrl-C |\n| Files | `ls` `stat` `dn` `up` `cp` `wr` `mkdir` `rm` `tail` | SFTP, plus `tail -n` / `tail -F` in a bounded window |\n| Visual | `shot` | Screenshots the remote desktop, downscaled before it reaches the model |\n| Ops | `facts` `sys` `svc` | Cached host profile, parsed `ps`/`df`/`mem`/`net`, systemd units |\n| Session | `hosts` `ping` `disconnect` `disconnect_all` `reload` `shells` | Discovery and lifecycle; `reload` swaps config without a restart, `shells` closes named PTYs |\n| Network | `forward` `unforward` `forwards` | Local TCP forwards over the same connection |\n\nEvery tool carries MCP annotations (`readOnlyHint`, `destructiveHint`,\n`idempotentHint`, `openWorldHint`) so a client can gate destructive calls.\n\n### Host-to-host copy\n\n`cp` moves a file from one configured host to another. The bytes never land on\nyour disk and never reach the model, and a sha256 is compared on both ends\nbefore success. Guards cover the destination too, so a read-only target still\nrefuses the write.\n\n### Remote screenshots\n\n`shot` hands the model an image instead of a wall of text. It uses whichever of\n`grim`, `gnome-screenshot`, `spectacle`, ImageMagick `import` or `scrot` the\nhost has, covering X11 and wlroots Wayland, and downscales locally so a 4K\nscreen is not a multi-megabyte payload.\n\n## Protocol\n\nSpeaks stateless MCP (`2026-07-28`) and every revision back to `2024-11-05`,\npicked per peer. Stateless changes three things:\n\n- Confirmations come back as an `input_required` result the client answers and\n  retries (SEP-2322), because a server may no longer open a request of its own.\n  Older peers keep `elicitation/create`.\n- Long calls hand back a task handle to poll (SEP-2663): `exec` past its 60s\n  timeout, `tail` with `follow=true`. Clients without the extension keep the\n  blocking call.\n- `tools/list` is sorted, so it is byte-identical between restarts, and carries\n  a one hour `ttlMs` (SEP-2549). A client's prompt cache keeps hitting.\n\nShells are unaffected. A PTY has always been addressed by the `host` and\n`session` arguments of the call, which is the explicit handle stateless wants.\n\n## Security\n\nGuards run before any SSH packet leaves. `deny_patterns` refuse outright,\n`confirm_patterns` ask the user, a client that cannot answer is denied, and\n`read_only = true` blocks anything that looks like a write.\n\nPaths are checked on both sides: remote reads of keys, shadow files and cloud\ncredentials, local writes into your `~/.bashrc` or an autostart folder. Every\npath-taking tool runs both checks, `tail` included, and re-checks once the\nserver has resolved the path, so a symlink cannot launder a blocked target. A\npath that will not resolve refuses the call.\n\nHost keys are pinned, TOFU by default, `strict` and per-host fingerprints\navailable. Every call lands in `~/.fast-mcp-ssh/audit.log` as NDJSON, with\ncredentials scrubbed.\n\nGuards stop accidents, not an adversary who controls the model. Scope the\nremote account accordingly. Threat model: [SECURITY.md](./SECURITY.md).\nVersion history: [CHANGELOG.md](./CHANGELOG.md).\n\n## Benchmark\n\n50 iterations per scenario, same Linux host, same LAN, same SSH key, client on\nWindows 11. Medians, lower is better, measured on `0.5.0`. Reproduce with\n[`benchmark/`](./benchmark), raw runs in\n[`benchmark/results/`](./benchmark/results).\n\n| | `fast-mcp-ssh` | [`mcp-ssh-manager`][mgr] | [`ssh-mcp-server`][fj] |\n|---|---:|---:|---:|\n| Cold start | **48 ms** | 280 ms | 260 ms |\n| `exec echo ok` | **2.2 ms** | 89.7 ms | 46.7 ms |\n| `exec uname -a; whoami; pwd` | **3.6 ms** | 90.9 ms | 50.6 ms |\n| `exec seq 1 5000` (~29 KB) | **19.6 ms** | 90.4 ms [^1] | 49.2 ms |\n| Write a 1 KB file | **1.1 ms** | 89.9 ms | 47.9 ms |\n| Read a 1 KB file | **1.7 ms** | 90.3 ms | 48.9 ms |\n| Tool surface, sent every session | 26 tools, 21.1 KB | 37 tools, 39.9 KB | **4 tools, 1.7 KB** |\n\nBoth alternatives are Node, so ~250 ms of their cold start is the runtime\nbooting. The steady-state gap is the connection: `fast-mcp-ssh` holds one SSH\nsession per host and opens a channel per call, the other two reconnect. Writes\ngo over SFTP here, through a `cat > file` heredoc there.\n\n[^1]: `mcp-ssh-manager` truncates that response to 12 KB, so it is not\nreturning the same output. `ssh-mcp-server` returns raw stdout with no exit\ncode, which is why its replies are the shortest and why a failed command looks\nlike a successful one.\n\n[mgr]: https://www.npmjs.com/package/mcp-ssh-manager\n[fj]: https://www.npmjs.com/package/@fangjunjie/ssh-mcp-server\n\n## Development\n\n```bash\ncargo install --path .        # build and install from a clone\ncargo test                    # unit tests\ncargo clippy --all-targets    # no warnings allowed in CI\n./scripts/test-sh.ps1         # end-to-end against a real host (Windows)\n```\n\nNever write to stdout outside the MCP transport: a stray `println!` corrupts\nthe JSON-RPC stream and the client disconnects without an error. `tracing`\nmacros go to stderr and are safe.\n\n## License\n\nMIT.\n",
  "bytes": 7290,
  "sha": "2a7dc3974c5286f2e5aa4899502dbbb54c2f6a5f1159fada28a709a9dbc77f4e",
  "repo_slug": "klnuno/fast-mcp-ssh",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_klnuno_fast_mcp_ssh_8b15a4a4/readme"
}