Skip to main content
Glama

pve-mcp

MCP server for Proxmox VE. Curated tools for everyday work, plus schema-validated access to every API endpoint.

  • Schema: src/pve_mcp/data/pve-api.json.gz, generated from a node's /usr/share/pve-docs/api-viewer/apidoc.js (currently PVE 9.2.24, 680 endpoints). Regenerate after a PVE upgrade: scp root@<node>:/usr/share/pve-docs/api-viewer/apidoc.js schema-src/ && python scripts/gen_schema.py schema-src/apidoc.js <version>

  • Generic: pve_api_search, pve_api_describe, pve_api (validates params, waits for tasks, dry_run).

  • Curated: cluster status/resources, guest status/power/config/snapshots, create/clone/migrate/destroy, guest-agent exec, storage, vzdump backups, tasks, optional node SSH.

Install in your agent

Install in VS Code Install in VS Code Insiders Install in Cursor

It works with any MCP client that can start a stdio command. It needs uv. uvx fetches the pinned release, so no clone or virtualenv is needed. Pick your agent below.

Variable

Meaning

Example

PVE_HOST

Proxmox host or IP

pve.example.lan

PVE_TOKEN_ID

API token id (user@realm!name)

root@pam!mcp

PVE_TOKEN_SECRET

API token secret

xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

Optional: PVE_ALLOW_WRITES=true (writes), PVE_VERIFY_SSL (true or a CA path), PVE_SSH_ENABLED. Without PVE_ALLOW_WRITES the server is read-only. Create the token under Datacenter → Permissions → API Tokens. For read-only use, give it the PVEAuditor role on /.

A different project uses the name pve-mcp on PyPI. Install from this repository as shown, not with pip install pve-mcp.

The VS Code buttons ask for each value, keeping secrets out of files. The Cursor button writes placeholders: edit them in ~/.cursor/mcp.json afterwards. Elsewhere, replace the example values.

claude mcp add proxmox -s user -e PVE_HOST=pve.example.lan -e PVE_TOKEN_ID=root@pam!mcp -e PVE_TOKEN_SECRET=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -- uvx --from git+https://github.com/bigbatmanorg/pve-mcp@v0.1.0 pve-mcp

Settings → Developer → Edit Config (claude_desktop_config.json):

{
  "mcpServers": {
    "proxmox": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/bigbatmanorg/pve-mcp@v0.1.0",
        "pve-mcp"
      ],
      "env": {
        "PVE_HOST": "pve.example.lan",
        "PVE_TOKEN_ID": "root@pam!mcp",
        "PVE_TOKEN_SECRET": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
      }
    }
  }
}

Use the button above, or add this to .vscode/mcp.json (workspace) or to the user mcp.json (command MCP: Open User Configuration):

{
  "inputs": [
    {
      "type": "promptString",
      "id": "pve_host",
      "description": "Proxmox host or IP",
      "password": false
    },
    {
      "type": "promptString",
      "id": "pve_token_id",
      "description": "API token id (user@realm!name)",
      "password": false
    },
    {
      "type": "promptString",
      "id": "pve_token_secret",
      "description": "API token secret",
      "password": true
    }
  ],
  "servers": {
    "proxmox": {
      "type": "stdio",
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/bigbatmanorg/pve-mcp@v0.1.0",
        "pve-mcp"
      ],
      "env": {
        "PVE_HOST": "${input:pve_host}",
        "PVE_TOKEN_ID": "${input:pve_token_id}",
        "PVE_TOKEN_SECRET": "${input:pve_token_secret}"
      }
    }
  }
}

Use the button above, or add this to ~/.cursor/mcp.json (global) or .cursor/mcp.json (project):

{
  "mcpServers": {
    "proxmox": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/bigbatmanorg/pve-mcp@v0.1.0",
        "pve-mcp"
      ],
      "env": {
        "PVE_HOST": "pve.example.lan",
        "PVE_TOKEN_ID": "root@pam!mcp",
        "PVE_TOKEN_SECRET": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
      }
    }
  }
}

Add this to ~/.codeium/windsurf/mcp_config.json:

{
  "mcpServers": {
    "proxmox": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/bigbatmanorg/pve-mcp@v0.1.0",
        "pve-mcp"
      ],
      "env": {
        "PVE_HOST": "pve.example.lan",
        "PVE_TOKEN_ID": "root@pam!mcp",
        "PVE_TOKEN_SECRET": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
      }
    }
  }
}
codex mcp add proxmox --env PVE_HOST=pve.example.lan --env PVE_TOKEN_ID=root@pam!mcp --env PVE_TOKEN_SECRET=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -- uvx --from git+https://github.com/bigbatmanorg/pve-mcp@v0.1.0 pve-mcp

Or add this to ~/.codex/config.toml:

[mcp_servers.proxmox]
command = "uvx"
args = ["--from", "git+https://github.com/bigbatmanorg/pve-mcp@v0.1.0", "pve-mcp"]
env = { PVE_HOST = "pve.example.lan", PVE_TOKEN_ID = "root@pam!mcp", PVE_TOKEN_SECRET = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }

Add this to ~/.gemini/settings.json (or to .gemini/settings.json in a project):

{
  "mcpServers": {
    "proxmox": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/bigbatmanorg/pve-mcp@v0.1.0",
        "pve-mcp"
      ],
      "env": {
        "PVE_HOST": "pve.example.lan",
        "PVE_TOKEN_ID": "root@pam!mcp",
        "PVE_TOKEN_SECRET": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
      }
    }
  }
}

Deep Agents CLI: add this to ~/.deepagents/.mcp.json (all projects) or to .mcp.json in a project. ${VAR} values are read from your shell or ~/.deepagents/.env, so secrets stay out of the file.

{
  "mcpServers": {
    "proxmox": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/bigbatmanorg/pve-mcp@v0.1.0",
        "pve-mcp"
      ],
      "env": {
        "PVE_HOST": "${PVE_HOST}",
        "PVE_TOKEN_ID": "${PVE_TOKEN_ID}",
        "PVE_TOKEN_SECRET": "${PVE_TOKEN_SECRET}"
      }
    }
  }
}

Python library (pip install deepagents langchain-mcp-adapters):

import asyncio
import os

from deepagents import create_deep_agent
from langchain_mcp_adapters.client import MultiServerMCPClient

client = MultiServerMCPClient({
    "proxmox": {
        "transport": "stdio",
        "command": "uvx",
        "args": ["--from", "git+https://github.com/bigbatmanorg/pve-mcp@v0.1.0", "pve-mcp"],
        "env": {
            "PVE_HOST": os.environ["PVE_HOST"],
            "PVE_TOKEN_ID": os.environ["PVE_TOKEN_ID"],
            "PVE_TOKEN_SECRET": os.environ["PVE_TOKEN_SECRET"],
        },
    }
})


async def main():
    tools = await client.get_tools()
    agent = create_deep_agent(model="anthropic:claude-sonnet-5-5", tools=tools)
    result = await agent.ainvoke({"messages": [{"role": "user", "content": "What can you do?"}]})
    print(result["messages"][-1].content)


asyncio.run(main())

Save this as agent.yaml, then run docker agent run agent.yaml:

agents:
  root:
    model: anthropic/claude-sonnet-5-5
    description: Assistant with the proxmox MCP tools
    instruction: Use the proxmox tools to answer the user's requests.
    toolsets:
      - type: mcp
        command: uvx
        args:
          - "--from"
          - "git+https://github.com/bigbatmanorg/pve-mcp@v0.1.0"
          - "pve-mcp"
        env:
          PVE_HOST: "pve.example.lan"
          PVE_TOKEN_ID: "root@pam!mcp"
          PVE_TOKEN_SECRET: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

Add this to ~/.hermes/config.yaml, then run /reload-mcp in a chat:

mcp_servers:
  proxmox:
    command: uvx
    args: ["--from", "git+https://github.com/bigbatmanorg/pve-mcp@v0.1.0", "pve-mcp"]
    env:
      PVE_HOST: "pve.example.lan"
      PVE_TOKEN_ID: "root@pam!mcp"
      PVE_TOKEN_SECRET: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

MCP Servers → Configure (cline_mcp_settings.json / mcp_settings.json):

{
  "mcpServers": {
    "proxmox": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/bigbatmanorg/pve-mcp@v0.1.0",
        "pve-mcp"
      ],
      "env": {
        "PVE_HOST": "pve.example.lan",
        "PVE_TOKEN_ID": "root@pam!mcp",
        "PVE_TOKEN_SECRET": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
      }
    }
  }
}

Add this to settings.json:

{
  "context_servers": {
    "proxmox": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/bigbatmanorg/pve-mcp@v0.1.0",
        "pve-mcp"
      ],
      "env": {
        "PVE_HOST": "pve.example.lan",
        "PVE_TOKEN_ID": "root@pam!mcp",
        "PVE_TOKEN_SECRET": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
      }
    }
  }
}

Add this to opencode.json (project) or ~/.config/opencode/opencode.json:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "proxmox": {
      "type": "local",
      "command": [
        "uvx",
        "--from",
        "git+https://github.com/bigbatmanorg/pve-mcp@v0.1.0",
        "pve-mcp"
      ],
      "environment": {
        "PVE_HOST": "pve.example.lan",
        "PVE_TOKEN_ID": "root@pam!mcp",
        "PVE_TOKEN_SECRET": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
      },
      "enabled": true
    }
  }
}

Run goose configure → Add Extension → Command-line Extension, with the command uvx --from git+https://github.com/bigbatmanorg/pve-mcp@v0.1.0 pve-mcp. Or add this to ~/.config/goose/config.yaml:

extensions:
  proxmox:
    type: stdio
    enabled: true
    cmd: uvx
    args: ["--from", "git+https://github.com/bigbatmanorg/pve-mcp@v0.1.0", "pve-mcp"]
    envs:
      PVE_HOST: "pve.example.lan"
      PVE_TOKEN_ID: "root@pam!mcp"
      PVE_TOKEN_SECRET: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

Most clients take the standard mcpServers JSON shown for Claude Desktop. Some use servers instead.

To install once instead of fetching on each start:

uv tool install git+https://github.com/bigbatmanorg/pve-mcp@v0.1.0

then use "command": "pve-mcp", "args": [].

From a checkout: "command": "uv", "args": ["run", "--directory", "/abs/path/pve-mcp", "pve-mcp"].

Related MCP server: ProxmoxMCP-Plus

Safety

Setting

Effect

default

read-only: only GET calls are sent

PVE_ALLOW_WRITES=true

POST/PUT allowed

confirm=true (per call)

also required for DELETE, stop/reset, rollback, node power

PVE_SSH_ENABLED=true

enables pve_node_ssh (also needs writes)

Task-returning calls wait for the task and fail with its log if it fails.

Configuration

PVE_HOST, PVE_PORT (8006), PVE_TOKEN_ID (user@realm!name), PVE_TOKEN_SECRET, PVE_VERIFY_SSL (false, true, or a CA bundle path such as a copy of /etc/pve/pve-root-ca.pem), PVE_TIMEOUT, PVE_SSH_USER, PVE_SSH_KEY, PVE_SSH_PORT, PVE_NODE_HOSTS (node:addr,...).

Tests

uv run pytest                                   # unit tests (mocked HTTP)
PVE_LAB=1 uv run pytest -m lab   # lifecycle tests on the lab only

Lab tests create and delete real objects; run them only against a disposable instance (PVE_LAB=1 with LABPVE_HOST, LABPVE_TOKEN_ID, LABPVE_TOKEN_SECRET).

License

AGPL-3.0-or-later. The bundled schema (src/pve_mcp/data/pve-api.json.gz) is generated from Proxmox VE's pve-docs API viewer data, which is AGPL-3.0 licensed, Copyright Proxmox Server Solutions GmbH.

Available Tools

20 tools
pve_agent_execA

Run a command inside a VM through the QEMU guest agent and return exit code and output. command is argv, e.g. ["systemctl", "is-active", "nginx"]. Requires the agent to be installed and enabled.

Inputs: vmid (required), command (required), timeout, input_data. Returns: object

pve_apiB

Call any PVE API endpoint, e.g. pve_api("GET", "/nodes/pve1/qemu/100/config"). Params are validated against the schema first. Task-returning calls are waited for unless wait=false. dry_run=true validates and shows the request without sending it.

Inputs: path (required), method (required), wait, params, +3 more.

pve_api_describeA

Show parameters, return shape and permissions for an endpoint. path may be a template (/nodes/{node}/qemu) or concrete (/nodes/pve1/qemu). verbose=true includes property-string sub-keys.

Inputs: path (required), method, verbose. Returns: object

pve_backupC

Back up guests with vzdump to a storage. Runs one job per node that hosts the guests.

Inputs: vmids (required), storage (required), mode, notes, +2 more. Returns: result

pve_cloneB

Clone a VM or container (templates give linked clones unless full=true). target is the destination node.

Inputs: vmid (required), full, name, newid, +4 more. Returns: object

pve_cluster_statusB

Cluster name, quorum, nodes (online, IP), PVE version, and whether writes are enabled here.

Inputs: no named parameters. Returns: object

pve_create_guestA

Create a VM (type="qemu") or container (type="lxc") on a node. params follow the PVE create API, e.g. VM: {"name": "web", "memory": 2048, "cores": 2, "scsi0": "local-lvm:32", "net0": "virtio,bridge=vmbr0", "ostype": "l26", "scsihw": "virtio-scsi-single"}; CT: {"ostemplate": "local:vztmpl/debian-13...tar.zst", "hostname": "ct1", "rootfs": "local-lvm:8", "net0": "name=eth0,bridge=vmbr0,ip=dhcp"}. Use pve_api_describe("/nodes/{node}/qemu", "POST") for every option. vmid defaults to the next free id.

