VPS Ops

io.github.koller-nexusPublished by GitHub account koller-nexusv0.1.0Updated Oct 24, 2026

Stdio MCP server that inspects and (with confirmation) mutates one VPS over SSH.

Overview

AI-generated overview

Lets an assistant inspect and, with confirmation, change one VPS over SSH: host health, Docker, Compose, Swarm, firewall and logs.

What you can do
A stdio MCP server that runs fixed SSH commands against a single VPS. Read-only tools cover host health (ping, resources, journal, failed units, top, dmesg, listening ports, firewall, fail2ban, SSH hardening) and Docker/Compose/Swarm inspection (ps, inspect, logs, stats, service and node lists, compose ps). Mutation tools restart, stop, start or remove containers, run compose up, restart and pull-up, and prune Docker. Every call returns exit_code, stdout, stderr, duration_ms and truncated.
When to use it
Useful when you want an assistant to check or operate one remote server without giving it a free-form shell: reading logs, container state, disk and memory, firewall status, or restarting a service during troubleshooting. Register with mutations disabled if you only want read-only access.
Requirements
Local process started by the client (Cursor or Codex), not run by hand. Needs Bun, Python 3 for the registration scripts, an OpenSSH client on PATH, and a readable SSH private key. Configure VPS_SSH_KEY_PATH (required), VPS_HOST, VPS_USER, VPS_PORT, VPS_COMPOSE_DIR, VPS_COMMAND_TIMEOUT_MS, VPS_LOG_MAX_BYTES, VPS_ALLOW_MUTATIONS. Remote host needs Docker, plus passwordless sudo for some firewall, fail2ban, sshd, ss and dmesg tools.
Before you install
The server holds SSH access to a real server: VPS_SSH_KEY_PATH points at a private key, and VPS_SSH_KEY_PASSPHRASE is an optional passphrase (ssh-agent is preferred). Mutation tools change live state: restart, stop, start, remove containers, compose up/restart/pull-up, and Docker prune, which can delete unused images, networks and, with confirm_volumes, volumes. Mutations need confirm: true and can be disabled entirely with VPS_ALLOW_MUTATIONS=false. Remote commands are fixed and arguments are…

Installation

In SourceWeft

  1. Open VPS Ops in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.

Other MCP clients

Follow the launch instructions in the repository.

README

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 (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.

VariableRequiredDefaultPurpose
VPS_SSH_KEY_PATHyes—Absolute path to the private key. The process refuses to start if the file is missing or unreadable.
VPS_HOSTnovps.example.invalidSSH host (placeholder; set your own host).
VPS_USERnoubuntuSSH user.
VPS_PORTno22SSH port.
VPS_COMPOSE_DIRno—Absolute Compose directory on the VPS. Without it, Compose tools require the dir argument.
VPS_COMMAND_TIMEOUT_MSno30000Remote command timeout. Expiry returns exit_code 124.
VPS_LOG_MAX_BYTESno200000Cap for stdout/stderr. Overflow is cut and truncated is true.
VPS_ALLOW_MUTATIONSnotruefalse, 0, no, or off disables every mutation.
VPS_SSH_KEY_PASSPHRASEno—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; 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 -asource .envset +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:

VariableDefaultPurpose
MCP_PROJECT_DIRthis repository rootSource of the src/index.ts path.
CURSOR_MCP_JSON~/.cursor/mcp.jsonCursor file to update.
CODEX_CONFIG~/.codex/config.tomlCodex 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

ToolArgumentsWhat it does
vps_ping—uname -a, uptime, hostname.
vps_resources—df -h, free -h, load average.
vps_journalunit, 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_inspectnamedocker inspect.
docker_logsname, 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_psdir?, file?docker compose ps em dir ou VPS_COMPOSE_DIR (file, ex. stack.yml, vira -f).
compose_config_varsdir?, 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_dmesgn? (1–200, default 100)dmesg -T + tail (sudo -n, otherwise without sudo).
host_firewall—ufw status verbose (sudo -n, otherwise without sudo).
host_fail2banjail?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.

ToolExtra argumentsRemote command
docker_restartnamedocker restart
docker_stopnamedocker stop
docker_startnamedocker start
compose_updir?, services?docker compose up -d
compose_restartdir?, services?docker compose restart
compose_pull_updir?, services?docker compose pull then up -d
docker_rmname, force_namedocker rm -f. force_name must equal name.
disk_cleanup_dockerconfirm_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.

Source: README.md at commit 1f4735f

Tools

0
SourceWeft has not started this package in its sandbox yet, so its tools are not known.

Version history

1
  1. v0.1.0LatestSep 23, 2026

Score

50% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 days25 pts

    Keep the endpoint answering the MCP handshake; SourceWeft probes it every 30 days.

  • Auth declared10 pts

    Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.

  • Tools listed15 pts

    Answer tools/list without a credential, or ship a package that starts in a sandbox without one.

  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts