Skip to content

git server: git_log accepts a non-positive max_count and a negative value silently returns the entire history #5214

Closed

Description

@simpleqt

{
"title": "git server: git_log accepts a non-positive max_count and a negative value silently returns the entire history",
"body": "### Which server?\n\ngit (mcp-server-git)\n\n### Server version\n\n2026.10.19 (from a clone of this repository at v2/main, commit f18beb7)\n\n### How did you run the server?\n\nFrom a clone of this repository\n\n### Transport\n\nstdio\n\n### Protocol era\n\nLegacy (2025-11-25 or earlier)\n\n### MCP client and version\n\nThe in-memory test client from the mcp SDK used by this repository's own src/git/tests/test_protocol.py (no external client)\n\n### Operating system and runtime\n\nWindows 11, Python 3.13\n\n### Server configuration\n\nBuilt-in test harness from src/git/tests/test_protocol.py, pointing at a temporary git repository.\n\n### Steps to reproduce\n\n1. Start the server against a temporary repository that has 6 commits.\n2. Call the git_log tool with {\"repo_path\": \"<tmp>\", \"max_count\": -1}.\n3. Call it again with {\"repo_path\": \"<tmp>\", \"max_count\": 0}.\n\n### Expected behavior\n\nmax_count is documented as "Maximum number of commits to show", so a value that cannot be a maximum — 0 or negative — should be rejected by the tool's input validation, or at least clamped to something meaningful. Git itself treats any negative -n as "no limit".\n\n### Actual behavior\n\n- max_count: -1 returns all 6 commits with isError: false — the whole history, while reporting success. The parameter is silently ignored, and the effect is the opposite of what the name says.\n- max_count: 0 returns an empty list, also with isError: false.\n- The advertised input schema is {\"default\": 10, \"type\": \"integer\"} with no minimum, so a client cannot know the bound either.\n\nThe value reaches git log -n <value> through GitPython, which is why Git's own interpretation of a negative limit applies.\n\n### Logs or error output\n\n\nmax_count=-1 -> isError=False, commits returned=6 (all of them)\nmax_count=0 -> isError=False, commits returned=0\n\n\n### Already prototyped a fix?\n\nYes — the change is small and I verified it locally, but per CONTRIBUTING.md I am reporting it instead of opening a pull request.\n\nPrompt used (this is the prompt that produced the local change):\n\n> In the src/git Python server of this repository, git_log's max_count parameter has no lower bound. Add Pydantic validation so max_count must be at least 1, with a message that explains the bound, and add the bound to the tool's advertised JSON schema. Add protocol tests that call git_log with -1, 0 and 1 over the in-memory client session and assert that the non-positive values come back as tool errors while 1 returns exactly one commit.\n\nBehavior before and after: before, -1 returned every commit and 0 returned nothing, both as successful results; after, both are rejected by input validation (isError: true, 1 validation error for logArguments) and the schema carries \"minimum\": 1. A valid max_count is unaffected.\n\nHow it was verified: uv run --frozen pytest -q in src/git (189 passed, 3 pre-existing Windows symlink-privilege failures that also fail at the base commit), ruff check, ruff format --check and pyright clean, and the per-file coverage gate (npm run coverage:py -- git) at 100% lines and branches. Red/green was captured by stashing only the source change: 6 failed before, 9 passed after.\n"
}

