An MCP (Model Context Protocol) server that enables AI assistants to run commands and access files on remote POSIX compatible system (Linux, BSD, macOS) systems via SSH. This server provides a secure way for AI models to perform system administration tasks, troubleshoot issues, execute commands, and read configuration files on remote POSIX compatible system (Linux, BSD, macOS) machines.
This MCP server enables LLMs to perform system administration tasks across remote infrastructure.
- Web performance → Check system resources (
top,free,df), examine web server logs, analyze network connections - Authentication failures → Investigate authentication logs (
/var/log/auth.log), check service status (systemctl status sshd), verify user accounts - Database timeouts → Examine database logs, check connection pools, analyze slow query logs, monitor system resources
- Comparative analysis: Check for differences between different web servers, databases, and load balancers
- Configuration analysis: Read and analyze config files (
nginx.conf,my.cnf, etc.) - Dependency tracking: Check systemd units, network connections, and process relationships
- Deployment verification: Verify services are running, configurations are correct, and applications are responding
- Incident response: Gather diagnostic information from multiple systems
- Documentation generation: Document system configurations, installed packages, and service dependencies
- Tools:
- Local command execution for SSH troubleshooting
- Remote SSH command execution (standard user permissions)
- Remote SSH command execution with sudo support
- File copying with rsync (preserves attributes, creates backups)
- Patch application over SSH (apply diffs to remote files)
- Configurable timeouts: Per-command timeout settings to prevent blocking
- Public key discovery: List available public keys from
~/.sshdirectory - Authentication: Uses existing SSH configuration and keys
- SSH configuration: Relies on existing SSH config file (
~/.ssh/config) for user and key specification - Security: Leverages SSH's built-in security features
- System administrator persona: Built-in instructions for system administration tasks
- Error handling: Error messages for connection and execution issues
- Rust toolchain (1.70 or later)
- SSH client installed on your system
- SSH access configured to your target Linux systems
- SSH keys set up for passwordless authentication (recommended)
- Clone this repository:
git clone https://github.com/subpop/mcp_linux_ssh
cd mcp_linux_ssh- Build the project:
cargo build --releaseThe compiled binary will be available at target/release/mcp_linux_ssh.
-
Build the project as described above
-
Edit your Claude Desktop configuration file:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
- macOS:
-
Add the MCP server configuration:
{
"mcpServers": {
"linux-ssh": {
"command": "/path/to/your/mcp_linux_ssh/target/release/mcp_linux_ssh",
"args": [],
"env": {
"RUST_LOG": "info"
}
}
}
}- Restart Claude Desktop
Gemini doesn't have native MCP support, but you can use it through compatible MCP clients or adapters. Follow the general MCP client setup pattern:
- Use an MCP-compatible client that supports Gemini
- Configure the server path:
/path/to/your/mcp_linux_ssh/target/release/mcp_linux_ssh - Set environment variables as needed
- Install an MCP extension for VSCode (such as the official MCP extension)
- Open VSCode settings (Cmd/Ctrl + ,)
- Search for "MCP" settings
- Add a new MCP server configuration:
- Name:
linux-ssh - Command:
/path/to/your/mcp_linux_ssh/target/release/mcp_linux_ssh - Args:
[] - Environment:
{"RUST_LOG": "info"}
- Name:
- Build the project as described above
- Open Cursor settings
- Navigate to the MCP servers section
- Add a new server configuration:
{
"name": "linux-ssh",
"command": "/path/to/your/mcp_linux_ssh/target/release/mcp_linux_ssh",
"args": [],
"env": {
"RUST_LOG": "info"
}
}- Build the project as described above
- Create or edit your Goose configuration file (
~/.config/goose/config.yamlor similar) - Add the MCP server configuration:
mcp_servers:
linux-ssh:
command: /path/to/your/mcp_linux_ssh/target/release/mcp_linux_ssh
args: []
env:
RUST_LOG: infoBefore using this MCP server, ensure your SSH is properly configured:
Generate an SSH key pair if you don't have one:
ssh-keygen -t ed25519 -C "your_email@example.com"Copy your public key to the remote server:
ssh-copy-id user@remote-hostCreate or edit ~/.ssh/config to configure connections. This is the recommended way to specify users, keys, and other SSH settings:
Host myserver
HostName 192.168.1.100
User myuser
Port 22
IdentityFile ~/.ssh/id_ed25519
Host production-server
HostName prod.example.com
User deploy
IdentityFile ~/.ssh/production_key
Host staging-server
HostName staging.example.com
User deploy
IdentityFile ~/.ssh/staging_key
Verify you can connect without a password:
ssh myserver whoamiOnce configured, you can use the following capabilities through your AI assistant:
Executes a command on the local system. This tool is primarily intended for troubleshooting SSH connectivity issues when remote commands fail.
Parameters:
command(required): The command to execute locallyargs(optional): Array of arguments to pass to the commandtimeout_seconds(optional): Timeout in seconds for command execution (default: 30, set to 0 to disable)
Examples:
{
"command": "ssh",
"args": ["-v", "user@remote-host", "echo", "test"]
}{
"command": "ping",
"args": ["-c", "5", "google.com"],
"timeout_seconds": 10
}Executes a command on a remote POSIX compatible system (Linux, BSD, macOS) system via SSH. This tool does not permit commands to be run with sudo.
Parameters:
command(required): The command to executeargs(optional): Array of arguments to pass to the commandremote_host(required): The hostname, IP address, or SSH config alias of the remote systemtimeout_seconds(optional): Timeout in seconds for command execution (default: 30, set to 0 to disable)options(optional): Additional SSH options to pass via-oflag (array of "key=value" strings)
Examples:
{
"command": "ls",
"args": ["-la", "/home"],
"remote_host": "myserver"
}{
"command": "systemctl",
"args": ["status", "nginx"],
"remote_host": "webserver.example.com"
}Executes a command on a remote POSIX compatible system (Linux, BSD, macOS) system via SSH. This tool permits commands to be run with sudo for administrative tasks.
Parameters:
command(required): The command to execute (can include sudo)args(optional): Array of arguments to pass to the commandremote_host(required): The hostname, IP address, or SSH config alias of the remote systemtimeout_seconds(optional): Timeout in seconds for command execution (default: 30, set to 0 to disable)options(optional): Additional SSH options to pass via-oflag (array of "key=value" strings)
Examples:
{
"command": "sudo",
"args": ["systemctl", "restart", "nginx"],
"remote_host": "webserver.example.com"
}{
"command": "sudo",
"args": ["tail", "-f", "/var/log/syslog"],
"remote_host": "logserver"
}Copies a file from the local machine to a remote system using rsync. Preserves file attributes (permissions, timestamps, ownership) and creates backups of existing files on the remote system.
Parameters:
source(required): The path to the source file on the local machinedestination(required): The destination path on the remote machineremote_host(required): The hostname, IP address, or SSH config alias of the remote systemtimeout_seconds(optional): Timeout in seconds for the copy operation (default: 30, set to 0 to disable)
Features:
- Archive mode: Preserves permissions, timestamps, ownership, and other file attributes
- Automatic backups: If a file exists at the destination, a backup is created with a
~suffix - Secure transfer: Uses SSH for encrypted file transfer
Examples:
{
"source": "/home/user/config.yaml",
"destination": "/etc/myapp/config.yaml",
"remote_host": "webserver.example.com"
}{
"source": "./build/app.jar",
"destination": "/opt/myapp/app.jar",
"remote_host": "production-server",
"timeout_seconds": 60
}Backup Behavior:
When copying to an existing file, rsync creates a backup with the original filename plus a ~ suffix:
- Original file:
/etc/myapp/config.yaml - Backup file:
/etc/myapp/config.yaml~ - New file:
/etc/myapp/config.yaml(updated)
Applies a patch/diff to a file on a remote system via SSH. The patch content is streamed through stdin over the SSH connection to the remote patch command.
Parameters:
patch(required): The patch/diff content to apply (unified diff format recommended)remote_file(required): The path to the file on the remote machine to patchremote_host(required): The hostname, IP address, or SSH config alias of the remote systemtimeout_seconds(optional): Timeout in seconds for the patch operation (default: 30, set to 0 to disable)options(optional): Additional SSH options to pass via-oflag (array of "key=value" strings)
Features:
- Stdin streaming: Patch content is securely streamed via SSH stdin
- Automatic strip detection: The
patchcommand automatically detects the appropriate-pstrip level - Unified diff support: Works best with unified diff format (
diff -uorgit diff) - Context preservation: Maintains file context for accurate patching
Examples:
Basic usage:
{
"patch": "--- config.yaml\n+++ config.yaml\n@@ -1,3 +1,3 @@\n-port: 8080\n+port: 9090\n",
"remote_file": "/etc/myapp/config.yaml",
"remote_host": "webserver.example.com"
}Applying a Git diff:
{
"patch": "diff --git a/app.py b/app.py\nindex 1234567..abcdefg 100644\n--- a/app.py\n+++ b/app.py\n@@ -10,7 +10,7 @@ def main():\n- return 'Hello'\n+ return 'Hello, World!'\n",
"remote_file": "/opt/myapp/app.py",
"remote_host": "production-server"
}With custom SSH options:
{
"patch": "--- nginx.conf\n+++ nginx.conf\n@@ -5,1 +5,1 @@\n-worker_processes 2;\n+worker_processes 4;\n",
"remote_file": "/etc/nginx/nginx.conf",
"remote_host": "192.168.1.100",
"options": ["StrictHostKeyChecking=no", "UserKnownHostsFile=/dev/null"]
}Workflow:
- Generate a diff locally:
diff -u original.txt modified.txt > changes.patch - Read the patch content
- Use
Patch_Fileto apply it to the remote file - Verify the changes with
SSHtool (e.g.,cat /path/to/file)
Notes:
- The
patchcommand must be installed on the remote system - Ensure the file to be patched exists on the remote system
- If the patch fails to apply cleanly, check the output for conflicts
- The remote file path should be absolute or relative to the remote user's home directory
- For large patches or binary files, consider using
Copy_Fileinstead
Common Patch Formats:
- Unified diff (recommended):
diff -u old.txt new.txt - Git diff:
git diff file.txt - Context diff:
diff -c old.txt new.txt
All commands support configurable timeouts to prevent indefinite blocking.
- Default: 30 seconds
- Disable: Set
timeout_secondsto0 - Custom: Set any positive integer (seconds)
// Quick command
{
"command": "whoami",
"remote_host": "server1",
"timeout_seconds": 5
}
// Long operation
{
"command": "find",
"args": ["/", "-name", "*.log"],
"remote_host": "server1",
"timeout_seconds": 300
}
// No timeout (monitoring)
{
"command": "tail",
"args": ["-f", "/var/log/syslog"],
"remote_host": "server1",
"timeout_seconds": 0
}The MCP server supports an optional LLM-based judge that evaluates tool calls before execution. This offers an additional layer of security by allowing another LLM to review commands and reject potentially dangerous operations.
The judge is configured entirely through environment variables. To enable the judge, set the required environment variables:
# Required: LLM provider service
export MCP_LINUX_SSH_JUDGE_SERVICE="openai"
# Required: Model name
export MCP_LINUX_SSH_JUDGE_MODEL="gpt-4o-mini"
# Required for OpenAI, Anthropic, Gemini: API key
export MCP_LINUX_SSH_JUDGE_API_KEY="sk-..."
# Optional: Custom base URL
export MCP_LINUX_SSH_JUDGE_BASE_URL="https://api.openai.com/v1"
# Optional: Timeout in seconds (default: 10)
export MCP_LINUX_SSH_JUDGE_TIMEOUT_SECONDS="10"
# Optional: Fail mode - "open" or "closed" (default: "open")
export MCP_LINUX_SSH_JUDGE_FAIL_MODE="open"
# Optional: Comma-separated list of tools to judge (default: all tools)
export MCP_LINUX_SSH_JUDGE_TOOLS="run_ssh_command,run_ssh_sudo_command,copy_file,patch_file,run_local_command"| Variable | Required | Default | Description |
|---|---|---|---|
MCP_LINUX_SSH_JUDGE_SERVICE |
Yes | - | LLM provider: "openai", "anthropic", "gemini", or "ollama" |
MCP_LINUX_SSH_JUDGE_MODEL |
Yes | - | Model name (e.g., "gpt-4o-mini", "claude-3-5-sonnet-20241022") |
MCP_LINUX_SSH_JUDGE_API_KEY |
Yes* | - | API key for the provider (*not required for Ollama) |
MCP_LINUX_SSH_JUDGE_BASE_URL |
No | Provider default | Custom base URL for the API |
MCP_LINUX_SSH_JUDGE_TIMEOUT_SECONDS |
No | 10 |
Timeout for LLM judge calls |
MCP_LINUX_SSH_JUDGE_FAIL_MODE |
No | "open" |
Behavior when judge unavailable: "open" (allow) or "closed" (reject) |
MCP_LINUX_SSH_JUDGE_TOOLS |
No | All tools | Comma-separated list of tool names to judge |
- OpenAI: Set
MCP_LINUX_SSH_JUDGE_SERVICE="openai"and provideMCP_LINUX_SSH_JUDGE_API_KEYandMCP_LINUX_SSH_JUDGE_MODEL - Anthropic: Set
MCP_LINUX_SSH_JUDGE_SERVICE="anthropic"and provideMCP_LINUX_SSH_JUDGE_API_KEYandMCP_LINUX_SSH_JUDGE_MODEL - Gemini: Set
MCP_LINUX_SSH_JUDGE_SERVICE="gemini"and provideMCP_LINUX_SSH_JUDGE_API_KEYandMCP_LINUX_SSH_JUDGE_MODEL - Ollama: Set
MCP_LINUX_SSH_JUDGE_SERVICE="ollama"and provideMCP_LINUX_SSH_JUDGE_MODEL(no API key needed)
The MCP_LINUX_SSH_JUDGE_FAIL_MODE setting controls behavior when the judge is unavailable:
"open"(default): If the judge fails or times out, allow the tool call to proceed"closed": If the judge fails or times out, reject the tool call
Only tools listed in MCP_LINUX_SSH_JUDGE_TOOLS will be evaluated. Available tool names:
"run_local_command"- Local command execution"run_ssh_command"- Remote SSH command execution"run_ssh_sudo_command"- Remote SSH command with sudo"copy_file"- File transfer with rsync"patch_file"- Apply patches to remote files
If MCP_LINUX_SSH_JUDGE_TOOLS is not set, all tools are judged by default.
The judge must return JSON in this format:
{
"allowed": false,
"reason": "Command attempts to delete root filesystem"
}If allowed is false, the tool call is rejected with the reason as the error message.
OpenAI Example:
export MCP_LINUX_SSH_JUDGE_SERVICE="openai"
export MCP_LINUX_SSH_JUDGE_MODEL="gpt-4o-mini"
export MCP_LINUX_SSH_JUDGE_API_KEY="sk-..."
export MCP_LINUX_SSH_JUDGE_FAIL_MODE="open"Anthropic Example:
export MCP_LINUX_SSH_JUDGE_SERVICE="anthropic"
export MCP_LINUX_SSH_JUDGE_MODEL="claude-3-5-sonnet-20241022"
export MCP_LINUX_SSH_JUDGE_API_KEY="sk-ant-..."
export MCP_LINUX_SSH_JUDGE_TIMEOUT_SECONDS="15"Ollama Example (Local):
export MCP_LINUX_SSH_JUDGE_SERVICE="ollama"
export MCP_LINUX_SSH_JUDGE_MODEL="llama3.2"
export MCP_LINUX_SSH_JUDGE_BASE_URL="http://localhost:11434"If MCP_LINUX_SSH_JUDGE_SERVICE is not set, the judge is disabled and all tool calls proceed normally.
- Permission Denied: Ensure SSH keys are properly set up and the user has access
- Host Key Verification Failed: Add the host to your known_hosts file:
ssh-keyscan -H remote-host >> ~/.ssh/known_hosts
- Command Not Found: Ensure the command exists on the remote system and is in the PATH
- Connection Timeout: Check network connectivity and SSH daemon status
- Authentication: Verify SSH key permissions (600 for private key, 644 for public key)
- Path Issues: Use absolute paths for commands when possible
-
User Not Specified: Ensure your
~/.ssh/confighas theUserdirective for each host:Host myserver User myuser IdentityFile ~/.ssh/id_ed25519 -
Wrong Key Path: Verify the
IdentityFilein your SSH config points to the correct key:# Check if key exists ls -la ~/.ssh/id_ed25519 # Verify key permissions chmod 600 ~/.ssh/id_ed25519
-
Key Not Added to Agent: If using SSH agent, ensure your key is loaded:
ssh-add ~/.ssh/id_ed25519 ssh-add -l # List loaded keys
-
Multiple Keys: Configure different keys for different hosts in
~/.ssh/config:Host production HostName prod.example.com User deploy IdentityFile ~/.ssh/production_key Host staging HostName staging.example.com User deploy IdentityFile ~/.ssh/staging_key -
Key Format Issues: Ensure your private key is in the correct format (OpenSSH format preferred)
To modify or extend this server:
- Edit the source code in
src/lib.rs - Add new tools by implementing functions with the
#[tool]attribute - Rebuild with
cargo build --release - Restart your MCP client to pick up changes
The server automatically logs all tool calls to ~/.local/state/mcp_linux_ssh/tool_calls.jsonl (following XDG Base Directory specification) for debugging and audit purposes. Logs are rotated daily.
Contributions are welcome! Please ensure:
- Code is properly formatted (
cargo fmt) - All tests pass (
cargo test) - Security best practices are followed
See LICENSE file for details.