vps-ops-mcp
Don't give your AI raw root SSH. A safe, structured MCP server that lets Claude (or any MCP client) inspect and operate your VPS — nginx, PM2, SSL, UFW, fail2ban — through typed, allowlisted tools instead of free-form shell access.
Read-only by default. Mutating actions don't even exist unless you opt in with an environment variable.
You: "Is everything OK on my server? Any cert expiring soon?"
Claude → server_health(myvps) → load 0.08, disk 61%, RAM fine
→ ssl_status(myvps) → b2fest.com expires in 71 days ✓
→ security_audit(myvps) → ufw active, root login disabled ✓
Why not just an SSH MCP server?
Generic SSH MCP servers hand the model a root shell and hope for the best. This server takes the opposite approach:
| Raw SSH MCP | vps-ops-mcp | |
|---|---|---|
| Command surface | anything | fixed command templates only |
| User input in commands | interpolated | strict allowlist regex, rejected otherwise |
| Credentials | often stored in config | never touched — delegates to your ssh client, ~/.ssh/config, ssh-agent |
| Password prompts | can hang | BatchMode=yes, fails fast |
| Writes/restarts | always on | off by default, opt-in via env var |
pm2 restart all |
sure, why not | refused by design |
Quickstart
Requires Node 18+ and a working ssh <your-host> from your terminal (key-based auth).
Claude Code
claude mcp add vps-ops -- npx -y vps-ops-mcp
Claude Desktop / Cursor / any MCP client — add to your MCP config:
{
"mcpServers": {
"vps-ops": {
"command": "npx",
"args": ["-y", "vps-ops-mcp"]
}
}
}
Then just ask: "Check the health of myvps" (any alias from your ~/.ssh/config, or user@host).
Enabling mutations (optional)
By default the server is strictly read-only. To enable the two mutating tools (nginx_check_and_reload, pm2_restart):
{
"mcpServers": {
"vps-ops": {
"command": "npx",
"args": ["-y", "vps-ops-mcp"],
"env": { "VPS_OPS_ALLOW_MUTATIONS": "true" }
}
}
}
Even then: no free-form commands, nginx -t always runs before a reload, and pm2 restart all is refused.
Tools
| Tool | What it does | Needs |
|---|---|---|
list_hosts |
Lists aliases from your local ~/.ssh/config |
nothing (local) |
server_health |
Uptime, load, memory, disk, top processes | ssh |
list_sites |
Enabled nginx sites, PM2 process list, running web services | ssh |
ssl_status |
Cert expiry for every domain found in nginx configs (or one domain) | ssh |
read_logs |
Tail nginx access/error, PM2 app, or journald unit logs | ssh (some logs: sudo) |
security_audit |
Listening ports, UFW, fail2ban, sshd hardening, pending security updates, recent logins | ssh (richer with sudo) |
nginx_check_and_reload 🔒 |
nginx -t, then graceful reload only if the test passes |
opt-in + sudo |
pm2_restart 🔒 |
Restart one named PM2 app (never all) |
opt-in |
🔒 = only registered when VPS_OPS_ALLOW_MUTATIONS=true.
About sudo
Some checks (ufw, fail2ban, sshd -T) need root. The server always uses sudo -n (non-interactive): if passwordless sudo isn't configured for those commands, the check degrades gracefully and tells you, instead of hanging on a password prompt. You choose how much to allow in /etc/sudoers.d/.
Safety model
- No credential handling. We spawn your system
sshbinary. Keys, agents,ProxyJump,known_hosts— all yours, all untouched. - Fixed command templates. Remote commands are string constants. There is no
run_commandtool and there never will be one in read-only mode. - Allowlist validation. Every user-supplied value (host, app name, domain, unit) must match a strict regex before it goes anywhere near a command line. No escaping heuristics — invalid input is simply rejected.
- Bounded output. Every call has a timeout and an output cap, so a runaway
tailcan't flood your context window. - Mutations are opt-in and minimal. Two tools, both narrow, both guarded.
Roadmap
-
site_provision— nginx vhost + certbot + PM2 registration in one guarded flow - Docker container inventory & log tools
- Caddy support
- Multi-server fleet summary (
health across all hosts) - Scheduled-check examples (cron + Claude Code headless)
PRs welcome — especially real-world ops workflows this doesn't cover yet.
License
MIT © Azat Akdağ
No comments yet
Be the first to share your take.