An MCP (Model Context Protocol) server that enables AI agents to establish and manage persistent SSH sessions.
- Smart Command Execution: Never hangs the server - automatically transitions to async mode if timeout is reached
- Persistent Sessions: SSH connections are reused across multiple command executions
- Async Command Execution: Non-blocking execution for long-running commands
- SSH Config Support: Automatically reads and uses settings from
~/.ssh/config - Multi-host Support: Manage connections to multiple hosts simultaneously
- Automatic Reconnection: Dead connections are detected and automatically re-established
- Thread-safe: Safe for concurrent operations
- Network Device Support: Automatic enable mode handling for routers and switches
- Sudo Support: Automatic password handling for sudo commands on Unix/Linux hosts
- File Operations: Safe helpers to read and write remote files over SFTP
- Command Interruption: Send Ctrl+C to interrupt running commands
uvx mcp-ssh-sessionAdd to your ~/.claude.json:
{
"mcpServers": {
"ssh-session": {
"type": "stdio",
"command": "uvx",
"args": ["mcp-ssh-session"],
"env": {}
}
}
}npx @modelcontextprotocol/inspector uvx mcp-ssh-sessionuv venv
source .venv/bin/activate
uv pip install -e .Execute a command on an SSH host using a persistent session.
Smart Execution: Starts synchronously and waits for completion. If timeout is reached, automatically transitions to async mode and returns a command ID. Server never hangs!
Advanced Features:
- Automatic timeout handling with async transition
- Interactive command support (use
send_inputfor prompts) - Command interruption capability (
interrupt_command_by_id) - Session persistence across multiple commands
Using SSH config alias:
{
"host": "myserver",
"command": "uptime"
}Using explicit parameters:
{
"host": "example.com",
"username": "user",
"command": "ls -la",
"key_filename": "~/.ssh/id_rsa",
"port": 22
}Network device with enable mode:
{
"host": "router.example.com",
"username": "admin",
"password": "ssh_password",
"enable_password": "enable_password",
"command": "show running-config"
}Unix/Linux with sudo:
{
"host": "server.example.com",
"username": "user",
"sudo_password": "user_password",
"command": "systemctl restart nginx"
}List all active SSH sessions.
Close a specific SSH session.
{
"host": "myserver"
}Close all active SSH sessions.
Execute a command asynchronously without blocking the server. Returns a command ID for tracking.
Use with companion tools:
get_command_status(command_id)- Check progress and retrieve outputinterrupt_command_by_id(command_id)- Send Ctrl+C to stop executionsend_input(command_id, text)- Provide input to interactive commands
{
"host": "myserver",
"command": "sleep 60 && echo 'Done'",
"timeout": 300
}Get the status and output of an async command.
{
"command_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}Interrupt a running async command by sending Ctrl+C.
{
"command_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}List all currently running async commands.
List recent command history (completed, failed, interrupted commands).
{
"limit": 50
}Read the contents of a remote file via SFTP, with optional sudo support.
Basic usage:
{
"host": "myserver",
"remote_path": "/etc/nginx/nginx.conf",
"max_bytes": 131072
}With passwordless sudo (NOPASSWD in sudoers):
{
"host": "myserver",
"remote_path": "/etc/shadow",
"use_sudo": true
}With sudo password:
{
"host": "myserver",
"remote_path": "/etc/shadow",
"sudo_password": "user_password"
}- Attempts SFTP first for best performance
- Falls back to
sudo catvia shell if permission denied anduse_sudo=trueorsudo_passwordprovided - Supports both passwordless sudo (NOPASSWD) and password-based sudo
- Enforces a 2 MB maximum per request (configurable per call up to that limit)
- Returns truncated notice when the content size exceeds the requested limit
Write text content to a remote file via SFTP, with optional sudo support.
Basic usage:
{
"host": "myserver",
"remote_path": "/tmp/app.env",
"content": "DEBUG=true\n",
"append": true,
"make_dirs": true
}With passwordless sudo (NOPASSWD in sudoers):
{
"host": "myserver",
"remote_path": "/etc/nginx/nginx.conf",
"content": "server { ... }",
"use_sudo": true,
"permissions": 420
}With sudo password:
{
"host": "myserver",
"remote_path": "/etc/nginx/nginx.conf",
"content": "server { ... }",
"sudo_password": "user_password",
"permissions": 420
}- Uses SFTP when
use_sudo=falseand nosudo_passwordprovided - Uses
sudo teevia shell whenuse_sudo=trueorsudo_passwordis provided - Supports both passwordless sudo (NOPASSWD) and password-based sudo
- Content larger than 2 MB is rejected for safety
- Optional
appendmode to add to existing files - Optional
make_dirsflag will create missing parent directories - Supports
permissionsto set octal file modes after write (e.g.,420for0644) - Note: Shell fallback is slower than SFTP but enables writing to protected files
The server automatically reads ~/.ssh/config and supports:
- Host aliases
- Hostname mappings
- Port configurations
- User specifications
- IdentityFile settings
Example ~/.ssh/config:
Host myserver
HostName example.com
User myuser
Port 2222
IdentityFile ~/.ssh/id_rsa
Then simply use:
{
"host": "myserver",
"command": "uptime"
}Commands execute in persistent interactive shells that maintain state:
- Current directory persists across commands (
cd /tmpstays in/tmp) - Environment variables remain set
- Shell history is maintained
Commands complete when either:
- Prompt detected: Standard shell prompts (
$,#,>,%) at end of output - Idle timeout: No output for 2 seconds after receiving data
Why idle timeout? Custom themed prompts may not match standard patterns. The 2-second idle timeout ensures commands complete even with non-standard prompts.
Long-running commands: The idle timer resets every time new output arrives, so builds or scripts that output sporadically continue running until naturally complete or the overall timeout is reached.
- ASYNC_COMMANDS.md - Smart execution and async commands
- SAFETY_PROTECTIONS.md
Distributed under the MIT License. See LICENSE for details.