zed-industries/zed · error

WWW-Authenticate header does not use Bearer scheme

Error message

WWW-Authenticate header does not use Bearer scheme

What it means

parse_www_authenticate() parses the WWW-Authenticate header that an MCP server returns on 401 to bootstrap OAuth discovery. It accepts only the Bearer scheme (RFC 6750): the header must be at least 6 bytes long and match 'bearer' case-insensitively in its first 6 bytes, after which everything up to the parameter list is expected. Any other scheme or a shorter header triggers this bail before parameters like resource_metadata are ever looked at.

Source

Thrown at crates/context_server/src/oauth.rs:281

    pub error: Option<BearerError>,
    pub error_description: Option<String>,
}

/// Parse a `WWW-Authenticate` header value.
///
/// Expects the `Bearer` scheme followed by comma-separated `key="value"` pairs.
/// Per RFC 6750 and RFC 9728, the relevant parameters are:
/// - `resource_metadata` — URL of the Protected Resource Metadata document
/// - `scope` — space-separated list of required scopes
/// - `error` — error code (e.g. "insufficient_scope")
/// - `error_description` — human-readable error description
pub fn parse_www_authenticate(header: &str) -> Result<WwwAuthenticate> {
    let header = header.trim();

    let params_str = if header.len() >= 6 && header[..6].eq_ignore_ascii_case("bearer") {
        header[6..].trim()
    } else {
        bail!("WWW-Authenticate header does not use Bearer scheme");
    };

    if params_str.is_empty() {
        return Ok(WwwAuthenticate {
            resource_metadata: None,
            scope: None,
            error: None,
            error_description: None,
        });
    }

    let params = parse_auth_params(params_str);

    let resource_metadata = params
        .get("resource_metadata")
        .map(|v| Url::parse(v))
        .transpose()
        .map_err(|e| anyhow!("invalid resource_metadata URL: {}", e))?;

View on GitHub (pinned to f4178619ac)

Solutions

  1. Configure the MCP server to return 401 with 'WWW-Authenticate: Bearer resource_metadata="https://server/.well-known/oauth-protected-resource"' as required by MCP authorization / RFC 9728
  2. Remove or bypass any intermediary (proxy, gateway) that rewrites or replaces the WWW-Authenticate header with a Basic/Digest challenge
  3. If the server only supports static API keys, use the header-based auth mode of the MCP server config instead of OAuth
  4. Check the raw 401 response headers with curl to confirm what scheme is actually being sent before blaming the client

Example fix

# before (server response)
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="mcp"

# after
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource"
Defensive patterns

Strategy: validation

Validate before calling

fn is_bearer_challenge(header: &str) -> bool {
    let h = header.trim();
    h.len() >= 6 && h[..6].eq_ignore_ascii_case("bearer")
}

// before parsing a 401 response's challenge:
let header = response.headers().get("WWW-Authenticate")
    .and_then(|v| v.to_str().ok())
    .unwrap_or("");
if !is_bearer_challenge(header) {
    // server does not implement MCP OAuth discovery; fall back to another auth mode
    return choose_static_auth_mode();
}
let challenge = parse_www_authenticate(header)?;

Type guard

fn is_bearer_challenge(header: &str) -> bool {
    let h = header.trim();
    h.len() >= 6 && h[..6].eq_ignore_ascii_case("bearer")
}

Try / catch

match parse_www_authenticate(&header_value) {
    Ok(challenge) => start_oauth_discovery(challenge),
    Err(err) if err.to_string().contains("does not use Bearer scheme") => {
        // not an OAuth-capable MCP server — do not retry OAuth; pick another auth strategy
        fall_back_to_static_credentials()
    }
    Err(err) => Err(err),
}

Prevention

When it happens

Trigger: Calling parse_www_authenticate(header) where header is e.g. 'Basic realm="x"', 'Digest realm=...', 'BearerX ...' (no space, scheme longer than 6 chars so header[6..] logic still runs but for Basic/Digest it fails outright), an empty string, or a header with leading garbage. The real trigger in the flow is an MCP server answering 401 with a non-Bearer WWW-Authenticate header, meaning it does not implement the RFC 9728 protected-resource-metadata discovery that Zed's MCP OAuth flow requires.

Common situations: MCP server behind a corporate proxy that injects its own Basic-auth challenge instead of the server's Bearer challenge; a server that implements only static API-key auth (returns WWW-Authenticate: ApiKey or nothing useful); header truncation or corruption in an intermediary; the server returns 'Bearer' alone with no parameters, which is fine — only non-Bearer schemes fail here.

Understand the failure class

Related errors


AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20). Data as JSON: /api/errors/5ab7cee37af0ba9a. Report an issue: GitHub.