Kuberwastaken/claurst · error

refresh: HTTP

Error message

refresh: HTTP {} — {}

What it means

Refreshing an MCP OAuth token via the refresh_token grant returned a non-success HTTP status. The library includes the status and response body so the provider's error (commonly invalid_grant) is visible. On this failure the cached token cannot be renewed and re-authorization is required.

Solutions

  1. Re-run the MCP OAuth login flow for the server to obtain a new refresh token
  2. Check the response body for invalid_grant and, if present, treat stored tokens as revoked
  3. Verify client_id/client_secret and token endpoint URL are still correct
  4. For 5xx responses, retry after confirming the provider is healthy
Defensive patterns

Strategy: fallback

Try / catch

match get_valid_mcp_token(server).await {
    Err(e) if e.to_string().starts_with("refresh: HTTP") && e.to_string().contains("invalid_grant") => {
        // clear stored tokens and trigger interactive re-auth
    }
    other => other?,
}

Prevention

When it happens

Trigger: refresh_mcp_token (called by get_valid_mcp_token when the stored access token is expired) POSTs the refresh token and receives a non-2xx status.

Common situations: Refresh token revoked or expired (provider rotation invalidates old tokens); user revoked the app in provider settings; client credentials changed; token endpoint URL wrong or provider returning 5xx.

Related errors


AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10). Data as JSON: /api/errors/e4210e081b23ff24. Report an issue: GitHub.

Appendix: source

Thrown at src-rust/crates/mcp/src/oauth.rs:544

        .refresh_token
        .as_deref()
        .ok_or_else(|| anyhow::anyhow!("Token for {} has no refresh token", server_name))?
        .to_string();

    let client = reqwest::Client::new();
    let params = [("grant_type", "refresh_token"), ("refresh_token", refresh.as_str())];

    let resp = client
        .post(token_endpoint)
        .form(&params)
        .send()
        .await
        .map_err(|e| anyhow::anyhow!("refresh: request failed: {}", e))?;

    if !resp.status().is_success() {
        let status = resp.status();
        let body = resp.text().await.unwrap_or_default();
        anyhow::bail!("refresh: HTTP {} — {}", status, body);
    }

    #[derive(serde::Deserialize)]
    struct TokenResponse {
        access_token: String,
        refresh_token: Option<String>,
        expires_in: Option<u64>,
    }

    let tr: TokenResponse = resp.json().await?;
    let expires_at = tr.expires_in.map(|s| {
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs()
            + s
    });

View on GitHub (pinned to b0637c97ec)