Skip to main content
Glama
VPS Docker MCP (Advanced)

VPS Docker MCP (Advanced)

by mordanov

VPS Docker MCP — Advanced

MCP server for Claude Code that provides safe VPS diagnostics and Docker/Docker Compose management over SSH.

Architecture

Claude Code
    |
    | MCP / stdio
    v
Local Python MCP server
    |
    | SSH / AsyncSSH
    v
VPS
    |
    +-- systemd
    +-- Docker
    +-- Docker Compose

The MCP server runs locally. Nothing needs to listen on the VPS.

Module layout

src/vps_docker_mcp/
├── server.py          # MCP instance, wires modules together
├── ssh.py             # SSH config, run_ssh/run_many helpers
├── docker.py          # Docker and Docker Compose tools
├── infrastructure.py  # VPS diagnostics tools
├── database.py        # PostgreSQL backup and stats tools
└── github.py          # GitHub Actions tools

Related MCP server: VPS-Guardian-MCP

Security model

This project intentionally does not expose arbitrary shell execution.

There is no:

ssh(command)
execute(command)
bash(command)

Instead, every operation is a predefined MCP tool.

Arguments such as container and service names are validated with a restrictive allow-list pattern and shell-quoted before being inserted into commands.

SSH host key checking is enabled through VPS_KNOWN_HOSTS.

Use a dedicated non-root SSH account, e.g. deploy, with Docker access.

Credential isolation

SSH key — The VPS SSH key path lives only in .env and is loaded inside ssh.py at connection time. Agents never see the key. Enforced via Claude Code's permissions.deny rules:

"deny": ["Bash(ssh *)", "Bash(scp *)", "Bash(sftp *)", "Bash(rsync *)"]

GitHub token — The gh CLI is authenticated on the VPS. All GitHub operations run over SSH through this MCP server. Agents cannot call gh directly. Enforced via:

"deny": ["Bash(gh *)"]

Add both deny rules to ~/.claude/settings.json to lock down access.

Requirements

  • Python 3.11+

  • uv

  • SSH access

  • Docker on VPS

  • Docker Compose v2 if Compose tools are used

  • deploy user with permission to run Docker

  • gh CLI installed and authenticated on the VPS if GitHub Actions tools are used

Installation

uv sync

Configure:

cp .env.example .env

Example:

VPS_HOST=YOUR_VPS_IP
VPS_PORT=22
VPS_USER=deploy
VPS_SSH_KEY=~/.ssh/vps_mcp
VPS_KNOWN_HOSTS=~/.ssh/known_hosts
DOCKER_COMPOSE_DIR=/opt/news-bot
GITHUB_REPO=acme/api
MAX_OUTPUT_CHARS=20000

Test SSH independently:

ssh -i ~/.ssh/vps_mcp deploy@YOUR_VPS_IP docker ps

Test MCP:

uv run vps-docker-mcp

It will wait for MCP stdio input. That is expected.

Claude Code configuration

Copy .mcp.json.example to the project-level .mcp.json used by Claude Code and replace the absolute path.

Example:

{
  "mcpServers": {
    "vps-docker": {
      "type": "stdio",
      "command": "uv",
      "args": [
        "--directory",
        "/home/me/projects/vps-docker-mcp-advanced",
        "run",
        "vps-docker-mcp"
      ]
    }
  }
}

Then start/restart Claude Code and inspect MCP status with:

/mcp

Depending on the Claude Code version, MCP servers can also be managed with the Claude Code CLI.

Tool groups

VPS diagnostics

  • system_info

  • disk_usage

  • top_processes

  • network_info

  • systemd_failed

  • journal_errors

  • diagnose_vps

Docker read-only

  • docker_ps

  • docker_logs

  • docker_inspect

  • docker_stats

  • docker_health

  • docker_images

  • docker_volumes

  • docker_networks

  • docker_disk_usage

Docker mutations

  • docker_restart

  • docker_start

  • docker_stop

Docker Compose

Read-only:

  • docker_compose_ps

  • docker_compose_config

  • docker_compose_logs

