@zereight/mcp-gitlab 2.0.30 → 2.0.33

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/oauth.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as fs from "fs";
2
+ import * as os from "os";
2
3
  import * as path from "path";
3
4
  import * as http from "http";
4
5
  import * as net from "net";
@@ -82,7 +83,7 @@ export class GitLabOAuth {
82
83
  constructor(config) {
83
84
  this.config = config;
84
85
  this.tokenStoragePath =
85
- config.tokenStoragePath || path.join(process.env.HOME || "", ".gitlab-mcp-token.json");
86
+ config.tokenStoragePath || path.join(os.homedir(), ".gitlab-mcp-token.json");
86
87
  }
87
88
  /**
88
89
  * Get the authorization URL for OAuth flow
@@ -505,9 +506,11 @@ export class GitLabOAuth {
505
506
  }
506
507
  }
507
508
  /**
508
- * Initialize OAuth authentication for GitLab MCP server
509
+ * Create and initialize a GitLabOAuth client.
510
+ * Performs initial authentication (triggers browser flow if needed).
511
+ * Returns the client instance and the initial access token.
509
512
  */
510
- export async function initializeOAuth(gitlabUrl = "https://gitlab.com") {
513
+ export async function initializeOAuthClient(gitlabUrl = "https://gitlab.com") {
511
514
  const clientId = process.env.GITLAB_OAUTH_CLIENT_ID;
512
515
  const clientSecret = process.env.GITLAB_OAUTH_CLIENT_SECRET;
513
516
  const redirectUri = process.env.GITLAB_OAUTH_REDIRECT_URI || "http://127.0.0.1:8888/callback";
@@ -523,5 +526,14 @@ export async function initializeOAuth(gitlabUrl = "https://gitlab.com") {
523
526
  scopes: ["api"],
524
527
  tokenStoragePath,
525
528
  });
526
- return await oauth.getAccessToken();
529
+ // Single call: triggers browser flow if needed, or reads cached token
530
+ const accessToken = await oauth.getAccessToken();
531
+ return { client: oauth, accessToken };
532
+ }
533
+ /**
534
+ * Initialize OAuth authentication for GitLab MCP server
535
+ */
536
+ export async function initializeOAuth(gitlabUrl = "https://gitlab.com") {
537
+ const { accessToken } = await initializeOAuthClient(gitlabUrl);
538
+ return accessToken;
527
539
  }
package/build/schemas.js CHANGED
@@ -62,6 +62,8 @@ export const GitLabPipelineJobSchema = z.object({
62
62
  started_at: z.string().nullable().optional(),
63
63
  finished_at: z.string().nullable().optional(),
64
64
  duration: z.number().nullable().optional(),
65
+ queued_duration: z.number().nullable().optional(),
66
+ failure_reason: z.string().nullable().optional(),
65
67
  user: z
66
68
  .object({
67
69
  id: z.coerce.string(),
@@ -89,6 +91,19 @@ export const GitLabPipelineJobSchema = z.object({
89
91
  })
90
92
  .optional(),
91
93
  web_url: z.string().optional(),
94
+ allow_failure: z.boolean().optional(),
95
+ retried: z.boolean().optional(),
96
+ tag_list: z.array(z.string()).optional(),
97
+ runner: z
98
+ .object({
99
+ id: z.coerce.string().optional(),
100
+ description: z.string().nullable().optional(),
101
+ active: z.boolean().optional(),
102
+ is_shared: z.boolean().optional(),
103
+ runner_type: z.string().optional(),
104
+ })
105
+ .nullable()
106
+ .optional(),
92
107
  });
93
108
  // Pipeline trigger job (bridge) schema
94
109
  export const GitLabPipelineTriggerJobSchema = z.object({
@@ -247,6 +262,126 @@ export const ListPipelineTriggerJobsSchema = z
247
262
  .describe("The scope of trigger jobs to show"),
248
263
  })
249
264
  .merge(PaginationOptionsSchema);
265
+ // Deployment related schemas
266
+ export const GitLabDeploymentSchema = z.object({
267
+ id: z.coerce.string(),
268
+ iid: z.coerce.string().optional(),
269
+ status: z.string(),
270
+ ref: z.string().optional(),
271
+ sha: z.string(),
272
+ created_at: z.string(),
273
+ updated_at: z.string().optional(),
274
+ finished_at: z.string().nullable().optional(),
275
+ environment: z
276
+ .object({
277
+ id: z.coerce.string().optional(),
278
+ name: z.string(),
279
+ slug: z.string().optional(),
280
+ external_url: z.string().nullable().optional(),
281
+ state: z.string().optional(),
282
+ tier: z.string().optional(),
283
+ })
284
+ .optional(),
285
+ deployable: z
286
+ .object({
287
+ id: z.coerce.string().optional(),
288
+ name: z.string().optional(),
289
+ status: z.string().optional(),
290
+ stage: z.string().optional(),
291
+ web_url: z.string().optional(),
292
+ pipeline: z
293
+ .object({
294
+ id: z.coerce.string().optional(),
295
+ status: z.string().optional(),
296
+ ref: z.string().optional(),
297
+ sha: z.string().optional(),
298
+ web_url: z.string().optional(),
299
+ })
300
+ .optional(),
301
+ })
302
+ .nullable()
303
+ .optional(),
304
+ user: z
305
+ .object({
306
+ id: z.coerce.string().optional(),
307
+ username: z.string().optional(),
308
+ name: z.string().optional(),
309
+ avatar_url: z.string().nullable().optional(),
310
+ })
311
+ .optional(),
312
+ web_url: z.string().optional(),
313
+ });
314
+ export const ListDeploymentsSchema = z
315
+ .object({
316
+ project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
317
+ environment: z.string().optional().describe("Filter by environment name"),
318
+ ref: z.string().optional().describe("Filter by ref"),
319
+ sha: z
320
+ .string()
321
+ .optional()
322
+ .describe("Filter by commit SHA (if supported by your GitLab version)"),
323
+ status: z.string().optional().describe("Filter by deployment status"),
324
+ updated_after: z
325
+ .string()
326
+ .optional()
327
+ .describe("Return deployments updated after the specified date"),
328
+ updated_before: z
329
+ .string()
330
+ .optional()
331
+ .describe("Return deployments updated before the specified date"),
332
+ order_by: z
333
+ .enum(["id", "iid", "created_at", "updated_at", "ref", "status", "environment"])
334
+ .optional()
335
+ .describe("Order deployments by"),
336
+ sort: z.enum(["asc", "desc"]).optional().describe("Sort deployments"),
337
+ })
338
+ .merge(PaginationOptionsSchema);
339
+ export const GetDeploymentSchema = z.object({
340
+ project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
341
+ deployment_id: z.coerce.string().describe("The ID of the deployment"),
342
+ });
343
+ // Environment related schemas
344
+ const GitLabEnvironmentLastDeploymentSchema = z.object({
345
+ id: z.coerce.string().optional(),
346
+ iid: z.coerce.string().optional(),
347
+ status: z.string().optional(),
348
+ ref: z.string().optional(),
349
+ sha: z.string().optional(),
350
+ created_at: z.string().optional(),
351
+ updated_at: z.string().optional(),
352
+ web_url: z.string().optional(),
353
+ });
354
+ export const GitLabEnvironmentSchema = z.object({
355
+ id: z.coerce.string(),
356
+ name: z.string(),
357
+ slug: z.string().optional(),
358
+ external_url: z.string().nullable().optional(),
359
+ state: z.string().optional(),
360
+ tier: z.string().optional(),
361
+ environment_type: z.string().optional(),
362
+ created_at: z.string().optional(),
363
+ updated_at: z.string().optional(),
364
+ auto_stop_at: z.string().nullable().optional(),
365
+ enable_advanced_logs_querying: z.boolean().optional(),
366
+ logs_api_path: z.string().optional(),
367
+ web_url: z.string().optional(),
368
+ last_deployment: GitLabEnvironmentLastDeploymentSchema.nullable().optional(),
369
+ });
370
+ export const ListEnvironmentsSchema = z
371
+ .object({
372
+ project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
373
+ name: z.string().optional().describe("Return environments with this exact name"),
374
+ search: z.string().optional().describe("Search environments by name"),
375
+ states: z
376
+ .enum(["available", "stopped"])
377
+ .optional()
378
+ .describe("Filter environments by state"),
379
+ })
380
+ .merge(PaginationOptionsSchema);
381
+ export const GetEnvironmentSchema = z.object({
382
+ project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
383
+ environment_id: z.coerce.string().describe("The ID of the environment"),
384
+ });
250
385
  // Schema for creating a new pipeline
251
386
  export const CreatePipelineSchema = z.object({
252
387
  project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
@@ -258,6 +393,10 @@ export const CreatePipelineSchema = z.object({
258
393
  }))
259
394
  .optional()
260
395
  .describe("An array of variables to use for the pipeline"),
396
+ inputs: z
397
+ .record(z.string(), z.string())
398
+ .optional()
399
+ .describe("Input parameters for the pipeline (key-value pairs for spec:inputs)"),
261
400
  });
262
401
  // Schema for retrying a pipeline
263
402
  export const RetryPipelineSchema = z.object({
@@ -436,17 +575,17 @@ export const GitLabRepositorySchema = z.object({
436
575
  export const GitLabProjectSchema = GitLabRepositorySchema;
437
576
  // File content schemas
438
577
  export const GitLabFileContentSchema = z.object({
439
- file_name: z.string(), // Changed from name to match GitLab API
440
- file_path: z.string(), // Changed from path to match GitLab API
441
- size: z.number(),
578
+ file_name: z.string().optional(),
579
+ file_path: z.string(),
580
+ size: z.coerce.number().optional(),
442
581
  encoding: z.string(),
443
582
  content: z.string(),
444
- content_sha256: z.string(), // Changed from sha to match GitLab API
445
- ref: z.string(), // Added as GitLab requires branch reference
446
- blob_id: z.string(), // Added to match GitLab API
447
- commit_id: z.string(), // ID of the current file version
448
- last_commit_id: z.string(), // Added to match GitLab API
449
- execute_filemode: z.boolean().optional(), // Added to match GitLab API
583
+ content_sha256: z.string().optional(),
584
+ ref: z.string().optional(),
585
+ blob_id: z.string().optional(),
586
+ commit_id: z.string().optional(),
587
+ last_commit_id: z.string().optional(),
588
+ execute_filemode: z.boolean().optional(),
450
589
  });
451
590
  export const GitLabDirectoryContentSchema = z.object({
452
591
  name: z.string(),
@@ -713,6 +852,15 @@ export const GitLabMergeRequestSchema = z.object({
713
852
  allow_collaboration: z.boolean().optional(),
714
853
  allow_maintainer_to_push: z.boolean().optional(),
715
854
  changes_count: z.string().nullable().optional(),
855
+ diverged_commits_count: z.coerce
856
+ .number()
857
+ .nullable()
858
+ .optional()
859
+ .describe("Number of commits the source branch is behind the target branch"),
860
+ rebase_in_progress: z
861
+ .boolean()
862
+ .optional()
863
+ .describe("Whether rebase is currently in progress for this merge request"),
716
864
  merge_when_pipeline_succeeds: z.boolean().optional(),
717
865
  squash: z.boolean().optional(),
718
866
  labels: z.array(z.string()).optional(),
@@ -953,10 +1101,43 @@ export const CreateRepositorySchema = z.object({
953
1101
  .describe("Repository visibility level"),
954
1102
  initialize_with_readme: z.boolean().optional().describe("Initialize with README.md"),
955
1103
  });
956
- export const GetFileContentsSchema = ProjectParamsSchema.extend({
957
- file_path: z.string().describe("Path to the file or directory"),
1104
+ export const GetFileContentsSchema = z
1105
+ .object({
1106
+ project_id: z.coerce
1107
+ .string()
1108
+ .optional()
1109
+ .describe("Project ID or URL-encoded path (optional; falls back to env)"),
1110
+ file_path: z
1111
+ .string()
1112
+ .optional()
1113
+ .describe("Path to the file or directory. Takes precedence over 'path' when both are provided"),
1114
+ path: z.string().optional().describe("Alias of file_path"),
958
1115
  ref: z.string().optional().describe("Branch/tag/commit to get contents from"),
959
- });
1116
+ })
1117
+ .superRefine((data, ctx) => {
1118
+ const fp = data.file_path?.trim();
1119
+ const p = data.path?.trim();
1120
+ if (!fp && !p) {
1121
+ ctx.addIssue({
1122
+ code: z.ZodIssueCode.custom,
1123
+ message: "Either 'file_path' or 'path' must be provided",
1124
+ path: ["file_path"],
1125
+ });
1126
+ }
1127
+ const finalPath = fp && fp.length > 0 ? fp : p ?? "";
1128
+ if (finalPath.trim().length === 0) {
1129
+ ctx.addIssue({
1130
+ code: z.ZodIssueCode.custom,
1131
+ message: "file_path cannot be empty or whitespace",
1132
+ path: ["file_path"],
1133
+ });
1134
+ }
1135
+ })
1136
+ .transform(data => ({
1137
+ project_id: (data.project_id ?? "").trim() || undefined,
1138
+ file_path: ((data.file_path ?? "").trim() || (data.path ?? "").trim()).trim(),
1139
+ ref: (data.ref ?? "").trim() || undefined,
1140
+ }));
960
1141
  export const PushFilesSchema = ProjectParamsSchema.extend({
961
1142
  branch: z.string().describe("Branch to push to"),
962
1143
  files: z
@@ -1129,9 +1310,25 @@ export const GitLabApprovalRuleSchema = z.object({
1129
1310
  .optional(),
1130
1311
  approved: z.boolean().optional(),
1131
1312
  });
1313
+ export const GitLabMergeRequestApprovalsResponseSchema = z.object({
1314
+ approved: z.boolean().optional(),
1315
+ user_has_approved: z.boolean().optional(),
1316
+ user_can_approve: z.boolean().optional(),
1317
+ approved_by: z
1318
+ .array(z.object({
1319
+ user: GitLabApprovalUserSchema,
1320
+ }))
1321
+ .optional(),
1322
+ });
1132
1323
  export const GitLabMergeRequestApprovalStateSchema = z.object({
1133
1324
  approval_rules_overwritten: z.boolean().optional(),
1134
1325
  rules: z.array(GitLabApprovalRuleSchema).optional(),
1326
+ approved: z.boolean().optional(),
1327
+ user_has_approved: z.boolean().optional(),
1328
+ user_can_approve: z.boolean().optional(),
1329
+ approved_by: z.array(GitLabApprovalUserSchema).optional(),
1330
+ approved_by_usernames: z.array(z.string()).optional(),
1331
+ source_endpoint: z.enum(["approval_state", "approvals"]).optional(),
1135
1332
  });
1136
1333
  export const GetMergeRequestApprovalStateSchema = ProjectParamsSchema.extend({
1137
1334
  merge_request_iid: z.coerce.string().describe("The IID of the merge request"),
@@ -2210,6 +2407,41 @@ export const CreateReleaseEvidenceSchema = z.object({
2210
2407
  project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
2211
2408
  tag_name: z.string().describe("The Git tag the release is associated with"),
2212
2409
  });
2410
+ // Job Artifacts schemas
2411
+ export const ListJobArtifactsSchema = z.object({
2412
+ project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
2413
+ job_id: z.coerce.string().describe("The ID of the job"),
2414
+ path: z
2415
+ .string()
2416
+ .optional()
2417
+ .describe("Directory path within the artifacts archive (defaults to root)"),
2418
+ recursive: z
2419
+ .boolean()
2420
+ .optional()
2421
+ .describe("Whether to list artifacts recursively"),
2422
+ });
2423
+ export const GitLabArtifactEntrySchema = z.object({
2424
+ name: z.string(),
2425
+ path: z.string(),
2426
+ type: z.enum(["file", "directory"]),
2427
+ size: z.number().optional(),
2428
+ mode: z.string().optional(),
2429
+ });
2430
+ export const DownloadJobArtifactsSchema = z.object({
2431
+ project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
2432
+ job_id: z.coerce.string().describe("The ID of the job"),
2433
+ local_path: z
2434
+ .string()
2435
+ .optional()
2436
+ .describe("Local directory to save the artifact archive (defaults to current directory)"),
2437
+ });
2438
+ export const GetJobArtifactFileSchema = z.object({
2439
+ project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
2440
+ job_id: z.coerce.string().describe("The ID of the job"),
2441
+ artifact_path: z
2442
+ .string()
2443
+ .describe("Path to the file within the artifacts archive"),
2444
+ });
2213
2445
  export const DownloadReleaseAssetSchema = z.object({
2214
2446
  project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
2215
2447
  tag_name: z.string().describe("The Git tag the release is associated with"),
@@ -2217,3 +2449,69 @@ export const DownloadReleaseAssetSchema = z.object({
2217
2449
  .string()
2218
2450
  .describe("Path to the release asset file as specified when creating or updating its link"),
2219
2451
  });
2452
+ // --- Webhook schemas ---
2453
+ export const ListWebhooksSchema = z
2454
+ .object({
2455
+ project_id: z.coerce
2456
+ .string()
2457
+ .optional()
2458
+ .describe("Project ID or URL-encoded path. Provide either project_id or group_id, not both."),
2459
+ group_id: z.coerce
2460
+ .string()
2461
+ .optional()
2462
+ .describe("Group ID or URL-encoded path. Provide either project_id or group_id, not both."),
2463
+ })
2464
+ .merge(PaginationOptionsSchema)
2465
+ .refine(data => (data.project_id || data.group_id) && !(data.project_id && data.group_id), {
2466
+ message: "Provide exactly one of project_id or group_id",
2467
+ });
2468
+ export const ListWebhookEventsSchema = z
2469
+ .object({
2470
+ project_id: z.coerce
2471
+ .string()
2472
+ .optional()
2473
+ .describe("Project ID or URL-encoded path. Provide either project_id or group_id, not both."),
2474
+ group_id: z.coerce
2475
+ .string()
2476
+ .optional()
2477
+ .describe("Group ID or URL-encoded path. Provide either project_id or group_id, not both."),
2478
+ hook_id: z.coerce.number().describe("ID of the webhook"),
2479
+ status: z
2480
+ .union([z.number(), z.string()])
2481
+ .optional()
2482
+ .describe("Filter by response status code (e.g. 200, 500) or category: successful, client_failure, server_failure"),
2483
+ summary: z
2484
+ .boolean()
2485
+ .optional()
2486
+ .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."),
2487
+ per_page: z
2488
+ .number()
2489
+ .max(20)
2490
+ .optional()
2491
+ .default(20)
2492
+ .describe("Number of events per page"),
2493
+ page: z.number().optional().describe("Page number for pagination"),
2494
+ })
2495
+ .refine(data => (data.project_id || data.group_id) && !(data.project_id && data.group_id), {
2496
+ message: "Provide exactly one of project_id or group_id",
2497
+ });
2498
+ export const GetWebhookEventSchema = z
2499
+ .object({
2500
+ project_id: z.coerce
2501
+ .string()
2502
+ .optional()
2503
+ .describe("Project ID or URL-encoded path. Provide either project_id or group_id, not both."),
2504
+ group_id: z.coerce
2505
+ .string()
2506
+ .optional()
2507
+ .describe("Group ID or URL-encoded path. Provide either project_id or group_id, not both."),
2508
+ hook_id: z.coerce.number().describe("ID of the webhook"),
2509
+ event_id: z.coerce.number().describe("ID of the webhook event to retrieve"),
2510
+ page: z
2511
+ .number()
2512
+ .optional()
2513
+ .describe("If known, the page where the event is located (from list_webhook_events). Skips auto-pagination and fetches only this page."),
2514
+ })
2515
+ .refine(data => (data.project_id || data.group_id) && !(data.project_id && data.group_id), {
2516
+ message: "Provide exactly one of project_id or group_id",
2517
+ });