gitlab-mcp 1.2.1 → 1.4.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.
Files changed (52) hide show
  1. package/README.md +36 -29
  2. package/dist/config/dotenv.js +6 -2
  3. package/dist/config/dotenv.js.map +1 -1
  4. package/dist/config/env.d.ts +5 -2
  5. package/dist/config/env.js +27 -1
  6. package/dist/config/env.js.map +1 -1
  7. package/dist/http-app.js +10 -3
  8. package/dist/http-app.js.map +1 -1
  9. package/dist/http.js +6 -4
  10. package/dist/http.js.map +1 -1
  11. package/dist/index.js +6 -4
  12. package/dist/index.js.map +1 -1
  13. package/dist/lib/auth-context.d.ts +2 -1
  14. package/dist/lib/auth-context.js.map +1 -1
  15. package/dist/lib/gitlab-client.d.ts +42 -8
  16. package/dist/lib/gitlab-client.js +380 -42
  17. package/dist/lib/gitlab-client.js.map +1 -1
  18. package/dist/lib/network.js +12 -6
  19. package/dist/lib/network.js.map +1 -1
  20. package/dist/lib/oauth-scopes.d.ts +2 -0
  21. package/dist/lib/oauth-scopes.js +16 -0
  22. package/dist/lib/oauth-scopes.js.map +1 -0
  23. package/dist/lib/policy.d.ts +5 -1
  24. package/dist/lib/policy.js +11 -1
  25. package/dist/lib/policy.js.map +1 -1
  26. package/dist/lib/regex.d.ts +5 -0
  27. package/dist/lib/regex.js +111 -0
  28. package/dist/lib/regex.js.map +1 -0
  29. package/dist/lib/request-runtime.js +24 -11
  30. package/dist/lib/request-runtime.js.map +1 -1
  31. package/dist/lib/tool-capabilities.d.ts +3 -0
  32. package/dist/lib/tool-capabilities.js +3 -0
  33. package/dist/lib/tool-capabilities.js.map +1 -0
  34. package/dist/lib/tool-schema.d.ts +14 -0
  35. package/dist/lib/tool-schema.js +52 -0
  36. package/dist/lib/tool-schema.js.map +1 -0
  37. package/dist/tools/gitlab.js +496 -299
  38. package/dist/tools/gitlab.js.map +1 -1
  39. package/dist/tools/mr-code-context.d.ts +1 -1
  40. package/dist/tools/mr-code-context.js +2 -1
  41. package/dist/tools/mr-code-context.js.map +1 -1
  42. package/dist/types/auth.d.ts +1 -0
  43. package/dist/types/auth.js +2 -0
  44. package/dist/types/auth.js.map +1 -0
  45. package/dist/types/context.d.ts +1 -0
  46. package/docs/architecture.md +7 -6
  47. package/docs/authentication.md +4 -1
  48. package/docs/configuration.md +25 -22
  49. package/docs/deployment.md +9 -1
  50. package/docs/mcp-integration-testing-best-practices.md +381 -730
  51. package/docs/tools.md +76 -66
  52. package/package.json +1 -1
@@ -1,17 +1,31 @@
1
1
  import { Kind, parse } from "graphql";
2
2
  import { z } from "zod";
3
3
  import { GitLabApiError } from "../lib/gitlab-client.js";
4
+ import { bodySchema, displayNameSchema, nullableOptional, optionalBodySchema, optionalDisplayNameSchema, optionalProjectIdSchema, optionalRefLikeSchema, optionalUrlOrPathSchema, projectIdSchema, refLikeSchema, slugSchema } from "../lib/tool-schema.js";
4
5
  import { getSessionAuth } from "../lib/auth-context.js";
5
6
  import { stripNullsDeep } from "../lib/sanitize.js";
6
7
  import { getMergeRequestCodeContext, mergeRequestCodeContextSchema } from "./mr-code-context.js";
