Skip to content

OAuthClientProvider add prefix url for MCP Starlette Mounted endpoints to correctly route .well-known metadata discovery #1751

Closed

Description

@abneetwats24

Description

I am using Starlette mounting

hr_policy_mcp.settings.streamable_http_path = "/hr-policy"
math_mcp.settings.streamable_http_path = "/math"
main_server = Starlette(
        routes=[
            Mount("/hr-policy", app=hr_policy_mcp.streamable_http_app()),
            Mount("/math", app=math_mcp.streamable_http_app()),
        ],
        lifespan=combined_lifespan,
    )
main_server = CORSMiddleware(
        main_server,
        allow_origins=["*"],  # Configure appropriately for production
        allow_methods=["GET", "POST", "DELETE"],  # MCP streamable HTTP methods
        expose_headers=["Mcp-Session-Id"],
    )

With this I am getting 404 for Metadata Discovery

mcp-server  | INFO:     172.18.0.1:54902 - "GET /hr-policy/hr-policy HTTP/1.1" 401 Unauthorized
mcp-server  | INFO:     172.18.0.1:54902 - "GET /favicon.ico HTTP/1.1" 404 Not Found
mcp-server  | INFO:     172.18.0.1:33592 - "OPTIONS /hr-policy/hr-policy HTTP/1.1" 200 OK
mcp-server  | INFO:     172.18.0.1:33592 - "POST /hr-policy/hr-policy HTTP/1.1" 401 Unauthorized
mcp-server  | INFO:     172.18.0.1:42520 - "OPTIONS /.well-known/oauth-protected-resource/hr-policy/hr-policy HTTP/1.1" 400 Bad Request
mcp-server  | INFO:     172.18.0.1:42520 - "GET /.well-known/oauth-protected-resource/hr-policy/hr-policy HTTP/1.1" 404 Not Found
mcp-server  | INFO:     172.18.0.1:42520 - "OPTIONS /.well-known/oauth-protected-resource HTTP/1.1" 400 Bad Request
mcp-server  | INFO:     172.18.0.1:42520 - "GET /.well-known/oauth-protected-resource HTTP/1.1" 404 Not Found
mcp-server  | INFO:     172.18.0.1:42520 - "OPTIONS /.well-known/oauth-authorization-server HTTP/1.1" 400 Bad Request
mcp-server  | INFO:     172.18.0.1:42520 - "GET /.well-known/oauth-authorization-server HTTP/1.1" 404 Not Found

And I can see .well-known are serving on
http://127.0.0.1:3000/hr-policy/.well-known/oauth-protected-resource

http://127.0.0.1:3000/math/.well-known/oauth-protected-resource

Both provides same authorization_servers url as I am using same for both: possible having different for different realms and mounts

{"resource":"http://127.0.0.1:3000/","authorization_servers":["http://192.**.10.7:5555/realms/**testrealm"],"scopes_supported":["mcp:tools"],"bearer_methods_supported":["header"]}

Workaround
Currently I am doing Monkeypatches for url prefix fix, complete code here

import mcp.client.auth.oauth2
from mcp.client.auth import OAuthClientProvider
mcp.client.auth.oauth2.build_protected_resource_metadata_discovery_urls = _masked_build_protected_resource_metadata_discovery_urls
OAuthClientProvider._perform_authorization = _masked_perform_authorization

Can the above be also handled with the OAuthClientProvider like I can just pass in prefix mounts of mcp?

As even I have: MCP Server url as: http://127.0.0.1:3000/math/math
It is doing Metadata Discovery for base url.

References

No response

Activity

  1. changed the title [-]OAuthClientProvider add prefix url for MCP Mounted endpoints to correctly route .well-known metadata discovery[/-] [+]OAuthClientProvider add prefix url for MCP Starlette Mounted endpoints to correctly route .well-known metadata discovery[/+] on Dec 9, 2025
  2. maxisbey commented on Dec 10, 2025

    @maxisbey
    Contributor

    Thanks for the detailed issue! So the problem here is that Starlette's Mount() prefixes all routes including .well-known, which puts them in the wrong place per RFC 9728.

    The spec says .well-known goes between host and path - so for https://example.com/hr-policy/hr-policy the metadata should be at /.well-known/oauth-protected-resource/hr-policy/hr-policy, not /hr-policy/.well-known/oauth-protected-resource. See RFC 9728 §3.1 for the well-known URI construction rules.

    I tested this locally and confirmed:

    • When you mount at /hr-policy, routes like /.well-known/oauth-protected-resource become /hr-policy/.well-known/oauth-protected-resource
    • The client correctly looks at the RFC 9728 location (/.well-known/oauth-protected-resource/...) and gets 404

    The workaround is to register the .well-known routes at root level instead of inside the mounted app:

    from mcp.server.auth.routes import create_protected_resource_routes
    
    routes = [
        # Root-level .well-known routes (before mounting)
        *create_protected_resource_routes(
            resource_url="https://your-server.com/hr-policy/hr-policy",
            authorization_servers=["https://your-auth-server/..."],
            scopes_supported=["mcp:tools"],
        ),
        # Then mount the apps
        Mount("/hr-policy", app=hr_policy_mcp.streamable_http_app()),
    ]

    Also note - your resource_server_url should be the full endpoint path (https://your-server.com/hr-policy/hr-policy), not just the base URL. The resource field in the metadata must match the actual resource per RFC 9728.

    Closing #1762 since the client-side fix isn't spec-compliant. Let me know if the workaround works for you!

    AI Disclaimer

  3. abneetwats24 commented on Dec 12, 2025

    @abneetwats24
    Author

    Thanks for the workaround, it is working, I'll look if I can override Mount to create create_protected_resource_routes based on authorization_servers state in the server object reference automatically, so no need to manually add those routes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions