@structured-world/gitlab-mcp 6.0.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -40,566 +40,242 @@ exports.getFilteredMrsTools = getFilteredMrsTools;
40
40
  const z = __importStar(require("zod"));
41
41
  const schema_readonly_1 = require("./schema-readonly");
42
42
  const schema_1 = require("./schema");
43
- const fetch_1 = require("../../utils/fetch");
43
+ const gitlab_api_1 = require("../../utils/gitlab-api");
44
44
  const projectIdentifier_1 = require("../../utils/projectIdentifier");
45
- const idConversion_1 = require("../../utils/idConversion");
46
45
  exports.mrsToolRegistry = new Map([
47
46
  [
48
- "get_branch_diffs",
47
+ "browse_merge_requests",
49
48
  {
50
- name: "get_branch_diffs",
51
- description: "COMPARE: Get diffs between two branches or commits in a GitLab project. Use when: Reviewing changes before merging, Analyzing code differences, Generating change reports. Supports both direct comparison and merge-base comparison methods.",
52
- inputSchema: z.toJSONSchema(schema_readonly_1.GetBranchDiffsSchema),
49
+ name: "browse_merge_requests",
50
+ description: 'BROWSE merge requests. Actions: "list" shows MRs with filtering, "get" retrieves single MR by IID or branch, "diffs" shows file changes, "compare" diffs two branches/commits.',
51
+ inputSchema: z.toJSONSchema(schema_readonly_1.BrowseMergeRequestsSchema),
53
52
  handler: async (args) => {
54
- const options = schema_readonly_1.GetBranchDiffsSchema.parse(args);
55
- const { project_id, from, to, straight } = options;
56
- const queryParams = new URLSearchParams();
57
- if (straight !== undefined) {
58
- queryParams.set("straight", String(straight));
59
- }
60
- const apiUrl = `${process.env.GITLAB_API_URL}/api/v4/projects/${(0, projectIdentifier_1.normalizeProjectId)(project_id)}/repository/compare?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}&${queryParams}`;
61
- const response = await (0, fetch_1.enhancedFetch)(apiUrl);
62
- if (!response.ok) {
63
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}`);
64
- }
65
- const diff = await response.json();
66
- return (0, idConversion_1.cleanGidsFromObject)(diff);
67
- },
68
- },
69
- ],
70
- [
71
- "get_merge_request",
72
- {
73
- name: "get_merge_request",
74
- description: "READ: Get comprehensive details of a merge request including status, discussions, and approvals. Use when: Reviewing MR details, Checking merge status, Gathering information for automation. Accepts either MR IID or source branch name for flexibility.",
75
- inputSchema: z.toJSONSchema(schema_readonly_1.GetMergeRequestSchema),
76
- handler: async (args) => {
77
- const options = schema_readonly_1.GetMergeRequestSchema.parse(args);
78
- const { project_id, merge_request_iid, branch_name } = options;
79
- let apiUrl;
80
- if (merge_request_iid) {
81
- apiUrl = `${process.env.GITLAB_API_URL}/api/v4/projects/${(0, projectIdentifier_1.normalizeProjectId)(project_id)}/merge_requests/${merge_request_iid}`;
82
- }
83
- else if (branch_name) {
84
- apiUrl = `${process.env.GITLAB_API_URL}/api/v4/projects/${(0, projectIdentifier_1.normalizeProjectId)(project_id)}/merge_requests?source_branch=${encodeURIComponent(branch_name)}`;
85
- }
86
- else {
87
- throw new Error("Either merge_request_iid or branch_name must be provided");
88
- }
89
- const response = await (0, fetch_1.enhancedFetch)(apiUrl);
90
- if (!response.ok) {
91
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}`);
92
- }
93
- const result = await response.json();
94
- if (branch_name) {
95
- if (Array.isArray(result) && result.length > 0) {
96
- return result[0];
53
+ const input = schema_readonly_1.BrowseMergeRequestsSchema.parse(args);
54
+ switch (input.action) {
55
+ case "list": {
56
+ const { action: _action, project_id, ...rest } = input;
57
+ const query = (0, gitlab_api_1.toQuery)(rest, []);
58
+ const path = project_id
59
+ ? `projects/${(0, projectIdentifier_1.normalizeProjectId)(project_id)}/merge_requests`
60
+ : `merge_requests`;
61
+ return gitlab_api_1.gitlab.get(path, { query });
97
62
  }
98
- else {
99
- throw new Error("No merge request found for branch");
63
+ case "get": {
64
+ const query = {};
65
+ if (input.include_diverged_commits_count !== undefined)
66
+ query.include_diverged_commits_count = input.include_diverged_commits_count;
67
+ if (input.include_rebase_in_progress !== undefined)
68
+ query.include_rebase_in_progress = input.include_rebase_in_progress;
69
+ if (input.merge_request_iid) {
70
+ return gitlab_api_1.gitlab.get(`projects/${(0, projectIdentifier_1.normalizeProjectId)(input.project_id)}/merge_requests/${input.merge_request_iid}`, Object.keys(query).length > 0 ? { query } : undefined);
71
+ }
72
+ else if (input.branch_name) {
73
+ const result = await gitlab_api_1.gitlab.get(`projects/${(0, projectIdentifier_1.normalizeProjectId)(input.project_id)}/merge_requests`, { query: { source_branch: input.branch_name, ...query } });
74
+ if (Array.isArray(result) && result.length > 0) {
75
+ return result[0];
76
+ }
77
+ throw new Error("No merge request found for branch");
78
+ }
79
+ throw new Error("Either merge_request_iid or branch_name must be provided");
100
80
  }
101
- }
102
- return (0, idConversion_1.cleanGidsFromObject)(result);
103
- },
104
- },
105
- ],
106
- [
107
- "list_merge_requests",
108
- {
109
- name: "list_merge_requests",
110
- description: "BROWSE: List merge requests in a GitLab project with extensive filtering capabilities. Use when: Finding MRs by state/author/assignee, Complex queries for MR management, Reporting on merge requests. Can search globally or within specific projects.",
111
- inputSchema: z.toJSONSchema(schema_readonly_1.ListMergeRequestsSchema),
112
- handler: async (args) => {
113
- const options = schema_readonly_1.ListMergeRequestsSchema.parse(args);
114
- const queryParams = new URLSearchParams();
115
- Object.entries(options).forEach(([key, value]) => {
116
- if (value !== undefined && value !== null && key !== "project_id") {
117
- queryParams.set(key, String(value));
81
+ case "diffs": {
82
+ const query = {};
83
+ if (input.page !== undefined)
84
+ query.page = input.page;
85
+ if (input.per_page !== undefined)
86
+ query.per_page = input.per_page;
87
+ if (input.include_diverged_commits_count !== undefined)
88
+ query.include_diverged_commits_count = input.include_diverged_commits_count;
89
+ if (input.include_rebase_in_progress !== undefined)
90
+ query.include_rebase_in_progress = input.include_rebase_in_progress;
91
+ return gitlab_api_1.gitlab.get(`projects/${(0, projectIdentifier_1.normalizeProjectId)(input.project_id)}/merge_requests/${input.merge_request_iid}/changes`, { query });
118
92
  }
119
- });
120
- const apiUrl = options.project_id
121
- ? `${process.env.GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(options.project_id)}/merge_requests?${queryParams}`
122
- : `${process.env.GITLAB_API_URL}/api/v4/merge_requests?${queryParams}`;
123
- const response = await (0, fetch_1.enhancedFetch)(apiUrl);
124
- if (!response.ok) {
125
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}`);
126
- }
127
- const mergeRequests = await response.json();
128
- return (0, idConversion_1.cleanGidsFromObject)(mergeRequests);
129
- },
130
- },
131
- ],
132
- [
133
- "get_merge_request_diffs",
134
- {
135
- name: "get_merge_request_diffs",
136
- description: "READ: Get all file changes and diffs included in a merge request. Use when: Reviewing code changes, Analyzing modifications, Automating code review processes. Shows actual file differences that would be applied if merged.",
137
- inputSchema: z.toJSONSchema(schema_readonly_1.GetMergeRequestDiffsSchema),
138
- handler: async (args) => {
139
- const options = schema_readonly_1.GetMergeRequestDiffsSchema.parse(args);
140
- const { project_id, merge_request_iid, page, per_page } = options;
141
- const queryParams = new URLSearchParams();
142
- if (page !== undefined) {
143
- queryParams.set("page", String(page));
144
- }
145
- if (per_page !== undefined) {
146
- queryParams.set("per_page", String(per_page));
147
- }
148
- const apiUrl = `${process.env.GITLAB_API_URL}/api/v4/projects/${(0, projectIdentifier_1.normalizeProjectId)(project_id)}/merge_requests/${merge_request_iid}/changes?${queryParams}`;
149
- const response = await (0, fetch_1.enhancedFetch)(apiUrl);
150
- if (!response.ok) {
151
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}`);
152
- }
153
- const diffs = await response.json();
154
- return (0, idConversion_1.cleanGidsFromObject)(diffs);
155
- },
156
- },
157
- ],
158
- [
159
- "list_merge_request_diffs",
160
- {
161
- name: "list_merge_request_diffs",
162
- description: "BROWSE: List all diffs in a merge request with pagination for large changesets. Use when: Dealing with MRs containing many changes, Managing memory usage, Processing large diffs efficiently. Provides paginated access to file modifications.",
163
- inputSchema: z.toJSONSchema(schema_readonly_1.ListMergeRequestDiffsSchema),
164
- handler: async (args) => {
165
- const options = schema_readonly_1.ListMergeRequestDiffsSchema.parse(args);
166
- const { project_id, merge_request_iid, page, per_page } = options;
167
- const queryParams = new URLSearchParams();
168
- if (page !== undefined) {
169
- queryParams.set("page", String(page));
170
- }
171
- if (per_page !== undefined) {
172
- queryParams.set("per_page", String(per_page));
173
- }
174
- const apiUrl = `${process.env.GITLAB_API_URL}/api/v4/projects/${(0, projectIdentifier_1.normalizeProjectId)(project_id)}/merge_requests/${merge_request_iid}/diffs?${queryParams}`;
175
- const response = await (0, fetch_1.enhancedFetch)(apiUrl);
176
- if (!response.ok) {
177
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}`);
178
- }
179
- const diffs = await response.json();
180
- return (0, idConversion_1.cleanGidsFromObject)(diffs);
181
- },
182
- },
183
- ],
184
- [
185
- "mr_discussions",
186
- {
187
- name: "mr_discussions",
188
- description: "DISCUSS: List all discussion threads and comments on a merge request. Use when: Tracking code review feedback, Managing conversations, Extracting review insights. Includes both resolved and unresolved discussions with full context.",
189
- inputSchema: z.toJSONSchema(schema_readonly_1.ListMergeRequestDiscussionsSchema),
190
- handler: async (args) => {
191
- const options = schema_readonly_1.ListMergeRequestDiscussionsSchema.parse(args);
192
- const { project_id, merge_request_iid } = options;
193
- const queryParams = new URLSearchParams();
194
- Object.entries(options).forEach(([key, value]) => {
195
- if (value !== undefined && key !== "project_id" && key !== "merge_request_iid") {
196
- queryParams.set(key, String(value));
93
+ case "compare": {
94
+ const query = {
95
+ from: input.from,
96
+ to: input.to,
97
+ };
98
+ if (input.straight !== undefined)
99
+ query.straight = input.straight;
100
+ return gitlab_api_1.gitlab.get(`projects/${(0, projectIdentifier_1.normalizeProjectId)(input.project_id)}/repository/compare`, { query });
197
101
  }
198
- });
199
- const apiUrl = `${process.env.GITLAB_API_URL}/api/v4/projects/${(0, projectIdentifier_1.normalizeProjectId)(project_id)}/merge_requests/${merge_request_iid}/discussions?${queryParams}`;
200
- const response = await (0, fetch_1.enhancedFetch)(apiUrl);
201
- if (!response.ok) {
202
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}`);
203
- }
204
- const discussions = await response.json();
205
- return (0, idConversion_1.cleanGidsFromObject)(discussions);
206
- },
207
- },
208
- ],
209
- [
210
- "get_draft_note",
211
- {
212
- name: "get_draft_note",
213
- description: "DRAFT: Retrieve a specific draft note (unpublished comment) from a merge request. Use when: Reviewing pending feedback before publishing, Managing draft review comments. Draft notes are only visible to their author until published.",
214
- inputSchema: z.toJSONSchema(schema_readonly_1.GetDraftNoteSchema),
215
- handler: async (args) => {
216
- const options = schema_readonly_1.GetDraftNoteSchema.parse(args);
217
- const { project_id, merge_request_iid, draft_note_id } = options;
218
- const apiUrl = `${process.env.GITLAB_API_URL}/api/v4/projects/${(0, projectIdentifier_1.normalizeProjectId)(project_id)}/merge_requests/${merge_request_iid}/draft_notes/${draft_note_id}`;
219
- const response = await (0, fetch_1.enhancedFetch)(apiUrl);
220
- if (!response.ok) {
221
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}`);
222
- }
223
- const draftNote = await response.json();
224
- return (0, idConversion_1.cleanGidsFromObject)(draftNote);
225
- },
226
- },
227
- ],
228
- [
229
- "list_draft_notes",
230
- {
231
- name: "list_draft_notes",
232
- description: "DRAFT: List all draft notes (unpublished comments) for a merge request. Use when: Reviewing all pending feedback before publishing, Managing batch review comments. Draft notes allow reviewers to prepare comprehensive feedback before sharing.",
233
- inputSchema: z.toJSONSchema(schema_readonly_1.ListDraftNotesSchema),
234
- handler: async (args) => {
235
- const options = schema_readonly_1.ListDraftNotesSchema.parse(args);
236
- const { project_id, merge_request_iid } = options;
237
- const queryParams = new URLSearchParams();
238
- Object.entries(options).forEach(([key, value]) => {
239
- if (value !== undefined && key !== "project_id" && key !== "merge_request_iid") {
240
- queryParams.set(key, String(value));
102
+ default: {
103
+ const _exhaustive = input;
104
+ throw new Error(`Unknown action: ${_exhaustive.action}`);
241
105
  }
242
- });
243
- const apiUrl = `${process.env.GITLAB_API_URL}/api/v4/projects/${(0, projectIdentifier_1.normalizeProjectId)(project_id)}/merge_requests/${merge_request_iid}/draft_notes?${queryParams}`;
244
- const response = await (0, fetch_1.enhancedFetch)(apiUrl);
245
- if (!response.ok) {
246
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}`);
247
106
  }
248
- const draftNotes = await response.json();
249
- return (0, idConversion_1.cleanGidsFromObject)(draftNotes);
250
107
  },
