SSH MCP Server
Hardened SSH operations for VS Code Copilot Chat via the Model Context Protocol
Features • Quick Start • Tools • Configuration • Security • Testing • Contributing
What Is This?
SSH MCP Server lets you manage remote Linux servers through natural language in VS Code Copilot Chat. Instead of switching to a terminal and remembering SSH commands, you just ask:
"Check disk usage on production-web"
"Show me the last 200 lines of /var/log/nginx/error.log on staging"
"Download /var/log/syslog from web-server-01 for incident INC-2026-0309"
The server enforces strict security policies — no raw shell access, all commands go through pre-approved templates, parameters are regex-validated, secrets in output are auto-redacted, and privileged operations require an approval workflow.
Features
- 23 MCP tools — host discovery, session management, command execution (sync + background), file transfer, SFTP operations, SSH key management, certificate lifecycle, approval workflows
- Template-only execution — no raw shell; every command matches a registered template with regex-validated parameters
- 3-tier security model — read-only (Tier 0), controlled mutation with confirmation (Tier 1), privileged with approval workflow (Tier 2)
- Automatic secret redaction — AWS keys, bearer tokens, passwords, private keys are scrubbed from output
- Tamper-evident audit log — every operation is logged with hash-chain integrity verification
- Short-lived SSH certificates — issue and revoke certificates with TTL enforcement via a local CA
- Persistent SSH sessions — connection pooling with keepalive probes and configurable idle timeout
- Background command execution — run long-running template commands asynchronously with output polling
- Path traversal protection —
..sequences blocked in all path parameters and file transfers - Transfer policy — allowed paths, blocked extensions, size limits, mandatory justification for downloads
Quick Start
Prerequisites
- Python 3.11+
- VS Code with GitHub Copilot extension
- SSH access to at least one remote Linux host
Install
As vscode plugin
Follow instructions on link:
https://marketplace.visualstudio.com/items?itemName=bhayanak.ssh-mcp-server-secure
Python package:https://pypi.org/project/ssh-mcp-server-copilot/
Install from Source (for development / contributing)
git clone https://github.com/bhayanak/ssh-mcp-server.git
cd ssh-mcp-server
python -m venv .venv
source .venv/bin/activate # macOS/Linux
# .venv\Scripts\activate # Windows
pip install -e ".[dev]"
For local development, create .vscode/mcp.json pointing to the local source:
{
"servers": {
"ssh-mcp": {
"type": "stdio",
"command": "${workspaceFolder}/.venv/bin/python",
"args": ["-m", "ssh_mcp.server"],
"env": {
"SSH_MCP_CONFIG_DIR": "${workspaceFolder}/config",
"SSH_MCP_HOSTS_FILE": "${workspaceFolder}/config/hosts.json",
"SSH_MCP_TEMPLATES_FILE": "${workspaceFolder}/config/templates.json",
"SSH_MCP_AUDIT_LOG_DIR": "${workspaceFolder}/audit_logs",
"SSH_MCP_CERT_DATA_DIR": "${workspaceFolder}/cert_data",
"SSH_MCP_APPROVAL_DATA_DIR": "${workspaceFolder}/approval_data",
"SSH_MCP_SSH_KNOWN_HOSTS_FILE": "~/.ssh/known_hosts"
}
}
}
}
Configure Your Hosts
Edit the hosts file with your actual servers. If you used ssh-mcp-server-copilot init, edit ~/.ssh-mcp/hosts.json. If developing from source, edit config/hosts.json.
[
{
"host_id": "my-server",
"hostname": "192.168.1.10",
"port": 22,
"ssh_user": "deploy",
"labels": {"env": "production", "role": "web"},
"description": "Production web server",
"allowed_roles": ["operator", "admin"]
}
]
| Field | Required | Description |
|---|---|---|
host_id |
Yes | Unique identifier (alphanumeric, dots, dashes) |
hostname |
Yes | IP address or FQDN |
port |
No | SSH port (default: 22) |
ssh_user |
Yes* | Remote SSH username. If empty, uses your OS username |
labels |
No | Key-value tags for organization |
description |
No | Human-readable description |
allowed_roles |
No | Which roles can access this host (default: operator, admin) |
Set Up SSH Access
Your SSH key must be authorized on each host:
# Generate a key if you don't have one
ssh-keygen -t ed25519 -C "[email protected]"
# Copy to each host
ssh-copy-id -i ~/.ssh/id_ed25519.pub [email protected]
# Load into ssh-agent (required — the MCP server uses the agent)
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
# Verify access
ssh [email protected] "echo OK"
Add host keys to known_hosts:
ssh-keyscan -H 192.168.1.10 >> ~/.ssh/known_hosts
Start Using
- Open the workspace in VS Code
- The MCP server auto-starts from
.vscode/mcp.json - Open Copilot Chat (Cmd+Shift+I / Ctrl+Shift+I)
- Switch to "Agent" mode (critical — only Agent mode can invoke MCP tools)
- Verify tools are loaded — click the tools icon in the chat input bar, you should see 23 tools from
ssh-mcp
Now just ask in natural language:
> List all my SSH hosts
> Check disk usage on my-server
> Show me the last 100 lines of /var/log/syslog on my-server
> What's the status of nginx on my-server?
Tools (23)
Tier 0 — Read-Only (No Confirmation)
| Tool | Description |
|---|---|
list_hosts |
List all configured SSH hosts with labels and metadata |
get_host_facts |
Get OS, uptime, kernel info from a host |
get_audit_logs |
View the tamper-evident audit trail |
list_templates |
List available command templates |
list_pending_approvals |
View pending approval requests |
Session Management
| Tool | Description |
|---|---|
ssh_connect |
Open a persistent SSH session (returns session_id for reuse) |
ssh_disconnect |
Close a persistent session |
ssh_list_sessions |
List active sessions and remaining connection slots |
ssh_session_ping |
Health-check a session (liveness, idle time, uptime) |
Tier 1 — Controlled Mutation (Confirmation Required)
| Tool | Description |
|---|---|
run_ssh_command |
Execute a template command on a host (supports session_id for persistent connections) |
transfer_file |
Download/upload files via SFTP with path and extension policies (supports session_id) |
Background Command Execution
| Tool | Description |
|---|---|
run_ssh_command_background |
Start a template command in the background (returns job_id) |
poll_background_job |
Read accumulated stdout/stderr of a background job (redacted) |
list_background_jobs |
List all background jobs (running + completed) |
cancel_background_job |
Cancel a running background job |
Enhanced SFTP
| Tool | Description |
|---|---|
sftp_list_directory |
List files in a remote directory (within allowed paths) |
sftp_delete |
Delete a remote file (within allowed paths, requires justification) |
Tier 2 — Privileged (Approval Required)
| Tool | Description |
|---|---|
add_ssh_key |
Register a public SSH key with TTL enforcement |
remove_ssh_key |
Revoke a registered SSH key |
issue_cert |
Issue a short-lived SSH certificate via the local CA |
revoke_cert |
Revoke a certificate and delete its PEM |
request_approval / approve_request |
Approval workflow for privileged ops |
Configuration
Command Templates
Templates define which commands can be executed. Edit config/templates.json:
[
{
"template_id": "disk_usage",
"description": "Show disk usage summary",
"command": "df -h",
"allowed_params": {},
"allowed_roles": ["developer", "operator", "admin"],
"timeout_seconds": 10,
"risk_level": "low"
},
{
"template_id": "tail_log",
"description": "Tail the last N lines of a log file",
"command": "tail -n {lines} {log_path}",
"allowed_params": {
"lines": "^[0-9]{1,5}$",
"log_path": "^/var/log/[a-zA-Z0-9_./-]+$"
},
"allowed_roles": ["operator", "admin"],
"timeout_seconds": 15,
"risk_level": "low"
}
]
Each parameter is validated against a regex pattern before substitution. Path traversal (..) is blocked automatically.
Environment Variables
All configuration is via environment variables with the SSH_MCP_ prefix:
| Variable | Default | Description |
|---|---|---|
SSH_MCP_CONFIG_DIR |
~/.ssh-mcp |
Base config directory (all other paths derive from this) |
SSH_MCP_HOSTS_FILE |
{config_dir}/hosts.json |
Path to hosts configuration |
SSH_MCP_TEMPLATES_FILE |
{config_dir}/templates.json |
Path to command templates |
SSH_MCP_AUDIT_LOG_DIR |
{config_dir}/audit_logs |
Audit log directory |
SSH_MCP_CERT_DATA_DIR |
{config_dir}/cert_data |
Certificate storage directory |
SSH_MCP_APPROVAL_DATA_DIR |
{config_dir}/approval_data |
Approval data directory |
SSH_MCP_MAX_SESSIONS |
10 |
Max simultaneous SSH sessions |
SSH_MCP_SESSION_IDLE_TIMEOUT |
300 |
Idle session timeout (seconds) |
SSH_MCP_KEEPALIVE_INTERVAL |
15 |
SSH keepalive interval (seconds) |
SSH_MCP_KEEPALIVE_COUNT_MAX |
3 |
Max failed keepalive probes before disconnect |
SSH_MCP_MAX_BACKGROUND_JOBS |
10 |
Max concurrent background jobs |
SSH_MCP_JOB_OUTPUT_MAX_BYTES |
1048576 |
Max output buffer per background job (1 MB) |
SSH_MCP_JOB_TTL_SECONDS |
3600 |
Background job auto-expiry (1 hour) |
SSH_MCP_SSH_KNOWN_HOSTS_FILE |
(none) | Path to SSH known_hosts file |
SSH_MCP_REQUIRE_TWO_PARTY_APPROVAL |
true |
Require different user as approver |
SSH_MCP_AUTH_TOKEN |
(none) | Bearer token (empty = dev mode) |
SSH_MCP_SSH_TIMEOUT_SECONDS |
30 |
SSH connection timeout |
Transfer Policy (Defaults)
| Setting | Default |
|---|---|
| Allowed paths | /tmp/*, /var/log/* |
| Blocked extensions | .exe, .sh, .bat, .ps1, .dll, .so |
| Max file size | 50 MB |
| Require justification for downloads | Yes |
Roles
| Role | Access |
|---|---|
developer |
Read-only tools, low-risk commands |
operator |
All Tier 0 + Tier 1 tools |
admin |
All tools including Tier 2 (key/cert management) |
auditor |
Audit log access |
Security
Design Principles
- No raw shell — all commands go through registered templates
- Parameter validation — every parameter is regex-validated before substitution
- Path traversal blocking —
..sequences rejected in all parameters and file paths - Secret redaction — AWS keys, bearer tokens, passwords, private keys automatically scrubbed from output
- Approval workflow — privileged operations (key/cert management) require explicit approval with HMAC-verified tokens
- Tamper-evident audit — hash-chained audit log for forensic analysis
- Strict host validation — paramiko
RejectPolicyby default (no auto-accepting unknown hosts) - TTL enforcement — SSH certificates and keys have configurable maximum lifetimes
Approval Workflow
Tier 2 operations follow a 3-step flow:
1. request_approval → returns request_id + one-time approval_token
2. approve_request → verifies HMAC token, marks approved
3. call the tool → pass approval_request_id, consumed after use
With REQUIRE_TWO_PARTY_APPROVAL=true (production), a different user must approve.
Audit Log Verification
python -c "
from ssh_mcp.audit import AuditLogger
from pathlib import Path
logger = AuditLogger(Path('audit_logs'))
ok, msg = logger.verify_chain()
print(f'Chain integrity: {ok} — {msg}')
"
Testing
Unit Tests
# Run all tests
pytest tests/ -v
# With coverage
pytest tests/ -v --cov=ssh_mcp
Runtime Directories (git-ignored, auto-created)
| Directory | Purpose |
|---|---|
audit_logs/ |
Tamper-evident audit log (audit.jsonl) |
cert_data/ |
CA keys, issued certificates, revocation list |
approval_data/ |
Pending and consumed approval requests |
CLI Reference
ssh-mcp-server-copilot --version # Print version
ssh-mcp-server-copilot init # Create ~/.ssh-mcp with default configs
ssh-mcp-server-copilot # Start MCP server (stdio transport)
ssh-mcp-server-copilot --config-dir PATH # Use custom config directory
ssh-mcp-server-copilot --config-dir PATH init # Init a custom config directory
Contributing
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Make your changes
- Run tests:
pytest tests/ -v - Lint:
ruff check src/ tests/ - Submit a pull request
License
MIT