@zereight/mcp-gitlab 2.1.58 → 2.1.59

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/README.ko.md CHANGED
@@ -96,9 +96,21 @@ npm으로 설치할 수도 있습니다:
96
96
  npm install -g @zereight/mcp-gitlab
97
97
  ```
98
98
 
99
+ 또는 Nix를 사용해 이 플레이크를 자신의 플레이크에 추가할 수 있습니다:
100
+
101
+ ```nix
102
+ # flake.nix
103
+ inputs.gitlab-mcp.url = "github:zereight/gitlab-mcp";
104
+
105
+ # wherever you configure your MCP client:
106
+ command = lib.getExe inputs.gitlab-mcp.packages.${system}.default;
107
+ ```
108
+
109
+ 스토어 경로는 lock 파일에 고정되며, `nix flake update gitlab-mcp`로 업데이트합니다.
110
+
99
111
  예시는 기존 `mcp-gitlab`보다 충돌 가능성이 낮은 `zereight-mcp-gitlab` 별칭을 사용합니다. MCP 클라이언트가 찾지 못하면 `which zereight-mcp-gitlab`의 절대 경로를 사용하세요.
100
112
 
101
- 전역 설치를 쓰지 않으려면 `npx -y @zereight/mcp-gitlab@2.1.57`처럼 직전 안정 버전(문서가 권장하는 버전)으로 고정하세요. 항상 최신 버전을 원하면 `npx -y @zereight/mcp-gitlab@latest`를 사용하세요. 새 버전이 나오면 서버가 시작 시 stderr로 알려줍니다(`GITLAB_DISABLE_VERSION_CHECK=true`로 비활성화 가능).
113
+ 전역 설치를 쓰지 않으려면 `npx -y @zereight/mcp-gitlab@2.1.58`처럼 직전 안정 버전(문서가 권장하는 버전)으로 고정하세요. 항상 최신 버전을 원하면 `npx -y @zereight/mcp-gitlab@latest`를 사용하세요. 새 버전이 나오면 서버가 시작 시 stderr로 알려줍니다(`GITLAB_DISABLE_VERSION_CHECK=true`로 비활성화 가능).
102
114
 
103
115
  #### CLI 인자 사용하기(환경 변수 문제가 있는 클라이언트용)
104
116
 
package/README.md CHANGED
@@ -101,9 +101,21 @@ Or with npm:
101
101
  npm install -g @zereight/mcp-gitlab
102
102
  ```
103
103
 
104
+ Or with Nix, by adding this flake to your own:
105
+
106
+ ```nix
107
+ # flake.nix
108
+ inputs.gitlab-mcp.url = "github:zereight/gitlab-mcp";
109
+
110
+ # wherever you configure your MCP client:
111
+ command = lib.getExe inputs.gitlab-mcp.packages.${system}.default;
112
+ ```
113
+
114
+ The store path is pinned by your lock file; update it with `nix flake update gitlab-mcp`.
115
+
104
116
  The examples use `zereight-mcp-gitlab`, a less collision-prone alias for the legacy `mcp-gitlab` binary. If your MCP client cannot find it, use the absolute path from `which zereight-mcp-gitlab`.
105
117
 
106
- No global install? Pin `npx` to the previous stable release (the version these docs recommend), for example `npx -y @zereight/mcp-gitlab@2.1.57`. If you always want the newest release, use `npx -y @zereight/mcp-gitlab@latest` instead. The server prints a notice to stderr on startup when a newer version is available (disable with `GITLAB_DISABLE_VERSION_CHECK=true`).
118
+ No global install? Pin `npx` to the previous stable release (the version these docs recommend), for example `npx -y @zereight/mcp-gitlab@2.1.58`. If you always want the newest release, use `npx -y @zereight/mcp-gitlab@latest` instead. The server prints a notice to stderr on startup when a newer version is available (disable with `GITLAB_DISABLE_VERSION_CHECK=true`).
107
119
 
108
120
  #### Using CLI Arguments (for clients with env var issues)
109
121
 
package/README.zh-CN.md CHANGED
@@ -96,9 +96,21 @@ brew install zereight/gitlab-mcp/zereight-mcp-gitlab
96
96
  npm install -g @zereight/mcp-gitlab
97
97
  ```
98
98
 
99
+ 也可以使用 Nix,把此 flake 添加到你自己的 flake 中:
100
+
101
+ ```nix
102
+ # flake.nix
103
+ inputs.gitlab-mcp.url = "github:zereight/gitlab-mcp";
104
+
105
+ # wherever you configure your MCP client:
106
+ command = lib.getExe inputs.gitlab-mcp.packages.${system}.default;
107
+ ```
108
+
109
+ 存储路径由你的 lock 文件固定,使用 `nix flake update gitlab-mcp` 更新。
110
+
99
111
  示例使用 `zereight-mcp-gitlab`,这是比旧的 `mcp-gitlab` 更不容易冲突的别名。如果 MCP 客户端找不到它,请使用 `which zereight-mcp-gitlab` 输出的绝对路径。
100
112
 
101
- 如果不想全局安装,请将 `npx` 固定到上一个稳定版本(即文档推荐的版本),例如 `npx -y @zereight/mcp-gitlab@2.1.57`。如果始终想使用最新版本,请改用 `npx -y @zereight/mcp-gitlab@latest`。有新版本发布时,服务器会在启动时通过 stderr 提示(可用 `GITLAB_DISABLE_VERSION_CHECK=true` 关闭)。
113
+ 如果不想全局安装,请将 `npx` 固定到上一个稳定版本(即文档推荐的版本),例如 `npx -y @zereight/mcp-gitlab@2.1.58`。如果始终想使用最新版本,请改用 `npx -y @zereight/mcp-gitlab@latest`。有新版本发布时,服务器会在启动时通过 stderr 提示(可用 `GITLAB_DISABLE_VERSION_CHECK=true` 关闭)。
102
114
 
103
115
  #### 使用 CLI 参数(适用于环境变量有问题的客户端)
104
116
 
package/build/index.js CHANGED
@@ -104,8 +104,8 @@ import { parseSearchReplaceBlocks, applySearchReplace, applyUnifiedDiff, } from
104
104
  import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
105
105
  import { GitLabClientPool } from "./gitlab-client-pool.js";
106
106
  import { allTools, readOnlyTools, destructiveTools, deleteTools, parseEnabledToolsets, parseIndividualTools, buildFeatureFlagOverrides, isToolInEnabledToolset, TOOLSET_DEFINITIONS, ALL_TOOLSET_IDS, } from "./tools/registry.js";
107
- import { BulkPublishDraftNotesSchema, CancelPipelineJobSchema, CancelPipelineSchema, CreateBranchSchema, CreateDraftNoteSchema, CreateIssueLinkSchema, CreateIssueNoteSchema, CreateIssueSchema, CreateIssueEmojiReactionSchema, CreateIssueNoteEmojiReactionSchema, ListIssueEmojiReactionsSchema, ListIssueNoteEmojiReactionsSchema, CreateLabelSchema, // Added
108
- CreateMergeRequestNoteSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, CreateMergeRequestNoteEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateCommitStatusSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateGroupMilestoneSchema, CreateRepositorySchema, CreateGroupSchema, CreateWikiPageSchema, CreateGroupWikiPageSchema, DeleteBranchSchema, GetProtectedBranchSchema, ListProtectedBranchesSchema, ProtectBranchSchema, UnprotectBranchSchema, UpdateDefaultBranchSchema, DeleteDraftNoteSchema, DeleteGroupMilestoneSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteProjectMilestoneSchema, DeleteWikiPageSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, EditProjectMilestoneSchema, EditGroupMilestoneSchema, ForkRepositorySchema, GetBranchDiffsSchema, GetBranchSchema, GetCommitDiffSchema, GetCommitSchema, GetFileBlameSchema, GitLabBlameEntrySchema, GetDraftNoteSchema, GetFileContentsSchema, GetIssueLinkSchema, GetIssueSchema, GetLabelSchema, GetMergeRequestDiffsSchema, GetMergeRequestSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetGroupMilestoneSchema, GetGroupMilestoneIssuesSchema, GetGroupMilestoneMergeRequestsSchema, GetGroupMilestoneBurndownEventsSchema, GetDeploymentSchema, GetEnvironmentSchema, GetNamespaceSchema, GitLabCiLintResultSchema, GetPipelineJobOutputSchema, PipelineJobControlSchema, GetPipelineSchema, GetProjectMilestoneSchema, GetProjectSchema, GetRepositoryTreeSchema, GetUsersSchema, GetUserSchema, GitLabUserFullSchema, WhoAmISchema, GitLabCurrentUserSchema, GetWikiPageSchema, GitLabCommitSchema, GitLabCommitStatusSchema, GitLabCompareResultSchema, GitLabContentSchema, GitLabCreateUpdateFileResponseSchema, GitLabDiffSchema,
107
+ import { BulkPublishDraftNotesSchema, CancelPipelineJobSchema, ErasePipelineJobSchema, CancelPipelineSchema, CreateBranchSchema, CreateDraftNoteSchema, CreateIssueLinkSchema, CreateIssueNoteSchema, CreateIssueSchema, CreateIssueEmojiReactionSchema, CreateIssueNoteEmojiReactionSchema, ListIssueEmojiReactionsSchema, ListIssueNoteEmojiReactionsSchema, CreateLabelSchema, // Added
108
+ CreateMergeRequestNoteSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, CreateMergeRequestNoteEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateCommitStatusSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateGroupMilestoneSchema, CreateRepositorySchema, CreateGroupSchema, CreateWikiPageSchema, CreateGroupWikiPageSchema, DeleteBranchSchema, GetProtectedBranchSchema, ListProtectedBranchesSchema, ProtectBranchSchema, UnprotectBranchSchema, UpdateDefaultBranchSchema, DeleteDraftNoteSchema, DeleteGroupMilestoneSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteProjectMilestoneSchema, DeleteWikiPageSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, EditProjectMilestoneSchema, EditGroupMilestoneSchema, ForkRepositorySchema, GetBranchDiffsSchema, GetBranchSchema, GetCommitDiffSchema, GetCommitSchema, GetFileBlameSchema, GitLabBlameEntrySchema, GetDraftNoteSchema, GetFileContentsSchema, GetIssueLinkSchema, GetIssueSchema, GetLabelSchema, GetMergeRequestDiffsSchema, GetMergeRequestSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetGroupMilestoneSchema, GetGroupMilestoneIssuesSchema, GetGroupMilestoneMergeRequestsSchema, GetGroupMilestoneBurndownEventsSchema, GetDeploymentSchema, CreateDeploymentSchema, UpdateDeploymentSchema, DeploymentApprovalSchema, ListDeploymentMergeRequestsSchema, GetEnvironmentSchema, UpdateEnvironmentSchema, StopEnvironmentSchema, StopStaleEnvironmentsSchema, DeleteReviewAppEnvironmentsSchema, PipelineTriggerIdSchema, ListPipelineTriggersSchema, CreatePipelineTriggerSchema, UpdatePipelineTriggerSchema, TriggerPipelineSchema, GetNamespaceSchema, GitLabCiLintResultSchema, GetPipelineJobOutputSchema, PipelineJobControlSchema, GetPipelineSchema, GetPipelineVariablesSchema, UpdatePipelineMetadataSchema, DeletePipelineSchema, PipelineReportSchema, WaitForPipelineSchema, WaitForPipelineJobSchema, PlayPipelineJobsSchema, GetProjectMilestoneSchema, GetProjectSchema, GetRepositoryTreeSchema, GetUsersSchema, GetUserSchema, GitLabUserFullSchema, WhoAmISchema, GitLabCurrentUserSchema, GetWikiPageSchema, GitLabCommitSchema, GitLabCommitStatusSchema, GitLabCompareResultSchema, GitLabContentSchema, GitLabCreateUpdateFileResponseSchema, GitLabDiffSchema,
109
109
  // Discussion Schemas
110
110
  GitLabDiscussionNoteSchema, // Added
111
111
  GitLabDiscussionSchema,
@@ -5184,11 +5184,12 @@ async function listPipelines(projectId, options = {}) {
5184
5184
  * @param {number} pipelineId - The ID of the pipeline
5185
5185
  * @returns {Promise<GitLabPipeline>} Pipeline details
5186
5186
  */
5187
- async function getPipeline(projectId, pipelineId) {
5187
+ async function getPipeline(projectId, pipelineId, signal) {
5188
5188
  projectId = decodeURIComponent(projectId); // Decode project ID
5189
5189
  const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}`);
5190
5190
  const response = await fetch(url.toString(), {
5191
5191
  ...getFetchConfig(),
5192
+ signal,
5192
5193
  });
5193
5194
  if (response.status === 404) {
5194
5195
  throw new Error(`Pipeline not found`);
@@ -5197,6 +5198,41 @@ async function getPipeline(projectId, pipelineId) {
5197
5198
  const data = await response.json();
5198
5199
  return GitLabPipelineSchema.parse(data);
5199
5200
  }
5201
+ async function getPipelineVariables(projectId, pipelineId, page, perPage) {
5202
+ projectId = decodeURIComponent(projectId);
5203
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/variables`);
5204
+ if (page !== undefined)
5205
+ url.searchParams.set("page", String(page));
5206
+ if (perPage !== undefined)
5207
+ url.searchParams.set("per_page", String(perPage));
5208
+ const response = await fetch(url.toString(), { ...getFetchConfig() });
5209
+ await handleGitLabError(response);
5210
+ return response.json();
5211
+ }
5212
+ async function getPipelineReport(projectId, pipelineId, summary = false, page, perPage) {
5213
+ projectId = decodeURIComponent(projectId);
5214
+ const endpoint = summary ? "test_report_summary" : "test_report";
5215
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/${endpoint}`);
5216
+ if (page !== undefined)
5217
+ url.searchParams.set("page", String(page));
5218
+ if (perPage !== undefined)
5219
+ url.searchParams.set("per_page", String(perPage));
5220
+ const response = await fetch(url.toString(), { ...getFetchConfig() });
5221
+ await handleGitLabError(response);
5222
+ return response.json();
5223
+ }
5224
+ async function deletePipeline(projectId, pipelineId) {
5225
+ projectId = decodeURIComponent(projectId);
5226
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}`, { ...getFetchConfig(), method: "DELETE" });
5227
+ await handleGitLabError(response);
5228
+ return response.status === 204 ? { deleted: true } : response.json();
5229
+ }
5230
+ async function updatePipelineMetadata(projectId, pipelineId, name) {
5231
+ projectId = decodeURIComponent(projectId);
5232
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/metadata`, { ...getFetchConfig(), method: "PUT", body: JSON.stringify({ name }) });
5233
+ await handleGitLabError(response);
5234
+ return response.json();
5235
+ }
5200
5236
  /**
5201
5237
  * List deployments in a GitLab project
5202
5238
  *
@@ -5228,7 +5264,7 @@ async function listDeployments(projectId, options = {}) {
5228
5264
  */
5229
5265
  async function getDeployment(projectId, deploymentId) {
5230
5266
  projectId = decodeURIComponent(projectId);
5231
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/deployments/${deploymentId}`);
5267
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/deployments/${encodeGitLabPathSegment(deploymentId)}`);
5232
5268
  const response = await fetch(url.toString(), {
5233
5269
  ...getFetchConfig(),
5234
5270
  });
@@ -5239,6 +5275,20 @@ async function getDeployment(projectId, deploymentId) {
5239
5275
  const data = await response.json();
5240
5276
  return GitLabDeploymentSchema.parse(data);
5241
5277
  }
5278
+ async function deploymentRequest(projectId, path = "", method = "GET", body, query) {
5279
+ projectId = decodeURIComponent(projectId);
5280
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/deployments${path}`);
5281
+ for (const [key, value] of Object.entries(query ?? {})) {
5282
+ if (value !== undefined)
5283
+ url.searchParams.set(key, String(value));
5284
+ }
5285
+ const response = await fetch(url.toString(), {
5286
+ ...getFetchConfig(), method,
5287
+ body: body === undefined ? undefined : JSON.stringify(body),
5288
+ });
5289
+ await handleGitLabError(response);
5290
+ return response.status === 204 ? { success: true } : response.json();
5291
+ }
5242
5292
  /**
5243
5293
  * List environments in a GitLab project
5244
5294
  *
@@ -5270,7 +5320,7 @@ async function listEnvironments(projectId, options = {}) {
5270
5320
  */
5271
5321
  async function getEnvironment(projectId, environmentId) {
5272
5322
  projectId = decodeURIComponent(projectId);
5273
- const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/environments/${environmentId}`);
5323
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/environments/${encodeGitLabPathSegment(environmentId)}`);
5274
5324
  const response = await fetch(url.toString(), {
5275
5325
  ...getFetchConfig(),
5276
5326
  });
@@ -5281,6 +5331,36 @@ async function getEnvironment(projectId, environmentId) {
5281
5331
  const data = await response.json();
5282
5332
  return GitLabEnvironmentSchema.parse(data);
5283
5333
  }
5334
+ async function environmentRequest(projectId, path, method, body, query) {
5335
+ projectId = decodeURIComponent(projectId);
5336
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/environments${path}`);
5337
+ for (const [key, value] of Object.entries(query ?? {}))
5338
+ if (value !== undefined)
5339
+ url.searchParams.set(key, String(value));
5340
+ const response = await fetch(url.toString(), { ...getFetchConfig(), method, body: body === undefined ? undefined : JSON.stringify(body) });
5341
+ await handleGitLabError(response);
5342
+ return response.status === 204 ? { success: true } : response.json();
5343
+ }
5344
+ async function pipelineTriggerRequest(projectId, path = "", method = "GET", body) {
5345
+ projectId = decodeURIComponent(projectId);
5346
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/triggers${path}`, { ...getFetchConfig(), method, body: body === undefined ? undefined : JSON.stringify(body) });
5347
+ await handleGitLabError(response);
5348
+ return response.status === 204 ? { success: true } : response.json();
5349
+ }
5350
+ async function triggerPipeline(projectId, token, ref, variables, inputs) {
5351
+ projectId = decodeURIComponent(projectId);
5352
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/trigger/pipeline`);
5353
+ url.searchParams.set("token", token);
5354
+ url.searchParams.set("ref", ref);
5355
+ const body = {};
5356
+ if (variables)
5357
+ body.variables = variables;
5358
+ if (inputs)
5359
+ body.inputs = inputs;
5360
+ const response = await fetch(url.toString(), { ...getFetchConfig(), method: "POST", body: Object.keys(body).length ? JSON.stringify(body) : undefined });
5361
+ await handleGitLabError(response);
5362
+ return response.json();
5363
+ }
5284
5364
  /**
5285
5365
  * List all jobs in a specific pipeline
5286
5366
  *
@@ -5345,11 +5425,12 @@ async function listPipelineTriggerJobs(projectId, pipelineId, options = {}) {
5345
5425
  const data = await response.json();
5346
5426
  return z.array(GitLabPipelineTriggerJobSchema).parse(data);
5347
5427
  }
5348
- async function getPipelineJob(projectId, jobId) {
5428
+ async function getPipelineJob(projectId, jobId, signal) {
5349
5429
  projectId = decodeURIComponent(projectId); // Decode project ID
5350
5430
  const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}`);
5351
5431
  const response = await fetch(url.toString(), {
5352
5432
  ...getFetchConfig(),
5433
+ signal,
5353
5434
  });
5354
5435
  if (response.status === 404) {
5355
5436
  throw new Error(`Job not found`);
@@ -5819,13 +5900,15 @@ async function deletePipelineScheduleVariable(projectId, pipelineScheduleId, key
5819
5900
  * @param {Object} variables - Optional job variables
5820
5901
  * @returns {Promise<GitLabPipelineJob>} The run job
5821
5902
  */
5822
- async function playPipelineJob(projectId, jobId, variables) {
5903
+ async function playPipelineJob(projectId, jobId, variables, jobInputs) {
5823
5904
  projectId = decodeURIComponent(projectId);
5824
5905
  const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}/play`);
5825
5906
  const body = {};
5826
5907
  if (variables && variables.length > 0) {
5827
5908
  body.job_variables_attributes = variables;
5828
5909
  }
5910
+ if (jobInputs)
5911
+ body.job_inputs = jobInputs;
5829
5912
  const response = await fetch(url.toString(), {
5830
5913
  ...getFetchConfig(),
5831
5914
  method: "POST",
@@ -5835,6 +5918,15 @@ async function playPipelineJob(projectId, jobId, variables) {
5835
5918
  const data = await response.json();
5836
5919
  return GitLabPipelineJobSchema.parse(data);
5837
5920
  }
5921
+ async function playPipelineJobs(projectId, jobIds, variables, timeoutSeconds = 300, pollIntervalSeconds = 5) {
5922
+ const jobs = [];
5923
+ for (const jobId of jobIds) {
5924
+ const played = await playPipelineJob(projectId, jobId, variables);
5925
+ const completed = await waitForStatus(signal => getPipelineJob(projectId, played.id, signal), TERMINAL_JOB_STATUSES, timeoutSeconds, pollIntervalSeconds);
5926
+ jobs.push(completed);
5927
+ }
5928
+ return jobs;
5929
+ }
5838
5930
  /**
5839
5931
  * Retry a job
5840
5932
  *
@@ -5842,17 +5934,46 @@ async function playPipelineJob(projectId, jobId, variables) {
5842
5934
  * @param {number} jobId - The ID of the job to retry
5843
5935
  * @returns {Promise<GitLabPipelineJob>} The retried job
5844
5936
  */
5845
- async function retryPipelineJob(projectId, jobId) {
5937
+ async function retryPipelineJob(projectId, jobId, jobInputs) {
5846
5938
  projectId = decodeURIComponent(projectId);
5847
5939
  const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}/retry`);
5848
5940
  const response = await fetch(url.toString(), {
5849
5941
  ...getFetchConfig(),
5850
5942
  method: "POST",
5943
+ body: jobInputs ? JSON.stringify({ job_inputs: jobInputs }) : undefined,
5851
5944
  });
5852
5945
  await handleGitLabError(response);
5853
5946
  const data = await response.json();
5854
5947
  return GitLabPipelineJobSchema.parse(data);
5855
5948
  }
