README.md (3470 bytes)
1 # sshp-ai 2 3 A parallel SSH runner for AI coding agents: run one command on many hosts at once and get per-host output and exit codes. 4 5 sshp-ai wraps [sshp](https://github.com/bahamas10/sshp) by Dave Eddy (bahamas10), a fast parallel SSH executor written in C. It adds two things: 6 7 - **`skills/sshp/SKILL.md`**: an agent skill that teaches when and how to use sshp: `-n` dry runs before risky commands, `BatchMode` and timeouts so nothing hangs, output grouping, and per-host exit codes. It works in the skills folders of Claude Code, Codex, Copilot CLI and Antigravity. 8 - **`sshp_mcp.py`**: a stdio MCP server (Python 3 stdlib only) with two tools: 9 - `sshp_run(command, hosts?, max_jobs?, timeout?, user?, dry_run?)` returns JSON with each host's output and exit code. 10 - `sshp_hosts()` lists the allowlisted hosts and whether writes are enabled. 11 12 sshp-ai does not include sshp's code. Install sshp yourself. 13 14 ## Safety model 15 16 The operator sets the limits in a config file. The model cannot change them. 17 18 - **Host allowlist.** Only hosts in the hosts file can be targeted. 19 - **Conservative default.** Out of the box, a command must start with an allowed verb (`uptime`, `df`, `systemctl status`, `journalctl`, `docker ps`, ...) and must not contain shell metacharacters (`; | & > < $ \` ( )`). Write-like forms such as `find -delete`, `systemctl restart` or `date -s` are refused. 20 - **`"allow_write": true` makes it a general-purpose runner.** Set it in the config to run any command (restarts, installs, edits). Only the operator can set it. The host allowlist still applies. 21 - ssh runs with `BatchMode=yes` and `ConnectTimeout=10`, so a prompt fails fast. The whole run has a timeout (default 60 s, max 600 s). 22 23 ## Install 24 25 1. Build and install sshp (see its README). It must be on `PATH`, or set `"sshp"` in the config. 26 2. Copy the skill into your agent's skills folder: 27 28 ```sh 29 for d in ~/.claude/skills ~/.codex/skills ~/.copilot/skills ~/.gemini/skills ~/.gemini/antigravity/skills; do 30 mkdir -p "$d" && cp -r skills/sshp "$d/" 31 done 32 ``` 33 34 3. Create the hosts file and (optionally) the config: 35 36 ```sh 37 mkdir -p ~/.config/sshp-ai 38 printf 'web1.example.com\nroot@db1.example.com\n' > ~/.config/sshp-ai/hosts 39 ``` 40 41 `~/.config/sshp-ai/config.json` (all keys optional): 42 43 ```json 44 { 45 "hosts_file": "~/.config/sshp-ai/hosts", 46 "allow_write": false, 47 "sshp": "sshp", 48 "ssh_options": ["BatchMode=yes", "ConnectTimeout=10"] 49 } 50 ``` 51 52 Set `SSHP_AI_CONFIG` to use another config path. 53 54 4. Register the MCP server, for example in Claude Code: 55 56 ```sh 57 claude mcp add -s user sshp -- python3 /path/to/sshp_mcp.py 58 ``` 59 60 Other clients take the same command (`python3 /path/to/sshp_mcp.py`) in their MCP config. 61 62 ## Example result 63 64 ```json 65 { 66 "sshp_exit_code": 1, 67 "dry_run": false, 68 "hosts": { 69 "web1.example.com": {"output": " 10:00:01 up 3 days, load average: 0.10", "exit_code": 0}, 70 "db1.example.com": {"output": "ssh: connect to host db1.example.com port 22: Connection refused", "exit_code": 255} 71 }, 72 "sshp_stderr": "" 73 } 74 ``` 75 76 sshp merges each host's stdout and stderr into one stream, so `output` holds both. 77 78 ## Tests 79 80 ```sh 81 python3 -m unittest -v test_sshp_mcp 82 ``` 83 84 The policy tests run anywhere. The integration tests need `sshp` on `PATH` and a POSIX system; a mock `ssh` script plays the remote hosts, so no network is used. 85 86 ## License 87 88 MIT, the same license as sshp. sshp is Copyright 2021 Dave Eddy.