Skip to main content
Glama

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

GET /healthcheck — la única ruta sin autorización

solo platform_probe: shm.live sigue siendo null, «la facturación está caída» y «la contraseña no es correcta» dejan de distinguirse (shm_healthcheck_route_absent). Las demás herramientas no se ven afectadas

SHM < 2.11.3

GET /admin/user/search

client_search, client_resolve — fallo, no lista vacía

SHM < 2.9.0

GET /user/referrals

client_account_state pierde el contador de referidos

SHM < 2.4.0

GET /user/email

client_account_state pierde la dirección y el indicador de confirmación

Panel < 3.0.0

el usuario se direcciona por uuid, no por id numérico

client_overview, subscription_inspect, traffic_stats, provisioning_diagnose, subscription_ops: /api/users/{id} es rechazado por validación con 400

Panel < 3.0.0

no hay /api/connections/*

connections_inspect — toda la herramienta

Panel < 3.0.0

no hay POST /api/users/{id}/actions/extend

subscription_ops pierde la renovación (al panel solo le queda la masiva)

Panel < 3.0.0

no hay /api/system/stats/digest y /stats/http

panel_activity pierde dos de sus cinco muestras

Panel < 3.2.0

no hay GET /api/system/configuration

solo platform_probe: la capacidad remna.subscriptionRequestHistory sigue siendo unknown — a propósito, no false

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 con run. pnpm setup es 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

platform_probe

Qué está vivo ahora mismo: versiones, capacidades, túneles y si la falla es una avería o credenciales

client_resolve

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

client_search

Búsqueda de clientes SHM por fragmento, con el número de coincidencias del servidor

client_overview

El cliente completo en ambos sistemas en una sola llamada

client_account_state

Cómo entra la cuenta: email y su confirmación, OTP, passkey, si es posible entrar con contraseña, referidos

client_billing_view

El dinero con los ojos del cliente: el próximo cobro y los métodos de pago que realmente se le ofrecen

client_catalog_view

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

billing_ledger

Pagos, bonos, cobros y dos conciliaciones independientes (saldo y bono son columnas distintas con rutas de actualización distintas)

autopay_inspect

Estado del pago automático y todas las comisiones retenidas — está en el campo JSON comment de las líneas de pago, no en user.settings

promo_read

Códigos promocionales y sus canjes: son líneas distintas y no se pueden leer juntas

catalog_read

Tarifas, precio del pedido, servicios hijos, mapa de eventos, categorías — la fuente de los service_id permitidos

config_read

Una clave de configuración SHM de la lista cerrada, los secretos están enmascarados. No existe la lectura completa de la configuración

template_read

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

service_inspect

Servicios del cliente: estado, plazo, siguiente tarifa planificada, tareas del spool por cada uno

spool_inspect

Cola de provisión: atascados, caídos, pausados y la profundidad real

provisioning_diagnose

«Pagado, pero no hay configuración» — por cada servicio, no por cliente

sync_audit

Conciliación por lotes de la facturación con el panel, ambos lados se leen hasta el final

notify_history

Si realmente se le dijo al cliente y, si no, por qué; veredicto de entrega que nada más muestra

server_inventory

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

subscription_inspect

Tarjeta de Remnawave: estado, plazo, tráfico, dispositivos HWID, últimas solicitudes de suscripción. Claves — nunca

subpage_read

Qué muestra realmente la página de suscripción al cliente: plataformas, aplicaciones, pasos de instalación, enlaces de botones

client_reach

A qué nodos llega realmente este cliente y qué squads y tags de inbounds lo permiten

device_inventory

Panorama de HWID en todo el fleet — la base sin la cual el número de dispositivos de un cliente no significa nada

traffic_stats

Tráfico por días desglosado por nodos y squads; es una serie temporal, no contadores de tarjeta

connections_inspect

Quién está conectado ahora mismo. El panel responde con un job, y la herramienta gestiona la consulta por sí misma

infra_map

Nodos × perfiles de configuración × inbounds × hosts × squads y las rupturas entre ellos

infra_costs

Cuánto cuesta la infraestructura, en el cruce con el panel: un nodo pagado al que nadie llega es dinero que se va

country_health

Nodos, online, tráfico y hosts de un país

node_config_audit

Lo que declara el perfil frente a lo que el panel realmente entregaría al nodo

squads_read

Ambas familias de squads: los internos deciden el acceso, los externos cómo se presenta la suscripción

panel_activity

Qué ocurre con el propio panel: resumen, digest de la ventana, qué rutas se consultan, historial de solicitudes de suscripción

torrent_reports

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

abuse_report

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_query

SQL solo de lectura — preflight y nada más, ver abajo

De escritura (solo rw, solo perfil human, primero el plan)

Herramienta

Qué cambia

billing_adjust

El saldo o los bonos del cliente SHM

billing_refund_service

Devuelve al saldo la cantidad que SHM registró como cobrada por el período pagado actual

bulk_ops

Operaciones masivas sobre clientes del panel — por un conjunto de ids nombrado o por todo el fleet

host_edit

Un host Remnawave: etiqueta, dirección, puerto, SNI/host/path/ALPN/fingerprint, capa de seguridad, etiquetas, habilitación y ocultación

host_cleanup

Elimina hosts por una lista explícita de uuid. Irreversible

node_manage

Un nodo: enable, disable, restart, reset_traffic, update, create

subscription_ops

Una suscripción en el panel: enable, disable, extend, reset_traffic, revoke, set_limits, retirada de dispositivos

service_lifecycle

El servicio del cliente: give, touch, change_plan, schedule_change, stop, activate, delete

provisioning_repair

retry, resume o pause de una tarea de spool atascada

template_edit

Sobrescribe el cuerpo de una plantilla SHM existente

storage_edit

Escribe el storage SHM personalizado según la lista de claves emitida para esta instalación

server_edit

La línea de transporte o el grupo de transportes SHM — webhooks, punto ssh de provisioning, remitentes de correo

user_flags

Bloquea al cliente o edita los campos seguros de la tarjeta (full_name, phone, comment)

ops_confirm

Aplica el plan según su plan_id. Escribe lo que escribe la herramienta planificada

ops_audit

Nada. Lee el registro local de mutaciones — rw porque el registro es parte de la superficie de mutación

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_id

El 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 emite Mcp-Session-Id), mensajes iniciados por el servidor y, con ellos, el flujo SSE en GET y la reanudación por Last-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 /metrics ven 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 eso invalid_input y not_found no aparecen por esta ruta ni en la respuesta ni en el informe. En la fachada REST ambos son alcanzables.

  • Una petición a /mcp con la cabecera Origin se rechaza con 403 sin opciones: el servidor escucha en loopback, y una página en el navegador puede llevar su dominio a 127.0.0.1 y venir aquí en nombre del operador. El navegador pone Origin en 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. Los allowedHosts/allowedOrigins integrados 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_audit lee 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.

Maintenance

ActivityMaintained
ResponsivenessSyncing

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

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    A 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.
    13
    MIT
  • A
    license
    B
    quality
    C
    maintenance
    MCP server for the DataGate billing platform API, providing read-only tools to manage customers, invoices, products, agreements, sites, and payments.
    13
    MIT

Latest Blog Posts

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