hq-mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@hq-mcpWhy does client 1001 have no working config?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
hq-mcp
Russian version · English
MCP server that gives an AI agent access to a VPN business: the billing SHM and the panel Remnawave, stitched together so that a question spanning both systems can be answered with a single call.
No tool changes anything with the same call that requested it: a writing tool first returns a plan, and applying it is a second call carrying that plan's identifier.
How it looks in practice
The first call on any installation is platform_probe. It answers what exists in this deployment and what of it is alive; everything else here derives from what it reports. The answers below are trimmed, the values are fictional.
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": "…" }]
}Next is a question that neither system answers alone: “the client writes that they paid, but there is no config.”
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": "…" }] }
}The panel knows nothing about him, but count: 0 here is not “no account”: paths names each search performed and what it does not see. What actually happened is told by the second pair of eyes:
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 }
}] }
}The service is ACTIVE, the configuration snapshot is in place, provisioning reported success — but the user to whom that success belongs is not in the panel. Neither billing nor panel alone shows this.
There are thirty-four tools that read both systems. The rw mode adds fifteen writing ones: thirteen change real data, one applies a plan, and another reads the local mutation log.
Related MCP server: xendit-mcp
Why composite tools instead of endpoint proxies
The obvious design is one tool per HTTP endpoint, about a hundred and fifty of them. It was written and thrown away, for two reasons.
A raw proxy nullifies any denylist. If the model can call GET <any path>, then the list of operations you decided not to allow is decoration: the forbidden path is one line away. Here tools call named routes by name, and a build-time scanner fails the run if a forbidden path appears as a literal in the source.
And an endpoint is not a question. The example above touches four SHM routes and two panel routes, and the interesting part is precisely the seam. client_overview, sync_audit, and provisioning_diagnose exist because bugs live on that seam.
The rule that defined everything else
An empty answer must never be taken as proven absence.
When a backend fails, the tool degrades: the failure goes into degraded, the partial_result warning names the missing half, and any finding that depended on that half is suppressed, not computed from what survived. When a list is truncated, the server total arrives with it — so that “no such service” does not rely on an undeclared window.
This is not theoretical caution. During development, one tool read all panel records, discarded them all because a field had been renamed on that side, and then reported that hundreds of clients needed reprovisioning — a destructive recommendation, stated confidently and derived from an empty set. The fix was not just the renamed field: it was that a basket computed from an unusable input must refuse to be a finding.
Compatibility: will it work for you
Tested on SHM 2.19.4 and Remnawave 3.2.3 — both numbers taken from a working deployment, not from the specification.
Minimum is SHM 2.18.0 and Remnawave 3.0.0. The official danuk/shm works: all routes the tools call are upstream, no fork needed. The only place where that deployment's patch was visible to the tool is the fourth flag GET /user/password-auth; now its absence is called a sign_in_flag_absent warning, not passed off as a diagnosis. The full list of routes for both systems, the version each appeared in, and a detailed answer about the fork are in COMPATIBILITY.md.
The check takes one call — the same platform_probe. If the version is below the minimum, it responds with a backend_version_below_minimum warning, naming the version, the minimum, and what exactly will break. Nothing is disabled: an old version gives loud failures on specific routes, not quiet empty answers.
Version | What disappears | Whom it affects |
SHM < 2.18.0 |
| only |
SHM < 2.11.3 |
|
|
SHM < 2.9.0 |
|
|
SHM < 2.4.0 |
|
|
Panel < 3.0.0 | user is addressed by |
|
Panel < 3.0.0 | no |
|
Panel < 3.0.0 | no |
|
Panel < 3.0.0 | no |
|
Panel < 3.2.0 | no | only |
Remnawave 3.x breaks compatibility with everything written for 2.x, and breaks it quietly. The uuid was removed from the user object, and with it the by-telegram-id, by-email, and by-tag routes disappeared, and /api/users/{uuid} responds 400, not 404 — so the failure doesn't even look like “no such user”. These routes are not present here at all; where the server does encounter a legacy uuid (for example, in an old SHM storage snapshot), it says so in the response rather than silently sliding into a guess.
OpenAPI specifications lag behind working systems, so platform_probe carries a specs_are_stale warning on every call; SHM is worse than usual — its spec stamps info.version from the config at runtime, meaning it describes the stand where the export was made, not yours. So the probe does not read versions from files at all, but asks the working systems — and there it also establishes what is true for this deployment: whether the server-side filter narrows anything in SHM, whether the panel respects filters in the user listing (both respond 200 and silently discard unknown parameters), whether the panel keeps a subscription request log, whether the realtime traffic route exists, which ssh tunnels are open. And it separates “backend is down” from “our credentials are wrong”: 401/403 is reported as credentialsRejected.
Installation
You need Node 22.12+ and pnpm, and at least one of the two systems — SHM or Remnawave. Both are not required: each is configured separately and alone is a full configuration. Tools for a system that is absent are not published at all — they don't “answer empty”, they are absent, and platform_probe directly names what is configured. So the number of tools depends on the installation: panel only — 16, SHM only — 18, both — 34 (and more in rw mode).
pnpm install
pnpm build
pnpm run setup
pnpm run setup, specifically withrun.pnpm setupis a built-in command of pnpm itself: it edits your shell profile and never reaches this repository.
The wizard exists because the step it replaces — writing .env by hand — fails silently: a typo in the panel token does not prevent the server from starting and later surfaces as a tool error in the middle of an unrelated question. So it checks each credential against the working system and distinguishes three failures — the host did not respond at all (DNS, TLS, closed port), the host responded and rejected the credentials, the host responded with something that proves nothing (502, 429): they are fixed differently, and a single “login failed” would send you to fix the wrong thing.
It asks only about the system you have and about the access mode; everything else is tucked behind a single question Configure the optional settings? [y/N]. It reads the timezone from the working SHM rather than guessing: SHM writes dates in its own local time without an offset, and a wrong zone silently shifts every age. It does not print secrets. By default it sets ro; for rw it requires typing the word rw and a separate confirmation — first naming how many tools will appear and how many of them write to the real billing and the real panel, counted from the registry at that moment. It writes .env with 0600 permissions over a copy of the previous one, carrying over variables it did not ask about, and prints connection commands for Claude Code, Codex, and opencode — but it does not edit other people's configs: a wizard that rewrites JSONC would one day break someone's working setup. You can restart it at any time; Enter keeps the existing value. Without a terminal it refuses to start: an MCP client starts the server without a TTY, and a wizard capable of waking up there would hang on a question no one sees.
Or by hand
cp .env.example .env && chmod 600 .env # и заполнитьEach variable is described in .env.example. A missing or incorrect one crashes the start with the variable name and what is expected from it, instead of surfacing later as an obscure tool error.
{
"mcpServers": {
"hq": {
"command": "node",
"args": ["/absolute/path/to/hq-mcp/apps/stdio/dist/index.js"]
}
}
}Second transport: MCP over HTTP
The same set of tools is available over HTTP — this is needed when the client cannot start the process itself: it is in a container, on another machine, or there are several of them. A separate application, configuration from the same .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 …Without HQ_MCP_HTTP_TOKENS it does not start at all, and it refuses before building clients to billing and panel. It listens on loopback; to open it to the network — HQ_MCP_HTTP_HOST=0.0.0.0, and a warning is printed about that, because only that token will remain between the server and the network. The port is HQ_MCP_HTTP_PORT. The client connects to /mcp, passing the token with the usual Authorization: Bearer:
{
"mcpServers": {
"hq": {
"type": "http",
"url": "http://127.0.0.1:42480/mcp",
"headers": { "Authorization": "Bearer <тот же токен>" }
}
}
}The route is sessionless: Mcp-Session-Id is neither issued nor required, so you can keep several copies of the process behind a reverse proxy without sticky connections. It has no server messages, so GET on the SSE stream and DELETE on session close respond 405 — an MCP client understands this. A request with an Origin header is rejected with 403: protection against DNS rebinding, see “Limitations”.
The neighboring /v1/tools is not MCP but an internal REST facade for ai-bot: one list handle and one per call, with its own response envelope and its own request ceiling.
Production HTTP image
A production image is built only from a verified 40-character lowercase commit SHA. This SHA is sealed simultaneously into an OCI label, a root-owned read-only file, and /healthz; the entrypoint restores the value from the file, so a runtime override of HQ_MCP_IMAGE_REVISION does not change the health evidence.
pnpm test && pnpm test:guards && pnpm typecheck && pnpm build
HQ_MCP_COMMIT_SHA="$(git rev-parse HEAD)"
test "${#HQ_MCP_COMMIT_SHA}" -eq 40
docker build --build-arg "HQ_MCP_DEPLOYMENT_REVISION=${HQ_MCP_COMMIT_SHA}" --tag "hq-mcp-http:${HQ_MCP_COMMIT_SHA}" .
scripts/http-container-smoke.sh "hq-mcp-http:${HQ_MCP_COMMIT_SHA}"A Compose consumer pins exactly this 40-character tag and does not declare host ports. The container runs as UID/GID 10001, publishes only /healthz and the REST facade in bot+ro, and requires a shared non-secret HQ_MCP_DEPLOYMENT_CONFIG_REVISION in lowercase UUID format. Both revisions are included in health so that ai-bot can shut down before reading the catalog if they do not match.
Production passes secrets only through three regular non-symlink files with exact mode 0600: SHM_ADMIN_AUTH_FILE, REMNA_API_TOKEN_FILE, and HQ_MCP_HTTP_TOKENS_FILE. The latter contains only ai-bot:<dedicated token>. This is a separate server token; SHM and Remnawave credentials are also allocated to this deployment separately and are not reused from the support bot.
Fitting to your own installation
The upstream danuk/shm does not know the word "Remnawave" — not a single line. The bridge between billing and the panel lives entirely in your provisioning templates: one panel user per user_service_id, name <NAME_PREFIX><user_service_id>, configuration snapshot in SHM storage under <STORAGE_PREFIX><user_service_id>. The server reads both prefixes at runtime from config.remnawave of your SHM and allows overriding (HQ_MCP_STORAGE_PREFIX, HQ_MCP_PANEL_PREFIXES) — the operator knows what is in the panel today better than a configuration key describing what SHM will build tomorrow.
The panel username is the only linking key, and a prefix that matches nothing does not produce an error: it produces a confidently wrong answer in which every service looks unprovisioned. Therefore, tools that can prove this return code prefix_unverified and suppress the affected finding — sync_audit does not return the missingPanelUser bucket at all, provisioning_diagnose marks the result with the same code or panel_username_guessed. The convention is needed by exactly three tools (sync_audit, provisioning_diagnose, the storage_edit mutator); client_overview accepts remna_user_id as an optional parameter and without it simply does not show half of the panel. If you do not have the convention, all other tools work as usual, and these three do not invent findings. The full breakdown, with prefix order and inherited names, is in COMPATIBILITY.md.
Tools
Thirty-four are visible in ro; rw mode adds fifteen from the last table and removes nothing. The numbers are for the human profile; what bot sees of this is stated in the security model.
Platform and a single client
Tool | What it answers |
| What is alive right now: versions, capabilities, tunnels, and whether a failure is an outage or credentials |
| Any identifier (telegram id, email, login, id, panel name) into canonical ids of both systems — all matches, not the first |
| Search SHM clients by fragment, with server-side match count |
| The client in full in both systems in one call |
| How the account signs in: email and its confirmation, OTP, passkey, whether password login is possible, referrals |
| Money through the client's eyes: upcoming charge and the payment methods actually offered to them |
| Catalog and promo codes through one client's eyes — their discount, their bonuses, plans hidden from them |
Money, catalog, configuration
Tool | What it answers |
| Payments, bonuses, charges, and two independent reconciliations (balance and bonus are different columns with different update paths) |
| Autopay state and all withheld fees — it lives in the JSON |
| Promo codes and their redemptions: these are different rows and cannot be read together |
| Plans, order pricing, child services, event map, categories — the source of valid |
| One SHM configuration key from a closed list, secrets masked. There is no whole-configuration read |
| Template list or the body of exactly one — the file that actually produces a notification or provisioning script |
Services and provisioning
Tool | What it answers |
| Client services: status, expiry, scheduled next plan, spool tasks per service |
| Provisioning queue: stuck, failed, paused, and the real depth |
| "Paid but no config" — per service, not per client |
| Batch reconciliation of billing with the panel, both sides read to the end |
| Whether the client was actually told, and if not — why; a delivery verdict that nothing else shows |
| SHM's own transports and their groups (ssh, http, mail, telegram) and gaps that silently stop provisioning. This is not a Remnawave node list |
Panel — first from the client side, then from the fleet side
Tool | What it answers |
| Remnawave card: status, expiry, traffic, HWID devices, recent subscription requests. Keys — never |
| What the subscription page actually shows the client: platforms, apps, install steps, button links |
| Which nodes this client can actually reach and which inbound squads and tags provide that |
| HWID picture across the whole fleet — the base without which one client's device count means nothing |
| Daily traffic broken down by nodes and squads; this is a time series, not card counters |
| Who is connected right now. The panel answers this with a job, and the tool polls it itself |
| Nodes × configuration profiles × inbounds × hosts × squads and the gaps between them |
| What the infrastructure costs, at the junction with the panel: a paid node nobody reaches is money going out |
| Nodes, online status, traffic, and hosts of one country |
| What a profile declares versus what the panel would actually hand to a node |
| Both squad families: internal ones decide access, external ones decide how the subscription is presented |
| What is happening with the panel itself: summary, digest over a window, which routes are being hit, subscription request history |
| Torrent-blocker evidence — and, separately, whether it is installed at all and whether it is watching |
Behind the tunnel (these two fail without it, naming the exact ssh command)
Tool | What it answers |
| Anti-abuse hook findings plus panel tops. Expensive: unlimited scans of the working billing MySQL, cap of 5 calls per 5 minutes |
| Read-only SQL only — preflight and nothing more, see below |
Writing (only rw, only human profile, plan first)
Tool | What it changes |
| Balance or bonuses of an SHM client |
| Returns to the balance the amount SHM recorded as charged for the current paid period |
| Bulk operations on panel clients — by a named set of ids or across the entire fleet |
| One Remnawave host: label, address, port, SNI/host/path/ALPN/fingerprint, security layer, tags, enable and hide |
| Deletes hosts by an explicit list of uuids. Irreversible |
| One node: enable, disable, restart, reset_traffic, update, create |
| One subscription in the panel: enable, disable, extend, reset_traffic, revoke, set_limits, device removal |
| Client service: give, touch, change_plan, schedule_change, stop, activate, delete |
| retry, resume or pause of one stuck spool task |
| Overwrites the body of an existing SHM template |
| Writes a custom SHM storage by the list of keys printed for this installation |
| An SHM transport line or transport group — webhooks, ssh provisioning endpoint, mail senders |
| Blocks a client or edits safe card fields ( |
| Applies a plan by its |
| Nothing. Reads the local mutation log — |
Mutations
Nothing is applied by the call that asks for it. A mutator without a
plan_id reads the current state, builds the target state, and returns a plan:
before, after, a field-level diff, side effects, rollback where one
exists, and an identifier. It writes nothing. Applying is a second call:
ops_confirm { "plan_id": "…" } # либо: тот же мутатор, ТЕ ЖЕ аргументы, плюс plan_idA plan is bound to the profile that built it, to the tool it was built
for, and to the hash of the arguments: it cannot be redeemed by a different
caller, by a different tool, or by the same tool with a single changed number.
It lives for 10 minutes. Single-use is an atomic rename on disk, not
"read and delete": out of twenty simultaneous confirmations exactly one wins,
the rest get "not found". A failed plan is not burned by a refusal — all
checks happen after acquisition, and a failed one puts the file back; the
attempt itself burns it, and if the backend crashed, the plan is spent. This is
intentional, and it is the difference between one charge and three. Before
applying, the tool re-reads the world and compares it with the snapshot the
plan was built from: if the object has moved, the plan is rejected rather than
rolled on top of someone else's change.
Every attempt is logged to HQ_MCP_AUDIT_PATH (JSONL, permissions 0600):
who, with what, with which arguments, what the object looked like before and
after, and how it ended — planned, applying, applied, failed or
rejected; refusals on par with successes. applying is written before the
backend is touched, and that is the whole point of the construction: a record
without a paired terminal one means the process died mid-way, the plan snapshot
is already destroyed, and money may have moved. ops_audit searches for such
unclosed records across the entire log, regardless of the requested window, and
reports them first; unparsed lines are counted, not silently skipped.
The framework holds the ceilings, not the tool author. A mutation above
HQ_MCP_MAX_OP_AMOUNT is rejected before the plan is built, and the framework
refuses to register a tool that declared a money endpoint but did not say how
to read the amount from its input. The ceiling covers both kinds of money
movement, and the second is easy to miss: both payments and bonuses, where the
caller names the amount, and lifecycle actions that spend the client's balance
(give, touch, change_plan, activate), where the amount is the plan
price from the catalog. A plan whose price could not be read is not issued:
not knowing the number does not make the charge free. HQ_MCP_MAX_BULK_USERS
limits how many panel clients one bulk operation may touch, and a plan that
could not establish that number from the panel is rejected rather than
estimated by eye. Above the ceiling the operation is rejected in full — never
truncated.
The ceiling is checked when the plan is built and only there: applying
works off an already-built plan and does not measure it again. The ceiling
cannot be bypassed this way — the arguments are pinned by the hash — but a
ceiling lowered in .env after the plan was issued will not affect that plan.
template_edit and storage_edit first write their own rollback to
HQ_MCP_BACKUP_DIR (directory 0700, files 0600); the path is returned in the
response, restore_from puts the bytes back, and neither writes without a
snapshot taken. The backup is deliberately separate from the plan snapshot:
template bodies and configuration snapshots carry secrets as bare substrings
with no field name to mask them by — so they cannot ride back to the model
inside before/rollback; and the rollback must survive a change, whereas
plan snapshots are swept within an hour.
Two more things the writing tools refuse to do. A body with <redacted:…>
markers is never written: it is the output of a reading tool, and writing it
would replace a live credential with the very word used to hide it. And raw
panel blobs (finalMask, xhttpExtraParams, muxParams, sockoptParams)
are excluded from any host patch — in a working installation a noticeable share
of hosts carry a working Hysteria2 password inside finalMask.
What is actually proven, and what is not
host_edit is the only mutator whose apply branch was run against a live
system: on a working Remnawave 3.2.3 panel we changed a host label, verified
that the password in finalMask survived and that nothing beyond the declared
field changed, and rolled it back. Everything else is proven up to and
including the plan: the plan is built on real data, the applying part is
covered by tests, but its branch was not run against a live system. This should
be read literally. A plan that looks correct is a certificate about a plan.
Security model
Two profiles. human is a trusted operator, and gets concrete actionable
refusals, including the exact ssh command when the tunnel is down. bot is an
untrusted channel: any refusal collapses into the same message, so the registry
cannot be probed to feel out which names answer differently. No writing tool is
ever offered to a bot: in rw the bot profile sees the same twenty reading
tools as in ro.
A forbidden class, separate from merely dangerous. These operations are not
gated — they do not exist, and a build-time scanner fails the run if their path
appears in the source as a literal. Identity and node keygen routes (GET, whose
response body carries the private key). Token, authorization and passkey routes
(the panel returns tokens in plaintext, and a created token is a permanent
admin past all gates). Panel and subscription settings. The full /admin/config
export. Manually marking a provisioning task successful — it does not do the
work, it only moves the service to ACTIVE while the user is still absent from
the panel. Deleting a payment, bonus or charge — a bare DELETE FROM on the
registry, with users.balance not recalculated. Ready-to-use subscription
links and connection-keys. restart-all, reorder, squad bulk-actions and
PUT /admin/spool with job_users — broadcast to all clients with no undo.
The class narrowed, and every narrowing was a fix, not a relaxation. Reading
templates was forbidden along with writing, although the reason — no git, no
rollback — spoke only about writing; the breadth cost was not theoretical: a
noticeable share of notifications in one observed window rendered empty and
sent nothing, the task reported SUCCESS, and the cause of the silence lies
inside the template body. Now reading is open, POST is under template_edit,
which brought a rollback with it, and PUT and DELETE are closed: a template
that just appeared and one that just disappeared have no previous state to
capture. The ban on /api/sub was prefix-based and also covered
/api/subscription-page-configs and /api/subscription-request-history — two
reading controllers that issue no keys; now it is exact plus a prefix on
/api/sub/. Bulk operations on panel clients were forbidden because they apply
to the whole database with no list to review — true exactly until someone
counts: bulk_ops counts at the panel before applying, refuses when the number
could not be established or is above HQ_MCP_MAX_BULK_USERS, and is not
offered to a bot.
POST /api/users/bulk/delete-by-status remains forbidden by its very
shape: its body carries a status, not a list of people. The panel queues the
task and deletes whoever matches at the moment it runs — not the people the
operator reviewed — and answers 202 with an empty body and no counter, so
accounts that expired in the meantime are deleted invisibly. The capability is
preserved as bulk_ops delete_by_status: it enumerates specific ids, shows
them, and deletes exactly those via bulk/delete. Bulk routes on other
entities — hosts, nodes, squads, spool broadcasts — have no such counting step
and remain absent.
Secrets are masked on output — both by key name and by value shape. By
name: a closed list of credential keys, a match against
token|secret|key|password|auth with an explicit exception list, tail masking
for several, and PII masking for the bot profile. That was not enough, and in
one day it failed three times: a Telegram bot token rode inside
response.request.url of a spool line (the key is called url), the same
token sat in the host column of SHM transport lines, and template bodies
carry credentials as bare substrings with no field name next to them at all.
So the pass-through also runs every passing line through value-shape
rules: JWT; NAME=<value> where the name promises a secret and the value does
not look like a placeholder; Telegram bot tokens with and without surrounding
path; user:password@ inside URLs. It lives inside redact, which both HTTP
clients call on input and the executor calls on output — no individual tool has
to remember it.
The rules are calibrated, not guessed, and the calibration is declared right in
the source: the "opaque run" threshold (32+ characters that look random) was
measured on real template bodies and disabled on structured API responses,
where data-URI icons and the hex uniq_id of a payment cross it — cutting
those out would have extinguished exactly the fields the tool was written for.
This is still not a security boundary, and the source says so: a secret written
in words has no shape; everything that passes the filter stays inside the
human profile. The same rules are used by scripts/no-secrets.test.ts, which
keeps a secret out of a published commit: two copies of the knowledge of "what
a secret looks like" diverge silently, and the second one keeps looking
functional.
sql_query does not execute anything. It validates and refuses, and says so in its own source. Lexical checking is a cheap first filter and clearly not a security boundary; the module lists the bypasses that pass through it, and the tests keep them open so that no one mistakes the filter for a guarantee. Until execution is wired up, the preconditions are declared in the same file: read-only role, read-only transaction, query timeout, and a forbidden column list.
What to know about the limitations
The HTTP transport speaks MCP (
/mcp, streamable HTTP) and exposes the same set of tools as stdio: one function publishes them for both transports. What it deliberately cannot do: sessions (Mcp-Session-Idis not issued), server-initiated messages, and with them — an SSE stream onGETand resumption viaLast-Event-ID. Each call is self-contained, so the server and transport are created fresh per request; the SDK itself requires this, as its sessionless transport must not be reused.On the
/mcproute, two executor outcomes are unreachable, and the/metricscounters see two out of four for it. Invalid input is parsed by the SDK BEFORE the tool, and it answers-32602itself; a nonexistent name is also rejected by it, without reaching the registry. Thereforeinvalid_inputandnot_founddo not appear on this route either in the response or in the report. On the REST facade, both are reachable.A request to
/mcpwith anOriginheader is rejected with 403, no exceptions: the server listens on the loopback, and a browser page can point its own domain at127.0.0.1and go there on behalf of the operator. The browser setsOriginon any cross-origin POST; a real MCP client never does, and the server sends no CORS headers, so it has no browser client and cannot have one. The built-inallowedHosts/allowedOriginsare not suitable for this: in this SDK version they are marked deprecated in favor of external middleware, and an empty origin list for them means "checking is disabled," not "no origin is acceptable."Two tools need a tunnel into the internal network, and without it they refuse. They remain visible deliberately: a vanished tool teaches the model that such a capability does not exist, when in fact the port is closed.
sync_auditreads out both systems to the end and is the only expensive call here — for that it has its own request quota.The panel page size is measured at runtime, not assumed: the API does not declare a maximum, and the actual one changed between releases.
The panel's bulk routes respond with 202 or 204 and an empty body and queue part of the work, so "applied" means "accepted by the panel," not "done for everyone." The number set by the plan in advance is the only honest one that exists here at all.
Edits made in the panel are not carried back into SHM billing, and the tools that make them say so. There is no reconciliation step;
sync_auditwill show you the discrepancy later.
Development
pnpm test # модульные тесты
pnpm typecheck
pnpm test:guards # сканер секретов и предохранители скрипта захвата фикстурTests run on fixtures that mirror the shape of real responses. Where a defect was visible only on a live system, the test that pins it down says so.
License
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