Skip to content

Routes & well-known endpoints

RFC 8414 / RFC 9728 metadata endpoints and Dynamic Client Registration façade.

auth_routes

mcpauthkit.auth_routes — generic MCP OAuth metadata endpoints.

Provides the well-known OAuth protected-resource and authorization-server discovery documents required by the MCP OAuth spec, plus a Dynamic Client Registration (DCR) façade that returns a pre-registered public client ID.

Works with any standard OIDC provider (Keycloak, Okta, Entra ID, Duende, …).

Usage
from mcpauthkit.auth_routes import oauth_meta_router

app.include_router(oauth_meta_router(
    server_base_url="http://localhost:8005",
    issuer_url="http://localhost:8889/realms/mcp-quickstart",
    client_id="mcp-quickstart-vscode",
))

Call include_router before app.mount("/", ...).

oauth_meta_router

oauth_meta_router(
    *,
    server_base_url: str,
    issuer_url: str,
    client_id: str,
    extra_authorize_params: dict[str, str] | None = None,
) -> APIRouter

Return an APIRouter with well-known OAuth metadata routes and a DCR façade. Mount it on the app with app.include_router(...).

Parameters:

Name Type Description Default
server_base_url str

Full URL of this MCP server, e.g. "http://localhost:8005".

required
issuer_url str

Base URL of the OIDC issuer, e.g. "http://localhost:8889/realms/mcp-poc5" or "https://login.microsoftonline.com/{tenant}/v2.0".

required
client_id str

Pre-registered public client ID returned by the DCR façade.

required
extra_authorize_params dict[str, str] | None

Optional extra query parameters appended to the authorization_endpoint in the /.well-known/oauth-authorization-server response. MCP clients read that URL and use it verbatim when redirecting the user to the OIDC provider, so any hint placed here is automatically forwarded.

Use this for provider-specific routing parameters that fall outside the standard OAuth 2.0 / OIDC spec. For example, Okta's idp parameter bypasses the Okta login page and routes users directly to a configured external Identity Provider::

app.include_router(oauth_meta_router(
    server_base_url=settings.server_base_url,
    issuer_url="https://your-org.okta.com/oauth2/default",
    client_id=settings.okta_client_id,
    extra_authorize_params={"idp": "0oaz2r21a8RBmZyOL0h7"},
))

Default: None (no extra params — fully retro-compatible).