Inputs: node (required), type (required), params (required), vmid, +3 more. Returns: object

pve_destroy_guestA

Delete a VM or container and its disks. It must be stopped. Needs confirm=true.

Inputs: vmid (required), purge, confirm, timeout, +1 more.

pve_guestB

Status of a VM or container (node and type are found automatically), plus its config and snapshots.

Inputs: vmid (required), include_config. Returns: object

pve_guest_configA

Read a guest's config, or change it: set={"memory": 4096, "net1": "virtio,bridge=vmbr0"}, delete=["ide2"]. pending=true shows changes waiting for a reboot. Pass the digest from a read to refuse the change if someone else edited the config meanwhile.

Inputs: vmid (required), set, delete, digest, +1 more.

pve_guest_powerA

Change a guest's power state and wait for it to finish. stop and reset are hard (need confirm=true); shutdown is a clean ACPI/init shutdown.

Inputs: vmid (required), action (required), wait, confirm, +1 more. Returns: object

pve_migrateA

Migrate a guest to another node. Running VMs migrate live when online=true; containers restart.

Inputs: vmid (required), target (required), online, timeout, +1 more.

pve_next_vmidB

The next free VM/CT id in the cluster.

Inputs: no named parameters. Returns: result

pve_node_sshA

Run a shell command on a node over SSH, for what the API cannot do (pct exec, logs, files). Needs PVE_SSH_ENABLED=true and PVE_ALLOW_WRITES=true, since commands can change the system.

Inputs: node (required), command (required), timeout. Returns: object

pve_resourcesB

Every guest, storage, node or SDN zone in the cluster, with usage figures (cluster/resources).

Inputs: type. Returns: result

pve_snapshotA

List, create, roll back or delete snapshots. rollback and delete need confirm=true. vmstate=true also saves RAM (VMs only).

Inputs: vmid (required), action, confirm, timeout, +3 more.

pve_storageB

Without storage: storage list with usage (per node, or cluster-wide). With storage: its contents, optionally filtered by content type (images, rootdir, iso, vztmpl, backup, snippets, import).

Inputs: node, content, storage.

pve_taskB

Status and log tail of a task by UPID. wait=true blocks until it finishes (up to timeout).

Inputs: upid (required), wait, timeout, log_lines. Returns: object

pve_tasksB

Recent tasks on one node, or across the cluster when node is omitted.

Inputs: node, vmid, limit, running, +1 more. Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.4/5.0

    Scored across 20 tools

    Disambiguation4/5

    Most tools target a distinct action (create, clone, migrate, destroy, power, snapshot, backup). There is some overlap in the read paths: pve_guest aggregates status+config+snapshots, which overlaps pve_guest_config and pve_snapshot, and pve_resources overlaps pve_cluster_status and pve_storage. The descriptions do clarify each tool's primary focus, so misselection risk is moderate rather than high.

    Naming Consistency4/5

    All names share the pve_ prefix and snake_case, which gives a predictable family. However, the second segment mixes noun-only forms (pve_storage, pve_tasks, pve_guest, pve_snapshot) with verb_noun forms (pve_create_guest, pve_destroy_guest, pve_guest_power), so the grammar is not uniform even though it reads clearly.

    Tool Count4/5

    20 tools is at the high end of the comfortable range, but the domain (VMs, containers, storage, snapshots, migrations, tasks, schema introspection) legitimately requires this breadth. Each tool maps to a real workflow rather than a trivial variant, so the count is justified though slightly heavy.

    Completeness4/5

    Lifecycle coverage is strong: create, clone, migrate, destroy, power, config, snapshot, backup, task inspection, plus a generic pve_api escape hatch and schema search/describe for anything not wrapped. Minor gaps: no dedicated backup restore and no first-class network/SDN/firewall tools (only reachable via pve_api or ssh), which agents can work around but may not discover easily.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables comprehensive management of Proxmox virtualization environments, including VM and container lifecycle, snapshots, backups, monitoring, and OpenAPI integration.
      12
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Exposes 338 tools for the Proxmox Virtual Environment API, enabling users to inspect and control nodes, QEMU VMs, LXC containers, storage and backups, clustering and HA, users and permissions, firewall rules, disks, Ceph, ACME certificates, SDN, notifications, and resource pools. Includes lifecycle actions, snapshots, migration, metrics, task tracking, and guest-agent command execution, with an optional read-only mode that blocks all writes.
      MIT