{
  "markdown": "<!-- SPDX-FileCopyrightText: 2026 Zw-awa\nSPDX-License-Identifier: Apache-2.0 -->\n\n# ssh-session-mcp\n\n[中文](README.zh-CN.md) | **English**\n\n[![License: Apache%202.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)\n[![Node.js Version](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org/)\n[![TypeScript](https://img.shields.io/badge/TypeScript-5.9.2-blue)](https://www.typescriptlang.org/)\n[![npm version](https://img.shields.io/npm/v/ssh-session-mcp)](https://www.npmjs.com/package/ssh-session-mcp)\n\nPersistent shared-terminal runtime for MCP clients over SSH.\n\n`ssh-session-mcp` gives the user and the AI the same SSH PTY session, adds a browser viewer, tracks who typed what, and makes long-running remote work manageable instead of stateless.\n\n![ssh-session-mcp hero demo](https://raw.githubusercontent.com/Zw-awa/ssh-session-mcp/main/site/assets/hero-loop.gif)\n\n## Contents\n\n- [Install At A Glance](#install-at-a-glance)\n- [Project Structure](#project-structure)\n- [Quick Start](#quick-start)\n- [Docker Status](#6-docker-status)\n- [MCP Tools](#mcp-tools)\n- [Configuration Summary](#configuration-summary)\n- [Security](#security)\n- [Docs](#docs)\n- [Development](#development)\n\n## Install At A Glance\n\n- Normal users do not need to `git clone` this repository.\n- Preferred install path for MCP clients: `npx -y ssh-session-mcp --viewerPort=auto`\n- Preferred install path for human operators who want local binaries: `npm install -g ssh-session-mcp`\n- Official container distribution can be published to a public registry such as `docker.io/zwawa/ssh-session-mcp`\n- `git clone` is only for contributors, source builds, and local development.\n- For the common desktop MCP workflow, `npx` or a global npm install is still the lowest-friction path. Docker is mainly useful when you want a pinned runtime, container-based deployment, or registry-backed distribution.\n\n## Why It Exists\n\nMost SSH-oriented MCP servers can execute commands, but they do not manage terminal state well enough for real collaboration.\n\n`ssh-session-mcp` focuses on the missing runtime layer:\n\n- One shared PTY for both the human and the AI\n- Browser terminal for live inspection and manual intervention\n- Input lock so the AI does not type over the user\n- Safe/full execution modes for risky commands\n- Configurable default policy rules plus session-level custom rule overrides\n- Async command tracking for long-running remote work\n- Multi-device and multi-connection profile support\n- Local debug mode for demos, offline testing, and prompt iteration\n\n## Best Fit\n\n- AI-assisted remote development on Linux boards and SSH servers\n- Embedded, ROS, training, and deployment hosts that need a real terminal\n- Users who want the AI to help, but do not want to surrender the terminal\n- MCP Marketplace listings where the install and demo path must be clear\n\n## Project Structure\n\nKey directories and files:\n\n| Path | Purpose |\n|------|---------|\n| `src/` | Core TypeScript implementation for the MCP server, SSH session runtime, viewer, tools, and config CLIs |\n| `src/viewer-html/` | HTML page generators and browser-side scripts for the terminal viewer |\n| `test/` | Vitest coverage for runtime behavior, viewer contracts, config loading, and repository validation |\n| `docs/` | Supporting documentation such as contracts, failure taxonomy, platform notes, and Docker usage |\n| `docs/examples/` | Example config files for normal and Docker-oriented setups |\n| `scripts/` | Build, version sync, and local operator helper scripts |\n| `deploy/helm/` | Helm chart for Kubernetes deployment in single-node or distributed v0 mode |\n| `site/` | GitHub Pages landing page source |\n| `dist/` | Generated static site output from `npm run build:site` |\n| `build/` | Generated JavaScript output from `npm run build` |\n| `Dockerfile` | Container image build definition |\n| `docker-compose.yml` | Profile-based Docker Compose example |\n| `docker-compose.env.yml` | Legacy `.env`-style Docker Compose example |\n| `server.json` | MCP server metadata for marketplace-style distribution |\n| `AGENT.md` | Primary agent/operator playbook |\n| `llms-install.md` | Agent-focused installation and environment checklist |\n| `.env.example` | Legacy single-target environment variable template |\n\n## Quick Start\n\n### 1. Agent-First Install (Auto-download on first run)\n\nIf the goal is to let Claude Code, Codex, or OpenCode install the server automatically, prefer `npx -y ssh-session-mcp` in the MCP command instead of a prior global install.\n\nFor Cline Marketplace and other agent installers, see [llms-install.md](llms-install.md). This repo is structured to be one-click installable through an `npx -y ssh-session-mcp --viewerPort=auto` command.\n\n#### Claude Code\n\n```bash\nclaude mcp add --transport stdio ssh-session-mcp -- npx -y ssh-session-mcp --viewerPort=auto\n```\n\nWindows note from the Claude Code docs: native Windows users should wrap `npx` with `cmd /c` for stdio MCP servers.\n\n```bash\nclaude mcp add --transport stdio ssh-session-mcp -- cmd /c npx -y ssh-session-mcp --viewerPort=auto\n```\n\n#### Codex\n\n```bash\ncodex mcp add ssh-session-mcp -- npx -y ssh-session-mcp --viewerPort=auto\n```\n\n#### OpenCode\n\nOpenCode's `opencode mcp add` flow is interactive. Choose a local MCP server and use this command:\n\n```bash\nnpx -y ssh-session-mcp --viewerPort=auto\n```\n\nIf you prefer config instead of the interactive flow:\n\n```json\n{\n  \"$schema\": \"https://opencode.ai/config.json\",\n  \"mcp\": {\n    \"ssh-session-mcp\": {\n      \"type\": \"local\",\n      \"command\": [\"npx\", \"-y\", \"ssh-session-mcp\", \"--viewerPort=auto\"]\n    }\n  }\n}\n```\n\nThis is the closest thing to \"automatic installation\" for stdio MCP servers today: the MCP client stores the command, and `npx -y` downloads the package automatically the first time it runs.\n\n### 2. Fastest Local Demo\n\n```bash\nnpm install -g ssh-session-mcp\nssh-session-mcp-ctl launch --local --viewerPort=auto\n```\n\nThis starts a local shell instead of SSH and opens the browser terminal, which is the easiest way to test the MCP runtime before touching a real server.\n\n### 3. Register As An MCP Server\n\nUse the MCP server binary directly when wiring a client:\n\n```bash\n# Global install\nnpm install -g ssh-session-mcp\n\n# Server command used by MCP clients\nssh-session-mcp --viewerPort=auto\n```\n\n```bash\n# Claude Code\nclaude mcp add --transport stdio ssh-session-mcp -- ssh-session-mcp --viewerPort=auto\n\n# Codex CLI\ncodex mcp add ssh-session-mcp -- ssh-session-mcp --viewerPort=auto\n```\n\nIf you prefer `npx` instead of a global install:\n\n```bash\nnpx -y ssh-session-mcp --viewerPort=auto\n```\n\n### 4. Connect To A Real SSH Target\n\nCreate `.env` from `.env.example`:\n\n```bash\ncp .env.example .env\n```\n\n```ini\nSSH_HOST=YOUR_DEVICE_HOST\nSSH_PORT=22\nSSH_USER=YOUR_DEVICE_USER\nSSH_PASSWORD=\nSSH_KEY=\nVIEWER_PORT=auto\nAUTO_OPEN_TERMINAL=false\nSSH_MCP_MODE=safe\n```\n\nThen launch:\n\n```bash\nssh-session-mcp-ctl launch --viewerPort=auto\n```\n\n### 5. Multi-Device Config\n\nFor multiple boards or named targets, create `ssh-session-mcp.config.json`:\n\n```json\n{\n  \"defaultDevice\": \"DEVICE_A_ID\",\n  \"devices\": [\n    {\n      \"id\": \"DEVICE_A_ID\",\n      \"host\": \"DEVICE_A_HOST\",\n      \"port\": 22,\n      \"user\": \"DEVICE_A_USER\",\n      \"auth\": { \"passwordEnv\": \"DEVICE_A_PASSWORD\" },\n      \"defaults\": {\n        \"term\": \"xterm-256color\",\n        \"cols\": 120,\n        \"rows\": 40,\n        \"autoOpenViewer\": true,\n        \"viewerMode\": \"browser\"\n      }\n    }\n  ]\n}\n```\n\nDiscovery order:\n\n1. `--config=/path/to/config.json`\n2. Workspace `ssh-session-mcp.config.json`\n3. User-global config\n4. Legacy `.env` fallback\n\nImportant:\n\n- Config discovery is based on the MCP process working directory.\n- `auth.password` is intentionally unsupported. Use `auth.passwordEnv` or `auth.keyPath`.\n- Secrets belong in `.env` or the parent environment, not in repo-tracked JSON.\n\n### 6. Docker Status\n\nPublic Docker images should be distributed through Docker Hub, with GitHub Container Registry as an optional secondary registry:\n\n```bash\ndocker.io/zwawa/ssh-session-mcp:<version>\ndocker.io/zwawa/ssh-session-mcp:latest\nghcr.io/zw-awa/ssh-session-mcp:<version>\n```\n\nRecommended container launch for a real SSH target:\n\n```bash\ndocker run --rm -i \\\n  -p 8793:8793 \\\n  -e VIEWER_PORT=8793 \\\n  -e VIEWER_HOST=0.0.0.0 \\\n  -e SSH_HOST=YOUR_DEVICE_HOST \\\n  -e SSH_PORT=22 \\\n  -e SSH_USER=YOUR_DEVICE_USER \\\n  -e SSH_PASSWORD \\\n  docker.io/zwawa/ssh-session-mcp:latest\n```\n\nExport the password in your shell first instead of placing it directly on the command line.\n\nRecommended launch for profile-based config:\n\n```bash\ndocker run --rm -i \\\n  -p 8793:8793 \\\n  -e VIEWER_PORT=8793 \\\n  -e VIEWER_HOST=0.0.0.0 \\\n  -e SSH_MCP_CONFIG=/workspace/ssh-session-mcp.config.json \\\n  -v \"$PWD/ssh-session-mcp.config.json:/workspace/ssh-session-mcp.config.json:ro\" \\\n  -v \"/path/to/host/keys:/workspace/keys:ro\" \\\n  docker.io/zwawa/ssh-session-mcp:latest\n```\n\nEquivalent Compose example:\n\n```bash\ndocker compose up -d\n```\n\nSee [docker-compose.yml](docker-compose.yml) for a ready-to-run example that mounts `ssh-session-mcp.config.json`, publishes the viewer on `8793`, and uses `SSH_KEY_DIR` when set or falls back to a dedicated `./keys` directory.\nFor the full Docker guide, including the legacy `.env` compose variant and MCP client config snippets, see [docs/docker.md](docs/docker.md).\nFor a container-oriented profile example, see [docs/examples/ssh-session-mcp.config.docker.example.json](docs/examples/ssh-session-mcp.config.docker.example.json).\n\nContainer-specific notes:\n\n- The image defaults `VIEWER_PORT` to `8793` when unset so the browser viewer can be published reliably.\n- The image defaults `VIEWER_HOST` to `0.0.0.0` inside the container so the mapped port is reachable from the host.\n- `AUTO_OPEN_TERMINAL` defaults to `false` in the container because browser auto-open from inside a container is usually not useful.\n- Mount config files or SSH keys read-only when possible.\n- Prefer mounting SSH keys from a directory outside the repo root.\n- In `docker-compose.yml`, `SSH_KEY_DIR` overrides the default key mount path. If it is unset, Compose falls back to `./keys`, not the repo root.\n- Avoid putting passwords directly on the command line. Prefer exported env vars, Compose `.env`, or `--env-file`.\n- For stdio MCP clients, Docker is viable, but host-native `npx` is still simpler unless your client explicitly prefers containerized commands.\n\nDocker-based MCP client command examples:\n\n```bash\n# Claude Code\nclaude mcp add --transport stdio ssh-session-mcp -- docker run --rm -i -p 8793:8793 -e VIEWER_PORT=8793 -e VIEWER_HOST=0.0.0.0 docker.io/zwawa/ssh-session-mcp:latest\n\n# Codex CLI\ncodex mcp add ssh-session-mcp -- docker run --rm -i -p 8793:8793 -e VIEWER_PORT=8793 -e VIEWER_HOST=0.0.0.0 docker.io/zwawa/ssh-session-mcp:latest\n```\n\nFor JSON-based MCP clients, the same pattern works by using `docker` as the command and passing the remaining `run ... docker.io/zwawa/ssh-session-mcp:latest` tokens as args.\n\nThis is useful when:\n\n- The primary workflow is a local stdio MCP server command, not a long-lived network service.\n- You want a pinned Node/runtime environment without a local install.\n- You need registry-based distribution for a team or managed host.\n- You want container-level isolation for the MCP server process.\n\nFor many users, publishing to npm and recommending `npx -y ssh-session-mcp --viewerPort=auto` is still the lower-friction install path.\n\n## Viewer And Collaboration Model\n\nThe browser viewer is not decorative. It is part of the workflow:\n\n- The user can see exactly what the AI did.\n- The AI can pause when the user takes over.\n- Password prompts, pagers, and editors become visible state instead of hidden failure modes.\n- Session diagnostics and history turn terminal debugging into something inspectable.\n\n## Marketplace-Friendly Flow\n\nFor users:\n\n```text\ninstall -> launch viewer -> connect once -> keep the session alive -> let the AI help\n```\n\nFor agents:\n\n```text\nssh-quick-connect -> ssh-run -> inspect output -> ssh-command-status if needed -> ssh-run again\n```\n\nUse [AGENT.md](AGENT.md) when you want the AI to install, inspect config, connect devices, and help the user end-to-end. Compatibility notes for older agent setups remain in [AI_AGENT_GUIDE.md](AI_AGENT_GUIDE.md).\n\n## Core Differences From A Stateless MCP SSH Wrapper\n\n- Shared PTY instead of one-off command execution\n- Actor-aware transcript markers for user, system, and agent input\n- Terminal-state checks before dangerous or nonsensical writes\n- Auto cleanup for sessions and viewer processes\n- Session-scoped browser viewer with diagnostics and history\n- Local debug mode with `--local` for offline testing\n\n## Operation Modes\n\n| Mode | Behavior |\n|------|----------|\n| `safe` | Default per session. Automatically blocks obviously dangerous, interactive, or never-ending commands. |\n| `full` | Per session. Relaxes the guardrails for advanced use, while still blocking a small set of clearly destructive abuse cases. |\n\nEach session now owns its own `safe` / `full` mode. Switching one browser terminal to `full` does not change other sessions.\n\nThe default rule set can be customized if needed. Custom rules now support:\n\n- `error`: block the command\n- `warning`: allow but surface a warning\n- `log`: allow and annotate only\n\nRule precedence is `error > warning > log`, and within the same level, earlier rules win.\n\n## Lock Policy\n\nThe browser terminal UI lets the operator choose one of these input policies:\n\n| Policy | What the operator experiences |\n|------|-------------------------------|\n| `common` | User and agent can both type into the shared terminal. |\n| `user` | Only the user can type. Agent write actions are blocked. |\n| `auto` | The user can start typing without fighting the agent. While the user is actively drafting input, agent writes are blocked. |\n| `agent` | Only the agent can type. User input is blocked until the policy changes. |\n\nWhen the terminal is not available for agent input, tools such as `ssh-run`, `ssh-session-send`, and `ssh-session-control` return a blocked response instead of forcing input into the PTY.\n\n## MCP Tools\n\n### Recommended Daily Tools\n\n| Tool | Purpose |\n|------|---------|\n| `ssh-quick-connect` | Connect or reuse the default target and optionally open the viewer |\n| `ssh-run` | Execute a command with completion detection and exit-code capture |\n| `ssh-status` | Inspect sessions, viewer state, and operation mode |\n| `ssh-command-status` | Poll async command progress |\n| `ssh-retry` | Retry flaky commands with backoff |\n| `ssh-session-policy-list` | Inspect inherited defaults and current session custom policy rules |\n| `ssh-session-policy-upsert` | Add or update a session-level custom policy rule |\n| `ssh-session-policy-remove` | Remove a session-level custom policy rule |\n| `ssh-session-policy-reset` | Reset session custom rules back to inherited defaults |\n\n### Full Tool Catalog\n\n| Tool | Purpose |\n|------|---------|\n| `ssh-session-open` | Open a session with explicit SSH parameters |\n| `ssh-session-send` | Send raw PTY input |\n| `ssh-device-list` | List configured devices and defaults |\n| `ssh-session-read` | Read buffered terminal output by offset |\n| `ssh-session-watch` | Long-poll for output and dashboard changes |\n| `ssh-session-history` | Read line-numbered mixed terminal history |\n| `ssh-session-control` | Send control keys such as `ctrl_c`, arrows, or `tab` |\n| `ssh-session-resize` | Resize the PTY |\n| `ssh-session-list` | List tracked sessions |\n| `ssh-session-diagnostics` | Inspect lock state, warnings, running command state, and viewer health |\n| `ssh-session-policy-list` | Show inherited policy defaults and the current session rule set |\n| `ssh-session-policy-upsert` | Add or update a session-specific custom policy rule |\n| `ssh-session-policy-remove` | Remove a session-specific custom policy rule |\n| `ssh-session-policy-reset` | Restore inherited rules for the current session |\n| `ssh-session-set-active` | Choose the default session |\n| `ssh-viewer-ensure` | Open or reuse the local viewer |\n| `ssh-viewer-list` | List tracked viewer processes |\n| `ssh-session-close` | Close a session cleanly |\n| `ssh-quick-connect` | One-step connect flow for agents |\n| `ssh-run` | Main command execution tool |\n| `ssh-status` | Runtime overview |\n| `ssh-command-status` | Async poller |\n| `ssh-retry` | Retry executor |\n\n## Local Operator Commands\n\nThese helpers are for humans on the workstation that owns the viewer:\n\n```bash\nssh-session-mcp-ctl status\nssh-session-mcp-ctl devices\nssh-session-mcp-ctl launch --viewerPort=auto\nssh-session-mcp-ctl launch --local --viewerPort=auto\nssh-session-mcp-ctl logs --tail=60\nssh-session-mcp-ctl cleanup\n```\n\nDefault rule library management for operators:\n\n```bash\nssh-session-mcp-config policy list --scope=merged\nssh-session-mcp-config policy set error-kubectl-delete --pattern=\"\\\\bkubectl\\\\s+delete\\\\b\" --category=dangerous --action=error --priority=0 --message=\"kubectl delete is blocked in safe mode\"\nssh-session-mcp-config policy remove error-kubectl-delete\n```\n\nEquivalent repo-local commands also exist:\n\n```bash\nnpm run launch\nnpm run status\nnpm run devices\nnpm run logs\nnpm run cleanup\n```\n\n## Configuration Summary\n\nKey environment variables:\n\n| Variable | Meaning | Default |\n|----------|---------|---------|\n| `SSH_HOST` | Legacy single-target SSH host | required in legacy mode |\n| `SSH_PORT` | Legacy single-target SSH port | `22` |\n| `SSH_USER` | Legacy single-target SSH user | required in legacy mode |\n| `SSH_PASSWORD` | Password auth | empty |\n| `SSH_KEY` | Local private key path | empty |\n| `SSH_PASSWORD_FILE` | File containing the SSH password | empty |\n| `SSH_KEY_FILE` | File containing the SSH private key | empty |\n| `SSH_MCP_INSTANCE` | Runtime isolation key | `proc-<pid>` or helper-selected |\n| `SSH_MCP_CONFIG` | Explicit config file path | auto-discovery |\n| `SSH_MCP_STATE_DIR` | Runtime state root directory | platform default |\n| `VIEWER_HOST` | Viewer bind host | `127.0.0.1` |\n| `VIEWER_PORT` | Viewer port or `auto` | `0` unless configured |\n| `VIEWER_ACCESS_MODE` | Viewer IP filter mode | config-driven |\n| `SSH_MCP_MODE` | `safe` or `full` | `safe` |\n| `SSH_MCP_LOCAL` | Launch a local shell instead of SSH | `false` |\n| `SSH_MCP_DEBUG` | Enable debug browser actions | `false` |\n| `AUTO_OPEN_TERMINAL` | Auto-open browser terminal | `false` |\n| `SSH_MCP_LOG_MODE` | `off`, `meta`, or `stderr` logging | `off` |\n| `SSH_MCP_LOG_DIR` | Metadata log directory | platform default |\n\n### Distributed v0\n\nDistributed v0 intentionally implements a narrow boundary:\n\n- Supported runtime modes: `single-node` and `distributed`\n- Distributed mode shares control-plane state only: node heartbeat, session metadata, binding metadata, command metadata, and viewer access policy\n- When the current replica is not the owner, HTTP APIs return `REMOTE_OWNER`, HTML pages render a remote-owner error page, and websocket attaches close with code `4009`\n- Cross-node PTY migration is not supported\n- Transparent cross-node HTTP or websocket proxying is not supported\n\nDistributed v0 requires Redis for real multi-node deployments. `SSH_MCP_STORE=memory` only exists for local skeleton testing and does not provide a shared store across replicas.\n\nDistributed configuration:\n\n| Variable | Meaning | Default |\n|----------|---------|---------|\n| `SSH_MCP_RUNTIME_MODE` | `single-node` or `distributed` | `single-node` |\n| `SSH_MCP_STORE` | `memory` or `redis` | `redis` in distributed mode, otherwise `memory` |\n| `SSH_MCP_REDIS_URL` | Redis connection URL | required when `SSH_MCP_STORE=redis` |\n| `SSH_MCP_NODE_ID` | Stable logical node id for this replica | runtime instance id |\n| `SSH_MCP_PUBLIC_BASE_URL` | Public viewer base URL advertised to other replicas | unset |\n| `SSH_MCP_AUTH_MODE` | `off` or `proxy` | `off` |\n| `SSH_MCP_TRUST_PROXY` | Whether to trust authenticated proxy headers | `false` |\n| `SSH_MCP_AUTH_USER_HEADER` | Authenticated user header name | `x-ssh-session-mcp-user` |\n| `SSH_MCP_AUTH_ROLE_HEADER` | Authenticated role header name | `x-ssh-session-mcp-role` |\n\nRecommended distributed env example:\n\n```bash\nSSH_MCP_RUNTIME_MODE=distributed\nSSH_MCP_STORE=redis\nSSH_MCP_REDIS_URL=redis://redis:6379/0\nSSH_MCP_NODE_ID=node-a\nSSH_MCP_PUBLIC_BASE_URL=https://ssh-mcp.example.com\nSSH_MCP_AUTH_MODE=proxy\nSSH_MCP_TRUST_PROXY=true\nSSH_MCP_AUTH_USER_HEADER=x-forwarded-user\nSSH_MCP_AUTH_ROLE_HEADER=x-forwarded-role\n```\n\nProxy auth is most useful in distributed mode behind a trusted reverse proxy. The built-in role mapping is:\n\n- `viewer_read`: pages, session list/read endpoints, history, diagnostics, health, readiness, metrics\n- `viewer_write`: attach input, resize, control\n- `session_admin`: mode changes, policy updates, close, set-active, debug-agent actions, local debug session creation\n\n### Macro / Environment Variable Reference\n\nUse these variables according to your installation path:\n\n| Variable | Required When | Accepted Values / Example | Notes |\n|----------|---------------|---------------------------|-------|\n| `SSH_HOST` | Legacy single-target SSH mode | `YOUR_DEVICE_HOST` | Required unless you use `ssh-session-mcp.config.json` or `--local`. |\n| `SSH_PORT` | Legacy single-target SSH mode | `22` | Optional in legacy mode; defaults to `22`. |\n| `SSH_USER` | Legacy single-target SSH mode | `YOUR_DEVICE_USER` | Required unless you use device profiles. |\n| `SSH_PASSWORD` | Password-based auth | exported env var | Prefer env export over putting the password directly in the command line. |\n| `SSH_PASSWORD_FILE` | Password-based auth via secret file | `/run/secrets/ssh_password` | The file contents are used as the password. This is the preferred pattern for Docker and Kubernetes secrets. |\n| `SSH_KEY` | Key-based auth in legacy mode | `/absolute/path/to/private/key` | The path must exist on the host running the MCP server. |\n| `SSH_KEY_FILE` | Key-based auth via secret file | `/run/secrets/ssh_private_key` | The file contents are used as the private key. This works well with mounted container secrets. |\n| `SSH_MCP_CONFIG` | Profile-based mode or config outside cwd | `/path/to/ssh-session-mcp.config.json` | Use this when config auto-discovery is not enough. |\n| `SSH_MCP_INSTANCE` | Multi-agent / multi-client isolation | `agent-a` | Use different values when two agents should not share runtime state. |\n| `SSH_MCP_STATE_DIR` | Runtime state root override | `/workspace/state` | Controls where per-instance server info, viewer state, and default logs are stored. Mount it persistently in containers. |\n| `SSH_MCP_RUNTIME_MODE` | Distributed topology selection | `single-node`, `distributed` | Distributed v0 only shares control-plane state; it does not migrate PTYs across nodes. |\n| `SSH_MCP_STORE` | Distributed state backend | `memory`, `redis` | Use `redis` for any real multi-node deployment. `memory` is only for local distributed skeleton testing. |\n| `SSH_MCP_REDIS_URL` | Redis backend enabled | `redis://redis:6379/0` | Required when `SSH_MCP_RUNTIME_MODE=distributed` and `SSH_MCP_STORE=redis`. |\n| `SSH_MCP_NODE_ID` | Stable distributed node id | `node-a` | Useful when multiple replicas share Redis and need durable owner ids. |\n| `SSH_MCP_PUBLIC_BASE_URL` | Public routing hint for this node | `https://ssh-mcp.example.com` | Used in `REMOTE_OWNER` payloads and cluster status output. |\n| `SSH_MCP_AUTH_MODE` | Viewer auth mode | `off`, `proxy` | `proxy` is recommended only behind a trusted reverse proxy. |\n| `SSH_MCP_TRUST_PROXY` | Trust viewer identity headers | `true`, `false` | Must be enabled together with `SSH_MCP_AUTH_MODE=proxy`. |\n| `SSH_MCP_AUTH_USER_HEADER` | Proxy-auth viewer user header | `x-forwarded-user` | Header names are normalized to lowercase internally. |\n| `SSH_MCP_AUTH_ROLE_HEADER` | Proxy-auth viewer role header | `x-forwarded-role` | Roles are comma-separated and mapped to `viewer_read`, `viewer_write`, `session_admin`. |\n| `VIEWER_HOST` | Custom viewer bind | `127.0.0.1`, `0.0.0.0` | Use `0.0.0.0` inside containers; keep `127.0.0.1` on normal host installs unless you need remote access. |\n| `VIEWER_PORT` | Viewer enabled | `auto`, `0`, `8793` | `auto` picks a free port, `0` disables the viewer, fixed ports are best for Docker. |\n| `VIEWER_ACCESS_MODE` | Viewer access control mode | `allow_all`, `allowlist`, `denylist` | Usually edited in the viewer home page. Keep `allow_all` only when you stay on localhost. |\n| `AUTO_OPEN_TERMINAL` | Auto-open viewer tab | `true`, `false` | Usually `false` in containers. |\n| `SSH_MCP_MODE` | Runtime safety mode | `safe`, `full` | `safe` is the recommended default. |\n| `SSH_MCP_LOCAL` | Local demo mode | `true`, `false` | Starts a local shell instead of SSH. |\n| `SSH_MCP_DEBUG` | Browser debug controls | `true`, `false` | Intended for demos and troubleshooting. |\n| `SSH_MCP_LOG_MODE` | Runtime metadata logging | `off`, `meta`, `stderr` | `meta` writes JSONL metadata logs without storing raw secrets. `stderr` is the preferred container mode because it preserves stdio MCP transport while exposing structured logs to the container runtime. |\n| `SSH_MCP_LOG_DIR` | Override metadata log directory | `/workspace/state/instances/<instance>/logs` | Mainly useful with `SSH_MCP_LOG_MODE=meta`; ignored for `stderr`. |\n| `SSH_KEY_DIR` | Docker Compose profile-based example | `/path/to/host/keys` | Optional in `docker-compose.yml`; when unset it falls back to `./keys`. |\n| `SSH_SESSION_MCP_IMAGE` | Docker Compose image override | `docker.io/zwawa/ssh-session-mcp:latest` | Override this if you mirror the image or test another tag. |\n\n### Minimum Required Settings\n\nChoose one of these minimum configuration sets:\n\n- Local demo: `SSH_MCP_LOCAL=true` and `VIEWER_PORT=auto`\n- Legacy SSH with password: `SSH_HOST`, `SSH_USER`, `SSH_PASSWORD`\n- Legacy SSH with key: `SSH_HOST`, `SSH_USER`, `SSH_KEY`\n- Profile-based mode: `ssh-session-mcp.config.json`, plus any `passwordEnv` variables referenced by that config\n- Docker Compose profile mode: `ssh-session-mcp.config.json`, optional `SSH_KEY_DIR`, optional `SSH_SESSION_MCP_IMAGE`\n\n### Container Runtime Notes\n\n- Container defaults now set `SSH_MCP_LOG_MODE=stderr` so logs go to the container runtime without corrupting stdio MCP transport.\n- Mount `SSH_MCP_STATE_DIR` persistently when you want viewer policy, server info, and state files to survive container restarts.\n- Distributed multi-node deployments need Redis plus a routable `SSH_MCP_PUBLIC_BASE_URL` per replica.\n- Distributed v0 does not provide cross-node PTY migration or transparent cross-node proxying. Route requests to the owner node when you receive `REMOTE_OWNER`.\n- Health endpoints:\n  - `/livez` for process liveness\n  - `/readyz` for readiness checks\n  - `/metrics` for Prometheus text metrics\n- Example single-instance Kubernetes baseline: [docs/examples/ssh-session-mcp.k8s.single-instance.yaml](docs/examples/ssh-session-mcp.k8s.single-instance.yaml)\n- Example distributed Kubernetes baseline: [docs/examples/ssh-session-mcp.k8s.distributed.example.yaml](docs/examples/ssh-session-mcp.k8s.distributed.example.yaml)\n- Primary Kubernetes installation path: `deploy/helm/ssh-session-mcp`\n\nExample config file: [docs/examples/ssh-session-mcp.config.example.json](docs/examples/ssh-session-mcp.config.example.json)\n\n## Security\n\n- The package never requires raw passwords inside tracked JSON config.\n- `.env` is ignored by git and npm.\n- Viewer HTTP binds to localhost by default.\n- The MCP server treats terminal mode and input lock as first-class safety signals.\n- CI runs Trivy filesystem and container-image scans against high and critical vulnerabilities.\n- CI installs a pinned Trivy CLI release with checksum verification instead of relying on a floating third-party action tag.\n- Release builds generate a CycloneDX SBOM for the published GHCR image digest and attach it to the GitHub release.\n- Release builds sign the published GHCR image digest with keyless Cosign.\n- GHCR digest is the primary verification path. Docker Hub remains a distribution path, not the main signature-verification target.\n\nSee [SECURITY.md](SECURITY.md) for the full policy.\n\n## Platform Notes\n\n- Windows 10/11: first-class host environment\n- Linux: strong fit for headless MCP + browser viewer workflows\n- macOS: standard Node.js path supported\n- Remote Linux hosts: first-class target\n\nMore detail: [docs/platform-compatibility.md](docs/platform-compatibility.md)\n\n## Docs\n\n- [AGENT.md](AGENT.md)\n- [AI_AGENT_GUIDE.md](AI_AGENT_GUIDE.md)\n- [llms-install.md](llms-install.md)\n- [docs/contracts.md](docs/contracts.md)\n- [docs/failure-taxonomy.md](docs/failure-taxonomy.md)\n- [docs/acceptance-scenarios.md](docs/acceptance-scenarios.md)\n- [docs/docker.md](docs/docker.md)\n- [docs/kubernetes.md](docs/kubernetes.md)\n- [docs/ingress-proxy-auth.md](docs/ingress-proxy-auth.md)\n- [CHANGELOG.md](CHANGELOG.md)\n\n## Development\n\nClone the repo only if you want to modify the source, run tests locally, or build release artifacts.\n\n```bash\nnpm install\nnpm run build\nnpm run test\nnpm run validate:repo\nnpm run build:site\n```\n\nGitHub Actions included in this repo can:\n\n- run CI on push and pull request\n- deploy a GitHub Pages landing page from `dist/`\n- build a tagged GitHub Release with the npm package tarball attached\n\n## License\n\nApache-2.0. See [LICENSE](LICENSE).\n",
  "bytes": 29380,
  "sha": "3fdb12a42165c893c9f6c550719611245f1beaee689f0e871c72208823b7860e",
  "repo_slug": "zw-awa/ssh-session-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_zw_awa_ssh_session_mcp_6a090aae/readme"
}