Skip to content

Commit 05ca1e6

Browse files
committed
docs(skills): add operating-ssh-fleet skill for the ssh-manager MCP
Teaches Claude when and how to drive the 13 ssh_* fat verb-tools instead of raw ssh/scp/rsync: the prefer-MCP discipline, a tool/action quick-reference, task recipes (deploy, incident triage, db snapshot, detached jobs, sessions), common mistakes, and a full per-action parameter matrix under references/. Every action, parameter, and behavioral claim verified against the src/ schemas and dispatchers via multi-agent review.
1 parent b333600 commit 05ca1e6

2 files changed

Lines changed: 279 additions & 0 deletions

File tree

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
---
2+
name: operating-ssh-fleet
3+
description: "Use when operating a remote server or fleet through the ssh-manager MCP and its ssh_* tools — running or scripting commands, deploying files, tailing/following logs, checking health, managing systemd services, databases, backups, Docker, SSH tunnels, or persistent shell sessions on a configured host. Also use for any raw ssh/scp/rsync (via Bash) operation against a configured server, or when the user says \"deploy to\", \"restart the service\", \"tail the log\", \"check the fleet\", or names a configured server."
4+
---
5+
6+
# Operating the SSH Fleet
7+
8+
## Overview
9+
10+
`ssh-manager` is an MCP server that exposes **13 fat verb-tools** (`ssh_*`) for operating a fleet of configured servers. Each tool covers one domain and multiplexes many operations through an `action` enum.
11+
12+
**Core principle:** For any server in the configuration, the `ssh_*` tools are the intended way to operate it — not raw `ssh`/`scp`/`rsync` through Bash. They are not a read-only convenience layer; they are the operator interface.
13+
14+
## When to use
15+
16+
- Running, scripting, or backgrounding a command on a configured server
17+
- Moving, reading, editing, or deploying files
18+
- Reading or following logs; checking health, services, processes
19+
- Databases, backups, Docker, tunnels, port probes, persistent sessions
20+
- Multi-step changes across one or more servers
21+
- **Any time you would otherwise type `ssh host "..."`, `scp`, or `rsync` in Bash for a configured host**
22+
23+
## When NOT to use
24+
25+
- The host is **not** in the configuration → raw `ssh` in Bash is fine. Run `ssh_fleet` `action: servers` to see what is configured.
26+
- Pure local work with no remote target.
27+
28+
## The discipline: `ssh_*` over raw `ssh`
29+
30+
| Raw Bash | Why the tool wins |
31+
|---|---|
32+
| `ssh host "cmd"` reconnects every call | Tools hold a **pooled** connection — no per-call handshake |
33+
| `ssh host journalctl` dumps everything | Output is **head+tail truncated + compressed** — won't flood context |
34+
| `ssh host` with inline password leaks on argv | Credentials go via **stdin/env**, never `ps`-visible |
35+
| Raw terminal dump | **Structured** results — per-segment exit codes, typed snapshots, sha256-verified transfers |
36+
37+
**Violating the letter (using raw ssh "just this once") is violating the spirit.** If the host is configured, use the tool.
38+
39+
## The 13 tools
40+
41+
Always loaded. Pick the tool, then the `action`.
42+
43+
| Tool | Group | Actions | Replaces |
44+
|---|---|---|---|
45+
| `ssh_run` | core | exec, sudo, script, fleet, detach, job-status, job-kill | `ssh host "cmd"` |
46+
| `ssh_find` | core | grep, locate, ls | `ssh host "grep -rn …"` |
47+
| `ssh_file` | core | upload, download, sync, read, write, edit, diff, deploy, deploy-artifact | `scp`, `cat > f <<EOF` |
48+
| `ssh_logs` | core | tail, follow-start, follow-read, follow-stop, journal | `ssh host journalctl` / `tail -f` |
49+
| `ssh_service` | ops | status, start, stop, restart, enable, disable | `ssh host systemctl …` |
50+
| `ssh_health` | ops | check, watch, procs, alerts | `ssh host top`/`df`/`free` |
51+
| `ssh_db` | ops | query, list, dump, import | `ssh host "mysql -e …"` |
52+
| `ssh_backup` | ops | create, list, restore, schedule | `ssh host "tar/mysqldump …"` |
53+
| `ssh_docker` | ops | ps, logs, exec, restart, inspect | `ssh host "docker …"` |
54+
| `ssh_session` | advanced | start, send, list, close, replay, memory | repeated `ssh host "cmd"` |
55+
| `ssh_net` | advanced | tunnel-open, tunnel-list, tunnel-close, port-test | `ssh -L/-R/-D`, `nc -z` |
56+
| `ssh_fleet` | advanced | servers, groups, aliases, command_alias, profiles, hooks, keys, history, connections | `ssh -G`, `~/.ssh/config` |
57+
| `ssh_plan` | advanced | run, approve | hand-sequenced batch of calls |
58+
59+
Full per-action parameter matrix: see `references/tool-matrix.md`.
60+
61+
## Always start with discovery
62+
63+
If you do not know what is configured, call `ssh_fleet` `action: servers` first. Server names are normalized to lowercase and aliases resolve before direct names. `server` is **required** for most tools; omit it only for `ssh_run` `action: fleet` (uses `group`), `ssh_fleet`, and `ssh_plan` (plan-level default).
64+
65+
## Cross-cutting parameters
66+
67+
- **`format`**: `compact` (default) | `json` | `markdown`. Use `json` when you will parse the result.
68+
- **`raw`**: `true` disables compression/truncation. Use sparingly — the cap exists to protect context. Prefer narrowing the query (`grep`, `head`, `tail`, `lines`) over `raw: true`.
69+
- **`preview`**: `true` shows the plan without executing. **Set it before any destructive/mutating action** — deploy, restore, service stop/restart, process kill, db import, tunnel-open, docker mutations.
70+
- **`approve_token`** (`ssh_plan`): high-risk plans gate behind `action: approve` with any non-empty token.
71+
72+
## Recipes
73+
74+
**Run something**
75+
- One command → `ssh_run` `exec` (`command`, optional `cwd`, `timeout`).
76+
- Several in one round trip with shared `cd`/env → `ssh_run` `script` (`commands: [...]`). Add `isolate: true` for independent shells. Segments are **`;`-sequenced, not `&&`** — a failing segment does *not* abort the rest; you get a per-segment exit-code table. For fail-fast (e.g. `npm ci && npm run build`), put the `&&` inside one segment.
77+
- Needs root → `ssh_run` `sudo` (password streams via `sudo_password`/stdin).
78+
- Across a group → `ssh_run` `fleet` (`group`, no `server`).
79+
- Long-running → `ssh_run` `detach` (returns a `job_id`) → poll `job-status` (returns `state` running/done + `exit_code` + `log_size`; feed the prior `log_size` back as `since_offset` for incremental output) → `job-kill` to stop.
80+
81+
**Files**
82+
- Read a slice, not the whole file → `ssh_file` `read` with `head`/`tail`/`grep`/`line_start`/`line_end`. Line ranges are **1-based and inclusive** (`line_start: 50, line_end: 80` = 31 lines).
83+
- Change a file in place → `ssh_file` `edit` (`old_text`/`new_text`) — never heredoc/quoting through `ssh_run`.
84+
- Write a new file → `ssh_file` `write` (`content`).
85+
- Push/pull → `upload`/`download`; mirror a tree → `sync` (`source`/`destination` with `local:`/`remote:` prefixes, `exclude`, `delete_extra`).
86+
- Ship a build safely → `ssh_file` `deploy` (`artifact_local_path`, `target_path`). **Preview first.** Order of operations: upload → `post_hooks``health_check` → (on failure) rollback. Shapes:
87+
- `post_hooks`: array of **shell-command strings**, run in order after upload, e.g. `["systemctl restart nginx"]`.
88+
- `health_check`: a single **shell command**; **non-zero exit = unhealthy**, e.g. `"systemctl is-active nginx"`.
89+
- `rollback_on_fail: true`: restores the prior artifact if a `post_hook` *or* the `health_check` fails. `rollback_hook` is an optional command run *after* that restore (e.g. to bounce the service back).
90+
- `deploy-artifact` is an **alias** of `deploy` (same handler, same args); use `deploy`.
91+
92+
**Incident triage**
93+
1. `ssh_health` `check` — cpu/mem/disk/process snapshot, at-capacity rows on top.
94+
2. `ssh_logs` `journal` (`unit`, `since`, `priority`) or `tail` (`file`, `grep`, `lines`). `tail` applies `lines` **first, then** `grep` (it's `tail -n N | grep`), so a filtered tail can return fewer than `lines` rows — raise `lines` when filtering.
95+
3. `ssh_service` `status` on the suspect unit.
96+
4. Live watch → `ssh_logs` `follow-start` → loop `follow-read` (carry `since_offset`) → `follow-stop`.
97+
98+
**Search the box**`ssh_find` `grep` (`pattern`, `path`, `context_lines`, `match_cap`). It prunes `/proc /sys /dev .git` and stops early — don't hand-roll `grep -rn /`. **`match_cap` defaults to 200** — for an exhaustive sweep raise it, or results silently truncate at 200. `allow_root: true` is needed *only* to search a bare `/` (any normal path like `/etc` works without it).
99+
100+
**Databases**
101+
- Read → `ssh_db` `query` (**SELECT-only**; mutations are blocked by a safety check — for writes use `ssh_run` with the DB CLI or `ssh_db` `import`).
102+
- Snapshot → `ssh_db` `dump` (`db_type`, `database`, `gzip`; `output_path` optional). If you set `output_path`, `gzip: true` pipes to it *as-is***no `.gz` appended**, so name it `….sql.gz` yourself. If you omit `output_path` it auto-names `/tmp/<db>-<ts>.sql[.gz]` (the auto-name *does* add `.gz`). Restore data → `import` (`input_path`; preview first).
103+
104+
**Backups**`ssh_backup` `create` (content-addressed, sha256). Before any risky change, create one. `restore` shows a high-risk preview — review it before confirming.
105+
106+
**Multi-step / multi-server**`ssh_plan` `run` with `steps: [...]` that dispatch to the other tools; high-risk plans need `approve`. Better than a hand-sequenced batch because each step returns a structured result and rollback is built in.
107+
108+
**Persistent shell state** (cwd/env must survive across commands) → `ssh_session` `start``send``replay`/`memory` to inspect → `close`.
109+
110+
## Common mistakes
111+
112+
| Mistake | Fix |
113+
|---|---|
114+
| Reaching for raw `ssh`/`scp`/`rsync` on a configured host | Use the matching `ssh_*` tool |
115+
| `raw: true` to "see everything" | Narrow with `grep`/`head`/`tail`/`lines` instead; the cap protects context |
116+
| Heredoc / shell-quoting to write a file | `ssh_file` `write` or `edit` |
117+
| Destructive action with no `preview` | `preview: true` first (deploy, restore, kill, stop, import) |
118+
| Non-SELECT through `ssh_db` `query` | Blocked by design; use `ssh_run`/DB CLI or `ssh_db` `import` |
119+
| Passing `server` to `ssh_run` `fleet` | Use `group`; `server` is omitted there |
120+
| Polling a detached job from scratch each time | Carry `since_offset` for incremental output |
121+
| Guessing server names | `ssh_fleet` `action: servers` |
122+
123+
## Configuration (context)
124+
125+
Servers come from `~/.ssh-manager/.env` as `SSH_SERVER_<NAME>_HOST/USER/PORT/KEYPATH/...` (or TOML for Codex). You don't edit these to operate — `ssh_fleet` reads them. Add/remove servers via the `ssh-manager` CLI (`ssh-manager server add`).
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# Full parameter matrix
2+
3+
Every action-scoped argument is optional at the schema level; each dispatcher enforces its own per-action required set and returns a structured failure naming any missing argument. The "needs" column lists what a call actually requires.
4+
5+
Common to every tool: `format` (`compact`|`json`|`markdown`, default `compact`), and where noted `raw` (disable truncation) and `preview` (dry-run).
6+
7+
---
8+
9+
## ssh_run — run commands
10+
11+
| action | needs | optional |
12+
|---|---|---|
13+
| `exec` | `server`, `command` | `cwd`, `timeout`, `raw`, `format` |
14+
| `sudo` | `server`, `command` | `cwd`, `sudo_password`, `timeout`, `raw`, `format` |
15+
| `script` | `server`, `commands[]` | `isolate` (separate shells, no shared cd/env), `raw`, `format` |
16+
| `fleet` | `group` (no `server`), `command` | `cwd`, `format` |
17+
| `detach` | `server`, `command` | returns a `job_id` |
18+
| `job-status` | `server`, `job_id` | `since_offset` (incremental output; pass back prior `log_size`) |
19+
| `job-kill` | `server`, `job_id` | |
20+
21+
`script` segments are `;`-sequenced — a failing segment does NOT abort the rest; returns a per-segment exit-code table. `isolate: true` runs each segment in its own shell (no shared cd/env). `job-status` returns `state` (running/done) + `exit_code` + `log_size`; feed the prior `log_size` back as `since_offset` for incremental output. Default `timeout`: `exec` 120000 ms, `sudo` 30000 ms.
22+
23+
## ssh_find — search files
24+
25+
| action | needs | optional |
26+
|---|---|---|
27+
| `grep` | `server`, `path`, `pattern` | `context_lines`, `match_cap`, `timeout_secs`, `cross_mounts`, `allow_root`, `format` |
28+
| `locate` | `server`, `path`, `name` (glob) | `match_cap`, `timeout_secs`, `cross_mounts`, `allow_root`, `format` |
29+
| `ls` | `server`, `path` | `format` |
30+
31+
Prunes `/proc /sys /dev` and `.git`; server-side timeout + match cap stop the walk early. **`match_cap` defaults to 200** (raise it for exhaustive sweeps or results truncate silently). `allow_root: true` is required only to search a bare `/`; normal paths need nothing.
32+
33+
## ssh_file — move/edit files
34+
35+
| action | needs | optional |
36+
|---|---|---|
37+
| `upload` | `server`, `local_path`, `remote_path` | `format` |
38+
| `download` | `server`, `local_path`, `remote_path` | `format` |
39+
| `read` | `server`, `remote_path` | `head`, `tail`, `grep`, `line_start`, `line_end` (1-based, inclusive), `format` |
40+
| `write` | `server`, `remote_path`, `content` | `format` |
41+
| `edit` | `server`, `remote_path`, `old_text`, `new_text` | `format` |
42+
| `diff` | `server`, `path_a`, `path_b` | `server_b` (cross-server diff), `format` |
43+
| `sync` | `server`, `source`, `destination` (`local:`/`remote:` prefixed) | `exclude[]`, `delete_extra`, `format` |
44+
| `deploy` | `server`, `artifact_local_path`, `target_path` | `post_hooks[]` (shell-cmd strings, in order), `health_check` (cmd; non-zero=unhealthy), `rollback_on_fail`, `rollback_hook`, `preview`, `format` |
45+
| `deploy-artifact` | alias of `deploy` (same handler + args) ||
46+
47+
Deploy order: upload → `post_hooks``health_check` → (on `post_hook` or `health_check` failure, if `rollback_on_fail`) restore prior artifact → `rollback_hook`.
48+
49+
## ssh_logs — read logs
50+
51+
| action | needs | optional |
52+
|---|---|---|
53+
| `tail` | `server`, `file` | `lines`, `grep`, `format` |
54+
| `follow-start` | `server`, `file` | `lines`, `grep` → returns `session_id` |
55+
| `follow-read` | `session_id` | `since_offset` (resume cursor), `format` |
56+
| `follow-stop` | `session_id` | |
57+
| `journal` | `server` | `unit`, `since`, `until`, `priority`, `lines`, `grep`, `format` |
58+
59+
`tail`/`follow-start` apply `lines` first, then `grep` (`tail -n N | grep -E`), so a filtered result can have fewer than `lines` rows — raise `lines` when filtering.
60+
61+
## ssh_service — systemd
62+
63+
| action | needs | optional |
64+
|---|---|---|
65+
| `status` | `server`, `service` | `format` (returns ActiveState/SubState/recent log) |
66+
| `start`/`stop`/`restart`/`enable`/`disable` | `server`, `service` | `preview`, `format` |
67+
68+
## ssh_health — health
69+
70+
| action | needs | optional |
71+
|---|---|---|
72+
| `check` | `server` | `format` (cpu/mem/disk/process snapshot) |
73+
| `watch` | `server` | `watch_type` (`overview`|`cpu`|`memory`|`disk`|`network`|`process`), `format` |
74+
| `procs` | `server` | `proc_action` (`list` default|`kill`|`info`), `pid`, `signal` (`TERM`|`KILL`|`HUP`|`INT`|`QUIT`), `preview`, `format` |
75+
| `alerts` | `server`, `alert_action` (`set`|`get`|`check`) | `cpu_threshold`, `memory_threshold`, `disk_threshold` (0–100), `enabled`, `format` |
76+
77+
## ssh_db — databases
78+
79+
| action | needs | optional |
80+
|---|---|---|
81+
| `query` | `server`, `db_type`, `database`, `query` | `user`, `password`, `format`. **SELECT-only** (Mongo find ok). |
82+
| `list` | `server`, `db_type` | `database` (list tables/collections), `user`, `password`, `format` |
83+
| `dump` | `server`, `db_type`, `database` | `output_path` (default `/tmp/<db>-<ts>.sql[.gz]`), `gzip`, `user`, `password`, `format` |
84+
| `import` | `server`, `db_type`, `database`, `input_path` | `preview`, `user`, `password`, `format` |
85+
86+
`db_type`: `mysql` | `postgresql` | `mongodb`. `query` is SELECT-only (Mongo: find/read only; mutations blocked). `dump`: a *supplied* `output_path` is used verbatim — `gzip` does NOT append `.gz`, add it yourself; an *omitted* `output_path` auto-names `/tmp/<db>-<ts>.sql[.gz]` (auto-name adds `.gz`).
87+
88+
## ssh_backup — backups
89+
90+
| action | needs | optional |
91+
|---|---|---|
92+
| `create` | `server`, `backup_type` | `name`, `database`, `paths[]`, `exclude[]`, `backup_dir`, `gzip`, `verify`, `format` |
93+
| `list` | `server` | `backup_dir`, `format` |
94+
| `restore` | `server`, `backup_id` | `target_path` (file backups), `verify`, `preview`, `format` |
95+
| `schedule` | `server`, `backup_type`, `cron` | `name`, `database`, `paths[]`, `retention`, `format` |
96+
97+
`backup_type`: `mysql` | `postgresql` | `mongodb` | `files`. Content-addressed + sha256; restore shows a high-risk preview.
98+
99+
## ssh_docker — Docker
100+
101+
| action | needs | optional |
102+
|---|---|---|
103+
| `ps` | `server` | `format` |
104+
| `logs` | `server`, `container` | `tail_lines`, `format` |
105+
| `exec` | `server`, `container`, `command` | `format` |
106+
| `restart` | `server`, `container` | `preview`, `format` |
107+
| `inspect` | `server`, `container` | `format` |
108+
109+
Container/image names are validated; mutations show a preview.
110+
111+
## ssh_session — persistent shell
112+
113+
| action | needs | optional |
114+
|---|---|---|
115+
| `start` | `server` | returns `session_id` |
116+
| `send` | `session_id`, `command` | `timeout`, `format` |
117+
| `list` || `format` |
118+
| `close` | `session_id` | |
119+
| `replay` | `session_id` | `limit`, `format` |
120+
| `memory` | `session_id` | `format` (inferred-state snapshot: cwd/env/exit) |
121+
122+
## ssh_net — tunnels + port probes
123+
124+
| action | needs | optional |
125+
|---|---|---|
126+
| `tunnel-open` | `server`, `tunnel_type` (`local`|`remote`|`dynamic`) | `bind`, `local_port`, `remote_host`, `remote_port`, `preview`, `format` |
127+
| `tunnel-list` || `format` |
128+
| `tunnel-close` | `tunnel_id` | |
129+
| `port-test` | `server`, `target_host`, `target_port` | `probe_chain[]` (`dns`/`tcp`/`tls`/`http`), `timeout_ms_per_probe`, `continue_on_fail`, `format` |
130+
131+
## ssh_fleet — fleet + config metadata
132+
133+
`action` selects the entity; `op` selects the sub-operation (default `list`/`status`).
134+
135+
| action | op values | key params |
136+
|---|---|---|
137+
| `servers` | list ||
138+
| `groups` | list, add, remove, update | `name`, `members[]`, `description` |
139+
| `aliases` | list, add, remove | `name`, `target` |
140+
| `command_alias` | list, add, remove, suggest | `alias`, `command` (or search term for `suggest`) |
141+
| `profiles` | list, show, update | `name` |
142+
| `hooks` | list, update | `name` |
143+
| `keys` | list, add, verify, accept, show | `server`, `host`, `port` |
144+
| `history` | list | `limit`, `search` |
145+
| `connections` | status, reconnect, disconnect, cleanup | `server` |
146+
147+
## ssh_plan — multi-step plan
148+
149+
| action | needs | optional |
150+
|---|---|---|
151+
| `run` | `steps[]` (ordered step objects; each dispatches to another tool) | `server` (plan default), `rollback_on_fail`, `format` |
152+
| `approve` | `steps[]`, `approve_token` (any non-empty) | `server`, `rollback_on_fail`, `format` |
153+
154+
High-risk steps gate behind `approve`. Each step returns its own structured result; `rollback_on_fail` walks completed steps in reverse on failure.

0 commit comments

Comments
 (0)