You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Reject non-HTTPS, non-loopback redirect URIs at client registration
Two fixes to the optional bundled OAuth authorization server (the
`auth_server_provider=` path).
The registration endpoint accepted any well-formed URL as a
`redirect_uris` entry: cleartext `http://` on a non-loopback host,
`javascript:`, `data:`, and URIs carrying a fragment all registered
successfully. The MCP authorization specification's Communication
Security section requires every redirect URI to be either localhost or
HTTPS, and OAuth 2.1 section 2.3 forbids a fragment component. Such an
entry is now rejected with `400 invalid_client_metadata`. Loopback is
exactly the three forms OAuth 2.1 section 8.4.2 names (`localhost`,
`127.0.0.1`, `[::1]`), on any port; query strings remain permitted.
This also rejects RFC 8252 private-use schemes such as
`com.example.app:/callback`: MCP restricts redirect URIs to HTTPS or
loopback, with no carve-out for native apps.
The rule lives on the request model: `RegistrationRequest`, until now a
dead alias of `OAuthClientMetadata`, becomes a real subclass with a
`redirect_uris` field validator, so a forbidden URI fails parsing and
takes the handler's existing `invalid_client_metadata` arm rather than
needing a post-parse check. `OAuthClientMetadata` itself is unchanged:
the client also serializes it when registering against third-party
authorization servers whose redirect-URI policies the SDK does not own.
The loopback host set moves to a single `LOOPBACK_HOSTS` constant in
`mcp.server.auth.provider`, shared with `validate_issuer_url`, which
previously inlined the same tuple.
Separately, the token endpoint now answers an authorization-code
exchange whose `redirect_uri` does not match the one used at
`/authorize` with `error=invalid_grant` instead of `invalid_request`.
RFC 6749 section 5.2 assigns this case to `invalid_grant` ("does not
match the redirection URI used in the authorization request"), and the
handler's other authorization-code failures already use it. The
exchange was already rejected with HTTP 400; only the `error` field
changes.
Update the affected tests and the interaction-requirement entries, and
add a migration note.
Closes#2629
Copy file name to clipboardExpand all lines: docs/migration.md
+8Lines changed: 8 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1420,6 +1420,14 @@ Leaving `resource_server_url=None` continues to disable the check entirely (ther
1420
1420
1421
1421
`RefreshToken` gains an optional `resource` field so an `OAuthAuthorizationServerProvider` can propagate the original grant's audience binding through `exchange_refresh_token`; without it a refreshed access token would carry no audience and be rejected. `BearerAuthBackend.__init__` gains a keyword-only `resource_server_url: AnyHttpUrl | None = None`, wired automatically from `AuthSettings.enforced_audience`; `None` (the default, and what the SDK passes when `verifier_validates_audience` is set) means no audience is enforced.
Two fixes to the optional bundled OAuth authorization server (the `auth_server_provider=` path).
1426
+
1427
+
The token endpoint now answers an authorization-code exchange whose `redirect_uri` does not match the one used at `/authorize` with `error=invalid_grant` instead of `error=invalid_request`. RFC 6749 §5.2 assigns this case to `invalid_grant` ("does not match the redirection URI used in the authorization request"). The exchange was already rejected with HTTP 400; only the `error` field changes.
1428
+
1429
+
The registration endpoint now rejects a `redirect_uris` entry that is neither HTTPS nor a loopback host (`localhost`, `127.0.0.1`, or `[::1]`) with `400 invalid_client_metadata`. Previously any well-formed URL — including cleartext `http://` on a non-loopback host, `javascript:`, and `data:` — was accepted and stored. The MCP authorization specification's Communication Security section requires every redirect URI to be either `localhost` or HTTPS; the SDK accepts the three loopback forms OAuth 2.1 §8.4.2 names. Local development against `http://localhost:*`, `http://127.0.0.1:*`, or `http://[::1]:*` is unaffected. Note that this also rejects RFC 8252 private-use URI schemes (such as `com.example.app:/callback`): MCP restricts redirect URIs to HTTPS or loopback, which is stricter than vanilla OAuth allows for native apps. A redirect URI carrying a fragment component is also rejected (OAuth 2.1 §2.3). Query strings remain permitted.
1430
+
1423
1431
### Lowlevel `Server`: `subscribe` capability now correctly reported
1424
1432
1425
1433
Previously, the lowlevel `Server` hardcoded `subscribe=False` in resource capabilities even when a `subscribe_resource()` handler was registered. The `subscribe` capability is now dynamically set to `True` when an `on_subscribe_resource` handler is provided. Clients that previously didn't see `subscribe: true` in capabilities will now see it when a handler is registered, which may change client behavior.
0 commit comments