hq-mcp
hq-mcp
Versión en ruso · English
Servidor MCP que da a un agente de IA acceso al negocio de VPN: facturación SHM y panel Remnawave, cosidos de modo que a una pregunta que atraviesa ambos sistemas se pueda responder con una sola llamada.
Ninguna herramienta cambia nada con la misma llamada con la que se le pidió: la escritura primero devuelve un plan, y la aplicación es una segunda llamada que lleva el identificador de ese plan.
Cómo se ve en funcionamiento
La primera llamada en cualquier instalación es platform_probe. Responde qué hay
en este despliegue y qué de eso está vivo; todo lo demás aquí deriva de
lo que informe. Las respuestas abajo están recortadas, los valores son inventados.
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": "…" }]
}Luego, una pregunta a la que ninguno de los dos sistemas responde por sí solo: «el cliente escribe que pagó, pero no hay configuración».
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": "…" }] }
}El panel no sabe nada de él, pero count: 0 aquí no es «no hay cuenta»:
paths nombra cada búsqueda recorrida y lo que no ve. Lo que realmente
sucedió lo dice el segundo par de ojos:
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 }
}] }
}El servicio está ACTIVE, la instantánea de configuración está en su lugar, el aprovisionamiento informó éxito — pero el usuario al que pertenece ese éxito no está en el panel. Ni la facturación ni el panel por separado muestran algo así.
Hay treinta y cuatro herramientas que leen ambos sistemas. El modo rw añade
quince de escritura: trece cambian datos reales, una aplica un plan, y otra
lee el registro local de mutaciones.
Related MCP server: xendit-mcp
Por qué herramientas compuestas, y no un proxy de endpoints
La construcción obvia — una herramienta por endpoint HTTP, unas ciento cincuenta — fue escrita y descartada, por dos razones.
Un proxy crudo anula cualquier lista de prohibiciones. Si el modelo puede llamar
GET <cualquier ruta>, entonces la lista de operaciones que decidiste no dar es
un adorno: hasta la ruta prohibida hay una línea. Aquí las herramientas llaman por nombre
a rutas específicas, y un escáner en la etapa de compilación hace fallar la ejecución si una ruta
prohibida aparece como literal en el código fuente.
Y un endpoint no es una pregunta. El ejemplo anterior toca cuatro rutas de SHM y dos
rutas del panel, y lo interesante es precisamente la unión. client_overview,
sync_audit y provisioning_diagnose existen porque los errores viven en esa
costura.
La regla que definió todo lo demás
Una respuesta vacía nunca debe tomarse como ausencia demostrada.
Cuando el backend falla, la herramienta se degrada: el fallo va a degraded,
la advertencia partial_result nombra la mitad que falta, y cualquier hallazgo
que dependía de esa mitad se suprime, en lugar de calcularse a partir de lo que
sobrevivió. Cuando la lista se trunca, junto con ella llega el total del servidor — para que
«ese servicio no existe» no se apoye en una ventana no declarada.
Esto no es precaución teórica. Durante el desarrollo, una herramienta leyó todos los registros del panel, los descartó todos porque un campo fue renombrado en el otro lado, y luego informó que cientos de clientes necesitaban reaprovisionamiento — una recomendación destructiva, expresada con confianza y derivada de un conjunto vacío. La corrección no consistió solo en el campo renombrado: consistió en que una cesta calculada a partir de una entrada inservible debe negarse a ser un hallazgo.
Compatibilidad: ¿funcionará en tu caso?
Probado en SHM 2.19.4 y Remnawave 3.2.3 — ambos números tomados de un despliegue en funcionamiento, no de una especificación.
Mínimo: SHM 2.18.0 y Remnawave 3.0.0. El danuk/shm oficial sirve:
todas las rutas que llaman las herramientas son de upstream, no se necesita un fork.
El único lugar donde un parche de ese despliegue era visible para la herramienta era
el cuarto flag GET /user/password-auth; ahora su ausencia se llama
advertencia sign_in_flag_absent, en lugar de presentarse como diagnóstico. La lista completa
de rutas de ambos sistemas, la versión de aparición de cada una y una respuesta detallada sobre el fork están en
COMPATIBILITY.md.
La verificación toma una sola llamada — la misma platform_probe. Si la versión está por debajo
del mínimo, responde con la advertencia backend_version_below_minimum, nombrando la
versión, el mínimo y qué exactamente fallará. No se desactiva nada: una versión
antigua da fallos ruidosos en rutas concretas, no respuestas vacías silenciosas.
Versión | Qué desaparece | A quién afecta |
SHM < 2.18.0 |
| solo |
SHM < 2.11.3 |
|
|
SHM < 2.9.0 |
|
|
SHM < 2.4.0 |
|
|
Panel < 3.0.0 | el usuario se direcciona por |
|
Panel < 3.0.0 | no hay |
|
Panel < 3.0.0 | no hay |
|
Panel < 3.0.0 | no hay |
|
Panel < 3.2.0 | no hay | solo |
Remnawave 3.x rompe la compatibilidad con todo lo escrito para 2.x, y lo rompe
de forma no silenciosa. Del objeto de usuario se eliminó uuid, y junto con él desaparecieron
las rutas by-telegram-id, by-email y by-tag, y además /api/users/{uuid}
responde 400, no 404 — así que el fallo ni siquiera parece «no existe tal
usuario». Aquí esas rutas no están en absoluto; donde el servidor
aun así encuentra un uuid heredado (por ejemplo, en una instantánea antigua del storage SHM), lo dice
en la respuesta, en lugar de deslizarse silenciosamente a una suposición.
Las especificaciones OpenAPI van por detrás de los sistemas en funcionamiento, por lo que platform_probe
lleva la advertencia specs_are_stale en cada llamada; en SHM es peor de lo habitual —
su especificación estampa info.version desde la configuración en tiempo de ejecución, es decir,
describe el stand donde se hizo la exportación, no el tuyo. Por eso la sonda no lee
versiones de archivos en absoluto, sino que las pregunta a los sistemas en funcionamiento — y allí mismo
establece qué es cierto para este despliegue: si el filter del servidor de SHM reduce algo, si el panel
respeta filters en el listado de usuarios (ambos responden 200 y descartan silenciosamente parámetros desconocidos), si el panel
lleva un registro de solicitudes de suscripción, si existe
la ruta de tráfico en tiempo real, qué túneles ssh están abiertos. Y separa «el backend está caído»
de «nuestras credenciales no son correctas»: 401/403 se informa como credentialsRejected.
Instalación
Se necesitan Node 22.12+ y pnpm, y al menos uno de los dos sistemas — SHM o
Remnawave. No son obligatorios ambos: cada uno se configura por separado y por sí solo
es una configuración completa. Las herramientas del sistema que no existe no se publican en absoluto — no «responden vacío», sino que están ausentes, y platform_probe
nombra directamente qué está configurado. Por eso el número de herramientas depende de la instalación:
solo panel — 16, solo SHM — 18, ambos — 34 (y más en modo rw).
pnpm install
pnpm build
pnpm run setup
pnpm run setup, exactamente conrun.pnpm setupes un comando integrado del propio pnpm: modifica el perfil de tu shell y no llega a este repositorio.
El asistente existe porque el paso que reemplaza — escribir .env
a mano — falla en silencio: un error tipográfico en el token del panel no impide que el servidor se levante
y aparece más tarde como error de herramienta en medio de una pregunta no relacionada. Por eso
verifica cada credencial contra el sistema en funcionamiento y distingue tres fallos —
el host no respondió en absoluto (DNS, TLS, puerto cerrado), el host respondió y rechazó las credenciales,
el host respondió con algo que no demuestra nada (502, 429): se arreglan de manera diferente,
y un solo «login failed» habría enviado a arreglar lo que no era.
Pregunta solo por el sistema que tienes y por el modo de acceso;
todo lo demás está detrás de una pregunta Configure the optional settings? [y/N].
La zona horaria la lee de la SHM en funcionamiento, no la adivina: SHM escribe fechas con su propia hora
local sin offset, y una zona incorrecta desplaza silenciosamente cada antigüedad.
No imprime secretos. Por defecto pone ro; para rw exige escribir la palabra
rw y confirmar por separado — antes nombrando cuántas herramientas aparecerán y
cuántas de ellas escriben en la facturación real y en el panel real, calculado desde el
registro en ese mismo momento. Escribe .env con permisos 0600 sobre una copia del anterior,
transfiriendo variables sobre las que no preguntó, e imprime comandos de conexión
para Claude Code, Codex y opencode — pero no modifica configuraciones ajenas: un asistente
que reescribiera JSONC algún día rompería una configuración que funciona a alguien. Se puede
volver a ejecutar en cualquier momento; Enter conserva el valor existente. Sin terminal
se niega a ejecutarse: un cliente MCP inicia el servidor sin TTY, y un asistente
capaz de despertarse allí se quedaría colgado en una pregunta que nadie ve.
O a mano
cp .env.example .env && chmod 600 .env # и заполнитьCada variable está descrita en .env.example. Una ausente o incorrecta hace fallar
el inicio indicando el nombre de la variable y lo que se espera de ella, en lugar de
aparecer más tarde como un error confuso de herramienta.
{
"mcpServers": {
"hq": {
"command": "node",
"args": ["/absolute/path/to/hq-mcp/apps/stdio/dist/index.js"]
}
}
}Segundo transporte: MCP sobre HTTP
El mismo conjunto de herramientas está disponible por HTTP — esto se necesita cuando el cliente no puede
iniciar el proceso por sí mismo: está en un contenedor, en otra máquina, o hay varios.
Una aplicación separada, configuración desde el mismo .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 …Sin HQ_MCP_HTTP_TOKENS no arranca en absoluto, y falla antes de reunir
clientes hacia la facturación y el panel. Escucha en el bucle; abrirlo a la red —
HQ_MCP_HTTP_HOST=0.0.0.0, y se imprime una advertencia al respecto, porque
entre el servidor y la red solo quedará ese token. El puerto — HQ_MCP_HTTP_PORT.
El cliente se conecta a /mcp, pasando el token con el habitual Authorization: Bearer:
{
"mcpServers": {
"hq": {
"type": "http",
"url": "http://127.0.0.1:42480/mcp",
"headers": { "Authorization": "Bearer <тот же токен>" }
}
}
}La ruta es sin sesión: Mcp-Session-Id no se emite ni se requiere, por lo que detrás de un
proxy inverso se pueden tener varias copias del proceso sin conexiones pegajosas.
No tiene mensajes de servidor, por lo que GET en el flujo SSE y DELETE para
cerrar sesión responden 405 — el cliente MCP lo entiende. Una solicitud con el encabezado
Origin se rechaza con 403: protección contra DNS rebinding, ver «Limitaciones».
El vecino /v1/tools no es MCP, sino una fachada REST interna para ai-bot: una ruta
de listado y una de llamada, con su propio sobre de respuesta y su propio límite de solicitudes.
Imagen HTTP de producción
La compilación de producción se genera únicamente a partir de un SHA de commit verificado de 40 caracteres en minúsculas. Ese SHA se sella simultáneamente en la etiqueta OCI, en un archivo de solo lectura propiedad de root y en /healthz; el entrypoint restaura el valor desde el archivo, de modo que la sobrescritura en runtime de HQ_MCP_IMAGE_REVISION no cambia la evidencia de salud.
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}"El consumidor de Compose fija exactamente esa etiqueta de 40 caracteres y no declara ports de host. El contenedor opera como UID/GID 10001, en modo bot+ro publica únicamente /healthz y la fachada REST, y requiere la HQ_MCP_DEPLOYMENT_CONFIG_REVISION compartida no secreta en formato UUID en minúsculas. En el health entran ambas revisiones, para que ai-bot pueda cerrarse antes de leer el catálogo si no coinciden.
Production entrega los secretos únicamente a través de tres archivos regulares que no son symlinks con mode exacto 0600: SHM_ADMIN_AUTH_FILE, REMNA_API_TOKEN_FILE y HQ_MCP_HTTP_TOKENS_FILE. El último contiene solo ai-bot:<dedicated token>. Es un token separado del servidor; las credenciales de SHM y Remnawave también se asignan a este deployment por separado y no se reutilizan del support bot.
Ajuste a tu propia instalación
El danuk/shm upstream no conoce la palabra «Remnawave» — ni una línea. El puente entre la facturación y el panel vive por completo en tus plantillas de provisión: un usuario de panel por user_service_id, nombre <NAME_PREFIX><user_service_id>, instantánea de configuración en el storage de SHM bajo <STORAGE_PREFIX><user_service_id>. Ambos prefijos los lee el servidor en runtime desde config.remnawave de tu SHM y permite sobrescribirlos (HQ_MCP_STORAGE_PREFIX, HQ_MCP_PANEL_PREFIXES) — el operador sabe qué hay hoy en el panel mejor que una clave de configuración que describe lo que SHM construirá mañana.
El nombre de usuario del panel es la única clave de enlace, y un prefijo que no coincide con nada no da error: da una respuesta errónea con seguridad, en la que cada servicio parece no provisto. Por eso las herramientas capaces de demostrarlo responden con el código prefix_unverified y suprimen el hallazgo afectado — sync_audit no devuelve en absoluto la cesta missingPanelUser, provisioning_diagnose marca el resultado con el mismo código o con panel_username_guessed. La convención se necesita exactamente para tres herramientas (sync_audit, provisioning_diagnose, el mutador storage_edit); client_overview acepta remna_user_id como parámetro opcional y sin él simplemente no muestra la mitad del panel. Si no tienes la convención, todas las demás herramientas funcionan con normalidad, y estas tres no inventan hallazgos. El análisis completo, con el orden de prefijos y los nombres heredados, está en COMPATIBILITY.md.
Herramientas
Treinta y cuatro son visibles en ro; el modo rw añade quince de la última tabla y no quita nada. Los números son para el perfil human; lo que ve bot se indica en el modelo de seguridad.
Plataforma y un cliente
Herramienta | A qué responde |
| Qué está vivo ahora mismo: versiones, capacidades, túneles y si la falla es una avería o credenciales |
| Cualquier identificador (telegram id, email, login, id, nombre en el panel) a los ids canónicos de ambos sistemas — todas las coincidencias, no la primera |
| Búsqueda de clientes SHM por fragmento, con el número de coincidencias del servidor |
| El cliente completo en ambos sistemas en una sola llamada |
| Cómo entra la cuenta: email y su confirmación, OTP, passkey, si es posible entrar con contraseña, referidos |
| El dinero con los ojos del cliente: el próximo cobro y los métodos de pago que realmente se le ofrecen |
| El catálogo y los códigos promocionales con los ojos de un cliente — su descuento, sus bonos, las tarifas ocultas para él |
Dinero, catálogo, configuración
Herramienta | A qué responde |
| Pagos, bonos, cobros y dos conciliaciones independientes (saldo y bono son columnas distintas con rutas de actualización distintas) |
| Estado del pago automático y todas las comisiones retenidas — está en el campo JSON |
| Códigos promocionales y sus canjes: son líneas distintas y no se pueden leer juntas |
| Tarifas, precio del pedido, servicios hijos, mapa de eventos, categorías — la fuente de los |
| Una clave de configuración SHM de la lista cerrada, los secretos están enmascarados. No existe la lectura completa de la configuración |
| Lista de plantillas o el cuerpo de exactamente una — el archivo que produce la notificación o el script de provisión |
Servicios y provisión
Herramienta | A qué responde |
| Servicios del cliente: estado, plazo, siguiente tarifa planificada, tareas del spool por cada uno |
| Cola de provisión: atascados, caídos, pausados y la profundidad real |
| «Pagado, pero no hay configuración» — por cada servicio, no por cliente |
| Conciliación por lotes de la facturación con el panel, ambos lados se leen hasta el final |
| Si realmente se le dijo al cliente y, si no, por qué; veredicto de entrega que nada más muestra |
| Los transportes propios de SHM y sus grupos (ssh, http, mail, telegram) y las rupturas que detienen la provisión en silencio. No es la lista de nodos de Remnawave |
Panel — primero desde el lado del cliente, luego desde el lado del fleet
Herramienta | A qué responde |
| Tarjeta de Remnawave: estado, plazo, tráfico, dispositivos HWID, últimas solicitudes de suscripción. Claves — nunca |
| Qué muestra realmente la página de suscripción al cliente: plataformas, aplicaciones, pasos de instalación, enlaces de botones |
| A qué nodos llega realmente este cliente y qué squads y tags de inbounds lo permiten |
| Panorama de HWID en todo el fleet — la base sin la cual el número de dispositivos de un cliente no significa nada |
| Tráfico por días desglosado por nodos y squads; es una serie temporal, no contadores de tarjeta |
| Quién está conectado ahora mismo. El panel responde con un job, y la herramienta gestiona la consulta por sí misma |
| Nodos × perfiles de configuración × inbounds × hosts × squads y las rupturas entre ellos |
| Cuánto cuesta la infraestructura, en el cruce con el panel: un nodo pagado al que nadie llega es dinero que se va |
| Nodos, online, tráfico y hosts de un país |
| Lo que declara el perfil frente a lo que el panel realmente entregaría al nodo |
| Ambas familias de squads: los internos deciden el acceso, los externos cómo se presenta la suscripción |
| Qué ocurre con el propio panel: resumen, digest de la ventana, qué rutas se consultan, historial de solicitudes de suscripción |
| Evidencias del bloqueador de torrents — y, por separado, si está instalado y si está vigilando |
Detrás del túnel (estos dos sin él fallan, indicando el comando ssh exacto)
Herramienta | A qué responde |
| Hallazgos del hook antiabuso más los tops del panel. Costoso: escaneos ilimitados de la MySQL de facturación en producción, tope de 5 llamadas cada 5 minutos |
| SQL solo de lectura — preflight y nada más, ver abajo |
De escritura (solo rw, solo perfil human, primero el plan)
Herramienta | Qué cambia |
| El saldo o los bonos del cliente SHM |
| Devuelve al saldo la cantidad que SHM registró como cobrada por el período pagado actual |
| Operaciones masivas sobre clientes del panel — por un conjunto de ids nombrado o por todo el fleet |
| Un host Remnawave: etiqueta, dirección, puerto, SNI/host/path/ALPN/fingerprint, capa de seguridad, etiquetas, habilitación y ocultación |
| Elimina hosts por una lista explícita de uuid. Irreversible |
| Un nodo: enable, disable, restart, reset_traffic, update, create |
| Una suscripción en el panel: enable, disable, extend, reset_traffic, revoke, set_limits, retirada de dispositivos |
| El servicio del cliente: give, touch, change_plan, schedule_change, stop, activate, delete |
| retry, resume o pause de una tarea de spool atascada |
| Sobrescribe el cuerpo de una plantilla SHM existente |
| Escribe el storage SHM personalizado según la lista de claves emitida para esta instalación |
| La línea de transporte o el grupo de transportes SHM — webhooks, punto ssh de provisioning, remitentes de correo |
| Bloquea al cliente o edita los campos seguros de la tarjeta ( |
| Aplica el plan según su |
| Nada. Lee el registro local de mutaciones — |
Mutaciones
Nada se aplica con la llamada que lo solicita. El mutador sin
plan_id lee el estado actual, construye el estado objetivo y devuelve un plan: before,
after, diff por campos, efectos secundarios, rollback donde lo haya, e
identificador. No escribe nada. La aplicación es una segunda llamada:
ops_confirm { "plan_id": "…" } # либо: тот же мутатор, ТЕ ЖЕ аргументы, плюс plan_idEl plan está vinculado al perfil que lo construyó, a la herramienta bajo la
que se construyó y al hash de los argumentos: no puede ejecutarse ni desde otro
invocador, ni con otra herramienta, ni con la misma herramienta con un solo número
modificado. Vive 10 minutos. El uso único es un rename atómico en disco, no un
«leer y eliminar»: de veinte confirmaciones simultáneas gana exactamente
una, las demás reciben «no encontrado». Un fallo no quema un plan válido — todas
las comprobaciones ocurren después de la captura, y una que falla devuelve el archivo a su lugar; lo quema
el propio intento, y si el backend se cae, el plan queda consumido. Esto es
intencional, y ahí está la diferencia entre un solo cargo y tres. Antes de aplicar, la herramienta relee
el mundo y lo contrasta con la instantánea desde la que se construyó el plan: si el objeto se movió, el plan
se rechaza, no se aplica por encima de un cambio ajeno.
Cada intento se registra en HQ_MCP_AUDIT_PATH (JSONL, permisos 0600): quién,
con qué, con qué argumentos, cómo se veía el objeto antes y después y cómo terminó —
planned, applying, applied, failed o rejected; los fallos al mismo nivel que los
éxitos. applying se escribe antes de contactar al backend, y ahí está todo el sentido de la
construcción: un registro sin su terminal pareja significa que el proceso murió en medio,
la instantánea del plan ya está destruida y el dinero pudo haberse ido. ops_audit busca esos
registros sin cerrar en todo el registro, independientemente de la ventana solicitada, y los reporta
primero; las líneas sin parsear se cuentan, no se omiten en silencio.
Los topes los mantiene el framework, no el autor de la herramienta. Una mutación por encima de
HQ_MCP_MAX_OP_AMOUNT se rechaza antes de construir el plan, y el framework se niega a
registrar una herramienta que declaró un endpoint monetario pero no dijo
cómo leer el importe de su entrada. El tope cubre ambos tipos de movimiento de dinero, y
el segundo es fácil de pasar por alto: tanto los pagos con bonos, donde el importe lo nombra el invocador, como
las acciones del ciclo de vida que gastan el saldo del cliente (give, touch,
change_plan, activate), donde el importe es el precio del plan del catálogo. Un plan cuyo
precio no pudo leerse no se emite: no saber el número no hace que el cargo sea gratis. HQ_MCP_MAX_BULK_USERS limita a cuántos clientes
del panel puede afectar una sola operación masiva, y un plan que no pudo establecer ese
número en el panel se rechaza, no se estima a ojo. Por encima del tope, la operación
se rechaza por completo — nunca se trunca.
El tope se comprueba al construir el plan y solo ahí: la aplicación trabaja
sobre un plan ya construido y no lo vuelve a medir. No se puede eludir el tope así —
los argumentos están fijados por el hash —, pero un tope bajado en .env después de emitir el plan no
afectará a ese plan.
template_edit y storage_edit primero escriben su propio rollback en
HQ_MCP_BACKUP_DIR (directorio 0700, archivos 0600); la ruta se devuelve en la respuesta,
restore_from coloca los bytes de vuelta, y sin una instantánea tomada no escribe ninguno de los
dos. El backup está separado de la instantánea del plan a propósito: los cuerpos de las plantillas y las instantáneas de
configuración llevan secretos como subcadenas desnudas, que no tienen nombre de campo para
enmascararlas — por lo tanto, no pueden viajar de vuelta al modelo dentro de
before/rollback; y el rollback debe sobrevivir al cambio, mientras que las instantáneas de los planes
se barren en el plazo de una hora.
Hay otras dos cosas que las herramientas de escritura se niegan a hacer. Un cuerpo con marcadores <redacted:…> no
se escribe jamás: es la salida de una herramienta de lectura, y escribirlo habría reemplazado
una credencial viva por la palabra con la que se ocultó. Y los blobs crudos del panel
(finalMask, xhttpExtraParams, muxParams, sockoptParams) quedan excluidos de
cualquier parche de host — en una instalación en funcionamiento, una parte notable de los hosts lleva dentro de
finalMask una contraseña Hysteria2 operativa.
Qué está realmente demostrado y qué no
host_edit es el único mutador cuya rama de aplicación se ejecutó en un
sistema en funcionamiento: en un panel Remnawave 3.2.3 en producción se cambió la etiqueta del host,
se verificó que la contraseña en finalMask sobrevivió y que no cambió nada más allá del
campo declarado, y se revirtió. Todos los demás están demostrados hasta el plan
inclusive: el plan se construye con datos reales, la parte aplicativa está cubierta por pruebas,
pero su rama no se ejecutó en un sistema en funcionamiento. Esto debe leerse
literalmente. Un plan que parece correcto es un certificado sobre el plan.
Modelo de seguridad
Dos perfiles. human es un operador de confianza, y recibe fallos concretos
accionables, incluido el comando ssh exacto cuando el túnel está cerrado.
bot es un canal no confiable: cualquier fallo se colapsa en el mismo mensaje,
para que no se pueda recorrer el registro tanteando qué nombres responden distinto. Ninguna
herramienta de escritura se le ofrece jamás al bot: en rw, el perfil bot ve
las mismas veinte herramientas de lectura que en ro.
Clase prohibida, separada de la simplemente peligrosa. Estas operaciones no están cerradas por
una puerta — no existen, y el escáner en la fase de build hace fallar la ejecución si su ruta
aparece en el código fuente como literal. Las rutas de identity y keygen de nodos (GET, cuyo
cuerpo de respuesta contiene la clave privada). Las rutas de tokens, autorización y passkey
(el panel entrega tokens en texto plano, y un token creado es un admin permanente
fuera de todas las puertas). La configuración del panel y de las suscripciones. La descarga de /admin/config completa.
El marcado manual de una tarea de provisioning como exitosa — no realiza el trabajo, solo
pasa el servicio a ACTIVE mientras el usuario sigue ausente en el panel.
La eliminación de un pago, bono o cargo — un DELETE FROM desnudo sobre el registro, en el que
users.balance no se recalcula. Los enlaces de suscripción listos para usar y las connection-keys. restart-all, reorder, bulk-actions de squads y
PUT /admin/spool con job_users — envío a todos los clientes sin cancelación.
La clase se estrechó, y cada estrechamiento fue una corrección, no una relajación. La lectura de
plantillas estaba prohibida junto con la escritura, aunque la razón — no hay git, no hay rollback —
hablaba solo de la escritura; la amplitud costó no teóricamente: una parte notable de
las notificaciones en una ventana observada se renderizó vacía y no envió
nada, la tarea reportó SUCCESS, y la causa del silencio está dentro del
cuerpo de la plantilla. Ahora la lectura está
abierta, POST está bajo template_edit, que trajo consigo el rollback, y PUT y
DELETE están cerrados: una plantilla que acaba de aparecer y una que acaba de desaparecer
no tienen estado previo que se pueda capturar. La prohibición de /api/sub era
prefijada y cubría también /api/subscription-page-configs y
/api/subscription-request-history — dos controladores de lectura que no emiten
claves; ahora es exact más prefix sobre /api/sub/. Las operaciones masivas sobre
clientes del panel estaban prohibidas porque se aplican a toda la base sin una lista para
revisar — cierto exactamente hasta que alguien cuente: bulk_ops cuenta en el
panel antes de aplicar, se niega cuando el número no pudo establecerse o está
por encima de HQ_MCP_MAX_BULK_USERS, y no se ofrece al bot.
POST /api/users/bulk/delete-by-status sigue prohibido por su forma:
en su cuerpo hay un estado, no una lista de personas. El panel pone la tarea en cola y elimina
a quienes coincidan en el momento de su ejecución — no a quienes revisó el
operador —, y responde 202 con cuerpo vacío y sin contador, de modo que las cuentas
expiradas en el intervalo se eliminan de forma invisible. La capacidad se conserva como
bulk_ops delete_by_status: enumera los ids concretos, los muestra y
elimina exactamente esos mediante bulk/delete. Las rutas masivas sobre otras entidades —
hosts, nodos, squads, envíos de spool — no tienen ese paso de conteo y siguen ausentes.
Los secretos se enmascaran a la salida — tanto por nombre de clave como por forma de valor. Por
nombre: lista cerrada de claves de credenciales, coincidencia con
token|secret|key|password|auth con una lista explícita de excepciones, enmascaramiento
de cola para varios y enmascaramiento de PII para el perfil bot. Esto no basta, y en
un solo día falló tres veces: el token del bot de Telegram viajaba dentro de
response.request.url de la línea de spool (la clave se llama url), el mismo estaba en la
columna host de las líneas de transporte SHM, y los cuerpos de las plantillas llevan credenciales
como subcadenas desnudas, junto a las cuales no hay ningún nombre de campo. Por eso el
paso de saneamiento ejecuta sobre cada línea que pasa, además, las reglas de forma de valor: JWT;
NAME=<valor>, donde el nombre promete un secreto y el valor no parece un placeholder;
tokens de bot de Telegram con ruta circundante y sin ella; user:password@ dentro de una URL.
Vive dentro de redact, a la que llaman ambos clientes HTTP en la entrada y el ejecutor
en la salida — ninguna herramienta individual tiene que acordarse de esto.
Las reglas están calibradas, no adivinadas, y la calibración está declarada directamente en el código fuente:
el umbral de «pasada opaca» (32+ caracteres que parecen aleatorios) se midió en
cuerpos reales de plantillas y está desactivado en respuestas API estructuradas, donde lo
superan los data-URI de iconos y el hex uniq_id del pago — al recortarlos, la limpieza
habría apagado exactamente los campos para los que se escribió la herramienta. Esto no es de todos modos
una frontera de seguridad, y el código fuente lo dice: un secreto
escrito con palabras no tiene forma; todo lo que pasa el filtro permanece dentro del
perfil human. Las mismas reglas las usa scripts/no-secrets.test.ts, que no
deja pasar un secreto a un commit publicado: dos copias del conocimiento sobre «cómo se ve
un secreto» divergen en silencio, y la segunda sigue pareciendo funcional.
sql_query no ejecuta nada. Valida y rechaza, y lo dice en su propio código fuente. La comprobación léxica es un primer filtro barato y claramente no es un límite de seguridad; el módulo enumera los rodeos que la atraviesan, y las pruebas los mantienen abiertos para que nadie tome el filtro por una garantía. Mientras la ejecución no esté conectada, las precondiciones se declaran en el mismo archivo: rol de solo lectura, transacción de solo lectura, tiempo de espera de consulta y lista de columnas prohibidas.
Lo que conviene saber sobre las limitaciones
El transporte HTTP habla en MCP (
/mcp, streamable HTTP) y ofrece el mismo conjunto de herramientas que stdio: una sola función las publica para ambos transportes. Lo que deliberadamente no sabe hacer: sesiones (no se emiteMcp-Session-Id), mensajes iniciados por el servidor y, con ellos, el flujo SSE enGETy la reanudación porLast-Event-ID. Cada llamada es autosuficiente, por lo que el servidor y el transporte se crean nuevos por petición; así lo exige el propio SDK, cuyo transporte sin sesiones está prohibido reutilizar.Por la ruta
/mcp, dos resultados del ejecutor son inalcanzables, y los contadores de/metricsven por ella dos de cuatro. La entrada inválida la procesa el SDK ANTES del instrumento y responde él mismo-32602; el nombre inexistente también lo rechaza él mismo, sin llegar al registro. Por esoinvalid_inputynot_foundno aparecen por esta ruta ni en la respuesta ni en el informe. En la fachada REST ambos son alcanzables.Una petición a
/mcpcon la cabeceraOriginse rechaza con 403 sin opciones: el servidor escucha en loopback, y una página en el navegador puede llevar su dominio a127.0.0.1y venir aquí en nombre del operador. El navegador poneOriginen cualquier POST de origen cruzado, un cliente MCP real nunca lo hace, y el servidor no emite cabeceras CORS, por lo que no tiene cliente de navegador ni puede tenerlo. LosallowedHosts/allowedOriginsintegrados no sirven para esto: en esta versión del SDK están marcados como obsoletos en favor de un middleware externo, y su lista vacía de orígenes significa «verificación desactivada», no «ningún origin vale».Dos herramientas necesitan un túnel a la red interna y, sin él, se niegan a funcionar. Siguen siendo visibles a propósito: una herramienta desaparecida enseña al modelo que esa posibilidad no existe, cuando en realidad lo que está cerrado es un puerto.
sync_auditlee ambos sistemas hasta el final y es aquí la única llamada costosa; por eso tiene su propia cuota de peticiones.El tamaño de página del panel se mide en tiempo de ejecución, no se supone: la API no declara un máximo, y el real ha cambiado entre versiones.
Las rutas masivas del panel responden 202 o 204 con cuerpo vacío y ponen parte del trabajo en cola, por lo que «aplicado» significa «aceptado por el panel», no «hecho para todos». El número fijado de antemano por el plan es el único honesto que existe aquí.
Los cambios hechos en el panel no se trasladan de vuelta a la facturación SHM, y las herramientas que los hacen lo dicen. No hay paso de conciliación; la discrepancia se la mostrará luego
sync_audit.
Desarrollo
pnpm test # модульные тесты
pnpm typecheck
pnpm test:guards # сканер секретов и предохранители скрипта захвата фикстурLas pruebas se ejecutan sobre fixtures que reproducen la forma de las respuestas reales. Allí donde el defecto solo era visible en un sistema en funcionamiento, la prueba que lo fija así lo dice.
Licencia
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