Authentication#
Every MCP request authenticates with a scoped api key. The key is presented as an HTTP-style Authorization: Bearer <key> value, or as a bare key string on the stdio transport. The server resolves the key to a single brand and role, then governs every call against that context.
The api key#
- Keys are minted show-once by a brand
ownerormanager. The raw value is
shown exactly once at creation; only an HMAC-SHA-256 hash is stored server-side. qaqnuz cannot recover a lost key — rotate instead.
- A key carries a role (default
viewer, i.e. read-only) and resolves to
exactly one brand. The brand is taken from the key record alone; it can never be overridden by a request header or parameter.
- A key carries a set of scopes —
<resource>.<action>strings. A key with
no scopes can call nothing (default-deny).
- Keys support
expires_atandrevoked_at. A revoked or expired key fails auth
before any tool is resolved.
Entitlement gate. The MCP surface is available on the Growth and Enterprise plans only. On a non-entitled brand,list_toolsreturns an empty list andcall_toolreturns apermissionerror — regardless of the key's scopes. This check runs first, before scopes are considered.
Presenting the key#
The auth layer accepts either form:
Authorization: Bearer qx_live_8s9d... # standard Bearer scheme
qx_live_8s9d... # bare key (stdio transport)The Bearer prefix is case-insensitive. An empty or missing value is rejected as Unauthorized — the server never reveals why a key failed (unknown vs. revoked vs. expired all look identical), which is deliberate enumeration-resistance.
Connecting over MCP (stdio)#
The v1 GA transport is stdio: the qaqnuz MCP server runs as a local or sidecar process that your MCP client launches and speaks JSON-RPC to over stdin/stdout. Your api key is supplied to that process (typically via an environment variable it reads on startup).
A typical MCP client configuration entry:
{
"mcpServers": {
"qaqnuz": {
"command": "qaqnuz-mcp",
"args": [],
"env": {
"QAQNUZ_API_KEY": "qx_live_8s9d..."
}
}
}
}No public HTTPS base URL yet. A hosted mcp.qaqnuz.uz/v1 HTTP/SSE endpoint is designed but not yet deployed (it depends on platform GATE-5 infrastructure). Until it is announced, integrate over stdio. The authentication model is identical on both transports, so code written against stdio carries over unchanged when the gateway ships.
Scopes#
Scopes use the same grammar the in-product policy engine uses:
<resource>.<action>Examples: sheets.read, orders.write, contacts.read.
Rules:
- No wildcards. Any scope containing
*(*,*.*,admin.*,*.read) is
rejected — at key creation and again at resolution time. Wildcards are never expanded to a set of tools.
- Subtract-only. Your effective authorization is the intersection of the
key's scopes and your brand's existing tool-permission policy. A key can narrow what the brand allows; it can never grant something the brand denies.
- Exact match. A tool is callable only if the key's scope set contains that
tool's exact scope. There is no prefix or hierarchical matching.
Verifying a key works#
Once connected, call list_tools. A working, entitled key with at least one in-scope tool returns a non-empty tool list. An empty list means one of: the brand is not entitled, no tool is flagged MCP-exposable, or the key's scopes do not intersect any exposable tool — all of which are indistinguishable by design.
Security notes for integrators#
- Never embed a key in client-side or public code. Treat it like a password.
- Scope minimally. Request only the scopes your integration needs; prefer a
read-only (viewer) key unless you specifically need write/external tools.
- Rotate on suspicion. Mint a new key, cut over, then revoke the old one.
- Expect generic auth errors. Do not build logic that depends on
distinguishing failure reasons — the server intentionally returns the same Unauthorized for every auth failure.