251
108
  },
252
109
  ],
253
110
  [
254
- "create_merge_request",
111
+ "browse_mr_discussions",
255
112
  {
256
- name: "create_merge_request",
257
- description: "CREATE: Create a new merge request to propose code changes for review and merging. Use when: Initiating code review process, Proposing features, Submitting fixes. For labels: Use list_labels FIRST to discover existing project taxonomy. Requires source and target branches, supports setting assignees, reviewers, and labels.",
258
- inputSchema: z.toJSONSchema(schema_1.CreateMergeRequestSchema),
113
+ name: "browse_mr_discussions",
114
+ description: 'BROWSE MR discussions and draft notes. Actions: "list" shows all discussion threads, "drafts" lists unpublished draft notes, "draft" gets single draft note.',
115
+ inputSchema: z.toJSONSchema(schema_readonly_1.BrowseMrDiscussionsSchema),
259
116
  handler: async (args) => {
260
- const options = schema_1.CreateMergeRequestSchema.parse(args);
261
- const body = new URLSearchParams();
262
- Object.entries(options).forEach(([key, value]) => {
263
- if (value !== undefined && value !== null) {
264
- if (Array.isArray(value)) {
265
- body.set(key, value.join(","));
266
- }
267
- else {
268
- body.set(key, String(value));
269
- }
117
+ const input = schema_readonly_1.BrowseMrDiscussionsSchema.parse(args);
118
+ switch (input.action) {
119
+ case "list": {
120
+ const { action: _action, project_id, merge_request_iid, ...rest } = input;
121
+ const query = (0, gitlab_api_1.toQuery)(rest, []);
122
+ return gitlab_api_1.gitlab.get(`projects/${(0, projectIdentifier_1.normalizeProjectId)(project_id)}/merge_requests/${merge_request_iid}/discussions`, { query });
270
123
  }
271
- });
272
- const apiUrl = `${process.env.GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(options.project_id)}/merge_requests`;
273
- const response = await (0, fetch_1.enhancedFetch)(apiUrl, {
274
- method: "POST",
275
- headers: {
276
- "Content-Type": "application/x-www-form-urlencoded",
277
- },
278
- body: body.toString(),
279
- });
280
- if (!response.ok) {
281
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}`);
282
- }
283
- const mergeRequest = await response.json();
284
- return (0, idConversion_1.cleanGidsFromObject)(mergeRequest);
285
- },
286
- },
287
- ],
288
- [
289
- "merge_merge_request",
290
- {
291
- name: "merge_merge_request",
292
- description: "MERGE: Merge an approved merge request into the target branch. Use when: Completing the code review process, Integrating changes. Supports various merge methods (merge commit, squash, rebase) and can delete source branch after merging.",
293
- inputSchema: z.toJSONSchema(schema_1.MergeMergeRequestSchema),
294
- handler: async (args) => {
295
- const options = schema_1.MergeMergeRequestSchema.parse(args);
296
- const body = new URLSearchParams();
297
- Object.entries(options).forEach(([key, value]) => {
298
- if (value !== undefined && key !== "project_id" && key !== "merge_request_iid") {
299
- body.set(key, String(value));
124
+ case "drafts": {
125
+ return gitlab_api_1.gitlab.get(`projects/${(0, projectIdentifier_1.normalizeProjectId)(input.project_id)}/merge_requests/${input.merge_request_iid}/draft_notes`);
126
+ }
127
+ case "draft": {
128
+ return gitlab_api_1.gitlab.get(`projects/${(0, projectIdentifier_1.normalizeProjectId)(input.project_id)}/merge_requests/${input.merge_request_iid}/draft_notes/${input.draft_note_id}`);
129
+ }
130
+ default: {
131
+ const _exhaustive = input;
132
+ throw new Error(`Unknown action: ${_exhaustive.action}`);
300
133
  }
301
- });
302
- const apiUrl = `${process.env.GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(options.project_id)}/merge_requests/${options.merge_request_iid}/merge`;
303
- const response = await (0, fetch_1.enhancedFetch)(apiUrl, {
304
- method: "PUT",
305
- headers: {
306
- "Content-Type": "application/x-www-form-urlencoded",
307
- },
308
- body: body.toString(),
309
- });
310
- if (!response.ok) {
311
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}`);
312
- }
313
- const result = await response.json();
314
- return (0, idConversion_1.cleanGidsFromObject)(result);
315
- },
316
- },
317
- ],
318
- [
319
- "create_note",
320
- {
321
- name: "create_note",
322
- description: "COMMENT: Add a comment to an issue or merge request for discussion or feedback. Use when: Providing code review comments, Asking questions, Documenting decisions. Supports markdown formatting and can trigger notifications to participants.",
323
- inputSchema: z.toJSONSchema(schema_1.CreateNoteSchema),
324
- handler: async (args) => {
325
- const options = schema_1.CreateNoteSchema.parse(args);
326
- const body = new URLSearchParams();
327
- body.set("body", options.body);
328
- if (options.created_at) {
329
- body.set("created_at", options.created_at);
330
- }
331
- if (options.confidential !== undefined) {
332
- body.set("confidential", String(options.confidential));
333
- }
334
- const resourceType = options.noteable_type === "merge_request" ? "merge_requests" : "issues";
335
- const resourceId = options.noteable_id;
336
- const apiUrl = `${process.env.GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(options.project_id)}/${resourceType}/${resourceId}/notes`;
337
- const response = await (0, fetch_1.enhancedFetch)(apiUrl, {
338
- method: "POST",
339
- headers: {
340
- "Content-Type": "application/x-www-form-urlencoded",
341
- },
342
- body: body.toString(),
343
- });
344
- if (!response.ok) {
345
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}`);
346
- }
347
- const note = await response.json();
348
- return (0, idConversion_1.cleanGidsFromObject)(note);
349
- },
350
- },
351
- ],
352
- [
353
- "create_draft_note",
354
- {
355
- name: "create_draft_note",
356
- description: "DRAFT: Create a draft note (unpublished comment) on a merge request. Use when: Preparing review feedback that can be refined before publishing. Draft notes are ideal for comprehensive reviews where all comments are published together.",
357
- inputSchema: z.toJSONSchema(schema_1.CreateDraftNoteSchema),
358
- handler: async (args) => {
359
- const options = schema_1.CreateDraftNoteSchema.parse(args);
360
- const body = new URLSearchParams();
361
- body.set("note", options.note);
362
- if (options.position) {
363
- body.set("position", JSON.stringify(options.position));
364
- }
365
- const apiUrl = `${process.env.GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(options.project_id)}/merge_requests/${options.merge_request_iid}/draft_notes`;
366
- const response = await (0, fetch_1.enhancedFetch)(apiUrl, {
367
- method: "POST",
368
- headers: {
369
- "Content-Type": "application/x-www-form-urlencoded",
370
- },
371
- body: body.toString(),
372
- });
373
- if (!response.ok) {
374
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}`);
375
- }
376
- const draftNote = await response.json();
377
- return (0, idConversion_1.cleanGidsFromObject)(draftNote);
378
- },
379
- },
380
- ],
381
- [
382
- "publish_draft_note",
383
- {
384
- name: "publish_draft_note",
385
- description: "DRAFT: Publish a previously created draft note to make it visible to all participants. Use when: Selectively sharing specific review comments when ready. Once published, the note becomes a regular comment and triggers notifications.",
386
- inputSchema: z.toJSONSchema(schema_1.PublishDraftNoteSchema),
387
- handler: async (args) => {
388
- const options = schema_1.PublishDraftNoteSchema.parse(args);
389
- const apiUrl = `${process.env.GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(options.project_id)}/merge_requests/${options.merge_request_iid}/draft_notes/${options.draft_note_id}/publish`;
390
- const response = await (0, fetch_1.enhancedFetch)(apiUrl, {
391
- method: "PUT",
392
- });
393
- if (!response.ok) {
394
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}`);
395
- }
396
- const result = response.status === 204 ? { published: true } : await response.json();
397
- return (0, idConversion_1.cleanGidsFromObject)(result);
398
- },
399
- },
400
- ],
401
- [
402
- "bulk_publish_draft_notes",
403
- {
404
- name: "bulk_publish_draft_notes",
405
- description: "Publish all pending draft notes for a merge request simultaneously. Use to share comprehensive review feedback in one action. Ideal for thorough code reviews where all comments should be seen together for context.",
406
- inputSchema: z.toJSONSchema(schema_1.BulkPublishDraftNotesSchema),
407
- handler: async (args) => {
408
- const options = schema_1.BulkPublishDraftNotesSchema.parse(args);
409
- const apiUrl = `${process.env.GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(options.project_id)}/merge_requests/${options.merge_request_iid}/draft_notes/bulk_publish`;
410
- const response = await (0, fetch_1.enhancedFetch)(apiUrl, {
411
- method: "POST",
412
- });
413
- if (!response.ok) {
414
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}`);
415
134
  }
416
- const result = response.status === 204 ? { published: true } : await response.json();
417
- return (0, idConversion_1.cleanGidsFromObject)(result);
418
135
  },
