Skip to content

Commit e80c285

Browse files
authored
Merge pull request #22 from sacha-development-stuff/codex/resolve-merge-conflicts-in-sdk-fork
Resolve OAuth auth flow merge conflicts
2 parents f9589d6 + 5896e17 commit e80c285

File tree

2 files changed

+45
-63
lines changed

2 files changed

+45
-63
lines changed

src/mcp/client/auth.py

Lines changed: 29 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -549,18 +549,6 @@ def _add_auth_header(self, request: httpx.Request) -> None:
549549
"""Add authorization header to request if we have valid tokens."""
550550
if self.context.current_tokens and self.context.current_tokens.access_token:
551551
request.headers["Authorization"] = f"Bearer {self.context.current_tokens.access_token}"
552-
553-
#<<<<<<< main
554-
#=======
555-
def _create_oauth_metadata_request(self, url: str) -> httpx.Request:
556-
return httpx.Request("GET", url, headers={MCP_PROTOCOL_VERSION: LATEST_PROTOCOL_VERSION})
557-
558-
async def _handle_oauth_metadata_response(self, response: httpx.Response) -> None:
559-
content = await response.aread()
560-
metadata = OAuthMetadata.model_validate_json(content)
561-
self.context.oauth_metadata = metadata
562-
563-
#>>>>>>> main
564552
async def async_auth_flow(self, request: httpx.Request) -> AsyncGenerator[httpx.Request, httpx.Response]:
565553
"""HTTPX auth flow integration."""
566554
async with self.context.lock:
@@ -593,16 +581,13 @@ async def async_auth_flow(self, request: httpx.Request) -> AsyncGenerator[httpx.
593581
discovery_response = yield discovery_request
594582
await self._handle_protected_resource_response(discovery_response)
595583

596-
#<<<<<<< main
597-
# Step 2: Discover OAuth metadata (with fallback for legacy servers)
598-
discovery_urls = self._get_discovery_urls(self.context.auth_server_url or self.context.server_url)
599-
#=======
600584
# Step 2: Apply scope selection strategy
601585
self._select_scopes(response)
602586

603587
# Step 3: Discover OAuth metadata (with fallback for legacy servers)
604-
discovery_urls = self._get_discovery_urls()
605-
#>>>>>>> main
588+
discovery_urls = self._get_discovery_urls(
589+
self.context.auth_server_url or self.context.server_url
590+
)
606591
for url in discovery_urls:
607592
oauth_metadata_request = self._create_oauth_metadata_request(url)
608593
oauth_metadata_response = yield oauth_metadata_request
@@ -617,13 +602,8 @@ async def async_auth_flow(self, request: httpx.Request) -> AsyncGenerator[httpx.
617602
elif oauth_metadata_response.status_code < 400 or oauth_metadata_response.status_code >= 500:
618603
break # Non-4XX error, stop trying
619604

620-
#<<<<<<< main
621-
# Step 3: Register client if needed
622-
registration_request = self._create_registration_request(self._metadata)
623-
#=======
624605
# Step 4: Register client if needed
625-
registration_request = await self._register_client()
626-
#>>>>>>> main
606+
registration_request = self._create_registration_request(self._metadata)
627607
if registration_request:
628608
registration_response = yield registration_request
629609
await self._handle_registration_response(registration_response)
@@ -643,7 +623,31 @@ async def async_auth_flow(self, request: httpx.Request) -> AsyncGenerator[httpx.
643623
# Retry with new tokens
644624
self._add_auth_header(request)
645625
yield request
646-
#<<<<<<< main
626+
627+
elif response.status_code == 403:
628+
# Step 1: Extract error field from WWW-Authenticate header
629+
error = self._extract_field_from_www_auth(response, "error")
630+
631+
# Step 2: Check if we need to step-up authorization
632+
if error == "insufficient_scope":
633+
try:
634+
# Step 2a: Update the required scopes
635+
self._select_scopes(response)
636+
637+
# Step 2b: Perform (re-)authorization
638+
auth_code, code_verifier = await self._perform_authorization()
639+
640+
# Step 2c: Exchange authorization code for tokens
641+
token_request = await self._exchange_token(auth_code, code_verifier)
642+
token_response = yield token_request
643+
await self._handle_token_response(token_response)
644+
except Exception:
645+
logger.exception("OAuth flow error")
646+
raise
647+
648+
# Retry with new tokens
649+
self._add_auth_header(request)
650+
yield request
647651

648652

649653
class ClientCredentialsProvider(BaseOAuthProvider):
@@ -919,29 +923,3 @@ async def async_auth_flow(self, request: httpx.Request) -> AsyncGenerator[httpx.
919923
response = yield request
920924
if response.status_code == 401:
921925
self._current_tokens = None
922-
#=======
923-
elif response.status_code == 403:
924-
# Step 1: Extract error field from WWW-Authenticate header
925-
error = self._extract_field_from_www_auth(response, "error")
926-
927-
# Step 2: Check if we need to step-up authorization
928-
if error == "insufficient_scope":
929-
try:
930-
# Step 2a: Update the required scopes
931-
self._select_scopes(response)
932-
933-
# Step 2b: Perform (re-)authorization
934-
auth_code, code_verifier = await self._perform_authorization()
935-
936-
# Step 2c: Exchange authorization code for tokens
937-
token_request = await self._exchange_token(auth_code, code_verifier)
938-
token_response = yield token_request
939-
await self._handle_token_response(token_response)
940-
except Exception:
941-
logger.exception("OAuth flow error")
942-
raise
943-
944-
# Retry with new tokens
945-
self._add_auth_header(request)
946-
yield request
947-
#>>>>>>> main

tests/client/test_auth.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ async def callback_handler() -> tuple[str, str | None]:
9494

9595

9696
@pytest.fixture
97-
#<<<<<<< main
9897
def client_credentials_metadata():
9998
return OAuthClientMetadata(
10099
redirect_uris=[AnyHttpUrl("http://localhost:3000/callback")],
@@ -103,7 +102,10 @@ def client_credentials_metadata():
103102
response_types=["code"],
104103
scope="read write",
105104
token_endpoint_auth_method="client_secret_post",
106-
#=======
105+
)
106+
107+
108+
@pytest.fixture
107109
def prm_metadata_response():
108110
"""PRM metadata response with scopes."""
109111
return httpx.Response(
@@ -113,12 +115,10 @@ def prm_metadata_response():
113115
b'"authorization_servers": ["https://auth.example.com"], '
114116
b'"scopes_supported": ["resource:read", "resource:write"]}'
115117
),
116-
#>>>>>>> main
117118
)
118119

119120

120121
@pytest.fixture
121-
#<<<<<<< main
122122
def oauth_metadata():
123123
return OAuthMetadata(
124124
issuer=AnyHttpUrl("https://auth.example.com"),
@@ -129,7 +129,10 @@ def oauth_metadata():
129129
response_types_supported=["code"],
130130
grant_types_supported=["authorization_code", "refresh_token", "client_credentials"],
131131
code_challenge_methods_supported=["S256"],
132-
#=======
132+
)
133+
134+
135+
@pytest.fixture
133136
def prm_metadata_without_scopes_response():
134137
"""PRM metadata response without scopes."""
135138
return httpx.Response(
@@ -139,12 +142,10 @@ def prm_metadata_without_scopes_response():
139142
b'"authorization_servers": ["https://auth.example.com"], '
140143
b'"scopes_supported": null}'
141144
),
142-
#>>>>>>> main
143145
)
144146

145147

146148
@pytest.fixture
147-
#<<<<<<< main
148149
def oauth_client_info():
149150
return OAuthClientInformationFull(
150151
client_id="test_client_id",
@@ -154,19 +155,20 @@ def oauth_client_info():
154155
grant_types=["authorization_code", "refresh_token"],
155156
response_types=["code"],
156157
scope="read write",
157-
#=======
158+
)
159+
160+
161+
@pytest.fixture
158162
def init_response_with_www_auth_scope():
159163
"""Initial 401 response with WWW-Authenticate header containing scope."""
160164
return httpx.Response(
161165
401,
162166
headers={"WWW-Authenticate": 'Bearer scope="special:scope from:www-authenticate"'},
163167
request=httpx.Request("GET", "https://api.example.com/test"),
164-
#>>>>>>> main
165168
)
166169

167170

168171
@pytest.fixture
169-
#<<<<<<< main
170172
def oauth_token():
171173
return OAuthToken(
172174
access_token="test_access_token",
@@ -197,14 +199,16 @@ async def token_exchange_provider(
197199
client_metadata=client_credentials_metadata,
198200
storage=mock_storage,
199201
subject_token_supplier=lambda: asyncio.sleep(0, result="user_token"),
200-
#=======
202+
)
203+
204+
205+
@pytest.fixture
201206
def init_response_without_www_auth_scope():
202207
"""Initial 401 response without WWW-Authenticate scope."""
203208
return httpx.Response(
204209
401,
205210
headers={},
206211
request=httpx.Request("GET", "https://api.example.com/test"),
207-
#>>>>>>> main
208212
)
209213

210214

0 commit comments

Comments
 (0)