proxmox-mcp
# proxmox-mcp
[](https://github.com/nrohozen/proxmox-mcp/actions/workflows/ci.yml)
[](LICENSE)
[](pyproject.toml)
A small [MCP](https://modelcontextprotocol.io) server that exposes a Proxmox VE
node/cluster as tools any MCP client (Claude Code, Claude Desktop) can call —
so instead of hand-writing `curl` against the Proxmox REST API, the assistant
calls clean tools like `list_guests` or `start_guest`.
**Scope:** read, power control, limited reconfigure (CPU/memory), plus
snapshots and one-shot backups. It can inspect everything,
start/stop/shutdown/reboot guests, adjust a guest's cores/memory, and manage
restore points and vzdump backups. It does **not** create or delete the guests
themselves.
## Tools
| Tool | Effect |
|---|---|
| `list_nodes()` | Nodes with status, CPU, memory (GB), uptime |
| `list_guests(type?)` | All VMs (`qemu`) and containers (`lxc`): vmid, name, node, status, usage |
| `guest_status(node, vmid, type)` | Detailed status of one guest |
| `guest_config(node, vmid, type)` | Full provisioning config: cores, memory (MB), disks/mounts, network |
| `guest_network(node, vmid, type)` | Live interfaces + IP addresses (LXC direct; QEMU needs guest agent) |
| `storage(node?)` | Storage pools with real usage (GB, %) |
| `storage_content(node, storage, content?)` | What's on a storage pool: templates, ISOs, backups, disk images |
| `recent_tasks(node, limit?)` | Recent task log for a node |
| `task_status(node, upid, log_lines?)` | One task's status, exit status, and log tail (by UPID) |
| `start_guest(node, vmid, type)` | Start a stopped guest |
| `shutdown_guest(node, vmid, type)` | Graceful shutdown (preferred) |
| `stop_guest(node, vmid, type)` | Hard stop (pulls the cord) |
| `reboot_guest(node, vmid, type)` | Graceful reboot |
| `set_guest_resources(node, vmid, type, cores?, memory_mb?)` | **Reconfigure** CPU cores and/or memory |
| `list_snapshots(node, vmid, type)` | A guest's snapshots |
| `create_snapshot(node, vmid, type, name, description?)` | Take a snapshot |
| `rollback_snapshot(node, vmid, type, name, confirm?)` | **Irreversible** — roll back to a snapshot (typed-confirm) |
| `delete_snapshot(node, vmid, type, name)` | Delete a snapshot |
| `list_backups(node, storage, vmid?)` | Backup archives on a storage (newest first) |
| `create_backup(node, vmid, storage, mode?, compress?)` | Back up a guest now (vzdump) |
`type` is `"qemu"` (VM) or `"lxc"` (container). Use `list_guests` to find
`vmid`/`node`/`type`.
## Example
Ask your assistant *"which guests use the most memory, and is anything
stopped?"* — it calls `list_guests()` and gets clean, ready-to-reason data:
```json
[
{"vmid": 100, "name": "web", "node": "pve1", "type": "lxc", "status": "running", "cpu_pct": 0.4, "mem_used_gb": 0.21, "mem_max_gb": 1.0, "uptime_hours": 412.6},
{"vmid": 101, "name": "db", "node": "pve1", "type": "qemu", "status": "running", "cpu_pct": 3.1, "mem_used_gb": 6.84, "mem_max_gb": 8.0, "uptime_hours": 412.6},
{"vmid": 102, "name": "backups", "node": "pve2", "type": "lxc", "status": "stopped", "cpu_pct": 0.0, "mem_used_gb": 0.0, "mem_max_gb": 2.0, "uptime_hours": 0.0}
]
```
From there it can `start_guest("pve2", 102, "lxc")` or `set_guest_resources(...)`
— each a single-purpose, confirmable action, not a freeform shell command.
## Setup
**Linux / macOS**
```bash
git clone https://github.com/nrohozen/proxmox-mcp
cd proxmox-mcp
python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt
cp .env.example .env # then edit .env with your real values
```
**Windows (PowerShell)**
```powershell
git clone https://github.com/nrohozen/proxmox-mcp
cd proxmox-mcp
py -3.12 -m venv .venv
.venv\Scripts\python -m pip install -r requirements.txt
copy .env.example .env # then edit .env with your real values
```
> Prefer a package install? `pip install .` exposes a `proxmox-mcp` console
> command; set the `PROXMOX_*` vars in the environment or a `.env` in the
> working directory.
## Configuration (`.env`)
Config lives in a **`.env` file next to `server.py`** (gitignored). `server.py`
loads it automatically, so the MCP client config just launches the server — no
env block required. Copy `.env.example` to `.env` and set:
| Var | Example | Notes |
|---|---|---|
| `PROXMOX_BASE_URL` | `https://proxmox.example.com:8006` | scheme + host (+ optional `:port`) |
| `PROXMOX_TOKEN_ID` | `mcp@pve!proxmox` | API token id (see [Security](#security)) |
| `PROXMOX_TOKEN_SECRET` | `xxxxxxxx-...` | API token secret |
| `PROXMOX_VERIFY_TLS` | `false` | set `false` for a node's self-signed cert |
> Proxmox's API is on `:8006` with a **self-signed** certificate by default, so
> `PROXMOX_VERIFY_TLS=false` is typical on a LAN. For stricter TLS, front the API
> with a reverse proxy holding a trusted cert and point `PROXMOX_BASE_URL` there.
>
> A variable set explicitly in the client's `env` block still overrides `.env`.
### Register with Claude Code (CLI)
With config in `.env`, registration just points at the server — no `-e` flags.
Use absolute paths to the venv's Python and `server.py`.
**Linux / macOS**
```bash
claude mcp add proxmox -s user -- \
/path/to/proxmox-mcp/.venv/bin/python \
/path/to/proxmox-mcp/server.py
```
**Windows (PowerShell)**
```powershell
claude mcp add proxmox -s user `
-- C:\path\to\proxmox-mcp\.venv\Scripts\python.exe `
C:\path\to\proxmox-mcp\server.py
```
`-s user` makes it available in every project. Verify with `claude mcp list`.
Tools load in a **new** Claude Code session.
### Register with Claude Desktop
Copy `claude_desktop_config.example.json` to
`%APPDATA%\Claude\claude_desktop_config.json` (it only points at `server.py`;
config comes from `.env`), then fully restart Claude Desktop. The Proxmox tools
appear under the 🔌 / tools menu.
## Security
**Use a dedicated, least-privilege token — never `root@pam`.** This server needs
read, power, CPU/memory reconfigure, and snapshot/backup privileges. Create a
scoped token once, on a Proxmox node:
```bash
# a role with exactly the privileges this server uses
pveum role add ProxmoxMCP --privs "VM.Audit,VM.PowerMgmt,VM.Config.CPU,VM.Config.Memory,VM.Snapshot,VM.Snapshot.Rollback,VM.Backup,Sys.Audit,Datastore.Audit,Datastore.AllocateSpace"
# a dedicated, non-root user + API token
pveum user add mcp@pve
pveum aclmod / -user mcp@pve -role ProxmoxMCP
pveum user token add mcp@pve proxmox --privsep 0
# -> copy the printed token id (mcp@pve!proxmox) and value into .env
```
A token scoped this way can power-manage, resize, snapshot, and back up guests
but **cannot** open a host shell, change node/network/firewall config, or create
users/tokens — so a leaked token can't own the hypervisor. (`Datastore.AllocateSpace`
is needed only for writing backups; drop it to make the token read-plus-power only.)
- The token secret lives in `.env` in plaintext (gitignored) — keep it private.
`.env.example` (no secret) is the committed template.
- MCP clients prompt before running a tool; that confirmation is the human
guardrail on the power-control tools.
TDQS
Scored across 20 tools
Each tool targets a distinct operation: backup, snapshot lifecycle, guest configuration reading and modification, status queries, resource listing, power management, and task tracking. No two tools overlap in purpose.
All tool names follow a consistent verb_noun pattern using snake_case (e.g., create_backup, list_guests, rollback_snapshot). No mixing of styles or vague verbs.
With 20 tools, the server covers a broad range of Proxmox administration tasks. While it's slightly on the larger side, each tool serves a clear purpose and the count is still manageable for the domain.
The tool set lacks essential lifecycle operations: there is no tool to create or delete a guest, clone, or migrate. While backup, snapshot, and power control are covered, these missing functions create dead ends for common workflows.