0x02. Керування Proxmox через AI-агента по MCP
0x00. Інтро
До ери ШІ я керував Proxmox VE вручну, через веб інтерфейс. Так, є варіант керувати через ssh скрипти, але я вирішив спробувати інакше — дозволив AI-агенту керувати Proxmox напряму під моїм наглядом.
Клеєм тут виступає MCP — Model Context Protocol. Простими словами, MCP — це стандартний спосіб дати LLM «меню» інструментів (функцій, які вона може викликати) плюс транспорт для їх виклику; модель обирає інструмент, заповнює аргументи й отримує результат. Існує community-сервер ProxmoxMCP-Plus, який загортає API Proxmox саме в такі інструменти (get_vms, create_container, clone_vm, execute_container_command тощо). Під’єднуєш до нього клієнт на кшталт Claude Code чи OpenCode — і агент може переглядати ноди, створювати LXC-контейнери, виконувати команди всередині них — увесь життєвий цикл.
Трохи термінів:
- LXC / CT — Linux-контейнер у Proxmox: легкий, ізольований Linux-простір користувача, що ділить ядро з хостом (дешевший за повноцінну VM). Непривілейований CT відображає свій root на не-root користувача хоста — заради безпеки.
- Bearer-токен — довгий рандомний секрет, який передається в HTTP-заголовку
Authorization; хто його має — той «авторизований». Вважайте це паролем до API.
Цей допис — це як розгорнути й захистити MCP-сервер, як під’єднати до нього клієнт і невелике демо керування життєвим циклом контейнера з промпта. Усе нижче перевірено на хості з Debian 13 та ProxmoxMCP-Plus v0.5.8. Усі IP-адреси — приклади у приватній мережі (192.168.x.x); підставте свої.
Зауваження щодо безпеки: віддавати LLM ключі від вашого гіпервізора рівно настільки небезпечно, як це й звучить. Уся конструкція нижче — про те, щоб тримати це під контролем: API-токен з мінімальними правами, сервер лише на localhost, проксі з перевіркою bearer-токена попереду й політика команд у режимі лише-аудиту. HTTP тільки в межах хосту, під’єднання ззовні тільки через HTTPS із авторизацією по токену. Але якщо плануєте розгортати десь на підприємстві/офісі - маєте додати ще механізмів захисту - обмеження по ip адресам, використання certificates pinning тощо. Але це виходить за межі цієї статті.
0x01. Архітектура
Сервер недосяжний з мережі напряму. Є три шари, кожен зі своєю автентифікацією:
┌─────────────────────┐ HTTP + Bearer Token ┌───────────────────────┐
│ AI Agent / Client │ ──────────────────────────── │ Caddy (:8443) │
│ (Claude / OpenCode)│ │ - Token validation │
└─────────────────────┘ │ - reverse proxy │
└──────────┬────────────┘
│ http://127.0.0.1:8000
┌──────────▼────────────┐
│ ProxmoxMCP-Plus │
│ - Streamable HTTP │
│ - localhost only │
└──────────┬────────────┘
│ HTTPS (verify_ssl=false)
┌──────────▼────────────┐
│ Proxmox VE API │
│ (self-signed cert) │
└───────────────────────┘
Три шари безпеки:
- Caddy перевіряє Bearer-токен і відхиляє все неавтентифіковане.
- ProxmoxMCP-Plus слухає лише на
127.0.0.1:8000— він ніколи не досяжний з мережі напряму. - Proxmox автентифікує сервер власним API-токеном, повністю окремим від MCP bearer-токена.
Сам MCP-сервер працює як невеликий LXC-контейнер або VM (Debian 13, 1 vCPU / 1 ГБ цілком достатньо). У прикладах нижче він живе за адресою 192.168.1.10 і спілкується з API Proxmox за адресою 192.168.1.2:8006.
Крок 1 — встановлення ProxmoxMCP-Plus
На MCP-хості (звичайний Debian 13) встановлюємо Python і клонуємо застосунок у /opt/ProxmoxMCP-Plus. Зверніть увагу на curl та git — їх немає в мінімальному образі Debian за замовчуванням:
apt update
apt install -y python3 python3-venv python3-pip git curl
# uv — швидкий менеджер пакетів Python; встановлюємо й підхоплюємо в PATH
curl -LsSf https://astral.sh/uv/install.sh | sh
source ~/.bashrc
# Отримуємо ProxmoxMCP-Plus
cd /opt
git clone https://github.com/RekklesNA/ProxmoxMCP-Plus.git
cd ProxmoxMCP-Plus
uv venv
uv pip install -e ".[dev]"
Далі — systemd-юніт у /etc/systemd/system/proxmox-mcp.service, щоб сервіс стартував під час завантаження й перезапускався при збої:
# /etc/systemd/system/proxmox-mcp.service
cat > /etc/systemd/system/proxmox-mcp.service << 'EOF'
[Unit]
Description=Proxmox MCP Server (ProxmoxMCP-Plus)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/ProxmoxMCP-Plus
Environment="PROXMOX_MCP_CONFIG=/opt/ProxmoxMCP-Plus/proxmox-config/config.json"
Environment="PYTHONPATH=/opt/ProxmoxMCP-Plus/src"
ExecStart=/opt/ProxmoxMCP-Plus/.venv/bin/python -m proxmox_mcp.server
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable proxmox-mcp.service
Крок 2 — конфіг MCP-сервера
ProxmoxMCP-Plus керується єдиним конфіг-файлом — /opt/ProxmoxMCP-Plus/proxmox-config/config.json (саме на цей шлях systemd-юніт вище вказує через PROXMOX_MCP_CONFIG). Найважливіші частини:
{
"proxmox": {
"host": "192.168.1.2",
"port": 8006,
"verify_ssl": false,
"service": "PVE"
},
"auth": {
"user": "mcp@pve",
"token_name": "claude",
"token_value": "<PROXMOX_API_TOKEN>"
},
"mcp": {
"host": "127.0.0.1",
"port": 8000,
"transport": "STREAMABLE"
},
"security": {
"dev_mode": true
},
"command_policy": {
"mode": "audit_only",
"deny_patterns": [
"(^|\\s)rm\\s+-rf(\\s|$)",
":\\(\\)\\{:\\|:\\&\\};:"
]
}
}
Кілька неочевидних рішень:
| Параметр | Значення | Чому |
|---|---|---|
mcp.host | 127.0.0.1 | Досяжний лише через проксі Caddy, не з мережі |
mcp.transport | STREAMABLE | Сучасний транспорт MCP (замінює застарілий SSE) |
proxmox.verify_ssl | false | Proxmox за замовчуванням має самопідписаний сертифікат |
security.dev_mode | true | Потрібен, щоб застосунок прийняв verify_ssl: false |
command_policy.mode | audit_only | Логує команди, але не блокує; deny-список усе одно вбиває rm -rf / fork-бомби |
Крок 3 — API-токен Proxmox
Блок auth вище посилається на API-токен Proxmox. ProxmoxMCP-Plus потребує ролі Administrator з вимкненим розділенням привілеїв — ролі PVEAdmin не достатньо, бо їй бракує Sys.Modify, потрібного для створення контейнерів/VM:
# На хості Proxmox:
pveum user add mcp@pve
pveum acl modify / --users mcp@pve --roles Administrator
pveum user token add mcp@pve claude -privsep 0
# privsep=0 → токен успадковує права користувача
Proxmox показує секрет токена рівно один раз. Підставте це значення в auth.token_value у config.json (використайте свій справжній токен там, де у прикладі стоїть <PROXMOX_API_TOKEN>), потім запустіть сервіс:
systemctl start proxmox-mcp.service
ss -tlnp | grep 8000 # очікуємо: LISTEN 127.0.0.1:8000 (лише localhost)
Крок 4 — Caddy: гейт з bearer-токеном
Caddy (невеликий сучасний веб-сервер / реверс-проксі) стоїть попереду й робить рівно дві речі — термінує TLS (тобто робить https->http “перетворення”) і додає автентифікацію по токену, щоб виключити неавторизване використання MCP. Встановлюємо його з офіційного репозиторію:
apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
| gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
| tee /etc/apt/sources.list.d/caddy-stable.list
apt update
apt install -y caddy
Для внутрішньої мережі самопідписаного сертифіката цілком достатньо — генеруємо сам сертифікат у /etc/caddy/mcp.crt, а приватний ключ — у /etc/caddy/mcp.key, для IP сервера (використайте свою справжню адресу замість 192.168.1.10):
# записує /etc/caddy/mcp.crt (сертифікат) і /etc/caddy/mcp.key (приватний ключ)
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \
-days 3650 -nodes \
-keyout /etc/caddy/mcp.key -out /etc/caddy/mcp.crt \
-subj '/CN=mcp-server' \
-addext 'subjectAltName=IP:192.168.1.10,IP:127.0.0.1,DNS:localhost'
chown caddy:caddy /etc/caddy/mcp.key /etc/caddy/mcp.crt
chmod 640 /etc/caddy/mcp.key
Далі — сам Caddyfile, у /etc/caddy/Caddyfile:
# /etc/caddy/Caddyfile
:8443 {
tls /etc/caddy/mcp.crt /etc/caddy/mcp.key
@unauthorized {
not header Authorization "Bearer {env.MCP_TOKEN}"
}
respond @unauthorized 401 {
body "Unauthorized"
close
}
reverse_proxy 127.0.0.1:8000 {
header_up Host 127.0.0.1:8000
}
}
Токен — це просто 32 випадкові байти, збережені у /etc/caddy/mcp_token, а потім передані в Caddy через systemd environment-файл /etc/caddy/mcp_env:
# токен → /etc/caddy/mcp_token ; env-файл → /etc/caddy/mcp_env
openssl rand -hex 32 > /etc/caddy/mcp_token
chmod 600 /etc/caddy/mcp_token
echo "MCP_TOKEN=$(cat /etc/caddy/mcp_token)" > /etc/caddy/mcp_env
chmod 600 /etc/caddy/mcp_env
Кажемо systemd підвантажити цей env-файл через drop-in override у /etc/systemd/system/caddy.service.d/override.conf і запускаємо Caddy:
# /etc/systemd/system/caddy.service.d/override.conf
mkdir -p /etc/systemd/system/caddy.service.d
cat > /etc/systemd/system/caddy.service.d/override.conf << 'EOF'
[Service]
EnvironmentFile=/etc/caddy/mcp_env
EOF
systemctl daemon-reload
systemctl enable --now caddy
Рядок header_up Host 127.0.0.1:8000 — не косметика. MCP Python SDK (v1.24+) має захист від DNS-rebinding: прив’язаний до 127.0.0.1, він відхиляє будь-який запит, у якого заголовок Host не localhost/127.0.0.1. Без переписування Caddy передав би оригінальний Host клієнта (напр. 192.168.1.10:8443), і сервер відповів би 421 Misdirected Request. Переписування заголовка на те, що очікує сервер, це виправляє.
Швидка перевірка з самого сервера:
# Без токена — має бути відхилено:
curl -sk https://127.0.0.1:8443/mcp
# Unauthorized
# З токеном — 406 означає «автентифікація пройшла, тепер надішли правильні MCP-заголовки»:
curl -sk -H "Authorization: Bearer $(cat /etc/caddy/mcp_token)" https://127.0.0.1:8443/mcp
0x02. Під’єднання клієнта
Наш MCP-сервер комунікує по Streamable HTTP за bearer-токеном, тож будь-який MCP-клієнт, що підтримує віддалені HTTP-сервери, може ним користуватися. Нижче — два, якими я користуюся: Claude Code та OpenCode. В обох URL вказує на порт Caddy (8443), а токен іде в заголовку Authorization: Bearer.
Claude Code
Claude Code читає MCP-сервери з файлу .mcp.json у корені проєкту (для сервера на всю машину використайте глобальний ~/.claude.json). В моєму варіанті токен береться зі змінної середовища, тож ніколи не потрапляє у файл:
{
"mcpServers": {
"proxmox": {
"type": "http",
"url": "https://192.168.1.10:8443/mcp",
"headers": {
"Authorization": "Bearer ${PROXMOX_MCP_TOKEN}"
}
}
}
}
OpenCode
OpenCode читає свій конфіг з opencode.jsonc (Linux/macOS: ~/.config/opencode/opencode.jsonc; Windows: C:\Users\<USERNAME>\.config\opencode\opencode.jsonc). Додайте блок mcp типу remote (тут <MCP_TOKEN> вже явно вказаний як є - підставте ваш):
// ~/.config/opencode/opencode.jsonc
// (Windows: C:\Users\<USERNAME>\.config\opencode\opencode.jsonc)
{
"mcp": {
"proxmox": {
"type": "remote",
"url": "https://192.168.1.10:8443/mcp",
"enabled": true,
"headers": {
"Authorization": "Bearer <MCP_TOKEN>"
}
}
}
}
Порада: замість вставляння токена у файл OpenCode може прочитати його зі змінної середовища — задайте заголовок "Bearer {env:MCP_TOKEN}" і експортуйте MCP_TOKEN у своїй оболонці.
Перезапустіть OpenCode (він кешує MCP-з’єднання під час старту) і перевірте з CLI:
opencode mcp list
# Має показати: proxmox connected
Тонкість із самопідписаним сертифікатом (обидва клієнти)
Claude Code і OpenCode обидва збудовані на Node.js, а Node за замовчуванням суворо перевіряє TLS — окремого прапорця insecure на сервер немає. Із самопідписаним сертифікатом обхід — змінна середовища Node, що вимикає перевірку сертифіката для цього процесу:
# Windows (постійно, у області User)
[Environment]::SetEnvironmentVariable("NODE_TLS_REJECT_UNAUTHORIZED", "0", "User")
# Linux/macOS — додайте до ~/.bashrc / ~/.zshrc
export NODE_TLS_REJECT_UNAUTHORIZED=0
Це вимикає перевірку TLS у Node глобально для цього користувача, тож на машині, доступній з інтернету, це було б поганою ідеєю. У внутрішній / довіреній мережі це прийнятний компроміс. (Якщо взагалі не хочете чіпати Node, Caddy може працювати й по звичайному HTTP на :8443; bearer-токен усе одно контролює доступ, просто трафік буде нешифрований. Або, маючи справжній домен, перейдіть на сертифікат від CA й приберіть цю змінну зовсім.)
Після цього перезапустіть клієнт — і сервер з’явиться як під’єднаний.

