hq-mcp
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 |
| nur |
SHM < 2.11.3 |
|
|
SHM < 2.9.0 |
|
|
SHM < 2.4.0 |
|
|
Panel < 3.0.0 | Benutzer wird per |
|
Panel < 3.0.0 | kein |
|
Panel < 3.0.0 | kein |
|
Panel < 3.0.0 | kein |
|
Panel < 3.2.0 | kein | nur |
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 mitrun.pnpm setupist 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 |
| Was gerade lebt: Versionen, Fähigkeiten, Tunnel und ob ein Ausfall ein Unfall oder ein Credential-Problem ist |
| Jede Kennung (Telegram-ID, E-Mail, Login, ID, Name im Panel) in kanonische IDs beider Systeme — alle Treffer, nicht der erste |
| Suche nach SHM-Clients anhand eines Fragments, mit serverseitiger Trefferzahl |
| Der Client vollständig in beiden Systemen mit einem einzigen Aufruf |
| Wie das Konto sich anmeldet: E-Mail und ihre Bestätigung, OTP, Passkey, ob Passwort-Login möglich ist, Empfehlungen |
| Geld aus Sicht des Clients: anstehende Abbuchung und die Zahlungsmethoden, die ihm tatsächlich angeboten werden |
| Katalog und Promo-Codes aus Sicht eines einzelnen Clients — sein Rabatt, seine Boni, vor ihm verborgene Tarife |
Geld, Katalog, Konfiguration
Werkzeug | Worauf es antwortet |
| Zahlungen, Boni, Abbuchungen und zwei unabhängige Abgleiche (Saldo und Bonus sind verschiedene Spalten mit verschiedenen Aktualisierungspfaden) |
| Zustand der automatischen Zahlung und alle einbehaltenen Gebühren — sie liegen im JSON-Feld |
| Promo-Codes und ihre Einlösungen: Das sind verschiedene Zeilen, und man kann sie nicht zusammen lesen |
| Tarife, Bestellpreis, untergeordnete Dienste, Ereigniskarte, Kategorien — Quelle der zulässigen |
| Ein einzelner SHM-Konfigurationsschlüssel aus einer geschlossenen Liste, Geheimnisse werden maskiert. Ein vollständiges Lesen der Konfiguration existiert nicht |
| 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 |
| Dienste des Clients: Status, Laufzeit, geplanter nächster Tarif, Spool-Aufgaben pro Dienst |
| Provisioning-Warteschlange: hängengebliebene, abgestürzte, angehaltene und die tatsächliche Tiefe |
| „Bezahlt, aber keine Konfiguration“ — pro Dienst, nicht pro Client |
| Stapelweiser Abgleich des Billings mit dem Panel, beide Seiten werden vollständig gelesen |
| Ob dem Client tatsächlich mitgeteilt wurde, und wenn nicht — warum; ein Zustellungsurteil, das sonst nichts zeigt |
| 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 |
| Remnawave-Karte: Status, Laufzeit, Traffic, HWID-Geräte, letzte Abrufe der Abonnement-URL. Schlüssel — niemals |
| Was die Abonnement-Seite dem Client tatsächlich zeigt: Plattformen, Apps, Installationsschritte, Button-Links |
| Zu welchen Knoten dieser Client tatsächlich durchdringt und welche Squads und Inbound-Tags das ergeben |
| HWID-Bild über die gesamte Flotte — die Basis, ohne die die Gerätezahl eines einzelnen Clients nichts bedeutet |
| Traffic pro Tag, aufgeschlüsselt nach Knoten und Squads; das ist eine Zeitreihe, keine Karten-Zähler |
| Wer gerade verbunden ist. Das Panel beantwortet das mit einem Job, und die Abfrage führt das Werkzeug selbst |
| Knoten × Konfigurationsprofile × Inbounds × Hosts × Squads und die Lücken dazwischen |
| Was die Infrastruktur kostet, im Zusammenspiel mit dem Panel: ein bezahlter Knoten, zu dem niemand durchdringt, ist fließendes Geld |
| Knoten, Online-Status, Traffic und Hosts eines Landes |
| Was das Profil deklariert, gegenüber dem, was das Panel dem Knoten tatsächlich ausliefern würde |
| Beide Squad-Familien: interne entscheiden über Zugriff, externe darüber, wie das Abonnement präsentiert wird |
| Was mit dem Panel selbst passiert: Zusammenfassung, Digest für ein Fenster, welche Routen aufgerufen werden, Verlauf der Abonnement-Abrufe |
| 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 |
| Befunde des Anti-Abuse-Hooks plus Panel-Toplisten. Teuer: unbegrenzte Scans der Arbeits-MySQL des Billings, Obergrenze 5 Aufrufe in 5 Minuten |
| SQL nur lesend — Preflight und sonst nichts, siehe unten |
Schreibende (nur rw, nur Profil human, zuerst ein Plan)
Werkzeug | Was es verändert |
| Guthaben oder Boni eines SHM-Kunden |
| Gibt den Betrag auf das Guthaben zurück, den SHM für den aktuellen bezahlten Zeitraum als abgebucht verbucht hat |
| Massenoperationen an Panel-Kunden – über eine benannte Menge von IDs oder über die gesamte Flotte |
| Ein einzelner Remnawave-Host: Beschriftung, Adresse, Port, SNI/host/path/ALPN/fingerprint, Sicherheitsebene, Tags, Aktivierung und Ausblenden |
| Löscht Hosts anhand einer expliziten UUID-Liste. Unumkehrbar |
| Ein einzelner Node: enable, disable, restart, reset_traffic, update, create |
| Ein einzelnes Abonnement im Panel: enable, disable, extend, reset_traffic, revoke, set_limits, Entfernen von Geräten |
| Kundendienst: give, touch, change_plan, schedule_change, stop, activate, delete |
| retry, resume oder pause einer einzelnen festhängenden Spool-Aufgabe |
| Überschreibt den Inhalt eines bestehenden SHM-Templates |
| Schreibt einen benutzerdefinierten SHM-Speicher anhand der für diese Installation ausgegebenen Schlüsselliste |
| Transportzeile oder Transportgruppe von SHM – Webhooks, SSH-Provisionierungs-Endpunkt, E-Mail-Absender |
| Sperrt einen Kunden oder bearbeitet sichere Felder der Karte ( |
| Wendet einen Plan anhand seiner |
| Nichts. Liest das lokale Mutationsprotokoll – |
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_idDer 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-Idwird nicht ausgegeben), server-initiated Nachrichten und damit auch SSE-Streams überGETund Wiederaufnahme überLast-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
/mcpsind zwei Ausgänge des Ausführenden unerreichbar, und die Zähler/metricssehen 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 erscheineninvalid_inputundnot_foundauf dieser Route weder in der Antwort noch im Bericht. Auf der REST-Fassade sind beide erreichbar.Eine Anfrage an
/mcpmit dem HeaderOriginwird ohne Ausnahme mit 403 abgewiesen: Der Server lauscht auf der Loopback-Schnittstelle, und eine Seite im Browser kann ihre Domain auf127.0.0.1umleiten und von dort im Namen des Operators zugreifen. Der Browser setztOriginbei 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 eingebautenallowedHosts/allowedOriginssind 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_auditliest 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.
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