Stdio MCP server that inspects and (with confirmation) mutates one VPS over SSH.
- ✓Open-source license (MIT)
- ✓Actively maintained (<30d)
- ✓Clear description
- ✓Documented (README)
git clone https://github.com/koller-nexus/vps-ops-mcpVPS_HOSTResumen de MCP Servers
# vps-ops-mcp
A stdio [MCP](https://modelcontextprotocol.io) 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](./CONTRIBUTING.md) (branches, pull requests, verification) and [ISSUE.md](./ISSUE.md) (how to file an issue).
## Requirements
- [Bun](https://bun.sh)
- Python 3 (used by the registration scripts)
- OpenSSH client (`ssh` on `PATH`)
- A readable SSH private key with access to the remote user
- On the remote host: Docker (and passwordless `sudo -n` for `ufw`, `fail2ban`, `sshd -T`, `ss`, and `dmesg` if you use those tools)
## Configuration
```bash
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
```bash
bun install
```
npm package (requires [Bun](https://bun.sh); the MCP Registry points at this artifact):
```bash
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:
```bash
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):
```bash
./scripts/register-cursor-mcp.sh
```
The script writes `~/.cursor/mcp.json` in this shape:
```json
{
"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`:
```bash
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
```bash
./scripts/register-codex-mcp.sh
```
The script writes `~/.codex/config.toml`:
```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:
```bash
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:
```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` as JSON lines (Swarm). |
| `docker_node_ls` | — | `docker node ls` as JSON lines (Swarm). |
| `compose_ps` | `dir?` | `docker compose ps` in `dir` or `VPS_COMPOSE_DIR`. |
| `host_listen` | — | `ss -lntup` (`sudo -n`, otherwise without 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`, and `StrictHostKeyChecking=accept-new`.
- A mutation without `confirm: true` is rejected. `docker_rm` asks for the name twice. Volume prune requires `confirm_volumes: true`.
- For a read-only client, register with `VPS_ALLOW_MUTATIONS=false`.
Lo que la gente pregunta sobre vps-ops-mcp
¿Qué es koller-nexus/vps-ops-mcp?
+
koller-nexus/vps-ops-mcp es mcp servers para el ecosistema de Claude AI. Stdio MCP server that inspects and (with confirmation) mutates one VPS over SSH. Tiene 0 estrellas en GitHub y su última actualización registrada es del 2026-09-22.
¿Cómo se instala vps-ops-mcp?
+
Puedes instalar vps-ops-mcp clonando el repositorio (https://github.com/koller-nexus/vps-ops-mcp) o siguiendo las instrucciones del README en GitHub. ClaudeWave también te ofrece bloques de instalación rápida en esta misma página.
¿Es seguro usar koller-nexus/vps-ops-mcp?
+
Nuestro agente de seguridad ha analizado koller-nexus/vps-ops-mcp y le ha asignado un Trust Score de 87/100 (tier: Trusted). Revisa el desglose completo de comprobaciones superadas y flags en esta página.
¿Quién mantiene koller-nexus/vps-ops-mcp?
+
koller-nexus/vps-ops-mcp es mantenido por koller-nexus. La última actividad registrada en GitHub es del 2026-09-22, con 0 issues abiertos.
¿Hay alternativas a vps-ops-mcp?
+
Sí. En ClaudeWave puedes explorar mcp servers similares en /categories/mcp, ordenados por popularidad o actividad reciente.
Despliega vps-ops-mcp en tu cloud
Lleva este repo a producción en minutos. Cada plataforma genera su propio entorno con variables de entorno editables.
¿Mantienes este repo? Añade un badge a tu README
Pega el badge en tu README de GitHub para mostrar que está auditado por ClaudeWave. Cada badge enlaza de vuelta a esta página y muestra el Trust Score actual.
[](https://claudewave.com/repo/koller-nexus-vps-ops-mcp)<a href="https://claudewave.com/repo/koller-nexus-vps-ops-mcp"><img src="https://claudewave.com/api/badge/koller-nexus-vps-ops-mcp" alt="Featured on ClaudeWave: koller-nexus/vps-ops-mcp" width="320" height="64" /></a>Más MCP Servers
Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.
User-friendly AI Interface (Supports Ollama, OpenAI API, ...)
An open-source AI agent that brings the power of Gemini directly into your terminal.
Real-time global intelligence dashboard. AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface
🕷️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl! Don't be shy, join here: https://discord.gg/EMgGbDceNQ and follow here for daily tips and tricks: https://x.com/Scrapling_dev
The fastest path to AI-powered full stack observability, even for lean teams.