5949
+ async function erasePipelineJob(projectId, jobId) {
5950
+ projectId = decodeURIComponent(projectId);
5951
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}/erase`, { ...getFetchConfig(), method: "POST" });
5952
+ await handleGitLabError(response);
5953
+ return response.status === 204 ? { erased: true } : response.json();
5954
+ }
5955
+ const TERMINAL_PIPELINE_STATUSES = new Set(["success", "failed", "canceled", "skipped", "manual"]);
5956
+ const TERMINAL_JOB_STATUSES = new Set(["success", "failed", "canceled", "skipped", "manual"]);
5957
+ async function waitForStatus(fetchStatus, terminalStatuses, timeoutSeconds = 300, pollIntervalSeconds = 5) {
5958
+ const deadline = Date.now() + timeoutSeconds * 1000;
5959
+ const fetchBeforeDeadline = async () => {
5960
+ const remaining = deadline - Date.now();
5961
+ if (remaining <= 0)
5962
+ throw new Error("Timed out waiting for terminal status");
5963
+ return fetchStatus(AbortSignal.timeout(remaining));
5964
+ };
5965
+ let value = await fetchBeforeDeadline();
5966
+ while (!terminalStatuses.has(value.status)) {
5967
+ const remaining = deadline - Date.now();
5968
+ if (remaining <= 0)
5969
+ break;
5970
+ await new Promise(resolve => setTimeout(resolve, Math.min(pollIntervalSeconds * 1000, remaining)));
5971
+ value = await fetchBeforeDeadline();
5972
+ }
5973
+ if (!terminalStatuses.has(value.status))
5974
+ throw new Error(`Timed out waiting for terminal status; last status: ${value.status}`);
5975
+ return value;
5976
+ }
5856
5977
  /**
5857
5978
  * Cancel a job
5858
5979
  *
@@ -8795,6 +8916,26 @@ async function handleToolCall(params) {
8795
8916
  ],
8796
8917
  };
8797
8918
  }
8919
+ case "get_pipeline_variables": {
8920
+ const { project_id, pipeline_id, page, per_page } = GetPipelineVariablesSchema.parse(params.arguments);
8921
+ return { content: [{ type: "text", text: JSON.stringify(await getPipelineVariables(project_id, pipeline_id, page, per_page)) }] };
8922
+ }
8923
+ case "get_pipeline_test_report": {
8924
+ const { project_id, pipeline_id, page, per_page } = PipelineReportSchema.parse(params.arguments);
8925
+ return { content: [{ type: "text", text: JSON.stringify(await getPipelineReport(project_id, pipeline_id, false, page, per_page)) }] };
8926
+ }
8927
+ case "get_pipeline_test_report_summary": {
8928
+ const { project_id, pipeline_id, page, per_page } = PipelineReportSchema.parse(params.arguments);
8929
+ return { content: [{ type: "text", text: JSON.stringify(await getPipelineReport(project_id, pipeline_id, true, page, per_page)) }] };
8930
+ }
8931
+ case "delete_pipeline": {
8932
+ const { project_id, pipeline_id } = DeletePipelineSchema.parse(params.arguments);
8933
+ return { content: [{ type: "text", text: JSON.stringify(await deletePipeline(project_id, pipeline_id)) }] };
8934
+ }
8935
+ case "update_pipeline_metadata": {
8936
+ const { project_id, pipeline_id, name } = UpdatePipelineMetadataSchema.parse(params.arguments);
8937
+ return { content: [{ type: "text", text: JSON.stringify(await updatePipelineMetadata(project_id, pipeline_id, name)) }] };
8938
+ }
8798
8939
  case "list_deployments": {
8799
8940
  const args = ListDeploymentsSchema.parse(params.arguments);
8800
8941
  const { project_id, ...options } = args;
@@ -8810,6 +8951,26 @@ async function handleToolCall(params) {
8810
8951
  content: [{ type: "text", text: JSON.stringify(deployment) }],
8811
8952
  };
8812
8953
  }
8954
+ case "create_deployment": {
8955
+ const { project_id, ...body } = CreateDeploymentSchema.parse(params.arguments);
8956
+ return { content: [{ type: "text", text: JSON.stringify(await deploymentRequest(project_id, "", "POST", body)) }] };
8957
+ }
8958
+ case "update_deployment": {
8959
+ const { project_id, deployment_id, ...body } = UpdateDeploymentSchema.parse(params.arguments);
8960
+ return { content: [{ type: "text", text: JSON.stringify(await deploymentRequest(project_id, `/${encodeGitLabPathSegment(deployment_id)}`, "PUT", body)) }] };
8961
+ }
8962
+ case "delete_deployment": {
8963
+ const { project_id, deployment_id } = GetDeploymentSchema.parse(params.arguments);
8964
+ return { content: [{ type: "text", text: JSON.stringify(await deploymentRequest(project_id, `/${encodeGitLabPathSegment(deployment_id)}`, "DELETE")) }] };
8965
+ }
8966
+ case "list_deployment_merge_requests": {
8967
+ const { project_id, deployment_id, ...query } = ListDeploymentMergeRequestsSchema.parse(params.arguments);
8968
+ return { content: [{ type: "text", text: JSON.stringify(await deploymentRequest(project_id, `/${encodeGitLabPathSegment(deployment_id)}/merge_requests`, "GET", undefined, query)) }] };
8969
+ }
8970
+ case "approve_deployment": {
8971
+ const { project_id, deployment_id, ...body } = DeploymentApprovalSchema.parse(params.arguments);
8972
+ return { content: [{ type: "text", text: JSON.stringify(await deploymentRequest(project_id, `/${encodeGitLabPathSegment(deployment_id)}/approval`, "POST", body)) }] };
8973
+ }
8813
8974
  case "list_environments": {
8814
8975
  const args = ListEnvironmentsSchema.parse(params.arguments);
8815
8976
  const { project_id, ...options } = args;
@@ -8825,6 +8986,50 @@ async function handleToolCall(params) {
8825
8986
  content: [{ type: "text", text: JSON.stringify(environment) }],
8826
8987
  };
8827
8988
  }
8989
+ case "update_environment": {
8990
+ const { project_id, environment_id, ...body } = UpdateEnvironmentSchema.parse(params.arguments);
8991
+ return { content: [{ type: "text", text: JSON.stringify(await environmentRequest(project_id, `/${encodeGitLabPathSegment(environment_id)}`, "PUT", body)) }] };
8992
+ }
8993
+ case "delete_environment": {
8994
+ const { project_id, environment_id } = GetEnvironmentSchema.parse(params.arguments);
8995
+ return { content: [{ type: "text", text: JSON.stringify(await environmentRequest(project_id, `/${encodeGitLabPathSegment(environment_id)}`, "DELETE")) }] };
8996
+ }
8997
+ case "stop_environment": {
8998
+ const { project_id, environment_id, force } = StopEnvironmentSchema.parse(params.arguments);
8999
+ return { content: [{ type: "text", text: JSON.stringify(await environmentRequest(project_id, `/${encodeGitLabPathSegment(environment_id)}/stop`, "POST", undefined, { force })) }] };
9000
+ }
9001
+ case "stop_stale_environments": {
9002
+ const { project_id, before } = StopStaleEnvironmentsSchema.parse(params.arguments);
9003
+ return { content: [{ type: "text", text: JSON.stringify(await environmentRequest(project_id, "/stop_stale", "POST", undefined, { before })) }] };
9004
+ }
9005
+ case "delete_review_app_environments": {
9006
+ const { project_id, ...query } = DeleteReviewAppEnvironmentsSchema.parse(params.arguments);
9007
+ return { content: [{ type: "text", text: JSON.stringify(await environmentRequest(project_id, "/review_apps", "DELETE", undefined, query)) }] };
9008
+ }
9009
+ case "list_pipeline_triggers": {
9010
+ const { project_id } = ListPipelineTriggersSchema.parse(params.arguments);
9011
+ return { content: [{ type: "text", text: JSON.stringify(redactSensitiveGitLabFields(await pipelineTriggerRequest(project_id))) }] };
9012
+ }
9013
+ case "get_pipeline_trigger": {
9014
+ const { project_id, trigger_id } = PipelineTriggerIdSchema.parse(params.arguments);
9015
+ return { content: [{ type: "text", text: JSON.stringify(redactSensitiveGitLabFields(await pipelineTriggerRequest(project_id, `/${encodeGitLabPathSegment(trigger_id)}`))) }] };
9016
+ }
9017
+ case "create_pipeline_trigger": {
9018
+ const { project_id, ...body } = CreatePipelineTriggerSchema.parse(params.arguments);
9019
+ return { content: [{ type: "text", text: JSON.stringify(await pipelineTriggerRequest(project_id, "", "POST", body)) }] };
9020
+ }
9021
+ case "update_pipeline_trigger": {
9022
+ const { project_id, trigger_id, ...body } = UpdatePipelineTriggerSchema.parse(params.arguments);
9023
+ return { content: [{ type: "text", text: JSON.stringify(redactSensitiveGitLabFields(await pipelineTriggerRequest(project_id, `/${encodeGitLabPathSegment(trigger_id)}`, "PUT", body))) }] };
9024
+ }
9025
+ case "delete_pipeline_trigger": {
9026
+ const { project_id, trigger_id } = PipelineTriggerIdSchema.parse(params.arguments);
9027
+ return { content: [{ type: "text", text: JSON.stringify(await pipelineTriggerRequest(project_id, `/${encodeGitLabPathSegment(trigger_id)}`, "DELETE")) }] };
9028
+ }
9029
+ case "trigger_pipeline": {
9030
+ const { project_id, token, ref, variables, inputs } = TriggerPipelineSchema.parse(params.arguments);
9031
+ return { content: [{ type: "text", text: JSON.stringify(await triggerPipeline(project_id, token, ref, variables, inputs)) }] };
9032
+ }
8828
9033
  case "list_pipeline_jobs": {
8829
9034
  const { project_id, pipeline_id, ...options } = ListPipelineJobsSchema.parse(params.arguments);
8830
9035
  const jobs = await listPipelineJobs(project_id, pipeline_id, options);
@@ -9178,8 +9383,8 @@ async function handleToolCall(params) {
9178
9383
  };
9179
9384
  }
9180
9385
  case "play_pipeline_job": {
9181
- const { project_id, job_id, job_variables_attributes } = PlayPipelineJobSchema.parse(params.arguments);
9182
- const job = await playPipelineJob(project_id, job_id, job_variables_attributes);
9386
+ const { project_id, job_id, job_variables_attributes, job_inputs } = PlayPipelineJobSchema.parse(params.arguments);
9387
+ const job = await playPipelineJob(project_id, job_id, job_variables_attributes, job_inputs);
9183
9388
  return {
9184
9389
  content: [
9185
9390
  {
@@ -9189,9 +9394,14 @@ async function handleToolCall(params) {
9189
9394
  ],
9190
9395
  };
9191
9396
  }
9397
+ case "play_pipeline_jobs": {
9398
+ const { project_id, job_ids, job_variables_attributes, timeout_seconds, poll_interval_seconds } = PlayPipelineJobsSchema.parse(params.arguments);
9399
+ const jobs = await playPipelineJobs(project_id, job_ids, job_variables_attributes, timeout_seconds, poll_interval_seconds);
9400
+ return { content: [{ type: "text", text: JSON.stringify(jobs) }] };
9401
+ }
9192
9402
  case "retry_pipeline_job": {
9193
- const { project_id, job_id } = RetryPipelineJobSchema.parse(params.arguments);
9194
- const job = await retryPipelineJob(project_id, job_id);
9403
+ const { project_id, job_id, job_inputs } = RetryPipelineJobSchema.parse(params.arguments);
9404
+ const job = await retryPipelineJob(project_id, job_id, job_inputs);
9195
9405
  return {
9196
9406
  content: [
9197
9407
  {
@@ -9213,6 +9423,20 @@ async function handleToolCall(params) {
9213
9423
  ],
9214
9424
  };
9215
9425
  }
9426
+ case "erase_pipeline_job": {
9427
+ const { project_id, job_id } = ErasePipelineJobSchema.parse(params.arguments);
9428
+ return { content: [{ type: "text", text: JSON.stringify(await erasePipelineJob(project_id, job_id)) }] };
9429
+ }
9430
+ case "wait_for_pipeline": {
9431
+ const { project_id, pipeline_id, timeout_seconds, poll_interval_seconds } = WaitForPipelineSchema.parse(params.arguments);
9432
+ const pipeline = await waitForStatus(signal => getPipeline(project_id, pipeline_id, signal), TERMINAL_PIPELINE_STATUSES, timeout_seconds, poll_interval_seconds);
9433
+ return { content: [{ type: "text", text: JSON.stringify(pipeline) }] };
9434
+ }
9435
+ case "wait_for_job": {
9436
+ const { project_id, job_id, timeout_seconds, poll_interval_seconds } = WaitForPipelineJobSchema.parse(params.arguments);
9437
+ const job = await waitForStatus(signal => getPipelineJob(project_id, job_id, signal), TERMINAL_JOB_STATUSES, timeout_seconds, poll_interval_seconds);
9438
+ return { content: [{ type: "text", text: JSON.stringify(job) }] };
9439
+ }
9216
9440
  case "list_job_artifacts": {
9217
9441
  const { project_id, job_id, ...options } = ListJobArtifactsSchema.parse(params.arguments);
9218
9442
  const artifacts = await listJobArtifacts(project_id, job_id, options);
package/build/schemas.js CHANGED
@@ -119,6 +119,8 @@ export const GitLabPipelineJobSchema = z.object({
119
119
  web_url: z.string().optional(),
120
120
  allow_failure: z.coerce.boolean().optional(),
121
121
  retried: z.coerce.boolean().optional(),
122
+ when: z.string().optional(),
123
+ needs: z.array(z.unknown()).optional(),
122
124
  tag_list: z.array(z.string()).optional(),
123
125
  runner: z
124
126
  .object({
@@ -257,6 +259,38 @@ export const GetPipelineSchema = z.object({
257
259
  project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
258
260
  pipeline_id: z.coerce.string().describe("The ID of the pipeline"),
259
261
  });
262
+ export const GetPipelineVariablesSchema = GetPipelineSchema.merge(PaginationOptionsSchema);
263
+ export const UpdatePipelineMetadataSchema = GetPipelineSchema.extend({
264
+ name: z.string().min(1).describe("New pipeline name"),
265
+ });
266
+ export const DeletePipelineSchema = GetPipelineSchema;
267
+ export const PipelineReportSchema = GetPipelineSchema.merge(PaginationOptionsSchema);
268
+ export const PlayPipelineJobsSchema = z.object({
269
+ project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
270
+ job_ids: z
271
+ .array(z.coerce.string())
272
+ .min(1)
273
+ .refine((jobIds) => new Set(jobIds).size === jobIds.length, "job_ids must not contain duplicates")
274
+ .describe("Job IDs to play, in dependency order"),
275
+ job_variables_attributes: z
276
+ .array(z.object({ key: z.string(), value: z.string() }))
277
+ .optional()
278
+ .describe("Custom job variables to use when running each job"),
279
+ timeout_seconds: z.coerce
280
+ .number()
281
+ .int()
282
+ .min(1)
283
+ .max(600)
284
+ .optional()
285
+ .describe("Maximum seconds to wait for each job to reach a terminal status (applied per job; total duration scales with the batch size)"),
286
+ poll_interval_seconds: z.coerce
287
+ .number()
288
+ .int()
289
+ .min(1)
290
+ .max(30)
291
+ .optional()
292
+ .describe("Seconds between status polls while waiting for each job"),
293
+ });
260
294
  export const GitLabMergeRequestPipelineSchema = z.object({
261
295
  id: z.coerce.string(),
262
296
  sha: z.string(),
@@ -380,6 +414,28 @@ export const GetCiCatalogResourceSchema = z.union([
380
414
  message: "Provide exactly one of id or full_path",
381
415
  });
382
416
  // Deployment related schemas
417
+ const GitLabDeploymentApprovalUserSchema = z.object({
418
+ id: z.coerce.string().optional(),
419
+ username: z.string().optional(),
420
+ name: z.string().optional(),
421
+ state: z.string().optional(),
422
+ avatar_url: z.string().nullable().optional(),
423
+ web_url: z.string().optional(),
424
+ });
425
+ const GitLabDeploymentApprovalSchema = z.object({
426
+ user: GitLabDeploymentApprovalUserSchema.optional(),
427
+ status: z.string().optional(),
428
+ created_at: z.string().optional(),
429
+ comment: z.string().nullable().optional(),
430
+ });
431
+ const GitLabDeploymentApprovalRuleSchema = z.object({
432
+ user_id: z.coerce.string().nullable().optional(),
433
+ group_id: z.coerce.string().nullable().optional(),
434
+ access_level: z.coerce.number().nullable().optional(),
435
+ access_level_description: z.string().nullable().optional(),
436
+ required_approvals: z.coerce.number().optional(),
437
+ deployment_approvals: z.array(GitLabDeploymentApprovalSchema).optional(),
438
+ });
383
439
  export const GitLabDeploymentSchema = z.object({
384
440
  id: z.coerce.string(),
385
441
  iid: z.coerce.string().optional(),
@@ -427,6 +483,11 @@ export const GitLabDeploymentSchema = z.object({
427
483
  })
428
484
  .optional(),
429
485
  web_url: z.string().optional(),
486
+ pending_approval_count: z.coerce.number().optional(),
487
+ approvals: z.array(GitLabDeploymentApprovalSchema).optional(),
488
+ approval_summary: z
489
+ .object({ rules: z.array(GitLabDeploymentApprovalRuleSchema).optional() })
490
+ .optional(),
430
491
  });
431
492
  export const ListDeploymentsSchema = z
432
493
  .object({
@@ -457,6 +518,19 @@ export const GetDeploymentSchema = z.object({
457
518
  project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
458
519
  deployment_id: z.coerce.string().describe("The ID of the deployment"),
459
520
  });
521
+ export const CreateDeploymentSchema = z.object({
522
+ project_id: z.coerce.string(), environment: z.string(), sha: z.string(), ref: z.string(),
523
+ tag: z.boolean(), status: z.enum(["running", "success", "failed", "canceled"]),
524
+ });
525
+ export const UpdateDeploymentSchema = GetDeploymentSchema.extend({
526
+ status: z.enum(["running", "success", "failed", "canceled"]),
527
+ });
528
+ export const DeploymentApprovalSchema = GetDeploymentSchema.extend({
529
+ status: z.enum(["approved", "rejected"]),
530
+ comment: z.string().optional(),
531
+ represented_as: z.string().optional(),
532
+ });
533
+ export const ListDeploymentMergeRequestsSchema = GetDeploymentSchema.merge(PaginationOptionsSchema);
460
534
  // Environment related schemas
461
535
  const GitLabEnvironmentLastDeploymentSchema = z.object({
462
536
  id: z.coerce.string().optional(),
@@ -496,6 +570,31 @@ export const GetEnvironmentSchema = z.object({
496
570
  project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
497
571
  environment_id: z.coerce.string().describe("The ID of the environment"),
498
572
  });
573
+ export const UpdateEnvironmentSchema = GetEnvironmentSchema.extend({
574
+ external_url: z.string().url().nullable().optional(),
575
+ tier: z.enum(["production", "staging", "testing", "development", "other"]).optional(),
576
+ auto_stop_setting: z.enum(["always", "with_action"]).optional(),
577
+ });
578
+ export const StopEnvironmentSchema = GetEnvironmentSchema.extend({ force: coerceBooleanString.optional() });
579
+ export const StopStaleEnvironmentsSchema = z.object({ project_id: z.coerce.string(), before: z.string() });
580
+ export const DeleteReviewAppEnvironmentsSchema = z.object({
581
+ project_id: z.coerce.string(), before: z.string().optional(), limit: z.coerce.number().optional(),
582
+ dry_run: coerceBooleanString
583
+ .optional()
584
+ .describe("Preview the cleanup when true (default); set to false to schedule deletion one week later"),
585
+ });
586
+ export const PipelineTriggerIdSchema = z.object({ project_id: z.coerce.string(), trigger_id: z.coerce.string() });
587
+ export const ListPipelineTriggersSchema = z.object({ project_id: z.coerce.string() });
588
+ export const CreatePipelineTriggerSchema = z.object({ project_id: z.coerce.string(), description: z.string() });
589
+ export const UpdatePipelineTriggerSchema = PipelineTriggerIdSchema.extend({ description: z.string() });
590
+ export const TriggerPipelineSchema = z.object({
591
+ project_id: z.coerce.string(), token: z.string(), ref: z.string(),
592
+ variables: z.record(z.string()).optional(),
593
+ inputs: z
594
+ .record(z.unknown())
595
+ .optional()
596
+ .describe("Structured pipeline inputs; supported from GitLab 17.10 behind a feature flag and generally available from GitLab 18.1. Omit on older GitLab versions."),
597
+ });
499
598
  // Schema for creating a new pipeline
500
599
  export const CreatePipelineSchema = z.object({
501
600
  project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
@@ -771,15 +870,51 @@ export const PlayPipelineJobSchema = z.object({
771
870
  }))
772
871
  .optional()
773
872
  .describe("Custom job variables to use when running the job"),
873
+ job_inputs: z.record(z.unknown()).optional().describe("Typed job input values"),
774
874
  });
775
875
  // Schema for retrying a job
776
- export const RetryPipelineJobSchema = PipelineJobControlSchema;
876
+ export const RetryPipelineJobSchema = PipelineJobControlSchema.extend({
877
+ job_inputs: z.record(z.unknown()).optional().describe("Typed job input values"),
878
+ });
777
879
  // Schema for canceling a job
778
880
  export const CancelPipelineJobSchema = z.object({
779
881
  project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
780
882
  job_id: z.coerce.string().describe("The ID of the job"),
781
883
  force: z.coerce.boolean().optional().describe("Force cancellation of the job"),
782
884
  });
885
+ export const ErasePipelineJobSchema = PipelineJobControlSchema;
886
+ export const WaitForPipelineSchema = GetPipelineSchema.extend({
887
+ timeout_seconds: z.coerce
888
+ .number()
889
+ .int()
890
+ .min(1)
891
+ .max(600)
892
+ .optional()
893
+ .describe("Maximum seconds to wait for this pipeline to reach a terminal status"),
894
+ poll_interval_seconds: z.coerce
895
+ .number()
896
+ .int()
897
+ .min(1)
898
+ .max(30)
899
+ .optional()
900
+ .describe("Seconds between status polls while waiting for this pipeline"),
901
+ });
902
+ export const WaitForPipelineJobSchema = PipelineJobControlSchema.extend({
903
+ timeout_seconds: z.coerce
904
+ .number()
905
+ .int()
906
+ .min(1)
907
+ .max(600)
908
+ .optional()
909
+ .describe("Maximum seconds to wait for this job to reach a terminal status"),
910
+ poll_interval_seconds: z.coerce
911
+ .number()
912
+ .int()
913
+ .min(1)
914
+ .max(30)
915
+ .optional()
916
+ .describe("Seconds between status polls while waiting for this job"),
917
+ });
783
918
  // User schemas
784
919
  export const GitLabUserSchema = z.object({
785
920
  username: z.string().optional(), // Changed from login to match GitLab API
@@ -2360,8 +2495,7 @@ export const ListGroupMergeRequestsSchema = ListMergeRequestsSchema.omit({
2360
2495
  project_id: true,
2361
2496
  }).extend({
2362
2497
  group_id: z.coerce.string().describe("Group ID or URL-encoded path"),
2363
- non_archived: z.coerce
2364
- .boolean()
2498
+ non_archived: coerceBooleanString
2365
2499
  .optional()
2366
2500
  .describe("Return merge requests from non-archived projects only. Defaults to true."),
2367
2501
  source_project_id: z.coerce
@@ -22,6 +22,11 @@ const REFERENCE_FIELD_ALLOWLIST = new Set([
22
22
  "new_file",
23
23
  "new_path",
24
24
  "old_path",
25
+ "name",
26
+ "page",
27
+ "per_page",
28
+ "pipeline_id",
29
+ "job_inputs",
25
30
  "project_id",
26
31
  "renamed_file",
27
32
  ]);
@@ -62,7 +62,7 @@ async function callTool(toolName, args, env) {
62
62
  env: {
63
63
  ...process.env,
64
64
  ...env,
65
- GITLAB_READ_ONLY_MODE: "true",
65
+ GITLAB_READ_ONLY_MODE: env.GITLAB_READ_ONLY_MODE ?? "true",
66
66
  USE_PIPELINE: "true",
67
67
  },
68
68
  });
@@ -238,8 +238,62 @@ describe("deployment and environment tools", () => {
238
238
  stage: "deploy",
239
239
  pipeline: { id: "190349", status: "success", ref: "master", sha: "abc123" },
240
240
  },
241
+ pending_approval_count: 1,
242
+ approvals: [
243
+ {
244
+ user: {
245
+ id: "100",
246
+ username: "security-user-1",
247
+ name: "Security User",
248
+ state: "active",
249
+ web_url: "https://gitlab.example.com/security-user-1",
250
+ },
251
+ status: "approved",
252
+ created_at: "2026-02-20T16:33:00.000Z",
253
+ comment: "Looks good to me",
254
+ },
255
+ ],
256
+ approval_summary: {
257
+ rules: [
258
+ {
259
+ group_id: "134",
260
+ access_level: null,
261
+ access_level_description: "security-group",
262
+ required_approvals: 2,
263
+ deployment_approvals: [
264
+ {
265
+ user: {
266
+ id: "100",
267
+ username: "security-user-1",
268
+ name: "Security User",
269
+ state: "active",
270
+ web_url: "https://gitlab.example.com/security-user-1",
271
+ },
272
+ status: "approved",
273
+ created_at: "2026-02-20T16:33:00.000Z",
274
+ comment: "Looks good to me",
275
+ },
276
+ ],
277
+ },
278
+ ],
279
+ },
241
280
  });
242
281
  });
282
+ mockGitLab.addMockHandler("post", `/projects/${TEST_PROJECT_ID}/deployments`, (_req, res) => {
283
+ res.status(201).json({ id: "created-deployment", status: "created" });
284
+ });
285
+ mockGitLab.addMockHandler("put", `/projects/${TEST_PROJECT_ID}/deployments/${TEST_DEPLOYMENT_ID}`, (_req, res) => {
286
+ res.json({ id: TEST_DEPLOYMENT_ID, status: "success" });
287
+ });
288
+ mockGitLab.addMockHandler("delete", `/projects/${TEST_PROJECT_ID}/deployments/${TEST_DEPLOYMENT_ID}`, (_req, res) => {
289
+ res.status(204).send();
290
+ });
291
+ mockGitLab.addMockHandler("get", `/projects/${TEST_PROJECT_ID}/deployments/${TEST_DEPLOYMENT_ID}/merge_requests`, (_req, res) => {
292
+ res.json([{ iid: TEST_MERGE_REQUEST_IID, title: "Shipped change" }]);
293
+ });
294
+ mockGitLab.addMockHandler("post", `/projects/${TEST_PROJECT_ID}/deployments/${TEST_DEPLOYMENT_ID}/approval`, (_req, res) => {
295
+ res.json({ id: TEST_DEPLOYMENT_ID, status: "approved" });
296
+ });
243
297
  mockGitLab.addMockHandler("get", `/projects/${TEST_PROJECT_ID}/environments`, (req, res) => {
244
298
  res.json([
245
299
  {
@@ -298,6 +352,35 @@ describe("deployment and environment tools", () => {
298
352
  assert.strictEqual(result.id, TEST_DEPLOYMENT_ID);
299
353
  assert.strictEqual(result.ref, "master");
300
354
  assert.strictEqual(result.status, "success");
355
+ assert.strictEqual(result.pending_approval_count, 1);
356
+ assert.strictEqual(result.approvals[0].status, "approved");
357
+ assert.strictEqual(result.approvals[0].user.username, "security-user-1");
358
+ assert.strictEqual(result.approval_summary.rules[0].group_id, "134");
359
+ assert.strictEqual(result.approval_summary.rules[0].deployment_approvals[0].comment, "Looks good to me");
360
+ });
361
+ test("supports deployment lifecycle and approval operations", async () => {
362
+ const env = {
363
+ GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
364
+ GITLAB_PERSONAL_ACCESS_TOKEN: MOCK_TOKEN,
365
+ GITLAB_READ_ONLY_MODE: "false",
366
+ };
367
+ const created = await callTool("create_deployment", {
368
+ project_id: TEST_PROJECT_ID,
369
+ environment: "stage",
370
+ sha: "abc123",
371
+ ref: "master",
372
+ tag: false,
373
+ status: "running",
374
+ }, env);
375
+ assert.strictEqual(created.id, "created-deployment");
376
+ const updated = await callTool("update_deployment", { project_id: TEST_PROJECT_ID, deployment_id: TEST_DEPLOYMENT_ID, status: "success" }, env);
377
+ assert.strictEqual(updated.status, "success");
378
+ const mergeRequests = await callTool("list_deployment_merge_requests", { project_id: TEST_PROJECT_ID, deployment_id: TEST_DEPLOYMENT_ID }, env);
379
+ assert.strictEqual(mergeRequests[0].iid, TEST_MERGE_REQUEST_IID);
380
+ const approval = await callTool("approve_deployment", { project_id: TEST_PROJECT_ID, deployment_id: TEST_DEPLOYMENT_ID, status: "approved" }, env);
381
+ assert.strictEqual(approval.status, "approved");
382
+ const deleted = await callTool("delete_deployment", { project_id: TEST_PROJECT_ID, deployment_id: TEST_DEPLOYMENT_ID }, env);
383
+ assert.deepStrictEqual(deleted, { success: true });
301
384
  });
302
385
  test("list_environments returns environment list", async () => {
303
386
  const result = await callTool("list_environments", { project_id: TEST_PROJECT_ID }, {
@@ -67,6 +67,7 @@ const MODIFY_SAMPLE_TOOLS = [
67
67
  "create_branch",
68
68
  "push_files",
69
69
  "merge_merge_request",
70
+ "stop_stale_environments",
70
71
  ];
71
72
  const READ_SAMPLE_TOOLS = ["list_issues", "get_project", "list_merge_requests"];
72
73
  describe("Permission Mode", { concurrency: 1 }, () => {
@@ -23,7 +23,7 @@ const TOOLSET_TOOL_COUNTS = {
23
23
  projects: 11,
24
24
  labels: 5,
25
25
  ci: 4,
26
- pipelines: 31,
26
+ pipelines: 56,
27
27
  milestones: 17,
28
28
  wiki: 10,
29
29
  releases: 7,
@@ -2,7 +2,7 @@ import { zodToJsonSchema } from "zod-to-json-schema";
2
2
  import { toJSONSchema } from "../utils/schema.js";
3
3
  import { USE_GITLAB_WIKI, USE_MILESTONE, USE_PIPELINE, SSE, STREAMABLE_HTTP, } from "../config.js";
4
4
  import { getToolDescription } from "./tool-descriptions.js";
5
- import { ApproveMergeRequestSchema, BulkPublishDraftNotesSchema, CancelPipelineJobSchema, CancelPipelineSchema, ConvertWorkItemTypeSchema, CreateBranchSchema, CreateDraftNoteSchema, CreateGroupSchema, CreateGroupWikiPageSchema, CreateIssueLinkSchema, CreateIssueNoteSchema, CreateIssueSchema, CreateIssueEmojiReactionSchema, CreateIssueNoteEmojiReactionSchema, ListIssueEmojiReactionsSchema, ListIssueNoteEmojiReactionsSchema, CreateLabelSchema, MarkAllTodosDoneSchema, ListTodosSchema, MarkTodoDoneSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestNoteSchema, CreateMergeRequestNoteEmojiReactionSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateCommitStatusSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateGroupMilestoneSchema, CreateReleaseEvidenceSchema, CreateReleaseSchema, CreateRepositorySchema, CreateTagSchema, CreateTimelineEventSchema, CreateWebhookSchema, CreateWikiPageSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, CreateWorkItemSchema, DeleteBranchSchema, GetProtectedBranchSchema, ListProtectedBranchesSchema, ProtectBranchSchema, UnprotectBranchSchema, UpdateDefaultBranchSchema, DeleteDraftNoteSchema, DeleteGroupMilestoneSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteMergeRequestDiscussionNoteSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, DeleteProjectMilestoneSchema, DeleteReleaseSchema, DeleteTagSchema, DeleteWebhookSchema, DeleteWikiPageSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, DownloadAttachmentSchema, DownloadAttachmentRemoteSchema, DownloadJobArtifactsSchema, DownloadJobArtifactsRemoteSchema, DownloadReleaseAssetSchema, EditProjectMilestoneSchema, EditGroupMilestoneSchema, ExecuteGraphQLSchema, ForkRepositorySchema, HealthCheckSchema, GetBranchSchema, GetBranchDiffsSchema, GetCommitDiffSchema, GetCommitSchema, GetFileBlameSchema, GetDeploymentSchema, GetDraftNoteSchema, GetEnvironmentSchema, GetFileContentsSchema, GetGroupWikiPageSchema, GetIssueLinkSchema, GetIssueSchema, GetJobArtifactFileSchema, GetLabelSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GetMergeRequestDiffsSchema, GetMergeRequestFileDiffSchema, GetMergeRequestNoteSchema, GetMergeRequestNotesSchema, GetMergeRequestSchema, GetMergeRequestVersionSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetGroupMilestoneSchema, GetGroupMilestoneIssuesSchema, GetGroupMilestoneMergeRequestsSchema, GetGroupMilestoneBurndownEventsSchema, GetNamespaceSchema, GetPipelineJobOutputSchema, PipelineJobControlSchema, GetPipelineSchema, GetProjectEventsSchema, GetProjectMilestoneSchema, GetProjectSchema, GetReleaseSchema, GetRepositoryTreeSchema, GetTagSchema, GetTagSignatureSchema, GetTimelineEventsSchema, GetUsersSchema, GetUserSchema, WhoAmISchema, GetWebhookEventSchema, GetWikiPageSchema, GetWorkItemSchema, ListBranchesSchema, ListCommitsSchema, ListCommitStatusesSchema, ListCustomFieldDefinitionsSchema, ListDeploymentsSchema, ListDraftNotesSchema, ListEnvironmentsSchema, ListEventsSchema, ListGroupIterationsSchema, ListGroupMilestonesSchema, ListGroupProjectsSchema, ListGroupWikiPagesSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListJobArtifactsSchema, ListLabelsSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiffsSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestPipelinesSchema, ListMergeRequestVersionsSchema, ListMergeRequestsSchema, ListGroupMergeRequestsSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListCiCatalogResourcesSchema, GetCiCatalogResourceSchema, ListPipelinesSchema, ListPipelineSchedulesSchema, GetPipelineScheduleSchema, ListPipelineSchedulePipelinesSchema, CreatePipelineScheduleSchema, UpdatePipelineScheduleSchema, DeletePipelineScheduleSchema, PlayPipelineScheduleSchema, TakeOwnershipPipelineScheduleSchema, GetPipelineScheduleVariableSchema, CreatePipelineScheduleVariableSchema, UpdatePipelineScheduleVariableSchema, DeletePipelineScheduleVariableSchema, ListGroupMembersSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListReleasesSchema, ListTagsSchema, ListWebhookEventsSchema, ListWebhooksSchema, ListWikiPagesSchema, ListWorkItemNotesSchema, ListWorkItemStatusesSchema, ListWorkItemsSchema, MarkdownUploadSchema, MarkdownUploadRemoteSchema, MergeMergeRequestSchema, MoveWorkItemSchema, MyIssuesSchema, PlayPipelineJobSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PushFilesSchema, ResolveMergeRequestThreadSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UnapproveMergeRequestSchema, UpdateDraftNoteSchema, UpdateGroupWikiPageSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateIssueDescriptionPatchSchema, UpdateLabelSchema, UpdateProjectSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestSchema, UpdateReleaseSchema, UpdateWebhookSchema, UpdateWikiPageSchema, UpdateWorkItemSchema, VerifyNamespaceSchema, ListProjectVariablesSchema, GetProjectVariableSchema, CreateProjectVariableSchema, UpdateProjectVariableSchema, DeleteProjectVariableSchema, ListGroupVariablesSchema, GetGroupVariableSchema, CreateGroupVariableSchema, UpdateGroupVariableSchema, DeleteGroupVariableSchema, GetDependencyProxySettingsSchema, UpdateDependencyProxySettingsSchema, ListDependencyProxyBlobsSchema, PurgeDependencyProxyCacheSchema, ListProjectVulnerabilitiesSchema, GetVulnerabilitySchema, DismissVulnerabilitySchema, ConfirmVulnerabilitySchema, } from "../schemas.js";
5
+ import { ApproveMergeRequestSchema, BulkPublishDraftNotesSchema, CancelPipelineJobSchema, ErasePipelineJobSchema, CancelPipelineSchema, ConvertWorkItemTypeSchema, CreateBranchSchema, CreateDraftNoteSchema, CreateGroupSchema, CreateGroupWikiPageSchema, CreateIssueLinkSchema, CreateIssueNoteSchema, CreateIssueSchema, CreateIssueEmojiReactionSchema, CreateIssueNoteEmojiReactionSchema, ListIssueEmojiReactionsSchema, ListIssueNoteEmojiReactionsSchema, CreateLabelSchema, MarkAllTodosDoneSchema, ListTodosSchema, MarkTodoDoneSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestNoteSchema, CreateMergeRequestNoteEmojiReactionSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateCommitStatusSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateGroupMilestoneSchema, CreateReleaseEvidenceSchema, CreateReleaseSchema, CreateRepositorySchema, CreateTagSchema, CreateTimelineEventSchema, CreateWebhookSchema, CreateWikiPageSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, CreateWorkItemSchema, DeleteBranchSchema, GetProtectedBranchSchema, ListProtectedBranchesSchema, ProtectBranchSchema, UnprotectBranchSchema, UpdateDefaultBranchSchema, DeleteDraftNoteSchema, DeleteGroupMilestoneSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteMergeRequestDiscussionNoteSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, DeleteProjectMilestoneSchema, DeleteReleaseSchema, DeleteTagSchema, DeleteWebhookSchema, DeleteWikiPageSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, DownloadAttachmentSchema, DownloadAttachmentRemoteSchema, DownloadJobArtifactsSchema, DownloadJobArtifactsRemoteSchema, DownloadReleaseAssetSchema, EditProjectMilestoneSchema, EditGroupMilestoneSchema, ExecuteGraphQLSchema, ForkRepositorySchema, HealthCheckSchema, GetBranchSchema, GetBranchDiffsSchema, GetCommitDiffSchema, GetCommitSchema, GetFileBlameSchema, GetDeploymentSchema, CreateDeploymentSchema, UpdateDeploymentSchema, DeploymentApprovalSchema, ListDeploymentMergeRequestsSchema, GetDraftNoteSchema, GetEnvironmentSchema, UpdateEnvironmentSchema, StopEnvironmentSchema, StopStaleEnvironmentsSchema, DeleteReviewAppEnvironmentsSchema, PipelineTriggerIdSchema, ListPipelineTriggersSchema, CreatePipelineTriggerSchema, UpdatePipelineTriggerSchema, TriggerPipelineSchema, GetFileContentsSchema, GetGroupWikiPageSchema, GetIssueLinkSchema, GetIssueSchema, GetJobArtifactFileSchema, GetLabelSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GetMergeRequestDiffsSchema, GetMergeRequestFileDiffSchema, GetMergeRequestNoteSchema, GetMergeRequestNotesSchema, GetMergeRequestSchema, GetMergeRequestVersionSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetGroupMilestoneSchema, GetGroupMilestoneIssuesSchema, GetGroupMilestoneMergeRequestsSchema, GetGroupMilestoneBurndownEventsSchema, GetNamespaceSchema, GetPipelineJobOutputSchema, PipelineJobControlSchema, GetPipelineSchema, GetPipelineVariablesSchema, UpdatePipelineMetadataSchema, DeletePipelineSchema, PipelineReportSchema, WaitForPipelineSchema, WaitForPipelineJobSchema, PlayPipelineJobsSchema, GetProjectEventsSchema, GetProjectMilestoneSchema, GetProjectSchema, GetReleaseSchema, GetRepositoryTreeSchema, GetTagSchema, GetTagSignatureSchema, GetTimelineEventsSchema, GetUsersSchema, GetUserSchema, WhoAmISchema, GetWebhookEventSchema, GetWikiPageSchema, GetWorkItemSchema, ListBranchesSchema, ListCommitsSchema, ListCommitStatusesSchema, ListCustomFieldDefinitionsSchema, ListDeploymentsSchema, ListDraftNotesSchema, ListEnvironmentsSchema, ListEventsSchema, ListGroupIterationsSchema, ListGroupMilestonesSchema, ListGroupProjectsSchema, ListGroupWikiPagesSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListJobArtifactsSchema, ListLabelsSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiffsSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestPipelinesSchema, ListMergeRequestVersionsSchema, ListMergeRequestsSchema, ListGroupMergeRequestsSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListCiCatalogResourcesSchema, GetCiCatalogResourceSchema, ListPipelinesSchema, ListPipelineSchedulesSchema, GetPipelineScheduleSchema, ListPipelineSchedulePipelinesSchema, CreatePipelineScheduleSchema, UpdatePipelineScheduleSchema, DeletePipelineScheduleSchema, PlayPipelineScheduleSchema, TakeOwnershipPipelineScheduleSchema, GetPipelineScheduleVariableSchema, CreatePipelineScheduleVariableSchema, UpdatePipelineScheduleVariableSchema, DeletePipelineScheduleVariableSchema, ListGroupMembersSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListReleasesSchema, ListTagsSchema, ListWebhookEventsSchema, ListWebhooksSchema, ListWikiPagesSchema, ListWorkItemNotesSchema, ListWorkItemStatusesSchema, ListWorkItemsSchema, MarkdownUploadSchema, MarkdownUploadRemoteSchema, MergeMergeRequestSchema, MoveWorkItemSchema, MyIssuesSchema, PlayPipelineJobSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PushFilesSchema, ResolveMergeRequestThreadSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UnapproveMergeRequestSchema, UpdateDraftNoteSchema, UpdateGroupWikiPageSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateIssueDescriptionPatchSchema, UpdateLabelSchema, UpdateProjectSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestSchema, UpdateReleaseSchema, UpdateWebhookSchema, UpdateWikiPageSchema, UpdateWorkItemSchema, VerifyNamespaceSchema, ListProjectVariablesSchema, GetProjectVariableSchema, CreateProjectVariableSchema, UpdateProjectVariableSchema, DeleteProjectVariableSchema, ListGroupVariablesSchema, GetGroupVariableSchema, CreateGroupVariableSchema, UpdateGroupVariableSchema, DeleteGroupVariableSchema, GetDependencyProxySettingsSchema, UpdateDependencyProxySettingsSchema, ListDependencyProxyBlobsSchema, PurgeDependencyProxyCacheSchema, ListProjectVulnerabilitiesSchema, GetVulnerabilitySchema, DismissVulnerabilitySchema, ConfirmVulnerabilitySchema, } from "../schemas.js";
6
6
  const IS_REMOTE = SSE || STREAMABLE_HTTP;
7
7
  // Define all available tools
8
8
  export const allTools = [
@@ -550,6 +550,15 @@ export const allTools = [
550
550
  description: "Get details of a specific pipeline",
551
551
  inputSchema: toJSONSchema(GetPipelineSchema),
552
552
  },
553
+ {
554
+ name: "get_pipeline_variables",
555
+ description: "Get variables configured for a pipeline",
556
+ inputSchema: toJSONSchema(GetPipelineVariablesSchema),
557
+ },
558
+ { name: "get_pipeline_test_report", description: "Get pipeline test report", inputSchema: toJSONSchema(PipelineReportSchema) },
559
+ { name: "get_pipeline_test_report_summary", description: "Get pipeline test report summary", inputSchema: toJSONSchema(PipelineReportSchema) },
560
+ { name: "delete_pipeline", description: "Delete a pipeline. Requires the project Owner role, cannot be undone, and does not automatically delete child pipelines.", inputSchema: toJSONSchema(DeletePipelineSchema) },
561
+ { name: "update_pipeline_metadata", description: "Update pipeline metadata", inputSchema: toJSONSchema(UpdatePipelineMetadataSchema) },
553
562
  {
554
563
  name: "list_deployments",
555
564
  description: "List deployments with filtering options",
@@ -557,9 +566,14 @@ export const allTools = [
557
566
  },
558
567
  {
559
568
  name: "get_deployment",
560
- description: "Get details of a specific deployment",
569
+ description: "Get deployment details, including approval_summary, approvals, and pending_approval_count when GitLab provides them",
561
570
  inputSchema: toJSONSchema(GetDeploymentSchema),
562
571
  },
572
+ { name: "create_deployment", description: "Create a deployment", inputSchema: toJSONSchema(CreateDeploymentSchema) },
573
+ { name: "update_deployment", description: "Update a deployment status", inputSchema: toJSONSchema(UpdateDeploymentSchema) },
574
+ { name: "delete_deployment", description: "Delete a deployment", inputSchema: toJSONSchema(GetDeploymentSchema) },
575
+ { name: "list_deployment_merge_requests", description: "List merge requests shipped with a deployment", inputSchema: toJSONSchema(ListDeploymentMergeRequestsSchema) },
576
+ { name: "approve_deployment", description: "Approve or reject a protected-environment deployment", inputSchema: toJSONSchema(DeploymentApprovalSchema) },
563
577
  {
564
578
  name: "list_environments",
565
579
  description: "List environments in a project",
@@ -570,6 +584,17 @@ export const allTools = [
570
584
  description: "Get details of a specific environment",
571
585
  inputSchema: toJSONSchema(GetEnvironmentSchema),
572
586
  },
587
+ { name: "update_environment", description: "Update an environment", inputSchema: toJSONSchema(UpdateEnvironmentSchema) },
588
+ { name: "delete_environment", description: "Delete a stopped environment", inputSchema: toJSONSchema(GetEnvironmentSchema) },
589
+ { name: "stop_environment", description: "Stop an environment", inputSchema: toJSONSchema(StopEnvironmentSchema) },
590
+ { name: "stop_stale_environments", description: "Stop eligible stale environments; protected environments are excluded and environments are stopped, not deleted", inputSchema: toJSONSchema(StopStaleEnvironmentsSchema) },
591
+ { name: "delete_review_app_environments", description: "Schedule deletion of stopped review-app environments one week later; dry_run defaults to true and actual scheduling requires dry_run=false", inputSchema: toJSONSchema(DeleteReviewAppEnvironmentsSchema) },
592
+ { name: "list_pipeline_triggers", description: "List project pipeline trigger tokens", inputSchema: toJSONSchema(ListPipelineTriggersSchema) },
593
+ { name: "get_pipeline_trigger", description: "Get a project pipeline trigger", inputSchema: toJSONSchema(PipelineTriggerIdSchema) },
594
+ { name: "create_pipeline_trigger", description: "Create a project pipeline trigger", inputSchema: toJSONSchema(CreatePipelineTriggerSchema) },
595
+ { name: "update_pipeline_trigger", description: "Update a project pipeline trigger", inputSchema: toJSONSchema(UpdatePipelineTriggerSchema) },
596
+ { name: "delete_pipeline_trigger", description: "Delete a project pipeline trigger", inputSchema: toJSONSchema(PipelineTriggerIdSchema) },
597
+ { name: "trigger_pipeline", description: "Trigger a pipeline with a pipeline trigger token", inputSchema: toJSONSchema(TriggerPipelineSchema) },
573
598
  {
574
599
  name: "list_pipeline_jobs",
575
600
  description: "List all jobs in a specific pipeline",
@@ -690,6 +715,11 @@ export const allTools = [
690
715
  description: "Run a manual pipeline job",
691
716
  inputSchema: toJSONSchema(PlayPipelineJobSchema),
692
717
  },
718
+ {
719
+ name: "play_pipeline_jobs",
720
+ description: "Play multiple manual pipeline jobs sequentially",
721
+ inputSchema: toJSONSchema(PlayPipelineJobsSchema),
722
+ },
693
723
  {
694
724
  name: "retry_pipeline_job",
695
725
  description: "Retry a failed or canceled pipeline job",
@@ -700,6 +730,9 @@ export const allTools = [
700
730
  description: "Cancel a running pipeline job",
701
731
  inputSchema: toJSONSchema(CancelPipelineJobSchema),
702
732
  },
733
+ { name: "erase_pipeline_job", description: "Erase a pipeline job log and artifacts", inputSchema: toJSONSchema(ErasePipelineJobSchema) },
734
+ { name: "wait_for_pipeline", description: "Wait for a pipeline to reach a terminal status", inputSchema: toJSONSchema(WaitForPipelineSchema) },
735
+ { name: "wait_for_job", description: "Wait for a job to reach a terminal status", inputSchema: toJSONSchema(WaitForPipelineJobSchema) },
703
736
  {
704
737
  name: "list_job_artifacts",
705
738
  description: "List artifact files in a job's archive",
@@ -1250,6 +1283,9 @@ export const readOnlyTools = new Set([
1250
1283
  "list_project_members",
1251
1284
  "list_group_members",
1252
1285
  "get_pipeline",
1286
+ "get_pipeline_variables",
1287
+ "get_pipeline_test_report",
1288
+ "get_pipeline_test_report_summary",
1253
1289
  "list_pipelines",
1254
1290
  "list_pipeline_schedules",
1255
1291
  "get_pipeline_schedule",
@@ -1257,12 +1293,17 @@ export const readOnlyTools = new Set([
1257
1293
  "get_pipeline_schedule_variable",
1258
1294
  "list_deployments",
1259
1295
  "get_deployment",
1296
+ "list_deployment_merge_requests",
1260
1297
  "list_environments",
1261
1298
  "get_environment",
1299
+ "list_pipeline_triggers",
1300
+ "get_pipeline_trigger",
1262
1301
  "list_pipeline_jobs",
1263
1302
  "list_pipeline_trigger_jobs",
1264
1303
  "get_pipeline_job",
1265
1304
  "get_pipeline_job_output",
1305
+ "wait_for_pipeline",
1306
+ "wait_for_job",
1266
1307
  "validate_ci_lint",
1267
1308
  "validate_project_ci_lint",
1268
1309
  "list_ci_catalog_resources",
@@ -1335,6 +1376,13 @@ export const readOnlyTools = new Set([
1335
1376
  ]);
1336
1377
  // Define which tools are destructive (data loss potential)
1337
1378
  export const destructiveTools = new Set([
1379
+ "delete_pipeline",
1380
+ "erase_pipeline_job",
1381
+ "delete_deployment",
1382
+ "approve_deployment",
1383
+ "delete_environment",
1384
+ "delete_review_app_environments",
1385
+ "delete_pipeline_trigger",
1338
1386
  "delete_issue",
1339
1387
  "delete_issue_link",
1340
1388
  "delete_label",
@@ -1369,6 +1417,12 @@ export const destructiveTools = new Set([
1369
1417
  // Tools that permanently delete resources — blocked in "modify" permission mode.
1370
1418
  // Narrower than destructiveTools: merge/protect/push are modifications, not deletions.
1371
1419
  export const deleteTools = new Set([
1420
+ "delete_pipeline",
1421
+ "erase_pipeline_job",
1422
+ "delete_deployment",
1423
+ "delete_environment",
1424
+ "delete_review_app_environments",
1425
+ "delete_pipeline_trigger",
1372
1426
  "delete_branch",
1373
1427
  "delete_draft_note",
1374
1428
  "delete_group_variable",
@@ -1433,14 +1487,39 @@ export const milestoneToolNames = new Set([
1433
1487
  export const pipelineToolNames = new Set([
1434
1488
  "list_pipelines",
1435
1489
  "get_pipeline",
1490
+ "get_pipeline_variables",
1491
+ "get_pipeline_test_report",
1492
+ "get_pipeline_test_report_summary",
1493
+ "delete_pipeline",
1494
+ "update_pipeline_metadata",
1436
1495
  "list_deployments",
1437
1496
  "get_deployment",
1497
+ "create_deployment",
1498
+ "update_deployment",
1499
+ "delete_deployment",
1500
+ "list_deployment_merge_requests",
1501
+ "approve_deployment",
1438
1502
  "list_environments",
1439
1503
  "get_environment",
1504
+ "update_environment",
1505
+ "delete_environment",
1506
+ "stop_environment",
1507
+ "stop_stale_environments",
1508
+ "delete_review_app_environments",
1509
+ "list_pipeline_triggers",
1510
+ "get_pipeline_trigger",
1511
+ "create_pipeline_trigger",
1512
+ "update_pipeline_trigger",
1513
+ "delete_pipeline_trigger",
1514
+ "trigger_pipeline",
1440
1515
  "list_pipeline_jobs",
1441
1516
  "list_pipeline_trigger_jobs",
1442
1517
  "get_pipeline_job",
1443
1518
  "get_pipeline_job_output",
1519
+ "play_pipeline_jobs",
1520
+ "erase_pipeline_job",
1521
+ "wait_for_pipeline",
1522
+ "wait_for_job",
1444
1523
  "validate_ci_lint",
1445
1524
  "validate_project_ci_lint",
1446
1525
  "create_pipeline",
@@ -1629,10 +1708,31 @@ export const TOOLSET_DEFINITIONS = [
1629
1708
  tools: new Set([
1630
1709
  "list_pipelines",
1631
1710
  "get_pipeline",
1711
+ "get_pipeline_variables",
1712
+ "get_pipeline_test_report",
1713
+ "get_pipeline_test_report_summary",
1714
+ "delete_pipeline",
1715
+ "update_pipeline_metadata",
1632
1716
  "list_deployments",
1633
1717
  "get_deployment",
1718
+ "create_deployment",
1719
+ "update_deployment",
1720
+ "delete_deployment",
1721
+ "list_deployment_merge_requests",
1722
+ "approve_deployment",
1634
1723
  "list_environments",
1635
1724
  "get_environment",
1725
+ "update_environment",
1726
+ "delete_environment",
1727
+ "stop_environment",
1728
+ "stop_stale_environments",
1729
+ "delete_review_app_environments",
1730
+ "list_pipeline_triggers",
1731
+ "get_pipeline_trigger",
1732
+ "create_pipeline_trigger",
1733
+ "update_pipeline_trigger",
1734
+ "delete_pipeline_trigger",
1735
+ "trigger_pipeline",
1636
1736
  "list_pipeline_jobs",
1637
1737
  "list_pipeline_trigger_jobs",
1638
1738
  "get_pipeline_job",
@@ -1653,8 +1753,12 @@ export const TOOLSET_DEFINITIONS = [
1653
1753
  "update_pipeline_schedule_variable",
1654
1754
  "delete_pipeline_schedule_variable",
1655
1755
  "play_pipeline_job",
1756
+ "play_pipeline_jobs",
1656
1757
  "retry_pipeline_job",
1657
1758
  "cancel_pipeline_job",
1759
+ "erase_pipeline_job",
1760
+ "wait_for_pipeline",
1761
+ "wait_for_job",
1658
1762
  "list_job_artifacts",
1659
1763
  "download_job_artifacts",
1660
1764
  "get_job_artifact_file",
@@ -46,6 +46,8 @@ const TOOL_GUIDANCE = {
46
46
  create_pipeline_schedule_variable: "Use this to attach a new variable to a pipeline schedule so its scheduled pipelines receive it; use `update_pipeline_schedule_variable` when the key already exists and `create_project_variable` for a project-wide variable. `variable_type` defaults to `env_var` and `key` is limited to 255 characters matching /[a-zA-Z0-9_]+/. It creates remote CI configuration, requires the Developer, Maintainer, or Owner role and ownership of the schedule, and returns the new variable or a validation, duplicate-key, or permission error.",
47
47
  update_pipeline_schedule_variable: "Use this to change the value or type of an existing pipeline schedule variable; use `create_pipeline_schedule_variable` when the key does not exist yet. It changes remote CI configuration that scheduled pipelines consume, requires the Developer, Maintainer, or Owner role and ownership of the schedule, and returns the updated variable or a missing-key, validation, or permission error.",
48
48
  delete_pipeline_schedule_variable: "Use this only after confirming the key with `get_pipeline_schedule` or `get_pipeline_schedule_variable`; use `update_pipeline_schedule_variable` to change a value without removing it. The operation permanently removes the variable from the schedule so later scheduled pipelines no longer receive it, requires the Developer, Maintainer, or Owner role and ownership of the schedule, and returns a confirmation or a missing-key or permission error.",
49
+ stop_stale_environments: "Use this to stop eligible stale environments in a project. GitLab excludes protected environments, and this operation stops environments without deleting them. It requires the necessary project permission and returns the result or a validation, permission, or rate-limit error.",
50
+ delete_review_app_environments: "Use this to clean up stopped review-app environments after verifying the scope. GitLab schedules deletion one week later rather than deleting environments immediately; `dry_run` defaults to `true`, so actual scheduling requires `dry_run: false`. It requires the necessary project permission and returns the cleanup result or a validation, permission, or rate-limit error.",
49
51
  };
50
52
  const PARAMETER_GUIDANCE = "When `project_id` or `group_id` is accepted, provide the numeric ID or complete URL-encoded path described by the schema; use required identifiers and pagination fields exactly as documented.";
51
53
  function ensureSentence(text) {
@@ -3,16 +3,23 @@
3
3
  * MCP tool results become part of the AI conversation context, so these must
4
4
  * never be forwarded to the model.
5
5
  */
6
- const SENSITIVE_GITLAB_FIELDS = ["runners_token", "import_url"];
6
+ const SENSITIVE_GITLAB_FIELDS = ["runners_token", "import_url", "token"];
7
7
  /**
8
8
  * Strip known sensitive fields from a GitLab API response object before it is
9
9
  * returned as an MCP tool result. Mutates and returns the same object.
10
+ * Recurses into arrays (e.g. list responses) since those hold one object per item.
10
11
  *
11
- * Used for raw passthrough responses (get_project / update_project) that do not
12
- * go through a Zod schema, since Zod would otherwise strip unknown keys for us.
12
+ * Used for raw passthrough responses (get_project / update_project,
13
+ * list/get_pipeline_trigger) that do not go through a Zod schema, since Zod
14
+ * would otherwise strip unknown keys for us. Do NOT apply to
15
+ * create_pipeline_trigger: its response carries the newly issued token once.
13
16
  */
14
17
  export function redactSensitiveGitLabFields(data) {
15
- if (data && typeof data === "object") {
18
+ if (Array.isArray(data)) {
19
+ for (const item of data)
20
+ redactSensitiveGitLabFields(item);
21
+ }
22
+ else if (data && typeof data === "object") {
16
23
  for (const key of SENSITIVE_GITLAB_FIELDS) {
17
24
  delete data[key];
18
25
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zereight/mcp-gitlab",
3
- "version": "2.1.58",
3
+ "version": "2.1.59",
4
4
  "mcpName": "io.github.zereight/gitlab-mcp",
5
5
  "description": "GitLab MCP server for projects, merge requests, issues, pipelines, wiki, releases, and more",
6
6
  "keywords": [