|
35 | 35 | handle_token_response_scopes, |
36 | 36 | is_valid_client_metadata_url, |
37 | 37 | should_use_client_metadata_url, |
| 38 | + union_scopes, |
38 | 39 | ) |
39 | 40 | from mcp.client.streamable_http import MCP_PROTOCOL_VERSION |
40 | 41 | from mcp.shared.auth import ( |
@@ -624,20 +625,25 @@ async def async_auth_flow(self, request: httpx.Request) -> AsyncGenerator[httpx. |
624 | 625 | error = extract_field_from_www_auth(response, "error") |
625 | 626 |
|
626 | 627 | # Step 2: Check if we need to step-up authorization |
627 | | - if error == "insufficient_scope": # pragma: no branch |
| 628 | + if error == "insufficient_scope": |
628 | 629 | try: |
629 | | - # Step 2a: Update the required scopes |
630 | | - self.context.client_metadata.scope = get_client_metadata_scopes( |
| 630 | + # Step 2a: Union previously requested scopes with the |
| 631 | + # step-up challenge so prior grants survive (SEP-2350). |
| 632 | + challenge_scopes = get_client_metadata_scopes( |
631 | 633 | extract_scope_from_www_auth(response), |
632 | 634 | self.context.protected_resource_metadata, |
633 | 635 | self.context.oauth_metadata, |
634 | 636 | self.context.client_metadata.grant_types, |
635 | 637 | ) |
| 638 | + self.context.client_metadata.scope = union_scopes( |
| 639 | + self.context.client_metadata.scope, |
| 640 | + challenge_scopes, |
| 641 | + ) |
636 | 642 |
|
637 | 643 | # Step 2b: Perform (re-)authorization and token exchange |
638 | 644 | token_response = yield await self._perform_authorization() |
639 | 645 | await self._handle_token_response(token_response) |
640 | | - except Exception: # pragma: no cover |
| 646 | + except Exception: |
641 | 647 | logger.exception("OAuth flow error") |
642 | 648 | raise |
643 | 649 |
|
|
0 commit comments