VPS Ops
Stdio MCP server that inspects and (with confirmation) mutates one VPS over SSH.
التثبيت
أضف هذا إلى إعدادات MCP في عميلك (Claude Desktop: claude_desktop_config.json، وCursor: .cursor/mcp.json، ويستخدم VS Code وغيره الصيغة نفسها «mcpServers»).
@koller-nexus/vps-ops-mcpclaude mcp add vps-ops -e 'VPS_SSH_KEY_PATH=<your vps_ssh_key_path>' -e 'VPS_HOST=<VPS_HOST>' -e 'VPS_USER=<VPS_USER>' -e 'VPS_PORT=<VPS_PORT>' -e 'VPS_COMPOSE_DIR=<VPS_COMPOSE_DIR>' -e 'VPS_COMMAND_TIMEOUT_MS=<VPS_COMMAND_TIMEOUT_MS>' -e 'VPS_LOG_MAX_BYTES=<VPS_LOG_MAX_BYTES>' -e 'VPS_ALLOW_MUTATIONS=<VPS_ALLOW_MUTATIONS>' -e 'VPS_SSH_KEY_PASSPHRASE=<your vps_ssh_key_passphrase>' -- bunx -y @koller-nexus/vps-ops-mcp@0.1.0
{
"mcpServers": {
"vps-ops": {
"command": "bunx",
"args": [
"-y",
"@koller-nexus/vps-ops-mcp@0.1.0"
],
"env": {
"VPS_SSH_KEY_PATH": "<your vps_ssh_key_path>",
"VPS_HOST": "<VPS_HOST>",
"VPS_USER": "<VPS_USER>",
"VPS_PORT": "<VPS_PORT>",
"VPS_COMPOSE_DIR": "<VPS_COMPOSE_DIR>",
"VPS_COMMAND_TIMEOUT_MS": "<VPS_COMMAND_TIMEOUT_MS>",
"VPS_LOG_MAX_BYTES": "<VPS_LOG_MAX_BYTES>",
"VPS_ALLOW_MUTATIONS": "<VPS_ALLOW_MUTATIONS>",
"VPS_SSH_KEY_PASSPHRASE": "<your vps_ssh_key_passphrase>"
}
}
}
}
متغيرات البيئة
VPS_SSH_KEY_PATHإلزامي سري Absolute path to a readable SSH private keyVPS_HOSTSSH hostname of the single target VPSVPS_USERSSH usernameVPS_PORTSSH portVPS_COMPOSE_DIRAbsolute Compose directory on the remote hostVPS_COMMAND_TIMEOUT_MSRemote command timeout in millisecondsVPS_LOG_MAX_BYTESStdout/stderr byte cap for remote commandsVPS_ALLOW_MUTATIONSSet false to reject every mutation toolVPS_SSH_KEY_PASSPHRASEسري Optional key passphrase; prefer ssh-agent instead
التوثيق
مأخوذ من ملف README للمشروع على GitHub ويُحدَّث أسبوعيًا. تفتح الروابط على GitHub.
المحتويات
vps-ops-mcp
A stdio MCP server that operates one VPS over SSH. Cursor and Codex start the process with bun and call read-only tools (host health, Unix debug, Docker, Compose, Swarm, firewall) plus mutation tools (restart, stop, start, prune). Mutations require an explicit confirmation.
Transport is stdio. Do not start the server as a long-lived process by hand: the client (Cursor or Codex) launches it.
Contributing
See CONTRIBUTING.md (branches, pull requests, verification) and ISSUE.md (how to file an issue).
Requirements
- Bun
- Python 3 (used by the registration scripts)
- OpenSSH client (
sshonPATH) - A readable SSH private key with access to the remote user
- On the remote host: Docker (and passwordless
sudo -nforufw,fail2ban,sshd -T,ss, anddmesgif you use those tools)
Configuration
cp .env.example .env
Edit .env. The file is gitignored.
| Variable | Required | Default | Purpose |
|---|---|---|---|
VPS_SSH_KEY_PATH | yes | — | Absolute path to the private key. The process refuses to start if the file is missing or unreadable. |
VPS_HOST | no | vps.example.invalid | SSH host (placeholder; set your own host). |
VPS_USER | no | ubuntu | SSH user. |
VPS_PORT | no | 22 | SSH port. |
VPS_COMPOSE_DIR | no | — | Absolute Compose directory on the VPS. Without it, Compose tools require the dir argument. |
VPS_COMMAND_TIMEOUT_MS | no | 30000 | Remote command timeout. Expiry returns exit_code 124. |
VPS_LOG_MAX_BYTES | no | 200000 | Cap for stdout/stderr. Overflow is cut and truncated is true. |
VPS_ALLOW_MUTATIONS | no | true | false, 0, no, or off disables every mutation. |
VPS_SSH_KEY_PASSPHRASE | no | — | Avoid. Prefer ssh-agent. Registration scripts do not copy this variable into the client. |
VPS_COMPOSE_DIR must be absolute and match /^[a-zA-Z0-9/_.-]+$/ (it must start with /).
Installation
bun install
npm package (requires Bun; the MCP Registry points at this artifact):
bunx @koller-nexus/vps-ops-mcp
MCP Registry name: io.github.koller-nexus/vps-ops-mcp. The registry publishes metadata only after the package exists on public npm.
Register with clients
The scripts write MCP config from variables already exported in the shell. They do not load .env themselves. If you skip the export, the scripts fall back to their defaults (host, user, port, and a local key path).
Do this once at the repository root before each script:
set -a
source .env
set +a
Each run backs up the destination file (*.bak.YYYYMMDDHHMMSS) and replaces only the vps-ops server. Other MCP servers stay in place.
Optional script variables:
| Variable | Default | Purpose |
|---|---|---|
MCP_PROJECT_DIR | this repository root | Source of the src/index.ts path. |
CURSOR_MCP_JSON | ~/.cursor/mcp.json | Cursor file to update. |
CODEX_CONFIG | ~/.codex/config.toml | Codex file to update. |
VPS_COMPOSE_DIR is written into the client config only when it is set and non-empty.
Cursor
Global registration (applies in every workspace):
./scripts/register-cursor-mcp.sh
The script writes ~/.cursor/mcp.json in this shape:
{
"mcpServers": {
"vps-ops": {
"command": "bun",
"args": ["/absolute/path/vps-ops-mcp/src/index.ts"],
"env": {
"VPS_HOST": "your.host",
"VPS_USER": "ubuntu",
"VPS_PORT": "22",
"VPS_SSH_KEY_PATH": "/absolute/path/to/key",
"VPS_COMMAND_TIMEOUT_MS": "30000",
"VPS_LOG_MAX_BYTES": "200000",
"VPS_ALLOW_MUTATIONS": "true"
}
}
}
}
To scope it to one project, point the script at that project's mcp.json:
CURSOR_MCP_JSON="/absolute/path/to/project/.cursor/mcp.json" ./scripts/register-cursor-mcp.sh
Then reload the Cursor window (Command Palette → Developer: Reload Window) or restart the server under Settings → MCP. The server appears as vps-ops.
Codex
./scripts/register-codex-mcp.sh
The script writes ~/.codex/config.toml:
[mcp_servers.vps-ops]
command = "bun"
args = ["/absolute/path/vps-ops-mcp/src/index.ts"]
[mcp_servers.vps-ops.env]
VPS_HOST = "your.host"
VPS_USER = "ubuntu"
VPS_PORT = "22"
VPS_SSH_KEY_PATH = "/absolute/path/to/key"
VPS_COMMAND_TIMEOUT_MS = "30000"
VPS_LOG_MAX_BYTES = "200000"
VPS_ALLOW_MUTATIONS = "true"
Close and reopen the Codex session so it rereads config.toml. If the CLI is on PATH, codex mcp list should show vps-ops.
Verify
Test SSH outside MCP with the same flags the server uses:
ssh -i "$VPS_SSH_KEY_PATH" \
-o BatchMode=yes \
-o IdentitiesOnly=yes \
-o StrictHostKeyChecking=accept-new \
-p "${VPS_PORT:-22}" \
"${VPS_USER}@${VPS_HOST}" \
'uname -a'
In Cursor or Codex, ask the client to call vps_ping. The response is JSON:
{
"exit_code": 0,
"stdout": "...",
"stderr": "",
"duration_ms": 0,
"truncated": false
}
A non-zero exit_code is an MCP error. If the process exits immediately with VPS_SSH_KEY_PATH is required or missing or unreadable, the variable never reached the client env — rerun the registration script with .env exported.
Tools
Every call returns exit_code, stdout, stderr, duration_ms, and truncated.
Read-only
| Tool | Arguments | What it does |
|---|---|---|
vps_ping | — | uname -a, uptime, hostname. |
vps_resources | — | df -h, free -h, load average. |
vps_journal | unit, n? (1–500, default 100) | journalctl -u. Unit from the allowlist (docker, sshd, fail2ban, ufw, cron, with or without .service) or a safe name ending in .service. |
docker_ps | — | docker ps -a as JSON lines. |
docker_inspect | name | docker inspect. |
docker_logs | name, n? (1–1000, default 200), since? | docker logs --tail --timestamps. |
docker_stats | — | docker stats --no-stream. |
docker_service_ls | — | docker service ls em JSON lines (Swarm). |
docker_node_ls | — | docker node ls em JSON lines (Swarm). |
compose_ps | dir?, file? | docker compose ps em dir ou VPS_COMPOSE_DIR (file, ex. stack.yml, vira -f). |
compose_config_vars | dir?, file? | Renderiza o stack (docker compose config) e devolve só os nomes das variáveis de ambiente por serviço, nunca os valores. |
host_deps | — | Versões de docker, docker compose (v2) e jq. |
host_listen | — | ss -lntup (sudo -n, senão sem sudo). |
host_failed_units | — | systemctl --failed --no-pager --full. |
host_top | — | Top 30 processes by memory (ps aux --sort=-%mem). |
host_dmesg | n? (1–200, default 100) | dmesg -T + tail (sudo -n, otherwise without sudo). |
host_firewall | — | ufw status verbose (sudo -n, otherwise without sudo). |
host_fail2ban | jail? | fail2ban-client status (sudo -n). |
ssh_hardening_check | — | Filtered sshd -T: port, password, root login, pubkey. |
Container, service, image, and jail names must match ^[a-zA-Z0-9][a-zA-Z0-9_.-]*$.
Mutation
These require confirm: true. With VPS_ALLOW_MUTATIONS=false, all of them are rejected.
| Tool | Extra arguments | Remote command |
|---|---|---|
docker_restart | name | docker restart |
docker_stop | name | docker stop |
docker_start | name | docker start |
compose_up | dir?, services? | docker compose up -d |
compose_restart | dir?, services? | docker compose restart |
compose_pull_up | dir?, services? | docker compose pull then up -d |
docker_rm | name, force_name | docker rm -f. force_name must equal name. |
disk_cleanup_docker | confirm_volumes? | docker system prune -f. Volumes only with confirm_volumes: true. |
Security
- Remote commands are fixed. There is no free-form shell tool.
- Name and path arguments go through an allowlist and are quoted in the shell.
- SSH uses
BatchMode=yes,IdentitiesOnly=yes, andStrictHostKeyChecking=accept-new. - A mutation without
confirm: trueis rejected.docker_rmasks for the name twice. Volume prune requiresconfirm_volumes: true. - For a read-only client, register with
VPS_ALLOW_MUTATIONS=false.
الأسئلة الشائعة
ما هو VPS Ops؟
Stdio MCP server that inspects and (with confirmation) mutates one VPS over SSH.
كيف أثبّت VPS Ops؟
يعمل على جهازك. في Claude Code: claude mcp add vps-ops -e 'VPS_SSH_KEY_PATH=<your vps_ssh_key_path>' -e 'VPS_HOST=<VPS_HOST>' -e 'VPS_USER=<VPS_USER>' -e 'VPS_PORT=<VPS_PORT>' -e 'VPS_COMPOSE_DIR=<VPS_COMPOSE_DIR>' -e 'VPS_COMMAND_TIMEOUT_MS=<VPS_COMMAND_TIMEOUT_MS>' -e 'VPS_LOG_MAX_BYTES=<VPS_LOG_MAX_BYTES>' -e 'VPS_ALLOW_MUTATIONS=<VPS_ALLOW_MUTATIONS>' -e 'VPS_SSH_KEY_PASSPHRASE=<your vps_ssh_key_passphrase>' -- bunx -y @koller-nexus/vps-ops-mcp@0.1.0. وفي العملاء الآخرين، الصق إعداد JSON من هذه الصفحة في إعدادات MCP الخاصة بهم.
هل يحتاج VPS Ops إلى مفتاح API؟
نعم. يتطلب VPS_SSH_KEY_PATH, VPS_SSH_KEY_PASSPHRASE، وتضبطها في إعداداته.
هل VPS Ops مفتوح المصدر؟
نعم. شيفرته متاحة على GitHub بترخيص MIT.
من يصون VPS Ops؟
ينشره koller-nexus. يدرجه AI.tn لكنه لا يصونه ولا يوصي به.
من سجل MCP الرسمي. الإدراج ليس توصية: راجع شيفرة الخادم وصلاحياته قبل منحه الوصول إلى بياناتك.