Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ at `<app>.<HOLA_BASE_DOMAIN>` — **no host ports**, ingress is Traefik-only).
| 🔗 n8n | Workflow automation tool |
| 📄 Paperless-ngx | Document management — scan, index, and archive your documents |
| 📅 Postiz | Open-source social media scheduling tool with AI features |
| 🧠 Proxmox MCP | MCP server for Proxmox VE — manage nodes, VMs, containers, snapshots and backups from AI agents |
| 🐧 Ubuntu Webtop | A full Ubuntu desktop environment in your browser |
| 📈 Uptime Kuma | Self-hosted uptime monitoring and status pages |
| 🔐 Vaultwarden | Self-hosted, Bitwarden-compatible password manager |
Expand Down
3 changes: 3 additions & 0 deletions icons/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,6 @@ the apps they represent. SVGs were sourced from community icon sets:
2. Set `"icon": "https://raw.githubusercontent.com/try-hola/apps/main/icons/<name>.svg"`
in `src/<name>/src/manifest.json`.
3. Regenerate the index: `./bin/build-catalog.sh`.
- `proxmox-mcp.svg`: upstream's own logo from
[RekklesNA/ProxmoxMCP-Plus](https://github.com/RekklesNA/ProxmoxMCP-Plus/blob/main/docs/assets/logo-proxmoxmcp-plus-400.png)
(MIT). A 400² PNG in an SVG wrapper (~75KB), the same approach as `remo.svg`.
1 change: 1 addition & 0 deletions icons/proxmox-mcp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
83 changes: 83 additions & 0 deletions src/proxmox-mcp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# proxmox-mcp

[ProxmoxMCP-Plus](https://github.com/RekklesNA/ProxmoxMCP-Plus) — an MCP server for
Proxmox VE (nodes, VMs, containers, snapshots, backups, task logs) — packaged for Hola
so an AI agent can manage your hypervisor through one authenticated endpoint.

## Layout (Hola app package format)

```
src/proxmox-mcp/
├── package.json # name + version + OCI annotations
└── src/
├── compose.yaml # one service, MCP Streamable HTTP mode, no host ports
└── manifest.json # Hola defaults (ingress port, wizard fields, auth)
```

## How it is exposed

- **Endpoint:** `https://proxmox-mcp.<HOLA_BASE_DOMAIN>/mcp` (MCP Streamable HTTP).
Traefik is the only ingress; the container publishes no host port.
- **Auth:** the manifest declares `forward-auth` with `bypassPaths: ["/mcp"]` — the same
pattern `remo` uses for its setup API. `/mcp` is exempted from the interactive Authentik
login because the app protects it with its **own** bearer credential (`MCP_API_KEY`,
generated at install); everything else on the host stays behind SSO. MCP clients are not
browsers, so a cookie-based forward-auth gate would only lock them out.
- **Client config:** point your MCP client at the URL above with
`Authorization: Bearer <MCP_API_KEY>`; the key is on the deployment's configuration tab.

## What it can do — and the blast radius

This app hands whoever holds `MCP_API_KEY` control of your hypervisor, so it ships
defensively:

- `MCP_TOOL_DENYLIST` defaults to `delete_vm,delete_container,delete_snapshot,rollback_snapshot`.
Clear it in the wizard (advanced) to expose everything.
- Guest command execution (`execute_vm_command`, `execute_container_command`) is gated
by the server's command policy, which defaults to deny-all
([upstream docs](https://github.com/RekklesNA/ProxmoxMCP-Plus/blob/main/docs/container-command-execution.md)).
Two advanced wizard fields expose it: `COMMAND_POLICY_MODE` (`deny_all` | `allowlist` |
`audit_only`) and `COMMAND_POLICY_ALLOW_PATTERNS` (comma-separated regexes). An
agent that provisions VMs typically needs exactly one allowlisted command — appending
an SSH key to a freshly cloned guest's `authorized_keys` — since the MCP tool set can
clone, start, stop, snapshot and delete VMs but has no tool to write cloud-init
settings (`sshkeys`, `ciuser`, `ipconfig0`) on the clone.
- Use a **dedicated Proxmox user + API token** with only the ACLs the exposed tools need
(`PVEAuditor` for read-only use; add `PVEVMAdmin` on the pools you want managed).
Do not point it at `root@pam`.
- DNS-rebinding protection is on, allowing only the public host Traefik serves.

## Configuration

**Self-signed Proxmox certificate?** Turn *Verify Proxmox TLS certificate* off **and**
*Allow unverified TLS (dev mode)* on. The upstream server refuses to start with
verification off unless its dev-mode flag is set (verified: the container exits with
"Insecure TLS configuration blocked" otherwise). Dev mode gates only that check.

All settings are wizard fields (`manifest.defaultEnv`): the Proxmox host, API user,
token name and secret are required; the MCP key is generated; port, TLS verification
and the tool denylist are advanced. The image's `PROXMOX_MCP_CONFIG` file is never
mounted, so the server reads exactly these environment variables.

## State

None worth keeping: the optional job store is an SQLite file inside the container's
own working directory and is recreated on restart. No data volume, so nothing to back
up (`accepts` is intentionally empty).

## Pre-release: fork image

Version `0.1.0-beta.1` (channel `beta`) runs the image built from the
[pofallon/ProxmoxMCP-Plus](https://github.com/pofallon/ProxmoxMCP-Plus) fork at
`v0.5.15-hola.1`: upstream v0.5.15 plus the VM provisioning tools proposed upstream in
[RekklesNA/ProxmoxMCP-Plus#127](https://github.com/RekklesNA/ProxmoxMCP-Plus/pull/127)
(`update_vm_config`, `get_vm_ip_addresses`, `get_next_vmid`), which an agent needs to take a
cloud-init template clone to a reachable guest. The stable version returns to the upstream
image once a release includes them.

## Aggregation (later)

Hola's planned aggregated MCP gateway (try-hola/hola spec 002) will front every
MCP-capable app behind one endpoint with Authentik OAuth. This package already speaks
the transport that gateway requires, so joining it will be a manifest `mcp` block, not a
repackage.
13 changes: 13 additions & 0 deletions src/proxmox-mcp/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "proxmox-mcp",
"version": "0.1.0-beta.1",
"description": "Proxmox MCP — MCP server for Proxmox VE, so AI agents can manage nodes, VMs, containers, snapshots and backups (Hola app package)",
"license": "MIT",
"oci": {
"annotations": {
"org.opencontainers.image.title": "proxmox-mcp",
"org.opencontainers.image.description": "MCP server for Proxmox VE — manage nodes, VMs, containers, snapshots and backups from AI agents",
"org.opencontainers.image.version": "${npm_package_version}"
}
}
}
59 changes: 59 additions & 0 deletions src/proxmox-mcp/src/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
services:
proxmox-mcp:
# ProxmoxMCP-Plus (MIT) — https://github.com/RekklesNA/ProxmoxMCP-Plus
# Multi-arch (linux/amd64, linux/arm64); runs as the unprivileged `proxmoxmcp` user.
#
# PRE-RELEASE (channel `beta`): built from the pofallon/ProxmoxMCP-Plus fork at
# v0.5.15-hola.1 = upstream v0.5.15 + the VM provisioning tools proposed in
# RekklesNA/ProxmoxMCP-Plus#127 (update_vm_config, get_vm_ip_addresses,
# get_next_vmid). Switch back to the upstream image once a release includes them.
image: ghcr.io/pofallon/proxmoxmcp-plus:v0.5.15-hola.1@sha256:d36d2fdba3e7dd5f503ef869f85197f9fea97e442b0c5ed17d7826c5623a7aff
environment:
# Native MCP Streamable HTTP mode (the image defaults to its OpenAPI/REST
# bridge on 8811, which Hola does not expose). The MCP endpoint is /mcp.
PROXMOX_MCP_MODE: mcp-http
MCP_HOST: 0.0.0.0
MCP_PORT: "8000"
MCP_TRANSPORT: STREAMABLE_HTTP
# Proxmox VE connection — all from the install wizard (manifest defaultEnv).
# The image's PROXMOX_MCP_CONFIG points at a config file that is never
# mounted here, so the loader falls back to these variables.
PROXMOX_HOST: "${PROXMOX_HOST}"
PROXMOX_PORT: "${PROXMOX_PORT}"
PROXMOX_USER: "${PROXMOX_USER}"
PROXMOX_TOKEN_NAME: "${PROXMOX_TOKEN_NAME}"
PROXMOX_TOKEN_VALUE: "${PROXMOX_TOKEN_VALUE}"
PROXMOX_VERIFY_SSL: "${PROXMOX_VERIFY_SSL}"
# Upstream blocks verify_ssl=false unless dev_mode is on; the wizard exposes
# both so an operator with a self-signed Proxmox cert can flip them together.
PROXMOX_DEV_MODE: "${PROXMOX_DEV_MODE}"
# Inbound bearer credential every MCP client must present. Generated by
# Hola at install (manifest defaultEnv). /mcp is exempted from Authentik's
# forward-auth precisely because this key protects it.
MCP_API_KEY: "${MCP_API_KEY}"
# Tools hidden from every client (comma-separated). Defaults to the
# destructive ones; clear it in the wizard to expose everything.
MCP_TOOL_DENYLIST: "${MCP_TOOL_DENYLIST}"
# Guest-agent command execution policy (deny_all | allowlist | audit_only)
# and the allowlist patterns; both from the wizard's advanced fields.
COMMAND_POLICY_MODE: "${COMMAND_POLICY_MODE}"
COMMAND_POLICY_ALLOW_PATTERNS: "${COMMAND_POLICY_ALLOW_PATTERNS}"
# DNS-rebinding protection: only the public host Traefik serves, plus
# loopback for the container healthcheck below. Each host is listed both
# bare and with `:*` — the MCP SDK matches a `host:*` pattern only when
# the Host header carries a port, and Traefik forwards the bare host
# (verified: bare-host-only allowlists answer 421 behind a proxy).
MCP_DNS_REBINDING_PROTECTION: "true"
MCP_ALLOWED_HOSTS: "${HOLA_APP_HOST},${HOLA_APP_HOST}:*,127.0.0.1,127.0.0.1:*,localhost,localhost:*"
# Browser origin, for a UI client; non-browser MCP clients send no Origin
# header and pass regardless.
MCP_ALLOWED_ORIGINS: "https://${HOLA_APP_HOST}"
healthcheck:
# The MCP transport has no unauthenticated health route; any HTTP answer
# on /mcp (a 401 without the bearer key) proves the server is up, so this
# deliberately does NOT use `curl -f`.
test: ["CMD", "curl", "-sS", "-o", "/dev/null", "http://127.0.0.1:8000/mcp"]
interval: 30s
timeout: 5s
retries: 5
start_period: 15s
164 changes: 164 additions & 0 deletions src/proxmox-mcp/src/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
{
"name": "proxmox-mcp",
"version": "0.1.0-beta.1",
"title": "Proxmox MCP",
"description": "MCP server for Proxmox VE — let AI agents manage nodes, VMs, containers, snapshots and backups over one authenticated endpoint",
"icon": "https://raw.githubusercontent.com/try-hola/apps/main/icons/proxmox-mcp.svg",
"category": "developer-tools",
"tags": [
"mcp",
"proxmox",
"ai",
"virtualization"
],
"ingress": {
"service": "proxmox-mcp",
"port": 8000
},
"defaultEnv": [
{
"key": "PROXMOX_HOST",
"value": "",
"isSecret": false,
"type": "string",
"label": "Proxmox host",
"placeholder": "pve.example.internal",
"description": "Hostname or IP of the Proxmox VE API this server talks to. Must be reachable from the Hola host's Docker network.",
"required": true
},
{
"key": "PROXMOX_USER",
"value": "",
"isSecret": false,
"type": "string",
"label": "Proxmox API user",
"placeholder": "mcp@pve",
"description": "The user the API token belongs to, in Proxmox's user@realm form (e.g. mcp@pve). Prefer a dedicated user with only the privileges the tools you expose need, not root@pam.",
"required": true
},
{
"key": "PROXMOX_TOKEN_NAME",
"value": "",
"isSecret": false,
"type": "string",
"label": "API token name",
"placeholder": "hola-mcp",
"description": "Name of the API token created for that user (Datacenter → Permissions → API Tokens). With 'Privilege Separation' on, grant the token's own ACLs.",
"required": true
},
{
"key": "PROXMOX_TOKEN_VALUE",
"value": "",
"isSecret": true,
"type": "string",
"label": "API token secret",
"description": "The token's secret value (the UUID shown once when the token is created). Stored as a secret; never returned by the API.",
"required": true
},
{
"key": "MCP_API_KEY",
"value": "",
"isSecret": true,
"type": "string",
"label": "MCP bearer key",
"description": "The credential every MCP client must send as `Authorization: Bearer <key>` to https://<this app's address>/mcp. Generated automatically; copy it from this deployment's configuration into your client. Anyone holding it can drive Proxmox through the tools you expose.",
"required": true,
"generate": {
"kind": "hex",
"length": 32
}
},
{
"key": "MCP_TOOL_DENYLIST",
"value": "delete_vm,delete_container,delete_snapshot,rollback_snapshot",
"isSecret": false,
"type": "string",
"label": "Hidden tools",
"description": "Comma-separated MCP tool names that are never exposed to clients. Defaults to the destructive ones; clear it to expose every tool. Guest command execution (execute_vm_command / execute_container_command) is additionally gated by the server's own command policy, which defaults to deny-all.",
"advanced": true
},
{
"key": "COMMAND_POLICY_MODE",
"value": "deny_all",
"isSecret": false,
"type": "enum",
"options": [
{
"value": "deny_all",
"label": "Deny all",
"description": "execute_vm_command / execute_container_command never run anything."
},
{
"value": "allowlist",
"label": "Allowlist",
"description": "Only commands matching one of the allow patterns run."
},
{
"value": "audit_only",
"label": "Audit only",
"description": "Everything runs; each command is logged. Only for a trusted, private endpoint."
}
],
"label": "Guest command policy",
"description": "Governs the guest-agent command tools. Leave at Deny all unless an agent must run commands inside guests (e.g. injecting an SSH key into a freshly cloned VM); then use Allowlist with narrow patterns.",
"advanced": true
},
{
"key": "COMMAND_POLICY_ALLOW_PATTERNS",
"value": "",
"isSecret": false,
"type": "string",
"label": "Allowed command patterns",
"description": "Comma-separated regular expressions a guest command must match when the policy is Allowlist. Keep them narrow — e.g. ^mkdir -p /home/hola/\\.ssh$,^tee -a /home/hola/\\.ssh/authorized_keys$.",
"placeholder": "^tee -a /home/hola/\\.ssh/authorized_keys$",
"advanced": true
},
{
"key": "PROXMOX_PORT",
"value": "8006",
"isSecret": false,
"type": "port",
"label": "Proxmox API port",
"description": "Port of the Proxmox VE API (8006 by default).",
"advanced": true
},
{
"key": "PROXMOX_VERIFY_SSL",
"value": "true",
"isSecret": false,
"type": "boolean",
"label": "Verify Proxmox TLS certificate",
"description": "Turn off only for a Proxmox host that still uses its self-signed certificate. Upstream refuses to start with verification off unless 'Allow unverified TLS (dev mode)' below is also on — flip both together.",
"advanced": true
},
{
"key": "PROXMOX_DEV_MODE",
"value": "false",
"isSecret": false,
"type": "boolean",
"label": "Allow unverified TLS (dev mode)",
"description": "Must be on whenever 'Verify Proxmox TLS certificate' is off: the server's config check blocks unverified TLS otherwise and the container exits at startup. It has no other effect. Leave off when verification is on.",
"advanced": true
}
],
"defaults": {
"ports": [
{
"container": 8000,
"protocol": "tcp"
}
],
"volumes": []
},
"auth": {
"mode": "forward-auth",
"forwardAuth": {
"bypassPaths": [
"/mcp"
]
}
},
"upgrade": {
"upgradeNotesUrl": "https://github.com/RekklesNA/ProxmoxMCP-Plus/releases"
}
}
Loading