{"record":{"id":"f463e6e4c8f4cbc7","repo":"PrefectHQ/fastmcp","slug":"oauth-token-endpoint-error-token-error-tok","errorCode":null,"errorMessage":"OAuth token endpoint error: {token['error']}: {token.get('error_description')}","messagePattern":"OAuth token endpoint error: (.+?): (.+?)","errorType":"http","errorClass":"OAuthError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/auth/oauth_proxy/upstream.py","lineNumber":94,"sourceCode":"        elif method == \"none\":\n            data[\"client_id\"] = self.client_id\n        else:\n            raise ValueError(\n                f\"Unsupported token_endpoint_auth_method: {method!r}. \"\n                \"Supported methods: client_secret_basic, client_secret_post, none.\"\n            )\n\n    async def _request_token(self, url: str, data: dict[str, Any]) -> dict[str, Any]:\n        headers = dict(_DEFAULT_TOKEN_HEADERS)\n        self._apply_client_auth(data, headers)\n\n        response = await self._client.post(url, data=data, headers=headers)\n        if response.status_code >= 500:\n            response.raise_for_status()\n\n        token: dict[str, Any] = response.json()\n        if \"error\" in token:\n            raise OAuthError(\n                error=token[\"error\"], description=token.get(\"error_description\")\n            )\n\n        # Mirror authlib's OAuth2Token: derive expires_at from expires_in so\n        # the stored raw token data keeps the same shape as before.\n        if token.get(\"expires_at\") is not None:\n            try:\n                token[\"expires_at\"] = int(token[\"expires_at\"])\n            except ValueError:\n                if token.get(\"expires_in\"):\n                    token[\"expires_at\"] = int(time.time()) + int(token[\"expires_in\"])\n        elif token.get(\"expires_in\"):\n            token[\"expires_at\"] = int(time.time()) + int(token[\"expires_in\"])\n\n        return token\n\n    async def fetch_token(\n        self,","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/oauth_proxy/upstream.py#L76-L112","documentation":"The upstream OAuth provider's token endpoint responded with a JSON body containing an 'error' field (standard RFC 6749 error response). The library wraps it in OAuthError preserving the provider's error code and optional description, e.g. invalid_grant or invalid_client.","triggerScenarios":"Any token endpoint call — authorization-code exchange (fetch_token) or refresh_token — where the provider returns 200/4xx with {\"error\": ...}: expired/revoked refresh tokens, mismatched redirect_uri, wrong client credentials, or invalid authorization codes.","commonSituations":"Refresh token rotated/expired by the provider; client secret changed in the provider dashboard; redirect URI mismatch between config and provider settings; clock skew invalidating codes.","solutions":["Read the wrapped error/description to identify the provider's specific code (e.g. invalid_grant => re-authenticate the user)","If invalid_grant on refresh, clear the stored tokens and redirect the user through the authorization flow again","Verify client_id, client_secret, and redirect_uri exactly match the provider app configuration","Check provider status/logs — some errors indicate provider-side revocation or outage"],"exampleFix":"// before: blindly reusing a rotated refresh token\nawait client.refresh_token(refresh_token=old_token)\n// after: handle invalid_grant by full re-auth\ntry:\n    await client.refresh_token(refresh_token=old_token)\nexcept OAuthError as e:\n    if e.error == \"invalid_grant\":\n        start_authorization_flow(user)","handlingStrategy":"try-catch","validationCode":"# Pre-flight: verify provider credentials before serving traffic\nresp = await httpx_client.get(f\"{issuer}/.well-known/openid-configuration\")\nresp.raise_for_status()","typeGuard":null,"tryCatchPattern":"try:\n    token = await client.refresh_token(refresh_token=rt)\nexcept OAuthError as e:\n    if e.error in {\"invalid_grant\", \"invalid_token\"}:\n        clear_stored_tokens(user)\n        return redirect_to_authorization(user)\n    raise  # invalid_client etc: config problem, do not retry","preventionTips":["Treat invalid_grant as 're-authenticate', not 'retry'","Keep client_id/secret/redirect_uri in sync with provider app settings","Refresh tokens proactively before expiry; assume refresh tokens may rotate"],"tags":["oauth","network","token-endpoint","upstream-provider"],"backgroundTag":"oauth-token-endpoint-error","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}