Skip to content

Delegate authorize-time scope policy to the provider - #3222

Draft
maxisbey wants to merge 2 commits into
mainfrom
fix/authorize-scope-policy
Draft

Delegate authorize-time scope policy to the provider#3222
maxisbey wants to merge 2 commits into
mainfrom
fix/authorize-scope-policy

Conversation

@maxisbey

Copy link
Copy Markdown
Contributor

Supersedes #2301. Fixes #2216, fixes #977.

Motivation and Context

The SDK-hosted authorization server (create_auth_routes) rejected any /authorize request whose scope wasn't in the client's registered scope metadata, and read a registered scope=None as an empty allowlist. Three consequences:

RFC 7591 §2 makes registered scope self-asserted metadata the client "can use", not an allowlist the authorization server must enforce, and RFC 6749 §4.1.2.1's invalid_scope means "invalid, unknown, or malformed", not "outside this client's registration". The authorize handler now enforces only the server-wide ClientRegistrationOptions.valid_scopes (the list already advertised as scopes_supported and enforced at registration); per-client scope policy belongs in OAuthAuthorizationServerProvider.authorize() via AuthorizeError(error="invalid_scope"). An empty scope parameter is treated as omitted.

How Has This Been Tested?

./scripts/test (full suite, 100% coverage). New interaction requirement hosting:auth:as:authorize-scope; the two step-up interaction tests now register the client with only the narrow scope, so they prove the wider re-authorization is accepted end to end. Also driven manually over HTTP against a mounted authorization server: step-up-shaped request accepted, scope outside valid_scopes rejected with invalid_scope, empty scope= accepted, and a provider-raised invalid_scope reaching the redirect.

Breaking Changes

OAuthClientInformationFull.validate_scope() and mcp.shared.auth.InvalidScopeError are removed; both existed only to implement the deleted check. docs/migration.md shows the provider-side replacement.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Design note versus #2301: rather than deleting scope enforcement at /authorize outright, this keeps the server-wide ceiling (valid_scopes) applied to every client, which preserves the transitive registration→authorization ceiling for DCR deployments; only the per-client registered-scope allowlist is gone. The TypeScript SDK removed its equivalent check in modelcontextprotocol/typescript-sdk#983.

Thanks to @nik1097 for the report and to everyone who opened fixes for the scope=None case on #2216.

AI Disclaimer

maxisbey added 2 commits July 29, 2026 15:39
The SDK-hosted authorization server rejected any /authorize request
whose scope was not in the client's registered `scope` metadata, and
treated a client registered without a scope as allowed to request
nothing at all. That broke the spec's step-up flow, in which a client
answers a 403 insufficient_scope challenge by re-authorizing for the
union of its previous and challenged scopes without re-registering, and
it rejected every scoped request from scope-less registrations (#2216)
as well as an empty `scope=` parameter (#977).

The authorize handler now enforces only the server-wide scope set,
`ClientRegistrationOptions.valid_scopes` (the list already advertised
as `scopes_supported` and enforced at registration). A requested scope
outside it redirects back with `invalid_scope`; when it is unset, every
requested scope reaches `provider.authorize()`, which can reject a
client's request by raising `AuthorizeError(error="invalid_scope")`.
An empty scope parameter is treated as omitted.

`OAuthClientInformationFull.validate_scope()` and `InvalidScopeError`
are removed; both existed only to implement the deleted check.

Github-Issue: #2216
Spell out on OAuthAuthorizationServerProvider.authorize() that the
requested scopes have only been checked against the server-wide
valid_scopes and that per-client scope policy is enforced here, so the
contract lives on the interface operators implement rather than only
in the migration guide.

No-Verification-Needed: docstring-only change
@github-actions

Copy link
Copy Markdown
Contributor

📚 Documentation preview

Preview https://pr-3222.mcp-python-docs.pages.dev
Deployment https://e10118bc.mcp-python-docs.pages.dev
Commit b863e4c
Triggered by @maxisbey
Updated 2026-07-30 14:58:55 UTC

@keeltrace

This comment was marked as spam.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants