Token-efficient MCP server for Proxmox VE (guests, snapshots, backups, storage, LVM/ZFS, guest/host exec, dry-run + tamper-evident audit)
Project description
Proxmox MCP Server
A local MCP (Model Context Protocol) server that lets Claude Desktop (or any MCP-compatible client) manage a Proxmox VE cluster through its REST API using token authentication.
Tested with Proxmox VE 9.1.9 on a single-node setup. Multi-node clusters
should work โ every tool accepts a node parameter.
๐น๐ท Tรผrkรงe README iรงin: README.tr.md
Features
The package exposes 52 tools, grouped by category below. Read-only
tools are safe to call automatically; every state-changing tool requires
confirm=true, and tools that destroy persistent data additionally
require i_understand_data_loss=true (see Built-in safety).
Token-efficiency features (v1.0):
proxmox_health_overviewanswers "how is the server?" in one call.- List filters โ e.g.
proxmox_list_vms(status='running', node=...),proxmox_list_backups(vmid=...)โ return only what you asked for. fieldsprojection โ withresponse_format='json', passfields=['vmid','name','status']to strip every other key.wait_secondson task-launching tools (power, snapshot, backup, restore, clone, move-disk, LVM/ZFS provisioning): the tool polls the Proxmox task and returns the final result inline, saving a follow-up status call.- JSON output is compact and hard-capped; oversized lists are truncated
at the item level and stay valid JSON (
{"_truncated_items": N}marker).
Overview & nodes (read-only)
| Tool | Description |
|---|---|
proxmox_health_overview |
One-call summary: node load, guest states, storage fill levels, ZFS pool health |
proxmox_list_nodes |
List all nodes in the cluster with status, uptime, CPU and memory usage |
proxmox_get_node_status |
Detailed status for one node: CPU model, kernel, load average, disk, swap |
VMs & containers
| Tool | Description |
|---|---|
proxmox_list_vms |
List VMs/CTs; filters: node, vmid, status, guest_type (read-only) |
proxmox_get_vm_status |
Detailed runtime metrics for a single VM/CT (read-only) |
proxmox_vm_power |
Power action: action='start' | 'shutdown' | 'stop' | 'reboot' (guest type auto-detected) |
proxmox_resize_vm |
Change RAM (memory_mb) and/or CPU cores of a VM/CT |
Guest creation (from scratch)
| Tool | Description |
|---|---|
proxmox_create_vm |
Create a QEMU VM: core hardware (cores/sockets/memory/ostype), one virtio-scsi boot disk, one virtio NIC, optional install ISO (boots from the CD first). Requires confirm=true; dry_run=true previews the exact payload without creating. |
proxmox_create_container |
Create an LXC container from a template: rootfs, one NIC (dhcp or static CIDR), root access via password and/or ssh_public_key, unprivileged by default, optional nesting. Requires confirm=true; dry_run=true previews (secrets masked). |
Snapshots
| Tool | Description |
|---|---|
proxmox_list_snapshots |
Snapshots for a specific VM/CT (read-only) |
proxmox_snapshot |
action='create' | 'rollback' | 'delete'. Rollback loses state newer than the snapshot; delete also needs i_understand_data_loss=true |
Backups
| Tool | Description |
|---|---|
proxmox_list_backups |
Backup files on a storage, newest first; filters: vmid, limit (read-only) |
proxmox_create_backup |
Create a backup with selectable mode and compression |
proxmox_restore_backup |
Restore a VM/CT from a backup archive. Refuses to overwrite an existing VMID unless force=true and i_understand_data_loss=true. dry_run=true previews the endpoint + payload and reports whether it would be a fresh restore or an overwrite. |
Storage (read-only)
| Tool | Description |
|---|---|
proxmox_list_storage |
Storage pools on a node with usage info |
proxmox_storage_usage_detail |
Per-storage content breakdown: items by type with totals, plus top-N largest items. Useful for capacity planning ("which VM is eating my backup storage?"). |
Disk / LVM / ZFS inventory (read-only)
| Tool | Description |
|---|---|
proxmox_list_disks |
Physical disks on a node: model, size, type, health, usage |
proxmox_get_disk_smart |
SMART health and attributes for one physical disk |
proxmox_list_lvm |
LVM volume groups and logical volumes on a node |
proxmox_list_lvm_thin |
LVM-thin pools on a node |
proxmox_list_zfs |
ZFS pools on a node with capacity/health |
proxmox_get_zfs_pool |
Detailed status of a single ZFS pool |
Disk preparation & storage provisioning
| Tool | Description |
|---|---|
proxmox_disk_prepare |
action='wipe' | 'init_gpt' on a disk (destructive). via='auto' (default) uses the REST API and falls back to SSH (wipefs/sgdisk) when the endpoint rejects the API token |
proxmox_lvm_manage |
action='create_vg' | 'create_thin' | 'destroy_vg' | 'destroy_thin' (destroys are destructive) |
proxmox_zfs_pool_manage |
action='create' | 'destroy' a ZFS pool (destroy is destructive) |
proxmox_list_cluster_storage |
List datacenter-level storage configuration (read-only) |
proxmox_storage_config |
action='add_zfs' | 'add_dir' | 'remove' on datacenter storage entries (remove never touches data) |
SSH-backed ZFS ops
These run shell commands on the node over SSH, for operations the API token can't perform. They need the SSH configuration described in the Configuration reference.
| Tool | Description |
|---|---|
proxmox_zfs_dataset |
action='create' | 'destroy' a ZFS dataset (recursive destroy needs i_understand_data_loss=true) |
proxmox_zfs_property |
action='get' | 'set' ZFS properties (set is allow-listed and needs confirm=true) |
proxmox_zfs_create_snapshot |
Create a ZFS snapshot of a dataset |
proxmox_zfs_list_datasets |
List ZFS datasets (read-only) |
proxmox_zfs_destroy_snapshots_by_pattern |
Bulk-delete ZFS snapshots whose name matches a glob pattern. Two-step: dry_run=true (default) lists matches; dry_run=false with confirm=true and i_understand_data_loss=true actually deletes. Capped at max_delete (default 1000). |
VM disk / clone / ISO + ZFS status
| Tool | Description |
|---|---|
proxmox_move_disk |
Move a VM disk to another storage |
proxmox_clone_vm |
Clone a VM/CT to a new VMID. dry_run=true previews the payload without cloning. |
proxmox_list_isos |
List ISO images available on storages (read-only) |
proxmox_zfs_pool_status |
zpool status for a pool: health, errors, scrub state (read-only) |
proxmox_zfs_scrub |
Start a scrub on a ZFS pool |
proxmox_zfs_send |
ZFS send stream for replication |
Guest VM SSH (full shell, audit-logged)
| Tool | Description |
|---|---|
proxmox_vm_list_hosts |
List guest VM aliases registered in vm_ssh_hosts.json (read-only) |
proxmox_vm_exec |
Run a shell command on a registered guest VM via SSH (uses vm_ssh_hosts.json). Requires confirm=true. |
proxmox_vm_read_file |
Read a file from a registered guest VM over SSH (read-only) |
Proxmox host SSH (full shell, audit-logged)
| Tool | Description |
|---|---|
proxmox_host_exec |
Run an arbitrary shell command on the Proxmox host over SSH. Requires confirm=true; commands matching destructive patterns also require i_understand_data_loss=true. Every call is appended to _host_ssh_audit.log. |
proxmox_host_read_exec |
Run a single read-only, allow-listed command on the Proxmox host. Broad diagnostic coverage: files/text (cat, tail, grep, zcat, โฆ), storage (lsblk, pvs/lvs/vgs, zpool status, zfs list, zdb, smartctl, nvme <read>, proxmox-boot-tool status), hardware (lspci, lsusb, lshw, dmidecode, dmesg), processes/net (ps, ss, lsof, ip), services (systemctl status/โฆ, journalctl, coredumpctl list/info), Proxmox (pveversion, pct/qm config, pvesm, pvesh get), packages (dpkg-query, apt list, apt-cache). No confirm needed โ rejects shell metacharacters (pipes/redirects/substitution/chaining), non-allow-listed binaries, mutating subcommands, and write/state/hanging flags (dmesg -C, dmidecode --dump-bin, tail -f, โฆ). Safe to grant to read-only agents. |
Audit-log integrity
| Tool | Description |
|---|---|
proxmox_audit_verify |
Verify the tamper-evident hash chain of the SSH audit logs (which='host' | 'vm' | 'all'). Every host/guest shell exec is appended as a hash-chained line (SHA-256, or HMAC-SHA256 when PROXMOX_AUDIT_HMAC_KEY is set); this recomputes the chain and reports INTACT/BROKEN with the first offending line. Set tail=N to also replay the most recent N recorded entries for review. Read-only. |
LXC exec
| Tool | Description |
|---|---|
proxmox_lxc_exec |
Run a shell command inside an LXC container via pct exec from the Proxmox host. No SSH inside the CT needed. Requires confirm=true. |
proxmox_ct_service_action |
Typed shortcut on top of proxmox_lxc_exec: systemctl --no-pager <action> <service> inside a CT. Read-only actions (status, is-active, is-enabled, show) skip confirm; state-changing actions (start, stop, restart, reload, enable, disable, mask, unmask) require confirm=true. |
proxmox_ct_log_tail |
Read-only tail of a journald unit or log file from inside a CT. Two modes: service (journalctl -u) and file (tail -n). Optional grep server-side filter. |
Diagnostics, forensics & cleanup
| Tool | Description |
|---|---|
proxmox_zfs_list_snapshots |
List ZFS snapshots (name, used, referenced, creation) directly via SSH, including transient @vzdump scratch snapshots the PVE config-snapshot API never reports. Filter by dataset and/or contains. (read-only) |
proxmox_list_tasks |
List recent PVE tasks (vzdump/snapshot/...) with start time, status and UPID. Filter by typefilter, vmid, errors_only. (read-only) |
proxmox_get_task_log |
Read a PVE task's log lines by UPID โ e.g. the exact reason a vzdump run failed. (read-only) |
proxmox_list_backup_jobs |
List configured vzdump jobs (schedule, storage, vmids, retention) from /cluster/backup. (read-only) |
proxmox_cleanup_vzdump_snapshots |
Self-heal. Remove leftover @vzdump ZFS snapshots that block a guest's backups (a stale scratch snapshot from an interrupted run makes every later backup fail with dataset already exists). Targets only names ending in @vzdump and skips any younger than min_age_minutes (default 30) so it can never race a running backup. dry_run=true by default; real removal needs dry_run=false + confirm=true. |
Built-in safety
Every tool falls into one of three safety classes, enforced by the input model itself โ not just documented:
| Class | Gate | Examples |
|---|---|---|
| Read-only | none โ runs freely | proxmox_health_overview, all list_*/get_*, proxmox_audit_verify, proxmox_host_read_exec |
| Mutating | confirm=true |
proxmox_vm_power, proxmox_create_vm, proxmox_create_container, proxmox_snapshot (create/rollback), proxmox_host_exec |
| Data-destroying | confirm=true and i_understand_data_loss=true |
proxmox_snapshot (delete), proxmox_restore_backup (overwrite), disk wipe, LVM/ZFS pool/dataset destroy, bulk snapshot destroy |
The gates are Pydantic fields defaulting to false, and every input model uses
extra="forbid" โ so a forgotten flag, a typo (confirmed=true), or a
malformed value (confirm="maybe") is rejected, never silently treated as
consent. tests/test_safety_gates.py proves a refused mutation issues no HTTP
write call.
Read-only first. The read-only set (start with proxmox_health_overview)
carries no gate, so an agent can inspect a node freely; nothing changes state
until an explicit confirm=true call.
dry-run previews. The highest-consequence mutations โ proxmox_create_vm,
proxmox_create_container, proxmox_clone_vm and proxmox_restore_backup โ
also accept dry_run=true, which returns the exact API call they would make
(secrets masked) without executing it.
Tamper-evident audit. Every host/guest SSH exec is appended to a
hash-chained log (SHA-256, or HMAC-SHA256 with PROXMOX_AUDIT_HMAC_KEY).
proxmox_audit_verify recomputes the chain (INTACT/BROKEN) and, with tail=N,
replays the most recent entries.
Safety in action โ a destructive request is refused, previewed, then applied only after explicit consent, and stays on the audit trail:
# 1. Restore over an existing guest, force+confirm but no ack -> refused, no write
restore_backup(vmid=101, archive=โฆ, force=true, confirm=true)
-> Refused: requires i_understand_data_loss=true (no HTTP write made)
# 2. Preview first -> see the exact call, nothing changes
restore_backup(vmid=101, archive=โฆ, dry_run=true)
-> DRY RUN โ POST /nodes/pve/qemu โฆ Effect: OVERWRITE existing guest.
# 3. Apply only after the user explicitly accepts the data loss
restore_backup(vmid=101, archive=โฆ, force=true, confirm=true,
i_understand_data_loss=true)
-> OK: restore started โฆ
# 4. The exec trail stays verifiable
audit_verify(tail=5) -> ๐ข host: INTACT โ 128 chained โฆ + last 5 entries
Requirements
- Python 3.11+
- A Proxmox VE host you can reach over HTTPS (default port 8006)
- A Proxmox API token with the right privileges (see below)
- Claude Desktop (or any MCP client)
1. Create a Proxmox API token
The server authenticates with an API token, never with a root password. Tokens can be revoked individually and limit blast radius.
- Log in to the Proxmox web UI.
- Go to Datacenter โ Permissions โ API Tokens.
- Click Add:
- User:
root@pam(or a dedicated user โ recommended) - Token ID:
mcp-server(any name) - Privilege Separation: keep enabled unless you know you want otherwise
- User:
- Click Add. A dialog shows the secret value (a UUID) once. Copy it now; you cannot retrieve it again.
If you keep Privilege Separation enabled, you must also grant the token permissions. Go to Datacenter โ Permissions โ Add โ API Token Permission:
- Path:
/(or narrower if you prefer) - API Token: the token you just made
- Role:
PVEAdminfor full access, orPVEVMAdminif you only want VM/CT management - Propagate: checked
You can scope this much more tightly in production. For a homelab / with
PVEAdmin is the simplest.
2. Install the server
Windows (PowerShell)
git clone https://github.com/<your-username>/proxmox-mcp.git C:\mcp-servers\proxmox-mcp
cd C:\mcp-servers\proxmox-mcp
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
If PowerShell blocks the activation script, run this once in an administrator PowerShell:
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
Linux / macOS
git clone https://github.com/<your-username>/proxmox-mcp.git ~/mcp-servers/proxmox-mcp
cd ~/mcp-servers/proxmox-mcp
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
3. Configure .env
copy .env.example .env
notepad .env
Fill in:
PROXMOX_HOST=192.168.1.10 # IP or hostname of your Proxmox host
PROXMOX_PORT=8006 # default
PROXMOX_USER=root@pam # the user owning the token
PROXMOX_TOKEN_NAME=mcp-server # the Token ID you chose
PROXMOX_TOKEN_VALUE=xxxxxxxx-xxxx-... # the secret UUID
PROXMOX_VERIFY_SSL=false # most homelabs use self-signed certs
PROXMOX_TIMEOUT=30
Never commit .env to git. The included .gitignore already excludes it,
but double-check.
4. Smoke test
With the venv active:
python proxmox_mcp.py --help
You should see the tool list and exit cleanly. An import error here means a dependency didn't install correctly.
5. Register with Claude Desktop
Open Claude Desktop's config file:
- Windows:
%APPDATA%\Claude\claude_desktop_config.json - macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
If the file doesn't exist, create it. Add (or extend) the mcpServers block:
{
"mcpServers": {
"proxmox": {
"command": "C:\\mcp-servers\\proxmox-mcp\\.venv\\Scripts\\python.exe",
"args": ["C:\\mcp-servers\\proxmox-mcp\\proxmox_mcp.py"],
"cwd": "C:\\mcp-servers\\proxmox-mcp"
}
}
}
Adjust paths for your OS. On Windows, double-backslashes are required inside JSON strings.
Fully quit Claude Desktop (tray icon โ Quit) and reopen it. In a new chat the Proxmox tools appear under the hammer/connector icon.
First test in chat
Start with a read-only call:
"List my Proxmox nodes."
Claude calls proxmox_list_nodes. You should see a list of nodes with their
status. If you get an authentication error, recheck PROXMOX_TOKEN_VALUE and
the token's permissions.
Then try:
"Show me all VMs."
"What's the status of VM 101?"
"List backups on the local storage of node pve."
Once you're confident the read-only tools work, you can try action tools:
"Reboot VM 101."
Claude will ask you to confirm. After you say yes, it calls
proxmox_vm_power with action='reboot', confirm=true.
Example workflows
Resize a VM and reboot it:
"Set VM 101 to 4 GB of RAM, then reboot it."
Claude calls proxmox_resize_vm with memory_mb=4096, confirm=true, then
proxmox_vm_power with action='reboot', confirm=true, wait_seconds=60.
Quick backup before a risky upgrade:
"Create a snapshot of VM 102 called
pre-upgradewith description 'before kernel update'."
Claude calls proxmox_snapshot with action='create', confirm=true.
Inspect health:
"How is the server? Any storage filling up?"
Claude calls proxmox_health_overview โ one call covers node load, guest
states, storage fill levels, and ZFS pool health.
Configuration reference
All settings come from environment variables, loaded from .env:
| Variable | Default | Description |
|---|---|---|
PROXMOX_HOST |
โ (required) | IP or hostname of the Proxmox host |
PROXMOX_PORT |
8006 |
API port |
PROXMOX_USER |
โ (required) | User owning the token (e.g. root@pam) |
PROXMOX_TOKEN_NAME |
โ (required) | API token ID |
PROXMOX_TOKEN_VALUE |
โ (required) | API token secret (UUID) |
PROXMOX_VERIFY_SSL |
false |
Verify the TLS certificate of the API |
PROXMOX_TIMEOUT |
30 |
HTTP timeout in seconds |
SSH (optional โ required by the SSH-backed tools)
The SSH-backed tools run shell commands on the Proxmox host (and, for
proxmox_vm_*, on guest VMs). They are inactive until you configure SSH.
PROXMOX_SSH_HOST defaults to PROXMOX_HOST. Prefer key auth; if both a
key and a password are set, the key is used. Guest VMs are defined
separately in vm_ssh_hosts.json.
| Variable | Default | Description |
|---|---|---|
PROXMOX_SSH_HOST |
= PROXMOX_HOST |
Host for the host-level SSH tools |
PROXMOX_SSH_PORT |
22 |
SSH port |
PROXMOX_SSH_USER |
root |
SSH user |
PROXMOX_SSH_KEY_PATH |
โ | Path to a private key (preferred auth) |
PROXMOX_SSH_PASSWORD |
โ | Password auth (fallback if no key) |
PROXMOX_SSH_KNOWN_HOSTS |
โ | Path to a known_hosts file; ignore skips host-key verification (trusted LAN only) |
PROXMOX_SSH_TIMEOUT |
30 |
SSH connect timeout in seconds |
Audit log (optional)
| Variable | Default | Description |
|---|---|---|
PROXMOX_AUDIT_HMAC_KEY |
โ | If set, the SSH audit log is hash-chained with HMAC-SHA256 (tamper-evident) instead of plain SHA-256 (integrity-evident). Keep it secret and stable โ changing it makes older lines fail verification. Check the chain with proxmox_audit_verify. |
Security notes
- The token secret sits in
.env. Restrict that file to your user account (icaclson Windows;chmod 600on Linux). - Never expose the Proxmox API to the internet. Keep it on a trusted LAN/VLAN, or behind a VPN.
- Privilege-separate the API token. Use a non-root user where possible.
- Action tools require
confirm=true. Don't remove that guard. PROXMOX_VERIFY_SSL=falseis the default because homelab certs are usually self-signed. If you've installed a trusted certificate, set it totrue.
Troubleshooting
-
"Authentication failed. Check PROXMOX_TOKEN_VALUE." The token secret is wrong, or the token user is wrong. The user must match the user the token was created under (e.g.
root@pam, not justroot). -
"Permission denied. Token lacks privileges." You probably have privilege separation enabled on the token but haven't given the token a role yet. Go to Datacenter โ Permissions and add an API Token Permission for it.
-
"Cannot connect to :8006" Network problem. Ping the host. Check the firewall on both ends. Confirm the web UI loads at
https://<host>:8006/from the same machine. -
"Request timed out after 30s" Increase
PROXMOX_TIMEOUTor check that the Proxmox node isn't under heavy load. -
Tools don't appear in Claude Desktop. Check
%APPDATA%\Claude\logs\mcp*.log(Windows) or~/Library/Logs/Claude/mcp*.log(macOS) for errors. The most common cause is a wrong path inclaude_desktop_config.jsonor backslashes that weren't doubled.
Project structure
proxmox-mcp/
โโโ proxmox_mcp/ # The MCP server package
โ โโโ __main__.py # entry for `python -m proxmox_mcp`
โ โโโ server.py # entry point + full tool registry (TOOLS)
โ โโโ mcp_instance.py # the shared FastMCP instance
โ โโโ config.py # environment-variable configuration
โ โโโ http_client.py # Proxmox REST client
โ โโโ ssh.py / host_ssh.py / vm_ssh.py # SSH backends (host + guests)
โ โโโ tools/ # one module per tool group (nodes, vms,
โ # disks, lvm, zfs, backups, ct_ops, โฆ)
โโโ proxmox_mcp.py # compatibility shim (`python proxmox_mcp.py`)
โโโ requirements.txt # Python dependencies
โโโ .env.example # Template for your local .env
โโโ vm_ssh_hosts.json # (git-ignored) guest-VM SSH registry
โโโ .gitignore
โโโ LICENSE # GPL v3
โโโ README.md # This file
โโโ README.tr.md # Turkish version
Contributing
Issues and PRs welcome. If you add a tool, please:
- Follow the existing pattern: pydantic input model +
_require_config+ error handling. - Tag destructive tools with
destructiveHint: Truein the annotations and requireconfirm=Truein the input model. - Update the tool list in this README.
License
GNU General Public License v3.0 โ see the LICENSE file for the
full text.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pve_mcp-1.3.0.tar.gz.
File metadata
- Download URL: pve_mcp-1.3.0.tar.gz
- Upload date:
- Size: 104.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
08102d583731afa10c71ada996b647224df792808f69bcce0d60f1fa98c81c67
|
|
| MD5 |
cbd3a8c855cca5e5845accafd71497fc
|
|
| BLAKE2b-256 |
dfbdb99a51fd874e80e7604648151c51a2cfcf62a04b8fbed31f258d1b16dc9b
|
File details
Details for the file pve_mcp-1.3.0-py3-none-any.whl.
File metadata
- Download URL: pve_mcp-1.3.0-py3-none-any.whl
- Upload date:
- Size: 113.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ca6349052ed4ceea4d3ee05c5bc5e0cfbb254a0ddfbd93377e93d0293e471e3e
|
|
| MD5 |
0aa7fe0c80fc6548fed7fe91bebcd695
|
|
| BLAKE2b-256 |
173365427e3a57fa76e005a726ee0d6122c26d0c8ca7df947702fb3dcd32cd74
|