@zereight/mcp-gitlab 2.0.33 → 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.
@@ -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 'child_process';
6
- import * as path from 'path';
7
- export const HOST = process.env.HOST || '127.0.0.1';
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 if remote authorization is enabled
26
- const isRemoteAuth = env.REMOTE_AUTHORIZATION === 'true';
27
- // Validate that we have required configuration (unless using remote auth)
28
- if (!GITLAB_TOKEN && !isRemoteAuth) {
29
- throw new Error('GITLAB_TOKEN_TEST or GITLAB_TOKEN environment variable is required for server testing');
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 && !isRemoteAuth && env.ENABLE_DYNAMIC_API_URL !== 'true') {
32
- throw new Error('TEST_PROJECT_ID environment variable is required for server testing');
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 remote auth
39
- if (!isRemoteAuth && GITLAB_TOKEN) {
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 = 'true';
49
+ serverEnv.SSE = "true";
46
50
  serverEnv.PORT = port.toString();
47
51
  break;
48
52
  case TransportMode.STREAMABLE_HTTP:
49
- serverEnv.STREAMABLE_HTTP = 'true';
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(), 'build/index.js');
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('node', [serverPath], {
63
+ const serverProcess = spawn("node", [serverPath], {
60
64
  env: serverEnv,
61
- stdio: ['pipe', 'pipe', 'pipe'],
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('SIGTERM');
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('SIGKILL');
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
- 'Starting GitLab MCP Server with stdio transport',
110
- 'Starting GitLab MCP Server with SSE transport',
111
- 'Starting GitLab MCP Server with Streamable HTTP transport',
112
- 'GitLab MCP Server running',
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('data', onData);
143
- process.on('close', onClose);
144
- process.stderr?.on('data', onData);
145
- process.on('error', onError);
146
- process.on('exit', onExit);
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('net');
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 === 'object' && address ? address.port : basePort;
162
+ const port = typeof address === "object" && address ? address.port : basePort;
159
163
  server.close(() => resolve(port));
160
164
  });
161
- server.on('error', (err) => {
162
- if (err.code === 'EADDRINUSE') {
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: 'GET',
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 === 'AbortError') {
215
- lastError = new Error('Request timeout after 5000ms');
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.33",
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",