BerriAI/litellm · error · ValueError

MCPJWTSigner: token_introspection_endpoint is required for o

Error message

MCPJWTSigner: token_introspection_endpoint is required for opaque token verification but is not configured.

What it means

Opaque (non-JWT) bearer tokens cannot be verified against a JWKS, so MCPJWTSigner verifies them via RFC 7662 token introspection. _introspect_opaque_token raises ValueError when it is reached but token_introspection_endpoint was never configured - the verification path is impossible to complete.

Source

Thrown at litellm/proxy/guardrails/guardrail_hooks/mcp_jwt_signer/mcp_jwt_signer.py:484

            decode_kwargs["audience"] = self.verify_audience
        else:
            decode_options["verify_aud"] = False

        if self.verify_issuer:
            decode_kwargs["issuer"] = self.verify_issuer

        payload: Final[dict[str, object]] = jwt.decode(raw_token, signing_jwk.key, **decode_kwargs)
        return payload

    async def _introspect_opaque_token(self, token: str) -> dict[str, object]:
        """
        Perform RFC 7662 token introspection for opaque (non-JWT) tokens.

        Returns the introspection response dict.  Raises on HTTP error or
        inactive token.
        """
        if not self.token_introspection_endpoint:
            raise ValueError(
                "MCPJWTSigner: token_introspection_endpoint is required for "
                "opaque token verification but is not configured."
            )

        from litellm.llms.custom_httpx.http_handler import (
            get_async_httpx_client,
            httpxSpecialProvider,
        )

        client: Final = get_async_httpx_client(llm_provider=httpxSpecialProvider.Oauth2Check)
        resp: Final = await client.post(
            self.token_introspection_endpoint,
            data={"token": token},
            headers={"Accept": "application/json"},
        )
        resp.raise_for_status()
        result: Final[dict[str, object]] = resp.json()
        if not result.get("active", False):

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Configure token_introspection_endpoint in the guardrail litellm_params, pointing at the IdP's RFC 7662 introspection endpoint
  2. Alternatively send JWT access tokens from clients so the JWKS path is used
  3. If opaque tokens need not be verified, remove access_token_discovery_uri - the signer then logs a warning and proceeds without verification

Example fix

# before - opaque tokens arrive but no introspection endpoint
litellm_params:
  access_token_discovery_uri: https://idp.example.com/.well-known/openid-configuration

# after - RFC 7662 introspection configured
litellm_params:
  access_token_discovery_uri: https://idp.example.com/.well-known/openid-configuration
  token_introspection_endpoint: https://idp.example.com/oauth2/introspect
Defensive patterns

Strategy: validation

Validate before calling

lp = guardrail_entry["litellm_params"]  
if lp.get("access_token_discovery_uri"):  
    # opaque bearer tokens will need RFC 7662 introspection  
    assert lp.get("token_introspection_endpoint") or clients_send_jwt_only, (  
        "token_introspection_endpoint required when opaque tokens may arrive"  
    )

Prevention

When it happens

Trigger: access_token_discovery_uri is set, an incoming request carries an opaque bearer token (not three dot-separated base64 segments), and the guardrail's litellm_params lack token_introspection_endpoint.

Common situations: Mixing JWT and opaque-token clients (legacy API keys, reference tokens from IdentityServer); enabling incoming verification for JWTs and then an opaque-token client arrives; introspection endpoint configured under a typo'd key so it reads as None.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/5696323dfdb6d1e2. Report an issue: GitHub.