0x00. Вступ

У попередньому дописі про MCP я дав AI-агенту керувати кластером Proxmox. Та сама ідея працює й з роутером: даєте асистенту набір «інструментів», які він може викликати, — і можете простою людською мовою спитати «покажи правила фаєрволу» чи «які оренди DHCP зараз активні» та отримати справжню відповідь від живого пристрою.

Клеєм знову виступає MCPModel 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Користувач RouterOSadmin
MIKROTIK_PASSWORDПароль (не задавайте, коли використовуєте ключ)(порожньо)
MIKROTIK_KEY_FILENAMEШлях до приватного SSH-ключа(порожньо)
MIKROTIK_PORTПорт SSH22
MIKROTIK_MCP__TRANSPORTstdio / sse / streamable-httpstdio
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