@zereight/mcp-gitlab 2.1.25 → 2.1.26

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.
package/build/schemas.js CHANGED
@@ -2,18 +2,20 @@ import { z } from "zod";
2
2
  import { omitIncompleteMergeRequestPosition } from "./utils/merge-request-position.js";
3
3
  // Helper: coerce a JSON-stringified array to an actual array.
4
4
  // LLMs sometimes send '["a", "b"]' (string) instead of ["a", "b"] (array).
5
- const coerceStringArray = z.preprocess((val) => {
5
+ const coerceStringArray = z.preprocess(val => {
6
6
  if (typeof val === "string") {
7
7
  try {
8
8
  const parsed = JSON.parse(val);
9
9
  if (Array.isArray(parsed))
10
10
  return parsed;
11
11
  }
12
- catch { /* not JSON, fall through */ }
12
+ catch {
13
+ /* not JSON, fall through */
14
+ }
13
15
  }
14
16
  return val;
15
17
  }, z.array(z.string()));
16
- const coerceBooleanString = z.preprocess((val) => {
18
+ const coerceBooleanString = z.preprocess(val => {
17
19
  if (typeof val === "string") {
18
20
  const normalized = val.trim().toLowerCase();
19
21
  if (normalized === "true")
@@ -199,7 +201,10 @@ export const GitLabPipelineTriggerJobSchema = z.object({
199
201
  // See https://docs.gitlab.com/api/rest/#pagination
200
202
  export const PaginationOptionsSchema = z.object({
201
203
  page: z.coerce.number().optional().describe("Page number for pagination (default: 1)"),
202
- per_page: z.coerce.number().optional().describe("Number of items per page (max: 100, default: 20)"),
204
+ per_page: z.coerce
205
+ .number()
206
+ .optional()
207
+ .describe("Number of items per page (max: 100, default: 20)"),
203
208
  });
204
209
  // Schema for listing pipelines
205
210
  export const ListPipelinesSchema = z
@@ -227,7 +232,10 @@ export const ListPipelinesSchema = z
227
232
  .describe("The status of pipelines"),
228
233
  ref: z.string().optional().describe("The ref of pipelines"),
229
234
  sha: z.string().optional().describe("The SHA of pipelines"),
230
- yaml_errors: z.coerce.boolean().optional().describe("Returns pipelines with invalid configurations"),
235
+ yaml_errors: z.coerce
236
+ .boolean()
237
+ .optional()
238
+ .describe("Returns pipelines with invalid configurations"),
231
239
  username: z.string().optional().describe("The username of the user who triggered pipelines"),
232
240
  updated_after: z
233
241
  .string()
@@ -319,12 +327,56 @@ export const ValidateProjectCiLintSchema = z.object({
319
327
  .optional()
320
328
  .describe("Commit SHA, branch, or tag to read the existing CI config from"),
321
329
  dry_run: z.coerce.boolean().optional().describe("Run pipeline creation simulation"),
322
- dry_run_ref: z
323
- .string()
324
- .optional()
325
- .describe("Branch or tag context for dry_run validation"),
330
+ dry_run_ref: z.string().optional().describe("Branch or tag context for dry_run validation"),
326
331
  include_jobs: z.coerce.boolean().optional().describe("Include jobs in the lint response"),
327
332
  });
333
+ const CiCatalogResourceSortSchema = z.enum([
334
+ "CREATED_ASC",
335
+ "CREATED_DESC",
336
+ "LATEST_RELEASED_AT_ASC",
337
+ "LATEST_RELEASED_AT_DESC",
338
+ "NAME_ASC",
339
+ "NAME_DESC",
340
+ "STAR_COUNT_ASC",
341
+ "STAR_COUNT_DESC",
342
+ "USAGE_COUNT_ASC",
343
+ "USAGE_COUNT_DESC",
344
+ ]);
345
+ const CiCatalogResourceVerificationLevelSchema = z.enum([
346
+ "GITLAB_MAINTAINED",
347
+ "GITLAB_PARTNER_MAINTAINED",
348
+ "UNVERIFIED",
349
+ "VERIFIED_CREATOR_MAINTAINED",
350
+ "VERIFIED_CREATOR_SELF_MANAGED",
351
+ ]);
352
+ export const ListCiCatalogResourcesSchema = z.object({
353
+ search: z.string().optional().describe("Search catalog resources by name or description"),
354
+ first: z.coerce.number().int().min(1).max(100).optional().describe("Number of resources to return (default: 20, max: 100)"),
355
+ after: z.string().optional().describe("GraphQL cursor for the next page"),
356
+ group_ids: z.array(z.string()).optional().describe("Filter to catalog resources in these group IDs"),
357
+ scope: z.enum(["ALL", "NAMESPACES"]).optional().describe("Catalog resource scope"),
358
+ sort: CiCatalogResourceSortSchema.optional().describe("Sort order"),
359
+ topics: z.array(z.string()).optional().describe("Filter by project topic names"),
360
+ verification_level: CiCatalogResourceVerificationLevelSchema.optional().describe("Filter by verification level"),
361
+ });
362
+ const GetCiCatalogResourceOptionsSchema = z.object({
363
+ version_limit: z.coerce.number().int().min(1).max(20).optional().describe("Number of versions to include (default: 5, max: 20)"),
364
+ component_limit: z.coerce.number().int().min(1).max(50).optional().describe("Number of components per version to include (default: 20, max: 50)"),
365
+ component_name: z.string().optional().describe("Filter returned components by component name"),
366
+ include_readme: z.coerce.boolean().optional().describe("Include version README content"),
367
+ });
368
+ export const GetCiCatalogResourceSchema = z.union([
369
+ GetCiCatalogResourceOptionsSchema.extend({
370
+ id: z.string().min(1).describe("CI/CD Catalog resource global ID. Required when full_path is omitted."),
371
+ full_path: z.string().min(1).optional().describe("CI/CD Catalog resource full project path. Required when id is omitted."),
372
+ }),
373
+ GetCiCatalogResourceOptionsSchema.extend({
374
+ id: z.string().min(1).optional().describe("CI/CD Catalog resource global ID. Required when full_path is omitted."),
375
+ full_path: z.string().min(1).describe("CI/CD Catalog resource full project path. Required when id is omitted."),
376
+ }),
377
+ ]).refine(args => Boolean(args.id) !== Boolean(args.full_path), {
378
+ message: "Provide exactly one of id or full_path",
379
+ });
328
380
  // Deployment related schemas
329
381
  export const GitLabDeploymentSchema = z.object({
330
382
  id: z.coerce.string(),
@@ -435,10 +487,7 @@ export const ListEnvironmentsSchema = z
435
487
  project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
436
488
  name: z.string().optional().describe("Return environments with this exact name"),
437
489
  search: z.string().optional().describe("Search environments by name"),
438
- states: z
439
- .enum(["available", "stopped"])
440
- .optional()
441
- .describe("Filter environments by state"),
490
+ states: z.enum(["available", "stopped"]).optional().describe("Filter environments by state"),
442
491
  })
443
492
  .merge(PaginationOptionsSchema);
444
493
  export const GetEnvironmentSchema = z.object({
@@ -532,7 +581,8 @@ export const GitLabUsersResponseSchema = z.record(z.string(), z
532
581
  export const GetUserSchema = z.object({
533
582
  user_id: z.coerce.string().describe("The ID of the user"),
534
583
  });
535
- export const GitLabUserFullSchema = z.object({
584
+ export const GitLabUserFullSchema = z
585
+ .object({
536
586
  id: z.coerce.string(),
537
587
  username: z.string(),
538
588
  name: z.string(),
@@ -561,19 +611,23 @@ export const GitLabUserFullSchema = z.object({
561
611
  color_scheme_id: z.number().nullable().optional(),
562
612
  projects_limit: z.number().nullable().optional(),
563
613
  current_sign_in_at: z.string().nullable().optional(),
564
- identities: z.array(z.object({
614
+ identities: z
615
+ .array(z.object({
565
616
  provider: z.string(),
566
617
  extern_uid: z.string(),
567
- })).optional(),
618
+ }))
619
+ .optional(),
568
620
  can_create_group: z.boolean().nullable().optional(),
569
621
  can_create_project: z.boolean().nullable().optional(),
570
622
  two_factor_enabled: z.boolean().nullable().optional(),
571
623
  external: z.boolean().nullable().optional(),
572
624
  private_profile: z.boolean().nullable().optional(),
573
625
  is_admin: z.boolean().nullable().optional(),
574
- }).passthrough();
626
+ })
627
+ .passthrough();
575
628
  export const WhoAmISchema = z.object({});
576
- export const GitLabCurrentUserSchema = z.object({
629
+ export const GitLabCurrentUserSchema = z
630
+ .object({
577
631
  id: z.coerce.string(),
578
632
  username: z.string(),
579
633
  name: z.string(),
@@ -594,17 +648,23 @@ export const GitLabCurrentUserSchema = z.object({
594
648
  is_admin: z.boolean().optional(),
595
649
  can_create_group: z.boolean().optional(),
596
650
  can_create_project: z.boolean().optional(),
597
- identities: z.array(z.object({
651
+ identities: z
652
+ .array(z.object({
598
653
  provider: z.string(),
599
654
  extern_uid: z.string(),
600
- })).optional(),
601
- }).passthrough();
655
+ }))
656
+ .optional(),
657
+ })
658
+ .passthrough();
602
659
  // Group related schemas
603
660
  export const CreateGroupSchema = z.object({
604
661
  name: z.string().describe("The name of the group"),
605
662
  path: z.string().describe("The path of the group"),
606
663
  description: z.string().optional().describe("The group's description"),
607
- visibility: z.enum(["private", "internal", "public"]).optional().describe("The group's visibility level"),
664
+ visibility: z
665
+ .enum(["private", "internal", "public"])
666
+ .optional()
667
+ .describe("The group's visibility level"),
608
668
  parent_id: z.coerce.number().optional().describe("The parent group ID for creating a subgroup"),
609
669
  });
610
670
  export const GitLabGroupSchema = z.object({
@@ -882,6 +942,7 @@ export const GitLabMilestonesSchema = z.object({
882
942
  // Input schemas for operations
883
943
  export const CreateRepositoryOptionsSchema = z.object({
884
944
  name: z.string(),
945
+ namespace_id: z.preprocess(val => (val === "" ? undefined : val), z.coerce.number().int().min(1).optional()),
885
946
  description: z.string().optional(),
886
947
  visibility: z.enum(["private", "internal", "public"]).optional(), // Changed from private to match GitLab API
887
948
  initialize_with_readme: z.coerce.boolean().optional(), // Changed from auto_init to match GitLab API
@@ -936,10 +997,7 @@ const SearchBlobsBaseSchema = z.object({
936
997
  search: z
937
998
  .string()
938
999
  .describe('Code search query string. On instances with exact code search (Zoekt), the query supports rich inline syntax: "class foo" (exact match), foo file:\\.js$ (file pattern), foo lang:ruby (language), sym:foo (symbol search), foo -bar (negation), case:yes (case-sensitive). When using Zoekt inline filters, prefer them over the separate filename/path/extension params which are for basic search.'),
939
- filename: z
940
- .string()
941
- .optional()
942
- .describe("Filter by filename (supports * wildcard, e.g. '*.ts')"),
1000
+ filename: z.string().optional().describe("Filter by filename (supports * wildcard, e.g. '*.ts')"),
943
1001
  path: z
944
1002
  .string()
945
1003
  .optional()
@@ -1093,6 +1151,12 @@ export const GitLabMergeRequestSchema = z.object({
1093
1151
  merged_at: z.string().nullable(),
1094
1152
  closed_at: z.string().nullable(),
1095
1153
  merge_commit_sha: z.string().nullable(),
1154
+ merge_user: GitLabUserSchema.nullable()
1155
+ .optional()
1156
+ .describe("User who performed the merge (GitLab API v4 field)"),
1157
+ merged_by: GitLabUserSchema.nullable()
1158
+ .optional()
1159
+ .describe("Deprecated alias for merge_user, kept for backwards compatibility"),
1096
1160
  detailed_merge_status: z.string().optional(),
1097
1161
  merge_status: z.string().optional(),
1098
1162
  merge_error: z.string().nullable().optional(),
@@ -1108,8 +1172,8 @@ export const GitLabMergeRequestSchema = z.object({
1108
1172
  .nullable()
1109
1173
  .optional()
1110
1174
  .describe("Number of commits the source branch is behind the target branch"),
1111
- rebase_in_progress: z
1112
- .coerce.boolean()
1175
+ rebase_in_progress: z.coerce
1176
+ .boolean()
1113
1177
  .optional()
1114
1178
  .describe("Whether rebase is currently in progress for this merge request"),
1115
1179
  merge_when_pipeline_succeeds: z.coerce.boolean().optional(),
@@ -1324,7 +1388,10 @@ export const UpdateIssueNoteSchema = ProjectParamsSchema.extend({
1324
1388
  // Input schema for adding a note to an issue (top-level comment or discussion reply)
1325
1389
  export const CreateIssueNoteSchema = ProjectParamsSchema.extend({
1326
1390
  issue_iid: z.coerce.string().describe("The IID of an issue"),
1327
- discussion_id: z.coerce.string().optional().describe("The ID of a thread. If provided, replies to that thread; otherwise creates a top-level note"),
1391
+ discussion_id: z.coerce
1392
+ .string()
1393
+ .optional()
1394
+ .describe("The ID of a thread. If provided, replies to that thread; otherwise creates a top-level note"),
1328
1395
  body: z.string().describe("The content of the note or reply"),
1329
1396
  created_at: z.string().optional().describe("Date the note was created at (ISO 8601 format)"),
1330
1397
  });
@@ -1344,7 +1411,7 @@ export const SearchRepositoriesSchema = z
1344
1411
  query: z.string().optional().describe("Search query (alias for 'search')"),
1345
1412
  })
1346
1413
  .merge(PaginationOptionsSchema)
1347
- .transform((data) => {
1414
+ .transform(data => {
1348
1415
  const search = data.search || data.query;
1349
1416
  if (!search) {
1350
1417
  throw new Error("Either 'search' or 'query' must be provided");
@@ -1353,6 +1420,9 @@ export const SearchRepositoriesSchema = z
1353
1420
  });
1354
1421
  export const CreateRepositorySchema = z.object({
1355
1422
  name: z.string().describe("Repository name"),
1423
+ namespace_id: z
1424
+ .preprocess(val => (val === "" ? undefined : val), z.coerce.number().int().min(1).optional())
1425
+ .describe("Group namespace ID to create the project in. Omit to use the current user's namespace."),
1356
1426
  description: z.string().optional().describe("Repository description"),
1357
1427
  visibility: z
1358
1428
  .enum(["private", "internal", "public"])
@@ -1383,7 +1453,7 @@ export const GetFileContentsSchema = z
1383
1453
  path: ["file_path"],
1384
1454
  });
1385
1455
  }
1386
- const finalPath = fp && fp.length > 0 ? fp : p ?? "";
1456
+ const finalPath = fp && fp.length > 0 ? fp : (p ?? "");
1387
1457
  if (finalPath.trim().length === 0) {
1388
1458
  ctx.addIssue({
1389
1459
  code: z.ZodIssueCode.custom,
@@ -1418,7 +1488,10 @@ export const CreateIssueSchema = ProjectParamsSchema.extend({
1418
1488
  .optional()
1419
1489
  .default("issue")
1420
1490
  .describe("The type of issue. One of issue, incident, test_case or task."),
1421
- weight: z.coerce.number().optional().describe("Weight of the issue (numeric, typically hours of work)"),
1491
+ weight: z.coerce
1492
+ .number()
1493
+ .optional()
1494
+ .describe("Weight of the issue (numeric, typically hours of work)"),
1422
1495
  });
1423
1496
  export const GitLabTodoSchema = z.object({
1424
1497
  id: z.coerce.number(),
@@ -1480,21 +1553,27 @@ const MergeRequestOptionsSchema = {
1480
1553
  source_branch: z.string().describe("Branch containing changes"),
1481
1554
  target_branch: z.string().describe("Branch to merge into"),
1482
1555
  target_project_id: z.coerce.string().optional().describe("Numeric ID of the target project."),
1483
- assignee_ids: z.array(z.coerce.number()).optional().describe("The ID of the users to assign the MR to"),
1556
+ assignee_ids: z
1557
+ .array(z.coerce.number())
1558
+ .optional()
1559
+ .describe("The ID of the users to assign the MR to"),
1484
1560
  reviewer_ids: z
1485
1561
  .array(z.coerce.number())
1486
1562
  .optional()
1487
1563
  .describe("The ID of the users to assign as reviewers of the MR"),
1488
1564
  labels: coerceStringArray.optional().describe("Labels for the MR"),
1489
1565
  draft: z.coerce.boolean().optional().describe("Create as draft merge request"),
1490
- allow_collaboration: z.coerce.boolean().optional().describe("Allow commits from upstream members"),
1491
- remove_source_branch: z
1492
- .coerce.boolean()
1566
+ allow_collaboration: z.coerce
1567
+ .boolean()
1568
+ .optional()
1569
+ .describe("Allow commits from upstream members"),
1570
+ remove_source_branch: z.coerce
1571
+ .boolean()
1493
1572
  .nullable()
1494
1573
  .optional()
1495
1574
  .describe("Flag indicating if a merge request should remove the source branch when merging."),
1496
- squash: z
1497
- .coerce.boolean()
1575
+ squash: z.coerce
1576
+ .boolean()
1498
1577
  .nullable()
1499
1578
  .optional()
1500
1579
  .describe("If true, squash all commits into a single commit on merge."),
@@ -1526,7 +1605,7 @@ export const GetProtectedBranchSchema = ProjectParamsSchema.extend({
1526
1605
  branch_name: z.string().describe("Name of the protected branch"),
1527
1606
  });
1528
1607
  // String-aware boolean preprocessing: correctly handles "false" → false
1529
- const stringBoolean = z.preprocess((val) => {
1608
+ const stringBoolean = z.preprocess(val => {
1530
1609
  if (typeof val === "string") {
1531
1610
  const lower = val.toLowerCase();
1532
1611
  if (lower === "false" || lower === "0")
@@ -1539,10 +1618,10 @@ const stringBoolean = z.preprocess((val) => {
1539
1618
  const protectedBranchAccessLevel = z.coerce
1540
1619
  .number()
1541
1620
  .int()
1542
- .refine((level) => [0, 30, 40, 60].includes(level), {
1621
+ .refine(level => [0, 30, 40, 60].includes(level), {
1543
1622
  message: "Access level must be one of 0 (No access), 30 (Developer), 40 (Maintainer), or 60 (Admin)",
1544
1623
  });
1545
- export const ProtectBranchSchema = z.preprocess((input) => {
1624
+ export const ProtectBranchSchema = z.preprocess(input => {
1546
1625
  if (typeof input !== "object" || input === null) {
1547
1626
  return input;
1548
1627
  }
@@ -1616,8 +1695,8 @@ export const GitLabBranchSchema = z.object({
1616
1695
  export const GetBranchDiffsSchema = ProjectParamsSchema.extend({
1617
1696
  from: z.string().describe("The base branch or commit SHA to compare from"),
1618
1697
  to: z.string().describe("The target branch or commit SHA to compare to"),
1619
- straight: z
1620
- .coerce.boolean()
1698
+ straight: z.coerce
1699
+ .boolean()
1621
1700
  .optional()
1622
1701
  .describe("Comparison method: false for '...' (default), true for '--'"),
1623
1702
  excluded_file_patterns: z
@@ -1642,7 +1721,10 @@ export const UpdateMergeRequestSchema = GetMergeRequestSchema.extend({
1642
1721
  title: z.string().optional().describe("The title of the merge request"),
1643
1722
  description: z.string().optional().describe("The description of the merge request"),
1644
1723
  target_branch: z.string().optional().describe("The target branch"),
1645
- assignee_ids: z.array(z.coerce.number()).optional().describe("The ID of the users to assign the MR to"),
1724
+ assignee_ids: z
1725
+ .array(z.coerce.number())
1726
+ .optional()
1727
+ .describe("The ID of the users to assign the MR to"),
1646
1728
  reviewer_ids: z
1647
1729
  .array(z.coerce.number())
1648
1730
  .optional()
@@ -1652,11 +1734,14 @@ export const UpdateMergeRequestSchema = GetMergeRequestSchema.extend({
1652
1734
  .enum(["close", "reopen"])
1653
1735
  .optional()
1654
1736
  .describe("New state (close/reopen) for the MR"),
1655
- remove_source_branch: z
1656
- .coerce.boolean()
1737
+ remove_source_branch: z.coerce
1738
+ .boolean()
1657
1739
  .optional()
1658
1740
  .describe("Flag indicating if the source branch should be removed"),
1659
- squash: z.coerce.boolean().optional().describe("Squash commits into a single commit when merging"),
1741
+ squash: z.coerce
1742
+ .boolean()
1743
+ .optional()
1744
+ .describe("Squash commits into a single commit when merging"),
1660
1745
  draft: z.coerce.boolean().optional().describe("Work in progress merge request"),
1661
1746
  });
1662
1747
  export const MergeMergeRequestSchema = ProjectParamsSchema.extend({
@@ -1783,9 +1868,12 @@ export const GetMergeRequestDiffsSchema = GetMergeRequestSchema.extend({
1783
1868
  });
1784
1869
  export const ListMergeRequestDiffsSchema = GetMergeRequestSchema.extend({
1785
1870
  page: z.coerce.number().optional().describe("Page number for pagination (default: 1)"),
1786
- per_page: z.coerce.number().optional().describe("Number of items per page (max: 100, default: 20)"),
1787
- unidiff: z
1788
- .coerce.boolean()
1871
+ per_page: z.coerce
1872
+ .number()
1873
+ .optional()
1874
+ .describe("Number of items per page (max: 100, default: 20)"),
1875
+ unidiff: z.coerce
1876
+ .boolean()
1789
1877
  .optional()
1790
1878
  .describe("Present diffs in the unified diff format. Default is false. Introduced in GitLab 16.5."),
1791
1879
  });
@@ -1800,8 +1888,8 @@ export const GetMergeRequestFileDiffSchema = GetMergeRequestSchema.extend({
1800
1888
  .array(z.string())
1801
1889
  .describe("List of file paths to retrieve diffs for (e.g. ['src/api/users.ts', 'src/repo/user.go']). " +
1802
1890
  "Call list_merge_request_changed_files first to get the full list of changed paths."),
1803
- unidiff: z
1804
- .coerce.boolean()
1891
+ unidiff: z.coerce
1892
+ .boolean()
1805
1893
  .optional()
1806
1894
  .describe("Present diff in the unified diff format. Default is false."),
1807
1895
  });
@@ -1811,8 +1899,8 @@ export const ListMergeRequestVersionsSchema = ProjectParamsSchema.extend({
1811
1899
  });
1812
1900
  export const GetMergeRequestVersionSchema = ListMergeRequestVersionsSchema.extend({
1813
1901
  version_id: z.coerce.string().describe("The ID of the merge request diff version"),
1814
- unidiff: z
1815
- .coerce.boolean()
1902
+ unidiff: z.coerce
1903
+ .boolean()
1816
1904
  .optional()
1817
1905
  .describe("Present diffs in the unified diff format. Default is false. Introduced in GitLab 16.5."),
1818
1906
  });
@@ -1872,7 +1960,10 @@ export const ListIssuesSchema = z
1872
1960
  .describe("Return issues with a specific state"),
1873
1961
  updated_after: z.string().optional().describe("Return issues updated after the given time"),
1874
1962
  updated_before: z.string().optional().describe("Return issues updated before the given time"),
1875
- with_labels_details: z.coerce.boolean().optional().describe("Return more details for each label"),
1963
+ with_labels_details: z.coerce
1964
+ .boolean()
1965
+ .optional()
1966
+ .describe("Return more details for each label"),
1876
1967
  })
1877
1968
  .merge(PaginationOptionsSchema);
1878
1969
  // Merge Requests API operation schemas
@@ -1963,7 +2054,10 @@ export const ListMergeRequestsSchema = z
1963
2054
  .enum(["yes", "no"])
1964
2055
  .optional()
1965
2056
  .describe("Filter merge requests against their wip status"),
1966
- with_labels_details: z.coerce.boolean().optional().describe("Return more details for each label"),
2057
+ with_labels_details: z.coerce
2058
+ .boolean()
2059
+ .optional()
2060
+ .describe("Return more details for each label"),
1967
2061
  })
1968
2062
  .merge(PaginationOptionsSchema);
1969
2063
  export const GetIssueSchema = z.object({
@@ -1975,15 +2069,23 @@ export const UpdateIssueSchema = z.object({
1975
2069
  issue_iid: z.coerce.string().describe("The internal ID of the project issue"),
1976
2070
  title: z.string().optional().describe("The title of the issue"),
1977
2071
  description: z.string().optional().describe("The description of the issue"),
1978
- assignee_ids: z.array(z.coerce.number()).optional().describe("Array of user IDs to assign issue to"),
2072
+ assignee_ids: z
2073
+ .array(z.coerce.number())
2074
+ .optional()
2075
+ .describe("Array of user IDs to assign issue to"),
1979
2076
  confidential: z.coerce.boolean().optional().describe("Set the issue to be confidential"),
1980
2077
  discussion_locked: z.coerce.boolean().optional().describe("Flag to lock discussions"),
1981
2078
  due_date: z.string().optional().describe("Date the issue is due (YYYY-MM-DD)"),
1982
2079
  labels: coerceStringArray.optional().describe("Array of label names"),
1983
2080
  milestone_id: z.coerce.string().optional().describe("Milestone ID to assign"),
1984
2081
  state_event: z.enum(["close", "reopen"]).optional().describe("Update issue state (close/reopen)"),
1985
- weight: z.coerce.number().optional().describe("Weight of the issue (numeric, typically hours of work)"),
1986
- issue_type: z.preprocess((val) => (typeof val === "string" ? val.toLowerCase() : val), z.enum(["issue", "incident", "test_case", "task"]).optional()).describe("The type of issue. One of issue, incident, test_case or task."),
2082
+ weight: z.coerce
2083
+ .number()
2084
+ .optional()
2085
+ .describe("Weight of the issue (numeric, typically hours of work)"),
2086
+ issue_type: z
2087
+ .preprocess(val => (typeof val === "string" ? val.toLowerCase() : val), z.enum(["issue", "incident", "test_case", "task"]).optional())
2088
+ .describe("The type of issue. One of issue, incident, test_case or task."),
1987
2089
  });
1988
2090
  export const DeleteIssueSchema = z.object({
1989
2091
  project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
@@ -1998,10 +2100,16 @@ export const UpdateIssueDescriptionPatchSchema = z.object({
1998
2100
  .min(1)
1999
2101
  .max(50000)
2000
2102
  .describe("The patch content to apply to the issue description"),
2001
- dry_run: z.coerce.boolean().optional().describe("If true, preview changes without updating the issue"),
2002
- create_note: z.coerce.boolean().optional().describe("If true, add a note summarizing the change after update"),
2003
- allow_multiple: z
2004
- .coerce.boolean()
2103
+ dry_run: z.coerce
2104
+ .boolean()
2105
+ .optional()
2106
+ .describe("If true, preview changes without updating the issue"),
2107
+ create_note: z.coerce
2108
+ .boolean()
2109
+ .optional()
2110
+ .describe("If true, add a note summarizing the change after update"),
2111
+ allow_multiple: z.coerce
2112
+ .boolean()
2005
2113
  .optional()
2006
2114
  .describe("For search_replace: allow multiple matches to all be replaced (default: false — fail on duplicate)"),
2007
2115
  });
@@ -2047,19 +2155,65 @@ export const GetNamespaceSchema = z.object({
2047
2155
  });
2048
2156
  export const VerifyNamespaceSchema = z.object({
2049
2157
  path: z.string().describe("Namespace path to verify"),
2050
- parent_id: z.preprocess(val => (val === "" ? undefined : val), z.number().int().optional()).describe("Parent namespace ID; required to correctly resolve paths in nested namespaces where the same path may exist under different parents"),
2158
+ parent_id: z
2159
+ .preprocess(val => (val === "" ? undefined : val), z.number().int().optional())
2160
+ .describe("Parent namespace ID; required to correctly resolve paths in nested namespaces where the same path may exist under different parents"),
2051
2161
  });
2052
2162
  // Project API operation schemas
2053
2163
  export const GetProjectSchema = z.object({
2054
2164
  project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
2055
2165
  });
2166
+ const ProjectFeatureAccessLevelSchema = z
2167
+ .enum(["disabled", "private", "enabled"])
2168
+ .describe("Project feature access level");
2169
+ const ProjectPagesAccessLevelSchema = z
2170
+ .enum(["disabled", "private", "enabled", "public"])
2171
+ .describe("Project pages access level. Unlike other features, pages can be set to 'public'");
2172
+ export const UpdateProjectSchema = ProjectParamsSchema.extend({
2173
+ name: z.string().optional().describe("Project display name"),
2174
+ path: z.string().optional().describe("Project path/slug"),
2175
+ description: z.string().optional().describe("Project description"),
2176
+ default_branch: z.string().optional().describe("Default branch name"),
2177
+ visibility: z.enum(["private", "internal", "public"]).optional().describe("Project visibility"),
2178
+ topics: z.array(z.string()).optional().describe("Project topics"),
2179
+ request_access_enabled: coerceBooleanString.optional().describe("Allow users to request access"),
2180
+ remove_source_branch_after_merge: coerceBooleanString
2181
+ .optional()
2182
+ .describe("Remove source branches after merge by default"),
2183
+ only_allow_merge_if_pipeline_succeeds: coerceBooleanString
2184
+ .optional()
2185
+ .describe("Require successful pipeline before merge"),
2186
+ only_allow_merge_if_all_discussions_are_resolved: coerceBooleanString
2187
+ .optional()
2188
+ .describe("Require all discussions to be resolved before merge"),
2189
+ squash_option: z
2190
+ .enum(["never", "always", "default_on", "default_off"])
2191
+ .optional()
2192
+ .describe("Squash commits setting"),
2193
+ merge_method: z.enum(["merge", "rebase_merge", "ff"]).optional().describe("Merge method"),
2194
+ issues_access_level: ProjectFeatureAccessLevelSchema.optional().describe("Issues feature visibility"),
2195
+ merge_requests_access_level: ProjectFeatureAccessLevelSchema.optional().describe("Merge requests feature visibility"),
2196
+ builds_access_level: ProjectFeatureAccessLevelSchema.optional().describe("CI/CD pipelines feature visibility"),
2197
+ wiki_access_level: ProjectFeatureAccessLevelSchema.optional().describe("Wiki feature visibility"),
2198
+ snippets_access_level: ProjectFeatureAccessLevelSchema.optional().describe("Snippets feature visibility"),
2199
+ container_registry_access_level: ProjectFeatureAccessLevelSchema.optional().describe("Container registry feature visibility"),
2200
+ environments_access_level: ProjectFeatureAccessLevelSchema.optional().describe("Environments feature visibility"),
2201
+ forking_access_level: ProjectFeatureAccessLevelSchema.optional().describe("Forking feature visibility"),
2202
+ package_registry_access_level: ProjectFeatureAccessLevelSchema.optional().describe("Package registry feature visibility"),
2203
+ pages_access_level: ProjectPagesAccessLevelSchema.optional().describe("Pages feature visibility"),
2204
+ }).refine(args => Object.keys(args).some(key => key !== "project_id" && args[key] !== undefined), {
2205
+ message: "Provide at least one project setting to update",
2206
+ });
2056
2207
  export const ListProjectsSchema = z
2057
2208
  .object({
2058
2209
  search: z.string().optional().describe("Search term for projects"),
2059
- search_namespaces: z.coerce.boolean().optional().describe("Needs to be true if search is full path"),
2210
+ search_namespaces: z.coerce
2211
+ .boolean()
2212
+ .optional()
2213
+ .describe("Needs to be true if search is full path"),
2060
2214
  owned: z.coerce.boolean().optional().describe("Filter for projects owned by current user"),
2061
- membership: z
2062
- .coerce.boolean()
2215
+ membership: z.coerce
2216
+ .boolean()
2063
2217
  .optional()
2064
2218
  .describe("Filter for projects where current user is a member"),
2065
2219
  simple: z.coerce.boolean().optional().describe("Return only limited fields"),
@@ -2076,12 +2230,12 @@ export const ListProjectsSchema = z
2076
2230
  .enum(["asc", "desc"])
2077
2231
  .optional()
2078
2232
  .describe("Return projects sorted in ascending or descending order"),
2079
- with_issues_enabled: z
2080
- .coerce.boolean()
2233
+ with_issues_enabled: z.coerce
2234
+ .boolean()
2081
2235
  .optional()
2082
2236
  .describe("Filter projects with issues feature enabled"),
2083
- with_merge_requests_enabled: z
2084
- .coerce.boolean()
2237
+ with_merge_requests_enabled: z.coerce
2238
+ .boolean()
2085
2239
  .optional()
2086
2240
  .describe("Filter projects with merge requests feature enabled"),
2087
2241
  min_access_level: z.coerce.number().optional().describe("Filter by minimum access level"),
@@ -2092,8 +2246,8 @@ export const ListProjectsSchema = z
2092
2246
  export const ListLabelsSchema = z
2093
2247
  .object({
2094
2248
  project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
2095
- with_counts: z
2096
- .coerce.boolean()
2249
+ with_counts: z.coerce
2250
+ .boolean()
2097
2251
  .optional()
2098
2252
  .describe("Whether to include issue and merge request counts"),
2099
2253
  include_ancestor_groups: z.coerce.boolean().optional().describe("Include ancestor groups"),
@@ -2145,12 +2299,12 @@ export const ListGroupProjectsSchema = z
2145
2299
  .enum(["public", "internal", "private"])
2146
2300
  .optional()
2147
2301
  .describe("Filter by project visibility"),
2148
- with_issues_enabled: z
2149
- .coerce.boolean()
2302
+ with_issues_enabled: z.coerce
2303
+ .boolean()
2150
2304
  .optional()
2151
2305
  .describe("Filter projects with issues feature enabled"),
2152
- with_merge_requests_enabled: z
2153
- .coerce.boolean()
2306
+ with_merge_requests_enabled: z.coerce
2307
+ .boolean()
2154
2308
  .optional()
2155
2309
  .describe("Filter projects with merge requests feature enabled"),
2156
2310
  min_access_level: z.coerce.number().optional().describe("Filter by minimum access level"),
@@ -2384,8 +2538,8 @@ export const CreateDraftNoteSchema = ProjectParamsSchema.extend({
2384
2538
  .optional()
2385
2539
  .describe("The ID of a discussion the draft note replies to"),
2386
2540
  position: optionalMergeRequestThreadPosition.describe("Position when creating a diff note"),
2387
- resolve_discussion: z
2388
- .coerce.boolean()
2541
+ resolve_discussion: z.coerce
2542
+ .boolean()
2389
2543
  .optional()
2390
2544
  .describe("Whether to resolve the discussion when publishing"),
2391
2545
  });
@@ -2395,8 +2549,8 @@ export const UpdateDraftNoteSchema = ProjectParamsSchema.extend({
2395
2549
  draft_note_id: z.coerce.string().describe("The ID of the draft note"),
2396
2550
  body: z.string().optional().describe("The content of the draft note"),
2397
2551
  position: optionalMergeRequestThreadPosition.describe("Position when creating a diff note"),
2398
- resolve_discussion: z
2399
- .coerce.boolean()
2552
+ resolve_discussion: z.coerce
2553
+ .boolean()
2400
2554
  .optional()
2401
2555
  .describe("Whether to resolve the discussion when publishing"),
2402
2556
  });
@@ -2429,7 +2583,10 @@ export const ResolveMergeRequestThreadSchema = ProjectParamsSchema.extend({
2429
2583
  // Milestone related schemas
2430
2584
  // Schema for listing project milestones
2431
2585
  export const ListProjectMilestonesSchema = ProjectParamsSchema.extend({
2432
- iids: z.array(z.coerce.number()).optional().describe("Return only the milestones having the given iid"),
2586
+ iids: z
2587
+ .array(z.coerce.number())
2588
+ .optional()
2589
+ .describe("Return only the milestones having the given iid"),
2433
2590
  state: z
2434
2591
  .enum(["active", "closed"])
2435
2592
  .optional()
@@ -2502,15 +2659,24 @@ export const ListCommitsSchema = z.object({
2502
2659
  path: z.string().optional().describe("The file path"),
2503
2660
  author: z.string().optional().describe("Search commits by commit author"),
2504
2661
  all: z.coerce.boolean().optional().describe("Retrieve every commit from the repository"),
2505
- with_stats: z.coerce.boolean().optional().describe("Stats about each commit are added to the response"),
2506
- first_parent: z
2507
- .coerce.boolean()
2662
+ with_stats: z.coerce
2663
+ .boolean()
2664
+ .optional()
2665
+ .describe("Stats about each commit are added to the response"),
2666
+ first_parent: z.coerce
2667
+ .boolean()
2508
2668
  .optional()
2509
2669
  .describe("Follow only the first parent commit upon seeing a merge commit"),
2510
2670
  order: z.enum(["default", "topo"]).optional().describe("List commits in order"),
2511
- trailers: z.coerce.boolean().optional().describe("Parse and include Git trailers for every commit"),
2671
+ trailers: z.coerce
2672
+ .boolean()
2673
+ .optional()
2674
+ .describe("Parse and include Git trailers for every commit"),
2512
2675
  page: z.coerce.number().optional().describe("Page number for pagination (default: 1)"),
2513
- per_page: z.coerce.number().optional().describe("Number of items per page (max: 100, default: 20)"),
2676
+ per_page: z.coerce
2677
+ .number()
2678
+ .optional()
2679
+ .describe("Number of items per page (max: 100, default: 20)"),
2514
2680
  });
2515
2681
  export const GetCommitSchema = z.object({
2516
2682
  project_id: z.coerce.string().describe("Project ID or complete URL-encoded path to project"),
@@ -2520,8 +2686,8 @@ export const GetCommitSchema = z.object({
2520
2686
  export const GetCommitDiffSchema = z.object({
2521
2687
  project_id: z.coerce.string().describe("Project ID or complete URL-encoded path to project"),
2522
2688
  sha: z.string().describe("The commit hash or name of a repository branch or tag"),
2523
- full_diff: z
2524
- .coerce.boolean()
2689
+ full_diff: z.coerce
2690
+ .boolean()
2525
2691
  .optional()
2526
2692
  .describe("Whether to return the full diff or only first page (default: false)"),
2527
2693
  });
@@ -2529,27 +2695,23 @@ export const GetFileBlameSchema = z
2529
2695
  .object({
2530
2696
  project_id: z.coerce.string().describe("Project ID or complete URL-encoded path to project"),
2531
2697
  file_path: z.string().describe("The full path of the file to blame, relative to repo root"),
2532
- ref: z
2533
- .string()
2534
- .describe("The name of branch, tag or commit (required by GitLab blame API)"),
2535
- range_start: z
2536
- .coerce.number()
2698
+ ref: z.string().describe("The name of branch, tag or commit (required by GitLab blame API)"),
2699
+ range_start: z.coerce
2700
+ .number()
2537
2701
  .int()
2538
2702
  .optional()
2539
2703
  .describe("First line of the blame range (inclusive, 1-based). Both range[start] and range[end] must be set together."),
2540
- range_end: z
2541
- .coerce.number()
2704
+ range_end: z.coerce
2705
+ .number()
2542
2706
  .int()
2543
2707
  .optional()
2544
2708
  .describe("Last line of the blame range (inclusive, 1-based). Both range[start] and range[end] must be set together."),
2545
2709
  })
2546
- .refine((v) => (v.range_start === undefined) === (v.range_end === undefined), {
2710
+ .refine(v => (v.range_start === undefined) === (v.range_end === undefined), {
2547
2711
  message: "range_start and range_end must be provided together (both or neither). Passing only one silently returned full-file blame on GitLab side.",
2548
2712
  path: ["range_end"],
2549
2713
  })
2550
- .refine((v) => v.range_start === undefined ||
2551
- v.range_end === undefined ||
2552
- v.range_start <= v.range_end, {
2714
+ .refine(v => v.range_start === undefined || v.range_end === undefined || v.range_start <= v.range_end, {
2553
2715
  message: "range_start must be less than or equal to range_end.",
2554
2716
  path: ["range_start"],
2555
2717
  });
@@ -2577,10 +2739,7 @@ export const ListCommitStatusesSchema = z
2577
2739
  stage: z.string().optional().describe("Filter statuses by build stage"),
2578
2740
  name: z.string().optional().describe("Filter statuses by status name or context"),
2579
2741
  pipeline_id: z.coerce.number().optional().describe("Filter statuses by pipeline ID"),
2580
- order_by: z
2581
- .enum(["id", "pipeline_id"])
2582
- .optional()
2583
- .describe("Field to order statuses by"),
2742
+ order_by: z.enum(["id", "pipeline_id"]).optional().describe("Field to order statuses by"),
2584
2743
  sort: z.enum(["asc", "desc"]).optional().describe("Sort direction"),
2585
2744
  all: coerceBooleanString.optional().describe("Return all statuses, not only latest ones"),
2586
2745
  })
@@ -2636,7 +2795,10 @@ export const MyIssuesSchema = z.object({
2636
2795
  .string()
2637
2796
  .optional()
2638
2797
  .describe("Return issues updated before the given time (ISO 8601)"),
2639
- per_page: z.coerce.number().optional().describe("Number of items per page (default: 20, max: 100)"),
2798
+ per_page: z.coerce
2799
+ .number()
2800
+ .optional()
2801
+ .describe("Number of items per page (default: 20, max: 100)"),
2640
2802
  page: z.coerce.number().optional().describe("Page number for pagination (default: 1)"),
2641
2803
  });
2642
2804
  // Schema for listing project members
@@ -2645,11 +2807,14 @@ export const ListProjectMembersSchema = z.object({
2645
2807
  query: z.string().optional().describe("Search for members by name or username"),
2646
2808
  user_ids: z.array(z.coerce.number()).optional().describe("Filter by user IDs"),
2647
2809
  skip_users: z.array(z.coerce.number()).optional().describe("User IDs to exclude"),
2648
- include_inheritance: z
2649
- .coerce.boolean()
2810
+ include_inheritance: z.coerce
2811
+ .boolean()
2650
2812
  .optional()
2651
2813
  .describe("Include inherited members. Defaults to false."),
2652
- per_page: z.coerce.number().optional().describe("Number of items per page (default: 20, max: 100)"),
2814
+ per_page: z.coerce
2815
+ .number()
2816
+ .optional()
2817
+ .describe("Number of items per page (default: 20, max: 100)"),
2653
2818
  page: z.coerce.number().optional().describe("Page number for pagination (default: 1)"),
2654
2819
  });
2655
2820
  // Schema for GitLab project member
@@ -2668,7 +2833,7 @@ export const GitLabProjectMemberSchema = z.object({
2668
2833
  });
2669
2834
  // Markdown upload schemas
2670
2835
  export const GitLabMarkdownUploadSchema = z.object({
2671
- id: z.preprocess((val) => (val == null ? undefined : val), z.coerce.number().optional()),
2836
+ id: z.preprocess(val => (val == null ? undefined : val), z.coerce.number().optional()),
2672
2837
  alt: z.string(),
2673
2838
  url: z.string(),
2674
2839
  full_path: z.string(),
@@ -2726,12 +2891,12 @@ export const ListGroupIterationsSchema = z
2726
2891
  .array(z.enum(["title", "cadence_title"]))
2727
2892
  .optional()
2728
2893
  .describe("Fields in which fuzzy search should be performed with the query given in the argument search. The available options are title and cadence_title. Default is [title]."),
2729
- include_ancestors: z
2730
- .coerce.boolean()
2894
+ include_ancestors: z.coerce
2895
+ .boolean()
2731
2896
  .optional()
2732
2897
  .describe("Include iterations for group and its ancestors. Defaults to true."),
2733
- include_descendants: z
2734
- .coerce.boolean()
2898
+ include_descendants: z.coerce
2899
+ .boolean()
2735
2900
  .optional()
2736
2901
  .describe("Include iterations for group and its descendants. Defaults to false."),
2737
2902
  updated_before: z
@@ -2929,8 +3094,8 @@ export const ListReleasesSchema = z
2929
3094
  .enum(["desc", "asc"])
2930
3095
  .optional()
2931
3096
  .describe("The direction of the order. Either desc (default) for descending order or asc for ascending order."),
2932
- include_html_description: z
2933
- .coerce.boolean()
3097
+ include_html_description: z.coerce
3098
+ .boolean()
2934
3099
  .optional()
2935
3100
  .describe("If true, a response includes HTML rendered Markdown of the release description."),
2936
3101
  })
@@ -2938,8 +3103,8 @@ export const ListReleasesSchema = z
2938
3103
  export const GetReleaseSchema = z.object({
2939
3104
  project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
2940
3105
  tag_name: z.string().describe("The Git tag the release is associated with"),
2941
- include_html_description: z
2942
- .coerce.boolean()
3106
+ include_html_description: z.coerce
3107
+ .boolean()
2943
3108
  .optional()
2944
3109
  .describe("If true, a response includes HTML rendered Markdown of the release description."),
2945
3110
  });
@@ -3021,10 +3186,7 @@ export const ListJobArtifactsSchema = z.object({
3021
3186
  .string()
3022
3187
  .optional()
3023
3188
  .describe("Directory path within the artifacts archive (defaults to root)"),
3024
- recursive: z
3025
- .coerce.boolean()
3026
- .optional()
3027
- .describe("Whether to list artifacts recursively"),
3189
+ recursive: z.coerce.boolean().optional().describe("Whether to list artifacts recursively"),
3028
3190
  });
3029
3191
  export const GitLabArtifactEntrySchema = z.object({
3030
3192
  name: z.string(),
@@ -3048,9 +3210,7 @@ export const DownloadJobArtifactsRemoteSchema = z.object({
3048
3210
  export const GetJobArtifactFileSchema = z.object({
3049
3211
  project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
3050
3212
  job_id: z.coerce.string().describe("The ID of the job"),
3051
- artifact_path: z
3052
- .string()
3053
- .describe("Path to the file within the artifacts archive"),
3213
+ artifact_path: z.string().describe("Path to the file within the artifacts archive"),
3054
3214
  });
3055
3215
  export const DownloadReleaseAssetSchema = z.object({
3056
3216
  project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
@@ -3139,8 +3299,23 @@ export const GitLabTagSignatureSchema = z.object({
3139
3299
  });
3140
3300
  // --- Work item schemas (GraphQL-based) ---
3141
3301
  // Case-insensitive work item type enum (accepts "ISSUE", "Issue", "issue")
3142
- const workItemTypeEnum = z.string().transform(v => v.toLowerCase()).pipe(z.enum(["issue", "task", "incident", "test_case", "epic", "key_result", "objective", "requirement", "ticket"]));
3143
- const ProjectIdOrPathSchema = z.coerce.string().describe("Project ID, URL-encoded project path, or group path (e.g. 'group/subgroup' for group-level work items)");
3302
+ const workItemTypeEnum = z
3303
+ .string()
3304
+ .transform(v => v.toLowerCase())
3305
+ .pipe(z.enum([
3306
+ "issue",
3307
+ "task",
3308
+ "incident",
3309
+ "test_case",
3310
+ "epic",
3311
+ "key_result",
3312
+ "objective",
3313
+ "requirement",
3314
+ "ticket",
3315
+ ]));
3316
+ const ProjectIdOrPathSchema = z.coerce
3317
+ .string()
3318
+ .describe("Project ID, URL-encoded project path, or group path (e.g. 'group/subgroup' for group-level work items)");
3144
3319
  // Common params for work item tools
3145
3320
  const WorkItemParamsSchema = z.object({
3146
3321
  project_id: ProjectIdOrPathSchema,
@@ -3153,24 +3328,12 @@ export const ListWorkItemsSchema = z.object({
3153
3328
  .array(workItemTypeEnum)
3154
3329
  .optional()
3155
3330
  .describe("Filter by work item types. If not set, returns all types."),
3156
- state: z
3157
- .enum(["opened", "closed"])
3158
- .optional()
3159
- .describe("Filter by state"),
3160
- search: z
3161
- .string()
3162
- .optional()
3163
- .describe("Search in title and description"),
3164
- assignee_usernames: z
3165
- .array(z.string())
3166
- .optional()
3167
- .describe("Filter by assignee usernames"),
3168
- label_names: z
3169
- .array(z.string())
3170
- .optional()
3171
- .describe("Filter by label names"),
3172
- first: z
3173
- .coerce.number()
3331
+ state: z.enum(["opened", "closed"]).optional().describe("Filter by state"),
3332
+ search: z.string().optional().describe("Search in title and description"),
3333
+ assignee_usernames: z.array(z.string()).optional().describe("Filter by assignee usernames"),
3334
+ label_names: z.array(z.string()).optional().describe("Filter by label names"),
3335
+ first: z.coerce
3336
+ .number()
3174
3337
  .optional()
3175
3338
  .default(20)
3176
3339
  .describe("Number of items to return (max 100). Default 20."),
@@ -3191,11 +3354,20 @@ export const CreateWorkItemSchema = z.object({
3191
3354
  assignee_usernames: coerceStringArray.optional().describe("Array of usernames to assign"),
3192
3355
  parent_iid: z.coerce.number().optional().describe("IID of the parent work item to set hierarchy"),
3193
3356
  weight: z.coerce.number().optional().describe("Weight of the work item"),
3194
- health_status: z.enum(["onTrack", "needsAttention", "atRisk"]).optional().describe("Set health status"),
3357
+ health_status: z
3358
+ .enum(["onTrack", "needsAttention", "atRisk"])
3359
+ .optional()
3360
+ .describe("Set health status"),
3195
3361
  start_date: z.string().optional().describe("Start date in YYYY-MM-DD format"),
3196
3362
  due_date: z.string().optional().describe("Due date in YYYY-MM-DD format"),
3197
- milestone_id: z.string().optional().describe("Milestone ID (GitLab global ID format, e.g. 'gid://gitlab/Milestone/123', or numeric ID)"),
3198
- iteration_id: z.string().optional().describe("Iteration ID (e.g. 'gid://gitlab/Iteration/123' or numeric ID). Use list_group_iterations to find available iterations."),
3363
+ milestone_id: z
3364
+ .string()
3365
+ .optional()
3366
+ .describe("Milestone ID (GitLab global ID format, e.g. 'gid://gitlab/Milestone/123', or numeric ID)"),
3367
+ iteration_id: z
3368
+ .string()
3369
+ .optional()
3370
+ .describe("Iteration ID (e.g. 'gid://gitlab/Iteration/123' or numeric ID). Use list_group_iterations to find available iterations."),
3199
3371
  confidential: z.coerce.boolean().optional().describe("Set confidentiality"),
3200
3372
  });
3201
3373
  export const UpdateWorkItemSchema = WorkItemParamsSchema.extend({
@@ -3203,43 +3375,105 @@ export const UpdateWorkItemSchema = WorkItemParamsSchema.extend({
3203
3375
  description: z.string().optional().describe("New description (Markdown supported)"),
3204
3376
  add_labels: coerceStringArray.optional().describe("Label names to add"),
3205
3377
  remove_labels: coerceStringArray.optional().describe("Label names to remove"),
3206
- assignee_usernames: coerceStringArray.optional().describe("Set assignees by username (replaces existing)"),
3378
+ assignee_usernames: coerceStringArray
3379
+ .optional()
3380
+ .describe("Set assignees by username (replaces existing)"),
3207
3381
  state_event: z.enum(["close", "reopen"]).optional().describe("Close or reopen the work item"),
3208
3382
  weight: z.coerce.number().optional().describe("Set weight (issues, tasks, epics only)"),
3209
- status: z.string().optional().describe("Set status by ID. Use list_work_item_statuses to get available status IDs."),
3210
- parent_iid: z.coerce.number().optional().describe("Set parent work item by IID. Use with parent_project_id if parent is in a different project."),
3211
- parent_project_id: z.coerce.string().optional().describe("Project ID or path of the parent work item (defaults to same project as the work item)"),
3212
- remove_parent: z.coerce.boolean().optional().describe("Set to true to remove the parent from hierarchy"),
3213
- children_to_add: z.array(z.object({
3214
- project_id: z.coerce.string().optional().describe("Project ID or path of the child work item. Defaults to the parent work item's project if omitted."),
3383
+ status: z
3384
+ .string()
3385
+ .optional()
3386
+ .describe("Set status by ID. Use list_work_item_statuses to get available status IDs."),
3387
+ parent_iid: z.coerce
3388
+ .number()
3389
+ .optional()
3390
+ .describe("Set parent work item by IID. Use with parent_project_id if parent is in a different project."),
3391
+ parent_project_id: z.coerce
3392
+ .string()
3393
+ .optional()
3394
+ .describe("Project ID or path of the parent work item (defaults to same project as the work item)"),
3395
+ remove_parent: z.coerce
3396
+ .boolean()
3397
+ .optional()
3398
+ .describe("Set to true to remove the parent from hierarchy"),
3399
+ children_to_add: z
3400
+ .array(z.object({
3401
+ project_id: z.coerce
3402
+ .string()
3403
+ .optional()
3404
+ .describe("Project ID or path of the child work item. Defaults to the parent work item's project if omitted."),
3215
3405
  iid: z.coerce.number().describe("IID of the child work item"),
3216
- })).optional().describe("Array of children to add to this work item's hierarchy"),
3217
- children_to_remove: z.array(z.object({
3218
- project_id: z.coerce.string().optional().describe("Project ID or path of the child work item. Defaults to the parent work item's project if omitted."),
3406
+ }))
3407
+ .optional()
3408
+ .describe("Array of children to add to this work item's hierarchy"),
3409
+ children_to_remove: z
3410
+ .array(z.object({
3411
+ project_id: z.coerce
3412
+ .string()
3413
+ .optional()
3414
+ .describe("Project ID or path of the child work item. Defaults to the parent work item's project if omitted."),
3219
3415
  iid: z.coerce.number().describe("IID of the child work item"),
3220
- })).optional().describe("Array of children to remove from this work item's hierarchy"),
3221
- health_status: z.enum(["onTrack", "needsAttention", "atRisk"]).optional().describe("Set health status on issues and epics"),
3416
+ }))
3417
+ .optional()
3418
+ .describe("Array of children to remove from this work item's hierarchy"),
3419
+ health_status: z
3420
+ .enum(["onTrack", "needsAttention", "atRisk"])
3421
+ .optional()
3422
+ .describe("Set health status on issues and epics"),
3222
3423
  start_date: z.string().optional().describe("Start date in YYYY-MM-DD format"),
3223
3424
  due_date: z.string().optional().describe("Due date in YYYY-MM-DD format"),
3224
- milestone_id: z.string().optional().describe("Milestone ID (GitLab global ID format, e.g. 'gid://gitlab/Milestone/123', or numeric ID)"),
3225
- iteration_id: z.string().optional().describe("Iteration ID (e.g. 'gid://gitlab/Iteration/123' or numeric ID). Use list_group_iterations to find available iterations."),
3425
+ milestone_id: z
3426
+ .string()
3427
+ .optional()
3428
+ .describe("Milestone ID (GitLab global ID format, e.g. 'gid://gitlab/Milestone/123', or numeric ID)"),
3429
+ iteration_id: z
3430
+ .string()
3431
+ .optional()
3432
+ .describe("Iteration ID (e.g. 'gid://gitlab/Iteration/123' or numeric ID). Use list_group_iterations to find available iterations."),
3226
3433
  confidential: z.coerce.boolean().optional().describe("Set confidentiality"),
3227
- linked_items_to_add: z.array(z.object({
3228
- project_id: z.coerce.string().optional().describe("Project ID or path of the work item to link. Defaults to the same project if omitted."),
3434
+ linked_items_to_add: z
3435
+ .array(z.object({
3436
+ project_id: z.coerce
3437
+ .string()
3438
+ .optional()
3439
+ .describe("Project ID or path of the work item to link. Defaults to the same project if omitted."),
3229
3440
  iid: z.coerce.number().describe("IID of the work item to link"),
3230
- link_type: z.enum(["RELATED", "BLOCKED_BY", "BLOCKS"]).optional().default("RELATED").describe("Link type: RELATED, BLOCKED_BY, or BLOCKS. Defaults to RELATED."),
3231
- })).optional().describe("Work items to link"),
3232
- linked_items_to_remove: z.array(z.object({
3233
- project_id: z.coerce.string().optional().describe("Project ID or path of the linked work item to remove. Defaults to the same project if omitted."),
3441
+ link_type: z
3442
+ .enum(["RELATED", "BLOCKED_BY", "BLOCKS"])
3443
+ .optional()
3444
+ .default("RELATED")
3445
+ .describe("Link type: RELATED, BLOCKED_BY, or BLOCKS. Defaults to RELATED."),
3446
+ }))
3447
+ .optional()
3448
+ .describe("Work items to link"),
3449
+ linked_items_to_remove: z
3450
+ .array(z.object({
3451
+ project_id: z.coerce
3452
+ .string()
3453
+ .optional()
3454
+ .describe("Project ID or path of the linked work item to remove. Defaults to the same project if omitted."),
3234
3455
  iid: z.coerce.number().describe("IID of the linked work item to remove"),
3235
- })).optional().describe("Linked work items to remove"),
3236
- custom_fields: z.array(z.object({
3237
- custom_field_id: z.string().describe("Custom field ID (e.g. 'gid://gitlab/IssuablesCustomField/123' or numeric ID)"),
3456
+ }))
3457
+ .optional()
3458
+ .describe("Linked work items to remove"),
3459
+ custom_fields: z
3460
+ .array(z.object({
3461
+ custom_field_id: z
3462
+ .string()
3463
+ .describe("Custom field ID (e.g. 'gid://gitlab/IssuablesCustomField/123' or numeric ID)"),
3238
3464
  text_value: z.string().optional().describe("Text value (for text fields)"),
3239
3465
  number_value: z.coerce.number().optional().describe("Number value (for number fields)"),
3240
- selected_option_ids: z.array(z.string()).optional().describe("Selected option IDs (for select fields)"),
3241
- date_value: z.string().optional().describe("Date value in YYYY-MM-DD format (for date fields)"),
3242
- })).optional().describe("Custom field values to set"),
3466
+ selected_option_ids: z
3467
+ .array(z.string())
3468
+ .optional()
3469
+ .describe("Selected option IDs (for select fields)"),
3470
+ date_value: z
3471
+ .string()
3472
+ .optional()
3473
+ .describe("Date value in YYYY-MM-DD format (for date fields)"),
3474
+ }))
3475
+ .optional()
3476
+ .describe("Custom field values to set"),
3243
3477
  severity: z
3244
3478
  .enum(["UNKNOWN", "LOW", "MEDIUM", "HIGH", "CRITICAL"])
3245
3479
  .optional()
@@ -3264,21 +3498,40 @@ export const ListWorkItemStatusesSchema = z.object({
3264
3498
  export const ListWorkItemNotesSchema = z.object({
3265
3499
  project_id: ProjectIdOrPathSchema,
3266
3500
  iid: z.coerce.number().describe("The internal ID of the work item"),
3267
- page_size: z.coerce.number().optional().default(20).describe("Number of discussions to return (default 20)"),
3501
+ page_size: z.coerce
3502
+ .number()
3503
+ .optional()
3504
+ .default(20)
3505
+ .describe("Number of discussions to return (default 20)"),
3268
3506
  after: z.string().optional().describe("Cursor for pagination"),
3269
- sort: z.enum(["CREATED_ASC", "CREATED_DESC"]).optional().default("CREATED_ASC").describe("Sort order for discussions"),
3507
+ sort: z
3508
+ .enum(["CREATED_ASC", "CREATED_DESC"])
3509
+ .optional()
3510
+ .default("CREATED_ASC")
3511
+ .describe("Sort order for discussions"),
3270
3512
  });
3271
3513
  export const CreateWorkItemNoteSchema = z.object({
3272
3514
  project_id: ProjectIdOrPathSchema,
3273
3515
  iid: z.coerce.number().describe("The internal ID of the work item"),
3274
3516
  body: z.string().describe("Note body (Markdown supported)"),
3275
- internal: z.coerce.boolean().optional().default(false).describe("Create as internal/confidential note (only visible to project members)"),
3276
- discussion_id: z.string().optional().describe("Discussion ID to reply to (for threaded replies). If omitted, creates a new top-level note."),
3517
+ internal: z.coerce
3518
+ .boolean()
3519
+ .optional()
3520
+ .default(false)
3521
+ .describe("Create as internal/confidential note (only visible to project members)"),
3522
+ discussion_id: z
3523
+ .string()
3524
+ .optional()
3525
+ .describe("Discussion ID to reply to (for threaded replies). If omitted, creates a new top-level note."),
3277
3526
  });
3278
3527
  export const MoveWorkItemSchema = z.object({
3279
- project_id: z.coerce.string().describe("Project ID, URL-encoded project path, or group path of the source namespace"),
3528
+ project_id: z.coerce
3529
+ .string()
3530
+ .describe("Project ID, URL-encoded project path, or group path of the source namespace"),
3280
3531
  iid: z.coerce.number().describe("The internal ID of the work item to move"),
3281
- target_project_id: z.coerce.string().describe("Project ID, URL-encoded project path, or group path of the target namespace"),
3532
+ target_project_id: z.coerce
3533
+ .string()
3534
+ .describe("Project ID, URL-encoded project path, or group path of the target namespace"),
3282
3535
  });
3283
3536
  export const ListCustomFieldDefinitionsSchema = z.object({
3284
3537
  project_id: ProjectIdOrPathSchema,
@@ -3288,9 +3541,14 @@ export const ListCustomFieldDefinitionsSchema = z.object({
3288
3541
  .describe("The work item type to list custom field definitions for. Defaults to 'issue'."),
3289
3542
  });
3290
3543
  // --- Emoji Reaction schemas (REST: MRs and Issues) ---
3291
- const emojiNameField = z.string().describe("Name of the emoji without colons (e.g. 'thumbsup', 'rocket', 'eyes')");
3544
+ const emojiNameField = z
3545
+ .string()
3546
+ .describe("Name of the emoji without colons (e.g. 'thumbsup', 'rocket', 'eyes')");
3292
3547
  const awardIdField = z.coerce.string().describe("The ID of the emoji reaction to delete");
3293
- const noteEmojiDiscussionField = z.coerce.string().optional().describe("The ID of a discussion thread. Required for notes that are discussion replies; omit for top-level notes.");
3548
+ const noteEmojiDiscussionField = z.coerce
3549
+ .string()
3550
+ .optional()
3551
+ .describe("The ID of a discussion thread. Required for notes that are discussion replies; omit for top-level notes.");
3294
3552
  export const CreateMergeRequestEmojiReactionSchema = ProjectParamsSchema.extend({
3295
3553
  merge_request_iid: z.coerce.string().describe("The IID of a merge request"),
3296
3554
  name: emojiNameField,
@@ -3345,13 +3603,17 @@ export const DeleteWorkItemEmojiReactionSchema = z.object({
3345
3603
  export const CreateWorkItemNoteEmojiReactionSchema = z.object({
3346
3604
  project_id: ProjectIdOrPathSchema,
3347
3605
  iid: z.coerce.number().describe("The internal ID of the work item"),
3348
- note_id: z.string().describe("The GraphQL GID of the note (e.g. 'gid://gitlab/Note/123' from list_work_item_notes)"),
3606
+ note_id: z
3607
+ .string()
3608
+ .describe("The GraphQL GID of the note (e.g. 'gid://gitlab/Note/123' from list_work_item_notes)"),
3349
3609
  name: emojiNameField,
3350
3610
  });
3351
3611
  export const DeleteWorkItemNoteEmojiReactionSchema = z.object({
3352
3612
  project_id: ProjectIdOrPathSchema,
3353
3613
  iid: z.coerce.number().describe("The internal ID of the work item"),
3354
- note_id: z.string().describe("The GraphQL GID of the note (e.g. 'gid://gitlab/Note/123' from list_work_item_notes)"),
3614
+ note_id: z
3615
+ .string()
3616
+ .describe("The GraphQL GID of the note (e.g. 'gid://gitlab/Note/123' from list_work_item_notes)"),
3355
3617
  name: emojiNameField,
3356
3618
  });
3357
3619
  export const ListMergeRequestEmojiReactionsSchema = ProjectParamsSchema.extend({
@@ -3377,7 +3639,9 @@ export const ListWorkItemEmojiReactionsSchema = z.object({
3377
3639
  export const ListWorkItemNoteEmojiReactionsSchema = z.object({
3378
3640
  project_id: ProjectIdOrPathSchema,
3379
3641
  iid: z.coerce.number().describe("The internal ID of the work item"),
3380
- note_id: z.string().describe("The GraphQL GID of the note (e.g. 'gid://gitlab/Note/123' from list_work_item_notes)"),
3642
+ note_id: z
3643
+ .string()
3644
+ .describe("The GraphQL GID of the note (e.g. 'gid://gitlab/Note/123' from list_work_item_notes)"),
3381
3645
  });
3382
3646
  // --- Incident Timeline Event schemas ---
3383
3647
  export const GetTimelineEventsSchema = z.object({
@@ -3388,9 +3652,18 @@ export const CreateTimelineEventSchema = z.object({
3388
3652
  project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
3389
3653
  incident_iid: z.coerce.number().describe("The internal ID (IID) of the incident"),
3390
3654
  note: z.string().describe("Description of the timeline event (Markdown supported)"),
3391
- occurred_at: z.string().describe("When the event occurred in ISO 8601 format (e.g. '2026-03-15T09:00:00.000Z')"),
3655
+ occurred_at: z
3656
+ .string()
3657
+ .describe("When the event occurred in ISO 8601 format (e.g. '2026-03-15T09:00:00.000Z')"),
3392
3658
  tag_names: z
3393
- .array(z.enum(["Start time", "End time", "Impact detected", "Response initiated", "Impact mitigated", "Cause identified"]))
3659
+ .array(z.enum([
3660
+ "Start time",
3661
+ "End time",
3662
+ "Impact detected",
3663
+ "Response initiated",
3664
+ "Impact mitigated",
3665
+ "Cause identified",
3666
+ ]))
3394
3667
  .optional()
3395
3668
  .describe("Timeline event tags to attach. Available: 'Start time', 'End time', 'Impact detected', 'Response initiated', 'Impact mitigated', 'Cause identified'."),
3396
3669
  });
@@ -3425,16 +3698,11 @@ export const ListWebhookEventsSchema = z
3425
3698
  .union([z.coerce.number(), z.string()])
3426
3699
  .optional()
3427
3700
  .describe("Filter by response status code (e.g. 200, 500) or category: successful, client_failure, server_failure"),
3428
- summary: z
3429
- .coerce.boolean()
3701
+ summary: z.coerce
3702
+ .boolean()
3430
3703
  .optional()
3431
3704
  .describe("If true, return only summary fields (id, url, trigger, response_status, execution_duration) without full request/response payloads. Recommended for overview queries to avoid huge responses."),
3432
- per_page: z
3433
- .number()
3434
- .max(20)
3435
- .optional()
3436
- .default(20)
3437
- .describe("Number of events per page"),
3705
+ per_page: z.number().max(20).optional().default(20).describe("Number of events per page"),
3438
3706
  page: z.coerce.number().optional().describe("Page number for pagination"),
3439
3707
  })
3440
3708
  .refine(data => (data.project_id || data.group_id) && !(data.project_id && data.group_id), {
@@ -3485,7 +3753,10 @@ const ciVariableFields = {
3485
3753
  .optional()
3486
3754
  .describe("Whether the variable is only available on protected branches/tags"),
3487
3755
  masked: z.boolean().optional().describe("Whether the variable value is masked in job logs"),
3488
- raw: z.boolean().optional().describe("Whether the variable is not expanded (treated as raw string)"),
3756
+ raw: z
3757
+ .boolean()
3758
+ .optional()
3759
+ .describe("Whether the variable is not expanded (treated as raw string)"),
3489
3760
  environment_scope: z
3490
3761
  .string()
3491
3762
  .optional()
@@ -3611,13 +3882,19 @@ export const GetDependencyProxySettingsSchema = z.object({
3611
3882
  export const UpdateDependencyProxySettingsSchema = z.object({
3612
3883
  group_id: z.coerce.string().describe("Group ID or URL-encoded path"),
3613
3884
  enabled: z.boolean().optional().describe("Enable or disable the dependency proxy"),
3614
- identity: z.string().optional().describe("Proxy username for authenticated Docker Hub pulls (Premium/Ultimate)"),
3885
+ identity: z
3886
+ .string()
3887
+ .optional()
3888
+ .describe("Proxy username for authenticated Docker Hub pulls (Premium/Ultimate)"),
3615
3889
  secret: z.string().optional().describe("Proxy password / access token for authenticated pulls"),
3616
3890
  });
3617
3891
  export const ListDependencyProxyBlobsSchema = z.object({
3618
3892
  group_id: z.coerce.string().describe("Group ID or URL-encoded path"),
3619
3893
  first: z.number().int().optional().describe("Number of blobs to return (default: 20)"),
3620
- after: z.string().optional().describe("Cursor for pagination (from previous response pageInfo.endCursor)"),
3894
+ after: z
3895
+ .string()
3896
+ .optional()
3897
+ .describe("Cursor for pagination (from previous response pageInfo.endCursor)"),
3621
3898
  });
3622
3899
  export const PurgeDependencyProxyCacheSchema = z.object({
3623
3900
  group_id: z.coerce.string().describe("Group ID or URL-encoded path"),