Hmbown/CodeWhale · error

MCP HTTP redirect must not contain credentials

Error message

MCP HTTP redirect must not contain credentials

What it means

When following an MCP HTTP redirect, the target Location URL is checked for embedded credentials. If a server redirects to a URL containing user:password@, the client refuses with this error to prevent credential handling on untrusted redirect targets.

Solutions

  1. Fix the server to redirect to a credential-free URL and authenticate via headers at the target
  2. Redirect to the bare origin/path and send the Authorization header on the follow-up request
  3. If you control the hop, remove userinfo from the Location header
Defensive patterns

Strategy: validation

Validate before calling

fn redirect_target_is_clean(base: &Url, location: &str) -> bool {
    base.join(location).ok()
        .map(|u| !(!u.username().is_empty() || u.password().is_some()))
        .unwrap_or(false)
}

Try / catch

if let Err(e) = client.execute(req).await {
    if e.to_string().contains("redirect must not contain credentials") {
        // server-side fix required: strip userinfo from Location
    }
}

Prevention

When it happens

Trigger: execute_inner follows a 3xx response whose Location header parses (after url.join) to a URL with a non-empty username or a password, i.e. url_has_credentials(&next_url) is true.

Common situations: A misbehaving or compromised server redirecting to an auth-embedded URL; a staging gateway that bakes basic-auth into redirect targets; a hand-written Location header copied from a curl example.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/9a416c197afa3f6b. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/mcp/http_client.rs:131

            let next_request = request
                .try_clone()
                .context("MCP request body cannot be replayed")?;
            let response = client.execute(request).await?;
            if !follow_redirects
                || !matches!(response.status().as_u16(), 301 | 302 | 303 | 307 | 308)
            {
                return Ok(response);
            }
            let Some(location) = response.headers().get(header::LOCATION) else {
                return Ok(response);
            };
            if redirect_count == 5 {
                bail!("MCP HTTP redirect limit exceeded");
            }
            let next_url = url.join(location.to_str().context("invalid MCP redirect Location")?)?;
            validate_url(&next_url)?;
            if url_has_credentials(&next_url) {
                bail!("MCP HTTP redirect must not contain credentials");
            }
            if url.scheme() == "https" && next_url.scheme() != "https" {
                bail!("MCP HTTP redirect would downgrade HTTPS");
            }
            request = next_request;
            if (matches!(response.status().as_u16(), 301 | 302) && request.method() == Method::POST)
                || (response.status().as_u16() == 303 && request.method() != Method::HEAD)
            {
                *request.method_mut() = Method::GET;
                *request.body_mut() = None;
                request.headers_mut().remove(header::CONTENT_TYPE);
                request.headers_mut().remove(header::CONTENT_LENGTH);
                request.headers_mut().remove(header::TRANSFER_ENCODING);
            }
            if next_url.origin() != url.origin() {
                // Custom headers can contain credentials under arbitrary names;
                // retaining just Authorization/ Cookie exclusions is insufficient.
                let mut headers = header::HeaderMap::new();

View on GitHub (pinned to 73e0f67d83)