@@ -829,6 +829,119 @@ async def post(self, url: str, *, data: dict[str, str], headers: dict[str, str])
829829 assert provider .client_metadata .scope is None
830830
831831
832+ @pytest .mark .anyio
833+ async def test_token_exchange_request_token_stops_on_non_authoritative_response (
834+ monkeypatch : pytest .MonkeyPatch ,
835+ ) -> None :
836+ storage = InMemoryStorage ()
837+ client_metadata = OAuthClientMetadata (redirect_uris = _redirect_uris (), scope = "alpha" )
838+
839+ provider = TokenExchangeProvider (
840+ "https://api.example.com/service" ,
841+ client_metadata ,
842+ storage ,
843+ subject_token_supplier = AsyncMock (return_value = "subject-token" ),
844+ )
845+
846+ metadata_responses = [
847+ _make_response (204 ),
848+ _make_response (200 , json_data = _metadata_json ()),
849+ ]
850+ registration_response = _make_response (200 , json_data = _registration_json ())
851+ token_response = _make_response (200 , json_data = _token_json ("alpha" ))
852+
853+ class RecordingAsyncClient (DummyAsyncClient ):
854+ def __init__ (self , * args : object , ** kwargs : object ) -> None :
855+ super ().__init__ (* args , ** kwargs )
856+ self .send_calls = 0
857+
858+ async def send (self , request : httpx .Request ) -> httpx .Response :
859+ self .send_calls += 1
860+ return await super ().send (request )
861+
862+ recording_client = RecordingAsyncClient (send_responses = list (metadata_responses ))
863+ clients = [
864+ recording_client ,
865+ DummyAsyncClient (send_responses = [registration_response ]),
866+ DummyAsyncClient (post_responses = [token_response ]),
867+ ]
868+
869+ monkeypatch .setattr ("mcp.client.auth.oauth2.httpx.AsyncClient" , AsyncClientFactory (clients ))
870+
871+ await provider ._request_token ()
872+
873+ assert recording_client .send_calls == 1
874+ assert storage .tokens is not None
875+ assert storage .tokens .scope == "alpha"
876+ assert provider ._metadata is None
877+
878+
879+ @pytest .mark .anyio
880+ async def test_token_exchange_request_token_stops_on_server_error (
881+ monkeypatch : pytest .MonkeyPatch ,
882+ ) -> None :
883+ storage = InMemoryStorage ()
884+ client_metadata = OAuthClientMetadata (redirect_uris = _redirect_uris (), scope = "alpha" )
885+
886+ provider = TokenExchangeProvider (
887+ "https://api.example.com/service" ,
888+ client_metadata ,
889+ storage ,
890+ subject_token_supplier = AsyncMock (return_value = "subject-token" ),
891+ )
892+
893+ metadata_responses = [_make_response (503 )]
894+ registration_response = _make_response (200 , json_data = _registration_json ())
895+ token_response = _make_response (200 , json_data = _token_json ("alpha" ))
896+
897+ clients = [
898+ DummyAsyncClient (send_responses = metadata_responses ),
899+ DummyAsyncClient (send_responses = [registration_response ]),
900+ DummyAsyncClient (post_responses = [token_response ]),
901+ ]
902+
903+ monkeypatch .setattr ("mcp.client.auth.oauth2.httpx.AsyncClient" , AsyncClientFactory (clients ))
904+
905+ await provider ._request_token ()
906+
907+ assert storage .tokens is not None
908+ assert storage .tokens .scope == "alpha"
909+ assert provider ._metadata is None
910+
911+
912+ @pytest .mark .anyio
913+ async def test_token_exchange_request_token_without_metadata (
914+ monkeypatch : pytest .MonkeyPatch ,
915+ ) -> None :
916+ storage = InMemoryStorage ()
917+ client_metadata = OAuthClientMetadata (redirect_uris = _redirect_uris (), scope = "alpha" )
918+
919+ provider = TokenExchangeProvider (
920+ "https://api.example.com/service" ,
921+ client_metadata ,
922+ storage ,
923+ subject_token_supplier = AsyncMock (return_value = "subject-token" ),
924+ )
925+
926+ metadata_responses = [_make_response (404 ) for _ in range (4 )]
927+ registration_response = _make_response (200 , json_data = _registration_json ())
928+ token_response = _make_response (200 , json_data = _token_json ("alpha" ))
929+
930+ clients = [
931+ DummyAsyncClient (send_responses = metadata_responses ),
932+ DummyAsyncClient (send_responses = [registration_response ]),
933+ DummyAsyncClient (post_responses = [token_response ]),
934+ ]
935+
936+ monkeypatch .setattr ("mcp.client.auth.oauth2.httpx.AsyncClient" , AsyncClientFactory (clients ))
937+
938+ await provider ._request_token ()
939+
940+ assert storage .tokens is not None
941+ assert storage .tokens .scope == "alpha"
942+ assert provider ._metadata is None
943+
944+
832945@pytest .mark .anyio
833946async def test_token_exchange_request_token_raises_on_failure (monkeypatch : pytest .MonkeyPatch ) -> None :
834947 storage = InMemoryStorage ()
0 commit comments