Authentication
Authenticate management requests with a Supabase admin session, and MCP tool calls or HTTP API requests with a scoped mcp_ API key.
Management API authentication
Every endpoint under /admin/mcp (servers, tools, agency access, API keys) requires a Supabase session JWT for an allowlisted admin account. Provide it as a bearer token.
Supabase session JWT for an allowlisted admin account. Format: Bearer <access_token>.
A missing token, an invalid/expired token, or a token for an account not on the admin allowlist returns 401 with { "error": "Unauthorized" }.
MCP server authentication
One mcp_ API key authenticates both MCP surfaces — the Settings Server your AI client connects to, and the upstream-tool gateway at POST /mcp on the worker host. Provide it as a bearer token or in the X-Api-Key header.
MCP API key as bearer token. Format: Bearer mcp_....
MCP API key. Alternative to the Authorization header.
The key must begin with mcp_. A missing, malformed, or deactivated key returns HTTP 401 with a JSON-RPC error (code -32600).
What a 401 tells you
The 401 carries a WWW-Authenticate challenge naming the credential. Two shapes, by whether the request sent one at all:
WWW-Authenticate: Bearer realm="mythic-mcp",
error_description="Send a Mythic mcp_ API key as a bearer token. This server does not use OAuth.",
error_uri="https://docs.mythicdata.io/mcp/settings-server"
WWW-Authenticate: Bearer realm="mythic-mcp", error="invalid_token",
error_description="The mcp_ API key is unknown, malformed, or deactivated.",
error_uri="https://docs.mythicdata.io/mcp/settings-server"
There is no OAuth
Neither server implements OAuth, and neither advertises it. Every discovery path a client probes — /.well-known/oauth-protected-resource, /.well-known/oauth-authorization-server, /.well-known/openid-configuration, each also under /mcp — answers:
{
"error": "oauth_not_supported",
"error_description": "This MCP server authenticates with a static Mythic mcp_ API key sent as Authorization: Bearer mcp_..., not OAuth. There is no authorization server to discover.",
"documentation_url": "https://docs.mythicdata.io/mcp/settings-server"
}
with HTTP 404. The challenge deliberately omits resource_metadata: pointing at protected-resource metadata with no authorization server behind it would tell a client to start a flow that cannot finish. Configure the key as a static header.
Scopes
An mcp_ key carries a scopes array that gates which tools it can call (only tools within the scopes are listed; calling any other tool by name answers insufficient_scope naming the scope to add) — and, when the key is sent as a Bearer to the HTTP API, which routes it may call. Scopes use <family>:<action> form, with * wildcards:
| Form | Example | Meaning |
|---|---|---|
family:read | people:read | Read operations in one family |
family:write | insights:write | Write operations in one family — implies family:read |
family:* | dashboards:* | Any action on one family |
* | * | Everything |
An empty scopes array grants nothing. Unrestricted access is the explicit ["*"]. Keys created without a scopes field default to ["*"].
Families
Families follow the plan features they belong to, so a plan and a key describe access in the same words. The families:
| Family | Grants | Actions |
|---|---|---|
people | Person profiles, identities, merges, merge warnings | read |
events | Event volumes, trends, breakdowns, property discovery, Explore queries, tracking coverage, Core Web Vitals | read |
sessions | Session analytics | read |
replays | Session replay timelines | read |
exceptions | Error tracking | read |
heatmaps | Click and scroll heatmaps | read |
bigquery_export | Export status and schema | read |
insights | Saved insights, run and preview | read, write |
dashboards | Dashboards | read, write |
canvases | Canvases | read, write |
segments | Segments, counts, membership | read, write |
constraints | Constraint finder and its benchmarks | read, write |
correlation | Correlation | read |
incrementality | Incrementality experiments and coefficients | read, write |
contracts | Event contracts and violation reports | read, write |
destinations | Destinations, credentials, deliveries | read, write |
settings | Tracking settings and the ingestion on/off switch | read, write |
links | Link redirects, click analytics, link webhooks, uptime | read, write |
tags / snippets / transformers | SDK configuration | read, write |
airbyte | Airbyte sync orchestration — agency-wide keys only | read, write |
alerts | The alert feed across every client, routing rules, Slack/webhook channels — agency-wide keys only | read, write |
agency | Client roster and cross-location rollups — agency-wide keys only | read |
extension | The Mythic Ads Manager browser extension's bootstrap: roster with ad-platform flags and ad-account to client resolution — agency-wide keys only. The extension's key holds extension:read + constraints:read | read |
Every tool's exact scope is in the Tool Reference, which is generated from the server's registry. list_clients and select_client need no scope on an agency-wide key.
Key issuance (POST/PATCH /builder/mcp/api-keys) validates every scope string against this catalog and answers 400 invalid_scope — listing the offending strings and every valid one — rather than storing a typo that silently grants nothing. An explicit empty scopes array is refused on both with 400 empty_scopes: omit scopes on create for unrestricted (["*"]), and set is_active: false to disable a key. The retired data:* and analysis:* families are rejected the same way; existing keys that held them were expanded to the new families.
A key with a null location is agency-wide (it selects a client per session, or names one per HTTP request); one bound to a location is client-scoped and can never reach an agency-grain operation, whatever its scopes say.
Gateway tools
Gateway (upstream) tools at POST /mcp require <scope_prefix>:call, where <scope_prefix> is the owning server's configured prefix. If the server has no scope prefix, the required scope is upstream:call.
Using an mcp_ key over HTTP
The same mcp_ key is accepted as a Bearer on every /client/v1 and /builder route. It is the scoped credential of the HTTP API: an agency key (ak_) is read-write across the whole agency and a location secret (sk_) is read-only for one location, so an mcp_ key is how you hand an integration exactly one capability.
curl "https://mythic-analytics.gulp.workers.dev/client/v1/data/people?location_id=loc_abc" \
-H "Authorization: Bearer mcp_..."
Each route requires the scope of the tool that proxies it — the Proxies column of the Tool Reference is the mapping. Rules:
- Location binding works as for
ak_/sk_. A client-scoped key carries its own location; an agency-wide key names one withlocation_idorX-Location-Idon location-grain routes (400 location_requiredotherwise). - Writes need the
:writescope, whichever key shape holds it — unlikesk_, a client-scopedmcp_key can write. - Agency-grain routes (
/client/v1/agency/*,/client/v1/airbyte/*,/client/v1/extension/*,GET /builder/locations) refuse client-scoped keys with403 agency_key_required. - Routes with no tool are closed to
mcp_keys unless the route names a scope of its own (GET /client/v1/extension/meandPOST /client/v1/extension/issuestakeextension:read). Otherwise even an unrestricted key is refused: the OAuth browser legs, the embed SPA's internals, and above all key issuance under/builder/mcpanswer403 not_available_to_scoped_keys. Minting or rescoping a key is the privilege boundary and stays withak_. - Some routes need two scopes. A person's or a session's logs are both that family's data and logs:
GET /client/v1/data/people/{personId}/logsneedspeople:readandlogs:read,GET /client/v1/data/replays/{sessionId}/logsneedsreplays:readandlogs:read(the Tool Reference lists both). The session timeline staysreplays:read; itsinclude=logsmerges logs only for a key that also holdslogs:read, and otherwise answers without them andmeta.logs: "forbidden". - A missing scope answers
403 insufficient_scopewith therequiredscope named. - Plan entitlements and per-key rate limits apply as on any other key (
rate_limit_per_minute, default 60).
Never expose admin session tokens or mcp_ API keys in client-side JavaScript, public repositories, or logs. Both are server-side credentials. The raw mcp_ key is shown only once, at creation.