Skip to content

feat(mrs): Add suggestion apply actions (apply_suggestion, apply_suggestions) #66

@polaz

Description

@polaz

Summary

Add suggestion apply functionality to complete code review workflow. This closes the only significant gap compared to specialized code review MCP servers (midodimori/gitlab-review-mcp).

Motivation

GitLab suggestions allow reviewers to propose specific code changes inline:

```suggestion
const result = items.filter(x => x.active);

Currently we support:
- ✅ Creating line comments with suggestions (via `manage_mr_discussion:thread`)
- ✅ Reading discussions with suggestions (via `browse_mr_discussions:list`)
- ❌ **Applying suggestions** - missing!

Without apply functionality, the workflow is incomplete - users must go to GitLab UI to apply suggestions.

## Proposed Solution

Add two new actions to `manage_mr_discussion`:

### Action: `apply_suggestion`

Apply a single suggestion to create a commit.

```typescript
{
  "action": "apply_suggestion",
  "project_id": "myproject",
  "merge_request_iid": 42,
  "suggestion_id": 12345,
  "commit_message": "Apply suggestion: use filter instead of manual loop"
}

Response:

{
  "id": 12345,
  "applied": true,
  "commit": {
    "sha": "abc123def",
    "message": "Apply suggestion: use filter instead of manual loop"
  }
}

Action: apply_suggestions

Batch apply multiple suggestions in a single commit.

{
  "action": "apply_suggestions",
  "project_id": "myproject",
  "merge_request_iid": 42,
  "suggestion_ids": [12345, 12346, 12347],
  "commit_message": "Apply code review suggestions"
}

Response:

{
  "applied_count": 3,
  "commit": {
    "sha": "def456abc",
    "message": "Apply code review suggestions"
  }
}

GitLab API

Apply Single Suggestion

PUT /projects/:id/merge_requests/:merge_request_iid/suggestions/:suggestion_id/apply

Body (optional):

{
  "commit_message": "Custom commit message"
}

Batch Apply Suggestions

PUT /projects/:id/merge_requests/:merge_request_iid/suggestions/batch_apply

Body:

{
  "ids": [12345, 12346, 12347],
  "commit_message": "Apply suggestions"
}

Docs: https://docs.gitlab.com/ee/api/suggestions.html

Implementation

Schema Changes

// src/entities/mrs/schema.ts

// Add to ManageMrDiscussionSchema discriminated union:

const ApplySuggestionSchema = z.object({
  action: z.literal("apply_suggestion").describe("Apply a single code suggestion"),
  project_id: projectIdField,
  merge_request_iid: mergeRequestIidField,
  suggestion_id: requiredId.describe("ID of the suggestion to apply"),
  commit_message: z.string().optional().describe("Custom commit message for the apply commit"),
});

const ApplySuggestionsSchema = z.object({
  action: z.literal("apply_suggestions").describe("Batch apply multiple code suggestions"),
  project_id: projectIdField,
  merge_request_iid: mergeRequestIidField,
  suggestion_ids: z.array(z.number()).min(1).describe("Array of suggestion IDs to apply"),
  commit_message: z.string().optional().describe("Custom commit message for the apply commit"),
});

Handler Changes

// src/entities/mrs/registry.ts - in manage_mr_discussion handler

case "apply_suggestion": {
  const { project_id, merge_request_iid, suggestion_id, commit_message } = input;
  const encodedProjectId = encodeURIComponent(project_id);

  const body: Record<string, unknown> = {};
  if (commit_message) {
    body.commit_message = commit_message;
  }

  return gitlab.put(
    `projects/${encodedProjectId}/merge_requests/${merge_request_iid}/suggestions/${suggestion_id}/apply`,
    { body: Object.keys(body).length > 0 ? body : undefined, contentType: "json" }
  );
}

case "apply_suggestions": {
  const { project_id, merge_request_iid, suggestion_ids, commit_message } = input;
  const encodedProjectId = encodeURIComponent(project_id);

  const body: Record<string, unknown> = {
    ids: suggestion_ids,
  };
  if (commit_message) {
    body.commit_message = commit_message;
  }

  return gitlab.put(
    `projects/${encodedProjectId}/merge_requests/${merge_request_iid}/suggestions/batch_apply`,
    { body, contentType: "json" }
  );
}

Use Cases

1. AI Code Review Workflow

User: "Review the changes in MR !42 and apply any suggestions you agree with"

Agent:
1. browse_mr_discussions({ action: "list", project_id: "x", merge_request_iid: 42 })
   → Gets discussions with suggestions
2. Analyzes each suggestion
3. manage_mr_discussion({ action: "apply_suggestions", suggestion_ids: [1,2,3], ... })
   → Applies approved suggestions in single commit

2. Interactive Review

User: "Apply the suggestion about using async/await"

Agent:
1. Finds suggestion ID from previous context
2. manage_mr_discussion({ action: "apply_suggestion", suggestion_id: 123, ... })

3. Batch Apply After Review

User: "Apply all the formatting suggestions from the linting review"

Agent:
1. Filters suggestions by reviewer/content
2. manage_mr_discussion({ action: "apply_suggestions", suggestion_ids: [...] })

Testing

Unit Tests

describe("manage_mr_discussion", () => {
  describe("apply_suggestion", () => {
    it("applies a single suggestion", async () => {
      mockEnhancedFetch.mockResolvedValueOnce({
        id: 12345,
        applied: true,
      });

      const result = await handler({
        action: "apply_suggestion",
        project_id: "test/project",
        merge_request_iid: 42,
        suggestion_id: 12345,
      });

      expect(mockEnhancedFetch).toHaveBeenCalledWith(
        expect.stringContaining("/suggestions/12345/apply"),
        expect.objectContaining({ method: "PUT" })
      );
    });

    it("includes custom commit message when provided", async () => {
      // ...
    });
  });

  describe("apply_suggestions", () => {
    it("batch applies multiple suggestions", async () => {
      // ...
    });
  });
});

Integration Tests

describe("suggestion workflow", () => {
  it("creates suggestion and applies it", async () => {
    // 1. Create MR with changes
    // 2. Add discussion with suggestion
    // 3. Apply suggestion
    // 4. Verify commit was created
  });
});

Tasks

  • Add ApplySuggestionSchema to schema.ts
  • Add ApplySuggestionsSchema to schema.ts
  • Update ManageMrDiscussionSchema discriminated union
  • Implement apply_suggestion handler
  • Implement apply_suggestions handler
  • Add unit tests for both actions
  • Add integration test for suggestion workflow
  • Update tool description to mention suggestion support
  • Document in TOOLS.md

Acceptance Criteria

  • apply_suggestion applies single suggestion and returns result
  • apply_suggestions batch applies multiple suggestions
  • Custom commit messages are supported
  • Errors are handled gracefully (already applied, conflicts, etc.)
  • Works with GitLab Free tier (suggestions are free feature)

Complexity

Low - ~50-100 lines of code, straightforward API mapping

Priority

HIGH - Completes code review workflow, closes gap with competitors

Dependencies

None - can be implemented immediately

Related

  • Competitive analysis: midodimori/gitlab-review-mcp has this feature
  • Enhances existing manage_mr_discussion tool

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature, new MCP tool, new capability

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions