{"record":{"id":"ffdff804f5ac423f","repo":"screenpipe/screenpipe","slug":"invalid-mcp-resource-url","errorCode":null,"errorMessage":"invalid MCP resource URL: {}","messagePattern":"invalid MCP resource URL: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/screenpipe-connect/src/mcp_servers.rs","lineNumber":1124,"sourceCode":"/// Note: do NOT use [`random_url_token`] here — it base64-encodes a 128-char\n/// hex string, yielding a 171-char verifier. Strict token endpoints (e.g.\n/// Krisp) validate the length and reject the exchange with\n/// `400 invalid_request: Invalid parameter: code_verifier`.\nfn pkce_verifier() -> String {\n    let mut raw = [0u8; 32];\n    raw[..16].copy_from_slice(uuid::Uuid::new_v4().as_bytes());\n    raw[16..].copy_from_slice(uuid::Uuid::new_v4().as_bytes());\n    base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(raw)\n}\n\nfn pkce_challenge(verifier: &str) -> String {\n    let digest = Sha256::digest(verifier.as_bytes());\n    base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(digest)\n}\n\nfn protected_resource_metadata_url(resource: &str) -> Result<Vec<String>> {\n    let resource_url =\n        reqwest::Url::parse(resource).map_err(|e| anyhow!(\"invalid MCP resource URL: {}\", e))?;\n    let mut urls = Vec::new();\n\n    // Origin-based URL first — this is what Notion's guide and RFC 9470 recommend:\n    // new URL(\"/.well-known/oauth-protected-resource\", serverUrl) resolves to the\n    // origin, not the path. Try this first to avoid spurious auth challenges on\n    // sub-path variants.\n    let mut origin = resource_url.clone();\n    origin.set_path(\"/.well-known/oauth-protected-resource\");\n    origin.set_query(None);\n    origin.set_fragment(None);\n    urls.push(origin.to_string());\n\n    // RFC path variant (path component embedded after the well-known prefix).\n    let original_path = resource_url\n        .path()\n        .trim_start_matches('/')\n        .trim_end_matches('/');\n    if !original_path.is_empty() {","sourceCodeStart":1106,"sourceCodeEnd":1142,"githubUrl":"https://github.com/screenpipe/screenpipe/blob/4ebf712990fee17eeaf904dacf749b6e96ac9bf3/crates/screenpipe-connect/src/mcp_servers.rs#L1106-L1142","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Fix the resource field in the MCP server config to be an absolute URL including scheme (e.g. https://mcp.notion.com/mcp)","Validate/normalize the URL (prepend https:// if scheme missing, trim whitespace) when saving config","Verify no quoting/escape artifacts got persisted with the resource string"],"exampleFix":"// before: bare-host resource fails Url::parse\nlet urls = protected_resource_metadata_url(\"mcp.notion.com\")?;\n// after: normalize before calling\nlet resource = \"mcp.notion.com\";\nlet normalized = if resource.contains(\"://\") { resource.to_string() } else { format!(\"https://{resource}\") };\nlet urls = protected_resource_metadata_url(normalized.trim())?;","handlingStrategy":"validation","validationCode":"fn valid_resource_url(s: &str) -> bool {\n    reqwest::Url::parse(s.trim())\n        .map(|u| u.scheme().starts_with(\"http\"))\n        .unwrap_or(false)\n}\n// call only if valid_resource_url(&cfg.resource)","typeGuard":"fn parse_resource(s: &str) -> Option<reqwest::Url> {\n    let s = s.trim();\n    let s = if s.contains(\"://\") { s.to_string() } else { format!(\"https://{s}\") };\n    reqwest::Url::parse(&s).ok().filter(|u| u.scheme().starts_with(\"http\"))\n}","tryCatchPattern":"match discovery_result {\n    Err(e) if e.to_string().contains(\"invalid MCP resource URL\") => {\n        // normalize: trim + prepend scheme, then retry discovery\n        let fixed = normalize_url(&cfg.resource);\n        discovery_with_resource(&fixed).await\n    }\n    other => other,\n}","preventionTips":["Always store resource as a full absolute URL with scheme","Normalize (trim, prepend https://) at config-save time","Add a config validator that runs Url::parse on resource before persisting","Beware copy-paste artifacts (quotes, spaces, missing scheme) in URL fields"],"tags":["oauth","url","validation","mcp"],"backgroundTag":"invalid-url-format","analyzedSha":"4ebf712990fee17eeaf904dacf749b6e96ac9bf3","analyzedAt":"2026-09-01T23:33:43.065Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T06:17:21.866Z"}