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
) -> 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
Source code in mcpauthkit/auth_routes.py
 35
 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
def oauth_meta_router(
    *,
    server_base_url: str,
    issuer_url: str,
    client_id: str,
) -> 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.
    """
    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)
    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)

        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