None
Source code in mcpauthkit/auth_routes.py
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
def oauth_meta_router(
    *,
    server_base_url: str,
    issuer_url: str,
    client_id: str,
    extra_authorize_params: dict[str, str] | None = None,
) -> APIRouter:
    """
    Return an ``APIRouter`` with well-known OAuth metadata routes and a DCR
    façade.  Mount it on the app with ``app.include_router(...)``.

    Parameters
    ----------
    server_base_url
        Full URL of this MCP server, e.g. ``"http://localhost:8005"``.
    issuer_url
        Base URL of the OIDC issuer,
        e.g. ``"http://localhost:8889/realms/mcp-poc5"`` or
        ``"https://login.microsoftonline.com/{tenant}/v2.0"``.
    client_id
        Pre-registered public client ID returned by the DCR façade.
    extra_authorize_params
        Optional extra query parameters appended to the
        ``authorization_endpoint`` in the
        ``/.well-known/oauth-authorization-server`` response.  MCP clients
        read that URL and use it verbatim when redirecting the user to the
        OIDC provider, so any hint placed here is automatically forwarded.

        Use this for provider-specific routing parameters that fall outside
        the standard OAuth 2.0 / OIDC spec.  For example, Okta's ``idp``
        parameter bypasses the Okta login page and routes users directly to
        a configured external Identity Provider::

            app.include_router(oauth_meta_router(
                server_base_url=settings.server_base_url,
                issuer_url="https://your-org.okta.com/oauth2/default",
                client_id=settings.okta_client_id,
                extra_authorize_params={"idp": "0oaz2r21a8RBmZyOL0h7"},
            ))

        Default: ``None`` (no extra params — fully retro-compatible).
    """
    router = APIRouter()
    base = server_base_url.rstrip("/")
    issuer = issuer_url.rstrip("/")

    @router.get("/.well-known/oauth-protected-resource", include_in_schema=False)
    @router.get("/.well-known/oauth-protected-resource/{path:path}", include_in_schema=False)
    async def _protected_resource_metadata(path: str = ""):
        return JSONResponse(
            {
                "resource": f"{base}/mcp",
                "authorization_servers": [base],
                "bearer_methods_supported": ["header"],
                "scopes_supported": ["openid", "profile", "email"],
            }
        )

    @router.get("/.well-known/oauth-authorization-server", include_in_schema=False)
    @router.get("/.well-known/oauth-authorization-server/{path:path}", include_in_schema=False)
    async def _authorization_server_metadata():
        auth_ep = f"{issuer}/protocol/openid-connect/auth"
        token_ep = f"{issuer}/protocol/openid-connect/token"
        jwks_ep = f"{issuer}/protocol/openid-connect/certs"
        try:
            async with httpx.AsyncClient(timeout=5) as client:
                resp = await client.get(f"{issuer}/.well-known/openid-configuration")
                if resp.status_code == 200:
                    meta = resp.json()
                    auth_ep = meta.get("authorization_endpoint", auth_ep)
                    token_ep = meta.get("token_endpoint", token_ep)
                    jwks_ep = meta.get("jwks_uri", jwks_ep)
        except Exception as exc:
            logger.warning("Could not fetch OIDC metadata: %s", exc)

        if extra_authorize_params:
            sep = "&" if "?" in auth_ep else "?"
            auth_ep += sep + urlencode(extra_authorize_params)

        return JSONResponse(
            {
                "issuer": base,
                "authorization_endpoint": auth_ep,
                "token_endpoint": token_ep,
                "jwks_uri": jwks_ep,
                "registration_endpoint": f"{base}/register",
                "response_types_supported": ["code"],
                "grant_types_supported": ["authorization_code"],
                "code_challenge_methods_supported": ["S256"],
                "token_endpoint_auth_methods_supported": ["none"],
            }
        )

    @router.post("/register", include_in_schema=False)
    async def _dynamic_client_registration(request: Request):
        """DCR façade — always echoes back the pre-registered public client ID."""
        try:
            body = await request.json()
        except Exception:
            return JSONResponse(
                status_code=400,
                content={"error": "invalid_client_metadata"},
            )
        redirect_uris = body.get("redirect_uris", [])
        logger.info(
            "DCR façade: client_name=%s redirect_uris=%s",
            body.get("client_name"),
            redirect_uris,
        )
        return JSONResponse(
            status_code=201,
            content={
                "client_id": client_id,
                "client_id_issued_at": int(time.time()),
                "redirect_uris": redirect_uris,
                "grant_types": ["authorization_code"],
                "response_types": ["code"],
                "token_endpoint_auth_method": "none",
            },
        )

    return router

Provider-specific routing hints

Some OIDC providers accept extra query parameters on the authorization endpoint that route users to a specific Identity Provider without showing the provider's own login page. Pass them via extra_authorize_params — they are appended to the authorization_endpoint URL returned in /.well-known/oauth-authorization-server, which MCP clients use verbatim.

Okta — routing to an external Identity Provider

Okta's idp parameter bypasses the Okta login page and sends users directly to a configured external IdP (Microsoft Entra, Google Workspace, a SAML IdP, …). The value is the IdP ID shown in the Okta Admin Console under Security → Identity Providers.

app.include_router(oauth_meta_router(
    server_base_url=settings.server_base_url,
    issuer_url="https://your-org.okta.com/oauth2/default",
    client_id=settings.okta_client_id,
    extra_authorize_params={"idp": "0oaz2r21a8RBmZyOL0h7"},
))

The resulting authorization_endpoint in the well-known document will be:

https://your-org.okta.com/oauth2/default/v1/authorize?idp=0oaz2r21a8RBmZyOL0h7

MCP clients (VS Code Copilot, MCP Inspector, …) read this URL and include the idp hint when redirecting the user, so they are sent straight to the external IdP without ever seeing the Okta login screen.