0x04. Даємо AI-асистенту керування роутером MikroTik через MCP (по SSH)
0x00. Вступ
У попередньому дописі про MCP я дав AI-агенту керувати кластером Proxmox. Та сама ідея працює й з роутером: даєте асистенту набір «інструментів», які він може викликати, — і можете простою людською мовою спитати «покажи правила фаєрволу» чи «які оренди DHCP зараз активні» та отримати справжню відповідь від живого пристрою.
Клеєм знову виступає MCP — Model Context Protocol, стандартний спосіб надати LLM набір інструментів, які можна викликати, разом із транспортом для їх виклику. Цього разу інструменти обгортають пристрій MikroTik RouterOS: перелічити інтерфейси, прочитати фаєрвол, переглянути оренди DHCP, додати запис до address-list тощо. Під’єднуєте клієнт на кшталт Claude Code чи OpenCode — і агент може опитувати роутер, а за вашого дозволу й змінювати його, прямо з чату.
Трохи термінології наперед:
- RouterOS — операційна система роутерів MikroTik. Усе керується єдиною граматикою CLI (
/interface print,/ip firewall filter print, …), доступною по SSH. - Bearer-токен — довгий випадковий секрет, який передається в HTTP-заголовку
Authorization; хто ним володіє, той «авторизований». Уявіть його як пароль до API. - Зворотний проксі — невеликий вебсервер, який стоїть перед іншою службою, завершує TLS, перевіряє облікові дані й пересилає запит далі.
Це загальна відтворювана інструкція: обрати правильний MCP, дати йому обмежений у правах обліковий запис роутера, запустити його на localhost, поставити спереду автентифікований TLS-проксі та під’єднати клієнт. Усі приклади — з узагальненими значеннями: користувач automation, роутер 192.168.88.1, хост MCP mcp.example.com. Підставте свої.
Дозволити LLM торкатися вашого роутера — рівно настільки ж ризикована ідея, як і звучить. Уся конструкція нижче про те, щоб тримати це під контролем: виділений користувач RouterOS з мінімальними правами, вхід лише за ключем, сервер лише на localhost та bearer-токен-проксі спереду, зміни тільки через safe-mode щоб не втратити контроль над віддаленим роутером.
0x01. Чому SSH, а не REST API
RouterOS v7 справді має REST API, і спокуса взятися спершу за нього велика. Не робіть цього. На практиці REST-поверхня неповна й непослідовна між версіями — багатьох меню бракує, дещо повертає незручно сформовані дані, а поведінка зсувається між випусками. Ви кінчаєте тим, що воюєте з транспортом замість роутера.
SSH CLI, навпаки, є повним і стабільним інтерфейсом до RouterOS. Усе, що можна зробити на пристрої, можна зробити по SSH, граматика однакова, і вона не змінюється під вами. Тож розважливий вибір MCP — той, що спілкується з роутером так само, як це робили б ви, тобто по SSH, а не той, що прикручений до хиткого REST-ендпоінта.
Саме такий MCP ми й візьмемо.
0x02. MCP-сервер
Ми скористаємося jeff-nasseri/mikrotik-mcp — сервером з відкритим кодом, написаним на Python поверх paramiko (чистої Python-бібліотеки для SSH). Він відкриває SSH-сесію до роутера й виставляє CLI RouterOS як інструменти MCP.
Дві причини, чому він добре підходить:
- Він підтримує три транспорти MCP —
stdio(локальний, один процес на клієнта),sse(застарілий HTTP) таstreamable-http(сучасний віддалений HTTP-транспорт). Нам потрібенstreamable-http, щоб один запущений сервер міг стояти за проксі й обслуговувати віддалених клієнтів. - Він налаштовується повністю через змінні середовища
MIKROTIK_*, тож немає файлу конфігурації, за яким треба глядіти:
| Змінна | Призначення | Типово |
|---|---|---|
MIKROTIK_HOST | Адреса роутера | 192.168.88.1 |
MIKROTIK_USERNAME | Користувач RouterOS | admin |
MIKROTIK_PASSWORD | Пароль (не задавайте, коли використовуєте ключ) | (порожньо) |
MIKROTIK_KEY_FILENAME | Шлях до приватного SSH-ключа | (порожньо) |
MIKROTIK_PORT | Порт SSH | 22 |
MIKROTIK_MCP__TRANSPORT | stdio / sse / streamable-http | stdio |
MIKROTIK_MCP__HOST | Адреса прив’язки для HTTP-транспортів | 0.0.0.0 |
MIKROTIK_MCP__PORT | Порт прив’язки для HTTP-транспортів | 8000 |
MIKROTIK_MCP__ALLOWED_HOSTS | Список дозволених хостів (захист від DNS-rebinding) | (порожньо) |
Зверніть увагу на подвійне підкреслення в MCP__* — так вкладені налаштування записуються в середовищі.
0x03. Виділений користувач роутера з мінімальними правами + SSH-ключ
Не спрямовуйте MCP на admin і не давайте йому пароль. Створіть окремий обліковий запис RouterOS, обмежте, звідки він може під’єднуватися, і входьте лише за ключем ed25519.
На роутері створіть користувача. RouterOS має кілька типових груп (read, write, full); оберіть найменшу, що покриває те, що ви справді хочете дозволити агенту, — read, якщо потрібно лише спостерігати, write, якщо він має ще й змінювати конфігурацію. Прив’яжіть його до вашої керівної підмережі через address=, щоб обліковий запис не можан було використати деінде:
/user add name=automation group=read address=192.168.88.0/24 comment="AI MCP (read-only)"
Починайте з
group=read. Переходьте наwriteлише тоді, коли довіритеся налаштуванню, — і навіть тоді зберігайте обмеження за адресою.
Тепер дайте цьому користувачу ключ замість пароля. Повний покроковий опис генерації / завантаження / імпорту ключа є в попередньому дописі про SSH-ключі RouterOS — коротка версія, яку виконують на машині, що хостить MCP:
# генеруємо виділений ключ для автоматизації (без парольної фрази для службового ключа)
ssh-keygen -t ed25519 -f ~/.ssh/mikrotik_automation -C "automation@mcp" -N ''
# копіюємо ПУБЛІЧНУ половину на роутер (зверніть увагу на -O: RouterOS потребує застарілого протоколу scp)
scp -O ~/.ssh/mikrotik_automation.pub automation@192.168.88.1:
Потім на роутері імпортуйте публічний ключ на нового користувача (саме завантаження нічого не робить — чому, див. допис 0x03):
/user ssh-keys import public-key-file=mikrotik_automation.pub user=automation
Відтепер приватний ключ ~/.ssh/mikrotik_automation ніколи не залишає хост MCP, а роутер тримає лише публічну половину. MCP вказуватиме на приватний ключ через MIKROTIK_KEY_FILENAME — жодного пароля ніде.
0x04. Встановлення та запуск MCP-сервера
На хості MCP (будь-яка Linux-машина з Python 3.8+) встановіть усе у віртуальне середовище. Пакет називається mcp-server-mikrotik:
apt update
apt install -y python3 python3-venv python3-pip
# ізольоване середовище в /opt
python3 -m venv /opt/mikrotik-mcp
/opt/mikrotik-mcp/bin/pip install mcp-server-mikrotik
Це встановлює консольну команду mcp-server-mikrotik. Запустіть її із середовищем, що вказує на роутер, виділеного користувача, приватний ключ і сучасний HTTP-транспорт, прив’язаний лише до localhost:
MIKROTIK_HOST=192.168.88.1 \
MIKROTIK_USERNAME=automation \
MIKROTIK_KEY_FILENAME=/root/.ssh/mikrotik_automation \
MIKROTIK_PORT=22 \
MIKROTIK_MCP__TRANSPORT=streamable-http \
MIKROTIK_MCP__HOST=127.0.0.1 \
MIKROTIK_MCP__PORT=9100 \
/opt/mikrotik-mcp/bin/mcp-server-mikrotik
Найважливіше тут — MIKROTIK_MCP__HOST=127.0.0.1: сервер тепер досяжний лише з самої машини, ніколи напряму з мережі. Перевірте це:
ss -tlnp | grep 9100 # очікуємо: LISTEN 127.0.0.1:9100 (лише localhost)
Щоб зробити з нього повноцінну службу, помістіть те саме середовище в systemd-юніт /etc/systemd/system/mikrotik-mcp.service:
# /etc/systemd/system/mikrotik-mcp.service
[Unit]
Description=MikroTik MCP server (SSH)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
Environment="MIKROTIK_HOST=192.168.88.1"
Environment="MIKROTIK_USERNAME=automation"
Environment="MIKROTIK_KEY_FILENAME=/root/.ssh/mikrotik_automation"
Environment="MIKROTIK_PORT=22"
Environment="MIKROTIK_MCP__TRANSPORT=streamable-http"
Environment="MIKROTIK_MCP__HOST=127.0.0.1"
Environment="MIKROTIK_MCP__PORT=9100"
Environment="MIKROTIK_MCP__ALLOWED_HOSTS=mcp.example.com,127.0.0.1,localhost"
ExecStart=/opt/mikrotik-mcp/bin/mcp-server-mikrotik
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now mikrotik-mcp.service
0x05. Захист: TLS + bearer-токен через Caddy
Із streamable-http є заковика: у нього немає вбудованої автентифікації. Будь-хто, хто дотягнеться до порту, зможе керувати роутером. Саме тому ми прив’язали його до 127.0.0.1 — і саме тому тепер ставимо спереду зворотний проксі, щоб додати те, чого йому бракує: TLS та шлюз автентифікації.
Caddy (невеликий сучасний вебсервер) робить і те, і те. Він завершує HTTPS на публічному імені й пересилає далі лише запити, що несуть правильний bearer-токен; усе решта отримує 401. Встановіть його з офіційного репозиторію:
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
Згенеруйте токен (32 випадкові байти) і передайте його Caddy через файл середовища systemd /etc/caddy/mcp_env:
# токен → файл середовища, який systemd завантажує для Caddy
echo "MCP_TOKEN=$(openssl rand -hex 32)" > /etc/caddy/mcp_env
chmod 600 /etc/caddy/mcp_env
# змушуємо systemd завантажувати цей файл середовища для Caddy
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
Далі Caddyfile за шляхом /etc/caddy/Caddyfile. Зі справжнім DNS-іменем Caddy автоматично отримує сертифікат Let’s Encrypt; матчер @unauthorized відкидає будь-який запит без точного bearer-токена, а все дійсне пересилається до localhost-MCP:
# /etc/caddy/Caddyfile
mcp.example.com {
@unauthorized {
not header Authorization "Bearer {env.MCP_TOKEN}"
}
respond @unauthorized 401 {
body "Unauthorized"
close
}
reverse_proxy 127.0.0.1:9100
}
systemctl enable --now caddy
Ще один момент, про який варто знати: mikrotik-mcp має захист від DNS-rebinding — за проксі він може відкидати запити, чий заголовок Host він не впізнає. Якщо натрапите на це, додайте ім’я вашого проксі до списку дозволених через MIKROTIK_MCP__ALLOWED_HOSTS (уже задано в systemd-юніті вище, напр. mcp.example.com,127.0.0.1,localhost).
Швидка перевірка з самого сервера:
# без токена → відмова
curl -s https://mcp.example.com/mcp
# Unauthorized
# з токеном → автентифікація проходить (406/400 тут просто означає «тепер надішли належні заголовки MCP»)
curl -s -H "Authorization: Bearer $(grep -oP '(?<=MCP_TOKEN=).*' /etc/caddy/mcp_env)" \
https://mcp.example.com/mcp
0x06. Під’єднання клієнта
Тепер сервер говорить Streamable HTTP за TLS та bearer-токеном, тож ним може скористатися будь-який MCP-клієнт із підтримкою віддалених HTTP-серверів. В обох клієнтах нижче URL вказує на ім’я Caddy (https://mcp.example.com/mcp), а токен іде в заголовку Authorization: Bearer.
Claude Code
Claude Code читає MCP-сервери з .mcp.json у корені проєкту (для сервера на всю машину використовуйте глобальний ~/.claude.json). Тримайте токен у змінній середовища, щоб він ніколи не потрапляв у файл:
{
"mcpServers": {
"mikrotik": {
"type": "http",
"url": "https://mcp.example.com/mcp",
"headers": {
"Authorization": "Bearer ${MIKROTIK_MCP_TOKEN}"
}
}
}
}
OpenCode
OpenCode читає конфігурацію з opencode.jsonc (Linux/macOS: ~/.config/opencode/opencode.jsonc; Windows: C:\Users\<USERNAME>\.config\opencode\opencode.jsonc). Додайте блок mcp типу remote:
// ~/.config/opencode/opencode.jsonc
{
"mcp": {
"mikrotik": {
"type": "remote",
"url": "https://mcp.example.com/mcp",
"enabled": true,
"headers": {
"Authorization": "Bearer <MIKROTIK_MCP_TOKEN>"
}
}
}
}
Порада: замість вставляння токена у файл OpenCode може прочитати його зі змінної середовища — задайте заголовок "Bearer {env:MIKROTIK_MCP_TOKEN}" і експортуйте MIKROTIK_MCP_TOKEN у своїй оболонці.
Перезапустіть OpenCode (він кешує з’єднання MCP під час старту) і перевірте:
opencode mcp list
# Має показати: mikrotik connected
0x07. Спробуйте, і завершальна нотатка про безпеку
Коли клієнт під’єднано, спитайте простою мовою й спостерігайте, як агент перекладає це на виклики CLI RouterOS через MCP на основі SSH:
- «Перелічи інтерфейси роутера та їхній стан.»
- «Покажи правила фаєрволу filter у ланцюжку input.»
- «Які оренди DHCP зараз активні?»
Якщо ви почали з group=read, це і є весь цикл — запитання в клієнті, жива відповідь від роутера, нічого не змінено. Надавайте write пізніше, свідомо, лише коли довіритеся налаштуванню та матимете спосіб переглядати, що робить агент.
Кілька правил, яких варто триматися:
- Спершу мінімум прав. Користувач лише для читання відповідає на більшість питань і нічого не може зламати. Розширюйте обсяг лише за потреби.
- Тримайте ендпоінт автентифікованим. У
streamable-httpнемає власної автентифікації — bearer-токен-проксі не є необов’язковим. Прив’яжіть MCP до127.0.0.1і ніколи не давайте «сирому» порту дивитися в мережу. - Ніколи не виставляйте його в недовірені мережі. TLS + токен у довіреній керівній мережі — це нормально; відкритий інтернет не місце для інструмента, що може переналаштувати ваш роутер.
Ось і все: запит у клієнті, справжня відповідь від RouterOS і — коли ви цього захочете — справжні зміни, усе через один автентифікований ендпоінт, без відкривання WinBox і без жодної набраної вручну команди CLI.
Refs
- mikrotik-mcp (jeff-nasseri) — https://github.com/jeff-nasseri/mikrotik-mcp
- Model Context Protocol — https://modelcontextprotocol.io/
- MikroTik RouterOS — SSH access — https://help.mikrotik.com/docs/display/ROS/SSH
- MikroTik RouterOS — user management — https://help.mikrotik.com/docs/display/ROS/User
- MikroTik RouterOS — REST API — https://help.mikrotik.com/docs/display/ROS/REST+API
- Caddy reverse proxy — https://caddyserver.com/docs/