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, orproject_id+commit_sha(exactly one path; enforced in Ruby). include: an array of facet names bounded bymaxItems: 1(one facet per call), matching theget_merge_requestconvention so each call stays a single bounded query. Values:diff,comments. Base metadata is always returned.detail(applies to thedifffacet only):none|stats|full_patch. Defaultstats.- Cursor pagination (
comments_after/comments_first) for thecommentsfacet.
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:
includeis 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.- Comments use GraphQL cursor pagination (
comments_after/comments_first), not the REST-stylecomments_page/comments_per_pagein 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/diffStatsSummaryto theCommitGraphQL type (this MR's first commit) so thestatsfacet 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_pageexample 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 withget_merge_request. - Note the docs' REST pagination examples appear to lag the current direction:
get_merge_requestalready 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
-
Restart the Rails web process so the tool registry picks up the new tool:
gdk restart rails-web -
Connect using MCP inspector, list the MCP tools, and confirm
get_commitis 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 -
Call
get_commitwith no facet and confirm only base metadata comes back (sha,title,author, dates,message). Identify the commit with eitherurl, orproject_idandcommit_sha:{ "project_id": "gitlab-org/gitlab", "commit_sha": "<sha>" } -
Call
get_commitwith thedifffacet (one per call) and confirm the diff comes back inline. Withdetail: statsyou getdiffStatsSummaryplus per-filediffStats; withdetail: full_patchyou get the patch text instead:{ "project_id": "gitlab-org/gitlab", "commit_sha": "<sha>", "include": ["diff"], "detail": "stats" } -
Call
get_commitwith thecommentsfacet and confirm the paginated notes connection comes back. Page withcomments_afterandcomments_first:{ "project_id": "gitlab-org/gitlab", "commit_sha": "<sha>", "include": ["comments"], "comments_first": 20 } -
Call it with multiple facets and confirm a validation error, because
includeis 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
- #605874 (closed)
- Related: !248373 (merged)