Loading
Add MCP tool for retrieving merge request conflict data
Summary
Implements Phase 1 of the Conflict Resolver Agent:
- Epic: gitlab-org#20688
- Issue: #588528 (closed)
This adds a new MCP tool (get_merge_request_conflicts, ID 89) that agents can use to retrieve raw conflict data from merge requests with conflicts.
Architecture
CustomService implementation that reuses existing infrastructure:
┌─────────────────────────────────────────┐
│ MergeRequests::Conflicts::ListService │ ← Existing logic
│ (battle-tested, powers web UI) │
└─────────────────────────────────────────┘
↑ ↑
│ │
┌──────┴─────┐ ┌──────┴──────────┐
│ Controller │ │ CustomService │ ← NEW
│ (Web UI) │ │ (MCP Tool) │
└────────────┘ └─────────────────┘Both call the exact same service - no duplication!
Changes
- Service: GetMergeRequestConflictsService CustomService (106 lines)
- Registration: Register tool in CUSTOM_TOOLS manager and catalog (ID 89)
- Tests: Comprehensive specs for service (27 examples, 456 lines)
- Integration: Updated MCP tools list test to include new tool
Features
- Returns raw git conflict markers (
<<<<<<<,=======,>>>>>>>) in plain text - Supports both numeric project IDs and full paths
- Handles multiple error cases gracefully (no conflicts, missing branches, not found)
- Authorization via
can_push_to_branch?(Developer+ access) - matches controller behavior - Explicit project validation for better error messages
- Memoization for both project and merge request lookups
- Defensive nil guard for file content
- Returns ALL conflict data (no distinction between "simple" and "complex")
- Lower token consumption for LLM usage compared to structured JSON
Output Format
The MCP tool returns conflict data as plain text with simple file markers and git conflict markers:
# File: files/ruby/regex.rb
<<<<<<< files/ruby/regex.rb
def project_name_regexp
/\A[a-zA-Z0-9][a-zA-Z0-9_\-\. ]*\z/
end
=======
def project_name_regex
%r{\A[a-zA-Z0-9][a-zA-Z0-9_\-\. ]*\z}
end
>>>>>>> files/ruby/regex.rb
# File: another/file.rb
[next conflict...]For renamed files, both paths are shown:
# File: old/path.rb -> new/path.rb
[conflict content...]Benefits:
✅ Native git conflict format - LLMs understand it naturally✅ Lower token consumption - no JSON structure overhead✅ Simpler implementation - no parsing required✅ Handles ALL conflicts - no parser errors on complex cases✅ Matches developer experience - same format seen in editors✅ Clear file markers without pretending to be git diff output
Testing
All 27 specs passing:
✅ Version registration and metadata✅ Tool execution with conflicts✅ Error handling (merge states, missing MRs, missing branches, invalid projects)✅ Authorization (Developer+ required, Guest denied)✅ Memoization (both project and merge request)✅ Raw conflict output format with file markers
Testing approach:
- Creates MRs with conflicts using existing test branches (
conflict-resolvable,conflict-start) - Calls the MCP tool and verifies returned plain text format
- Tests various error scenarios and merge states
- Verifies authorization at different permission levels
- Tests project validation with both numeric IDs and full paths
See spec/services/mcp/tools/get_merge_request_conflicts_service_spec.rb for complete test coverage.
Security
- Explicit project validation before merge request lookup for clear error messages
- Defensive nil guard for file.content to handle unexpected edge cases
- Uses ResourceFinder concern patterns for secure project lookups
- Authorization matches controller behavior (can_push_to_branch?)
- No exposure of sensitive data - only returns conflict content
Implementation Details
Memoization:
- Project lookup memoized to avoid duplicate database queries
- Merge request lookup memoized with explicit cache key (
project_id_merge_request_iid)
Authorization:
- Overrides base
authorize!method to usecan_push_to_branch? - Same authorization as conflicts controller (Developer+ access required)
- Early project validation provides clear error messages
Error Handling:
- Project not found: "project not found or inaccessible"
- Merge request not found: "merge request not found"
- No conflicts: "Merge request does not have conflicts"
- Missing branches: "Cannot retrieve conflicts: missing branches or diff refs"
- Unchecked merge status: Returns status with explanation
Closes
Closes #588528 (closed)
Edited by Kai Armstrong