{"record":{"id":"826a8fbe1f023ac7","repo":"BerriAI/litellm","slug":"mcpjwtsigner-incoming-token-verification-failed","errorCode":null,"errorMessage":"MCPJWTSigner: incoming token verification failed: {exc}","messagePattern":"MCPJWTSigner: incoming token verification failed: (.+?)","errorType":"http","errorClass":"HTTPException","httpStatus":401,"severity":"error","filePath":"litellm/proxy/guardrails/guardrail_hooks/mcp_jwt_signer/mcp_jwt_signer.py","lineNumber":820,"sourceCode":"            # Three-dot pattern → JWT;  otherwise opaque.\n            is_jwt: Final = raw_token.count(\".\") == 2\n            try:\n                if is_jwt:\n                    jwt_claims = await self._verify_incoming_jwt(raw_token)\n                elif self.token_introspection_endpoint:\n                    jwt_claims = await self._introspect_opaque_token(raw_token)\n                else:\n                    verbose_proxy_logger.warning(\n                        \"MCPJWTSigner: access_token_discovery_uri is set but the \"\n                        \"incoming token appears to be opaque and no \"\n                        \"token_introspection_endpoint is configured. \"\n                        \"Proceeding without incoming token verification.\"\n                    )\n            except Exception as exc:\n                verbose_proxy_logger.error(\"MCPJWTSigner: incoming token verification failed: %s\", exc)\n                from fastapi import HTTPException\n\n                raise HTTPException(\n                    status_code=401,\n                    detail={\"error\": (f\"MCPJWTSigner: incoming token verification failed: {exc}\")},\n                )\n        elif not raw_token and self.access_token_discovery_uri:\n            verbose_proxy_logger.debug(\n                \"MCPJWTSigner: access_token_discovery_uri configured but no Bearer \"\n                \"token found in request (API-key auth request — skipping verification).\"\n            )\n\n        # Fall back to LiteLLM-decoded JWT claims (available when proxy uses JWT auth).\n        if jwt_claims is None:\n            jwt_claims = user_api_key_dict.jwt_claims\n\n        # ------------------------------------------------------------------\n        # FR-15: Validate required claims\n        # ------------------------------------------------------------------\n        self._validate_required_claims(jwt_claims)\n","sourceCodeStart":802,"sourceCodeEnd":838,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/guardrails/guardrail_hooks/mcp_jwt_signer/mcp_jwt_signer.py#L802-L838","documentation":"MCPJWTSigner wraps the entire incoming-token verification step in a try/except; any exception (PyJWT signature/expiry/audience errors, JWKS fetch failures, the missing-jwks_uri ValueError, introspection errors) is logged and re-raised as HTTPException 401 with the original exception text embedded. The inner message is the real diagnosis; this 401 is the uniform 'unauthenticated' surface.","triggerScenarios":"An MCP request with a Bearer token that fails verification: expired token (exp), wrong issuer/audience vs verify_issuer/verify_audience, signature not matching JWKS keys, unknown kid, clock skew on iat/nbf, unreachable discovery/JWKS endpoint, or the opaque-token path hitting misconfigured introspection.","commonSituations":"Tokens from a different IdP than the one configured; system clocks drifted so valid tokens read expired; JWKS endpoint blocked by firewall; rotated signing keys with stale cache; test tokens signed with a dev key against a prod discovery URI.","solutions":["Read the {exc} text in the 401 detail - it names the actual failure (expired, audience, jwks_uri, network) and dictates the fix","For exp/iat failures, sync the proxy host clock (NTP) or account for clock skew","For issuer/audience failures, align verify_issuer/verify_audience with the claims the IdP actually mints","For fetch failures, ensure the proxy can reach the discovery and JWKS URLs (no firewall, valid TLS, correct DNS)"],"exampleFix":"# before - verification config does not match the token's claims\nlitellm_params:\n  access_token_discovery_uri: https://idp.example.com/.well-known/openid-configuration\n  verify_issuer: https://wrong-issuer.example.com\n  verify_audience: my-mcp-api\n\n# after - matches the IdP's issued claims (check with jwt.io)\nlitellm_params:\n  access_token_discovery_uri: https://idp.example.com/.well-known/openid-configuration\n  verify_issuer: https://idp.example.com\n  verify_audience: mcp-gateway","handlingStrategy":"try-catch","validationCode":"import httpx, jwt as pyjwt  \n  \ndef verify_token_will_pass(token: str, discovery_uri: str, issuer: str, audience: str) -> None:  \n    doc = httpx.get(discovery_uri, timeout=10).json()  \n    assert doc.get(\"jwks_uri\"), \"discovery doc lacks jwks_uri\"  \n    jwks = httpx.get(doc[\"jwks_uri\"], timeout=10).json()  \n    header = pyjwt.get_unverified_header(token)  \n    assert any(k.get(\"kid\") == header.get(\"kid\") for k in jwks.get(\"keys\", [])), \"signing kid not in JWKS\"  \n    claims = pyjwt.decode(token, options={\"verify_signature\": False})  \n    assert issuer in (claims.get(\"iss\"), None) or claims.get(\"iss\") == issuer, \"issuer mismatch\"  \n    assert audience in claims.get(\"aud\", []), \"audience mismatch\"","typeGuard":null,"tryCatchPattern":"import openai  \n  \ntry:  \n    resp = client.responses.create(model=deployment, tools=mcp_tools, input=prompt)  \nexcept openai.AuthenticationError as e:  \n    msg = getattr(e, \"body\", {}).get(\"error\", \"\") if isinstance(getattr(e, \"body\", None), dict) else str(e)  \n    if msg.startswith(\"MCPJWTSigner: incoming token verification failed\"):  \n        reason = msg.rsplit(\":\", 1)[-1].strip()  \n        if \"expired\" in reason:  \n            token = refresh_access_token()  \n            return retry_with(token)  \n        log_and_surface_auth_diagnostic(reason)  \n    raise","preventionTips":["Keep proxy host clocks NTP-synced to avoid spurious exp/iat failures","Match verify_issuer/verify_audience against decoded tokens, not against documentation","Ensure egress from the proxy to the discovery and JWKS URLs is allowed (firewall, TLS, DNS)","Refresh tokens before expiry in clients so verification never sees an expired token"],"tags":["mcp","jwt","authentication","http-401","guardrail"],"backgroundTag":"jwt-verification-failed","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}