@zereight/mcp-gitlab 2.1.57 → 2.1.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +13 -1
- package/README.md +13 -1
- package/README.zh-CN.md +13 -1
- package/build/config.js +2 -0
- package/build/index.js +388 -47
- package/build/schemas.js +138 -3
- package/build/scripts/check-skill-sync.js +5 -0
- package/build/stateless/session-id.js +6 -1
- package/build/test/stateless/session-id.test.js +26 -0
- package/build/test/test-deployment-tools.js +84 -1
- package/build/test/test-dynamic-project-scope.js +362 -0
- package/build/test/test-permission-mode.js +1 -0
- package/build/test/test-toolset-filtering.js +1 -1
- package/build/tools/registry.js +106 -2
- package/build/tools/tool-descriptions.js +2 -0
- package/build/utils/redact-sensitive.js +11 -4
- package/package.json +1 -1
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { describe, test, before, after } from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { cleanupServers, findAvailablePort, HOST, launchServer, TransportMode, } from "./utils/server-launcher.js";
|
|
4
|
+
import { MockGitLabServer, findMockServerPort } from "./utils/mock-gitlab-server.js";
|
|
5
|
+
import { CustomHeaderClient } from "./clients/custom-header-client.js";
|
|
6
|
+
const MOCK_TOKEN = "glpat-dynamic-scope-token";
|
|
7
|
+
const SCOPE_HEADER = "x-gitlab-allowed-project-ids";
|
|
8
|
+
async function connectClient(mcpUrl, scope) {
|
|
9
|
+
const headers = { authorization: `Bearer ${MOCK_TOKEN}` };
|
|
10
|
+
if (scope !== undefined) {
|
|
11
|
+
headers[SCOPE_HEADER] = scope;
|
|
12
|
+
}
|
|
13
|
+
const client = new CustomHeaderClient(headers);
|
|
14
|
+
await client.connect(mcpUrl);
|
|
15
|
+
return client;
|
|
16
|
+
}
|
|
17
|
+
async function getProjectId(client, projectId) {
|
|
18
|
+
const result = await client.callTool("get_project", { project_id: projectId });
|
|
19
|
+
assert.ok(result.content, "Should have content");
|
|
20
|
+
const content = result.content[0];
|
|
21
|
+
assert.ok("text" in content, "Content should have text");
|
|
22
|
+
const project = JSON.parse(content.text);
|
|
23
|
+
return project.id.toString();
|
|
24
|
+
}
|
|
25
|
+
describe("Dynamic project scope", { concurrency: 1 }, () => {
|
|
26
|
+
describe("with GITLAB_ALLOWED_PROJECT_IDS set", () => {
|
|
27
|
+
let mockGitLab;
|
|
28
|
+
const servers = [];
|
|
29
|
+
let mcpUrl;
|
|
30
|
+
before(async () => {
|
|
31
|
+
const mockPort = await findMockServerPort();
|
|
32
|
+
mockGitLab = new MockGitLabServer({ port: mockPort, validTokens: [MOCK_TOKEN] });
|
|
33
|
+
mockGitLab.addMockHandler("get", "/projects", (req, res) => {
|
|
34
|
+
if (req.query.search === "no-totals") {
|
|
35
|
+
const requestedPage = Number(req.query.page ?? "1");
|
|
36
|
+
if (requestedPage < 3) {
|
|
37
|
+
res.set("x-next-page", String(requestedPage + 1));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
res.set("x-total", "3");
|
|
42
|
+
res.set("x-total-pages", "2");
|
|
43
|
+
}
|
|
44
|
+
res.json(["1", "2", "3"].map(id => ({
|
|
45
|
+
id: Number(id),
|
|
46
|
+
name: `Project ${id}`,
|
|
47
|
+
path: `project-${id}`,
|
|
48
|
+
path_with_namespace: `group/project-${id}`,
|
|
49
|
+
description: null,
|
|
50
|
+
visibility: "private",
|
|
51
|
+
namespace: { id: 1, name: "Group", path: "group", kind: "group", full_path: "group" },
|
|
52
|
+
})));
|
|
53
|
+
});
|
|
54
|
+
await mockGitLab.start();
|
|
55
|
+
const mcpPort = await findAvailablePort(3200);
|
|
56
|
+
const server = await launchServer({
|
|
57
|
+
mode: TransportMode.STREAMABLE_HTTP,
|
|
58
|
+
port: mcpPort,
|
|
59
|
+
timeout: 5000,
|
|
60
|
+
env: {
|
|
61
|
+
STREAMABLE_HTTP: "true",
|
|
62
|
+
REMOTE_AUTHORIZATION: "true",
|
|
63
|
+
ENABLE_DYNAMIC_PROJECT_SCOPE: "true",
|
|
64
|
+
ENABLE_STRICT_PROJECT_SCOPE: "true",
|
|
65
|
+
GITLAB_API_URL: `${mockGitLab.getUrl()}/api/v4`,
|
|
66
|
+
GITLAB_ALLOWED_PROJECT_IDS: "1,2",
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
servers.push(server);
|
|
70
|
+
mcpUrl = `http://${HOST}:${mcpPort}/mcp`;
|
|
71
|
+
});
|
|
72
|
+
after(async () => {
|
|
73
|
+
cleanupServers(servers);
|
|
74
|
+
await mockGitLab?.stop();
|
|
75
|
+
});
|
|
76
|
+
test("header narrows the allowlist to the requested projects", async () => {
|
|
77
|
+
const client = await connectClient(mcpUrl, "1");
|
|
78
|
+
try {
|
|
79
|
+
assert.strictEqual(await getProjectId(client, ""), "1");
|
|
80
|
+
await assert.rejects(() => client.callTool("get_project", { project_id: "2" }), /Access denied/);
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
await client.disconnect();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
test("project outside GITLAB_ALLOWED_PROJECT_IDS rejects the session", async () => {
|
|
87
|
+
const client = new CustomHeaderClient({
|
|
88
|
+
authorization: `Bearer ${MOCK_TOKEN}`,
|
|
89
|
+
[SCOPE_HEADER]: "1,3",
|
|
90
|
+
});
|
|
91
|
+
let connected = false;
|
|
92
|
+
try {
|
|
93
|
+
await client.connect(mcpUrl);
|
|
94
|
+
connected = true;
|
|
95
|
+
await client.callTool("get_project", { project_id: "1" });
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Expected: the session is rejected before any tool call is possible.
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
await client.disconnect();
|
|
102
|
+
}
|
|
103
|
+
assert.strictEqual(connected, false, "out-of-scope header must not initialize a session");
|
|
104
|
+
});
|
|
105
|
+
test("empty header rejects the session", async () => {
|
|
106
|
+
const client = new CustomHeaderClient({
|
|
107
|
+
authorization: `Bearer ${MOCK_TOKEN}`,
|
|
108
|
+
[SCOPE_HEADER]: " ",
|
|
109
|
+
});
|
|
110
|
+
let connected = false;
|
|
111
|
+
try {
|
|
112
|
+
await client.connect(mcpUrl);
|
|
113
|
+
connected = true;
|
|
114
|
+
await client.callTool("get_project", { project_id: "1" });
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Expected: the session is rejected before any tool call is possible.
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
await client.disconnect();
|
|
121
|
+
}
|
|
122
|
+
assert.strictEqual(connected, false, "an empty header must not initialize a session");
|
|
123
|
+
});
|
|
124
|
+
test("scoped session refuses non-scopable tools", async () => {
|
|
125
|
+
const client = await connectClient(mcpUrl, "1");
|
|
126
|
+
try {
|
|
127
|
+
await assert.rejects(() => client.callTool("execute_graphql", { query: "query { currentUser { id } }" }), /not allowed while a project allowlist is in effect/);
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
await client.disconnect();
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
test("session without the header keeps the full env allowlist", async () => {
|
|
134
|
+
const client = await connectClient(mcpUrl);
|
|
135
|
+
try {
|
|
136
|
+
assert.strictEqual(await getProjectId(client, "2"), "2");
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
await client.disconnect();
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
test("list_issues without project_id stays within the session scope", async () => {
|
|
143
|
+
const client = await connectClient(mcpUrl, "1");
|
|
144
|
+
try {
|
|
145
|
+
const result = await client.callTool("list_issues", {});
|
|
146
|
+
assert.ok(result.content, "Should have content");
|
|
147
|
+
const content = result.content[0];
|
|
148
|
+
assert.ok("text" in content, "Content should have text");
|
|
149
|
+
const issues = JSON.parse(content.text);
|
|
150
|
+
assert.ok(issues.every(issue => issue.web_url.includes("/project/1/")), "Should only return issues of the scoped project");
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
await client.disconnect();
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
test("list_todos is refused under strict scope", async () => {
|
|
157
|
+
const client = await connectClient(mcpUrl, "1");
|
|
158
|
+
try {
|
|
159
|
+
await assert.rejects(() => client.callTool("list_todos", {}), /not allowed while a project allowlist is in effect/);
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
await client.disconnect();
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
test("search_repositories keeps the unfiltered pagination totals", async () => {
|
|
166
|
+
const client = await connectClient(mcpUrl, "1");
|
|
167
|
+
try {
|
|
168
|
+
const result = await client.callTool("search_repositories", { search: "project" });
|
|
169
|
+
assert.ok(result.content, "Should have content");
|
|
170
|
+
const content = result.content[0];
|
|
171
|
+
assert.ok("text" in content, "Content should have text");
|
|
172
|
+
const search = JSON.parse(content.text);
|
|
173
|
+
assert.deepStrictEqual(search.items.map(item => item.id), ["1"]);
|
|
174
|
+
assert.strictEqual(search.count, 3, "count must reflect the unfiltered GitLab total");
|
|
175
|
+
assert.strictEqual(search.total_pages, 2, "total_pages must cover the unfiltered pages");
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
await client.disconnect();
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
test("search_repositories follows next_page across all pages when totals are missing", async () => {
|
|
182
|
+
const client = await connectClient(mcpUrl, "1");
|
|
183
|
+
try {
|
|
184
|
+
let page = 1;
|
|
185
|
+
const visited = [];
|
|
186
|
+
while (page !== undefined) {
|
|
187
|
+
const result = await client.callTool("search_repositories", {
|
|
188
|
+
search: "no-totals",
|
|
189
|
+
page,
|
|
190
|
+
});
|
|
191
|
+
assert.ok(result.content, "Should have content");
|
|
192
|
+
const content = result.content[0];
|
|
193
|
+
assert.ok("text" in content, "Content should have text");
|
|
194
|
+
const search = JSON.parse(content.text);
|
|
195
|
+
assert.strictEqual(search.total_pages, undefined, "total_pages must stay unset");
|
|
196
|
+
assert.deepStrictEqual(search.items.map(item => item.id), ["1"]);
|
|
197
|
+
visited.push(search.current_page);
|
|
198
|
+
page = search.next_page;
|
|
199
|
+
}
|
|
200
|
+
assert.deepStrictEqual(visited, [1, 2, 3], "next_page must make every page reachable");
|
|
201
|
+
}
|
|
202
|
+
finally {
|
|
203
|
+
await client.disconnect();
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
test("list_projects is filtered to the session scope", async () => {
|
|
207
|
+
const client = await connectClient(mcpUrl, "1");
|
|
208
|
+
try {
|
|
209
|
+
const result = await client.callTool("list_projects", {});
|
|
210
|
+
assert.ok(result.content, "Should have content");
|
|
211
|
+
const content = result.content[0];
|
|
212
|
+
assert.ok("text" in content, "Content should have text");
|
|
213
|
+
const projects = JSON.parse(content.text);
|
|
214
|
+
assert.deepStrictEqual(projects.map(project => project.id), ["1"]);
|
|
215
|
+
}
|
|
216
|
+
finally {
|
|
217
|
+
await client.disconnect();
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
test("list_projects is filtered to the env allowlist without the header", async () => {
|
|
221
|
+
const client = await connectClient(mcpUrl);
|
|
222
|
+
try {
|
|
223
|
+
const result = await client.callTool("list_projects", {});
|
|
224
|
+
assert.ok(result.content, "Should have content");
|
|
225
|
+
const content = result.content[0];
|
|
226
|
+
assert.ok("text" in content, "Content should have text");
|
|
227
|
+
const projects = JSON.parse(content.text);
|
|
228
|
+
assert.deepStrictEqual(projects.map(project => project.id), ["1", "2"]);
|
|
229
|
+
}
|
|
230
|
+
finally {
|
|
231
|
+
await client.disconnect();
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
describe("without GITLAB_ALLOWED_PROJECT_IDS", () => {
|
|
236
|
+
let mockGitLab;
|
|
237
|
+
const servers = [];
|
|
238
|
+
let mcpUrl;
|
|
239
|
+
before(async () => {
|
|
240
|
+
const mockPort = await findMockServerPort();
|
|
241
|
+
mockGitLab = new MockGitLabServer({ port: mockPort, validTokens: [MOCK_TOKEN] });
|
|
242
|
+
mockGitLab.addRootHandler("post", "/api/graphql", (req, res) => {
|
|
243
|
+
const { query } = req.body;
|
|
244
|
+
if (query.includes("vulnerability(")) {
|
|
245
|
+
res.json({
|
|
246
|
+
data: {
|
|
247
|
+
vulnerability: {
|
|
248
|
+
id: "gid://gitlab/Vulnerability/42",
|
|
249
|
+
title: "Mock vulnerability",
|
|
250
|
+
project: {
|
|
251
|
+
id: "gid://gitlab/Project/8",
|
|
252
|
+
name: "Other project",
|
|
253
|
+
fullPath: "other/project",
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
res.status(500).json({ message: `Unexpected GraphQL query: ${query}` });
|
|
261
|
+
});
|
|
262
|
+
await mockGitLab.start();
|
|
263
|
+
const mcpPort = await findAvailablePort(3200);
|
|
264
|
+
const server = await launchServer({
|
|
265
|
+
mode: TransportMode.STREAMABLE_HTTP,
|
|
266
|
+
port: mcpPort,
|
|
267
|
+
timeout: 5000,
|
|
268
|
+
env: {
|
|
269
|
+
STREAMABLE_HTTP: "true",
|
|
270
|
+
REMOTE_AUTHORIZATION: "true",
|
|
271
|
+
ENABLE_DYNAMIC_PROJECT_SCOPE: "true",
|
|
272
|
+
GITLAB_API_URL: `${mockGitLab.getUrl()}/api/v4`,
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
servers.push(server);
|
|
276
|
+
mcpUrl = `http://${HOST}:${mcpPort}/mcp`;
|
|
277
|
+
});
|
|
278
|
+
after(async () => {
|
|
279
|
+
cleanupServers(servers);
|
|
280
|
+
await mockGitLab?.stop();
|
|
281
|
+
});
|
|
282
|
+
test("header bounds an otherwise unrestricted server", async () => {
|
|
283
|
+
const client = await connectClient(mcpUrl, "7");
|
|
284
|
+
try {
|
|
285
|
+
assert.strictEqual(await getProjectId(client, ""), "7");
|
|
286
|
+
await assert.rejects(() => client.callTool("get_project", { project_id: "8" }), /Access denied/);
|
|
287
|
+
await assert.rejects(() => client.callTool("execute_graphql", { query: "query { currentUser { id } }" }), /not allowed while a project allowlist is in effect/);
|
|
288
|
+
}
|
|
289
|
+
finally {
|
|
290
|
+
await client.disconnect();
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
test("session without the header stays unrestricted", async () => {
|
|
294
|
+
const client = await connectClient(mcpUrl);
|
|
295
|
+
try {
|
|
296
|
+
assert.strictEqual(await getProjectId(client, "8"), "8");
|
|
297
|
+
}
|
|
298
|
+
finally {
|
|
299
|
+
await client.disconnect();
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
test("scoped session cannot reach vulnerabilities of other projects", async () => {
|
|
303
|
+
const client = await connectClient(mcpUrl, "7");
|
|
304
|
+
try {
|
|
305
|
+
await assert.rejects(() => client.callTool("get_vulnerability", { vulnerability_id: "42" }), /Access denied/);
|
|
306
|
+
}
|
|
307
|
+
finally {
|
|
308
|
+
await client.disconnect();
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
test("unscoped session can reach vulnerabilities", async () => {
|
|
312
|
+
const client = await connectClient(mcpUrl);
|
|
313
|
+
try {
|
|
314
|
+
const result = await client.callTool("get_vulnerability", { vulnerability_id: "42" });
|
|
315
|
+
assert.ok(result.content, "Should have content");
|
|
316
|
+
const content = result.content[0];
|
|
317
|
+
assert.ok("text" in content, "Content should have text");
|
|
318
|
+
const vulnerability = JSON.parse(content.text);
|
|
319
|
+
assert.strictEqual(vulnerability.project.fullPath, "other/project");
|
|
320
|
+
}
|
|
321
|
+
finally {
|
|
322
|
+
await client.disconnect();
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
describe("with ENABLE_DYNAMIC_PROJECT_SCOPE disabled", () => {
|
|
327
|
+
let mockGitLab;
|
|
328
|
+
const servers = [];
|
|
329
|
+
let mcpUrl;
|
|
330
|
+
before(async () => {
|
|
331
|
+
const mockPort = await findMockServerPort();
|
|
332
|
+
mockGitLab = new MockGitLabServer({ port: mockPort, validTokens: [MOCK_TOKEN] });
|
|
333
|
+
await mockGitLab.start();
|
|
334
|
+
const mcpPort = await findAvailablePort(3200);
|
|
335
|
+
const server = await launchServer({
|
|
336
|
+
mode: TransportMode.STREAMABLE_HTTP,
|
|
337
|
+
port: mcpPort,
|
|
338
|
+
timeout: 5000,
|
|
339
|
+
env: {
|
|
340
|
+
STREAMABLE_HTTP: "true",
|
|
341
|
+
REMOTE_AUTHORIZATION: "true",
|
|
342
|
+
GITLAB_API_URL: `${mockGitLab.getUrl()}/api/v4`,
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
servers.push(server);
|
|
346
|
+
mcpUrl = `http://${HOST}:${mcpPort}/mcp`;
|
|
347
|
+
});
|
|
348
|
+
after(async () => {
|
|
349
|
+
cleanupServers(servers);
|
|
350
|
+
await mockGitLab?.stop();
|
|
351
|
+
});
|
|
352
|
+
test("header is ignored when the feature flag is off", async () => {
|
|
353
|
+
const client = await connectClient(mcpUrl, "1");
|
|
354
|
+
try {
|
|
355
|
+
assert.strictEqual(await getProjectId(client, "2"), "2");
|
|
356
|
+
}
|
|
357
|
+
finally {
|
|
358
|
+
await client.disconnect();
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
});
|
|
@@ -67,6 +67,7 @@ const MODIFY_SAMPLE_TOOLS = [
|
|
|
67
67
|
"create_branch",
|
|
68
68
|
"push_files",
|
|
69
69
|
"merge_merge_request",
|
|
70
|
+
"stop_stale_environments",
|
|
70
71
|
];
|
|
71
72
|
const READ_SAMPLE_TOOLS = ["list_issues", "get_project", "list_merge_requests"];
|
|
72
73
|
describe("Permission Mode", { concurrency: 1 }, () => {
|
package/build/tools/registry.js
CHANGED
|
@@ -2,7 +2,7 @@ import { zodToJsonSchema } from "zod-to-json-schema";
|
|
|
2
2
|
import { toJSONSchema } from "../utils/schema.js";
|
|
3
3
|
import { USE_GITLAB_WIKI, USE_MILESTONE, USE_PIPELINE, SSE, STREAMABLE_HTTP, } from "../config.js";
|
|
4
4
|
import { getToolDescription } from "./tool-descriptions.js";
|
|
5
|
-
import { ApproveMergeRequestSchema, BulkPublishDraftNotesSchema, CancelPipelineJobSchema, CancelPipelineSchema, ConvertWorkItemTypeSchema, CreateBranchSchema, CreateDraftNoteSchema, CreateGroupSchema, CreateGroupWikiPageSchema, CreateIssueLinkSchema, CreateIssueNoteSchema, CreateIssueSchema, CreateIssueEmojiReactionSchema, CreateIssueNoteEmojiReactionSchema, ListIssueEmojiReactionsSchema, ListIssueNoteEmojiReactionsSchema, CreateLabelSchema, MarkAllTodosDoneSchema, ListTodosSchema, MarkTodoDoneSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestNoteSchema, CreateMergeRequestNoteEmojiReactionSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateCommitStatusSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateGroupMilestoneSchema, CreateReleaseEvidenceSchema, CreateReleaseSchema, CreateRepositorySchema, CreateTagSchema, CreateTimelineEventSchema, CreateWebhookSchema, CreateWikiPageSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, CreateWorkItemSchema, DeleteBranchSchema, GetProtectedBranchSchema, ListProtectedBranchesSchema, ProtectBranchSchema, UnprotectBranchSchema, UpdateDefaultBranchSchema, DeleteDraftNoteSchema, DeleteGroupMilestoneSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteMergeRequestDiscussionNoteSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, DeleteProjectMilestoneSchema, DeleteReleaseSchema, DeleteTagSchema, DeleteWebhookSchema, DeleteWikiPageSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, DownloadAttachmentSchema, DownloadAttachmentRemoteSchema, DownloadJobArtifactsSchema, DownloadJobArtifactsRemoteSchema, DownloadReleaseAssetSchema, EditProjectMilestoneSchema, EditGroupMilestoneSchema, ExecuteGraphQLSchema, ForkRepositorySchema, HealthCheckSchema, GetBranchSchema, GetBranchDiffsSchema, GetCommitDiffSchema, GetCommitSchema, GetFileBlameSchema, GetDeploymentSchema, GetDraftNoteSchema, GetEnvironmentSchema, GetFileContentsSchema, GetGroupWikiPageSchema, GetIssueLinkSchema, GetIssueSchema, GetJobArtifactFileSchema, GetLabelSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GetMergeRequestDiffsSchema, GetMergeRequestFileDiffSchema, GetMergeRequestNoteSchema, GetMergeRequestNotesSchema, GetMergeRequestSchema, GetMergeRequestVersionSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetGroupMilestoneSchema, GetGroupMilestoneIssuesSchema, GetGroupMilestoneMergeRequestsSchema, GetGroupMilestoneBurndownEventsSchema, GetNamespaceSchema, GetPipelineJobOutputSchema, PipelineJobControlSchema, GetPipelineSchema, GetProjectEventsSchema, GetProjectMilestoneSchema, GetProjectSchema, GetReleaseSchema, GetRepositoryTreeSchema, GetTagSchema, GetTagSignatureSchema, GetTimelineEventsSchema, GetUsersSchema, GetUserSchema, WhoAmISchema, GetWebhookEventSchema, GetWikiPageSchema, GetWorkItemSchema, ListBranchesSchema, ListCommitsSchema, ListCommitStatusesSchema, ListCustomFieldDefinitionsSchema, ListDeploymentsSchema, ListDraftNotesSchema, ListEnvironmentsSchema, ListEventsSchema, ListGroupIterationsSchema, ListGroupMilestonesSchema, ListGroupProjectsSchema, ListGroupWikiPagesSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListJobArtifactsSchema, ListLabelsSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiffsSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestPipelinesSchema, ListMergeRequestVersionsSchema, ListMergeRequestsSchema, ListGroupMergeRequestsSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListCiCatalogResourcesSchema, GetCiCatalogResourceSchema, ListPipelinesSchema, ListPipelineSchedulesSchema, GetPipelineScheduleSchema, ListPipelineSchedulePipelinesSchema, CreatePipelineScheduleSchema, UpdatePipelineScheduleSchema, DeletePipelineScheduleSchema, PlayPipelineScheduleSchema, TakeOwnershipPipelineScheduleSchema, GetPipelineScheduleVariableSchema, CreatePipelineScheduleVariableSchema, UpdatePipelineScheduleVariableSchema, DeletePipelineScheduleVariableSchema, ListGroupMembersSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListReleasesSchema, ListTagsSchema, ListWebhookEventsSchema, ListWebhooksSchema, ListWikiPagesSchema, ListWorkItemNotesSchema, ListWorkItemStatusesSchema, ListWorkItemsSchema, MarkdownUploadSchema, MarkdownUploadRemoteSchema, MergeMergeRequestSchema, MoveWorkItemSchema, MyIssuesSchema, PlayPipelineJobSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PushFilesSchema, ResolveMergeRequestThreadSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UnapproveMergeRequestSchema, UpdateDraftNoteSchema, UpdateGroupWikiPageSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateIssueDescriptionPatchSchema, UpdateLabelSchema, UpdateProjectSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestSchema, UpdateReleaseSchema, UpdateWebhookSchema, UpdateWikiPageSchema, UpdateWorkItemSchema, VerifyNamespaceSchema, ListProjectVariablesSchema, GetProjectVariableSchema, CreateProjectVariableSchema, UpdateProjectVariableSchema, DeleteProjectVariableSchema, ListGroupVariablesSchema, GetGroupVariableSchema, CreateGroupVariableSchema, UpdateGroupVariableSchema, DeleteGroupVariableSchema, GetDependencyProxySettingsSchema, UpdateDependencyProxySettingsSchema, ListDependencyProxyBlobsSchema, PurgeDependencyProxyCacheSchema, ListProjectVulnerabilitiesSchema, GetVulnerabilitySchema, DismissVulnerabilitySchema, ConfirmVulnerabilitySchema, } from "../schemas.js";
|
|
5
|
+
import { ApproveMergeRequestSchema, BulkPublishDraftNotesSchema, CancelPipelineJobSchema, ErasePipelineJobSchema, CancelPipelineSchema, ConvertWorkItemTypeSchema, CreateBranchSchema, CreateDraftNoteSchema, CreateGroupSchema, CreateGroupWikiPageSchema, CreateIssueLinkSchema, CreateIssueNoteSchema, CreateIssueSchema, CreateIssueEmojiReactionSchema, CreateIssueNoteEmojiReactionSchema, ListIssueEmojiReactionsSchema, ListIssueNoteEmojiReactionsSchema, CreateLabelSchema, MarkAllTodosDoneSchema, ListTodosSchema, MarkTodoDoneSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestNoteSchema, CreateMergeRequestNoteEmojiReactionSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateCommitStatusSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateGroupMilestoneSchema, CreateReleaseEvidenceSchema, CreateReleaseSchema, CreateRepositorySchema, CreateTagSchema, CreateTimelineEventSchema, CreateWebhookSchema, CreateWikiPageSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, CreateWorkItemSchema, DeleteBranchSchema, GetProtectedBranchSchema, ListProtectedBranchesSchema, ProtectBranchSchema, UnprotectBranchSchema, UpdateDefaultBranchSchema, DeleteDraftNoteSchema, DeleteGroupMilestoneSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteMergeRequestDiscussionNoteSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, DeleteProjectMilestoneSchema, DeleteReleaseSchema, DeleteTagSchema, DeleteWebhookSchema, DeleteWikiPageSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, DownloadAttachmentSchema, DownloadAttachmentRemoteSchema, DownloadJobArtifactsSchema, DownloadJobArtifactsRemoteSchema, DownloadReleaseAssetSchema, EditProjectMilestoneSchema, EditGroupMilestoneSchema, ExecuteGraphQLSchema, ForkRepositorySchema, HealthCheckSchema, GetBranchSchema, GetBranchDiffsSchema, GetCommitDiffSchema, GetCommitSchema, GetFileBlameSchema, GetDeploymentSchema, CreateDeploymentSchema, UpdateDeploymentSchema, DeploymentApprovalSchema, ListDeploymentMergeRequestsSchema, GetDraftNoteSchema, GetEnvironmentSchema, UpdateEnvironmentSchema, StopEnvironmentSchema, StopStaleEnvironmentsSchema, DeleteReviewAppEnvironmentsSchema, PipelineTriggerIdSchema, ListPipelineTriggersSchema, CreatePipelineTriggerSchema, UpdatePipelineTriggerSchema, TriggerPipelineSchema, GetFileContentsSchema, GetGroupWikiPageSchema, GetIssueLinkSchema, GetIssueSchema, GetJobArtifactFileSchema, GetLabelSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GetMergeRequestDiffsSchema, GetMergeRequestFileDiffSchema, GetMergeRequestNoteSchema, GetMergeRequestNotesSchema, GetMergeRequestSchema, GetMergeRequestVersionSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetGroupMilestoneSchema, GetGroupMilestoneIssuesSchema, GetGroupMilestoneMergeRequestsSchema, GetGroupMilestoneBurndownEventsSchema, GetNamespaceSchema, GetPipelineJobOutputSchema, PipelineJobControlSchema, GetPipelineSchema, GetPipelineVariablesSchema, UpdatePipelineMetadataSchema, DeletePipelineSchema, PipelineReportSchema, WaitForPipelineSchema, WaitForPipelineJobSchema, PlayPipelineJobsSchema, GetProjectEventsSchema, GetProjectMilestoneSchema, GetProjectSchema, GetReleaseSchema, GetRepositoryTreeSchema, GetTagSchema, GetTagSignatureSchema, GetTimelineEventsSchema, GetUsersSchema, GetUserSchema, WhoAmISchema, GetWebhookEventSchema, GetWikiPageSchema, GetWorkItemSchema, ListBranchesSchema, ListCommitsSchema, ListCommitStatusesSchema, ListCustomFieldDefinitionsSchema, ListDeploymentsSchema, ListDraftNotesSchema, ListEnvironmentsSchema, ListEventsSchema, ListGroupIterationsSchema, ListGroupMilestonesSchema, ListGroupProjectsSchema, ListGroupWikiPagesSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListJobArtifactsSchema, ListLabelsSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiffsSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestPipelinesSchema, ListMergeRequestVersionsSchema, ListMergeRequestsSchema, ListGroupMergeRequestsSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListCiCatalogResourcesSchema, GetCiCatalogResourceSchema, ListPipelinesSchema, ListPipelineSchedulesSchema, GetPipelineScheduleSchema, ListPipelineSchedulePipelinesSchema, CreatePipelineScheduleSchema, UpdatePipelineScheduleSchema, DeletePipelineScheduleSchema, PlayPipelineScheduleSchema, TakeOwnershipPipelineScheduleSchema, GetPipelineScheduleVariableSchema, CreatePipelineScheduleVariableSchema, UpdatePipelineScheduleVariableSchema, DeletePipelineScheduleVariableSchema, ListGroupMembersSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListReleasesSchema, ListTagsSchema, ListWebhookEventsSchema, ListWebhooksSchema, ListWikiPagesSchema, ListWorkItemNotesSchema, ListWorkItemStatusesSchema, ListWorkItemsSchema, MarkdownUploadSchema, MarkdownUploadRemoteSchema, MergeMergeRequestSchema, MoveWorkItemSchema, MyIssuesSchema, PlayPipelineJobSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PushFilesSchema, ResolveMergeRequestThreadSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UnapproveMergeRequestSchema, UpdateDraftNoteSchema, UpdateGroupWikiPageSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateIssueDescriptionPatchSchema, UpdateLabelSchema, UpdateProjectSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestSchema, UpdateReleaseSchema, UpdateWebhookSchema, UpdateWikiPageSchema, UpdateWorkItemSchema, VerifyNamespaceSchema, ListProjectVariablesSchema, GetProjectVariableSchema, CreateProjectVariableSchema, UpdateProjectVariableSchema, DeleteProjectVariableSchema, ListGroupVariablesSchema, GetGroupVariableSchema, CreateGroupVariableSchema, UpdateGroupVariableSchema, DeleteGroupVariableSchema, GetDependencyProxySettingsSchema, UpdateDependencyProxySettingsSchema, ListDependencyProxyBlobsSchema, PurgeDependencyProxyCacheSchema, ListProjectVulnerabilitiesSchema, GetVulnerabilitySchema, DismissVulnerabilitySchema, ConfirmVulnerabilitySchema, } from "../schemas.js";
|
|
6
6
|
const IS_REMOTE = SSE || STREAMABLE_HTTP;
|
|
7
7
|
// Define all available tools
|
|
8
8
|
export const allTools = [
|
|
@@ -550,6 +550,15 @@ export const allTools = [
|
|
|
550
550
|
description: "Get details of a specific pipeline",
|
|
551
551
|
inputSchema: toJSONSchema(GetPipelineSchema),
|
|
552
552
|
},
|
|
553
|
+
{
|
|
554
|
+
name: "get_pipeline_variables",
|
|
555
|
+
description: "Get variables configured for a pipeline",
|
|
556
|
+
inputSchema: toJSONSchema(GetPipelineVariablesSchema),
|
|
557
|
+
},
|
|
558
|
+
{ name: "get_pipeline_test_report", description: "Get pipeline test report", inputSchema: toJSONSchema(PipelineReportSchema) },
|
|
559
|
+
{ name: "get_pipeline_test_report_summary", description: "Get pipeline test report summary", inputSchema: toJSONSchema(PipelineReportSchema) },
|
|
560
|
+
{ name: "delete_pipeline", description: "Delete a pipeline. Requires the project Owner role, cannot be undone, and does not automatically delete child pipelines.", inputSchema: toJSONSchema(DeletePipelineSchema) },
|
|
561
|
+
{ name: "update_pipeline_metadata", description: "Update pipeline metadata", inputSchema: toJSONSchema(UpdatePipelineMetadataSchema) },
|
|
553
562
|
{
|
|
554
563
|
name: "list_deployments",
|
|
555
564
|
description: "List deployments with filtering options",
|
|
@@ -557,9 +566,14 @@ export const allTools = [
|
|
|
557
566
|
},
|
|
558
567
|
{
|
|
559
568
|
name: "get_deployment",
|
|
560
|
-
description: "Get details
|
|
569
|
+
description: "Get deployment details, including approval_summary, approvals, and pending_approval_count when GitLab provides them",
|
|
561
570
|
inputSchema: toJSONSchema(GetDeploymentSchema),
|
|
562
571
|
},
|
|
572
|
+
{ name: "create_deployment", description: "Create a deployment", inputSchema: toJSONSchema(CreateDeploymentSchema) },
|
|
573
|
+
{ name: "update_deployment", description: "Update a deployment status", inputSchema: toJSONSchema(UpdateDeploymentSchema) },
|
|
574
|
+
{ name: "delete_deployment", description: "Delete a deployment", inputSchema: toJSONSchema(GetDeploymentSchema) },
|
|
575
|
+
{ name: "list_deployment_merge_requests", description: "List merge requests shipped with a deployment", inputSchema: toJSONSchema(ListDeploymentMergeRequestsSchema) },
|
|
576
|
+
{ name: "approve_deployment", description: "Approve or reject a protected-environment deployment", inputSchema: toJSONSchema(DeploymentApprovalSchema) },
|
|
563
577
|
{
|
|
564
578
|
name: "list_environments",
|
|
565
579
|
description: "List environments in a project",
|
|
@@ -570,6 +584,17 @@ export const allTools = [
|
|
|
570
584
|
description: "Get details of a specific environment",
|
|
571
585
|
inputSchema: toJSONSchema(GetEnvironmentSchema),
|
|
572
586
|
},
|
|
587
|
+
{ name: "update_environment", description: "Update an environment", inputSchema: toJSONSchema(UpdateEnvironmentSchema) },
|
|
588
|
+
{ name: "delete_environment", description: "Delete a stopped environment", inputSchema: toJSONSchema(GetEnvironmentSchema) },
|
|
589
|
+
{ name: "stop_environment", description: "Stop an environment", inputSchema: toJSONSchema(StopEnvironmentSchema) },
|
|
590
|
+
{ name: "stop_stale_environments", description: "Stop eligible stale environments; protected environments are excluded and environments are stopped, not deleted", inputSchema: toJSONSchema(StopStaleEnvironmentsSchema) },
|
|
591
|
+
{ name: "delete_review_app_environments", description: "Schedule deletion of stopped review-app environments one week later; dry_run defaults to true and actual scheduling requires dry_run=false", inputSchema: toJSONSchema(DeleteReviewAppEnvironmentsSchema) },
|
|
592
|
+
{ name: "list_pipeline_triggers", description: "List project pipeline trigger tokens", inputSchema: toJSONSchema(ListPipelineTriggersSchema) },
|
|
593
|
+
{ name: "get_pipeline_trigger", description: "Get a project pipeline trigger", inputSchema: toJSONSchema(PipelineTriggerIdSchema) },
|
|
594
|
+
{ name: "create_pipeline_trigger", description: "Create a project pipeline trigger", inputSchema: toJSONSchema(CreatePipelineTriggerSchema) },
|
|
595
|
+
{ name: "update_pipeline_trigger", description: "Update a project pipeline trigger", inputSchema: toJSONSchema(UpdatePipelineTriggerSchema) },
|
|
596
|
+
{ name: "delete_pipeline_trigger", description: "Delete a project pipeline trigger", inputSchema: toJSONSchema(PipelineTriggerIdSchema) },
|
|
597
|
+
{ name: "trigger_pipeline", description: "Trigger a pipeline with a pipeline trigger token", inputSchema: toJSONSchema(TriggerPipelineSchema) },
|
|
573
598
|
{
|
|
574
599
|
name: "list_pipeline_jobs",
|
|
575
600
|
description: "List all jobs in a specific pipeline",
|
|
@@ -690,6 +715,11 @@ export const allTools = [
|
|
|
690
715
|
description: "Run a manual pipeline job",
|
|
691
716
|
inputSchema: toJSONSchema(PlayPipelineJobSchema),
|
|
692
717
|
},
|
|
718
|
+
{
|
|
719
|
+
name: "play_pipeline_jobs",
|
|
720
|
+
description: "Play multiple manual pipeline jobs sequentially",
|
|
721
|
+
inputSchema: toJSONSchema(PlayPipelineJobsSchema),
|
|
722
|
+
},
|
|
693
723
|
{
|
|
694
724
|
name: "retry_pipeline_job",
|
|
695
725
|
description: "Retry a failed or canceled pipeline job",
|
|
@@ -700,6 +730,9 @@ export const allTools = [
|
|
|
700
730
|
description: "Cancel a running pipeline job",
|
|
701
731
|
inputSchema: toJSONSchema(CancelPipelineJobSchema),
|
|
702
732
|
},
|
|
733
|
+
{ name: "erase_pipeline_job", description: "Erase a pipeline job log and artifacts", inputSchema: toJSONSchema(ErasePipelineJobSchema) },
|
|
734
|
+
{ name: "wait_for_pipeline", description: "Wait for a pipeline to reach a terminal status", inputSchema: toJSONSchema(WaitForPipelineSchema) },
|
|
735
|
+
{ name: "wait_for_job", description: "Wait for a job to reach a terminal status", inputSchema: toJSONSchema(WaitForPipelineJobSchema) },
|
|
703
736
|
{
|
|
704
737
|
name: "list_job_artifacts",
|
|
705
738
|
description: "List artifact files in a job's archive",
|
|
@@ -1250,6 +1283,9 @@ export const readOnlyTools = new Set([
|
|
|
1250
1283
|
"list_project_members",
|
|
1251
1284
|
"list_group_members",
|
|
1252
1285
|
"get_pipeline",
|
|
1286
|
+
"get_pipeline_variables",
|
|
1287
|
+
"get_pipeline_test_report",
|
|
1288
|
+
"get_pipeline_test_report_summary",
|
|
1253
1289
|
"list_pipelines",
|
|
1254
1290
|
"list_pipeline_schedules",
|
|
1255
1291
|
"get_pipeline_schedule",
|
|
@@ -1257,12 +1293,17 @@ export const readOnlyTools = new Set([
|
|
|
1257
1293
|
"get_pipeline_schedule_variable",
|
|
1258
1294
|
"list_deployments",
|
|
1259
1295
|
"get_deployment",
|
|
1296
|
+
"list_deployment_merge_requests",
|
|
1260
1297
|
"list_environments",
|
|
1261
1298
|
"get_environment",
|
|
1299
|
+
"list_pipeline_triggers",
|
|
1300
|
+
"get_pipeline_trigger",
|
|
1262
1301
|
"list_pipeline_jobs",
|
|
1263
1302
|
"list_pipeline_trigger_jobs",
|
|
1264
1303
|
"get_pipeline_job",
|
|
1265
1304
|
"get_pipeline_job_output",
|
|
1305
|
+
"wait_for_pipeline",
|
|
1306
|
+
"wait_for_job",
|
|
1266
1307
|
"validate_ci_lint",
|
|
1267
1308
|
"validate_project_ci_lint",
|
|
1268
1309
|
"list_ci_catalog_resources",
|
|
@@ -1335,6 +1376,13 @@ export const readOnlyTools = new Set([
|
|
|
1335
1376
|
]);
|
|
1336
1377
|
// Define which tools are destructive (data loss potential)
|
|
1337
1378
|
export const destructiveTools = new Set([
|
|
1379
|
+
"delete_pipeline",
|
|
1380
|
+
"erase_pipeline_job",
|
|
1381
|
+
"delete_deployment",
|
|
1382
|
+
"approve_deployment",
|
|
1383
|
+
"delete_environment",
|
|
1384
|
+
"delete_review_app_environments",
|
|
1385
|
+
"delete_pipeline_trigger",
|
|
1338
1386
|
"delete_issue",
|
|
1339
1387
|
"delete_issue_link",
|
|
1340
1388
|
"delete_label",
|
|
@@ -1369,6 +1417,12 @@ export const destructiveTools = new Set([
|
|
|
1369
1417
|
// Tools that permanently delete resources — blocked in "modify" permission mode.
|
|
1370
1418
|
// Narrower than destructiveTools: merge/protect/push are modifications, not deletions.
|
|
1371
1419
|
export const deleteTools = new Set([
|
|
1420
|
+
"delete_pipeline",
|
|
1421
|
+
"erase_pipeline_job",
|
|
1422
|
+
"delete_deployment",
|
|
1423
|
+
"delete_environment",
|
|
1424
|
+
"delete_review_app_environments",
|
|
1425
|
+
"delete_pipeline_trigger",
|
|
1372
1426
|
"delete_branch",
|
|
1373
1427
|
"delete_draft_note",
|
|
1374
1428
|
"delete_group_variable",
|
|
@@ -1433,14 +1487,39 @@ export const milestoneToolNames = new Set([
|
|
|
1433
1487
|
export const pipelineToolNames = new Set([
|
|
1434
1488
|
"list_pipelines",
|
|
1435
1489
|
"get_pipeline",
|
|
1490
|
+
"get_pipeline_variables",
|
|
1491
|
+
"get_pipeline_test_report",
|
|
1492
|
+
"get_pipeline_test_report_summary",
|
|
1493
|
+
"delete_pipeline",
|
|
1494
|
+
"update_pipeline_metadata",
|
|
1436
1495
|
"list_deployments",
|
|
1437
1496
|
"get_deployment",
|
|
1497
|
+
"create_deployment",
|
|
1498
|
+
"update_deployment",
|
|
1499
|
+
"delete_deployment",
|
|
1500
|
+
"list_deployment_merge_requests",
|
|
1501
|
+
"approve_deployment",
|
|
1438
1502
|
"list_environments",
|
|
1439
1503
|
"get_environment",
|
|
1504
|
+
"update_environment",
|
|
1505
|
+
"delete_environment",
|
|
1506
|
+
"stop_environment",
|
|
1507
|
+
"stop_stale_environments",
|
|
1508
|
+
"delete_review_app_environments",
|
|
1509
|
+
"list_pipeline_triggers",
|
|
1510
|
+
"get_pipeline_trigger",
|
|
1511
|
+
"create_pipeline_trigger",
|
|
1512
|
+
"update_pipeline_trigger",
|
|
1513
|
+
"delete_pipeline_trigger",
|
|
1514
|
+
"trigger_pipeline",
|
|
1440
1515
|
"list_pipeline_jobs",
|
|
1441
1516
|
"list_pipeline_trigger_jobs",
|
|
1442
1517
|
"get_pipeline_job",
|
|
1443
1518
|
"get_pipeline_job_output",
|
|
1519
|
+
"play_pipeline_jobs",
|
|
1520
|
+
"erase_pipeline_job",
|
|
1521
|
+
"wait_for_pipeline",
|
|
1522
|
+
"wait_for_job",
|
|
1444
1523
|
"validate_ci_lint",
|
|
1445
1524
|
"validate_project_ci_lint",
|
|
1446
1525
|
"create_pipeline",
|
|
@@ -1629,10 +1708,31 @@ export const TOOLSET_DEFINITIONS = [
|
|
|
1629
1708
|
tools: new Set([
|
|
1630
1709
|
"list_pipelines",
|
|
1631
1710
|
"get_pipeline",
|
|
1711
|
+
"get_pipeline_variables",
|
|
1712
|
+
"get_pipeline_test_report",
|
|
1713
|
+
"get_pipeline_test_report_summary",
|
|
1714
|
+
"delete_pipeline",
|
|
1715
|
+
"update_pipeline_metadata",
|
|
1632
1716
|
"list_deployments",
|
|
1633
1717
|
"get_deployment",
|
|
1718
|
+
"create_deployment",
|
|
1719
|
+
"update_deployment",
|
|
1720
|
+
"delete_deployment",
|
|
1721
|
+
"list_deployment_merge_requests",
|
|
1722
|
+
"approve_deployment",
|
|
1634
1723
|
"list_environments",
|
|
1635
1724
|
"get_environment",
|
|
1725
|
+
"update_environment",
|
|
1726
|
+
"delete_environment",
|
|
1727
|
+
"stop_environment",
|
|
1728
|
+
"stop_stale_environments",
|
|
1729
|
+
"delete_review_app_environments",
|
|
1730
|
+
"list_pipeline_triggers",
|
|
1731
|
+
"get_pipeline_trigger",
|
|
1732
|
+
"create_pipeline_trigger",
|
|
1733
|
+
"update_pipeline_trigger",
|
|
1734
|
+
"delete_pipeline_trigger",
|
|
1735
|
+
"trigger_pipeline",
|
|
1636
1736
|
"list_pipeline_jobs",
|
|
1637
1737
|
"list_pipeline_trigger_jobs",
|
|
1638
1738
|
"get_pipeline_job",
|
|
@@ -1653,8 +1753,12 @@ export const TOOLSET_DEFINITIONS = [
|
|
|
1653
1753
|
"update_pipeline_schedule_variable",
|
|
1654
1754
|
"delete_pipeline_schedule_variable",
|
|
1655
1755
|
"play_pipeline_job",
|
|
1756
|
+
"play_pipeline_jobs",
|
|
1656
1757
|
"retry_pipeline_job",
|
|
1657
1758
|
"cancel_pipeline_job",
|
|
1759
|
+
"erase_pipeline_job",
|
|
1760
|
+
"wait_for_pipeline",
|
|
1761
|
+
"wait_for_job",
|
|
1658
1762
|
"list_job_artifacts",
|
|
1659
1763
|
"download_job_artifacts",
|
|
1660
1764
|
"get_job_artifact_file",
|
|
@@ -46,6 +46,8 @@ const TOOL_GUIDANCE = {
|
|
|
46
46
|
create_pipeline_schedule_variable: "Use this to attach a new variable to a pipeline schedule so its scheduled pipelines receive it; use `update_pipeline_schedule_variable` when the key already exists and `create_project_variable` for a project-wide variable. `variable_type` defaults to `env_var` and `key` is limited to 255 characters matching /[a-zA-Z0-9_]+/. It creates remote CI configuration, requires the Developer, Maintainer, or Owner role and ownership of the schedule, and returns the new variable or a validation, duplicate-key, or permission error.",
|
|
47
47
|
update_pipeline_schedule_variable: "Use this to change the value or type of an existing pipeline schedule variable; use `create_pipeline_schedule_variable` when the key does not exist yet. It changes remote CI configuration that scheduled pipelines consume, requires the Developer, Maintainer, or Owner role and ownership of the schedule, and returns the updated variable or a missing-key, validation, or permission error.",
|
|
48
48
|
delete_pipeline_schedule_variable: "Use this only after confirming the key with `get_pipeline_schedule` or `get_pipeline_schedule_variable`; use `update_pipeline_schedule_variable` to change a value without removing it. The operation permanently removes the variable from the schedule so later scheduled pipelines no longer receive it, requires the Developer, Maintainer, or Owner role and ownership of the schedule, and returns a confirmation or a missing-key or permission error.",
|
|
49
|
+
stop_stale_environments: "Use this to stop eligible stale environments in a project. GitLab excludes protected environments, and this operation stops environments without deleting them. It requires the necessary project permission and returns the result or a validation, permission, or rate-limit error.",
|
|
50
|
+
delete_review_app_environments: "Use this to clean up stopped review-app environments after verifying the scope. GitLab schedules deletion one week later rather than deleting environments immediately; `dry_run` defaults to `true`, so actual scheduling requires `dry_run: false`. It requires the necessary project permission and returns the cleanup result or a validation, permission, or rate-limit error.",
|
|
49
51
|
};
|
|
50
52
|
const PARAMETER_GUIDANCE = "When `project_id` or `group_id` is accepted, provide the numeric ID or complete URL-encoded path described by the schema; use required identifiers and pagination fields exactly as documented.";
|
|
51
53
|
function ensureSentence(text) {
|
|
@@ -3,16 +3,23 @@
|
|
|
3
3
|
* MCP tool results become part of the AI conversation context, so these must
|
|
4
4
|
* never be forwarded to the model.
|
|
5
5
|
*/
|
|
6
|
-
const SENSITIVE_GITLAB_FIELDS = ["runners_token", "import_url"];
|
|
6
|
+
const SENSITIVE_GITLAB_FIELDS = ["runners_token", "import_url", "token"];
|
|
7
7
|
/**
|
|
8
8
|
* Strip known sensitive fields from a GitLab API response object before it is
|
|
9
9
|
* returned as an MCP tool result. Mutates and returns the same object.
|
|
10
|
+
* Recurses into arrays (e.g. list responses) since those hold one object per item.
|
|
10
11
|
*
|
|
11
|
-
* Used for raw passthrough responses (get_project / update_project
|
|
12
|
-
* go through a Zod schema, since Zod
|
|
12
|
+
* Used for raw passthrough responses (get_project / update_project,
|
|
13
|
+
* list/get_pipeline_trigger) that do not go through a Zod schema, since Zod
|
|
14
|
+
* would otherwise strip unknown keys for us. Do NOT apply to
|
|
15
|
+
* create_pipeline_trigger: its response carries the newly issued token once.
|
|
13
16
|
*/
|
|
14
17
|
export function redactSensitiveGitLabFields(data) {
|
|
15
|
-
if (data
|
|
18
|
+
if (Array.isArray(data)) {
|
|
19
|
+
for (const item of data)
|
|
20
|
+
redactSensitiveGitLabFields(item);
|
|
21
|
+
}
|
|
22
|
+
else if (data && typeof data === "object") {
|
|
16
23
|
for (const key of SENSITIVE_GITLAB_FIELDS) {
|
|
17
24
|
delete data[key];
|
|
18
25
|
}
|