Skip to main content
Glama
README.md
# proxmox-mcp

An MCP server that lets an AI assistant fully navigate a single-node Proxmox VE
host — inspect, control, provision, and destroy — **without exposing any new
network service**.

The server runs *on the Proxmox node itself*, launched over SSH and speaking
stdio. Nothing listens. There is no port, no API token, no TLS, and no daemon.
The node's existing sshd is the only entry point, and your existing SSH key is
the only credential.

```
Claude Code (your PC)  ──ssh──►  proxmox-mcp (on the node)  ──►  pvesh / qm / pct
```

## Install on the node

From the Proxmox host, as root:

```bash
apt-get install -y git          # not present on a stock PVE install
git clone https://github.com/ssan9876/proxmox-mcp /opt/proxmox-mcp-src
/opt/proxmox-mcp-src/install.sh
```

The installer creates a venv at `/opt/proxmox-mcp`, installs the package, drops
a launcher at `/opt/proxmox-mcp/bin/proxmox-mcp`, and writes a starter config to
`/etc/proxmox-mcp/config.yaml` if none exists.

Verify it:

```bash
/opt/proxmox-mcp/bin/proxmox-mcp --dry-run --log-level DEBUG
```

It should log a startup line and then wait on stdin. Ctrl-C to exit.

## Connect from your PC

You need password-less SSH to the node first:

```bash
ssh-copy-id root@pve
```

Then add the server to your MCP client. For Claude Code:

```bash
claude mcp add proxmox -- ssh -o BatchMode=yes root@pve /opt/proxmox-mcp/bin/proxmox-mcp --mode safe
```

Or by hand, in your MCP config:

```json
{
  "mcpServers": {
    "proxmox": {
      "command": "ssh",
      "args": [
        "-o", "BatchMode=yes",
        "root@pve",
        "/opt/proxmox-mcp/bin/proxmox-mcp", "--mode", "safe"
      ]
    }
  }
}
```

Replace `pve` with your node's hostname or IP.

## Modes

The mode decides which tools are registered. Tools outside the active mode are
never exposed — the model cannot see them or call them.

| Mode       | What it can do                                                        |
|------------|-----------------------------------------------------------------------|
| `readonly` | Inspect everything. Zero mutation.                                    |
| `safe`     | + start/stop/reboot, create VMs and containers, snapshot, back up.    |
| `full`     | + destroy, restore, roll back, shrink disks, run commands in guests, root shell on the host. |

`safe` is the default. Change it per-connection with `--mode` in the SSH args,
so the power a session has is visible in your client config. Keeping a second
entry named `proxmox-full` and only enabling it when you need it is a reasonable
setup.

## Guardrails

Four checks sit in front of anything that can lose data:

1. **Protected denylist** — VMIDs, names, and pools listed in the config are
   refused by every destructive tool, *including in `full` mode*. Disruptive
   lifecycle actions (stop, reboot, shutdown) honour it too, and a disk whose
   volume ID belongs to a protected VMID is protected with it.
2. **Mode gate** — the tool must be registered by the active mode.
3. **Confirm token** — destructive tools require a `confirm` argument holding
   the target's exact token: `vm:104`, `ct:110/snap:nightly`,
   `volume:local-lvm:vm-104-disk-0`, `node:pve`, `api:DELETE:/nodes/pve/...`.
   A mismatch returns the target's real details — name, status, disks, uptime —
   so what was about to be destroyed is visible in the transcript before any
   retry.
4. **Protective snapshot or backup** — `rollback_snapshot` and `shrink_disk`
   snapshot first. `destroy_guest` and `restore_backup` cannot be protected by a
   snapshot, so they run `vzdump` first and **abort if the backup fails**.

`node_shell` is deliberately not gated on every call — requiring a constant
token to run `ls` would just train the model to paste it. Instead it demands the
node token only when the command matches a destructive pattern (`rm -rf`,
`mkfs`, `dd`, `zfs destroy`, `lvremove`, `reboot`, `apt purge`, and similar).

### What the confirm token is and is not

