BerriAI/litellm · error · HTTPException

refresh_token is required for refresh_token grant

Error message

refresh_token is required for refresh_token grant

What it means

Raised in the MCP OAuth2 token endpoint when grant_type is refresh_token but the request body carries no refresh_token (or, for a bridge server, the sealed envelope failed to unwrap into a usable upstream token). The endpoint cannot proxy a refresh exchange without a token to refresh, so it rejects with HTTP 400 before contacting the upstream server.

Source

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

    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
            bridge_upstream_scope = prepared_refresh.upstream_scope
        # A bridge server sends the unwrapped upstream refresh token recovered from the client's refresh
        # envelope above; every other server sends the client's own refresh token verbatim.
        upstream_refresh_token: Final = (
            bridge_upstream_refresh.get_secret_value() if bridge_upstream_refresh is not None else refresh_token
        )
        if not upstream_refresh_token:
            raise HTTPException(
                status_code=400,
                detail="refresh_token is required for refresh_token grant",
            )
        token_data: dict = {
            "grant_type": "refresh_token",
            "refresh_token": upstream_refresh_token,
            **token_request.body,
        }
        refresh_request_scope = scope or bridge_upstream_scope
        if refresh_request_scope:
            token_data["scope"] = refresh_request_scope
    else:
        if not code:
            raise HTTPException(
                status_code=400,
                detail="code is required for authorization_code grant",
            )
        # Interactive dcr_bridge oauth_delegate: the client presents the gateway authorization code the

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Include the refresh_token parameter with the refresh_token grant.

Example fix

grant_type=refresh_token&refresh_token=<token>
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py:944 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/bf3ba22abac99590. Report an issue: GitHub.