Skip to main content
Glama

proxmox-ve-mcp

An MCP server that exposes one or more Proxmox VE hosts as tools an LLM client can call — inventory nodes, guests, storage and network bridges, read live status, and create, clone, start, stop and delete VMs and containers.

It speaks MCP over Streamable HTTP, so it runs as its own service on the network rather than as a local subprocess of one client.

Built for universal-network-director, a chat-driven multi-vendor network manager with a human approval gate on every write — but it is a standalone MCP server and works with any MCP client.

Not affiliated with, endorsed by, or supported by Proxmox Server Solutions GmbH. "Proxmox" and "Proxmox VE" are trademarks of their respective owners and are used here only to describe what this software talks to.


Read this before you point it at production

Twelve of the twenty-four tools change state, and this server does not ask before executing them. There is no confirmation and no dry-run. If a model decides to call one, it happens.

Tool

What it does

Risk

write_set_vm_description

Sets a guest's notes field

Cosmetic. Reversible.

write_start_vm

Powers a guest on

Low.

write_shutdown_vm

ACPI shutdown — the guest OS shuts itself down

Takes a workload offline. Clean.

write_reboot_vm

Clean guest restart

Takes a workload offline briefly.

write_stop_vm

Immediate power-off, like pulling the cord

Takes a workload offline, uncleanly. Risks filesystem damage.

write_clone_vm

Clones a guest to a new vmid

Consumes storage. Source untouched.

write_create_vm_from_image

Creates a VM from a staged disk image

Consumes storage and a vmid.

write_create_vm_from_iso

Creates a VM with an empty disk booting an installer ISO

Consumes storage and a vmid.

write_download_image

Pulls a disk image from a URL into import storage

Consumes storage and egress bandwidth.

write_delete_image

Deletes a staged image, ISO or template

Destructive. Refuses if a guest still has it attached.

write_set_vm_nic_bridge

Patches a guest NIC into a bridge, or unpatches it

Can move a running guest onto the wrong segment — or off the network.

write_delete_vm

Permanently deletes a guest and its disks

Destructive and irreversible. No snapshot, no undo.

Three ways to handle that, in the order they actually help:

  1. Scope the Proxmox API token read-only. This is the real control, and it lives on Proxmox, not in this code. Give the token the built-in PVEAuditor role at path / and every write tool fails at the API no matter what any model decides. Do this unless you specifically intend the writes to work.

  2. Use the protected-guest denylist. config/protected-vms.json lists guests the write tools refuse to touch, checked locally before any backend call — so it holds even if a human approves something by accident. A missing or unparseable file refuses every guest write rather than silently protecting nothing. See below.

  3. Gate the writes in your client. Every state-changing tool is prefixed write_. That prefix is a convention of this codebase precisely so a client can match on it and route those calls through a human-approval step before execution. This server deliberately doesn't do that itself — it has no user to ask.

The MCP endpoint has no authentication

This server exposes its tools to anyone who can reach its port. There is no token, no client auth, no TLS on the MCP side.

MCP_HOST defaults to 127.0.0.1 for that reason. The container image sets 0.0.0.0 because it has to, which means publishing the container's port puts an unauthenticated control plane for your hypervisors on that interface. Keep it on an internal network with the client, or terminate TLS and authentication in front of it.


Multi-host by design

Proxmox clusters share one API, but plenty of setups run several standalone hosts on different subnets with no cluster between them. This server holds one connection per host, keyed by a short freeform label, and every tool takes that label to pick which host to talk to.

A host is defined by a pair of environment variables:

PROXMOX_SERVER1_URL=https://pve1.example.com:8006
PROXMOX_SERVER1_TOKEN='automation@pve!mcp=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'

The <LABEL> in PROXMOX_<LABEL>_URL, lowercased, becomes the host value the tools take (server1 above). Add a third host by adding a third pair — no code change. Name them for the site so the model and the logs read clearly.

The token is the whole user@realm!tokenid=secret string, shown once at creation under Datacenter → Permissions → API Tokens. Auth is stateless: every request carries an Authorization: PVEAPIToken=... header. There is no login call and no CSRF token — that's the username/password session path, which this deliberately does not use.

