loading…
Search for a command to run...
loading…
SSH Session MCP fills a gap in the MCP ecosystem by offering a persistent shared SSH PTY runtime, not just stateless command execution. It features browser coll
SSH Session MCP fills a gap in the MCP ecosystem by offering a persistent shared SSH PTY runtime, not just stateless command execution. It features browser collaboration, input locking, safe/full execution modes, async command tracking, configurable policy rules, and multi-device profiles. Ideal for remote development, embedded systems, infrastructure workflows, and hardware control scenarios.
中文 | English
License: Apache%202.0 Node.js Version TypeScript npm version
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.

git clone this repository.npx -y ssh-session-mcp --viewerPort=autonpm install -g ssh-session-mcpdocker.io/zwawa/ssh-session-mcpgit clone is only for contributors, source builds, and local development.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.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:
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 |
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 |
.env.example |
Legacy single-target environment variable template |
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.
claude mcp add --transport stdio ssh-session-mcp -- npx -y ssh-session-mcp --viewerPort=auto
Windows note from the Claude Code docs: native Windows users should wrap npx with cmd /c for stdio MCP servers.
claude mcp add --transport stdio ssh-session-mcp -- cmd /c npx -y ssh-session-mcp --viewerPort=auto
codex mcp add ssh-session-mcp -- npx -y ssh-session-mcp --viewerPort=auto
OpenCode's opencode mcp add flow is interactive. Choose a local MCP server and use this command:
npx -y ssh-session-mcp --viewerPort=auto
If you prefer config instead of the interactive flow:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"ssh-session-mcp": {
"type": "local",
"command": ["npx", "-y", "ssh-session-mcp", "--viewerPort=auto"]
}
}
}
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.
npm install -g ssh-session-mcp
ssh-session-mcp-ctl launch --local --viewerPort=auto
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.
Use the MCP server binary directly when wiring a client:
# Global install
npm install -g ssh-session-mcp
# Server command used by MCP clients
ssh-session-mcp --viewerPort=auto
# Claude Code
claude mcp add --transport stdio ssh-session-mcp -- ssh-session-mcp --viewerPort=auto
# Codex CLI
codex mcp add ssh-session-mcp -- ssh-session-mcp --viewerPort=auto
If you prefer npx instead of a global install:
npx -y ssh-session-mcp --viewerPort=auto
Create .env from .env.example:
cp .env.example .env
SSH_HOST=YOUR_DEVICE_HOST
SSH_PORT=22
SSH_USER=YOUR_DEVICE_USER
SSH_PASSWORD=
SSH_KEY=
VIEWER_PORT=auto
AUTO_OPEN_TERMINAL=false
SSH_MCP_MODE=safe
Then launch:
ssh-session-mcp-ctl launch --viewerPort=auto
For multiple boards or named targets, create ssh-session-mcp.config.json:
{
"defaultDevice": "DEVICE_A_ID",
"devices": [
{
"id": "DEVICE_A_ID",
"host": "DEVICE_A_HOST",
"port": 22,
"user": "DEVICE_A_USER",
"auth": { "passwordEnv": "DEVICE_A_PASSWORD" },
"defaults": {
"term": "xterm-256color",
"cols": 120,
"rows": 40,
"autoOpenViewer": true,
"viewerMode": "browser"
}
}
]
}
Discovery order:
--config=/path/to/config.jsonssh-session-mcp.config.json.env fallbackImportant:
auth.password is intentionally unsupported. Use auth.passwordEnv or auth.keyPath..env or the parent environment, not in repo-tracked JSON.Public Docker images should be distributed through Docker Hub, with GitHub Container Registry as an optional secondary registry:
docker.io/zwawa/ssh-session-mcp:<version>
docker.io/zwawa/ssh-session-mcp:latest
ghcr.io/zw-awa/ssh-session-mcp:<version>
Recommended container launch for a real SSH target:
docker run --rm -i \
-p 8793:8793 \
-e VIEWER_PORT=8793 \
-e VIEWER_HOST=0.0.0.0 \
-e SSH_HOST=YOUR_DEVICE_HOST \
-e SSH_PORT=22 \
-e SSH_USER=YOUR_DEVICE_USER \
-e SSH_PASSWORD \
docker.io/zwawa/ssh-session-mcp:latest
Export the password in your shell first instead of placing it directly on the command line.
Recommended launch for profile-based config:
docker run --rm -i \
-p 8793:8793 \
-e VIEWER_PORT=8793 \
-e VIEWER_HOST=0.0.0.0 \
-e SSH_MCP_CONFIG=/workspace/ssh-session-mcp.config.json \
-v "$PWD/ssh-session-mcp.config.json:/workspace/ssh-session-mcp.config.json:ro" \
-v "/path/to/host/keys:/workspace/keys:ro" \
docker.io/zwawa/ssh-session-mcp:latest
Equivalent Compose example:
docker compose up -d
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:
VIEWER_PORT to 8793 when unset so the browser viewer can be published reliably.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.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..env, or --env-file.npx is still simpler unless your client explicitly prefers containerized commands.Docker-based MCP client command examples:
# 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:
For many users, publishing to npm and recommending npx -y ssh-session-mcp --viewerPort=auto is still the lower-friction install path.
The browser viewer is not decorative. It is part of the workflow:
For users:
install -> launch viewer -> connect once -> keep the session alive -> let the AI help
For agents:
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.
--local for offline testing| Mode | Behavior |
|---|---|
safe |
Default. Automatically blocks obviously dangerous, interactive, or never-ending commands. |
full |
Relaxes the guardrails for advanced use, while still blocking a small set of clearly destructive abuse cases. |
The default rule set can be customized if needed.
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.
| 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 |
| 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 |
These helpers are for humans on the workstation that owns the viewer:
ssh-session-mcp-ctl status
ssh-session-mcp-ctl devices
ssh-session-mcp-ctl launch --viewerPort=auto
ssh-session-mcp-ctl launch --local --viewerPort=auto
ssh-session-mcp-ctl logs --tail=60
ssh-session-mcp-ctl cleanup
Default rule library management for operators:
ssh-session-mcp-config policy list --scope=merged
ssh-session-mcp-config policy set block-kubectl-delete --pattern="\\bkubectl\\s+delete\\b" --category=dangerous --action=block --message="kubectl delete is blocked in safe mode"
ssh-session-mcp-config policy remove block-kubectl-delete
Equivalent repo-local commands also exist:
npm run launch
npm run status
npm run devices
npm run logs
npm run cleanup
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_MCP_INSTANCE |
Runtime isolation key | proc-<pid> or helper-selected |
SSH_MCP_CONFIG |
Explicit config file path | auto-discovery |
VIEWER_HOST |
Viewer bind host | 127.0.0.1 |
VIEWER_PORT |
Viewer port or auto |
0 unless configured |
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 or meta JSONL logging |
off |
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_KEY |
Key-based auth in legacy mode | /absolute/path/to/private/key |
The path must exist on the host running the MCP server. |
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. |
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. |
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 |
meta writes JSONL metadata logs without storing raw secrets. |
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. |
Choose one of these minimum configuration sets:
SSH_MCP_LOCAL=true and VIEWER_PORT=autoSSH_HOST, SSH_USER, SSH_PASSWORDSSH_HOST, SSH_USER, SSH_KEYssh-session-mcp.config.json, plus any passwordEnv variables referenced by that configssh-session-mcp.config.json, optional SSH_KEY_DIR, optional SSH_SESSION_MCP_IMAGEExample config file: docs/examples/ssh-session-mcp.config.example.json
.env is ignored by git and npm.See SECURITY.md for the full policy.
More detail: docs/platform-compatibility.md
Clone the repo only if you want to modify the source, run tests locally, or build release artifacts.
npm install
npm run build
npm run test
npm run validate:repo
npm run build:site
GitHub Actions included in this repo can:
dist/Apache-2.0. See LICENSE.
Run in your terminal:
claude mcp add ssh-session-mcp -- npx