Skip to main content
Glama

Relay Shell

v0.5.2 · Python 3.12 / 3.14 · transports stdio and streamable-http · MCP SDK mcp>=2,<3 · Apache-2.0

relay-shell is a Model Context Protocol server that gives an MCP client the full reach of a shell: run commands on the local host, hold interactive PTY sessions, run scripts, operate a fleet over SSH, move files with SFTP and open port forwards.

It is operator infrastructure for hosts you own and administer. It runs with the privileges of its service account, with no sandbox, and it is open by default: no command is refused for what it does. What it adds is a record and a warning:

  • Per-call audit: one JSON line with the redacted arguments, the output's hash and length (never its body), the exit code, the client and the assessed tier.

  • Risk notices: a call classified Tier 2 (changes state) or Tier 3 (irreversible), or an SSH call with host-key checking off, ends with a [NOTICE ...] line. server_info lists the standing unsafe settings.

  • Refusal is opt-in: guarded and readonly modes, a deny list and a Tier-3 confirmation step are available and all off by default.

Audit records describe completed, denied and cancelled calls; a process crash can leave an execution without a completion record. Opt-in RELAY_SHELL_AUDIT_INTENT=true adds a pre-execution record. Runtime sink failures mark audit health degraded while calls continue; monitor that health and ship evidence off-host. See the audit lifecycle.

Tools

Local shell

Purpose

shell_exec

Run a command with timeout and output caps, cwd, env overlay and stdin.

shell_script

Run a multi-line bash, sh or python script, optionally under set -euo pipefail.

shell_spawn

Start a persistent PTY session (REPLs, TUIs, prompts, long jobs).

SSH

Purpose

ssh_exec

Run a command on a remote host (jump host, key or agent, known-hosts policy).

ssh_spawn

Start an interactive remote PTY session.

ssh_upload / ssh_download

SFTP transfer, recursive supported.

ssh_forward

Local (L), remote (R) or dynamic SOCKS (D) forward; L/D listen on loopback only.

ssh_forward_list / ssh_forward_close

List or close active forwards.

ssh_check

Probe connectivity across the inventory or a host list.

ssh_fanout

Run one command on many hosts in parallel; per-host exit codes in one JSON.

ssh_keyscan

Fetch host keys with ssh-keyscan to pre-populate known_hosts.

ssh_hosts

Show the resolved inventory (~/.ssh/config plus the inventory file).

Sessions (local and SSH PTY)

Purpose

session_send

Send input, optionally followed by Enter.

session_recv

Read buffered and new output, with a short wait.

session_resize

Resize the PTY.

session_kill

Signal or terminate a session.

session_list

List active sessions.

Diagnostics

Purpose

server_info

Version, effective limits, policy mode, audit path, unsafe-setting warnings.

audit_tail

Read recent audit records, filterable by tool, tier and denied.

operation_confirm

Arm a Tier-3 confirmation token when that opt-in step is enabled.

