list_commits MCP tool

Note

Before picking up this work: this issue adds an MCP tool. Please follow the Adding a new tool guidance first. That includes creating an MCP Tool Proposal, using verb_object naming (get_ / list_ / save_ / delete_), and the shared resource-identification input base classes.

Problem Statement / Use Case

Agents need to browse a project's commit history, filtered by ref, author, path, or date range, when investigating changes or building context. list_commits is the collection reader for the commit domain, paired with the single-object get_commit facet reader.

Scope and Non-Goals

  • In scope: list commits with filters (ref, author, path, since/until) and pagination.
  • Non-goals: single-commit detail/diff/comments (get_commit), creating commits (add_commit), commit-message search (unified search, scope=commits).
  • Follow-ups: none.

Data Shape and Context Engineering

Commit lists grow unbounded, so pagination is the guard. Entries are commit metadata (sha, title, author, date); full diffs are never inlined — the model drills into get_commit for those. with_stats optionally adds per-commit line counts without pulling patches.

  • Input schema (example):
{
  "tool": "list_commits",
  "description": "List commits in a GitLab project, filtered by ref, author, path, or date.",
  "parameters": {
    "url": "string (optional)",
    "project_id": "int | string (optional)",
    "ref_name": "string (optional; branch/tag)",
    "author": "string (optional)",
    "path": "string (optional; only commits touching this path)",
    "since": "string (optional; ISO 8601)",
    "until": "string (optional; ISO 8601)",
    "order": "string (optional)",
    "first_parent": "boolean (optional)",
    "with_stats": "boolean (optional)",
    "first": "integer (optional, 1-100, default: 20)",
    "after": "string (optional; cursor from the previous response's page_info.end_cursor)"
  }
}
  • Output schema (JSON example):
{
  "commits": [
    { "sha": "9a1b2c", "title": "Fix panic in cache", "author_name": "Jane Doe", "created_at": "2026-07-01T10:00:00Z", "stats": { "additions": 12, "deletions": 3 } }
  ],
  "page_info": { "has_next_page": true, "end_cursor": "eyJpZCI6..." }
}

Resources (already implemented similar tools etc.)

  • Existing GraphQL field: Query.project(fullPath:) { repository { commits(ref:, path:, author:, committedBefore:, committedAfter:, first:, after:) } } — Resolvers::Repositories::CommitsResolver (app/graphql/resolvers/repositories/commits_resolver.rb) → Types::Repositories::CommitType connection, cursor pagination via Gitaly ListCommits.
  • Field mapping: ref_name → ref, author → author, path → path, since → committedAfter, until → committedBefore. Output fields on CommitType: sha/shortId → sha, title, authoredDate → created_at, authorName → author_name, webUrl (a real field here — no client-side construction needed).
  • Depends on #609537 (closed) (blocks this issue): with_stats, first_parent, and order are not on the resolver/type today. That issue adds a stats field to CommitType (from Commit#diff_stats) and first_parent/order arguments to CommitsResolver (the model's list_commits already accepts the latter two). Build this tool against those once they land.
  • Resource resolution precedent: Mcp::Tools::Concerns::ResourceFinder#find_parent_by_id_or_path! — resolve project_id/url to a project, pass its full_path as fullPath.
  • Reference shape: list_merge_requests (app/services/mcp/tools/merge_requests/list_merge_requests_{tool,service}.rb).
  • MCP dev guidelines: doc/development/duo_agent_platform/mcp/_index.md; gitlab-mcp-tool-builder skill's build recipe — verify a GraphQL field exists before writing a custom one.

Implementation Plan

  1. Two classes: Mcp::Tools::Commits::ListCommitsTool < Mcp::Tools::Base::GraphqlTool and ListCommitsService < Base::GraphqlService.
  2. Operation file: app/graphql/queries/mcp/commits/list_commits.query.graphql, calling project(fullPath:) { repository { commits(ref:, path:, author:, committedBefore:, committedAfter:, firstParent:, order:, first:, after:) { pageInfo { hasNextPage endCursor } nodes { sha title authoredDate authorName webUrl stats { additions deletions } } } } } (the firstParent/order args and stats field come from #609537 (closed)).
  3. Resolve url/project_id → fullPath via find_parent_by_id_or_path!(:project, identifier). Map ref_name/since/until to ref/committedAfter/committedBefore.
  4. with_stats: only select the stats field in the query when with_stats is true (it triggers a per-commit Gitaly call, so don't request it otherwise).
  5. Pagination: first/after, returning page_info.has_next_page/end_cursor.
  6. Register in GRAPHQL_TOOLS in app/services/mcp/tools/manager.rb.
  7. Specs: spec/graphql/all_queries_spec.rb coverage comes free from the committed .graphql file; add Tool/Service specs for ref/author/path/date filters, with_stats, first_parent, pagination, empty result, missing/inaccessible project.
Edited by 🤖 GitLab Bot 🤖