Skip to main content
Glama

hq-mcp

Russische Version · English

MCP-Server, der einem KI-Agenten Zugriff auf ein VPN-Geschäft gibt: Abrechnung SHM und Panel Remnawave, so zusammengefügt, dass auf eine Frage, die quer über beide Systeme liegt, mit einem einzigen Aufruf geantwortet werden kann.

Kein Werkzeug ändert etwas mit demselben Aufruf, mit dem es darum gebeten wurde: Ein schreibendes gibt zuerst einen Plan zurück, und die Anwendung ist ein zweiter Aufruf, der die Kennung dieses Plans trägt.

Wie das in der Praxis aussieht

Der erste Aufruf auf jeder Installation ist platform_probe. Er antwortet, was es überhaupt in dieser Bereitstellung gibt und was davon lebt; alles andere hier ist davon abgeleitet, was er meldet. Die Antworten unten sind beschnitten, die Werte erfunden.

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": "…" }]
}

Weiter geht es mit einer Frage, auf die keines der beiden Systeme allein antwortet: „Der Kunde schreibt, dass er bezahlt hat, aber es gibt keine Konfiguration.“

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": "…" }] }
}

Das Panel weiß nichts über ihn, aber count: 0 ist hier nicht „Konto existiert nicht“: paths nennt jede durchsuchte Suche und das, was sie nicht sieht. Was tatsächlich passiert ist, sagt das zweite Augenpaar:

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 }
  }] }
}

Dienst ACTIVE, Konfigurations-Snapshot vorhanden, Provisioning hat Erfolg gemeldet – aber der Benutzer, dem dieser Erfolg gehört, ist im Panel nicht vorhanden. Weder Abrechnung noch Panel zeigen so etwas einzeln.

Es gibt vierunddreißig Werkzeuge, die beide Systeme lesen. Der Modus rw fügt fünfzehn schreibende hinzu: dreizehn ändern echte Daten, eines wendet einen Plan an, noch eines liest das lokale Mutationsprotokoll.

Related MCP server: xendit-mcp

Warum zusammengesetzte Werkzeuge und nicht ein Proxy von Endpunkten

Die naheliegende Konstruktion – ein Werkzeug pro HTTP-Endpunkt, etwa hundertfünfzig Stück – wurde geschrieben und verworfen, aus zwei Gründen.

Ein roher Proxy macht jede Verbotsliste zunichte. Wenn das Modell GET <beliebiger Pfad> aufrufen kann, ist die Liste der Operationen, die Sie sich entschieden haben nicht zu geben, eine Dekoration: Bis zum verbotenen Pfad ist es eine Zeile. Hier rufen Werkzeuge namentlich benannte Routen auf, und ein Scanner zur Build-Zeit lässt den Lauf scheitern, wenn ein verbotener Pfad als Literal im Quelltext vorkommt.

Und ein Endpunkt ist keine Frage. Das obige Beispiel betrifft vier Routen von SHM und zwei Routen des Panels, und das Interessante daran ist genau die Nahtstelle. client_overview, sync_audit und provisioning_diagnose existieren, weil Fehler an dieser Naht leben.

Die Regel, die alles andere bestimmt hat

Eine leere Antwort darf niemals als bewiesene Abwesenheit gelten.

Wenn das Backend versagt, degradiert das Werkzeug: Der Fehler wandert in degraded, die Warnung partial_result nennt die fehlende Hälfte, und jeder Fund, der von dieser Hälfte abhing, wird unterdrückt, statt aus dem berechnet zu werden, was überlebt hat. Wenn eine Liste abgeschnitten ist, kommt zusammen mit ihr der Server-Total – damit „es gibt keinen solchen Dienst“ sich nicht auf ein nicht deklariertes Fenster stützt.

Das ist keine theoretische Vorsicht. Während der Entwicklung las ein Werkzeug alle Datensätze des Panels, verwarf sie alle, weil ein Feld auf der anderen Seite umbenannt worden war, und meldete danach, dass Hunderte von Kunden eine Neubereitstellung benötigen – eine destruktive Empfehlung, selbstbewusst ausgesprochen und aus einer leeren Menge abgeleitet. Die Reparatur bestand nicht nur in dem umbenannten Feld: Sie bestand darin, dass ein Korb, der aus unbrauchbarem Eingang berechnet wurde, sich weigern muss, ein Fund zu sein.

Kompatibilität: Wird es bei Ihnen funktionieren

Getestet mit SHM 2.19.4 und Remnawave 3.2.3 – beide Zahlen stammen von einer laufenden Bereitstellung, nicht aus der Spezifikation.

Minimum – SHM 2.18.0 und Remnawave 3.0.0. Das offizielle danuk/shm passt: Alle Routen, die die Werkzeuge aufrufen, sind Upstream-Routen, ein Fork ist nicht nötig. Die einzige Stelle, an der ein Patch dieser Bereitstellung für das Werkzeug sichtbar war, – das vierte Flag GET /user/password-auth; jetzt wird sein Fehlen als Warnung sign_in_flag_absent bezeichnet, nicht als Diagnose ausgegeben. Die vollständige Liste der Routen beider Systeme, die Version des Erscheinens jeder und eine ausführliche Antwort zum Fork finden Sie in COMPATIBILITY.md.

Die Prüfung dauert einen Aufruf – denselben platform_probe. Wenn die Version unter dem Minimum liegt, antwortet er mit der Warnung backend_version_below_minimum, nennt die Version, das Minimum und was genau ausfallen wird. Dabei wird nichts abgeschaltet: Eine alte Version gibt laute Fehler auf bestimmten Routen, keine stillen leeren Antworten.

Version

Was verschwindet

Wen das betrifft

SHM < 2.18.0

GET /healthcheck – die einzige Route ohne Autorisierung

nur platform_probe: shm.live bleibt null, „Abrechnung liegt“ und „Passwort ist falsch“ werden nicht mehr unterschieden (shm_healthcheck_route_absent). Andere Werkzeuge sind nicht betroffen

SHM < 2.11.3

GET /admin/user/search

client_search, client_resolve – Fehler, keine leere Liste

SHM < 2.9.0

GET /user/referrals

client_account_state verliert den Empfehlungszähler

SHM < 2.4.0

GET /user/email

client_account_state verliert die Adresse und das Bestätigungsmerkmal

Panel < 3.0.0

Benutzer wird per uuid adressiert, nicht per numerischer id

client_overview, subscription_inspect, traffic_stats, provisioning_diagnose, subscription_ops: /api/users/{id} wird von der Validierung mit 400 abgelehnt

Panel < 3.0.0

