Hmbown/CodeWhale · error

MCP HTTP redirect would downgrade HTTPS

Error message

MCP HTTP redirect would downgrade HTTPS

What it means

A redirect from an https:// MCP endpoint to a non-https target (http:, or another scheme) is rejected. Downgrading transport security mid-redirect would expose MCP traffic and headers to interception, so the client refuses to follow.

Solutions

  1. Fix the server/proxy to emit https:// redirect targets (set X-Forwarded-Proto correctly, use relative Locations)
  2. Point the client directly at the final https URL, skipping the redirect chain
  3. If the target genuinely only supports http, configure the client against an explicit http:// endpoint at the operator level (accepting the security implications)

Example fix

// before (server config)
return redirect("http://example.com/mcp");
// after
return redirect("https://example.com/mcp"); // or relative "/mcp"
Defensive patterns

Strategy: validation

Validate before calling

let u = Url::parse(endpoint)?;
if u.scheme() != "https" {
    // decide up front: use https endpoint or explicitly configure http
}

Try / catch

if let Err(e) = client.execute(req).await {
    if e.to_string().contains("would downgrade HTTPS") {
        // fix proxy X-Forwarded-Proto / Location scheme server-side
    }
}

Prevention

When it happens

Trigger: execute_inner processes a redirect where the current URL scheme is https but the joined next_url scheme is not https — e.g. Location: http://example.com/mcp.

Common situations: Reverse proxy emitting absolute http:// Location headers behind TLS termination; misconfigured X-Forwarded-Proto so the app generates http redirect URLs; server redirecting to a plain-HTTP health/login endpoint.

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/42f4d69a27259712. Report an issue: GitHub.

Appendix: source

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

            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();
                for name in [header::ACCEPT, header::CONTENT_TYPE] {
                    if let Some(value) = request.headers().get(&name) {
                        headers.insert(name, value.clone());

View on GitHub (pinned to 73e0f67d83)