7
- const optionalString = z.preprocess((value) => (value === null ? undefined : value), z.string().optional());
8
- const optionalNumber = z.preprocess((value) => (value === null ? undefined : value), z.number().optional());
9
- const optionalBoolean = z.preprocess((value) => (value === null ? undefined : value), z.boolean().optional());
10
- const optionalStringArray = z.preprocess((value) => (value === null ? undefined : value), z.array(z.string()).optional());
11
- const optionalNumberArray = z.preprocess((value) => (value === null ? undefined : value), z.array(z.number()).optional());
12
- const optionalStringOrNumber = z.preprocess((value) => (value === null ? undefined : value), z.union([z.string(), z.number()]).optional());
13
- const optionalStringOrStringArray = z.preprocess((value) => (value === null ? undefined : value), z.union([z.string(), z.array(z.string())]).optional());
14
- const optionalRecord = z.preprocess((value) => (value === null ? undefined : value), z.record(z.string(), z.unknown()).optional());
8
+ const readCapabilities = ["read"];
9
+ const writeCapabilities = ["write"];
10
+ const deleteCapabilities = ["delete"];
11
+ const adminCapabilities = ["admin"];
12
+ const readGraphqlCapabilities = ["read", "graphql"];
13
+ const writeGraphqlCapabilities = ["write", "graphql"];
14
+ const optionalString = nullableOptional(z.string());
15
+ const optionalNumber = nullableOptional(z.number());
16
+ const optionalBoolean = nullableOptional(z.boolean());
17
+ const optionalStringArray = nullableOptional(z.array(z.string()));
18
+ const optionalNumberArray = nullableOptional(z.array(z.number()));
19
+ const optionalStringOrNumber = nullableOptional(z.union([z.string(), z.number()]));
20
+ const optionalStringOrStringArray = nullableOptional(z.union([z.string(), z.array(z.string())]));
21
+ const optionalRecord = nullableOptional(z.record(z.string(), z.unknown()));
22
+ const pipelineInputValueSchema = z.union([
23
+ z.string(),
24
+ z.number(),
25
+ z.boolean(),
26
+ z.array(z.union([z.string(), z.number(), z.boolean()]))
27
+ ]);
28
+ const optionalPipelineInputsRecord = nullableOptional(z.record(z.string(), pipelineInputValueSchema));
15
29
  const paginationShape = {
16
30
  page: optionalNumber,
17
31
  per_page: optionalNumber
@@ -21,7 +35,7 @@ export function registerGitLabTools(server, context) {
21
35
  const disableGraphqlTools = shouldDisableGraphqlTools(context.env.GITLAB_ALLOWED_PROJECT_IDS, context.env.GITLAB_ALLOW_GRAPHQL_WITH_PROJECT_SCOPE);
22
36
  const filtered = context.policy.filterTools(definitions.map((item) => ({
23
37
  name: item.name,
24
- mutating: item.mutating,
38
+ capabilities: item.capabilities,
25
39
  requiresFeature: item.requiresFeature
26
40
  })));
27
41
  const enabledNames = new Set(filtered.map((item) => item.name));
@@ -29,6 +43,9 @@ export function registerGitLabTools(server, context) {
29
43
  if (!enabledNames.has(definition.name)) {
30
44
  continue;
31
45
  }
46
+ if (definition.requiresLocalFileTools && !context.allowLocalFileTools) {
47
+ continue;
48
+ }
32
49
  if (disableGraphqlTools && isGraphqlToolName(definition.name)) {
33
50
  continue;
34
51
  }
@@ -40,7 +57,7 @@ export function registerGitLabTools(server, context) {
40
57
  try {
41
58
  context.policy.assertCanExecute({
42
59
  name: definition.name,
43
- mutating: definition.mutating,
60
+ capabilities: definition.capabilities,
44
61
  requiresFeature: definition.requiresFeature
45
62
  });
46
63
  if (definition.requiresAuth ?? true) {
@@ -80,9 +97,9 @@ function getGitLabToolDefinitions() {
80
97
  name: "gitlab_get_project",
81
98
  title: "Get Project",
82
99
  description: "Get project details by ID or path.",
83
- mutating: false,
100
+ capabilities: readCapabilities,
84
101
  inputSchema: {
85
- project_id: z.string().optional()
102
+ project_id: optionalProjectIdSchema
86
103
  },
87
104
  handler: async (args, context) => {
88
105
  const projectId = resolveProjectId(args, context, true);
@@ -93,7 +110,7 @@ function getGitLabToolDefinitions() {
93
110
  name: "gitlab_list_projects",
94
111
  title: "List Projects",
95
112
  description: "List projects available to the current user.",
96
- mutating: false,
113
+ capabilities: readCapabilities,
97
114
  inputSchema: {
98
115
  search: optionalString,
99
116
  search_namespaces: optionalBoolean,
@@ -117,15 +134,15 @@ function getGitLabToolDefinitions() {
117
134
  name: "gitlab_create_repository",
118
135
  title: "Create Repository",
119
136
  description: "Create a new GitLab project/repository.",
120
- mutating: true,
137
+ capabilities: adminCapabilities,
121
138
  inputSchema: {
122
- name: optionalString,
139
+ name: displayNameSchema,
123
140
  description: optionalString,
124
141
  visibility: z.enum(["private", "internal", "public"]).optional(),
125
142
  initialize_with_readme: optionalBoolean,
126
143
  path: optionalString,
127
- namespace_id: optionalString,
128
- default_branch: optionalString
144
+ namespace_id: optionalProjectIdSchema,
145
+ default_branch: optionalRefLikeSchema
129
146
  },
130
147
  handler: async (args, context) => context.gitlab.createRepository({
131
148
  name: getString(args, "name"),
@@ -141,9 +158,9 @@ function getGitLabToolDefinitions() {
141
158
  name: "gitlab_list_project_members",
142
159
  title: "List Project Members",
143
160
  description: "List members of a project.",
144
- mutating: false,
161
+ capabilities: readCapabilities,
145
162
  inputSchema: {
146
- project_id: z.string().optional(),
163
+ project_id: optionalProjectIdSchema,
147
164
  query: optionalString,
148
165
  user_ids: optionalNumberArray,
149
166
  skip_users: optionalNumberArray,
@@ -161,7 +178,7 @@ function getGitLabToolDefinitions() {
161
178
  name: "gitlab_list_group_projects",
162
179
  title: "List Group Projects",
163
180
  description: "List projects under a group.",
164
- mutating: false,
181
+ capabilities: readCapabilities,
165
182
  inputSchema: {
166
183
  group_id: z.string(),
167
184
  include_subgroups: optionalBoolean,
@@ -192,7 +209,7 @@ function getGitLabToolDefinitions() {
192
209
  name: "gitlab_list_group_iterations",
193
210
  title: "List Group Iterations",
194
211
  description: "List iterations for a group.",
195
- mutating: false,
212
+ capabilities: readCapabilities,
196
213
  inputSchema: {
197
214
  group_id: z.string().min(1),
198
215
  state: optionalString,
@@ -218,7 +235,7 @@ function getGitLabToolDefinitions() {
218
235
  name: "gitlab_search_repositories",
219
236
  title: "Search Repositories",
220
237
  description: "Search repositories by keyword.",
221
- mutating: false,
238
+ capabilities: readCapabilities,
222
239
  inputSchema: {
223
240
  search: z.string().min(1),
224
241
  ...paginationShape
@@ -231,11 +248,11 @@ function getGitLabToolDefinitions() {
231
248
  name: "gitlab_search_code_blobs",
232
249
  title: "Search Code Blobs",
233
250
  description: "Search repository code blobs in a specific project.",
234
- mutating: false,
251
+ capabilities: readCapabilities,
235
252
  inputSchema: {
236
- project_id: z.string().optional(),
253
+ project_id: optionalProjectIdSchema,
237
254
  search: z.string().min(1),
238
- ref: optionalString,
255
+ ref: optionalRefLikeSchema,
239
256
  ...paginationShape
240
257
  },
241
258
  handler: async (args, context) => context.gitlab.searchCodeBlobs(resolveProjectId(args, context, true), getString(args, "search"), { query: toQuery(omit(args, ["project_id", "search"])) })
@@ -244,11 +261,11 @@ function getGitLabToolDefinitions() {
244
261
  name: "gitlab_get_repository_tree",
245
262
  title: "Get Repository Tree",
246
263
  description: "List files and directories in a repository tree.",
247
- mutating: false,
264
+ capabilities: readCapabilities,
248
265
  inputSchema: {
249
- project_id: z.string().optional(),
266
+ project_id: optionalProjectIdSchema,
250
267
  path: optionalString,
251
- ref: optionalString,
268
+ ref: optionalRefLikeSchema,
252
269
  recursive: optionalBoolean,
253
270
  ...paginationShape
254
271
  },
@@ -263,11 +280,11 @@ function getGitLabToolDefinitions() {
263
280
  name: "gitlab_get_file_contents",
264
281
  title: "Get File Contents",
265
282
  description: "Get a file in repository by path and ref.",
266
- mutating: false,
283
+ capabilities: readCapabilities,
267
284
  inputSchema: {
268
- project_id: z.string().optional(),
285
+ project_id: optionalProjectIdSchema,
269
286
  file_path: z.string().min(1),
270
- ref: optionalString
287
+ ref: optionalRefLikeSchema
271
288
  },
272
289
  handler: async (args, context) => {
273
290
  const projectId = resolveProjectId(args, context, true);
@@ -283,11 +300,11 @@ function getGitLabToolDefinitions() {
283
300
  name: "gitlab_create_or_update_file",
284
301
  title: "Create Or Update File",
285
302
  description: "Create or update one file in repository.",
286
- mutating: true,
303
+ capabilities: writeCapabilities,
287
304
  inputSchema: {
288
- project_id: z.string().optional(),
305
+ project_id: optionalProjectIdSchema,
289
306
  file_path: z.string().min(1),
290
- branch: z.string().min(1),
307
+ branch: refLikeSchema,
291
308
  content: z.string(),
292
309
  commit_message: z.string().min(1),
293
310
  previous_path: optionalString,
@@ -318,10 +335,10 @@ function getGitLabToolDefinitions() {
318
335
  name: "gitlab_push_files",
319
336
  title: "Push Files",
320
337
  description: "Create a commit with multiple file actions.",
321
- mutating: true,
338
+ capabilities: writeCapabilities,
322
339
  inputSchema: {
323
- project_id: z.string().optional(),
324
- branch: z.string().min(1),
340
+ project_id: optionalProjectIdSchema,
341
+ branch: refLikeSchema,
325
342
  commit_message: z.string().min(1),
326
343
  actions: z
327
344
  .array(z.object({
@@ -381,11 +398,11 @@ function getGitLabToolDefinitions() {
381
398
  name: "gitlab_create_branch",
382
399
  title: "Create Branch",
383
400
  description: "Create a new branch from an existing ref.",
384
- mutating: true,
401
+ capabilities: writeCapabilities,
385
402
  inputSchema: {
386
- project_id: z.string().optional(),
387
- branch: z.string().min(1),
388
- ref: optionalString
403
+ project_id: optionalProjectIdSchema,
404
+ branch: refLikeSchema,
405
+ ref: optionalRefLikeSchema
389
406
  },
390
407
  handler: async (args, context) => {
391
408
  const projectId = resolveProjectId(args, context, true);
@@ -404,11 +421,11 @@ function getGitLabToolDefinitions() {
404
421
  name: "gitlab_get_branch_diffs",
405
422
  title: "Get Branch Diffs",
406
423
  description: "Compare two branches/refs and return diffs.",
407
- mutating: false,
424
+ capabilities: readCapabilities,
408
425
  inputSchema: {
409
- project_id: z.string().optional(),
410
- from: z.string().min(1),
411
- to: z.string().min(1),
426
+ project_id: optionalProjectIdSchema,
427
+ from: refLikeSchema,
428
+ to: refLikeSchema,
412
429
  straight: optionalBoolean,
413
430
  excluded_file_patterns: optionalStringArray
414
431
  },
@@ -428,10 +445,10 @@ function getGitLabToolDefinitions() {
428
445
  name: "gitlab_list_commits",
429
446
  title: "List Commits",
430
447
  description: "List commits in a project.",
431
- mutating: false,
448
+ capabilities: readCapabilities,
432
449
  inputSchema: {
433
- project_id: z.string().optional(),
434
- ref_name: optionalString,
450
+ project_id: optionalProjectIdSchema,
451
+ ref_name: optionalRefLikeSchema,
435
452
  since: optionalString,
436
453
  until: optionalString,
437
454
  path: optionalString,
@@ -454,9 +471,9 @@ function getGitLabToolDefinitions() {
454
471
  name: "gitlab_get_commit",
455
472
  title: "Get Commit",
456
473
  description: "Get one commit by SHA.",
457
- mutating: false,
474
+ capabilities: readCapabilities,
458
475
  inputSchema: {
459
- project_id: z.string().optional(),
476
+ project_id: optionalProjectIdSchema,
460
477
  sha: z.string().min(1),
461
478
  stats: optionalBoolean
462
479
  },
@@ -471,9 +488,9 @@ function getGitLabToolDefinitions() {
471
488
  name: "gitlab_get_commit_diff",
472
489
  title: "Get Commit Diff",
473
490
  description: "Get diff for one commit.",
474
- mutating: false,
491
+ capabilities: readCapabilities,
475
492
  inputSchema: {
476
- project_id: z.string().optional(),
493
+ project_id: optionalProjectIdSchema,
477
494
  sha: z.string().min(1),
478
495
  full_diff: optionalBoolean,
479
496
  ...paginationShape
@@ -489,9 +506,9 @@ function getGitLabToolDefinitions() {
489
506
  name: "gitlab_list_merge_requests",
490
507
  title: "List Merge Requests",
491
508
  description: "List merge requests for a project.",
492
- mutating: false,
509
+ capabilities: readCapabilities,
493
510
  inputSchema: {
494
- project_id: z.string().optional(),
511
+ project_id: optionalProjectIdSchema,
495
512
  assignee_id: optionalStringOrNumber,
496
513
  assignee_username: optionalString,
497
514
  author_id: optionalStringOrNumber,
@@ -517,8 +534,8 @@ function getGitLabToolDefinitions() {
517
534
  ])
518
535
  .optional(),
519
536
  sort: z.enum(["asc", "desc"]).optional(),
520
- source_branch: optionalString,
521
- target_branch: optionalString,
537
+ source_branch: optionalRefLikeSchema,
538
+ target_branch: optionalRefLikeSchema,
522
539
  search: optionalString,
523
540
  wip: z.enum(["yes", "no"]).optional(),
524
541
  with_labels_details: optionalBoolean,
@@ -526,7 +543,7 @@ function getGitLabToolDefinitions() {
526
543
  },
527
544
  handler: async (args, context) => {
528
545
  const projectId = resolveProjectId(args, context, false);
529
- const query = toQuery(omit(args, ["project_id"]));
546
+ const query = toQuery(cleanMergeRequestListArgs(omit(args, ["project_id"])));
530
547
  if (projectId) {
531
548
  return context.gitlab.listMergeRequests(projectId, { query });
532
549
  }
@@ -537,11 +554,11 @@ function getGitLabToolDefinitions() {
537
554
  name: "gitlab_get_merge_request",
538
555
  title: "Get Merge Request",
539
556
  description: "Get one merge request.",
540
- mutating: false,
557
+ capabilities: readCapabilities,
541
558
  inputSchema: {
542
- project_id: z.string().optional(),
559
+ project_id: optionalProjectIdSchema,
543
560
  merge_request_iid: optionalString,
544
- source_branch: optionalString
561
+ source_branch: optionalRefLikeSchema
545
562
  },
546
563
  handler: async (args, context) => {
547
564
  const projectId = resolveProjectId(args, context, true);
@@ -570,14 +587,14 @@ function getGitLabToolDefinitions() {
570
587
  name: "gitlab_create_merge_request",
571
588
  title: "Create Merge Request",
572
589
  description: "Create a merge request.",
573
- mutating: true,
590
+ capabilities: writeCapabilities,
574
591
  inputSchema: {
575
- project_id: z.string().optional(),
576
- source_branch: z.string().min(1),
577
- target_branch: z.string().min(1),
592
+ project_id: optionalProjectIdSchema,
593
+ source_branch: refLikeSchema,
594
+ target_branch: refLikeSchema,
578
595
  title: z.string().min(1),
579
596
  description: optionalString,
580
- target_project_id: optionalString,
597
+ target_project_id: optionalProjectIdSchema,
581
598
  assignee_ids: optionalNumberArray,
582
599
  reviewer_ids: optionalNumberArray,
583
600
  labels: optionalStringOrStringArray,
@@ -608,16 +625,16 @@ function getGitLabToolDefinitions() {
608
625
  name: "gitlab_fork_repository",
609
626
  title: "Fork Repository",
610
627
  description: "Fork an existing project to another namespace.",
611
- mutating: true,
628
+ capabilities: adminCapabilities,
612
629
  inputSchema: {
613
- project_id: z.string().optional(),
630
+ project_id: optionalProjectIdSchema,
614
631
  namespace: optionalString,
615
- namespace_id: optionalString,
632
+ namespace_id: optionalProjectIdSchema,
616
633
  path: optionalString,
617
- name: optionalString,
634
+ name: optionalDisplayNameSchema,
618
635
  description: optionalString,
619
636
  visibility: z.enum(["private", "internal", "public"]).optional(),
620
- default_branch: optionalString
637
+ default_branch: optionalRefLikeSchema
621
638
  },
622
639
  handler: async (args, context) => context.gitlab.forkRepository(resolveProjectId(args, context, true), {
623
640
  namespace: getOptionalString(args, "namespace"),
@@ -633,14 +650,14 @@ function getGitLabToolDefinitions() {
633
650
  name: "gitlab_update_merge_request",
634
651
  title: "Update Merge Request",
635
652
  description: "Update merge request fields.",
636
- mutating: true,
653
+ capabilities: writeCapabilities,
637
654
  inputSchema: {
638
- project_id: z.string().optional(),
655
+ project_id: optionalProjectIdSchema,
639
656
  merge_request_iid: z.string().min(1),
640
- source_branch: optionalString,
657
+ source_branch: optionalRefLikeSchema,
641
658
  title: optionalString,
642
659
  description: optionalString,
643
- target_branch: optionalString,
660
+ target_branch: optionalRefLikeSchema,
644
661
  assignee_ids: optionalNumberArray,
645
662
  reviewer_ids: optionalNumberArray,
646
663
  reviewers: optionalStringArray,
@@ -672,11 +689,11 @@ function getGitLabToolDefinitions() {
672
689
  name: "gitlab_merge_merge_request",
673
690
  title: "Merge Merge Request",
674
691
  description: "Merge an existing merge request.",
675
- mutating: true,
692
+ capabilities: writeCapabilities,
676
693
  inputSchema: {
677
- project_id: z.string().optional(),
694
+ project_id: optionalProjectIdSchema,
678
695
  merge_request_iid: optionalString,
679
- source_branch: optionalString,
696
+ source_branch: optionalRefLikeSchema,
680
697
  auto_merge: optionalBoolean,
681
698
  merge_when_pipeline_succeeds: optionalBoolean,
682
699
  merge_commit_message: optionalString,
@@ -713,9 +730,9 @@ function getGitLabToolDefinitions() {
713
730
  name: "gitlab_get_merge_request_diffs",
714
731
  title: "Get Merge Request Diffs",
715
732
  description: "Get MR diffs with changed files.",
716
- mutating: false,
733
+ capabilities: readCapabilities,
717
734
  inputSchema: {
718
- project_id: z.string().optional(),
735
+ project_id: optionalProjectIdSchema,
719
736
  merge_request_iid: z.string().min(1),
720
737
  view: z.enum(["inline", "parallel"]).optional(),
721
738
  excluded_file_patterns: optionalStringArray
@@ -726,9 +743,9 @@ function getGitLabToolDefinitions() {
726
743
  name: "gitlab_list_merge_request_diffs",
727
744
  title: "List Merge Request Diffs",
728
745
  description: "List detailed MR diffs (versions/changes view).",
729
- mutating: false,
746
+ capabilities: readCapabilities,
730
747
  inputSchema: {
731
- project_id: z.string().optional(),
748
+ project_id: optionalProjectIdSchema,
732
749
  merge_request_iid: z.string().min(1),
733
750
  page: optionalNumber,
734
751
  per_page: optionalNumber,
@@ -740,7 +757,7 @@ function getGitLabToolDefinitions() {
740
757
  name: "gitlab_get_merge_request_code_context",
741
758
  title: "Get Merge Request Code Context",
742
759
  description: "High-signal MR code context with include/exclude filters, sorting, and token-budgeted output.",
743
- mutating: false,
760
+ capabilities: readCapabilities,
744
761
  inputSchema: mergeRequestCodeContextSchema,
745
762
  handler: async (args, context) => getMergeRequestCodeContext({
746
763
  projectId: resolveProjectId(args, context, true),
@@ -761,9 +778,9 @@ function getGitLabToolDefinitions() {
761
778
  name: "gitlab_list_merge_request_versions",
762
779
  title: "List Merge Request Versions",
763
780
  description: "List MR diff versions.",
764
- mutating: false,
781
+ capabilities: readCapabilities,
765
782
  inputSchema: {
766
- project_id: z.string().optional(),
783
+ project_id: optionalProjectIdSchema,
767
784
  merge_request_iid: z.string().min(1)
768
785
  },
769
786
  handler: async (args, context) => context.gitlab.listMergeRequestVersions(resolveProjectId(args, context, true), getString(args, "merge_request_iid"))
@@ -772,9 +789,9 @@ function getGitLabToolDefinitions() {
772
789
  name: "gitlab_get_merge_request_version",
773
790
  title: "Get Merge Request Version",
774
791
  description: "Get one MR diff version.",
775
- mutating: false,
792
+ capabilities: readCapabilities,
776
793
  inputSchema: {
777
- project_id: z.string().optional(),
794
+ project_id: optionalProjectIdSchema,
778
795
  merge_request_iid: z.string().min(1),
779
796
  version_id: z.string().min(1),
780
797
  unidiff: optionalBoolean
@@ -785,9 +802,9 @@ function getGitLabToolDefinitions() {
785
802
  name: "gitlab_approve_merge_request",
786
803
  title: "Approve Merge Request",
787
804
  description: "Approve a merge request.",
788
- mutating: true,
805
+ capabilities: writeCapabilities,
789
806
  inputSchema: {
790
- project_id: z.string().optional(),
807
+ project_id: optionalProjectIdSchema,
791
808
  merge_request_iid: z.string().min(1),
792
809
  sha: optionalString,
793
810
  approval_password: optionalString
@@ -798,9 +815,9 @@ function getGitLabToolDefinitions() {
798
815
  name: "gitlab_unapprove_merge_request",
799
816
  title: "Unapprove Merge Request",
800
817
  description: "Remove current user approval from MR.",
801
- mutating: true,
818
+ capabilities: writeCapabilities,
802
819
  inputSchema: {
803
- project_id: z.string().optional(),
820
+ project_id: optionalProjectIdSchema,
804
821
  merge_request_iid: z.string().min(1)
805
822
  },
806
823
  handler: async (args, context) => context.gitlab.unapproveMergeRequest(resolveProjectId(args, context, true), getString(args, "merge_request_iid"))
@@ -809,20 +826,31 @@ function getGitLabToolDefinitions() {
809
826
  name: "gitlab_get_merge_request_approval_state",
810
827
  title: "Get Merge Request Approval State",
811
828
  description: "Get approval state for MR.",
812
- mutating: false,
829
+ capabilities: readCapabilities,
813
830
  inputSchema: {
814
- project_id: z.string().optional(),
831
+ project_id: optionalProjectIdSchema,
815
832
  merge_request_iid: z.string().min(1)
816
833
  },
817
834
  handler: async (args, context) => context.gitlab.getMergeRequestApprovalState(resolveProjectId(args, context, true), getString(args, "merge_request_iid"))
818
835
  },
836
+ {
837
+ name: "gitlab_get_merge_request_conflicts",
838
+ title: "Get Merge Request Conflicts",
839
+ description: "Get conflict details for MR.",
840
+ capabilities: readCapabilities,
841
+ inputSchema: {
842
+ project_id: optionalProjectIdSchema,
843
+ merge_request_iid: z.string().min(1)
844
+ },
845
+ handler: async (args, context) => context.gitlab.getMergeRequestConflicts(resolveProjectId(args, context, true), getString(args, "merge_request_iid"))
846
+ },
819
847
  {
820
848
  name: "gitlab_list_merge_request_discussions",
821
849
  title: "List Merge Request Discussions",
822
850
  description: "List MR discussions.",
823
- mutating: false,
851
+ capabilities: readCapabilities,
824
852
  inputSchema: {
825
- project_id: z.string().optional(),
853
+ project_id: optionalProjectIdSchema,
826
854
  merge_request_iid: z.string().min(1),
827
855
  ...paginationShape
828
856
  },
@@ -832,11 +860,11 @@ function getGitLabToolDefinitions() {
832
860
  name: "gitlab_create_merge_request_thread",
833
861
  title: "Create Merge Request Thread",
834
862
  description: "Create a new MR discussion thread (supports diff positions).",
835
- mutating: true,
863
+ capabilities: writeCapabilities,
836
864
  inputSchema: {
837
- project_id: z.string().optional(),
865
+ project_id: optionalProjectIdSchema,
838
866
  merge_request_iid: z.string().min(1),
839
- body: z.string().min(1),
867
+ body: bodySchema,
840
868
  position: optionalRecord,
841
869
  created_at: optionalString
842
870
  },
@@ -850,9 +878,9 @@ function getGitLabToolDefinitions() {
850
878
  name: "gitlab_mr_discussions",
851
879
  title: "Merge Request Discussions (Alias)",
852
880
  description: "Backward-compatible alias of gitlab_list_merge_request_discussions.",
853
- mutating: false,
881
+ capabilities: readCapabilities,
854
882
  inputSchema: {
855
- project_id: z.string().optional(),
883
+ project_id: optionalProjectIdSchema,
856
884
  merge_request_iid: z.string().min(1),
857
885
  ...paginationShape
858
886
  },
@@ -862,12 +890,12 @@ function getGitLabToolDefinitions() {
862
890
  name: "gitlab_create_merge_request_discussion_note",
863
891
  title: "Create MR Discussion Note",
864
892
  description: "Add note to existing MR discussion thread.",
865
- mutating: true,
893
+ capabilities: writeCapabilities,
866
894
  inputSchema: {
867
- project_id: z.string().optional(),
895
+ project_id: optionalProjectIdSchema,
868
896
  merge_request_iid: z.string().min(1),
869
897
  discussion_id: z.string().min(1),
870
- body: z.string().min(1),
898
+ body: bodySchema,
871
899
  created_at: optionalString
872
900
  },
873
901
  handler: async (args, context) => context.gitlab.createMergeRequestDiscussionNote(resolveProjectId(args, context, true), getString(args, "merge_request_iid"), getString(args, "discussion_id"), {
@@ -879,13 +907,13 @@ function getGitLabToolDefinitions() {
879
907
  name: "gitlab_update_merge_request_discussion_note",
880
908
  title: "Update MR Discussion Note",
881
909
  description: "Update note body/resolved state in MR discussion.",
882
- mutating: true,
910
+ capabilities: writeCapabilities,
883
911
  inputSchema: {
884
- project_id: z.string().optional(),
912
+ project_id: optionalProjectIdSchema,
885
913
  merge_request_iid: z.string().min(1),
886
914
  discussion_id: z.string().min(1),
887
915
  note_id: z.string().min(1),
888
- body: optionalString,
916
+ body: optionalBodySchema,
889
917
  resolved: optionalBoolean
890
918
  },
891
919
  handler: async (args, context) => {
@@ -906,10 +934,10 @@ function getGitLabToolDefinitions() {
906
934
  {
907
935
  name: "gitlab_delete_merge_request_discussion_note",
908
936
  title: "Delete MR Discussion Note",
909
- description: "Delete note from MR discussion thread.",
910
- mutating: true,
937
+ description: "Delete an MR discussion note permanently. Irreversible. Requires merge_request_iid, discussion_id, and note_id. Recommended pre-check: gitlab_list_merge_request_discussions.",
938
+ capabilities: deleteCapabilities,
911
939
  inputSchema: {
912
- project_id: z.string().optional(),
940
+ project_id: optionalProjectIdSchema,
913
941
  merge_request_iid: z.string().min(1),
914
942
  discussion_id: z.string().min(1),
915
943
  note_id: z.string().min(1)
@@ -920,9 +948,9 @@ function getGitLabToolDefinitions() {
920
948
  name: "gitlab_resolve_merge_request_thread",
921
949
  title: "Resolve Merge Request Thread",
922
950
  description: "Resolve/unresolve an MR discussion note.",
923
- mutating: true,
951
+ capabilities: writeCapabilities,
924
952
  inputSchema: {
925
- project_id: z.string().optional(),
953
+ project_id: optionalProjectIdSchema,
926
954
  merge_request_iid: z.string().min(1),
927
955
  discussion_id: z.string().min(1),
928
956
  note_id: z.string().min(1),
@@ -934,9 +962,9 @@ function getGitLabToolDefinitions() {
934
962
  name: "gitlab_list_merge_request_notes",
935
963
  title: "List Merge Request Notes",
936
964
  description: "List top-level notes for an MR.",
937
- mutating: false,
965
+ capabilities: readCapabilities,
938
966
  inputSchema: {
939
- project_id: z.string().optional(),
967
+ project_id: optionalProjectIdSchema,
940
968
  merge_request_iid: z.string().min(1),
941
969
  sort: optionalString,
942
970
  order_by: optionalString,
@@ -948,9 +976,9 @@ function getGitLabToolDefinitions() {
948
976
  name: "gitlab_get_merge_request_notes",
949
977
  title: "Get Merge Request Notes (Alias)",
950
978
  description: "Backward-compatible alias of gitlab_list_merge_request_notes.",
951
- mutating: false,
979
+ capabilities: readCapabilities,
952
980
  inputSchema: {
953
- project_id: z.string().optional(),
981
+ project_id: optionalProjectIdSchema,
954
982
  merge_request_iid: z.string().min(1),
955
983
  sort: optionalString,
956
984
  order_by: optionalString,
@@ -962,9 +990,9 @@ function getGitLabToolDefinitions() {
962
990
  name: "gitlab_get_draft_note",
963
991
  title: "Get Draft Note",
964
992
  description: "Get a single merge-request draft note.",
965
- mutating: false,
993
+ capabilities: readCapabilities,
966
994
  inputSchema: {
967
- project_id: z.string().optional(),
995
+ project_id: optionalProjectIdSchema,
968
996
  merge_request_iid: z.string().min(1),
969
997
  draft_note_id: z.string().min(1)
970
998
  },
@@ -974,9 +1002,9 @@ function getGitLabToolDefinitions() {
974
1002
  name: "gitlab_list_draft_notes",
975
1003
  title: "List Draft Notes",
976
1004
  description: "List draft notes on a merge request.",
977
- mutating: false,
1005
+ capabilities: readCapabilities,
978
1006
  inputSchema: {
979
- project_id: z.string().optional(),
1007
+ project_id: optionalProjectIdSchema,
980
1008
  merge_request_iid: z.string().min(1)
981
1009
  },
982
1010
  handler: async (args, context) => context.gitlab.listDraftNotes(resolveProjectId(args, context, true), getString(args, "merge_request_iid"))
@@ -985,11 +1013,11 @@ function getGitLabToolDefinitions() {
985
1013
  name: "gitlab_create_draft_note",
986
1014
  title: "Create Draft Note",
987
1015
  description: "Create a merge-request draft note.",
988
- mutating: true,
1016
+ capabilities: writeCapabilities,
989
1017
  inputSchema: {
990
- project_id: z.string().optional(),
1018
+ project_id: optionalProjectIdSchema,
991
1019
  merge_request_iid: z.string().min(1),
992
- body: z.string().min(1),
1020
+ body: bodySchema,
993
1021
  position: optionalRecord,
994
1022
  resolve_discussion: optionalBoolean
995
1023
  },
@@ -1003,12 +1031,12 @@ function getGitLabToolDefinitions() {
1003
1031
  name: "gitlab_update_draft_note",
1004
1032
  title: "Update Draft Note",
1005
1033
  description: "Update a merge-request draft note.",
1006
- mutating: true,
1034
+ capabilities: writeCapabilities,
1007
1035
  inputSchema: {
1008
- project_id: z.string().optional(),
1036
+ project_id: optionalProjectIdSchema,
1009
1037
  merge_request_iid: z.string().min(1),
1010
1038
  draft_note_id: z.string().min(1),
1011
- body: optionalString,
1039
+ body: optionalBodySchema,
1012
1040
  position: optionalRecord,
1013
1041
  resolve_discussion: optionalBoolean
1014
1042
  },
@@ -1028,10 +1056,10 @@ function getGitLabToolDefinitions() {
1028
1056
  {
1029
1057
  name: "gitlab_delete_draft_note",
1030
1058
  title: "Delete Draft Note",
1031
- description: "Delete a merge-request draft note.",
1032
- mutating: true,
1059
+ description: "Delete a merge-request draft note permanently. Irreversible. Requires merge_request_iid and draft_note_id. Recommended pre-check: gitlab_get_draft_note or gitlab_list_draft_notes.",
1060
+ capabilities: deleteCapabilities,
1033
1061
  inputSchema: {
1034
- project_id: z.string().optional(),
1062
+ project_id: optionalProjectIdSchema,
1035
1063
  merge_request_iid: z.string().min(1),
1036
1064
  draft_note_id: z.string().min(1)
1037
1065
  },
@@ -1041,9 +1069,9 @@ function getGitLabToolDefinitions() {
1041
1069
  name: "gitlab_publish_draft_note",
1042
1070
  title: "Publish Draft Note",
1043
1071
  description: "Publish one merge-request draft note.",
1044
- mutating: true,
1072
+ capabilities: writeCapabilities,
1045
1073
  inputSchema: {
1046
- project_id: z.string().optional(),
1074
+ project_id: optionalProjectIdSchema,
1047
1075
  merge_request_iid: z.string().min(1),
1048
1076
  draft_note_id: z.string().min(1)
1049
1077
  },
@@ -1053,9 +1081,9 @@ function getGitLabToolDefinitions() {
1053
1081
  name: "gitlab_bulk_publish_draft_notes",
1054
1082
  title: "Bulk Publish Draft Notes",
1055
1083
  description: "Publish all merge-request draft notes.",
1056
- mutating: true,
1084
+ capabilities: writeCapabilities,
1057
1085
  inputSchema: {
1058
- project_id: z.string().optional(),
1086
+ project_id: optionalProjectIdSchema,
1059
1087
  merge_request_iid: z.string().min(1)
1060
1088
  },
1061
1089
  handler: async (args, context) => context.gitlab.bulkPublishDraftNotes(resolveProjectId(args, context, true), getString(args, "merge_request_iid"))
@@ -1064,9 +1092,9 @@ function getGitLabToolDefinitions() {
1064
1092
  name: "gitlab_get_merge_request_note",
1065
1093
  title: "Get Merge Request Note",
1066
1094
  description: "Get a single MR note.",
1067
- mutating: false,
1095
+ capabilities: readCapabilities,
1068
1096
  inputSchema: {
1069
- project_id: z.string().optional(),
1097
+ project_id: optionalProjectIdSchema,
1070
1098
  merge_request_iid: z.string().min(1),
1071
1099
  note_id: z.string().min(1)
1072
1100
  },
@@ -1076,11 +1104,11 @@ function getGitLabToolDefinitions() {
1076
1104
  name: "gitlab_create_merge_request_note",
1077
1105
  title: "Create Merge Request Note",
1078
1106
  description: "Create a top-level MR note.",
1079
- mutating: true,
1107
+ capabilities: writeCapabilities,
1080
1108
  inputSchema: {
1081
- project_id: z.string().optional(),
1109
+ project_id: optionalProjectIdSchema,
1082
1110
  merge_request_iid: z.string().min(1),
1083
- body: z.string().min(1)
1111
+ body: bodySchema
1084
1112
  },
1085
1113
  handler: async (args, context) => context.gitlab.createMergeRequestNote(resolveProjectId(args, context, true), getString(args, "merge_request_iid"), getString(args, "body"))
1086
1114
  },
@@ -1088,12 +1116,12 @@ function getGitLabToolDefinitions() {
1088
1116
  name: "gitlab_create_note",
1089
1117
  title: "Create Note",
1090
1118
  description: "Create a note on an issue or merge request.",
1091
- mutating: true,
1119
+ capabilities: writeCapabilities,
1092
1120
  inputSchema: {
1093
- project_id: z.string().optional(),
1121
+ project_id: optionalProjectIdSchema,
1094
1122
  noteable_type: z.enum(["issue", "merge_request"]),
1095
1123
  noteable_iid: z.string().min(1),
1096
- body: z.string().min(1)
1124
+ body: bodySchema
1097
1125
  },
1098
1126
  handler: async (args, context) => context.gitlab.createNote(resolveProjectId(args, context, true), getString(args, "noteable_type"), getString(args, "noteable_iid"), getString(args, "body"))
1099
1127
  },
@@ -1101,22 +1129,22 @@ function getGitLabToolDefinitions() {
1101
1129
  name: "gitlab_update_merge_request_note",
1102
1130
  title: "Update Merge Request Note",
1103
1131
  description: "Update MR note body.",
1104
- mutating: true,
1132
+ capabilities: writeCapabilities,
1105
1133
  inputSchema: {
1106
- project_id: z.string().optional(),
1134
+ project_id: optionalProjectIdSchema,
1107
1135
  merge_request_iid: z.string().min(1),
1108
1136
  note_id: z.string().min(1),
1109
- body: z.string().min(1)
1137
+ body: bodySchema
1110
1138
  },
1111
1139
  handler: async (args, context) => context.gitlab.updateMergeRequestNote(resolveProjectId(args, context, true), getString(args, "merge_request_iid"), getString(args, "note_id"), getString(args, "body"))
1112
1140
  },
1113
1141
  {
1114
1142
  name: "gitlab_delete_merge_request_note",
1115
1143
  title: "Delete Merge Request Note",
1116
- description: "Delete an MR note.",
1117
- mutating: true,
1144
+ description: "Delete a top-level MR note permanently. Irreversible. Requires merge_request_iid and note_id. Recommended pre-check: gitlab_get_merge_request_note or gitlab_list_merge_request_notes.",
1145
+ capabilities: deleteCapabilities,
1118
1146
  inputSchema: {
1119
- project_id: z.string().optional(),
1147
+ project_id: optionalProjectIdSchema,
1120
1148
  merge_request_iid: z.string().min(1),
1121
1149
  note_id: z.string().min(1)
1122
1150
  },
@@ -1126,9 +1154,9 @@ function getGitLabToolDefinitions() {
1126
1154
  name: "gitlab_list_issues",
1127
1155
  title: "List Issues",
1128
1156
  description: "List issues in project.",
1129
- mutating: false,
1157
+ capabilities: readCapabilities,
1130
1158
  inputSchema: {
1131
- project_id: z.string().optional(),
1159
+ project_id: optionalProjectIdSchema,
1132
1160
  assignee_id: optionalStringOrNumber,
1133
1161
  assignee_username: optionalStringArray,
1134
1162
  author_id: optionalStringOrNumber,
@@ -1162,9 +1190,9 @@ function getGitLabToolDefinitions() {
1162
1190
  name: "gitlab_my_issues",
1163
1191
  title: "My Issues",
1164
1192
  description: "List issues assigned to the current authenticated user.",
1165
- mutating: false,
1193
+ capabilities: readCapabilities,
1166
1194
  inputSchema: {
1167
- project_id: z.string().optional(),
1195
+ project_id: optionalProjectIdSchema,
1168
1196
  state: z.enum(["opened", "closed", "all"]).optional(),
1169
1197
  labels: optionalStringOrStringArray,
1170
1198
  milestone: optionalString,
@@ -1187,9 +1215,9 @@ function getGitLabToolDefinitions() {
1187
1215
  name: "gitlab_get_issue",
1188
1216
  title: "Get Issue",
1189
1217
  description: "Get issue by IID.",
1190
- mutating: false,
1218
+ capabilities: readCapabilities,
1191
1219
  inputSchema: {
1192
- project_id: z.string().optional(),
1220
+ project_id: optionalProjectIdSchema,
1193
1221
  issue_iid: z.string().min(1)
1194
1222
  },
1195
1223
  handler: async (args, context) => context.gitlab.getIssue(resolveProjectId(args, context, true), getString(args, "issue_iid"))
@@ -1198,9 +1226,9 @@ function getGitLabToolDefinitions() {
1198
1226
  name: "gitlab_create_issue",
1199
1227
  title: "Create Issue",
1200
1228
  description: "Create a new issue.",
1201
- mutating: true,
1229
+ capabilities: writeCapabilities,
1202
1230
  inputSchema: {
1203
- project_id: z.string().optional(),
1231
+ project_id: optionalProjectIdSchema,
1204
1232
  title: z.string().min(1),
1205
1233
  description: optionalString,
1206
1234
  labels: optionalStringOrStringArray,
@@ -1225,9 +1253,9 @@ function getGitLabToolDefinitions() {
1225
1253
  name: "gitlab_update_issue",
1226
1254
  title: "Update Issue",
1227
1255
  description: "Update issue fields.",
1228
- mutating: true,
1256
+ capabilities: writeCapabilities,
1229
1257
  inputSchema: {
1230
- project_id: z.string().optional(),
1258
+ project_id: optionalProjectIdSchema,
1231
1259
  issue_iid: z.string().min(1),
1232
1260
  title: optionalString,
1233
1261
  description: optionalString,
@@ -1255,10 +1283,10 @@ function getGitLabToolDefinitions() {
1255
1283
  {
1256
1284
  name: "gitlab_delete_issue",
1257
1285
  title: "Delete Issue",
1258
- description: "Delete an issue.",
1259
- mutating: true,
1286
+ description: "Delete an issue permanently. Irreversible. Requires issue_iid. Recommended pre-check: gitlab_get_issue.",
1287
+ capabilities: deleteCapabilities,
1260
1288
  inputSchema: {
1261
- project_id: z.string().optional(),
1289
+ project_id: optionalProjectIdSchema,
1262
1290
  issue_iid: z.string().min(1)
1263
1291
  },
1264
1292
  handler: async (args, context) => context.gitlab.deleteIssue(resolveProjectId(args, context, true), getString(args, "issue_iid"))
@@ -1267,9 +1295,9 @@ function getGitLabToolDefinitions() {
1267
1295
  name: "gitlab_list_issue_discussions",
1268
1296
  title: "List Issue Discussions",
1269
1297
  description: "List issue discussions.",
1270
- mutating: false,
1298
+ capabilities: readCapabilities,
1271
1299
  inputSchema: {
1272
- project_id: z.string().optional(),
1300
+ project_id: optionalProjectIdSchema,
1273
1301
  issue_iid: z.string().min(1),
1274
1302
  ...paginationShape
1275
1303
  },
@@ -1279,12 +1307,12 @@ function getGitLabToolDefinitions() {
1279
1307
  name: "gitlab_create_issue_note",
1280
1308
  title: "Create Issue Note",
1281
1309
  description: "Create issue comment (top-level or discussion note).",
1282
- mutating: true,
1310
+ capabilities: writeCapabilities,
1283
1311
  inputSchema: {
1284
- project_id: z.string().optional(),
1312
+ project_id: optionalProjectIdSchema,
1285
1313
  issue_iid: z.string().min(1),
1286
1314
  discussion_id: optionalString,
1287
- body: z.string().min(1),
1315
+ body: bodySchema,
1288
1316
  created_at: optionalString
1289
1317
  },
1290
1318
  handler: async (args, context) => context.gitlab.createIssueNote(resolveProjectId(args, context, true), getString(args, "issue_iid"), {
@@ -1297,13 +1325,13 @@ function getGitLabToolDefinitions() {
1297
1325
  name: "gitlab_update_issue_note",
1298
1326
  title: "Update Issue Note",
1299
1327
  description: "Update an issue discussion note body or resolved state.",
1300
- mutating: true,
1328
+ capabilities: writeCapabilities,
1301
1329
  inputSchema: {
1302
- project_id: z.string().optional(),
1330
+ project_id: optionalProjectIdSchema,
1303
1331
  issue_iid: z.string().min(1),
1304
1332
  discussion_id: z.string().min(1),
1305
1333
  note_id: z.string().min(1),
1306
- body: optionalString,
1334
+ body: optionalBodySchema,
1307
1335
  resolved: optionalBoolean
1308
1336
  },
1309
1337
  handler: async (args, context) => {
@@ -1322,9 +1350,9 @@ function getGitLabToolDefinitions() {
1322
1350
  name: "gitlab_list_issue_links",
1323
1351
  title: "List Issue Links",
1324
1352
  description: "List related issue links for an issue.",
1325
- mutating: false,
1353
+ capabilities: readCapabilities,
1326
1354
  inputSchema: {
1327
- project_id: z.string().optional(),
1355
+ project_id: optionalProjectIdSchema,
1328
1356
  issue_iid: z.string().min(1)
1329
1357
  },
1330
1358
  handler: async (args, context) => context.gitlab.listIssueLinks(resolveProjectId(args, context, true), getString(args, "issue_iid"))
@@ -1333,9 +1361,9 @@ function getGitLabToolDefinitions() {
1333
1361
  name: "gitlab_get_issue_link",
1334
1362
  title: "Get Issue Link",
1335
1363
  description: "Get a single issue link by ID.",
1336
- mutating: false,
1364
+ capabilities: readCapabilities,
1337
1365
  inputSchema: {
1338
- project_id: z.string().optional(),
1366
+ project_id: optionalProjectIdSchema,
1339
1367
  issue_iid: z.string().min(1),
1340
1368
  issue_link_id: z.string().min(1)
1341
1369
  },
@@ -1345,11 +1373,11 @@ function getGitLabToolDefinitions() {
1345
1373
  name: "gitlab_create_issue_link",
1346
1374
  title: "Create Issue Link",
1347
1375
  description: "Create a relation between two issues.",
1348
- mutating: true,
1376
+ capabilities: writeCapabilities,
1349
1377
  inputSchema: {
1350
- project_id: z.string().optional(),
1378
+ project_id: optionalProjectIdSchema,
1351
1379
  issue_iid: z.string().min(1),
1352
- target_project_id: z.string().min(1),
1380
+ target_project_id: projectIdSchema,
1353
1381
  target_issue_iid: z.string().min(1),
1354
1382
  link_type: z.enum(["relates_to", "blocks", "is_blocked_by"]).optional()
1355
1383
  },
@@ -1362,10 +1390,10 @@ function getGitLabToolDefinitions() {
1362
1390
  {
1363
1391
  name: "gitlab_delete_issue_link",
1364
1392
  title: "Delete Issue Link",
1365
- description: "Delete a relation between issues.",
1366
- mutating: true,
1393
+ description: "Delete an issue link permanently. Irreversible for that relation. Requires issue_iid and issue_link_id. Recommended pre-check: gitlab_get_issue_link or gitlab_list_issue_links.",
1394
+ capabilities: deleteCapabilities,
1367
1395
  inputSchema: {
1368
- project_id: z.string().optional(),
1396
+ project_id: optionalProjectIdSchema,
1369
1397
  issue_iid: z.string().min(1),
1370
1398
  issue_link_id: z.string().min(1)
1371
1399
  },
@@ -1375,10 +1403,10 @@ function getGitLabToolDefinitions() {
1375
1403
  name: "gitlab_list_wiki_pages",
1376
1404
  title: "List Wiki Pages",
1377
1405
  description: "List wiki pages in a project.",
1378
- mutating: false,
1406
+ capabilities: readCapabilities,
1379
1407
  requiresFeature: "wiki",
1380
1408
  inputSchema: {
1381
- project_id: z.string().optional(),
1409
+ project_id: optionalProjectIdSchema,
1382
1410
  with_content: optionalBoolean,
1383
1411
  ...paginationShape
1384
1412
  },
@@ -1390,11 +1418,11 @@ function getGitLabToolDefinitions() {
1390
1418
  name: "gitlab_get_wiki_page",
1391
1419
  title: "Get Wiki Page",
1392
1420
  description: "Get wiki page by slug.",
1393
- mutating: false,
1421
+ capabilities: readCapabilities,
1394
1422
  requiresFeature: "wiki",
1395
1423
  inputSchema: {
1396
- project_id: z.string().optional(),
1397
- slug: z.string().min(1),
1424
+ project_id: optionalProjectIdSchema,
1425
+ slug: slugSchema,
1398
1426
  version: optionalString
1399
1427
  },
1400
1428
  handler: async (args, context) => context.gitlab.getWikiPage(resolveProjectId(args, context, true), getString(args, "slug"), {
@@ -1405,10 +1433,10 @@ function getGitLabToolDefinitions() {
1405
1433
  name: "gitlab_create_wiki_page",
1406
1434
  title: "Create Wiki Page",
1407
1435
  description: "Create a wiki page.",
1408
- mutating: true,
1436
+ capabilities: writeCapabilities,
1409
1437
  requiresFeature: "wiki",
1410
1438
  inputSchema: {
1411
- project_id: z.string().optional(),
1439
+ project_id: optionalProjectIdSchema,
1412
1440
  title: z.string().min(1),
1413
1441
  content: z.string().min(1),
1414
1442
  format: optionalString
@@ -1423,11 +1451,11 @@ function getGitLabToolDefinitions() {
1423
1451
  name: "gitlab_update_wiki_page",
1424
1452
  title: "Update Wiki Page",
1425
1453
  description: "Update wiki page by slug.",
1426
- mutating: true,
1454
+ capabilities: writeCapabilities,
1427
1455
  requiresFeature: "wiki",
1428
1456
  inputSchema: {
1429
- project_id: z.string().optional(),
1430
- slug: z.string().min(1),
1457
+ project_id: optionalProjectIdSchema,
1458
+ slug: slugSchema,
1431
1459
  content: z.string().min(1),
1432
1460
  title: optionalString,
1433
1461
  format: optionalString
@@ -1441,12 +1469,12 @@ function getGitLabToolDefinitions() {
1441
1469
  {
1442
1470
  name: "gitlab_delete_wiki_page",
1443
1471
  title: "Delete Wiki Page",
1444
- description: "Delete wiki page by slug.",
1445
- mutating: true,
1472
+ description: "Delete a wiki page permanently. Irreversible. Requires slug. Recommended pre-check: gitlab_get_wiki_page or gitlab_list_wiki_pages.",
1473
+ capabilities: deleteCapabilities,
1446
1474
  requiresFeature: "wiki",
1447
1475
  inputSchema: {
1448
- project_id: z.string().optional(),
1449
- slug: z.string().min(1)
1476
+ project_id: optionalProjectIdSchema,
1477
+ slug: slugSchema
1450
1478
  },
1451
1479
  handler: async (args, context) => context.gitlab.deleteWikiPage(resolveProjectId(args, context, true), getString(args, "slug"))
1452
1480
  },
@@ -1454,10 +1482,10 @@ function getGitLabToolDefinitions() {
1454
1482
  name: "gitlab_list_pipelines",
1455
1483
  title: "List Pipelines",
1456
1484
  description: "List pipelines for a project.",
1457
- mutating: false,
1485
+ capabilities: readCapabilities,
1458
1486
  requiresFeature: "pipeline",
1459
1487
  inputSchema: {
1460
- project_id: z.string().optional(),
1488
+ project_id: optionalProjectIdSchema,
1461
1489
  scope: z.enum(["running", "pending", "finished", "branches", "tags"]).optional(),
1462
1490
  status: z
1463
1491
  .enum([
@@ -1474,7 +1502,7 @@ function getGitLabToolDefinitions() {
1474
1502
  "scheduled"
1475
1503
  ])
1476
1504
  .optional(),
1477
- ref: optionalString,
1505
+ ref: optionalRefLikeSchema,
1478
1506
  sha: optionalString,
1479
1507
  yaml_errors: optionalBoolean,
1480
1508
  username: optionalString,
@@ -1493,22 +1521,87 @@ function getGitLabToolDefinitions() {
1493
1521
  name: "gitlab_get_pipeline",
1494
1522
  title: "Get Pipeline",
1495
1523
  description: "Get one pipeline.",
1496
- mutating: false,
1524
+ capabilities: readCapabilities,
1497
1525
  requiresFeature: "pipeline",
1498
1526
  inputSchema: {
1499
- project_id: z.string().optional(),
1527
+ project_id: optionalProjectIdSchema,
1500
1528
  pipeline_id: z.string().min(1)
1501
1529
  },
1502
1530
  handler: async (args, context) => context.gitlab.getPipeline(resolveProjectId(args, context, true), getString(args, "pipeline_id"))
1503
1531
  },
1532
+ {
1533
+ name: "gitlab_list_deployments",
1534
+ title: "List Deployments",
1535
+ description: "List deployments in a project.",
1536
+ capabilities: readCapabilities,
1537
+ requiresFeature: "pipeline",
1538
+ inputSchema: {
1539
+ project_id: optionalProjectIdSchema,
1540
+ environment: optionalString,
1541
+ ref: optionalRefLikeSchema,
1542
+ sha: optionalString,
1543
+ status: optionalString,
1544
+ updated_after: optionalString,
1545
+ updated_before: optionalString,
1546
+ order_by: z
1547
+ .enum(["id", "iid", "created_at", "updated_at", "ref", "status", "environment"])
1548
+ .optional(),
1549
+ sort: z.enum(["asc", "desc"]).optional(),
1550
+ ...paginationShape
1551
+ },
1552
+ handler: async (args, context) => context.gitlab.listDeployments(resolveProjectId(args, context, true), {
1553
+ query: toQuery(omit(args, ["project_id"]))
1554
+ })
1555
+ },
1556
+ {
1557
+ name: "gitlab_get_deployment",
1558
+ title: "Get Deployment",
1559
+ description: "Get one deployment by ID.",
1560
+ capabilities: readCapabilities,
1561
+ requiresFeature: "pipeline",
1562
+ inputSchema: {
1563
+ project_id: optionalProjectIdSchema,
1564
+ deployment_id: z.string().min(1)
1565
+ },
1566
+ handler: async (args, context) => context.gitlab.getDeployment(resolveProjectId(args, context, true), getString(args, "deployment_id"))
1567
+ },
1568
+ {
1569
+ name: "gitlab_list_environments",
1570
+ title: "List Environments",
1571
+ description: "List environments in a project.",
1572
+ capabilities: readCapabilities,
1573
+ requiresFeature: "pipeline",
1574
+ inputSchema: {
1575
+ project_id: optionalProjectIdSchema,
1576
+ name: optionalDisplayNameSchema,
1577
+ search: optionalString,
1578
+ states: z.enum(["available", "stopped"]).optional(),
1579
+ ...paginationShape
1580
+ },
1581
+ handler: async (args, context) => context.gitlab.listEnvironments(resolveProjectId(args, context, true), {
1582
+ query: toQuery(omit(args, ["project_id"]))
1583
+ })
1584
+ },
1585
+ {
1586
+ name: "gitlab_get_environment",
1587
+ title: "Get Environment",
1588
+ description: "Get one environment by ID.",
1589
+ capabilities: readCapabilities,
1590
+ requiresFeature: "pipeline",
1591
+ inputSchema: {
1592
+ project_id: optionalProjectIdSchema,
1593
+ environment_id: z.string().min(1)
1594
+ },
1595
+ handler: async (args, context) => context.gitlab.getEnvironment(resolveProjectId(args, context, true), getString(args, "environment_id"))
1596
+ },
1504
1597
  {
1505
1598
  name: "gitlab_list_pipeline_jobs",
1506
1599
  title: "List Pipeline Jobs",
1507
1600
  description: "List jobs in a pipeline.",
1508
- mutating: false,
1601
+ capabilities: readCapabilities,
1509
1602
  requiresFeature: "pipeline",
1510
1603
  inputSchema: {
1511
- project_id: z.string().optional(),
1604
+ project_id: optionalProjectIdSchema,
1512
1605
  pipeline_id: z.string().min(1),
1513
1606
  scope: z
1514
1607
  .enum([
@@ -1531,10 +1624,10 @@ function getGitLabToolDefinitions() {
1531
1624
  name: "gitlab_list_pipeline_trigger_jobs",
1532
1625
  title: "List Pipeline Trigger Jobs",
1533
1626
  description: "List downstream/bridge trigger jobs in a pipeline.",
1534
- mutating: false,
1627
+ capabilities: readCapabilities,
1535
1628
  requiresFeature: "pipeline",
1536
1629
  inputSchema: {
1537
- project_id: z.string().optional(),
1630
+ project_id: optionalProjectIdSchema,
1538
1631
  pipeline_id: z.string().min(1),
1539
1632
  scope: z
1540
1633
  .enum([
@@ -1560,10 +1653,10 @@ function getGitLabToolDefinitions() {
1560
1653
  name: "gitlab_get_pipeline_job",
1561
1654
  title: "Get Pipeline Job",
1562
1655
  description: "Get one job by job ID.",
1563
- mutating: false,
1656
+ capabilities: readCapabilities,
1564
1657
  requiresFeature: "pipeline",
1565
1658
  inputSchema: {
1566
- project_id: z.string().optional(),
1659
+ project_id: optionalProjectIdSchema,
1567
1660
  job_id: z.string().min(1)
1568
1661
  },
1569
1662
  handler: async (args, context) => context.gitlab.getPipelineJob(resolveProjectId(args, context, true), getString(args, "job_id"))
@@ -1572,23 +1665,92 @@ function getGitLabToolDefinitions() {
1572
1665
  name: "gitlab_get_pipeline_job_output",
1573
1666
  title: "Get Pipeline Job Output",
1574
1667
  description: "Get raw job trace output.",
1575
- mutating: false,
1668
+ capabilities: readCapabilities,
1576
1669
  requiresFeature: "pipeline",
1577
1670
  inputSchema: {
1578
- project_id: z.string().optional(),
1671
+ project_id: optionalProjectIdSchema,
1579
1672
  job_id: z.string().min(1)
1580
1673
  },
1581
1674
  handler: async (args, context) => context.gitlab.getPipelineJobOutput(resolveProjectId(args, context, true), getString(args, "job_id"))
1582
1675
  },
1676
+ {
1677
+ name: "gitlab_list_job_artifacts",
1678
+ title: "List Job Artifacts",
1679
+ description: "List files and directories inside a job artifacts archive.",
1680
+ capabilities: readCapabilities,
1681
+ requiresFeature: "pipeline",
1682
+ inputSchema: {
1683
+ project_id: optionalProjectIdSchema,
1684
+ job_id: z.string().min(1),
1685
+ path: optionalString,
1686
+ recursive: optionalBoolean
1687
+ },
1688
+ handler: async (args, context) => context.gitlab.listJobArtifacts(resolveProjectId(args, context, true), getString(args, "job_id"), { query: toQuery(omit(args, ["project_id", "job_id"])) })
1689
+ },
1690
+ {
1691
+ name: "gitlab_download_job_artifacts",
1692
+ title: "Download Job Artifacts",
1693
+ description: "Download the full job artifacts archive as base64 content.",
1694
+ capabilities: readCapabilities,
1695
+ requiresFeature: "pipeline",
1696
+ inputSchema: {
1697
+ project_id: optionalProjectIdSchema,
1698
+ job_id: z.string().min(1)
1699
+ },
1700
+ handler: async (args, context) => context.gitlab.downloadJobArtifacts(resolveProjectId(args, context, true), getString(args, "job_id"))
1701
+ },
1702
+ {
1703
+ name: "gitlab_download_job_artifacts_local",
1704
+ title: "Download Job Artifacts Local",
1705
+ description: "Download the full job artifacts archive to a local directory.",
1706
+ capabilities: writeCapabilities,
1707
+ requiresFeature: "pipeline",
1708
+ requiresLocalFileTools: true,
1709
+ inputSchema: {
1710
+ project_id: optionalProjectIdSchema,
1711
+ job_id: z.string().min(1),
1712
+ local_path: optionalString
1713
+ },
1714
+ handler: async (args, context) => context.gitlab.saveJobArtifacts(resolveProjectId(args, context, true), getString(args, "job_id"), getOptionalString(args, "local_path"))
1715
+ },
1716
+ {
1717
+ name: "gitlab_get_job_artifact_file",
1718
+ title: "Get Job Artifact File",
1719
+ description: "Return one file from a job artifacts archive as inline UTF-8 or base64 content.",
1720
+ capabilities: readCapabilities,
1721
+ requiresFeature: "pipeline",
1722
+ inputSchema: {
1723
+ project_id: optionalProjectIdSchema,
1724
+ job_id: z.string().min(1),
1725
+ artifact_path: z.string().min(1)
1726
+ },
1727
+ handler: async (args, context) => context.gitlab.getJobArtifactFile(resolveProjectId(args, context, true), getString(args, "job_id"), getString(args, "artifact_path"))
1728
+ },
1729
+ {
1730
+ name: "gitlab_get_job_artifact_file_local",
1731
+ title: "Get Job Artifact File Local",
1732
+ description: "Save one file from a job artifacts archive to a local directory.",
1733
+ capabilities: writeCapabilities,
1734
+ requiresFeature: "pipeline",
1735
+ requiresLocalFileTools: true,
1736
+ inputSchema: {
1737
+ project_id: optionalProjectIdSchema,
1738
+ job_id: z.string().min(1),
1739
+ artifact_path: z.string().min(1),
1740
+ local_path: optionalString
1741
+ },
1742
+ handler: async (args, context) => context.gitlab.saveJobArtifactFile(resolveProjectId(args, context, true), getString(args, "job_id"), getString(args, "artifact_path"), getOptionalString(args, "local_path"))
1743
+ },
1583
1744
  {
1584
1745
  name: "gitlab_create_pipeline",
1585
1746
  title: "Create Pipeline",
1586
1747
  description: "Trigger a new pipeline.",
1587
- mutating: true,
1748
+ capabilities: writeCapabilities,
1588
1749
  requiresFeature: "pipeline",
1589
1750
  inputSchema: {
1590
- project_id: z.string().optional(),
1591
- ref: z.string().min(1),
1751
+ project_id: optionalProjectIdSchema,
1752
+ ref: refLikeSchema,
1753
+ inputs: optionalPipelineInputsRecord,
1592
1754
  variables: z
1593
1755
  .array(z.object({
1594
1756
  key: z.string(),
@@ -1599,17 +1761,18 @@ function getGitLabToolDefinitions() {
1599
1761
  },
1600
1762
  handler: async (args, context) => context.gitlab.createPipeline(resolveProjectId(args, context, true), {
1601
1763
  ref: getString(args, "ref"),
1602
- variables: getArray(args, "variables")
1764
+ inputs: getOptionalPipelineInputsRecord(args, "inputs"),
1765
+ variables: getOptionalArray(args, "variables")
1603
1766
  })
1604
1767
  },
1605
1768
  {
1606
1769
  name: "gitlab_retry_pipeline",
1607
1770
  title: "Retry Pipeline",
1608
1771
  description: "Retry failed jobs in pipeline.",
1609
- mutating: true,
1772
+ capabilities: writeCapabilities,
1610
1773
  requiresFeature: "pipeline",
1611
1774
  inputSchema: {
1612
- project_id: z.string().optional(),
1775
+ project_id: optionalProjectIdSchema,
1613
1776
  pipeline_id: z.string().min(1)
1614
1777
  },
1615
1778
  handler: async (args, context) => context.gitlab.retryPipeline(resolveProjectId(args, context, true), getString(args, "pipeline_id"))
@@ -1618,10 +1781,10 @@ function getGitLabToolDefinitions() {
1618
1781
  name: "gitlab_cancel_pipeline",
1619
1782
  title: "Cancel Pipeline",
1620
1783
  description: "Cancel a running pipeline.",
1621
- mutating: true,
1784
+ capabilities: writeCapabilities,
1622
1785
  requiresFeature: "pipeline",
1623
1786
  inputSchema: {
1624
- project_id: z.string().optional(),
1787
+ project_id: optionalProjectIdSchema,
1625
1788
  pipeline_id: z.string().min(1)
1626
1789
  },
1627
1790
  handler: async (args, context) => context.gitlab.cancelPipeline(resolveProjectId(args, context, true), getString(args, "pipeline_id"))
@@ -1630,10 +1793,10 @@ function getGitLabToolDefinitions() {
1630
1793
  name: "gitlab_retry_pipeline_job",
1631
1794
  title: "Retry Pipeline Job",
1632
1795
  description: "Retry one failed job.",
1633
- mutating: true,
1796
+ capabilities: writeCapabilities,
1634
1797
  requiresFeature: "pipeline",
1635
1798
  inputSchema: {
1636
- project_id: z.string().optional(),
1799
+ project_id: optionalProjectIdSchema,
1637
1800
  job_id: z.string().min(1)
1638
1801
  },
1639
1802
  handler: async (args, context) => context.gitlab.retryPipelineJob(resolveProjectId(args, context, true), getString(args, "job_id"))
@@ -1642,10 +1805,10 @@ function getGitLabToolDefinitions() {
1642
1805
  name: "gitlab_cancel_pipeline_job",
1643
1806
  title: "Cancel Pipeline Job",
1644
1807
  description: "Cancel one running job.",
1645
- mutating: true,
1808
+ capabilities: writeCapabilities,
1646
1809
  requiresFeature: "pipeline",
1647
1810
  inputSchema: {
1648
- project_id: z.string().optional(),
1811
+ project_id: optionalProjectIdSchema,
1649
1812
  job_id: z.string().min(1)
1650
1813
  },
1651
1814
  handler: async (args, context) => context.gitlab.cancelPipelineJob(resolveProjectId(args, context, true), getString(args, "job_id"))
@@ -1654,10 +1817,10 @@ function getGitLabToolDefinitions() {
1654
1817
  name: "gitlab_play_pipeline_job",
1655
1818
  title: "Play Pipeline Job",
1656
1819
  description: "Play a manual job.",
1657
- mutating: true,
1820
+ capabilities: writeCapabilities,
1658
1821
  requiresFeature: "pipeline",
1659
1822
  inputSchema: {
1660
- project_id: z.string().optional(),
1823
+ project_id: optionalProjectIdSchema,
1661
1824
  job_id: z.string().min(1)
1662
1825
  },
1663
1826
  handler: async (args, context) => context.gitlab.playPipelineJob(resolveProjectId(args, context, true), getString(args, "job_id"))
@@ -1666,10 +1829,10 @@ function getGitLabToolDefinitions() {
1666
1829
  name: "gitlab_list_milestones",
1667
1830
  title: "List Milestones",
1668
1831
  description: "List project milestones.",
1669
- mutating: false,
1832
+ capabilities: readCapabilities,
1670
1833
  requiresFeature: "milestone",
1671
1834
  inputSchema: {
1672
- project_id: z.string().optional(),
1835
+ project_id: optionalProjectIdSchema,
1673
1836
  iids: optionalNumberArray,
1674
1837
  state: optionalString,
1675
1838
  title: optionalString,
@@ -1687,10 +1850,10 @@ function getGitLabToolDefinitions() {
1687
1850
  name: "gitlab_get_milestone",
1688
1851
  title: "Get Milestone",
1689
1852
  description: "Get a milestone by ID.",
1690
- mutating: false,
1853
+ capabilities: readCapabilities,
1691
1854
  requiresFeature: "milestone",
1692
1855
  inputSchema: {
1693
- project_id: z.string().optional(),
1856
+ project_id: optionalProjectIdSchema,
1694
1857
  milestone_id: z.string().min(1)
1695
1858
  },
1696
1859
  handler: async (args, context) => context.gitlab.getMilestone(resolveProjectId(args, context, true), getString(args, "milestone_id"))
@@ -1699,10 +1862,10 @@ function getGitLabToolDefinitions() {
1699
1862
  name: "gitlab_create_milestone",
1700
1863
  title: "Create Milestone",
1701
1864
  description: "Create a milestone.",
1702
- mutating: true,
1865
+ capabilities: writeCapabilities,
1703
1866
  requiresFeature: "milestone",
1704
1867
  inputSchema: {
1705
- project_id: z.string().optional(),
1868
+ project_id: optionalProjectIdSchema,
1706
1869
  title: z.string().min(1),
1707
1870
  description: optionalString,
1708
1871
  due_date: optionalString,
@@ -1719,10 +1882,10 @@ function getGitLabToolDefinitions() {
1719
1882
  name: "gitlab_update_milestone",
1720
1883
  title: "Update Milestone",
1721
1884
  description: "Update milestone fields.",
1722
- mutating: true,
1885
+ capabilities: writeCapabilities,
1723
1886
  requiresFeature: "milestone",
1724
1887
  inputSchema: {
1725
- project_id: z.string().optional(),
1888
+ project_id: optionalProjectIdSchema,
1726
1889
  milestone_id: z.string().min(1),
1727
1890
  title: optionalString,
1728
1891
  description: optionalString,
@@ -1736,10 +1899,10 @@ function getGitLabToolDefinitions() {
1736
1899
  name: "gitlab_edit_milestone",
1737
1900
  title: "Edit Milestone (Alias)",
1738
1901
  description: "Backward-compatible alias of gitlab_update_milestone.",
1739
- mutating: true,
1902
+ capabilities: writeCapabilities,
1740
1903
  requiresFeature: "milestone",
1741
1904
  inputSchema: {
1742
- project_id: z.string().optional(),
1905
+ project_id: optionalProjectIdSchema,
1743
1906
  milestone_id: z.string().min(1),
1744
1907
  title: optionalString,
1745
1908
  description: optionalString,
@@ -1752,11 +1915,11 @@ function getGitLabToolDefinitions() {
1752
1915
  {
1753
1916
  name: "gitlab_delete_milestone",
1754
1917
  title: "Delete Milestone",
1755
- description: "Delete a milestone.",
1756
- mutating: true,
1918
+ description: "Delete a milestone permanently. Irreversible. Requires milestone_id. Recommended pre-check: gitlab_get_milestone or gitlab_list_milestones.",
1919
+ capabilities: deleteCapabilities,
1757
1920
  requiresFeature: "milestone",
1758
1921
  inputSchema: {
1759
- project_id: z.string().optional(),
1922
+ project_id: optionalProjectIdSchema,
1760
1923
  milestone_id: z.string().min(1)
1761
1924
  },
1762
1925
  handler: async (args, context) => context.gitlab.deleteMilestone(resolveProjectId(args, context, true), getString(args, "milestone_id"))
@@ -1765,10 +1928,10 @@ function getGitLabToolDefinitions() {
1765
1928
  name: "gitlab_get_milestone_issue",
1766
1929
  title: "Get Milestone Issues",
1767
1930
  description: "List issues assigned to a milestone.",
1768
- mutating: false,
1931
+ capabilities: readCapabilities,
1769
1932
  requiresFeature: "milestone",
1770
1933
  inputSchema: {
1771
- project_id: z.string().optional(),
1934
+ project_id: optionalProjectIdSchema,
1772
1935
  milestone_id: z.string().min(1)
1773
1936
  },
1774
1937
  handler: async (args, context) => context.gitlab.getMilestoneIssues(resolveProjectId(args, context, true), getString(args, "milestone_id"))
@@ -1777,10 +1940,10 @@ function getGitLabToolDefinitions() {
1777
1940
  name: "gitlab_get_milestone_merge_requests",
1778
1941
  title: "Get Milestone Merge Requests",
1779
1942
  description: "List merge requests assigned to a milestone.",
1780
- mutating: false,
1943
+ capabilities: readCapabilities,
1781
1944
  requiresFeature: "milestone",
1782
1945
  inputSchema: {
1783
- project_id: z.string().optional(),
1946
+ project_id: optionalProjectIdSchema,
1784
1947
  milestone_id: z.string().min(1),
1785
1948
  ...paginationShape
1786
1949
  },
@@ -1790,10 +1953,10 @@ function getGitLabToolDefinitions() {
1790
1953
  name: "gitlab_promote_milestone",
1791
1954
  title: "Promote Milestone",
1792
1955
  description: "Promote a project milestone to a group milestone.",
1793
- mutating: true,
1956
+ capabilities: adminCapabilities,
1794
1957
  requiresFeature: "milestone",
1795
1958
  inputSchema: {
1796
- project_id: z.string().optional(),
1959
+ project_id: optionalProjectIdSchema,
1797
1960
  milestone_id: z.string().min(1)
1798
1961
  },
1799
1962
  handler: async (args, context) => context.gitlab.promoteMilestone(resolveProjectId(args, context, true), getString(args, "milestone_id"))
@@ -1802,10 +1965,10 @@ function getGitLabToolDefinitions() {
1802
1965
  name: "gitlab_get_milestone_burndown_events",
1803
1966
  title: "Get Milestone Burndown Events",
1804
1967
  description: "List burndown events for a milestone.",
1805
- mutating: false,
1968
+ capabilities: readCapabilities,
1806
1969
  requiresFeature: "milestone",
1807
1970
  inputSchema: {
1808
- project_id: z.string().optional(),
1971
+ project_id: optionalProjectIdSchema,
1809
1972
  milestone_id: z.string().min(1),
1810
1973
  ...paginationShape
1811
1974
  },
@@ -1815,10 +1978,10 @@ function getGitLabToolDefinitions() {
1815
1978
  name: "gitlab_list_releases",
1816
1979
  title: "List Releases",
1817
1980
  description: "List project releases.",
1818
- mutating: false,
1981
+ capabilities: readCapabilities,
1819
1982
  requiresFeature: "release",
1820
1983
  inputSchema: {
1821
- project_id: z.string().optional(),
1984
+ project_id: optionalProjectIdSchema,
1822
1985
  order_by: z.enum(["released_at", "created_at"]).optional(),
1823
1986
  sort: z.enum(["asc", "desc"]).optional(),
1824
1987
  include_html_description: optionalBoolean,
@@ -1832,11 +1995,11 @@ function getGitLabToolDefinitions() {
1832
1995
  name: "gitlab_get_release",
1833
1996
  title: "Get Release",
1834
1997
  description: "Get one release by tag name.",
1835
- mutating: false,
1998
+ capabilities: readCapabilities,
1836
1999
  requiresFeature: "release",
1837
2000
  inputSchema: {
1838
- project_id: z.string().optional(),
1839
- tag_name: z.string().min(1),
2001
+ project_id: optionalProjectIdSchema,
2002
+ tag_name: refLikeSchema,
1840
2003
  include_html_description: optionalBoolean
1841
2004
  },
1842
2005
  handler: async (args, context) => context.gitlab.getRelease(resolveProjectId(args, context, true), getString(args, "tag_name"), { query: toQuery(omit(args, ["project_id", "tag_name"])) })
@@ -1845,15 +2008,15 @@ function getGitLabToolDefinitions() {
1845
2008
  name: "gitlab_create_release",
1846
2009
  title: "Create Release",
1847
2010
  description: "Create a release.",
1848
- mutating: true,
2011
+ capabilities: writeCapabilities,
1849
2012
  requiresFeature: "release",
1850
2013
  inputSchema: {
1851
- project_id: z.string().optional(),
1852
- name: optionalString,
1853
- tag_name: z.string().min(1),
2014
+ project_id: optionalProjectIdSchema,
2015
+ name: optionalDisplayNameSchema,
2016
+ tag_name: refLikeSchema,
1854
2017
  tag_message: optionalString,
1855
2018
  description: optionalString,
1856
- ref: optionalString,
2019
+ ref: optionalRefLikeSchema,
1857
2020
  released_at: optionalString,
1858
2021
  milestones: optionalStringArray,
1859
2022
  assets: optionalRecord
@@ -1864,12 +2027,12 @@ function getGitLabToolDefinitions() {
1864
2027
  name: "gitlab_update_release",
1865
2028
  title: "Update Release",
1866
2029
  description: "Update existing release.",
1867
- mutating: true,
2030
+ capabilities: writeCapabilities,
1868
2031
  requiresFeature: "release",
1869
2032
  inputSchema: {
1870
- project_id: z.string().optional(),
1871
- tag_name: z.string().min(1),
1872
- name: optionalString,
2033
+ project_id: optionalProjectIdSchema,
2034
+ tag_name: refLikeSchema,
2035
+ name: optionalDisplayNameSchema,
1873
2036
  description: optionalString,
1874
2037
  released_at: optionalString,
1875
2038
  milestones: optionalStringArray,
@@ -1880,12 +2043,12 @@ function getGitLabToolDefinitions() {
1880
2043
  {
1881
2044
  name: "gitlab_delete_release",
1882
2045
  title: "Delete Release",
1883
- description: "Delete a release by tag.",
1884
- mutating: true,
2046
+ description: "Delete the release record for tag_name permanently. Irreversible for the release entry. Requires tag_name. Recommended pre-check: gitlab_get_release or gitlab_list_releases.",
2047
+ capabilities: deleteCapabilities,
1885
2048
  requiresFeature: "release",
1886
2049
  inputSchema: {
1887
- project_id: z.string().optional(),
1888
- tag_name: z.string().min(1)
2050
+ project_id: optionalProjectIdSchema,
2051
+ tag_name: refLikeSchema
1889
2052
  },
1890
2053
  handler: async (args, context) => context.gitlab.deleteRelease(resolveProjectId(args, context, true), getString(args, "tag_name"))
1891
2054
  },
@@ -1893,11 +2056,11 @@ function getGitLabToolDefinitions() {
1893
2056
  name: "gitlab_create_release_evidence",
1894
2057
  title: "Create Release Evidence",
1895
2058
  description: "Create evidence for an existing release.",
1896
- mutating: true,
2059
+ capabilities: writeCapabilities,
1897
2060
  requiresFeature: "release",
1898
2061
  inputSchema: {
1899
- project_id: z.string().optional(),
1900
- tag_name: z.string().min(1)
2062
+ project_id: optionalProjectIdSchema,
2063
+ tag_name: refLikeSchema
1901
2064
  },
1902
2065
  handler: async (args, context) => context.gitlab.createReleaseEvidence(resolveProjectId(args, context, true), getString(args, "tag_name"))
1903
2066
  },
@@ -1905,11 +2068,11 @@ function getGitLabToolDefinitions() {
1905
2068
  name: "gitlab_download_release_asset",
1906
2069
  title: "Download Release Asset",
1907
2070
  description: "Download a release asset using its direct asset path.",
1908
- mutating: false,
2071
+ capabilities: readCapabilities,
1909
2072
  requiresFeature: "release",
1910
2073
  inputSchema: {
1911
- project_id: z.string().optional(),
1912
- tag_name: z.string().min(1),
2074
+ project_id: optionalProjectIdSchema,
2075
+ tag_name: refLikeSchema,
1913
2076
  direct_asset_path: z.string().min(1)
1914
2077
  },
1915
2078
  handler: async (args, context) => context.gitlab.downloadReleaseAsset(resolveProjectId(args, context, true), getString(args, "tag_name"), getString(args, "direct_asset_path"))
@@ -1918,9 +2081,9 @@ function getGitLabToolDefinitions() {
1918
2081
  name: "gitlab_list_labels",
1919
2082
  title: "List Labels",
1920
2083
  description: "List project labels.",
1921
- mutating: false,
2084
+ capabilities: readCapabilities,
1922
2085
  inputSchema: {
1923
- project_id: z.string().optional(),
2086
+ project_id: optionalProjectIdSchema,
1924
2087
  with_counts: optionalBoolean,
1925
2088
  include_ancestor_groups: optionalBoolean,
1926
2089
  search: optionalString,
@@ -1934,10 +2097,10 @@ function getGitLabToolDefinitions() {
1934
2097
  name: "gitlab_get_label",
1935
2098
  title: "Get Label",
1936
2099
  description: "Get one label by ID.",
1937
- mutating: false,
2100
+ capabilities: readCapabilities,
1938
2101
  inputSchema: {
1939
- project_id: z.string().optional(),
1940
- label_id: z.string().min(1),
2102
+ project_id: optionalProjectIdSchema,
2103
+ label_id: displayNameSchema,
1941
2104
  include_ancestor_groups: optionalBoolean
1942
2105
  },
1943
2106
  handler: async (args, context) => context.gitlab.getLabel(resolveProjectId(args, context, true), getString(args, "label_id"), {
@@ -1948,10 +2111,10 @@ function getGitLabToolDefinitions() {
1948
2111
  name: "gitlab_create_label",
1949
2112
  title: "Create Label",
1950
2113
  description: "Create a label.",
1951
- mutating: true,
2114
+ capabilities: writeCapabilities,
1952
2115
  inputSchema: {
1953
- project_id: z.string().optional(),
1954
- name: z.string().min(1),
2116
+ project_id: optionalProjectIdSchema,
2117
+ name: displayNameSchema,
1955
2118
  color: z.string().min(1),
1956
2119
  description: optionalString,
1957
2120
  priority: optionalNumber
@@ -1962,12 +2125,12 @@ function getGitLabToolDefinitions() {
1962
2125
  name: "gitlab_update_label",
1963
2126
  title: "Update Label",
1964
2127
  description: "Update a label.",
1965
- mutating: true,
2128
+ capabilities: writeCapabilities,
1966
2129
  inputSchema: {
1967
- project_id: z.string().optional(),
1968
- name: optionalString,
1969
- label_id: optionalString,
1970
- new_name: optionalString,
2130
+ project_id: optionalProjectIdSchema,
2131
+ name: optionalDisplayNameSchema,
2132
+ label_id: optionalDisplayNameSchema,
2133
+ new_name: optionalDisplayNameSchema,
1971
2134
  color: optionalString,
1972
2135
  description: optionalString,
1973
2136
  priority: optionalNumber
@@ -1986,12 +2149,12 @@ function getGitLabToolDefinitions() {
1986
2149
  {
1987
2150
  name: "gitlab_delete_label",
1988
2151
  title: "Delete Label",
1989
- description: "Delete a label by name.",
1990
- mutating: true,
2152
+ description: "Delete a label permanently. Irreversible. Requires name or label_id. Recommended pre-check: gitlab_get_label or gitlab_list_labels.",
2153
+ capabilities: deleteCapabilities,
1991
2154
  inputSchema: {
1992
- project_id: z.string().optional(),
1993
- name: optionalString,
1994
- label_id: optionalString
2155
+ project_id: optionalProjectIdSchema,
2156
+ name: optionalDisplayNameSchema,
2157
+ label_id: optionalDisplayNameSchema
1995
2158
  },
1996
2159
  handler: async (args, context) => {
1997
2160
  const labelName = getOptionalString(args, "name") ?? getOptionalString(args, "label_id");
@@ -2005,7 +2168,7 @@ function getGitLabToolDefinitions() {
2005
2168
  name: "gitlab_list_namespaces",
2006
2169
  title: "List Namespaces",
2007
2170
  description: "List namespaces visible to user.",
2008
- mutating: false,
2171
+ capabilities: readCapabilities,
2009
2172
  inputSchema: {
2010
2173
  search: optionalString,
2011
2174
  owned: optionalBoolean,
@@ -2017,10 +2180,10 @@ function getGitLabToolDefinitions() {
2017
2180
  name: "gitlab_get_namespace",
2018
2181
  title: "Get Namespace",
2019
2182
  description: "Get namespace by ID or path.",
2020
- mutating: false,
2183
+ capabilities: readCapabilities,
2021
2184
  inputSchema: {
2022
- namespace_id_or_path: optionalString,
2023
- namespace_id: optionalString
2185
+ namespace_id_or_path: optionalProjectIdSchema,
2186
+ namespace_id: optionalProjectIdSchema
2024
2187
  },
2025
2188
  handler: async (args, context) => {
2026
2189
  const namespaceId = getOptionalString(args, "namespace_id_or_path") ??
@@ -2035,7 +2198,7 @@ function getGitLabToolDefinitions() {
2035
2198
  name: "gitlab_verify_namespace",
2036
2199
  title: "Verify Namespace",
2037
2200
  description: "Verify if namespace path exists.",
2038
- mutating: false,
2201
+ capabilities: readCapabilities,
2039
2202
  inputSchema: {
2040
2203
  path: z.string().min(1)
2041
2204
  },
@@ -2045,7 +2208,7 @@ function getGitLabToolDefinitions() {
2045
2208
  name: "gitlab_get_users",
2046
2209
  title: "Get Users",
2047
2210
  description: "Search users.",
2048
- mutating: false,
2211
+ capabilities: readCapabilities,
2049
2212
  inputSchema: {
2050
2213
  username: optionalString,
2051
2214
  search: optionalString,
@@ -2060,7 +2223,7 @@ function getGitLabToolDefinitions() {
2060
2223
  name: "gitlab_list_events",
2061
2224
  title: "List Events",
2062
2225
  description: "List current user events.",
2063
- mutating: false,
2226
+ capabilities: readCapabilities,
2064
2227
  inputSchema: {
2065
2228
  action: optionalString,
2066
2229
  target_type: optionalString,
@@ -2076,9 +2239,9 @@ function getGitLabToolDefinitions() {
2076
2239
  name: "gitlab_get_project_events",
2077
2240
  title: "Get Project Events",
2078
2241
  description: "List events for a specific project.",
2079
- mutating: false,
2242
+ capabilities: readCapabilities,
2080
2243
  inputSchema: {
2081
- project_id: z.string().optional(),
2244
+ project_id: optionalProjectIdSchema,
2082
2245
  action: optionalString,
2083
2246
  target_type: optionalString,
2084
2247
  before: optionalString,
@@ -2094,9 +2257,9 @@ function getGitLabToolDefinitions() {
2094
2257
  name: "gitlab_upload_markdown",
2095
2258
  title: "Upload Markdown",
2096
2259
  description: "Upload markdown file/attachment to project.",
2097
- mutating: true,
2260
+ capabilities: writeCapabilities,
2098
2261
  inputSchema: {
2099
- project_id: z.string().optional(),
2262
+ project_id: optionalProjectIdSchema,
2100
2263
  content: optionalString,
2101
2264
  filename: z.string().default("upload.md"),
2102
2265
  file_path: optionalString
@@ -2118,10 +2281,10 @@ function getGitLabToolDefinitions() {
2118
2281
  name: "gitlab_download_attachment",
2119
2282
  title: "Download Attachment",
2120
2283
  description: "Download attachment by URL/path and return base64.",
2121
- mutating: false,
2284
+ capabilities: readCapabilities,
2122
2285
  inputSchema: {
2123
- project_id: z.string().optional(),
2124
- url_or_path: optionalString,
2286
+ project_id: optionalProjectIdSchema,
2287
+ url_or_path: optionalUrlOrPathSchema,
2125
2288
  secret: optionalString,
2126
2289
  filename: optionalString
2127
2290
  },
@@ -2160,7 +2323,7 @@ function getGitLabToolDefinitions() {
2160
2323
  name: "gitlab_execute_graphql_query",
2161
2324
  title: "Execute GraphQL Query",
2162
2325
  description: "Execute read-only GraphQL query.",
2163
- mutating: false,
2326
+ capabilities: readGraphqlCapabilities,
2164
2327
  inputSchema: {
2165
2328
  query: z.string().min(1),
2166
2329
  variables: optionalRecord
@@ -2177,7 +2340,7 @@ function getGitLabToolDefinitions() {
2177
2340
  name: "gitlab_execute_graphql_mutation",
2178
2341
  title: "Execute GraphQL Mutation",
2179
2342
  description: "Execute GraphQL mutation (disabled in read-only mode).",
2180
- mutating: true,
2343
+ capabilities: writeGraphqlCapabilities,
2181
2344
  inputSchema: {
2182
2345
  query: z.string().min(1),
2183
2346
  variables: optionalRecord
@@ -2194,7 +2357,7 @@ function getGitLabToolDefinitions() {
2194
2357
  name: "gitlab_execute_graphql",
2195
2358
  title: "Execute GraphQL (Compat)",
2196
2359
  description: "Backward-compatible GraphQL executor. Mutation payloads still honor read-only policy.",
2197
- mutating: false,
2360
+ capabilities: readGraphqlCapabilities,
2198
2361
  inputSchema: {
2199
2362
  query: z.string().min(1),
2200
2363
  variables: optionalRecord
@@ -2204,7 +2367,7 @@ function getGitLabToolDefinitions() {
2204
2367
  if (containsGraphqlMutation(query)) {
2205
2368
  context.policy.assertCanExecute({
2206
2369
  name: "gitlab_execute_graphql",
2207
- mutating: true
2370
+ capabilities: writeGraphqlCapabilities
2208
2371
  });
2209
2372
  }
2210
2373
  return context.gitlab.executeGraphql(query, getOptionalRecord(args, "variables"));
@@ -2516,8 +2679,11 @@ function getOptionalNumber(args, key) {
2516
2679
  }
2517
2680
  return value;
2518
2681
  }
2519
- function getArray(args, key) {
2682
+ function getOptionalArray(args, key) {
2520
2683
  const value = args[key];
2684
+ if (value === undefined) {
2685
+ return undefined;
2686
+ }
2521
2687
  if (!Array.isArray(value)) {
2522
2688
  throw new Error(`'${key}' must be array`);
2523
2689
  }
@@ -2543,6 +2709,37 @@ function getOptionalRecord(args, key) {
2543
2709
  }
2544
2710
  return value;
2545
2711
  }
2712
+ function getOptionalPipelineInputsRecord(args, key) {
2713
+ const value = getOptionalRecord(args, key);
2714
+ if (!value) {
2715
+ return undefined;
2716
+ }
2717
+ for (const [entryKey, entryValue] of Object.entries(value)) {
2718
+ if (!pipelineInputValueSchema.safeParse(entryValue).success) {
2719
+ throw new Error(`'${key}.${entryKey}' must be a string, number, boolean, or array of primitive values`);
2720
+ }
2721
+ }
2722
+ return value;
2723
+ }
2724
+ function cleanMergeRequestListArgs(args) {
2725
+ const cleanedArgs = { ...args };
2726
+ if (hasValue(cleanedArgs.author_username)) {
2727
+ delete cleanedArgs.author_id;
2728
+ }
2729
+ if (hasValue(cleanedArgs.assignee_username)) {
2730
+ delete cleanedArgs.assignee_id;
2731
+ }
2732
+ if (hasValue(cleanedArgs.reviewer_username)) {
2733
+ delete cleanedArgs.reviewer_id;
2734
+ }
2735
+ return cleanedArgs;
2736
+ }
2737
+ function hasValue(value) {
2738
+ if (typeof value === "string") {
2739
+ return value.length > 0;
2740
+ }
2741
+ return value !== undefined && value !== null;
2742
+ }
2546
2743
  function requireArrayValue(items, index, errorMessage) {
2547
2744
  const value = items[index];
2548
2745
  if (value === undefined) {