Shared SSH PTY runtime for MCP clients with viewer, input lock, async tracking, and policy rules.
io.github.Zw-awa/ssh-session-mcp is a Model Context Protocol (MCP) server that provides a shared SSH PTY runtime for MCP clients. It supports a viewer, input locking, asynchronous tracking, and policy rules. The project is described as a “persistent shared-terminal runtime” and is built for JavaScript/TypeScript environments.
🛠️ Key Features
Shared SSH PTY runtime for MCP clients
Viewer component for terminal interaction
Input lock for controlled input handling
Async tracking
Policy rules support
🚀 Use Cases
Enabling remote terminal sessions for MCP-powered tools
Providing a shared terminal experience across MCP clients
Applying policy rules to govern SSH session behavior
⚡ Developer Benefits
Developer-oriented runtime for MCP clients
Uses topics spanning ssh, terminal, pty, remote, and session
Available in the npm ecosystem (ssh-session-mcp) and targets Node.js (node >= 18) and TypeScript (TypeScript 5.9.2)
⚠️ Limitations
Source excerpt does not specify supported platforms, authentication flow, or deployment details.
Persistent shared-terminal runtime for MCP clients over SSH.
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.
Normal users do not need to git clone this repository.
Preferred install path for MCP clients: npx -y ssh-session-mcp --viewerPort=auto
Preferred install path for human operators who want local binaries: npm install -g ssh-session-mcp
Official container distribution can be published to a public registry such as docker.io/zwawa/ssh-session-mcp
git clone is only for contributors, source builds, and local development.
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.
Why It Exists
Most SSH-oriented MCP servers can execute commands, but they do not manage terminal state well enough for real collaboration.
ssh-session-mcp focuses on the missing runtime layer:
One shared PTY for both the human and the AI
Browser terminal for live inspection and manual intervention
Input lock so the AI does not type over the user
Safe/full execution modes for risky commands
Configurable default policy rules plus session-level custom rule overrides
Async command tracking for long-running remote work
Multi-device and multi-connection profile support
Local debug mode for demos, offline testing, and prompt iteration
Best Fit
AI-assisted remote development on Linux boards and SSH servers
Embedded, ROS, training, and deployment hosts that need a real terminal
Users who want the AI to help, but do not want to surrender the terminal
MCP Marketplace listings where the install and demo path must be clear
Project Structure
Key directories and files:
Path
Purpose
src/
Core TypeScript implementation for the MCP server, SSH session runtime, viewer, tools, and config CLIs
src/viewer-html/
HTML page generators and browser-side scripts for the terminal viewer
test/
Vitest coverage for runtime behavior, viewer contracts, config loading, and repository validation
docs/
Supporting documentation such as contracts, failure taxonomy, platform notes, and Docker usage
docs/examples/
Example config files for normal and Docker-oriented setups
scripts/
Build, version sync, and local operator helper scripts
deploy/helm/
Helm chart for Kubernetes deployment in single-node or distributed v0 mode
site/
GitHub Pages landing page source
dist/
Generated static site output from npm run build:site
build/
Generated JavaScript output from npm run build
Dockerfile
Container image build definition
docker-compose.yml
Profile-based Docker Compose example
docker-compose.env.yml
Legacy .env-style Docker Compose example
server.json
MCP server metadata for marketplace-style distribution
AGENT.md
Primary agent/operator playbook
llms-install.md
Agent-focused installation and environment checklist
1. Agent-First Install (Auto-download on first run)
If 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.
For Cline Marketplace and other agent installers, see llms-install.md. This repo is structured to be one-click installable through an npx -y ssh-session-mcp --viewerPort=auto command.
This 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.
This 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.
3. Register As An MCP Server
Use the MCP server binary directly when wiring a client:
bash
# Global install
npm install -g ssh-session-mcp
# Server command used by MCP clients
ssh-session-mcp --viewerPort=auto
See 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.
For the full Docker guide, including the legacy .env compose variant and MCP client config snippets, see docs/docker.md.
For a container-oriented profile example, see docs/examples/ssh-session-mcp.config.docker.example.json.
Container-specific notes:
The image defaults VIEWER_PORT to 8793 when unset so the browser viewer can be published reliably.
The image defaults VIEWER_HOST to 0.0.0.0 inside the container so the mapped port is reachable from the host.
AUTO_OPEN_TERMINAL defaults to false in the container because browser auto-open from inside a container is usually not useful.
Mount config files or SSH keys read-only when possible.
Prefer mounting SSH keys from a directory outside the repo root.
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.
Avoid putting passwords directly on the command line. Prefer exported env vars, Compose .env, or --env-file.
For stdio MCP clients, Docker is viable, but host-native npx is still simpler unless your client explicitly prefers containerized commands.
Docker-based MCP client command examples:
bash
# Claude Code
claude 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
# Codex CLI
codex 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
For 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.
This is useful when:
The primary workflow is a local stdio MCP server command, not a long-lived network service.
You want a pinned Node/runtime environment without a local install.
You need registry-based distribution for a team or managed host.
You want container-level isolation for the MCP server process.
For many users, publishing to npm and recommending npx -y ssh-session-mcp --viewerPort=auto is still the lower-friction install path.
Viewer And Collaboration Model
The browser viewer is not decorative. It is part of the workflow:
The user can see exactly what the AI did.
The AI can pause when the user takes over.
Password prompts, pagers, and editors become visible state instead of hidden failure modes.
Session diagnostics and history turn terminal debugging into something inspectable.
Marketplace-Friendly Flow
For users:
text
install -> launch viewer -> connect once -> keep the session alive -> let the AI help
For agents:
text
ssh-quick-connect -> ssh-run -> inspect output -> ssh-command-status if needed -> ssh-run again
Use 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.
Core Differences From A Stateless MCP SSH Wrapper
Shared PTY instead of one-off command execution
Actor-aware transcript markers for user, system, and agent input
Terminal-state checks before dangerous or nonsensical writes
Auto cleanup for sessions and viewer processes
Session-scoped browser viewer with diagnostics and history
Local debug mode with --local for offline testing
Operation Modes
Mode
Behavior
safe
Default per session. Automatically blocks obviously dangerous, interactive, or never-ending commands.
full
Per session. Relaxes the guardrails for advanced use, while still blocking a small set of clearly destructive abuse cases.
Each session now owns its own safe / full mode. Switching one browser terminal to full does not change other sessions.
The default rule set can be customized if needed. Custom rules now support:
error: block the command
warning: allow but surface a warning
log: allow and annotate only
Rule precedence is error > warning > log, and within the same level, earlier rules win.
Lock Policy
The browser terminal UI lets the operator choose one of these input policies:
Policy
What the operator experiences
common
User and agent can both type into the shared terminal.
user
Only the user can type. Agent write actions are blocked.
auto
The user can start typing without fighting the agent. While the user is actively drafting input, agent writes are blocked.
agent
Only the agent can type. User input is blocked until the policy changes.
When 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.
MCP Tools
Recommended Daily Tools
Tool
Purpose
ssh-quick-connect
Connect or reuse the default target and optionally open the viewer
ssh-run
Execute a command with completion detection and exit-code capture
ssh-status
Inspect sessions, viewer state, and operation mode
ssh-command-status
Poll async command progress
ssh-retry
Retry flaky commands with backoff
ssh-session-policy-list
Inspect inherited defaults and current session custom policy rules
ssh-session-policy-upsert
Add or update a session-level custom policy rule
ssh-session-policy-remove
Remove a session-level custom policy rule
ssh-session-policy-reset
Reset session custom rules back to inherited defaults
Full Tool Catalog
Tool
Purpose
ssh-session-open
Open a session with explicit SSH parameters
ssh-session-send
Send raw PTY input
ssh-device-list
List configured devices and defaults
ssh-session-read
Read buffered terminal output by offset
ssh-session-watch
Long-poll for output and dashboard changes
ssh-session-history
Read line-numbered mixed terminal history
ssh-session-control
Send control keys such as ctrl_c, arrows, or tab
ssh-session-resize
Resize the PTY
ssh-session-list
List tracked sessions
ssh-session-diagnostics
Inspect lock state, warnings, running command state, and viewer health
ssh-session-policy-list
Show inherited policy defaults and the current session rule set
ssh-session-policy-upsert
Add or update a session-specific custom policy rule
ssh-session-policy-remove
Remove a session-specific custom policy rule
ssh-session-policy-reset
Restore inherited rules for the current session
ssh-session-set-active
Choose the default session
ssh-viewer-ensure
Open or reuse the local viewer
ssh-viewer-list
List tracked viewer processes
ssh-session-close
Close a session cleanly
ssh-quick-connect
One-step connect flow for agents
ssh-run
Main command execution tool
ssh-status
Runtime overview
ssh-command-status
Async poller
ssh-retry
Retry executor
Local Operator Commands
These helpers are for humans on the workstation that owns the viewer:
ssh-session-mcp-config policy list --scope=merged
ssh-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"
ssh-session-mcp-config policy remove error-kubectl-delete
Equivalent repo-local commands also exist:
bash
npm run launch
npm run status
npm run devices
npm run logs
npm run cleanup
Configuration Summary
Key environment variables:
Variable
Meaning
Default
SSH_HOST
Legacy single-target SSH host
required in legacy mode
SSH_PORT
Legacy single-target SSH port
22
SSH_USER
Legacy single-target SSH user
required in legacy mode
SSH_PASSWORD
Password auth
empty
SSH_KEY
Local private key path
empty
SSH_PASSWORD_FILE
File containing the SSH password
empty
SSH_KEY_FILE
File containing the SSH private key
empty
SSH_MCP_INSTANCE
Runtime isolation key
proc-<pid> or helper-selected
SSH_MCP_CONFIG
Explicit config file path
auto-discovery
SSH_MCP_STATE_DIR
Runtime state root directory
platform default
VIEWER_HOST
Viewer bind host
127.0.0.1
VIEWER_PORT
Viewer port or auto
0 unless configured
VIEWER_ACCESS_MODE
Viewer IP filter mode
config-driven
SSH_MCP_MODE
safe or full
safe
SSH_MCP_LOCAL
Launch a local shell instead of SSH
false
SSH_MCP_DEBUG
Enable debug browser actions
false
AUTO_OPEN_TERMINAL
Auto-open browser terminal
false
SSH_MCP_LOG_MODE
off, meta, or stderr logging
off
SSH_MCP_LOG_DIR
Metadata log directory
platform default
Distributed v0
Distributed v0 intentionally implements a narrow boundary:
Supported runtime modes: single-node and distributed
Distributed mode shares control-plane state only: node heartbeat, session metadata, binding metadata, command metadata, and viewer access policy
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
Cross-node PTY migration is not supported
Transparent cross-node HTTP or websocket proxying is not supported
Distributed 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.
Distributed configuration:
Variable
Meaning
Default
SSH_MCP_RUNTIME_MODE
single-node or distributed
single-node
SSH_MCP_STORE
memory or redis
redis in distributed mode, otherwise memory
SSH_MCP_REDIS_URL
Redis connection URL
required when SSH_MCP_STORE=redis
SSH_MCP_NODE_ID
Stable logical node id for this replica
runtime instance id
SSH_MCP_PUBLIC_BASE_URL
Public viewer base URL advertised to other replicas
Use these variables according to your installation path:
Variable
Required When
Accepted Values / Example
Notes
SSH_HOST
Legacy single-target SSH mode
YOUR_DEVICE_HOST
Required unless you use ssh-session-mcp.config.json or --local.
SSH_PORT
Legacy single-target SSH mode
22
Optional in legacy mode; defaults to 22.
SSH_USER
Legacy single-target SSH mode
YOUR_DEVICE_USER
Required unless you use device profiles.
SSH_PASSWORD
Password-based auth
exported env var
Prefer env export over putting the password directly in the command line.
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.
SSH_KEY
Key-based auth in legacy mode
/absolute/path/to/private/key
The path must exist on the host running the MCP server.
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.
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.
SSH_MCP_INSTANCE
Multi-agent / multi-client isolation
agent-a
Use different values when two agents should not share runtime state.
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.
SSH_MCP_RUNTIME_MODE
Distributed topology selection
single-node, distributed
Distributed v0 only shares control-plane state; it does not migrate PTYs across nodes.
SSH_MCP_STORE
Distributed state backend
memory, redis
Use redis for any real multi-node deployment. memory is only for local distributed skeleton testing.
SSH_MCP_REDIS_URL
Redis backend enabled
redis://redis:6379/0
Required when SSH_MCP_RUNTIME_MODE=distributed and SSH_MCP_STORE=redis.
SSH_MCP_NODE_ID
Stable distributed node id
node-a
Useful when multiple replicas share Redis and need durable owner ids.
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.
SSH_MCP_AUTH_MODE
Viewer auth mode
off, proxy
proxy is recommended only behind a trusted reverse proxy.
SSH_MCP_TRUST_PROXY
Trust viewer identity headers
true, false
Must be enabled together with SSH_MCP_AUTH_MODE=proxy.
SSH_MCP_AUTH_USER_HEADER
Proxy-auth viewer user header
x-forwarded-user
Header names are normalized to lowercase internally.
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.
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.
VIEWER_PORT
Viewer enabled
auto, 0, 8793
auto picks a free port, 0 disables the viewer, fixed ports are best for Docker.
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.
AUTO_OPEN_TERMINAL
Auto-open viewer tab
true, false
Usually false in containers.
SSH_MCP_MODE
Runtime safety mode
safe, full
safe is the recommended default.
SSH_MCP_LOCAL
Local demo mode
true, false
Starts a local shell instead of SSH.
SSH_MCP_DEBUG
Browser debug controls
true, false
Intended for demos and troubleshooting.
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.
SSH_MCP_LOG_DIR
Override metadata log directory
/workspace/state/instances/<instance>/logs
Mainly useful with SSH_MCP_LOG_MODE=meta; ignored for stderr.
SSH_KEY_DIR
Docker Compose profile-based example
/path/to/host/keys
Optional in docker-compose.yml; when unset it falls back to ./keys.
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.
Minimum Required Settings
Choose one of these minimum configuration sets:
Local demo: SSH_MCP_LOCAL=true and VIEWER_PORT=auto
Legacy SSH with password: SSH_HOST, SSH_USER, SSH_PASSWORD
Legacy SSH with key: SSH_HOST, SSH_USER, SSH_KEY
Profile-based mode: ssh-session-mcp.config.json, plus any passwordEnv variables referenced by that config
Container defaults now set SSH_MCP_LOG_MODE=stderr so logs go to the container runtime without corrupting stdio MCP transport.
Mount SSH_MCP_STATE_DIR persistently when you want viewer policy, server info, and state files to survive container restarts.
Distributed multi-node deployments need Redis plus a routable SSH_MCP_PUBLIC_BASE_URL per replica.
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.