remnawave-mcp
Read-only MCP server for Remnawave 3.x that lets an AI assistant inspect VPN panel users, infrastructure, traffic, and privacy-safe reports.
Find users by ID, username, email, Telegram ID, tag, or short UUID; get full user reports, subscriptions, devices, current connections, and accessible nodes.
Summarize panel health: online/offline users and nodes, traffic, expiring subscriptions, stats, metrics, health, and metadata.
Inspect nodes, hosts, config profiles, inbounds, internal/external squads, node plugins, shared lists, subscription settings/templates, HWID devices, torrent-blocker reports, and infra/billing data.
Run GeoCheck on nodes and query current node/user connections, with cooldowns and timeout limits.
Analyze bandwidth usage by user, node, internal squad, or date range, plus subscription request history.
Detect suspected subscription sharing using IP/app diversity and top users by HWID device count.
Support multiple Remnawave panels in one server, labeling responses by panel.
Mask secrets, keys, UUIDs, IPs, usernames, emails, Telegram IDs, and HWIDs with pseudonyms by default; stays read-only unless write mode is explicitly enabled.
Supports connecting to a Remnawave panel that is served behind Caddy using a secret path, by sending an API key via the X-Api-Key header (REMNAWAVE_API_KEY).
Supports connecting to a Remnawave panel protected by Cloudflare Access by supplying Cloudflare Access service token credentials (CF_ACCESS_CLIENT_ID / CF_ACCESS_CLIENT_SECRET) with each request.
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., "@remnawave-mcp Find the user with Telegram ID 123456789 and show their traffic"
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.
MCP-сервер для панели Remnawave 3.x. Даёт ИИ-ассистентам — Claude, Cursor, VS Code (Copilot), Windsurf, Codex, Gemini CLI и любым другим MCP-клиентам — доступ к пользователям, нодам, трафику, устройствам, подключениям и GeoCheck. Только чтение и фильтр приватности на вашей стороне.
Быстрый старт. В Claude Desktop — скачайте remnawave-3.4.mcpb из последнего релиза и откройте его, всё. В любом другом клиенте — соберите сервер тремя командами или возьмите Docker-образ и вставьте готовый блок настроек из раздела «Подключение к клиентам» ниже.
Приватность. Ответы панели фильтруются у вас на компьютере, до отправки в чат: ключи и пароли скрыты, а клиенты заменены псевдонимами вроде user~3fa2c1.
Как это выглядит
Related MCP server: remnawave-mcp-server
Возможности
Только чтение по умолчанию. Ничего в панели не меняется, пока вы явно не включите запись.
Всегда под вашу версию панели. Инструменты не написаны руками, а собираются из официального пакета
@remnawave/backend-contract. Обновили панель → подняли версию пакета → пересобрали.Секреты недоступны никогда: вход в панель, passkey, SECRET_KEY нод, API-токены, SSH-билеты и хранилище SSH-ключей нод. Веб-терминал панели через MCP не открывается — чтобы ИИ заходил на сервер, используйте отдельный SSH MCP с ограниченным пользователем.
Утечка чата не выдаёт ни ключей, ни клиентов. Приватные ключи Reality, пароли, UUID и ссылки подключения скрываются, а личные данные клиентов (username, email, Telegram ID, IP, HWID) заменяются псевдонимами. Подробнее — Приватность.
Готовые отчёты одной командой:
panel_overview(сводка по панели),user_report(всё о клиенте),sharing_suspects(кто делится подпиской), плюсfind_user,geocheck_node,node_connections,user_connections.Несколько панелей в одном сервере. Подключите две-три панели (или больше) — ассистент сам выберет нужную по имени, а ответы подписаны, с какой панели они пришли. Подробнее — Несколько панелей.
Шаблоны запросов в клиентах с поддержкой MCP-шаблонов: «Сводка по панели», «Разбор клиента», «Кто делится подпиской», «Проверка нод».
Бережёт ноды и лимиты: повторный GeoCheck одной ноды в течение 30 минут отдаёт прошлый результат, ответы сжаты (в 2–3 раза меньше токенов), список нод и отчёт GeoCheck по умолчанию короткие (
full: true— полные).Три способа установки: сборка из исходников (Node.js) и Docker-образ под каждую версию панели — для любого клиента; для Claude Desktop — ещё и расширение в один клик, токен хранится в защищённом хранилище системы.
Совместимость
Версия панели | Статус |
3.4.x | ✅ проверено на панелях 3.4.5, 3.4.6, 3.4.7, 3.4.8, 3.4.10 и 3.4.11 — чтение и запись |
3.0 – 3.3 | ✅ отдельный файл расширения под каждую версию; при ручной установке — |
2.8.x | ⚠️ не поддерживается официально: основные команды собираются, но на живой панели не проверялось, часть доп. команд недоступна |
2.7 и старше | ❌ используйте TrackLine/mcp-remnawave |
Версия контракта должна совпадать с версией панели хотя бы по первым двум цифрам. Сервер сам подстраивается под установленный контракт (адреса, параметры и список команд берутся из него), а при подключении сверяет версию панели: если она другая, в чате появится предупреждение с подсказкой, какую сборку взять (файл расширения или версию контракта).
Как устроено
flowchart LR
A["ИИ-клиент<br/>Claude · Cursor · VS Code…"] -- stdio --> B["<b>remnawave-mcp</b><br/>фильтр приватности<br/>только чтение · отчёты"]
B -- HTTPS --> C["Remnawave API<br/>REST, токен"]
C --> D["Ноды<br/>Xray"]Документация
Установка
Сервер нужно один раз поставить на компьютер (или сервер), где работает ваш ИИ-клиент, а затем указать его в настройках клиента — см. Подключение к клиентам.
Пользуетесь Claude Desktop? Можно ничего не ставить — у него есть расширение в один клик, см. «Claude Desktop» в разделе Подключение к клиентам.
Способ 1 — из исходников (Node.js)
Нужны Node.js 22+ и Git. Windows:
winget install OpenJS.NodeJS.LTS
winget install Git.GitПосле установки перезапустите PowerShell и соберите сервер:
cd C:\Tools
git clone https://github.com/3APA3A-3AHO3A/remnawave-mcp.git
cd remnawave-mcp
npm ci
npm run build
npm run list-toolsПоследняя строка должна быть вида 84 API tools + 7 extra (contract 3.4.11). Сервер — это файл C:\Tools\remnawave-mcp\dist\index.js, его путь понадобится в настройках клиента.
На macOS / Linux — те же команды, путь любой (например, ~/remnawave-mcp).
Панель не 3.4.x? Поставьте контракт под свою версию и пересоберите:
npm i @remnawave/backend-contract@<версия_панели> --save-exact
npm run buildСпособ 2 — Docker
Node.js не нужен — только Docker. Удобнее всего на сервере с панелью: Docker там уже есть (Remnawave сама работает в Docker).
docker pull ghcr.io/3apa3a-3aho3a/remnawave-mcp:3.4:3.4— образ под панель 3.4.x; есть:3.3,:3.2,:3.1,:3.0и:latest(текущая стабильная). Образы для amd64 и arm64, запускаются не от root.В настройках клиента вместо
nodeи пути к файлу указываетсяdocker run— готовые блоки есть в Подключение к клиентам.-e REMNAWAVE_API_TOKENуdocker runбез значения передаёт токен в контейнер из окружения, поэтому он не светится в списке процессов (ps).
Напрямую к контейнеру панели, минуя nginx / Cloudflare и интернет — подключите контейнер MCP к Docker-сети панели:
docker network ls # в стандартной установке сеть называется remnawave-network
docker ps --format '{{.Names}}' # контейнер панели — обычно remnawaveи в настройках клиента используйте адрес http://remnawave:3000 и аргументы docker run -i --rm --network remnawave-network …. При адресе http://… сервер сам добавляет заголовки, которые обычно ставит обратный прокси (X-Forwarded-Proto, X-Forwarded-For). Если панель всё равно отвечает ошибкой — используйте внешний https:// адрес.
⚠ Безопасность на сервере. MCP даёт ИИ удобные команды и прячет секреты, но не ограничивает консольных агентов (Claude Code, Codex, Gemini CLI и т.п.): если у агента есть доступ к консоли сервера, он может выполнить любую команду. Запускайте такие агенты на боевом сервере не от root, не включайте режим «разрешать всё», а токен панели давайте только с правами
read.
API-токен в панели
Настройки → API-токены → Создать. Выдайте только права read:
users, nodes, hosts, hwid, connections, bandwidth-stats, system, subscriptions, subscription-request-history, internal-squads, external-squads, config-profiles, node-plugins (по желанию — остальные разделы тоже на read).
Не выдавайте write, *, а также api-tokens, passkeys, auth, keygen.
Права в Remnawave делятся на «чтение/запись», а не на GET/POST. Поэтому GeoCheck, запросы подключений и поиск пользователя работают с правами
read, хотя это POST-запросы.
Подключение к клиентам
Во всех клиентах одно и то же: команда запуска сервера и две переменные — REMNAWAVE_BASE_URL (адрес панели) и REMNAWAVE_API_TOKEN (токен). Отличается только, куда это вписать.
В примерах — сервер, собранный из исходников в C:\Tools\remnawave-mcp (в JSON обратные слэши удваиваются: \\). На macOS / Linux укажите свой путь, например /home/user/remnawave-mcp/dist/index.js.
Через Docker — в любом примере замените command и args:
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "REMNAWAVE_BASE_URL", "-e", "REMNAWAVE_API_TOKEN", "ghcr.io/3apa3a-3aho3a/remnawave-mcp:3.4"],Несколько панелей — в том же блоке добавьте переменные второй панели (REMNAWAVE_2_BASE_URL, REMNAWAVE_2_API_TOKEN, …), см. Несколько панелей.
Раскройте свой клиент:
Расширение в один клик (проще всего, Node.js не нужен — он встроен в Claude Desktop):
Узнайте версию своей панели — она написана внизу панели Remnawave (например,
3.4.5). Откройте последний релиз и скачайте файл под неё:
Панель | Файл |
3.4.x |
|
3.3.x |
|
3.2.x |
|
3.1.x |
|
3.0.x |
|
Взяли не тот файл — не страшно: сервер сам сравнит версии и подскажет в чате, какой файл нужен. 2. Дважды щёлкните по файлу (или Claude → Настройки → Расширения и перетащите файл в окно). 3. Нажмите Установить и заполните адрес панели 1 и API-токен панели 1. Остальные поля можно оставить пустыми. Вторая и третья панель — поля панели 2 и панели 3 (имя, адрес, токен), см. Несколько панелей. 4. Готово — спросите в чате: «Сделай сводку по панели».
Токен хранится в защищённом хранилище Windows/macOS, а не текстом в файле. Обновление — скачать новый .mcpb и открыть его так же. Расширение подключает до трёх панелей; больше трёх, а также вторая и третья панель за Caddy или Cloudflare Access — вручную, как ниже.
Вручную (сервер из исходников или Docker): Claude → Настройки → Разработчик → Edit Config. Полностью закройте Claude (трей → Выход) и в claude_desktop_config.json добавьте сразу после первой {:
"mcpServers": {
"remnawave": {
"command": "node",
"args": ["C:\\Tools\\remnawave-mcp\\dist\\index.js"],
"env": {
"REMNAWAVE_BASE_URL": "https://panel.example.com",
"REMNAWAVE_API_TOKEN": "ВАШ_ТОКЕН"
}
}
},Запятая после блока обязательна, если в файле есть другие настройки. Запустите Claude — в Настройки → Разработчик сервер должен быть в статусе running. Пример целиком: examples/claude_desktop_config.example.json.
Одна команда:
claude mcp add remnawave --scope user -e REMNAWAVE_BASE_URL=https://panel.example.com -e REMNAWAVE_API_TOKEN=ВАШ_ТОКЕН -- node "C:\Tools\remnawave-mcp\dist\index.js"Через Docker (например, Claude Code прямо на сервере с панелью):
claude mcp add remnawave --scope user \
-e REMNAWAVE_BASE_URL=https://panel.example.com \
-e REMNAWAVE_API_TOKEN=ВАШ_ТОКЕН \
-- docker run -i --rm -e REMNAWAVE_BASE_URL -e REMNAWAVE_API_TOKEN ghcr.io/3apa3a-3aho3a/remnawave-mcp:3.4--scope user— сервер доступен во всех проектах.Несколько панелей в одном сервере — добавьте
-e REMNAWAVE_NAME=main -e REMNAWAVE_2_NAME=… -e REMNAWAVE_2_BASE_URL=… -e REMNAWAVE_2_API_TOKEN=…, см. Несколько панелей.--scope local— только в текущем проекте. Так удобно держать разные панели в разных проектах: в каждом своя командаclaude mcp addсо своим адресом и токеном. Настройки хранятся в личном конфиге Claude Code, а не в репозитории.Не используйте
--scope project: он записывает сервер вместе с токеном в.mcp.json, который обычно попадает в git.Проверка:
claude mcp list— сервер должен быть✓ Connected.
Файл ~/.cursor/mcp.json — для всех проектов, или .cursor/mcp.json в папке проекта. Сервер появится в Settings → MCP.
{
"mcpServers": {
"remnawave": {
"command": "node",
"args": ["C:\\Tools\\remnawave-mcp\\dist\\index.js"],
"env": {
"REMNAWAVE_BASE_URL": "https://panel.example.com",
"REMNAWAVE_API_TOKEN": "ВАШ_ТОКЕН"
}
}
}
}Файл .vscode/mcp.json в проекте или команда MCP: Open User Configuration — для всех проектов. Ключ здесь servers, а токен VS Code спросит при первом запуске и сохранит в защищённом хранилище — в файл он не попадает.
{
"inputs": [
{ "type": "promptString", "id": "remnawave-token", "description": "Remnawave API token", "password": true }
],
"servers": {
"remnawave": {
"command": "node",
"args": ["C:\\Tools\\remnawave-mcp\\dist\\index.js"],
"env": {
"REMNAWAVE_BASE_URL": "https://panel.example.com",
"REMNAWAVE_API_TOKEN": "${input:remnawave-token}"
},
"type": "stdio"
}
}
}Шаблоны запросов в чате Copilot — через «/».
Settings → Cascade → MCP Servers → View raw config — откроется mcp_config.json.
{
"mcpServers": {
"remnawave": {
"command": "node",
"args": ["C:\\Tools\\remnawave-mcp\\dist\\index.js"],
"env": {
"REMNAWAVE_BASE_URL": "https://panel.example.com",
"REMNAWAVE_API_TOKEN": "ВАШ_ТОКЕН"
}
}
}
}Значок MCP Servers в панели Cline → Configure → Configure MCP Servers — откроется cline_mcp_settings.json.
{
"mcpServers": {
"remnawave": {
"command": "node",
"args": ["C:\\Tools\\remnawave-mcp\\dist\\index.js"],
"env": {
"REMNAWAVE_BASE_URL": "https://panel.example.com",
"REMNAWAVE_API_TOKEN": "ВАШ_ТОКЕН"
}
}
}
}Команда zed: open settings file (или Settings → AI → MCP Servers). Ключ здесь context_servers.
{
"context_servers": {
"remnawave": {
"command": "node",
"args": ["C:\\Tools\\remnawave-mcp\\dist\\index.js"],
"env": {
"REMNAWAVE_BASE_URL": "https://panel.example.com",
"REMNAWAVE_API_TOKEN": "ВАШ_ТОКЕН"
}
}
}
}Вкладка Program справа → Install → Edit mcp.json.
{
"mcpServers": {
"remnawave": {
"command": "node",
"args": ["C:\\Tools\\remnawave-mcp\\dist\\index.js"],
"env": {
"REMNAWAVE_BASE_URL": "https://panel.example.com",
"REMNAWAVE_API_TOKEN": "ВАШ_ТОКЕН"
}
}
}
}Файл ~/.codex/config.toml (формат TOML):
[mcp_servers.remnawave]
command = "node"
args = ['C:\Tools\remnawave-mcp\dist\index.js']
[mcp_servers.remnawave.env]
REMNAWAVE_BASE_URL = "https://panel.example.com"
REMNAWAVE_API_TOKEN = "ВАШ_ТОКЕН"Файл ~/.gemini/settings.json — для всех проектов, или .gemini/settings.json в проекте.
{
"mcpServers": {
"remnawave": {
"command": "node",
"args": ["C:\\Tools\\remnawave-mcp\\dist\\index.js"],
"env": {
"REMNAWAVE_BASE_URL": "https://panel.example.com",
"REMNAWAVE_API_TOKEN": "ВАШ_ТОКЕН"
}
}
}
}Удалённые клиенты (Streamable HTTP)
По умолчанию ничего не меняется: сервер использует stdio. Для HTTP установите
REMNAWAVE_TRANSPORT=http и отдельный Bearer-токен. Без токена сервер не запустится.
Используйте случайный ключ (например, openssl rand -hex 32), не токен панели.
export REMNAWAVE_BASE_URL=https://panel.example.com
export REMNAWAVE_API_TOKEN=YOUR_PANEL_TOKEN
export REMNAWAVE_HTTP_TOKEN="$(openssl rand -hex 32)"
REMNAWAVE_TRANSPORT=http node dist/index.jsПеременная | По умолчанию | Назначение |
|
|
|
|
| Адрес прослушивания |
|
| Порт HTTP |
| — | Обязательный ключ доступа в HTTP-режиме |
В клиенте с поддержкой Streamable HTTP и пользовательских заголовков укажите
http://127.0.0.1:3001/mcp и заголовок Authorization: Bearer <HTTP_TOKEN>.
OAuth и подключение облачных клиентов, требующих OAuth, пока не поддерживаются.
Браузерные запросы с заголовком Origin отклоняются.
Для удалённого доступа используйте HTTPS reverse proxy (пример фрагмента nginx внутри TLS server):
location = /mcp {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
proxy_set_header Authorization $http_authorization;
proxy_buffering off;
proxy_read_timeout 1800s;
client_max_body_size 1m;
}Для Docker можно явно установить REMNAWAVE_HTTP_HOST=0.0.0.0 внутри контейнера
и опубликовать порт только на loopback хоста (-p 127.0.0.1:3001:3001).
Не публикуйте HTTP без TLS в интернет. Передавайте ключи через защищённый env-файл.
Каждая MCP-сессия получает собственные карты псевдонимов и кэши. В HTTP-режиме
REMNAWAVE_PRIVACY_SALT игнорируется, псевдонимы не переносятся между сессиями.
Лимит — 32 сессии, сессии удаляются после 30 минут без активности (проверка раз в минуту).
Ключ доступа общий: это не многопользовательская авторизация; любой владелец ключа
получает доступ ко всем настроенным панелям. Ограничивайте доступ и частоту запросов на reverse proxy.
Внимание: по умолчанию действуют read-only и strict privacy. Как и в stdio,
REMNAWAVE_READONLY=false включает запись, а REMNAWAVE_PRIVACY=off отключает
маскирование. Утечка HTTP-ключа в режиме записи означает доступ к изменению панели.
Не включайте эти режимы без необходимости, используйте HTTPS и минимальные права токена панели.
Нет вашего клиента в списке? Подойдёт любой, который умеет запускать локальные MCP-серверы: укажите команду
node, путь кdist/index.jsи две переменные —REMNAWAVE_BASE_URLиREMNAWAVE_API_TOKEN.
Несколько панелей
Один сервер может работать сразу с несколькими панелями Remnawave. Инструментов при этом столько же, сколько с одной панелью, — у каждого появляется обязательный параметр panel со списком имён ваших панелей:
ассистент выбирает панель по смыслу вопроса («на панели second»), а если не понял — уточняет или проверяет все и подписывает результаты;
каждый ответ начинается со строки
[panel: имя], а инструментlist_panelsпоказывает подключённые панели и их версии;ID пользователей, UUID нод и хостов у каждой панели свои — сервер никогда не отправит запрос не в ту панель, а без
panelзапрос не выполняется вовсе;версия, лимит повторов GeoCheck и ошибки — отдельно по каждой панели; одна недоступная панель не мешает работать с остальными;
псевдонимы клиентов общие: одинаковый псевдоним означает одно и то же значение, даже если он встретился в разных панелях (например, один e-mail в обеих).
Если панель одна, ничего не меняется: параметра panel нет, настройки прежние.
Настройка. Первая панель — как обычно, остальные — те же переменные с номером после REMNAWAVE_. Имена задавать необязательно (по умолчанию panel1, panel2, …), но с понятными именами ассистенту проще:
"env": {
"REMNAWAVE_NAME": "main",
"REMNAWAVE_BASE_URL": "https://panel.example.com",
"REMNAWAVE_API_TOKEN": "ТОКЕН_ПАНЕЛИ_1",
"REMNAWAVE_2_NAME": "second",
"REMNAWAVE_2_BASE_URL": "https://panel2.example.net",
"REMNAWAVE_2_API_TOKEN": "ТОКЕН_ПАНЕЛИ_2"
}Третья панель — REMNAWAVE_3_…, и так далее, без ограничения. Если вторая панель стоит за Caddy или Cloudflare Access — REMNAWAVE_2_API_KEY, REMNAWAVE_2_CF_ACCESS_CLIENT_ID, REMNAWAVE_2_CF_ACCESS_CLIENT_SECRET, REMNAWAVE_2_HEADERS.
Расширение Claude Desktop — до трёх панелей: поля панели 2 и панели 3 в настройках расширения (Настройки → Расширения → Remnawave → Configure). Пустые поля — панели нет.
Docker — передайте и переменные второй панели:
"-e", "REMNAWAVE_NAME", "-e", "REMNAWAVE_2_NAME", "-e", "REMNAWAVE_2_BASE_URL", "-e", "REMNAWAVE_2_API_TOKEN".Сервер собран под одну версию контракта, поэтому панели лучше держать на одной версии. Если версия одной панели другая, сервер предупредит именно про неё. Не меняйте ради неё версию контракта — сломаются остальные; такую панель подключите отдельным сервером (отдельным блоком
mcpServersили вторым Docker-образом под её версию).
Старый способ — отдельный блок mcpServers на каждую панель — тоже работает, но тогда у ассистента два набора одинаковых инструментов, и в части клиентов (Cursor и др.) упирается в лимит числа инструментов.
Настройки (переменные окружения)
Переменная | Обязательна | Что делает |
| да | Адрес панели: |
| да | API-токен |
| нет | Имя панели для ассистента (латиница, цифры, |
| нет | Вторая панель; третья — |
| нет |
|
| нет |
|
| нет | Любая длинная строка — псевдонимы не меняются после перезапуска |
| нет | Устаревшее: |
| нет | Заголовок |
| нет | Дополнительные заголовки к каждому запросу, JSON: |
| нет | Панель за Cloudflare Access |
| нет | Скрыть инструменты (через запятую) |
| нет | Оставить только эти инструменты |
| нет | Максимальная длина ответа, по умолчанию 60000. Длинные списки сокращаются с пометкой «показано N из M». 0 — без ограничения |
| нет |
|
| нет | Потолок размера списков за один запрос, по умолчанию 200 (0 — без ограничения). Исключение — |
| нет | Не чаще одного GeoCheck на ноду раз в N минут, по умолчанию 30 (0 — без ограничения) |
| нет | То же для списков подключений, по умолчанию 2 |
| нет | Таймаут запроса, по умолчанию 30000 (0 — без таймаута) |
Адрес, токен, имя и заголовки без номера (REMNAWAVE_BASE_URL, REMNAWAVE_API_KEY, REMNAWAVE_HEADERS, CF_ACCESS_…) относятся к первой панели, с номером — к панели с этим номером. Все остальные настройки (REMNAWAVE_READONLY, REMNAWAVE_PRIVACY, лимиты, таймаут) общие для всех панелей.
Что можно спросить
Готовые шаблоны есть в клиентах, которые поддерживают MCP-шаблоны (prompts): Сводка по панели, Разбор клиента, Кто делится подпиской, Проверка нод. В остальных просто пишите текстом.
Или обычным текстом:
«Как дела у панели?» — сводка: онлайн, офлайн-ноды, трафик, истекающие подписки
«Разбери клиента 1234» — подписка, устройства, трафик по дням и нодам, последние запросы
«Кто похоже делится подпиской?»
«Сколько активных и онлайн пользователей?»
«Найди клиента с Telegram ID 123456789, покажи устройства и срок подписки»
«Какие ноды офлайн?» / «Трафик по нодам за прошлую неделю»
«Сделай GeoCheck ноды NL-1»
«Кто сейчас на ноде DE-1 и с каких IP?»
«Топ пользователей по количеству устройств» / «Отчёт по торрентам за сутки»
С несколькими панелями: «Сравни онлайн на main и second», «На какой панели клиент с Telegram ID 123456789?»
GeoCheck и запросы подключений выполняются на ноде и тратят её трафик. Поэтому повторный запрос для той же ноды в течение 30 минут (подключения — 2 минут) отдаёт прошлый результат с пометкой, а не нагружает ноду снова.
Приватность
Всё, что сервер отдаёт ИИ, попадает в историю чата. Поэтому ответы панели фильтруются у вас на компьютере, до отправки в чат.
Что |
|
|
|
Приватные ключи и shortId(s) Reality, UUID и | скрыто | скрыто | видно |
VLESS UUID, ссылки | скрыто | скрыто | видно |
Команды «ключи подключения» и «сырая подписка» | недоступны | недоступны | доступны |
username, email, Telegram ID, описание клиента | псевдоним | видно | видно |
IP-адреса клиентов (в т.ч. с портом и в отчётах торрент-блокера), HWID (в т.ч. ID устройства в User-Agent), имена компьютеров клиентов ( | псевдоним | видно | видно |
ID пользователя в панели, статус, трафик, сроки, ноды, статистика | видно | видно | видно |
Как работают псевдонимы. Вместо ivan_petrov ИИ видит user~dca596, вместо IP — ip~f055f3. Одинаковые значения дают одинаковые псевдонимы, поэтому ИИ всё равно заметит, что «у двух клиентов один IP» или «это тот же человек», но самих данных не узнает. Псевдоним можно передать обратно в команду — сервер подставит настоящее значение локально.
Настоящие данные нужны вам — откройте клиента в панели по его ID.
Чего фильтр не может: то, что вы сами пишете в чат (например, «найди Telegram ID 123…»), в чате остаётся. Спрашивайте по ID пользователя или псевдониму, когда это возможно.
Также:
В расширении токен хранится в защищённом хранилище системы; при ручной установке — только в конфиге вашего MCP-клиента, в репозиторий не попадает.
Каждое изменение проверяется автотестами: если что-то начнёт пропускать ключи или данные клиентов, CI станет красным.
Используйте отдельный токен только на чтение, чтобы его можно было отозвать, не трогая боты и мониторинг.
Если не работает
Сначала проверьте сервер без клиента — в папке сервера:
$env:REMNAWAVE_BASE_URL="https://panel.example.com"; $env:REMNAWAVE_API_TOKEN="ВАШ_ТОКЕН"; node dist/index.jsСтрока remnawave-mcp 1.5.0: 91 tools, read-only, privacy strict, https://panel.example.com (с несколькими панелями — … 2 panels: main (https://…), second (https://…)) значит, что сервер запускается (дальше он ждёт клиента — закройте его Ctrl+C). Ошибка вместо неё — ищите её в таблице.
Симптом | Что делать |
Клиент показывает сервер с ошибкой / красным статусом | Проверьте блок настроек: запятые, скобки, |
| Токен истёк или удалён — создайте новый |
| Токену не хватает |
| Панель недоступна или неверный |
| Подключено несколько панелей, а ассистент не указал, какую, — уточните в вопросе («на панели main») |
| У двух панелей одинаковое имя — задайте разные |
Инструменты не появились | Полностью перезапустите клиент (Claude Desktop — через трей → Выход) |
Где логи сервера. Их показывает сам клиент — обычно в разделе настроек MCP или в окне вывода. Claude Desktop пишет их в файлы:
Get-Content "$env:APPDATA\Claude\logs\mcp-server-remnawave.log" -Tail 30Проверить, что Claude Desktop видит настройки:
Get-Content "$env:APPDATA\Claude\claude_desktop_config.json" -Raw | ConvertFrom-Json | Select-Object -ExpandProperty mcpServers | Format-ListОбновление
Расширение Claude Desktop — скачать новый
.mcpbиз последнего релиза и открыть.Docker —
docker pull ghcr.io/3apa3a-3aho3a/remnawave-mcp:3.4.Из исходников:
cd C:\Tools\remnawave-mcp
git pull
npm ci
npm run buildПосле обновления перезапустите клиент.
Раз в сутки GitHub Actions сверяет последний стабильный релиз панели Remnawave с версией контракта в проекте и, если вышла новая, сам открывает Pull Request с обновлённым и собранным проектом и запускает на нём обычные проверки CI. Промежуточные сборки контракта (dev-версии между релизами) пропускаются.
Как это устроено
В пакете @remnawave/backend-contract каждая ручка API описана «командой»: адрес, метод, схемы параметров и пометка чтение/запись. Сервер при запуске проходит по всем командам и превращает каждую в MCP-инструмент, проверяя аргументы теми же схемами, что и панель. Поэтому код почти не зависит от версии Remnawave.
src/
index.ts — точка входа: настройки, выбор инструментов, запуск
server.ts — MCP-сервер: список инструментов, проверка аргументов, вызовы, шаблоны
registry.ts — сборка инструментов из контракта, список запрещённых
extras.ts — отчёты и удобные инструменты (panel_overview, user_report, …)
validate.ts — проверка аргументов и безопасная подстановка в адрес запроса
views.ts — компактный вид ответов (get_nodes и др.)
hints.ts — предупреждения к командам записи
prompts.ts — шаблоны запросов (MCP prompts)
redact.ts — фильтр приватности: скрытие секретов и псевдонимы
format.ts — компактный вывод и сокращение длинных списков
limits.ts — ограничение повторов GeoCheck и размера списков
version.ts — сверка версии панели с версией сборки
client.ts — HTTP-запросы к панели
config.ts — переменные окружения, список панелейДля разработчиков
npm test # 75 тестов: приватность (ничего не утекает), отчёты, ограничения, список инструментов, несколько панелей
npm run pack:mcpb # расширение под текущую версию → build\remnawave-3.4.mcpb
npm run pack:mcpb -- --all # под все версии 3.x → build\remnawave-3.0.mcpb … remnawave-3.4.mcpbCI на каждый push: сборка, тесты,
npm audit, сборка расширения (файл доступен в Actions → запуск → Artifacts).Релиз: поднять версию в
package.json, добавить раздел вCHANGELOG.md, затем на сайте Releases → Draft a new release (тегvX.Y.Z) или из консолиgit tag vX.Y.Z+git push origin vX.Y.Z. GitHub сам соберёт, прогонит тесты и приложит файлы.mcpbпод все версии 3.x; пустой текст релиза заполнится из CHANGELOG.Перед релизом поправьте статичные бейджи в шапке обоих README (поддерживаемые версии Remnawave, число тестов), а при смене подписей — баннер и пример диалога в
docs/(это SVG, текст в них правится как в обычном файле).Новая версия панели: раз в сутки workflow сверяет стабильный релиз Remnawave и открывает PR с обновлённым контрактом.
Благодарности
Идея — TrackLine/mcp-remnawave (под Remnawave 2.x).
Режим Streamable HTTP для удалённых клиентов — Иван Зорин (@localzet), #24.
Лицензия
Available Tools
91 toolsfind_userBRead-only
Find users by any identifier: id, username, shortUuid, telegramId, email or tag. Returns full user objects. Use this instead of guessing which endpoint to call.
Inputs: id, tag, email, username, +2 more.
generate_x25519BRead-only
Generate 30 X25519 keypairs [READ] GET /api/system/tools/x25519/generate
Inputs: no named parameters.
geocheck_nodeARead-only
Run GeoCheck on a node and wait for the result (how services see the node IP: country, blocks). Returns a compact report: country per service and GeoIP base, reputation, blocks, one line per route target. Pass full: true for the raw report with every traceroute hop (large); the SVG image is always dropped. Uses node traffic: repeated calls for the same node within the cooldown return the previous result (also with full: true — it does not start a new check).
Inputs: nodeUuid (required), ip, full, interface, +1 more.
get_all_inboundsARead-only
Get all inbounds from all config profiles [READ] GET /api/config-profiles/inbounds
Inputs: no named parameters.
get_bandwidth_statsCRead-only
Get Bandwidth Stats [READ] GET /api/system/stats/bandwidth
Inputs: tz.
get_computed_config_profile_by_uuidBRead-only
Get computed config profile by uuid [READ] GET /api/config-profiles/:uuid/computed-config
Inputs: uuid (required).
get_config_profile_by_uuidBRead-only
Get config profile by uuid [READ] GET /api/config-profiles/:uuid
Inputs: uuid (required).
get_config_profilesCRead-only
Get config profiles [READ] GET /api/config-profiles/
Inputs: no named parameters.
get_config_profiles_tagsBRead-only
Get tags of Config Profiles [READ] GET /api/config-profiles/tags
Inputs: no named parameters.
get_configurationBRead-only
Get Remnawave Configuration Returns some of the configuration values. [READ] GET /api/system/configuration
Inputs: no named parameters.
get_external_squad_by_uuidBRead-only
Get external squad by uuid [READ] GET /api/external-squads/:uuid
Inputs: uuid (required).
get_external_squadsBRead-only
Get all external squads [READ] GET /api/external-squads/
Inputs: no named parameters.
get_external_squads_tagsBRead-only
Get tags of External Squads [READ] GET /api/external-squads/tags
Inputs: no named parameters.
get_hostBRead-only
Get a host by UUID [READ] GET /api/hosts/:uuid
Inputs: uuid (required).
get_hostsBRead-only
Get hosts [READ] GET /api/hosts/
Inputs: no named parameters.
get_hosts_tagsBRead-only
Get tags of hosts [READ] GET /api/hosts/tags
Inputs: no named parameters.
get_http_statsCRead-only
Get HTTP Stats [READ] GET /api/system/stats/http
Inputs: no named parameters.
get_hwid_devicesBRead-only
Get HWID devices Please note that the filters here are primarily intended for use by the frontend and rely on expensive operators such as LIKE under the hood. Misusing these filters may negatively impact the performance of your database. [READ] GET /api/hwid/devices
Inputs: size, start, filters, sorting, +2 more.
get_hwid_devices_statsCRead-only
Get HWID devices stats [READ] GET /api/hwid/devices/stats
Inputs: no named parameters.
get_inbounds_by_profile_uuidARead-only
Get inbounds by profile uuid [READ] GET /api/config-profiles/:uuid/inbounds
Inputs: uuid (required).
get_infra_billing_nodesCRead-only
Get infra billing nodes [READ] GET /api/infra-billing/nodes
Inputs: no named parameters.
get_infra_billing_recordsBRead-only
Get infra billing history [READ] GET /api/infra-billing/history
Inputs: size, start.
get_infra_providerCRead-only
Get infra provider by uuid [READ] GET /api/infra-billing/providers/:uuid
Inputs: uuid (required).
get_infra_providersBRead-only
Get all infra providers [READ] GET /api/infra-billing/providers
Inputs: no named parameters.
get_internal_squadBRead-only
Get internal squad by uuid [READ] GET /api/internal-squads/:uuid
Inputs: uuid (required).
get_internal_squad_accessible_nodesCRead-only
Get internal squad accessible nodes [READ] GET /api/internal-squads/:uuid/accessible-nodes
Inputs: uuid (required).
get_internal_squadsBRead-only
Get all internal squads [READ] GET /api/internal-squads/
Inputs: no named parameters.
get_internal_squads_tagsBRead-only
Get tags of Internal Squads [READ] GET /api/internal-squads/tags
Inputs: no named parameters.
get_internal_squad_usageARead-only
Get internal squad users traffic usage for a period Returns users whose total usage over the period on the given nodes is >= minTotalBytes, scoped to the nodes reachable via the internal squad inbounds. Underlying usage data is flushed to the database roughly every 2 minutes. [READ] GET /api/bandwidth-stats/internal-squads/:uuid/usage
Inputs: end (required), uuid (required), start (required), limit, +2 more.
get_internal_squad_user_usageARead-only
Get a single user daily traffic usage on the internal squad nodes for a period Returns users whose total usage over the period on the given nodes is >= minTotalBytes, scoped to the nodes reachable via the Internal Squad inbounds. Every day in the range is present (zero-filled). Underlying usage data is flushed to the database roughly every 2 minutes. [READ] GET /api/bandwidth-stats/internal-squads/:squadUuid/users/:userId/usage
Inputs: end (required), start (required), userId (required), squadUuid (required).
get_metadataBRead-only
Get Remnawave Information [READ] GET /api/system/metadata
Inputs: no named parameters.
get_nodeBRead-only
Get node by UUID [READ] GET /api/nodes/:uuid Returns a compact view; pass full: true for the raw panel response.
Inputs: uuid (required), full.
get_node_integrationBRead-only
Get Node Integration by uuid [READ] GET /api/node-integrations/:uuid
Inputs: uuid (required).
get_node_integrationsBRead-only
Get all Node Integrations [READ] GET /api/node-integrations/
Inputs: no named parameters.
get_node_metadataCRead-only
Get node metadata [READ] GET /api/metadata/node/:uuid
Inputs: uuid (required).
get_node_pluginBRead-only
Get Node Plugin by uuid [READ] GET /api/node-plugins/:uuid
Inputs: uuid (required).
get_node_pluginsBRead-only
Get all Node Plugins [READ] GET /api/node-plugins/
Inputs: no named parameters.
get_node_plugins_tagsBRead-only
Get tags of Node Plugins [READ] GET /api/node-plugins/tags
Inputs: no named parameters.
get_nodesCRead-only
Get nodes [READ] GET /api/nodes/ Returns a compact view; pass full: true for the raw panel response.
Inputs: full.
get_nodes_metricsCRead-only
Get Nodes Metrics [READ] GET /api/system/nodes/metrics
Inputs: no named parameters.
get_nodes_statisticsCRead-only
Get Nodes Statistics [READ] GET /api/system/stats/nodes
Inputs: tz.
get_nodes_tagsCRead-only
Get nodes tags [READ] GET /api/nodes/tags
Inputs: no named parameters.
get_node_usageBRead-only
Get users exceeding a traffic threshold on the given nodes for a period Returns users whose total usage over the period on the given nodes is >= minTotalBytes. Underlying usage data is flushed to the database roughly every 2 minutes. [READ] POST /api/bandwidth-stats/nodes/usage
Inputs: end (required), body (required), start (required), minTotalBytes.
get_recapCRead-only
Get Recap [READ] GET /api/system/stats/recap
Inputs: no named parameters.
get_remnawave_healthBRead-only
Get Remnawave Health [READ] GET /api/system/health
Inputs: no named parameters.
get_remnawave_settingsBRead-only
Get Remnawave settings [READ] GET /api/remnawave-settings/
Inputs: no named parameters.
get_snippetsCRead-only
Get snippets [READ] GET /api/snippets/
Inputs: no named parameters.
get_statsDRead-only
Get Stats [READ] GET /api/system/stats
Inputs: tz.
get_stats_digestARead-only
Get Stats Digest Aggregated statistics for a datetime range [start, end): created and expired users, total traffic, traffic spent by users created within the range and new HWID devices. Per-user traffic history is stored with daily granularity (UTC), so the "traffic by new users" metric snaps to whole days at the range edges. [READ] GET /api/system/stats/digest
Inputs: end (required), start (required).
get_stats_nodes_usageCRead-only
Get Nodes Usage by Range [READ] GET /api/bandwidth-stats/nodes/
Inputs: end (required), start (required), topNodesLimit.
get_stats_nodes_users_usageCRead-only
Get Nodes Users Usage by Nodes UUIDs [READ] POST /api/bandwidth-stats/nodes/users
Inputs: end (required), body (required), start (required), topUsersLimit.
get_stats_node_users_usageCRead-only
Get Node Users Usage by Node UUID [READ] GET /api/bandwidth-stats/nodes/:uuid/users
Inputs: end (required), uuid (required), start (required), topUsersLimit.
get_stats_user_usageCRead-only
Get User Usage by Range [READ] GET /api/bandwidth-stats/users/:userId
Inputs: end (required), start (required), userId (required), topNodesLimit.
get_statusBRead-only
Get the status of the authentication [READ] GET /api/auth/status
Inputs: no named parameters.
get_subpage_configBRead-only
Get subscription page config by uuid [READ] GET /api/subscription-page-configs/:uuid
Inputs: uuid (required).
get_subpage_config_by_short_uuidCRead-only
Get Subpage Config by Short UUID [READ] GET /api/subscriptions/subpage-config/:shortUuid
Inputs: shortUuid (required).
get_subpage_configsARead-only
Get all subscription page configs [READ] GET /api/subscription-page-configs/
Inputs: no named parameters.
get_subpage_configs_tagsBRead-only
Get tags of Subpage Configs [READ] GET /api/subscription-page-configs/tags
Inputs: no named parameters.
get_subscription_by_idBRead-only
Get subscription by User ID [READ] GET /api/subscriptions/by-id/:userId
Inputs: userId (required).
get_subscription_by_short_uuid_protectedCRead-only
Get subscription by short uuid (protected route) [READ] GET /api/subscriptions/by-short-uuid/:shortUuid
Inputs: shortUuid (required).
get_subscription_by_usernameBRead-only
Get subscription by username [READ] GET /api/subscriptions/by-username/:username
Inputs: username (required).
get_subscription_info_by_short_uuidDRead-only
Get Subscription Info by Short UUID [READ] GET /api/sub/:shortUuid/info
Inputs: shortUuid (required).
get_subscription_request_historyARead-only
Get all subscription request history Please note that the filters here are primarily intended for use by the frontend and rely on expensive operators such as LIKE under the hood. Misusing these filters may negatively impact the performance of your database. [READ] GET /api/subscription-request-history/
Inputs: size, start, filters, sorting, +2 more.
get_subscription_request_history_statsCRead-only
Get subscription request history stats [READ] GET /api/subscription-request-history/stats
Inputs: no named parameters.
get_subscriptionsCRead-only
Get all subscriptions [READ] GET /api/subscriptions/
Inputs: size, start.
get_subscription_settingsBRead-only
Get subscription settings [READ] GET /api/subscription-settings/
Inputs: no named parameters.
get_subscription_templateBRead-only
Get subscription template by uuid [READ] GET /api/subscription-templates/:uuid
Inputs: uuid (required).
get_subscription_templatesBRead-only
Get all subscription templates (wihout content) [READ] GET /api/subscription-templates/
Inputs: no named parameters.
get_subscription_templates_tagsBRead-only
Get tags of Subscription Templates [READ] GET /api/subscription-templates/tags
Inputs: no named parameters.
get_top_users_by_hwid_devicesCRead-only
Get top users by HWID devices [READ] GET /api/hwid/devices/top-users
Inputs: size, start.
get_torrent_blocker_reportsBRead-only
Get Torrent Blocker Reports Please note that the filters here are primarily intended for use by the frontend and rely on expensive operators such as LIKE under the hood. Misusing these filters may negatively impact the performance of your database. [READ] GET /api/node-plugins/torrent-blocker
Inputs: size, start, filters, sorting, +2 more.
get_torrent_blocker_reports_statsBRead-only
Get Torrent Blocker Reports Stats [READ] GET /api/node-plugins/torrent-blocker/stats
Inputs: no named parameters.
get_user_accessible_nodesCRead-only
Get user accessible nodes [READ] GET /api/users/:userId/accessible-nodes
Inputs: userId (required).
get_user_by_idBRead-only
Get user by ID [READ] GET /api/users/:userId
Inputs: userId (required).
get_user_by_short_uuidBRead-only
Get user by Short UUID [READ] GET /api/users/by-short-uuid/:shortUuid
Inputs: shortUuid (required).
get_user_by_usernameCRead-only
Get user by username [READ] GET /api/users/by-username/:username
Inputs: username (required).
get_user_hwid_devicesCRead-only
Get user HWID devices [READ] GET /api/hwid/devices/:userId
Inputs: userId (required).
get_user_metadataCRead-only
Get user metadata [READ] GET /api/metadata/user/:userId
Inputs: userId (required).
get_usersBRead-only
Get all users using offset-based pagination Please note that the filters here are primarily intended for use by the frontend and rely on expensive operators such as LIKE under the hood. Misusing these filters may negatively impact the performance of your database. [READ] GET /api/users/
Inputs: size, start, filters, sorting, +2 more.
get_users_streamARead-only
Get all users using cursor-based (keyset) pagination with filtering options [READ] GET /api/users/stream
Inputs: tag, size, email, cursor, +4 more.
get_users_tagsCRead-only
Get users tags [READ] GET /api/users/tags
Inputs: no named parameters.
get_user_subscription_request_historyBRead-only
Get user subscription request history, recent 24 records [READ] GET /api/users/:userId/subscription-request-history
Inputs: userId (required).
node_connectionsARead-only
Get users currently connected to a node with their IPs and last-seen time (starts a job and waits).
Inputs: nodeUuid (required), timeoutSec.
panel_overviewARead-only
One-call panel summary: users by status and online, node list with online/offline state, users online and traffic per node, bandwidth (2 days / 7 days / month) and subscriptions expiring soon. Start here for "how is the panel doing" questions.
Inputs: expiringDays.
resolve_userBRead-only
Resolve a user Resolve a user by ID, Short UUID or username. Exactly one of the fields must be provided. [READ] POST /api/users/resolve
Inputs: body (required).
sharing_suspectsARead-only
Find clients who probably share their subscription: many different IPs / apps in recent subscription requests, and top users by number of HWID devices. Only counts and pseudonyms are returned. Cheap: reads panel history, does not touch the nodes.
Inputs: minIps, records.
test_srr_matcherCRead-only
Test SRR Matcher [READ] POST /api/system/testers/srr-matcher
Inputs: body (required).
user_connectionsARead-only
Get a user's current connections (IPs per node) — starts a job and waits for the result.
Inputs: userId (required), timeoutSec.
user_reportARead-only
Everything about one client in one call: subscription and status, devices (HWID), traffic per day and per node for the last N days, recent subscription requests (apps, IP pseudonyms). Identify the user by id, username, telegramId, email, shortUuid or a pseudonym.
Inputs: id, tag, days, email, +3 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 91 tools
Many tools overlap heavily: find_user, resolve_user, get_user_by_id, get_user_by_username, and get_user_by_short_uuid all target the same resource, and subscriptions have similar redundancy (get_subscription_by_short_uuid_protected, get_subscription_by_username, get_subscription_by_id, get_subscription_info_by_short_uuid). Tags endpoints are duplicated across five resources, and multiple stats/usage tools have subtle distinctions that are easy to misselect.
Most tools follow get_<resource> snake_case, which is predictable, but several deviate to noun-first or non-get verbs: panel_overview, user_report, sharing_suspects, find_user, resolve_user, geocheck_node, node_connections, user_connections, test_srr_matcher, generate_x25519. The mixed conventions are readable but reduce consistency.
91 tools is far beyond a well-scoped set and many are thin per-endpoint wrappers (separate tags endpoints, duplicate user lookups, many stats variants). This volume makes selection hard and should be consolidated into broader resource-level tools.
The surface is overwhelmingly read-only, with no create/update/delete operations for users, nodes, squads, configs, or other core resources. It is rich for observation and reporting but severely incomplete for actual panel management workflows.
Maintenance
Related MCP Connectors
Talk to your public-facing AI from any MCP client — Claude, ChatGPT, Cursor, Cline, Windsurf.
Search your AI chat history (ChatGPT, Claude, Codex) from any MCP client. Remote, private, read-only
Connect ChatGPT and Claude to your Mac, Windows or Linux with secure MCP remote access.
Remote streamable-HTTP MCP server running on a single Cloudflare Worker. Your assistant gets live Airbnb, Amazon, Booking.com, Google Flights, Maps and Reddit data, social search on X, Instagram and TikTok, the Meta Ad Library, and image/video generation without any keys. Connect your own accounts to let it send WhatsApp or Telegram messages, work an IMAP inbox, manage Meta Ads campaigns and publish to X and LinkedIn. OAuth 2.1 with PKCE; stored credentials are AES-256-GCM encrypted.
Related MCP Servers
AlicenseAqualityBmaintenanceLets you use Claude Desktop, or any MCP Client, to use natural language to accomplish things on your Cloudflare account.21,735 npm4,465Apache 2.0- AlicenseAqualityBmaintenanceMCP server for Remnawave panel API. Manage VPN users, nodes, hosts, and system stats from Claude Code or any MCP-compatible client.341MIT
- AlicenseAqualityCmaintenanceInvestigate fraud directly from Claude, Cursor, or any MCP-compatible client. Analyze suspicious activity with clear, evidence-backed verdicts. Pivot from a single signup to every account sharing the same device, IP address, or email inbox. Check entities against a cross-operator abuse network, review linked accounts, and efficiently process your fraud review queue. Read-only by default, with no r1069 npmMIT
- AlicenseCqualityBmaintenanceEnables MCP clients like Claude Code, Claude Desktop, or Cursor to read and manage Remnawave 3.x VPN panel resources — users, nodes, hosts, config profiles, squads, subscription templates, billing, and HWID devices — through the panel's REST API, with contract-driven tool schemas, numeric user IDs, multi-panel config lookup, and an optional readonly mode.20413 npmMIT