ssh-shell-mcp
README.md
# ssh-shell-mcp
> **AI-native SSH orchestration for security engineers, DevSecOps, and sysadmins.**
> 57+ MCP tools. Async. Audited. Built on [AsyncSSH](https://asyncssh.readthedocs.io/) + [FastMCP](https://github.com/jlowin/fastmcp).
[](https://github.com/jaguar999paw-droid/ssh-shell-mcp/actions/workflows/ci.yml)
[](LICENSE)
[](https://www.python.org/)
[](https://modelcontextprotocol.io/)
---
## What it does
`ssh-shell-mcp` turns any SSH-accessible host into a fully agentic target. Connect Claude (or any MCP-compatible AI agent) to your infrastructure, then let the agent execute commands, triage incidents, orchestrate fleets, manage tunnels, and audit operations — all over SSH, with no credentials in prompts and a full audit trail.
```
┌─────────────┐ MCP (stdio/HTTP) ┌──────────────────┐
│ Claude / │ ◄──────────────────────────► │ ssh-shell-mcp │
│ AI Agent │ │ (this server) │
└─────────────┘ └────────┬─────────┘
│ AsyncSSH
┌───────────┼───────────┐
web01 db01 jump01
```
---
## Why ssh-shell-mcp?
| Feature | ssh-shell-mcp | Ansible | Fabric | Paramiko |
|---|:---:|:---:|:---:|:---:|
| AI agent / MCP native | ✅ | ❌ | ❌ | ❌ |
| Async connection pool | ✅ | ❌ | ❌ | ❌ |
| Built-in audit log | ✅ | partial | ❌ | ❌ |
| Security policy gate | ✅ | ❌ | ❌ | ❌ |
| Persistent shell sessions | ✅ | ❌ | ❌ | ❌ |
| Live SOCKS5 / tunnel mgmt | ✅ | ❌ | ❌ | ❌ |
| Zero-dependency config | ✅ | ❌ | ❌ | ✅ |
| Fleet health checks | ✅ | ✅ | ❌ | ❌ |
---
## Use Cases
### 🔵 Blue Team / Incident Response
- Ask Claude to `ssh_journalctl` across all production hosts for a suspicious PID, then `ssh_kill` it
- Run `ssh_health_check_fleet` to instantly see which hosts went dark after an incident
- Use `ssh_operation_history` + `ssh_audit_stats` to reconstruct what an agent did during triage
- `ssh_playbook_on_group` to push a hardened `sshd_config` to the entire `linux` host group
### 🔴 Red Team / Authorized Testing (own systems only)
- `ssh_socks_proxy` through a jump host for proxychains-style traffic routing
- `ssh_port_forward` to expose internal services for enumeration during authorized assessments
- `ssh_reverse_tunnel` to create C2-style callbacks on lab environments
- `ssh_tmux_send` to drive interactive sessions from an AI agent
### ⚙️ DevSecOps / Fleet Automation
- Rolling deployments with `ssh_rolling` — zero-downtime, stop-on-failure
- Push secrets via `ssh_run_with_env` — never in command strings
- `ssh_sync` config directories, then `ssh_playbook` to restart affected services
- Group hosts by `tags` (e.g. `web`, `database`, `staging`) and broadcast commands to each tier
---
## Quickstart
```bash
git clone https://github.com/jaguar999paw-droid/ssh-shell-mcp.git
cd ssh-shell-mcp
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp config.example.json config.json # fill in your hosts
python server.py --transport stdio
```
Add to `claude_desktop_config.json`:
```json
{
"mcpServers": {
"ssh-shell": {
"command": "/path/to/ssh-shell-mcp/.venv/bin/python",
"args": ["server.py", "--transport", "stdio"],
"env": {
"SSH_HOSTS_YAML": "/path/to/ssh-shell-mcp/config/hosts.yaml"
}
}
}
}
```
---
## 🚀 Non-Docker Setup (Complete Guide)
For users who prefer **native installation** without Docker, follow these step-by-step instructions.
### Prerequisites
Before starting, ensure you have:
- **Python 3.10 or higher** — Check: `python3 --version`
- **pip** (Python package manager) — Check: `pip3 --version`
- **SSH client** — Usually pre-installed on macOS/Linux; on Windows, install [Git Bash](https://gitforwindows.org/) or [WSL2](https://learn.microsoft.com/en-us/windows/wsl/install)
- **SSH keys** — Have an SSH public key in `~/.ssh/id_ed25519` (or `id_rsa`). [Generate one](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent) if needed.
- **SSH-accessible hosts** — At least one remote server you can connect to via SSH
### Step 1: Clone the Repository
```bash
git clone https://github.com/jaguar999paw-droid/ssh-shell-mcp.git
cd ssh-shell-mcp
```
### Step 2: Create a Virtual Environment
Isolate dependencies in a virtual environment:
```bash
python3 -m venv .venv
```
Activate it:
**On macOS/Linux:**
```bash
source .venv/bin/activate
```
**On Windows (Git Bash):**
```bash
source .venv/Scripts/activate
```
**On Windows (PowerShell):**
```bash
.\.venv\Scripts\Activate.ps1
```
You should see `(.venv)` appear in your shell prompt.
### Step 3: Install Dependencies
```bash
pip install --upgrade pip setuptools wheel
pip install -r requirements.txt
```
This installs:
- `asyncssh` — Async SSH library
- `mcp` & `fastmcp` — Model Context Protocol framework
- `pyyaml` — Configuration file parsing
- `pytest` — Testing framework
### Step 4: Configure Your SSH Hosts
Create a configuration file defining which SSH targets to connect to:
```bash
cp config.example.json config.json
```
Edit `config.json` with your SSH hosts:
```json
{
"hosts": {
"web01": {
"host": "192.168.1.100",
"port": 22,
"user": "deploy",
"key_path": "~/.ssh/id_ed25519",
"tags": ["web", "production"]
},
"db01": {
"host": "192.168.1.200",
"port": 22,
"user": "deploy",
"key_path": "~/.ssh/id_ed25519",
"tags": ["database", "production"]
},
"localhost": {
"host": "127.0.0.1",
"port": 22,
"user": "$(whoami)",
"key_path": "~/.ssh/id_ed25519",
"tags": ["local", "testing"]
}
}
}
```
**Key fields:**
- `host` — IP address or domain of the SSH server
- `port` — SSH port (default: 22)
- `user` — SSH username
- `key_path` — Path to your SSH private key (e.g. `~/.ssh/id_ed25519`)
- `tags` — Arbitrary labels for grouping hosts (e.g. web, database, production)
### Step 5: Test SSH Connectivity
Before connecting via MCP, verify SSH works:
```bash
ssh -i ~/.ssh/id_ed25519 deploy@192.168.1.100 'echo "✓ SSH works!"'
```
If you get `Permission denied`, check:
- SSH key file permissions: `chmod 600 ~/.ssh/id_ed25519`
- SSH server is running on the target host
- User credentials are correct in `config.json`
- Firewall isn't blocking port 22
### Step 6: Start the Server
Run the server in **stdio mode** (for Claude Desktop):
```bash
python server.py --transport stdio
```
You should see:
```
[ssh-shell-mcp] INFO Starting MCP server in stdio mode
[ssh-shell-mcp] INFO Loaded config from config.json
[ssh-shell-mcp] INFO Registered 3 hosts: web01, db01, localhost
```
The server is now ready to accept MCP connections. Leave this terminal open.
### Step 7: Connect to Claude Desktop
In a **new terminal**, find the full path to your Python executable:
```bash
which python # or 'python' depending on your setup
# Output: /Users/you/projects/ssh-shell-mcp/.venv/bin/python
```
Copy the path (e.g., `/Users/you/projects/ssh-shell-mcp/.venv/bin/python`).
Edit your Claude Desktop configuration file:
**On macOS:**
```bash
nano ~/Library/Application\ Support/Claude/claude_desktop_config.json
```
**On Linux:**
```bash
nano ~/.config/Claude/claude_desktop_config.json
```
**On Windows:**
```bash
notepad %APPDATA%\Claude\claude_desktop_config.json
```
Add or update the `mcpServers` section:
```json
{
"mcpServers": {
"ssh-shell": {
"command": "/Users/you/projects/ssh-shell-mcp/.venv/bin/python",
"args": ["server.py", "--transport", "stdio"],
"env": {
"SSH_HOSTS_YAML": "/Users/you/projects/ssh-shell-mcp/config/hosts.yaml"
}
}
}
}
```
**Replace `/Users/you/projects/ssh-shell-mcp/` with your actual repository path.**
### Step 8: Restart Claude Desktop
Fully restart Claude Desktop for the new MCP server to load:
1. Quit Claude completely
2. Wait 2 seconds
3. Reopen Claude
You should see a **hammer icon** (🔨) or **settings gear** in the Claude interface — click it to verify the server loaded.
### Step 9: Test a Tool
In Claude, try:
```
Can you list the files in /tmp on web01?
```
Claude will use the `ssh_ls` tool to run `ls /tmp` on `web01` via SSH. Watch the terminal running `server.py` to see the command executed with full audit logging.
### Troubleshooting
| Issue | Solution |
|---|---|
| `ModuleNotFoundError: No module named 'asyncssh'` | Ensure venv is activated: `source .venv/bin/activate` |
| `Permission denied (publickey)` | Check SSH key permissions: `chmod 600 ~/.ssh/id_ed25519` |
| `Could not open config file 'config.json'` | Verify `config.json` exists in the repo root: `ls config.json` |
| MCP server not showing in Claude | Restart Claude Desktop completely (not just app minimize) |
| `Connection refused` when testing SSH | Verify SSH server is running on target: `ssh web01 'echo ok'` |
| `Host key verification failed` | SSH host unknown. Accept the key: `ssh-keyscan -H 192.168.1.100 >> ~/.ssh/known_hosts` |
### Next Steps
- **Read the [tool reference](docs/tools.md)** for all 57 available tools
- **Review [SECURITY.md](SECURITY.md)** for production hardening
- **Check [CONTRIBUTING.md](CONTRIBUTING.md)** if you want to extend the server
---
## Tool Categories (57 tools)
| # | Category | Tools |
|---|---|---|
| 1 | **Shell execution** | `ssh_run`, `ssh_run_batch`, `ssh_run_script`, `ssh_run_with_env`, `ssh_exec_retry` |
| 2 | **Persistent sessions** | `ssh_create_session`, `ssh_session_exec`, `ssh_session_read_buffer`, `ssh_close_session`, `ssh_session_list`, `ssh_session_set_env` |
| 3 | **File management** | `ssh_upload`, `ssh_download`, `ssh_ls`, `ssh_cat`, `ssh_write`, `ssh_rm`, `ssh_sync` |
| 4 | **Process management** | `ssh_ps`, `ssh_kill`, `ssh_start`, `ssh_background`, `ssh_monitor` |
| 5 | **System inspection** | `ssh_info`, `ssh_df`, `ssh_free`, `ssh_netstat`, `ssh_service`, `ssh_journalctl`, `ssh_docker` |
| 6 | **Fleet orchestration** | `ssh_parallel`, `ssh_rolling`, `ssh_group_exec`, `ssh_broadcast_batch`, `ssh_playbook`, `ssh_playbook_on_group` |
| 7 | **Tunnels & proxies** | `ssh_port_forward`, `ssh_reverse_tunnel`, `ssh_socks_proxy`, `ssh_close_tunnel`, `ssh_active_tunnels` |
| 8 | **Security controls** | `ssh_check_command`, `ssh_check_host_access`, `ssh_security_status` |
| 9 | **Host registry** | `ssh_register_host`, `ssh_list_hosts`, `ssh_remove_host`, `ssh_connection_status` |
| 10 | **Health & observability** | `ssh_ping_host`, `ssh_health_check_fleet`, `ssh_full_status`, `ssh_operation_history`, `ssh_audit_stats` |
| 11 | **tmux** | `ssh_tmux_new`, `ssh_tmux_send`, `ssh_tmux_list`, `ssh_tmux_kill` |
---
## Configuration
**`config/hosts.yaml`** — registers your SSH targets:
```yaml
hosts:
web01:
host: 192.168.1.100
port: 22
user: deploy
key: ~/.ssh/id_ed25519
tags: [web, production]
db01:
host: 192.168.1.200
port: 22
user: deploy
key: ~/.ssh/id_ed25519
tags: [database, production]
```
**`config/policies.yaml`** — security policy (host allowlist + command blocklist):
```yaml
policies:
host_allowlist: [] # empty = all registered hosts permitted
command_blocklist:
- "rm -rf /"
- "rm -rf /*"
- "mkfs*"
- ":(){:|:&};:" # fork bomb
```
> **Never commit `hosts.yaml`** — it contains real credentials. It is already in `.gitignore`. Use `hosts.example.yaml` as a template.
### Environment Variables
| Variable | Description | Default |
|---|---|---|
| `SSH_HOSTS_YAML` | Path to hosts config | `config/hosts.yaml` |
| `SSH_POLICIES_YAML` | Path to security policy config | `config/policies.yaml` |
| `SSH_MCP_LOG_DIR` | Directory for audit logs | `logs/` |
| `MCP_AUTH_TOKEN` | Bearer token for HTTP transport | _(none)_ |
---
## Security Design
- **Key-based auth only** — password auth is intentionally unsupported.
- **Policy gate on every tool** — `_gate()` checks host allowlist and command blocklist before any execution.
- **Full audit log** — every operation is recorded with host, command, result, and timestamp.
- **No outbound telemetry** — the server connects only to your configured SSH targets.
- **stdio default** — no network port opened on the MCP host by default.
- Running targets behind a VPN (e.g. Tailscale) is strongly recommended.
See [SECURITY.md](SECURITY.md) for vulnerability reporting.
---
## Running Tests
```bash
# Integration tests — requires SSH server on localhost
TEST_USER=$USER TEST_KEY_PATH=~/.ssh/id_ed25519 pytest tests/ -v
```
Tests cover: exec, file transfer, persistent sessions, fleet orchestration, and tunnels.
They use **real SSH connections** — no mocking.
---
## HTTP Transport (remote agents)
```bash
MCP_AUTH_TOKEN=your-secret python server.py --transport streamable_http --port 8000
```
The server exposes `/mcp` with Bearer token authentication. Suitable for remote AI agents over a private network.
---
## Alternative Interfaces
Beyond stdio/HTTP MCP transport, this repo also includes:
- **`web_server.py`** — a FastAPI wrapper exposing all tools as a REST API (`/api/tools`, `/api/ssh-shell-mcp/call`, `/api/ssh-shell-mcp/exec`), with Swagger docs at `/docs`. Useful for browser-based or non-MCP integrations.
- **`copilot_bridge.py`** — a lightweight FastMCP bridge exposing a curated subset of tools (`ssh_exec`, `ssh_list_hosts`, `ssh_register_host`, `ssh_process_list`, `ssh_system_info`) for GitHub Copilot Chat / VS Code, routed through `web_server.py` over HTTP.
Both are optional — the primary interface remains the stdio MCP server (`server.py`). See [`ARCHITECTURE.md`](ARCHITECTURE.md) for the full startup-performance redesign (lazy connection loading, concurrent-startup fixes, and a recommended fast/slow server split for Claude Desktop configs).
---
## Project Structure
```
ssh-shell-mcp/
├── server.py # MCP entrypoint — all 57 tools
├── server/
│ ├── connection_manager.py # AsyncSSH connection pool
│ ├── session_manager.py # Persistent shell sessions
│ ├── shell_engine.py # Core command execution
│ ├── file_ops.py # SFTP file operations
│ ├── process_manager.py # Process lifecycle
│ ├── system_inspector.py # System info, logs, Docker
│ ├── network_tools.py # Tunnel manager
│ ├── orchestrator.py # Multi-host execution
│ ├── audit.py # Audit log
│ └── security.py # Policy enforcement
├── config.example.json
├── requirements.txt
├── Dockerfile
├── docker-compose.yml
└── SECURITY.md
```
---
For per-tool parameter documentation see [`docs/tools.md`](docs/tools.md).
---
## ⚠️ Legal Notice
This tool provides programmatic SSH access to remote systems. **Use only on systems you own or have explicit written authorization to access.** Unauthorized access to computer systems is illegal in most jurisdictions.
---
## 🔐 Cryptography Notice
This software uses the SSH protocol, which relies on cryptographic algorithms. Export, import, and use may be restricted in some jurisdictions. See the [Wassenaar Arrangement](https://www.wassenaar.org/) for reference.
---
## License
[Apache License 2.0](LICENSE)
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues