hq-mcp
hq-mcp
러시아어 버전 · English
AI 에이전트에게 VPN 비즈니스에 대한 접근을 제공하는 MCP 서버: SHM의 빌링과 Remnawave 패널을 하나로 엮어, 두 시스템에 걸쳐 있는 질문에 한 번의 호출로 답할 수 있게 만든 서버입니다.
어떤 도구도 요청한 그 호출로 무언가를 변경하지 않습니다. 쓰기 도구는 먼저 계획을 반환하고, 적용은 그 계획의 식별자를 담은 두 번째 호출로 이루어집니다.
실제 작동 모습
어떤 설치본에서든 첫 번째 호출은 platform_probe입니다. 이 호출은 이 배포에 무엇이 있고 그중 무엇이 살아 있는지 답하며, 그 외의 모든 것은 이 호출이 알려주는 내용에서 파생됩니다. 아래 답변은 잘라낸 것이고 값은 가상의 것입니다.
platform_probe {}{
"shm": { "configured": true, "reachable": true, "version": "2.19.4", "live": true },
"remna": { "configured": true, "reachable": true, "version": "3.2.3",
"runtime": { "instances": 6, "youngestUptimeSeconds": 54294 } },
"capabilities": { "shm.filter": false, "remna.realtimeBandwidth": true,
"tunnel.mysql": false, "…": "…" },
"warnings": [{ "code": "specs_are_stale", "message": "…" }]
}다음은 두 시스템 중 어느 하나도 단독으로 답하지 못하는 질문입니다: "고객이 결제했다고 하는데 구성이 없습니다."
client_resolve { "query": "kot@example.com" }{
"shm": { "count": 1, "matches": [{ "user_id": 4821, "email": "kot@example.com",
"blocked": false }] },
"remna": { "count": 0, "ambiguous": false,
"paths": [{ "path": "email", "tried": true, "found": 0, "note": null },
{ "path": "service", "tried": true, "found": 0, "note": "…" }] }
}패널은 그에 대해 아무것도 모르지만, 여기서 count: 0은 "계정이 없다"는 뜻이 아닙니다. paths는 거친 모든 검색과 그 검색이 보지 못한 것을 각각 지목합니다. 실제로 무슨 일이 있었는지는 두 번째 시선이 말해줍니다:
provisioning_diagnose { "shm_user_id": 4821 }{
"verdict": "panel_user_missing",
"services": { "items": 1, "diagnosed": [{
"user_service_id": 90210,
"status": "ACTIVE",
"verdict": "panel_user_missing",
"storage": { "name": "vpn_mrzb_90210", "present": true, "checked": true },
"panel": { "username": "HQVPN_90210", "id": 11274, "found": false, "checked": true },
"spool": { "total": 0, "stuck": 0, "failed": 0, "succeeded": 0 },
"history": { "total": 1, "success": 1 }
}] }
}서비스는 ACTIVE, 구성 스냅샷은 제자리에, 프로비저닝은 성공을 보고했는데 — 그 성공의 주인인 사용자가 패널에 없습니다. 빌링도 패널도 각각으로는 이런 모습을 보여주지 않습니다.
두 시스템을 모두 읽는 도구는 서른네 개입니다. rw 모드는 쓰기 도구 열다섯 개를 추가합니다: 열세 개는 실제 데이터를 변경하고, 하나는 계획을 적용하며, 또 하나는 로컬 변이 로그를 읽습니다.
Related MCP server: xendit-mcp
엔드포인트 프록시가 아니라 복합 도구인 이유
가장 당연한 구조는 HTTP 엔드포인트당 도구 하나씩, 약 150개입니다. 그것은 작성되었고 버려졌습니다. 두 가지 이유 때문입니다.
원시 프록시는 어떤 금지 목록도 무력화합니다. 모델이 GET <임의의 경로>를 호출할 수 있다면, 여러분이 주지 않기로 결정한 작업 목록은 장식에 불과합니다. 금지된 경로까지는 한 줄이면 됩니다. 여기서 도구는 이름이 명시된 라우트를 호출하며, 빌드 단계의 스캐너는 금지된 경로가 소스에 리터럴로 등장하면 실행을 중단시킵니다.
그리고 엔드포인트는 질문이 아닙니다. 위 예시는 SHM 라우트 네 개와 패널 라우트 두 개를 건드리며, 그중 흥미로운 것은 바로 이음매입니다. client_overview, sync_audit, provisioning_diagnose는 버그가 바로 그 이음새에 살고 있기 때문에 존재합니다.
그 외 모든 것을 결정한 규칙
빈 응답은 결코 입증된 부재로 받아들여져서는 안 됩니다.
백엔드가 거부하면 도구는 저하됩니다: 거부는 degraded로 이동하고, partial_result 경고는 누락된 절반을 지목하며, 그 절반에 의존했던 어떤 발견도 억제됩니다 — 살아남은 것으로부터 계산되지 않습니다. 목록이 잘리면 서버 측 total이 함께 도착합니다 — "그런 서비스는 없다"가 선언되지 않은 창에 기대지 않도록 말입니다.
이것은 이론적 주의가 아닙니다. 개발 중에 한 도구가 패널의 모든 레코드를 읽고, 상대편에서 필드 이름이 바뀌었기 때문에 전부 버린 다음, 수백 명의 고객이 재프로비저닝이 필요하다고 보고했습니다 — 빈 집합에서 도출된, 자신 있게 말해진 파괴적인 권고였습니다. 수정은 단지 이름이 바뀐 필드만이 아니었습니다: 부적합한 입력에서 계산된 바구니는 발견이 되기를 거부해야 한다는 것이 수정이었습니다.
호환성: 여러분 환경에서 작동할까요
SHM 2.19.4 및 Remnawave 3.2.3에서 검증되었습니다 — 두 숫자 모두 작동 중인 배포에서 가져온 것이지 사양에서 가져온 것이 아닙니다.
최소 요구 사항은 SHM 2.18.0 및 Remnawave 3.0.0입니다. 공식 danuk/shm으로 충분합니다: 도구가 호출하는 모든 라우트는 업스트림이며 포크가 필요 없습니다. 그 배포의 패치가 도구에 보였던 유일한 곳은 GET /user/password-auth의 네 번째 플래그였습니다. 이제 그 부재는 sign_in_flag_absent 경고로 불리며 진단으로 둔갑하지 않습니다. 두 시스템의 전체 라우트 목록, 각각의 등장 버전, 포크에 대한 자세한 답변은 COMPATIBILITY.md에 있습니다.
검증은 한 번의 호출로 끝납니다 — 바로 그 platform_probe입니다. 버전이 최소 요구 사항보다 낮으면 backend_version_below_minimum 경고로 답하며 버전, 최소 요구 사항, 그리고 정확히 무엇이 작동을 멈출지를 지목합니다. 그 과정에서 아무것도 꺼지지 않습니다: 오래된 버전은 조용한 빈 응답이 아니라 특정 라우트에서 시끄러운 거부를 냅니다.
버전 | 사라지는 것 | 해당되는 경우 |
SHM < 2.18.0 |
|
|
SHM < 2.11.3 |
|
|
SHM < 2.9.0 |
|
|
SHM < 2.4.0 |
|
|
패널 < 3.0.0 | 사용자가 숫자 |
|
패널 < 3.0.0 |
|
|
패널 < 3.0.0 |
|
|
패널 < 3.0.0 |
|
|
패널 < 3.2.0 |
|
|
Remnawave 3.x는 2.x용으로 작성된 모든 것과의 호환성을 깨뜨리며, 조용히 깨뜨리지 않습니다. 사용자 객체에서 uuid가 제거되었고, 그와 함께 by-telegram-id, by-email, by-tag 라우트가 사라졌으며, /api/users/{uuid}는 404가 아니라 400으로 응답합니다 — 그래서 거부는 "그런 사용자가 없다"처럼 보이지조차 않습니다. 여기에는 그 라우트들이 아예 없습니다. 서버가 여전히 상속된 uuid를 만나는 곳(예: 오래된 SHM storage 스냅샷)에서는 추측으로 조용히 미끄러지지 않고 응답에서 그 사실을 말합니다.
OpenAPI 사양은 작동 중인 시스템보다 뒤처져 있으므로 platform_probe는 호출할 때마다 specs_are_stale 경고를 담습니다. SHM은 평소보다 더 심합니다 — 그 사양은 런타임에 info.version을 구성에서 찍어내므로, 여러분의 스탠드가 아니라 덤프를 만든 그 스탠드를 설명합니다. 따라서 프로브는 파일에서 버전을 전혀 읽지 않고 작동 중인 시스템에 직접 묻습니다 — 그리고 그 자리에서 이 배포에 무엇이 참인지 확정합니다: SHM의 서버 측 filter가 무언가를 좁히는지, 패널이 사용자 목록에서 filters를 존중하는지(둘 다 200으로 응답하고 낯선 매개변수를 조용히 버립니다), 패널이 구독 요청 로그를 유지하는지, realtime 트래픽 라우트가 존재하는지, 어떤 ssh 터널이 열려 있는지. 그리고 "백엔드가 다운됨"과 "우리 자격 증명이 틀림"을 구분합니다: 401/403은 credentialsRejected로 보고됩니다.
설치
Node 22.12+와 pnpm이 필요하며, 두 시스템 중 하나 이상 — SHM 또는 Remnawave — 이 필요합니다. 둘 다 필수는 아닙니다: 각각 별도로 구성되며 단독으로도 완전한 구성입니다. 없는 시스템의 도구는 아예 게시되지 않습니다 — "빈 응답"이 아니라 부재하며, platform_probe가 무엇이 구성되었는지 직접 지목합니다. 따라서 도구 수는 설치에 따라 달라집니다: 패널만 — 16, SHM만 — 18, 둘 다 — 34 (rw 모드에서는 더 많음).
pnpm install
pnpm build
pnpm run setup
pnpm run setup, 반드시run을 붙여서.pnpm setup은 pnpm 자체의 내장 명령입니다: 셸 프로필을 수정하며 이 저장소와는 무관합니다.
마스터가 존재하는 이유는 그것이 대체하는 단계 — .env를 손으로 쓰는 것 — 이 조용히 실패하기 때문입니다: 패널 토큰의 오타는 서버가 뜨는 것을 막지 못하고, 나중에 무관한 질문 한가운데서 도구 오류로 떠오릅니다. 따라서 마스터는 각 자격 증명을 작동 중인 시스템에서 검증하고 세 가지 거부를 구분합니다 — 호스트가 전혀 응답하지 않음(DNS, TLS, 닫힌 포트), 호스트가 응답하고 자격 증명을 거부함, 호스트가 아무것도 증명하지 않는 것으로 응답함(502, 429): 이들은 서로 다르게 고쳐지며, 하나의 "login failed"는 엉뚱한 것을 고치러 보냈을 것입니다.
마스터는 여러분이 가진 시스템과 접근 모드에 대해서만 묻습니다. 그 외 모든 것은 Configure the optional settings? [y/N] 한 질문으로 접혀 있습니다. 시간대는 추측하지 않고 작동 중인 SHM에서 읽습니다: SHM은 오프셋 없이 자체 로컬 시간으로 날짜를 쓰며, 잘못된 시간대는 모든 연령을 조용히 밀어냅니다. 비밀은 출력하지 않습니다. 기본값은 ro이며, rw에서는 rw라는 단어를 쓰고 별도로 확인해야 합니다 — 그 전에 도구가 몇 개 생기고 그중 몇 개가 실제 빌링과 실제 패널에 쓰는지, 그 순간의 레지스트리로 계산하여 말해줍니다. .env는 이전 사본 위에 0600 권한으로 쓰며, 묻지 않은 변수는 옮기고, Claude Code, Codex, opencode용 연결 명령을 출력합니다 — 그러나 남의 구성을 고치지는 않습니다: JSONC를 다시 쓰는 마스터는 언젠가 누군가의 작동하는 설정을 망가뜨릴 것입니다. 언제든 다시 실행할 수 있으며, Enter는 기존 값을 유지합니다. 터미널 없이는 실행을 거부합니다: MCP 클라이언트는 TTY 없이 서버를 시작하며, 거기서 깨어날 수 있는 마스터는 아무도 보지 못하는 질문에 멈춰 있었을 것입니다.
또는 수동으로
cp .env.example .env && chmod 600 .env # и заполнить각 변수는 .env.example에 설명되어 있습니다. 누락되거나 잘못된 변수는 변수 이름과 기대되는 내용을 지목하며 시작을 중단시킵니다 — 나중에 이해할 수 없는 도구 오류로 떠오르는 대신 말입니다.
{
"mcpServers": {
"hq": {
"command": "node",
"args": ["/absolute/path/to/hq-mcp/apps/stdio/dist/index.js"]
}
}
}두 번째 전송: HTTP 위의 MCP
동일한 도구 세트가 HTTP로 제공됩니다 — 클라이언트가 프로세스를 직접 시작할 수 없을 때 필요합니다: 컨테이너 안에 있거나, 다른 머신에 있거나, 여러 대일 때입니다. 별도의 애플리케이션, 동일한 .env의 구성:
# метка произвольная (её показывает /metrics), токен — не короче 24 символов:
# openssl rand -hex 24
HQ_MCP_HTTP_TOKENS='<label>:<token>' pnpm --filter @hq/http start
# hq-mcp http ready: url=http://127.0.0.1:42480 mode=ro profile=human tools=34 …HQ_MCP_HTTP_TOKENS 없이는 아예 시작하지 않으며, 빌링과 패널에 대한 클라이언트를 만들기 전에 거부합니다. 루프백을 수신합니다. 네트워크에 여는 것은 HQ_MCP_HTTP_HOST=0.0.0.0이며, 이에 대해 경고가 출력됩니다 — 서버와 네트워크 사이에는 이 토큰만 남기 때문입니다. 포트는 HQ_MCP_HTTP_PORT입니다. 클라이언트는 일반적인 Authorization: Bearer로 토큰을 전달하며 /mcp에 연결합니다:
{
"mcpServers": {
"hq": {
"type": "http",
"url": "http://127.0.0.1:42480/mcp",
"headers": { "Authorization": "Bearer <тот же токен>" }
}
}
}라우트는 세션 없는 방식입니다: Mcp-Session-Id가 발급되지도 요구되지도 않으므로, 리버스 프록시 뒤에서 스티키 연결 없이 프로세스 사본 여러 개를 유지할 수 있습니다. 서버 메시지가 없으므로 SSE 스트림에 대한 GET과 세션 종료에 대한 DELETE는 405로 응답합니다 — MCP 클라이언트는 이를 이해합니다. Origin 헤더가 있는 요청은 403으로 거부됩니다: DNS 리바인딩 방어입니다, "제한 사항" 참조.
옆의 /v1/tools는 MCP가 아니라 ai-bot용 내부 REST 파사드입니다: 목록 핸들 하나와 호출 핸들 하나, 자체 응답 봉투와 자체 요청 상한이 있습니다.
Production HTTP 이미지
Production-образ собирается только из проверенного 40-символьного lowercase commit SHA. Этот SHA запечатывается одновременно в OCI label, root-owned read-only файл и /healthz; entrypoint восстанавливает значение из файла, так что runtime-переопределение HQ_MCP_IMAGE_REVISION не меняет health evidence.
pnpm test && pnpm test:guards && pnpm typecheck && pnpm build
HQ_MCP_COMMIT_SHA="$(git rev-parse HEAD)"
test "${#HQ_MCP_COMMIT_SHA}" -eq 40
docker build --build-arg "HQ_MCP_DEPLOYMENT_REVISION=${HQ_MCP_COMMIT_SHA}" --tag "hq-mcp-http:${HQ_MCP_COMMIT_SHA}" .
scripts/http-container-smoke.sh "hq-mcp-http:${HQ_MCP_COMMIT_SHA}"Compose-потребитель фиксирует именно этот 40-символьный tag и не объявляет host ports. Контейнер работает как UID/GID 10001, в bot+ro публикует только /healthz и REST-фасад и требует общий non-secret HQ_MCP_DEPLOYMENT_CONFIG_REVISION в формате lowercase UUID. В health входят обе revision, чтобы ai-bot мог закрыться до чтения каталога при несовпадении.
Production передаёт секреты только через три regular non-symlink файла с точным mode 0600: SHM_ADMIN_AUTH_FILE, REMNA_API_TOKEN_FILE и HQ_MCP_HTTP_TOKENS_FILE. Последний содержит только ai-bot:<dedicated token>. Это отдельный токен сервера; креды SHM и Remnawave тоже выделяются этому deployment отдельно и не переиспользуются из support bot.
Подгонка под свою установку
Апстримный danuk/shm не знает слова «Remnawave» — ни строки. Мост между биллингом и панелью живёт целиком в ваших шаблонах провижининга: один пользователь панели на user_service_id, имя <NAME_PREFIX><user_service_id>, снимок конфигурации в storage SHM под <STORAGE_PREFIX><user_service_id>. Оба префикса сервер читает в рантайме из config.remnawave вашей SHM и позволяет переопределить (HQ_MCP_STORAGE_PREFIX, HQ_MCP_PANEL_PREFIXES) — оператор знает, что в панели лежит сегодня, лучше, чем ключ конфигурации, описывающий, что SHM соберёт завтра.
Имя пользователя панели — единственный ключ связи, и префикс, не совпадающий ни с чем, не даёт ошибки: он даёт уверенный неверный ответ, в котором каждая услуга выглядит непровижиненной. Поэтому инструменты, способные это доказать, говорят кодом prefix_unverified и подавляют затронутую находку — sync_audit не возвращает корзину missingPanelUser вовсе, provisioning_diagnose помечает результат тем же кодом или panel_username_guessed. Конвенция нужна ровно трём инструментам (sync_audit, provisioning_diagnose, мутатор storage_edit); client_overview принимает remna_user_id необязательным параметром и без него просто не показывает половину панели. Если конвенции у вас нет, все остальные инструменты работают как обычно, а эти три не выдумывают находок. Разбор целиком, с порядком префиксов и наследными именами, — в COMPATIBILITY.md.
Инструменты
Тридцать четыре видны в ro; режим rw добавляет пятнадцать из последней таблицы и не убирает ничего. Числа — для профиля human; что из этого видит bot, сказано в модели безопасности.
Платформа и один клиент
Инструмент | На что отвечает |
| Что живо прямо сейчас: версии, возможности, туннели и является ли отказ аварией или кредами |
| Любой идентификатор (telegram id, email, логин, id, имя в панели) в канонические id обеих систем — все совпадения, а не первое |
| Поиск клиентов SHM по фрагменту, с серверным числом совпадений |
| Клиент целиком в обеих системах за один вызов |
| Как учётка входит: email и его подтверждение, OTP, passkey, возможен ли вход паролем, рефералы |
| Деньги глазами клиента: предстоящее списание и те платёжные методы, что реально ему предложены |
| Каталог и промокоды глазами одного клиента — его скидка, его бонусы, скрытые от него тарифы |
Деньги, каталог, конфигурация
Инструмент | На что отвечает |
| Платежи, бонусы, списания и две независимые сверки (баланс и бонус — разные колонки с разными путями обновления) |
| Состояние автоплатежа и все удержанные комиссии — оно лежит в JSON-поле |
| Промокоды и их погашения: это разные строки, и читать их с одной нельзя |
| Тарифы, прайс заказа, дочерние услуги, карта событий, категории — источник допустимых |
| Один ключ конфигурации SHM из закрытого списка, секреты замаскированы. Чтения конфигурации целиком не существует |
| Список шаблонов или тело ровно одного — того файла, который и производит уведомление или скрипт провижининга |
Услуги и провижининг
Инструмент | На что отвечает |
| Услуги клиента: статус, срок, запланированный следующий тариф, задачи спула по каждой |
| Очередь провижининга: залипшие, упавшие, приостановленные и реальная глубина |
| «Оплачено, а конфига нет» — по каждой услуге, а не по клиенту |
| Пакетная сверка биллинга с панелью, обе стороны вычитываются до конца |
| Сказали ли клиенту на самом деле, а если нет — почему; вердикт доставки, которого не показывает больше ничто |
| Собственные транспорты SHM и их группы (ssh, http, mail, telegram) и разрывы, молча останавливающие провижининг. Это не список нод Remnawave |
Панель — сперва со стороны клиента, затем со стороны флота
Инструмент | На что отвечает |
| Карточка Remnawave: статус, срок, трафик, HWID-устройства, последние обращения за подпиской. Ключи — никогда |
| Что страница подписки реально показывает клиенту: платформы, приложения, шаги установки, ссылки кнопок |
| До каких нод этот клиент реально дотягивается и какие сквады и теги инбаундов это дают |
| Картина HWID по всему флоту — та база, без которой число устройств одного клиента ничего не значит |
| Трафик по дням в разрезе нод и сквадов; это временной ряд, а не счётчики карточки |
| Кто подключён прямо сейчас. Панель отвечает на это джобом, и опрос инструмент ведёт сам |
| Ноды × профили конфигурации × инбаунды × хосты × сквады и разрывы между ними |
| Сколько стоит инфраструктура, в стыке с панелью: оплаченная нода, до которой никто не доходит, — это уходящие деньги |
| Ноды, онлайн, трафик и хосты одной страны |
| Что профиль объявляет против того, что панель на самом деле отдала бы ноде |
| Оба семейства сквадов: внутренние решают доступ, внешние — как подписка подана |
| Что происходит с самой панелью: сводка, дайджест за окно, какие маршруты дёргают, история обращений за подпиской |
| Улики торрент-блокера — и, отдельно, установлен ли он вообще и следит ли |
За туннелем (эти два без него отказывают, называя точную ssh-команду)
Инструмент | На что отвечает |
| Находки антиабуз-хука плюс топы панели. Дорого: неограниченные сканы рабочей MySQL биллинга, потолок 5 вызовов на 5 минут |
| SQL только на чтение — префлайт и ничего больше, см. ниже |
Пишущие (только rw, только профиль human, сначала план)
도구 | 변경 내용 |
| SHM 클라이언트의 잔액 또는 보너스 |
| SHM이 현재 결제 기간에 대해 차감한 것으로 기록한 금액을 잔액으로 반환 |
| 패널 클라이언트에 대한 대량 작업 — 지정된 id 집합 또는 전체 플릿 대상 |
| Remnawave 호스트 하나: 라벨, 주소, 포트, SNI/host/path/ALPN/fingerprint, 보안 계층, 태그, 활성화 및 숨김 |
| 명시적 uuid 목록으로 호스트 삭제. 되돌릴 수 없음 |
| 노드 하나: enable, disable, restart, reset_traffic, update, create |
| 패널의 구독 하나: enable, disable, extend, reset_traffic, revoke, set_limits, 기기 해제 |
| 클라이언트 서비스: give, touch, change_plan, schedule_change, stop, activate, delete |
| 스풀의 멈춘 작업 하나에 대한 retry, resume 또는 pause |
| 기존 SHM 템플릿 본문 덮어쓰기 |
| 이 설치 환경에 대해 출력된 키 목록으로 SHM 사용자 스토리지 작성 |
| SHM 전송 문자열 또는 전송 그룹 — 웹훅, 프로비저닝 ssh 엔드포인트, 메일 발신자 |
| 클라이언트 차단 또는 카드의 안전한 필드 수정 ( |
|
|
| 아무것도 아님. 로컬 변이 로그 읽기 — |
변이
아무것도 요청한 호출 자체로 적용되지 않는다. plan_id 없는 변이 도구는
현재 상태를 읽고, 목표 상태를 구성하여 플랜을 반환한다: before,
after, 필드별 diff, 부수 효과, 존재하는 경우 rollback, 그리고
식별자. 아무것도 쓰지 않는다. 적용은 두 번째 호출이다:
ops_confirm { "plan_id": "…" } # либо: тот же мутатор, ТЕ ЖЕ аргументы, плюс plan_id플랜은 이를 구성한 프로필, 이를 위해 구성된 도구, 그리고 인자의
해시에 바인딩된다: 다른 호출자도, 다른 도구도, 하나의 숫자만 바뀐 동일한
도구도 이를 소멸시킬 수 없다. 수명은 10분. 일회성은 디스크에서의 원자적
rename이지 "읽고 삭제"가 아니다: 동시에 이루어지는 스무 번의 확인 중 정확히
하나만 승리하고, 나머지는 "찾을 수 없음"을 받는다. 실패는 유효한 플랜을
소멸시키지 않는다 — 모든 검사는 획득 이후에 이루어지며, 실패한 검사는 파일을
제자리에 되돌린다; 소멸시키는 것은 시도 자체이며, 백엔드가 다운되면 플랜은
소진된다. 이는 의도적이며, 이것이 한 번의 차감과 세 번의 차감의 차이다.
적용 전에 도구는 세계를 다시 읽고 플랜이 구성된 스냅샷과 대조한다: 객체가
이동했다면 플랜은 거부되며, 다른 사람의 변경 위에 덮어쓰지 않는다.
모든 시도는 HQ_MCP_AUDIT_PATH에 기록된다 (JSONL, 권한 0600): 누가,
무엇으로, 어떤 인자로, 객체가 이전과 이후에 어떻게 보였는지, 그리고 결과가
무엇이었는지 — planned, applying, applied, failed 또는 rejected;
실패도 성공과 동등하게. applying은 백엔드 호출 이전에 기록되며, 이것이
구성의 핵심이다: 짝이 되는 종료 기록이 없는 항목은 프로세스가 중간에
죽었음을 의미하며, 플랜 스냅샷은 이미 파괴되었고, 돈은 빠져나갔을 수 있다.
ops_audit는 요청된 창과 무관하게 전체 로그에서 이러한 미종결 기록을 찾아
가장 먼저 보고한다; 파싱되지 않은 줄은 조용히 건너뛰지 않고 집계된다.
상한은 프레임워크가 지키며, 도구 작성자가 지키는 것이 아니다.
HQ_MCP_MAX_OP_AMOUNT를 초과하는 변이는 플랜 구성 전에 거부되며, 프레임워크는
금전 엔드포인트를 선언했지만 입력에서 금액을 읽는 방법을 명시하지 않은 도구의
등록을 거부한다. 상한은 두 종류의 금전 이동을 모두 덮으며, 두 번째는 놓치기
쉽다: 호출자가 금액을 지정하는 결제 및 보너스와, 클라이언트 잔액을 소비하는
수명 주기 작업 (give, touch, change_plan, activate) — 여기서 금액은
카탈로그의 요금제 가격이다. 가격을 읽을 수 없는 플랜은 발급되지 않는다:
숫자를 모르는 것이 차감을 무료로 만들지 않는다. HQ_MCP_MAX_BULK_USERS는
하나의 대량 작업이 건드릴 수 있는 패널 클라이언트 수를 제한하며, 패널에서 이
수를 확인할 수 없는 플랜은 어림짐작으로 평가되지 않고 거부된다. 상한을
초과하면 작업은 전체가 거부된다 — 절대 잘리지 않는다.
상한은 플랜 구성 시에만, 그리고 그때만 검사된다: 적용은 이미 구성된
플랜으로 작동하며 다시 측정하지 않는다. 이로 상한을 우회할 수는 없다 —
인자는 해시로 고정되어 있으므로 — 하지만 플랜 발급 후 .env에서 상한을
낮추어도 이 플랜에는 영향을 미치지 않는다.
template_edit와 storage_edit는 먼저 자체 롤백을 HQ_MCP_BACKUP_DIR에
쓴다 (디렉토리 0700, 파일 0600); 경로는 응답에 반환되고, restore_from은
바이트를 다시 넣으며, 스냅샷을 찍지 않으면 둘 중 어느 것도 쓰지 않는다.
백업은 의도적으로 플랜 스냅샷과 분리된다: 템플릿 본문과 구성 스냅샷은
마스킹할 필드 이름이 없는 순수 하위 문자열로 비밀을 담고 있다 — 즉, 이들을
before/rollback 내부의 모델로 다시 보낼 수 없으며; 롤백은 변경을
견뎌야 하는 반면, 플랜 스냅샷은 한 시간 내에 정리된다.
쓰기 도구가 거부하는 두 가지가 더 있다. <redacted:…> 마커가 있는 본문은
절대 기록되지 않는다: 이는 읽기 도구의 출력이며, 이를 기록하면 살아있는
자격 증명이 그것을 숨긴 단어로 대체될 것이다. 그리고 패널의 원시 블롭
(finalMask, xhttpExtraParams, muxParams, sockoptParams)은 모든 호스트
패치에서 제외된다 — 운영 중인 설치 환경에서 상당수의 호스트가 finalMask
내부에 작동 중인 Hysteria2 비밀번호를 담고 있기 때문이다.
실제로 증명된 것과 그렇지 않은 것
host_edit는 적용 분기가 운영 중인 시스템에서 실행된 유일한 변이
도구이다: 운영 중인 Remnawave 3.2.3 패널에서 호스트 라벨을 변경하고,
finalMask의 비밀번호가 보존되었으며 선언된 필드 외에는 아무것도 변경되지
않았음을 확인하고, 되돌렸다. 다른 모든 것은 플랜까지 포함하여 증명되었다:
플랜은 실제 데이터로 구성되고, 적용 부분은 테스트로 덮여 있지만, 운영 중인
시스템에서는 그 분기가 실행되지 않았다. 이는 문자 그대로 읽어야 한다.
올바르게 보이는 플랜은 플랜에 대한 증명서이다.
보안 모델
두 가지 프로필. human은 신뢰된 운영자이며, 터널이 닫혔을 때의 정확한
ssh 명령을 포함한 구체적이고 실행 가능한 오류를 받는다. bot은 신뢰되지 않은
채널이다: 어떤 오류든 동일한 메시지로 축소되어, 어떤 이름이 다르게 응답하는지
더듬으며 레지스트리를 탐색할 수 없게 한다. 쓰기 도구는 봇에게 절대 제안되지
않는다: rw 프로필에서 bot은 ro와 동일한 스무 개의 읽기 도구를 본다.
금지된 클래스, 단순히 위험한 것과는 별개. 이러한 작업은 게이트로 닫힌
것이 아니라 존재하지 않으며, 빌드 단계의 스캐너는 그 경로가 소스에 리터럴로
나타나면 실행을 중단시킨다. 노드의 identity 및 keygen 라우트 (응답 본문에
개인 키가 있는 GET). 토큰, 인증 및 passkey 라우트 (패널은 토큰을 평문으로
제공하며, 생성된 토큰은 모든 게이트를 우회하는 영구 관리자이다). 패널 및
구독 설정. /admin/config 전체 내보내기. 프로비저닝 작업을 수동으로 성공으로
표시 — 이는 작업을 수행하는 것이 아니라 패널에 사용자가 여전히 없는 상태에서
서비스를 ACTIVE로 전환할 뿐이다. 결제, 보너스 또는 차감 삭제 — users.balance를
재계산하지 않는 레지스트리에 대한 단순 DELETE FROM. 바로 사용 가능한 구독
링크 및 connection-keys. restart-all, reorder, 스쿼드 bulk-actions 및
job_users가 있는 PUT /admin/spool — 취소 없이 모든 클라이언트에게 발송.
클래스는 좁혀졌으며, 각 축소는 완화가 아니라 수정이었다. 템플릿 읽기는
쓰기와 함께 금지되었지만, 그 이유 — git 없음, 롤백 없음 — 는 쓰기에만
해당했다; 그 폭은 이론적으로만 비용이 든 것이 아니었다: 관찰된 한 기간 동안
알림의 상당 부분이 빈 상태로 렌더링되어 아무것도 보내지 않았고, 작업은
그럼에도 SUCCESS로 보고되었으며, 침묵의 원인은 템플릿 본문 내부에 있다.
이제 읽기는 열려 있고, POST는 롤백을 가져온 template_edit 아래에 있으며,
PUT과 DELETE는 닫혀 있다: 방금 나타난 템플릿과 방금 사라진 템플릿에는
촬영할 이전 상태가 없기 때문이다. /api/sub 금지는 접두사 방식이었고
동시에 /api/subscription-page-configs와 /api/subscription-request-history —
키를 발급하지 않는 두 개의 읽기 컨트롤러 — 를 덮었다; 이제는 exact에
/api/sub/에 대한 prefix를 더한 것이다. 패널 클라이언트에 대한 대량 작업은
보기 목록 없이 전체 데이터베이스에 적용되기 때문에 금지되었다 — 아무도
세지 않는 한 정확하다: bulk_ops는 적용 전에 패널에서 수를 세고, 수를
확인할 수 없거나 HQ_MCP_MAX_BULK_USERS를 초과하면 거부하며, 봇에게 제안되지
않는다.
POST /api/users/bulk/delete-by-status는 그 형태 때문에 금지된 채로
남는다: 그 본문에는 사람 목록이 아니라 상태가 있다. 패널은 작업을 큐에 넣고
실행 시점에 해당하는 사람들을 삭제한다 — 운영자가 본 사람들이 아니라 — 그리고
빈 본문과 카운터 없이 202로 응답하므로, 그 사이에 만료된 계정은 보이지 않게
삭제된다. 이 기능은 bulk_ops delete_by_status로 보존되었다: 구체적인 id를
열거하고, 이를 표시하며, bulk/delete를 통해 정확히 그들만 삭제한다. 다른
엔티티 — 호스트, 노드, 스쿼드, 스풀 발송 — 에 대한 대량 라우트는 이러한
계수 단계가 없으며 존재하지 않는 채로 남는다.
비밀은 출력에서 마스킹된다 — 키 이름과 값 형태 모두로. 이름으로: 폐쇄된
자격 증명 키 목록, 명시적 예외 목록이 있는 token|secret|key|password|auth
일치, 여러 개에 대한 꼬리 마스킹, bot 프로필에 대한 PII 마스킹. 이것으로는
부족했고 하루 만에 세 번 실패했다: Telegram 봇 토큰이 스풀 문자열의
response.request.url 내부로 이동했다 (키 이름은 url), 동일한 토큰이 SHM
전송 문자열의 host 열에 있었으며, 템플릿 본문은 옆에 필드 이름조차 없는
순수 하위 문자열로 자격 증명을 담고 있다. 따라서 우회는 통과하는 모든 문자열을
값 형태 규칙으로도 실행한다: JWT; 이름이 비밀을 약속하고 값이 플레이스홀더처럼
보이지 않는 NAME=<값>; 주변 경로가 있거나 없는 Telegram 봇 토큰; URL 내부의
user:password@. 이는 두 HTTP 클라이언트가 입력에서 호출하고 실행기가 출력에서
호출하는 redact 내부에 살아 있다 — 개별 도구는 이를 기억할 필요가 없다.
규칙은 추측이 아니라 보정되었으며, 보정은 소스에 직접 선언되어 있다:
"불투명 실행" 임계값 (무작위로 보이는 32+ 문자)은 실제 템플릿 본문에서
측정되었고, data-URI 아이콘과 결제의 hex uniq_id가 이를 초과하는 구조화된
API 응답에서는 꺼져 있다 — 이를 잘라내면 정리는 도구가 작성된 바로 그 필드를
소멸시킬 것이다. 이것이 보안 경계는 아니며 소스도 그렇게 말한다: 단어로 쓰인
비밀에는 형태가 없다; 필터를 통과하는 모든 것은 human 프로필 내부에 남는다.
동일한 규칙을 scripts/no-secrets.test.ts가 사용하여 게시된 커밋에 비밀이
들어가지 못하게 한다: "비밀이 어떻게 생겼는지"에 대한 두 개의 지식 사본은
조용히 갈라지며, 두 번째는 계속 작동하는 것처럼 보인다.
sql_query는 아무것도 실행하지 않는다. 그것은 검증하고 거부하며, 그 사실을 자체 소스코드에 명시한다. 어휘 검사는 값싼 첫 번째 필터이지 보안 경계가 아니다; 모듈은 이를 통과하는 우회 방법들을 나열하고, 테스트는 아무도 필터를 보장으로 오해하지 않도록 그 우회 방법들을 열어 둔다. 실행이 연결되지 않은 동안, 전제 조건은 같은 파일에 선언되어 있다: 읽기 전용 역할, 읽기 전용 트랜잭션, 쿼리 타임아웃, 금지된 컬럼 목록.
제한 사항에 대해 알아야 할 것
HTTP 전송은 MCP(
/mcp, streamable HTTP)로 통신하며 stdio와 동일한 도구 세트를 제공한다: 하나의 함수가 두 전송 모두에 게시한다. 의도적으로 지원하지 않는 것: 세션(Mcp-Session-Id가 발급되지 않음), 서버 주도 메시지, 그리고 그에 따른GET의 SSE 스트림과Last-Event-ID에 의한 재개. 각 호출은 자족적이므로 서버와 전송은 요청마다 새로 생성된다; 이는 SDK 자체도 요구하는 사항으로, 세션 없는 전송은 재사용이 금지되어 있다./mcp경로에서는 실행기의 두 가지 결과에 도달할 수 없으며,/metrics카운터는 이 경로에서 네 가지 중 두 가지만 본다. 잘못된 입력은 SDK가 도구 이전에 처리하고 자체적으로-32602로 응답한다; 존재하지 않는 이름도 레지스트리에 도달하기 전에 SDK가 자체적으로 거부한다. 따라서 이 경로에서는invalid_input과not_found가 응답에도 보고서에도 나타나지 않는다. REST 파사드에서는 둘 다 도달 가능하다.Origin헤더가 있는/mcp요청은 무조건 403으로 거부된다: 서버는 루프백을 수신하며, 브라우저의 페이지는 자신의 도메인을127.0.0.1로 돌려 운영자 명의로 여기에 접근할 수 있다. 브라우저는 모든 교차 출처 POST에Origin을 설정하지만, 실제 MCP 클라이언트는 절대 설정하지 않으며, 서버는 CORS 헤더를 제공하지 않으므로 브라우저 클라이언트는 존재하지 않으며 존재할 수도 없다. 내장된allowedHosts/allowedOrigins는 이 용도에 적합하지 않다: 이 SDK 버전에서는 외부 미들웨어를 위해 더 이상 사용되지 않는 것으로 표시되어 있으며, 빈 origin 목록은 "어떤 origin도 허용되지 않음"이 아니라 "검사가 꺼져 있음"을 의미한다.두 도구는 내부 네트워크로의 터널이 필요하며, 그것 없이는 거부한다. 의도적으로 보이게 유지된다: 사라진 도구는 모델에게 그러한 기능이 존재하지 않는다고 가르치지만, 실제로는 포트가 닫혀 있는 것이다.
sync_audit은 두 시스템을 끝까지 읽어내며 여기서 유일하게 비용이 많이 드는 호출이다 — 이를 위해 자체 요청 할당량이 있다.패널 페이지 크기는 런타임에 측정되지 가정되지 않는다: API는 최대값을 선언하지 않으며, 실제 값은 릴리스마다 달라졌다.
패널의 대량 라우트는 빈 본문으로 202 또는 204로 응답하고 일부 작업을 큐에 넣으므로, "적용됨"은 "모두에게 완료됨"이 아니라 "패널이 수락함"을 의미한다. 계획이 미리 설정한 숫자만이 여기서 유일하게 정직한 숫자다.
패널에서 이루어진 수정은 SHM 빌링으로 다시 전달되지 않으며, 이를 수행하는 도구들은 그 사실을 명시한다. 조정 단계는 없다; 차이는 나중에
sync_audit이 보여줄 것이다.
개발
pnpm test # модульные тесты
pnpm typecheck
pnpm test:guards # сканер секретов и предохранители скрипта захвата фикстур테스트는 실제 응답의 형태를 재현하는 픽스처로 실행된다. 결함이 실행 중인 시스템에서만 보였던 경우, 그 결함을 고정하는 테스트는 그렇게 명시한다.
라이선스
MIT.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Read-only MCP server for ClassQuill, a tutoring-business-management platform.
MCP server for querying and analyzing data from ad platforms, analytics tools, and spreadsheets
Read-only MCP server for The Quiet Protocol's engines, benchmarks, proof, and business data.
A paid remote MCP for hosted MCP server, built to return verdicts, receipts, usage logs, and audit-r
Related MCP Servers
- AlicenseAqualityDmaintenanceMCP server to connect MySQL DB for read-only queries. It offers accurate query execution.4191MIT
- AlicenseAqualityDmaintenanceA read-only MCP server for securely accessing Xendit payment platform data. It enables querying balances, invoices, transactions, disbursements, refunds, and virtual account payments while preventing any money-moving operations.13MIT
- AlicenseBqualityCmaintenanceMCP server for the DataGate billing platform API, providing read-only tools to manage customers, invoices, products, agreements, sites, and payments.13MIT
- AlicenseAqualityAmaintenanceA read-only MCP server for querying AI provider administration APIs, providing normalized usage, cost, and dashboard data for OpenAI and Anthropic.419MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/qwertyhq/hq-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server