Set PROXMOX_VERIFY_TLS=false for hosts with self-signed certificates. It defaults to on.

The protected-guest denylist

config/protected-vms.json is mounted read-only into the container and holds the guests write tools must never touch:

{
  "protected_vms": [
    {
      "host": "server1",
      "vmid": 100,
      "name": "example-mcp-host",
      "reason": "EXAMPLE -- the VM this MCP server itself runs in"
    }
  ]
}

host is the label from list_hosts, not the Proxmox node name. reason is shown verbatim in the refusal, so write it for whoever hits it.

This file is meant to be tracked in git. It started life as an environment variable in an untracked .env, which meant the protection did not survive a fresh clone and an empty list looked exactly like a populated one. Now a missing or unparseable file refuses every guest write; an empty list is allowed but logs a loud warning at startup.

The entries shipped here are examples. Replace them before pointing this at anything you care about.


Running it

docker build -t proxmox-ve-mcp .
docker run --rm \
  -e PROXMOX_SERVER1_URL=https://pve1.example.com:8006 \
  -e PROXMOX_SERVER1_TOKEN='automation@pve!mcp=...' \
  -e PROXMOX_VERIFY_TLS=false \
  -v "$PWD/config/protected-vms.json:/app/config/protected-vms.json:ro" \
  -p 127.0.0.1:8002:8002 \
  proxmox-ve-mcp

Or point pip install -r requirements.txt at a virtualenv and run python server.py directly.

Variable

Default

Meaning

PROXMOX_<LABEL>_URL

API root of a host, e.g. https://pve1.example.com:8006

PROXMOX_<LABEL>_TOKEN

The full user@realm!tokenid=secret string

PROXMOX_VERIFY_TLS

true

Set false for self-signed certs (lab only)

PROXMOX_PROTECTED_VMS

unset

Escape hatch (label:vmid,...) that adds to the JSON denylist

PROXMOX_PROTECTED_VMS_FILE

/app/config/protected-vms.json

Denylist path

MCP_HOST

127.0.0.1

Bind address (the image sets 0.0.0.0)

MCP_PORT

8002

Bind port

Tests

Standalone scripts, no pytest. Run them in the container so they have the PROXMOX_* environment the client needs:

docker run --rm proxmox-ve-mcp python test_network_bridges.py
docker run --rm proxmox-ve-mcp python test_media_in_use.py
docker run --rm proxmox-ve-mcp python test_client.py

The offline sections use fabricated interface and guest lists and pass with no hosts configured. The live sections read whatever your PROXMOX_* variables point at, and skip cleanly when nothing is configured — point them at a real host to exercise the one distinction that can't be faked: whether a bridge is uplinked or isolated.

Design notes

  • /cluster/resources is the inventory backbone. One call returns every VM, container, node and storage already tagged with its node, vmid and type. It works on a standalone host too (it reports that one node), so this is used instead of walking /nodes/nodes/{node}/qemu per node.

  • list_network_bridges exists because a NIC on the wrong bridge is a guest you can't reach. It reports, per bridge, whether it has a member port (a way off the box) or is an isolated segment — the distinction that decides whether a new VM comes up reachable.

  • VLAN tags fail closed. A tag= on a bridge that isn't bridge_vlan_aware is accepted by Proxmox and then silently not carried — untagged traffic where isolation was asked for. The write paths refuse that rather than warn, and refuse if they can't read the bridge list to check.

  • Writes are asynchronous. Most return a Proxmox UPID; poll it with get_task_status rather than assuming completion.

  • Guest writes run behind one guard. A single wrapper does the protected-VM check and the vmid→node/kind resolution, so an individual tool cannot forget the guard and cannot reach the backend for a protected guest.

License

Apache-2.0. See LICENSE.

-
license - not tested
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

  • MCP server for the FFmpeg Micro video transcoding API — create, monitor, download transcodes.

  • An MCP server that let you interact with Cycloid.io Internal Development Portal and Platform

  • Streamable HTTP MCP server for Google Calendar and Sheets with OAuth login.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/anderson-jason573/proxmox-ve-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server