MCP server

Authentication

Three methods, one resulting identity.

OAuth 2.1 (interactive clients). Your MCP host discovers the authorization server from /.well-known/oauth-protected-resource and completes an Authorization Code + PKCE flow with WorkOS. Tokens carry your role, scopes and workspace allowlist.

Personal access token (people connecting an assistant). Created in the studio at /o/<org>/connect — pick the assistant, tick up to five client workspaces, and copy the one-time apt_… secret into your assistant's config. Sent as Authorization: Bearer apt_…. See connect-your-assistant.md for the walkthrough. Two differences from an API key:

  • Scopes come from your role, never from a picker. Every connection — personal access token or OAuth alike — carries the read set (org:read, workspace:read, brand:read, prompt:read, answer:read, usage:read, run:read) plus whatever your current membership role adds, re-derived from the live membership on every call (never from the token itself):

    RoleGrant
    organization_owner, organization_adminreads + content writes (brand:write, prompt:write, report:write, workspace:write) + run (run:execute, run:cancel)
    agency_analystreads + content writes — no run scopes
    client_admin, client_viewerreads only

    The write and run rows are switched on per deployment. They apply only where the operator has set MCP_CONNECTION_WRITES_ENABLED=true on the MCP service. Where it is off (the default), every connection carries the read set only, whatever the role, and write and run tools are not listed. The setting is re-read on every call, so turning it on or off reaches existing connections without reconnecting. get_account_context and tools/list always show what your connection can do right now.

    Never granted over MCP, to any role: member:manage, billing:manage, org:manage, webhook:manage, workspace:archive, report:approve, report:send. Those stay console-only.

    Spending the organization's allowance is owner/admin-only regardless of scope. run_question_now, retry_failed_executions, onboard_brand, generate_buyer_questions, discover_keywords, get_keyword_volumes and expand_topics refuse any connection whose role is not organization_owner/organization_admin, even one that otherwise holds run:execute or prompt:write — an API key's own stored scopes are unaffected by this rule.

    A destructive write still requires request_approval's two-call preview/confirm exchange regardless of role (see writes.md); the role table above governs whether a tool is reachable at all, not whether an approval is still required once it is.

  • The workspace allowlist is chosen at creation, capped at 5, and always narrower than or equal to your own. The connect page offers every workspace of your organization that your own membership lets you see; picking one you cannot see yourself (outside your own memberships.workspace_allowlist) is refused, never silently widened. Effective visibility is the intersection of what you picked and what your own membership already allows — the same "never wider than the creator" rule an API key's own workspace scoping follows.

Like an API key, the plaintext secret (apt_<32 hex>) is shown exactly once, at creation, and stored only as a SHA-256 hash thereafter — losing it means creating a new token and disconnecting the old one, not recovering it.

API key (agents, CI, servers). Create a key in Settings → API keys and send it as Authorization: Bearer awk_…. Keys are shown once and stored only as a hash. A key has an explicit scope list; a key with no scopes can see the documentation tools and nothing else.

Scopes are granular: answer:read, prompt:write, run:execute, billing:manage and so on. Authorization is the intersection of your plan's entitlements, your role, your token's scopes and its workspace allowlist — a scope never widens a role.

← All docs