AGT-32Recommended
Name every missing scope in one challenge
When a token lacks a scope a call needs, answer 403 with error="insufficient_scope", every scope the call needs in scope, and the resource_metadata address.
Spec version
MCP 2026-07-28
Review by
2027-04-30
How to do it
An agent can only ask its person for more access when it knows which scopes to ask for. A bare 403 leaves it guessing. A challenge that names one missing scope at a time sends the person through consent again and again. Work out every scope the call needs from its arguments, then list them all in one WWW-Authenticate header. Treat a broader scope as covering the narrower ones it implies. Keep offline_access out of the list, because refresh tokens are not a resource requirement. The 401 for a missing token is in describe how to get access. The person then sees the agent named on the consent screen.
Examples
HTTP example
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope", scope="files:read files:write", resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource"
JavaScript example
const RESOURCE_METADATA = 'https://mcp.example.com/.well-known/oauth-protected-resource';
export function scopeChallenge(neededScopes, grantedScopes) {
if (neededScopes.every(scope => grantedScopes.includes(scope))) return null;
return { status: 403, header: `Bearer error="insufficient_scope", scope="${neededScopes.join(' ')}", resource_metadata="${RESOURCE_METADATA}"` };
}
Python example
RESOURCE_METADATA = "https://mcp.example.com/.well-known/oauth-protected-resource"
def scope_challenge(needed_scopes: list[str], granted_scopes: set[str]) -> dict | None:
if all(scope in granted_scopes for scope in needed_scopes):
return None
scopes = " ".join(needed_scopes)
return {"status": 403, "header": f'Bearer error="insufficient_scope", scope="{scopes}", resource_metadata="{RESOURCE_METADATA}"'}
How to check
Call a tool that needs files:read files:write with a token that holds only files:read. The answer is 403 with error="insufficient_scope", a scope value holding both scopes, and a resource_metadata URL that answers 200.
Sources
Related rules
- WEB-173 Publish your sign-in server settings in Website
- AGT-8 Name the agent on the consent screen in Agents as users
- AGT-37 Publish protected resource metadata for every MCP server that needs sign-in in Agents as users
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/agents-as-users/delegated-access-and-consent/scope-challenge">Agent Wranglers agents as users playbook</a>. In code you write, add a comment next to it with the same link.