Skip to content

Commit 6e418e6

Browse files
RodriguespnKludex
andauthored
Fix building auth metadata paths (#779)
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
1 parent 8a2359f commit 6e418e6

File tree

2 files changed

+85
-21
lines changed

2 files changed

+85
-21
lines changed

src/mcp/server/auth/routes.py

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -147,31 +147,19 @@ def create_auth_routes(
147147
return routes
148148

149149

150-
def modify_url_path(url: AnyHttpUrl, path_mapper: Callable[[str], str]) -> AnyHttpUrl:
151-
return AnyHttpUrl.build(
152-
scheme=url.scheme,
153-
username=url.username,
154-
password=url.password,
155-
host=url.host,
156-
port=url.port,
157-
path=path_mapper(url.path or ""),
158-
query=url.query,
159-
fragment=url.fragment,
160-
)
161-
162-
163150
def build_metadata(
164151
issuer_url: AnyHttpUrl,
165152
service_documentation_url: AnyHttpUrl | None,
166153
client_registration_options: ClientRegistrationOptions,
167154
revocation_options: RevocationOptions,
168155
) -> OAuthMetadata:
169-
authorization_url = modify_url_path(
170-
issuer_url, lambda path: path.rstrip("/") + AUTHORIZATION_PATH.lstrip("/")
156+
authorization_url = AnyHttpUrl(
157+
str(issuer_url).rstrip("/") + AUTHORIZATION_PATH
171158
)
172-
token_url = modify_url_path(
173-
issuer_url, lambda path: path.rstrip("/") + TOKEN_PATH.lstrip("/")
159+
token_url = AnyHttpUrl(
160+
str(issuer_url).rstrip("/") + TOKEN_PATH
174161
)
162+
175163
# Create metadata
176164
metadata = OAuthMetadata(
177165
issuer=issuer_url,
@@ -193,14 +181,14 @@ def build_metadata(
193181

194182
# Add registration endpoint if supported
195183
if client_registration_options.enabled:
196-
metadata.registration_endpoint = modify_url_path(
197-
issuer_url, lambda path: path.rstrip("/") + REGISTRATION_PATH.lstrip("/")
184+
metadata.registration_endpoint = AnyHttpUrl(
185+
str(issuer_url).rstrip("/") + REGISTRATION_PATH
198186
)
199187

200188
# Add revocation endpoint if supported
201189
if revocation_options.enabled:
202-
metadata.revocation_endpoint = modify_url_path(
203-
issuer_url, lambda path: path.rstrip("/") + REVOCATION_PATH.lstrip("/")
190+
metadata.revocation_endpoint = AnyHttpUrl(
191+
str(issuer_url).rstrip("/") + REVOCATION_PATH
204192
)
205193
metadata.revocation_endpoint_auth_methods_supported = ["client_secret_post"]
206194

tests/client/test_auth.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@
1010

1111
import httpx
1212
import pytest
13+
from inline_snapshot import snapshot
1314
from pydantic import AnyHttpUrl
1415

1516
from mcp.client.auth import OAuthClientProvider
17+
from mcp.server.auth.routes import build_metadata
18+
from mcp.server.auth.settings import ClientRegistrationOptions, RevocationOptions
1619
from mcp.shared.auth import (
1720
OAuthClientInformationFull,
1821
OAuthClientMetadata,
@@ -905,3 +908,76 @@ async def test_token_exchange_error_basic(self, oauth_provider, oauth_client_inf
905908
await oauth_provider._exchange_code_for_token(
906909
"invalid_auth_code", oauth_client_info
907910
)
911+
912+
913+
@pytest.mark.parametrize(
914+
(
915+
"issuer_url",
916+
"service_documentation_url",
917+
"authorization_endpoint",
918+
"token_endpoint",
919+
"registration_endpoint",
920+
"revocation_endpoint",
921+
),
922+
(
923+
pytest.param(
924+
"https://auth.example.com",
925+
"https://auth.example.com/docs",
926+
"https://auth.example.com/authorize",
927+
"https://auth.example.com/token",
928+
"https://auth.example.com/register",
929+
"https://auth.example.com/revoke",
930+
id="simple-url",
931+
),
932+
pytest.param(
933+
"https://auth.example.com/",
934+
"https://auth.example.com/docs",
935+
"https://auth.example.com/authorize",
936+
"https://auth.example.com/token",
937+
"https://auth.example.com/register",
938+
"https://auth.example.com/revoke",
939+
id="with-trailing-slash",
940+
),
941+
pytest.param(
942+
"https://auth.example.com/v1/mcp",
943+
"https://auth.example.com/v1/mcp/docs",
944+
"https://auth.example.com/v1/mcp/authorize",
945+
"https://auth.example.com/v1/mcp/token",
946+
"https://auth.example.com/v1/mcp/register",
947+
"https://auth.example.com/v1/mcp/revoke",
948+
id="with-path-param",
949+
),
950+
),
951+
)
952+
def test_build_metadata(
953+
issuer_url: str,
954+
service_documentation_url: str,
955+
authorization_endpoint: str,
956+
token_endpoint: str,
957+
registration_endpoint: str,
958+
revocation_endpoint: str,
959+
):
960+
metadata = build_metadata(
961+
issuer_url=AnyHttpUrl(issuer_url),
962+
service_documentation_url=AnyHttpUrl(service_documentation_url),
963+
client_registration_options=ClientRegistrationOptions(
964+
enabled=True, valid_scopes=["read", "write", "admin"]
965+
),
966+
revocation_options=RevocationOptions(enabled=True),
967+
)
968+
969+
assert metadata == snapshot(
970+
OAuthMetadata(
971+
issuer=AnyHttpUrl(issuer_url),
972+
authorization_endpoint=AnyHttpUrl(authorization_endpoint),
973+
token_endpoint=AnyHttpUrl(token_endpoint),
974+
registration_endpoint=AnyHttpUrl(registration_endpoint),
975+
scopes_supported=["read", "write", "admin"],
976+
grant_types_supported=["authorization_code", "refresh_token"],
977+
token_endpoint_auth_methods_supported=["client_secret_post"],
978+
service_documentation=AnyHttpUrl(service_documentation_url),
979+
revocation_endpoint=AnyHttpUrl(revocation_endpoint),
980+
revocation_endpoint_auth_methods_supported=["client_secret_post"],
981+
code_challenge_methods_supported=["S256"],
982+
)
983+
)

0 commit comments

Comments
 (0)