screenpipe/screenpipe · error

invalid MCP resource URL: {}

Error message

invalid MCP resource URL: {}

What it means

protected_resource_metadata_url parses the server's resource string as a reqwest::Url to derive well-known protected-resource metadata URLs; a parse failure produces this error wrapping the url::ParseError. The resource must be an absolute, valid URL for RFC 9728/9470 discovery to work.

Source

Thrown at crates/screenpipe-connect/src/mcp_servers.rs:1124

/// Note: do NOT use [`random_url_token`] here — it base64-encodes a 128-char
/// hex string, yielding a 171-char verifier. Strict token endpoints (e.g.
/// Krisp) validate the length and reject the exchange with
/// `400 invalid_request: Invalid parameter: code_verifier`.
fn pkce_verifier() -> String {
    let mut raw = [0u8; 32];
    raw[..16].copy_from_slice(uuid::Uuid::new_v4().as_bytes());
    raw[16..].copy_from_slice(uuid::Uuid::new_v4().as_bytes());
    base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(raw)
}

fn pkce_challenge(verifier: &str) -> String {
    let digest = Sha256::digest(verifier.as_bytes());
    base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(digest)
}

fn protected_resource_metadata_url(resource: &str) -> Result<Vec<String>> {
    let resource_url =
        reqwest::Url::parse(resource).map_err(|e| anyhow!("invalid MCP resource URL: {}", e))?;
    let mut urls = Vec::new();

    // Origin-based URL first — this is what Notion's guide and RFC 9470 recommend:
    // new URL("/.well-known/oauth-protected-resource", serverUrl) resolves to the
    // origin, not the path. Try this first to avoid spurious auth challenges on
    // sub-path variants.
    let mut origin = resource_url.clone();
    origin.set_path("/.well-known/oauth-protected-resource");
    origin.set_query(None);
    origin.set_fragment(None);
    urls.push(origin.to_string());

    // RFC path variant (path component embedded after the well-known prefix).
    let original_path = resource_url
        .path()
        .trim_start_matches('/')
        .trim_end_matches('/');
    if !original_path.is_empty() {

View on GitHub (pinned to 4ebf712990)

Solutions

  1. Fix the resource field in the MCP server config to be an absolute URL including scheme (e.g. https://mcp.notion.com/mcp)
  2. Validate/normalize the URL (prepend https:// if scheme missing, trim whitespace) when saving config
  3. Verify no quoting/escape artifacts got persisted with the resource string

Example fix

// before: bare-host resource fails Url::parse
let urls = protected_resource_metadata_url("mcp.notion.com")?;
// after: normalize before calling
let resource = "mcp.notion.com";
let normalized = if resource.contains("://") { resource.to_string() } else { format!("https://{resource}") };
let urls = protected_resource_metadata_url(normalized.trim())?;
Defensive patterns

Strategy: validation

Validate before calling

fn valid_resource_url(s: &str) -> bool {
    reqwest::Url::parse(s.trim())
        .map(|u| u.scheme().starts_with("http"))
        .unwrap_or(false)
}
// call only if valid_resource_url(&cfg.resource)

Type guard

fn parse_resource(s: &str) -> Option<reqwest::Url> {
    let s = s.trim();
    let s = if s.contains("://") { s.to_string() } else { format!("https://{s}") };
    reqwest::Url::parse(&s).ok().filter(|u| u.scheme().starts_with("http"))
}

Try / catch

match discovery_result {
    Err(e) if e.to_string().contains("invalid MCP resource URL") => {
        // normalize: trim + prepend scheme, then retry discovery
        let fixed = normalize_url(&cfg.resource);
        discovery_with_resource(&fixed).await
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling the OAuth discovery path for an MCP server whose stored resource value is malformed: missing scheme ("notion.com/mcp"), fully empty, containing spaces/illegal characters, or a relative path.

Common situations: Config saved from a form where the resource URL was typed without https://; copy-paste dropping the scheme; server config migration introducing a placeholder resource value; trailing junk like quotes or whitespace in the stored string.

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 screenpipe/screenpipe@4ebf712990 (2026-09-01). Data as JSON: /api/errors/ffdff804f5ac423f. Report an issue: GitHub.