Add save_merge_request MCP tool folding create and update
What does this MR do and why?
Adds save_merge_request, an aggregated REST-backed MCP server tool that combines the existing create and update merge request API endpoints into one tool. The presence of merge_request_iid selects the operation — absent creates, present updates. It follows the same aggregated-tool pattern as search and manage_pipeline. The previous create_merge_request tool name and new update_merge_request are kept as aliases, so existing MCP clients continue to resolve.
Closes #605880 (closed)
Design notes
- The create route (
POST /projects/:id/merge_requests) now joins the aggregator; a newroute_setting :mcpis added to the update route (PUT /projects/:id/merge_requests/:merge_request_iid). Both carryaggregators: [SaveMergeRequestService]. - REST was chosen over GraphQL because each REST endpoint is coarse-grained and accepts the full field set (labels, assignees, reviewers, milestone, squash, state transitions) in a single call. The GraphQL path would need multiple mutations and cannot set fields on a merge request it just created in one operation.
SaveMergeRequestServicesubclassesBase::AggregatedServiceand exposes its owntool_aliases, so no dependency on other in-flight alias work.- Argument-shape change under the aliases: the project argument is now
project_id(ID or full path), not the oldcreate_merge_requestid. A cached or hardcoded caller still sending{id: ...}under the alias must switch toproject_id; clients that re-fetchtools/listper session pick this up automatically. - A scoped spec asserts the tool's mcp param lists stay a subset of the params its two routes actually declare, guarding against silent schema drift. It is intentionally scoped to this tool's routes rather than all MCP tools, because a codebase-wide sweep surfaces pre-existing drift in the search and pipeline tools that is tracked separately in #607880 and #607881 (closed).
References
- Issue: #605880 (closed)
- MCP tool development guide:
doc/development/duo_agent_platform/mcp/_index.md - Related pre-existing drift the scoped guard avoids: #607880 and #607881 (closed)
Screenshots or screen recordings
No UI changes.
How to set up and validate locally
- Restart Rails so the new tool registers:
gdk restart rails-web(the tool registry is memoized at boot). - Add GDK mcp server to your tool of choice
- Pick a project you have access to work in
- Ask the tool to update an existing merge request
- Have it create a new merge request
I ended up writing a script to test this out and here's the output from it:
script: $6034623
❯ GDK_DIR=~/Developer/gdk/gitlab MODEL=sonnet PROJECT=top-level-public/top-project-public ANTHROPIC_API_KEY="$(op read 'op://Employee/ANTHROPIC_API_KEY/password')" bash /tmp/test-save-merge-request.sh
>> Minting a local-GDK PAT (api+mcp) — boots Rails, ~30-60s...
minted PAT from GITLAB_DIR=/Users/terrichu/Developer/gdk/gitlab (length 57; a real PAT is ~26 chars)
>> Preflight OK. Project: top-level-public/top-project-public target branch: main
>> Seeding ephemeral branches...
==================================================================
A. save_merge_request CREATE (username assignee + array labels)
PASS A create returns success
PASS A created MR !4
PASS A state opened
PASS A assignee root resolved from username
B. save_merge_request UPDATE (add_labels + state_event close/reopen)
PASS B update+close returns success
PASS B state closed
PASS B reopened
C. create_merge_request ALIAS create (assignee_ids integer spelling)
PASS C alias create returns success
PASS C created MR !5 via create_merge_request alias
D. update_merge_request ALIAS update (title change)
PASS D alias update returns success
PASS D title updated
E. guard: state_event without merge_request_iid must NOT create
PASS E refuses update-only fields on create
F. guard: assignees + assignee_ids together is rejected
PASS F rejects both spellings
==================================================================
G. agentic smoke: does an agent pick save_merge_request for 'create an MR'?
agent called: Skill,mcp__gdk__save_merge_request
PASS G agent used save_merge_request
==================================================================
>> Cleanup...
closed MR !4
closed MR !5
closed MR !6
deleted branch mcp-mrtest-a-1787074293
deleted branch mcp-mrtest-b-1787074293
deleted branch mcp-mrtest-c-1787074293
revoked test PAT
==================================================================
RESULT: 14 passed, 0 failedMR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.