@zereight/mcp-gitlab 2.1.5 → 2.1.7
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 +442 -0
- package/README.md +43 -26
- package/README.zh-CN.md +442 -0
- package/build/config.js +65 -2
- package/build/index.js +477 -6
- package/build/oauth-proxy.js +182 -47
- package/build/schemas.js +78 -0
- package/build/stateless/client-id.js +68 -0
- package/build/stateless/codec.js +205 -0
- package/build/stateless/errors.js +24 -0
- package/build/stateless/index.js +14 -0
- package/build/stateless/pending-auth.js +65 -0
- package/build/stateless/secret.js +98 -0
- package/build/stateless/session-id.js +68 -0
- package/build/stateless/stored-tokens.js +66 -0
- package/build/stateless/types.js +18 -0
- package/build/test/stateless/callback-proxy.test.js +393 -0
- package/build/test/stateless/client-id.test.js +176 -0
- package/build/test/stateless/codec.test.js +328 -0
- package/build/test/stateless/config-ttl.test.js +149 -0
- package/build/test/stateless/session-id-integration.test.js +675 -0
- package/build/test/stateless/session-id.test.js +131 -0
- package/build/test/test-tags.js +206 -0
- package/build/test/test-toolset-filtering.js +12 -1
- package/build/tools/registry.js +41 -1
- package/package.json +3 -2
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the sealed Mcp-Session-Id helpers.
|
|
3
|
+
*/
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
6
|
+
import { describe, test } from "node:test";
|
|
7
|
+
import { loadKeyMaterialFromEnv, looksLikeStatelessSessionId, mintSessionId, openSessionId, } from "../../stateless/index.js";
|
|
8
|
+
function secret() {
|
|
9
|
+
return randomBytes(32).toString("base64url");
|
|
10
|
+
}
|
|
11
|
+
function load(current, previous) {
|
|
12
|
+
const env = {
|
|
13
|
+
OAUTH_STATELESS_SECRET: current,
|
|
14
|
+
};
|
|
15
|
+
if (previous)
|
|
16
|
+
env.OAUTH_STATELESS_SECRET_PREVIOUS = previous;
|
|
17
|
+
const m = loadKeyMaterialFromEnv(true, env);
|
|
18
|
+
assert.ok(m);
|
|
19
|
+
return m;
|
|
20
|
+
}
|
|
21
|
+
describe("mintSessionId / openSessionId", () => {
|
|
22
|
+
test("roundtrips across pods", () => {
|
|
23
|
+
const s = secret();
|
|
24
|
+
const a = load(s);
|
|
25
|
+
const b = load(s);
|
|
26
|
+
const sid = mintSessionId(a, {
|
|
27
|
+
header: "Authorization",
|
|
28
|
+
token: "glpat-ABCDEFG123456789-abcdef",
|
|
29
|
+
apiUrl: "https://gitlab.example.com/api/v4",
|
|
30
|
+
});
|
|
31
|
+
assert.ok(looksLikeStatelessSessionId(sid));
|
|
32
|
+
const opened = openSessionId(b, sid, 3600);
|
|
33
|
+
assert.ok(opened);
|
|
34
|
+
assert.equal(opened.h, "Authorization");
|
|
35
|
+
assert.equal(opened.t, "glpat-ABCDEFG123456789-abcdef");
|
|
36
|
+
assert.equal(opened.u, "https://gitlab.example.com/api/v4");
|
|
37
|
+
});
|
|
38
|
+
test("preserves Private-Token / JOB-TOKEN headers", () => {
|
|
39
|
+
const m = load(secret());
|
|
40
|
+
for (const h of ["Private-Token", "JOB-TOKEN"]) {
|
|
41
|
+
const sid = mintSessionId(m, {
|
|
42
|
+
header: h,
|
|
43
|
+
token: "some-token-value-at-least-20-chars",
|
|
44
|
+
apiUrl: "https://gitlab.example.com/api/v4",
|
|
45
|
+
});
|
|
46
|
+
const opened = openSessionId(m, sid, 3600);
|
|
47
|
+
assert.ok(opened);
|
|
48
|
+
assert.equal(opened.h, h);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
test("rejects unknown header values", () => {
|
|
52
|
+
// Hand-craft a payload with a bogus header value. We do this by minting a
|
|
53
|
+
// legitimate token then mutating the decoded payload via a second mint
|
|
54
|
+
// won't work (AEAD). Instead, assert the helper rejects garbage input
|
|
55
|
+
// by tampering with an unrelated value and checking it still fails safely.
|
|
56
|
+
const m = load(secret());
|
|
57
|
+
const sid = mintSessionId(m, {
|
|
58
|
+
header: "Authorization",
|
|
59
|
+
token: "t".repeat(25),
|
|
60
|
+
apiUrl: "u",
|
|
61
|
+
});
|
|
62
|
+
// Truncate to force bad ciphertext on open.
|
|
63
|
+
const broken = sid.slice(0, -3);
|
|
64
|
+
assert.equal(openSessionId(m, broken, 3600), null);
|
|
65
|
+
});
|
|
66
|
+
test("expired sid rejected", () => {
|
|
67
|
+
const m = load(secret());
|
|
68
|
+
const past = Math.floor(Date.now() / 1000) - 10_000;
|
|
69
|
+
const sid = mintSessionId(m, {
|
|
70
|
+
header: "Authorization",
|
|
71
|
+
token: "t".repeat(25),
|
|
72
|
+
apiUrl: "u",
|
|
73
|
+
now: () => past,
|
|
74
|
+
});
|
|
75
|
+
assert.equal(openSessionId(m, sid, 60), null);
|
|
76
|
+
});
|
|
77
|
+
test("different secret rejects", () => {
|
|
78
|
+
const a = load(secret());
|
|
79
|
+
const b = load(secret());
|
|
80
|
+
const sid = mintSessionId(a, {
|
|
81
|
+
header: "Authorization",
|
|
82
|
+
token: "t".repeat(25),
|
|
83
|
+
apiUrl: "u",
|
|
84
|
+
});
|
|
85
|
+
assert.equal(openSessionId(b, sid, 3600), null);
|
|
86
|
+
});
|
|
87
|
+
test("rotation: sid minted under previous secret opens on rotated pod", () => {
|
|
88
|
+
const s1 = secret();
|
|
89
|
+
const s2 = secret();
|
|
90
|
+
const old = load(s1);
|
|
91
|
+
const rotated = load(s2, s1);
|
|
92
|
+
const sid = mintSessionId(old, {
|
|
93
|
+
header: "Authorization",
|
|
94
|
+
token: "t".repeat(25),
|
|
95
|
+
apiUrl: "u",
|
|
96
|
+
});
|
|
97
|
+
const opened = openSessionId(rotated, sid, 3600);
|
|
98
|
+
assert.ok(opened);
|
|
99
|
+
});
|
|
100
|
+
test("looksLikeStatelessSessionId distinguishes legacy UUIDs", () => {
|
|
101
|
+
const m = load(secret());
|
|
102
|
+
const sid = mintSessionId(m, {
|
|
103
|
+
header: "Authorization",
|
|
104
|
+
token: "t".repeat(25),
|
|
105
|
+
apiUrl: "u",
|
|
106
|
+
});
|
|
107
|
+
assert.ok(looksLikeStatelessSessionId(sid));
|
|
108
|
+
assert.ok(!looksLikeStatelessSessionId("a4f1c2b8-f2c4-4ee6-bc14-2b7ab0e6ab11"));
|
|
109
|
+
assert.ok(!looksLikeStatelessSessionId(""));
|
|
110
|
+
});
|
|
111
|
+
test("fresh sid has different iat each time (rotation on every refresh)", () => {
|
|
112
|
+
const m = load(secret());
|
|
113
|
+
const sid1 = mintSessionId(m, {
|
|
114
|
+
header: "Authorization",
|
|
115
|
+
token: "t".repeat(25),
|
|
116
|
+
apiUrl: "u",
|
|
117
|
+
now: () => 1000,
|
|
118
|
+
});
|
|
119
|
+
const sid2 = mintSessionId(m, {
|
|
120
|
+
header: "Authorization",
|
|
121
|
+
token: "t".repeat(25),
|
|
122
|
+
apiUrl: "u",
|
|
123
|
+
now: () => 2000,
|
|
124
|
+
});
|
|
125
|
+
assert.notEqual(sid1, sid2);
|
|
126
|
+
const open1 = openSessionId(m, sid1, 3600, () => 2000);
|
|
127
|
+
const open2 = openSessionId(m, sid2, 3600, () => 2000);
|
|
128
|
+
assert.equal(open1.iat, 1000);
|
|
129
|
+
assert.equal(open2.iat, 2000);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { after, before, describe, test } from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import { MockGitLabServer, findMockServerPort } from "./utils/mock-gitlab-server.js";
|
|
5
|
+
const MOCK_TOKEN = "glpat-mock-token-tags";
|
|
6
|
+
const TEST_PROJECT_ID = "123";
|
|
7
|
+
const TEST_TAG_NAME = "v1.0.0";
|
|
8
|
+
function buildTag(overrides = {}) {
|
|
9
|
+
return {
|
|
10
|
+
name: TEST_TAG_NAME,
|
|
11
|
+
message: "Annotated release tag",
|
|
12
|
+
target: "abc123def4567890",
|
|
13
|
+
commit: {
|
|
14
|
+
id: "abc123def4567890",
|
|
15
|
+
short_id: "abc123de",
|
|
16
|
+
title: "Release v1.0.0",
|
|
17
|
+
created_at: "2026-03-13T10:00:00.000Z",
|
|
18
|
+
parent_ids: ["1111111111111111"],
|
|
19
|
+
message: "Release v1.0.0",
|
|
20
|
+
author_name: "Test User",
|
|
21
|
+
author_email: "test@example.com",
|
|
22
|
+
authored_date: "2026-03-13T09:55:00.000Z",
|
|
23
|
+
committer_name: "Test User",
|
|
24
|
+
committer_email: "test@example.com",
|
|
25
|
+
committed_date: "2026-03-13T10:00:00.000Z",
|
|
26
|
+
},
|
|
27
|
+
release: {
|
|
28
|
+
tag_name: TEST_TAG_NAME,
|
|
29
|
+
description: "Release notes",
|
|
30
|
+
},
|
|
31
|
+
protected: false,
|
|
32
|
+
created_at: "2026-03-13T10:00:00.000Z",
|
|
33
|
+
...overrides,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
async function callTool(toolName, args, env) {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
const proc = spawn("node", ["build/index.js"], {
|
|
39
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
40
|
+
env: {
|
|
41
|
+
...process.env,
|
|
42
|
+
...env,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
let output = "";
|
|
46
|
+
let errorOutput = "";
|
|
47
|
+
proc.stdout?.on("data", (d) => (output += d));
|
|
48
|
+
proc.stderr?.on("data", (d) => (errorOutput += d));
|
|
49
|
+
proc.on("close", code => {
|
|
50
|
+
if (code !== 0) {
|
|
51
|
+
return reject(new Error(`Process exited with code ${code}: ${errorOutput}`));
|
|
52
|
+
}
|
|
53
|
+
const line = output.split("\n").find(l => l.startsWith("{"));
|
|
54
|
+
if (!line) {
|
|
55
|
+
return reject(new Error("No JSON output found"));
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const response = JSON.parse(line);
|
|
59
|
+
if (response.error) {
|
|
60
|
+
reject(response.error);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const content = response.result?.content?.[0]?.text;
|
|
64
|
+
if (!content) {
|
|
65
|
+
resolve(response.result);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
resolve(JSON.parse(content));
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
resolve(content);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
reject(error);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
proc.stdin?.end(JSON.stringify({
|
|
80
|
+
jsonrpc: "2.0",
|
|
81
|
+
id: 1,
|
|
82
|
+
method: "tools/call",
|
|
83
|
+
params: { name: toolName, arguments: args },
|
|
84
|
+
}) + "\n");
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
describe("tag tools", () => {
|
|
88
|
+
let mockGitLab;
|
|
89
|
+
let mockGitLabUrl;
|
|
90
|
+
before(async () => {
|
|
91
|
+
const mockPort = await findMockServerPort(20000, 50);
|
|
92
|
+
mockGitLab = new MockGitLabServer({
|
|
93
|
+
port: mockPort,
|
|
94
|
+
validTokens: [MOCK_TOKEN],
|
|
95
|
+
});
|
|
96
|
+
mockGitLab.addMockHandler("get", `/projects/${TEST_PROJECT_ID}/repository/tags`, (req, res) => {
|
|
97
|
+
assert.strictEqual(req.query.search, "^v");
|
|
98
|
+
assert.strictEqual(req.query.order_by, "version");
|
|
99
|
+
assert.strictEqual(req.query.sort, "desc");
|
|
100
|
+
assert.strictEqual(req.query.page, "2");
|
|
101
|
+
assert.strictEqual(req.query.per_page, "10");
|
|
102
|
+
res.json([buildTag()]);
|
|
103
|
+
});
|
|
104
|
+
mockGitLab.addMockHandler("get", `/projects/${TEST_PROJECT_ID}/repository/tags/${encodeURIComponent(TEST_TAG_NAME)}`, (_req, res) => {
|
|
105
|
+
res.json(buildTag());
|
|
106
|
+
});
|
|
107
|
+
mockGitLab.addMockHandler("post", `/projects/${TEST_PROJECT_ID}/repository/tags`, (req, res) => {
|
|
108
|
+
assert.deepStrictEqual(req.body, {
|
|
109
|
+
tag_name: TEST_TAG_NAME,
|
|
110
|
+
ref: "main",
|
|
111
|
+
message: "Release tag",
|
|
112
|
+
});
|
|
113
|
+
res.json(buildTag({ created_at: null }));
|
|
114
|
+
});
|
|
115
|
+
mockGitLab.addMockHandler("delete", `/projects/${TEST_PROJECT_ID}/repository/tags/${encodeURIComponent(TEST_TAG_NAME)}`, (_req, res) => {
|
|
116
|
+
res.status(204).send();
|
|
117
|
+
});
|
|
118
|
+
mockGitLab.addMockHandler("get", `/projects/${TEST_PROJECT_ID}/repository/tags/${encodeURIComponent(TEST_TAG_NAME)}/signature`, (_req, res) => {
|
|
119
|
+
res.json({
|
|
120
|
+
signature_type: "X509",
|
|
121
|
+
verification_status: "unverified",
|
|
122
|
+
x509_certificate: {
|
|
123
|
+
id: 1,
|
|
124
|
+
subject: "CN=Test User,O=Example",
|
|
125
|
+
subject_key_identifier: "A1725E379E7B25B2",
|
|
126
|
+
email: null,
|
|
127
|
+
serial_number: 123456789,
|
|
128
|
+
certificate_status: "good",
|
|
129
|
+
x509_issuer: {
|
|
130
|
+
id: 1,
|
|
131
|
+
subject: "CN=Example CA,O=Example",
|
|
132
|
+
subject_key_identifier: "C6411E6F5B9E2CFD",
|
|
133
|
+
crl_url: null,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
await mockGitLab.start();
|
|
139
|
+
mockGitLabUrl = mockGitLab.getUrl();
|
|
140
|
+
});
|
|
141
|
+
after(async () => {
|
|
142
|
+
await mockGitLab.stop();
|
|
143
|
+
});
|
|
144
|
+
const env = () => ({
|
|
145
|
+
GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
|
|
146
|
+
GITLAB_PERSONAL_ACCESS_TOKEN: MOCK_TOKEN,
|
|
147
|
+
GITLAB_TOOLSETS: "tags",
|
|
148
|
+
});
|
|
149
|
+
test("list_tags returns parsed tags with query filters", async () => {
|
|
150
|
+
const result = await callTool("list_tags", {
|
|
151
|
+
project_id: TEST_PROJECT_ID,
|
|
152
|
+
search: "^v",
|
|
153
|
+
order_by: "version",
|
|
154
|
+
sort: "desc",
|
|
155
|
+
page: 2,
|
|
156
|
+
per_page: 10,
|
|
157
|
+
}, env());
|
|
158
|
+
assert.ok(Array.isArray(result));
|
|
159
|
+
assert.strictEqual(result.length, 1);
|
|
160
|
+
assert.strictEqual(result[0].name, TEST_TAG_NAME);
|
|
161
|
+
});
|
|
162
|
+
test("get_tag returns a single tag", async () => {
|
|
163
|
+
const result = await callTool("get_tag", { project_id: TEST_PROJECT_ID, tag_name: TEST_TAG_NAME }, env());
|
|
164
|
+
assert.strictEqual(result.name, TEST_TAG_NAME);
|
|
165
|
+
assert.strictEqual(result.commit.short_id, "abc123de");
|
|
166
|
+
});
|
|
167
|
+
test("create_tag posts the expected payload", async () => {
|
|
168
|
+
const result = await callTool("create_tag", {
|
|
169
|
+
project_id: TEST_PROJECT_ID,
|
|
170
|
+
tag_name: TEST_TAG_NAME,
|
|
171
|
+
ref: "main",
|
|
172
|
+
message: "Release tag",
|
|
173
|
+
}, env());
|
|
174
|
+
assert.strictEqual(result.name, TEST_TAG_NAME);
|
|
175
|
+
assert.strictEqual(result.release.description, "Release notes");
|
|
176
|
+
assert.strictEqual(result.created_at, null);
|
|
177
|
+
});
|
|
178
|
+
test("delete_tag returns a success payload", async () => {
|
|
179
|
+
const result = await callTool("delete_tag", { project_id: TEST_PROJECT_ID, tag_name: TEST_TAG_NAME }, env());
|
|
180
|
+
assert.deepStrictEqual(result, {
|
|
181
|
+
status: "success",
|
|
182
|
+
message: `Tag '${TEST_TAG_NAME}' deleted successfully`,
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
test("get_tag_signature returns signature data", async () => {
|
|
186
|
+
const result = await callTool("get_tag_signature", { project_id: TEST_PROJECT_ID, tag_name: TEST_TAG_NAME }, env());
|
|
187
|
+
assert.deepStrictEqual(result, {
|
|
188
|
+
signature_type: "X509",
|
|
189
|
+
verification_status: "unverified",
|
|
190
|
+
x509_certificate: {
|
|
191
|
+
id: 1,
|
|
192
|
+
subject: "CN=Test User,O=Example",
|
|
193
|
+
subject_key_identifier: "A1725E379E7B25B2",
|
|
194
|
+
email: null,
|
|
195
|
+
serial_number: 123456789,
|
|
196
|
+
certificate_status: "good",
|
|
197
|
+
x509_issuer: {
|
|
198
|
+
id: 1,
|
|
199
|
+
subject: "CN=Example CA,O=Example",
|
|
200
|
+
subject_key_identifier: "C6411E6F5B9E2CFD",
|
|
201
|
+
crl_url: null,
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
});
|
|
@@ -26,6 +26,7 @@ const TOOLSET_TOOL_COUNTS = {
|
|
|
26
26
|
milestones: 9,
|
|
27
27
|
wiki: 10,
|
|
28
28
|
releases: 7,
|
|
29
|
+
tags: 5,
|
|
29
30
|
users: 5,
|
|
30
31
|
search: 3,
|
|
31
32
|
workitems: 18,
|
|
@@ -40,7 +41,16 @@ const DEFAULT_TOOLSETS = [
|
|
|
40
41
|
"labels",
|
|
41
42
|
"users",
|
|
42
43
|
];
|
|
43
|
-
const NON_DEFAULT_TOOLSETS = [
|
|
44
|
+
const NON_DEFAULT_TOOLSETS = [
|
|
45
|
+
"pipelines",
|
|
46
|
+
"milestones",
|
|
47
|
+
"wiki",
|
|
48
|
+
"releases",
|
|
49
|
+
"tags",
|
|
50
|
+
"workitems",
|
|
51
|
+
"webhooks",
|
|
52
|
+
"search",
|
|
53
|
+
];
|
|
44
54
|
// discover_tools meta-tool is always force-injected (Step 5.5)
|
|
45
55
|
const DISCOVER_TOOLS_COUNT = 1;
|
|
46
56
|
const DEFAULT_TOOL_COUNT = DEFAULT_TOOLSETS.reduce((sum, id) => sum + TOOLSET_TOOL_COUNTS[id], 0) + DISCOVER_TOOLS_COUNT;
|
|
@@ -57,6 +67,7 @@ const TOOLSET_SAMPLE_TOOLS = {
|
|
|
57
67
|
milestones: ["list_milestones", "create_milestone", "get_milestone_burndown_events"],
|
|
58
68
|
wiki: ["list_wiki_pages", "create_wiki_page", "list_group_wiki_pages", "create_group_wiki_page"],
|
|
59
69
|
releases: ["list_releases", "create_release", "download_release_asset"],
|
|
70
|
+
tags: ["list_tags", "create_tag", "get_tag_signature"],
|
|
60
71
|
users: ["get_users", "upload_markdown", "download_attachment"],
|
|
61
72
|
search: ["search_code", "search_project_code", "search_group_code"],
|
|
62
73
|
webhooks: ["list_webhooks", "list_webhook_events", "get_webhook_event"],
|
package/build/tools/registry.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
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, } from "../config.js";
|
|
4
|
-
import { ApproveMergeRequestSchema, BulkPublishDraftNotesSchema, CancelPipelineJobSchema, CancelPipelineSchema, ConvertWorkItemTypeSchema, CreateBranchSchema, CreateDraftNoteSchema, CreateGroupWikiPageSchema, CreateIssueLinkSchema, CreateIssueNoteSchema, CreateIssueSchema, CreateIssueEmojiReactionSchema, CreateIssueNoteEmojiReactionSchema, ListIssueEmojiReactionsSchema, ListIssueNoteEmojiReactionsSchema, CreateLabelSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestNoteSchema, CreateMergeRequestNoteEmojiReactionSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateReleaseEvidenceSchema, CreateReleaseSchema, CreateRepositorySchema, CreateTimelineEventSchema, CreateWikiPageSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, CreateWorkItemSchema, DeleteDraftNoteSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteMergeRequestDiscussionNoteSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, DeleteProjectMilestoneSchema, DeleteReleaseSchema, DeleteWikiPageSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, DownloadAttachmentSchema, DownloadJobArtifactsSchema, DownloadReleaseAssetSchema, EditProjectMilestoneSchema, ExecuteGraphQLSchema, ForkRepositorySchema, GetBranchDiffsSchema, GetCommitDiffSchema, GetCommitSchema, GetDeploymentSchema, GetDraftNoteSchema, GetEnvironmentSchema, GetFileContentsSchema, GetGroupWikiPageSchema, GetIssueLinkSchema, GetIssueSchema, GetJobArtifactFileSchema, GetLabelSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GetMergeRequestDiffsSchema, GetMergeRequestFileDiffSchema, GetMergeRequestNoteSchema, GetMergeRequestNotesSchema, GetMergeRequestSchema, GetMergeRequestVersionSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetNamespaceSchema, GetPipelineJobOutputSchema, GetPipelineSchema, GetProjectEventsSchema, GetProjectMilestoneSchema, GetProjectSchema, GetReleaseSchema, GetRepositoryTreeSchema, GetTimelineEventsSchema, GetUsersSchema, GetWebhookEventSchema, GetWikiPageSchema, GetWorkItemSchema, ListCommitsSchema, ListCustomFieldDefinitionsSchema, ListDeploymentsSchema, ListDraftNotesSchema, ListEnvironmentsSchema, ListEventsSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, ListGroupWikiPagesSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListJobArtifactsSchema, ListLabelsSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiffsSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestVersionsSchema, ListMergeRequestsSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelineTriggerJobsSchema, ListPipelinesSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListReleasesSchema, ListWebhookEventsSchema, ListWebhooksSchema, ListWikiPagesSchema, ListWorkItemNotesSchema, ListWorkItemStatusesSchema, ListWorkItemsSchema, MarkdownUploadSchema, MergeMergeRequestSchema, MoveWorkItemSchema, MyIssuesSchema, PlayPipelineJobSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PushFilesSchema, ResolveMergeRequestThreadSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UnapproveMergeRequestSchema, UpdateDraftNoteSchema, UpdateGroupWikiPageSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateLabelSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestSchema, UpdateReleaseSchema, UpdateWikiPageSchema, UpdateWorkItemSchema, VerifyNamespaceSchema, } from "../schemas.js";
|
|
4
|
+
import { ApproveMergeRequestSchema, BulkPublishDraftNotesSchema, CancelPipelineJobSchema, CancelPipelineSchema, ConvertWorkItemTypeSchema, CreateBranchSchema, CreateDraftNoteSchema, CreateGroupWikiPageSchema, CreateIssueLinkSchema, CreateIssueNoteSchema, CreateIssueSchema, CreateIssueEmojiReactionSchema, CreateIssueNoteEmojiReactionSchema, ListIssueEmojiReactionsSchema, ListIssueNoteEmojiReactionsSchema, CreateLabelSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestNoteSchema, CreateMergeRequestNoteEmojiReactionSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateReleaseEvidenceSchema, CreateReleaseSchema, CreateRepositorySchema, CreateTagSchema, CreateTimelineEventSchema, CreateWikiPageSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, CreateWorkItemSchema, DeleteDraftNoteSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteMergeRequestDiscussionNoteSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, DeleteProjectMilestoneSchema, DeleteReleaseSchema, DeleteTagSchema, DeleteWikiPageSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, DownloadAttachmentSchema, DownloadJobArtifactsSchema, DownloadReleaseAssetSchema, EditProjectMilestoneSchema, ExecuteGraphQLSchema, ForkRepositorySchema, GetBranchDiffsSchema, GetCommitDiffSchema, GetCommitSchema, GetDeploymentSchema, GetDraftNoteSchema, GetEnvironmentSchema, GetFileContentsSchema, GetGroupWikiPageSchema, GetIssueLinkSchema, GetIssueSchema, GetJobArtifactFileSchema, GetLabelSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GetMergeRequestDiffsSchema, GetMergeRequestFileDiffSchema, GetMergeRequestNoteSchema, GetMergeRequestNotesSchema, GetMergeRequestSchema, GetMergeRequestVersionSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetNamespaceSchema, GetPipelineJobOutputSchema, GetPipelineSchema, GetProjectEventsSchema, GetProjectMilestoneSchema, GetProjectSchema, GetReleaseSchema, GetRepositoryTreeSchema, GetTagSchema, GetTagSignatureSchema, GetTimelineEventsSchema, GetUsersSchema, GetWebhookEventSchema, GetWikiPageSchema, GetWorkItemSchema, ListCommitsSchema, ListCustomFieldDefinitionsSchema, ListDeploymentsSchema, ListDraftNotesSchema, ListEnvironmentsSchema, ListEventsSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, ListGroupWikiPagesSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListJobArtifactsSchema, ListLabelsSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiffsSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestVersionsSchema, ListMergeRequestsSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelineTriggerJobsSchema, ListPipelinesSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListReleasesSchema, ListTagsSchema, ListWebhookEventsSchema, ListWebhooksSchema, ListWikiPagesSchema, ListWorkItemNotesSchema, ListWorkItemStatusesSchema, ListWorkItemsSchema, MarkdownUploadSchema, MergeMergeRequestSchema, MoveWorkItemSchema, MyIssuesSchema, PlayPipelineJobSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PushFilesSchema, ResolveMergeRequestThreadSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UnapproveMergeRequestSchema, UpdateDraftNoteSchema, UpdateGroupWikiPageSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateLabelSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestSchema, UpdateReleaseSchema, UpdateWikiPageSchema, UpdateWorkItemSchema, VerifyNamespaceSchema, } from "../schemas.js";
|
|
5
5
|
// Define all available tools
|
|
6
6
|
export const allTools = [
|
|
7
7
|
{
|
|
@@ -681,6 +681,31 @@ export const allTools = [
|
|
|
681
681
|
description: "Download a release asset file by direct asset path",
|
|
682
682
|
inputSchema: toJSONSchema(DownloadReleaseAssetSchema),
|
|
683
683
|
},
|
|
684
|
+
{
|
|
685
|
+
name: "list_tags",
|
|
686
|
+
description: "List repository tags for a project",
|
|
687
|
+
inputSchema: toJSONSchema(ListTagsSchema),
|
|
688
|
+
},
|
|
689
|
+
{
|
|
690
|
+
name: "get_tag",
|
|
691
|
+
description: "Get a repository tag by name",
|
|
692
|
+
inputSchema: toJSONSchema(GetTagSchema),
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
name: "create_tag",
|
|
696
|
+
description: "Create a new repository tag",
|
|
697
|
+
inputSchema: toJSONSchema(CreateTagSchema),
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
name: "delete_tag",
|
|
701
|
+
description: "Delete a repository tag",
|
|
702
|
+
inputSchema: toJSONSchema(DeleteTagSchema),
|
|
703
|
+
},
|
|
704
|
+
{
|
|
705
|
+
name: "get_tag_signature",
|
|
706
|
+
description: "Get the X.509 signature of a signed tag (404 if unsigned)",
|
|
707
|
+
inputSchema: toJSONSchema(GetTagSignatureSchema),
|
|
708
|
+
},
|
|
684
709
|
// --- Work item tools (GraphQL-based) ---
|
|
685
710
|
{
|
|
686
711
|
name: "get_work_item",
|
|
@@ -892,6 +917,9 @@ export const readOnlyTools = new Set([
|
|
|
892
917
|
"list_releases",
|
|
893
918
|
"get_release",
|
|
894
919
|
"download_release_asset",
|
|
920
|
+
"list_tags",
|
|
921
|
+
"get_tag",
|
|
922
|
+
"get_tag_signature",
|
|
895
923
|
"get_merge_request_approval_state",
|
|
896
924
|
"get_work_item",
|
|
897
925
|
"list_work_items",
|
|
@@ -919,6 +947,7 @@ export const destructiveTools = new Set([
|
|
|
919
947
|
"delete_group_wiki_page",
|
|
920
948
|
"delete_milestone",
|
|
921
949
|
"delete_release",
|
|
950
|
+
"delete_tag",
|
|
922
951
|
"delete_merge_request_note",
|
|
923
952
|
"delete_merge_request_discussion_note",
|
|
924
953
|
"delete_draft_note",
|
|
@@ -1169,6 +1198,17 @@ export const TOOLSET_DEFINITIONS = [
|
|
|
1169
1198
|
"download_release_asset",
|
|
1170
1199
|
]),
|
|
1171
1200
|
},
|
|
1201
|
+
{
|
|
1202
|
+
id: "tags",
|
|
1203
|
+
isDefault: false,
|
|
1204
|
+
tools: new Set([
|
|
1205
|
+
"list_tags",
|
|
1206
|
+
"get_tag",
|
|
1207
|
+
"create_tag",
|
|
1208
|
+
"delete_tag",
|
|
1209
|
+
"get_tag_signature",
|
|
1210
|
+
]),
|
|
1211
|
+
},
|
|
1172
1212
|
{
|
|
1173
1213
|
id: "users",
|
|
1174
1214
|
isDefault: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zereight/mcp-gitlab",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.7",
|
|
4
4
|
"mcpName": "io.github.zereight/gitlab-mcp",
|
|
5
5
|
"description": "GitLab MCP server for projects, merge requests, issues, pipelines, wiki, releases, and more",
|
|
6
6
|
"keywords": [
|
|
@@ -51,7 +51,8 @@
|
|
|
51
51
|
"changelog": "auto-changelog -p",
|
|
52
52
|
"test": "npm run test:all",
|
|
53
53
|
"test:all": "npm run build && npm run test:mock && npm run test:live",
|
|
54
|
-
"test:mock": "node --import tsx/esm --test test/remote-auth-simple-test.ts && node --import tsx/esm --test test/mcp-oauth-tests.ts && tsx test/oauth-tests.ts && tsx test/test-list-merge-requests.ts && tsx test/test-list-project-members.ts && tsx test/test-download-attachment.ts && node --import tsx/esm --test test/test-job-artifacts.ts && node --import tsx/esm --test test/test-deployment-tools.ts && node --import tsx/esm --test test/test-merge-request-approval-state-tools.ts && node --import tsx/esm --test test/test-search-code.ts && node --import tsx/esm --test test/test-toolset-filtering.ts && node --import tsx/esm --test test/test-auth-retry.ts",
|
|
54
|
+
"test:mock": "node --import tsx/esm --test test/remote-auth-simple-test.ts && node --import tsx/esm --test test/mcp-oauth-tests.ts && tsx test/oauth-tests.ts && tsx test/test-list-merge-requests.ts && tsx test/test-list-project-members.ts && tsx test/test-download-attachment.ts && node --import tsx/esm --test test/test-job-artifacts.ts && node --import tsx/esm --test test/test-deployment-tools.ts && node --import tsx/esm --test test/test-merge-request-approval-state-tools.ts && node --import tsx/esm --test test/test-search-code.ts && node --import tsx/esm --test test/test-tags.ts && node --import tsx/esm --test test/test-toolset-filtering.ts && node --import tsx/esm --test test/test-auth-retry.ts && node --import tsx/esm --test test/stateless/codec.test.ts test/stateless/client-id.test.ts test/stateless/callback-proxy.test.ts test/stateless/session-id.test.ts test/stateless/session-id-integration.test.ts test/stateless/config-ttl.test.ts",
|
|
55
|
+
"test:stateless": "npm run build && node --import tsx/esm --test test/stateless/codec.test.ts test/stateless/client-id.test.ts test/stateless/callback-proxy.test.ts test/stateless/session-id.test.ts test/stateless/session-id-integration.test.ts test/stateless/config-ttl.test.ts",
|
|
55
56
|
"test:mcp-oauth": "npm run build && node --import tsx/esm --test test/mcp-oauth-tests.ts",
|
|
56
57
|
"test:live": "node test/validate-api.js",
|
|
57
58
|
"test:remote-auth": "npm run build && node --import tsx/esm --test test/remote-auth-simple-test.ts",
|