{"record":{"id":"8a7d393a2d7be2c5","repo":"gitbutlerapp/gitbutler","slug":"mcp-root-is-not-a-file-uri","errorCode":null,"errorMessage":"MCP root is not a file URI: {}","messagePattern":"MCP root is not a file URI: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but/src/command/mcp/mod.rs","lineNumber":545,"sourceCode":"            }\n        };\n        match open_repository(&path) {\n            Ok(resolved) => return Ok(resolved),\n            Err(err) => failures.push(format!(\"{}: {err:#}\", path.display())),\n        }\n    }\n\n    bail!(\n        \"None of the MCP client's filesystem roots identify a Git repository. {}\",\n        failures.join(\"; \")\n    )\n}\n\nfn root_path(root: &Root) -> Result<PathBuf> {\n    let url = Url::parse(&root.uri)\n        .with_context(|| format!(\"MCP root is not a valid URI: {}\", root.uri))?;\n    url.to_file_path()\n        .map_err(|()| anyhow::anyhow!(\"MCP root is not a file URI: {}\", root.uri))\n}\n\nfn open_repository(repository: &Path) -> Result<ResolvedRepository> {\n    let repository = repository\n        .canonicalize()\n        .with_context(|| format!(\"Could not resolve repository at {}\", repository.display()))?;\n    let ctx = but_ctx::Context::discover(&repository)\n        .with_context(|| format!(\"Could not open repository at {}\", repository.display()))?;\n    let repository = ctx\n        .workdir_or_gitdir()\n        .context(\"Could not determine the repository root\")?\n        .canonicalize()\n        .context(\"Could not resolve the repository root\")?;\n    let name = repository\n        .file_name()\n        .and_then(|name| name.to_str())\n        .unwrap_or(\"repository\")\n        .to_owned();","sourceCodeStart":527,"sourceCodeEnd":563,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but/src/command/mcp/mod.rs#L527-L563","documentation":"When the but MCP server resolves the repository from the client's filesystem roots, each root URI is parsed and converted to a local path with Url::to_file_path(). Only file:// URIs can be converted; any other scheme (http, vscode-remote, untitled, ...) fails for that root.","triggerScenarios":"An MCP client sends workspace roots whose URI scheme is not file, e.g. vscode-remote:// for Remote-SSH/devcontainers, untitled:, or http(s) URLs, while but iterates roots to find a git repository.","commonSituations":"IDE remoting (SSH, devcontainer, web IDE) where the workspace folder is not a local path; clients that include virtual documents as roots.","solutions":["Open or attach the local directory in the client so at least one root is a file:// URI","Add the local repository checkout as an additional workspace folder","Run the MCP client on the machine where the repository actually lives"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const isFileRoot = (root: { uri: string }) => root.uri.startsWith('file://');\n// client side: only send local roots\nconst roots = workspaceFolders.filter(isFileRoot);","typeGuard":"const isFileRoot = (r: { uri: string }): r is { uri: `file://${string}` } =>\n  r.uri.startsWith('file://');","tryCatchPattern":"Server side: skip non-file roots with a debug log instead of erroring; only fail with the aggregated 'no repository found' message when no file root identifies a repo (root_path's caller already aggregates failures).","preventionTips":["Send at least one local file:// root from the client","Filter non-file URIs client-side before connecting in remote-IDE setups"],"tags":["mcp","uri","file-scheme","workspace-roots","client-config"],"backgroundTag":"invalid-uri-scheme","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}