@zereight/mcp-gitlab 1.0.69 → 1.0.71
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/index.js +38 -28
- package/build/schemas.js +70 -84
- package/build/utils.js +9 -0
- package/package.json +6 -4
package/build/index.js
CHANGED
|
@@ -29,7 +29,20 @@ GitLabDiscussionNoteSchema, // Added
|
|
|
29
29
|
GitLabDiscussionSchema, PaginatedDiscussionsResponseSchema, UpdateMergeRequestNoteSchema, // Added
|
|
30
30
|
CreateMergeRequestNoteSchema, // Added
|
|
31
31
|
ListMergeRequestDiscussionsSchema, UpdateIssueNoteSchema, CreateIssueNoteSchema, ListMergeRequestsSchema, GitLabMilestonesSchema, ListProjectMilestonesSchema, GetProjectMilestoneSchema, CreateProjectMilestoneSchema, EditProjectMilestoneSchema, DeleteProjectMilestoneSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, PromoteProjectMilestoneSchema, GetMilestoneBurndownEventsSchema, GitLabCompareResultSchema, GetBranchDiffsSchema, ListCommitsSchema, GetCommitSchema, GetCommitDiffSchema, ListMergeRequestDiffsSchema, } from "./schemas.js";
|
|
32
|
+
import { formatBoolean } from "./utils.js";
|
|
32
33
|
import { randomUUID } from "crypto";
|
|
34
|
+
import { pino } from 'pino';
|
|
35
|
+
const logger = pino({
|
|
36
|
+
level: process.env.LOG_LEVEL || 'info',
|
|
37
|
+
transport: {
|
|
38
|
+
target: 'pino-pretty',
|
|
39
|
+
options: {
|
|
40
|
+
colorize: true,
|
|
41
|
+
levelFirst: true,
|
|
42
|
+
destination: 2,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
});
|
|
33
46
|
/**
|
|
34
47
|
* Available transport modes for MCP server
|
|
35
48
|
*/
|
|
@@ -72,7 +85,7 @@ const USE_MILESTONE = process.env.USE_MILESTONE === "true";
|
|
|
72
85
|
const USE_PIPELINE = process.env.USE_PIPELINE === "true";
|
|
73
86
|
const SSE = process.env.SSE === "true";
|
|
74
87
|
const STREAMABLE_HTTP = process.env.STREAMABLE_HTTP === "true";
|
|
75
|
-
const HOST = process.env.HOST || '
|
|
88
|
+
const HOST = process.env.HOST || '0.0.0.0';
|
|
76
89
|
const PORT = process.env.PORT || 3002;
|
|
77
90
|
// Add proxy configuration
|
|
78
91
|
const HTTP_PROXY = process.env.HTTP_PROXY;
|
|
@@ -144,7 +157,7 @@ const createCookieJar = () => {
|
|
|
144
157
|
return jar;
|
|
145
158
|
}
|
|
146
159
|
catch (error) {
|
|
147
|
-
|
|
160
|
+
logger.error("Error loading cookie file:", error);
|
|
148
161
|
return null;
|
|
149
162
|
}
|
|
150
163
|
};
|
|
@@ -642,7 +655,7 @@ function normalizeGitLabApiUrl(url) {
|
|
|
642
655
|
const GITLAB_API_URL = normalizeGitLabApiUrl(process.env.GITLAB_API_URL || "");
|
|
643
656
|
const GITLAB_PROJECT_ID = process.env.GITLAB_PROJECT_ID;
|
|
644
657
|
if (!GITLAB_PERSONAL_ACCESS_TOKEN) {
|
|
645
|
-
|
|
658
|
+
logger.error("GITLAB_PERSONAL_ACCESS_TOKEN environment variable is not set");
|
|
646
659
|
process.exit(1);
|
|
647
660
|
}
|
|
648
661
|
/**
|
|
@@ -657,8 +670,8 @@ async function handleGitLabError(response) {
|
|
|
657
670
|
const errorBody = await response.text();
|
|
658
671
|
// Check specifically for Rate Limit error
|
|
659
672
|
if (response.status === 403 && errorBody.includes("User API Key Rate limit exceeded")) {
|
|
660
|
-
|
|
661
|
-
|
|
673
|
+
logger.error("GitLab API Rate Limit Exceeded:", errorBody);
|
|
674
|
+
logger.error("User API Key Rate limit exceeded. Please try again later.");
|
|
662
675
|
throw new Error(`GitLab API Rate Limit Exceeded: ${errorBody}`);
|
|
663
676
|
}
|
|
664
677
|
else {
|
|
@@ -1046,7 +1059,7 @@ async function createMergeRequest(projectId, options) {
|
|
|
1046
1059
|
labels: options.labels?.join(","),
|
|
1047
1060
|
allow_collaboration: options.allow_collaboration,
|
|
1048
1061
|
draft: options.draft,
|
|
1049
|
-
remove_source_branch: options.remove_source_branch,
|
|
1062
|
+
remove_source_branch: formatBoolean(options.remove_source_branch),
|
|
1050
1063
|
squash: options.squash,
|
|
1051
1064
|
}),
|
|
1052
1065
|
});
|
|
@@ -2176,10 +2189,7 @@ async function createPipeline(projectId, ref, variables) {
|
|
|
2176
2189
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipeline`);
|
|
2177
2190
|
const body = { ref };
|
|
2178
2191
|
if (variables && variables.length > 0) {
|
|
2179
|
-
body.variables = variables
|
|
2180
|
-
acc[key] = value;
|
|
2181
|
-
return acc;
|
|
2182
|
-
}, {});
|
|
2192
|
+
body.variables = variables;
|
|
2183
2193
|
}
|
|
2184
2194
|
const response = await fetch(url.toString(), {
|
|
2185
2195
|
method: "POST",
|
|
@@ -2256,7 +2266,7 @@ async function getRepositoryTree(options) {
|
|
|
2256
2266
|
else {
|
|
2257
2267
|
headers["Authorization"] = `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`;
|
|
2258
2268
|
}
|
|
2259
|
-
const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(options.project_id)}/repository/tree?${queryParams.toString()}`, {
|
|
2269
|
+
const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(options.project_id))}/repository/tree?${queryParams.toString()}`, {
|
|
2260
2270
|
headers,
|
|
2261
2271
|
});
|
|
2262
2272
|
if (response.status === 404) {
|
|
@@ -2456,7 +2466,7 @@ async function getUser(username) {
|
|
|
2456
2466
|
return null;
|
|
2457
2467
|
}
|
|
2458
2468
|
catch (error) {
|
|
2459
|
-
|
|
2469
|
+
logger.error(`Error fetching user by username '${username}':`, error);
|
|
2460
2470
|
return null;
|
|
2461
2471
|
}
|
|
2462
2472
|
}
|
|
@@ -2475,7 +2485,7 @@ async function getUsers(usernames) {
|
|
|
2475
2485
|
users[username] = user;
|
|
2476
2486
|
}
|
|
2477
2487
|
catch (error) {
|
|
2478
|
-
|
|
2488
|
+
logger.error(`Error processing username '${username}':`, error);
|
|
2479
2489
|
users[username] = null;
|
|
2480
2490
|
}
|
|
2481
2491
|
}
|
|
@@ -2621,7 +2631,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2621
2631
|
};
|
|
2622
2632
|
}
|
|
2623
2633
|
catch (forkError) {
|
|
2624
|
-
|
|
2634
|
+
logger.error("Error forking repository:", forkError);
|
|
2625
2635
|
let forkErrorMessage = "Failed to fork repository";
|
|
2626
2636
|
if (forkError instanceof Error) {
|
|
2627
2637
|
forkErrorMessage = `${forkErrorMessage}: ${forkError.message}`;
|
|
@@ -2842,7 +2852,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2842
2852
|
}
|
|
2843
2853
|
case "get_project": {
|
|
2844
2854
|
const args = GetProjectSchema.parse(request.params.arguments);
|
|
2845
|
-
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(args.project_id)}`);
|
|
2855
|
+
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(args.project_id))}`);
|
|
2846
2856
|
const response = await fetch(url.toString(), {
|
|
2847
2857
|
...DEFAULT_FETCH_CONFIG,
|
|
2848
2858
|
});
|
|
@@ -3368,10 +3378,10 @@ async function startSSEServer() {
|
|
|
3368
3378
|
});
|
|
3369
3379
|
});
|
|
3370
3380
|
app.listen(Number(PORT), HOST, () => {
|
|
3371
|
-
|
|
3381
|
+
logger.info(`GitLab MCP Server running with SSE transport`);
|
|
3372
3382
|
const colorGreen = "\x1b[32m";
|
|
3373
3383
|
const colorReset = "\x1b[0m";
|
|
3374
|
-
|
|
3384
|
+
logger.info(`${colorGreen}Endpoint: http://${HOST}:${PORT}/sse${colorReset}`);
|
|
3375
3385
|
});
|
|
3376
3386
|
}
|
|
3377
3387
|
/**
|
|
@@ -3398,14 +3408,14 @@ async function startStreamableHTTPServer() {
|
|
|
3398
3408
|
sessionIdGenerator: () => randomUUID(),
|
|
3399
3409
|
onsessioninitialized: (newSessionId) => {
|
|
3400
3410
|
streamableTransports[newSessionId] = transport;
|
|
3401
|
-
|
|
3411
|
+
logger.warn(`Streamable HTTP session initialized: ${newSessionId}`);
|
|
3402
3412
|
}
|
|
3403
3413
|
});
|
|
3404
3414
|
// Set up cleanup handler when transport closes
|
|
3405
3415
|
transport.onclose = () => {
|
|
3406
3416
|
const sid = transport.sessionId;
|
|
3407
3417
|
if (sid && streamableTransports[sid]) {
|
|
3408
|
-
|
|
3418
|
+
logger.warn(`Streamable HTTP transport closed for session ${sid}, cleaning up`);
|
|
3409
3419
|
delete streamableTransports[sid];
|
|
3410
3420
|
}
|
|
3411
3421
|
};
|
|
@@ -3415,7 +3425,7 @@ async function startStreamableHTTPServer() {
|
|
|
3415
3425
|
}
|
|
3416
3426
|
}
|
|
3417
3427
|
catch (error) {
|
|
3418
|
-
|
|
3428
|
+
logger.error('Streamable HTTP error:', error);
|
|
3419
3429
|
res.status(500).json({
|
|
3420
3430
|
error: 'Internal server error',
|
|
3421
3431
|
message: error instanceof Error ? error.message : 'Unknown error'
|
|
@@ -3433,8 +3443,8 @@ async function startStreamableHTTPServer() {
|
|
|
3433
3443
|
});
|
|
3434
3444
|
// Start server
|
|
3435
3445
|
app.listen(Number(PORT), HOST, () => {
|
|
3436
|
-
|
|
3437
|
-
|
|
3446
|
+
logger.info(`GitLab MCP Server running with Streamable HTTP transport`);
|
|
3447
|
+
logger.info(`${colorGreen}Endpoint: http://${HOST}:${PORT}/mcp${colorReset}`);
|
|
3438
3448
|
});
|
|
3439
3449
|
}
|
|
3440
3450
|
/**
|
|
@@ -3442,18 +3452,18 @@ async function startStreamableHTTPServer() {
|
|
|
3442
3452
|
* Handle transport-specific initialization logic
|
|
3443
3453
|
*/
|
|
3444
3454
|
async function initializeServerByTransportMode(mode) {
|
|
3445
|
-
|
|
3455
|
+
logger.info('Initializing server with transport mode:', mode);
|
|
3446
3456
|
switch (mode) {
|
|
3447
3457
|
case TransportMode.STDIO:
|
|
3448
|
-
|
|
3458
|
+
logger.warn('Starting GitLab MCP Server with stdio transport');
|
|
3449
3459
|
await startStdioServer();
|
|
3450
3460
|
break;
|
|
3451
3461
|
case TransportMode.SSE:
|
|
3452
|
-
|
|
3462
|
+
logger.warn('Starting GitLab MCP Server with SSE transport');
|
|
3453
3463
|
await startSSEServer();
|
|
3454
3464
|
break;
|
|
3455
3465
|
case TransportMode.STREAMABLE_HTTP:
|
|
3456
|
-
|
|
3466
|
+
logger.warn('Starting GitLab MCP Server with Streamable HTTP transport');
|
|
3457
3467
|
await startStreamableHTTPServer();
|
|
3458
3468
|
break;
|
|
3459
3469
|
default:
|
|
@@ -3472,11 +3482,11 @@ async function runServer() {
|
|
|
3472
3482
|
await initializeServerByTransportMode(transportMode);
|
|
3473
3483
|
}
|
|
3474
3484
|
catch (error) {
|
|
3475
|
-
|
|
3485
|
+
logger.error("Error initializing server:", error);
|
|
3476
3486
|
process.exit(1);
|
|
3477
3487
|
}
|
|
3478
3488
|
}
|
|
3479
3489
|
runServer().catch(error => {
|
|
3480
|
-
|
|
3490
|
+
logger.error("Fatal error in main():", error);
|
|
3481
3491
|
process.exit(1);
|
|
3482
3492
|
});
|
package/build/schemas.js
CHANGED
|
@@ -7,8 +7,8 @@ export const GitLabAuthorSchema = z.object({
|
|
|
7
7
|
});
|
|
8
8
|
// Pipeline related schemas
|
|
9
9
|
export const GitLabPipelineSchema = z.object({
|
|
10
|
-
id: z.number(),
|
|
11
|
-
project_id: z.number(),
|
|
10
|
+
id: z.string().or(z.number()),
|
|
11
|
+
project_id: z.string().or(z.number()),
|
|
12
12
|
sha: z.string(),
|
|
13
13
|
ref: z.string(),
|
|
14
14
|
status: z.string(),
|
|
@@ -22,7 +22,7 @@ export const GitLabPipelineSchema = z.object({
|
|
|
22
22
|
coverage: z.number().nullable().optional(),
|
|
23
23
|
user: z
|
|
24
24
|
.object({
|
|
25
|
-
id: z.number(),
|
|
25
|
+
id: z.string().or(z.number()),
|
|
26
26
|
name: z.string(),
|
|
27
27
|
username: z.string(),
|
|
28
28
|
avatar_url: z.string().nullable().optional(),
|
|
@@ -51,7 +51,7 @@ export const GitLabPipelineSchema = z.object({
|
|
|
51
51
|
});
|
|
52
52
|
// Pipeline job related schemas
|
|
53
53
|
export const GitLabPipelineJobSchema = z.object({
|
|
54
|
-
id: z.number(),
|
|
54
|
+
id: z.string().or(z.number()),
|
|
55
55
|
status: z.string(),
|
|
56
56
|
stage: z.string(),
|
|
57
57
|
name: z.string(),
|
|
@@ -64,7 +64,7 @@ export const GitLabPipelineJobSchema = z.object({
|
|
|
64
64
|
duration: z.number().nullable().optional(),
|
|
65
65
|
user: z
|
|
66
66
|
.object({
|
|
67
|
-
id: z.number(),
|
|
67
|
+
id: z.string().or(z.number()),
|
|
68
68
|
name: z.string(),
|
|
69
69
|
username: z.string(),
|
|
70
70
|
avatar_url: z.string().nullable().optional(),
|
|
@@ -81,8 +81,8 @@ export const GitLabPipelineJobSchema = z.object({
|
|
|
81
81
|
.optional(),
|
|
82
82
|
pipeline: z
|
|
83
83
|
.object({
|
|
84
|
-
id: z.number(),
|
|
85
|
-
project_id: z.number(),
|
|
84
|
+
id: z.string().or(z.number()),
|
|
85
|
+
project_id: z.string().or(z.number()),
|
|
86
86
|
status: z.string(),
|
|
87
87
|
ref: z.string(),
|
|
88
88
|
sha: z.string(),
|
|
@@ -140,12 +140,12 @@ export const ListPipelinesSchema = z.object({
|
|
|
140
140
|
// Schema for getting a specific pipeline
|
|
141
141
|
export const GetPipelineSchema = z.object({
|
|
142
142
|
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
143
|
-
pipeline_id: z.number().describe("The ID of the pipeline"),
|
|
143
|
+
pipeline_id: z.string().or(z.number().describe("The ID of the pipeline")),
|
|
144
144
|
});
|
|
145
145
|
// Schema for listing jobs in a pipeline
|
|
146
146
|
export const ListPipelineJobsSchema = z.object({
|
|
147
147
|
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
148
|
-
pipeline_id: z.number().describe("The ID of the pipeline"),
|
|
148
|
+
pipeline_id: z.string().or(z.number().describe("The ID of the pipeline")),
|
|
149
149
|
scope: z
|
|
150
150
|
.enum(["created", "pending", "running", "failed", "success", "canceled", "skipped", "manual"])
|
|
151
151
|
.optional()
|
|
@@ -167,24 +167,24 @@ export const CreatePipelineSchema = z.object({
|
|
|
167
167
|
// Schema for retrying a pipeline
|
|
168
168
|
export const RetryPipelineSchema = z.object({
|
|
169
169
|
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
170
|
-
pipeline_id: z.number().describe("The ID of the pipeline to retry"),
|
|
170
|
+
pipeline_id: z.string().or(z.number().describe("The ID of the pipeline to retry")),
|
|
171
171
|
});
|
|
172
172
|
// Schema for canceling a pipeline
|
|
173
173
|
export const CancelPipelineSchema = z.object({
|
|
174
174
|
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
175
|
-
pipeline_id: z.number().describe("The ID of the pipeline to cancel"),
|
|
175
|
+
pipeline_id: z.string().or(z.number().describe("The ID of the pipeline to cancel")),
|
|
176
176
|
});
|
|
177
177
|
// Schema for the input parameters for pipeline job operations
|
|
178
178
|
export const GetPipelineJobOutputSchema = z.object({
|
|
179
179
|
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
180
|
-
job_id: z.number().describe("The ID of the job"),
|
|
180
|
+
job_id: z.string().or(z.number().describe("The ID of the job")),
|
|
181
181
|
limit: z.number().optional().describe("Maximum number of lines to return from the end of the log (default: 1000)"),
|
|
182
182
|
offset: z.number().optional().describe("Number of lines to skip from the end of the log (default: 0)"),
|
|
183
183
|
});
|
|
184
184
|
// User schemas
|
|
185
185
|
export const GitLabUserSchema = z.object({
|
|
186
186
|
username: z.string(), // Changed from login to match GitLab API
|
|
187
|
-
id: z.number(),
|
|
187
|
+
id: z.string().or(z.number()),
|
|
188
188
|
name: z.string(),
|
|
189
189
|
avatar_url: z.string().nullable(),
|
|
190
190
|
web_url: z.string(), // Changed from html_url to match GitLab API
|
|
@@ -193,7 +193,7 @@ export const GetUsersSchema = z.object({
|
|
|
193
193
|
usernames: z.array(z.string()).describe("Array of usernames to search for"),
|
|
194
194
|
});
|
|
195
195
|
export const GitLabUsersResponseSchema = z.record(z.string(), z.object({
|
|
196
|
-
id: z.number(),
|
|
196
|
+
id: z.string().or(z.number()),
|
|
197
197
|
username: z.string(),
|
|
198
198
|
name: z.string(),
|
|
199
199
|
avatar_url: z.string().nullable(),
|
|
@@ -205,12 +205,12 @@ const ProjectParamsSchema = z.object({
|
|
|
205
205
|
project_id: z.string().describe("Project ID or complete URL-encoded path to project"), // Changed from owner/repo to match GitLab API
|
|
206
206
|
});
|
|
207
207
|
export const GitLabNamespaceSchema = z.object({
|
|
208
|
-
id: z.number(),
|
|
208
|
+
id: z.string().or(z.number()),
|
|
209
209
|
name: z.string(),
|
|
210
210
|
path: z.string(),
|
|
211
211
|
kind: z.enum(["user", "group"]),
|
|
212
212
|
full_path: z.string(),
|
|
213
|
-
parent_id: z.number().nullable(),
|
|
213
|
+
parent_id: z.string().or(z.number().nullable()),
|
|
214
214
|
avatar_url: z.string().nullable(),
|
|
215
215
|
web_url: z.string(),
|
|
216
216
|
members_count_with_descendants: z.number().optional(),
|
|
@@ -231,14 +231,14 @@ export const GitLabNamespaceExistsResponseSchema = z.object({
|
|
|
231
231
|
// Repository related schemas
|
|
232
232
|
export const GitLabOwnerSchema = z.object({
|
|
233
233
|
username: z.string(), // Changed from login to match GitLab API
|
|
234
|
-
id: z.number(),
|
|
234
|
+
id: z.string().or(z.number()),
|
|
235
235
|
avatar_url: z.string().nullable(),
|
|
236
236
|
web_url: z.string(), // Changed from html_url to match GitLab API
|
|
237
237
|
name: z.string(), // Added as GitLab includes full name
|
|
238
238
|
state: z.string(), // Added as GitLab includes user state
|
|
239
239
|
});
|
|
240
240
|
export const GitLabRepositorySchema = z.object({
|
|
241
|
-
id: z.number(),
|
|
241
|
+
id: z.string().or(z.number()),
|
|
242
242
|
name: z.string(),
|
|
243
243
|
path_with_namespace: z.string(),
|
|
244
244
|
visibility: z.string().optional(),
|
|
@@ -253,7 +253,7 @@ export const GitLabRepositorySchema = z.object({
|
|
|
253
253
|
default_branch: z.string().optional(),
|
|
254
254
|
namespace: z
|
|
255
255
|
.object({
|
|
256
|
-
id: z.number(),
|
|
256
|
+
id: z.string().or(z.number()),
|
|
257
257
|
name: z.string(),
|
|
258
258
|
path: z.string(),
|
|
259
259
|
kind: z.string(),
|
|
@@ -300,7 +300,7 @@ export const GitLabRepositorySchema = z.object({
|
|
|
300
300
|
shared_runners_enabled: z.boolean().optional(),
|
|
301
301
|
shared_with_groups: z
|
|
302
302
|
.array(z.object({
|
|
303
|
-
group_id: z.number(),
|
|
303
|
+
group_id: z.string().or(z.number()),
|
|
304
304
|
group_name: z.string(),
|
|
305
305
|
group_full_path: z.string(),
|
|
306
306
|
group_access_level: z.number(),
|
|
@@ -396,9 +396,9 @@ export const GitLabReferenceSchema = z.object({
|
|
|
396
396
|
});
|
|
397
397
|
// Milestones rest api output schemas
|
|
398
398
|
export const GitLabMilestonesSchema = z.object({
|
|
399
|
-
id: z.number(),
|
|
400
|
-
iid: z.number(),
|
|
401
|
-
project_id: z.number(),
|
|
399
|
+
id: z.string().or(z.number()),
|
|
400
|
+
iid: z.string().or(z.number()),
|
|
401
|
+
project_id: z.string().or(z.number()),
|
|
402
402
|
title: z.string(),
|
|
403
403
|
description: z.string().nullable(),
|
|
404
404
|
due_date: z.string().nullable(),
|
|
@@ -420,27 +420,9 @@ export const CreateIssueOptionsSchema = z.object({
|
|
|
420
420
|
title: z.string(),
|
|
421
421
|
description: z.string().optional(), // Changed from body to match GitLab API
|
|
422
422
|
assignee_ids: z.array(z.number()).optional(), // Changed from assignees to match GitLab API
|
|
423
|
-
milestone_id: z.number().optional(), // Changed from milestone to match GitLab API
|
|
423
|
+
milestone_id: z.string().or(z.number().optional()), // Changed from milestone to match GitLab API
|
|
424
424
|
labels: z.array(z.string()).optional(),
|
|
425
425
|
});
|
|
426
|
-
export const CreateMergeRequestOptionsSchema = z.object({
|
|
427
|
-
// Changed from CreatePullRequestOptionsSchema
|
|
428
|
-
title: z.string(),
|
|
429
|
-
description: z.string().optional(), // Changed from body to match GitLab API
|
|
430
|
-
source_branch: z.string(), // Changed from head to match GitLab API
|
|
431
|
-
target_branch: z.string(), // Changed from base to match GitLab API
|
|
432
|
-
assignee_ids: z
|
|
433
|
-
.array(z.number())
|
|
434
|
-
.optional(),
|
|
435
|
-
reviewer_ids: z
|
|
436
|
-
.array(z.number())
|
|
437
|
-
.optional(),
|
|
438
|
-
labels: z.array(z.string()).optional(),
|
|
439
|
-
allow_collaboration: z.boolean().optional(), // Changed from maintainer_can_modify to match GitLab API
|
|
440
|
-
draft: z.boolean().optional(),
|
|
441
|
-
remove_source_branch: z.boolean().optional().describe("Flag indicating if a merge request should remove the source branch when merging."),
|
|
442
|
-
squash: z.boolean().optional().describe("If true, squash all commits into a single commit on merge.")
|
|
443
|
-
});
|
|
444
426
|
export const GitLabDiffSchema = z.object({
|
|
445
427
|
old_path: z.string(),
|
|
446
428
|
new_path: z.string(),
|
|
@@ -485,7 +467,7 @@ export const GitLabCompareResultSchema = z.object({
|
|
|
485
467
|
});
|
|
486
468
|
// Issue related schemas
|
|
487
469
|
export const GitLabLabelSchema = z.object({
|
|
488
|
-
id: z.number(),
|
|
470
|
+
id: z.string().or(z.number()),
|
|
489
471
|
name: z.string(),
|
|
490
472
|
color: z.string(),
|
|
491
473
|
text_color: z.string(),
|
|
@@ -499,17 +481,17 @@ export const GitLabLabelSchema = z.object({
|
|
|
499
481
|
is_project_label: z.boolean().optional(),
|
|
500
482
|
});
|
|
501
483
|
export const GitLabMilestoneSchema = z.object({
|
|
502
|
-
id: z.number(),
|
|
503
|
-
iid: z.number(), // Added to match GitLab API
|
|
484
|
+
id: z.string().or(z.number()),
|
|
485
|
+
iid: z.string().or(z.number()), // Added to match GitLab API
|
|
504
486
|
title: z.string(),
|
|
505
487
|
description: z.string().nullable().default(""),
|
|
506
488
|
state: z.string(),
|
|
507
489
|
web_url: z.string(), // Changed from html_url to match GitLab API
|
|
508
490
|
});
|
|
509
491
|
export const GitLabIssueSchema = z.object({
|
|
510
|
-
id: z.number(),
|
|
511
|
-
iid: z.number(), // Added to match GitLab API
|
|
512
|
-
project_id: z.number(), // Added to match GitLab API
|
|
492
|
+
id: z.string().or(z.number()),
|
|
493
|
+
iid: z.string().or(z.number()), // Added to match GitLab API
|
|
494
|
+
project_id: z.string().or(z.number()), // Added to match GitLab API
|
|
513
495
|
title: z.string(),
|
|
514
496
|
description: z.string().nullable().default(""), // Changed from body to match GitLab API
|
|
515
497
|
state: z.string(),
|
|
@@ -543,7 +525,7 @@ export const GitLabIssueSchema = z.object({
|
|
|
543
525
|
});
|
|
544
526
|
// NEW SCHEMA: For issue with link details (used in listing issue links)
|
|
545
527
|
export const GitLabIssueWithLinkDetailsSchema = GitLabIssueSchema.extend({
|
|
546
|
-
issue_link_id: z.number(),
|
|
528
|
+
issue_link_id: z.string().or(z.number()),
|
|
547
529
|
link_type: z.enum(["relates_to", "blocks", "is_blocked_by"]),
|
|
548
530
|
link_created_at: z.string(),
|
|
549
531
|
link_updated_at: z.string(),
|
|
@@ -555,7 +537,7 @@ export const GitLabForkParentSchema = z.object({
|
|
|
555
537
|
owner: z
|
|
556
538
|
.object({
|
|
557
539
|
username: z.string(), // Changed from login to match GitLab API
|
|
558
|
-
id: z.number(),
|
|
540
|
+
id: z.string().or(z.number()),
|
|
559
541
|
avatar_url: z.string().nullable(),
|
|
560
542
|
})
|
|
561
543
|
.optional(), // Made optional to handle cases where GitLab API doesn't include it
|
|
@@ -571,9 +553,9 @@ export const GitLabMergeRequestDiffRefSchema = z.object({
|
|
|
571
553
|
start_sha: z.string(),
|
|
572
554
|
});
|
|
573
555
|
export const GitLabMergeRequestSchema = z.object({
|
|
574
|
-
id: z.number(),
|
|
575
|
-
iid: z.number(),
|
|
576
|
-
project_id: z.number(),
|
|
556
|
+
id: z.string().or(z.number()),
|
|
557
|
+
iid: z.string().or(z.number()),
|
|
558
|
+
project_id: z.string().or(z.number()),
|
|
577
559
|
title: z.string(),
|
|
578
560
|
description: z.string().nullable(),
|
|
579
561
|
state: z.string(),
|
|
@@ -621,7 +603,7 @@ export const LineRangeSchema = z.object({
|
|
|
621
603
|
}).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.");
|
|
622
604
|
// Discussion related schemas
|
|
623
605
|
export const GitLabDiscussionNoteSchema = z.object({
|
|
624
|
-
id: z.number(),
|
|
606
|
+
id: z.string().or(z.number()),
|
|
625
607
|
type: z.enum(["DiscussionNote", "DiffNote", "Note"]).nullable(), // Allow null type for regular notes
|
|
626
608
|
body: z.string(),
|
|
627
609
|
attachment: z.any().nullable(), // Can be string or object, handle appropriately
|
|
@@ -629,9 +611,9 @@ export const GitLabDiscussionNoteSchema = z.object({
|
|
|
629
611
|
created_at: z.string(),
|
|
630
612
|
updated_at: z.string(),
|
|
631
613
|
system: z.boolean(),
|
|
632
|
-
noteable_id: z.number(),
|
|
614
|
+
noteable_id: z.string().or(z.number()),
|
|
633
615
|
noteable_type: z.enum(["Issue", "MergeRequest", "Snippet", "Commit", "Epic"]),
|
|
634
|
-
project_id: z.number().optional(), // Optional for group-level discussions like Epics
|
|
616
|
+
project_id: z.string().or(z.number().optional()), // Optional for group-level discussions like Epics
|
|
635
617
|
noteable_iid: z.coerce.number().nullable(),
|
|
636
618
|
resolvable: z.boolean().optional(),
|
|
637
619
|
resolved: z.boolean().optional(),
|
|
@@ -683,17 +665,17 @@ export const PaginatedDiscussionsResponseSchema = z.object({
|
|
|
683
665
|
});
|
|
684
666
|
export const ListIssueDiscussionsSchema = z.object({
|
|
685
667
|
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
686
|
-
issue_iid: z.number().describe("The internal ID of the project issue"),
|
|
668
|
+
issue_iid: z.string().or(z.number().describe("The internal ID of the project issue")),
|
|
687
669
|
}).merge(PaginationOptionsSchema);
|
|
688
670
|
// Input schema for listing merge request discussions
|
|
689
671
|
export const ListMergeRequestDiscussionsSchema = ProjectParamsSchema.extend({
|
|
690
|
-
merge_request_iid: z.number().describe("The IID of a merge request"),
|
|
672
|
+
merge_request_iid: z.string().or(z.number().describe("The IID of a merge request")),
|
|
691
673
|
}).merge(PaginationOptionsSchema);
|
|
692
674
|
// Input schema for updating a merge request discussion note
|
|
693
675
|
export const UpdateMergeRequestNoteSchema = ProjectParamsSchema.extend({
|
|
694
|
-
merge_request_iid: z.number().describe("The IID of a merge request"),
|
|
676
|
+
merge_request_iid: z.string().or(z.number().describe("The IID of a merge request")),
|
|
695
677
|
discussion_id: z.string().describe("The ID of a thread"),
|
|
696
|
-
note_id: z.number().describe("The ID of a thread note"),
|
|
678
|
+
note_id: z.string().or(z.number().describe("The ID of a thread note")),
|
|
697
679
|
body: z.string().optional().describe("The content of the note or reply"),
|
|
698
680
|
resolved: z.boolean().optional().describe("Resolve or unresolve the note"),
|
|
699
681
|
})
|
|
@@ -705,21 +687,21 @@ export const UpdateMergeRequestNoteSchema = ProjectParamsSchema.extend({
|
|
|
705
687
|
});
|
|
706
688
|
// Input schema for adding a note to an existing merge request discussion
|
|
707
689
|
export const CreateMergeRequestNoteSchema = ProjectParamsSchema.extend({
|
|
708
|
-
merge_request_iid: z.number().describe("The IID of a merge request"),
|
|
690
|
+
merge_request_iid: z.string().or(z.number().describe("The IID of a merge request")),
|
|
709
691
|
discussion_id: z.string().describe("The ID of a thread"),
|
|
710
692
|
body: z.string().describe("The content of the note or reply"),
|
|
711
693
|
created_at: z.string().optional().describe("Date the note was created at (ISO 8601 format)"),
|
|
712
694
|
});
|
|
713
695
|
// Input schema for updating an issue discussion note
|
|
714
696
|
export const UpdateIssueNoteSchema = ProjectParamsSchema.extend({
|
|
715
|
-
issue_iid: z.number().describe("The IID of an issue"),
|
|
697
|
+
issue_iid: z.string().or(z.number().describe("The IID of an issue")),
|
|
716
698
|
discussion_id: z.string().describe("The ID of a thread"),
|
|
717
|
-
note_id: z.number().describe("The ID of a thread note"),
|
|
699
|
+
note_id: z.string().or(z.number().describe("The ID of a thread note")),
|
|
718
700
|
body: z.string().describe("The content of the note or reply"),
|
|
719
701
|
});
|
|
720
702
|
// Input schema for adding a note to an existing issue discussion
|
|
721
703
|
export const CreateIssueNoteSchema = ProjectParamsSchema.extend({
|
|
722
|
-
issue_iid: z.number().describe("The IID of an issue"),
|
|
704
|
+
issue_iid: z.string().or(z.number().describe("The IID of an issue")),
|
|
723
705
|
discussion_id: z.string().describe("The ID of a thread"),
|
|
724
706
|
body: z.string().describe("The content of the note or reply"),
|
|
725
707
|
created_at: z.string().optional().describe("Date the note was created at (ISO 8601 format)"),
|
|
@@ -765,9 +747,9 @@ export const CreateIssueSchema = ProjectParamsSchema.extend({
|
|
|
765
747
|
description: z.string().optional().describe("Issue description"),
|
|
766
748
|
assignee_ids: z.array(z.number()).optional().describe("Array of user IDs to assign"),
|
|
767
749
|
labels: z.array(z.string()).optional().describe("Array of label names"),
|
|
768
|
-
milestone_id: z.number().optional().describe("Milestone ID to assign"),
|
|
750
|
+
milestone_id: z.string().or(z.number().optional().describe("Milestone ID to assign")),
|
|
769
751
|
});
|
|
770
|
-
|
|
752
|
+
const MergeRequestOptionsSchema = {
|
|
771
753
|
title: z.string().describe("Merge request title"),
|
|
772
754
|
description: z.string().optional().describe("Merge request description"),
|
|
773
755
|
source_branch: z.string().describe("Branch containing changes"),
|
|
@@ -786,7 +768,11 @@ export const CreateMergeRequestSchema = ProjectParamsSchema.extend({
|
|
|
786
768
|
.boolean()
|
|
787
769
|
.optional()
|
|
788
770
|
.describe("Allow commits from upstream members"),
|
|
789
|
-
|
|
771
|
+
remove_source_branch: z.boolean().optional().nullable().describe("Flag indicating if a merge request should remove the source branch when merging."),
|
|
772
|
+
squash: z.boolean().optional().nullable().describe("If true, squash all commits into a single commit on merge."),
|
|
773
|
+
};
|
|
774
|
+
export const CreateMergeRequestOptionsSchema = z.object(MergeRequestOptionsSchema);
|
|
775
|
+
export const CreateMergeRequestSchema = ProjectParamsSchema.extend(MergeRequestOptionsSchema);
|
|
790
776
|
export const ForkRepositorySchema = ProjectParamsSchema.extend({
|
|
791
777
|
namespace: z.string().optional().describe("Namespace to fork to (full path)"),
|
|
792
778
|
});
|
|
@@ -802,7 +788,7 @@ export const GetBranchDiffsSchema = ProjectParamsSchema.extend({
|
|
|
802
788
|
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\"]"),
|
|
803
789
|
});
|
|
804
790
|
export const GetMergeRequestSchema = ProjectParamsSchema.extend({
|
|
805
|
-
merge_request_iid: z.number().optional().describe("The IID of a merge request"),
|
|
791
|
+
merge_request_iid: z.string().or(z.number().optional().describe("The IID of a merge request")),
|
|
806
792
|
source_branch: z.string().optional().describe("Source branch name"),
|
|
807
793
|
});
|
|
808
794
|
export const UpdateMergeRequestSchema = GetMergeRequestSchema.extend({
|
|
@@ -848,9 +834,9 @@ export const CreateNoteSchema = z.object({
|
|
|
848
834
|
// Issues API operation schemas
|
|
849
835
|
export const ListIssuesSchema = z.object({
|
|
850
836
|
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
851
|
-
assignee_id: z.number().optional().describe("Return issues assigned to the given user ID"),
|
|
837
|
+
assignee_id: z.string().or(z.number().optional().describe("Return issues assigned to the given user ID")),
|
|
852
838
|
assignee_username: z.array(z.string()).optional().describe("Return issues assigned to the given username"),
|
|
853
|
-
author_id: z.number().optional().describe("Return issues created by the given user ID"),
|
|
839
|
+
author_id: z.string().or(z.number().optional().describe("Return issues created by the given user ID")),
|
|
854
840
|
author_username: z.string().optional().describe("Return issues created by the given username"),
|
|
855
841
|
confidential: z.boolean().optional().describe("Filter confidential or public issues"),
|
|
856
842
|
created_after: z.string().optional().describe("Return issues created after the given time"),
|
|
@@ -882,7 +868,7 @@ export const ListMergeRequestsSchema = z.object({
|
|
|
882
868
|
.string()
|
|
883
869
|
.optional()
|
|
884
870
|
.describe("Returns merge requests assigned to the given username"),
|
|
885
|
-
author_id: z.number().optional().describe("Returns merge requests created by the given user ID"),
|
|
871
|
+
author_id: z.string().or(z.number().optional().describe("Returns merge requests created by the given user ID")),
|
|
886
872
|
author_username: z
|
|
887
873
|
.string()
|
|
888
874
|
.optional()
|
|
@@ -943,11 +929,11 @@ export const ListMergeRequestsSchema = z.object({
|
|
|
943
929
|
}).merge(PaginationOptionsSchema);
|
|
944
930
|
export const GetIssueSchema = z.object({
|
|
945
931
|
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
946
|
-
issue_iid: z.number().describe("The internal ID of the project issue"),
|
|
932
|
+
issue_iid: z.string().or(z.number().describe("The internal ID of the project issue")),
|
|
947
933
|
});
|
|
948
934
|
export const UpdateIssueSchema = z.object({
|
|
949
935
|
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
950
|
-
issue_iid: z.number().describe("The internal ID of the project issue"),
|
|
936
|
+
issue_iid: z.string().or(z.number().describe("The internal ID of the project issue")),
|
|
951
937
|
title: z.string().optional().describe("The title of the issue"),
|
|
952
938
|
description: z.string().optional().describe("The description of the issue"),
|
|
953
939
|
assignee_ids: z.array(z.number()).optional().describe("Array of user IDs to assign issue to"),
|
|
@@ -955,13 +941,13 @@ export const UpdateIssueSchema = z.object({
|
|
|
955
941
|
discussion_locked: z.boolean().optional().describe("Flag to lock discussions"),
|
|
956
942
|
due_date: z.string().optional().describe("Date the issue is due (YYYY-MM-DD)"),
|
|
957
943
|
labels: z.array(z.string()).optional().describe("Array of label names"),
|
|
958
|
-
milestone_id: z.number().optional().describe("Milestone ID to assign"),
|
|
944
|
+
milestone_id: z.string().or(z.number().optional().describe("Milestone ID to assign")),
|
|
959
945
|
state_event: z.enum(["close", "reopen"]).optional().describe("Update issue state (close/reopen)"),
|
|
960
946
|
weight: z.number().optional().describe("Weight of the issue (0-9)"),
|
|
961
947
|
});
|
|
962
948
|
export const DeleteIssueSchema = z.object({
|
|
963
949
|
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
964
|
-
issue_iid: z.number().describe("The internal ID of the project issue"),
|
|
950
|
+
issue_iid: z.string().or(z.number().describe("The internal ID of the project issue")),
|
|
965
951
|
});
|
|
966
952
|
// Issue links related schemas
|
|
967
953
|
export const GitLabIssueLinkSchema = z.object({
|
|
@@ -971,18 +957,18 @@ export const GitLabIssueLinkSchema = z.object({
|
|
|
971
957
|
});
|
|
972
958
|
export const ListIssueLinksSchema = z.object({
|
|
973
959
|
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
974
|
-
issue_iid: z.number().describe("The internal ID of a project's issue"),
|
|
960
|
+
issue_iid: z.string().or(z.number().describe("The internal ID of a project's issue")),
|
|
975
961
|
});
|
|
976
962
|
export const GetIssueLinkSchema = z.object({
|
|
977
963
|
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
978
|
-
issue_iid: z.number().describe("The internal ID of a project's issue"),
|
|
979
|
-
issue_link_id: z.number().describe("ID of an issue relationship"),
|
|
964
|
+
issue_iid: z.string().or(z.number().describe("The internal ID of a project's issue")),
|
|
965
|
+
issue_link_id: z.string().or(z.number().describe("ID of an issue relationship")),
|
|
980
966
|
});
|
|
981
967
|
export const CreateIssueLinkSchema = z.object({
|
|
982
968
|
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
983
|
-
issue_iid: z.number().describe("The internal ID of a project's issue"),
|
|
969
|
+
issue_iid: z.string().or(z.number().describe("The internal ID of a project's issue")),
|
|
984
970
|
target_project_id: z.string().describe("The ID or URL-encoded path of a target project"),
|
|
985
|
-
target_issue_iid: z.number().describe("The internal ID of a target project's issue"),
|
|
971
|
+
target_issue_iid: z.string().or(z.number().describe("The internal ID of a target project's issue")),
|
|
986
972
|
link_type: z
|
|
987
973
|
.enum(["relates_to", "blocks", "is_blocked_by"])
|
|
988
974
|
.optional()
|
|
@@ -990,8 +976,8 @@ export const CreateIssueLinkSchema = z.object({
|
|
|
990
976
|
});
|
|
991
977
|
export const DeleteIssueLinkSchema = z.object({
|
|
992
978
|
project_id: z.string().describe("Project ID or URL-encoded path"),
|
|
993
|
-
issue_iid: z.number().describe("The internal ID of a project's issue"),
|
|
994
|
-
issue_link_id: z.number().describe("The ID of an issue relationship"),
|
|
979
|
+
issue_iid: z.string().or(z.number().describe("The internal ID of a project's issue")),
|
|
980
|
+
issue_link_id: z.string().or(z.number().describe("The ID of an issue relationship")),
|
|
995
981
|
});
|
|
996
982
|
// Namespace API operation schemas
|
|
997
983
|
export const ListNamespacesSchema = z.object({
|
|
@@ -1159,7 +1145,7 @@ export const MergeRequestThreadPositionSchema = z.object({
|
|
|
1159
1145
|
});
|
|
1160
1146
|
// Schema for creating a new merge request thread
|
|
1161
1147
|
export const CreateMergeRequestThreadSchema = ProjectParamsSchema.extend({
|
|
1162
|
-
merge_request_iid: z.number().describe("The IID of a merge request"),
|
|
1148
|
+
merge_request_iid: z.string().or(z.number().describe("The IID of a merge request")),
|
|
1163
1149
|
body: z.string().describe("The content of the thread"),
|
|
1164
1150
|
position: MergeRequestThreadPositionSchema.optional().describe("Position when creating a diff note"),
|
|
1165
1151
|
created_at: z.string().optional().describe("Date the thread was created at (ISO 8601 format)"),
|
|
@@ -1192,7 +1178,7 @@ export const ListProjectMilestonesSchema = ProjectParamsSchema.extend({
|
|
|
1192
1178
|
}).merge(PaginationOptionsSchema);
|
|
1193
1179
|
// Schema for getting a single milestone
|
|
1194
1180
|
export const GetProjectMilestoneSchema = ProjectParamsSchema.extend({
|
|
1195
|
-
milestone_id: z.number().describe("The ID of a project milestone"),
|
|
1181
|
+
milestone_id: z.string().or(z.number().describe("The ID of a project milestone")),
|
|
1196
1182
|
});
|
|
1197
1183
|
// Schema for creating a new milestone
|
|
1198
1184
|
export const CreateProjectMilestoneSchema = ProjectParamsSchema.extend({
|
package/build/utils.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zereight/mcp-gitlab",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.71",
|
|
4
4
|
"description": "MCP server for using the GitLab API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "zereight",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"deploy": "npm publish --access public",
|
|
24
24
|
"generate-tools": "npx ts-node scripts/generate-tools-readme.ts",
|
|
25
25
|
"changelog": "auto-changelog -p",
|
|
26
|
-
"test": "node test/validate-api.js
|
|
26
|
+
"test": "node test/validate-api.js",
|
|
27
27
|
"test:integration": "node test/validate-api.js",
|
|
28
28
|
"test:server": "npm run build && node build/test/test-all-transport-server.js",
|
|
29
29
|
"lint": "eslint . --ext .ts",
|
|
@@ -40,6 +40,8 @@
|
|
|
40
40
|
"http-proxy-agent": "^7.0.2",
|
|
41
41
|
"https-proxy-agent": "^7.0.6",
|
|
42
42
|
"node-fetch": "^3.3.2",
|
|
43
|
+
"pino": "^9.7.0",
|
|
44
|
+
"pino-pretty": "^13.0.0",
|
|
43
45
|
"socks-proxy-agent": "^8.0.5",
|
|
44
46
|
"tough-cookie": "^5.1.2",
|
|
45
47
|
"zod-to-json-schema": "^3.23.5"
|
|
@@ -49,11 +51,11 @@
|
|
|
49
51
|
"@types/node": "^22.13.10",
|
|
50
52
|
"@typescript-eslint/eslint-plugin": "^8.21.0",
|
|
51
53
|
"@typescript-eslint/parser": "^8.21.0",
|
|
54
|
+
"auto-changelog": "^2.4.0",
|
|
52
55
|
"eslint": "^9.18.0",
|
|
53
56
|
"prettier": "^3.4.2",
|
|
54
57
|
"ts-node": "^10.9.2",
|
|
55
58
|
"typescript": "^5.8.2",
|
|
56
|
-
"zod": "^3.24.2"
|
|
57
|
-
"auto-changelog": "^2.4.0"
|
|
59
|
+
"zod": "^3.24.2"
|
|
58
60
|
}
|
|
59
61
|
}
|