The server also exposes three read-only MCP resources (relay-shell://inventory, relay-shell://inventory/{host}, relay-shell://ssh-config) and one prompt, operating_guide, with guidance on which tool to use when; resource reads and prompt fetches are audited too. Full reference: docs/tools.md. Over HTTP, GET /metrics serves Prometheus counters and gauges (docs/deployment.md §9a).

Related MCP server: SSH MCP Server

Quickstart

Qualified host profiles are Ubuntu 24.04 LTS with CPython 3.12 and Ubuntu 26.04 LTS with CPython 3.14. Use the matching hashed dependency profile. The package minimum is CPython 3.12; other interpreter/OS combinations are not qualified.

git clone https://github.com/rmednitzer/relay-shell.git && cd relay-shell
python3.12 -m venv .venv
. .venv/bin/activate
python -m pip install --only-binary=:all: --require-hashes -r requirements/development-py3.12.txt
python -m pip install --no-deps --no-build-isolation -e ".[dev]"
python -m pip check

relay-shell                              # stdio transport
RELAY_SHELL_TRANSPORT=http relay-shell   # streamable-http on 127.0.0.1:8080
relay-shell --check-config               # validate config, print unsafe-setting notes
relay-shell --verify-deploy              # compare installed templates with the shipped ones

The example uses Ubuntu 24.04 / CPython 3.12. On Ubuntu 26.04, use python3.14 and requirements/development-py3.14.txt instead. For a service installation using the runtime-only lock and an isolated builder, follow the deployment guide.

Register with an MCP client (stdio):

{ "mcpServers": { "relay-shell": { "command": "relay-shell" } } }

Configuration comes from RELAY_SHELL_* environment variables, optionally via a .env file; see .env.example and docs/deployment.md.

Default behaviour

With the default configuration every command on every tool is admitted, including rm -rf /, mkfs, dd to a block device, shutdown, curl | sh and sudo (pinned by tests/test_notices.py). The call is classified and audited, and a risky one ends with a notice:

[exit 0]
...command output...
[NOTICE tier 3 (IRREVERSIBLE), matched an irreversible-operation pattern. It may not be
undoable and ran with no confirmation step; verify the target and take a backup or snapshot
first. Classification is a heuristic, not a boundary]

The classifier is a regex heuristic over the command text, not a parse of the shell language. A call without a notice is not thereby safe, and a notice does not block anything.

Open mode controls command admission; it does not bypass argument validation, SSH host verification, transport authentication or resource limits. With the default policy, a command is not refused because of its meaning. Important bounds and startup checks include:

Bound

Setting (default)

Behaviour

Input size (command, stdin, script, env)

RELAY_SHELL_MAX_INPUT (2,097,152 characters)

refused, because the policy scan is synchronous

One-shot command timeout

RELAY_SHELL_MAX_TIMEOUT (900 s)

local process group terminated; remote termination is best effort; use shell_spawn / ssh_spawn for longer work

Live sessions / forwards

RELAY_SHELL_MAX_SESSIONS (64), RELAY_SHELL_MAX_FORWARDS (64)

a new one is refused at the cap

Output returned

RELAY_SHELL_MAX_OUTPUT (64 KiB)

truncated, not refused

HTTP without OAuth on a non-loopback address

RELAY_SHELL_ALLOW_UNAUTH_NETWORK

refused at startup unless set

Session output buffers are also bounded, and idle sessions (RELAY_SHELL_SESSION_IDLE_TIMEOUT, 1800 s) and exited, drained sessions are reaped. Persistent sessions have no maximum lifetime. SFTP transfers default to timeout=0 (no per-call deadline); set a positive transfer timeout when needed. After a remote timeout or disconnect, reconcile the remote operation before retrying a mutation.

Making it stricter

Every option below is opt-in and they combine:

Want

Setting

Refuse state-changing calls unless allow-listed

RELAY_SHELL_POLICY_MODE=guarded + RELAY_SHELL_POLICY_ALLOW

Observe only

RELAY_SHELL_POLICY_MODE=readonly

Refuse specific text (checked first, in every mode)

RELAY_SHELL_POLICY_DENY

Two-step confirmation for Tier-3 calls

RELAY_SHELL_CONFIRM_TIER3=true

Verified SSH host keys, no per-call downgrade

RELAY_SHELL_SSH_KNOWN_HOSTS=strict, RELAY_SHELL_SSH_ALLOW_WEAKER_KNOWN_HOSTS=false

Tamper-evident audit

RELAY_SHELL_AUDIT_CHAIN=true, plus the off-host sync in deploy/evidence/

Authenticated HTTP transport

RELAY_SHELL_AUTH_ENABLED=true (OAuth 2.1) behind the Caddy edge

Silence the [NOTICE] lines

RELAY_SHELL_UNSAFE_NOTICES=false

Security

The server grants real administrative power. Run it as a scoped service account, only on hosts you are authorized to administer, behind the network controls in docs/deployment.md. A privileged (root or sudo) deployment is supported for isolated administrative hosts with strict network controls and full audit shipping. SECURITY.md has the threat model and how to report a vulnerability.

Besides the audit record, notices and bounds above:

  • Audit integrity: the log is append-only on disk with a rotation-safe handler. The optional hash chain makes edits, insertions, reorders and interior deletions detectable with relay-shell --verify-audit (fail-closed); detecting tail truncation needs the off-host copy.

  • Redaction: audited arguments are scrubbed of tokens, keys and Authorization material. Heuristic; tested with synthetic secrets.

  • Syscall visibility (optional): RELAY_SHELL_SECCOMP_NOTIFY records a spawned child's execve, privilege, namespace, mount, write-open and privilege-relevant prctl syscalls. It never blocks a syscall and installs only with CAP_SYS_ADMIN.

  • OAuth 2.1 (optional, off by default): dynamic client registration behind an operator approval gate, PKCE, rotating refresh tokens with reuse detection. See docs/auth.md.

  • Edge: the Caddy config from deploy/install-edge.sh restricts the MCP endpoint to known CIDRs, adds security headers, caps public OAuth request bodies, keeps authorization codes out of the access log and automates TLS via ACME. The systemd unit applies resource caps.

Compatibility

Surface

Supported

Notes

Python

3.12 / 3.14

Full CI on the corresponding Ubuntu LTS; 3.13 is not a maintained profile.

Host OS

Ubuntu 24.04 / 26.04 LTS

24.04 uses Python 3.12; 26.04 uses Python 3.14. Both run full tests and installer/recovery rehearsals. macOS is development-only.

Windows

SSH target only

Over OpenSSH with PowerShell 7; not supported as a host (no PTY contract, no systemd).

Transports

stdio, streamable-http

stdio is the default; HTTP binds loopback and needs a TLS edge.

MCP SDK

mcp>=2,<3

Pinned in requirements.txt; a bump triggers a fresh validation pass.

SSH library

asyncssh>=2.23.0,<3 (tested 2.24.1)

Native async; no shell-out to the system ssh (only ssh_keyscan runs ssh-keyscan).

Development

ruff check . && ruff format --check .
mypy
pytest

Layout: src/relay_shell/ (server and modules), deploy/ (systemd unit, Caddyfile, logrotate, installers, off-host audit sync, monitoring), docs/ (start at docs/index.md), tests/ (unit and integration; an in-process SSH server, no network).

CONTRIBUTING.md covers scope, branch naming, the local loop and review of security-sensitive changes; docs/runbook.md holds the audit, review and validation procedures and the backlog. AI agents follow AGENTS.md (with CLAUDE.md for Claude). Participation is governed by the Code of Conduct.

License

Apache-2.0. See LICENSE and NOTICE.

Available Tools

18 tools
server_infoA

Report version, effective limits, policy mode, and audit status.

Inputs: no named parameters. Returns: result

session_killA

Signal a session and (by default) close and reap it.

Inputs: session_id (required), signal_name, close. Returns: result

session_listA

List active sessions with size, age, idle, and byte counters.

Inputs: no named parameters. Returns: result

session_recvA

Read buffered/new output from a session, waiting up to timeout seconds.

Inputs: session_id (required), timeout, max_bytes. Returns: result

session_resizeC

Resize a session's PTY.

Inputs: session_id (required), cols (required), rows (required). Returns: result

session_sendC

Send input to a session (local or SSH). enter appends a newline.

Inputs: session_id (required), data (required), enter. Returns: result

shell_execB

Run a shell command on the local host and return its combined output.

Timeout and output size are clamped to the server limits. Set use_shell=false to exec an argv without a shell.

Inputs: command (required), timeout, max_output, cwd, +4 more. Returns: result

shell_scriptA

Run a multi-line script (bash/sh/python) fed on stdin.

With strict and a shell interpreter, set -euo pipefail is prepended so failures abort early.

Inputs: script (required), interpreter, strict, timeout, +2 more. Returns: result

shell_spawnB

Start a persistent local PTY session; returns a session id.

Drive it with session_send / session_recv.

Inputs: command, cols, rows, cwd, +1 more. Returns: result

ssh_checkC

Probe connectivity to the given hosts (or the whole inventory).

Inputs: hosts, timeout. Returns: result

ssh_downloadC

Download a file or tree from a remote host via SFTP.

Inputs: host (required), remote_path (required), local_path (required), recursive, +5 more. Returns: result

ssh_execA

Run a command on a remote host over SSH and return its output.

host may be an inventory/ssh_config alias or user@host. known_hosts is strict | accept-new | ignore.

Inputs: host (required), command (required), timeout, user, +4 more. Returns: result

ssh_forwardC

Create a port forward.

Spec: L:lport:dhost:dport (local), R:rport:dhost:dport (remote), or D:lport (dynamic SOCKS).

Inputs: host (required), spec (required), user, port, +3 more. Returns: result

ssh_forward_closeB

Close an SSH port forward by id.

Inputs: forward_id (required). Returns: result

ssh_forward_listB

List active SSH port forwards.

Inputs: no named parameters. Returns: result

ssh_hostsA

Show the resolved host inventory (ssh_config + inventory file).

Inputs: no named parameters. Returns: result

ssh_spawnB

Open a persistent interactive PTY on a remote host; returns a session id.

Inputs: host (required), command, cols, rows, +5 more. Returns: result

ssh_uploadB

Upload a file or tree to a remote host via SFTP.

Inputs: host (required), local_path (required), remote_path (required), recursive, +5 more. Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.4/5.0

    Scored across 18 tools

    Disambiguation4/5

    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 Consistency4/5

    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.

    Tool Count5/5

    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.

    Completeness4/5

    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.

    Maintenance

    ActivityActive
    ResponsivenessLimited data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      B
      quality
      D
      maintenance
      A server that enables remote command execution over SSH through the Model Context Protocol (MCP), supporting both password and private key authentication.
      1
      10 npm
      2
      MIT
    • F
      license
      A
      quality
      D
      maintenance
      A local Model Context Protocol server that allows LLMs to securely execute shell commands on remote Linux and Windows systems via SSH connections.
      6
      10 npm
      2
      -
    • A
      license
      A
      quality
      A
      maintenance
      A Model Context Protocol server that allows LLMs to securely execute shell commands on remote Linux and Windows systems via SSH. It supports password and key-based authentication, command timeouts, and sudo elevation for administrative tasks.
      15
      16,179 npm
      869
      MIT