Skip to content

routes

validate_issuer_url

validate_issuer_url(url: AnyHttpUrl)

Validate that the issuer URL meets OAuth 2.0 requirements.

Parameters:

Name Type Description Default
url AnyHttpUrl

The issuer URL to validate.

required

Raises:

Type Description
ValueError

If the issuer URL is invalid.

Source code in src/mcp/server/auth/routes.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def validate_issuer_url(url: AnyHttpUrl):
    """Validate that the issuer URL meets OAuth 2.0 requirements.

    Args:
        url: The issuer URL to validate.

    Raises:
        ValueError: If the issuer URL is invalid.
    """

    # RFC 8414 requires HTTPS, but we allow loopback/localhost HTTP for testing
    if url.scheme != "https" and url.host not in ("localhost", "127.0.0.1", "[::1]"):
        raise ValueError("Issuer URL must be HTTPS")

    # No fragments or query parameters allowed
    if url.fragment:
        raise ValueError("Issuer URL must not have a fragment")
    if url.query:
        raise ValueError("Issuer URL must not have a query string")

build_resource_metadata_url

build_resource_metadata_url(
    resource_server_url: AnyHttpUrl,
) -> AnyHttpUrl

Build RFC 9728 compliant protected resource metadata URL.

Inserts /.well-known/oauth-protected-resource between host and resource path as specified in RFC 9728 §3.1.

Parameters:

Name Type Description Default
resource_server_url AnyHttpUrl

The resource server URL (e.g., https://example.com/mcp)

required

Returns:

Type Description
AnyHttpUrl

The metadata URL (e.g., https://example.com/.well-known/oauth-protected-resource/mcp)

Source code in src/mcp/server/auth/routes.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
def build_resource_metadata_url(resource_server_url: AnyHttpUrl) -> AnyHttpUrl:
    """Build RFC 9728 compliant protected resource metadata URL.

    Inserts /.well-known/oauth-protected-resource between host and resource path
    as specified in RFC 9728 §3.1.

    Args:
        resource_server_url: The resource server URL (e.g., https://example.com/mcp)

    Returns:
        The metadata URL (e.g., https://example.com/.well-known/oauth-protected-resource/mcp)
    """
    parsed = urlparse(str(resource_server_url))
    # Handle trailing slash: if path is just "/", treat as empty
    resource_path = parsed.path if parsed.path != "/" else ""
    return AnyHttpUrl(f"{parsed.scheme}://{parsed.netloc}/.well-known/oauth-protected-resource{resource_path}")

create_protected_resource_routes

create_protected_resource_routes(
    resource_url: AnyHttpUrl,
    authorization_servers: list[AnyHttpUrl],
    scopes_supported: list[str] | None = None,
    resource_name: str | None = None,
    resource_documentation: AnyHttpUrl | None = None,
) -> list[Route]

Create routes for OAuth 2.0 Protected Resource Metadata (RFC 9728).

Parameters:

Name Type Description Default
resource_url AnyHttpUrl

The URL of this resource server

required
authorization_servers list[AnyHttpUrl]

List of authorization servers that can issue tokens

required
scopes_supported list[str] | None

Optional list of scopes supported by this resource

None
resource_name str | None

Optional human-readable name for this resource

None
resource_documentation AnyHttpUrl | None

Optional URL to documentation for this resource

None

Returns:

Type Description
list[Route]

List of Starlette routes for protected resource metadata

Source code in src/mcp/server/auth/routes.py
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
def create_protected_resource_routes(
    resource_url: AnyHttpUrl,
    authorization_servers: list[AnyHttpUrl],
    scopes_supported: list[str] | None = None,
    resource_name: str | None = None,
    resource_documentation: AnyHttpUrl | None = None,
) -> list[Route]:
    """Create routes for OAuth 2.0 Protected Resource Metadata (RFC 9728).

    Args:
        resource_url: The URL of this resource server
        authorization_servers: List of authorization servers that can issue tokens
        scopes_supported: Optional list of scopes supported by this resource
        resource_name: Optional human-readable name for this resource
        resource_documentation: Optional URL to documentation for this resource

    Returns:
        List of Starlette routes for protected resource metadata
    """
    metadata = ProtectedResourceMetadata(
        resource=resource_url,
        authorization_servers=authorization_servers,
        scopes_supported=scopes_supported,
        resource_name=resource_name,
        resource_documentation=resource_documentation,
        # bearer_methods_supported defaults to ["header"] in the model
    )

    handler = ProtectedResourceMetadataHandler(metadata)

    # RFC 9728 §3.1: Register route at /.well-known/oauth-protected-resource + resource path
    metadata_url = build_resource_metadata_url(resource_url)
    # Extract just the path part for route registration
    parsed = urlparse(str(metadata_url))
    well_known_path = parsed.path

    return [
        Route(
            well_known_path,
            endpoint=cors_middleware(handler.handle, ["GET", "OPTIONS"]),
            methods=["GET", "OPTIONS"],
        )
    ]