Mutating:

  • docker_compose_restart

  • docker_compose_pull

  • docker_compose_up

Database (PostgreSQL)

Read-only:

  • postgres_stats — show database sizes, active queries, and optionally top tables by size for a specific database

  • postgres_list_backups — list backup files on the server, newest first

Mutating:

  • postgres_backup_database — dump a single database to a custom-format .dump file; restore with pg_restore

  • postgres_backup_all — dump all databases via pg_dumpall, compressed with gzip; restore with gunzip | psql

  • postgres_download_backup — download a backup file from the server to a local directory via SFTP

All tools default to the container, user, and backup directory configured in .env (POSTGRES_CONTAINER, POSTGRES_USER, POSTGRES_BACKUP_DIR).

GitHub Actions

Read-only:

  • gh_workflow_list — list all workflows in a repo

  • gh_run_list — list recent runs, filterable by workflow, status, and limit

  • gh_run_view — show summary and job status for a specific run

  • gh_run_logs — fetch full or failed-only logs for a run

  • gh_job_logs — fetch logs for a single job within a run

  • gh_diagnose_last_run — fetch the most recent run (any status) and show its failed-step logs; answers "why did the last run fail?" in one call

  • gh_diagnose_failure — find the latest failed run and show its error logs in one call

Mutating:

  • gh_run_cancel — cancel an in-progress run

  • gh_run_rerun — re-run a run, optionally failed jobs only

All tools accept a repo parameter (owner/repo). If omitted, they fall back to the GITHUB_REPO env var.

Restricted diagnostics

diagnostic_command only permits a fixed list:

  • pwd

  • whoami

  • date

  • df

  • free

  • uptime

  • docker_version

  • docker_info

It does not accept arbitrary shell syntax.

Example Claude Code requests

Check the VPS health.

Claude can use:

diagnose_vps

For a broken container:

Find out why news-bot is unhealthy.

A useful diagnostic sequence is:

docker_health
docker_ps
docker_logs
docker_stats
docker_inspect

For a general resource problem:

Check whether the VPS is running out of RAM or disk space.

Claude can inspect:

system_info
disk_usage
top_processes
docker_stats
docker_disk_usage

Then:

Restart news-bot if the diagnosis indicates that a restart is appropriate.

The restart operation is a separate mutating tool.

For a broken CI build:

Why did the last build fail?

Claude can call:

gh_diagnose_failure

This finds the most recent failed run and returns the failed-step logs in one call. To scope it to a specific workflow:

gh_diagnose_failure(workflow="deploy.yml")

To browse recent runs and then dig into a specific one:

gh_run_list(status="failure", limit=5)
gh_run_logs(run_id="<id>", failed_only=True)

Development

Setup

uv sync
uv pip install -e ".[dev]"

Tests

pytest

77 tests cover all modules: utils, github, docker, infrastructure, database, and ssh. Tests use a FakeMCP helper that captures registered tool closures without starting a real MCP server. asyncssh.connect is mocked so SSH tests run without a real host.

Lint and type check

ruff check src tests
ruff format src tests
mypy src/vps_docker_mcp

Pre-commit

pre-commit install

Runs ruff check --fix and ruff format on every commit.

CI

GitHub Actions runs on every push to main and on pull requests: checkout → install → ruff → mypy → pytest.

Important production recommendation

Keep Claude Code's own permission/approval mechanism enabled for mutating operations.

The MCP server intentionally labels mutating tools in their descriptions, but MCP itself should not be treated as an authorization boundary.

For a production VPS, do not add arbitrary shell execution unless you deliberately accept the risk.

Future improvements

Good next additions would be:

  • application-specific health checks

  • Docker container restart-loop detection

  • OOM detection

  • disk growth detection

  • systemd service restart tools

  • configurable allow-list of Compose projects

  • audit logging

  • command execution timeouts per tool

  • separate read-only and write SSH credentials

  • GitHub Actions workflow dispatch (trigger a run)

Available Tools

26 tools
diagnose_vpsA

Run a compact VPS health check covering resources, disk, systemd and Docker.

Inputs: no named parameters. Returns: result

