@zereight/mcp-gitlab 1.0.72 → 1.0.75
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/customSchemas.js +19 -0
- package/build/index.js +18 -9
- package/build/schemas.js +115 -126
- package/package.json +1 -1
- package/build/tests/integration.test.js +0 -151
- package/build/tests/unit.test.js +0 -122
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { pino } from 'pino';
|
|
3
|
+
const logger = pino({
|
|
4
|
+
level: process.env.LOG_LEVEL || 'info',
|
|
5
|
+
transport: {
|
|
6
|
+
target: 'pino-pretty',
|
|
7
|
+
options: {
|
|
8
|
+
colorize: true,
|
|
9
|
+
levelFirst: true,
|
|
10
|
+
destination: 2,
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
export const flexibleBoolean = z.preprocess((val) => {
|
|
15
|
+
if (typeof val === 'string') {
|
|
16
|
+
return val.toLowerCase() === 'true';
|
|
17
|
+
}
|
|
18
|
+
return val;
|
|
19
|
+
}, z.boolean());
|
package/build/index.js
CHANGED
|
@@ -320,7 +320,7 @@ const allTools = [
|
|
|
320
320
|
},
|
|
321
321
|
{
|
|
322
322
|
name: "list_issues",
|
|
323
|
-
description: "List issues
|
|
323
|
+
description: "List issues (default: created by current user only; use scope='all' for all accessible issues)",
|
|
324
324
|
inputSchema: zodToJsonSchema(ListIssuesSchema),
|
|
325
325
|
},
|
|
326
326
|
{
|
|
@@ -823,17 +823,23 @@ async function createIssue(projectId, options) {
|
|
|
823
823
|
return GitLabIssueSchema.parse(data);
|
|
824
824
|
}
|
|
825
825
|
/**
|
|
826
|
-
* List issues
|
|
826
|
+
* List issues across all accessible projects or within a specific project
|
|
827
827
|
* 프로젝트의 이슈 목록 조회
|
|
828
828
|
*
|
|
829
|
-
* @param {string} projectId - The ID or URL-encoded path of the project
|
|
829
|
+
* @param {string} projectId - The ID or URL-encoded path of the project (optional)
|
|
830
830
|
* @param {Object} options - Options for listing issues
|
|
831
831
|
* @returns {Promise<GitLabIssue[]>} List of issues
|
|
832
832
|
*/
|
|
833
833
|
async function listIssues(projectId, options = {}) {
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
834
|
+
let url;
|
|
835
|
+
if (projectId) {
|
|
836
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
837
|
+
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
838
|
+
url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/issues`);
|
|
839
|
+
}
|
|
840
|
+
else {
|
|
841
|
+
url = new URL(`${GITLAB_API_URL}/issues`);
|
|
842
|
+
}
|
|
837
843
|
// Add all query parameters
|
|
838
844
|
Object.entries(options).forEach(([key, value]) => {
|
|
839
845
|
if (value !== undefined) {
|
|
@@ -845,12 +851,12 @@ async function listIssues(projectId, options = {}) {
|
|
|
845
851
|
url.searchParams.append(`${key}[]`, label.toString());
|
|
846
852
|
});
|
|
847
853
|
}
|
|
848
|
-
else {
|
|
854
|
+
else if (value) {
|
|
849
855
|
url.searchParams.append(`${key}[]`, value.toString());
|
|
850
856
|
}
|
|
851
857
|
}
|
|
852
858
|
else {
|
|
853
|
-
url.searchParams.append(key, value
|
|
859
|
+
url.searchParams.append(key, String(value));
|
|
854
860
|
}
|
|
855
861
|
}
|
|
856
862
|
});
|
|
@@ -879,7 +885,7 @@ async function listMergeRequests(projectId, options = {}) {
|
|
|
879
885
|
url.searchParams.append(key, value.join(","));
|
|
880
886
|
}
|
|
881
887
|
else {
|
|
882
|
-
url.searchParams.append(key, value
|
|
888
|
+
url.searchParams.append(key, String(value));
|
|
883
889
|
}
|
|
884
890
|
}
|
|
885
891
|
});
|
|
@@ -1053,6 +1059,7 @@ async function createMergeRequest(projectId, options) {
|
|
|
1053
1059
|
description: options.description,
|
|
1054
1060
|
source_branch: options.source_branch,
|
|
1055
1061
|
target_branch: options.target_branch,
|
|
1062
|
+
target_project_id: options.target_project_id,
|
|
1056
1063
|
assignee_ids: options.assignee_ids,
|
|
1057
1064
|
reviewer_ids: options.reviewer_ids,
|
|
1058
1065
|
labels: options.labels?.join(","),
|
|
@@ -2617,6 +2624,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2617
2624
|
if (GITLAB_AUTH_COOKIE_PATH) {
|
|
2618
2625
|
await ensureSessionForRequest();
|
|
2619
2626
|
}
|
|
2627
|
+
logger.info(request.params.name);
|
|
2620
2628
|
switch (request.params.name) {
|
|
2621
2629
|
case "fork_repository": {
|
|
2622
2630
|
if (GITLAB_PROJECT_ID) {
|
|
@@ -3305,6 +3313,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3305
3313
|
}
|
|
3306
3314
|
}
|
|
3307
3315
|
catch (error) {
|
|
3316
|
+
logger.debug(request.params);
|
|
3308
3317
|
if (error instanceof z.ZodError) {
|
|
3309
3318
|
throw new Error(`Invalid arguments: ${error.errors
|
|
3310
3319
|
.map(e => `${e.path.join(".")}: ${e.message}`)
|
package/build/schemas.js
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
if (typeof val === 'string') {
|
|
4
|
-
return val.toLowerCase() === 'true';
|
|
5
|
-
}
|
|
6
|
-
return val;
|
|
7
|
-
}, z.boolean());
|
|
2
|
+
import { flexibleBoolean } from "./customSchemas.js";
|
|
8
3
|
// Base schemas for common types
|
|
9
4
|
export const GitLabAuthorSchema = z.object({
|
|
10
5
|
name: z.string(),
|
|
@@ -13,8 +8,8 @@ export const GitLabAuthorSchema = z.object({
|
|
|
13
8
|
});
|
|
14
9
|
// Pipeline related schemas
|
|
15
10
|
export const GitLabPipelineSchema = z.object({
|
|
16
|
-
id: z.string()
|
|
17
|
-
project_id: z.string()
|
|
11
|
+
id: z.coerce.string(),
|
|
12
|
+
project_id: z.coerce.string(),
|
|
18
13
|
sha: z.string(),
|
|
19
14
|
ref: z.string(),
|
|
20
15
|
status: z.string(),
|
|
@@ -28,7 +23,7 @@ export const GitLabPipelineSchema = z.object({
|
|
|
28
23
|
coverage: z.number().nullable().optional(),
|
|
29
24
|
user: z
|
|
30
25
|
.object({
|
|
31
|
-
id: z.string()
|
|
26
|
+
id: z.coerce.string(),
|
|
32
27
|
name: z.string(),
|
|
33
28
|
username: z.string(),
|
|
34
29
|
avatar_url: z.string().nullable().optional(),
|
|
@@ -57,7 +52,7 @@ export const GitLabPipelineSchema = z.object({
|
|
|
57
52
|
});
|
|
58
53
|
// Pipeline job related schemas
|
|
59
54
|
export const GitLabPipelineJobSchema = z.object({
|
|
60
|
-
id: z.string()
|
|
55
|
+
id: z.coerce.string(),
|
|
61
56
|
status: z.string(),
|
|
62
57
|
stage: z.string(),
|
|
63
58
|
name: z.string(),
|
|
@@ -70,7 +65,7 @@ export const GitLabPipelineJobSchema = z.object({
|
|
|
70
65
|
duration: z.number().nullable().optional(),
|
|
71
66
|
user: z
|
|
72
67
|
.object({
|
|
73
|
-
id: z.string()
|
|
68
|
+
id: z.coerce.string(),
|
|
74
69
|
name: z.string(),
|
|
75
70
|
username: z.string(),
|
|
76
71
|
avatar_url: z.string().nullable().optional(),
|
|
@@ -87,8 +82,8 @@ export const GitLabPipelineJobSchema = z.object({
|
|
|
87
82
|
.optional(),
|
|
88
83
|
pipeline: z
|
|
89
84
|
.object({
|
|
90
|
-
id: z.string()
|
|
91
|
-
project_id: z.string()
|
|
85
|
+
id: z.coerce.string(),
|
|
86
|
+
project_id: z.coerce.string(),
|
|
92
87
|
status: z.string(),
|
|
93
88
|
ref: z.string(),
|
|
94
89
|
sha: z.string(),
|
|
@@ -104,7 +99,7 @@ export const PaginationOptionsSchema = z.object({
|
|
|
104
99
|
});
|
|
105
100
|
// Schema for listing pipelines
|
|
106
101
|
export const ListPipelinesSchema = z.object({
|
|
107
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
102
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
108
103
|
scope: z
|
|
109
104
|
.enum(["running", "pending", "finished", "branches", "tags"])
|
|
110
105
|
.optional()
|
|
@@ -145,13 +140,13 @@ export const ListPipelinesSchema = z.object({
|
|
|
145
140
|
}).merge(PaginationOptionsSchema);
|
|
146
141
|
// Schema for getting a specific pipeline
|
|
147
142
|
export const GetPipelineSchema = z.object({
|
|
148
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
149
|
-
pipeline_id: z.string().
|
|
143
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
144
|
+
pipeline_id: z.coerce.string().describe("The ID of the pipeline"),
|
|
150
145
|
});
|
|
151
146
|
// Schema for listing jobs in a pipeline
|
|
152
147
|
export const ListPipelineJobsSchema = z.object({
|
|
153
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
154
|
-
pipeline_id: z.string().
|
|
148
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
149
|
+
pipeline_id: z.coerce.string().describe("The ID of the pipeline"),
|
|
155
150
|
scope: z
|
|
156
151
|
.enum(["created", "pending", "running", "failed", "success", "canceled", "skipped", "manual"])
|
|
157
152
|
.optional()
|
|
@@ -160,7 +155,7 @@ export const ListPipelineJobsSchema = z.object({
|
|
|
160
155
|
}).merge(PaginationOptionsSchema);
|
|
161
156
|
// Schema for creating a new pipeline
|
|
162
157
|
export const CreatePipelineSchema = z.object({
|
|
163
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
158
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
164
159
|
ref: z.string().describe("The branch or tag to run the pipeline on"),
|
|
165
160
|
variables: z
|
|
166
161
|
.array(z.object({
|
|
@@ -172,25 +167,22 @@ export const CreatePipelineSchema = z.object({
|
|
|
172
167
|
});
|
|
173
168
|
// Schema for retrying a pipeline
|
|
174
169
|
export const RetryPipelineSchema = z.object({
|
|
175
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
176
|
-
pipeline_id: z.string().
|
|
170
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
171
|
+
pipeline_id: z.coerce.string().describe("The ID of the pipeline to retry"),
|
|
177
172
|
});
|
|
178
173
|
// Schema for canceling a pipeline
|
|
179
|
-
export const CancelPipelineSchema =
|
|
180
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
181
|
-
pipeline_id: z.string().or(z.number().describe("The ID of the pipeline to cancel")),
|
|
182
|
-
});
|
|
174
|
+
export const CancelPipelineSchema = RetryPipelineSchema;
|
|
183
175
|
// Schema for the input parameters for pipeline job operations
|
|
184
176
|
export const GetPipelineJobOutputSchema = z.object({
|
|
185
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
186
|
-
job_id: z.string().
|
|
177
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
178
|
+
job_id: z.coerce.string().describe("The ID of the job"),
|
|
187
179
|
limit: z.number().optional().describe("Maximum number of lines to return from the end of the log (default: 1000)"),
|
|
188
180
|
offset: z.number().optional().describe("Number of lines to skip from the end of the log (default: 0)"),
|
|
189
181
|
});
|
|
190
182
|
// User schemas
|
|
191
183
|
export const GitLabUserSchema = z.object({
|
|
192
184
|
username: z.string(), // Changed from login to match GitLab API
|
|
193
|
-
id: z.string()
|
|
185
|
+
id: z.coerce.string(),
|
|
194
186
|
name: z.string(),
|
|
195
187
|
avatar_url: z.string().nullable(),
|
|
196
188
|
web_url: z.string(), // Changed from html_url to match GitLab API
|
|
@@ -199,7 +191,7 @@ export const GetUsersSchema = z.object({
|
|
|
199
191
|
usernames: z.array(z.string()).describe("Array of usernames to search for"),
|
|
200
192
|
});
|
|
201
193
|
export const GitLabUsersResponseSchema = z.record(z.string(), z.object({
|
|
202
|
-
id: z.string()
|
|
194
|
+
id: z.coerce.string(),
|
|
203
195
|
username: z.string(),
|
|
204
196
|
name: z.string(),
|
|
205
197
|
avatar_url: z.string().nullable(),
|
|
@@ -208,15 +200,15 @@ export const GitLabUsersResponseSchema = z.record(z.string(), z.object({
|
|
|
208
200
|
// Namespace related schemas
|
|
209
201
|
// Base schema for project-related operations
|
|
210
202
|
const ProjectParamsSchema = z.object({
|
|
211
|
-
project_id: z.string().describe("Project ID or complete URL-encoded path to project"), // Changed from owner/repo to match GitLab API
|
|
203
|
+
project_id: z.coerce.string().describe("Project ID or complete URL-encoded path to project"), // Changed from owner/repo to match GitLab API
|
|
212
204
|
});
|
|
213
205
|
export const GitLabNamespaceSchema = z.object({
|
|
214
|
-
id: z.string()
|
|
206
|
+
id: z.coerce.string(),
|
|
215
207
|
name: z.string(),
|
|
216
208
|
path: z.string(),
|
|
217
209
|
kind: z.enum(["user", "group"]),
|
|
218
210
|
full_path: z.string(),
|
|
219
|
-
parent_id: z.string().
|
|
211
|
+
parent_id: z.coerce.string().nullable(),
|
|
220
212
|
avatar_url: z.string().nullable(),
|
|
221
213
|
web_url: z.string(),
|
|
222
214
|
members_count_with_descendants: z.number().optional(),
|
|
@@ -237,14 +229,14 @@ export const GitLabNamespaceExistsResponseSchema = z.object({
|
|
|
237
229
|
// Repository related schemas
|
|
238
230
|
export const GitLabOwnerSchema = z.object({
|
|
239
231
|
username: z.string(), // Changed from login to match GitLab API
|
|
240
|
-
id: z.string()
|
|
232
|
+
id: z.coerce.string(),
|
|
241
233
|
avatar_url: z.string().nullable(),
|
|
242
234
|
web_url: z.string(), // Changed from html_url to match GitLab API
|
|
243
235
|
name: z.string(), // Added as GitLab includes full name
|
|
244
236
|
state: z.string(), // Added as GitLab includes user state
|
|
245
237
|
});
|
|
246
238
|
export const GitLabRepositorySchema = z.object({
|
|
247
|
-
id: z.string()
|
|
239
|
+
id: z.coerce.string(),
|
|
248
240
|
name: z.string(),
|
|
249
241
|
path_with_namespace: z.string(),
|
|
250
242
|
visibility: z.string().optional(),
|
|
@@ -259,7 +251,7 @@ export const GitLabRepositorySchema = z.object({
|
|
|
259
251
|
default_branch: z.string().optional(),
|
|
260
252
|
namespace: z
|
|
261
253
|
.object({
|
|
262
|
-
id: z.string()
|
|
254
|
+
id: z.coerce.string(),
|
|
263
255
|
name: z.string(),
|
|
264
256
|
path: z.string(),
|
|
265
257
|
kind: z.string(),
|
|
@@ -306,7 +298,7 @@ export const GitLabRepositorySchema = z.object({
|
|
|
306
298
|
shared_runners_enabled: flexibleBoolean.optional(),
|
|
307
299
|
shared_with_groups: z
|
|
308
300
|
.array(z.object({
|
|
309
|
-
group_id: z.string()
|
|
301
|
+
group_id: z.coerce.string(),
|
|
310
302
|
group_name: z.string(),
|
|
311
303
|
group_full_path: z.string(),
|
|
312
304
|
group_access_level: z.number(),
|
|
@@ -355,7 +347,7 @@ export const GitLabTreeItemSchema = z.object({
|
|
|
355
347
|
mode: z.string(),
|
|
356
348
|
});
|
|
357
349
|
export const GetRepositoryTreeSchema = z.object({
|
|
358
|
-
project_id: z.string().describe("The ID or URL-encoded path of the project"),
|
|
350
|
+
project_id: z.coerce.string().describe("The ID or URL-encoded path of the project"),
|
|
359
351
|
path: z.string().optional().describe("The path inside the repository"),
|
|
360
352
|
ref: z
|
|
361
353
|
.string()
|
|
@@ -402,9 +394,9 @@ export const GitLabReferenceSchema = z.object({
|
|
|
402
394
|
});
|
|
403
395
|
// Milestones rest api output schemas
|
|
404
396
|
export const GitLabMilestonesSchema = z.object({
|
|
405
|
-
id: z.string()
|
|
406
|
-
iid: z.string()
|
|
407
|
-
project_id: z.string()
|
|
397
|
+
id: z.coerce.string(),
|
|
398
|
+
iid: z.coerce.string(),
|
|
399
|
+
project_id: z.coerce.string(),
|
|
408
400
|
title: z.string(),
|
|
409
401
|
description: z.string().nullable(),
|
|
410
402
|
due_date: z.string().nullable(),
|
|
@@ -426,7 +418,7 @@ export const CreateIssueOptionsSchema = z.object({
|
|
|
426
418
|
title: z.string(),
|
|
427
419
|
description: z.string().optional(), // Changed from body to match GitLab API
|
|
428
420
|
assignee_ids: z.array(z.number()).optional(), // Changed from assignees to match GitLab API
|
|
429
|
-
milestone_id: z.string().
|
|
421
|
+
milestone_id: z.coerce.string().optional(), // Changed from milestone to match GitLab API
|
|
430
422
|
labels: z.array(z.string()).optional(),
|
|
431
423
|
});
|
|
432
424
|
export const GitLabDiffSchema = z.object({
|
|
@@ -473,7 +465,7 @@ export const GitLabCompareResultSchema = z.object({
|
|
|
473
465
|
});
|
|
474
466
|
// Issue related schemas
|
|
475
467
|
export const GitLabLabelSchema = z.object({
|
|
476
|
-
id: z.string()
|
|
468
|
+
id: z.coerce.string(),
|
|
477
469
|
name: z.string(),
|
|
478
470
|
color: z.string(),
|
|
479
471
|
text_color: z.string(),
|
|
@@ -487,17 +479,17 @@ export const GitLabLabelSchema = z.object({
|
|
|
487
479
|
is_project_label: flexibleBoolean.optional(),
|
|
488
480
|
});
|
|
489
481
|
export const GitLabMilestoneSchema = z.object({
|
|
490
|
-
id: z.string()
|
|
491
|
-
iid: z.string()
|
|
482
|
+
id: z.coerce.string(),
|
|
483
|
+
iid: z.coerce.string(), // Added to match GitLab API
|
|
492
484
|
title: z.string(),
|
|
493
485
|
description: z.string().nullable().default(""),
|
|
494
486
|
state: z.string(),
|
|
495
487
|
web_url: z.string(), // Changed from html_url to match GitLab API
|
|
496
488
|
});
|
|
497
489
|
export const GitLabIssueSchema = z.object({
|
|
498
|
-
id: z.string()
|
|
499
|
-
iid: z.string()
|
|
500
|
-
project_id: z.string()
|
|
490
|
+
id: z.coerce.string(),
|
|
491
|
+
iid: z.coerce.string(), // Added to match GitLab API
|
|
492
|
+
project_id: z.coerce.string(), // Added to match GitLab API
|
|
501
493
|
title: z.string(),
|
|
502
494
|
description: z.string().nullable().default(""), // Changed from body to match GitLab API
|
|
503
495
|
state: z.string(),
|
|
@@ -531,7 +523,7 @@ export const GitLabIssueSchema = z.object({
|
|
|
531
523
|
});
|
|
532
524
|
// NEW SCHEMA: For issue with link details (used in listing issue links)
|
|
533
525
|
export const GitLabIssueWithLinkDetailsSchema = GitLabIssueSchema.extend({
|
|
534
|
-
issue_link_id: z.string()
|
|
526
|
+
issue_link_id: z.coerce.string(),
|
|
535
527
|
link_type: z.enum(["relates_to", "blocks", "is_blocked_by"]),
|
|
536
528
|
link_created_at: z.string(),
|
|
537
529
|
link_updated_at: z.string(),
|
|
@@ -543,7 +535,7 @@ export const GitLabForkParentSchema = z.object({
|
|
|
543
535
|
owner: z
|
|
544
536
|
.object({
|
|
545
537
|
username: z.string(), // Changed from login to match GitLab API
|
|
546
|
-
id: z.string()
|
|
538
|
+
id: z.coerce.string(),
|
|
547
539
|
avatar_url: z.string().nullable(),
|
|
548
540
|
})
|
|
549
541
|
.optional(), // Made optional to handle cases where GitLab API doesn't include it
|
|
@@ -559,9 +551,9 @@ export const GitLabMergeRequestDiffRefSchema = z.object({
|
|
|
559
551
|
start_sha: z.string(),
|
|
560
552
|
});
|
|
561
553
|
export const GitLabMergeRequestSchema = z.object({
|
|
562
|
-
id: z.string()
|
|
563
|
-
iid: z.string()
|
|
564
|
-
project_id: z.string()
|
|
554
|
+
id: z.coerce.string(),
|
|
555
|
+
iid: z.coerce.string(),
|
|
556
|
+
project_id: z.coerce.string(),
|
|
565
557
|
title: z.string(),
|
|
566
558
|
description: z.string().nullable(),
|
|
567
559
|
state: z.string(),
|
|
@@ -609,7 +601,7 @@ export const LineRangeSchema = z.object({
|
|
|
609
601
|
}).describe("Line range for multiline comments on GitLab merge request diffs. VALIDATION RULES: 1) line_code is critical for GitLab API success, 2) start/end must have consistent types, 3) line numbers must form valid range, 4) get line_code from GitLab diff API, never generate manually.");
|
|
610
602
|
// Discussion related schemas
|
|
611
603
|
export const GitLabDiscussionNoteSchema = z.object({
|
|
612
|
-
id: z.string()
|
|
604
|
+
id: z.coerce.string(),
|
|
613
605
|
type: z.enum(["DiscussionNote", "DiffNote", "Note"]).nullable(), // Allow null type for regular notes
|
|
614
606
|
body: z.string(),
|
|
615
607
|
attachment: z.any().nullable(), // Can be string or object, handle appropriately
|
|
@@ -617,10 +609,10 @@ export const GitLabDiscussionNoteSchema = z.object({
|
|
|
617
609
|
created_at: z.string(),
|
|
618
610
|
updated_at: z.string(),
|
|
619
611
|
system: flexibleBoolean,
|
|
620
|
-
noteable_id: z.string()
|
|
612
|
+
noteable_id: z.coerce.string(),
|
|
621
613
|
noteable_type: z.enum(["Issue", "MergeRequest", "Snippet", "Commit", "Epic"]),
|
|
622
|
-
project_id: z.string().
|
|
623
|
-
noteable_iid: z.coerce.
|
|
614
|
+
project_id: z.coerce.string().optional(),
|
|
615
|
+
noteable_iid: z.coerce.string().nullable().optional(),
|
|
624
616
|
resolvable: flexibleBoolean.optional(),
|
|
625
617
|
resolved: flexibleBoolean.optional(),
|
|
626
618
|
resolved_by: GitLabUserSchema.nullable().optional(),
|
|
@@ -660,7 +652,7 @@ export const PaginatedResponseSchema = z.object({
|
|
|
660
652
|
pagination: GitLabPaginationSchema.optional(),
|
|
661
653
|
});
|
|
662
654
|
export const GitLabDiscussionSchema = z.object({
|
|
663
|
-
id: z.string(),
|
|
655
|
+
id: z.coerce.string(),
|
|
664
656
|
individual_note: flexibleBoolean,
|
|
665
657
|
notes: z.array(GitLabDiscussionNoteSchema),
|
|
666
658
|
});
|
|
@@ -670,18 +662,18 @@ export const PaginatedDiscussionsResponseSchema = z.object({
|
|
|
670
662
|
pagination: GitLabPaginationSchema,
|
|
671
663
|
});
|
|
672
664
|
export const ListIssueDiscussionsSchema = z.object({
|
|
673
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
674
|
-
issue_iid: z.string().
|
|
665
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
666
|
+
issue_iid: z.coerce.string().describe("The internal ID of the project issue"),
|
|
675
667
|
}).merge(PaginationOptionsSchema);
|
|
676
668
|
// Input schema for listing merge request discussions
|
|
677
669
|
export const ListMergeRequestDiscussionsSchema = ProjectParamsSchema.extend({
|
|
678
|
-
merge_request_iid: z.string().
|
|
670
|
+
merge_request_iid: z.coerce.string().describe("The IID of a merge request"),
|
|
679
671
|
}).merge(PaginationOptionsSchema);
|
|
680
672
|
// Input schema for updating a merge request discussion note
|
|
681
673
|
export const UpdateMergeRequestNoteSchema = ProjectParamsSchema.extend({
|
|
682
|
-
merge_request_iid: z.string().
|
|
683
|
-
discussion_id: z.string().describe("The ID of a thread"),
|
|
684
|
-
note_id: z.string().
|
|
674
|
+
merge_request_iid: z.coerce.string().describe("The IID of a merge request"),
|
|
675
|
+
discussion_id: z.coerce.string().describe("The ID of a thread"),
|
|
676
|
+
note_id: z.coerce.string().describe("The ID of a thread note"),
|
|
685
677
|
body: z.string().optional().describe("The content of the note or reply"),
|
|
686
678
|
resolved: flexibleBoolean.optional().describe("Resolve or unresolve the note"),
|
|
687
679
|
})
|
|
@@ -693,22 +685,22 @@ export const UpdateMergeRequestNoteSchema = ProjectParamsSchema.extend({
|
|
|
693
685
|
});
|
|
694
686
|
// Input schema for adding a note to an existing merge request discussion
|
|
695
687
|
export const CreateMergeRequestNoteSchema = ProjectParamsSchema.extend({
|
|
696
|
-
merge_request_iid: z.string().
|
|
697
|
-
discussion_id: z.string().describe("The ID of a thread"),
|
|
688
|
+
merge_request_iid: z.coerce.string().describe("The IID of a merge request"),
|
|
689
|
+
discussion_id: z.coerce.string().describe("The ID of a thread"),
|
|
698
690
|
body: z.string().describe("The content of the note or reply"),
|
|
699
691
|
created_at: z.string().optional().describe("Date the note was created at (ISO 8601 format)"),
|
|
700
692
|
});
|
|
701
693
|
// Input schema for updating an issue discussion note
|
|
702
694
|
export const UpdateIssueNoteSchema = ProjectParamsSchema.extend({
|
|
703
|
-
issue_iid: z.string().
|
|
704
|
-
discussion_id: z.string().describe("The ID of a thread"),
|
|
705
|
-
note_id: z.string().
|
|
695
|
+
issue_iid: z.coerce.string().describe("The IID of an issue"),
|
|
696
|
+
discussion_id: z.coerce.string().describe("The ID of a thread"),
|
|
697
|
+
note_id: z.coerce.string().describe("The ID of a thread note"),
|
|
706
698
|
body: z.string().describe("The content of the note or reply"),
|
|
707
699
|
});
|
|
708
700
|
// Input schema for adding a note to an existing issue discussion
|
|
709
701
|
export const CreateIssueNoteSchema = ProjectParamsSchema.extend({
|
|
710
|
-
issue_iid: z.string().
|
|
711
|
-
discussion_id: z.string().describe("The ID of a thread"),
|
|
702
|
+
issue_iid: z.coerce.string().describe("The IID of an issue"),
|
|
703
|
+
discussion_id: z.coerce.string().describe("The ID of a thread"),
|
|
712
704
|
body: z.string().describe("The content of the note or reply"),
|
|
713
705
|
created_at: z.string().optional().describe("Date the note was created at (ISO 8601 format)"),
|
|
714
706
|
});
|
|
@@ -753,13 +745,14 @@ export const CreateIssueSchema = ProjectParamsSchema.extend({
|
|
|
753
745
|
description: z.string().optional().describe("Issue description"),
|
|
754
746
|
assignee_ids: z.array(z.number()).optional().describe("Array of user IDs to assign"),
|
|
755
747
|
labels: z.array(z.string()).optional().describe("Array of label names"),
|
|
756
|
-
milestone_id: z.string().
|
|
748
|
+
milestone_id: z.coerce.string().optional().describe("Milestone ID to assign"),
|
|
757
749
|
});
|
|
758
750
|
const MergeRequestOptionsSchema = {
|
|
759
751
|
title: z.string().describe("Merge request title"),
|
|
760
752
|
description: z.string().optional().describe("Merge request description"),
|
|
761
753
|
source_branch: z.string().describe("Branch containing changes"),
|
|
762
754
|
target_branch: z.string().describe("Branch to merge into"),
|
|
755
|
+
target_project_id: z.coerce.string().optional().describe("Numeric ID of the target project."),
|
|
763
756
|
assignee_ids: z
|
|
764
757
|
.array(z.number())
|
|
765
758
|
.optional()
|
|
@@ -794,7 +787,7 @@ export const GetBranchDiffsSchema = ProjectParamsSchema.extend({
|
|
|
794
787
|
excluded_file_patterns: z.array(z.string()).optional().describe("Array of regex patterns to exclude files from the diff results. Each pattern is a JavaScript-compatible regular expression that matches file paths to ignore. Examples: [\"^test/mocks/\", \"\\.spec\\.ts$\", \"package-lock\\.json\"]"),
|
|
795
788
|
});
|
|
796
789
|
export const GetMergeRequestSchema = ProjectParamsSchema.extend({
|
|
797
|
-
merge_request_iid: z.string().
|
|
790
|
+
merge_request_iid: z.coerce.string().optional().describe("The IID of a merge request"),
|
|
798
791
|
source_branch: z.string().optional().describe("Source branch name"),
|
|
799
792
|
});
|
|
800
793
|
export const UpdateMergeRequestSchema = GetMergeRequestSchema.extend({
|
|
@@ -830,19 +823,19 @@ export const ListMergeRequestDiffsSchema = GetMergeRequestSchema.extend({
|
|
|
830
823
|
unidiff: flexibleBoolean.optional().describe("Present diffs in the unified diff format. Default is false. Introduced in GitLab 16.5."),
|
|
831
824
|
});
|
|
832
825
|
export const CreateNoteSchema = z.object({
|
|
833
|
-
project_id: z.string().describe("Project ID or namespace/project_path"),
|
|
826
|
+
project_id: z.coerce.string().describe("Project ID or namespace/project_path"),
|
|
834
827
|
noteable_type: z
|
|
835
828
|
.enum(["issue", "merge_request"])
|
|
836
829
|
.describe("Type of noteable (issue or merge_request)"),
|
|
837
|
-
noteable_iid: z.coerce.
|
|
830
|
+
noteable_iid: z.coerce.string().describe("IID of the issue or merge request"),
|
|
838
831
|
body: z.string().describe("Note content"),
|
|
839
832
|
});
|
|
840
833
|
// Issues API operation schemas
|
|
841
834
|
export const ListIssuesSchema = z.object({
|
|
842
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
843
|
-
assignee_id: z.string().
|
|
835
|
+
project_id: z.coerce.string().optional().describe("Project ID or URL-encoded path (optional - if not provided, lists issues across all accessible projects)"),
|
|
836
|
+
assignee_id: z.coerce.string().optional().describe("Return issues assigned to the given user ID. user id or none or any"),
|
|
844
837
|
assignee_username: z.array(z.string()).optional().describe("Return issues assigned to the given username"),
|
|
845
|
-
author_id: z.string().
|
|
838
|
+
author_id: z.coerce.string().optional().describe("Return issues created by the given user ID"),
|
|
846
839
|
author_username: z.string().optional().describe("Return issues created by the given username"),
|
|
847
840
|
confidential: flexibleBoolean.optional().describe("Filter confidential or public issues"),
|
|
848
841
|
created_after: z.string().optional().describe("Return issues created after the given time"),
|
|
@@ -865,24 +858,20 @@ export const ListIssuesSchema = z.object({
|
|
|
865
858
|
}).merge(PaginationOptionsSchema);
|
|
866
859
|
// Merge Requests API operation schemas
|
|
867
860
|
export const ListMergeRequestsSchema = z.object({
|
|
868
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
869
|
-
assignee_id: z
|
|
870
|
-
.number()
|
|
871
|
-
.optional()
|
|
872
|
-
.describe("Returns merge requests assigned to the given user ID"),
|
|
861
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
862
|
+
assignee_id: z.coerce.string().optional().describe("Return issues assigned to the given user ID. user id or none or any"),
|
|
873
863
|
assignee_username: z
|
|
874
864
|
.string()
|
|
875
865
|
.optional()
|
|
876
866
|
.describe("Returns merge requests assigned to the given username"),
|
|
877
|
-
author_id: z.string().
|
|
867
|
+
author_id: z.coerce.string().optional().describe("Returns merge requests created by the given user ID"),
|
|
878
868
|
author_username: z
|
|
879
869
|
.string()
|
|
880
870
|
.optional()
|
|
881
871
|
.describe("Returns merge requests created by the given username"),
|
|
882
|
-
reviewer_id: z
|
|
883
|
-
.number()
|
|
872
|
+
reviewer_id: z.coerce.string()
|
|
884
873
|
.optional()
|
|
885
|
-
.describe("Returns merge requests which have the user as a reviewer"),
|
|
874
|
+
.describe("Returns merge requests which have the user as a reviewer. user id or none or any"),
|
|
886
875
|
reviewer_username: z
|
|
887
876
|
.string()
|
|
888
877
|
.optional()
|
|
@@ -934,12 +923,12 @@ export const ListMergeRequestsSchema = z.object({
|
|
|
934
923
|
with_labels_details: flexibleBoolean.optional().describe("Return more details for each label"),
|
|
935
924
|
}).merge(PaginationOptionsSchema);
|
|
936
925
|
export const GetIssueSchema = z.object({
|
|
937
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
938
|
-
issue_iid: z.string().
|
|
926
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
927
|
+
issue_iid: z.coerce.string().describe("The internal ID of the project issue"),
|
|
939
928
|
});
|
|
940
929
|
export const UpdateIssueSchema = z.object({
|
|
941
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
942
|
-
issue_iid: z.string().
|
|
930
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
931
|
+
issue_iid: z.coerce.string().describe("The internal ID of the project issue"),
|
|
943
932
|
title: z.string().optional().describe("The title of the issue"),
|
|
944
933
|
description: z.string().optional().describe("The description of the issue"),
|
|
945
934
|
assignee_ids: z.array(z.number()).optional().describe("Array of user IDs to assign issue to"),
|
|
@@ -947,13 +936,13 @@ export const UpdateIssueSchema = z.object({
|
|
|
947
936
|
discussion_locked: flexibleBoolean.optional().describe("Flag to lock discussions"),
|
|
948
937
|
due_date: z.string().optional().describe("Date the issue is due (YYYY-MM-DD)"),
|
|
949
938
|
labels: z.array(z.string()).optional().describe("Array of label names"),
|
|
950
|
-
milestone_id: z.string().
|
|
939
|
+
milestone_id: z.coerce.string().optional().describe("Milestone ID to assign"),
|
|
951
940
|
state_event: z.enum(["close", "reopen"]).optional().describe("Update issue state (close/reopen)"),
|
|
952
941
|
weight: z.number().optional().describe("Weight of the issue (0-9)"),
|
|
953
942
|
});
|
|
954
943
|
export const DeleteIssueSchema = z.object({
|
|
955
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
956
|
-
issue_iid: z.string().
|
|
944
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
945
|
+
issue_iid: z.coerce.string().describe("The internal ID of the project issue"),
|
|
957
946
|
});
|
|
958
947
|
// Issue links related schemas
|
|
959
948
|
export const GitLabIssueLinkSchema = z.object({
|
|
@@ -962,28 +951,28 @@ export const GitLabIssueLinkSchema = z.object({
|
|
|
962
951
|
link_type: z.enum(["relates_to", "blocks", "is_blocked_by"]),
|
|
963
952
|
});
|
|
964
953
|
export const ListIssueLinksSchema = z.object({
|
|
965
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
966
|
-
issue_iid: z.string().
|
|
954
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
955
|
+
issue_iid: z.coerce.string().describe("The internal ID of a project's issue"),
|
|
967
956
|
});
|
|
968
957
|
export const GetIssueLinkSchema = z.object({
|
|
969
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
970
|
-
issue_iid: z.string().
|
|
971
|
-
issue_link_id: z.string().
|
|
958
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
959
|
+
issue_iid: z.coerce.string().describe("The internal ID of a project's issue"),
|
|
960
|
+
issue_link_id: z.coerce.string().describe("ID of an issue relationship"),
|
|
972
961
|
});
|
|
973
962
|
export const CreateIssueLinkSchema = z.object({
|
|
974
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
975
|
-
issue_iid: z.string().
|
|
976
|
-
target_project_id: z.string().describe("The ID or URL-encoded path of a target project"),
|
|
977
|
-
target_issue_iid: z.string().
|
|
963
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
964
|
+
issue_iid: z.coerce.string().describe("The internal ID of a project's issue"),
|
|
965
|
+
target_project_id: z.coerce.string().describe("The ID or URL-encoded path of a target project"),
|
|
966
|
+
target_issue_iid: z.coerce.string().describe("The internal ID of a target project's issue"),
|
|
978
967
|
link_type: z
|
|
979
968
|
.enum(["relates_to", "blocks", "is_blocked_by"])
|
|
980
969
|
.optional()
|
|
981
970
|
.describe("The type of the relation, defaults to relates_to"),
|
|
982
971
|
});
|
|
983
972
|
export const DeleteIssueLinkSchema = z.object({
|
|
984
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
985
|
-
issue_iid: z.string().
|
|
986
|
-
issue_link_id: z.string().
|
|
973
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
974
|
+
issue_iid: z.coerce.string().describe("The internal ID of a project's issue"),
|
|
975
|
+
issue_link_id: z.coerce.string().describe("The ID of an issue relationship"),
|
|
987
976
|
});
|
|
988
977
|
// Namespace API operation schemas
|
|
989
978
|
export const ListNamespacesSchema = z.object({
|
|
@@ -991,14 +980,14 @@ export const ListNamespacesSchema = z.object({
|
|
|
991
980
|
owned: flexibleBoolean.optional().describe("Filter for namespaces owned by current user"),
|
|
992
981
|
}).merge(PaginationOptionsSchema);
|
|
993
982
|
export const GetNamespaceSchema = z.object({
|
|
994
|
-
namespace_id: z.string().describe("Namespace ID or full path"),
|
|
983
|
+
namespace_id: z.coerce.string().describe("Namespace ID or full path"),
|
|
995
984
|
});
|
|
996
985
|
export const VerifyNamespaceSchema = z.object({
|
|
997
986
|
path: z.string().describe("Namespace path to verify"),
|
|
998
987
|
});
|
|
999
988
|
// Project API operation schemas
|
|
1000
989
|
export const GetProjectSchema = z.object({
|
|
1001
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
990
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
1002
991
|
});
|
|
1003
992
|
export const ListProjectsSchema = z.object({
|
|
1004
993
|
search: z.string().optional().describe("Search term for projects"),
|
|
@@ -1031,7 +1020,7 @@ export const ListProjectsSchema = z.object({
|
|
|
1031
1020
|
}).merge(PaginationOptionsSchema);
|
|
1032
1021
|
// Label operation schemas
|
|
1033
1022
|
export const ListLabelsSchema = z.object({
|
|
1034
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
1023
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
1035
1024
|
with_counts: z
|
|
1036
1025
|
.boolean()
|
|
1037
1026
|
.optional()
|
|
@@ -1040,12 +1029,12 @@ export const ListLabelsSchema = z.object({
|
|
|
1040
1029
|
search: z.string().optional().describe("Keyword to filter labels by"),
|
|
1041
1030
|
});
|
|
1042
1031
|
export const GetLabelSchema = z.object({
|
|
1043
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
1044
|
-
label_id: z.string().describe("The ID or title of a project's label"),
|
|
1032
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
1033
|
+
label_id: z.coerce.string().describe("The ID or title of a project's label"),
|
|
1045
1034
|
include_ancestor_groups: flexibleBoolean.optional().describe("Include ancestor groups"),
|
|
1046
1035
|
});
|
|
1047
1036
|
export const CreateLabelSchema = z.object({
|
|
1048
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
1037
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
1049
1038
|
name: z.string().describe("The name of the label"),
|
|
1050
1039
|
color: z
|
|
1051
1040
|
.string()
|
|
@@ -1054,8 +1043,8 @@ export const CreateLabelSchema = z.object({
|
|
|
1054
1043
|
priority: z.number().nullable().optional().describe("The priority of the label"),
|
|
1055
1044
|
});
|
|
1056
1045
|
export const UpdateLabelSchema = z.object({
|
|
1057
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
1058
|
-
label_id: z.string().describe("The ID or title of a project's label"),
|
|
1046
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
1047
|
+
label_id: z.coerce.string().describe("The ID or title of a project's label"),
|
|
1059
1048
|
new_name: z.string().optional().describe("The new name of the label"),
|
|
1060
1049
|
color: z
|
|
1061
1050
|
.string()
|
|
@@ -1065,12 +1054,12 @@ export const UpdateLabelSchema = z.object({
|
|
|
1065
1054
|
priority: z.number().nullable().optional().describe("The new priority of the label"),
|
|
1066
1055
|
});
|
|
1067
1056
|
export const DeleteLabelSchema = z.object({
|
|
1068
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
1069
|
-
label_id: z.string().describe("The ID or title of a project's label"),
|
|
1057
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
1058
|
+
label_id: z.coerce.string().describe("The ID or title of a project's label"),
|
|
1070
1059
|
});
|
|
1071
1060
|
// Group projects schema
|
|
1072
1061
|
export const ListGroupProjectsSchema = z.object({
|
|
1073
|
-
group_id: z.string().describe("Group ID or path"),
|
|
1062
|
+
group_id: z.coerce.string().describe("Group ID or path"),
|
|
1074
1063
|
include_subgroups: flexibleBoolean.optional().describe("Include projects from subgroups"),
|
|
1075
1064
|
search: z.string().optional().describe("Search term to filter projects"),
|
|
1076
1065
|
order_by: z
|
|
@@ -1100,28 +1089,28 @@ export const ListGroupProjectsSchema = z.object({
|
|
|
1100
1089
|
}).merge(PaginationOptionsSchema);
|
|
1101
1090
|
// Add wiki operation schemas
|
|
1102
1091
|
export const ListWikiPagesSchema = z.object({
|
|
1103
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
1092
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
1104
1093
|
with_content: flexibleBoolean.optional().describe("Include content of the wiki pages"),
|
|
1105
1094
|
}).merge(PaginationOptionsSchema);
|
|
1106
1095
|
export const GetWikiPageSchema = z.object({
|
|
1107
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
1096
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
1108
1097
|
slug: z.string().describe("URL-encoded slug of the wiki page"),
|
|
1109
1098
|
});
|
|
1110
1099
|
export const CreateWikiPageSchema = z.object({
|
|
1111
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
1100
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
1112
1101
|
title: z.string().describe("Title of the wiki page"),
|
|
1113
1102
|
content: z.string().describe("Content of the wiki page"),
|
|
1114
1103
|
format: z.string().optional().describe("Content format, e.g., markdown, rdoc"),
|
|
1115
1104
|
});
|
|
1116
1105
|
export const UpdateWikiPageSchema = z.object({
|
|
1117
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
1106
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
1118
1107
|
slug: z.string().describe("URL-encoded slug of the wiki page"),
|
|
1119
1108
|
title: z.string().optional().describe("New title of the wiki page"),
|
|
1120
1109
|
content: z.string().optional().describe("New content of the wiki page"),
|
|
1121
1110
|
format: z.string().optional().describe("Content format, e.g., markdown, rdoc"),
|
|
1122
1111
|
});
|
|
1123
1112
|
export const DeleteWikiPageSchema = z.object({
|
|
1124
|
-
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
1113
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
1125
1114
|
slug: z.string().describe("URL-encoded slug of the wiki page"),
|
|
1126
1115
|
});
|
|
1127
1116
|
// Define wiki response schemas
|
|
@@ -1151,7 +1140,7 @@ export const MergeRequestThreadPositionSchema = z.object({
|
|
|
1151
1140
|
});
|
|
1152
1141
|
// Schema for creating a new merge request thread
|
|
1153
1142
|
export const CreateMergeRequestThreadSchema = ProjectParamsSchema.extend({
|
|
1154
|
-
merge_request_iid: z.string().
|
|
1143
|
+
merge_request_iid: z.coerce.string().describe("The IID of a merge request"),
|
|
1155
1144
|
body: z.string().describe("The content of the thread"),
|
|
1156
1145
|
position: MergeRequestThreadPositionSchema.optional().describe("Position when creating a diff note"),
|
|
1157
1146
|
created_at: z.string().optional().describe("Date the thread was created at (ISO 8601 format)"),
|
|
@@ -1184,7 +1173,7 @@ export const ListProjectMilestonesSchema = ProjectParamsSchema.extend({
|
|
|
1184
1173
|
}).merge(PaginationOptionsSchema);
|
|
1185
1174
|
// Schema for getting a single milestone
|
|
1186
1175
|
export const GetProjectMilestoneSchema = ProjectParamsSchema.extend({
|
|
1187
|
-
milestone_id: z.string().
|
|
1176
|
+
milestone_id: z.coerce.string().describe("The ID of a project milestone"),
|
|
1188
1177
|
});
|
|
1189
1178
|
// Schema for creating a new milestone
|
|
1190
1179
|
export const CreateProjectMilestoneSchema = ProjectParamsSchema.extend({
|
|
@@ -1216,7 +1205,7 @@ export const PromoteProjectMilestoneSchema = GetProjectMilestoneSchema;
|
|
|
1216
1205
|
export const GetMilestoneBurndownEventsSchema = GetProjectMilestoneSchema.merge(PaginationOptionsSchema);
|
|
1217
1206
|
// Add schemas for commit operations
|
|
1218
1207
|
export const ListCommitsSchema = z.object({
|
|
1219
|
-
project_id: z.string().describe("Project ID or complete URL-encoded path to project"),
|
|
1208
|
+
project_id: z.coerce.string().describe("Project ID or complete URL-encoded path to project"),
|
|
1220
1209
|
ref_name: z.string().optional().describe("The name of a repository branch, tag or revision range, or if not given the default branch"),
|
|
1221
1210
|
since: z.string().optional().describe("Only commits after or on this date are returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ"),
|
|
1222
1211
|
until: z.string().optional().describe("Only commits before or on this date are returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ"),
|
|
@@ -1231,11 +1220,11 @@ export const ListCommitsSchema = z.object({
|
|
|
1231
1220
|
per_page: z.number().optional().describe("Number of items per page (max: 100, default: 20)"),
|
|
1232
1221
|
});
|
|
1233
1222
|
export const GetCommitSchema = z.object({
|
|
1234
|
-
project_id: z.string().describe("Project ID or complete URL-encoded path to project"),
|
|
1223
|
+
project_id: z.coerce.string().describe("Project ID or complete URL-encoded path to project"),
|
|
1235
1224
|
sha: z.string().describe("The commit hash or name of a repository branch or tag"),
|
|
1236
1225
|
stats: flexibleBoolean.optional().describe("Include commit stats"),
|
|
1237
1226
|
});
|
|
1238
1227
|
export const GetCommitDiffSchema = z.object({
|
|
1239
|
-
project_id: z.string().describe("Project ID or complete URL-encoded path to project"),
|
|
1228
|
+
project_id: z.coerce.string().describe("Project ID or complete URL-encoded path to project"),
|
|
1240
1229
|
sha: z.string().describe("The commit hash or name of a repository branch or tag"),
|
|
1241
1230
|
});
|
package/package.json
CHANGED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';
|
|
2
|
-
import fetch from 'node-fetch';
|
|
3
|
-
// Integration tests that run against real GitLab API (when credentials are provided)
|
|
4
|
-
const GITLAB_API_URL = process.env.GITLAB_API_URL || 'https://gitlab.com';
|
|
5
|
-
const GITLAB_TOKEN = process.env.GITLAB_TOKEN || '';
|
|
6
|
-
const TEST_PROJECT_ID = process.env.TEST_PROJECT_ID || '';
|
|
7
|
-
const skipIntegrationTests = !GITLAB_TOKEN || !TEST_PROJECT_ID;
|
|
8
|
-
describe('GitLab MCP Server Integration Tests', () => {
|
|
9
|
-
if (skipIntegrationTests) {
|
|
10
|
-
it('should skip integration tests when credentials are missing', () => {
|
|
11
|
-
console.log('Skipping integration tests: Missing GITLAB_TOKEN or TEST_PROJECT_ID');
|
|
12
|
-
expect(true).toBe(true);
|
|
13
|
-
});
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
let testIssueId = null;
|
|
17
|
-
let testMRId = null;
|
|
18
|
-
beforeAll(() => {
|
|
19
|
-
console.log('Running integration tests with GitLab API');
|
|
20
|
-
});
|
|
21
|
-
afterAll(async () => {
|
|
22
|
-
// Cleanup: Delete test issue and MR if created
|
|
23
|
-
if (testIssueId) {
|
|
24
|
-
await fetch(`${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(TEST_PROJECT_ID)}/issues/${testIssueId}`, {
|
|
25
|
-
method: 'DELETE',
|
|
26
|
-
headers: {
|
|
27
|
-
'Authorization': `Bearer ${GITLAB_TOKEN}`
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
describe('Project Access', () => {
|
|
33
|
-
it('should fetch project information', async () => {
|
|
34
|
-
const response = await fetch(`${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(TEST_PROJECT_ID)}`, {
|
|
35
|
-
headers: {
|
|
36
|
-
'Authorization': `Bearer ${GITLAB_TOKEN}`,
|
|
37
|
-
'Accept': 'application/json'
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
expect(response.ok).toBe(true);
|
|
41
|
-
const project = await response.json();
|
|
42
|
-
expect(project).toHaveProperty('id');
|
|
43
|
-
expect(project).toHaveProperty('name');
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
describe('Issue Operations', () => {
|
|
47
|
-
it('should create an issue', async () => {
|
|
48
|
-
const issueData = {
|
|
49
|
-
title: `Test Issue ${Date.now()}`,
|
|
50
|
-
description: 'This is a test issue created by MCP integration tests'
|
|
51
|
-
};
|
|
52
|
-
const response = await fetch(`${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(TEST_PROJECT_ID)}/issues`, {
|
|
53
|
-
method: 'POST',
|
|
54
|
-
headers: {
|
|
55
|
-
'Authorization': `Bearer ${GITLAB_TOKEN}`,
|
|
56
|
-
'Accept': 'application/json',
|
|
57
|
-
'Content-Type': 'application/json'
|
|
58
|
-
},
|
|
59
|
-
body: JSON.stringify(issueData)
|
|
60
|
-
});
|
|
61
|
-
expect(response.ok).toBe(true);
|
|
62
|
-
const issue = await response.json();
|
|
63
|
-
expect(issue).toHaveProperty('iid');
|
|
64
|
-
expect(issue.title).toBe(issueData.title);
|
|
65
|
-
testIssueId = issue.iid;
|
|
66
|
-
});
|
|
67
|
-
it('should list issues', async () => {
|
|
68
|
-
const response = await fetch(`${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(TEST_PROJECT_ID)}/issues?state=opened`, {
|
|
69
|
-
headers: {
|
|
70
|
-
'Authorization': `Bearer ${GITLAB_TOKEN}`,
|
|
71
|
-
'Accept': 'application/json'
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
expect(response.ok).toBe(true);
|
|
75
|
-
const issues = await response.json();
|
|
76
|
-
expect(Array.isArray(issues)).toBe(true);
|
|
77
|
-
});
|
|
78
|
-
it('should add a comment to an issue', async () => {
|
|
79
|
-
if (!testIssueId) {
|
|
80
|
-
console.log('Skipping comment test: No test issue created');
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
const commentData = {
|
|
84
|
-
body: 'Test comment from MCP integration tests'
|
|
85
|
-
};
|
|
86
|
-
const response = await fetch(`${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(TEST_PROJECT_ID)}/issues/${testIssueId}/notes`, {
|
|
87
|
-
method: 'POST',
|
|
88
|
-
headers: {
|
|
89
|
-
'Authorization': `Bearer ${GITLAB_TOKEN}`,
|
|
90
|
-
'Accept': 'application/json',
|
|
91
|
-
'Content-Type': 'application/json'
|
|
92
|
-
},
|
|
93
|
-
body: JSON.stringify(commentData)
|
|
94
|
-
});
|
|
95
|
-
expect(response.ok).toBe(true);
|
|
96
|
-
const comment = await response.json();
|
|
97
|
-
expect(comment.body).toBe(commentData.body);
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
describe('Merge Request Operations', () => {
|
|
101
|
-
it('should list merge requests', async () => {
|
|
102
|
-
const response = await fetch(`${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(TEST_PROJECT_ID)}/merge_requests?state=opened`, {
|
|
103
|
-
headers: {
|
|
104
|
-
'Authorization': `Bearer ${GITLAB_TOKEN}`,
|
|
105
|
-
'Accept': 'application/json'
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
expect(response.ok).toBe(true);
|
|
109
|
-
const mrs = await response.json();
|
|
110
|
-
expect(Array.isArray(mrs)).toBe(true);
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
describe('Repository Operations', () => {
|
|
114
|
-
it('should fetch repository branches', async () => {
|
|
115
|
-
const response = await fetch(`${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(TEST_PROJECT_ID)}/repository/branches`, {
|
|
116
|
-
headers: {
|
|
117
|
-
'Authorization': `Bearer ${GITLAB_TOKEN}`,
|
|
118
|
-
'Accept': 'application/json'
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
expect(response.ok).toBe(true);
|
|
122
|
-
const branches = await response.json();
|
|
123
|
-
expect(Array.isArray(branches)).toBe(true);
|
|
124
|
-
expect(branches.length).toBeGreaterThan(0);
|
|
125
|
-
});
|
|
126
|
-
it('should fetch repository commits', async () => {
|
|
127
|
-
const response = await fetch(`${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(TEST_PROJECT_ID)}/repository/commits?per_page=5`, {
|
|
128
|
-
headers: {
|
|
129
|
-
'Authorization': `Bearer ${GITLAB_TOKEN}`,
|
|
130
|
-
'Accept': 'application/json'
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
expect(response.ok).toBe(true);
|
|
134
|
-
const commits = await response.json();
|
|
135
|
-
expect(Array.isArray(commits)).toBe(true);
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
describe('Pipeline Operations', () => {
|
|
139
|
-
it('should list pipelines', async () => {
|
|
140
|
-
const response = await fetch(`${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(TEST_PROJECT_ID)}/pipelines?per_page=5`, {
|
|
141
|
-
headers: {
|
|
142
|
-
'Authorization': `Bearer ${GITLAB_TOKEN}`,
|
|
143
|
-
'Accept': 'application/json'
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
expect(response.ok).toBe(true);
|
|
147
|
-
const pipelines = await response.json();
|
|
148
|
-
expect(Array.isArray(pipelines)).toBe(true);
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
});
|
package/build/tests/unit.test.js
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
|
|
2
|
-
import fetch from 'node-fetch';
|
|
3
|
-
// Mock fetch for unit tests
|
|
4
|
-
jest.mock('node-fetch');
|
|
5
|
-
const mockedFetch = fetch;
|
|
6
|
-
describe('GitLab MCP Server Unit Tests', () => {
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
jest.clearAllMocks();
|
|
9
|
-
});
|
|
10
|
-
afterEach(() => {
|
|
11
|
-
jest.restoreAllMocks();
|
|
12
|
-
});
|
|
13
|
-
describe('API URL Construction', () => {
|
|
14
|
-
it('should use plural resource names in API endpoints', () => {
|
|
15
|
-
const projectId = 'test/project';
|
|
16
|
-
const issueIid = 123;
|
|
17
|
-
// Test issue endpoint
|
|
18
|
-
const issueUrl = `/api/v4/projects/${encodeURIComponent(projectId)}/issues/${issueIid}`;
|
|
19
|
-
expect(issueUrl).toContain('/issues/');
|
|
20
|
-
expect(issueUrl).not.toContain('/issue/');
|
|
21
|
-
// Test merge request endpoint
|
|
22
|
-
const mrUrl = `/api/v4/projects/${encodeURIComponent(projectId)}/merge_requests/${issueIid}`;
|
|
23
|
-
expect(mrUrl).toContain('/merge_requests/');
|
|
24
|
-
expect(mrUrl).not.toContain('/merge_request/');
|
|
25
|
-
});
|
|
26
|
-
it('should properly encode project IDs with special characters', () => {
|
|
27
|
-
const projectId = 'namespace/project-name';
|
|
28
|
-
const encoded = encodeURIComponent(projectId);
|
|
29
|
-
expect(encoded).toBe('namespace%2Fproject-name');
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
describe('API Response Handling', () => {
|
|
33
|
-
it('should handle successful responses', async () => {
|
|
34
|
-
const mockResponse = {
|
|
35
|
-
ok: true,
|
|
36
|
-
status: 200,
|
|
37
|
-
json: async () => ({ id: 1, title: 'Test Issue' })
|
|
38
|
-
};
|
|
39
|
-
mockedFetch.mockResolvedValueOnce(mockResponse);
|
|
40
|
-
const response = await fetch('https://gitlab.com/api/v4/projects/1/issues/1');
|
|
41
|
-
const data = await response.json();
|
|
42
|
-
expect(response.ok).toBe(true);
|
|
43
|
-
expect(data).toEqual({ id: 1, title: 'Test Issue' });
|
|
44
|
-
});
|
|
45
|
-
it('should handle error responses', async () => {
|
|
46
|
-
const mockResponse = {
|
|
47
|
-
ok: false,
|
|
48
|
-
status: 404,
|
|
49
|
-
statusText: 'Not Found',
|
|
50
|
-
text: async () => '{"message":"404 Project Not Found"}'
|
|
51
|
-
};
|
|
52
|
-
mockedFetch.mockResolvedValueOnce(mockResponse);
|
|
53
|
-
const response = await fetch('https://gitlab.com/api/v4/projects/999/issues/1');
|
|
54
|
-
expect(response.ok).toBe(false);
|
|
55
|
-
expect(response.status).toBe(404);
|
|
56
|
-
});
|
|
57
|
-
it('should handle rate limiting', async () => {
|
|
58
|
-
const mockResponse = {
|
|
59
|
-
ok: false,
|
|
60
|
-
status: 429,
|
|
61
|
-
statusText: 'Too Many Requests',
|
|
62
|
-
headers: {
|
|
63
|
-
get: (name) => name === 'RateLimit-Reset' ? '1234567890' : null
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
mockedFetch.mockResolvedValueOnce(mockResponse);
|
|
67
|
-
const response = await fetch('https://gitlab.com/api/v4/projects/1/issues');
|
|
68
|
-
expect(response.status).toBe(429);
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
describe('Authentication', () => {
|
|
72
|
-
it('should include Bearer token in Authorization header', () => {
|
|
73
|
-
const token = 'test-token-123';
|
|
74
|
-
const headers = {
|
|
75
|
-
'Authorization': `Bearer ${token}`,
|
|
76
|
-
'Accept': 'application/json',
|
|
77
|
-
'Content-Type': 'application/json'
|
|
78
|
-
};
|
|
79
|
-
expect(headers.Authorization).toBe('Bearer test-token-123');
|
|
80
|
-
});
|
|
81
|
-
it('should handle missing token gracefully', () => {
|
|
82
|
-
const token = process.env.GITLAB_TOKEN || '';
|
|
83
|
-
expect(token).toBeDefined();
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
describe('Data Validation', () => {
|
|
87
|
-
it('should validate required fields for issue creation', () => {
|
|
88
|
-
const validIssue = {
|
|
89
|
-
title: 'Test Issue',
|
|
90
|
-
description: 'Test Description'
|
|
91
|
-
};
|
|
92
|
-
expect(validIssue.title).toBeTruthy();
|
|
93
|
-
expect(validIssue.title.length).toBeGreaterThan(0);
|
|
94
|
-
});
|
|
95
|
-
it('should validate merge request parameters', () => {
|
|
96
|
-
const validMR = {
|
|
97
|
-
source_branch: 'feature-branch',
|
|
98
|
-
target_branch: 'main',
|
|
99
|
-
title: 'Test MR'
|
|
100
|
-
};
|
|
101
|
-
expect(validMR.source_branch).not.toBe(validMR.target_branch);
|
|
102
|
-
expect(validMR.title).toBeTruthy();
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
describe('Error Handling', () => {
|
|
106
|
-
it('should handle network errors', async () => {
|
|
107
|
-
mockedFetch.mockRejectedValueOnce(new Error('Network error'));
|
|
108
|
-
await expect(fetch('https://gitlab.com/api/v4/projects/1/issues'))
|
|
109
|
-
.rejects.toThrow('Network error');
|
|
110
|
-
});
|
|
111
|
-
it('should handle JSON parsing errors', async () => {
|
|
112
|
-
const mockResponse = {
|
|
113
|
-
ok: true,
|
|
114
|
-
status: 200,
|
|
115
|
-
json: async () => { throw new Error('Invalid JSON'); }
|
|
116
|
-
};
|
|
117
|
-
mockedFetch.mockResolvedValueOnce(mockResponse);
|
|
118
|
-
const response = await fetch('https://gitlab.com/api/v4/projects/1/issues');
|
|
119
|
-
await expect(response.json()).rejects.toThrow('Invalid JSON');
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
});
|