Kuberwastaken/claurst · warning · anyhow::Error

Failed to parse OAuth callback URL

Error message

Failed to parse OAuth callback URL '{}': {}

What it means

Thrown when the URL reconstructed from the callback request line ('http://{host}{path}') fails url::Url::parse. This happens when the request line lacks a path (split_whitespace().nth(1) yields None and "" is used) or the target contains characters invalid in a URL.

Solutions

  1. Retry the flow and ensure a real browser performs the OAuth redirect
  2. Keep the callback bound to 127.0.0.1 (not 0.0.0.0) so only local clients can hit it
  3. Inspect what is connecting to the port (netstat/lsof) if this recurs — likely a scanner or misconfigured client
  4. Verify no proxy rewrites the request target into a form the parser rejects

Example fix

// before: bind exposed to LAN scanners
TcpListener::bind(("0.0.0.0", 8090))
// after
TcpListener::bind(("127.0.0.1", 8090))
Defensive patterns

Strategy: retry

Try / catch

Err(e) if e.to_string().contains("parse OAuth callback URL") => {
    eprintln!("non-HTTP client hit the callback port; retrying loopback-only");
    run_mcp_auth_flow(server).await
}

Prevention

When it happens

Trigger: wait_for_authorization_code receives a malformed request line — e.g. a bare 'GET' with no target, or a target containing raw spaces/control characters so the parsed URL is invalid.

Common situations: Non-browser clients (curl probes, health checks, port scanners) hitting the callback port and sending non-standard request lines; a proxy sending an absolute-form target; stray bot traffic.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


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

Appendix: source

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

    let mut request_line = String::new();
    reader
        .read_line(&mut request_line)
        .await
        .map_err(|e| anyhow::anyhow!("Failed to read OAuth callback request: {}", e))?;
    loop {
        let mut header = String::new();
        reader
            .read_line(&mut header)
            .await
            .map_err(|e| anyhow::anyhow!("Failed to read OAuth callback headers: {}", e))?;
        if header.trim().is_empty() {
            break;
        }
    }

    let path = request_line.split_whitespace().nth(1).unwrap_or("");
    let parsed_url = url::Url::parse(&format!("http://{}{}", host, path))
        .map_err(|e| anyhow::anyhow!("Failed to parse OAuth callback URL '{}': {}", path, e))?;

    let response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\nMCP OAuth authentication finished. You can close this tab.\r\n";
    writer
        .write_all(response.as_bytes())
        .await
        .map_err(|e| anyhow::anyhow!("Failed to write OAuth callback response: {}", e))?;

    if parsed_url.path() != callback_path {
        anyhow::bail!(
            "OAuth callback path mismatch: expected '{}', got '{}'",
            callback_path,
            parsed_url.path()
        );
    }

    if let Some(expected_state) = expected_state {
        let received_state = parsed_url
            .query_pairs()

View on GitHub (pinned to b0637c97ec)