419
136
  },
420
137
  ],
421
138
  [
422
- "update_merge_request",
139
+ "manage_merge_request",
423
140
  {
424
- name: "update_merge_request",
425
- description: "UPDATE: Update properties of an existing merge request such as title, description, or assignees. Use when: Refining MR details, Changing reviewers, Updating labels. For labels: Use list_labels FIRST to discover existing taxonomy before updating. Accepts either MR IID or source branch name for identification.",
426
- inputSchema: z.toJSONSchema(schema_1.UpdateMergeRequestSchema),
141
+ name: "manage_merge_request",
142
+ description: 'MANAGE merge requests. Actions: "create" creates new MR, "update" modifies existing MR, "merge" merges approved MR into target branch.',
143
+ inputSchema: z.toJSONSchema(schema_1.ManageMergeRequestSchema),
427
144
  handler: async (args) => {
428
- const options = schema_1.UpdateMergeRequestSchema.parse(args);
429
- const body = new URLSearchParams();
430
- Object.entries(options).forEach(([key, value]) => {
431
- if (value !== undefined && key !== "project_id" && key !== "merge_request_iid") {
432
- if (Array.isArray(value)) {
433
- body.set(key, value.join(","));
145
+ const input = schema_1.ManageMergeRequestSchema.parse(args);
146
+ switch (input.action) {
147
+ case "create": {
148
+ const { action: _action, project_id, ...body } = input;
149
+ const processedBody = {};
150
+ for (const [key, value] of Object.entries(body)) {
151
+ if (Array.isArray(value)) {
152
+ processedBody[key] = value.join(",");
153
+ }
154
+ else {
155
+ processedBody[key] = value;
156
+ }
434
157
  }
435
- else {
436
- body.set(key, String(value));
158
+ return gitlab_api_1.gitlab.post(`projects/${(0, projectIdentifier_1.normalizeProjectId)(project_id)}/merge_requests`, {
159
+ body: processedBody,
160
+ contentType: "form",
161
+ });
162
+ }
163
+ case "update": {
164
+ const { action: _action, project_id, merge_request_iid, ...body } = input;
165
+ const processedBody = {};
166
+ for (const [key, value] of Object.entries(body)) {
167
+ if (Array.isArray(value)) {
168
+ processedBody[key] = value.join(",");
169
+ }
170
+ else {
171
+ processedBody[key] = value;
172
+ }
437
173
  }
174
+ return gitlab_api_1.gitlab.put(`projects/${(0, projectIdentifier_1.normalizeProjectId)(project_id)}/merge_requests/${merge_request_iid}`, { body: processedBody, contentType: "form" });
175
+ }
176
+ case "merge": {
177
+ const { action: _action, project_id, merge_request_iid, ...body } = input;
178
+ return gitlab_api_1.gitlab.put(`projects/${(0, projectIdentifier_1.normalizeProjectId)(project_id)}/merge_requests/${merge_request_iid}/merge`, { body, contentType: "form" });
179
+ }
180
+ default: {
181
+ const _exhaustive = input;
182
+ throw new Error(`Unknown action: ${_exhaustive.action}`);
438
183
  }
439
- });
440
- const apiUrl = `${process.env.GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(options.project_id)}/merge_requests/${options.merge_request_iid}`;
441
- const response = await (0, fetch_1.enhancedFetch)(apiUrl, {
442
- method: "PUT",
443
- headers: {
444
- "Content-Type": "application/x-www-form-urlencoded",
445
- },
446
- body: body.toString(),
447
- });
448
- if (!response.ok) {
449
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}`);
450
- }
451
- const mergeRequest = await response.json();
452
- return (0, idConversion_1.cleanGidsFromObject)(mergeRequest);
453
- },
454
- },
455
- ],
456
- [
457
- "create_merge_request_thread",
458
- {
459
- name: "create_merge_request_thread",
460
- description: "Start a new discussion thread on a merge request for focused conversation. Use to raise specific concerns, ask questions about code sections, or initiate design discussions. Threads can be resolved when addressed.",
461
- inputSchema: z.toJSONSchema(schema_1.CreateMergeRequestThreadSchema),
462
- handler: async (args) => {
463
- const options = schema_1.CreateMergeRequestThreadSchema.parse(args);
464
- const body = new URLSearchParams();
465
- body.set("body", options.body);
466
- if (options.position) {
467
- body.set("position", JSON.stringify(options.position));
468
- }
469
- if (options.commit_id) {
470
- body.set("commit_id", options.commit_id);
471
- }
472
- const apiUrl = `${process.env.GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(options.project_id)}/merge_requests/${options.merge_request_iid}/discussions`;
473
- const response = await (0, fetch_1.enhancedFetch)(apiUrl, {
474
- method: "POST",
475
- headers: {
476
- "Content-Type": "application/x-www-form-urlencoded",
477
- },
478
- body: body.toString(),
479
- });
480
- if (!response.ok) {
481
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}`);
482
- }
483
- const discussion = await response.json();
484
- return (0, idConversion_1.cleanGidsFromObject)(discussion);
485
- },
486
- },
487
- ],
488
- [
489
- "update_merge_request_note",
490
- {
491
- name: "update_merge_request_note",
492
- description: "Edit an existing comment within a merge request discussion thread. Use to correct mistakes, clarify points, or update information in previous comments. Maintains discussion history while allowing content refinement.",
493
- inputSchema: z.toJSONSchema(schema_1.UpdateMergeRequestNoteSchema),
494
- handler: async (args) => {
495
- const options = schema_1.UpdateMergeRequestNoteSchema.parse(args);
496
- const body = new URLSearchParams();
497
- body.set("body", options.body);
498
- const apiUrl = `${process.env.GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(options.project_id)}/merge_requests/${options.merge_request_iid}/notes/${options.note_id}`;
499
- const response = await (0, fetch_1.enhancedFetch)(apiUrl, {
500
- method: "PUT",
501
- headers: {
502
- "Content-Type": "application/x-www-form-urlencoded",
503
- },
504
- body: body.toString(),
505
- });
506
- if (!response.ok) {
507
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}`);
508
- }
509
- const note = await response.json();
510
- return (0, idConversion_1.cleanGidsFromObject)(note);
511
- },
512
- },
513
- ],
514
- [
515
- "create_merge_request_note",
516
- {
517
- name: "create_merge_request_note",
518
- description: "Reply to an existing discussion thread in a merge request. Use to continue conversations, provide answers, or add context to ongoing discussions. Keeps related comments organized in threaded format.",
519
- inputSchema: z.toJSONSchema(schema_1.CreateMergeRequestNoteSchema),
520
- handler: async (args) => {
521
- const options = schema_1.CreateMergeRequestNoteSchema.parse(args);
522
- const body = new URLSearchParams();
523
- body.set("body", options.body);
524
- if (options.created_at) {
525
- body.set("created_at", options.created_at);
526
- }
527
- const apiUrl = `${process.env.GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(options.project_id)}/merge_requests/${options.merge_request_iid}/discussions/${options.discussion_id}/notes`;
528
- const response = await (0, fetch_1.enhancedFetch)(apiUrl, {
529
- method: "POST",
530
- headers: {
531
- "Content-Type": "application/x-www-form-urlencoded",
532
- },
533
- body: body.toString(),
534
- });
535
- if (!response.ok) {
536
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}`);
537
184
  }
538
- const note = await response.json();
539
- return (0, idConversion_1.cleanGidsFromObject)(note);
540
185
  },
541
186
  },
542
187
  ],
