BerriAI/litellm · error · HTTPException

challenge.body if challenge.body is not None else error.summ

Error message

challenge.body if challenge.body is not None else error.summary

What it means

raise_public maps an outbound-credential resolution failure tagged unauthorized onto the proxy's public contract: HTTP 401 whose detail is the challenge body (or the CredError summary) and whose WWW-Authenticate header relays the upstream challenge. It fires when the proxy must authenticate to an upstream MCP server (per-user OAuth, delegated auth, or token exchange) and the stored credential was rejected by the upstream IdP or server.

Source

Thrown at litellm/proxy/_experimental/mcp_server/outbound_credentials/adapter.py:273

        return ClientSecretAuth(client_secret=SecretStr(server.client_secret))
    return None


def _id_jag_subject_token_type(server: MCPServer) -> str:
    """ID-JAG asserts the user's id_token, so the token-exchange access_token default maps to id_token;
    an explicitly configured value (e.g. a SAML2 assertion type) is honored verbatim."""
    configured: Final = server.subject_token_type
    if configured and configured != _TOKEN_EXCHANGE_SUBJECT_TOKEN_DEFAULT:
        return configured
    return _ID_JAG_SUBJECT_TOKEN_DEFAULT


def raise_public(error: CredError) -> NoReturn:
    """Map a resolver CredError onto the proxy's public HTTP contract. The one edge that raises."""
    match error.tag:
        case "unauthorized":
            challenge: Final = error.unauthorized
            raise HTTPException(
                status_code=401,
                detail=challenge.body if challenge.body is not None else error.summary,
                headers=({"WWW-Authenticate": challenge.www_authenticate} if challenge.www_authenticate else None),
            )
        case "misconfigured":
            raise HTTPException(status_code=500, detail=error.summary)
        case "upstream_unavailable":
            raise HTTPException(status_code=503, detail=error.summary)
        case "unsupported_mode":
            raise HTTPException(status_code=500, detail=error.summary)
        case "precondition_required":
            raise HTTPException(status_code=412, detail=error.summary)
        case "not_implemented":
            raise HTTPException(status_code=501, detail=error.summary)
    assert_never(error.tag)


def oauth_protected_resource_path(root_path: str, server: MCPServer) -> str:

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Re-authenticate: follow the WWW-Authenticate challenge on the 401 (it points at the server's RFC 9728 protected-resource metadata) and run the OAuth flow to mint fresh credentials.
  2. If re-auth keeps failing, delete the stored credential (per-user/BYOK OAuth UI or DB) and re-consent from scratch.
  3. For token-exchange servers, verify the gateway's client credentials and audience/scope - an IdP rejecting the gateway itself is a different code path (500), but a mis-scoped user token shows up here too.
Defensive patterns

Strategy: try-catch

Try / catch

resp = await client.post(f"{proxy}/mcp/tool-call", json=payload, headers=headers)
if resp.status_code == 401:
    challenge = resp.headers.get("www-authenticate", "")
    if "resource_metadata=" in challenge:
        await reauthorize_upstream(challenge)  # run the advertised OAuth flow, store fresh creds
        resp = await client.post(f"{proxy}/mcp/tool-call", json=payload, headers=headers)
resp.raise_for_status()

Prevention

When it happens

Trigger: Calling an MCP tool or the tools/list REST facade for a server with delegated/per-user upstream OAuth where the user's or key's tokens are expired, revoked, or invalid; the token endpoint or upstream returned 401/invalid_grant during resolve_credentials; the challenge is relayed verbatim so standards-compliant MCP clients can start the upstream OAuth flow.

Common situations: Per-user OAuth tokens gone stale (IdP rotation window passed, user inactive); user or admin revoked consent for the OAuth app; refresh-token rotation invalidated the stored token; upstream API keys rotated without updating the stored credential.

Related errors


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