Add get_commit MCP server tool

What does this MR do and why?

Adds get_commit, a GraphQL-backed Duo Agent Platform MCP server tool that returns a single commit's metadata and, through an include parameter, its diff or its comments. This consolidates the three shipped Duo Workflow tools (get_commit, get_commit_diff, get_commit_comments) into one facet reader, following the same pattern as get_merge_request (facets scoped to one parent object fold into that object's get_ tool through include).

To back the diff stats facet through GraphQL, this MR also adds diffStats and diffStatsSummary to the GraphQL Commit type, mirroring the existing MergeRequest fields and reusing the existing Commit#diff_stats model method.

Tool contract

  • Identify the commit with url, or project_id + commit_sha (exactly one path; enforced in Ruby).
  • include: an array of facet names bounded by maxItems: 1 (one facet per call), matching the get_merge_request convention so each call stays a single bounded query. Values: diff, comments. Base metadata is always returned.
  • detail (applies to the diff facet only): none | stats | full_patch. Default stats.
  • Cursor pagination (comments_after / comments_first) for the comments facet.

The tool runs the operation as the calling user through GitlabSchema, so it inherits the caller's permissions.

Alignment with the get_merge_request conversion

This follows the conventions established by the sibling get_merge_request GraphQL conversion: include as an array bounded by maxItems, facet-scoped cursor pagination, and the same tool/service/query structure. Two notes for the MCP Tool Proposal:

  1. include is one facet per call (maxItems: 1), which diverges from the issue's "diff and/or comments" wording but matches the codified convention and bounds each call to one query. Raising the cap later is additive and non-breaking.
  2. Comments use GraphQL cursor pagination (comments_after / comments_first), not the REST-style comments_page / comments_per_page in the issue.

Unlike get_merge_request (whose diff facet is stats-only, deferring patch text to get_merge_request_diffs), get_commit keeps detail: full_patch because it consolidates get_commit_diff. The shipped Duo Workflow Python tools remain the migration/compatibility path.

Backing: GraphQL vs REST (reviewer input welcome)

This tool is GraphQL-backed, which is worth an explicit call-out because the issue's implementation plan and the MCP dev docs model the commit tools as REST: list_commits is REST/offset, and the docs' facet-pagination example for get_commit is comments_page/comments_per_page. GraphQL was chosen to match the flagship facet reader get_merge_request, which is also GraphQL and uses cursor pagination (notes_after/notes_first), so get_commit stays structurally identical to it.

Trade-offs:

  • GraphQL cost: required adding diffStats/diffStatsSummary to the Commit GraphQL type (this MR's first commit) so the stats facet has a source. A REST backing would get stats, diff, and comments from existing endpoints with no schema change or GraphQL API review.
  • REST would match the docs' comments_page/comments_per_page example and map more directly onto the three shipped REST Python tools it consolidates.
  • GraphQL gives permission inheritance through GitlabSchema (runs as the calling user), a single typed round trip, and structural parity with get_merge_request.
  • Note the docs' REST pagination examples appear to lag the current direction: get_merge_request already diverged to cursor pagination the same way, without updating that example.

Open question for the mcp-tool-review-board: should the commit tool family (get_commit, list_commits, and so on) be kept REST for a uniform commit surface, or is GraphQL acceptable here given the get_merge_request precedent? Happy to switch to a REST-backed implementation if the board prefers consistency across commit tools.

How to set up and validate locally

  1. Restart the Rails web process so the tool registry picks up the new tool:

    gdk restart rails-web
  2. Connect using MCP inspector, list the MCP tools, and confirm get_commit is present (and no tools are missing):

    npx -y @modelcontextprotocol/inspector -- env NODE_TLS_REJECT_UNAUTHORIZED=0 mise x -- npx -y mcp-remote https://gdk.test:3443/api/v4/mcp --debug
  3. Call get_commit with no facet and confirm only base metadata comes back (sha, title, author, dates, message). Identify the commit with either url, or project_id and commit_sha:

    { "project_id": "gitlab-org/gitlab", "commit_sha": "<sha>" }
  4. Call get_commit with the diff facet (one per call) and confirm the diff comes back inline. With detail: stats you get diffStatsSummary plus per-file diffStats; with detail: full_patch you get the patch text instead:

    { "project_id": "gitlab-org/gitlab", "commit_sha": "<sha>", "include": ["diff"], "detail": "stats" }
  5. Call get_commit with the comments facet and confirm the paginated notes connection comes back. Page with comments_after and comments_first:

    { "project_id": "gitlab-org/gitlab", "commit_sha": "<sha>", "include": ["comments"], "comments_first": 20 }
  6. Call it with multiple facets and confirm a validation error, because include is capped at one facet per call:

    { "project_id": "gitlab-org/gitlab", "commit_sha": "<sha>", "include": ["diff", "comments"] }

Automated coverage in this MR: tool and service unit specs, a Commit GraphQL type spec for the new diffStats/diffStatsSummary fields, a call_tool request spec (end-to-end tools/call), and both CE and EE list_tools specs.

References

Edited by Jessie Young

Merge request reports

Loading
Loading