Resources & Scopes
A resource is the thing AuthPlane issues tokens for. Every MCP server you protect, and every upstream provider you vend tokens for, is one row in authserver’s resources table. The backend_kind column tells AuthPlane what to do when a client requests a token for that resource.
Backend kinds
Section titled “Backend kinds”| Backend | What AuthPlane returns | When to use |
|---|---|---|
| Mint | AuthPlane-signed JWT (at+jwt) | Default for new MCP servers — they trust AuthPlane’s JWKS and verify offline. |
| Broker | The user’s upstream-provider access token (e.g. a GitHub gho_…) | You want the MCP server to call GitHub/Slack/Google as the user, using the user’s consent. |
| Fronted | AuthPlane hands off to a downstream resource server that mints its own token | You have an existing resource server with its own authorization model and can’t re-architect it. |
These aren’t mutually exclusive within a deployment — an MCP server might be a Mint resource for its own tools and also call out to a Broker resource (GitHub) on behalf of the user. Mint and Broker are covered in depth in Token Vault: Mint vs Broker.
The data model
Section titled “The data model”Three tables matter:
resources— one row per downstream system. Columns:slug,backend_kind,aud(audience URI),scopes(the catalog),policy(exchange allowlist),broker_provider_id(for Broker),fronting_link(for Fronted).broker_providers— registration of an upstream OAuth provider. One Broker resource references one provider viabroker_provider_id.consent_grants— per-(user, agent, resource) consent attestations. Required before AuthPlane will issue a token for any (user, client, resource) tuple.
For Broker resources, a fourth table — broker_grants — records the per-(user, broker_provider) upstream OAuth grant: the encrypted refresh-grant AuthPlane uses to mint fresh upstream access tokens.
Scope catalogs
Section titled “Scope catalogs”A scope is a string in the token’s scope claim. Scopes are declared on the resource:
resources: - slug: mcp-server-prod backend_kind: mint scopes: - { name: mcp:echo } - { name: mcp:query_database }Naming convention. Scope names are arbitrary strings — AuthPlane doesn’t parse them. The docs and examples use mcp:<tool_name> to match OAuth scope conventions (read:user, write:repo). Pick a convention and apply it consistently: the string you put in Resource.scopes is the same string you’ll write in @require_scopes(...) on the tool handler and the same string the client asks for on /oauth/token.
For Broker resources, scopes have an extra dimension — an upstream mapping from the AuthPlane-side scope name to the upstream provider’s scopes:
resources: - slug: github backend_kind: broker broker_provider_slug: github scopes: - { name: repo, upstream: [repo] } - { name: read:user, upstream: [read:user] }This indirection lets you publish a stable AuthPlane-side scope catalog even when the upstream provider’s scopes change.
Per-tool scopes: the four places one scope name appears
Section titled “Per-tool scopes: the four places one scope name appears”A single scope string (e.g. mcp:echo) shows up in four places. They must agree, and each side is enforced independently. Skim this once and you’ll never get a confusing 401 from a scope mismatch again.
| # | Where | Who declares it | What happens if it doesn’t match |
|---|---|---|---|
| 1 | Resource.scopes[].name in config or POST /admin/resources | The operator, once per resource | A scope not listed here is not issuable — AuthPlane strips it from token requests. |
| 2 | scope=... form param on POST /oauth/token | The OAuth client, per request | Asking for a scope the resource doesn’t declare → rejected with invalid_scope. |
| 3 | scope claim in the issued JWT | AuthPlane, automatically | This is what your server reads. It’s the intersection of what the client asked for and what the resource declares. |
| 4 | require_scopes("mcp:echo") on the tool handler (Python), RequireScope middleware (Go), requireScope (TypeScript) | The MCP server builder, per tool | A token missing the required scope → 403 insufficient_scope with the missing scope name in the body. |
Resource indicators (RFC 8707)
Section titled “Resource indicators (RFC 8707)”Clients name the target resource in their requests via the resource parameter, defined by RFC 8707:
POST /oauth/tokengrant_type=authorization_codecode=...resource=mcp-server-prodAuthPlane looks up resources.slug = 'mcp-server-prod', sets the aud claim, and dispatches to the right issuer (Mint vs Broker). For token exchange, the same resource parameter selects the target. The indicator can name the resource by URI or by slug.
Audience binding
Section titled “Audience binding”Once a token is minted, the aud claim names which resource(s) it’s for. Your MCP server MUST reject any token whose aud doesn’t include its own URI. Audience binding is what stops a token issued for resource A from being replayed against resource B.
Matching is exact-string membership: for multi-audience tokens (one client, several MCP servers), aud is an array, and the resource server accepts the token if and only if its own URI appears, as-is, anywhere in that array.
The three bounds (Broker only)
Section titled “The three bounds (Broker only)”When a token exchange targets a Broker resource, AuthPlane enforces three bounds before vending the upstream token:
- requested scopes ⊆
consent_grants.scopes(per-agent attestation) - ⊆
broker_grants.scopes_granted(per-provider grant) - The acting client must satisfy
resources.policy.exchange.allowed_client_ids(an empty list allows any consented client).
If any bound fails, AuthPlane returns a consent_required error with the appropriate consent_url and cause — see the failure-mode tables in Token Vault: Mint vs Broker.