gitlab-mcp 0.1.4 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +7 -0
- package/.editorconfig +9 -0
- package/.env.example +75 -0
- package/.github/workflows/nodejs.yml +31 -0
- package/.github/workflows/npm-publish.yml +31 -0
- package/.husky/pre-commit +1 -0
- package/.nvmrc +1 -0
- package/.prettierrc.json +6 -0
- package/Dockerfile +20 -0
- package/README.md +416 -251
- package/docker-compose.yml +10 -0
- package/docs/architecture.md +310 -0
- package/docs/authentication.md +299 -0
- package/docs/configuration.md +149 -0
- package/docs/deployment.md +336 -0
- package/docs/tools.md +294 -0
- package/eslint.config.js +23 -0
- package/package.json +70 -32
- package/scripts/get-oauth-token.example.sh +15 -0
- package/src/config/env.ts +171 -0
- package/src/http.ts +605 -0
- package/src/index.ts +77 -0
- package/src/lib/auth-context.ts +19 -0
- package/src/lib/gitlab-client.ts +1810 -0
- package/src/lib/logger.ts +17 -0
- package/src/lib/network.ts +45 -0
- package/src/lib/oauth.ts +287 -0
- package/src/lib/output.ts +51 -0
- package/src/lib/policy.ts +78 -0
- package/src/lib/request-runtime.ts +376 -0
- package/src/lib/sanitize.ts +25 -0
- package/src/server/build-server.ts +17 -0
- package/src/tools/gitlab.ts +3128 -0
- package/src/tools/health.ts +27 -0
- package/src/tools/mr-code-context.ts +473 -0
- package/src/types/context.ts +13 -0
- package/tests/auth-context.test.ts +102 -0
- package/tests/gitlab-client.test.ts +674 -0
- package/tests/graphql-guard.test.ts +121 -0
- package/tests/integration/agent-loop.integration.test.ts +552 -0
- package/tests/integration/server.integration.test.ts +543 -0
- package/tests/mr-code-context.test.ts +600 -0
- package/tests/oauth.test.ts +43 -0
- package/tests/output.test.ts +186 -0
- package/tests/policy.test.ts +324 -0
- package/tests/request-runtime.test.ts +252 -0
- package/tests/sanitize.test.ts +123 -0
- package/tests/upload-reference.test.ts +84 -0
- package/tsconfig.build.json +11 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +12 -0
- package/LICENSE +0 -21
- package/build/index.js +0 -1641
- package/build/schemas.js +0 -684
- package/build/test-note.js +0 -54
|
@@ -0,0 +1,3128 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
import { GitLabApiError, type PushFileAction } from "../lib/gitlab-client.js";
|
|
6
|
+
import { getSessionAuth } from "../lib/auth-context.js";
|
|
7
|
+
import { stripNullsDeep } from "../lib/sanitize.js";
|
|
8
|
+
import type { AppContext } from "../types/context.js";
|
|
9
|
+
import { getMergeRequestCodeContext, mergeRequestCodeContextSchema } from "./mr-code-context.js";
|
|
10
|
+
|
|
11
|
+
type ToolArgs = Record<string, unknown>;
|
|
12
|
+
|
|
13
|
+
type ToolSchemaShape = Record<string, z.ZodTypeAny>;
|
|
14
|
+
|
|
15
|
+
interface GitLabToolDefinition {
|
|
16
|
+
name: string;
|
|
17
|
+
title: string;
|
|
18
|
+
description: string;
|
|
19
|
+
mutating: boolean;
|
|
20
|
+
requiresAuth?: boolean;
|
|
21
|
+
requiresFeature?: "wiki" | "milestone" | "pipeline" | "release";
|
|
22
|
+
inputSchema?: ToolSchemaShape;
|
|
23
|
+
handler: (args: ToolArgs, context: AppContext) => Promise<unknown>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const optionalString = z.preprocess(
|
|
27
|
+
(value) => (value === null ? undefined : value),
|
|
28
|
+
z.string().optional()
|
|
29
|
+
);
|
|
30
|
+
const optionalNumber = z.preprocess(
|
|
31
|
+
(value) => (value === null ? undefined : value),
|
|
32
|
+
z.number().optional()
|
|
33
|
+
);
|
|
34
|
+
const optionalBoolean = z.preprocess(
|
|
35
|
+
(value) => (value === null ? undefined : value),
|
|
36
|
+
z.boolean().optional()
|
|
37
|
+
);
|
|
38
|
+
const optionalStringArray = z.preprocess(
|
|
39
|
+
(value) => (value === null ? undefined : value),
|
|
40
|
+
z.array(z.string()).optional()
|
|
41
|
+
);
|
|
42
|
+
const optionalNumberArray = z.preprocess(
|
|
43
|
+
(value) => (value === null ? undefined : value),
|
|
44
|
+
z.array(z.number()).optional()
|
|
45
|
+
);
|
|
46
|
+
const optionalStringOrNumber = z.preprocess(
|
|
47
|
+
(value) => (value === null ? undefined : value),
|
|
48
|
+
z.union([z.string(), z.number()]).optional()
|
|
49
|
+
);
|
|
50
|
+
const optionalStringOrStringArray = z.preprocess(
|
|
51
|
+
(value) => (value === null ? undefined : value),
|
|
52
|
+
z.union([z.string(), z.array(z.string())]).optional()
|
|
53
|
+
);
|
|
54
|
+
const optionalRecord = z.preprocess(
|
|
55
|
+
(value) => (value === null ? undefined : value),
|
|
56
|
+
z.record(z.string(), z.unknown()).optional()
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const paginationShape = {
|
|
60
|
+
page: optionalNumber,
|
|
61
|
+
per_page: optionalNumber
|
|
62
|
+
} satisfies ToolSchemaShape;
|
|
63
|
+
|
|
64
|
+
export function registerGitLabTools(server: McpServer, context: AppContext): void {
|
|
65
|
+
const definitions = getGitLabToolDefinitions();
|
|
66
|
+
const disableGraphqlTools = shouldDisableGraphqlTools(
|
|
67
|
+
context.env.GITLAB_ALLOWED_PROJECT_IDS,
|
|
68
|
+
context.env.GITLAB_ALLOW_GRAPHQL_WITH_PROJECT_SCOPE
|
|
69
|
+
);
|
|
70
|
+
const filtered = context.policy.filterTools(
|
|
71
|
+
definitions.map((item) => ({
|
|
72
|
+
name: item.name,
|
|
73
|
+
mutating: item.mutating,
|
|
74
|
+
requiresFeature: item.requiresFeature
|
|
75
|
+
}))
|
|
76
|
+
);
|
|
77
|
+
const enabledNames = new Set(filtered.map((item) => item.name));
|
|
78
|
+
|
|
79
|
+
for (const definition of definitions) {
|
|
80
|
+
if (!enabledNames.has(definition.name)) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (disableGraphqlTools && isGraphqlToolName(definition.name)) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
server.registerTool(
|
|
89
|
+
definition.name,
|
|
90
|
+
{
|
|
91
|
+
title: definition.title,
|
|
92
|
+
description: definition.description,
|
|
93
|
+
inputSchema: definition.inputSchema ?? {}
|
|
94
|
+
},
|
|
95
|
+
async (rawArgs) => {
|
|
96
|
+
try {
|
|
97
|
+
context.policy.assertCanExecute({
|
|
98
|
+
name: definition.name,
|
|
99
|
+
mutating: definition.mutating,
|
|
100
|
+
requiresFeature: definition.requiresFeature
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
if (definition.requiresAuth ?? true) {
|
|
104
|
+
assertAuthReady(context);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const args = stripNullsDeep((rawArgs ?? {}) as ToolArgs);
|
|
108
|
+
const result = await definition.handler(args, context);
|
|
109
|
+
const formatted = context.formatter.format(result);
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
content: [
|
|
113
|
+
{
|
|
114
|
+
type: "text",
|
|
115
|
+
text: formatted.text
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
structuredContent: {
|
|
119
|
+
result: toStructuredContent(result),
|
|
120
|
+
meta: {
|
|
121
|
+
truncated: formatted.truncated,
|
|
122
|
+
bytes: formatted.bytes
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
} catch (error) {
|
|
127
|
+
return toToolError(error, context);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function getGitLabToolDefinitions(): GitLabToolDefinition[] {
|
|
135
|
+
return [
|
|
136
|
+
{
|
|
137
|
+
name: "gitlab_get_project",
|
|
138
|
+
title: "Get Project",
|
|
139
|
+
description: "Get project details by ID or path.",
|
|
140
|
+
mutating: false,
|
|
141
|
+
inputSchema: {
|
|
142
|
+
project_id: z.string().optional()
|
|
143
|
+
},
|
|
144
|
+
handler: async (args, context) => {
|
|
145
|
+
const projectId = resolveProjectId(args, context, true);
|
|
146
|
+
return context.gitlab.getProject(projectId);
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: "gitlab_list_projects",
|
|
151
|
+
title: "List Projects",
|
|
152
|
+
description: "List projects available to the current user.",
|
|
153
|
+
mutating: false,
|
|
154
|
+
inputSchema: {
|
|
155
|
+
search: optionalString,
|
|
156
|
+
search_namespaces: optionalBoolean,
|
|
157
|
+
membership: optionalBoolean,
|
|
158
|
+
owned: optionalBoolean,
|
|
159
|
+
simple: optionalBoolean,
|
|
160
|
+
archived: optionalBoolean,
|
|
161
|
+
visibility: z.enum(["public", "internal", "private"]).optional(),
|
|
162
|
+
order_by: z
|
|
163
|
+
.enum(["id", "name", "path", "created_at", "updated_at", "last_activity_at"])
|
|
164
|
+
.optional(),
|
|
165
|
+
sort: z.enum(["asc", "desc"]).optional(),
|
|
166
|
+
with_issues_enabled: optionalBoolean,
|
|
167
|
+
with_merge_requests_enabled: optionalBoolean,
|
|
168
|
+
min_access_level: optionalNumber,
|
|
169
|
+
...paginationShape
|
|
170
|
+
},
|
|
171
|
+
handler: async (args, context) => context.gitlab.listProjects({ query: toQuery(args) })
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: "gitlab_create_repository",
|
|
175
|
+
title: "Create Repository",
|
|
176
|
+
description: "Create a new GitLab project/repository.",
|
|
177
|
+
mutating: true,
|
|
178
|
+
inputSchema: {
|
|
179
|
+
name: optionalString,
|
|
180
|
+
description: optionalString,
|
|
181
|
+
visibility: z.enum(["private", "internal", "public"]).optional(),
|
|
182
|
+
initialize_with_readme: optionalBoolean,
|
|
183
|
+
path: optionalString,
|
|
184
|
+
namespace_id: optionalString,
|
|
185
|
+
default_branch: optionalString
|
|
186
|
+
},
|
|
187
|
+
handler: async (args, context) =>
|
|
188
|
+
context.gitlab.createRepository({
|
|
189
|
+
name: getString(args, "name"),
|
|
190
|
+
description: getOptionalString(args, "description"),
|
|
191
|
+
visibility: getOptionalString(args, "visibility") as
|
|
192
|
+
| "private"
|
|
193
|
+
| "internal"
|
|
194
|
+
| "public"
|
|
195
|
+
| undefined,
|
|
196
|
+
initialize_with_readme: getOptionalBoolean(args, "initialize_with_readme"),
|
|
197
|
+
path: getOptionalString(args, "path"),
|
|
198
|
+
namespace_id: getOptionalString(args, "namespace_id"),
|
|
199
|
+
default_branch: getOptionalString(args, "default_branch")
|
|
200
|
+
})
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: "gitlab_list_project_members",
|
|
204
|
+
title: "List Project Members",
|
|
205
|
+
description: "List members of a project.",
|
|
206
|
+
mutating: false,
|
|
207
|
+
inputSchema: {
|
|
208
|
+
project_id: z.string().optional(),
|
|
209
|
+
query: optionalString,
|
|
210
|
+
user_ids: optionalNumberArray,
|
|
211
|
+
skip_users: optionalNumberArray,
|
|
212
|
+
include_inheritance: optionalBoolean,
|
|
213
|
+
...paginationShape
|
|
214
|
+
},
|
|
215
|
+
handler: async (args, context) => {
|
|
216
|
+
const projectId = resolveProjectId(args, context, true);
|
|
217
|
+
return context.gitlab.listProjectMembers(projectId, {
|
|
218
|
+
query: toQuery(omit(args, ["project_id"]))
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: "gitlab_list_group_projects",
|
|
224
|
+
title: "List Group Projects",
|
|
225
|
+
description: "List projects under a group.",
|
|
226
|
+
mutating: false,
|
|
227
|
+
inputSchema: {
|
|
228
|
+
group_id: z.string(),
|
|
229
|
+
include_subgroups: optionalBoolean,
|
|
230
|
+
search: optionalString,
|
|
231
|
+
order_by: z
|
|
232
|
+
.enum(["name", "path", "created_at", "updated_at", "last_activity_at"])
|
|
233
|
+
.optional(),
|
|
234
|
+
sort: z.enum(["asc", "desc"]).optional(),
|
|
235
|
+
archived: optionalBoolean,
|
|
236
|
+
visibility: z.enum(["public", "internal", "private"]).optional(),
|
|
237
|
+
with_issues_enabled: optionalBoolean,
|
|
238
|
+
with_merge_requests_enabled: optionalBoolean,
|
|
239
|
+
min_access_level: optionalNumber,
|
|
240
|
+
with_programming_language: optionalString,
|
|
241
|
+
starred: optionalBoolean,
|
|
242
|
+
statistics: optionalBoolean,
|
|
243
|
+
with_custom_attributes: optionalBoolean,
|
|
244
|
+
with_security_reports: optionalBoolean,
|
|
245
|
+
...paginationShape
|
|
246
|
+
},
|
|
247
|
+
handler: async (args, context) => {
|
|
248
|
+
return context.gitlab.listGroupProjects(getString(args, "group_id"), {
|
|
249
|
+
query: toQuery(omit(args, ["group_id"]))
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: "gitlab_list_group_iterations",
|
|
255
|
+
title: "List Group Iterations",
|
|
256
|
+
description: "List iterations for a group.",
|
|
257
|
+
mutating: false,
|
|
258
|
+
inputSchema: {
|
|
259
|
+
group_id: z.string().min(1),
|
|
260
|
+
state: optionalString,
|
|
261
|
+
search: optionalString,
|
|
262
|
+
search_in: optionalStringArray,
|
|
263
|
+
include_ancestors: optionalBoolean,
|
|
264
|
+
include_descendants: optionalBoolean,
|
|
265
|
+
updated_before: optionalString,
|
|
266
|
+
updated_after: optionalString,
|
|
267
|
+
...paginationShape
|
|
268
|
+
},
|
|
269
|
+
handler: async (args, context) => {
|
|
270
|
+
const query = toQuery(omit(args, ["group_id"]));
|
|
271
|
+
const searchIn = getOptionalStringArray(args, "search_in");
|
|
272
|
+
if (searchIn && searchIn.length > 0) {
|
|
273
|
+
query.in = searchIn.join(",");
|
|
274
|
+
delete query.search_in;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return context.gitlab.listGroupIterations(getString(args, "group_id"), { query });
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: "gitlab_search_repositories",
|
|
282
|
+
title: "Search Repositories",
|
|
283
|
+
description: "Search repositories by keyword.",
|
|
284
|
+
mutating: false,
|
|
285
|
+
inputSchema: {
|
|
286
|
+
search: z.string().min(1),
|
|
287
|
+
...paginationShape
|
|
288
|
+
},
|
|
289
|
+
handler: async (args, context) =>
|
|
290
|
+
context.gitlab.searchRepositories(getString(args, "search"), {
|
|
291
|
+
query: toQuery(omit(args, ["search"]))
|
|
292
|
+
})
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
name: "gitlab_search_code_blobs",
|
|
296
|
+
title: "Search Code Blobs",
|
|
297
|
+
description: "Search repository code blobs in a specific project.",
|
|
298
|
+
mutating: false,
|
|
299
|
+
inputSchema: {
|
|
300
|
+
project_id: z.string().optional(),
|
|
301
|
+
search: z.string().min(1),
|
|
302
|
+
ref: optionalString,
|
|
303
|
+
...paginationShape
|
|
304
|
+
},
|
|
305
|
+
handler: async (args, context) =>
|
|
306
|
+
context.gitlab.searchCodeBlobs(
|
|
307
|
+
resolveProjectId(args, context, true),
|
|
308
|
+
getString(args, "search"),
|
|
309
|
+
{ query: toQuery(omit(args, ["project_id", "search"])) }
|
|
310
|
+
)
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
name: "gitlab_get_repository_tree",
|
|
314
|
+
title: "Get Repository Tree",
|
|
315
|
+
description: "List files and directories in a repository tree.",
|
|
316
|
+
mutating: false,
|
|
317
|
+
inputSchema: {
|
|
318
|
+
project_id: z.string().optional(),
|
|
319
|
+
path: optionalString,
|
|
320
|
+
ref: optionalString,
|
|
321
|
+
recursive: optionalBoolean,
|
|
322
|
+
...paginationShape
|
|
323
|
+
},
|
|
324
|
+
handler: async (args, context) => {
|
|
325
|
+
const projectId = resolveProjectId(args, context, true);
|
|
326
|
+
return context.gitlab.getRepositoryTree(projectId, {
|
|
327
|
+
query: toQuery(omit(args, ["project_id"]))
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
name: "gitlab_get_file_contents",
|
|
333
|
+
title: "Get File Contents",
|
|
334
|
+
description: "Get a file in repository by path and ref.",
|
|
335
|
+
mutating: false,
|
|
336
|
+
inputSchema: {
|
|
337
|
+
project_id: z.string().optional(),
|
|
338
|
+
file_path: z.string().min(1),
|
|
339
|
+
ref: optionalString
|
|
340
|
+
},
|
|
341
|
+
handler: async (args, context) => {
|
|
342
|
+
const projectId = resolveProjectId(args, context, true);
|
|
343
|
+
let ref = getOptionalString(args, "ref");
|
|
344
|
+
if (!ref) {
|
|
345
|
+
const project = (await context.gitlab.getProject(projectId)) as {
|
|
346
|
+
default_branch?: unknown;
|
|
347
|
+
};
|
|
348
|
+
ref = typeof project.default_branch === "string" ? project.default_branch : "main";
|
|
349
|
+
}
|
|
350
|
+
return context.gitlab.getFileContents(projectId, getString(args, "file_path"), ref);
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
name: "gitlab_create_or_update_file",
|
|
355
|
+
title: "Create Or Update File",
|
|
356
|
+
description: "Create or update one file in repository.",
|
|
357
|
+
mutating: true,
|
|
358
|
+
inputSchema: {
|
|
359
|
+
project_id: z.string().optional(),
|
|
360
|
+
file_path: z.string().min(1),
|
|
361
|
+
branch: z.string().min(1),
|
|
362
|
+
content: z.string(),
|
|
363
|
+
commit_message: z.string().min(1),
|
|
364
|
+
previous_path: optionalString,
|
|
365
|
+
author_email: optionalString,
|
|
366
|
+
author_name: optionalString,
|
|
367
|
+
encoding: optionalString,
|
|
368
|
+
execute_filemode: optionalBoolean,
|
|
369
|
+
start_branch: optionalString,
|
|
370
|
+
last_commit_id: optionalString,
|
|
371
|
+
commit_id: optionalString
|
|
372
|
+
},
|
|
373
|
+
handler: async (args, context) => {
|
|
374
|
+
const projectId = resolveProjectId(args, context, true);
|
|
375
|
+
return context.gitlab.createOrUpdateFile(projectId, getString(args, "file_path"), {
|
|
376
|
+
branch: getString(args, "branch"),
|
|
377
|
+
content: getString(args, "content"),
|
|
378
|
+
commit_message: getString(args, "commit_message"),
|
|
379
|
+
author_email: getOptionalString(args, "author_email"),
|
|
380
|
+
author_name: getOptionalString(args, "author_name"),
|
|
381
|
+
encoding: getOptionalString(args, "encoding") as "text" | "base64" | undefined,
|
|
382
|
+
execute_filemode: getOptionalBoolean(args, "execute_filemode"),
|
|
383
|
+
start_branch: getOptionalString(args, "start_branch"),
|
|
384
|
+
last_commit_id:
|
|
385
|
+
getOptionalString(args, "last_commit_id") ?? getOptionalString(args, "commit_id")
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
name: "gitlab_push_files",
|
|
391
|
+
title: "Push Files",
|
|
392
|
+
description: "Create a commit with multiple file actions.",
|
|
393
|
+
mutating: true,
|
|
394
|
+
inputSchema: {
|
|
395
|
+
project_id: z.string().optional(),
|
|
396
|
+
branch: z.string().min(1),
|
|
397
|
+
commit_message: z.string().min(1),
|
|
398
|
+
actions: z
|
|
399
|
+
.array(
|
|
400
|
+
z.object({
|
|
401
|
+
action: z.enum(["create", "delete", "move", "update", "chmod"]),
|
|
402
|
+
file_path: z.string(),
|
|
403
|
+
previous_path: optionalString,
|
|
404
|
+
content: optionalString,
|
|
405
|
+
encoding: optionalString,
|
|
406
|
+
execute_filemode: optionalBoolean,
|
|
407
|
+
last_commit_id: optionalString
|
|
408
|
+
})
|
|
409
|
+
)
|
|
410
|
+
.optional(),
|
|
411
|
+
files: z
|
|
412
|
+
.array(
|
|
413
|
+
z.object({
|
|
414
|
+
file_path: z.string(),
|
|
415
|
+
content: z.string()
|
|
416
|
+
})
|
|
417
|
+
)
|
|
418
|
+
.optional(),
|
|
419
|
+
start_branch: optionalString,
|
|
420
|
+
author_name: optionalString,
|
|
421
|
+
author_email: optionalString,
|
|
422
|
+
force: optionalBoolean
|
|
423
|
+
},
|
|
424
|
+
handler: async (args, context) => {
|
|
425
|
+
const projectId = resolveProjectId(args, context, true);
|
|
426
|
+
const actionsInput = args.actions;
|
|
427
|
+
const filesInput = args.files;
|
|
428
|
+
|
|
429
|
+
let actions: PushFileAction[] = [];
|
|
430
|
+
if (Array.isArray(actionsInput) && actionsInput.length > 0) {
|
|
431
|
+
actions = actionsInput as PushFileAction[];
|
|
432
|
+
} else if (Array.isArray(filesInput) && filesInput.length > 0) {
|
|
433
|
+
actions = filesInput.map((item) => {
|
|
434
|
+
const record = item as { file_path: string; content: string };
|
|
435
|
+
return {
|
|
436
|
+
action: "create",
|
|
437
|
+
file_path: record.file_path,
|
|
438
|
+
content: record.content
|
|
439
|
+
} satisfies PushFileAction;
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (actions.length === 0) {
|
|
444
|
+
throw new Error("Either actions or files must contain at least one item");
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return context.gitlab.pushFiles(projectId, {
|
|
448
|
+
branch: getString(args, "branch"),
|
|
449
|
+
commit_message: getString(args, "commit_message"),
|
|
450
|
+
actions,
|
|
451
|
+
start_branch: getOptionalString(args, "start_branch"),
|
|
452
|
+
author_name: getOptionalString(args, "author_name"),
|
|
453
|
+
author_email: getOptionalString(args, "author_email"),
|
|
454
|
+
force: getOptionalBoolean(args, "force")
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
name: "gitlab_create_branch",
|
|
460
|
+
title: "Create Branch",
|
|
461
|
+
description: "Create a new branch from an existing ref.",
|
|
462
|
+
mutating: true,
|
|
463
|
+
inputSchema: {
|
|
464
|
+
project_id: z.string().optional(),
|
|
465
|
+
branch: z.string().min(1),
|
|
466
|
+
ref: optionalString
|
|
467
|
+
},
|
|
468
|
+
handler: async (args, context) => {
|
|
469
|
+
const projectId = resolveProjectId(args, context, true);
|
|
470
|
+
let ref = getOptionalString(args, "ref");
|
|
471
|
+
|
|
472
|
+
if (!ref) {
|
|
473
|
+
const project = (await context.gitlab.getProject(projectId)) as {
|
|
474
|
+
default_branch?: unknown;
|
|
475
|
+
};
|
|
476
|
+
ref = typeof project.default_branch === "string" ? project.default_branch : "main";
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return context.gitlab.createBranch(projectId, {
|
|
480
|
+
branch: getString(args, "branch"),
|
|
481
|
+
ref
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
name: "gitlab_get_branch_diffs",
|
|
487
|
+
title: "Get Branch Diffs",
|
|
488
|
+
description: "Compare two branches/refs and return diffs.",
|
|
489
|
+
mutating: false,
|
|
490
|
+
inputSchema: {
|
|
491
|
+
project_id: z.string().optional(),
|
|
492
|
+
from: z.string().min(1),
|
|
493
|
+
to: z.string().min(1),
|
|
494
|
+
straight: optionalBoolean,
|
|
495
|
+
excluded_file_patterns: optionalStringArray
|
|
496
|
+
},
|
|
497
|
+
handler: async (args, context) => {
|
|
498
|
+
const projectId = resolveProjectId(args, context, true);
|
|
499
|
+
const query = toQuery({ excluded_file_patterns: args.excluded_file_patterns });
|
|
500
|
+
return context.gitlab.getBranchDiffs(
|
|
501
|
+
projectId,
|
|
502
|
+
{
|
|
503
|
+
from: getString(args, "from"),
|
|
504
|
+
to: getString(args, "to"),
|
|
505
|
+
straight: getOptionalBoolean(args, "straight")
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
query
|
|
509
|
+
}
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
name: "gitlab_list_commits",
|
|
515
|
+
title: "List Commits",
|
|
516
|
+
description: "List commits in a project.",
|
|
517
|
+
mutating: false,
|
|
518
|
+
inputSchema: {
|
|
519
|
+
project_id: z.string().optional(),
|
|
520
|
+
ref_name: optionalString,
|
|
521
|
+
since: optionalString,
|
|
522
|
+
until: optionalString,
|
|
523
|
+
path: optionalString,
|
|
524
|
+
author: optionalString,
|
|
525
|
+
all: optionalBoolean,
|
|
526
|
+
with_stats: optionalBoolean,
|
|
527
|
+
first_parent: optionalBoolean,
|
|
528
|
+
order: z.enum(["default", "topo"]).optional(),
|
|
529
|
+
trailers: optionalBoolean,
|
|
530
|
+
...paginationShape
|
|
531
|
+
},
|
|
532
|
+
handler: async (args, context) => {
|
|
533
|
+
const projectId = resolveProjectId(args, context, true);
|
|
534
|
+
return context.gitlab.listCommits(projectId, {
|
|
535
|
+
query: toQuery(omit(args, ["project_id"]))
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
name: "gitlab_get_commit",
|
|
541
|
+
title: "Get Commit",
|
|
542
|
+
description: "Get one commit by SHA.",
|
|
543
|
+
mutating: false,
|
|
544
|
+
inputSchema: {
|
|
545
|
+
project_id: z.string().optional(),
|
|
546
|
+
sha: z.string().min(1),
|
|
547
|
+
stats: optionalBoolean
|
|
548
|
+
},
|
|
549
|
+
handler: async (args, context) => {
|
|
550
|
+
const projectId = resolveProjectId(args, context, true);
|
|
551
|
+
return context.gitlab.getCommit(projectId, getString(args, "sha"), {
|
|
552
|
+
query: toQuery(omit(args, ["project_id", "sha"]))
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
name: "gitlab_get_commit_diff",
|
|
558
|
+
title: "Get Commit Diff",
|
|
559
|
+
description: "Get diff for one commit.",
|
|
560
|
+
mutating: false,
|
|
561
|
+
inputSchema: {
|
|
562
|
+
project_id: z.string().optional(),
|
|
563
|
+
sha: z.string().min(1),
|
|
564
|
+
full_diff: optionalBoolean,
|
|
565
|
+
...paginationShape
|
|
566
|
+
},
|
|
567
|
+
handler: async (args, context) => {
|
|
568
|
+
const projectId = resolveProjectId(args, context, true);
|
|
569
|
+
return context.gitlab.getCommitDiff(projectId, getString(args, "sha"), {
|
|
570
|
+
query: toQuery(omit(args, ["project_id", "sha"]))
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
name: "gitlab_list_merge_requests",
|
|
576
|
+
title: "List Merge Requests",
|
|
577
|
+
description: "List merge requests for a project.",
|
|
578
|
+
mutating: false,
|
|
579
|
+
inputSchema: {
|
|
580
|
+
project_id: z.string().optional(),
|
|
581
|
+
assignee_id: optionalStringOrNumber,
|
|
582
|
+
assignee_username: optionalString,
|
|
583
|
+
author_id: optionalStringOrNumber,
|
|
584
|
+
author_username: optionalString,
|
|
585
|
+
reviewer_id: optionalStringOrNumber,
|
|
586
|
+
reviewer_username: optionalString,
|
|
587
|
+
created_after: optionalString,
|
|
588
|
+
created_before: optionalString,
|
|
589
|
+
updated_after: optionalString,
|
|
590
|
+
updated_before: optionalString,
|
|
591
|
+
labels: optionalStringOrStringArray,
|
|
592
|
+
milestone: optionalString,
|
|
593
|
+
state: optionalString,
|
|
594
|
+
scope: optionalString,
|
|
595
|
+
order_by: z
|
|
596
|
+
.enum([
|
|
597
|
+
"created_at",
|
|
598
|
+
"updated_at",
|
|
599
|
+
"priority",
|
|
600
|
+
"label_priority",
|
|
601
|
+
"milestone_due",
|
|
602
|
+
"popularity"
|
|
603
|
+
])
|
|
604
|
+
.optional(),
|
|
605
|
+
sort: z.enum(["asc", "desc"]).optional(),
|
|
606
|
+
source_branch: optionalString,
|
|
607
|
+
target_branch: optionalString,
|
|
608
|
+
search: optionalString,
|
|
609
|
+
wip: z.enum(["yes", "no"]).optional(),
|
|
610
|
+
with_labels_details: optionalBoolean,
|
|
611
|
+
...paginationShape
|
|
612
|
+
},
|
|
613
|
+
handler: async (args, context) => {
|
|
614
|
+
const projectId = resolveProjectId(args, context, false);
|
|
615
|
+
const query = toQuery(omit(args, ["project_id"]));
|
|
616
|
+
|
|
617
|
+
if (projectId) {
|
|
618
|
+
return context.gitlab.listMergeRequests(projectId, { query });
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return context.gitlab.listGlobalMergeRequests({ query });
|
|
622
|
+
}
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
name: "gitlab_get_merge_request",
|
|
626
|
+
title: "Get Merge Request",
|
|
627
|
+
description: "Get one merge request.",
|
|
628
|
+
mutating: false,
|
|
629
|
+
inputSchema: {
|
|
630
|
+
project_id: z.string().optional(),
|
|
631
|
+
merge_request_iid: optionalString,
|
|
632
|
+
source_branch: optionalString
|
|
633
|
+
},
|
|
634
|
+
handler: async (args, context) => {
|
|
635
|
+
const projectId = resolveProjectId(args, context, true);
|
|
636
|
+
const mergeRequestIid = getOptionalString(args, "merge_request_iid");
|
|
637
|
+
|
|
638
|
+
if (mergeRequestIid) {
|
|
639
|
+
return context.gitlab.getMergeRequest(projectId, mergeRequestIid);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
const sourceBranch = getOptionalString(args, "source_branch");
|
|
643
|
+
if (!sourceBranch) {
|
|
644
|
+
throw new Error("Either merge_request_iid or source_branch must be provided");
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const candidates = await context.gitlab.listMergeRequests(projectId, {
|
|
648
|
+
query: {
|
|
649
|
+
source_branch: sourceBranch,
|
|
650
|
+
per_page: 100,
|
|
651
|
+
page: 1
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
const match = pickFirstMergeRequest(candidates);
|
|
655
|
+
if (!match) {
|
|
656
|
+
throw new Error(`No merge request found for source_branch='${sourceBranch}'`);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
return match;
|
|
660
|
+
}
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
name: "gitlab_create_merge_request",
|
|
664
|
+
title: "Create Merge Request",
|
|
665
|
+
description: "Create a merge request.",
|
|
666
|
+
mutating: true,
|
|
667
|
+
inputSchema: {
|
|
668
|
+
project_id: z.string().optional(),
|
|
669
|
+
source_branch: z.string().min(1),
|
|
670
|
+
target_branch: z.string().min(1),
|
|
671
|
+
title: z.string().min(1),
|
|
672
|
+
description: optionalString,
|
|
673
|
+
target_project_id: optionalString,
|
|
674
|
+
assignee_ids: optionalNumberArray,
|
|
675
|
+
reviewer_ids: optionalNumberArray,
|
|
676
|
+
labels: optionalStringOrStringArray,
|
|
677
|
+
allow_collaboration: optionalBoolean,
|
|
678
|
+
remove_source_branch: optionalBoolean,
|
|
679
|
+
squash: optionalBoolean,
|
|
680
|
+
draft: optionalBoolean
|
|
681
|
+
},
|
|
682
|
+
handler: async (args, context) => {
|
|
683
|
+
const projectId = resolveProjectId(args, context, true);
|
|
684
|
+
return context.gitlab.createMergeRequest(projectId, {
|
|
685
|
+
source_branch: getString(args, "source_branch"),
|
|
686
|
+
target_branch: getString(args, "target_branch"),
|
|
687
|
+
title: getString(args, "title"),
|
|
688
|
+
description: getOptionalString(args, "description"),
|
|
689
|
+
target_project_id: getOptionalString(args, "target_project_id"),
|
|
690
|
+
assignee_ids: getOptionalNumberArray(args, "assignee_ids"),
|
|
691
|
+
reviewer_ids: getOptionalNumberArray(args, "reviewer_ids"),
|
|
692
|
+
labels: toCsvValue(args.labels),
|
|
693
|
+
allow_collaboration: getOptionalBoolean(args, "allow_collaboration"),
|
|
694
|
+
remove_source_branch: getOptionalBoolean(args, "remove_source_branch"),
|
|
695
|
+
squash: getOptionalBoolean(args, "squash"),
|
|
696
|
+
draft: getOptionalBoolean(args, "draft")
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
},
|
|
700
|
+
{
|
|
701
|
+
name: "gitlab_fork_repository",
|
|
702
|
+
title: "Fork Repository",
|
|
703
|
+
description: "Fork an existing project to another namespace.",
|
|
704
|
+
mutating: true,
|
|
705
|
+
inputSchema: {
|
|
706
|
+
project_id: z.string().optional(),
|
|
707
|
+
namespace: optionalString,
|
|
708
|
+
namespace_id: optionalString,
|
|
709
|
+
path: optionalString,
|
|
710
|
+
name: optionalString,
|
|
711
|
+
description: optionalString,
|
|
712
|
+
visibility: z.enum(["private", "internal", "public"]).optional(),
|
|
713
|
+
default_branch: optionalString
|
|
714
|
+
},
|
|
715
|
+
handler: async (args, context) =>
|
|
716
|
+
context.gitlab.forkRepository(resolveProjectId(args, context, true), {
|
|
717
|
+
namespace: getOptionalString(args, "namespace"),
|
|
718
|
+
namespace_id: getOptionalString(args, "namespace_id"),
|
|
719
|
+
path: getOptionalString(args, "path"),
|
|
720
|
+
name: getOptionalString(args, "name"),
|
|
721
|
+
description: getOptionalString(args, "description"),
|
|
722
|
+
visibility: getOptionalString(args, "visibility") as
|
|
723
|
+
| "private"
|
|
724
|
+
| "internal"
|
|
725
|
+
| "public"
|
|
726
|
+
| undefined,
|
|
727
|
+
default_branch: getOptionalString(args, "default_branch")
|
|
728
|
+
})
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
name: "gitlab_update_merge_request",
|
|
732
|
+
title: "Update Merge Request",
|
|
733
|
+
description: "Update merge request fields.",
|
|
734
|
+
mutating: true,
|
|
735
|
+
inputSchema: {
|
|
736
|
+
project_id: z.string().optional(),
|
|
737
|
+
merge_request_iid: z.string().min(1),
|
|
738
|
+
source_branch: optionalString,
|
|
739
|
+
title: optionalString,
|
|
740
|
+
description: optionalString,
|
|
741
|
+
target_branch: optionalString,
|
|
742
|
+
assignee_ids: optionalNumberArray,
|
|
743
|
+
reviewer_ids: optionalNumberArray,
|
|
744
|
+
reviewers: optionalStringArray,
|
|
745
|
+
labels: optionalStringOrStringArray,
|
|
746
|
+
state_event: optionalString,
|
|
747
|
+
squash: optionalBoolean,
|
|
748
|
+
draft: optionalBoolean,
|
|
749
|
+
remove_source_branch: optionalBoolean
|
|
750
|
+
},
|
|
751
|
+
handler: async (args, context) => {
|
|
752
|
+
const projectId = resolveProjectId(args, context, true);
|
|
753
|
+
const payload = toQuery(omit(args, ["project_id", "merge_request_iid"])) as Record<
|
|
754
|
+
string,
|
|
755
|
+
unknown
|
|
756
|
+
>;
|
|
757
|
+
if (payload.labels === undefined) {
|
|
758
|
+
payload.labels = toCsvValue(args.labels);
|
|
759
|
+
}
|
|
760
|
+
if (Array.isArray(args.assignee_ids)) {
|
|
761
|
+
payload.assignee_ids = args.assignee_ids as number[];
|
|
762
|
+
}
|
|
763
|
+
if (Array.isArray(args.reviewer_ids)) {
|
|
764
|
+
payload.reviewer_ids = args.reviewer_ids as number[];
|
|
765
|
+
}
|
|
766
|
+
if (payload.reviewer_ids === undefined && Array.isArray(args.reviewers)) {
|
|
767
|
+
payload.reviewer_ids = (args.reviewers as string[]).join(",");
|
|
768
|
+
}
|
|
769
|
+
return context.gitlab.updateMergeRequest(
|
|
770
|
+
projectId,
|
|
771
|
+
getString(args, "merge_request_iid"),
|
|
772
|
+
payload
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
},
|
|
776
|
+
{
|
|
777
|
+
name: "gitlab_merge_merge_request",
|
|
778
|
+
title: "Merge Merge Request",
|
|
779
|
+
description: "Merge an existing merge request.",
|
|
780
|
+
mutating: true,
|
|
781
|
+
inputSchema: {
|
|
782
|
+
project_id: z.string().optional(),
|
|
783
|
+
merge_request_iid: optionalString,
|
|
784
|
+
source_branch: optionalString,
|
|
785
|
+
auto_merge: optionalBoolean,
|
|
786
|
+
merge_when_pipeline_succeeds: optionalBoolean,
|
|
787
|
+
merge_commit_message: optionalString,
|
|
788
|
+
squash_commit_message: optionalString,
|
|
789
|
+
should_remove_source_branch: optionalBoolean,
|
|
790
|
+
squash: optionalBoolean,
|
|
791
|
+
sha: optionalString
|
|
792
|
+
},
|
|
793
|
+
handler: async (args, context) => {
|
|
794
|
+
const projectId = resolveProjectId(args, context, true);
|
|
795
|
+
let mergeRequestIid = getOptionalString(args, "merge_request_iid");
|
|
796
|
+
if (!mergeRequestIid) {
|
|
797
|
+
const sourceBranch = getOptionalString(args, "source_branch");
|
|
798
|
+
if (!sourceBranch) {
|
|
799
|
+
throw new Error("Either merge_request_iid or source_branch must be provided");
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
const candidates = await context.gitlab.listMergeRequests(projectId, {
|
|
803
|
+
query: {
|
|
804
|
+
source_branch: sourceBranch,
|
|
805
|
+
per_page: 100,
|
|
806
|
+
page: 1
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
const match = pickFirstMergeRequest(candidates);
|
|
810
|
+
const iid = match?.iid;
|
|
811
|
+
if (typeof iid !== "number" && typeof iid !== "string") {
|
|
812
|
+
throw new Error(`No merge request found for source_branch='${sourceBranch}'`);
|
|
813
|
+
}
|
|
814
|
+
mergeRequestIid = String(iid);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
return context.gitlab.mergeMergeRequest(
|
|
818
|
+
projectId,
|
|
819
|
+
mergeRequestIid,
|
|
820
|
+
toQuery(omit(args, ["project_id", "merge_request_iid", "source_branch"]))
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
},
|
|
824
|
+
{
|
|
825
|
+
name: "gitlab_get_merge_request_diffs",
|
|
826
|
+
title: "Get Merge Request Diffs",
|
|
827
|
+
description: "Get MR diffs with changed files.",
|
|
828
|
+
mutating: false,
|
|
829
|
+
inputSchema: {
|
|
830
|
+
project_id: z.string().optional(),
|
|
831
|
+
merge_request_iid: z.string().min(1),
|
|
832
|
+
view: z.enum(["inline", "parallel"]).optional(),
|
|
833
|
+
excluded_file_patterns: optionalStringArray
|
|
834
|
+
},
|
|
835
|
+
handler: async (args, context) =>
|
|
836
|
+
context.gitlab.getMergeRequestDiffs(
|
|
837
|
+
resolveProjectId(args, context, true),
|
|
838
|
+
getString(args, "merge_request_iid"),
|
|
839
|
+
{ query: toQuery(omit(args, ["project_id", "merge_request_iid"])) }
|
|
840
|
+
)
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
name: "gitlab_list_merge_request_diffs",
|
|
844
|
+
title: "List Merge Request Diffs",
|
|
845
|
+
description: "List detailed MR diffs (versions/changes view).",
|
|
846
|
+
mutating: false,
|
|
847
|
+
inputSchema: {
|
|
848
|
+
project_id: z.string().optional(),
|
|
849
|
+
merge_request_iid: z.string().min(1),
|
|
850
|
+
page: optionalNumber,
|
|
851
|
+
per_page: optionalNumber,
|
|
852
|
+
unidiff: optionalBoolean
|
|
853
|
+
},
|
|
854
|
+
handler: async (args, context) =>
|
|
855
|
+
context.gitlab.listMergeRequestDiffs(
|
|
856
|
+
resolveProjectId(args, context, true),
|
|
857
|
+
getString(args, "merge_request_iid"),
|
|
858
|
+
{ query: toQuery(omit(args, ["project_id", "merge_request_iid"])) }
|
|
859
|
+
)
|
|
860
|
+
},
|
|
861
|
+
{
|
|
862
|
+
name: "gitlab_get_merge_request_code_context",
|
|
863
|
+
title: "Get Merge Request Code Context",
|
|
864
|
+
description:
|
|
865
|
+
"High-signal MR code context with include/exclude filters, sorting, and token-budgeted output.",
|
|
866
|
+
mutating: false,
|
|
867
|
+
inputSchema: mergeRequestCodeContextSchema,
|
|
868
|
+
handler: async (args, context) =>
|
|
869
|
+
getMergeRequestCodeContext(
|
|
870
|
+
{
|
|
871
|
+
projectId: resolveProjectId(args, context, true),
|
|
872
|
+
mergeRequestIid: getString(args, "merge_request_iid"),
|
|
873
|
+
includePaths: getOptionalStringArray(args, "include_paths"),
|
|
874
|
+
excludePaths: getOptionalStringArray(args, "exclude_paths"),
|
|
875
|
+
extensions: getOptionalStringArray(args, "extensions"),
|
|
876
|
+
languages: getOptionalStringArray(args, "languages"),
|
|
877
|
+
maxFiles: getOptionalNumber(args, "max_files") ?? 30,
|
|
878
|
+
maxTotalChars: getOptionalNumber(args, "max_total_chars") ?? 120_000,
|
|
879
|
+
contextLines: getOptionalNumber(args, "context_lines") ?? 20,
|
|
880
|
+
mode:
|
|
881
|
+
(getOptionalString(args, "mode") as
|
|
882
|
+
| "patch"
|
|
883
|
+
| "surrounding"
|
|
884
|
+
| "fullfile"
|
|
885
|
+
| undefined) ?? "patch",
|
|
886
|
+
sort:
|
|
887
|
+
(getOptionalString(args, "sort") as
|
|
888
|
+
| "changed_lines"
|
|
889
|
+
| "path"
|
|
890
|
+
| "file_size"
|
|
891
|
+
| undefined) ?? "changed_lines",
|
|
892
|
+
listOnly: getOptionalBoolean(args, "list_only") ?? false
|
|
893
|
+
},
|
|
894
|
+
context
|
|
895
|
+
)
|
|
896
|
+
},
|
|
897
|
+
{
|
|
898
|
+
name: "gitlab_list_merge_request_versions",
|
|
899
|
+
title: "List Merge Request Versions",
|
|
900
|
+
description: "List MR diff versions.",
|
|
901
|
+
mutating: false,
|
|
902
|
+
inputSchema: {
|
|
903
|
+
project_id: z.string().optional(),
|
|
904
|
+
merge_request_iid: z.string().min(1)
|
|
905
|
+
},
|
|
906
|
+
handler: async (args, context) =>
|
|
907
|
+
context.gitlab.listMergeRequestVersions(
|
|
908
|
+
resolveProjectId(args, context, true),
|
|
909
|
+
getString(args, "merge_request_iid")
|
|
910
|
+
)
|
|
911
|
+
},
|
|
912
|
+
{
|
|
913
|
+
name: "gitlab_get_merge_request_version",
|
|
914
|
+
title: "Get Merge Request Version",
|
|
915
|
+
description: "Get one MR diff version.",
|
|
916
|
+
mutating: false,
|
|
917
|
+
inputSchema: {
|
|
918
|
+
project_id: z.string().optional(),
|
|
919
|
+
merge_request_iid: z.string().min(1),
|
|
920
|
+
version_id: z.string().min(1),
|
|
921
|
+
unidiff: optionalBoolean
|
|
922
|
+
},
|
|
923
|
+
handler: async (args, context) =>
|
|
924
|
+
context.gitlab.getMergeRequestVersion(
|
|
925
|
+
resolveProjectId(args, context, true),
|
|
926
|
+
getString(args, "merge_request_iid"),
|
|
927
|
+
getString(args, "version_id"),
|
|
928
|
+
{ query: toQuery(omit(args, ["project_id", "merge_request_iid", "version_id"])) }
|
|
929
|
+
)
|
|
930
|
+
},
|
|
931
|
+
{
|
|
932
|
+
name: "gitlab_approve_merge_request",
|
|
933
|
+
title: "Approve Merge Request",
|
|
934
|
+
description: "Approve a merge request.",
|
|
935
|
+
mutating: true,
|
|
936
|
+
inputSchema: {
|
|
937
|
+
project_id: z.string().optional(),
|
|
938
|
+
merge_request_iid: z.string().min(1),
|
|
939
|
+
sha: optionalString,
|
|
940
|
+
approval_password: optionalString
|
|
941
|
+
},
|
|
942
|
+
handler: async (args, context) =>
|
|
943
|
+
context.gitlab.approveMergeRequest(
|
|
944
|
+
resolveProjectId(args, context, true),
|
|
945
|
+
getString(args, "merge_request_iid"),
|
|
946
|
+
toQuery(omit(args, ["project_id", "merge_request_iid"]))
|
|
947
|
+
)
|
|
948
|
+
},
|
|
949
|
+
{
|
|
950
|
+
name: "gitlab_unapprove_merge_request",
|
|
951
|
+
title: "Unapprove Merge Request",
|
|
952
|
+
description: "Remove current user approval from MR.",
|
|
953
|
+
mutating: true,
|
|
954
|
+
inputSchema: {
|
|
955
|
+
project_id: z.string().optional(),
|
|
956
|
+
merge_request_iid: z.string().min(1)
|
|
957
|
+
},
|
|
958
|
+
handler: async (args, context) =>
|
|
959
|
+
context.gitlab.unapproveMergeRequest(
|
|
960
|
+
resolveProjectId(args, context, true),
|
|
961
|
+
getString(args, "merge_request_iid")
|
|
962
|
+
)
|
|
963
|
+
},
|
|
964
|
+
{
|
|
965
|
+
name: "gitlab_get_merge_request_approval_state",
|
|
966
|
+
title: "Get Merge Request Approval State",
|
|
967
|
+
description: "Get approval state for MR.",
|
|
968
|
+
mutating: false,
|
|
969
|
+
inputSchema: {
|
|
970
|
+
project_id: z.string().optional(),
|
|
971
|
+
merge_request_iid: z.string().min(1)
|
|
972
|
+
},
|
|
973
|
+
handler: async (args, context) =>
|
|
974
|
+
context.gitlab.getMergeRequestApprovalState(
|
|
975
|
+
resolveProjectId(args, context, true),
|
|
976
|
+
getString(args, "merge_request_iid")
|
|
977
|
+
)
|
|
978
|
+
},
|
|
979
|
+
{
|
|
980
|
+
name: "gitlab_list_merge_request_discussions",
|
|
981
|
+
title: "List Merge Request Discussions",
|
|
982
|
+
description: "List MR discussions.",
|
|
983
|
+
mutating: false,
|
|
984
|
+
inputSchema: {
|
|
985
|
+
project_id: z.string().optional(),
|
|
986
|
+
merge_request_iid: z.string().min(1),
|
|
987
|
+
...paginationShape
|
|
988
|
+
},
|
|
989
|
+
handler: async (args, context) =>
|
|
990
|
+
context.gitlab.listMergeRequestDiscussions(
|
|
991
|
+
resolveProjectId(args, context, true),
|
|
992
|
+
getString(args, "merge_request_iid"),
|
|
993
|
+
{ query: toQuery(omit(args, ["project_id", "merge_request_iid"])) }
|
|
994
|
+
)
|
|
995
|
+
},
|
|
996
|
+
{
|
|
997
|
+
name: "gitlab_create_merge_request_thread",
|
|
998
|
+
title: "Create Merge Request Thread",
|
|
999
|
+
description: "Create a new MR discussion thread (supports diff positions).",
|
|
1000
|
+
mutating: true,
|
|
1001
|
+
inputSchema: {
|
|
1002
|
+
project_id: z.string().optional(),
|
|
1003
|
+
merge_request_iid: z.string().min(1),
|
|
1004
|
+
body: z.string().min(1),
|
|
1005
|
+
position: optionalRecord,
|
|
1006
|
+
created_at: optionalString
|
|
1007
|
+
},
|
|
1008
|
+
handler: async (args, context) =>
|
|
1009
|
+
context.gitlab.createMergeRequestThread(
|
|
1010
|
+
resolveProjectId(args, context, true),
|
|
1011
|
+
getString(args, "merge_request_iid"),
|
|
1012
|
+
{
|
|
1013
|
+
body: getString(args, "body"),
|
|
1014
|
+
position: getOptionalRecord(args, "position"),
|
|
1015
|
+
created_at: getOptionalString(args, "created_at")
|
|
1016
|
+
}
|
|
1017
|
+
)
|
|
1018
|
+
},
|
|
1019
|
+
{
|
|
1020
|
+
name: "gitlab_mr_discussions",
|
|
1021
|
+
title: "Merge Request Discussions (Alias)",
|
|
1022
|
+
description: "Backward-compatible alias of gitlab_list_merge_request_discussions.",
|
|
1023
|
+
mutating: false,
|
|
1024
|
+
inputSchema: {
|
|
1025
|
+
project_id: z.string().optional(),
|
|
1026
|
+
merge_request_iid: z.string().min(1),
|
|
1027
|
+
...paginationShape
|
|
1028
|
+
},
|
|
1029
|
+
handler: async (args, context) =>
|
|
1030
|
+
context.gitlab.listMergeRequestDiscussions(
|
|
1031
|
+
resolveProjectId(args, context, true),
|
|
1032
|
+
getString(args, "merge_request_iid"),
|
|
1033
|
+
{ query: toQuery(omit(args, ["project_id", "merge_request_iid"])) }
|
|
1034
|
+
)
|
|
1035
|
+
},
|
|
1036
|
+
{
|
|
1037
|
+
name: "gitlab_create_merge_request_discussion_note",
|
|
1038
|
+
title: "Create MR Discussion Note",
|
|
1039
|
+
description: "Add note to existing MR discussion thread.",
|
|
1040
|
+
mutating: true,
|
|
1041
|
+
inputSchema: {
|
|
1042
|
+
project_id: z.string().optional(),
|
|
1043
|
+
merge_request_iid: z.string().min(1),
|
|
1044
|
+
discussion_id: z.string().min(1),
|
|
1045
|
+
body: z.string().min(1),
|
|
1046
|
+
created_at: optionalString
|
|
1047
|
+
},
|
|
1048
|
+
handler: async (args, context) =>
|
|
1049
|
+
context.gitlab.createMergeRequestDiscussionNote(
|
|
1050
|
+
resolveProjectId(args, context, true),
|
|
1051
|
+
getString(args, "merge_request_iid"),
|
|
1052
|
+
getString(args, "discussion_id"),
|
|
1053
|
+
{
|
|
1054
|
+
body: getString(args, "body"),
|
|
1055
|
+
created_at: getOptionalString(args, "created_at")
|
|
1056
|
+
}
|
|
1057
|
+
)
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
name: "gitlab_update_merge_request_discussion_note",
|
|
1061
|
+
title: "Update MR Discussion Note",
|
|
1062
|
+
description: "Update note body/resolved state in MR discussion.",
|
|
1063
|
+
mutating: true,
|
|
1064
|
+
inputSchema: {
|
|
1065
|
+
project_id: z.string().optional(),
|
|
1066
|
+
merge_request_iid: z.string().min(1),
|
|
1067
|
+
discussion_id: z.string().min(1),
|
|
1068
|
+
note_id: z.string().min(1),
|
|
1069
|
+
body: optionalString,
|
|
1070
|
+
resolved: optionalBoolean
|
|
1071
|
+
},
|
|
1072
|
+
handler: async (args, context) => {
|
|
1073
|
+
const body = getOptionalString(args, "body");
|
|
1074
|
+
const resolved = getOptionalBoolean(args, "resolved");
|
|
1075
|
+
|
|
1076
|
+
if (body === undefined && resolved === undefined) {
|
|
1077
|
+
throw new Error("Either body or resolved must be provided");
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
if (body !== undefined && resolved !== undefined) {
|
|
1081
|
+
throw new Error("Provide either body or resolved, not both");
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
return context.gitlab.updateMergeRequestDiscussionNote(
|
|
1085
|
+
resolveProjectId(args, context, true),
|
|
1086
|
+
getString(args, "merge_request_iid"),
|
|
1087
|
+
getString(args, "discussion_id"),
|
|
1088
|
+
getString(args, "note_id"),
|
|
1089
|
+
{
|
|
1090
|
+
body,
|
|
1091
|
+
resolved
|
|
1092
|
+
}
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
},
|
|
1096
|
+
{
|
|
1097
|
+
name: "gitlab_delete_merge_request_discussion_note",
|
|
1098
|
+
title: "Delete MR Discussion Note",
|
|
1099
|
+
description: "Delete note from MR discussion thread.",
|
|
1100
|
+
mutating: true,
|
|
1101
|
+
inputSchema: {
|
|
1102
|
+
project_id: z.string().optional(),
|
|
1103
|
+
merge_request_iid: z.string().min(1),
|
|
1104
|
+
discussion_id: z.string().min(1),
|
|
1105
|
+
note_id: z.string().min(1)
|
|
1106
|
+
},
|
|
1107
|
+
handler: async (args, context) =>
|
|
1108
|
+
context.gitlab.deleteMergeRequestDiscussionNote(
|
|
1109
|
+
resolveProjectId(args, context, true),
|
|
1110
|
+
getString(args, "merge_request_iid"),
|
|
1111
|
+
getString(args, "discussion_id"),
|
|
1112
|
+
getString(args, "note_id")
|
|
1113
|
+
)
|
|
1114
|
+
},
|
|
1115
|
+
{
|
|
1116
|
+
name: "gitlab_resolve_merge_request_thread",
|
|
1117
|
+
title: "Resolve Merge Request Thread",
|
|
1118
|
+
description: "Resolve/unresolve an MR discussion note.",
|
|
1119
|
+
mutating: true,
|
|
1120
|
+
inputSchema: {
|
|
1121
|
+
project_id: z.string().optional(),
|
|
1122
|
+
merge_request_iid: z.string().min(1),
|
|
1123
|
+
discussion_id: z.string().min(1),
|
|
1124
|
+
note_id: z.string().min(1),
|
|
1125
|
+
resolved: z.boolean().default(true)
|
|
1126
|
+
},
|
|
1127
|
+
handler: async (args, context) =>
|
|
1128
|
+
context.gitlab.resolveMergeRequestThread(
|
|
1129
|
+
resolveProjectId(args, context, true),
|
|
1130
|
+
getString(args, "merge_request_iid"),
|
|
1131
|
+
getString(args, "discussion_id"),
|
|
1132
|
+
getString(args, "note_id"),
|
|
1133
|
+
getBoolean(args, "resolved")
|
|
1134
|
+
)
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
name: "gitlab_list_merge_request_notes",
|
|
1138
|
+
title: "List Merge Request Notes",
|
|
1139
|
+
description: "List top-level notes for an MR.",
|
|
1140
|
+
mutating: false,
|
|
1141
|
+
inputSchema: {
|
|
1142
|
+
project_id: z.string().optional(),
|
|
1143
|
+
merge_request_iid: z.string().min(1),
|
|
1144
|
+
sort: optionalString,
|
|
1145
|
+
order_by: optionalString,
|
|
1146
|
+
...paginationShape
|
|
1147
|
+
},
|
|
1148
|
+
handler: async (args, context) =>
|
|
1149
|
+
context.gitlab.listMergeRequestNotes(
|
|
1150
|
+
resolveProjectId(args, context, true),
|
|
1151
|
+
getString(args, "merge_request_iid"),
|
|
1152
|
+
{ query: toQuery(omit(args, ["project_id", "merge_request_iid"])) }
|
|
1153
|
+
)
|
|
1154
|
+
},
|
|
1155
|
+
{
|
|
1156
|
+
name: "gitlab_get_merge_request_notes",
|
|
1157
|
+
title: "Get Merge Request Notes (Alias)",
|
|
1158
|
+
description: "Backward-compatible alias of gitlab_list_merge_request_notes.",
|
|
1159
|
+
mutating: false,
|
|
1160
|
+
inputSchema: {
|
|
1161
|
+
project_id: z.string().optional(),
|
|
1162
|
+
merge_request_iid: z.string().min(1),
|
|
1163
|
+
sort: optionalString,
|
|
1164
|
+
order_by: optionalString,
|
|
1165
|
+
...paginationShape
|
|
1166
|
+
},
|
|
1167
|
+
handler: async (args, context) =>
|
|
1168
|
+
context.gitlab.listMergeRequestNotes(
|
|
1169
|
+
resolveProjectId(args, context, true),
|
|
1170
|
+
getString(args, "merge_request_iid"),
|
|
1171
|
+
{ query: toQuery(omit(args, ["project_id", "merge_request_iid"])) }
|
|
1172
|
+
)
|
|
1173
|
+
},
|
|
1174
|
+
{
|
|
1175
|
+
name: "gitlab_get_draft_note",
|
|
1176
|
+
title: "Get Draft Note",
|
|
1177
|
+
description: "Get a single merge-request draft note.",
|
|
1178
|
+
mutating: false,
|
|
1179
|
+
inputSchema: {
|
|
1180
|
+
project_id: z.string().optional(),
|
|
1181
|
+
merge_request_iid: z.string().min(1),
|
|
1182
|
+
draft_note_id: z.string().min(1)
|
|
1183
|
+
},
|
|
1184
|
+
handler: async (args, context) =>
|
|
1185
|
+
context.gitlab.getDraftNote(
|
|
1186
|
+
resolveProjectId(args, context, true),
|
|
1187
|
+
getString(args, "merge_request_iid"),
|
|
1188
|
+
getString(args, "draft_note_id")
|
|
1189
|
+
)
|
|
1190
|
+
},
|
|
1191
|
+
{
|
|
1192
|
+
name: "gitlab_list_draft_notes",
|
|
1193
|
+
title: "List Draft Notes",
|
|
1194
|
+
description: "List draft notes on a merge request.",
|
|
1195
|
+
mutating: false,
|
|
1196
|
+
inputSchema: {
|
|
1197
|
+
project_id: z.string().optional(),
|
|
1198
|
+
merge_request_iid: z.string().min(1)
|
|
1199
|
+
},
|
|
1200
|
+
handler: async (args, context) =>
|
|
1201
|
+
context.gitlab.listDraftNotes(
|
|
1202
|
+
resolveProjectId(args, context, true),
|
|
1203
|
+
getString(args, "merge_request_iid")
|
|
1204
|
+
)
|
|
1205
|
+
},
|
|
1206
|
+
{
|
|
1207
|
+
name: "gitlab_create_draft_note",
|
|
1208
|
+
title: "Create Draft Note",
|
|
1209
|
+
description: "Create a merge-request draft note.",
|
|
1210
|
+
mutating: true,
|
|
1211
|
+
inputSchema: {
|
|
1212
|
+
project_id: z.string().optional(),
|
|
1213
|
+
merge_request_iid: z.string().min(1),
|
|
1214
|
+
body: z.string().min(1),
|
|
1215
|
+
position: optionalRecord,
|
|
1216
|
+
resolve_discussion: optionalBoolean
|
|
1217
|
+
},
|
|
1218
|
+
handler: async (args, context) =>
|
|
1219
|
+
context.gitlab.createDraftNote(
|
|
1220
|
+
resolveProjectId(args, context, true),
|
|
1221
|
+
getString(args, "merge_request_iid"),
|
|
1222
|
+
{
|
|
1223
|
+
body: getString(args, "body"),
|
|
1224
|
+
position: getOptionalRecord(args, "position"),
|
|
1225
|
+
resolve_discussion: getOptionalBoolean(args, "resolve_discussion")
|
|
1226
|
+
}
|
|
1227
|
+
)
|
|
1228
|
+
},
|
|
1229
|
+
{
|
|
1230
|
+
name: "gitlab_update_draft_note",
|
|
1231
|
+
title: "Update Draft Note",
|
|
1232
|
+
description: "Update a merge-request draft note.",
|
|
1233
|
+
mutating: true,
|
|
1234
|
+
inputSchema: {
|
|
1235
|
+
project_id: z.string().optional(),
|
|
1236
|
+
merge_request_iid: z.string().min(1),
|
|
1237
|
+
draft_note_id: z.string().min(1),
|
|
1238
|
+
body: optionalString,
|
|
1239
|
+
position: optionalRecord,
|
|
1240
|
+
resolve_discussion: optionalBoolean
|
|
1241
|
+
},
|
|
1242
|
+
handler: async (args, context) => {
|
|
1243
|
+
if (
|
|
1244
|
+
getOptionalString(args, "body") === undefined &&
|
|
1245
|
+
getOptionalRecord(args, "position") === undefined &&
|
|
1246
|
+
getOptionalBoolean(args, "resolve_discussion") === undefined
|
|
1247
|
+
) {
|
|
1248
|
+
throw new Error("At least one of body, position, or resolve_discussion is required");
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
return context.gitlab.updateDraftNote(
|
|
1252
|
+
resolveProjectId(args, context, true),
|
|
1253
|
+
getString(args, "merge_request_iid"),
|
|
1254
|
+
getString(args, "draft_note_id"),
|
|
1255
|
+
{
|
|
1256
|
+
body: getOptionalString(args, "body"),
|
|
1257
|
+
position: getOptionalRecord(args, "position"),
|
|
1258
|
+
resolve_discussion: getOptionalBoolean(args, "resolve_discussion")
|
|
1259
|
+
}
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1262
|
+
},
|
|
1263
|
+
{
|
|
1264
|
+
name: "gitlab_delete_draft_note",
|
|
1265
|
+
title: "Delete Draft Note",
|
|
1266
|
+
description: "Delete a merge-request draft note.",
|
|
1267
|
+
mutating: true,
|
|
1268
|
+
inputSchema: {
|
|
1269
|
+
project_id: z.string().optional(),
|
|
1270
|
+
merge_request_iid: z.string().min(1),
|
|
1271
|
+
draft_note_id: z.string().min(1)
|
|
1272
|
+
},
|
|
1273
|
+
handler: async (args, context) =>
|
|
1274
|
+
context.gitlab.deleteDraftNote(
|
|
1275
|
+
resolveProjectId(args, context, true),
|
|
1276
|
+
getString(args, "merge_request_iid"),
|
|
1277
|
+
getString(args, "draft_note_id")
|
|
1278
|
+
)
|
|
1279
|
+
},
|
|
1280
|
+
{
|
|
1281
|
+
name: "gitlab_publish_draft_note",
|
|
1282
|
+
title: "Publish Draft Note",
|
|
1283
|
+
description: "Publish one merge-request draft note.",
|
|
1284
|
+
mutating: true,
|
|
1285
|
+
inputSchema: {
|
|
1286
|
+
project_id: z.string().optional(),
|
|
1287
|
+
merge_request_iid: z.string().min(1),
|
|
1288
|
+
draft_note_id: z.string().min(1)
|
|
1289
|
+
},
|
|
1290
|
+
handler: async (args, context) =>
|
|
1291
|
+
context.gitlab.publishDraftNote(
|
|
1292
|
+
resolveProjectId(args, context, true),
|
|
1293
|
+
getString(args, "merge_request_iid"),
|
|
1294
|
+
getString(args, "draft_note_id")
|
|
1295
|
+
)
|
|
1296
|
+
},
|
|
1297
|
+
{
|
|
1298
|
+
name: "gitlab_bulk_publish_draft_notes",
|
|
1299
|
+
title: "Bulk Publish Draft Notes",
|
|
1300
|
+
description: "Publish all merge-request draft notes.",
|
|
1301
|
+
mutating: true,
|
|
1302
|
+
inputSchema: {
|
|
1303
|
+
project_id: z.string().optional(),
|
|
1304
|
+
merge_request_iid: z.string().min(1)
|
|
1305
|
+
},
|
|
1306
|
+
handler: async (args, context) =>
|
|
1307
|
+
context.gitlab.bulkPublishDraftNotes(
|
|
1308
|
+
resolveProjectId(args, context, true),
|
|
1309
|
+
getString(args, "merge_request_iid")
|
|
1310
|
+
)
|
|
1311
|
+
},
|
|
1312
|
+
{
|
|
1313
|
+
name: "gitlab_get_merge_request_note",
|
|
1314
|
+
title: "Get Merge Request Note",
|
|
1315
|
+
description: "Get a single MR note.",
|
|
1316
|
+
mutating: false,
|
|
1317
|
+
inputSchema: {
|
|
1318
|
+
project_id: z.string().optional(),
|
|
1319
|
+
merge_request_iid: z.string().min(1),
|
|
1320
|
+
note_id: z.string().min(1)
|
|
1321
|
+
},
|
|
1322
|
+
handler: async (args, context) =>
|
|
1323
|
+
context.gitlab.getMergeRequestNote(
|
|
1324
|
+
resolveProjectId(args, context, true),
|
|
1325
|
+
getString(args, "merge_request_iid"),
|
|
1326
|
+
getString(args, "note_id")
|
|
1327
|
+
)
|
|
1328
|
+
},
|
|
1329
|
+
{
|
|
1330
|
+
name: "gitlab_create_merge_request_note",
|
|
1331
|
+
title: "Create Merge Request Note",
|
|
1332
|
+
description: "Create a top-level MR note.",
|
|
1333
|
+
mutating: true,
|
|
1334
|
+
inputSchema: {
|
|
1335
|
+
project_id: z.string().optional(),
|
|
1336
|
+
merge_request_iid: z.string().min(1),
|
|
1337
|
+
body: z.string().min(1)
|
|
1338
|
+
},
|
|
1339
|
+
handler: async (args, context) =>
|
|
1340
|
+
context.gitlab.createMergeRequestNote(
|
|
1341
|
+
resolveProjectId(args, context, true),
|
|
1342
|
+
getString(args, "merge_request_iid"),
|
|
1343
|
+
getString(args, "body")
|
|
1344
|
+
)
|
|
1345
|
+
},
|
|
1346
|
+
{
|
|
1347
|
+
name: "gitlab_create_note",
|
|
1348
|
+
title: "Create Note",
|
|
1349
|
+
description: "Create a note on an issue or merge request.",
|
|
1350
|
+
mutating: true,
|
|
1351
|
+
inputSchema: {
|
|
1352
|
+
project_id: z.string().optional(),
|
|
1353
|
+
noteable_type: z.enum(["issue", "merge_request"]),
|
|
1354
|
+
noteable_iid: z.string().min(1),
|
|
1355
|
+
body: z.string().min(1)
|
|
1356
|
+
},
|
|
1357
|
+
handler: async (args, context) =>
|
|
1358
|
+
context.gitlab.createNote(
|
|
1359
|
+
resolveProjectId(args, context, true),
|
|
1360
|
+
getString(args, "noteable_type") as "issue" | "merge_request",
|
|
1361
|
+
getString(args, "noteable_iid"),
|
|
1362
|
+
getString(args, "body")
|
|
1363
|
+
)
|
|
1364
|
+
},
|
|
1365
|
+
{
|
|
1366
|
+
name: "gitlab_update_merge_request_note",
|
|
1367
|
+
title: "Update Merge Request Note",
|
|
1368
|
+
description: "Update MR note body.",
|
|
1369
|
+
mutating: true,
|
|
1370
|
+
inputSchema: {
|
|
1371
|
+
project_id: z.string().optional(),
|
|
1372
|
+
merge_request_iid: z.string().min(1),
|
|
1373
|
+
note_id: z.string().min(1),
|
|
1374
|
+
body: z.string().min(1)
|
|
1375
|
+
},
|
|
1376
|
+
handler: async (args, context) =>
|
|
1377
|
+
context.gitlab.updateMergeRequestNote(
|
|
1378
|
+
resolveProjectId(args, context, true),
|
|
1379
|
+
getString(args, "merge_request_iid"),
|
|
1380
|
+
getString(args, "note_id"),
|
|
1381
|
+
getString(args, "body")
|
|
1382
|
+
)
|
|
1383
|
+
},
|
|
1384
|
+
{
|
|
1385
|
+
name: "gitlab_delete_merge_request_note",
|
|
1386
|
+
title: "Delete Merge Request Note",
|
|
1387
|
+
description: "Delete an MR note.",
|
|
1388
|
+
mutating: true,
|
|
1389
|
+
inputSchema: {
|
|
1390
|
+
project_id: z.string().optional(),
|
|
1391
|
+
merge_request_iid: z.string().min(1),
|
|
1392
|
+
note_id: z.string().min(1)
|
|
1393
|
+
},
|
|
1394
|
+
handler: async (args, context) =>
|
|
1395
|
+
context.gitlab.deleteMergeRequestNote(
|
|
1396
|
+
resolveProjectId(args, context, true),
|
|
1397
|
+
getString(args, "merge_request_iid"),
|
|
1398
|
+
getString(args, "note_id")
|
|
1399
|
+
)
|
|
1400
|
+
},
|
|
1401
|
+
{
|
|
1402
|
+
name: "gitlab_list_issues",
|
|
1403
|
+
title: "List Issues",
|
|
1404
|
+
description: "List issues in project.",
|
|
1405
|
+
mutating: false,
|
|
1406
|
+
inputSchema: {
|
|
1407
|
+
project_id: z.string().optional(),
|
|
1408
|
+
assignee_id: optionalStringOrNumber,
|
|
1409
|
+
assignee_username: optionalStringArray,
|
|
1410
|
+
author_id: optionalStringOrNumber,
|
|
1411
|
+
author_username: optionalString,
|
|
1412
|
+
confidential: optionalBoolean,
|
|
1413
|
+
created_after: optionalString,
|
|
1414
|
+
created_before: optionalString,
|
|
1415
|
+
due_date: optionalString,
|
|
1416
|
+
labels: optionalStringOrStringArray,
|
|
1417
|
+
milestone: optionalString,
|
|
1418
|
+
issue_type: z.enum(["issue", "incident", "test_case", "task"]).optional(),
|
|
1419
|
+
iteration_id: optionalStringOrNumber,
|
|
1420
|
+
scope: z.enum(["created_by_me", "assigned_to_me", "all"]).optional(),
|
|
1421
|
+
state: optionalString,
|
|
1422
|
+
search: optionalString,
|
|
1423
|
+
updated_after: optionalString,
|
|
1424
|
+
updated_before: optionalString,
|
|
1425
|
+
with_labels_details: optionalBoolean,
|
|
1426
|
+
...paginationShape
|
|
1427
|
+
},
|
|
1428
|
+
handler: async (args, context) => {
|
|
1429
|
+
const projectId = resolveProjectId(args, context, false);
|
|
1430
|
+
const query = toQuery(omit(args, ["project_id"]));
|
|
1431
|
+
|
|
1432
|
+
if (projectId) {
|
|
1433
|
+
return context.gitlab.listIssues(projectId, { query });
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
return context.gitlab.listGlobalIssues({ query });
|
|
1437
|
+
}
|
|
1438
|
+
},
|
|
1439
|
+
{
|
|
1440
|
+
name: "gitlab_my_issues",
|
|
1441
|
+
title: "My Issues",
|
|
1442
|
+
description: "List issues assigned to the current authenticated user.",
|
|
1443
|
+
mutating: false,
|
|
1444
|
+
inputSchema: {
|
|
1445
|
+
project_id: z.string().optional(),
|
|
1446
|
+
state: z.enum(["opened", "closed", "all"]).optional(),
|
|
1447
|
+
labels: optionalStringOrStringArray,
|
|
1448
|
+
milestone: optionalString,
|
|
1449
|
+
search: optionalString,
|
|
1450
|
+
created_after: optionalString,
|
|
1451
|
+
created_before: optionalString,
|
|
1452
|
+
updated_after: optionalString,
|
|
1453
|
+
updated_before: optionalString,
|
|
1454
|
+
...paginationShape
|
|
1455
|
+
},
|
|
1456
|
+
handler: async (args, context) => {
|
|
1457
|
+
const projectId = resolveProjectId(args, context, false);
|
|
1458
|
+
return context.gitlab.myIssues({
|
|
1459
|
+
project_id: projectId || undefined,
|
|
1460
|
+
...(toQuery(omit(args, ["project_id"])) as Record<string, string | number | boolean>)
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
},
|
|
1464
|
+
{
|
|
1465
|
+
name: "gitlab_get_issue",
|
|
1466
|
+
title: "Get Issue",
|
|
1467
|
+
description: "Get issue by IID.",
|
|
1468
|
+
mutating: false,
|
|
1469
|
+
inputSchema: {
|
|
1470
|
+
project_id: z.string().optional(),
|
|
1471
|
+
issue_iid: z.string().min(1)
|
|
1472
|
+
},
|
|
1473
|
+
handler: async (args, context) =>
|
|
1474
|
+
context.gitlab.getIssue(resolveProjectId(args, context, true), getString(args, "issue_iid"))
|
|
1475
|
+
},
|
|
1476
|
+
{
|
|
1477
|
+
name: "gitlab_create_issue",
|
|
1478
|
+
title: "Create Issue",
|
|
1479
|
+
description: "Create a new issue.",
|
|
1480
|
+
mutating: true,
|
|
1481
|
+
inputSchema: {
|
|
1482
|
+
project_id: z.string().optional(),
|
|
1483
|
+
title: z.string().min(1),
|
|
1484
|
+
description: optionalString,
|
|
1485
|
+
labels: optionalStringOrStringArray,
|
|
1486
|
+
milestone_id: optionalNumber,
|
|
1487
|
+
due_date: optionalString,
|
|
1488
|
+
confidential: optionalBoolean,
|
|
1489
|
+
issue_type: optionalString,
|
|
1490
|
+
assignee_ids: optionalNumberArray
|
|
1491
|
+
},
|
|
1492
|
+
handler: async (args, context) =>
|
|
1493
|
+
context.gitlab.createIssue(resolveProjectId(args, context, true), {
|
|
1494
|
+
title: getString(args, "title"),
|
|
1495
|
+
description: getOptionalString(args, "description"),
|
|
1496
|
+
labels: toCsvValue(args.labels),
|
|
1497
|
+
milestone_id: getOptionalNumber(args, "milestone_id"),
|
|
1498
|
+
due_date: getOptionalString(args, "due_date"),
|
|
1499
|
+
confidential: getOptionalBoolean(args, "confidential"),
|
|
1500
|
+
issue_type: getOptionalString(args, "issue_type"),
|
|
1501
|
+
assignee_ids: getOptionalNumberArray(args, "assignee_ids")
|
|
1502
|
+
})
|
|
1503
|
+
},
|
|
1504
|
+
{
|
|
1505
|
+
name: "gitlab_update_issue",
|
|
1506
|
+
title: "Update Issue",
|
|
1507
|
+
description: "Update issue fields.",
|
|
1508
|
+
mutating: true,
|
|
1509
|
+
inputSchema: {
|
|
1510
|
+
project_id: z.string().optional(),
|
|
1511
|
+
issue_iid: z.string().min(1),
|
|
1512
|
+
title: optionalString,
|
|
1513
|
+
description: optionalString,
|
|
1514
|
+
state_event: optionalString,
|
|
1515
|
+
labels: optionalStringOrStringArray,
|
|
1516
|
+
milestone_id: optionalNumber,
|
|
1517
|
+
due_date: optionalString,
|
|
1518
|
+
confidential: optionalBoolean,
|
|
1519
|
+
assignee_ids: optionalNumberArray,
|
|
1520
|
+
discussion_locked: optionalBoolean,
|
|
1521
|
+
weight: optionalNumber,
|
|
1522
|
+
issue_type: z.enum(["issue", "incident", "test_case", "task"]).optional()
|
|
1523
|
+
},
|
|
1524
|
+
handler: async (args, context) => {
|
|
1525
|
+
const payload = toQuery(omit(args, ["project_id", "issue_iid"])) as Record<string, unknown>;
|
|
1526
|
+
if (payload.labels === undefined) {
|
|
1527
|
+
payload.labels = toCsvValue(args.labels);
|
|
1528
|
+
}
|
|
1529
|
+
if (Array.isArray(args.assignee_ids)) {
|
|
1530
|
+
payload.assignee_ids = args.assignee_ids as number[];
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
return context.gitlab.updateIssue(
|
|
1534
|
+
resolveProjectId(args, context, true),
|
|
1535
|
+
getString(args, "issue_iid"),
|
|
1536
|
+
payload
|
|
1537
|
+
);
|
|
1538
|
+
}
|
|
1539
|
+
},
|
|
1540
|
+
{
|
|
1541
|
+
name: "gitlab_delete_issue",
|
|
1542
|
+
title: "Delete Issue",
|
|
1543
|
+
description: "Delete an issue.",
|
|
1544
|
+
mutating: true,
|
|
1545
|
+
inputSchema: {
|
|
1546
|
+
project_id: z.string().optional(),
|
|
1547
|
+
issue_iid: z.string().min(1)
|
|
1548
|
+
},
|
|
1549
|
+
handler: async (args, context) =>
|
|
1550
|
+
context.gitlab.deleteIssue(
|
|
1551
|
+
resolveProjectId(args, context, true),
|
|
1552
|
+
getString(args, "issue_iid")
|
|
1553
|
+
)
|
|
1554
|
+
},
|
|
1555
|
+
{
|
|
1556
|
+
name: "gitlab_list_issue_discussions",
|
|
1557
|
+
title: "List Issue Discussions",
|
|
1558
|
+
description: "List issue discussions.",
|
|
1559
|
+
mutating: false,
|
|
1560
|
+
inputSchema: {
|
|
1561
|
+
project_id: z.string().optional(),
|
|
1562
|
+
issue_iid: z.string().min(1),
|
|
1563
|
+
...paginationShape
|
|
1564
|
+
},
|
|
1565
|
+
handler: async (args, context) =>
|
|
1566
|
+
context.gitlab.listIssueDiscussions(
|
|
1567
|
+
resolveProjectId(args, context, true),
|
|
1568
|
+
getString(args, "issue_iid"),
|
|
1569
|
+
{ query: toQuery(omit(args, ["project_id", "issue_iid"])) }
|
|
1570
|
+
)
|
|
1571
|
+
},
|
|
1572
|
+
{
|
|
1573
|
+
name: "gitlab_create_issue_note",
|
|
1574
|
+
title: "Create Issue Note",
|
|
1575
|
+
description: "Create issue comment (top-level or discussion note).",
|
|
1576
|
+
mutating: true,
|
|
1577
|
+
inputSchema: {
|
|
1578
|
+
project_id: z.string().optional(),
|
|
1579
|
+
issue_iid: z.string().min(1),
|
|
1580
|
+
discussion_id: optionalString,
|
|
1581
|
+
body: z.string().min(1),
|
|
1582
|
+
created_at: optionalString
|
|
1583
|
+
},
|
|
1584
|
+
handler: async (args, context) =>
|
|
1585
|
+
context.gitlab.createIssueNote(
|
|
1586
|
+
resolveProjectId(args, context, true),
|
|
1587
|
+
getString(args, "issue_iid"),
|
|
1588
|
+
{
|
|
1589
|
+
body: getString(args, "body"),
|
|
1590
|
+
discussion_id: getOptionalString(args, "discussion_id"),
|
|
1591
|
+
created_at: getOptionalString(args, "created_at")
|
|
1592
|
+
}
|
|
1593
|
+
)
|
|
1594
|
+
},
|
|
1595
|
+
{
|
|
1596
|
+
name: "gitlab_update_issue_note",
|
|
1597
|
+
title: "Update Issue Note",
|
|
1598
|
+
description: "Update an issue discussion note body or resolved state.",
|
|
1599
|
+
mutating: true,
|
|
1600
|
+
inputSchema: {
|
|
1601
|
+
project_id: z.string().optional(),
|
|
1602
|
+
issue_iid: z.string().min(1),
|
|
1603
|
+
discussion_id: z.string().min(1),
|
|
1604
|
+
note_id: z.string().min(1),
|
|
1605
|
+
body: optionalString,
|
|
1606
|
+
resolved: optionalBoolean
|
|
1607
|
+
},
|
|
1608
|
+
handler: async (args, context) => {
|
|
1609
|
+
const body = getOptionalString(args, "body");
|
|
1610
|
+
const resolved = getOptionalBoolean(args, "resolved");
|
|
1611
|
+
|
|
1612
|
+
if (body === undefined && resolved === undefined) {
|
|
1613
|
+
throw new Error("Either body or resolved must be provided");
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
if (body !== undefined && resolved !== undefined) {
|
|
1617
|
+
throw new Error("Provide either body or resolved, not both");
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
return context.gitlab.updateIssueNote(
|
|
1621
|
+
resolveProjectId(args, context, true),
|
|
1622
|
+
getString(args, "issue_iid"),
|
|
1623
|
+
getString(args, "discussion_id"),
|
|
1624
|
+
getString(args, "note_id"),
|
|
1625
|
+
{ body, resolved }
|
|
1626
|
+
);
|
|
1627
|
+
}
|
|
1628
|
+
},
|
|
1629
|
+
{
|
|
1630
|
+
name: "gitlab_list_issue_links",
|
|
1631
|
+
title: "List Issue Links",
|
|
1632
|
+
description: "List related issue links for an issue.",
|
|
1633
|
+
mutating: false,
|
|
1634
|
+
inputSchema: {
|
|
1635
|
+
project_id: z.string().optional(),
|
|
1636
|
+
issue_iid: z.string().min(1)
|
|
1637
|
+
},
|
|
1638
|
+
handler: async (args, context) =>
|
|
1639
|
+
context.gitlab.listIssueLinks(
|
|
1640
|
+
resolveProjectId(args, context, true),
|
|
1641
|
+
getString(args, "issue_iid")
|
|
1642
|
+
)
|
|
1643
|
+
},
|
|
1644
|
+
{
|
|
1645
|
+
name: "gitlab_get_issue_link",
|
|
1646
|
+
title: "Get Issue Link",
|
|
1647
|
+
description: "Get a single issue link by ID.",
|
|
1648
|
+
mutating: false,
|
|
1649
|
+
inputSchema: {
|
|
1650
|
+
project_id: z.string().optional(),
|
|
1651
|
+
issue_iid: z.string().min(1),
|
|
1652
|
+
issue_link_id: z.string().min(1)
|
|
1653
|
+
},
|
|
1654
|
+
handler: async (args, context) =>
|
|
1655
|
+
context.gitlab.getIssueLink(
|
|
1656
|
+
resolveProjectId(args, context, true),
|
|
1657
|
+
getString(args, "issue_iid"),
|
|
1658
|
+
getString(args, "issue_link_id")
|
|
1659
|
+
)
|
|
1660
|
+
},
|
|
1661
|
+
{
|
|
1662
|
+
name: "gitlab_create_issue_link",
|
|
1663
|
+
title: "Create Issue Link",
|
|
1664
|
+
description: "Create a relation between two issues.",
|
|
1665
|
+
mutating: true,
|
|
1666
|
+
inputSchema: {
|
|
1667
|
+
project_id: z.string().optional(),
|
|
1668
|
+
issue_iid: z.string().min(1),
|
|
1669
|
+
target_project_id: z.string().min(1),
|
|
1670
|
+
target_issue_iid: z.string().min(1),
|
|
1671
|
+
link_type: z.enum(["relates_to", "blocks", "is_blocked_by"]).optional()
|
|
1672
|
+
},
|
|
1673
|
+
handler: async (args, context) =>
|
|
1674
|
+
context.gitlab.createIssueLink(
|
|
1675
|
+
resolveProjectId(args, context, true),
|
|
1676
|
+
getString(args, "issue_iid"),
|
|
1677
|
+
{
|
|
1678
|
+
target_project_id: getString(args, "target_project_id"),
|
|
1679
|
+
target_issue_iid: getString(args, "target_issue_iid"),
|
|
1680
|
+
link_type: getOptionalString(args, "link_type") as
|
|
1681
|
+
| "relates_to"
|
|
1682
|
+
| "blocks"
|
|
1683
|
+
| "is_blocked_by"
|
|
1684
|
+
| undefined
|
|
1685
|
+
}
|
|
1686
|
+
)
|
|
1687
|
+
},
|
|
1688
|
+
{
|
|
1689
|
+
name: "gitlab_delete_issue_link",
|
|
1690
|
+
title: "Delete Issue Link",
|
|
1691
|
+
description: "Delete a relation between issues.",
|
|
1692
|
+
mutating: true,
|
|
1693
|
+
inputSchema: {
|
|
1694
|
+
project_id: z.string().optional(),
|
|
1695
|
+
issue_iid: z.string().min(1),
|
|
1696
|
+
issue_link_id: z.string().min(1)
|
|
1697
|
+
},
|
|
1698
|
+
handler: async (args, context) =>
|
|
1699
|
+
context.gitlab.deleteIssueLink(
|
|
1700
|
+
resolveProjectId(args, context, true),
|
|
1701
|
+
getString(args, "issue_iid"),
|
|
1702
|
+
getString(args, "issue_link_id")
|
|
1703
|
+
)
|
|
1704
|
+
},
|
|
1705
|
+
{
|
|
1706
|
+
name: "gitlab_list_wiki_pages",
|
|
1707
|
+
title: "List Wiki Pages",
|
|
1708
|
+
description: "List wiki pages in a project.",
|
|
1709
|
+
mutating: false,
|
|
1710
|
+
requiresFeature: "wiki",
|
|
1711
|
+
inputSchema: {
|
|
1712
|
+
project_id: z.string().optional(),
|
|
1713
|
+
with_content: optionalBoolean,
|
|
1714
|
+
...paginationShape
|
|
1715
|
+
},
|
|
1716
|
+
handler: async (args, context) =>
|
|
1717
|
+
context.gitlab.listWikiPages(resolveProjectId(args, context, true), {
|
|
1718
|
+
query: toQuery(omit(args, ["project_id"]))
|
|
1719
|
+
})
|
|
1720
|
+
},
|
|
1721
|
+
{
|
|
1722
|
+
name: "gitlab_get_wiki_page",
|
|
1723
|
+
title: "Get Wiki Page",
|
|
1724
|
+
description: "Get wiki page by slug.",
|
|
1725
|
+
mutating: false,
|
|
1726
|
+
requiresFeature: "wiki",
|
|
1727
|
+
inputSchema: {
|
|
1728
|
+
project_id: z.string().optional(),
|
|
1729
|
+
slug: z.string().min(1),
|
|
1730
|
+
version: optionalString
|
|
1731
|
+
},
|
|
1732
|
+
handler: async (args, context) =>
|
|
1733
|
+
context.gitlab.getWikiPage(resolveProjectId(args, context, true), getString(args, "slug"), {
|
|
1734
|
+
query: toQuery(omit(args, ["project_id", "slug"]))
|
|
1735
|
+
})
|
|
1736
|
+
},
|
|
1737
|
+
{
|
|
1738
|
+
name: "gitlab_create_wiki_page",
|
|
1739
|
+
title: "Create Wiki Page",
|
|
1740
|
+
description: "Create a wiki page.",
|
|
1741
|
+
mutating: true,
|
|
1742
|
+
requiresFeature: "wiki",
|
|
1743
|
+
inputSchema: {
|
|
1744
|
+
project_id: z.string().optional(),
|
|
1745
|
+
title: z.string().min(1),
|
|
1746
|
+
content: z.string().min(1),
|
|
1747
|
+
format: optionalString
|
|
1748
|
+
},
|
|
1749
|
+
handler: async (args, context) =>
|
|
1750
|
+
context.gitlab.createWikiPage(resolveProjectId(args, context, true), {
|
|
1751
|
+
title: getString(args, "title"),
|
|
1752
|
+
content: getString(args, "content"),
|
|
1753
|
+
format: getOptionalString(args, "format") as
|
|
1754
|
+
| "markdown"
|
|
1755
|
+
| "rdoc"
|
|
1756
|
+
| "asciidoc"
|
|
1757
|
+
| "org"
|
|
1758
|
+
| undefined
|
|
1759
|
+
})
|
|
1760
|
+
},
|
|
1761
|
+
{
|
|
1762
|
+
name: "gitlab_update_wiki_page",
|
|
1763
|
+
title: "Update Wiki Page",
|
|
1764
|
+
description: "Update wiki page by slug.",
|
|
1765
|
+
mutating: true,
|
|
1766
|
+
requiresFeature: "wiki",
|
|
1767
|
+
inputSchema: {
|
|
1768
|
+
project_id: z.string().optional(),
|
|
1769
|
+
slug: z.string().min(1),
|
|
1770
|
+
content: z.string().min(1),
|
|
1771
|
+
title: optionalString,
|
|
1772
|
+
format: optionalString
|
|
1773
|
+
},
|
|
1774
|
+
handler: async (args, context) =>
|
|
1775
|
+
context.gitlab.updateWikiPage(
|
|
1776
|
+
resolveProjectId(args, context, true),
|
|
1777
|
+
getString(args, "slug"),
|
|
1778
|
+
{
|
|
1779
|
+
content: getString(args, "content"),
|
|
1780
|
+
title: getOptionalString(args, "title"),
|
|
1781
|
+
format: getOptionalString(args, "format") as
|
|
1782
|
+
| "markdown"
|
|
1783
|
+
| "rdoc"
|
|
1784
|
+
| "asciidoc"
|
|
1785
|
+
| "org"
|
|
1786
|
+
| undefined
|
|
1787
|
+
}
|
|
1788
|
+
)
|
|
1789
|
+
},
|
|
1790
|
+
{
|
|
1791
|
+
name: "gitlab_delete_wiki_page",
|
|
1792
|
+
title: "Delete Wiki Page",
|
|
1793
|
+
description: "Delete wiki page by slug.",
|
|
1794
|
+
mutating: true,
|
|
1795
|
+
requiresFeature: "wiki",
|
|
1796
|
+
inputSchema: {
|
|
1797
|
+
project_id: z.string().optional(),
|
|
1798
|
+
slug: z.string().min(1)
|
|
1799
|
+
},
|
|
1800
|
+
handler: async (args, context) =>
|
|
1801
|
+
context.gitlab.deleteWikiPage(
|
|
1802
|
+
resolveProjectId(args, context, true),
|
|
1803
|
+
getString(args, "slug")
|
|
1804
|
+
)
|
|
1805
|
+
},
|
|
1806
|
+
{
|
|
1807
|
+
name: "gitlab_list_pipelines",
|
|
1808
|
+
title: "List Pipelines",
|
|
1809
|
+
description: "List pipelines for a project.",
|
|
1810
|
+
mutating: false,
|
|
1811
|
+
requiresFeature: "pipeline",
|
|
1812
|
+
inputSchema: {
|
|
1813
|
+
project_id: z.string().optional(),
|
|
1814
|
+
scope: z.enum(["running", "pending", "finished", "branches", "tags"]).optional(),
|
|
1815
|
+
status: z
|
|
1816
|
+
.enum([
|
|
1817
|
+
"created",
|
|
1818
|
+
"waiting_for_resource",
|
|
1819
|
+
"preparing",
|
|
1820
|
+
"pending",
|
|
1821
|
+
"running",
|
|
1822
|
+
"success",
|
|
1823
|
+
"failed",
|
|
1824
|
+
"canceled",
|
|
1825
|
+
"skipped",
|
|
1826
|
+
"manual",
|
|
1827
|
+
"scheduled"
|
|
1828
|
+
])
|
|
1829
|
+
.optional(),
|
|
1830
|
+
ref: optionalString,
|
|
1831
|
+
sha: optionalString,
|
|
1832
|
+
yaml_errors: optionalBoolean,
|
|
1833
|
+
username: optionalString,
|
|
1834
|
+
updated_after: optionalString,
|
|
1835
|
+
updated_before: optionalString,
|
|
1836
|
+
order_by: z.enum(["id", "status", "ref", "updated_at", "user_id"]).optional(),
|
|
1837
|
+
sort: z.enum(["asc", "desc"]).optional(),
|
|
1838
|
+
source: optionalString,
|
|
1839
|
+
...paginationShape
|
|
1840
|
+
},
|
|
1841
|
+
handler: async (args, context) =>
|
|
1842
|
+
context.gitlab.listPipelines(resolveProjectId(args, context, true), {
|
|
1843
|
+
query: toQuery(omit(args, ["project_id"]))
|
|
1844
|
+
})
|
|
1845
|
+
},
|
|
1846
|
+
{
|
|
1847
|
+
name: "gitlab_get_pipeline",
|
|
1848
|
+
title: "Get Pipeline",
|
|
1849
|
+
description: "Get one pipeline.",
|
|
1850
|
+
mutating: false,
|
|
1851
|
+
requiresFeature: "pipeline",
|
|
1852
|
+
inputSchema: {
|
|
1853
|
+
project_id: z.string().optional(),
|
|
1854
|
+
pipeline_id: z.string().min(1)
|
|
1855
|
+
},
|
|
1856
|
+
handler: async (args, context) =>
|
|
1857
|
+
context.gitlab.getPipeline(
|
|
1858
|
+
resolveProjectId(args, context, true),
|
|
1859
|
+
getString(args, "pipeline_id")
|
|
1860
|
+
)
|
|
1861
|
+
},
|
|
1862
|
+
{
|
|
1863
|
+
name: "gitlab_list_pipeline_jobs",
|
|
1864
|
+
title: "List Pipeline Jobs",
|
|
1865
|
+
description: "List jobs in a pipeline.",
|
|
1866
|
+
mutating: false,
|
|
1867
|
+
requiresFeature: "pipeline",
|
|
1868
|
+
inputSchema: {
|
|
1869
|
+
project_id: z.string().optional(),
|
|
1870
|
+
pipeline_id: z.string().min(1),
|
|
1871
|
+
scope: z
|
|
1872
|
+
.enum([
|
|
1873
|
+
"created",
|
|
1874
|
+
"pending",
|
|
1875
|
+
"running",
|
|
1876
|
+
"failed",
|
|
1877
|
+
"success",
|
|
1878
|
+
"canceled",
|
|
1879
|
+
"skipped",
|
|
1880
|
+
"manual"
|
|
1881
|
+
])
|
|
1882
|
+
.optional(),
|
|
1883
|
+
include_retried: optionalBoolean,
|
|
1884
|
+
...paginationShape
|
|
1885
|
+
},
|
|
1886
|
+
handler: async (args, context) =>
|
|
1887
|
+
context.gitlab.listPipelineJobs(
|
|
1888
|
+
resolveProjectId(args, context, true),
|
|
1889
|
+
getString(args, "pipeline_id"),
|
|
1890
|
+
{ query: toQuery(omit(args, ["project_id", "pipeline_id"])) }
|
|
1891
|
+
)
|
|
1892
|
+
},
|
|
1893
|
+
{
|
|
1894
|
+
name: "gitlab_list_pipeline_trigger_jobs",
|
|
1895
|
+
title: "List Pipeline Trigger Jobs",
|
|
1896
|
+
description: "List downstream/bridge trigger jobs in a pipeline.",
|
|
1897
|
+
mutating: false,
|
|
1898
|
+
requiresFeature: "pipeline",
|
|
1899
|
+
inputSchema: {
|
|
1900
|
+
project_id: z.string().optional(),
|
|
1901
|
+
pipeline_id: z.string().min(1),
|
|
1902
|
+
scope: z
|
|
1903
|
+
.enum([
|
|
1904
|
+
"canceled",
|
|
1905
|
+
"canceling",
|
|
1906
|
+
"created",
|
|
1907
|
+
"failed",
|
|
1908
|
+
"manual",
|
|
1909
|
+
"pending",
|
|
1910
|
+
"preparing",
|
|
1911
|
+
"running",
|
|
1912
|
+
"scheduled",
|
|
1913
|
+
"skipped",
|
|
1914
|
+
"success",
|
|
1915
|
+
"waiting_for_resource"
|
|
1916
|
+
])
|
|
1917
|
+
.optional(),
|
|
1918
|
+
...paginationShape
|
|
1919
|
+
},
|
|
1920
|
+
handler: async (args, context) =>
|
|
1921
|
+
context.gitlab.listPipelineTriggerJobs(
|
|
1922
|
+
resolveProjectId(args, context, true),
|
|
1923
|
+
getString(args, "pipeline_id"),
|
|
1924
|
+
{ query: toQuery(omit(args, ["project_id", "pipeline_id"])) }
|
|
1925
|
+
)
|
|
1926
|
+
},
|
|
1927
|
+
{
|
|
1928
|
+
name: "gitlab_get_pipeline_job",
|
|
1929
|
+
title: "Get Pipeline Job",
|
|
1930
|
+
description: "Get one job by job ID.",
|
|
1931
|
+
mutating: false,
|
|
1932
|
+
requiresFeature: "pipeline",
|
|
1933
|
+
inputSchema: {
|
|
1934
|
+
project_id: z.string().optional(),
|
|
1935
|
+
job_id: z.string().min(1)
|
|
1936
|
+
},
|
|
1937
|
+
handler: async (args, context) =>
|
|
1938
|
+
context.gitlab.getPipelineJob(
|
|
1939
|
+
resolveProjectId(args, context, true),
|
|
1940
|
+
getString(args, "job_id")
|
|
1941
|
+
)
|
|
1942
|
+
},
|
|
1943
|
+
{
|
|
1944
|
+
name: "gitlab_get_pipeline_job_output",
|
|
1945
|
+
title: "Get Pipeline Job Output",
|
|
1946
|
+
description: "Get raw job trace output.",
|
|
1947
|
+
mutating: false,
|
|
1948
|
+
requiresFeature: "pipeline",
|
|
1949
|
+
inputSchema: {
|
|
1950
|
+
project_id: z.string().optional(),
|
|
1951
|
+
job_id: z.string().min(1)
|
|
1952
|
+
},
|
|
1953
|
+
handler: async (args, context) =>
|
|
1954
|
+
context.gitlab.getPipelineJobOutput(
|
|
1955
|
+
resolveProjectId(args, context, true),
|
|
1956
|
+
getString(args, "job_id")
|
|
1957
|
+
)
|
|
1958
|
+
},
|
|
1959
|
+
{
|
|
1960
|
+
name: "gitlab_create_pipeline",
|
|
1961
|
+
title: "Create Pipeline",
|
|
1962
|
+
description: "Trigger a new pipeline.",
|
|
1963
|
+
mutating: true,
|
|
1964
|
+
requiresFeature: "pipeline",
|
|
1965
|
+
inputSchema: {
|
|
1966
|
+
project_id: z.string().optional(),
|
|
1967
|
+
ref: z.string().min(1),
|
|
1968
|
+
variables: z
|
|
1969
|
+
.array(
|
|
1970
|
+
z.object({
|
|
1971
|
+
key: z.string(),
|
|
1972
|
+
value: z.string(),
|
|
1973
|
+
variable_type: optionalString
|
|
1974
|
+
})
|
|
1975
|
+
)
|
|
1976
|
+
.optional()
|
|
1977
|
+
},
|
|
1978
|
+
handler: async (args, context) =>
|
|
1979
|
+
context.gitlab.createPipeline(resolveProjectId(args, context, true), {
|
|
1980
|
+
ref: getString(args, "ref"),
|
|
1981
|
+
variables: getArray(args, "variables") as Array<{
|
|
1982
|
+
key: string;
|
|
1983
|
+
value: string;
|
|
1984
|
+
variable_type?: "env_var" | "file";
|
|
1985
|
+
}>
|
|
1986
|
+
})
|
|
1987
|
+
},
|
|
1988
|
+
{
|
|
1989
|
+
name: "gitlab_retry_pipeline",
|
|
1990
|
+
title: "Retry Pipeline",
|
|
1991
|
+
description: "Retry failed jobs in pipeline.",
|
|
1992
|
+
mutating: true,
|
|
1993
|
+
requiresFeature: "pipeline",
|
|
1994
|
+
inputSchema: {
|
|
1995
|
+
project_id: z.string().optional(),
|
|
1996
|
+
pipeline_id: z.string().min(1)
|
|
1997
|
+
},
|
|
1998
|
+
handler: async (args, context) =>
|
|
1999
|
+
context.gitlab.retryPipeline(
|
|
2000
|
+
resolveProjectId(args, context, true),
|
|
2001
|
+
getString(args, "pipeline_id")
|
|
2002
|
+
)
|
|
2003
|
+
},
|
|
2004
|
+
{
|
|
2005
|
+
name: "gitlab_cancel_pipeline",
|
|
2006
|
+
title: "Cancel Pipeline",
|
|
2007
|
+
description: "Cancel a running pipeline.",
|
|
2008
|
+
mutating: true,
|
|
2009
|
+
requiresFeature: "pipeline",
|
|
2010
|
+
inputSchema: {
|
|
2011
|
+
project_id: z.string().optional(),
|
|
2012
|
+
pipeline_id: z.string().min(1)
|
|
2013
|
+
},
|
|
2014
|
+
handler: async (args, context) =>
|
|
2015
|
+
context.gitlab.cancelPipeline(
|
|
2016
|
+
resolveProjectId(args, context, true),
|
|
2017
|
+
getString(args, "pipeline_id")
|
|
2018
|
+
)
|
|
2019
|
+
},
|
|
2020
|
+
{
|
|
2021
|
+
name: "gitlab_retry_pipeline_job",
|
|
2022
|
+
title: "Retry Pipeline Job",
|
|
2023
|
+
description: "Retry one failed job.",
|
|
2024
|
+
mutating: true,
|
|
2025
|
+
requiresFeature: "pipeline",
|
|
2026
|
+
inputSchema: {
|
|
2027
|
+
project_id: z.string().optional(),
|
|
2028
|
+
job_id: z.string().min(1)
|
|
2029
|
+
},
|
|
2030
|
+
handler: async (args, context) =>
|
|
2031
|
+
context.gitlab.retryPipelineJob(
|
|
2032
|
+
resolveProjectId(args, context, true),
|
|
2033
|
+
getString(args, "job_id")
|
|
2034
|
+
)
|
|
2035
|
+
},
|
|
2036
|
+
{
|
|
2037
|
+
name: "gitlab_cancel_pipeline_job",
|
|
2038
|
+
title: "Cancel Pipeline Job",
|
|
2039
|
+
description: "Cancel one running job.",
|
|
2040
|
+
mutating: true,
|
|
2041
|
+
requiresFeature: "pipeline",
|
|
2042
|
+
inputSchema: {
|
|
2043
|
+
project_id: z.string().optional(),
|
|
2044
|
+
job_id: z.string().min(1)
|
|
2045
|
+
},
|
|
2046
|
+
handler: async (args, context) =>
|
|
2047
|
+
context.gitlab.cancelPipelineJob(
|
|
2048
|
+
resolveProjectId(args, context, true),
|
|
2049
|
+
getString(args, "job_id")
|
|
2050
|
+
)
|
|
2051
|
+
},
|
|
2052
|
+
{
|
|
2053
|
+
name: "gitlab_play_pipeline_job",
|
|
2054
|
+
title: "Play Pipeline Job",
|
|
2055
|
+
description: "Play a manual job.",
|
|
2056
|
+
mutating: true,
|
|
2057
|
+
requiresFeature: "pipeline",
|
|
2058
|
+
inputSchema: {
|
|
2059
|
+
project_id: z.string().optional(),
|
|
2060
|
+
job_id: z.string().min(1)
|
|
2061
|
+
},
|
|
2062
|
+
handler: async (args, context) =>
|
|
2063
|
+
context.gitlab.playPipelineJob(
|
|
2064
|
+
resolveProjectId(args, context, true),
|
|
2065
|
+
getString(args, "job_id")
|
|
2066
|
+
)
|
|
2067
|
+
},
|
|
2068
|
+
{
|
|
2069
|
+
name: "gitlab_list_milestones",
|
|
2070
|
+
title: "List Milestones",
|
|
2071
|
+
description: "List project milestones.",
|
|
2072
|
+
mutating: false,
|
|
2073
|
+
requiresFeature: "milestone",
|
|
2074
|
+
inputSchema: {
|
|
2075
|
+
project_id: z.string().optional(),
|
|
2076
|
+
iids: optionalNumberArray,
|
|
2077
|
+
state: optionalString,
|
|
2078
|
+
title: optionalString,
|
|
2079
|
+
search: optionalString,
|
|
2080
|
+
include_ancestors: optionalBoolean,
|
|
2081
|
+
updated_before: optionalString,
|
|
2082
|
+
updated_after: optionalString,
|
|
2083
|
+
...paginationShape
|
|
2084
|
+
},
|
|
2085
|
+
handler: async (args, context) =>
|
|
2086
|
+
context.gitlab.listMilestones(resolveProjectId(args, context, true), {
|
|
2087
|
+
query: toQuery(omit(args, ["project_id"]))
|
|
2088
|
+
})
|
|
2089
|
+
},
|
|
2090
|
+
{
|
|
2091
|
+
name: "gitlab_get_milestone",
|
|
2092
|
+
title: "Get Milestone",
|
|
2093
|
+
description: "Get a milestone by ID.",
|
|
2094
|
+
mutating: false,
|
|
2095
|
+
requiresFeature: "milestone",
|
|
2096
|
+
inputSchema: {
|
|
2097
|
+
project_id: z.string().optional(),
|
|
2098
|
+
milestone_id: z.string().min(1)
|
|
2099
|
+
},
|
|
2100
|
+
handler: async (args, context) =>
|
|
2101
|
+
context.gitlab.getMilestone(
|
|
2102
|
+
resolveProjectId(args, context, true),
|
|
2103
|
+
getString(args, "milestone_id")
|
|
2104
|
+
)
|
|
2105
|
+
},
|
|
2106
|
+
{
|
|
2107
|
+
name: "gitlab_create_milestone",
|
|
2108
|
+
title: "Create Milestone",
|
|
2109
|
+
description: "Create a milestone.",
|
|
2110
|
+
mutating: true,
|
|
2111
|
+
requiresFeature: "milestone",
|
|
2112
|
+
inputSchema: {
|
|
2113
|
+
project_id: z.string().optional(),
|
|
2114
|
+
title: z.string().min(1),
|
|
2115
|
+
description: optionalString,
|
|
2116
|
+
due_date: optionalString,
|
|
2117
|
+
start_date: optionalString
|
|
2118
|
+
},
|
|
2119
|
+
handler: async (args, context) =>
|
|
2120
|
+
context.gitlab.createMilestone(resolveProjectId(args, context, true), {
|
|
2121
|
+
title: getString(args, "title"),
|
|
2122
|
+
description: getOptionalString(args, "description"),
|
|
2123
|
+
due_date: getOptionalString(args, "due_date"),
|
|
2124
|
+
start_date: getOptionalString(args, "start_date")
|
|
2125
|
+
})
|
|
2126
|
+
},
|
|
2127
|
+
{
|
|
2128
|
+
name: "gitlab_update_milestone",
|
|
2129
|
+
title: "Update Milestone",
|
|
2130
|
+
description: "Update milestone fields.",
|
|
2131
|
+
mutating: true,
|
|
2132
|
+
requiresFeature: "milestone",
|
|
2133
|
+
inputSchema: {
|
|
2134
|
+
project_id: z.string().optional(),
|
|
2135
|
+
milestone_id: z.string().min(1),
|
|
2136
|
+
title: optionalString,
|
|
2137
|
+
description: optionalString,
|
|
2138
|
+
due_date: optionalString,
|
|
2139
|
+
start_date: optionalString,
|
|
2140
|
+
state_event: optionalString
|
|
2141
|
+
},
|
|
2142
|
+
handler: async (args, context) =>
|
|
2143
|
+
context.gitlab.updateMilestone(
|
|
2144
|
+
resolveProjectId(args, context, true),
|
|
2145
|
+
getString(args, "milestone_id"),
|
|
2146
|
+
toQuery(omit(args, ["project_id", "milestone_id"]))
|
|
2147
|
+
)
|
|
2148
|
+
},
|
|
2149
|
+
{
|
|
2150
|
+
name: "gitlab_edit_milestone",
|
|
2151
|
+
title: "Edit Milestone (Alias)",
|
|
2152
|
+
description: "Backward-compatible alias of gitlab_update_milestone.",
|
|
2153
|
+
mutating: true,
|
|
2154
|
+
requiresFeature: "milestone",
|
|
2155
|
+
inputSchema: {
|
|
2156
|
+
project_id: z.string().optional(),
|
|
2157
|
+
milestone_id: z.string().min(1),
|
|
2158
|
+
title: optionalString,
|
|
2159
|
+
description: optionalString,
|
|
2160
|
+
due_date: optionalString,
|
|
2161
|
+
start_date: optionalString,
|
|
2162
|
+
state_event: optionalString
|
|
2163
|
+
},
|
|
2164
|
+
handler: async (args, context) =>
|
|
2165
|
+
context.gitlab.updateMilestone(
|
|
2166
|
+
resolveProjectId(args, context, true),
|
|
2167
|
+
getString(args, "milestone_id"),
|
|
2168
|
+
toQuery(omit(args, ["project_id", "milestone_id"]))
|
|
2169
|
+
)
|
|
2170
|
+
},
|
|
2171
|
+
{
|
|
2172
|
+
name: "gitlab_delete_milestone",
|
|
2173
|
+
title: "Delete Milestone",
|
|
2174
|
+
description: "Delete a milestone.",
|
|
2175
|
+
mutating: true,
|
|
2176
|
+
requiresFeature: "milestone",
|
|
2177
|
+
inputSchema: {
|
|
2178
|
+
project_id: z.string().optional(),
|
|
2179
|
+
milestone_id: z.string().min(1)
|
|
2180
|
+
},
|
|
2181
|
+
handler: async (args, context) =>
|
|
2182
|
+
context.gitlab.deleteMilestone(
|
|
2183
|
+
resolveProjectId(args, context, true),
|
|
2184
|
+
getString(args, "milestone_id")
|
|
2185
|
+
)
|
|
2186
|
+
},
|
|
2187
|
+
{
|
|
2188
|
+
name: "gitlab_get_milestone_issue",
|
|
2189
|
+
title: "Get Milestone Issues",
|
|
2190
|
+
description: "List issues assigned to a milestone.",
|
|
2191
|
+
mutating: false,
|
|
2192
|
+
requiresFeature: "milestone",
|
|
2193
|
+
inputSchema: {
|
|
2194
|
+
project_id: z.string().optional(),
|
|
2195
|
+
milestone_id: z.string().min(1)
|
|
2196
|
+
},
|
|
2197
|
+
handler: async (args, context) =>
|
|
2198
|
+
context.gitlab.getMilestoneIssues(
|
|
2199
|
+
resolveProjectId(args, context, true),
|
|
2200
|
+
getString(args, "milestone_id")
|
|
2201
|
+
)
|
|
2202
|
+
},
|
|
2203
|
+
{
|
|
2204
|
+
name: "gitlab_get_milestone_merge_requests",
|
|
2205
|
+
title: "Get Milestone Merge Requests",
|
|
2206
|
+
description: "List merge requests assigned to a milestone.",
|
|
2207
|
+
mutating: false,
|
|
2208
|
+
requiresFeature: "milestone",
|
|
2209
|
+
inputSchema: {
|
|
2210
|
+
project_id: z.string().optional(),
|
|
2211
|
+
milestone_id: z.string().min(1),
|
|
2212
|
+
...paginationShape
|
|
2213
|
+
},
|
|
2214
|
+
handler: async (args, context) =>
|
|
2215
|
+
context.gitlab.getMilestoneMergeRequests(
|
|
2216
|
+
resolveProjectId(args, context, true),
|
|
2217
|
+
getString(args, "milestone_id"),
|
|
2218
|
+
{ query: toQuery(omit(args, ["project_id", "milestone_id"])) }
|
|
2219
|
+
)
|
|
2220
|
+
},
|
|
2221
|
+
{
|
|
2222
|
+
name: "gitlab_promote_milestone",
|
|
2223
|
+
title: "Promote Milestone",
|
|
2224
|
+
description: "Promote a project milestone to a group milestone.",
|
|
2225
|
+
mutating: true,
|
|
2226
|
+
requiresFeature: "milestone",
|
|
2227
|
+
inputSchema: {
|
|
2228
|
+
project_id: z.string().optional(),
|
|
2229
|
+
milestone_id: z.string().min(1)
|
|
2230
|
+
},
|
|
2231
|
+
handler: async (args, context) =>
|
|
2232
|
+
context.gitlab.promoteMilestone(
|
|
2233
|
+
resolveProjectId(args, context, true),
|
|
2234
|
+
getString(args, "milestone_id")
|
|
2235
|
+
)
|
|
2236
|
+
},
|
|
2237
|
+
{
|
|
2238
|
+
name: "gitlab_get_milestone_burndown_events",
|
|
2239
|
+
title: "Get Milestone Burndown Events",
|
|
2240
|
+
description: "List burndown events for a milestone.",
|
|
2241
|
+
mutating: false,
|
|
2242
|
+
requiresFeature: "milestone",
|
|
2243
|
+
inputSchema: {
|
|
2244
|
+
project_id: z.string().optional(),
|
|
2245
|
+
milestone_id: z.string().min(1),
|
|
2246
|
+
...paginationShape
|
|
2247
|
+
},
|
|
2248
|
+
handler: async (args, context) =>
|
|
2249
|
+
context.gitlab.getMilestoneBurndownEvents(
|
|
2250
|
+
resolveProjectId(args, context, true),
|
|
2251
|
+
getString(args, "milestone_id"),
|
|
2252
|
+
{ query: toQuery(omit(args, ["project_id", "milestone_id"])) }
|
|
2253
|
+
)
|
|
2254
|
+
},
|
|
2255
|
+
{
|
|
2256
|
+
name: "gitlab_list_releases",
|
|
2257
|
+
title: "List Releases",
|
|
2258
|
+
description: "List project releases.",
|
|
2259
|
+
mutating: false,
|
|
2260
|
+
requiresFeature: "release",
|
|
2261
|
+
inputSchema: {
|
|
2262
|
+
project_id: z.string().optional(),
|
|
2263
|
+
order_by: z.enum(["released_at", "created_at"]).optional(),
|
|
2264
|
+
sort: z.enum(["asc", "desc"]).optional(),
|
|
2265
|
+
include_html_description: optionalBoolean,
|
|
2266
|
+
...paginationShape
|
|
2267
|
+
},
|
|
2268
|
+
handler: async (args, context) =>
|
|
2269
|
+
context.gitlab.listReleases(resolveProjectId(args, context, true), {
|
|
2270
|
+
query: toQuery(omit(args, ["project_id"]))
|
|
2271
|
+
})
|
|
2272
|
+
},
|
|
2273
|
+
{
|
|
2274
|
+
name: "gitlab_get_release",
|
|
2275
|
+
title: "Get Release",
|
|
2276
|
+
description: "Get one release by tag name.",
|
|
2277
|
+
mutating: false,
|
|
2278
|
+
requiresFeature: "release",
|
|
2279
|
+
inputSchema: {
|
|
2280
|
+
project_id: z.string().optional(),
|
|
2281
|
+
tag_name: z.string().min(1),
|
|
2282
|
+
include_html_description: optionalBoolean
|
|
2283
|
+
},
|
|
2284
|
+
handler: async (args, context) =>
|
|
2285
|
+
context.gitlab.getRelease(
|
|
2286
|
+
resolveProjectId(args, context, true),
|
|
2287
|
+
getString(args, "tag_name"),
|
|
2288
|
+
{ query: toQuery(omit(args, ["project_id", "tag_name"])) }
|
|
2289
|
+
)
|
|
2290
|
+
},
|
|
2291
|
+
{
|
|
2292
|
+
name: "gitlab_create_release",
|
|
2293
|
+
title: "Create Release",
|
|
2294
|
+
description: "Create a release.",
|
|
2295
|
+
mutating: true,
|
|
2296
|
+
requiresFeature: "release",
|
|
2297
|
+
inputSchema: {
|
|
2298
|
+
project_id: z.string().optional(),
|
|
2299
|
+
name: optionalString,
|
|
2300
|
+
tag_name: z.string().min(1),
|
|
2301
|
+
tag_message: optionalString,
|
|
2302
|
+
description: optionalString,
|
|
2303
|
+
ref: optionalString,
|
|
2304
|
+
released_at: optionalString,
|
|
2305
|
+
milestones: optionalStringArray,
|
|
2306
|
+
assets: optionalRecord
|
|
2307
|
+
},
|
|
2308
|
+
handler: async (args, context) =>
|
|
2309
|
+
context.gitlab.createRelease(
|
|
2310
|
+
resolveProjectId(args, context, true),
|
|
2311
|
+
toQuery(omit(args, ["project_id"]))
|
|
2312
|
+
)
|
|
2313
|
+
},
|
|
2314
|
+
{
|
|
2315
|
+
name: "gitlab_update_release",
|
|
2316
|
+
title: "Update Release",
|
|
2317
|
+
description: "Update existing release.",
|
|
2318
|
+
mutating: true,
|
|
2319
|
+
requiresFeature: "release",
|
|
2320
|
+
inputSchema: {
|
|
2321
|
+
project_id: z.string().optional(),
|
|
2322
|
+
tag_name: z.string().min(1),
|
|
2323
|
+
name: optionalString,
|
|
2324
|
+
description: optionalString,
|
|
2325
|
+
released_at: optionalString,
|
|
2326
|
+
milestones: optionalStringArray,
|
|
2327
|
+
assets: optionalRecord
|
|
2328
|
+
},
|
|
2329
|
+
handler: async (args, context) =>
|
|
2330
|
+
context.gitlab.updateRelease(
|
|
2331
|
+
resolveProjectId(args, context, true),
|
|
2332
|
+
getString(args, "tag_name"),
|
|
2333
|
+
toQuery(omit(args, ["project_id", "tag_name"]))
|
|
2334
|
+
)
|
|
2335
|
+
},
|
|
2336
|
+
{
|
|
2337
|
+
name: "gitlab_delete_release",
|
|
2338
|
+
title: "Delete Release",
|
|
2339
|
+
description: "Delete a release by tag.",
|
|
2340
|
+
mutating: true,
|
|
2341
|
+
requiresFeature: "release",
|
|
2342
|
+
inputSchema: {
|
|
2343
|
+
project_id: z.string().optional(),
|
|
2344
|
+
tag_name: z.string().min(1)
|
|
2345
|
+
},
|
|
2346
|
+
handler: async (args, context) =>
|
|
2347
|
+
context.gitlab.deleteRelease(
|
|
2348
|
+
resolveProjectId(args, context, true),
|
|
2349
|
+
getString(args, "tag_name")
|
|
2350
|
+
)
|
|
2351
|
+
},
|
|
2352
|
+
{
|
|
2353
|
+
name: "gitlab_create_release_evidence",
|
|
2354
|
+
title: "Create Release Evidence",
|
|
2355
|
+
description: "Create evidence for an existing release.",
|
|
2356
|
+
mutating: true,
|
|
2357
|
+
requiresFeature: "release",
|
|
2358
|
+
inputSchema: {
|
|
2359
|
+
project_id: z.string().optional(),
|
|
2360
|
+
tag_name: z.string().min(1)
|
|
2361
|
+
},
|
|
2362
|
+
handler: async (args, context) =>
|
|
2363
|
+
context.gitlab.createReleaseEvidence(
|
|
2364
|
+
resolveProjectId(args, context, true),
|
|
2365
|
+
getString(args, "tag_name")
|
|
2366
|
+
)
|
|
2367
|
+
},
|
|
2368
|
+
{
|
|
2369
|
+
name: "gitlab_download_release_asset",
|
|
2370
|
+
title: "Download Release Asset",
|
|
2371
|
+
description: "Download a release asset using its direct asset path.",
|
|
2372
|
+
mutating: false,
|
|
2373
|
+
requiresFeature: "release",
|
|
2374
|
+
inputSchema: {
|
|
2375
|
+
project_id: z.string().optional(),
|
|
2376
|
+
tag_name: z.string().min(1),
|
|
2377
|
+
direct_asset_path: z.string().min(1)
|
|
2378
|
+
},
|
|
2379
|
+
handler: async (args, context) =>
|
|
2380
|
+
context.gitlab.downloadReleaseAsset(
|
|
2381
|
+
resolveProjectId(args, context, true),
|
|
2382
|
+
getString(args, "tag_name"),
|
|
2383
|
+
getString(args, "direct_asset_path")
|
|
2384
|
+
)
|
|
2385
|
+
},
|
|
2386
|
+
{
|
|
2387
|
+
name: "gitlab_list_labels",
|
|
2388
|
+
title: "List Labels",
|
|
2389
|
+
description: "List project labels.",
|
|
2390
|
+
mutating: false,
|
|
2391
|
+
inputSchema: {
|
|
2392
|
+
project_id: z.string().optional(),
|
|
2393
|
+
with_counts: optionalBoolean,
|
|
2394
|
+
include_ancestor_groups: optionalBoolean,
|
|
2395
|
+
search: optionalString,
|
|
2396
|
+
...paginationShape
|
|
2397
|
+
},
|
|
2398
|
+
handler: async (args, context) =>
|
|
2399
|
+
context.gitlab.listLabels(resolveProjectId(args, context, true), {
|
|
2400
|
+
query: toQuery(omit(args, ["project_id"]))
|
|
2401
|
+
})
|
|
2402
|
+
},
|
|
2403
|
+
{
|
|
2404
|
+
name: "gitlab_get_label",
|
|
2405
|
+
title: "Get Label",
|
|
2406
|
+
description: "Get one label by ID.",
|
|
2407
|
+
mutating: false,
|
|
2408
|
+
inputSchema: {
|
|
2409
|
+
project_id: z.string().optional(),
|
|
2410
|
+
label_id: z.string().min(1),
|
|
2411
|
+
include_ancestor_groups: optionalBoolean
|
|
2412
|
+
},
|
|
2413
|
+
handler: async (args, context) =>
|
|
2414
|
+
context.gitlab.getLabel(
|
|
2415
|
+
resolveProjectId(args, context, true),
|
|
2416
|
+
getString(args, "label_id"),
|
|
2417
|
+
{
|
|
2418
|
+
query: toQuery(omit(args, ["project_id", "label_id"]))
|
|
2419
|
+
}
|
|
2420
|
+
)
|
|
2421
|
+
},
|
|
2422
|
+
{
|
|
2423
|
+
name: "gitlab_create_label",
|
|
2424
|
+
title: "Create Label",
|
|
2425
|
+
description: "Create a label.",
|
|
2426
|
+
mutating: true,
|
|
2427
|
+
inputSchema: {
|
|
2428
|
+
project_id: z.string().optional(),
|
|
2429
|
+
name: z.string().min(1),
|
|
2430
|
+
color: z.string().min(1),
|
|
2431
|
+
description: optionalString,
|
|
2432
|
+
priority: optionalNumber
|
|
2433
|
+
},
|
|
2434
|
+
handler: async (args, context) =>
|
|
2435
|
+
context.gitlab.createLabel(
|
|
2436
|
+
resolveProjectId(args, context, true),
|
|
2437
|
+
toQuery(omit(args, ["project_id"]))
|
|
2438
|
+
)
|
|
2439
|
+
},
|
|
2440
|
+
{
|
|
2441
|
+
name: "gitlab_update_label",
|
|
2442
|
+
title: "Update Label",
|
|
2443
|
+
description: "Update a label.",
|
|
2444
|
+
mutating: true,
|
|
2445
|
+
inputSchema: {
|
|
2446
|
+
project_id: z.string().optional(),
|
|
2447
|
+
name: optionalString,
|
|
2448
|
+
label_id: optionalString,
|
|
2449
|
+
new_name: optionalString,
|
|
2450
|
+
color: optionalString,
|
|
2451
|
+
description: optionalString,
|
|
2452
|
+
priority: optionalNumber
|
|
2453
|
+
},
|
|
2454
|
+
handler: async (args, context) => {
|
|
2455
|
+
const payload = toQuery(omit(args, ["project_id"])) as Record<string, unknown>;
|
|
2456
|
+
if (payload.name === undefined) {
|
|
2457
|
+
payload.name = getOptionalString(args, "label_id");
|
|
2458
|
+
}
|
|
2459
|
+
if (payload.name === undefined) {
|
|
2460
|
+
throw new Error("Either name or label_id must be provided");
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
return context.gitlab.updateLabel(resolveProjectId(args, context, true), payload);
|
|
2464
|
+
}
|
|
2465
|
+
},
|
|
2466
|
+
{
|
|
2467
|
+
name: "gitlab_delete_label",
|
|
2468
|
+
title: "Delete Label",
|
|
2469
|
+
description: "Delete a label by name.",
|
|
2470
|
+
mutating: true,
|
|
2471
|
+
inputSchema: {
|
|
2472
|
+
project_id: z.string().optional(),
|
|
2473
|
+
name: optionalString,
|
|
2474
|
+
label_id: optionalString
|
|
2475
|
+
},
|
|
2476
|
+
handler: async (args, context) => {
|
|
2477
|
+
const labelName = getOptionalString(args, "name") ?? getOptionalString(args, "label_id");
|
|
2478
|
+
if (!labelName) {
|
|
2479
|
+
throw new Error("Either name or label_id must be provided");
|
|
2480
|
+
}
|
|
2481
|
+
return context.gitlab.deleteLabel(resolveProjectId(args, context, true), labelName);
|
|
2482
|
+
}
|
|
2483
|
+
},
|
|
2484
|
+
{
|
|
2485
|
+
name: "gitlab_list_namespaces",
|
|
2486
|
+
title: "List Namespaces",
|
|
2487
|
+
description: "List namespaces visible to user.",
|
|
2488
|
+
mutating: false,
|
|
2489
|
+
inputSchema: {
|
|
2490
|
+
search: optionalString,
|
|
2491
|
+
owned: optionalBoolean,
|
|
2492
|
+
...paginationShape
|
|
2493
|
+
},
|
|
2494
|
+
handler: async (args, context) => context.gitlab.listNamespaces({ query: toQuery(args) })
|
|
2495
|
+
},
|
|
2496
|
+
{
|
|
2497
|
+
name: "gitlab_get_namespace",
|
|
2498
|
+
title: "Get Namespace",
|
|
2499
|
+
description: "Get namespace by ID or path.",
|
|
2500
|
+
mutating: false,
|
|
2501
|
+
inputSchema: {
|
|
2502
|
+
namespace_id_or_path: optionalString,
|
|
2503
|
+
namespace_id: optionalString
|
|
2504
|
+
},
|
|
2505
|
+
handler: async (args, context) => {
|
|
2506
|
+
const namespaceId =
|
|
2507
|
+
getOptionalString(args, "namespace_id_or_path") ??
|
|
2508
|
+
getOptionalString(args, "namespace_id");
|
|
2509
|
+
if (!namespaceId) {
|
|
2510
|
+
throw new Error("Either namespace_id_or_path or namespace_id must be provided");
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
return context.gitlab.getNamespace(namespaceId);
|
|
2514
|
+
}
|
|
2515
|
+
},
|
|
2516
|
+
{
|
|
2517
|
+
name: "gitlab_verify_namespace",
|
|
2518
|
+
title: "Verify Namespace",
|
|
2519
|
+
description: "Verify if namespace path exists.",
|
|
2520
|
+
mutating: false,
|
|
2521
|
+
inputSchema: {
|
|
2522
|
+
path: z.string().min(1)
|
|
2523
|
+
},
|
|
2524
|
+
handler: async (args, context) => context.gitlab.verifyNamespace(getString(args, "path"))
|
|
2525
|
+
},
|
|
2526
|
+
{
|
|
2527
|
+
name: "gitlab_get_users",
|
|
2528
|
+
title: "Get Users",
|
|
2529
|
+
description: "Search users.",
|
|
2530
|
+
mutating: false,
|
|
2531
|
+
inputSchema: {
|
|
2532
|
+
username: optionalString,
|
|
2533
|
+
search: optionalString,
|
|
2534
|
+
active: optionalBoolean,
|
|
2535
|
+
extern_uid: optionalString,
|
|
2536
|
+
provider: optionalString,
|
|
2537
|
+
...paginationShape
|
|
2538
|
+
},
|
|
2539
|
+
handler: async (args, context) => context.gitlab.getUsers({ query: toQuery(args) })
|
|
2540
|
+
},
|
|
2541
|
+
{
|
|
2542
|
+
name: "gitlab_list_events",
|
|
2543
|
+
title: "List Events",
|
|
2544
|
+
description: "List current user events.",
|
|
2545
|
+
mutating: false,
|
|
2546
|
+
inputSchema: {
|
|
2547
|
+
action: optionalString,
|
|
2548
|
+
target_type: optionalString,
|
|
2549
|
+
before: optionalString,
|
|
2550
|
+
after: optionalString,
|
|
2551
|
+
scope: optionalString,
|
|
2552
|
+
sort: optionalString,
|
|
2553
|
+
...paginationShape
|
|
2554
|
+
},
|
|
2555
|
+
handler: async (args, context) => context.gitlab.listEvents({ query: toQuery(args) })
|
|
2556
|
+
},
|
|
2557
|
+
{
|
|
2558
|
+
name: "gitlab_get_project_events",
|
|
2559
|
+
title: "Get Project Events",
|
|
2560
|
+
description: "List events for a specific project.",
|
|
2561
|
+
mutating: false,
|
|
2562
|
+
inputSchema: {
|
|
2563
|
+
project_id: z.string().optional(),
|
|
2564
|
+
action: optionalString,
|
|
2565
|
+
target_type: optionalString,
|
|
2566
|
+
before: optionalString,
|
|
2567
|
+
after: optionalString,
|
|
2568
|
+
sort: optionalString,
|
|
2569
|
+
...paginationShape
|
|
2570
|
+
},
|
|
2571
|
+
handler: async (args, context) =>
|
|
2572
|
+
context.gitlab.getProjectEvents(resolveProjectId(args, context, true), {
|
|
2573
|
+
query: toQuery(omit(args, ["project_id"]))
|
|
2574
|
+
})
|
|
2575
|
+
},
|
|
2576
|
+
{
|
|
2577
|
+
name: "gitlab_upload_markdown",
|
|
2578
|
+
title: "Upload Markdown",
|
|
2579
|
+
description: "Upload markdown file/attachment to project.",
|
|
2580
|
+
mutating: true,
|
|
2581
|
+
inputSchema: {
|
|
2582
|
+
project_id: z.string().optional(),
|
|
2583
|
+
content: optionalString,
|
|
2584
|
+
filename: z.string().default("upload.md"),
|
|
2585
|
+
file_path: optionalString
|
|
2586
|
+
},
|
|
2587
|
+
handler: async (args, context) => {
|
|
2588
|
+
const projectId = resolveProjectId(args, context, true);
|
|
2589
|
+
const filePath = getOptionalString(args, "file_path");
|
|
2590
|
+
if (filePath) {
|
|
2591
|
+
return context.gitlab.uploadMarkdownFile(projectId, filePath);
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
const content = getOptionalString(args, "content");
|
|
2595
|
+
if (!content) {
|
|
2596
|
+
throw new Error("Either file_path or content must be provided");
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2599
|
+
return context.gitlab.uploadMarkdown(projectId, content, getString(args, "filename"));
|
|
2600
|
+
}
|
|
2601
|
+
},
|
|
2602
|
+
{
|
|
2603
|
+
name: "gitlab_download_attachment",
|
|
2604
|
+
title: "Download Attachment",
|
|
2605
|
+
description: "Download attachment by URL/path and return base64.",
|
|
2606
|
+
mutating: false,
|
|
2607
|
+
inputSchema: {
|
|
2608
|
+
project_id: z.string().optional(),
|
|
2609
|
+
url_or_path: optionalString,
|
|
2610
|
+
secret: optionalString,
|
|
2611
|
+
filename: optionalString,
|
|
2612
|
+
local_path: optionalString
|
|
2613
|
+
},
|
|
2614
|
+
handler: async (args, context) => {
|
|
2615
|
+
const urlOrPath = getOptionalString(args, "url_or_path");
|
|
2616
|
+
if (urlOrPath) {
|
|
2617
|
+
const projectId = resolveProjectId(args, context, false);
|
|
2618
|
+
const upload = parseProjectUploadReference(urlOrPath);
|
|
2619
|
+
|
|
2620
|
+
if (context.env.GITLAB_ALLOWED_PROJECT_IDS.length > 0) {
|
|
2621
|
+
if (!projectId) {
|
|
2622
|
+
throw new Error(
|
|
2623
|
+
"project_id is required when GITLAB_ALLOWED_PROJECT_IDS is configured"
|
|
2624
|
+
);
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
if (!upload) {
|
|
2628
|
+
throw new Error(
|
|
2629
|
+
"In project-scoped mode, url_or_path must be a GitLab upload URL/path like '/uploads/<secret>/<filename>'"
|
|
2630
|
+
);
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
const apiRelativePath = `api/v4/projects/${encodeURIComponent(projectId)}/uploads/${encodeURIComponent(upload.secret)}/${encodeURIComponent(upload.filename)}`;
|
|
2634
|
+
return context.gitlab.downloadAttachment(apiRelativePath);
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
if (projectId && upload) {
|
|
2638
|
+
const apiRelativePath = `api/v4/projects/${encodeURIComponent(projectId)}/uploads/${encodeURIComponent(upload.secret)}/${encodeURIComponent(upload.filename)}`;
|
|
2639
|
+
return context.gitlab.downloadAttachment(apiRelativePath);
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
return context.gitlab.downloadAttachment(urlOrPath);
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
const secret = getOptionalString(args, "secret");
|
|
2646
|
+
const filename = getOptionalString(args, "filename");
|
|
2647
|
+
if (!secret || !filename) {
|
|
2648
|
+
throw new Error(
|
|
2649
|
+
"Either url_or_path must be provided, or both secret and filename must be provided"
|
|
2650
|
+
);
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
const projectId = resolveProjectId(args, context, true);
|
|
2654
|
+
const apiRelativePath = `api/v4/projects/${encodeURIComponent(projectId)}/uploads/${encodeURIComponent(secret)}/${encodeURIComponent(filename)}`;
|
|
2655
|
+
|
|
2656
|
+
return context.gitlab.downloadAttachment(apiRelativePath);
|
|
2657
|
+
}
|
|
2658
|
+
},
|
|
2659
|
+
{
|
|
2660
|
+
name: "gitlab_execute_graphql_query",
|
|
2661
|
+
title: "Execute GraphQL Query",
|
|
2662
|
+
description: "Execute read-only GraphQL query.",
|
|
2663
|
+
mutating: false,
|
|
2664
|
+
inputSchema: {
|
|
2665
|
+
query: z.string().min(1),
|
|
2666
|
+
variables: optionalRecord
|
|
2667
|
+
},
|
|
2668
|
+
handler: async (args, context) => {
|
|
2669
|
+
const query = getString(args, "query");
|
|
2670
|
+
|
|
2671
|
+
if (containsGraphqlMutation(query)) {
|
|
2672
|
+
throw new Error(
|
|
2673
|
+
"Mutation detected. Use gitlab_execute_graphql_mutation for mutation operations."
|
|
2674
|
+
);
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
return context.gitlab.executeGraphql(query, getOptionalRecord(args, "variables"));
|
|
2678
|
+
}
|
|
2679
|
+
},
|
|
2680
|
+
{
|
|
2681
|
+
name: "gitlab_execute_graphql_mutation",
|
|
2682
|
+
title: "Execute GraphQL Mutation",
|
|
2683
|
+
description: "Execute GraphQL mutation (disabled in read-only mode).",
|
|
2684
|
+
mutating: true,
|
|
2685
|
+
inputSchema: {
|
|
2686
|
+
query: z.string().min(1),
|
|
2687
|
+
variables: optionalRecord
|
|
2688
|
+
},
|
|
2689
|
+
handler: async (args, context) => {
|
|
2690
|
+
const query = getString(args, "query");
|
|
2691
|
+
|
|
2692
|
+
if (!containsGraphqlMutation(query)) {
|
|
2693
|
+
throw new Error("No mutation detected. Use gitlab_execute_graphql_query for queries.");
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
return context.gitlab.executeGraphql(query, getOptionalRecord(args, "variables"));
|
|
2697
|
+
}
|
|
2698
|
+
},
|
|
2699
|
+
{
|
|
2700
|
+
name: "gitlab_execute_graphql",
|
|
2701
|
+
title: "Execute GraphQL (Compat)",
|
|
2702
|
+
description:
|
|
2703
|
+
"Backward-compatible GraphQL executor. Mutation payloads still honor read-only policy.",
|
|
2704
|
+
mutating: false,
|
|
2705
|
+
inputSchema: {
|
|
2706
|
+
query: z.string().min(1),
|
|
2707
|
+
variables: optionalRecord
|
|
2708
|
+
},
|
|
2709
|
+
handler: async (args, context) => {
|
|
2710
|
+
const query = getString(args, "query");
|
|
2711
|
+
if (containsGraphqlMutation(query)) {
|
|
2712
|
+
context.policy.assertCanExecute({
|
|
2713
|
+
name: "gitlab_execute_graphql",
|
|
2714
|
+
mutating: true
|
|
2715
|
+
});
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2718
|
+
return context.gitlab.executeGraphql(query, getOptionalRecord(args, "variables"));
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
];
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
function assertAuthReady(context: AppContext): void {
|
|
2725
|
+
const auth = getSessionAuth();
|
|
2726
|
+
|
|
2727
|
+
if (context.env.REMOTE_AUTHORIZATION) {
|
|
2728
|
+
const token = auth?.token;
|
|
2729
|
+
if (!token) {
|
|
2730
|
+
throw new Error("Missing remote authorization token for this session");
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
if (context.env.ENABLE_DYNAMIC_API_URL && !auth?.apiUrl) {
|
|
2734
|
+
throw new Error("Missing remote API URL for this session");
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
return;
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
const hasFallbackAuth =
|
|
2741
|
+
Boolean(context.env.GITLAB_PERSONAL_ACCESS_TOKEN) ||
|
|
2742
|
+
Boolean(context.env.GITLAB_USE_OAUTH && context.env.GITLAB_OAUTH_CLIENT_ID) ||
|
|
2743
|
+
Boolean(context.env.GITLAB_TOKEN_SCRIPT) ||
|
|
2744
|
+
Boolean(context.env.GITLAB_TOKEN_FILE) ||
|
|
2745
|
+
Boolean(context.env.GITLAB_AUTH_COOKIE_PATH);
|
|
2746
|
+
|
|
2747
|
+
if (!hasFallbackAuth) {
|
|
2748
|
+
throw new Error(
|
|
2749
|
+
"Authentication required: set GITLAB_PERSONAL_ACCESS_TOKEN, GITLAB_TOKEN_SCRIPT, GITLAB_TOKEN_FILE, or GITLAB_AUTH_COOKIE_PATH"
|
|
2750
|
+
);
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
export function containsGraphqlMutation(query: string): boolean {
|
|
2755
|
+
if (!query.trim()) {
|
|
2756
|
+
return false;
|
|
2757
|
+
}
|
|
2758
|
+
|
|
2759
|
+
// Remove comments and string values to avoid false positives from text content.
|
|
2760
|
+
const normalized = query
|
|
2761
|
+
.replace(/#[^\n]*/g, " ")
|
|
2762
|
+
.replace(/"""[\s\S]*?"""/g, " ")
|
|
2763
|
+
.replace(/"(?:\\.|[^"\\])*"/g, " ");
|
|
2764
|
+
|
|
2765
|
+
return /\bmutation\b\s*(?:[A-Za-z_][A-Za-z0-9_]*)?\s*(?:\(|\{)/i.test(normalized);
|
|
2766
|
+
}
|
|
2767
|
+
|
|
2768
|
+
export function parseProjectUploadReference(
|
|
2769
|
+
input: string
|
|
2770
|
+
): { secret: string; filename: string } | undefined {
|
|
2771
|
+
const trimmed = input.trim();
|
|
2772
|
+
if (!trimmed) {
|
|
2773
|
+
return undefined;
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
let pathValue = trimmed;
|
|
2777
|
+
|
|
2778
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
2779
|
+
try {
|
|
2780
|
+
pathValue = new URL(trimmed).pathname;
|
|
2781
|
+
} catch {
|
|
2782
|
+
return undefined;
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
const [pathOnly] = pathValue.split(/[?#]/, 1);
|
|
2787
|
+
if (!pathOnly) {
|
|
2788
|
+
return undefined;
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
const marker = "/uploads/";
|
|
2792
|
+
const markerIndex = pathOnly.lastIndexOf(marker);
|
|
2793
|
+
if (markerIndex < 0) {
|
|
2794
|
+
return undefined;
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
const suffix = pathOnly.slice(markerIndex + marker.length);
|
|
2798
|
+
const [secret, ...filenameParts] = suffix.split("/").filter((segment) => segment.length > 0);
|
|
2799
|
+
|
|
2800
|
+
if (!secret || filenameParts.length === 0) {
|
|
2801
|
+
return undefined;
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
const filename = decodeURIComponent(filenameParts.join("/"));
|
|
2805
|
+
if (!filename) {
|
|
2806
|
+
return undefined;
|
|
2807
|
+
}
|
|
2808
|
+
|
|
2809
|
+
return { secret, filename };
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
export function shouldDisableGraphqlTools(
|
|
2813
|
+
allowedProjectIds: string[],
|
|
2814
|
+
allowGraphqlWithProjectScope: boolean
|
|
2815
|
+
): boolean {
|
|
2816
|
+
return allowedProjectIds.length > 0 && !allowGraphqlWithProjectScope;
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
function isGraphqlToolName(name: string): boolean {
|
|
2820
|
+
return (
|
|
2821
|
+
name === "gitlab_execute_graphql_query" ||
|
|
2822
|
+
name === "gitlab_execute_graphql_mutation" ||
|
|
2823
|
+
name === "gitlab_execute_graphql"
|
|
2824
|
+
);
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
function resolveProjectId(args: ToolArgs, context: AppContext, required: boolean): string {
|
|
2828
|
+
const fromArgs = getOptionalString(args, "project_id");
|
|
2829
|
+
const allowed = context.env.GITLAB_ALLOWED_PROJECT_IDS;
|
|
2830
|
+
|
|
2831
|
+
if (allowed.length > 0) {
|
|
2832
|
+
if (fromArgs && !allowed.includes(fromArgs)) {
|
|
2833
|
+
throw new Error(
|
|
2834
|
+
`Project '${fromArgs}' is not in GITLAB_ALLOWED_PROJECT_IDS: ${allowed.join(", ")}`
|
|
2835
|
+
);
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2838
|
+
if (!fromArgs && allowed.length === 1) {
|
|
2839
|
+
return requireArrayValue(allowed, 0, "GITLAB_ALLOWED_PROJECT_IDS is empty");
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
if (!fromArgs && allowed.length > 1) {
|
|
2843
|
+
throw new Error(
|
|
2844
|
+
`Multiple allowed projects configured (${allowed.join(", ")}). Please specify project_id.`
|
|
2845
|
+
);
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
return fromArgs ?? requireArrayValue(allowed, 0, "GITLAB_ALLOWED_PROJECT_IDS is empty");
|
|
2849
|
+
}
|
|
2850
|
+
|
|
2851
|
+
if (required && !fromArgs) {
|
|
2852
|
+
throw new Error("project_id is required");
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
return fromArgs ?? "";
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2858
|
+
function toToolError(error: unknown, context?: AppContext): CallToolResult {
|
|
2859
|
+
const detailMode = context?.env.GITLAB_ERROR_DETAIL_MODE ?? "full";
|
|
2860
|
+
|
|
2861
|
+
if (error instanceof GitLabApiError) {
|
|
2862
|
+
const payload: Record<string, unknown> = {
|
|
2863
|
+
error: `GitLab API error ${error.status}`
|
|
2864
|
+
};
|
|
2865
|
+
if (detailMode === "full") {
|
|
2866
|
+
payload.details = redactSensitive(error.details);
|
|
2867
|
+
}
|
|
2868
|
+
|
|
2869
|
+
return {
|
|
2870
|
+
isError: true,
|
|
2871
|
+
content: [
|
|
2872
|
+
{
|
|
2873
|
+
type: "text",
|
|
2874
|
+
text: JSON.stringify(payload, null, 2)
|
|
2875
|
+
}
|
|
2876
|
+
]
|
|
2877
|
+
};
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
if (error instanceof Error) {
|
|
2881
|
+
const message = detailMode === "full" ? error.message : "Request failed";
|
|
2882
|
+
return {
|
|
2883
|
+
isError: true,
|
|
2884
|
+
content: [
|
|
2885
|
+
{
|
|
2886
|
+
type: "text",
|
|
2887
|
+
text: message
|
|
2888
|
+
}
|
|
2889
|
+
]
|
|
2890
|
+
};
|
|
2891
|
+
}
|
|
2892
|
+
|
|
2893
|
+
return {
|
|
2894
|
+
isError: true,
|
|
2895
|
+
content: [
|
|
2896
|
+
{
|
|
2897
|
+
type: "text",
|
|
2898
|
+
text: "Unknown error"
|
|
2899
|
+
}
|
|
2900
|
+
]
|
|
2901
|
+
};
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
function toStructuredContent(value: unknown): Record<string, unknown> {
|
|
2905
|
+
if (typeof value === "object" && value !== null) {
|
|
2906
|
+
if (Array.isArray(value)) {
|
|
2907
|
+
return {
|
|
2908
|
+
items: value,
|
|
2909
|
+
count: value.length
|
|
2910
|
+
};
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2913
|
+
return value as Record<string, unknown>;
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
return {
|
|
2917
|
+
value
|
|
2918
|
+
};
|
|
2919
|
+
}
|
|
2920
|
+
|
|
2921
|
+
function omit(args: ToolArgs, keys: string[]): ToolArgs {
|
|
2922
|
+
const result: ToolArgs = {};
|
|
2923
|
+
for (const [key, value] of Object.entries(args)) {
|
|
2924
|
+
if (!keys.includes(key)) {
|
|
2925
|
+
result[key] = value;
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
return result;
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
function toQuery(args: ToolArgs): Record<string, string | number | boolean | undefined> {
|
|
2933
|
+
const output: Record<string, string | number | boolean | undefined> = {};
|
|
2934
|
+
|
|
2935
|
+
for (const [key, value] of Object.entries(args)) {
|
|
2936
|
+
if (value === undefined || value === null) {
|
|
2937
|
+
continue;
|
|
2938
|
+
}
|
|
2939
|
+
|
|
2940
|
+
if (Array.isArray(value)) {
|
|
2941
|
+
output[key] = value.join(",");
|
|
2942
|
+
continue;
|
|
2943
|
+
}
|
|
2944
|
+
|
|
2945
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
2946
|
+
output[key] = value;
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2950
|
+
return output;
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2953
|
+
function toCsvValue(value: unknown): string | undefined {
|
|
2954
|
+
if (value === undefined || value === null) {
|
|
2955
|
+
return undefined;
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
if (typeof value === "string") {
|
|
2959
|
+
return value;
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
if (Array.isArray(value)) {
|
|
2963
|
+
const items = value.filter((item): item is string => typeof item === "string");
|
|
2964
|
+
return items.length > 0 ? items.join(",") : undefined;
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2967
|
+
return undefined;
|
|
2968
|
+
}
|
|
2969
|
+
|
|
2970
|
+
function pickFirstMergeRequest(value: unknown): Record<string, unknown> | undefined {
|
|
2971
|
+
if (!Array.isArray(value)) {
|
|
2972
|
+
return undefined;
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2975
|
+
const first = value[0];
|
|
2976
|
+
if (typeof first !== "object" || first === null) {
|
|
2977
|
+
return undefined;
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2980
|
+
return first as Record<string, unknown>;
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
function getString(args: ToolArgs, key: string): string {
|
|
2984
|
+
const value = args[key];
|
|
2985
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
2986
|
+
throw new Error(`'${key}' must be a non-empty string`);
|
|
2987
|
+
}
|
|
2988
|
+
|
|
2989
|
+
return value;
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2992
|
+
function getOptionalString(args: ToolArgs, key: string): string | undefined {
|
|
2993
|
+
const value = args[key];
|
|
2994
|
+
if (value === undefined) {
|
|
2995
|
+
return undefined;
|
|
2996
|
+
}
|
|
2997
|
+
|
|
2998
|
+
if (typeof value !== "string") {
|
|
2999
|
+
throw new Error(`'${key}' must be a string`);
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
return value;
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
function getOptionalStringArray(args: ToolArgs, key: string): string[] | undefined {
|
|
3006
|
+
const value = args[key];
|
|
3007
|
+
if (value === undefined) {
|
|
3008
|
+
return undefined;
|
|
3009
|
+
}
|
|
3010
|
+
|
|
3011
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
3012
|
+
throw new Error(`'${key}' must be string[]`);
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
return value;
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
function getBoolean(args: ToolArgs, key: string): boolean {
|
|
3019
|
+
const value = args[key];
|
|
3020
|
+
if (typeof value !== "boolean") {
|
|
3021
|
+
throw new Error(`'${key}' must be boolean`);
|
|
3022
|
+
}
|
|
3023
|
+
|
|
3024
|
+
return value;
|
|
3025
|
+
}
|
|
3026
|
+
|
|
3027
|
+
function getOptionalBoolean(args: ToolArgs, key: string): boolean | undefined {
|
|
3028
|
+
const value = args[key];
|
|
3029
|
+
if (value === undefined) {
|
|
3030
|
+
return undefined;
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3033
|
+
if (typeof value !== "boolean") {
|
|
3034
|
+
throw new Error(`'${key}' must be boolean`);
|
|
3035
|
+
}
|
|
3036
|
+
|
|
3037
|
+
return value;
|
|
3038
|
+
}
|
|
3039
|
+
|
|
3040
|
+
function getOptionalNumber(args: ToolArgs, key: string): number | undefined {
|
|
3041
|
+
const value = args[key];
|
|
3042
|
+
if (value === undefined) {
|
|
3043
|
+
return undefined;
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
3047
|
+
throw new Error(`'${key}' must be number`);
|
|
3048
|
+
}
|
|
3049
|
+
|
|
3050
|
+
return value;
|
|
3051
|
+
}
|
|
3052
|
+
|
|
3053
|
+
function getArray(args: ToolArgs, key: string): unknown[] {
|
|
3054
|
+
const value = args[key];
|
|
3055
|
+
if (!Array.isArray(value)) {
|
|
3056
|
+
throw new Error(`'${key}' must be array`);
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
return value;
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
function getOptionalNumberArray(args: ToolArgs, key: string): number[] | undefined {
|
|
3063
|
+
const value = args[key];
|
|
3064
|
+
if (value === undefined) {
|
|
3065
|
+
return undefined;
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "number")) {
|
|
3069
|
+
throw new Error(`'${key}' must be number[]`);
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3072
|
+
return value;
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3075
|
+
function getOptionalRecord(args: ToolArgs, key: string): Record<string, unknown> | undefined {
|
|
3076
|
+
const value = args[key];
|
|
3077
|
+
if (value === undefined) {
|
|
3078
|
+
return undefined;
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
3082
|
+
throw new Error(`'${key}' must be an object`);
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
return value as Record<string, unknown>;
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3088
|
+
function requireArrayValue<T>(items: T[], index: number, errorMessage: string): T {
|
|
3089
|
+
const value = items[index];
|
|
3090
|
+
if (value === undefined) {
|
|
3091
|
+
throw new Error(errorMessage);
|
|
3092
|
+
}
|
|
3093
|
+
|
|
3094
|
+
return value;
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
function redactSensitive(value: unknown): unknown {
|
|
3098
|
+
if (typeof value === "string") {
|
|
3099
|
+
return value
|
|
3100
|
+
.replace(
|
|
3101
|
+
/\b(glpat-[a-z0-9_-]{10,}|ghp_[a-z0-9]{20,}|eyJ[a-zA-Z0-9._-]{20,})\b/g,
|
|
3102
|
+
"[REDACTED]"
|
|
3103
|
+
)
|
|
3104
|
+
.replace(
|
|
3105
|
+
/(private[-_]?token|authorization)["']?\s*[:=]\s*["']?[^"'\s,}]+/gi,
|
|
3106
|
+
"$1=[REDACTED]"
|
|
3107
|
+
);
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3110
|
+
if (Array.isArray(value)) {
|
|
3111
|
+
return value.map((item) => redactSensitive(item));
|
|
3112
|
+
}
|
|
3113
|
+
|
|
3114
|
+
if (value && typeof value === "object") {
|
|
3115
|
+
const input = value as Record<string, unknown>;
|
|
3116
|
+
const output: Record<string, unknown> = {};
|
|
3117
|
+
for (const [key, item] of Object.entries(input)) {
|
|
3118
|
+
if (/token|authorization|password|secret/i.test(key)) {
|
|
3119
|
+
output[key] = "[REDACTED]";
|
|
3120
|
+
continue;
|
|
3121
|
+
}
|
|
3122
|
+
output[key] = redactSensitive(item);
|
|
3123
|
+
}
|
|
3124
|
+
return output;
|
|
3125
|
+
}
|
|
3126
|
+
|
|
3127
|
+
return value;
|
|
3128
|
+
}
|