pve-mcp
# pve-mcp
An MCP server for managing Proxmox VE (single node or cluster): query resources,
manage VM/CT lifecycle, snapshots, backups, clones — from Claude Code or any MCP
host.
- Works with both **QEMU VMs and LXC containers**, on single nodes and clusters.
- Tools that target one guest only need the **`vmid`** — the node and guest type
are resolved automatically.
- **Safety first**: an optional read-only mode, `confirm=true` required for
destructive operations, and guest command execution disabled by default.
## Requirements
- Python >= 3.11 (a pinned 3.12 is used via `uv`)
- A Proxmox VE 7/8 host with an API token (see below)
## Installation
```bash
git clone <this-repo> pve-mcp
cd pve-mcp
uv sync # installs runtime + dev dependencies into .venv
uv run pytest # optional: run the test suite
```
Or run it directly with `uvx` (no install):
```bash
uvx --from /path/to/pve-mcp pve-mcp
```
The console entry point is `pve-mcp` (stdio transport).
## Creating a PVE API token
Authentication uses API tokens only (`Authorization: PVEAPIToken=<id>=<secret>`),
never username/password tickets. Create a dedicated user and token on the PVE
host (as root):
```bash
# 1. Dedicated user
pveum user add mcp@pve --comment "MCP server"
# 2a. Read-only usage: PVEAuditor on the whole tree
pveum acl modify / --users mcp@pve --roles PVEAuditor
# 2b. Management usage: PVEVMAdmin (VM/CT lifecycle, snapshots, backups)
pveum acl modify / --users mcp@pve --roles PVEVMAdmin
# For backup/restore you may also need PVEDatastoreUser on the backup storage:
pveum acl modify /storage/<backup-storage> --users mcp@pve --roles PVEDatastoreUser
# 3. Token
pveum user token add mcp@pve mcp --privsep 1
```
Note the **`privsep`** setting: with `--privsep 1` (recommended) the token has
its *own* ACLs — grant the roles above to the token too
(`pveum acl modify / --tokens 'mcp@pve!mcp' --roles PVEVMAdmin`), or the token
ends up with no permissions. With `--privsep 0` the token inherits all
permissions of the user.
The command prints the token secret **once** — store it safely.
## Configuration (environment variables)
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `PVE_HOST` | yes | — | PVE API URL, e.g. `https://192.168.1.10:8006` |
| `PVE_TOKEN_ID` | yes | — | Token ID: `user@realm!tokenname`, e.g. `mcp@pve!mcp` |
| `PVE_TOKEN_SECRET` | yes | — | Token secret (UUID) |
| `PVE_VERIFY_SSL` | no | `true` | Set `false` for self-signed certificates (home labs) |
| `PVE_TIMEOUT` | no | `30` | HTTP timeout in seconds |
| `PVE_MCP_READ_ONLY` | no | `false` | `true` registers only the 12 read-only tools |
| `PVE_MCP_ENABLE_EXEC` | no | `false` | `true` registers `pve_vm_exec` (guest agent commands) |
| `PVE_MCP_TASK_WAIT` | no | `30` | Seconds to wait for PVE tasks; on timeout the UPID is returned |
A `.env` file in the working directory is loaded automatically; see
`.env.example`.
## Registering with Claude Code
```bash
claude mcp add pve -e PVE_HOST=https://192.168.1.10:8006 \
-e PVE_TOKEN_ID='root@pam!mcp' -e PVE_TOKEN_SECRET=xxx \
-e PVE_VERIFY_SSL=false -- uvx --from /path/to/pve-mcp pve-mcp
```
Add `-e PVE_MCP_READ_ONLY=true` for a safe, audit-only setup.
## Tools
### Read-only (12, always registered)
| Tool | Purpose |
|------|---------|
| `pve_cluster_status` | Cluster/node health, quorum (works on single nodes too) |
| `pve_list_nodes` | Nodes with CPU/memory/disk usage and online status |
| `pve_node_status` | One node in detail (load, kernel, PVE version) |
| `pve_list_vms` | All VMs + CTs; filter by node / type / status |
| `pve_vm_status` | Live status of one guest (CPU/mem, agent, uptime) |
| `pve_vm_config` | Full config of one guest (cores, memory, disks, NICs) |
| `pve_list_storage` | Storage usage (cluster-wide or per node) |
| `pve_storage_content` | Contents of a storage (iso/backup/vztmpl/images) |
| `pve_list_backups` | Backups across storages; filter by vmid / storage |
| `pve_list_snapshots` | Snapshot tree of one guest |
| `pve_list_tasks` | Recent tasks (all nodes or one), errors-only option |
| `pve_task_status` | One task's status; failed tasks include a log tail |
### Write (11, skipped when `PVE_MCP_READ_ONLY=true`)
| Tool | Purpose | Destructive |
|------|---------|-------------|
| `pve_vm_power` | start / shutdown / stop / reboot / suspend / resume | no (`stop` is a hard power-off — prefer `shutdown`) |
| `pve_vm_migrate` | Move a guest to another node (QEMU live / CT restart) | no |
| `pve_vm_set_config` | Change config keys (cores, memory, onboot, ...) | no (most changes need a guest restart) |
| `pve_vm_resize_disk` | Grow a disk (`+10G` or absolute); shrinking unsupported | no |
| `pve_vm_clone` | Clone to a new vmid (full or linked) | no |
| `pve_snapshot_create` | Snapshot a guest (`vmstate` = include RAM, QEMU only) | no |
| `pve_backup_create` | vzdump backup (snapshot/suspend/stop mode) | no |
| `pve_snapshot_rollback` | Roll back to a snapshot | **yes** — requires `confirm=true` |
| `pve_snapshot_delete` | Delete a snapshot | **yes** — requires `confirm=true` |
| `pve_vm_delete` | Delete a guest and all its disks | **yes** — requires `confirm=true` |
| `pve_backup_restore` | Restore an archive to a vmid | new vmid: no; overwrite: **yes** — requires `force=true` + `confirm=true` |
### Exec (1, requires `PVE_MCP_ENABLE_EXEC=true`)
| Tool | Purpose |
|------|---------|
| `pve_vm_exec` | Run a shell command in a QEMU VM via the guest agent (`/bin/sh -c`); never registered in read-only mode |
## Security model
1. **Read-only mode** — with `PVE_MCP_READ_ONLY=true` the write tools are *not
registered at all* (they don't exist for the model), leaving exactly the 12
read-only tools.
2. **Tool annotations** — read-only tools carry `readOnlyHint`, the four
destructive tools carry `destructiveHint`, so MCP hosts (e.g. Claude Code)
can prompt appropriately.
3. **`confirm` parameter** — destructive tools refuse to run without
`confirm=true` and explain the consequences first. Nothing is sent to PVE
until confirmed.
4. **Exec opt-in** — `pve_vm_exec` (arbitrary command execution in guests) is
only registered with `PVE_MCP_ENABLE_EXEC=true`, and never in read-only mode.
Combine with a least-privilege token: `PVEAuditor` for read-only setups,
`PVEVMAdmin` for management.
## Behavior notes
- **Async tasks**: write operations wait up to `PVE_MCP_TASK_WAIT` seconds for
the PVE task to finish. Long operations (backups, clones, migrations) return
`{"status": "running", "upid": ...}` — follow up with `pve_task_status`.
- **Output**: responses are JSON with a per-tool field whitelist; byte values
keep the raw number and gain a `*_human` companion (e.g. `"31.25 GiB"`).
- **Errors**: HTTP/auth/SSL errors are translated into actionable messages
(e.g. 403 suggests the missing role; unknown vmids list the existing ones).
## Development
```bash
uv sync
uv run pytest # unit tests (PVE API mocked with respx)
```
See `DESIGN.md` for the full specification.
TDQS
Scored across 23 tools
Each tool targets a distinct resource and action, with clear boundaries between backup, snapshot, VM, node, and storage operations. Even tools with similar names like pve_vm_config and pve_vm_set_config are clearly differentiated as get vs set.
All tools follow the consistent pattern pve_<resource>_<action>, using snake_case. Actions include create, delete, list, set, get, etc., and resources are always in the plural or singular as appropriate. No mixing of styles.
23 tools is appropriate for a Proxmox VE management server, covering essential operations on VMs, containers, snapshots, backups, nodes, storage, and tasks. Each tool serves a distinct purpose without redundancy.
The tool surface covers most lifecycle operations for VMs/containers, snapshots, and backups. A minor gap is the lack of a tool to create a new VM from scratch (e.g., from an ISO), but cloning and configuration tools cover common provisioning workflows.