@zereight/mcp-gitlab 2.0.34 → 2.0.35
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.md +221 -89
- package/build/gitlab-client-pool.js +6 -0
- package/build/index.js +2129 -34
- package/build/oauth-proxy.js +257 -0
- package/build/schemas.js +455 -199
- package/build/test/mcp-oauth-tests.js +443 -0
- package/build/test/multi-server-test.js +16 -8
- package/build/test/test-geteffectiveprojectid.js +211 -202
- package/build/test/test-mr-file-diffs.js +251 -0
- package/build/test/test-search-code.js +272 -0
- package/build/test/test-toolset-filtering.js +22 -17
- package/build/test/utils/mock-gitlab-server.js +263 -163
- package/build/test/utils/server-launcher.js +45 -41
- package/package.json +3 -2
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Server launcher utility for testing different transport modes
|
|
3
3
|
* Manages server processes and provides clean shutdown
|
|
4
4
|
*/
|
|
5
|
-
import { spawn } from
|
|
6
|
-
import * as path from
|
|
7
|
-
export const HOST = process.env.HOST ||
|
|
5
|
+
import { spawn } from "child_process";
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
export const HOST = process.env.HOST || "127.0.0.1";
|
|
8
8
|
export var TransportMode;
|
|
9
9
|
(function (TransportMode) {
|
|
10
10
|
TransportMode["STDIO"] = "stdio";
|
|
@@ -22,45 +22,49 @@ export async function launchServer(config) {
|
|
|
22
22
|
const GITLAB_API_URL = process.env.GITLAB_API_URL || "https://gitlab.com";
|
|
23
23
|
const GITLAB_TOKEN = process.env.GITLAB_TOKEN_TEST || process.env.GITLAB_TOKEN;
|
|
24
24
|
const TEST_PROJECT_ID = process.env.TEST_PROJECT_ID;
|
|
25
|
-
// Check
|
|
26
|
-
const isRemoteAuth = env.REMOTE_AUTHORIZATION ===
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
// Check which auth modes are active
|
|
26
|
+
const isRemoteAuth = env.REMOTE_AUTHORIZATION === "true";
|
|
27
|
+
const isMcpOAuth = env.GITLAB_MCP_OAUTH === "true";
|
|
28
|
+
// Both REMOTE_AUTHORIZATION and GITLAB_MCP_OAUTH manage tokens at the HTTP layer;
|
|
29
|
+
// neither requires a pre-configured GITLAB_TOKEN on the server process.
|
|
30
|
+
const isServerManagedAuth = isRemoteAuth || isMcpOAuth;
|
|
31
|
+
// Validate that we have required configuration (unless using a server-managed auth mode)
|
|
32
|
+
if (!GITLAB_TOKEN && !isServerManagedAuth) {
|
|
33
|
+
throw new Error("GITLAB_TOKEN_TEST or GITLAB_TOKEN environment variable is required for server testing");
|
|
30
34
|
}
|
|
31
|
-
if (!TEST_PROJECT_ID && !
|
|
32
|
-
throw new Error(
|
|
35
|
+
if (!TEST_PROJECT_ID && !isServerManagedAuth && env.ENABLE_DYNAMIC_API_URL !== "true") {
|
|
36
|
+
throw new Error("TEST_PROJECT_ID environment variable is required for server testing");
|
|
33
37
|
}
|
|
34
38
|
const serverEnv = {
|
|
35
39
|
...process.env,
|
|
36
40
|
...env,
|
|
37
41
|
};
|
|
38
|
-
// Only set GITLAB_PERSONAL_ACCESS_TOKEN if not using
|
|
39
|
-
if (!
|
|
42
|
+
// Only set GITLAB_PERSONAL_ACCESS_TOKEN if not using a server-managed auth mode
|
|
43
|
+
if (!isServerManagedAuth && GITLAB_TOKEN) {
|
|
40
44
|
serverEnv.GITLAB_PERSONAL_ACCESS_TOKEN = GITLAB_TOKEN;
|
|
41
45
|
}
|
|
42
46
|
// Set transport-specific environment variables
|
|
43
47
|
switch (mode) {
|
|
44
48
|
case TransportMode.SSE:
|
|
45
|
-
serverEnv.SSE =
|
|
49
|
+
serverEnv.SSE = "true";
|
|
46
50
|
serverEnv.PORT = port.toString();
|
|
47
51
|
break;
|
|
48
52
|
case TransportMode.STREAMABLE_HTTP:
|
|
49
|
-
serverEnv.STREAMABLE_HTTP =
|
|
53
|
+
serverEnv.STREAMABLE_HTTP = "true";
|
|
50
54
|
serverEnv.PORT = port.toString();
|
|
51
55
|
break;
|
|
52
56
|
case TransportMode.STDIO:
|
|
53
57
|
// Stdio mode doesn't need port configuration - uses process communication
|
|
54
58
|
throw new Error(`${TransportMode.STDIO} mode is not supported for server testing, because it uses process communication.`);
|
|
55
59
|
}
|
|
56
|
-
const serverPath = path.resolve(process.cwd(),
|
|
60
|
+
const serverPath = path.resolve(process.cwd(), "build/index.js");
|
|
57
61
|
console.log("Launcher: Spawning server process with env:", serverEnv);
|
|
58
62
|
console.log("Launcher: Spawning server process with env:", serverEnv);
|
|
59
|
-
const serverProcess = spawn(
|
|
63
|
+
const serverProcess = spawn("node", [serverPath], {
|
|
60
64
|
env: serverEnv,
|
|
61
|
-
stdio: [
|
|
65
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
62
66
|
shell: false,
|
|
63
|
-
detached: false
|
|
67
|
+
detached: false,
|
|
64
68
|
});
|
|
65
69
|
console.log(`Launcher: Server process spawned with PID: ${serverProcess.pid}`);
|
|
66
70
|
console.log("Launcher: Server process spawned.");
|
|
@@ -72,15 +76,15 @@ export async function launchServer(config) {
|
|
|
72
76
|
mode,
|
|
73
77
|
kill: () => {
|
|
74
78
|
if (!serverProcess.killed) {
|
|
75
|
-
serverProcess.kill(
|
|
79
|
+
serverProcess.kill("SIGTERM");
|
|
76
80
|
// Force kill if not terminated within 5 seconds
|
|
77
81
|
setTimeout(() => {
|
|
78
82
|
if (!serverProcess.killed) {
|
|
79
|
-
serverProcess.kill(
|
|
83
|
+
serverProcess.kill("SIGKILL");
|
|
80
84
|
}
|
|
81
85
|
}, 5000);
|
|
82
86
|
}
|
|
83
|
-
}
|
|
87
|
+
},
|
|
84
88
|
};
|
|
85
89
|
return instance;
|
|
86
90
|
}
|
|
@@ -92,7 +96,7 @@ async function waitForServerStart(process, mode, port, timeout) {
|
|
|
92
96
|
const timer = setTimeout(() => {
|
|
93
97
|
reject(new Error(`Server failed to start within ${timeout}ms for mode ${mode}`));
|
|
94
98
|
}, timeout);
|
|
95
|
-
let outputBuffer =
|
|
99
|
+
let outputBuffer = "";
|
|
96
100
|
const onData = (data) => {
|
|
97
101
|
try {
|
|
98
102
|
const output = data.toString();
|
|
@@ -106,11 +110,11 @@ async function waitForServerStart(process, mode, port, timeout) {
|
|
|
106
110
|
}
|
|
107
111
|
// Check for server start messages
|
|
108
112
|
const startMessages = [
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
`port ${port}
|
|
113
|
+
"Starting GitLab MCP Server with stdio transport",
|
|
114
|
+
"Starting GitLab MCP Server with SSE transport",
|
|
115
|
+
"Starting GitLab MCP Server with Streamable HTTP transport",
|
|
116
|
+
"GitLab MCP Server running",
|
|
117
|
+
`port ${port}`,
|
|
114
118
|
];
|
|
115
119
|
const hasStartMessage = startMessages.some(msg => outputBuffer.includes(msg));
|
|
116
120
|
if (hasStartMessage) {
|
|
@@ -139,27 +143,27 @@ async function waitForServerStart(process, mode, port, timeout) {
|
|
|
139
143
|
clearTimeout(timer);
|
|
140
144
|
reject(new Error(`Server process exited with code ${code} before starting`));
|
|
141
145
|
};
|
|
142
|
-
process.stdout?.on(
|
|
143
|
-
process.on(
|
|
144
|
-
process.stderr?.on(
|
|
145
|
-
process.on(
|
|
146
|
-
process.on(
|
|
146
|
+
process.stdout?.on("data", onData);
|
|
147
|
+
process.on("close", onClose);
|
|
148
|
+
process.stderr?.on("data", onData);
|
|
149
|
+
process.on("error", onError);
|
|
150
|
+
process.on("exit", onExit);
|
|
147
151
|
});
|
|
148
152
|
}
|
|
149
153
|
/**
|
|
150
154
|
* Find an available port starting from a base port
|
|
151
155
|
*/
|
|
152
156
|
export async function findAvailablePort(basePort = 3002) {
|
|
153
|
-
const net = await import(
|
|
157
|
+
const net = await import("net");
|
|
154
158
|
return new Promise((resolve, reject) => {
|
|
155
159
|
const server = net.createServer();
|
|
156
160
|
server.listen(basePort, () => {
|
|
157
161
|
const address = server.address();
|
|
158
|
-
const port = typeof address ===
|
|
162
|
+
const port = typeof address === "object" && address ? address.port : basePort;
|
|
159
163
|
server.close(() => resolve(port));
|
|
160
164
|
});
|
|
161
|
-
server.on(
|
|
162
|
-
if (err.code ===
|
|
165
|
+
server.on("error", (err) => {
|
|
166
|
+
if (err.code === "EADDRINUSE") {
|
|
163
167
|
// Port is in use, try next one
|
|
164
168
|
resolve(findAvailablePort(basePort + 1));
|
|
165
169
|
}
|
|
@@ -199,11 +203,11 @@ export async function checkHealthEndpoint(port, maxRetries = 5) {
|
|
|
199
203
|
try {
|
|
200
204
|
const controller = createTimeoutController(5000);
|
|
201
205
|
const response = await fetch(`http://${HOST}:${port}/health`, {
|
|
202
|
-
method:
|
|
203
|
-
signal: controller.signal
|
|
206
|
+
method: "GET",
|
|
207
|
+
signal: controller.signal,
|
|
204
208
|
});
|
|
205
209
|
if (response.ok) {
|
|
206
|
-
const healthData = await response.json();
|
|
210
|
+
const healthData = (await response.json());
|
|
207
211
|
return healthData;
|
|
208
212
|
}
|
|
209
213
|
else {
|
|
@@ -211,8 +215,8 @@ export async function checkHealthEndpoint(port, maxRetries = 5) {
|
|
|
211
215
|
}
|
|
212
216
|
}
|
|
213
217
|
catch (error) {
|
|
214
|
-
if (error instanceof Error && error.name ===
|
|
215
|
-
lastError = new Error(
|
|
218
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
219
|
+
lastError = new Error("Request timeout after 5000ms");
|
|
216
220
|
}
|
|
217
221
|
else {
|
|
218
222
|
lastError = error instanceof Error ? error : new Error(String(error));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zereight/mcp-gitlab",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.35",
|
|
4
4
|
"description": "MCP server for using the GitLab API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "zereight",
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"changelog": "auto-changelog -p",
|
|
30
30
|
"test": "npm run test:all",
|
|
31
31
|
"test:all": "npm run build && npm run test:mock && npm run test:live",
|
|
32
|
-
"test:mock": "npx tsx --test test/remote-auth-simple-test.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 && tsx --test test/test-job-artifacts.ts && tsx --test test/test-deployment-tools.ts && tsx --test test/test-merge-request-approval-state-tools.ts",
|
|
32
|
+
"test:mock": "npx tsx --test test/remote-auth-simple-test.ts && npx tsx --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 && tsx --test test/test-job-artifacts.ts && tsx --test test/test-deployment-tools.ts && tsx --test test/test-merge-request-approval-state-tools.ts && npx tsx --test test/test-search-code.ts && npx tsx --test test/test-toolset-filtering.ts",
|
|
33
|
+
"test:mcp-oauth": "npm run build && npx tsx --test test/mcp-oauth-tests.ts",
|
|
33
34
|
"test:live": "node test/validate-api.js",
|
|
34
35
|
"test:remote-auth": "npm run build && npx tsx --test test/remote-auth-simple-test.ts",
|
|
35
36
|
"test:schema": "tsx test/schema-tests.ts",
|