async-ssh-mcp
Coming soon
This project is not yet released.
A security-focused, asynchronous Model Context Protocol
(MCP) server that lets AI assistants manage remote systems over SSH and
SFTP. Built on asyncssh, it is strictly typed (mypy --strict) and
carries 100% statement coverage across all modules.
Python · MIT License
Why
MCP gives an AI assistant a standard way to call tools, but handing that assistant a raw shell is a good way to regret it. This server sits between the two: every command, file read, and file write goes through permission toggles, rate limits, and regex allow/deny filters before it ever reaches a remote host, and every call is written to a local, redacted audit log.
Features
- Asynchronous first -- built on
asyncsshfor non-blocking I/O, so connection pooling and SFTP transfers run concurrently without blocking the event loop. - Resilience -- automatic reconnection with exponential backoff on transient network drops.
- Credential safety -- environment variable interpolation (
$VAR,${VAR}) keeps secrets out of the configuration file. - OS-level sandboxing -- experimental support for seccomp-BPF (Linux), Capsicum (FreeBSD), pledge/unveil (OpenBSD), and Seatbelt (macOS). Windows has no native sandbox yet; use Docker for isolation there.
- Per-server permissions -- toggle
allow_execute,allow_sftp_read, andallow_sftp_writeindependently for each configured host. - Rate limiting -- per-server call frequency and concurrency caps.
- Command filtering -- configurable regex
allowlist/denylist. - Audit logging -- local-only, structured JSONL with automatic credential redaction and result truncation.
- Path anchoring -- restrict file operations to a
remoteRoot. - Cross-platform targets -- personality abstraction handles command generation, quoting, and path resolution for both POSIX and Windows remote hosts.
Toolset
execute-command, get-system-info, list-directory, read-file,
write-file, patch-file, diff-files, find-files, mkdir, remove,
pre-flight, file-stat, compute-hash, upload, download.
Quick start
For multi-server setups, use a config file instead:
# config.yaml
servers:
- name: "prod-web"
host: "10.0.1.5"
username: "admin"
password: "$PROD_PASSWORD" # interpolated from the environment
remoteRoot: "/var/www"
allowlist: ["^ls", "^git status"]
allowed_local_paths: ["/home/user/deploy"]
audit_log: "ssh_audit.log"
Architecture
The server is layered so that CLI parsing, protocol handling, and transport stay decoupled:
- Entry layer (
main.py,config.py) -- Click-based CLI argument parsing and Pydantic-validated configuration; also applies OS sandboxing before the event loop starts. - Orchestration layer (
server.py) -- implements the MCP handlers and dispatchescall_tool/read_resource/get_promptrequests. Tools, resources, and prompts are aggregated through a factory pattern, so new capabilities plug in without touching the core server. - Transport layer (
ssh/,utils.py) -- aConnectionManagerorchestrates pooled SSH clients and multi-hopProxyJumptunnels; aSshPersonalityabstraction (POSIX vs. Windows) centralizes all OS-specific command generation and quoting.
Security is enforced at three points on every request: the sandbox layer restricts the server process itself, the logic layer applies regex filters and path anchoring to what the assistant can request, and the audit layer records the attempt with credentials redacted.
Sandboxing
Sandboxing follows a "lockdown after init" pattern -- load configuration,
pre-open resources, apply OS-level restrictions, then start serving tool
calls. It is experimental and opt-in via --sandbox.
| Platform | Mechanism | Status |
|---|---|---|
| Linux | Seccomp-BPF | Experimental (KILL default) |
| FreeBSD | Capsicum | Experimental |
| OpenBSD | pledge / unveil | Experimental |
| macOS | Seatbelt | Experimental |
| Windows | Mitigation policies / integrity level | Experimental |
Windows uses SetProcessMitigationPolicy to block child-process spawning
and SetTokenInformation to drop the process to Low Integrity Level,
rather than a POSIX-style syscall sandbox.