{"record":{"id":"bf3ba22abac99590","repo":"BerriAI/litellm","slug":"refresh-token-is-required-for-refresh-token-grant","errorCode":null,"errorMessage":"refresh_token is required for refresh_token grant","messagePattern":"refresh_token is required for refresh_token grant","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py","lineNumber":944,"sourceCode":"\n    if grant_type == \"refresh_token\":\n        # Phase 1 for a bridge refresh: open the client's refresh envelope, re-validate the sealed\n        # identity, and unwrap the real upstream refresh token BEFORE building token_data, so the exchange\n        # sends the upstream token and never the envelope. A failure returns without touching the upstream.\n        if is_bridge:\n            prepared_refresh: Final = await _prepare_bridge_refresh(mcp_server, refresh_token)\n            if not isinstance(prepared_refresh, _BridgeRefreshReady):\n                return _bridge_mint_error_response(prepared_refresh)\n            bridge_mint_ready = prepared_refresh.ready\n            bridge_upstream_refresh = prepared_refresh.upstream_refresh_token\n            bridge_upstream_scope = prepared_refresh.upstream_scope\n        # A bridge server sends the unwrapped upstream refresh token recovered from the client's refresh\n        # envelope above; every other server sends the client's own refresh token verbatim.\n        upstream_refresh_token: Final = (\n            bridge_upstream_refresh.get_secret_value() if bridge_upstream_refresh is not None else refresh_token\n        )\n        if not upstream_refresh_token:\n            raise HTTPException(\n                status_code=400,\n                detail=\"refresh_token is required for refresh_token grant\",\n            )\n        token_data: dict = {\n            \"grant_type\": \"refresh_token\",\n            \"refresh_token\": upstream_refresh_token,\n            **token_request.body,\n        }\n        refresh_request_scope = scope or bridge_upstream_scope\n        if refresh_request_scope:\n            token_data[\"scope\"] = refresh_request_scope\n    else:\n        if not code:\n            raise HTTPException(\n                status_code=400,\n                detail=\"code is required for authorization_code grant\",\n            )\n        # Interactive dcr_bridge oauth_delegate: the client presents the gateway authorization code the","sourceCodeStart":926,"sourceCodeEnd":962,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py#L926-L962","documentation":"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.","triggerScenarios":"Thrown at litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py:944 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Include the refresh_token parameter with the refresh_token grant."],"exampleFix":"grant_type=refresh_token&refresh_token=<token>","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}