mirror of
https://github.com/aljazceru/mcp-python-sdk.git
synced 2025-12-19 14:54:24 +01:00
Fix building auth metadata paths (#779)
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
This commit is contained in:
@@ -147,31 +147,19 @@ def create_auth_routes(
|
|||||||
return routes
|
return routes
|
||||||
|
|
||||||
|
|
||||||
def modify_url_path(url: AnyHttpUrl, path_mapper: Callable[[str], str]) -> AnyHttpUrl:
|
|
||||||
return AnyHttpUrl.build(
|
|
||||||
scheme=url.scheme,
|
|
||||||
username=url.username,
|
|
||||||
password=url.password,
|
|
||||||
host=url.host,
|
|
||||||
port=url.port,
|
|
||||||
path=path_mapper(url.path or ""),
|
|
||||||
query=url.query,
|
|
||||||
fragment=url.fragment,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def build_metadata(
|
def build_metadata(
|
||||||
issuer_url: AnyHttpUrl,
|
issuer_url: AnyHttpUrl,
|
||||||
service_documentation_url: AnyHttpUrl | None,
|
service_documentation_url: AnyHttpUrl | None,
|
||||||
client_registration_options: ClientRegistrationOptions,
|
client_registration_options: ClientRegistrationOptions,
|
||||||
revocation_options: RevocationOptions,
|
revocation_options: RevocationOptions,
|
||||||
) -> OAuthMetadata:
|
) -> OAuthMetadata:
|
||||||
authorization_url = modify_url_path(
|
authorization_url = AnyHttpUrl(
|
||||||
issuer_url, lambda path: path.rstrip("/") + AUTHORIZATION_PATH.lstrip("/")
|
str(issuer_url).rstrip("/") + AUTHORIZATION_PATH
|
||||||
)
|
)
|
||||||
token_url = modify_url_path(
|
token_url = AnyHttpUrl(
|
||||||
issuer_url, lambda path: path.rstrip("/") + TOKEN_PATH.lstrip("/")
|
str(issuer_url).rstrip("/") + TOKEN_PATH
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create metadata
|
# Create metadata
|
||||||
metadata = OAuthMetadata(
|
metadata = OAuthMetadata(
|
||||||
issuer=issuer_url,
|
issuer=issuer_url,
|
||||||
@@ -193,14 +181,14 @@ def build_metadata(
|
|||||||
|
|
||||||
# Add registration endpoint if supported
|
# Add registration endpoint if supported
|
||||||
if client_registration_options.enabled:
|
if client_registration_options.enabled:
|
||||||
metadata.registration_endpoint = modify_url_path(
|
metadata.registration_endpoint = AnyHttpUrl(
|
||||||
issuer_url, lambda path: path.rstrip("/") + REGISTRATION_PATH.lstrip("/")
|
str(issuer_url).rstrip("/") + REGISTRATION_PATH
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add revocation endpoint if supported
|
# Add revocation endpoint if supported
|
||||||
if revocation_options.enabled:
|
if revocation_options.enabled:
|
||||||
metadata.revocation_endpoint = modify_url_path(
|
metadata.revocation_endpoint = AnyHttpUrl(
|
||||||
issuer_url, lambda path: path.rstrip("/") + REVOCATION_PATH.lstrip("/")
|
str(issuer_url).rstrip("/") + REVOCATION_PATH
|
||||||
)
|
)
|
||||||
metadata.revocation_endpoint_auth_methods_supported = ["client_secret_post"]
|
metadata.revocation_endpoint_auth_methods_supported = ["client_secret_post"]
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,12 @@ from urllib.parse import parse_qs, urlparse
|
|||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
import pytest
|
import pytest
|
||||||
|
from inline_snapshot import snapshot
|
||||||
from pydantic import AnyHttpUrl
|
from pydantic import AnyHttpUrl
|
||||||
|
|
||||||
from mcp.client.auth import OAuthClientProvider
|
from mcp.client.auth import OAuthClientProvider
|
||||||
|
from mcp.server.auth.routes import build_metadata
|
||||||
|
from mcp.server.auth.settings import ClientRegistrationOptions, RevocationOptions
|
||||||
from mcp.shared.auth import (
|
from mcp.shared.auth import (
|
||||||
OAuthClientInformationFull,
|
OAuthClientInformationFull,
|
||||||
OAuthClientMetadata,
|
OAuthClientMetadata,
|
||||||
@@ -905,3 +908,76 @@ class TestOAuthClientProvider:
|
|||||||
await oauth_provider._exchange_code_for_token(
|
await oauth_provider._exchange_code_for_token(
|
||||||
"invalid_auth_code", oauth_client_info
|
"invalid_auth_code", oauth_client_info
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
(
|
||||||
|
"issuer_url",
|
||||||
|
"service_documentation_url",
|
||||||
|
"authorization_endpoint",
|
||||||
|
"token_endpoint",
|
||||||
|
"registration_endpoint",
|
||||||
|
"revocation_endpoint",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
pytest.param(
|
||||||
|
"https://auth.example.com",
|
||||||
|
"https://auth.example.com/docs",
|
||||||
|
"https://auth.example.com/authorize",
|
||||||
|
"https://auth.example.com/token",
|
||||||
|
"https://auth.example.com/register",
|
||||||
|
"https://auth.example.com/revoke",
|
||||||
|
id="simple-url",
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
"https://auth.example.com/",
|
||||||
|
"https://auth.example.com/docs",
|
||||||
|
"https://auth.example.com/authorize",
|
||||||
|
"https://auth.example.com/token",
|
||||||
|
"https://auth.example.com/register",
|
||||||
|
"https://auth.example.com/revoke",
|
||||||
|
id="with-trailing-slash",
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
"https://auth.example.com/v1/mcp",
|
||||||
|
"https://auth.example.com/v1/mcp/docs",
|
||||||
|
"https://auth.example.com/v1/mcp/authorize",
|
||||||
|
"https://auth.example.com/v1/mcp/token",
|
||||||
|
"https://auth.example.com/v1/mcp/register",
|
||||||
|
"https://auth.example.com/v1/mcp/revoke",
|
||||||
|
id="with-path-param",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_build_metadata(
|
||||||
|
issuer_url: str,
|
||||||
|
service_documentation_url: str,
|
||||||
|
authorization_endpoint: str,
|
||||||
|
token_endpoint: str,
|
||||||
|
registration_endpoint: str,
|
||||||
|
revocation_endpoint: str,
|
||||||
|
):
|
||||||
|
metadata = build_metadata(
|
||||||
|
issuer_url=AnyHttpUrl(issuer_url),
|
||||||
|
service_documentation_url=AnyHttpUrl(service_documentation_url),
|
||||||
|
client_registration_options=ClientRegistrationOptions(
|
||||||
|
enabled=True, valid_scopes=["read", "write", "admin"]
|
||||||
|
),
|
||||||
|
revocation_options=RevocationOptions(enabled=True),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert metadata == snapshot(
|
||||||
|
OAuthMetadata(
|
||||||
|
issuer=AnyHttpUrl(issuer_url),
|
||||||
|
authorization_endpoint=AnyHttpUrl(authorization_endpoint),
|
||||||
|
token_endpoint=AnyHttpUrl(token_endpoint),
|
||||||
|
registration_endpoint=AnyHttpUrl(registration_endpoint),
|
||||||
|
scopes_supported=["read", "write", "admin"],
|
||||||
|
grant_types_supported=["authorization_code", "refresh_token"],
|
||||||
|
token_endpoint_auth_methods_supported=["client_secret_post"],
|
||||||
|
service_documentation=AnyHttpUrl(service_documentation_url),
|
||||||
|
revocation_endpoint=AnyHttpUrl(revocation_endpoint),
|
||||||
|
revocation_endpoint_auth_methods_supported=["client_secret_post"],
|
||||||
|
code_challenge_methods_supported=["S256"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user