The mismatch error includes the expected token, so a model can read it and
retry. That is deliberate: a token the caller cannot learn makes the tool
unusable. What it buys is a blocked first attempt on a hallucinated or stale
VMID, a forced second round-trip, and the target's details in front of you. It
is **not** an authorization boundary. The denylist and the mode gate are.

## Configuration

`/etc/proxmox-mcp/config.yaml`:

```yaml
mode: safe              # readonly | safe | full
node: null              # auto-detected when null
auto_snapshot: true     # protective snapshots before lossy operations
task_poll_seconds: 10   # how long tools wait on a task before returning a UPID
command_timeout: 60
shell_timeout: 600      # ceiling for node_shell
max_output_bytes: 100000
protected:
  vmids: [100]
  names: ["opnsense", "truenas"]
  pools: ["production"]
```

CLI flags `--mode`, `--config`, `--node`, and `--dry-run` override the file.

## Tools

**Inspect** (all modes) — `resource_summary`, `node_status`, `list_guests`,
`guest_config`, `guest_status`, `list_storage`, `storage_content`,
`list_networks`, `list_tasks`, `task_log`, `list_snapshots`, `list_backups`,
`node_services`, `read_syslog`, `list_access`, `node_updates`

**Lifecycle** (`safe`+) — `start_guest`, `shutdown_guest`, `stop_guest`,
`reboot_guest`, `suspend_guest`, `resume_guest`, `wait_for_task`

**Provision** (`safe`+) — `create_vm`, `create_container`, `set_guest_config`,
`grow_disk`, `clone_guest`, `create_snapshot`, `create_backup`,
`convert_to_template`, `download_to_storage`

**Destructive** (`full`) — `destroy_guest`, `delete_snapshot`, `delete_volume`,
`rollback_snapshot`, `restore_backup`, `shrink_disk`, `guest_exec`,
`node_shell`, `node_reboot`

**Passthrough** — `pve_get` and `pve_discover` in all modes; `pve_post`,
`pve_put`, `pve_delete` in `full`. These reach every endpoint the Proxmox web UI
uses, so anything the curated tools miss is still reachable.

`resource_summary` is the intended starting point: node health, every guest, and
every storage in one call.

## Development

```bash
git clone https://github.com/ssan9876/proxmox-mcp && cd proxmox-mcp
uv venv && uv pip install -e ".[dev]"
uv run pytest
```

Tests inject a fake command runner, so the whole suite runs on any machine with
no Proxmox host involved. Tools are verified by asserting the exact `pvesh`
argv they build and feeding recorded JSON back.

## Notes

- Transport is stdio: **stdout belongs to the MCP protocol.** All logging goes
  to stderr. A stray `print()` corrupts the session.
- The server holds root on your hypervisor. In `full` mode it can delete VMs and
  run arbitrary commands. Run it in `safe` unless you are actively doing
  something that needs more.
- Tool results are capped at `max_output_bytes` so an unbounded `journalctl` or
  `node_shell` cannot exhaust the context window.

TDQS

A3.5/5.0

Scored across 34 tools

Disambiguation4/5

While the tool set is large, each tool targets a distinct resource or action. Potential overlaps like list_backups vs storage_content with content='backup' are clarified by descriptions. A few tools such as resource_summary aggregate data from multiple list tools, but this is an intentional convenience rather than a source of ambiguity.

Naming Consistency4/5

Most tools follow a verb_noun pattern (list_guests, create_vm, start_guest), but some use noun-based names (guest_config, node_status) and the pve_* pair deviates. The naming is still predictable and consistent within categories, so the mix is a minor deviation.

Tool Count2/5

At 34 tools, this exceeds the threshold for a well-scoped server. While each tool has a clear purpose, the count is high enough to make it heavy, and many tools could be consolidated (e.g., lifecycle actions). The availability of pve_get/pve_discover adds redundancy.

Completeness2/5

There are significant gaps in the lifecycle surface: no delete/destroy for guests, no snapshot deletion or rollback, no backup restore, and no network/access management beyond listing. The generic pve_get tool is read-only, so these missing operations cannot be performed at all, causing dead ends.

Maintenance

ActivitySlowing
ResponsivenessNo issues