remnawave-mcp
# remnawave-mcp
Автономный MCP-сервер (Model Context Protocol) для панели **Remnawave**. Один
инструмент — на каждую операцию `method + path` из OpenAPI-спеки панели
(217 операций), плюс несколько служебных инструментов. Сервер сгенерирован
из спеки, но не зависит от неё в рантайме — сгенерированный файл
`src/generated/tools.ts` закоммитен в репозиторий.
Пакет самодостаточен: копия спеки лежит в `spec/remnawave_openapi.json`,
внешних рантайм-зависимостей кроме `@modelcontextprotocol/sdk` и `zod` нет.
## Что это
- Полное покрытие API Remnawave 3.4.3: пользователи, ноды, хосты, подписки,
инбаунды, шаблоны конфигов, bulk-операции и т.д. — 217 инструментов.
- Слой безопасности в рантайме: режимы `ro`/`rw`, deny-листы, подтверждение
мутирующих операций (`confirm`), dry-run, лимит на bulk-операции,
редактирование секретов в ответах, аудит-журнал вызовов.
- Служебные инструменты: `api_search`, `api_describe`, `api_status`,
`api_audit_tail`, `system_metrics`.
- Транспорт: stdio (по умолчанию) и опционально Streamable HTTP.
## Установка
```bash
npm ci
npm run build
```
`npm ci` ставит зависимости строго по `package-lock.json`. Для разработки:
`npm test`, `npm run typecheck`.
## Конфигурация
Все настройки — через переменные окружения. Скопируйте `.env.example` в `.env`
и заполните нужные поля (для локального запуска сервера через `npm start`;
`.env` в `.gitignore`, секреты никуда не коммитятся).
Ключевые переменные (полный список с комментариями — в `.env.example`):
| Переменная | Назначение | По умолчанию |
|---|---|---|
| `REMNA_BASE_URL` | URL панели, без завершающего слэша | — (обязателен) |
| `REMNA_API_TOKEN` / `REMNA_API_TOKEN_FILE` | Bearer JWT-токен панели (или путь к файлу с токеном) | — |
| `REMNA_METRICS_USER` / `REMNA_METRICS_PASS` | Basic-auth для `/metrics` (схема `Prometheus` в спеке) | — |
| `MCP_MODE` | `ro` — только GET, `rw` — всё, кроме deny-листа | `ro` |
| `MCP_DENY` | Доп. правила `METHOD /prefix` или `/prefix`, через запятую | пусто |
| `MCP_DENY_DEFAULTS` | Включить встроенный deny-лист Remnawave | `1` |
| `MCP_CONFIRM` | Требовать `confirm:true` для не-GET | `1` в `rw`, `0` в `ro` (не имеет значения — там всё и так denied) |
| `MCP_DRY_RUN` | Не-GET никогда не отправляются, всегда превью | `0` |
| `MCP_AUDIT_LOG` | Путь к JSONL-журналу аудита | `./.audit/remnawave-mcp.jsonl` |
| `MCP_TIMEOUT_MS` | Таймаут HTTP-запроса к панели | `30000` |
| `MCP_MAX_RESPONSE_BYTES` | Лимит размера ответа инструмента | `200000` |
| `MCP_TOOL_FILTER` | Regex по имени инструмента — публикует подмножество | пусто (все) |
| `MCP_REDACT` | Редактирование секретов в ответах | `1` |
| `MCP_REDACT_SUBSCRIPTION` | Доп. редактирование полей подписки (trojanPassword и т.д.) | `0` |
| `MCP_MAX_BULK_ITEMS` | Лимит длины массивов `uuids`/`ids` в bulk-запросах | `100` |
| `MCP_HTTP_PORT` / `MCP_HTTP_TOKEN` | Опциональный HTTP-транспорт (см. ниже) | — |
## Режимы безопасности
### `ro` (по умолчанию)
Разрешены только `GET`-запросы. Любая попытка вызвать не-GET инструмент
возвращает отказ (`isError: true`, `denied: true`).
### `rw`
Разрешены все операции, кроме тех, что попадают в deny-лист. Для не-GET
операций по умолчанию (`MCP_CONFIRM=1`) первый вызов без `confirm: true`
возвращает **превью** запроса (метод, URL, тело) и ничего не отправляет.
Повторный вызов с `confirm: true` выполняет запрос.
```jsonc
// 1) без confirm — превью
{ "name": "users_create_user", "arguments": { "body": { "username": "bob", "expireAt": "2030-01-01T00:00:00Z" } } }
// -> { "preview": true, "method": "POST", "url": ".../api/users", "body": {...} }
// 2) с confirm — выполняется
{ "name": "users_create_user", "arguments": { "body": { "username": "bob", "expireAt": "2030-01-01T00:00:00Z" }, "confirm": true } }
// -> { "status": 201, "ok": true, "data": {...}, "truncated": false }
```
### Deny-лист
Действует независимо от режима. Встроенный дефолт для Remnawave
(`MCP_DENY_DEFAULTS=1`):
- `/api/keygen`
- `POST /api/tokens`
- `/api/auth`
- `/api/passkeys`
- `POST /api/nodes/actions/restart-all`
- `POST /api/users/bulk/delete-by-status`
Эти операции выдают постоянные админ-креды, генерируют ключи или уничтожают
пользователей — они запрещены, даже если модель попросит `confirm: true`.
Отключить дефолт: `MCP_DENY_DEFAULTS=0`. Добавить свои правила:
`MCP_DENY="DELETE /api/users,POST /api/hosts/bulk"`.
### Dry-run
`MCP_DRY_RUN=1` — ни одна не-GET операция не отправляется на панель, всегда
возвращается превью, даже с `confirm: true`. Полезно для проверки, что делает
модель, без реального изменения данных.
### Bulk-лимит
Для bulk-инструментов (`/api/users/bulk/*`, `/api/hosts/bulk/*`) массивы
`uuids`/`ids` в теле не могут превышать `MCP_MAX_BULK_ITEMS` (по умолчанию
100) — при превышении вызов отклоняется без обращения к панели.
### Редактирование секретов
По умолчанию (`MCP_REDACT=1`) в ответах инструментов заменяются на
`<redacted>` ключи, подходящие под
`/(password|passwd|secret|token|api_key|apikey|private_key|privateKey|authorization|cookie)/i`.
Поля, специфичные для подписок Remnawave (`trojanPassword`, `vlessUuid`,
`ssPassword`, `subscriptionUrl`, `links`, `happ.cryptoLink`) — это рабочие
данные, нужные клиентам, поэтому по умолчанию **не** редактируются, даже если
формально подпадают под базовый паттерн (например `trojanPassword`). Включить
их редактирование: `MCP_REDACT_SUBSCRIPTION=1`. Полностью выключить
редактирование: `MCP_REDACT=0`.
Заголовок `Authorization` никогда не попадает ни в ответы, ни в аудит-журнал.
## Подключение к Claude Code
> При старте сервер сам читает `.env` из корня своего каталога (уже заданные переменные окружения имеют приоритет). Поэтому проще всего заполнить `.env` и подключать без `--env`, чтобы секреты не попали в конфиг MCP-клиента:
>
> ```bash
> claude mcp add remnawave -- node /абсолютный/путь/до/remnawave-mcp/dist/index.js
> ```
```bash
claude mcp add remnawave \
--env REMNA_BASE_URL=https://panel.example.com \
--env REMNA_API_TOKEN=xxxxx \
--env MCP_MODE=ro \
-- node /абсолютный/путь/до/remnawave-mcp/dist/index.js
```
Для `rw`-режима добавьте `--env MCP_MODE=rw` (и, по вкусу, `MCP_CONFIRM`,
`MCP_DENY`). Не забудьте `npm run build` перед подключением — сервер
запускается из `dist/index.js`.
## Подключение через generic JSON-конфиг (любой MCP-клиент)
```json
{
"mcpServers": {
"remnawave": {
"command": "node",
"args": ["/абсолютный/путь/до/remnawave-mcp/dist/index.js"],
"env": {
"REMNA_BASE_URL": "https://panel.example.com",
"REMNA_API_TOKEN": "xxxxx",
"MCP_MODE": "ro"
}
}
}
}
```
## Опциональный HTTP-транспорт
`npm run start:http` поднимает Streamable HTTP на `127.0.0.1:$MCP_HTTP_PORT`.
Обязателен `MCP_HTTP_TOKEN` — без него сервер откажется стартовать. Каждый
HTTP-запрос должен нести заголовок `Authorization: Bearer <MCP_HTTP_TOKEN>`
(это отдельный токен транспорта, не токен панели).
## Имена инструментов
`snake_case`, ASCII, ≤ 60 символов, уникальные. Правило: из `operationId`
вида `XxxController_yyyZzz` отрезается `Controller`, обе части переводятся
в `snake_case` и склеиваются через `_`. Например:
`UsersController_getUsers` → `users_get_users`,
`SystemController_getRemnawaveHealth` → `system_get_remnawave_health`.
Слишком длинные имена (после конвертации > 60 символов) обрезаются с
добавлением короткого хэша для сохранения уникальности.
## Примеры вызова часто используемых инструментов
**Список пользователей** (`users_get_users`, `GET /api/users`, пагинация
через `start`/`size`):
```jsonc
{ "name": "users_get_users", "arguments": { "size": 20, "start": 0 } }
```
**Пользователь по ID** (`users_get_user_by_id`, `GET /api/users/{userId}`):
```jsonc
{ "name": "users_get_user_by_id", "arguments": { "userId": 42 } }
```
**Список нод** (`nodes_get_nodes`, `GET /api/nodes`):
```jsonc
{ "name": "nodes_get_nodes", "arguments": {} }
```
**Здоровье панели** (`system_get_remnawave_health`,
`GET /api/system/health`; это же дёргает служебный `api_status`):
```jsonc
{ "name": "system_get_remnawave_health", "arguments": {} }
```
**Список хостов** (`hosts_get_hosts`, `GET /api/hosts`):
```jsonc
{ "name": "hosts_get_hosts", "arguments": {} }
```
Найти нужный инструмент, если имя не угадывается:
```jsonc
{ "name": "api_search", "arguments": { "query": "subscription" } }
```
Посмотреть полную входную схему конкретного инструмента:
```jsonc
{ "name": "api_describe", "arguments": { "tool": "users_create_user" } }
```
## Как перегенерировать при обновлении панели
1. Скопируйте актуальный `openapi.json` из контейнера панели, например:
```bash
docker cp <remnawave-container>:/opt/app/openapi.json ./new_openapi.json
```
2. Замените файл спеки в пакете:
```bash
cp new_openapi.json spec/remnawave_openapi.json
```
3. Перегенерируйте инструменты и пересоберите:
```bash
npm run generate
npm run build
```
4. Прогоните тесты (число инструментов в тестах жёстко зашито — 217; если
панель добавила/убрала операции, обновите ожидаемое число в
`test/generator.test.ts`):
```bash
npm test
```
5. Проверьте `git diff src/generated/tools.ts` — новые/изменённые
инструменты, изменившиеся deny-правила (если появились новые опасные
операции — добавьте их в `DEFAULT_DENY_RULES` в `src/config.ts` и в
`MCP_DENY` в `.env.example`).
## Разработка
- `npm run typecheck` — проверка типов без сборки.
- `npm test` — vitest, все сетевые вызовы замоканы (`vi.stubGlobal("fetch", ...)`).
- `npm run smoke` — живая проверка `api_status` + один `GET` к реальной
панели; выполняется **только** если в корне пакета есть заполненный `.env`,
иначе печатает `skipped` и завершается с кодом 0. Использует только GET.
## Примечания к реализации
- `/metrics` (Basic-auth, схема `Prometheus`) отсутствует как отдельный
`path` в OpenAPI-спеке панели, поэтому не генерируется автоматически.
Вместо этого добавлен вручную написанный служебный инструмент
`system_metrics`, который использует `REMNA_METRICS_USER`/`REMNA_METRICS_PASS`
и Basic-auth (а не Bearer-токен панели). Без этих переменных возвращает
понятный отказ, не пытаясь ничего вызвать.
- Ответы панели, обёрнутые в `{ "response": ... }`, возвращаются как есть —
сервер не разворачивает эту обёртку.
TDQS
Scored across 222 tools
With 222 tools, many endpoints overlap in purpose (e.g., two internal squad usage tools, offset vs cursor user listing, multiple subscription getters), forcing the agent to read fine-grained descriptions to choose correctly. The controller prefixes help somewhat, but the volume and near-duplicate names make misselection likely.
Names generally follow a resource_action snake_case pattern (users_create_user, nodes_restart_node), but inconsistencies appear: random suffixes like _1r7914, a typo (infra_billing_delte_infra_provider), and mixed get vs get_all conventions. Still, the dominant pattern is readable.
222 tools vastly exceeds a reasonable MCP surface (calibration marks 50+ as extreme mismatch), overwhelming the agent and making navigation costly despite helper tools like api_search.
The surface covers nearly all Remnawave panel domains (users, nodes, hosts, squads, configs, billing, subscriptions, auth, passkeys, metrics, etc.) with full CRUD and bulk operations, leaving few obvious gaps.