543
188
  [
544
- "update_draft_note",
189
+ "manage_mr_discussion",
545
190
  {
546
- name: "update_draft_note",
547
- description: "Modify a draft note before publishing to refine review feedback. Use to edit, improve, or correct draft comments based on further code examination. Changes are only visible to the author until the note is published.",
548
- inputSchema: z.toJSONSchema(schema_1.UpdateDraftNoteSchema),
191
+ name: "manage_mr_discussion",
192
+ description: 'MANAGE MR discussions. Actions: "comment" adds comment to issue/MR, "thread" starts new discussion, "reply" responds to existing thread, "update" modifies note.',
193
+ inputSchema: z.toJSONSchema(schema_1.ManageMrDiscussionSchema),
549
194
  handler: async (args) => {
550
- const options = schema_1.UpdateDraftNoteSchema.parse(args);
551
- const body = new URLSearchParams();
552
- body.set("note", options.note);
553
- if (options.position) {
554
- body.set("position", JSON.stringify(options.position));
555
- }
556
- const apiUrl = `${process.env.GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(options.project_id)}/merge_requests/${options.merge_request_iid}/draft_notes/${options.draft_note_id}`;
557
- const response = await (0, fetch_1.enhancedFetch)(apiUrl, {
558
- method: "PUT",
559
- headers: {
560
- "Content-Type": "application/x-www-form-urlencoded",
561
- },
562
- body: body.toString(),
563
- });
564
- if (!response.ok) {
565
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}`);
195
+ const input = schema_1.ManageMrDiscussionSchema.parse(args);
196
+ switch (input.action) {
197
+ case "comment": {
198
+ const body = { body: input.body };
199
+ if (input.created_at)
200
+ body.created_at = input.created_at;
201
+ if (input.confidential !== undefined)
202
+ body.confidential = input.confidential;
203
+ const resourceType = input.noteable_type === "merge_request" ? "merge_requests" : "issues";
204
+ return gitlab_api_1.gitlab.post(`projects/${(0, projectIdentifier_1.normalizeProjectId)(input.project_id)}/${resourceType}/${input.noteable_id}/notes`, { body, contentType: "form" });
205
+ }
206
+ case "thread": {
207
+ const body = { body: input.body };
208
+ if (input.position)
209
+ body.position = JSON.stringify(input.position);
210
+ if (input.commit_id)
211
+ body.commit_id = input.commit_id;
212
+ return gitlab_api_1.gitlab.post(`projects/${(0, projectIdentifier_1.normalizeProjectId)(input.project_id)}/merge_requests/${input.merge_request_iid}/discussions`, { body, contentType: "form" });
213
+ }
214
+ case "reply": {
215
+ const body = { body: input.body };
216
+ if (input.created_at)
217
+ body.created_at = input.created_at;
218
+ return gitlab_api_1.gitlab.post(`projects/${(0, projectIdentifier_1.normalizeProjectId)(input.project_id)}/merge_requests/${input.merge_request_iid}/discussions/${input.discussion_id}/notes`, { body, contentType: "form" });
219
+ }
220
+ case "update": {
221
+ return gitlab_api_1.gitlab.put(`projects/${(0, projectIdentifier_1.normalizeProjectId)(input.project_id)}/merge_requests/${input.merge_request_iid}/notes/${input.note_id}`, { body: { body: input.body }, contentType: "form" });
222
+ }
223
+ default: {
224
+ const _exhaustive = input;
225
+ throw new Error(`Unknown action: ${_exhaustive.action}`);
226
+ }
566
227
  }
567
- const draftNote = await response.json();
568
- return (0, idConversion_1.cleanGidsFromObject)(draftNote);
569
228
  },
570
229
  },
571
230
  ],
572
231
  [
573
- "delete_draft_note",
232
+ "manage_draft_notes",
574
233
  {
575
- name: "delete_draft_note",
576
- description: "Remove a draft note that is no longer needed or relevant. Use to clean up draft feedback that won't be published or to start fresh with review comments. Only the author can delete their own draft notes.",
577
- inputSchema: z.toJSONSchema(schema_1.DeleteDraftNoteSchema),
234
+ name: "manage_draft_notes",
235
+ description: 'MANAGE draft notes. Actions: "create" creates draft note, "update" modifies draft, "publish" publishes single draft, "publish_all" publishes all drafts, "delete" removes draft.',
236
+ inputSchema: z.toJSONSchema(schema_1.ManageDraftNotesSchema),
578
237
  handler: async (args) => {
579
- const options = schema_1.DeleteDraftNoteSchema.parse(args);
580
- const apiUrl = `${process.env.GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(options.project_id)}/merge_requests/${options.merge_request_iid}/draft_notes/${options.draft_note_id}`;
581
- const response = await (0, fetch_1.enhancedFetch)(apiUrl, {
582
- method: "DELETE",
583
- });
584
- if (!response.ok) {
585
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}`);
238
+ const input = schema_1.ManageDraftNotesSchema.parse(args);
239
+ switch (input.action) {
240
+ case "create": {
241
+ const body = { note: input.note };
242
+ if (input.position)
243
+ body.position = JSON.stringify(input.position);
244
+ if (input.in_reply_to_discussion_id)
245
+ body.in_reply_to_discussion_id = input.in_reply_to_discussion_id;
246
+ if (input.commit_id)
247
+ body.commit_id = input.commit_id;
248
+ return gitlab_api_1.gitlab.post(`projects/${(0, projectIdentifier_1.normalizeProjectId)(input.project_id)}/merge_requests/${input.merge_request_iid}/draft_notes`, { body, contentType: "form" });
249
+ }
250
+ case "update": {
251
+ const body = { note: input.note };
252
+ if (input.position)
253
+ body.position = JSON.stringify(input.position);
254
+ return gitlab_api_1.gitlab.put(`projects/${(0, projectIdentifier_1.normalizeProjectId)(input.project_id)}/merge_requests/${input.merge_request_iid}/draft_notes/${input.draft_note_id}`, { body, contentType: "form" });
255
+ }
256
+ case "publish": {
257
+ const result = await gitlab_api_1.gitlab.put(`projects/${(0, projectIdentifier_1.normalizeProjectId)(input.project_id)}/merge_requests/${input.merge_request_iid}/draft_notes/${input.draft_note_id}/publish`);
258
+ return result ?? { published: true };
259
+ }
260
+ case "publish_all": {
261
+ const result = await gitlab_api_1.gitlab.post(`projects/${(0, projectIdentifier_1.normalizeProjectId)(input.project_id)}/merge_requests/${input.merge_request_iid}/draft_notes/bulk_publish`);
262
+ return result ?? { published: true };
263
+ }
264
+ case "delete": {
265
+ await gitlab_api_1.gitlab.delete(`projects/${(0, projectIdentifier_1.normalizeProjectId)(input.project_id)}/merge_requests/${input.merge_request_iid}/draft_notes/${input.draft_note_id}`);
266
+ return { success: true, message: "Draft note deleted successfully" };
267
+ }
268
+ default: {
269
+ const _exhaustive = input;
270
+ throw new Error(`Unknown action: ${_exhaustive.action}`);
271
+ }
586
272
  }
587
- return { success: true, message: "Draft note deleted successfully" };
588
273
  },
589
274
  },
590
275
  ],
591
276
  ]);
592
277
  function getMrsReadOnlyToolNames() {
593
- return [
594
- "get_branch_diffs",
595
- "get_merge_request",
596
- "get_merge_request_diffs",
597
- "list_merge_request_diffs",
598
- "mr_discussions",
599
- "get_draft_note",
600
- "list_draft_notes",
601
- "list_merge_requests",
602
- ];
278
+ return ["browse_merge_requests", "browse_mr_discussions"];
603
279
  }
604
280
  function getMrsToolDefinitions() {
605
281
  return Array.from(exports.mrsToolRegistry.values());