PDM MCP Server
Provides read-only integration with Proxmox Datacenter Manager (PDM), allowing listing and inspection of remotes, resources, QEMU VMs, nodes, LXC containers, storages, and remote summaries.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@PDM MCP Serverlist all VMs across all remotes"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
PDM MCP Server
Ejecutar con Docker
Editá las credenciales PDM y ejecutá:
docker run -d \
--name pdm-mcp-server \
--restart unless-stopped \
--pull always \
-p 3000:3000 \
-e PDM_URL="https://pdm.example.com:8443" \
-e PDM_TOKEN_ID='readonly@pdm!mcp' \
-e PDM_TOKEN_SECRET='replace-with-token-secret' \
-e PDM_TLS_INSECURE="false" \
-e MCP_AUTH_MODE="disabled" \
jer3m/pdm-mcp-server:latestEl servidor queda disponible en http://localhost:3000/mcp. Verificá que arrancó con:
curl http://localhost:3000/healthzSi PDM utiliza un certificado interno o autofirmado, cambiá PDM_TLS_INSECURE a true. Para evitar guardar el secreto en el historial del shell, también podés pasar estas mismas variables mediante --env-file.
Servidor MCP estrictamente read-only para Proxmox Datacenter Manager (PDM). Expone Streamable HTTP stateless en /mcp; cada request recibe un servidor y transporte nuevos, por lo que no depende de sesiones en memoria.
Related MCP server: Proxmox MCP Server
Autenticación y autorización por request
Elegí explícitamente MCP_AUTH_MODE=disabled para conservar el comportamiento sin autenticación, o MCP_AUTH_MODE=jwt para restringir cada request a los remotes permitidos. Si el modo falta, es desconocido o la configuración JWT es inválida, el servidor no arranca; nunca vuelve automáticamente al acceso sin restricciones. .env.example y Compose seleccionan disabled por compatibilidad.
Para activar JWT, además de las credenciales PDM, configurá:
MCP_AUTH_MODE=jwt
MCP_JWT_SECRET=<server-side-random-signing-secret>
MCP_JWT_AUDIENCE=pdm-mcpReemplazá el marcador del secreto por un valor aleatorio de al menos 32 bytes, generado y almacenado en el servidor. MCP_JWT_AUDIENCE es opcional y tiene como valor predeterminado pdm-mcp; el secreto es obligatorio en modo JWT. Compose recibe estas variables del entorno o de .env. Para ejecutar Node directamente, cargalas en el entorno o usá node --env-file=.env dist/index.js después del build.
El cliente debe enviar Authorization: Bearer <jwt> en cada request a /mcp, incluyendo initialize y tools/list. Se acepta únicamente HS256 y se verifican firma, audiencia (aud), vencimiento obligatorio (exp, segundos Unix) y nbf si existe. El claim pdm_remotes debe ser un array no vacío de strings no vacíos, por ejemplo {"pdm_remotes":["LAB-A"]} o {"pdm_remotes":["LAB-A","LAB-B"]}. Los nombres se comparan exactamente, distinguiendo mayúsculas y minúsculas; no se aceptan espacios al principio o al final. Emití tokens de corta duración con la audiencia configurada y un vencimiento futuro. Un JWT ausente, inválido o sin alcance válido recibe HTTP 401 con un error genérico. GET /healthz permanece público.
El modo JWT está pensado para aplicaciones que derivan la autorización del lado del servidor. El backend emisor debe obtener los remotes permitidos de su propia política de acceso: nunca debe dejar que el usuario final elija su claim pdm_remotes. El secreto de firma nunca debe exponerse a browsers ni LLMs. Usá HTTPS delante de /mcp para proteger los bearer tokens en tránsito. Esta integración verifica tokens emitidos por tu aplicación; no implementa un servidor OAuth ni un flujo de login.
Una misma instancia MCP puede atender múltiples alcances aislados. Cada request crea su propio cliente PDM con el alcance del JWT verificado. El MCP vuelve a aplicar la autorización aunque el caller o el LLM proporcionen un argumento remote: un remote no permitido produce Access denied. sin consultar PDM ni revelar otros remotes. Los argumentos de las tools no pueden ampliar el alcance.
En modo JWT, list_resources, list_vms, list_nodes, list_containers y list_storages sin remote consultan secuencialmente solo los remotes permitidos y agregan sus resultados; no utilizan el inventario global ni lo filtran después. En esas consultas max_age no aplica, porque se usan las rutas existentes por remote. list_remotes consulta la colección de configuración y devuelve únicamente las entradas con ID autorizado, sanitizando secretos. Los errores de autenticación y los errores de requests PDM en modo JWT son genéricos y no incluyen tokens, headers, secretos ni cuerpos de respuesta.
Con MCP_AUTH_MODE=disabled, se mantienen el inventario global y el acceso sin alcance previo. Restringí ese endpoint a clientes confiables mediante la red o un reverse proxy. Los permisos del token PDM siguen siendo el límite superior y todas las tools siguen siendo read-only.
Tools
Tool | Fuente PDM | Uso |
|
| Remotes configurados |
|
| Inventario global cacheado o de un remote |
| Inventario global o | VMs QEMU, globales o por remote |
|
| Runtime y configuración sanitizada |
| Inventario global o | Nodos globales o por remote |
|
| Estado detallado del nodo |
| Inventario global o | Containers LXC, globales o por remote |
|
| Runtime y configuración sanitizada |
| Inventario global o | Estado general, sin listar contenido |
| Inventario global y | Estado de un storage, sin su contenido |
|
| Totales y capacidad física sin listar cada recurso |
|
| Tareas recientes y activas (cluster o por nodo/vmid) |
|
| Estado detallado de una tarea por UPID |
En la tabla, ... significa /api2/json/pve/remotes/{remote}. La colección de configuración de remotes es distinta y conserva /api2/json/remotes/remote. Los nombres de remote, node y storage se codifican como segmentos URL. Las respuestas incluyen texto JSON por compatibilidad y structuredContent para clientes MCP que lo soportan.
El inventario global envía max-age=300 por defecto para reutilizar el cache de PDM y evitar recolectar todos los remotes en cada consulta. list_resources acepta max_age; usá 0 cuando necesites forzar una actualización. Con remote se consulta directamente /pve/remotes/{remote}/resources, evitando el fan-out global. PDM_TIMEOUT_MS sigue siendo configurable y mantiene su default de 15 segundos.
El inventario global envía max-age=300 por defecto para reutilizar el cache de PDM y evitar recolectar todos los remotes en cada consulta. list_resources acepta max_age; usá 0 cuando necesites forzar una actualización. Con remote se consulta directamente /pve/remotes/{remote}/resources, evitando el fan-out global. PDM_TIMEOUT_MS sigue siendo configurable y mantiene su default de 60 segundos.
get_vm, get_container y las consultas de tareas eliminan claves sensibles como passwords, tokens, secrets, claves SSH y valores equivalentes embebidos. El servidor nunca incluye el header de autorización ni el body de un error PDM en sus errores.
Respuestas compactas
Las tools de listado devuelven una frase corta en content y los datos una sola vez en structuredContent. La vista predeterminada es siempre compacta:
list_vmsylist_containers:summary,hardware,runtimeofull.list_nodes:summary,capacity,runtimeofull.list_storages:summary,capacityofull.list_resourcesylist_tasks:summaryofull.
summary sirve para descubrir e identificar recursos. hardware/capacity normalizan bytes a GiB; runtime normaliza ratios a porcentajes. full debe pedirse explícitamente y continúa sanitizando secretos. Para el detalle de un único elemento usá su tool get_*.
Para preguntas agregadas como cantidad de VMs encendidas o RAM física total, preferí get_remote_summary: usa solamente los listados de nodes, QEMU y LXC, sin hacer una request por guest.
Requisitos y configuración
Node.js 24 o posterior.
PDM 1.x accesible por HTTPS.
Usuario y token dedicados con rol
Auditorpropagado sobre/resource. Los permisos del token nunca superan los del usuario.
Variables requeridas:
PDM_URL=https://pdm.example.com:8443
PDM_TOKEN_ID=readonly@pdm!mcp
PDM_TOKEN_SECRET=replace-meVariables opcionales:
PDM_TLS_INSECURE=false
PDM_TIMEOUT_MS=15000
PDM_TIMEOUT_MS=60000
MCP_PORT=3000PDM_TLS_INSECURE=true deshabilita la validación TLS solamente para esa conexión PDM; usalo únicamente con certificados internos/autofirmados.
Desarrollo y prueba manual
npm install
npm test
npm startComprobá primero la salud:
curl.exe http://localhost:3000/healthzPara ejecutar tools sin poner credenciales PDM en comandos, iniciá MCP Inspector:
npx @modelcontextprotocol/inspectorEn Inspector elegí Streamable HTTP, usá http://localhost:3000/mcp, conectá y abrí Tools. Pruebas sugeridas:
list_resources {}
list_vms {"view":"summary"}
list_vms {"remote":"LAB-A"}
list_vms {"remote":"LAB-A","view":"hardware"}
list_vms {"remote":"LAB-A","view":"runtime"}
get_vm {"remote":"LAB-A","vmid":105}
list_nodes {"view":"summary"}
list_nodes {"remote":"LAB-A"}
list_containers {}
list_storages {}
list_storages {"remote":"LAB-A","node":"pve-test-01"}
get_remote_summary {"remote":"LAB-A"}
list_tasks {"remote":"LAB-A"}
list_tasks {"remote":"LAB-A","errors_only":true}
list_tasks {"remote":"LAB-A","node":"pve-test-01","vmid":105}
get_task {"remote":"LAB-A","upid":"UPID:pve-test-01:00001234:00005678:65A4B3C2:vzdump:105:readonly@pdm!mcp:"}Para Codex:
[mcp_servers.pdm]
url = "http://mcp.example.internal:3000/mcp"En modo JWT, configurá también el bearer token emitido por tu backend en el cliente MCP. En modo disabled, publicá el puerto 3000 únicamente en una red confiable o restringilo mediante firewall/reverse proxy.
Docker y CI
La imagen pública es jer3m/pdm-mcp-server:latest y se construye desde GitHub Actions. Para publicar nuevas versiones configurá:
Variable
DOCKERHUB_IMAGE, por ejemplotuusuario/pdm-mcp-server.Secret
DOCKERHUB_USERNAME.Secret
DOCKERHUB_TOKEN, un access token de Docker Hub con permiso de escritura.
El workflow Publish Docker image se ejecuta manualmente con el tag indicado o con un tag Git v*; en este último caso también publica latest.
compose.yml publica 3000:3000 y muestra todas las variables necesarias. Después de editar sus valores:
docker pull jer3m/pdm-mcp-server:latest
docker compose up -d
curl http://localhost:3000/healthzThis server cannot be deployed
Maintenance
Related MCP Connectors
Read-only MCP access to a documented IT fleet: state, changes, posture. 15 tools.
Read-only Dant3 MCP for public rooms, agents, jobs and provisional machine onboarding.
Provides read access to your GKE and Kubernetes resources.
Read-only MCP access to sessions, funnels, campaigns, errors, live visitors, and anomalies.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables read-only interaction with Proxmox homelab VMs and containers, allowing LLM agents to list VMs, monitor status and performance metrics, view snapshots, and check cluster health through natural language queries.8MIT
- AlicenseAqualityBmaintenanceMCP server for Proxmox VE and Proxmox Datacenter Manager, covering every API endpoint via six consolidated tools for list, describe, and call operations with a read-only safety gate.616 npm2AGPL 3.0
- AlicenseAqualityCmaintenanceA read-only MCP server for Proxmox VE that provides AI assistants with structured visibility into cluster nodes, guests, storage, and Docker workloads. It is designed to prevent any mutating operations by construction.12MIT
- FlicenseBqualityCmaintenanceRead-only Proxmox VE MCP server providing 25 tools for VM, LXC, node, storage, and cluster inspection via stdio transport.25-