Skip to content

Commit fc80795

Browse files
committed
fix(oauth): keep the auth flow under the complexity limit
1 parent 742290b commit fc80795

1 file changed

Lines changed: 13 additions & 9 deletions

File tree

‎src/mcp/client/auth/oauth2.py‎

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,16 @@ def _select_authorization_server(self, advertised: list[str]) -> str:
603603
"""Which of the servers listed in protected resource metadata to use: the first (the list is never empty)."""
604604
return advertised[0]
605605

606+
def _needs_token_refresh(self) -> bool:
607+
return (not self.context.is_token_valid()) and self.context.can_refresh_token()
608+
609+
def _unauthorized_token_changed(self, response: httpx2.Response, sent_access_token: str | None) -> bool:
610+
"""True when a 401 is stale because another request already replaced the access token."""
611+
if response.status_code != 401:
612+
return False
613+
current = self.context.current_tokens.access_token if self.context.current_tokens else None
614+
return current is not None and current != sent_access_token
615+
606616
def _expected_issuer(self) -> str:
607617
"""The issuer that authorization server metadata and client credentials must belong to: the
608618
PRM-advertised server, or on the legacy no-PRM path the resource server's origin, which is what
@@ -626,14 +636,13 @@ async def _auth_flow(self, request: httpx2.Request) -> AsyncGenerator[httpx2.Req
626636

627637
# Capture protocol version from request headers
628638
self.context.protocol_version = request.headers.get(MCP_PROTOCOL_VERSION_HEADER)
629-
if not self.context.is_token_valid() and self.context.can_refresh_token():
630-
needs_refresh = True
639+
needs_refresh = self._needs_token_refresh()
631640

632641
if needs_refresh:
633642
async with self.context.refresh_lock:
634643
refresh_request = None
635644
async with self.context.lock:
636-
if not self.context.is_token_valid() and self.context.can_refresh_token():
645+
if self._needs_token_refresh():
637646
refresh_request = await self._refresh_token()
638647
if refresh_request is not None:
639648
refresh_response = yield refresh_request
@@ -654,12 +663,7 @@ async def _auth_flow(self, request: httpx2.Request) -> AsyncGenerator[httpx2.Req
654663

655664
if response.status_code == 401 or step_up:
656665
async with self.context.lock:
657-
current_access_token = self.context.current_tokens.access_token if self.context.current_tokens else None
658-
if (
659-
response.status_code == 401
660-
and current_access_token is not None
661-
and current_access_token != sent_access_token
662-
):
666+
if self._unauthorized_token_changed(response, sent_access_token):
663667
# Another request refreshed or re-authorized while this one was in flight.
664668
self._add_auth_header(request)
665669
yield request

0 commit comments

Comments
 (0)