mcpField
P
Proxmox Mcp

Proxmox Mcp

Local
github.comCloud & Infra5Updated 8w ago

About Proxmox Mcp

Proxmox Mcp is an MCP server for managing Proxmox VE, aimed at users who want to inspect and operate nodes, VMs, and containers through clients such as Claude. It exposes read-only tools by default, with lifecycle and snapshot operations gated by risk level.

Key features

  1. 01Inspect nodes, QEMU VMs, and LXC containers
  2. 02View storage, cluster, and snapshot data
  3. 03Gate tools by configured risk level
  4. 04Use stdio or Streamable HTTP transport
  5. 05Redact sensitive response fields by default

Use cases

  1. 01Check cluster nodes and VM status
  2. 02Investigate node tasks and resource usage
  3. 03Enable VM lifecycle operations when needed

MCP support

Exposes Proxmox VE inspection and risk-gated management tools to MCP clients. Supports stdio and Streamable HTTP transports.

Configuration

Local server config is the same for every client. Copy it into your client’s MCP config file.

mcp.json · Universal config
{
"mcpServers": {
"akmalovaa-proxmox": {
"command": "uvx",
"args": [
"proxmox-ve-mcp"
],
"env": {
"PROXMOX_HOST": "<PROXMOX_HOST>",
"PROXMOX_PORT": "<PROXMOX_PORT>",
"PROXMOX_VERIFY_SSL": "<PROXMOX_VERIFY_SSL>",
"PROXMOX_TIMEOUT": "<PROXMOX_TIMEOUT>",
"PROXMOX_USER": "<PROXMOX_USER>",
"PROXMOX_TOKEN_NAME": "<PROXMOX_TOKEN_NAME>",
"PROXMOX_TOKEN_VALUE": "<PROXMOX_TOKEN_VALUE>",
"PROXMOX_PASSWORD": "<PROXMOX_PASSWORD>",
"PROXMOX_RISK_LEVEL": "<PROXMOX_RISK_LEVEL>",
"PROXMOX_TOOLS_ALLOW": "<PROXMOX_TOOLS_ALLOW>",
"PROXMOX_REDACT_SECRETS": "<PROXMOX_REDACT_SECRETS>",
"PROXMOX_MCP_TRANSPORT": "<PROXMOX_MCP_TRANSPORT>",
"PROXMOX_MCP_HOST": "<PROXMOX_MCP_HOST>",
"PROXMOX_MCP_PORT": "<PROXMOX_MCP_PORT>",
"PROXMOX_MCP_PATH": "<PROXMOX_MCP_PATH>",
"PROXMOX_MCP_JSON_RESPONSE": "<PROXMOX_MCP_JSON_RESPONSE>",
"PROXMOX_MCP_ALLOWED_HOSTS": "<PROXMOX_MCP_ALLOWED_HOSTS>",
"PROXMOX_MCP_ALLOWED_ORIGINS": "<PROXMOX_MCP_ALLOWED_ORIGINS>",
"SENTRY_DSN": "<SENTRY_DSN>"
}
}
}
}
Run commanduvx proxmox-ve-mcp
Environment variables to set
PROXMOX_HOSTProxmox host IP or hostname.
PROXMOX_PORTProxmox API port.
PROXMOX_VERIFY_SSLVerify the TLS certificate. Proxmox self-signs by default.
PROXMOX_TIMEOUTSeconds to wait for each Proxmox API request.
PROXMOX_USERAPI user, including the realm.
PROXMOX_TOKEN_NAMESecretAPI token name. Use this plus PROXMOX_TOKEN_VALUE, or PROXMOX_PASSWORD.
PROXMOX_TOKEN_VALUESecretAPI token secret.
PROXMOX_PASSWORDSecretPassword, used only when no API token is set.
PROXMOX_RISK_LEVELWhich tools exist: read-only, plus lifecycle, or everything.
PROXMOX_TOOLS_ALLOWComma-separated tool names to register, intersected with the risk level. Unset = all of them.
PROXMOX_REDACT_SECRETSSecretMask cipassword and sshkeys in every response.
PROXMOX_MCP_TRANSPORTstdio (default) or streamable-http.
PROXMOX_MCP_HOSTHTTP bind address. Anything but loopback also needs PROXMOX_MCP_ALLOWED_HOSTS.
PROXMOX_MCP_PORTHTTP port.
PROXMOX_MCP_PATHStreamable HTTP endpoint path.
PROXMOX_MCP_JSON_RESPONSEAnswer with JSON instead of an SSE stream.
PROXMOX_MCP_ALLOWED_HOSTSComma-separated Host headers to accept over HTTP; '*' disables the check.
PROXMOX_MCP_ALLOWED_ORIGINSComma-separated browser origins allowed to call the endpoint. Empty = same-origin only.
SENTRY_DSNSecretOptional: report tool traces and errors to Sentry. Unset disables it.

Local servers need a runtime on your machine (e.g. Node.js 18+). Config file locations differ by client; see your client’s docs.

Packages
pypiproxmox-ve-mcp2.3.0
ocighcr.io/akmalovaa/proxmox-mcp:2.3.0

Tools

No tool list yet

Local servers expose tools only once running on your machine, and the maintainer has not provided a tool list.

Details

From the project README

Proxmox MCP server

proxmox-mcp — MCP server for Proxmox VE

CI Release License: MIT Python 3.14 GHCR MCP

Simple Proxmox MCP

proxmox-mcp logo

MCP server for managing Proxmox VE

50 tools — nodes, QEMU VMs, LXC containers, storage, cluster, snapshots.

Why this one?
  • One image, multi-arch — docker run ghcr.io/akmalovaa/proxmox-mcp:latest and you're done
  • Just env vars — no config files, no database, no state
  • Read-only by default — destructive ops are gated behind an explicit PROXMOX_RISK_LEVEL
  • stdio or Streamable HTTP — one env var apart; stdio by default, HTTP binds loopback
  • Tiny codebase — a thin layer over Proxmoxer, no config files, no database, no state
  • Raw JSON out — no formatting, no emoji; LLM gets clean data
  • Readable failures — a 403, a dead host or a blocked tier come back as a sentence, not a stack trace

proxmox-mcp MCP server

Quick start

Image: ghcr.io/akmalovaa/proxmox-mcp:latest (multi-arch: amd64 + arm64).

1. Export credentials in your shell profile (~/.zprofile, ~/.zshrc or ~/.bashrc):

# token auth (recommended — see "Least privilege" for the user and role to give it):
export PROXMOX_HOST=192.168.1.100
export PROXMOX_USER=mcp@pve
export PROXMOX_TOKEN_NAME=mcp
export PROXMOX_TOKEN_VALUE=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

# or password auth:
# export PROXMOX_USER=root@pam
# export PROXMOX_PASSWORD=your-password

# optional:
export PROXMOX_RISK_LEVEL=read

Reload: source ~/.zprofile (or restart the shell).

2. Add to ~/.claude/settings.json (Claude Code) or claude_desktop_config.json (Claude Desktop):

{
  "mcpServers": {
    "proxmox": {
      "command": "docker",
      "args": ["run", "-i", "--rm",
        "-e", "PROXMOX_HOST",
        "-e", "PROXMOX_USER",
        "-e", "PROXMOX_PASSWORD",
        "ghcr.io/akmalovaa/proxmox-mcp:latest"]
    }
  }
}

or token auth:

{
  "mcpServers": {
    "proxmox": {
      "command": "docker",
      "args": ["run", "-i", "--rm",
        "-e", "PROXMOX_HOST",
        "-e", "PROXMOX_USER",
        "-e", "PROXMOX_TOKEN_NAME",
        "-e", "PROXMOX_TOKEN_VALUE",
        "ghcr.io/akmalovaa/proxmox-mcp:latest"]
    }
  }
}

docker run -e VAR without a value passes the host variable through — no secrets in the config file. Restart the client — 32 read-only Proxmox tools become available (more if you raise PROXMOX_RISK_LEVEL).

For password auth, swap the token vars for PROXMOX_PASSWORD.

Note: Claude Desktop on macOS is launched via launchd and does not inherit ~/.zprofile/~/.zshrc. Either put the exports in ~/.zshenv, or fall back to an inline "env": { ... } block in the config.

Configuration

All settings are environment variables — set them in your shell profile, pass them inline to docker run -e, or declare them in your MCP client's env block.

VariableDefaultDescription
PROXMOX_HOST—Proxmox host (IP or hostname)
PROXMOX_USERroot@pamAPI user
Auth—token or password — see below
PROXMOX_PORT8006API port
PROXMOX_VERIFY_SSLfalseVerify TLS certificate
PROXMOX_TIMEOUT15Seconds to wait for each API request
PROXMOX_RISK_LEVELreadread / lifecycle / all — see Risk levels
PROXMOX_TOOLS_ALLOW—Comma-separated tool names to register, on top of the risk level
PROXMOX_REDACT_SECRETStrueMask cipassword / sshkeys in responses

Transport settings live under a PROXMOX_MCP_ prefix — see Streamable HTTP.

Authentication: token or password

Pick one. If both are set, the token wins.

Token (recommended):

export PROXMOX_USER=mcp@pve
export PROXMOX_TOKEN_NAME=mcp
export PROXMOX_TOKEN_VALUE=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

Setting only one half of the pair is a startup error rather than a silent fallback to the password — a typo in PROXMOX_TOKEN_NAME used to mean quietly running as whoever PROXMOX_USER is.

Password (fallback):

export PROXMOX_PASSWORD=your-password
Least privilege

An MCP server is reachable by a model acting on text it did not write, so give it its own user and its own token rather than root@pam. On the Proxmox host:

# a user that is not root, with a role that matches the risk level you plan to run
pveum user add mcp@pve
pveum acl modify / --users mcp@pve --roles PVEAuditor          # read
# pveum acl modify / --users mcp@pve --roles PVEVMAdmin        # lifecycle / all

# a token for that user. Privilege Separation ON (the default) means the token starts
# with no rights at all, so grant it the same role explicitly:
pveum user token add mcp@pve mcp --privsep 1
pveum acl modify / --tokens 'mcp@pve!mcp' --roles PVEAuditor

Check what the token actually ended up with:

pveum user permissions mcp@pve --token mcp

PVEAuditor covers all 32 read tools. PVEVMAdmin on /vms adds guest lifecycle and snapshots; migrate_vm additionally needs VM.Migrate on the target node, and clone_vm needs Datastore.AllocateSpace on the target storage. Narrow the ACL path (/vms/101, /pool/homelab) if the server should only see part of the cluster.

root@pam still works and is the quickest thing for a local look around — it is just not what should be left running.

Risk levels

PROXMOX_RISK_LEVEL controls which tools exist. Tools above the active level are not registered, so they never appear in the MCP client's tool list:

LevelToolsAdds
read (default)32read-only tools
lifecycle46+ start / stop / reboot / suspend / clone / migrate / create-snapshot
all50+ delete-snapshot / rollback-snapshot

Each elevated call is also re-checked at call time and logged to stderr (ALLOW / DENY + tool + tier).

The active level is not otherwise observable from the client side — a tool that is missing looks the same as a tool that was never written — so get_server_info reports it, along with the versions and the tool count.

PROXMOX_TOOLS_ALLOW narrows further within the tier, for a deployment that serves one specific agent:

export PROXMOX_TOOLS_ALLOW=list_nodes,list_containers,get_container_status,get_cluster_resources

Names that do not exist are a startup error, so a typo cannot silently amputate the tool list. Keep get_server_info on the list unless you have a reason not to — it is how a client learns what the rest of the list means.

Response shape

Inventory listings (list_nodes, list_vms, list_containers, get_cluster_resources) return a compact subset of each row: identity, status, uptime, CPU and memory. What is dropped is per-second IO counters and PSI pressure gauges — real data, but nothing a triage decision turns on, and several times the volume of what is kept. Pass verbose=true for the untouched rows.

Write tools answer with the task Proxmox accepted, not with a finished result:

{"state":"accepted","operation":"start_vm","node":"pve","vmid":101,
 "upid":"UPID:pve:...","poll_with":"get_task_status"}

cipassword and sshkeys are replaced with ***redacted*** everywhere, keeping the key so the model can still tell that cloud-init is configured. Set PROXMOX_REDACT_SECRETS=false to get the raw values.

Streamable HTTP

stdio stays the default and is what an MCP client on your own machine should use. For a shared deployment — a container in a cluster, several clients on one URL — set the transport and nothing else changes:

docker run --rm -p 8000:8000 \
  -e PROXMOX_HOST -e PROXMOX_TOKEN_NAME -e PROXMOX_TOKEN_VALUE \
  -e PROXMOX_MCP_TRANSPORT=streamable-http \
  -e PROXMOX_MCP_HOST=0.0.0.0 \
  -e PROXMOX_MCP_ALLOWED_HOSTS=proxmox-mcp.example.com \
  ghcr.io/akmalovaa/proxmox-mcp:latest