diagnostic_commandB

Run one of a small predefined set of read-only diagnostic commands.

Inputs: command (required). Returns: result

disk_usageB

Show filesystem space and inode usage.

Inputs: no named parameters. Returns: result

docker_compose_configB

Render and validate the Docker Compose configuration.

Inputs: no named parameters. Returns: result

docker_compose_logsB

Show Docker Compose logs, optionally for one service.

Inputs: tail, service. Returns: result

docker_compose_psC

Show Docker Compose services.

Inputs: no named parameters. Returns: result

docker_compose_pullB

Pull Docker Compose images. This changes local Docker image state.

Inputs: no named parameters. Returns: result

docker_compose_restartB

Restart Docker Compose services. This changes server state.

Inputs: service. Returns: result

docker_compose_upA

Start/update the Docker Compose stack with detached mode. This changes server state.

Inputs: no named parameters. Returns: result

docker_disk_usageB

Show Docker disk usage.

Inputs: no named parameters. Returns: result

docker_healthB

Show container state, restart count and health status.

Inputs: container (required). Returns: result

docker_imagesB

List Docker images.

Inputs: no named parameters. Returns: result

docker_inspectC

Inspect a Docker container.

Inputs: container (required). Returns: result

docker_logsC

Show recent logs from a Docker container.

Inputs: container (required), tail. Returns: result

docker_networksB

List Docker networks.

Inputs: no named parameters. Returns: result

docker_psA

List Docker containers. Set all_containers=true to include stopped containers.

Inputs: all_containers. Returns: result

docker_restartB

Restart a Docker container. This changes server state.

Inputs: container (required). Returns: result

docker_startB

Start a Docker container. This changes server state.

Inputs: container (required). Returns: result

docker_statsA

Show current CPU, memory, network and block I/O usage of containers.

Inputs: no named parameters. Returns: result

docker_stopC

Stop a Docker container. This changes server state.

Inputs: container (required). Returns: result

docker_volumesB

List Docker volumes.

Inputs: no named parameters. Returns: result

journal_errorsC

Show recent system journal errors.

Inputs: hours, lines. Returns: result

network_infoB

Show network interfaces, routes and listening TCP/UDP ports.

Inputs: no named parameters. Returns: result

systemd_failedA

Show failed systemd services.

Inputs: no named parameters. Returns: result

system_infoA

Show VPS uptime, OS, CPU, memory, swap, load and kernel information.

Inputs: no named parameters. Returns: result

top_processesC

Show processes consuming the most CPU and memory.

Inputs: limit. Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.1/5.0

    Scored across 26 tools

    Disambiguation4/5

    Most tools have clearly distinct purposes (e.g., docker_ps vs docker_inspect, system_info vs disk_usage). However, diagnose_vps overlaps with several individual system tools, and diagnostic_command is vague, creating some ambiguity.

    Naming Consistency4/5

    Tools follow snake_case with clear prefixes (docker_, docker_compose_, system_, etc.). Docker tools use verb_noun pattern, but system tools are mostly noun_noun (e.g., system_info, disk_usage). This mix is not fully consistent but still readable.

    Tool Count4/5

    26 tools is on the higher side but appropriate for the 'Advanced' scope covering Docker, Docker Compose, and VPS diagnostics. The set is well-scoped, though a few tools could be merged (e.g., diagnose_vps and individual checks).

    Completeness3/5

    The tool surface covers monitoring and basic state changes for Docker and VPS, but lacks container creation/removal, Docker Compose down, and deeper system management. The diagnostic_command is undefined. Notable gaps exist for a truly 'Advanced' server.

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      C
      maintenance
      Enables MCP clients to inspect and manage a Dokku host over SSH with structured tools, read-only defaults, and explicit guardrails for safe operations.
      10
      MIT
    • F
      license
      Not graded
      quality
      B
      maintenance
      Enables controlled diagnostics and limited actions on a VPS through safe, auditable tools instead of an unrestricted root terminal, including read-only checks, basic Docker status, allowed-path file reading/searching, and YAML/JSON validation.
      -