Hardened SSH MCP server for VS Code / Copilot Chat — run policy-scoped commands on remote hosts via natural language
Project description
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
- 13 MCP tools — host discovery, command execution, file transfer, 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
- 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 from PyPI (Recommended — use with any project)
# Install globally (or use pipx for isolation)
pip install ssh-mcp-server
# Initialize config directory (~/.ssh-mcp)
ssh-mcp-server init
This creates ~/.ssh-mcp/ with:
hosts.json— edit with your real serverstemplates.json— pre-configured command templatesaudit_logs/,cert_data/,approval_data/— runtime directories
Add to any VS Code project — create .vscode/mcp.json:
{
"servers": {
"ssh-mcp": {
"type": "stdio",
"command": "ssh-mcp-server"
}
}
}
Or enable globally — add to VS Code User Settings (JSON) (Cmd+Shift+P → "Preferences: Open User Settings (JSON)"):
{
"mcp": {
"servers": {
"ssh-mcp": {
"type": "stdio",
"command": "ssh-mcp-server"
}
}
}
}
That's it — the server is now available in every VS Code window.
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 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 "your-email@example.com"
# Copy to each host
ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@192.168.1.10
# Load into ssh-agent (required — the MCP server uses the agent)
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
# Verify access
ssh deploy@192.168.1.10 "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 12 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
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 |
Tier 1 — Controlled Mutation (Confirmation Required)
| Tool | Description |
|---|---|
run_ssh_command |
Execute a template command on a host (e.g., disk_usage, tail_log) |
transfer_file |
Download/upload files via SFTP with path and extension policies |
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_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
Manual Testing with Copilot Chat
See test.md for a comprehensive manual testing guide with 31 test cases covering:
- Host discovery and facts retrieval
- Command execution with all templates
- Security tests (injection, path traversal, redaction)
- File transfer policies
- Full approval workflows
- Certificate and key lifecycle
- End-to-end incident response scenario
Project Structure
ssh-mcp-server/
├── config/
│ ├── hosts.json # Your hosts (git-ignored, create from example)
│ ├── hosts.example.json # Example hosts config
│ └── templates.json # Command templates (safe to commit)
├── src/ssh_mcp/
│ ├── server.py # MCP server entry point & tool definitions
│ ├── cli.py # CLI: init, run, --version, --config-dir
│ ├── config.py # Configuration models & validation
│ ├── executor.py # SSH command execution via paramiko
│ ├── auth.py # Authentication & authorization
│ ├── guardrails.py # Risk levels, confirmation, policy hooks
│ ├── approvals.py # Approval workflow manager
│ ├── audit.py # Tamper-evident audit logger
│ ├── certs.py # SSH certificate authority
│ ├── redact.py # Secret redaction engine
│ └── defaults/ # Bundled default configs for `init`
│ ├── templates.json
│ └── hosts.example.json
├── vscode-extension/ # VS Code extension wrapper (for marketplace)
│ ├── package.json
│ ├── README.md
│ └── CHANGELOG.md
├── tests/ # Unit tests (94 tests)
├── .vscode/
│ └── mcp.json.example # VS Code MCP config template
├── ssh-mcp-server-logo.png # Server icon
├── pyproject.toml # Package metadata
├── test.md # Manual test guide (31 test cases)
├── LICENSE # MIT License
└── README.md # This file
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 --version # Print version
ssh-mcp-server init # Create ~/.ssh-mcp with default configs
ssh-mcp-server # Start MCP server (stdio transport)
ssh-mcp-server --config-dir PATH # Use custom config directory
ssh-mcp-server --config-dir PATH init # Init a custom config directory
Publishing to VS Code Extension Marketplace
Important: An MCP server is not a VS Code extension. It runs as a standalone process that VS Code connects to via the MCP protocol. The simplest distribution is via PyPI — users
pip installand add a 3-line JSON config to VS Code.
Option A: Distribute as a Python Package (Recommended)
Publish to PyPI so users can pip install:
# Build
pip install build
python -m build
# Publish to PyPI
pip install twine
twine upload dist/*
Users install with:
pip install ssh-mcp-server
ssh-mcp-server init
Then add to any VS Code project's .vscode/mcp.json:
{
"servers": {
"ssh-mcp": {
"type": "stdio",
"command": "ssh-mcp-server"
}
}
}
Option B: Publish as a VS Code Extension
A pre-built extension wrapper lives in vscode-extension/. This registers the MCP server in VS Code's marketplace so users get one-click install:
-
Prerequisites:
npm install -g @vscode/vsce
-
Create a publisher at https://marketplace.visualstudio.com/manage (free, needs Azure DevOps org)
-
Update
vscode-extension/package.json— set"publisher"to your publisher ID -
Get a Personal Access Token (PAT) from https://dev.azure.com → User Settings → Personal Access Tokens. Scope: Marketplace (Manage)
-
Package and publish:
cd vscode-extension vsce package # Creates ssh-mcp-server-0.1.0.vsix vsce publish # Publishes to marketplace
Note: Users still need
pip install ssh-mcp-serverfor the Python backend. The extension just auto-registers the MCP server so users don't need to manually edit.vscode/mcp.json.
Option C: List on MCP Server Registry
Register at the community MCP server directories:
- MCP Servers Directory
- Submit a PR to add your server to the registry
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
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ssh_mcp_server_copilot-0.1.0.tar.gz.
File metadata
- Download URL: ssh_mcp_server_copilot-0.1.0.tar.gz
- Upload date:
- Size: 355.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d9ea87f016490de6c5a30c924e1d4c33c6707bdbf95d2340af7815f11225008c
|
|
| MD5 |
ea052e4dd20b34b6015a27021f2a222b
|
|
| BLAKE2b-256 |
b9a1d64a29f016214978212bf62ab811968118928a872aefebf148f1f851e71f
|
File details
Details for the file ssh_mcp_server_copilot-0.1.0-py3-none-any.whl.
File metadata
- Download URL: ssh_mcp_server_copilot-0.1.0-py3-none-any.whl
- Upload date:
- Size: 32.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f5d1a972fbf4da94d3b9d0ca517600a65ea2b8ccc82e6dc996448aa711fe8ae9
|
|
| MD5 |
a638f3b7f93443420fa1a447ab3f2226
|
|
| BLAKE2b-256 |
7d563fcbb537b052d6497b80e2902be652bbd528bfdee1515a074bae6fa7b814
|