0x03. Які інструменти отримує агент
Після під’єднання агент бачить API Proxmox як каталог типізованих інструментів. Ті, на які я найбільше спирався:
| Інструмент | Що робить |
|---|---|
get_nodes, get_cluster_status, get_node_status | Стан кластера / нод |
get_storage | Список пулів сховища та їхні типи |
get_vms, get_containers | Інвентар VM та CT |
create_container, create_vm | Створення нового CT / VM |
clone_vm | Клонування шаблону в нову VM |
start_container / stop_container, start_vm / stop_vm | Керування живленням |
delete_container, delete_vm | Знесення CT / VM |
execute_container_command | Виконання команди всередині CT (через pct exec по SSH до хоста) |
update_container_resources | Зміна CPU / RAM / диска |
create_snapshot, rollback_snapshot, create_backup | Знімки та резервні копії |
download_iso, list_templates | Керування ISO / шаблонами |
execute_container_command — саме той інструмент, що перетворює це з «інвентарного API» на «агента, який реально щось будує». Він не йде через API Proxmox — MCP-сервер заходить по SSH на хост Proxmox і виконує pct exec усередині цільового контейнера. Тож MCP-хост тримає SSH-ключ, чия публічна частина лежить в authorized_keys хоста Proxmox:
# На MCP-хості
ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -N ''
cat /root/.ssh/id_ed25519.pub # → додати до authorized_keys хоста Proxmox
Саме цей шлях через SSH до хоста дозволяє агенту не просто створити контейнер, а зайти в нього й налаштувати від початку до кінця.
0x04. Керування Proxmox через MCP — швидке демо
Коли сервер під’єднано, ви керуєте всім з промпта: «створи невеликий Debian-контейнер, запусти його, потім знеси». Агент перекладає це на виклики MCP-інструментів. Ось нейтральний, відтворюваний прохід на одноразових значеннях — VMID 9000, ім’я хоста demo-ct. (Запис викликів нижче ілюструє аргументи, які приймає кожен інструмент; агент заповнює їх за вас.)
Створюємо зразковий контейнер:
create_container(
node='<PROXMOX_NODE>',
vmid='9000',
ostemplate='local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst',
hostname='demo-ct',
cores=1,
memory=512,
disk_size=8,
storage='local-lvm',
network_bridge='vmbr0',
start_after_create=false,
onboot=false
)
Запускаємо, потім зупиняємо його:
start_container(node='<PROXMOX_NODE>', vmid='9000')
stop_container(node='<PROXMOX_NODE>', vmid='9000')
Кожен виклик повертає невеликий об’єкт статусу — приблизно VMID і його новий стан роботи — тож агент може підтвердити крок, перш ніж рухатися далі. Виклик get_containers після цього показує demo-ct в інвентарі з його поточним статусом.
Видаляємо його, коли закінчили — жодних залишків:
delete_container(node='<PROXMOX_NODE>', vmid='9000')
Життєвий цикл VM дзеркалить це точно так само з VM-версіями інструментів: create_vm, start_vm, stop_vm, delete_vm (плюс clone_vm, щоб «штампувати» нову VM із шаблону). Той самий патерн — створити, увімкнути, вимкнути, знищити — повністю через захищений MCP-ендпоінт.

