An AI agent for Proxmox VE. No shell. No token.
An MCP server for Claude Code, Codex and Cursor. The tools an agent can see are decided by your API key's role, and every change stops at Confirm until a human presses it, before anything reaches Proxmox.
lab node dclab-01 · cpu — · ram — · guest — · Proxmox VE connecting…
- action
- guest_power
- target
- VM 101 · web-01 · dclab-01
- operation
- shutdown
- requested
- via MCP in the editor
Dry-run: VM 101 has been up 14 days, has 2 snapshots, is not in HA. The node keeps quorum. Graceful shutdown, not a hard stop.
recorded in the audit log · who, when, from which IP, with what parameters
Four mandatory stages, no shortcut.
No endpoint executes anything directly. POST /actions only creates a proposal; running it takes a separate confirm call that only a human can make. The four stages beside this apply to every change, whether it comes from chat, the dashboard or MCP.
The request is checked against live Proxmox state, not against the model's assumptions. A VM that is already stopped is refused here.
You see the exact steps before anything runs, with warnings about cluster quorum, HA, remaining RAM and VM locks.
High-risk actions make you retype the VM name. A model cannot press this button.
Recorded permanently: who pressed Confirm, when, from which IP, with what parameters, and the outcome.
Two ways to call it, one permission model.
Both call the same endpoint, POST /api/v1/actions, are limited by the same API key role, and both stop before execution. Installing MCP opens no new permission path — it is just another way to call the API that already exists.
Built-in AI assistant
A chat panel inside the app. Each organization uses its own model API key, and the context is limited to that organization's infrastructure.
- 43 tools, 21 slash commands
- 9 commands answered straight from the Proxmox REST API — without calling a model
- Cannot execute anything: every change becomes a proposal
Local MCP server
Runs locally with no dependencies, and asks the platform for its tool list on every start.
- Claude Code · Codex · Cursor
- The tool list is decided by the API key role
- A Readonly key never receives a tool that writes
One command, every AI client registered.
The installer verifies the key against the platform before writing anything, then registers the server with every AI client it finds on that machine. The output below is the real thing, not an invented example.
Run
curl -fsSL https://proxmox.ai.id/mcp.sh | sh -s -- --key pak_xxxxxxxxxxxx
- The key is verified first — a typo fails here, not inside your AI client
- Credentials are written only to
~/.proxmox-ai/config.jsonmode600, never into an AI client's config file - Without Node the installer falls back to the
curlpath, which has no dependencies, and installs thepmxcommand. Force it with--curl-only - Self-hosting: add
--url https://panel.anda.id
What it prints
# curl -fsSL https://proxmox.ai.id/mcp.sh | sh -s -- --key pak_…
Checking https://proxmox.ai.id/api/v1 …
✓ key accepted — Default · role operator, 55 tools
✓ credentials saved — ~/.proxmox-ai/config.json (owner only)
Registering with the AI clients on this machine:
✓ Claude Code — registered (~/.claude.json)
· Claude Desktop — not installed
· Cursor — not installed
· Codex CLI — not installed
Restart your AI client, then ask it something.
This key may propose changes. Nothing runs until a
human confirms it in the dashboard.
What gets written to each client
The installer adds one server entry and leaves every other setting in that file alone.
Written to ~/.claude.json under the key mcpServers:
{
"mcpServers": {
"proxmox-ai": {
"command": "/usr/bin/node",
"args": ["~/.proxmox-ai/src/bin/proxmox-ai-mcp.mjs"]
}
}
}
Plugin /pmx-ai is available separately and already carries the read → propose → confirm flow:
/plugin install proxmox-ai --marketplace hamimistimewa/proxmox-ai-mcp
Codex uses TOML. The installer appends this block to ~/.codex/config.toml:
[mcp_servers.proxmox-ai]
command = "node"
args = ["~/.proxmox-ai/src/bin/proxmox-ai-mcp.mjs"]
Verify from the same terminal before opening Codex:
pmx get /nodes
Same format as Claude Code, only the file location differs:
| Client | File | Key |
|---|---|---|
| Claude Desktop | ~/Library/Application Support/Claude/claude_desktop_config.json | mcpServers |
| Cursor | ~/.cursor/mcp.json | mcpServers |
| Windsurf | ~/.codeium/windsurf/mcp_config.json | mcpServers |
| Gemini CLI | ~/.gemini/settings.json | mcpServers |
| VS Code (Copilot) | ~/Library/Application Support/Code/User/mcp.json | servers |
Without Node there is no MCP server; the agent uses the pmx command, which needs nothing but curl:
curl -fsSL https://proxmox.ai.id/mcp.sh | sh -s -- --key pak_xxxxxxxxxxxx --curl-only
pmx get /nodes
pmx propose guest_power vmid=101 operation=shutdown
pmx show 42
pmx confirm 42
Same commands, same API, same RBAC. The only thing missing is the tool-list integration inside the client.
What it actually looks like when an agent asks for a change
Both responses below were recorded straight from the production API.
Checked against live Proxmox state
# POST /api/v1/actions — vmid 100 is stopped
HTTP 422
{
"error": "VM 100 is stopped — only a
running guest can be shut down"
}
An agent cannot propose the impossible: it is validated against live state, not against a model's guess.
Accepted as a proposal, not an execution
HTTP 201
{
"id": 10110,
"status": "proposed",
"expires_in": 900,
"summary": "Start vm 100 (poc-01) on dclab-01",
"plan": {
"status_before": "stopped",
"node": "dclab-01", "agent": true
},
"warnings": [],
"confirmed_by": null
}
status: proposed and confirmed_by: null — nothing has run. The proposal expires in 900 seconds if it is not confirmed.
A tool that is not allowed never reaches the model.
The tool list is not stored in the MCP server. On every start it asks the platform, based on the API key's role — Readonly 49 tools, Operator 55, Superadmin 67.
Which means a tool that is not allowed never appears in the list the model sees. It is not refused when called — it simply is not there. Prompt injection has nothing to call.
Superadmin does get every tool, but still only within its own organization: that boundary is enforced by row-level security in the database, not by the tool list.
These numbers are read straight from the MCP manifest.
| Key role | Tools | Prompts | Missing from the list |
|---|---|---|---|
| Readonly | 49 | 6 | propose_action and every prompt that changes state |
| Operator | 55 | 7 | tools that change backup datastores and organization settings |
| Superadmin | 67 | 7 | nothing — but still limited to one organization |
| Platform | 27 | 7 | infrastructure-contents tools — this key manages organizations, not the guests inside them |
Built from scratch — and taken apart at JDD 2026.
The session “Build an AI Agent to Run Your Own Proxmox”: we start from nothing — define the tools, wire them to the Proxmox REST API, then build the guardrails. You leave with working code, not slides.
Already have access? Sign in through your organization link from the invitation email.