Scopes, claims, and discovery
First-party scopes
Section titled “First-party scopes”Four scopes are advertised in discovery as scopes_supported:
| Scope | Effect |
|---|---|
openid | Issues an ID token alongside the access token. |
profile | Adds name and email to the ID token. |
work_credential | Permits issuing a verifiable Work Credential for the subject. |
offline_access | Issues a refresh token. |
There is no separate email scope. Requesting one has no effect — the email claim travels with
profile.
A scope is only granted if it is also in the client’s own allowed_scopes. Requesting more than the
client is registered for narrows silently to the intersection rather than failing, so check the
scope value in the token response rather than assuming you got what you asked for.
Tool scopes
Section titled “Tool scopes”Agent and MCP scopes are namespaced and matched by pattern rather than enumerated:
mcp:<name> tool:<name> a2a:<name>Names may contain letters, digits, _, ., -, and * — for example mcp:tools.invoke,
tool:search, tool:email.*.
A client may request a tool scope only if it appears in its own allowed_scopes, which is set when
the agent is registered through the management API. Dynamically registered and URL
clients cannot obtain tool scopes at all — that path is deliberately closed, because those clients
are anonymous.
Access token claims
Section titled “Access token claims”{ "iss": "https://acme.oauth.work", "sub": "usr_01J…", "aud": "https://tools.acme.com/mcp", "exp": 1750003600, "iat": 1750000000, "jti": "…", "client_id": "client_7f3a…", "scope": "openid profile", "org_id": "org_01J…", "roles": ["admin"], "actor_type": "user", "cnf": { "jkt": "…" }, "act": { "sub": "did:key:z6Mk…" }, "authorization_details": [ ]}| Claim | Present when |
|---|---|
aud | A resource indicator was bound to the grant |
org_id | The subject or client belongs to an organization |
roles | The subject has a role in that organization |
actor_type | Always for machine tokens: user, agent, or service |
cnf.jkt | The token is DPoP-bound |
act | The token came from token exchange — the delegation chain |
authorization_details | Rich authorization was requested |
ID token claims
Section titled “ID token claims”{ "iss": "https://acme.oauth.work", "sub": "usr_01J…", "aud": "client_7f3a…", "exp": 1750003600, "nonce": "…", "name": "Ada Lovelace", "email": "ada@acme.com", "org_id": "org_01J…", "roles": ["admin"], "auth_time": 1750000000, "amr": ["pwd", "totp"]}name and email require the profile scope. amr reports how the user actually authenticated —
values include otp, link, pwd, sso, and totp — and multiple entries mean multiple factors,
so ["pwd","totp"] is a password plus a second factor. Use amr and auth_time together to decide
whether a session is fresh and strong enough for a sensitive operation, rather than asking for
re-authentication unconditionally.
Resource indicators
Section titled “Resource indicators”RFC 8707. Pass resource at /authorize, /par, or /token to bind the token’s audience to a
specific service:
&resource=https://tools.acme.com/mcpThe value becomes aud — a single string for one resource, an array for several. resource may
repeat, and a value passed at /token must be a subset of what was bound at /authorize;
otherwise the request fails with invalid_target.
A resource server must check that aud names it. Without that check, a token minted for one
service is replayable at every other service that trusts the same issuer, and the indicator has
bought you nothing.
Discovery
Section titled “Discovery”| Document | Contents |
|---|---|
/.well-known/openid-configuration | OpenID Provider metadata |
/.well-known/oauth-authorization-server | Authorization server metadata (RFC 8414) |
/.well-known/oauth-protected-resource | Protected resource metadata (RFC 9728) |
/.well-known/jwks.json | Public signing keys |
/.well-known/did.json | The tenant’s did:web document |
/.well-known/openid-credential-issuer | Credential issuer metadata (OID4VCI) |
/.well-known/agent-card.json | A2A AgentCard (also at /.well-known/agent.json) |
/auth.md | The agent skill document, in Markdown |
All of these are served per host, so a tenant’s documents describe that tenant. The four
OAuth-related ones send CORS headers, because browser relying parties and MCP inspectors fetch them
cross-origin — as does /auth.md, which agents reach by following the agent_auth.skill URL out of
the authorization server metadata.
What the metadata advertises
Section titled “What the metadata advertises”{ "issuer": "https://acme.oauth.work", "grant_types_supported": [ "authorization_code", "client_credentials", "refresh_token", "urn:ietf:params:oauth:grant-type:token-exchange" ], "response_types_supported": ["code"], "code_challenge_methods_supported": ["S256"], "id_token_signing_alg_values_supported": ["EdDSA"], "token_endpoint_auth_methods_supported": [ "none", "client_secret_basic", "client_secret_post", "private_key_jwt" ], "token_endpoint_auth_signing_alg_values_supported": ["ES256", "EdDSA", "RS256"], "resource_indicators_supported": true, "authorization_response_iss_parameter_supported": true, "authorization_details_types_supported": ["mcp_tool"], "client_id_metadata_document_supported": true, "agent_auth": { "skill": "https://acme.oauth.work/auth.md", "identity_types_supported": [] }, "require_pushed_authorization_requests": false, "request_parameter_supported": false, "request_uri_parameter_supported": false}Two absences are deliberate. response_types_supported is ["code"] only — the implicit and hybrid
flows are not offered. And request objects are explicitly unsupported at both
request_parameter_supported and request_uri_parameter_supported; use PAR instead, which
achieves the same integrity without the client having to sign anything.
The authorization response also carries iss (RFC 9207). Check it: it is what stops a response from
one authorization server being replayed at another in a multi-issuer client.