Activity

  1. youlemei2022-web commented on Oct 27, 2026

    @youlemei2022-web

    Confirming the behaviour from the v2 tree: max_count reaches git log unvalidated, and neither 0 nor -1 means what a caller would expect. git log -n 0 returns the full history rather than nothing, and a negative value is accepted and also returns everything - so a caller asking for zero commits silently gets the whole repository.

    Three changes, in order of value:

    1. Constrain the schema. Set minimum: 1 on max_count so well-behaved clients get a validation error before the call ever reaches git, instead of a surprising success with the wrong result set.
    2. Guard at the boundary anyway, for clients that bypass schema validation:
    if not isinstance(max_count, int) or isinstance(max_count, bool) or max_count < 1:
        raise ValueError("max_count must be a positive integer")

    Keep 0 an error rather than "empty list". The silent full-history response is a correctness bug, not a formatting choice - a model that asked for 0 commits and received 200 will happily summarise the wrong scope.

    1. Pass the value in --opt=value form (--max-count=<n> as a single argv entry) rather than as two arguments, so a value that begins with - can never be reinterpreted as another option by git. This also protects future numeric options.

    Tests that would have caught it: -1, 0, 1, and a repository with fewer commits than requested - the last one should still return all commits rather than error, which distinguishes the validation rule from an off-by-one.

  2. added
    bugSomething isn't working
    server-gitReference implementation for the Git MCP server - src/git
    on Oct 28, 2026
  3. cliffhall commented on Oct 28, 2026

    @cliffhall
    Member

    Triage: Priority Low (total 5)

    • Severity 2: a max_count of 0 or below is passed to git unvalidated and does not mean what the parameter says; the workaround is to pass a positive count. Confirmed on v2/main: the tool's schema has no minimum.
    • Urgency 2: wanted eventually, nothing blocked on it
    • Bonuses: +1 bug label

    Board: Servers V2 (#43), Status Incoming, awaiting maintainer review (no milestone yet).

  4. added this to the v2.4.0 milestone on Oct 29, 2026
  5. scks1 commented on Oct 29, 2026

    @scks1

    Prepared comment for issue #5214 — NOT POSTED

    Channel note: modelcontextprotocol/servers accepts issues, not outside PRs
    (CONTRIBUTING.md TL;DR). The sanctioned way to contribute a locally built fix
    is a comment on the issue sharing the prompt that produced it. This text is
    ready for Simon/the parent to post; the subagent that prepared it made no
    external writes.


    Disclosure: this contribution was written by Árbol, an AI assistant working under the direction of the account holder, who set the acceptance criteria and verified the results before submission.

    Adding a data point and a verified prototype for #5214, against current
    v2/main (72ad22c), following the fix shape the issue prescribes:

    Prompt used (produced the local change; no diff attached, per this
    repository's contribution model):

    In src/git of modelcontextprotocol/servers (checkout of v2/main), fix
    issue #5214: the git_log tool's max_count has no lower bound, so -1
    silently returns the entire history and 0 is accepted. (1) In the log
    tool registration in src/git/src/mcp_server_git/server.py, annotate
    max_count as Annotated[int, Field(ge=1, description="Maximum number of commits to show. Must be at least 1.")] = 10, following the file's existing
    Annotated[..., Field(description=...)] convention for the timestamp
    parameters, so the advertised schema carries "minimum": 1 and the SDK's
    argument validation rejects non-positive values as a tool error.
    (2) In the internal git_log() function, add a defense-in-depth guard in
    the style of its existing flag-injection guards:
    if max_count < 1: raise ValueError(f"Invalid max_count: {max_count} - must be at least 1"),
    with a comment explaining that git reads a negative -n as "no limit".
    (3) Update the git_log entry in tests/snapshots/list_tools.json for the
    new schema, and the max_count line in src/git/README.md to state the
    bound. (4) Tests: in tests/test_protocol.py, after
    test_git_log_max_count, add a protocol test that creates 3 commits and
    asserts max_count of -1 and 0 each return isError with a
    logArguments validation message naming max_count and the
    greater-than-or-equal-to-1 bound; in tests/test_server.py, next to
    test_git_log_rejects_timestamp_flag_injection, add a unit test asserting
    git_log(repo, max_count=0) and max_count=-1 raise ValueError.
    Do not change behavior for positive values.

    Verified locally (Linux, Python 3.10 venv from the server's uv.lock;
    full log in the working record's SUMMARY.md): the src/git suite passes
    301 tests at v2/main (72ad22c) and 303 with this change — the two added
    tests fail against the unmodified source and pass with it. The list_tools
    wire diff is exactly the two new keys on max_count (description,
    minimum: 1). ruff check and ruff format --check are clean, pyright
    reports 0 errors, uv build succeeds, and server.py coverage stays at
    100% lines / 100% branches. A valid max_count (including requesting more
    commits than the repository has) is unaffected.

  6. self-assigned this
    on Nov 5, 2026
  7. cliffhall commented on Nov 5, 2026

    @cliffhall
    Member

    Shipped in #5455 (merged to v2/main).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingserver-gitReference implementation for the Git MCP server - src/gitv2

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions