Authentication
Bearer tokens, per-tenant isolation enforced in the database, and what the server does not do.
Bearer tokens#
Every request carries a bearer token:
Authorization: Bearer qmcp_...
Keys are stored as SHA-256 digests. The server never holds a key in a form it could show you, so a lost key is replaced, not recovered.
An unauthenticated call to /mcp returns 401. There is no anonymous read
path to tenant data.
Isolation is enforced by the database, not by queries#
Every tenant-scoped table carries an owner_id and is protected by Postgres
row-level security, ENABLED and FORCED. The application sets
app.principal_id on the connection; the policy does the rest.
This is a deliberate choice about failure modes. If isolation lived in query
filters, a tool that forgot its WHERE owner_id = ... would return another
tenant's rows. Under row-level security the same bug returns nothing. One
of those is a data breach and the other is a visible malfunction.
Some consequences worth knowing:
holdout_one_shotis unique on(owner_id, strategy_id). Scoped tostrategy_idalone, one tenant could have spent another tenant's only holdout attempt.- Family names, spec hashes and
client_request_ids are per-tenant. Your familybtc-maand someone else's are unrelated. - Your trial count N is yours. It is never aggregated across tenants, and no
document — including this one — can tell you what it is. Call
experiment_ledgerfor your own number.
Market data is deliberately shared#
Bars are the expensive resource, and OHLCV for BTC-USD is the same fact for everyone. The catalog is shared across tenants on purpose.
What is not shared is what you did with it. A split freezes the gap policy in force when it was registered, so shared data cannot retroactively change what your registered backtest saw.
Scopes and plan limits#
A key carries its principal's plan. Quotas — trials per month, universe size, holdout evaluations — are enforced server-side on every call, not in the client. See Limits.
Check yours with:
account_status()
What the server does not implement#
The MCP authorization specification (revision 2026-07-28) makes OAuth optional: implementations using an HTTP transport SHOULD conform, not MUST. This server serves a static bearer token and has deliberately opted out of MCP authorization. That is permitted, and it is a considered choice rather than an unfinished one.
Two things follow if that ever changes:
- RFC 9728 Protected Resource Metadata becomes a MUST on the server, and clients MUST use it for authorization-server discovery.
- The target would be Client ID Metadata Documents, not Dynamic Client Registration. RFC 7591 DCR is deprecated in the current revision and retained only for backwards compatibility.
Adopting OAuth later would not invalidate any key already issued.
Rotating a key#
account_status() # shows key metadata, never the key
Contact support to rotate. A rotated key takes effect immediately — there is no positive cache, so a revoked key stops working on the next request rather than at the end of a TTL.
Read this page as Markdown:
/docs/authentication.md