Tokens & scoping
The MCP endpoint accepts two auth styles. Both end up at the same scope check on the server — the difference is purely in how Claude obtains the bearer.
OAuth tokens (preferred)
Section titled “OAuth tokens (preferred)”Issued by the OAuth Custom Connector flow. Stored only in Claude Desktop;
the worker keeps a hash plus rotation metadata. Format: oat_<random>.
| Attribute | Value |
|---|---|
| Lifetime | Access token 1 h, refresh token 30 d, rotated on every refresh |
| Granted scope | Always mcp:full — full surface visible to the user |
| Per-site scope | Bound to the user’s user_sites whitelist (DB-side, not in the token) |
| User binding | Linked to a real user account (oauth_access_tokens.user_id) |
| Refresh strategy | Automatic — Claude refreshes silently |
| Revoke | Click Remove in Claude → calls /oauth/revoke. Or master-admin can wipe rows in oauth_access_tokens table |
| Audit trail | Every tools/call records actor_user_id from the access token |
Get one: Add SEO Navigator as a Custom Connector in Claude Desktop — see Quick start.
Bearer tokens (legacy / fallback)
Section titled “Bearer tokens (legacy / fallback)”Plaintext static strings minted in the admin dashboard. Format:
sn_prod_<32 hex>. Used by the mcp-remote bridge when OAuth isn’t an
option, or by CLI/CI workflows.
| Attribute | Value |
|---|---|
| Lifetime | None (live until revoked) |
| Granted scope | Explicit per-token allowed_sites + allowed_tools arrays |
| Per-site scope | Whatever the creator ticked when minting |
| User binding | tokens.owner_user_id — NULL means super-admin-issued |
| Refresh strategy | None — recreate manually |
| Revoke | /admin/tokens → red Revoke button |
| Audit trail | actor_user_id set from tokens.owner_user_id |
Mint a bearer token
Section titled “Mint a bearer token”- Open
/admin/tokensand click + Create token. - Name: descriptive (eg “CI runner”, “blog writer staging”).
- Allowed sites:
- Super-admin: pick specific sites or
*for all. - Regular user: only your own sites are listed. Pick
*to expand to everything you own at this moment (locks at create time — later grants don’t widen the token).
- Super-admin: pick specific sites or
- Allowed tools:
- The built-in list is fetched from the live registry (
GET /admin/api/tools), so newly shipped tools always appear; read-only (T0) tools carry a green read badge. - Quick-pick Select all ticks every safe built-in + proxy tool.
- Quick-pick Read-only ticks only risk-tier-0 built-ins plus proxy tools
whose name is a read verb (
get/list/search/fetch/check) — the fastest way to mint a reporting-only token. - Quick-pick All except destructive filters out proxy
delete-*,publish-*tools. - Quick-pick Built-in only skips the wp-mcp-adapter proxy surface.
- Super-admin-only tools (eg
delete_astro_route) are hidden from regular users entirely.
- The built-in list is fetched from the live registry (
- Max risk (super-admin): cap the token to a risk tier — eg read-only (T0) for an analytics dashboard, or drafts-only (T1) for a content bot. Leave blank for no cap.
- Skip write-confirm (super-admin): tick only for trusted automation tokens that should bypass the write-confirm gate. Off by default; regular-user tokens can never set it.
- Create. Copy the plaintext — it’s shown once.
If you lose the plaintext, revoke and create a new one. The worker only stores SHA-256 of the bytes.
Edit a token’s permissions
Section titled “Edit a token’s permissions”You can change a token’s allowed_sites, allowed_tools, max_risk, and
(super-admin) skip_write_confirm without re-issuing the secret — click
Edit on a row in /admin/tokens. The plaintext stays valid; only the scope
changes. Regular users may only edit their own tokens, can only grant sites they
own, and cannot add super-admin-only tools or flip skip_write_confirm.
This is the safe way to widen or tighten a long-lived CI/automation token: no rotation, no redeploying the secret to your runner.
Risk tiers & the write-confirm gate
Section titled “Risk tiers & the write-confirm gate”Independently of which tools a token lists, every tool also carries a risk tier that two account-safety controls read:
| Tier | Meaning |
|---|---|
| T0 | read-only — no state change |
| T1 | reversible content / draft write (never publishes or deletes) |
| T2 | external write (CRM / Drive) — also super-admin flag-gated |
| T3 | production publish / push — also flag-gated |
| T4 | destructive / admin — super-admin only |
Write-confirm gate. Any tool at tier ≥ T1 must be called with confirm: true. Without it the tool returns a needs_confirmation preview and changes
nothing — a guard against a write being triggered by mistake. A token with
skip_write_confirm bypasses the gate (for trusted automation only).
Per-account risk cap. A max_risk ceiling on a token (or on the owning
user) refuses any call above that tier before it runs — the refusal is audited
as risk_capped. If a token leaks, a low cap contains the blast radius: a
max_risk = 0 token can only ever read, a max_risk = 1 token can only ever
touch drafts.
Dynamic proxy tools (GoHighLevel, wp-mcp-adapter) are tiered by action verb — a write counts as T2, a read as T0. The full per-tool tier table is in the tool reference.
OAuth vs bearer — when to pick which
Section titled “OAuth vs bearer — when to pick which”| Use case | Pick |
|---|---|
| Claude Desktop on a personal laptop | OAuth |
| Claude Desktop on a teammate’s machine — handover-friendly | OAuth (per-user token, not shared) |
| Old Claude Desktop without Custom Connector UI | Bearer + mcp-remote |
| CI runner posting to the MCP from CLI | Bearer (long-lived, no browser) |
Test fixtures or curl exploration | Bearer |
| Multi-tenant ops where each team has its own user | OAuth (cleaner audit) |
You can have BOTH on the same worker for the same user — the OAuth token
operates on the user’s user_sites, while a bearer token mints whatever scope
the user explicitly picked. Each leaves a separate audit trail.
Per-site scope and user_sites
Section titled “Per-site scope and user_sites”Sites are NOT pinned to bearer tokens by ID alone — they’re filtered against
the calling user’s user_sites whitelist on every request:
- OAuth: token has
user_id. Tool calls intersect that user’s sites with the requestedsite_id. Out-of-scope → 403E_SCOPE_DENIED. - Bearer: token has explicit
allowed_sites. If owner is a regular user, the worker additionally intersects with their currentuser_sites. So removing a site from a user’s grant also locks down their bearer tokens.
This means a super-admin can revoke a user’s access by removing site grants in
/admin/users — no need to revoke individual tokens.
Hard rule
Section titled “Hard rule”No token, of either type, can publish-now or delete a WordPress / Duda
post. The check happens at the tool registration layer — those tools simply
don’t exist on the server. A token with allowed_tools: ["*"] still cannot
call delete_post because there’s no delete_post to call.
The one documented exception — delete_astro_route — is super-admin-only at
runtime regardless of the token’s allowed_tools value.
Audit trail
Section titled “Audit trail”Every successful or failed tool call writes a row in audit_log:
ts INTEGER -- unix msactor_user_id TEXT -- from OAuth user_id or token owner_user_idtoken_id TEXTsite_id TEXTtool TEXTstatus TEXT -- ok | denied | errorduration_ms INTEGERargs_json TEXT -- truncated to 1KBerror TEXT -- when status != okvia TEXT -- "default" | "wp-mcp-adapter"Browse and filter at /admin/audit. Regular users see only their own rows;
super-admin sees everything.