kein /api/connections/*

connections_inspect – das gesamte Werkzeug

Panel < 3.0.0

kein POST /api/users/{id}/actions/extend

subscription_ops verliert die Verlängerung (beim Panel bleibt nur die Massenverlängerung)

Panel < 3.0.0

kein /api/system/stats/digest und /stats/http

panel_activity verliert zwei seiner fünf Abfragen

Panel < 3.2.0

kein GET /api/system/configuration

nur platform_probe: die Möglichkeit remna.subscriptionRequestHistory bleibt unknown – absichtlich, nicht false

Remnawave 3.x bricht die Kompatibilität mit allem, was für 2.x geschrieben wurde, und bricht sie nicht leise. Aus dem Benutzerobjekt wurde uuid entfernt, und damit verschwanden die Routen by-telegram-id, by-email und by-tag, wobei /api/users/{uuid} mit 400 antwortet, nicht mit 404 – so dass der Fehler nicht einmal wie „es gibt keinen solchen Benutzer“ aussieht. Hier gibt es diese Routen gar nicht; wo der Server doch auf eine geerbte uuid stößt (z. B. in einem alten Snapshot des SHM-Speichers), sagt er das in der Antwort, statt still auf eine Vermutung zu verfallen.

Die OpenAPI-Spezifikationen hinken den laufenden Systemen hinterher, daher trägt platform_probe bei jedem Aufruf die Warnung specs_are_stale; bei SHM ist es schlimmer als sonst – ihre Spezifikation stempelt info.version aus der Konfiguration zur Laufzeit, beschreibt also den Stand, auf dem der Export gemacht wurde, nicht Ihren. Deshalb liest die Probe Versionen gar nicht aus Dateien, sondern fragt sie bei den laufenden Systemen ab – und stellt dort auch fest, was für diese Bereitstellung gilt: ob der Server-filter bei SHM etwas einschränkt, ob das Panel filters in der Benutzerliste respektiert (beide antworten mit 200 und verwerfen still unbekannte Parameter), ob das Panel ein Protokoll der Anfragen für Abonnements führt, ob die Realtime-Verkehrsroute existiert, welche SSH-Tunnel offen sind. Und es trennt „Backend liegt“ von „unsere Zugangsdaten sind falsch“: 401/403 wird als credentialsRejected gemeldet.

Installation

Benötigt werden Node 22.12+ und pnpm, und mindestens eines der beiden Systeme – SHM oder Remnawave. Beide sind nicht nötig: Jedes wird separat konfiguriert und ist allein eine vollwertige Konfiguration. Werkzeuge des Systems, das nicht vorhanden ist, werden gar nicht veröffentlicht – nicht „antworten leer“, sondern fehlen, und platform_probe nennt direkt, was konfiguriert ist. Daher hängt die Anzahl der Werkzeuge von der Installation ab: nur Panel – 16, nur SHM – 18, beide – 34 (und mehr im Modus rw).

pnpm install
pnpm build
pnpm run setup

pnpm run setup, genau mit run. pnpm setup ist ein eingebauter Befehl von pnpm selbst: Er ändert das Profil Ihrer Shell und erreicht dieses Repository nicht.

Der Assistent existiert, weil der Schritt, den er ersetzt – .env von Hand zu schreiben – still versagt: Ein Tippfehler im Panel-Token hindert den Server nicht am Start und taucht später als Werkzeugfehler mitten in einer unzusammenhängenden Frage auf. Deshalb prüft er jeden Zugangsdatensatz auf dem laufenden System und unterscheidet drei Fehler – der Host hat gar nicht geantwortet (DNS, TLS, geschlossener Port), der Host hat geantwortet und die Zugangsdaten abgelehnt, der Host hat mit etwas geantwortet, das nichts beweist (502, 429): Sie werden unterschiedlich behoben, und ein einziges „login failed“ hätte zum Falschen geschickt.

Er fragt nur nach dem System, das Sie haben, und nach dem Zugriffsmodus; alles andere ist hinter einer Frage Configure the optional settings? [y/N] verborgen. Die Zeitzone liest er von der laufenden SHM, nicht rät: SHM schreibt Daten in eigener lokaler Zeit ohne Offset, und eine falsche Zone verschiebt still jedes Alter. Geheimnisse druckt er nicht. Standardmäßig setzt er ro; für rw verlangt er, das Wort rw zu schreiben, und separat zu bestätigen – nachdem er genannt hat, wie viele Werkzeuge erscheinen und wie viele davon in die echte Abrechnung und das echte Panel schreiben, berechnet aus dem Register im selben Moment. .env schreibt er mit Rechten 0600 über eine Kopie des vorherigen, überträgt Variablen, nach denen er nicht gefragt hat, und druckt Verbindungsbefehle für Claude Code, Codex und opencode – aber fremde Konfigurationen ändert er nicht: Ein Assistent, der JSONC umschreibt, würde eines Tages jemandem eine funktionierende Einstellung kaputt machen. Er kann jederzeit neu gestartet werden, Enter erhält den vorhandenen Wert. Ohne Terminal weigert er sich zu starten: Ein MCP-Client startet den Server ohne TTY, und ein Assistent, der dort aufwachen könnte, würde an einer Frage hängen bleiben, die niemand sieht.

Oder von Hand

cp .env.example .env && chmod 600 .env    # и заполнить

Jede Variable ist in .env.example beschrieben. Eine fehlende oder falsche lässt den Start mit Angabe des Variablennamens und dessen, was erwartet wird, scheitern, statt später als unklarer Werkzeugfehler aufzutauchen.

{
  "mcpServers": {
    "hq": {
      "command": "node",
      "args": ["/absolute/path/to/hq-mcp/apps/stdio/dist/index.js"]
    }
  }
}

Zweiter Transport: MCP über HTTP

Derselbe Werkzeugsatz ist über HTTP verfügbar – das wird benötigt, wenn der Client keinen Prozess selbst starten kann: Er ist in einem Container, auf einer anderen Maschine oder es sind mehrere. Eine separate Anwendung, Konfiguration aus derselben .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 …

Ohne HQ_MCP_HTTP_TOKENS startet er gar nicht, und er weigert sich früher, als er Clients zur Abrechnung und zum Panel aufbaut. Er lauscht auf der Loopback-Schnittstelle; ihn ins Netz zu öffnen – HQ_MCP_HTTP_HOST=0.0.0.0, und darüber wird eine Warnung gedruckt, weil zwischen Server und Netz nur noch dieses Token bleibt. Port – HQ_MCP_HTTP_PORT. Der Client verbindet sich mit /mcp und übergibt das Token als normales Authorization: Bearer:

{
  "mcpServers": {
    "hq": {
      "type": "http",
      "url": "http://127.0.0.1:42480/mcp",
      "headers": { "Authorization": "Bearer <тот же токен>" }
    }
  }
}

Die Route ist sitzungslos: Mcp-Session-Id wird nicht ausgegeben und nicht benötigt, daher können hinter einem Reverse-Proxy mehrere Kopien des Prozesses ohne Sticky-Sessions gehalten werden. Server-Nachrichten gibt es nicht, daher antworten GET auf den SSE-Stream und DELETE auf das Schließen einer Sitzung mit 405 – ein MCP-Client versteht das. Eine Anfrage mit dem Header Origin wird mit 403 abgewiesen: Schutz vor DNS-Rebinding, siehe „Einschränkungen“.

Das benachbarte /v1/tools ist kein MCP, sondern eine interne REST-Fassade für ai-bot: eine Handliste und eine für den Aufruf, mit eigenem Antwortumschlag und eigenem Anfragelimit.

Production HTTP image

Production-Builds werden ausschließlich aus einem verifizierten 40-Zeichen-lowercase-Commit-SHA erstellt. Dieser SHA wird gleichzeitig in ein OCI-Label, eine root-eigene, schreibgeschützte Datei und /healthz versiegelt; der Entrypoint stellt den Wert aus der Datei wieder her, sodass eine Runtime-Überschreibung von HQ_MCP_IMAGE_REVISION die Health-Evidenz nicht verändert.

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}"

Der Compose-Konsument fixiert genau diesen 40-Zeichen-Tag und deklariert keine Host-ports. Der Container läuft als UID/GID 10001, veröffentlicht im bot+ro-Modus nur /healthz und das REST-Fassade und verlangt eine gemeinsame, nicht geheime HQ_MCP_DEPLOYMENT_CONFIG_REVISION im Format lowercase-UUID. In den Health-Check gehen beide Revisionen ein, damit sich der ai-bot vor dem Lesen des Katalogs schließen kann, wenn sie nicht übereinstimmen.

Production übergibt Geheimnisse ausschließlich über drei reguläre, nicht-symlink-Dateien mit exaktem Mode 0600: SHM_ADMIN_AUTH_FILE, REMNA_API_TOKEN_FILE und HQ_MCP_HTTP_TOKENS_FILE. Letztere enthält nur ai-bot:<dedicated token>. Das ist ein separates Server-Token; auch die SHM- und Remnawave-Credentials werden diesem Deployment separat zugewiesen und nicht aus dem Support-Bot wiederverwendet.

Anpassung an die eigene Installation

Das Upstream-danuk/shm kennt das Wort „Remnawave“ nicht — keine einzige Zeile. Die Brücke zwischen Billing und Panel lebt vollständig in Ihren Provisioning-Vorlagen: ein Panel-Benutzer pro user_service_id, Name <NAME_PREFIX><user_service_id>, Konfigurations-Snapshot im SHM-Storage unter <STORAGE_PREFIX><user_service_id>. Beide Präfixe liest der Server zur Laufzeit aus config.remnawave Ihrer SHM und erlaubt eine Überschreibung (HQ_MCP_STORAGE_PREFIX, HQ_MCP_PANEL_PREFIXES) — der Operator weiß besser, was heute im Panel liegt, als ein Konfigurationsschlüssel, der beschreibt, was SHM morgen bauen wird.

Der Panel-Benutzername ist der einzige Verknüpfungsschlüssel, und ein Präfix, das mit nichts übereinstimmt, erzeugt keinen Fehler: Es erzeugt eine selbstsichere falsche Antwort, in der jede Dienstleistung wie nicht provisioniert aussieht. Deshalb sprechen die Werkzeuge, die das beweisen können, den Code prefix_unverified und unterdrücken den betroffenen Befund — sync_audit gibt den Eimer missingPanelUser gar nicht erst zurück, provisioning_diagnose markiert das Ergebnis mit demselben Code oder panel_username_guessed. Die Konvention wird genau drei Werkzeugen benötigt (sync_audit, provisioning_diagnose, der Mutator storage_edit); client_overview akzeptiert remna_user_id als optionalen Parameter und zeigt ohne ihn einfach die halbe Panel-Seite nicht. Wenn Sie die Konvention nicht haben, funktionieren alle anderen Werkzeuge wie gewohnt, und diese drei erfinden keine Befunde. Die vollständige Analyse, mit Präfix-Reihenfolge und geerbten Namen, finden Sie in COMPATIBILITY.md.

Werkzeuge

Vierunddreißig sind im ro-Modus sichtbar; der rw-Modus fügt fünfzehn aus der letzten Tabelle hinzu und entfernt nichts. Die Zahlen gelten für das Profil human; was davon der bot sieht, steht im Sicherheitsmodell.

Plattform und ein Client

Werkzeug

Worauf es antwortet

platform_probe

Was gerade lebt: Versionen, Fähigkeiten, Tunnel und ob ein Ausfall ein Unfall oder ein Credential-Problem ist

client_resolve

Jede Kennung (Telegram-ID, E-Mail, Login, ID, Name im Panel) in kanonische IDs beider Systeme — alle Treffer, nicht der erste

client_search

Suche nach SHM-Clients anhand eines Fragments, mit serverseitiger Trefferzahl

client_overview

Der Client vollständig in beiden Systemen mit einem einzigen Aufruf

client_account_state

Wie das Konto sich anmeldet: E-Mail und ihre Bestätigung, OTP, Passkey, ob Passwort-Login möglich ist, Empfehlungen

client_billing_view

Geld aus Sicht des Clients: anstehende Abbuchung und die Zahlungsmethoden, die ihm tatsächlich angeboten werden

client_catalog_view

Katalog und Promo-Codes aus Sicht eines einzelnen Clients — sein Rabatt, seine Boni, vor ihm verborgene Tarife

Geld, Katalog, Konfiguration

Werkzeug

Worauf es antwortet

billing_ledger

Zahlungen, Boni, Abbuchungen und zwei unabhängige Abgleiche (Saldo und Bonus sind verschiedene Spalten mit verschiedenen Aktualisierungspfaden)

autopay_inspect

Zustand der automatischen Zahlung und alle einbehaltenen Gebühren — sie liegen im JSON-Feld comment der Zahlungszeilen, nicht in user.settings

promo_read

Promo-Codes und ihre Einlösungen: Das sind verschiedene Zeilen, und man kann sie nicht zusammen lesen

catalog_read

Tarife, Bestellpreis, untergeordnete Dienste, Ereigniskarte, Kategorien — Quelle der zulässigen service_id

config_read

Ein einzelner SHM-Konfigurationsschlüssel aus einer geschlossenen Liste, Geheimnisse werden maskiert. Ein vollständiges Lesen der Konfiguration existiert nicht

template_read

Liste der Vorlagen oder der Inhalt genau einer — der Datei, die die Benachrichtigung oder das Provisioning-Skript erzeugt

Dienste und Provisioning

Werkzeug

Worauf es antwortet

service_inspect

Dienste des Clients: Status, Laufzeit, geplanter nächster Tarif, Spool-Aufgaben pro Dienst

spool_inspect

Provisioning-Warteschlange: hängengebliebene, abgestürzte, angehaltene und die tatsächliche Tiefe

provisioning_diagnose

„Bezahlt, aber keine Konfiguration“ — pro Dienst, nicht pro Client

sync_audit

Stapelweiser Abgleich des Billings mit dem Panel, beide Seiten werden vollständig gelesen

notify_history

Ob dem Client tatsächlich mitgeteilt wurde, und wenn nicht — warum; ein Zustellungsurteil, das sonst nichts zeigt

server_inventory

Eigene SHM-Transporte und ihre Gruppen (ssh, http, mail, telegram) und Unterbrechungen, die das Provisioning stillschweigend stoppen. Das ist keine Liste der Remnawave-Knoten

Panel — zuerst aus Client-Sicht, dann aus Sicht der Flotte

Werkzeug

Worauf es antwortet

subscription_inspect

Remnawave-Karte: Status, Laufzeit, Traffic, HWID-Geräte, letzte Abrufe der Abonnement-URL. Schlüssel — niemals

subpage_read

Was die Abonnement-Seite dem Client tatsächlich zeigt: Plattformen, Apps, Installationsschritte, Button-Links

client_reach

Zu welchen Knoten dieser Client tatsächlich durchdringt und welche Squads und Inbound-Tags das ergeben

device_inventory

HWID-Bild über die gesamte Flotte — die Basis, ohne die die Gerätezahl eines einzelnen Clients nichts bedeutet

traffic_stats

Traffic pro Tag, aufgeschlüsselt nach Knoten und Squads; das ist eine Zeitreihe, keine Karten-Zähler

connections_inspect

Wer gerade verbunden ist. Das Panel beantwortet das mit einem Job, und die Abfrage führt das Werkzeug selbst

infra_map

Knoten × Konfigurationsprofile × Inbounds × Hosts × Squads und die Lücken dazwischen

infra_costs

Was die Infrastruktur kostet, im Zusammenspiel mit dem Panel: ein bezahlter Knoten, zu dem niemand durchdringt, ist fließendes Geld

country_health

Knoten, Online-Status, Traffic und Hosts eines Landes

node_config_audit

Was das Profil deklariert, gegenüber dem, was das Panel dem Knoten tatsächlich ausliefern würde

squads_read

Beide Squad-Familien: interne entscheiden über Zugriff, externe darüber, wie das Abonnement präsentiert wird

panel_activity

Was mit dem Panel selbst passiert: Zusammenfassung, Digest für ein Fenster, welche Routen aufgerufen werden, Verlauf der Abonnement-Abrufe

torrent_reports

Beweise des Torrent-Blockers — und, separat, ob er überhaupt installiert ist und ob er überwacht

Hinter dem Tunnel (diese beiden schlagen ohne ihn fehl und nennen den genauen ssh-Befehl)

Werkzeug

Worauf es antwortet

abuse_report

Befunde des Anti-Abuse-Hooks plus Panel-Toplisten. Teuer: unbegrenzte Scans der Arbeits-MySQL des Billings, Obergrenze 5 Aufrufe in 5 Minuten

sql_query

SQL nur lesend — Preflight und sonst nichts, siehe unten

Schreibende (nur rw, nur Profil human, zuerst ein Plan)

Werkzeug

Was es verändert

billing_adjust

Guthaben oder Boni eines SHM-Kunden

billing_refund_service

Gibt den Betrag auf das Guthaben zurück, den SHM für den aktuellen bezahlten Zeitraum als abgebucht verbucht hat

bulk_ops

Massenoperationen an Panel-Kunden – über eine benannte Menge von IDs oder über die gesamte Flotte

host_edit

Ein einzelner Remnawave-Host: Beschriftung, Adresse, Port, SNI/host/path/ALPN/fingerprint, Sicherheitsebene, Tags, Aktivierung und Ausblenden

host_cleanup

Löscht Hosts anhand einer expliziten UUID-Liste. Unumkehrbar

node_manage

Ein einzelner Node: enable, disable, restart, reset_traffic, update, create

subscription_ops

Ein einzelnes Abonnement im Panel: enable, disable, extend, reset_traffic, revoke, set_limits, Entfernen von Geräten

service_lifecycle

Kundendienst: give, touch, change_plan, schedule_change, stop, activate, delete

provisioning_repair

retry, resume oder pause einer einzelnen festhängenden Spool-Aufgabe

template_edit

Überschreibt den Inhalt eines bestehenden SHM-Templates

storage_edit

Schreibt einen benutzerdefinierten SHM-Speicher anhand der für diese Installation ausgegebenen Schlüsselliste

server_edit

Transportzeile oder Transportgruppe von SHM – Webhooks, SSH-Provisionierungs-Endpunkt, E-Mail-Absender

user_flags

Sperrt einen Kunden oder bearbeitet sichere Felder der Karte (full_name, phone, comment)

ops_confirm

Wendet einen Plan anhand seiner plan_id an. Schreibt das, was das geplante Werkzeug schreibt

ops_audit

Nichts. Liest das lokale Mutationsprotokoll – rw, weil das Protokoll Teil der Mutationsoberfläche ist

Mutationen

Nichts wird durch den Aufruf angewendet, der darum bittet. Ein Mutator ohne plan_id liest den aktuellen Zustand, baut den Zielzustand und gibt einen Plan zurück: before, after, diff nach Feldern, Nebeneffekte, rollback, wo es einen gibt, und eine Kennung. Er schreibt nichts. Das Anwenden ist ein zweiter Aufruf:

ops_confirm { "plan_id": "…" }          # либо: тот же мутатор, ТЕ ЖЕ аргументы, плюс plan_id

Der Plan ist an das Profil, das ihn erstellt hat, an das Werkzeug, für das er erstellt wurde, und an den Hash der Argumente gebunden: Er kann weder von einem anderen Aufrufer noch mit einem anderen Werkzeug noch mit demselben Werkzeug mit einer einzigen geänderten Zahl eingelöst werden. Er lebt 10 Minuten. Die Einmaligkeit ist ein atomares rename auf der Platte, kein „lesen und löschen“: Von zwanzig gleichzeitigen Bestätigungen gewinnt genau eine, die übrigen erhalten „nicht gefunden“. Ein fehlgeschlagener Versuch verbrennt einen gültigen Plan nicht – alle Prüfungen laufen nach der Erfassung, und ein gescheiterter legt die Datei zurück; der Versuch selbst verbrennt ihn, und wenn das Backend abstürzt, ist der Plan verbraucht. Das ist beabsichtigt, und darin liegt der Unterschied zwischen einer Abbuchung und dreien. Vor dem Anwenden liest das Werkzeug die Welt erneut und gleicht sie mit dem Schnappschuss ab, aus dem der Plan erstellt wurde: Hat sich das Objekt verschoben, wird der Plan abgelehnt und nicht über eine fremde Änderung hinweg gerollt.

Jeder Versuch wird protokolliert in HQ_MCP_AUDIT_PATH (JSONL, Rechte 0600): wer, womit, mit welchen Argumenten, wie das Objekt vorher und nachher aussah und wie es endete – planned, applying, applied, failed oder rejected; Fehlschläge gleichberechtigt mit Erfolgen. applying wird vor dem Zugriff auf das Backend geschrieben, und genau darin liegt der Sinn der Konstruktion: Ein Eintrag ohne zugehörigen Abschluss bedeutet, dass der Prozess mitten drin gestorben ist, der Plan-Schnappschuss bereits vernichtet wurde und Geld abgeflossen sein könnte. ops_audit sucht solche ungeschlossenen Einträge im gesamten Protokoll, unabhängig vom angeforderten Fenster, und meldet sie zuerst; unverarbeitete Zeilen werden gezählt und nicht stillschweigend übersprungen.

Die Obergrenzen hält das Framework, nicht der Werkzeug-Autor. Eine Mutation über HQ_MCP_MAX_OP_AMOUNT wird vor der Planerstellung abgelehnt, und das Framework weigert sich, ein Werkzeug zu registrieren, das einen Geld-Endpunkt deklariert, aber nicht sagt, wie der Betrag aus seinem Eingang zu lesen ist. Die Obergrenze deckt beide Arten von Geldbewegungen ab, und die zweite übersieht man leicht: sowohl Zahlungen mit Boni, bei denen der Aufrufer den Betrag nennt, als auch Lebenszyklus-Aktionen, die Kundenguthaben verbrauchen (give, touch, change_plan, activate), bei denen der Betrag der Tarifpreis aus dem Katalog ist. Ein Plan, dessen Preis nicht gelesen werden konnte, wird nicht ausgegeben: Unkenntnis der Zahl macht die Abbuchung nicht kostenlos. HQ_MCP_MAX_BULK_USERS begrenzt, wie viele Panel-Kunden eine einzelne Massenoperation betreffen darf, und ein Plan, der diese Zahl beim Panel nicht ermitteln konnte, wird abgelehnt und nicht nach Augenmaß geschätzt. Über der Obergrenze wird die Operation vollständig abgelehnt – niemals abgeschnitten.

Die Obergrenze wird bei der Planerstellung und nur dort geprüft: Das Anwenden arbeitet mit dem bereits erstellten Plan und misst ihn nicht erneut. Die Obergrenze lässt sich so nicht umgehen – die Argumente sind per Hash fixiert –, aber eine Obergrenze, die nach der Planausgabe in .env gesenkt wird, wirkt nicht auf diesen Plan.

template_edit und storage_edit schreiben zuerst ein eigenes Rollback in HQ_MCP_BACKUP_DIR (Verzeichnis 0700, Dateien 0600); der Pfad wird in der Antwort zurückgegeben, restore_from legt die Bytes zurück, und ohne aufgenommenen Schnappschuss schreibt keiner der beiden. Das Backup ist bewusst vom Plan- Schnappschuss getrennt: Template-Inhalte und Konfigurations-Schnappschüsse tragen Geheimnisse als nackte Teilstrings, die kein Feldnamen haben, um sie zu maskieren – sie dürfen also nicht innerhalb von before/rollback zurück zum Modell fahren; und das Rollback muss einen Wechsel überleben, während Plan-Schnappschüsse innerhalb einer Stunde aufgeräumt werden.

Zwei weitere Dinge weigern sich die schreibenden zu tun. Ein Inhalt mit <redacted:…>-Markern wird niemals geschrieben: Das ist die Ausgabe eines lesenden Werkzeugs, und das Schreiben hätte eine lebende Zugangsdaten durch genau das Wort ersetzt, mit dem man sie versteckt hat. Und rohe Panel-Blobs (finalMask, xhttpExtraParams, muxParams, sockoptParams) sind von jedem Host-Patch ausgeschlossen – in einer laufenden Installation trägt ein nennenswerter Teil der Hosts innerhalb von finalMask ein funktionierendes Hysteria2-Passwort.

Was tatsächlich bewiesen ist und was nicht

host_edit ist der einzige Mutator, dessen Anwendungs-Zweig auf einem laufenden System ausgeführt wurde: Auf einem laufenden Remnawave-3.2.3-Panel wurde die Host-Beschriftung geändert, geprüft, dass das Passwort in finalMask überlebt hat und dass sich nichts über das deklarierte Feld hinaus geändert hat, und dann zurückgerollt. Alle anderen sind bis einschließlich zum Plan bewiesen: Der Plan wird mit echten Daten erstellt, der anwendende Teil ist durch Tests abgedeckt, aber auf einem laufenden System wurde sein Zweig nicht ausgeführt. Das ist wörtlich zu lesen. Ein Plan, der richtig aussieht, ist ein Zeugnis über den Plan.

Sicherheitsmodell

Zwei Profile. human ist ein vertrauenswürdiger Operator, und er bekommt konkrete, handlungsfähige Fehlermeldungen, einschließlich des genauen ssh-Befehls, wenn der Tunnel geschlossen ist. bot ist ein nicht vertrauenswürdiger Kanal: Jede Fehlermeldung wird auf dieselbe Nachricht zusammengestaucht, damit das Register nicht durchsucht werden kann, um zu ertasten, welche Namen anders antworten. Kein schreibendes Werkzeug wird dem Bot jemals angeboten: Im rw-Profil sieht der bot dieselben zwanzig lesenden Werkzeuge wie im ro.

Eine verbotene Klasse, getrennt von bloß Gefährlichem. Diese Operationen sind nicht durch Tore gesperrt – sie existieren nicht, und der Scanner in der Build-Phase lässt den Lauf scheitern, wenn ihr Pfad im Quelltext als Literal vorkommt. Identity- und Keygen-Routen von Nodes (GET, dessen Antwortkörper einen privaten Schlüssel enthält). Token-, Autorisierungs- und Passkey-Routen (das Panel gibt Tokens im Klartext aus, und ein erstelltes Token ist ein permanenter Admin an allen Toren vorbei). Panel- und Abonnement-Einstellungen. Der vollständige Export von /admin/config. Das manuelle Markieren einer Provisionierungsaufgabe als erfolgreich – sie erledigt die Arbeit nicht, sondern versetzt den Dienst nur in ACTIVE, während der Benutzer im Panel weiterhin fehlt. Das Löschen einer Zahlung, eines Bonus oder einer Abbuchung – ein nacktes DELETE FROM über das Register, bei dem users.balance nicht neu berechnet wird. Gebrauchsfertige Abonnement-Links und Connection-Keys. restart-all, reorder, Bulk-Aktionen von Squads und PUT /admin/spool mit job_users – eine Verteilung an alle Kunden ohne Abbestellung.

Die Klasse hat sich verengt, und jede Verengung war eine Korrektur, keine Lockerung. Das Lesen von Templates war zusammen mit dem Schreiben verboten, obwohl der Grund – kein Git, kein Rollback – nur über das Schreiben sprach; die Breite kostete nicht nur theoretisch: Ein nennenswerter Anteil der Benachrichtigungen in einem beobachteten Fenster wurde leer gerendert und verschickte nichts, die Aufgabe meldete dabei SUCCESS, und die Ursache des Schweigens liegt im Template-Inhalt. Jetzt ist das Lesen offen, POST liegt unter template_edit, das ein Rollback mitgebracht hat, und PUT und DELETE sind gesperrt: Ein gerade entstandenes und ein gerade verschwundenes Template haben keinen vorherigen Zustand, den man aufnehmen könnte. Das Verbot von /api/sub war präfixbasiert und deckte zugleich /api/subscription-page-configs und /api/subscription-request-history ab – zwei lesende Controller, die keine Schlüssel ausgeben; jetzt ist es exact plus prefix auf /api/sub/. Massenoperationen an Panel-Kunden waren verboten, weil sie auf die gesamte Datenbank ohne eine Liste zur Ansicht angewendet werden – das stimmt genau so lange, wie niemand zählt: bulk_ops zählt beim Panel vor der Anwendung, weigert sich, wenn die Zahl nicht ermittelt werden konnte oder über HQ_MCP_MAX_BULK_USERS liegt, und wird dem Bot nicht angeboten.

POST /api/users/bulk/delete-by-status bleibt seiner Form nach verboten: In seinem Körper steht ein Status, keine Personenliste. Das Panel stellt die Aufgabe in die Warteschlange und löscht die, die im Moment ihrer Ausführung passen – nicht die, die der Operator angesehen hat –, und antwortet mit 202 und leerem Körper ohne Zähler, sodass in der Zwischenzeit abgelaufene Konten unsichtbar gelöscht werden. Die Möglichkeit bleibt als bulk_ops delete_by_status erhalten: Er listet konkrete IDs auf, zeigt sie und löscht genau sie über bulk/delete. Massenrouten auf anderen Entitäten – Hosts, Nodes, Squads, Spool-Verteilungen – haben keinen solchen Zählschritt und bleiben nicht vorhanden.

Geheimnisse werden am Ausgang maskiert – sowohl nach Schlüsselnamen als auch nach Wertform. Nach Namen: eine geschlossene Liste von Credential-Schlüsseln, Übereinstimmung mit token|secret|key|password|auth bei einer expliziten Ausnahmeliste, Endmaskierung für mehrere und PII-Maskierung für das Profil bot. Das reicht nicht, und an einem einzigen Tag hat es dreimal versagt: Ein Telegram-Bot-Token fuhr innerhalb von response.request.url einer Spool-Zeile mit (der Schlüssel heißt url), es lag auch in der Spalte host von SHM-Transportzeilen, und Template-Inhalte tragen Credentials als nackte Teilstrings, neben denen es gar kein Feldnamen gibt. Deshalb lässt der Umweg jede durchlaufende Zeile zusätzlich durch die Regeln der Wertform laufen: JWT; NAME=<Wert>, wobei der Name ein Geheimnis verspricht und der Wert nicht wie ein Platzhalter aussieht; Telegram-Bot-Tokens mit und ohne umgebenden Pfad; user:password@ innerhalb von URLs. Er lebt innerhalb von redact, das beide HTTP-Clients am Eingang und der Ausführer am Ausgang aufrufen – kein einzelnes Werkzeug muss sich daran erinnern.

Die Regeln sind kalibriert, nicht geraten, und die Kalibrierung ist direkt im Quelltext erklärt: Die Schwelle des „undurchsichtigen Laufs“ (32+ Zeichen, die zufällig aussehen) wurde an echten Template-Inhalten gemessen und bei strukturierten API-Antworten abgeschaltet, wo sie von Data-URI-Icons und hex uniq_id der Zahlung überschritten wird – hätte man sie herausgeschnitten, hätte die Bereinigung genau die Felder ausgelöscht, für die das Werkzeug geschrieben wurde. Eine Sicherheitsgrenze ist das trotzdem nicht, und der Quelltext sagt es auch: Ein Geheimnis, das in Worten geschrieben ist, hat keine Form; alles, was den Filter passiert, bleibt innerhalb des Profils human. Dieselben Regeln verwendet scripts/no-secrets.test.ts, das kein Geheimnis in einen veröffentlichten Commit lässt: Zwei Kopien des Wissens darüber, „wie ein Geheimnis aussieht“, weichen stillschweigend voneinander ab, und die zweite sieht weiterhin so aus, als funktioniere sie.

sql_query führt nichts aus. Es validiert und lehnt ab und sagt dies in seinem eigenen Quellcode. Die lexikalische Prüfung ist ein billiger erster Filter und eindeutig keine Sicherheitsgrenze; das Modul listet Umgehungen auf, die sie passieren, und die Tests halten sie offen, damit niemand den Filter für eine Garantie hält. Solange die Ausführung nicht angeschlossen ist, sind die Vorbedingungen in derselben Datei deklariert: Nur-Lese-Rolle, Nur-Lese-Transaktion, Abfrage-Timeout und Verbotsliste für Spalten.

Was man über die Einschränkungen wissen sollte

  • Der HTTP-Transport spricht MCP (/mcp, streamable HTTP) und bietet denselben Satz von Werkzeugen wie stdio: Eine Funktion veröffentlicht sie für beide Transporte. Was er bewusst nicht kann: Sitzungen (Mcp-Session-Id wird nicht ausgegeben), server-initiated Nachrichten und damit auch SSE-Streams über GET und Wiederaufnahme über Last-Event-ID. Jeder Aufruf ist in sich abgeschlossen, daher werden Server und Transport pro Anfrage neu erstellt; das verlangt auch das SDK selbst, dessen sitzungsloser Transport nicht wiederverwendet werden darf.

  • Auf der Route /mcp sind zwei Ausgänge des Ausführenden unerreichbar, und die Zähler /metrics sehen darüber zwei von vier. Ungültige Eingaben werden vom SDK VOR dem Werkzeug verarbeitet, und es antwortet selbst mit -32602; einen nicht existierenden Namen weist es ebenfalls selbst ab, ohne das Register zu erreichen. Daher erscheinen invalid_input und not_found auf dieser Route weder in der Antwort noch im Bericht. Auf der REST-Fassade sind beide erreichbar.

  • Eine Anfrage an /mcp mit dem Header Origin wird ohne Ausnahme mit 403 abgewiesen: Der Server lauscht auf der Loopback-Schnittstelle, und eine Seite im Browser kann ihre Domain auf 127.0.0.1 umleiten und von dort im Namen des Operators zugreifen. Der Browser setzt Origin bei jedem POST mit Cross-Origin, ein echter MCP-Client nie, und CORS-Header gibt der Server nicht aus, daher hat er keinen Browser-Client und kann keinen haben. Die eingebauten allowedHosts/allowedOrigins sind dafür nicht geeignet: In dieser SDK-Version sind sie zugunsten externer Middleware als veraltet markiert, und eine leere Origin-Liste bedeutet bei ihnen „Prüfung deaktiviert", nicht „kein Origin ist geeignet".

  • Zwei Werkzeuge benötigen einen Tunnel ins interne Netzwerk und lehnen ohne ihn ab. Sie bleiben absichtlich sichtbar: Ein verschwundenes Werkzeug lehrt das Modell, dass eine solche Möglichkeit nicht existiert, während in Wirklichkeit nur ein Port geschlossen ist.

  • sync_audit liest beide Systeme vollständig aus und ist hier der einzige teure Aufruf – dafür hat es eine eigene Anforderungsrate.

  • Die Seitengröße des Panels wird zur Laufzeit gemessen, nicht angenommen: Die API deklariert kein Maximum, und der tatsächliche Wert änderte sich zwischen den Releases.

  • Die Massenrouten des Panels antworten mit 202 oder 204 und leerem Körper und stellen einen Teil der Arbeit in die Warteschlange, daher bedeutet „angewendet" „vom Panel angenommen", nicht „für alle erledigt". Die Zahl, die der Plan im Voraus festlegt, ist die einzige ehrliche, die es hier überhaupt gibt.

  • Änderungen, die im Panel vorgenommen werden, werden nicht zurück in die SHM-Abrechnung übertragen, und die Werkzeuge, die sie vornehmen, sagen dies. Es gibt keinen Abgleichsschritt; eine Abweichung zeigt Ihnen später sync_audit.

Entwicklung

pnpm test          # модульные тесты
pnpm typecheck
pnpm test:guards   # сканер секретов и предохранители скрипта захвата фикстур

Die Tests laufen auf Fixtures, die die Form echter Antworten nachbilden. Wo ein Defekt nur auf einem laufenden System sichtbar war, sagt der Test, der ihn fixiert, genau das.

Lizenz

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