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
- 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
- Remove or bypass any intermediary (proxy, gateway) that rewrites or replaces the WWW-Authenticate header with a Basic/Digest challenge
- If the server only supports static API keys, use the header-based auth mode of the MCP server config instead of OAuth
- 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
- Configure MCP servers meant for OAuth to answer 401 with a Bearer challenge carrying resource_metadata
- Keep proxies from overwriting WWW-Authenticate on 401 responses
- When integrating non-OAuth servers, use the static header auth config so discovery is never attempted
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- OAuth endpoint must use HTTPS (got {}://{})
- OAuth endpoint must not point to private/reserved IP: {}
- OAuth endpoint must not point to private/reserved IP: ::ffff
- OAuth endpoint must not point to reserved IPv6 address: {}
- OAuth endpoint must not point to IPv6 unique-local address:
AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20).
Data as JSON: /api/errors/5ab7cee37af0ba9a.
Report an issue: GitHub.