Phase 1: Implement MCP Tool for Conflict Data

Overview

Create the get_merge_request_conflicts MCP tool (ID 88) as a CustomService that reuses existing infrastructure - no logic duplication.

Architecture Decision: CustomService (Not REST API)

We're implementing this as an MCP Tool (CustomService) because:

✅ Reuses existing service logic - MergeRequests::Conflicts::ListService (same as web UI)
✅ No duplication - Both controller and CustomService call the same service
✅ Simpler - ~50 lines vs ~200+ for REST API
✅ Faster - 1-2 days vs 1 week
✅ Internal-only - Appropriate for experimental feature
✅ Python auto-wrapping - McpTool class handles it automatically

Shared Service Architecture

┌─────────────────────────────────────────┐
│   MergeRequests::Conflicts::ListService │  ← Existing logic
│   (battle-tested, powers web UI)        │
└─────────────────────────────────────────┘
            ↑                    ↑
            │                    │
     ┌──────┴─────┐       ┌─────┴──────────┐
     │ Controller │       │ CustomService   │ ← What we're adding
     │ (Web UI)   │       │ (MCP Tool)      │
     │ EXISTING   │       │ NEW (~50 lines) │
     └────────────┘       └─────────────────┘

Both call the exact same service - no duplication!

Tasks

Optional: Model Enhancement (Makes code cleaner)

  • Add conflict_list_service delegation method to MergeRequest model
  • Add structured_conflicts convenience method to MergeRequest model
  • Write specs for new MergeRequest methods

Note: This is optional but makes the CustomService code cleaner. Without it, we just call ListService.new(merge_request) directly.

CustomService Implementation (Core Work)

  • Create app/services/mcp/tools/get_merge_request_conflicts_service.rb
  • Extend CustomService base class
  • Implement auth_ability method (return :read_merge_request)
  • Implement auth_target method (return the merge request)
  • Implement perform_0_1_0 method that:
    • Finds the merge request
    • Creates ListService.new(merge_request) ← Same as controller!
    • Checks can_be_resolved_in_ui? ← Same as controller!
    • Returns to_json ← Same as controller!
  • Handle error cases (no conflicts, binary conflicts, not found)
  • Write comprehensive specs

Tool Registration

  • Register tool in CUSTOM_TOOLS hash in app/services/mcp/tools/manager.rb
  • Add tool definition (ID 88) to ee/lib/ai/catalog/built_in_tool_definitions.rb

Testing

  • Test tool manually in Rails console
  • Verify tool returns same structure as controller (line 24)
  • Test with various conflict scenarios (simple, multi-file, binary)
  • Verify authorization checks work
  • Test error handling

Code Reference: Reusing Controller Logic

ConflictsController (Existing - line 22-37)

format.json do
  if @conflicts_list.can_be_resolved_in_ui?
    render json: @conflicts_list  # ← Line 24: This is what we reuse!
  else
    render json: { message: 'Cannot resolve...', type: 'error' }
  end
end

CustomService (What We're Building)

class GetMergeRequestConflictsService < CustomService
  def perform_0_1_0(arguments)
    merge_request = find_merge_request(arguments)
    
    # Same service as controller line 81!
    conflicts_list = ::MergeRequests::Conflicts::ListService.new(merge_request)
    
    # Same check as controller lines 23-24!
    unless conflicts_list.can_be_resolved_in_ui?
      return error_response('Cannot resolve in UI')
    end
    
    # Same output as controller line 24!
    success_response(conflicts_list.to_json)
  end
end

Exact same logic - just different entry point!

Acceptance Criteria

  • CustomService tool exists and is registered
  • Tool reuses MergeRequests::Conflicts::ListService (no duplication)
  • Tool returns same JSON structure as controller (line 24)
  • Tool is discoverable via MCP
  • Tool returns structured conflict data with markers for resolvable conflicts
  • Tool returns appropriate errors for non-resolvable or non-existent conflicts
  • All specs pass with >90% coverage
  • Tool works in Rails console testing
  • Python automatically wraps it via McpTool (no Python code needed)

Implementation Notes

Why This Works

  1. Controller uses: ListService.new(merge_request).to_json
  2. CustomService uses: ListService.new(merge_request).to_json
  3. Result: Same data, same format, zero duplication!

What Gets Reused

  • ✅ MergeRequests::Conflicts::ListService - Main service
  • ✅ Gitlab::Conflict::FileCollection - Conflict data structure
  • ✅ Gitlab::Conflict::File - Individual file conflicts
  • ✅ Authorization logic pattern
  • ✅ Error handling patterns

What's New (Just Wrapper Code)

  • MCP tool input validation (~10 lines)
  • MCP tool response formatting (~10 lines)
  • Tool registration (~5 lines)
  • Tool definition in catalog (~10 lines)
  • Total new code: ~50-60 lines

Files Changed

New Files:

  • app/services/mcp/tools/get_merge_request_conflicts_service.rb (~50 lines)
  • spec/services/mcp/tools/get_merge_request_conflicts_service_spec.rb (~100 lines)

Modified Files:

  • app/models/merge_request.rb (optional: +10 lines)
  • spec/models/merge_request_spec.rb (optional: +30 lines)
  • app/services/mcp/tools/manager.rb (+1 line)
  • ee/lib/ai/catalog/built_in_tool_definitions.rb (+15 lines)

Reused (No Changes Needed):

  • app/services/merge_requests/conflicts/list_service.rb ← Battle-tested!
  • lib/gitlab/conflict/file_collection.rb ← Battle-tested!
  • lib/gitlab/conflict/file.rb ← Battle-tested!

Testing Checklist

  • Tool returns same data as ConflictsController#show (format.json)
  • Tool works with project ID (numeric)
  • Tool works with project path (namespace/project)
  • Tool handles already-resolved conflicts gracefully
  • Tool handles binary conflicts gracefully
  • Tool handles missing merge requests gracefully
  • Tool respects authorization (fails for unauthorized users)
  • Tool registered in CUSTOM_TOOLS
  • Tool defined in built_in_tool_definitions.rb
  • Python McpTool wrapper can call it (verify in logs)

Timeline

2 days

Day 1:

  • Create CustomService (~2 hours)
  • Register tool (~30 min)
  • Write tests (~3 hours)
  • Manual testing (~1 hour)

Day 2:

  • Fix any issues (~2 hours)
  • Code review feedback (~2 hours)
  • Documentation (~1 hour)

Related to epic &20688

Edited Feb 03, 2026 by Kai Armstrong
Assignee Loading
Time tracking Loading