gitbutlerapp/gitbutler · error · anyhow::Error

MCP root is not a file URI: {}

Error message

MCP root is not a file URI: {}

What it means

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.

Source

Thrown at crates/but/src/command/mcp/mod.rs:545

            }
        };
        match open_repository(&path) {
            Ok(resolved) => return Ok(resolved),
            Err(err) => failures.push(format!("{}: {err:#}", path.display())),
        }
    }

    bail!(
        "None of the MCP client's filesystem roots identify a Git repository. {}",
        failures.join("; ")
    )
}

fn root_path(root: &Root) -> Result<PathBuf> {
    let url = Url::parse(&root.uri)
        .with_context(|| format!("MCP root is not a valid URI: {}", root.uri))?;
    url.to_file_path()
        .map_err(|()| anyhow::anyhow!("MCP root is not a file URI: {}", root.uri))
}

fn open_repository(repository: &Path) -> Result<ResolvedRepository> {
    let repository = repository
        .canonicalize()
        .with_context(|| format!("Could not resolve repository at {}", repository.display()))?;
    let ctx = but_ctx::Context::discover(&repository)
        .with_context(|| format!("Could not open repository at {}", repository.display()))?;
    let repository = ctx
        .workdir_or_gitdir()
        .context("Could not determine the repository root")?
        .canonicalize()
        .context("Could not resolve the repository root")?;
    let name = repository
        .file_name()
        .and_then(|name| name.to_str())
        .unwrap_or("repository")
        .to_owned();

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Open or attach the local directory in the client so at least one root is a file:// URI
  2. Add the local repository checkout as an additional workspace folder
  3. Run the MCP client on the machine where the repository actually lives
Defensive patterns

Strategy: validation

Validate before calling

const isFileRoot = (root: { uri: string }) => root.uri.startsWith('file://');
// client side: only send local roots
const roots = workspaceFolders.filter(isFileRoot);

Type guard

const isFileRoot = (r: { uri: string }): r is { uri: `file://${string}` } =>
  r.uri.startsWith('file://');

Try / catch

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).

Prevention

When it happens

Trigger: 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.

Common situations: IDE remoting (SSH, devcontainer, web IDE) where the workspace folder is not a local path; clients that include virtual documents as roots.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/8a7d393a2d7be2c5. Report an issue: GitHub.