A secure Model Context Protocol (MCP) server that enables remote SSH command execution with strict read-only enforcement. Perfect for safely delegating SSH access to Claude while preventing accidental or malicious write operations.
✅ Read-Only Command Enforcement – Only allows safe, read-only commands
✅ SSH Connection Pooling – Support multiple simultaneous connections
✅ Command Validation – Blocks dangerous patterns and write operations
✅ Multicast Discovery – Auto-announces on network for easy discovery
✅ Flexible Transport – Stdio, HTTP, or Streamable-HTTP modes
✅ Comprehensive Logging – Full audit trail in ssh_mcp.log
✅ Environment Configuration – Fully configurable via .env
- Python 3.8+
uv
package manager
# Clone or create project directory
mkdir ssh-mcp-server
cd ssh-mcp-server
# Initialize uv project (if needed)
uv init
# Install dependencies
uv pip install fastmcp paramiko python-dotenv
Or use pyproject.toml
:
[project]
name = "ssh-mcp-server"
version = "0.1.0"
dependencies = [
"fastmcp>=0.1.0",
"paramiko>=3.0.0",
"python-dotenv>=1.0.0",
]
Then install:
uv sync
Create a .env
file in the project root:
# Transport mode: stdio (default), http, or streamable-http
MCP_TRANSPORT=stdio
# Server identification
MCP_SERVER_NAME=SSH Read-Only MCP Server
# HTTP mode settings (if using http/streamable-http)
MCP_HOST=0.0.0.0
MCP_PORT=3000
# Multicast discovery
MCP_ENABLE_BROADCAST=true
MCP_BROADCAST_INTERVAL=30
Variable | Default | Description |
---|---|---|
MCP_TRANSPORT |
stdio |
Communication transport: stdio , http , or streamable-http |
MCP_SERVER_NAME |
SSH Read-Only MCP Server |
Display name for the server |
MCP_HOST |
0.0.0.0 |
Bind address for HTTP mode |
MCP_PORT |
3000 |
Port for HTTP mode |
MCP_ENABLE_BROADCAST |
true |
Enable multicast discovery announcements |
MCP_BROADCAST_INTERVAL |
30 |
Seconds between broadcast announcements |
Stdio mode (default):
uv run ssh_readonly_fastmcp_mcast.py
HTTP mode with multicast discovery:
MCP_TRANSPORT=http MCP_PORT=3000 uv run ssh_readonly_fastmcp_mcast.py
HTTP mode without broadcasting:
MCP_ENABLE_BROADCAST=false MCP_TRANSPORT=http MCP_PORT=3000 uv run ssh_readonly_fastmcp_mcast.py
Establish an SSH connection to a remote machine.
Parameters:
host
(required) – Remote host IP or hostnameusername
(required) – SSH usernameport
(optional, default: 22) – SSH portkey_filename
(optional) – Path to private key file (recommended)password
(optional) – SSH password (fallback)
Example:
Connect to 192.168.1.100 as user 'admin' with private key
host: 192.168.1.100
username: admin
key_filename: /home/user/.ssh/id_rsa
Execute a read-only command on the connected remote machine.
Parameters:
host
(required) – Remote host (must be connected first)username
(required) – SSH usernamecommand
(required) – Read-only command to executeport
(optional, default: 22) – SSH port
Example:
Run 'ls -la /home' on connected server
host: 192.168.1.100
username: admin
command: ls -la /home
Close an SSH connection.
Parameters:
host
(required) – Remote hostusername
(required) – SSH usernameport
(optional, default: 22) – SSH port
View all active SSH connections.
Parameters: None
Retrieve the list of allowed read-only commands.
Parameters: None
The server permits the following read-only operations:
- File operations:
cat
,ls
,file
,head
,tail
,find
,locate
- System info:
ps
,top
,df
,du
,free
,uname
,hostname
,uptime
- Network:
netstat
,ss
,ifconfig
,ip
,curl
,wget
,dig
,nslookup
,ping
,traceroute
- Process management:
lsof
,systemctl
,service
- Text processing:
grep
,awk
,sed
,wc
- And many more read-only utilities
Blocked operations: rm
, mv
, cp
, chmod
, chown
, mkdir
, touch
, kill
, shutdown
, reboot
, sudo
, and any write/modify commands.
When running in HTTP mode with broadcasting enabled, the server announces itself on the multicast group:
- Address:
239.255.255.250
- Port:
5353
- Interval: Configurable (default: 30 seconds)
Discovery announcement includes:
- Server UUID
- Server name
- Local IP and port
- Transport type
- Protocol version
All activity is logged to ssh_mcp.log
:
2025-10-17 10:30:45,123 [INFO] ssh_mcp - Starting MCP server with transport=http
2025-10-17 10:30:46,456 [INFO] ssh_mcp - Starting multicast broadcaster on 239.255.255.250:5353
2025-10-17 10:30:47,789 [INFO] ssh_mcp - Successfully connected to admin@192.168.1.100:22
🔒 Read-Only Enforcement:
- Only whitelisted commands are allowed
- Dangerous patterns (pipes, redirects, subshells) are blocked
- Write operations are prevented at the command level
⏱️ Timeouts:
- 30-second execution timeout per command
- Prevents hanging commands from blocking the server
🔐 Authentication:
- SSH key authentication recommended over passwords
- Passwords stored in memory only, never persisted
📋 Audit Trail:
- All connections and commands are logged
- Review
ssh_mcp.log
for security audits
Error: Connection failed: [Errno 111] Connection refused
- Verify the remote host is reachable:
ping <host>
- Check SSH is running on the remote machine
- Verify port number (default 22)
Error: Connection failed: Authentication failed
- Verify username is correct
- For key auth: check key file path and permissions (
chmod 600
) - For password auth: verify credentials
- Ensure SSH public key is authorized on remote (
~/.ssh/authorized_keys
)
Error: Command not allowed for security reasons
- The command contains a blocked pattern or is not in the allowed list
- Use
ssh_get_allowed_commands
to see permitted commands - For write operations, use SSH directly instead
- Verify
MCP_ENABLE_BROADCAST=true
- Check network supports multicast (most corporate networks block it)
- Verify firewall allows UDP on port 5353
- Check
ssh_mcp.log
for broadcast errors
DEBUG=true uv run ssh_readonly_fastmcp_mcast.py
# Test connection
uv run -c "from ssh_readonly_fastmcp_mcast import is_command_safe; print(is_command_safe('ls -la'))"
# Should print: True
# Test blocked command
uv run -c "from ssh_readonly_fastmcp_mcast import is_command_safe; print(is_command_safe('rm -rf /'))"
# Should print: False
ssh-mcp-server/
├── ssh_readonly_fastmcp_mcast.py # Main server implementation
├── .env # Configuration file
├── .env.example # Configuration template
├── ssh_mcp.log # Server logs (auto-generated)
├── pyproject.toml # Project metadata
└── README.md # This file
All tools return consistent JSON responses:
Success:
{
"status": "success",
"host": "192.168.1.100",
"command": "ls -la /home",
"exit_code": 0,
"output": "total 24\ndrwxr-xr-x 3 root root 4096...",
"error": null
}
Error:
{
"status": "error",
"message": "Command not allowed for security reasons",
"reason": "Only read-only commands are permitted"
}
MIT
Contributions welcome! Please ensure:
- All changes maintain read-only enforcement
- Code is logged appropriately
- Tests pass for security validations
For issues or questions:
- Check
ssh_mcp.log
for error details - Review the Troubleshooting section
- Verify environment configuration
- Check network connectivity to remote hosts