gitlab-mcp 1.2.1 → 1.3.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 (40) hide show
  1. package/README.md +30 -27
  2. package/dist/config/dotenv.js +6 -2
  3. package/dist/config/dotenv.js.map +1 -1
  4. package/dist/config/env.d.ts +3 -1
  5. package/dist/config/env.js +15 -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 +5 -4
  10. package/dist/http.js.map +1 -1
  11. package/dist/index.js +5 -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/regex.d.ts +5 -0
  24. package/dist/lib/regex.js +111 -0
  25. package/dist/lib/regex.js.map +1 -0
  26. package/dist/lib/request-runtime.js +24 -11
  27. package/dist/lib/request-runtime.js.map +1 -1
  28. package/dist/tools/gitlab.js +193 -3
  29. package/dist/tools/gitlab.js.map +1 -1
  30. package/dist/types/auth.d.ts +1 -0
  31. package/dist/types/auth.js +2 -0
  32. package/dist/types/auth.js.map +1 -0
  33. package/dist/types/context.d.ts +1 -0
  34. package/docs/architecture.md +1 -1
  35. package/docs/authentication.md +4 -1
  36. package/docs/configuration.md +23 -21
  37. package/docs/deployment.md +2 -0
  38. package/docs/mcp-integration-testing-best-practices.md +381 -730
  39. package/docs/tools.md +24 -14
  40. package/package.json +1 -1
@@ -12,6 +12,13 @@ const optionalNumberArray = z.preprocess((value) => (value === null ? undefined
12
12
  const optionalStringOrNumber = z.preprocess((value) => (value === null ? undefined : value), z.union([z.string(), z.number()]).optional());
13
13
  const optionalStringOrStringArray = z.preprocess((value) => (value === null ? undefined : value), z.union([z.string(), z.array(z.string())]).optional());
14
14
  const optionalRecord = z.preprocess((value) => (value === null ? undefined : value), z.record(z.string(), z.unknown()).optional());
15
+ const pipelineInputValueSchema = z.union([
16
+ z.string(),
17
+ z.number(),
18
+ z.boolean(),
19
+ z.array(z.union([z.string(), z.number(), z.boolean()]))
20
+ ]);
21
+ const optionalPipelineInputsRecord = z.preprocess((value) => (value === null ? undefined : value), z.record(z.string(), pipelineInputValueSchema).optional());
15
22
  const paginationShape = {
16
23
  page: optionalNumber,
17
24
  per_page: optionalNumber
@@ -29,6 +36,9 @@ export function registerGitLabTools(server, context) {
29
36
  if (!enabledNames.has(definition.name)) {
30
37
  continue;
31
38
  }
39
+ if (definition.requiresLocalFileTools && !context.allowLocalFileTools) {
40
+ continue;
41
+ }
32
42
  if (disableGraphqlTools && isGraphqlToolName(definition.name)) {
33
43
  continue;
34
44
  }
@@ -526,7 +536,7 @@ function getGitLabToolDefinitions() {
526
536
  },
527
537
  handler: async (args, context) => {
528
538
  const projectId = resolveProjectId(args, context, false);
529
- const query = toQuery(omit(args, ["project_id"]));
539
+ const query = toQuery(cleanMergeRequestListArgs(omit(args, ["project_id"])));
530
540
  if (projectId) {
531
541
  return context.gitlab.listMergeRequests(projectId, { query });
532
542
  }
@@ -816,6 +826,17 @@ function getGitLabToolDefinitions() {
816
826
  },
817
827
  handler: async (args, context) => context.gitlab.getMergeRequestApprovalState(resolveProjectId(args, context, true), getString(args, "merge_request_iid"))
818
828
  },
829
+ {
830
+ name: "gitlab_get_merge_request_conflicts",
831
+ title: "Get Merge Request Conflicts",
832
+ description: "Get conflict details for MR.",
833
+ mutating: false,
834
+ inputSchema: {
835
+ project_id: z.string().optional(),
836
+ merge_request_iid: z.string().min(1)
837
+ },
838
+ handler: async (args, context) => context.gitlab.getMergeRequestConflicts(resolveProjectId(args, context, true), getString(args, "merge_request_iid"))
839
+ },
819
840
  {
820
841
  name: "gitlab_list_merge_request_discussions",
821
842
  title: "List Merge Request Discussions",
@@ -1501,6 +1522,71 @@ function getGitLabToolDefinitions() {
1501
1522
  },
1502
1523
  handler: async (args, context) => context.gitlab.getPipeline(resolveProjectId(args, context, true), getString(args, "pipeline_id"))
1503
1524
  },
1525
+ {
1526
+ name: "gitlab_list_deployments",
1527
+ title: "List Deployments",
1528
+ description: "List deployments in a project.",
1529
+ mutating: false,
1530
+ requiresFeature: "pipeline",
1531
+ inputSchema: {
1532
+ project_id: z.string().optional(),
1533
+ environment: optionalString,
1534
+ ref: optionalString,
1535
+ sha: optionalString,
1536
+ status: optionalString,
1537
+ updated_after: optionalString,
1538
+ updated_before: optionalString,
1539
+ order_by: z
1540
+ .enum(["id", "iid", "created_at", "updated_at", "ref", "status", "environment"])
1541
+ .optional(),
1542
+ sort: z.enum(["asc", "desc"]).optional(),
1543
+ ...paginationShape
1544
+ },
1545
+ handler: async (args, context) => context.gitlab.listDeployments(resolveProjectId(args, context, true), {
1546
+ query: toQuery(omit(args, ["project_id"]))
1547
+ })
1548
+ },
1549
+ {
1550
+ name: "gitlab_get_deployment",
1551
+ title: "Get Deployment",
1552
+ description: "Get one deployment by ID.",
1553
+ mutating: false,
1554
+ requiresFeature: "pipeline",
1555
+ inputSchema: {
1556
+ project_id: z.string().optional(),
1557
+ deployment_id: z.string().min(1)
1558
+ },
1559
+ handler: async (args, context) => context.gitlab.getDeployment(resolveProjectId(args, context, true), getString(args, "deployment_id"))
1560
+ },
1561
+ {
1562
+ name: "gitlab_list_environments",
1563
+ title: "List Environments",
1564
+ description: "List environments in a project.",
1565
+ mutating: false,
1566
+ requiresFeature: "pipeline",
1567
+ inputSchema: {
1568
+ project_id: z.string().optional(),
1569
+ name: optionalString,
1570
+ search: optionalString,
1571
+ states: z.enum(["available", "stopped"]).optional(),
1572
+ ...paginationShape
1573
+ },
1574
+ handler: async (args, context) => context.gitlab.listEnvironments(resolveProjectId(args, context, true), {
1575
+ query: toQuery(omit(args, ["project_id"]))
1576
+ })
1577
+ },
1578
+ {
1579
+ name: "gitlab_get_environment",
1580
+ title: "Get Environment",
1581
+ description: "Get one environment by ID.",
1582
+ mutating: false,
1583
+ requiresFeature: "pipeline",
1584
+ inputSchema: {
1585
+ project_id: z.string().optional(),
1586
+ environment_id: z.string().min(1)
1587
+ },
1588
+ handler: async (args, context) => context.gitlab.getEnvironment(resolveProjectId(args, context, true), getString(args, "environment_id"))
1589
+ },
1504
1590
  {
1505
1591
  name: "gitlab_list_pipeline_jobs",
1506
1592
  title: "List Pipeline Jobs",
@@ -1580,6 +1666,74 @@ function getGitLabToolDefinitions() {
1580
1666
  },
1581
1667
  handler: async (args, context) => context.gitlab.getPipelineJobOutput(resolveProjectId(args, context, true), getString(args, "job_id"))
1582
1668
  },
1669
+ {
1670
+ name: "gitlab_list_job_artifacts",
1671
+ title: "List Job Artifacts",
1672
+ description: "List files and directories inside a job artifacts archive.",
1673
+ mutating: false,
1674
+ requiresFeature: "pipeline",
1675
+ inputSchema: {
1676
+ project_id: z.string().optional(),
1677
+ job_id: z.string().min(1),
1678
+ path: optionalString,
1679
+ recursive: optionalBoolean
1680
+ },
1681
+ handler: async (args, context) => context.gitlab.listJobArtifacts(resolveProjectId(args, context, true), getString(args, "job_id"), { query: toQuery(omit(args, ["project_id", "job_id"])) })
1682
+ },
1683
+ {
1684
+ name: "gitlab_download_job_artifacts",
1685
+ title: "Download Job Artifacts",
1686
+ description: "Download the full job artifacts archive as base64 content.",
1687
+ mutating: false,
1688
+ requiresFeature: "pipeline",
1689
+ inputSchema: {
1690
+ project_id: z.string().optional(),
1691
+ job_id: z.string().min(1)
1692
+ },
1693
+ handler: async (args, context) => context.gitlab.downloadJobArtifacts(resolveProjectId(args, context, true), getString(args, "job_id"))
1694
+ },
1695
+ {
1696
+ name: "gitlab_download_job_artifacts_local",
1697
+ title: "Download Job Artifacts Local",
1698
+ description: "Download the full job artifacts archive to a local directory.",
1699
+ mutating: true,
1700
+ requiresFeature: "pipeline",
1701
+ requiresLocalFileTools: true,
1702
+ inputSchema: {
1703
+ project_id: z.string().optional(),
1704
+ job_id: z.string().min(1),
1705
+ local_path: optionalString
1706
+ },
1707
+ handler: async (args, context) => context.gitlab.saveJobArtifacts(resolveProjectId(args, context, true), getString(args, "job_id"), getOptionalString(args, "local_path"))
1708
+ },
1709
+ {
1710
+ name: "gitlab_get_job_artifact_file",
1711
+ title: "Get Job Artifact File",
1712
+ description: "Return one file from a job artifacts archive as inline UTF-8 or base64 content.",
1713
+ mutating: false,
1714
+ requiresFeature: "pipeline",
1715
+ inputSchema: {
1716
+ project_id: z.string().optional(),
1717
+ job_id: z.string().min(1),
1718
+ artifact_path: z.string().min(1)
1719
+ },
1720
+ handler: async (args, context) => context.gitlab.getJobArtifactFile(resolveProjectId(args, context, true), getString(args, "job_id"), getString(args, "artifact_path"))
1721
+ },
1722
+ {
1723
+ name: "gitlab_get_job_artifact_file_local",
1724
+ title: "Get Job Artifact File Local",
1725
+ description: "Save one file from a job artifacts archive to a local directory.",
1726
+ mutating: true,
1727
+ requiresFeature: "pipeline",
1728
+ requiresLocalFileTools: true,
1729
+ inputSchema: {
1730
+ project_id: z.string().optional(),
1731
+ job_id: z.string().min(1),
1732
+ artifact_path: z.string().min(1),
1733
+ local_path: optionalString
1734
+ },
1735
+ handler: async (args, context) => context.gitlab.saveJobArtifactFile(resolveProjectId(args, context, true), getString(args, "job_id"), getString(args, "artifact_path"), getOptionalString(args, "local_path"))
1736
+ },
1583
1737
  {
1584
1738
  name: "gitlab_create_pipeline",
1585
1739
  title: "Create Pipeline",
@@ -1589,6 +1743,7 @@ function getGitLabToolDefinitions() {
1589
1743
  inputSchema: {
1590
1744
  project_id: z.string().optional(),
1591
1745
  ref: z.string().min(1),
1746
+ inputs: optionalPipelineInputsRecord,
1592
1747
  variables: z
1593
1748
  .array(z.object({
1594
1749
  key: z.string(),
@@ -1599,7 +1754,8 @@ function getGitLabToolDefinitions() {
1599
1754
  },
1600
1755
  handler: async (args, context) => context.gitlab.createPipeline(resolveProjectId(args, context, true), {
1601
1756
  ref: getString(args, "ref"),
1602
- variables: getArray(args, "variables")
1757
+ inputs: getOptionalPipelineInputsRecord(args, "inputs"),
1758
+ variables: getOptionalArray(args, "variables")
1603
1759
  })
1604
1760
  },
1605
1761
  {
@@ -2516,8 +2672,11 @@ function getOptionalNumber(args, key) {
2516
2672
  }
2517
2673
  return value;
2518
2674
  }
2519
- function getArray(args, key) {
2675
+ function getOptionalArray(args, key) {
2520
2676
  const value = args[key];
2677
+ if (value === undefined) {
2678
+ return undefined;
2679
+ }
2521
2680
  if (!Array.isArray(value)) {
2522
2681
  throw new Error(`'${key}' must be array`);
2523
2682
  }
@@ -2543,6 +2702,37 @@ function getOptionalRecord(args, key) {
2543
2702
  }
2544
2703
  return value;
2545
2704
  }
2705
+ function getOptionalPipelineInputsRecord(args, key) {
2706
+ const value = getOptionalRecord(args, key);
2707
+ if (!value) {
2708
+ return undefined;
2709
+ }
2710
+ for (const [entryKey, entryValue] of Object.entries(value)) {
2711
+ if (!pipelineInputValueSchema.safeParse(entryValue).success) {
2712
+ throw new Error(`'${key}.${entryKey}' must be a string, number, boolean, or array of primitive values`);
2713
+ }
2714
+ }
2715
+ return value;
2716
+ }
2717
+ function cleanMergeRequestListArgs(args) {
2718
+ const cleanedArgs = { ...args };
2719
+ if (hasValue(cleanedArgs.author_username)) {
2720
+ delete cleanedArgs.author_id;
2721
+ }
2722
+ if (hasValue(cleanedArgs.assignee_username)) {
2723
+ delete cleanedArgs.assignee_id;
2724
+ }
2725
+ if (hasValue(cleanedArgs.reviewer_username)) {
2726
+ delete cleanedArgs.reviewer_id;
2727
+ }
2728
+ return cleanedArgs;
2729
+ }
2730
+ function hasValue(value) {
2731
+ if (typeof value === "string") {
2732
+ return value.length > 0;
2733
+ }
2734
+ return value !== undefined && value !== null;
2735
+ }
2546
2736
  function requireArrayValue(items, index, errorMessage) {
2547
2737
  const value = items[index];
2548
2738
  if (value === undefined) {