{"record":{"id":"8592503257bc72a4","repo":"BerriAI/litellm","slug":"mcpjwtsigner-incoming-token-is-missing-required-c","errorCode":null,"errorMessage":"MCPJWTSigner: incoming token is missing required claims: {missing}. Configure the IdP to include these claims.","messagePattern":"MCPJWTSigner: incoming token is missing required claims: (.+?)\\. Configure the IdP to include these claims\\.","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"error","filePath":"litellm/proxy/guardrails/guardrail_hooks/mcp_jwt_signer/mcp_jwt_signer.py","lineNumber":527,"sourceCode":"    # FR-15: Incoming claim validation\n    # ------------------------------------------------------------------\n\n    def _validate_required_claims(\n        self,\n        jwt_claims: Mapping[str, object] | None,\n    ) -> None:\n        \"\"\"\n        Raise HTTP 403 if any required_claims are absent from the verified\n        incoming token claims.\n        \"\"\"\n        if not self.required_claims:\n            return\n\n        from fastapi import HTTPException\n\n        missing: Final = [c for c in self.required_claims if not (jwt_claims or {}).get(c)]\n        if missing:\n            raise HTTPException(\n                status_code=403,\n                detail={\n                    \"error\": (\n                        f\"MCPJWTSigner: incoming token is missing required claims: \"\n                        f\"{missing}. Configure the IdP to include these claims.\"\n                    )\n                },\n            )\n\n    # ------------------------------------------------------------------\n    # FR-12: End-user identity mapping\n    # ------------------------------------------------------------------\n\n    def _resolve_end_user_identity(\n        self,\n        user_api_key_dict: UserAPIKeyAuth,\n        jwt_claims: Mapping[str, object] | None,\n    ) -> str:","sourceCodeStart":509,"sourceCodeEnd":545,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/guardrails/guardrail_hooks/mcp_jwt_signer/mcp_jwt_signer.py#L509-L545","documentation":"After successfully verifying an incoming token, MCPJWTSigner checks that every entry in required_claims is present and truthy in the verified claims; any missing claim yields HTTPException 403 naming the missing claims. This is an authorization policy check - the token is authentic but lacks required identity attributes.","triggerScenarios":"An MCP request whose verified JWT/opaque-token claims omit one of the configured required_claims - e.g. required_claims: [email, org_id] but the IdP-issued token only contains sub and scope.","commonSituations":"IdP access tokens that carry only standard claims while the policy expects custom ones (org_id, team); claim names cased differently (OrgID vs org_id); scope-only machine tokens with no user identity claims; required_claims copied from another IdP's claim vocabulary.","solutions":["Configure the IdP to include the required claims in access tokens (add claim to token via custom claims/scope mapping)","Align required_claims with names the IdP actually emits - check casing and spelling against a decoded token","Trim required_claims to what the token realistically contains, moving strict requirements elsewhere (e.g. scopes)"],"exampleFix":"# before - requires a claim the IdP never emits\nlitellm_params:\n  required_claims: [email, org_id]\n\n# after - matches the IdP's emitted claims\nlitellm_params:\n  required_claims: [sub, email]","handlingStrategy":"try-catch","validationCode":"import jwt as pyjwt  \n  \ndef claims_cover_required(token: str, required: list[str]) -> bool:  \n    claims = pyjwt.decode(token, options={\"verify_signature\": False})  \n    return all(claims.get(c) for c in required)  \n  \nassert claims_cover_required(sample_token, required_claims)  # validate config against a real token before rollout","typeGuard":null,"tryCatchPattern":"import openai  \n  \ntry:  \n    resp = client.responses.create(model=deployment, tools=mcp_tools, input=prompt)  \nexcept openai.PermissionDeniedError as e:  \n    msg = getattr(e, \"body\", {}).get(\"error\", \"\") if isinstance(getattr(e, \"body\", None), dict) else str(e)  \n    if \"missing required claims\" in msg:  \n        return guide_user_to_token_with_full_claims()  \n    raise","preventionTips":["Decode a real token from your IdP and copy required_claims verbatim from its claim names","Watch casing - claim names are case-sensitive","Prefer scope-based policies over custom claims when the IdP cannot mint custom attributes"],"tags":["mcp","jwt","claims","authorization","http-403"],"backgroundTag":"jwt-missing-claims","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","schemaVersion":2},"datasetVersion":"2026-08-25T06:17:31.827Z"}