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:
- Quick-pick Select all ticks every safe built-in + proxy tool.
- 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.
- 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.
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.