diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..a2a2b88 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "remote-ssh": { + "command": "node", + "args": ["/home/lkraven/development/remote-ssh-mcp/dist/index.js"], + "env": { + "SSH_MCP_STRICT_ALLOWLIST": "1" + } + } + } +} diff --git a/CLAUDE.md b/CLAUDE.md index 2ce838d..6882c75 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,14 +21,38 @@ lags the code defeats its own purpose. **New session starting here?** Read [`docs/orientation.md`](docs/orientation.md) first — fleet topology, backup architecture, governing principles, and all the NFS/DSM/naming gotchas that have cost past sessions time. -**For SSH-driven work: use `scripts/elway`.** Write a playbook under +**SSH work splits two ways. Pick by whether you are CHANGING something or +LOOKING at something — neither tool replaces the other.** + +**Changing things → `scripts/elway`.** Write a playbook under `playbooks/.yaml` and run `scripts/elway --playbook ...` instead of chaining `ssh -t host 'sudo …'` commands — handles sudo once lazily, structured pass/change/fail reporting, idempotency via -`creates:` / `when:` / `changed_when:`. Template: +`creates:` / `when:` / `changed_when:`, and file upload. Template: [`playbooks/elway-smoke.yaml`](playbooks/elway-smoke.yaml). +**Looking at things → the `remote-ssh` MCP server.** Registered project-scoped in +[`.mcp.json`](.mcp.json); tools `ssh_hosts` / `ssh_open` / `ssh_run` / `ssh_peek` / +`ssh_interrupt` / `ssh_list` / `ssh_close`. It holds a **persistent shell**, so +`cd` and exported variables survive between calls and each command costs ~6 ms +instead of a fresh handshake. Reach for it for reconnaissance — the +`docker ps` / `grep` / `curl` questions where authoring a playbook is ceremony. +It deliberately has **no file transfer and no idempotency**; that is elway's half. + +- Source: `~/development/remote-ssh-mcp` — our fork of + [the-nine-nation/remote-ssh-mcp](https://gitea.phasefinal.com/vh/remote-ssh-mcp) + (MIT). `.mcp.json` points at the **built** `dist/`, so after editing the fork + run `npm run build` or the server keeps serving the old code. +- ⚠ **The host allowlist is the only real containment boundary.** The denylist + is three regexes and a `bash -c` walks past it. We run with + `SSH_MCP_STRICT_ALLOWLIST=1`, which makes `~/.config/remote-ssh-mcp/config.json`'s + `allowedHosts` **authoritative** rather than additive — without it, every one of + the 18 `Host` entries in `~/.ssh/config` is reachable. Widening that list is a + deliberate act; do it in the config file, not by relying on discovery. +- Hosts not in the allowlist return `host_not_allowed`. Hosts reachable only by + raw IP (no `Host` alias) are not reachable at all — add an alias first. + **Task visibility via task-board.** If the Claude Code session has the `task-board` plugin enabled (installed from `git@gitea.phasefinal.com:vh/task-board.git`), a card at