@zereight/mcp-gitlab 1.0.77 → 2.0.2

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.
@@ -0,0 +1,1344 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
3
+ import { logger } from "./logger.js";
4
+ import { z } from "zod";
5
+ import { zodToJsonSchema } from "zod-to-json-schema";
6
+ import { fileURLToPath } from "url";
7
+ import { dirname } from "path";
8
+ import fs from "fs";
9
+ import path from "path";
10
+ import { config } from "./config.js";
11
+ import { GitlabHandler } from "./gitlabhandler.js";
12
+ import { ForkRepositorySchema, CreateBranchSchema, CreateOrUpdateFileSchema, SearchRepositoriesSchema, CreateRepositorySchema, GetFileContentsSchema, PushFilesSchema, CreateIssueSchema, CreateMergeRequestSchema, GetMergeRequestSchema, GetMergeRequestDiffsSchema, UpdateMergeRequestSchema, ListIssuesSchema, GetIssueSchema, UpdateIssueSchema, DeleteIssueSchema, ListIssueLinksSchema, ListIssueDiscussionsSchema, GetIssueLinkSchema, CreateIssueLinkSchema, DeleteIssueLinkSchema, ListNamespacesSchema, GetNamespaceSchema, VerifyNamespaceSchema, GetProjectSchema, ListProjectsSchema, ListLabelsSchema, GetLabelSchema, CreateLabelSchema, UpdateLabelSchema, DeleteLabelSchema, CreateNoteSchema, CreateMergeRequestThreadSchema, ListGroupProjectsSchema, ListWikiPagesSchema, GetWikiPageSchema, CreateWikiPageSchema, UpdateWikiPageSchema, DeleteWikiPageSchema, GetRepositoryTreeSchema, GetPipelineSchema, ListPipelinesSchema, ListPipelineJobsSchema, CreatePipelineSchema, RetryPipelineSchema, CancelPipelineSchema, GetPipelineJobOutputSchema, UpdateMergeRequestNoteSchema, CreateMergeRequestNoteSchema, ListMergeRequestDiscussionsSchema, UpdateIssueNoteSchema, CreateIssueNoteSchema, ListMergeRequestsSchema, ListProjectMilestonesSchema, GetProjectMilestoneSchema, CreateProjectMilestoneSchema, EditProjectMilestoneSchema, DeleteProjectMilestoneSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, PromoteProjectMilestoneSchema, GetMilestoneBurndownEventsSchema, GetBranchDiffsSchema, GetUsersSchema, ListCommitsSchema, GetCommitSchema, GetCommitDiffSchema, ListMergeRequestDiffsSchema, GetCurrentUserSchema, GetDraftNoteSchema, ListDraftNotesSchema, CreateDraftNoteSchema, UpdateDraftNoteSchema, DeleteDraftNoteSchema, PublishDraftNoteSchema, BulkPublishDraftNotesSchema, MergeMergeRequestSchema, MyIssuesSchema, ListProjectMembersSchema, MarkdownUploadSchema, DownloadAttachmentSchema, } from "./schemas.js";
13
+ import { createCookieJar } from "./authhelpers.js";
14
+ /**
15
+ * Read version from package.json
16
+ */
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = dirname(__filename);
19
+ const packageJsonPath = path.resolve(__dirname, "../package.json");
20
+ let SERVER_VERSION = "unknown";
21
+ try {
22
+ if (fs.existsSync(packageJsonPath)) {
23
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
24
+ SERVER_VERSION = packageJson.version || SERVER_VERSION;
25
+ }
26
+ }
27
+ catch (error) {
28
+ // Warning: Could not read version from package.json - silently continue
29
+ }
30
+ // create the underlying mcp server
31
+ const server = new Server({
32
+ name: "better-gitlab-mcp-server",
33
+ version: SERVER_VERSION,
34
+ }, {
35
+ capabilities: {
36
+ tools: {},
37
+ },
38
+ });
39
+ // Define all available tools
40
+ const allTools = [
41
+ {
42
+ name: "create_or_update_file",
43
+ description: "Create or update a single file in a GitLab project",
44
+ inputSchema: zodToJsonSchema(CreateOrUpdateFileSchema),
45
+ },
46
+ {
47
+ name: "search_repositories",
48
+ description: "Search for GitLab projects",
49
+ inputSchema: zodToJsonSchema(SearchRepositoriesSchema),
50
+ },
51
+ {
52
+ name: "create_repository",
53
+ description: "Create a new GitLab project",
54
+ inputSchema: zodToJsonSchema(CreateRepositorySchema),
55
+ },
56
+ {
57
+ name: "get_file_contents",
58
+ description: "Get the contents of a file or directory from a GitLab project",
59
+ inputSchema: zodToJsonSchema(GetFileContentsSchema),
60
+ },
61
+ {
62
+ name: "push_files",
63
+ description: "Push multiple files to a GitLab project in a single commit",
64
+ inputSchema: zodToJsonSchema(PushFilesSchema),
65
+ },
66
+ {
67
+ name: "create_issue",
68
+ description: "Create a new issue in a GitLab project",
69
+ inputSchema: zodToJsonSchema(CreateIssueSchema),
70
+ },
71
+ {
72
+ name: "create_merge_request",
73
+ description: "Create a new merge request in a GitLab project",
74
+ inputSchema: zodToJsonSchema(CreateMergeRequestSchema),
75
+ },
76
+ {
77
+ name: "fork_repository",
78
+ description: "Fork a GitLab project to your account or specified namespace",
79
+ inputSchema: zodToJsonSchema(ForkRepositorySchema),
80
+ },
81
+ {
82
+ name: "create_branch",
83
+ description: "Create a new branch in a GitLab project",
84
+ inputSchema: zodToJsonSchema(CreateBranchSchema),
85
+ },
86
+ {
87
+ name: "get_merge_request",
88
+ description: "Get details of a merge request (Either mergeRequestIid or branchName must be provided)",
89
+ inputSchema: zodToJsonSchema(GetMergeRequestSchema),
90
+ },
91
+ {
92
+ name: "get_merge_request_diffs",
93
+ description: "Get the changes/diffs of a merge request (Either mergeRequestIid or branchName must be provided)",
94
+ inputSchema: zodToJsonSchema(GetMergeRequestDiffsSchema),
95
+ },
96
+ {
97
+ name: "list_merge_request_diffs",
98
+ description: "List merge request diffs with pagination support (Either mergeRequestIid or branchName must be provided)",
99
+ inputSchema: zodToJsonSchema(ListMergeRequestDiffsSchema),
100
+ },
101
+ {
102
+ name: "get_branch_diffs",
103
+ description: "Get the changes/diffs between two branches or commits in a GitLab project",
104
+ inputSchema: zodToJsonSchema(GetBranchDiffsSchema),
105
+ },
106
+ {
107
+ name: "update_merge_request",
108
+ description: "Update a merge request (Either mergeRequestIid or branchName must be provided)",
109
+ inputSchema: zodToJsonSchema(UpdateMergeRequestSchema),
110
+ },
111
+ {
112
+ name: "create_note",
113
+ description: "Create a new note (comment) to an issue or merge request",
114
+ inputSchema: zodToJsonSchema(CreateNoteSchema),
115
+ },
116
+ {
117
+ name: "create_merge_request_thread",
118
+ description: "Create a new thread on a merge request",
119
+ inputSchema: zodToJsonSchema(CreateMergeRequestThreadSchema),
120
+ },
121
+ {
122
+ name: "mr_discussions",
123
+ description: "List discussion items for a merge request",
124
+ inputSchema: zodToJsonSchema(ListMergeRequestDiscussionsSchema),
125
+ },
126
+ {
127
+ name: "update_merge_request_note",
128
+ description: "Modify an existing merge request thread note",
129
+ inputSchema: zodToJsonSchema(UpdateMergeRequestNoteSchema),
130
+ },
131
+ {
132
+ name: "create_merge_request_note",
133
+ description: "Add a new note to an existing merge request thread",
134
+ inputSchema: zodToJsonSchema(CreateMergeRequestNoteSchema),
135
+ },
136
+ {
137
+ name: "update_issue_note",
138
+ description: "Modify an existing issue thread note",
139
+ inputSchema: zodToJsonSchema(UpdateIssueNoteSchema),
140
+ },
141
+ {
142
+ name: "create_issue_note",
143
+ description: "Add a new note to an existing issue thread",
144
+ inputSchema: zodToJsonSchema(CreateIssueNoteSchema),
145
+ },
146
+ {
147
+ name: "list_issues",
148
+ description: "List issues in a GitLab project with filtering options",
149
+ inputSchema: zodToJsonSchema(ListIssuesSchema),
150
+ },
151
+ {
152
+ name: "get_issue",
153
+ description: "Get details of a specific issue in a GitLab project",
154
+ inputSchema: zodToJsonSchema(GetIssueSchema),
155
+ },
156
+ {
157
+ name: "update_issue",
158
+ description: "Update an issue in a GitLab project",
159
+ inputSchema: zodToJsonSchema(UpdateIssueSchema),
160
+ },
161
+ {
162
+ name: "delete_issue",
163
+ description: "Delete an issue from a GitLab project",
164
+ inputSchema: zodToJsonSchema(DeleteIssueSchema),
165
+ },
166
+ {
167
+ name: "list_issue_links",
168
+ description: "List all issue links for a specific issue",
169
+ inputSchema: zodToJsonSchema(ListIssueLinksSchema),
170
+ },
171
+ {
172
+ name: "list_issue_discussions",
173
+ description: "List discussions for an issue in a GitLab project",
174
+ inputSchema: zodToJsonSchema(ListIssueDiscussionsSchema),
175
+ },
176
+ {
177
+ name: "get_issue_link",
178
+ description: "Get a specific issue link",
179
+ inputSchema: zodToJsonSchema(GetIssueLinkSchema),
180
+ },
181
+ {
182
+ name: "create_issue_link",
183
+ description: "Create an issue link between two issues",
184
+ inputSchema: zodToJsonSchema(CreateIssueLinkSchema),
185
+ },
186
+ {
187
+ name: "delete_issue_link",
188
+ description: "Delete an issue link",
189
+ inputSchema: zodToJsonSchema(DeleteIssueLinkSchema),
190
+ },
191
+ {
192
+ name: "list_namespaces",
193
+ description: "List all namespaces available to the current user",
194
+ inputSchema: zodToJsonSchema(ListNamespacesSchema),
195
+ },
196
+ {
197
+ name: "get_namespace",
198
+ description: "Get details of a namespace by ID or path",
199
+ inputSchema: zodToJsonSchema(GetNamespaceSchema),
200
+ },
201
+ {
202
+ name: "verify_namespace",
203
+ description: "Verify if a namespace path exists",
204
+ inputSchema: zodToJsonSchema(VerifyNamespaceSchema),
205
+ },
206
+ {
207
+ name: "get_project",
208
+ description: "Get details of a specific project",
209
+ inputSchema: zodToJsonSchema(GetProjectSchema),
210
+ },
211
+ {
212
+ name: "list_projects",
213
+ description: "List projects accessible by the current user",
214
+ inputSchema: zodToJsonSchema(ListProjectsSchema),
215
+ },
216
+ {
217
+ name: "list_labels",
218
+ description: "List labels for a project",
219
+ inputSchema: zodToJsonSchema(ListLabelsSchema),
220
+ },
221
+ {
222
+ name: "get_label",
223
+ description: "Get a single label from a project",
224
+ inputSchema: zodToJsonSchema(GetLabelSchema),
225
+ },
226
+ {
227
+ name: "create_label",
228
+ description: "Create a new label in a project",
229
+ inputSchema: zodToJsonSchema(CreateLabelSchema),
230
+ },
231
+ {
232
+ name: "update_label",
233
+ description: "Update an existing label in a project",
234
+ inputSchema: zodToJsonSchema(UpdateLabelSchema),
235
+ },
236
+ {
237
+ name: "delete_label",
238
+ description: "Delete a label from a project",
239
+ inputSchema: zodToJsonSchema(DeleteLabelSchema),
240
+ },
241
+ {
242
+ name: "list_group_projects",
243
+ description: "List projects in a GitLab group with filtering options",
244
+ inputSchema: zodToJsonSchema(ListGroupProjectsSchema),
245
+ },
246
+ {
247
+ name: "list_wiki_pages",
248
+ description: "List wiki pages in a GitLab project",
249
+ inputSchema: zodToJsonSchema(ListWikiPagesSchema),
250
+ },
251
+ {
252
+ name: "get_wiki_page",
253
+ description: "Get details of a specific wiki page",
254
+ inputSchema: zodToJsonSchema(GetWikiPageSchema),
255
+ },
256
+ {
257
+ name: "create_wiki_page",
258
+ description: "Create a new wiki page in a GitLab project",
259
+ inputSchema: zodToJsonSchema(CreateWikiPageSchema),
260
+ },
261
+ {
262
+ name: "update_wiki_page",
263
+ description: "Update an existing wiki page in a GitLab project",
264
+ inputSchema: zodToJsonSchema(UpdateWikiPageSchema),
265
+ },
266
+ {
267
+ name: "delete_wiki_page",
268
+ description: "Delete a wiki page from a GitLab project",
269
+ inputSchema: zodToJsonSchema(DeleteWikiPageSchema),
270
+ },
271
+ {
272
+ name: "get_repository_tree",
273
+ description: "Get the repository tree for a GitLab project (list files and directories)",
274
+ inputSchema: zodToJsonSchema(GetRepositoryTreeSchema),
275
+ },
276
+ {
277
+ name: "list_pipelines",
278
+ description: "List pipelines in a GitLab project with filtering options",
279
+ inputSchema: zodToJsonSchema(ListPipelinesSchema),
280
+ },
281
+ {
282
+ name: "get_pipeline",
283
+ description: "Get details of a specific pipeline in a GitLab project",
284
+ inputSchema: zodToJsonSchema(GetPipelineSchema),
285
+ },
286
+ {
287
+ name: "list_pipeline_jobs",
288
+ description: "List all jobs in a specific pipeline",
289
+ inputSchema: zodToJsonSchema(ListPipelineJobsSchema),
290
+ },
291
+ {
292
+ name: "get_pipeline_job",
293
+ description: "Get details of a GitLab pipeline job number",
294
+ inputSchema: zodToJsonSchema(GetPipelineJobOutputSchema),
295
+ },
296
+ {
297
+ name: "get_pipeline_job_output",
298
+ description: "Get the output/trace of a GitLab pipeline job with optional pagination to limit context window usage",
299
+ inputSchema: zodToJsonSchema(GetPipelineJobOutputSchema),
300
+ },
301
+ {
302
+ name: "create_pipeline",
303
+ description: "Create a new pipeline for a branch or tag",
304
+ inputSchema: zodToJsonSchema(CreatePipelineSchema),
305
+ },
306
+ {
307
+ name: "retry_pipeline",
308
+ description: "Retry a failed or canceled pipeline",
309
+ inputSchema: zodToJsonSchema(RetryPipelineSchema),
310
+ },
311
+ {
312
+ name: "cancel_pipeline",
313
+ description: "Cancel a running pipeline",
314
+ inputSchema: zodToJsonSchema(CancelPipelineSchema),
315
+ },
316
+ {
317
+ name: "list_merge_requests",
318
+ description: "List merge requests in a GitLab project with filtering options",
319
+ inputSchema: zodToJsonSchema(ListMergeRequestsSchema),
320
+ },
321
+ {
322
+ name: "list_milestones",
323
+ description: "List milestones in a GitLab project with filtering options",
324
+ inputSchema: zodToJsonSchema(ListProjectMilestonesSchema),
325
+ },
326
+ {
327
+ name: "get_milestone",
328
+ description: "Get details of a specific milestone",
329
+ inputSchema: zodToJsonSchema(GetProjectMilestoneSchema),
330
+ },
331
+ {
332
+ name: "create_milestone",
333
+ description: "Create a new milestone in a GitLab project",
334
+ inputSchema: zodToJsonSchema(CreateProjectMilestoneSchema),
335
+ },
336
+ {
337
+ name: "edit_milestone",
338
+ description: "Edit an existing milestone in a GitLab project",
339
+ inputSchema: zodToJsonSchema(EditProjectMilestoneSchema),
340
+ },
341
+ {
342
+ name: "delete_milestone",
343
+ description: "Delete a milestone from a GitLab project",
344
+ inputSchema: zodToJsonSchema(DeleteProjectMilestoneSchema),
345
+ },
346
+ {
347
+ name: "get_milestone_issue",
348
+ description: "Get issues associated with a specific milestone",
349
+ inputSchema: zodToJsonSchema(GetMilestoneIssuesSchema),
350
+ },
351
+ {
352
+ name: "get_milestone_merge_requests",
353
+ description: "Get merge requests associated with a specific milestone",
354
+ inputSchema: zodToJsonSchema(GetMilestoneMergeRequestsSchema),
355
+ },
356
+ {
357
+ name: "promote_milestone",
358
+ description: "Promote a milestone to the next stage",
359
+ inputSchema: zodToJsonSchema(PromoteProjectMilestoneSchema),
360
+ },
361
+ {
362
+ name: "get_milestone_burndown_events",
363
+ description: "Get burndown events for a specific milestone",
364
+ inputSchema: zodToJsonSchema(GetMilestoneBurndownEventsSchema),
365
+ },
366
+ {
367
+ name: "get_users",
368
+ description: "Get GitLab user details by usernames",
369
+ inputSchema: zodToJsonSchema(GetUsersSchema),
370
+ },
371
+ {
372
+ name: "list_commits",
373
+ description: "List repository commits with filtering options",
374
+ inputSchema: zodToJsonSchema(ListCommitsSchema),
375
+ },
376
+ {
377
+ name: "get_commit",
378
+ description: "Get details of a specific commit",
379
+ inputSchema: zodToJsonSchema(GetCommitSchema),
380
+ },
381
+ {
382
+ name: "get_commit_diff",
383
+ description: "Get changes/diffs of a specific commit",
384
+ inputSchema: zodToJsonSchema(GetCommitDiffSchema),
385
+ },
386
+ {
387
+ name: "get_current_user",
388
+ description: "Get details of the current authenticated user",
389
+ inputSchema: zodToJsonSchema(GetCurrentUserSchema),
390
+ },
391
+ {
392
+ name: "get_draft_note",
393
+ description: "Get a single draft note from a merge request",
394
+ inputSchema: zodToJsonSchema(GetDraftNoteSchema),
395
+ },
396
+ {
397
+ name: "list_draft_notes",
398
+ description: "List all draft notes for a merge request",
399
+ inputSchema: zodToJsonSchema(ListDraftNotesSchema),
400
+ },
401
+ {
402
+ name: "create_draft_note",
403
+ description: "Create a draft note for a merge request",
404
+ inputSchema: zodToJsonSchema(CreateDraftNoteSchema),
405
+ },
406
+ {
407
+ name: "update_draft_note",
408
+ description: "Update an existing draft note",
409
+ inputSchema: zodToJsonSchema(UpdateDraftNoteSchema),
410
+ },
411
+ {
412
+ name: "delete_draft_note",
413
+ description: "Delete a draft note",
414
+ inputSchema: zodToJsonSchema(DeleteDraftNoteSchema),
415
+ },
416
+ {
417
+ name: "publish_draft_note",
418
+ description: "Publish a single draft note",
419
+ inputSchema: zodToJsonSchema(PublishDraftNoteSchema),
420
+ },
421
+ {
422
+ name: "bulk_publish_draft_notes",
423
+ description: "Publish all draft notes for a merge request",
424
+ inputSchema: zodToJsonSchema(BulkPublishDraftNotesSchema),
425
+ },
426
+ {
427
+ name: "merge_merge_request",
428
+ description: "Merge a merge request in a GitLab project",
429
+ inputSchema: zodToJsonSchema(MergeMergeRequestSchema),
430
+ },
431
+ {
432
+ name: "my_issues",
433
+ description: "List issues assigned to the authenticated user",
434
+ inputSchema: zodToJsonSchema(MyIssuesSchema),
435
+ },
436
+ {
437
+ name: "list_project_members",
438
+ description: "List members of a GitLab project",
439
+ inputSchema: zodToJsonSchema(ListProjectMembersSchema),
440
+ },
441
+ {
442
+ name: "upload_markdown",
443
+ description: "Upload a file to a GitLab project for use in markdown content",
444
+ inputSchema: zodToJsonSchema(MarkdownUploadSchema),
445
+ },
446
+ {
447
+ name: "download_attachment",
448
+ description: "Download an uploaded file from a GitLab project by secret and filename",
449
+ inputSchema: zodToJsonSchema(DownloadAttachmentSchema),
450
+ }
451
+ ];
452
+ // Define which tools are read-only
453
+ const readOnlyTools = [
454
+ "search_repositories",
455
+ "get_file_contents",
456
+ "get_merge_request",
457
+ "get_merge_request_diffs",
458
+ "get_branch_diffs",
459
+ "mr_discussions",
460
+ "list_issues",
461
+ "list_merge_requests",
462
+ "get_issue",
463
+ "list_issue_links",
464
+ "list_issue_discussions",
465
+ "get_issue_link",
466
+ "list_namespaces",
467
+ "get_namespace",
468
+ "verify_namespace",
469
+ "get_project",
470
+ "get_pipeline",
471
+ "list_pipelines",
472
+ "list_pipeline_jobs",
473
+ "get_pipeline_job",
474
+ "get_pipeline_job_output",
475
+ "list_projects",
476
+ "list_labels",
477
+ "get_label",
478
+ "list_group_projects",
479
+ "get_repository_tree",
480
+ "list_milestones",
481
+ "get_milestone",
482
+ "get_milestone_issue",
483
+ "get_milestone_merge_requests",
484
+ "get_milestone_burndown_events",
485
+ "list_wiki_pages",
486
+ "get_wiki_page",
487
+ "get_users",
488
+ "list_commits",
489
+ "get_commit",
490
+ "get_commit_diff",
491
+ "get_current_user",
492
+ "download_attachment",
493
+ ];
494
+ // Define which tools are related to wiki and can be toggled by USE_GITLAB_WIKI
495
+ const wikiToolNames = [
496
+ "list_wiki_pages",
497
+ "get_wiki_page",
498
+ "create_wiki_page",
499
+ "update_wiki_page",
500
+ "delete_wiki_page",
501
+ "upload_wiki_attachment",
502
+ ];
503
+ // Define which tools are related to milestones and can be toggled by USE_MILESTONE
504
+ const milestoneToolNames = [
505
+ "list_milestones",
506
+ "get_milestone",
507
+ "create_milestone",
508
+ "edit_milestone",
509
+ "delete_milestone",
510
+ "get_milestone_issue",
511
+ "get_milestone_merge_requests",
512
+ "promote_milestone",
513
+ "get_milestone_burndown_events",
514
+ ];
515
+ // Define which tools are related to pipelines and can be toggled by USE_PIPELINE
516
+ const pipelineToolNames = [
517
+ "list_pipelines",
518
+ "get_pipeline",
519
+ "list_pipeline_jobs",
520
+ "get_pipeline_job",
521
+ "get_pipeline_job_output",
522
+ "create_pipeline",
523
+ "retry_pipeline",
524
+ "cancel_pipeline",
525
+ ];
526
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
527
+ // Apply read-only filter first
528
+ const tools0 = config.GITLAB_READ_ONLY_MODE
529
+ ? allTools.filter(tool => readOnlyTools.includes(tool.name))
530
+ : allTools;
531
+ // Toggle wiki tools by USE_GITLAB_WIKI flag
532
+ const tools1 = config.USE_GITLAB_WIKI
533
+ ? tools0
534
+ : tools0.filter(tool => !wikiToolNames.includes(tool.name));
535
+ // Toggle milestone tools by USE_MILESTONE flag
536
+ const tools2 = config.USE_MILESTONE
537
+ ? tools1
538
+ : tools1.filter(tool => !milestoneToolNames.includes(tool.name));
539
+ // Toggle pipeline tools by USE_PIPELINE flag
540
+ let tools = config.USE_PIPELINE ? tools2 : tools2.filter(tool => !pipelineToolNames.includes(tool.name));
541
+ // <<< START: Gemini 호환성을 위해 $schema 제거 >>>
542
+ tools = tools.map(tool => {
543
+ // inputSchema가 존재하고 객체인지 확인
544
+ if (tool.inputSchema && typeof tool.inputSchema === "object" && tool.inputSchema !== null) {
545
+ // $schema 키가 존재하면 삭제
546
+ if ("$schema" in tool.inputSchema) {
547
+ // 불변성을 위해 새로운 객체 생성 (선택적이지만 권장)
548
+ const modifiedSchema = { ...tool.inputSchema };
549
+ delete modifiedSchema.$schema;
550
+ return { ...tool, inputSchema: modifiedSchema };
551
+ }
552
+ }
553
+ // 변경이 필요 없으면 그대로 반환
554
+ return tool;
555
+ });
556
+ // <<< END: Gemini 호환성을 위해 $schema 제거 >>>
557
+ return {
558
+ tools, // $schema가 제거된 도구 목록 반환
559
+ };
560
+ });
561
+ // TODO: im pretty sure that the cookie jar should be scoped by token? instead of being global
562
+ // but i need to look into it. just don't use the cookie jar feature with oauth or passthrough...
563
+ const globalCookieJar = createCookieJar();
564
+ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
565
+ try {
566
+ if (!request.params.arguments) {
567
+ throw new Error("Arguments are required");
568
+ }
569
+ let cookieJar = undefined;
570
+ if (config.GITLAB_AUTH_COOKIE_PATH) {
571
+ cookieJar = globalCookieJar;
572
+ }
573
+ // Create GitlabSession instance
574
+ // TODO: we silently do nothing if the authInfo is not properly forwared. should we do something?
575
+ const gitlabSession = new GitlabHandler(extra.authInfo?.token || "", cookieJar);
576
+ if (cookieJar) {
577
+ await gitlabSession.ensureSessionForCookieJar();
578
+ }
579
+ logger.info(request.params.name);
580
+ switch (request.params.name) {
581
+ case "fork_repository": {
582
+ if (config.GITLAB_PROJECT_ID) {
583
+ throw new Error("Direct project ID is set. So fork_repository is not allowed");
584
+ }
585
+ const forkArgs = ForkRepositorySchema.parse(request.params.arguments);
586
+ try {
587
+ const forkedProject = await gitlabSession.forkProject(forkArgs.project_id, forkArgs.namespace);
588
+ return {
589
+ content: [{ type: "text", text: JSON.stringify(forkedProject, null, 2) }],
590
+ };
591
+ }
592
+ catch (forkError) {
593
+ console.error("Error forking repository:", forkError);
594
+ let forkErrorMessage = "Failed to fork repository";
595
+ if (forkError instanceof Error) {
596
+ forkErrorMessage = `${forkErrorMessage}: ${forkError.message}`;
597
+ }
598
+ return {
599
+ content: [
600
+ {
601
+ type: "text",
602
+ text: JSON.stringify({ error: forkErrorMessage }, null, 2),
603
+ },
604
+ ],
605
+ };
606
+ }
607
+ }
608
+ case "create_branch": {
609
+ const args = CreateBranchSchema.parse(request.params.arguments);
610
+ let ref = args.ref;
611
+ if (!ref) {
612
+ ref = await gitlabSession.getDefaultBranchRef(args.project_id);
613
+ }
614
+ const branch = await gitlabSession.createBranch(args.project_id, {
615
+ name: args.branch,
616
+ ref,
617
+ });
618
+ return {
619
+ content: [{ type: "text", text: JSON.stringify(branch, null, 2) }],
620
+ };
621
+ }
622
+ case "get_branch_diffs": {
623
+ const args = GetBranchDiffsSchema.parse(request.params.arguments);
624
+ const diffResp = await gitlabSession.getBranchDiffs(args.project_id, args.from, args.to, args.straight);
625
+ if (args.excluded_file_patterns?.length) {
626
+ const regexPatterns = args.excluded_file_patterns.map(pattern => new RegExp(pattern));
627
+ // Helper function to check if a path matches any regex pattern
628
+ const matchesAnyPattern = (path) => {
629
+ if (!path)
630
+ return false;
631
+ return regexPatterns.some(regex => regex.test(path));
632
+ };
633
+ // Filter out files that match any of the regex patterns on new files
634
+ diffResp.diffs = diffResp.diffs.filter(diff => !matchesAnyPattern(diff.new_path));
635
+ }
636
+ return {
637
+ content: [{ type: "text", text: JSON.stringify(diffResp, null, 2) }],
638
+ };
639
+ }
640
+ case "search_repositories": {
641
+ const args = SearchRepositoriesSchema.parse(request.params.arguments);
642
+ const results = await gitlabSession.searchProjects(args.search, args.page, args.per_page);
643
+ return {
644
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
645
+ };
646
+ }
647
+ case "create_repository": {
648
+ if (config.GITLAB_PROJECT_ID) {
649
+ throw new Error("Direct project ID is set. So fork_repository is not allowed");
650
+ }
651
+ const args = CreateRepositorySchema.parse(request.params.arguments);
652
+ const repository = await gitlabSession.createRepository(args);
653
+ return {
654
+ content: [{ type: "text", text: JSON.stringify(repository, null, 2) }],
655
+ };
656
+ }
657
+ case "get_file_contents": {
658
+ const args = GetFileContentsSchema.parse(request.params.arguments);
659
+ const contents = await gitlabSession.getFileContents(args.project_id, args.file_path, args.ref);
660
+ return {
661
+ content: [{ type: "text", text: JSON.stringify(contents, null, 2) }],
662
+ };
663
+ }
664
+ case "create_or_update_file": {
665
+ const args = CreateOrUpdateFileSchema.parse(request.params.arguments);
666
+ const result = await gitlabSession.createOrUpdateFile(args.project_id, args.file_path, args.content, args.commit_message, args.branch, args.previous_path, args.last_commit_id, args.commit_id);
667
+ return {
668
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
669
+ };
670
+ }
671
+ case "push_files": {
672
+ const args = PushFilesSchema.parse(request.params.arguments);
673
+ const result = await gitlabSession.createCommit(args.project_id, args.commit_message, args.branch, args.files.map(f => ({ path: f.file_path, content: f.content })));
674
+ return {
675
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
676
+ };
677
+ }
678
+ case "create_issue": {
679
+ const args = CreateIssueSchema.parse(request.params.arguments);
680
+ const { project_id, ...options } = args;
681
+ const issue = await gitlabSession.createIssue(project_id, options);
682
+ return {
683
+ content: [{ type: "text", text: JSON.stringify(issue, null, 2) }],
684
+ };
685
+ }
686
+ case "create_merge_request": {
687
+ const args = CreateMergeRequestSchema.parse(request.params.arguments);
688
+ const { project_id, ...options } = args;
689
+ const mergeRequest = await gitlabSession.createMergeRequest(project_id, options);
690
+ return {
691
+ content: [{ type: "text", text: JSON.stringify(mergeRequest, null, 2) }],
692
+ };
693
+ }
694
+ case "update_merge_request_note": {
695
+ const args = UpdateMergeRequestNoteSchema.parse(request.params.arguments);
696
+ const note = await gitlabSession.updateMergeRequestNote(args.project_id, args.merge_request_iid, args.discussion_id, args.note_id, args.body, // Now optional
697
+ args.resolved // Now one of body or resolved must be provided, not both
698
+ );
699
+ return {
700
+ content: [{ type: "text", text: JSON.stringify(note, null, 2) }],
701
+ };
702
+ }
703
+ case "create_merge_request_note": {
704
+ const args = CreateMergeRequestNoteSchema.parse(request.params.arguments);
705
+ const note = await gitlabSession.createMergeRequestNote(args.project_id, args.merge_request_iid, args.discussion_id, args.body, args.created_at);
706
+ return {
707
+ content: [{ type: "text", text: JSON.stringify(note, null, 2) }],
708
+ };
709
+ }
710
+ case "update_issue_note": {
711
+ const args = UpdateIssueNoteSchema.parse(request.params.arguments);
712
+ const note = await gitlabSession.updateIssueNote(args.project_id, args.issue_iid, args.discussion_id, args.note_id, args.body);
713
+ return {
714
+ content: [{ type: "text", text: JSON.stringify(note, null, 2) }],
715
+ };
716
+ }
717
+ case "create_issue_note": {
718
+ const args = CreateIssueNoteSchema.parse(request.params.arguments);
719
+ const note = await gitlabSession.createIssueNote(args.project_id, args.issue_iid, args.discussion_id, args.body, args.created_at);
720
+ return {
721
+ content: [{ type: "text", text: JSON.stringify(note, null, 2) }],
722
+ };
723
+ }
724
+ case "get_merge_request": {
725
+ const args = GetMergeRequestSchema.parse(request.params.arguments);
726
+ const mergeRequest = await gitlabSession.getMergeRequest(args.project_id, args.merge_request_iid, args.source_branch);
727
+ return {
728
+ content: [{ type: "text", text: JSON.stringify(mergeRequest, null, 2) }],
729
+ };
730
+ }
731
+ case "get_merge_request_diffs": {
732
+ const args = GetMergeRequestDiffsSchema.parse(request.params.arguments);
733
+ const diffs = await gitlabSession.getMergeRequestDiffs(args.project_id, args.merge_request_iid, args.source_branch, args.view);
734
+ return {
735
+ content: [{ type: "text", text: JSON.stringify(diffs, null, 2) }],
736
+ };
737
+ }
738
+ case "list_merge_request_diffs": {
739
+ const args = ListMergeRequestDiffsSchema.parse(request.params.arguments);
740
+ const changes = await gitlabSession.listMergeRequestDiffs(args.project_id, args.merge_request_iid, args.source_branch, args.page, args.per_page, args.unidiff);
741
+ return {
742
+ content: [{ type: "text", text: JSON.stringify(changes, null, 2) }],
743
+ };
744
+ }
745
+ case "update_merge_request": {
746
+ const args = UpdateMergeRequestSchema.parse(request.params.arguments);
747
+ const { project_id, merge_request_iid, source_branch, ...options } = args;
748
+ const mergeRequest = await gitlabSession.updateMergeRequest(project_id, options, merge_request_iid, source_branch);
749
+ return {
750
+ content: [{ type: "text", text: JSON.stringify(mergeRequest, null, 2) }],
751
+ };
752
+ }
753
+ case "mr_discussions": {
754
+ const args = ListMergeRequestDiscussionsSchema.parse(request.params.arguments);
755
+ const { project_id, merge_request_iid, ...options } = args;
756
+ const discussions = await gitlabSession.listMergeRequestDiscussions(project_id, merge_request_iid, options);
757
+ return {
758
+ content: [{ type: "text", text: JSON.stringify(discussions, null, 2) }],
759
+ };
760
+ }
761
+ case "list_namespaces": {
762
+ const args = ListNamespacesSchema.parse(request.params.arguments);
763
+ const namespaces = await gitlabSession.listNamespaces({
764
+ search: args.search,
765
+ owned_only: args.owned,
766
+ top_level_only: undefined
767
+ });
768
+ return {
769
+ content: [{ type: "text", text: JSON.stringify(namespaces, null, 2) }],
770
+ };
771
+ }
772
+ case "get_namespace": {
773
+ const args = GetNamespaceSchema.parse(request.params.arguments);
774
+ const namespace = await gitlabSession.getNamespace(args.namespace_id);
775
+ return {
776
+ content: [{ type: "text", text: JSON.stringify(namespace, null, 2) }],
777
+ };
778
+ }
779
+ case "verify_namespace": {
780
+ const args = VerifyNamespaceSchema.parse(request.params.arguments);
781
+ const namespaceExists = await gitlabSession.verifyNamespaceExistence(args.path);
782
+ return {
783
+ content: [{ type: "text", text: JSON.stringify(namespaceExists, null, 2) }],
784
+ };
785
+ }
786
+ case "get_project": {
787
+ const args = GetProjectSchema.parse(request.params.arguments);
788
+ const project = await gitlabSession.getProject(args.project_id);
789
+ return {
790
+ content: [{ type: "text", text: JSON.stringify(project, null, 2) }],
791
+ };
792
+ }
793
+ case "list_projects": {
794
+ const args = ListProjectsSchema.parse(request.params.arguments);
795
+ const projects = await gitlabSession.listProjects(args);
796
+ return {
797
+ content: [{ type: "text", text: JSON.stringify(projects, null, 2) }],
798
+ };
799
+ }
800
+ case "get_users": {
801
+ const args = GetUsersSchema.parse(request.params.arguments);
802
+ const usersMap = await gitlabSession.getUsers(args.usernames);
803
+ return {
804
+ content: [{ type: "text", text: JSON.stringify(usersMap, null, 2) }],
805
+ };
806
+ }
807
+ case "create_note": {
808
+ const args = CreateNoteSchema.parse(request.params.arguments);
809
+ const { project_id, noteable_type, noteable_iid, body } = args;
810
+ const note = await gitlabSession.createNote(project_id, noteable_type, noteable_iid, body);
811
+ return {
812
+ content: [{ type: "text", text: JSON.stringify(note, null, 2) }],
813
+ };
814
+ }
815
+ case "create_merge_request_thread": {
816
+ const args = CreateMergeRequestThreadSchema.parse(request.params.arguments);
817
+ const { project_id, merge_request_iid, body, position, created_at } = args;
818
+ const thread = await gitlabSession.createMergeRequestThread(project_id, merge_request_iid, body, position, created_at);
819
+ return {
820
+ content: [{ type: "text", text: JSON.stringify(thread, null, 2) }],
821
+ };
822
+ }
823
+ case "list_issues": {
824
+ const args = ListIssuesSchema.parse(request.params.arguments);
825
+ const { project_id, ...options } = args;
826
+ const issues = await gitlabSession.listIssues(project_id || "", options);
827
+ return {
828
+ content: [{ type: "text", text: JSON.stringify(issues, null, 2) }],
829
+ };
830
+ }
831
+ case "get_issue": {
832
+ const args = GetIssueSchema.parse(request.params.arguments);
833
+ const issue = await gitlabSession.getIssue(args.project_id, args.issue_iid);
834
+ return {
835
+ content: [{ type: "text", text: JSON.stringify(issue, null, 2) }],
836
+ };
837
+ }
838
+ case "update_issue": {
839
+ const args = UpdateIssueSchema.parse(request.params.arguments);
840
+ const { project_id, issue_iid, ...options } = args;
841
+ const issue = await gitlabSession.updateIssue(project_id, issue_iid, options);
842
+ return {
843
+ content: [{ type: "text", text: JSON.stringify(issue, null, 2) }],
844
+ };
845
+ }
846
+ case "delete_issue": {
847
+ const args = DeleteIssueSchema.parse(request.params.arguments);
848
+ await gitlabSession.deleteIssue(args.project_id, args.issue_iid);
849
+ return {
850
+ content: [
851
+ {
852
+ type: "text",
853
+ text: JSON.stringify({ status: "success", message: "Issue deleted successfully" }, null, 2),
854
+ },
855
+ ],
856
+ };
857
+ }
858
+ case "list_issue_links": {
859
+ const args = ListIssueLinksSchema.parse(request.params.arguments);
860
+ const links = await gitlabSession.listIssueLinks(args.project_id, args.issue_iid);
861
+ return {
862
+ content: [{ type: "text", text: JSON.stringify(links, null, 2) }],
863
+ };
864
+ }
865
+ case "list_issue_discussions": {
866
+ const args = ListIssueDiscussionsSchema.parse(request.params.arguments);
867
+ const { project_id, issue_iid, ...options } = args;
868
+ const discussions = await gitlabSession.listIssueDiscussions(project_id, issue_iid, options);
869
+ return {
870
+ content: [{ type: "text", text: JSON.stringify(discussions, null, 2) }],
871
+ };
872
+ }
873
+ case "get_issue_link": {
874
+ const args = GetIssueLinkSchema.parse(request.params.arguments);
875
+ const link = await gitlabSession.getIssueLink(args.project_id, args.issue_iid, args.issue_link_id);
876
+ return {
877
+ content: [{ type: "text", text: JSON.stringify(link, null, 2) }],
878
+ };
879
+ }
880
+ case "create_issue_link": {
881
+ const args = CreateIssueLinkSchema.parse(request.params.arguments);
882
+ const link = await gitlabSession.createIssueLink(args.project_id, args.issue_iid, args.target_project_id, args.target_issue_iid, args.link_type);
883
+ return {
884
+ content: [{ type: "text", text: JSON.stringify(link, null, 2) }],
885
+ };
886
+ }
887
+ case "delete_issue_link": {
888
+ const args = DeleteIssueLinkSchema.parse(request.params.arguments);
889
+ await gitlabSession.deleteIssueLink(args.project_id, args.issue_iid, args.issue_link_id);
890
+ return {
891
+ content: [
892
+ {
893
+ type: "text",
894
+ text: JSON.stringify({
895
+ status: "success",
896
+ message: "Issue link deleted successfully",
897
+ }, null, 2),
898
+ },
899
+ ],
900
+ };
901
+ }
902
+ case "list_labels": {
903
+ const args = ListLabelsSchema.parse(request.params.arguments);
904
+ const labels = await gitlabSession.listLabels(args.project_id, args);
905
+ return {
906
+ content: [{ type: "text", text: JSON.stringify(labels, null, 2) }],
907
+ };
908
+ }
909
+ case "get_label": {
910
+ const args = GetLabelSchema.parse(request.params.arguments);
911
+ const label = await gitlabSession.getLabel(args.project_id, args.label_id, args.include_ancestor_groups);
912
+ return {
913
+ content: [{ type: "text", text: JSON.stringify(label, null, 2) }],
914
+ };
915
+ }
916
+ case "create_label": {
917
+ const args = CreateLabelSchema.parse(request.params.arguments);
918
+ const label = await gitlabSession.createLabel(args.project_id, args);
919
+ return {
920
+ content: [{ type: "text", text: JSON.stringify(label, null, 2) }],
921
+ };
922
+ }
923
+ case "update_label": {
924
+ const args = UpdateLabelSchema.parse(request.params.arguments);
925
+ const { project_id, label_id, ...options } = args;
926
+ const label = await gitlabSession.updateLabel(project_id, label_id, options);
927
+ return {
928
+ content: [{ type: "text", text: JSON.stringify(label, null, 2) }],
929
+ };
930
+ }
931
+ case "delete_label": {
932
+ const args = DeleteLabelSchema.parse(request.params.arguments);
933
+ await gitlabSession.deleteLabel(args.project_id, args.label_id);
934
+ return {
935
+ content: [
936
+ {
937
+ type: "text",
938
+ text: JSON.stringify({ status: "success", message: "Label deleted successfully" }, null, 2),
939
+ },
940
+ ],
941
+ };
942
+ }
943
+ case "list_group_projects": {
944
+ const args = ListGroupProjectsSchema.parse(request.params.arguments);
945
+ const projects = await gitlabSession.listGroupProjects(args);
946
+ return {
947
+ content: [{ type: "text", text: JSON.stringify(projects, null, 2) }],
948
+ };
949
+ }
950
+ case "list_wiki_pages": {
951
+ const { project_id, page, per_page, with_content } = ListWikiPagesSchema.parse(request.params.arguments);
952
+ const wikiPages = await gitlabSession.listWikiPages(project_id, { page, per_page, with_content });
953
+ return {
954
+ content: [{ type: "text", text: JSON.stringify(wikiPages, null, 2) }],
955
+ };
956
+ }
957
+ case "get_wiki_page": {
958
+ const { project_id, slug } = GetWikiPageSchema.parse(request.params.arguments);
959
+ const wikiPage = await gitlabSession.getWikiPage(project_id, slug);
960
+ return {
961
+ content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }],
962
+ };
963
+ }
964
+ case "create_wiki_page": {
965
+ const { project_id, title, content, format } = CreateWikiPageSchema.parse(request.params.arguments);
966
+ const wikiPage = await gitlabSession.createWikiPage(project_id, title, content, format);
967
+ return {
968
+ content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }],
969
+ };
970
+ }
971
+ case "update_wiki_page": {
972
+ const { project_id, slug, title, content, format } = UpdateWikiPageSchema.parse(request.params.arguments);
973
+ const wikiPage = await gitlabSession.updateWikiPage(project_id, slug, title, content, format);
974
+ return {
975
+ content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }],
976
+ };
977
+ }
978
+ case "delete_wiki_page": {
979
+ const { project_id, slug } = DeleteWikiPageSchema.parse(request.params.arguments);
980
+ await gitlabSession.deleteWikiPage(project_id, slug);
981
+ return {
982
+ content: [
983
+ {
984
+ type: "text",
985
+ text: JSON.stringify({
986
+ status: "success",
987
+ message: "Wiki page deleted successfully",
988
+ }, null, 2),
989
+ },
990
+ ],
991
+ };
992
+ }
993
+ case "get_repository_tree": {
994
+ const args = GetRepositoryTreeSchema.parse(request.params.arguments);
995
+ const tree = await gitlabSession.getRepositoryTree(args);
996
+ return {
997
+ content: [{ type: "text", text: JSON.stringify(tree, null, 2) }],
998
+ };
999
+ }
1000
+ case "list_pipelines": {
1001
+ const args = ListPipelinesSchema.parse(request.params.arguments);
1002
+ const { project_id, ...options } = args;
1003
+ const pipelines = await gitlabSession.listPipelines(project_id, options);
1004
+ return {
1005
+ content: [{ type: "text", text: JSON.stringify(pipelines, null, 2) }],
1006
+ };
1007
+ }
1008
+ case "get_pipeline": {
1009
+ const { project_id, pipeline_id } = GetPipelineSchema.parse(request.params.arguments);
1010
+ const pipeline = await gitlabSession.getPipeline(project_id, pipeline_id);
1011
+ return {
1012
+ content: [
1013
+ {
1014
+ type: "text",
1015
+ text: JSON.stringify(pipeline, null, 2),
1016
+ },
1017
+ ],
1018
+ };
1019
+ }
1020
+ case "list_pipeline_jobs": {
1021
+ const { project_id, pipeline_id, ...options } = ListPipelineJobsSchema.parse(request.params.arguments);
1022
+ const jobs = await gitlabSession.listPipelineJobs(project_id, pipeline_id, options);
1023
+ return {
1024
+ content: [
1025
+ {
1026
+ type: "text",
1027
+ text: JSON.stringify(jobs, null, 2),
1028
+ },
1029
+ ],
1030
+ };
1031
+ }
1032
+ case "get_pipeline_job": {
1033
+ const { project_id, job_id } = GetPipelineJobOutputSchema.parse(request.params.arguments);
1034
+ const jobDetails = await gitlabSession.getPipelineJob(project_id, job_id);
1035
+ return {
1036
+ content: [
1037
+ {
1038
+ type: "text",
1039
+ text: JSON.stringify(jobDetails, null, 2),
1040
+ },
1041
+ ],
1042
+ };
1043
+ }
1044
+ case "get_pipeline_job_output": {
1045
+ const { project_id, job_id, limit, offset } = GetPipelineJobOutputSchema.parse(request.params.arguments);
1046
+ const jobOutput = await gitlabSession.getPipelineJobOutput(project_id, job_id, limit, offset);
1047
+ return {
1048
+ content: [
1049
+ {
1050
+ type: "text",
1051
+ text: jobOutput,
1052
+ },
1053
+ ],
1054
+ };
1055
+ }
1056
+ case "create_pipeline": {
1057
+ const { project_id, ref, variables } = CreatePipelineSchema.parse(request.params.arguments);
1058
+ const pipeline = await gitlabSession.createPipeline(project_id, ref, variables);
1059
+ return {
1060
+ content: [
1061
+ {
1062
+ type: "text",
1063
+ text: `Created pipeline #${pipeline.id} for ${ref}. Status: ${pipeline.status}\nWeb URL: ${pipeline.web_url}`,
1064
+ },
1065
+ ],
1066
+ };
1067
+ }
1068
+ case "retry_pipeline": {
1069
+ const { project_id, pipeline_id } = RetryPipelineSchema.parse(request.params.arguments);
1070
+ const pipeline = await gitlabSession.retryPipeline(project_id, pipeline_id);
1071
+ return {
1072
+ content: [
1073
+ {
1074
+ type: "text",
1075
+ text: `Retried pipeline #${pipeline.id}. Status: ${pipeline.status}\nWeb URL: ${pipeline.web_url}`,
1076
+ },
1077
+ ],
1078
+ };
1079
+ }
1080
+ case "cancel_pipeline": {
1081
+ const { project_id, pipeline_id } = CancelPipelineSchema.parse(request.params.arguments);
1082
+ const pipeline = await gitlabSession.cancelPipeline(project_id, pipeline_id);
1083
+ return {
1084
+ content: [
1085
+ {
1086
+ type: "text",
1087
+ text: `Canceled pipeline #${pipeline.id}. Status: ${pipeline.status}\nWeb URL: ${pipeline.web_url}`,
1088
+ },
1089
+ ],
1090
+ };
1091
+ }
1092
+ case "list_merge_requests": {
1093
+ const args = ListMergeRequestsSchema.parse(request.params.arguments);
1094
+ const mergeRequests = await gitlabSession.listMergeRequests(args.project_id, args);
1095
+ return {
1096
+ content: [{ type: "text", text: JSON.stringify(mergeRequests, null, 2) }],
1097
+ };
1098
+ }
1099
+ case "list_milestones": {
1100
+ const { project_id, ...options } = ListProjectMilestonesSchema.parse(request.params.arguments);
1101
+ const milestones = await gitlabSession.listProjectMilestones(project_id, options);
1102
+ return {
1103
+ content: [
1104
+ {
1105
+ type: "text",
1106
+ text: JSON.stringify(milestones, null, 2),
1107
+ },
1108
+ ],
1109
+ };
1110
+ }
1111
+ case "get_milestone": {
1112
+ const { project_id, milestone_id } = GetProjectMilestoneSchema.parse(request.params.arguments);
1113
+ const milestone = await gitlabSession.getProjectMilestone(project_id, milestone_id);
1114
+ return {
1115
+ content: [
1116
+ {
1117
+ type: "text",
1118
+ text: JSON.stringify(milestone, null, 2),
1119
+ },
1120
+ ],
1121
+ };
1122
+ }
1123
+ case "create_milestone": {
1124
+ const { project_id, ...options } = CreateProjectMilestoneSchema.parse(request.params.arguments);
1125
+ const milestone = await gitlabSession.createProjectMilestone(project_id, options);
1126
+ return {
1127
+ content: [
1128
+ {
1129
+ type: "text",
1130
+ text: JSON.stringify(milestone, null, 2),
1131
+ },
1132
+ ],
1133
+ };
1134
+ }
1135
+ case "edit_milestone": {
1136
+ const { project_id, milestone_id, ...options } = EditProjectMilestoneSchema.parse(request.params.arguments);
1137
+ const milestone = await gitlabSession.editProjectMilestone(project_id, milestone_id, options);
1138
+ return {
1139
+ content: [
1140
+ {
1141
+ type: "text",
1142
+ text: JSON.stringify(milestone, null, 2),
1143
+ },
1144
+ ],
1145
+ };
1146
+ }
1147
+ case "delete_milestone": {
1148
+ const { project_id, milestone_id } = DeleteProjectMilestoneSchema.parse(request.params.arguments);
1149
+ await gitlabSession.deleteProjectMilestone(project_id, milestone_id);
1150
+ return {
1151
+ content: [
1152
+ {
1153
+ type: "text",
1154
+ text: JSON.stringify({
1155
+ status: "success",
1156
+ message: "Milestone deleted successfully",
1157
+ }, null, 2),
1158
+ },
1159
+ ],
1160
+ };
1161
+ }
1162
+ case "get_milestone_issue": {
1163
+ const { project_id, milestone_id } = GetMilestoneIssuesSchema.parse(request.params.arguments);
1164
+ const issues = await gitlabSession.getMilestoneIssues(project_id, milestone_id);
1165
+ return {
1166
+ content: [
1167
+ {
1168
+ type: "text",
1169
+ text: JSON.stringify(issues, null, 2),
1170
+ },
1171
+ ],
1172
+ };
1173
+ }
1174
+ case "get_milestone_merge_requests": {
1175
+ const { project_id, milestone_id } = GetMilestoneMergeRequestsSchema.parse(request.params.arguments);
1176
+ const mergeRequests = await gitlabSession.getMilestoneMergeRequests(project_id, milestone_id);
1177
+ return {
1178
+ content: [
1179
+ {
1180
+ type: "text",
1181
+ text: JSON.stringify(mergeRequests, null, 2),
1182
+ },
1183
+ ],
1184
+ };
1185
+ }
1186
+ case "promote_milestone": {
1187
+ const { project_id, milestone_id } = PromoteProjectMilestoneSchema.parse(request.params.arguments);
1188
+ const milestone = await gitlabSession.promoteProjectMilestone(project_id, milestone_id);
1189
+ return {
1190
+ content: [
1191
+ {
1192
+ type: "text",
1193
+ text: JSON.stringify(milestone, null, 2),
1194
+ },
1195
+ ],
1196
+ };
1197
+ }
1198
+ case "get_milestone_burndown_events": {
1199
+ const { project_id, milestone_id } = GetMilestoneBurndownEventsSchema.parse(request.params.arguments);
1200
+ const events = await gitlabSession.getMilestoneBurndownEvents(project_id, milestone_id);
1201
+ return {
1202
+ content: [
1203
+ {
1204
+ type: "text",
1205
+ text: JSON.stringify(events, null, 2),
1206
+ },
1207
+ ],
1208
+ };
1209
+ }
1210
+ case "list_commits": {
1211
+ const args = ListCommitsSchema.parse(request.params.arguments);
1212
+ const commits = await gitlabSession.listCommits(args.project_id, args);
1213
+ return {
1214
+ content: [{ type: "text", text: JSON.stringify(commits, null, 2) }],
1215
+ };
1216
+ }
1217
+ case "get_commit": {
1218
+ const args = GetCommitSchema.parse(request.params.arguments);
1219
+ const commit = await gitlabSession.getCommit(args.project_id, args.sha, args.stats);
1220
+ return {
1221
+ content: [{ type: "text", text: JSON.stringify(commit, null, 2) }],
1222
+ };
1223
+ }
1224
+ case "get_commit_diff": {
1225
+ const args = GetCommitDiffSchema.parse(request.params.arguments);
1226
+ const diff = await gitlabSession.getCommitDiff(args.project_id, args.sha);
1227
+ return {
1228
+ content: [{ type: "text", text: JSON.stringify(diff, null, 2) }],
1229
+ };
1230
+ }
1231
+ case "get_current_user": {
1232
+ const user = await gitlabSession.getCurrentUser();
1233
+ return {
1234
+ content: [{ type: "text", text: JSON.stringify(user, null, 2) }],
1235
+ };
1236
+ }
1237
+ case "get_draft_note": {
1238
+ const args = GetDraftNoteSchema.parse(request.params.arguments);
1239
+ const { project_id, merge_request_iid, draft_note_id } = args;
1240
+ const draftNote = await gitlabSession.getDraftNote(project_id, merge_request_iid, draft_note_id);
1241
+ return {
1242
+ content: [{ type: "text", text: JSON.stringify(draftNote, null, 2) }],
1243
+ };
1244
+ }
1245
+ case "list_draft_notes": {
1246
+ const args = ListDraftNotesSchema.parse(request.params.arguments);
1247
+ const { project_id, merge_request_iid } = args;
1248
+ const draftNotes = await gitlabSession.listDraftNotes(project_id, merge_request_iid);
1249
+ return {
1250
+ content: [{ type: "text", text: JSON.stringify(draftNotes, null, 2) }],
1251
+ };
1252
+ }
1253
+ case "create_draft_note": {
1254
+ const args = CreateDraftNoteSchema.parse(request.params.arguments);
1255
+ const { project_id, merge_request_iid, body, position, resolve_discussion } = args;
1256
+ const draftNote = await gitlabSession.createDraftNote(project_id, merge_request_iid, body, position, resolve_discussion);
1257
+ return {
1258
+ content: [{ type: "text", text: JSON.stringify(draftNote, null, 2) }],
1259
+ };
1260
+ }
1261
+ case "update_draft_note": {
1262
+ const args = UpdateDraftNoteSchema.parse(request.params.arguments);
1263
+ const { project_id, merge_request_iid, draft_note_id, body, position, resolve_discussion } = args;
1264
+ const draftNote = await gitlabSession.updateDraftNote(project_id, merge_request_iid, draft_note_id, body, position, resolve_discussion);
1265
+ return {
1266
+ content: [{ type: "text", text: JSON.stringify(draftNote, null, 2) }],
1267
+ };
1268
+ }
1269
+ case "delete_draft_note": {
1270
+ const args = DeleteDraftNoteSchema.parse(request.params.arguments);
1271
+ const { project_id, merge_request_iid, draft_note_id } = args;
1272
+ await gitlabSession.deleteDraftNote(project_id, merge_request_iid, draft_note_id);
1273
+ return {
1274
+ content: [{ type: "text", text: "Draft note deleted successfully" }],
1275
+ };
1276
+ }
1277
+ case "publish_draft_note": {
1278
+ const args = PublishDraftNoteSchema.parse(request.params.arguments);
1279
+ const { project_id, merge_request_iid, draft_note_id } = args;
1280
+ const publishedNote = await gitlabSession.publishDraftNote(project_id, merge_request_iid, draft_note_id);
1281
+ return {
1282
+ content: [{ type: "text", text: JSON.stringify(publishedNote, null, 2) }],
1283
+ };
1284
+ }
1285
+ case "bulk_publish_draft_notes": {
1286
+ const args = BulkPublishDraftNotesSchema.parse(request.params.arguments);
1287
+ const { project_id, merge_request_iid } = args;
1288
+ const publishedNotes = await gitlabSession.bulkPublishDraftNotes(project_id, merge_request_iid);
1289
+ return {
1290
+ content: [{ type: "text", text: JSON.stringify(publishedNotes, null, 2) }],
1291
+ };
1292
+ }
1293
+ case "merge_merge_request": {
1294
+ const args = MergeMergeRequestSchema.parse(request.params.arguments);
1295
+ const { project_id, merge_request_iid, ...options } = args;
1296
+ const mergeRequest = await gitlabSession.mergeMergeRequest(project_id, options, merge_request_iid);
1297
+ return {
1298
+ content: [{ type: "text", text: JSON.stringify(mergeRequest, null, 2) }],
1299
+ };
1300
+ }
1301
+ case "my_issues": {
1302
+ const args = MyIssuesSchema.parse(request.params.arguments);
1303
+ const issues = await gitlabSession.myIssues(args);
1304
+ return {
1305
+ content: [{ type: "text", text: JSON.stringify(issues, null, 2) }],
1306
+ };
1307
+ }
1308
+ case "list_project_members": {
1309
+ const args = ListProjectMembersSchema.parse(request.params.arguments);
1310
+ const { project_id, ...options } = args;
1311
+ const members = await gitlabSession.listProjectMembers(project_id, options);
1312
+ return {
1313
+ content: [{ type: "text", text: JSON.stringify(members, null, 2) }],
1314
+ };
1315
+ }
1316
+ case "upload_markdown": {
1317
+ const args = MarkdownUploadSchema.parse(request.params.arguments);
1318
+ const upload = await gitlabSession.markdownUpload(args.project_id, args.file_path);
1319
+ return {
1320
+ content: [{ type: "text", text: JSON.stringify(upload, null, 2) }],
1321
+ };
1322
+ }
1323
+ case "download_attachment": {
1324
+ const args = DownloadAttachmentSchema.parse(request.params.arguments);
1325
+ const filePath = await gitlabSession.downloadAttachment(args.project_id, args.secret, args.filename, args.local_path);
1326
+ return {
1327
+ content: [{ type: "text", text: JSON.stringify({ success: true, file_path: filePath }, null, 2) }],
1328
+ };
1329
+ }
1330
+ default:
1331
+ throw new Error(`Unknown tool: ${request.params.name}`);
1332
+ }
1333
+ }
1334
+ catch (error) {
1335
+ logger.debug(request.params);
1336
+ if (error instanceof z.ZodError) {
1337
+ throw new Error(`Invalid arguments: ${error.errors
1338
+ .map(e => `${e.path.join(".")}: ${e.message}`)
1339
+ .join(", ")}`);
1340
+ }
1341
+ throw error;
1342
+ }
1343
+ });
1344
+ export const mcpserver = server;