BerriAI/litellm · error · HTTPException

token_endpoint_auth_method=client_secret_basic requires both

Error message

token_endpoint_auth_method=client_secret_basic requires both client_id and client_secret

What it means

Pre-flight check for the client_secret_basic token-endpoint auth method: basic auth is mathematically impossible without both halves of the credential pair, so the request is rejected 400 before the upstream exchange. Applies to the caller-supplied client case; gateway-stored clients use their own registered method.

Source

Thrown at litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py:918

    # recovered from a sealed code) must authenticate the way its own registration was granted,
    # not the way the server row is configured; callers that carry no method keep the row's method
    # as before.
    resolved_client_id: Final = mcp_server.client_id if mcp_server.client_id else client_id
    resolved_client_secret: Final = mcp_server.client_secret if mcp_server.client_id else client_secret
    resolved_auth_method: Final = (
        mcp_server.token_endpoint_auth_method
        if mcp_server.client_id
        else (client_token_endpoint_auth_method or mcp_server.token_endpoint_auth_method)
    )
    try:
        token_request: Final = build_upstream_oauth2_token_request(
            mcp_server,
            auth_method=resolved_auth_method,
            client_id=resolved_client_id,
            client_secret=resolved_client_secret,
        )
    except TokenEndpointAuthConfigError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc

    bridge_identity: _BridgeAuthorizationCode | None = None
    bridge_mint_ready: _BridgeMintReady | None = None
    bridge_upstream_refresh: SecretStr | None = None
    bridge_upstream_scope: str | None = None
    refresh_request_scope: str | None = None
    is_bridge: Final = mcp_server.is_oauth_delegate and mcp_server.is_dcr_bridge

    if grant_type == "refresh_token":
        # Phase 1 for a bridge refresh: open the client's refresh envelope, re-validate the sealed
        # identity, and unwrap the real upstream refresh token BEFORE building token_data, so the exchange
        # sends the upstream token and never the envelope. A failure returns without touching the upstream.
        if is_bridge:
            prepared_refresh: Final = await _prepare_bridge_refresh(mcp_server, refresh_token)
            if not isinstance(prepared_refresh, _BridgeRefreshReady):
                return _bridge_mint_error_response(prepared_refresh)
            bridge_mint_ready = prepared_refresh.ready
            bridge_upstream_refresh = prepared_refresh.upstream_refresh_token

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Provide both client_id and client_secret for client_secret_basic authentication.

Example fix

Send Authorization: Basic base64(client_id:client_secret).
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py:918 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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