|  | 
| 6 | 6 | 
 | 
| 7 | 7 | import httpx | 
| 8 | 8 | import pytest | 
|  | 9 | +from inline_snapshot import Is, snapshot | 
| 9 | 10 | from pydantic import AnyHttpUrl, AnyUrl | 
| 10 | 11 | 
 | 
| 11 | 12 | from mcp.client.auth import OAuthClientProvider, PKCEParameters | 
| @@ -580,3 +581,82 @@ async def test_auth_flow_with_valid_tokens(self, oauth_provider, mock_storage, v | 
| 580 | 581 |             await auth_flow.asend(response) | 
| 581 | 582 |         except StopAsyncIteration: | 
| 582 | 583 |             pass  # Expected | 
|  | 584 | + | 
|  | 585 | + | 
|  | 586 | +@pytest.mark.parametrize( | 
|  | 587 | +    ( | 
|  | 588 | +        "issuer_url", | 
|  | 589 | +        "service_documentation_url", | 
|  | 590 | +        "authorization_endpoint", | 
|  | 591 | +        "token_endpoint", | 
|  | 592 | +        "registration_endpoint", | 
|  | 593 | +        "revocation_endpoint", | 
|  | 594 | +    ), | 
|  | 595 | +    ( | 
|  | 596 | +        # Pydantic's AnyUrl incorrectly adds trailing slash to base URLs | 
|  | 597 | +        # This is being fixed in https://github.com/pydantic/pydantic-core/pull/1719 (Pydantic 2.12+) | 
|  | 598 | +        pytest.param( | 
|  | 599 | +            "https://auth.example.com", | 
|  | 600 | +            "https://auth.example.com/docs", | 
|  | 601 | +            "https://auth.example.com/authorize", | 
|  | 602 | +            "https://auth.example.com/token", | 
|  | 603 | +            "https://auth.example.com/register", | 
|  | 604 | +            "https://auth.example.com/revoke", | 
|  | 605 | +            id="simple-url", | 
|  | 606 | +            marks=pytest.mark.xfail( | 
|  | 607 | +                reason="Pydantic AnyUrl adds trailing slash to base URLs - fixed in Pydantic 2.12+" | 
|  | 608 | +            ), | 
|  | 609 | +        ), | 
|  | 610 | +        pytest.param( | 
|  | 611 | +            "https://auth.example.com/", | 
|  | 612 | +            "https://auth.example.com/docs", | 
|  | 613 | +            "https://auth.example.com/authorize", | 
|  | 614 | +            "https://auth.example.com/token", | 
|  | 615 | +            "https://auth.example.com/register", | 
|  | 616 | +            "https://auth.example.com/revoke", | 
|  | 617 | +            id="with-trailing-slash", | 
|  | 618 | +        ), | 
|  | 619 | +        pytest.param( | 
|  | 620 | +            "https://auth.example.com/v1/mcp", | 
|  | 621 | +            "https://auth.example.com/v1/mcp/docs", | 
|  | 622 | +            "https://auth.example.com/v1/mcp/authorize", | 
|  | 623 | +            "https://auth.example.com/v1/mcp/token", | 
|  | 624 | +            "https://auth.example.com/v1/mcp/register", | 
|  | 625 | +            "https://auth.example.com/v1/mcp/revoke", | 
|  | 626 | +            id="with-path-param", | 
|  | 627 | +        ), | 
|  | 628 | +    ), | 
|  | 629 | +) | 
|  | 630 | +def test_build_metadata( | 
|  | 631 | +    issuer_url: str, | 
|  | 632 | +    service_documentation_url: str, | 
|  | 633 | +    authorization_endpoint: str, | 
|  | 634 | +    token_endpoint: str, | 
|  | 635 | +    registration_endpoint: str, | 
|  | 636 | +    revocation_endpoint: str, | 
|  | 637 | +): | 
|  | 638 | +    from mcp.server.auth.routes import build_metadata | 
|  | 639 | +    from mcp.server.auth.settings import ClientRegistrationOptions, RevocationOptions | 
|  | 640 | + | 
|  | 641 | +    metadata = build_metadata( | 
|  | 642 | +        issuer_url=AnyHttpUrl(issuer_url), | 
|  | 643 | +        service_documentation_url=AnyHttpUrl(service_documentation_url), | 
|  | 644 | +        client_registration_options=ClientRegistrationOptions(enabled=True, valid_scopes=["read", "write", "admin"]), | 
|  | 645 | +        revocation_options=RevocationOptions(enabled=True), | 
|  | 646 | +    ) | 
|  | 647 | + | 
|  | 648 | +    assert metadata.model_dump(exclude_defaults=True, mode="json") == snapshot( | 
|  | 649 | +        { | 
|  | 650 | +            "issuer": Is(issuer_url), | 
|  | 651 | +            "authorization_endpoint": Is(authorization_endpoint), | 
|  | 652 | +            "token_endpoint": Is(token_endpoint), | 
|  | 653 | +            "registration_endpoint": Is(registration_endpoint), | 
|  | 654 | +            "scopes_supported": ["read", "write", "admin"], | 
|  | 655 | +            "grant_types_supported": ["authorization_code", "refresh_token"], | 
|  | 656 | +            "token_endpoint_auth_methods_supported": ["client_secret_post"], | 
|  | 657 | +            "service_documentation": Is(service_documentation_url), | 
|  | 658 | +            "revocation_endpoint": Is(revocation_endpoint), | 
|  | 659 | +            "revocation_endpoint_auth_methods_supported": ["client_secret_post"], | 
|  | 660 | +            "code_challenge_methods_supported": ["S256"], | 
|  | 661 | +        } | 
|  | 662 | +    ) | 
0 commit comments