Add approve and unapprove methods to save_merge_request_review MCP tool
What does this MR do and why?
Adds approve and unapprove values to the method enum of the save_merge_request_review MCP tool. This closes the Red Hat gap-analysis blocker where agents could not approve merge requests, and satisfies the Anthropic integration's hard requirement for unapprove support.
Both are Ruby-side dispatch branches following the existing post_duo_review precedent: approve calls MergeRequests::ApprovalService, unapprove calls MergeRequests::RemoveApprovalService — the same services backing the REST POST .../approve and POST .../unapprove endpoints.
This is a purely additive schema change: two enum values plus one optional sha parameter. Existing calls to save_merge_request_review are untouched.
Closes #617967 (closed)
Design decisions
- Idempotent statuses instead of REST's errors: approving an already-approved MR returns success with status
already_approved; unapproving without a prior approval returns success with statusnot_approved. REST returns 401 for both cases, indistinguishable from a permission failure. For unattended agent retries, explicit-state success is safer. This is a deliberate divergence from REST behavior. - sha guard: an optional
shaparameter on approve mirrors the REST staleness guard (compares againstdiff_head_sha, with the identical error message "SHA does not match HEAD of source branch: "). Silently approving stale content is a worse failure for an agent than for a human. The tool description recommends passing thediff_head_shaobtained fromget_merge_request. - Outcome verified from state, not the service return:
ApprovalServicereturns nil on every failure indistinguishably, and can even report success when the row save races. The tool re-checksapproved_by?after the call and derives a typed error (merged / not permitted / instance approval settings) locally rather than trusting the service's return value. - unapprove permission gate:
RemoveApprovalServiceperforms no permission check itself; the REST endpoint authorizes:approve_merge_requestbefore calling it, and the tool replicates that same gate. - EE approval settings not supported: instance settings that require a password or a SAML re-authentication within 5 seconds to approve cannot be satisfied by a PAT-driven MCP call. Those rejections surface as a typed error explaining the limitation. No
approval_passwordparameter is exposed.
How to validate locally
gdk restart rails-webon this branch (runrails db:migratefirst if pending).- Call the MCP endpoint with a PAT (
mcpscope):tools/listshows the 8-valuemethodenum and theshaparameter. tools/callsave_merge_request_reviewwithmethod: approve/unapproveagainst a merge request you can approve; verify the approval appears/disappears on the MR page.
GDK JSON-RPC round trip
1a. enum: ["create_note", "reply_discussion", "create_diff_note", "resolve_discussion", "submit_review", "post_duo_review", "approve", "unapprove"]
1b. sha param: Head SHA guard (approve). When given and it no longer matches the merg
MR: jashkenas/Underscore iid=2 author=sherril head=826ba14c
2. approve: isError=false {"method"=>"approve", "status"=>"approved", "merge_request_url"=>"https://gdk.test:3443/jashkenas/Underscore/-/merge_requests/2"}
db approved_by?: true
3. approve again: isError=false {"method"=>"approve", "status"=>"already_approved", ...}
4. unapprove: isError=false {"method"=>"unapprove", "status"=>"unapproved", ...}
db approved_by?: false
5. unapprove again: isError=false {"method"=>"unapprove", "status"=>"not_approved", ...}
6. approve good sha: isError=false {"method"=>"approve", "status"=>"approved", ...}
7. approve bad sha: isError=true "Validation error: SHA does not match HEAD of source branch: 826ba14cfc12f8a31e0d4f3b3e98737bc922cce8"
cleanup done; final approved_by?: falseThe non-member permission rejection is covered by unit specs; it cannot be driven end-to-end on GDK because the MCP endpoint itself returns 404 for users without Duo availability (a pre-existing endpoint gate, unrelated to this change).
References
- Issue: #617967 (closed)
- Tool's original MR: !249708 (merged)
- REST endpoints:
lib/api/merge_request_approvals.rb - Per the issue's implementation plan, the MCP Tool Proposal for these methods runs in parallel with this MR.