VariableDefaultDescription
PROXMOX_MCP_TRANSPORTstdiostdio or streamable-http
PROXMOX_MCP_HOST127.0.0.1Bind address — 0.0.0.0 in a container
PROXMOX_MCP_PORT8000Port
PROXMOX_MCP_PATH/mcpEndpoint path
PROXMOX_MCP_JSON_RESPONSEtrueJSON responses instead of an SSE stream
PROXMOX_MCP_ALLOWED_HOSTS—Host headers to accept; * disables the check
PROXMOX_MCP_ALLOWED_ORIGINS—Browser origins allowed; empty = same-origin only

The prefix is PROXMOX_MCP_, not PROXMOX_, because Kubernetes injects <SERVICE>_PORT for every linked Service — a Service named proxmox would otherwise redefine PROXMOX_PORT.

Sessions are not used (stateless_http): they were removed from the protocol in revision 2026-07-28, and without them several clients can share one URL and a rolling update does not cut anyone off.

Host and Origin validation. Binding anything other than loopback requires PROXMOX_MCP_ALLOWED_HOSTS; the server refuses to start otherwise. A foreign Origin gets 403 and an unexpected Host gets 421 — this is what stops a page in a browser on the same LAN from driving the server through DNS rebinding. A request with no Origin header (curl, MCP clients) always passes. Set PROXMOX_MCP_ALLOWED_HOSTS=* only when something in front already validates it.

There is no authentication. Anyone who can reach the endpoint gets whatever PROXMOX_RISK_LEVEL allows. Put it behind a gateway, an authenticating proxy or a network boundary you trust, and keep PROXMOX_RISK_LEVEL=read unless the path to it is authenticated.

Health endpoints are served alongside /mcp and need no auth:

PathMeaning
GET /healthzThe process is up. Never touches Proxmox — a liveness probe that fails when Proxmox is down would restart the server in a loop and fix nothing.
GET /readyzProxmox answered version.get(). This is the one that should gate traffic; 503 with a readable reason otherwise.
livenessProbe:
  httpGet: { path: /healthz, port: 8000 }
readinessProbe:
  httpGet: { path: /readyz, port: 8000 }

Flags mirror the variables for interactive use: uvx proxmox-ve-mcp --transport streamable-http --port 8080.

Sentry (optional)

Tool calls and failures can be shipped to Sentry — every tools/call becomes a span, every failing tool an issue. Nothing is sent, and the SDK is never even imported, while SENTRY_DSN is unset.

The ghcr.io image already contains the SDK. From PyPI, install the extra:

uvx --from 'proxmox-ve-mcp[sentry]' proxmox-ve-mcp
VariableDefaultDescription
SENTRY_DSN—Set it to enable reporting
SENTRY_ENVIRONMENTproductionFree-form environment label
SENTRY_TRACES_SAMPLE_RATE1.0Share of tool calls traced
SENTRY_SEND_DEFAULT_PIIfalseSend tool arguments and results as span data

Leave SENTRY_SEND_DEFAULT_PII off unless you mean it: with it on, tool arguments and results are attached to spans, and get_vm_config returns ssh keys and cipassword hashes. A DSN set without the extra installed logs a warning and the server runs on.

Tools

Nodes (10)
ToolDescription
list_nodesList all cluster nodes with status, CPU, memory, uptime
get_node_statusDetailed node metrics (CPU, memory, disk, load, kernel)
get_node_networksNetwork interfaces on a node
get_node_disksPhysical disks on a node
get_node_servicesProxmox system services and their state
get_node_updatesPending APT package updates
get_node_rrd_dataHistorical CPU/memory/disk/network metrics (RRD)
get_node_tasksRecent tasks on a node, optionally errors only
get_task_statusStatus of a specific task by UPID
get_task_logLog output from a task
QEMU VMs (17)
ToolTierDescription
list_vmsreadList all VMs, optionally filter by node
get_vm_statusreadCurrent VM status (running/stopped, CPU, memory)
get_vm_configreadVM configuration (hardware, disks, network)
get_vm_network_interfacesreadIP addresses of a running VM (via QEMU guest agent)
get_vm_rrd_datareadHistorical CPU/memory/disk/network metrics (RRD)
list_vm_snapshotsreadList all snapshots of a VM
start_vmlifecycleStart a VM
stop_vmlifecycleForce-stop a VM — annotated destructive, unsaved guest state is lost
shutdown_vmlifecycleGraceful ACPI shutdown with timeout
reboot_vmlifecycleReboot via ACPI
suspend_vmlifecycleSuspend a VM
resume_vmlifecycleResume a suspended VM
clone_vmlifecycleFull or linked clone
migrate_vmlifecycleMove a VM to another node, online or offline
create_vm_snapshotlifecycleCreate a snapshot
delete_vm_snapshotallDelete a snapshot
rollback_vm_snapshotallRollback to a snapshot
LXC Containers (13)
ToolTierDescription
list_containersreadList all LXC containers, optionally filter by node
get_container_statusreadCurrent container status
get_container_configreadContainer configuration
get_container_interfacesreadIP addresses of a running container
get_container_rrd_datareadHistorical CPU/memory/disk/network metrics (RRD)
list_container_snapshotsreadList all snapshots
start_containerlifecycleStart a container
stop_containerlifecycleForce-stop a container — annotated destructive
shutdown_containerlifecycleGraceful shutdown with timeout
reboot_containerlifecycleReboot a container
create_container_snapshotlifecycleCreate a snapshot
delete_container_snapshotallDelete a snapshot
rollback_container_snapshotallRollback to a snapshot
Storage (2)
ToolDescription
list_storageStorage pools with usage, optionally filter by node
get_storage_contentContents of a storage pool (ISOs, backups, images, templates)
Cluster (7)
ToolDescription
get_cluster_statusCluster health, quorum, node membership
get_cluster_resourcesAll resources (VMs, containers, storage, nodes)
get_cluster_backupsConfigured backup jobs
get_ha_statusHigh-availability resources and their state
list_poolsResource pools
get_cluster_logCluster-wide event log, newest first
get_next_vmidNext available VM/container ID
Server (1)
ToolDescription
get_server_infoThis server's own risk level, tool count, versions and Proxmox reachability

Architecture

src/proxmox_mcp/
├── server.py    # MCPServer instance, health routes, transport selection, entry point
├── config.py    # Pydantic Settings (PROXMOX_) + transport config (PROXMOX_MCP_)
├── client.py    # Proxmoxer connection, built once per process
└── tools/       # nodes, vms, containers, storage, cluster, server_info
  • Read-only by default — elevated tools gated by PROXMOX_RISK_LEVEL
  • Gated at registration — a tool above the tier is absent from tools/list, not refused at call time: what the model cannot see, it cannot retry
  • Admission and annotations are separate — tier= is the operator's policy, annotations= is what the client is told a call does, so a force-stop can be destructiveHint: true and still live at the lifecycle tier
  • Lazy connection — the Proxmoxer client is built on first use, once, and shared; the server therefore starts cleanly even when Proxmox is unreachable
  • Raw JSON output — compact, no formatting; LLM consumes data directly
  • Normalized errors — Proxmox and network failures are translated into one actionable sentence instead of a requests traceback

Development

Run standalone (testing)
export PROXMOX_HOST=192.168.1.100
export PROXMOX_USER=root@pam
export PROXMOX_TOKEN_NAME=mcp
export PROXMOX_TOKEN_VALUE=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

docker run -i --rm \
  -e PROXMOX_HOST -e PROXMOX_USER \
  -e PROXMOX_TOKEN_NAME -e PROXMOX_TOKEN_VALUE \
  ghcr.io/akmalovaa/proxmox-mcp:latest
Without Docker (UV)
git clone https://github.com/akmalovaa/proxmox-mcp.git && cd proxmox-mcp && uv sync

MCP client config:

{
  "mcpServers": {
    "proxmox": {
      "command": "uv",
      "args": ["run", "--directory", "/path/to/proxmox-mcp", "python", "-m", "proxmox_mcp"],
      "env": {
        "PROXMOX_HOST": "192.168.1.100",
        "PROXMOX_TOKEN_NAME": "mcp",
        "PROXMOX_TOKEN_VALUE": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
      }
    }
  }
}
Build from source
git clone https://github.com/akmalovaa/proxmox-mcp.git
cd proxmox-mcp
docker build -t proxmox-mcp .

The image is multi-stage: uv builds the virtualenv in a throwaway layer, and the runtime stage carries only Python plus the venv and runs as the unprivileged mcp user (uid 10001).

Lint, type-check, test
uv sync --locked --group dev
uv run ruff check .
uv run mypy src/
uv run pytest -v

License

MIT

Comments

Markdown supported

New to MCP?

From what the protocol is to installing your first server, these answers cover most first questions.

An open protocol that defines how AI applications connect to external tools and data in a uniform way. Clients talk to the model; MCP servers provide tools, resources and prompt templates. They exchange standard messages, so one server can be reused by many clients.