mcpx
# Relay Shell
[](https://deepwiki.com/rmednitzer/relay-shell)
**Status:** v0.3.0 · Python 3.12 / 3.13 / 3.14 · transports: `stdio` and
`streamable-http` · MCP SDK `mcp>=2,<3` · last validated against
upstream surfaces on 2026-08-12
([ADR 0005](docs/adr/0005-codebase-validation.md)).
A highly reliable, maximally capable [Model Context Protocol](https://modelcontextprotocol.io)
server for **complete shell and SSH mastery**.
`relay-shell` gives an MCP client (Claude, or any MCP-compatible agent) a robust,
auditable interface to operate a Linux host and a fleet of remote hosts over
SSH: one-shot command execution, long-lived interactive PTY sessions, scripted
runs, SFTP transfer, port forwarding, and host-inventory aware connectivity.
It is designed as **operator infrastructure tooling for hosts you own and
administer**. The default operating posture is native, full access (no
sandbox), matching the way real administration is performed, paired with the
defensive controls a production operator actually needs: an append-only,
output-hashed audit trail; a tiered-authority policy layer; secret redaction;
strict resource and timeout bounds; and an optional OAuth 2.1 edge.
The architecture, security model, and deployment patterns are modeled on a
mature production MCP gateway and on established operational best practices.
## Why
Engineers SSH into hosts and run commands from memory, with no structured
reasoning trail and no pre-execution review. A well-built MCP relay improves
on that baseline: every action is captured with arguments, an output hash, an
exit code, and a tier classification; limits and timeouts are enforced
centrally; failure paths never crash the transport. The reasoning layer sits
*inside* the loop and can assess blast radius before acting.
## Capabilities
### Local shell
| Tool | Purpose |
|------|---------|
| `shell_exec` | Run a command. Timeout/output clamps, cwd, env overlay, stdin, exit code. |
| `shell_script` | Run a multi-line script (bash/sh/python), optional `set -euo pipefail`. |
| `shell_spawn` | Start a persistent PTY session (REPLs, TUIs, prompts, long jobs). |
### SSH
| Tool | Purpose |
|------|---------|
| `ssh_exec` | Run a command on a remote host (jump host, key/agent, known-hosts policy). |
| `ssh_spawn` | Interactive remote PTY session. |
| `ssh_upload` / `ssh_download` | SFTP transfer (recursive supported). |
| `ssh_forward` | Local (`L`), remote (`R`), or dynamic SOCKS (`D`) forwarding. |
| `ssh_forward_list` / `ssh_forward_close` | Manage active forwards. |
| `ssh_check` | Connectivity probe across the inventory or a host list. |
| `ssh_fanout` | Run a command in parallel across hosts; per-host exit codes in one JSON. |
| `ssh_keyscan` | Fetch host public keys via `ssh-keyscan` (pre-populate `known_hosts` for `strict`). |
| `ssh_hosts` | Resolved host inventory (`~/.ssh/config` + inventory file). |
### Sessions (local PTY and SSH PTY, unified)
| Tool | Purpose |
|------|---------|
| `session_send` | Send input (optionally with Enter) to a session. |
| `session_recv` | Read buffered/new output, with a short wait. |
| `session_resize` | Resize the PTY (cols x rows). |
| `session_kill` | Signal / terminate a session. |
| `session_list` | List active sessions with metadata. |
### Diagnostics
| Tool | Purpose |
|------|---------|
| `server_info` | Server version, effective limits, policy mode, audit path. |
| `audit_tail` | Return recent audit records as JSONL, optionally filtered by `tool`/`tier`/`denied` (read-only, Tier 0). |
| `operation_confirm` | Arm a Tier-3 confirmation token (opt-in broker, ADR 0009), then re-issue the call. |
The HTTP transport also exposes `GET /metrics` (Prometheus text format):
`relay_shell_tool_calls_total{tool,tier,mode,outcome}`,
`relay_shell_seccomp_notify_events_total{syscall}`, and
`relay_shell_seccomp_notify_overflow_total` (counters), plus
`relay_shell_active_sessions`, `relay_shell_active_forwards`, and
`relay_shell_audit_degraded` (gauges). See
[`docs/deployment.md`](docs/deployment.md) §9a.
### Resources
Three MCP resources let clients read inventory and `ssh_config` views
the protocol-native way (no tool call needed):
| URI | meaning |
|--------------------------------------|--------------------------------------|
| `relay-shell://inventory` | Flat list of all known hosts (JSON). |
| `relay-shell://inventory/{host}` | One host's resolved spec (JSON). |
| `relay-shell://ssh-config` | ssh_config path + aliases (JSON). |
Resource reads are audited (tier 0). See
[`docs/tools.md`](docs/tools.md) for the full reference.
### Prompts
One MCP prompt, `operating_guide`, carries the detailed "when to use which
tool" guidance (one-shot command vs persistent PTY session, the spawn+session
workflow, fleet and transfer entry points) — the protocol-native counterpart
to the concise `instructions` string handed to clients at initialize. A fetch
is audited (tier 0, `prompt:operating_guide`) like a resource read; listing is
not ([ADR 0008](docs/adr/0008-operating-guidance-prompt.md)).
Full reference: [`docs/tools.md`](docs/tools.md).
## Quickstart
Requires Python 3.12+ (CPython, tested on Ubuntu 24.04).
```bash
git clone https://github.com/rmednitzer/relay-shell.git && cd relay-shell
python3 -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
# stdio transport (local agent / Claude Desktop / MCP Inspector)
relay-shell
# HTTP transport (streamable-http on 127.0.0.1:8080)
RELAY_SHELL_TRANSPORT=http relay-shell
# Validate config without starting the transport (useful for image bakes)
relay-shell --check-config
# Drift-detect shipped templates against /etc/... (useful in production cron)
relay-shell --verify-deploy
```
Register with an MCP client (stdio):
```json
{ "mcpServers": { "relay-shell": { "command": "relay-shell" } } }
```
Configuration is environment-driven; see [`.env.example`](.env.example) and
[`docs/deployment.md`](docs/deployment.md).
## Compatibility matrix
| Surface | Supported | Notes |
|-------------------|------------------------------------------------------------|----------------------------------------------------------------------------------------|
| Python | 3.12 / 3.13 / 3.14 | CI runs the full matrix; package floor is `>=3.12`. |
| Host OS (tested) | Ubuntu 24.04 LTS | Validation is run against this image; the systemd + Caddy installers assume it. |
| Host OS (dev) | macOS | Unsupported for production; `pty`/SSH paths work for local development. |
| Host OS (other) | Windows | Out of scope — no PTY contract, no systemd integration. |
| Transports | `stdio`, `streamable-http` | Stdio is the default. HTTP binds loopback and requires a TLS edge (see `deployment.md`). |
| SDK | `mcp>=2,<3` (validated set: `2.0.0`) | Range-pinned since the v2 migration (ADR 0001); bumps trigger a fresh validation pass. |
| SSH library | `asyncssh>=2.23.0` (tested at 2.24.0) | Native async; no shell-out to system `ssh`. |
## Security posture
`relay-shell` runs unsandboxed with the privileges of its service account by design
(see [`docs/adr/0002-no-sandbox-full-access.md`](docs/adr/0002-no-sandbox-full-access.md)):
sandboxing the process would defeat the very capability it exists to provide.
Safety is achieved with **compensating controls**, not by crippling the tool:
- **Audit** - every invocation appended as one JSON line with a SHA-256 hash
of the output (never the output body), byte length, exit code, request and
client id, and the assessed tier. Append-only on disk; rotation-safe handler.
Optional per-record hash chain (`RELAY_SHELL_AUDIT_CHAIN`,
[ADR 0007](docs/adr/0007-audit-hash-chain.md)) makes edits, insertions,
reorders, and interior deletions detectable with `relay-shell --verify-audit`,
which is fail-closed (a missing / empty / head-truncated log fails;
`--segment` accepts a rotation segment; tail-truncation needs the off-host copy).
- **Syscall visibility** (optional) - `RELAY_SHELL_SECCOMP_NOTIFY`
([ADR 0006](docs/adr/0006-seccomp-notify-audit-channel.md)) adds an
audit-only seccomp **user-notify** channel that appends `syscall_notify`
lines for a spawned child's `execve` / privilege / namespace / mount /
write-`open` / privilege-relevant `prctl` syscalls — for one-shot commands
and for `shell_spawn` PTY sessions, where the filter rides the session
child for the session's whole life. It **never blocks** a syscall and
installs only with `CAP_SYS_ADMIN` (never latching `no_new_privs`), so
set-uid/`sudo` posture is preserved verbatim — visibility added,
capability untouched.
- **Tiered authority** - every call is classified Tier 0..3
([`docs/adr/0003-tiered-authority.md`](docs/adr/0003-tiered-authority.md)).
`RELAY_SHELL_POLICY_MODE` selects `open` (default), `guarded`, or `readonly`.
- **Redaction** - audited arguments are scrubbed for tokens, keys, and
`Authorization` material.
- **Bounds** - timeout and output caps on every tool; bounded session count
and buffers; idle/lifetime reaping.
- **Optional OAuth 2.1** - DCR with single-client lockdown, PKCE, file-backed
rotating tokens, lazy expiry (HTTP transport; **opt-in, off by default**).
See [`docs/auth.md`](docs/auth.md) for the authentication lifecycle.
- **Edge** - parameterized Caddy config restricts the endpoint to known
CIDRs with security headers and automated TLS (ACME / Let's Encrypt)
installed via `deploy/install-edge.sh`; systemd unit applies resource
caps.
This server grants real administrative power. Run it only as a scoped service
account, only on hosts you are authorized to administer, behind the network
controls in [`docs/deployment.md`](docs/deployment.md). See
[`SECURITY.md`](SECURITY.md) for the threat model and reporting.
If your use case requires maximum model capability, `relay-shell` also supports
an explicit privileged posture (root/sudo workflows). Use that only on isolated
administrative hosts with strict network controls and full audit shipping.
## Layout
```
src/relay_shell/ server, config, audit, policy, redaction, sessions,
shelltools, sshpool, inventory, errors, util, auth
deploy/ systemd unit + hardening drop-in, Caddyfile, logrotate, installers
docs/ architecture, tool reference, deployment, ADRs
tests/ unit + integration (in-process SSH server, no network)
```
## Development
```bash
ruff check . && ruff format --check .
mypy
pytest
```
## Contributing
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for scope, branch naming,
the local development loop, and how security-sensitive PRs are
reviewed. [`docs/runbook.md`](docs/runbook.md) is the canonical
procedure for audit, review, validate, enhance, and extend tasks.
Participation is governed by the
[Code of Conduct](CODE_OF_CONDUCT.md).
## AI contributor guidance
- [`AGENTS.md`](AGENTS.md) - repository-wide agent operating contract
- [`CLAUDE.md`](CLAUDE.md) - Claude-focused development and review guidance
- [`docs/runbook.md`](docs/runbook.md) - executable audit / review / validate / enhance / extend procedures plus the prioritized backlog
## License
Apache-2.0. See [`LICENSE`](LICENSE) and [`NOTICE`](NOTICE).
TDQS
Scored across 18 tools
Most tools target distinct operations (e.g., session vs shell vs SSH), but shell_exec and shell_script overlap in running commands, and session_send/recv are closely tied to shells. Overall, an agent can distinguish them with careful reading.
Naming is predominantly <domain>_<action> with underscores, e.g., ssh_upload, session_kill. A few exceptions like server_info (reverse order) and ssh_hosts (noun) break the pattern slightly, but consistency is high overall.
18 tools cover a well-scoped domain of remote execution and session management without being excessive. Each tool serves a clear purpose, and the count feels appropriate for the functionality offered.
The set covers local and remote execution, interactive sessions, file transfer, port forwarding, and server info. Minor gaps exist (e.g., no remote file listing, no SSH key management), but core workflows are supported.