Ось і весь цикл: промпт у клієнті, типізовані виклики інструментів до Proxmox, зразковий контейнер створено й прибрано — без жодного дотику до веб-інтерфейсу й без власного SSH на хост.
0x05. Підсумок
Поставивши AI-агента перед Proxmox через MCP, ви перетворюєте купу ручних кліків в UI на розмову: «створи Debian CT, запусти його, знеси». Агент робить рутину; за вами лишаються архітектурні рішення й межі безпеки.
Здається - “і все? Що тут такого” - але ні, тепер ви можете задіяти усю міць ШІ для розгортання серверів від нуля. Тут головне правильно пояснити ШІ, що треба, і обов’язково мати способи автоматичного контролю того, що має бути на виході. По аналогії із програмуванням - не просто кажете “напишити програму сортування”, але також - напиши тесткейси для перевірки, із усіма можливими edge cases… (і далі вмикаєте фантазію, але то інша тема)
Безпека: межі — це і є вся суть. MCP-сервер лише на localhost, за проксі з bearer-токеном, з виділеним Proxmox-токеном з мінімальними правами, з політикою команд у режимі лише-аудиту, що логує все. Це мінімум, який я хотів би мати, перш ніж пускати будь-що автоматизоване до гіпервізора.
Дякую, що дочитали аж сюди. Якщо збираєтесь спробувати те саме: токен з мінімальними правами, прив’язка до localhost, токен попереду. Саме в такому порядку.
Refs
- ProxmoxMCP-Plus — https://github.com/RekklesNA/ProxmoxMCP-Plus
- Model Context Protocol — https://modelcontextprotocol.io/
- Proxmox VE API — https://pve.proxmox.com/pve-docs/api-viewer/
- Proxmox VE user / token management — https://pve.proxmox.com/wiki/User_Management
- Caddy reverse proxy — https://caddyserver.com/docs/