modelcontextprotocol/servers · error · ValueError

Invalid path: {repo_path}

Error message

Invalid path: {repo_path}

What it means

validate_repo_path() resolves both repo_path and the configured allowed_repository with Path.resolve(); if resolve() raises OSError or RuntimeError (broken symlink, symlink loop, illegal characters, permission error), it is rethrown as ValueError 'Invalid path: ...'. This is the resolve step; the separate outside-repo check produces a different message. validate_repo_path is a no-op when allowed_repository (the server's repository argument) is None.

Source

Thrown at src/git/src/mcp_server_git/server.py:262

        if d.diff is None:
            continue
        if isinstance(d.diff, bytes):
            output.append(d.diff.decode('utf-8'))
        else:
            output.append(d.diff)
    return "".join(output)

def validate_repo_path(repo_path: Path, allowed_repository: Path | None) -> None:
    """Validate that repo_path is within the allowed repository path."""
    if allowed_repository is None:
        return  # No restriction configured

    # Resolve both paths to handle symlinks and relative paths
    try:
        resolved_repo = repo_path.resolve()
        resolved_allowed = allowed_repository.resolve()
    except (OSError, RuntimeError):
        raise ValueError(f"Invalid path: {repo_path}")

    # Check if repo_path is the same as or a subdirectory of allowed_repository
    try:
        resolved_repo.relative_to(resolved_allowed)
    except ValueError:
        raise ValueError(
            f"Repository path '{repo_path}' is outside the allowed repository '{allowed_repository}'"
        )


def git_branch(repo: git.Repo, branch_type: str, contains: str | None = None, not_contains: str | None = None) -> str:
    # Defense in depth: reject values starting with '-' to prevent flag injection
    if contains and contains.startswith("-"):
        raise BadName(f"Invalid contains value: '{contains}' - cannot start with '-'")
    if not_contains and not_contains.startswith("-"):
        raise BadName(f"Invalid not_contains value: '{not_contains}' - cannot start with '-'")

    match contains:

View on GitHub (pinned to 76d64c822f)

Solutions

  1. Pass a repo_path that is an existing, valid directory resolvable on the server host.
  2. Use a repo_path returned by list_repos() (derived from Roots or the configured repository).
  3. Ensure the allowed_repository itself resolves correctly.

Example fix

# before
validate_repo_path(Path('/tmp/broken-symlink'), allowed_repository=Path('/repo'))  # -> ValueError

# after
p = Path(repo_path)
if not p.is_dir():
    raise ValueError('repo_path must be an existing directory')
validate_repo_path(p.resolve(), allowed_repository=Path('/repo'))
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path
def valid_repo_path(repo_path: Path, allowed: Path | None) -> None:
    if allowed is None:
        return
    if not repo_path.is_dir():
        raise ValueError(f'Invalid path: {repo_path}')
    resolved = repo_path.resolve()
    if not resolved.is_relative_to(allowed.resolve()):
        raise ValueError(f'Repository path {repo_path} is outside the allowed repository {allowed}')

Try / catch

try:
    validate_repo_path(repo_path, allowed_repository)
except ValueError as e:
    if e.args[0].startswith('Invalid path:'):
        # pick a repo_path from list_repos() instead
    raise

Prevention

When it happens

Trigger: Client passes a repo_path that is a broken symlink, a loop, or contains illegal characters while an allowed_repository is configured.

Common situations: Client supplies a stale or non-existent path; symlinked repo roots whose target is missing; permission issues on the path.

Related errors


AI-assisted analysis of modelcontextprotocol/servers@76d64c822f (2026-08-12). Data as JSON: /api/errors/a6a7774d76a6070e. Report an issue: GitHub.