BerriAI/litellm · error · ValueError
MCPJWTSigner: access_token_discovery_uri discovery document
Error message
MCPJWTSigner: access_token_discovery_uri discovery document at {self.access_token_discovery_uri!r} has no 'jwks_uri'. What it means
When access_token_discovery_uri is configured, MCPJWTSigner fetches the OIDC discovery document to find where verification keys live. _verify_incoming_jwt raises ValueError if the fetched document has no 'jwks_uri' entry, because there is then no way to obtain the JWKS needed to verify incoming JWTs.
Source
Thrown at litellm/proxy/guardrails/guardrail_hooks/mcp_jwt_signer/mcp_jwt_signer.py:423
doc: Final = await _fetch_oidc_discovery(self.access_token_discovery_uri)
if "jwks_uri" in doc:
self._oidc_discovery_doc = doc
self._oidc_discovery_fetched_at = now
else:
return doc
return self._oidc_discovery_doc or {}
async def _verify_incoming_jwt(self, raw_token: str) -> dict[str, object]:
"""
Verify an incoming Bearer JWT against the configured IdP's JWKS.
Returns the verified payload claims dict.
Raises jwt.PyJWTError (or subclass) if verification fails.
"""
discovery: Final = await self._get_oidc_discovery()
jwks_uri: Final = discovery.get("jwks_uri")
if not jwks_uri:
raise ValueError(
"MCPJWTSigner: access_token_discovery_uri discovery document "
f"at {self.access_token_discovery_uri!r} has no 'jwks_uri'."
)
jwks_keys: Final = await _fetch_jwks(jwks_uri)
# Only read `kid` from the unverified header — never `alg`.
# Reading `alg` from an attacker-controlled header enables algorithm
# confusion attacks (e.g. alg:none, HS256 with the public key as secret).
# The algorithm is determined from the JWKS key entry instead.
unverified_header: Final = jwt.get_unverified_header(raw_token)
kid: Final = unverified_header.get("kid")
# Build a JWKS object and pick the matching key.
# PyJWT's PyJWKSet handles key-type parsing and kid matching correctly.
from jwt import PyJWKSet
try:View on GitHub (pinned to 77b7c6c40c)
Solutions
- Point access_token_discovery_uri at the IdP's standard discovery endpoint: https://<idp>/.well-known/openid-configuration
- Verify the URL manually: curl it and confirm the JSON contains a non-empty jwks_uri
- If the IdP cannot publish jwks_uri, obtain keys another way or drop incoming-token verification (unset access_token_discovery_uri)
Example fix
# before - resource endpoint, no jwks_uri in the document litellm_params: access_token_discovery_uri: https://idp.example.com/oauth2/default # after - standard OIDC discovery document litellm_params: access_token_discovery_uri: https://idp.example.com/.well-known/openid-configuration
Defensive patterns
Strategy: validation
Validate before calling
import httpx
def check_discovery_doc(uri: str) -> None:
doc = httpx.get(uri, timeout=10).json()
assert doc.get("jwks_uri"), f"discovery document at {uri} has no jwks_uri - wrong endpoint?"
check_discovery_doc(config_lp["access_token_discovery_uri"]) # run at deploy time Prevention
- Always use the IdP's .well-known/openid-configuration URL for access_token_discovery_uri
- Add a startup/deploy smoke check that curls the discovery URL and asserts jwks_uri exists and resolves
When it happens
Trigger: access_token_discovery_uri points at a URL whose JSON response lacks jwks_uri - e.g. a resource-level endpoint instead of the IdP's .well-known/openid-configuration, an IdP that does not advertise JWKS in discovery, or a proxy/gateway returning an error document that parses as JSON.
Common situations: Discovery URL copy-pasted from the issuer base URL without the well-known path; pointing at a Keycloak tenant endpoint that redirects; custom auth servers that omit jwks_uri; intermediate proxies rewriting responses.
Related errors
- JWT Auth: OIDC discovery endpoint {url} returned status {res
- JWT Auth: OIDC discovery document at {url} does not contain
- JWT Auth: Failed to parse OIDC discovery document at {url}:
- Error parsing response: {e}. Check server logs for original
- No matching public key found. keys={resolved_jwks_url}, kid=
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/3299a72046207b95.
Report an issue: GitHub.