@zereight/mcp-gitlab 2.0.13 → 2.0.18

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.
@@ -8,22 +8,53 @@ export class MockGitLabServer {
8
8
  server = null;
9
9
  config;
10
10
  requestCount = 0;
11
+ customRouter;
12
+ customHandlers = new Map();
11
13
  constructor(config) {
12
14
  this.config = config;
13
15
  this.app = express();
16
+ this.customRouter = express.Router();
17
+ // Dynamic dispatcher for custom handlers
18
+ this.customRouter.use((req, res, next) => {
19
+ // Create a key from method and path (relative to /api/v4)
20
+ // req.path is already relative to the mount point
21
+ const key = `${req.method.toUpperCase()}:${req.path}`;
22
+ console.log(`[CustomRouter] Checking key: '${key}'`);
23
+ const handler = this.customHandlers.get(key);
24
+ if (handler) {
25
+ console.log(`[MockServer] Custom handler hit: ${key}`);
26
+ return handler(req, res, next);
27
+ }
28
+ else {
29
+ console.log(`[CustomRouter] No handler found for key: '${key}'. Available keys: ${Array.from(this.customHandlers.keys()).join(', ')}`);
30
+ }
31
+ next();
32
+ });
14
33
  this.setupMiddleware();
34
+ this.app.use('/api/v4', this.customRouter); // Mount router on API path
15
35
  this.setupRoutes();
16
36
  }
37
+ addMockHandler(method, path, handler) {
38
+ // Note: path should be relative to /api/v4
39
+ const key = `${method.toUpperCase()}:${path}`;
40
+ console.log(`[MockServer] Adding custom handler: ${key}`);
41
+ this.customHandlers.set(key, handler);
42
+ }
43
+ clearCustomHandlers() {
44
+ console.log('[MockServer] Clearing custom handlers');
45
+ this.customHandlers.clear();
46
+ }
17
47
  /**
18
48
  * Setup middleware including auth validation
19
49
  */
20
50
  setupMiddleware() {
21
- this.app.use(express.json());
22
- // Request counter for rate limiting tests
51
+ // Request counter for rate limiting tests - Place this FIRST to log everything
23
52
  this.app.use((req, res, next) => {
53
+ console.log(`[MockServer] ${req.method} ${req.originalUrl}`);
24
54
  this.requestCount++;
25
55
  next();
26
56
  });
57
+ this.app.use(express.json());
27
58
  // Artificial delay middleware (for timeout testing)
28
59
  if (this.config.responseDelay) {
29
60
  this.app.use((req, res, next) => {
@@ -158,21 +189,59 @@ export class MockGitLabServer {
158
189
  });
159
190
  // GET /api/v4/projects/:projectId/issues - List issues
160
191
  this.app.get('/api/v4/projects/:projectId/issues', (req, res) => {
192
+ const projectId = req.params.projectId;
161
193
  res.json([
162
194
  {
163
195
  id: 1,
164
196
  iid: 1,
197
+ project_id: projectId,
165
198
  title: 'Test Issue 1',
199
+ description: 'Test issue description',
166
200
  state: 'opened',
167
201
  created_at: '2024-01-01T00:00:00Z',
202
+ updated_at: '2024-01-02T00:00:00Z',
203
+ closed_at: null,
204
+ web_url: `https://gitlab.mock/project/${projectId}/issues/1`,
168
205
  author: {
169
206
  id: 1,
170
207
  username: 'test-user',
171
- name: 'Test User'
172
- }
208
+ name: 'Test User',
209
+ avatar_url: null,
210
+ web_url: 'https://gitlab.mock/test-user'
211
+ },
212
+ assignees: [],
213
+ labels: [],
214
+ milestone: null
173
215
  }
174
216
  ]);
175
217
  });
218
+ // GET /api/v4/projects/:projectId/issues/:issue_iid - Get single issue
219
+ this.app.get('/api/v4/projects/:projectId/issues/:issue_iid', (req, res) => {
220
+ const issueIid = parseInt(req.params.issue_iid);
221
+ const projectId = req.params.projectId;
222
+ res.json({
223
+ id: issueIid,
224
+ iid: issueIid,
225
+ project_id: projectId,
226
+ title: `Test Issue ${issueIid}`,
227
+ description: `Description for issue ${issueIid}`,
228
+ state: 'opened',
229
+ created_at: '2024-01-01T00:00:00Z',
230
+ updated_at: '2024-01-02T00:00:00Z',
231
+ closed_at: null,
232
+ web_url: `https://gitlab.mock/project/${projectId}/issues/${issueIid}`,
233
+ author: {
234
+ id: 1,
235
+ username: 'test-user',
236
+ name: 'Test User',
237
+ avatar_url: null,
238
+ web_url: 'https://gitlab.mock/test-user'
239
+ },
240
+ assignees: [],
241
+ labels: [],
242
+ milestone: null
243
+ });
244
+ });
176
245
  // GET /api/v4/projects - List projects
177
246
  this.app.get('/api/v4/projects', (req, res) => {
178
247
  res.json([
@@ -15,6 +15,7 @@ export var TransportMode;
15
15
  * Launch a server with specified configuration
16
16
  */
17
17
  export async function launchServer(config) {
18
+ console.log("Launcher: launchServer function entered.");
18
19
  const { mode, port = 3002, env = {}, timeout = 3000 } = config;
19
20
  // Prepare environment variables based on transport mode
20
21
  // Use same configuration pattern as existing validate-api.js
@@ -27,15 +28,11 @@ export async function launchServer(config) {
27
28
  if (!GITLAB_TOKEN && !isRemoteAuth) {
28
29
  throw new Error('GITLAB_TOKEN_TEST or GITLAB_TOKEN environment variable is required for server testing');
29
30
  }
30
- if (!TEST_PROJECT_ID && !isRemoteAuth) {
31
+ if (!TEST_PROJECT_ID && !isRemoteAuth && env.ENABLE_DYNAMIC_API_URL !== 'true') {
31
32
  throw new Error('TEST_PROJECT_ID environment variable is required for server testing');
32
33
  }
33
34
  const serverEnv = {
34
- // Add all environment variables from the current process
35
35
  ...process.env,
36
- GITLAB_API_URL: `${GITLAB_API_URL}/api/v4`,
37
- ...(TEST_PROJECT_ID ? { GITLAB_PROJECT_ID: TEST_PROJECT_ID } : {}),
38
- GITLAB_READ_ONLY_MODE: 'true', // Use read-only mode for testing
39
36
  ...env,
40
37
  };
41
38
  // Only set GITLAB_PERSONAL_ACCESS_TOKEN if not using remote auth
@@ -57,11 +54,16 @@ export async function launchServer(config) {
57
54
  throw new Error(`${TransportMode.STDIO} mode is not supported for server testing, because it uses process communication.`);
58
55
  }
59
56
  const serverPath = path.resolve(process.cwd(), 'build/index.js');
57
+ console.log("Launcher: Spawning server process with env:", serverEnv);
58
+ console.log("Launcher: Spawning server process with env:", serverEnv);
60
59
  const serverProcess = spawn('node', [serverPath], {
61
60
  env: serverEnv,
62
61
  stdio: ['pipe', 'pipe', 'pipe'],
62
+ shell: false,
63
63
  detached: false
64
64
  });
65
+ console.log(`Launcher: Server process spawned with PID: ${serverProcess.pid}`);
66
+ console.log("Launcher: Server process spawned.");
65
67
  // Wait for server to start
66
68
  await waitForServerStart(serverProcess, mode, port, timeout);
67
69
  const instance = {
@@ -92,8 +94,16 @@ async function waitForServerStart(process, mode, port, timeout) {
92
94
  }, timeout);
93
95
  let outputBuffer = '';
94
96
  const onData = (data) => {
95
- const output = data.toString();
96
- outputBuffer += output;
97
+ try {
98
+ const output = data.toString();
99
+ console.log(`[Server Output]: ${output}`);
100
+ outputBuffer += output;
101
+ }
102
+ catch (e) {
103
+ console.error("Error converting server output to string:", e);
104
+ reject(e);
105
+ return;
106
+ }
97
107
  // Check for server start messages
98
108
  const startMessages = [
99
109
  'Starting GitLab MCP Server with stdio transport',
@@ -105,8 +115,8 @@ async function waitForServerStart(process, mode, port, timeout) {
105
115
  const hasStartMessage = startMessages.some(msg => outputBuffer.includes(msg));
106
116
  if (hasStartMessage) {
107
117
  clearTimeout(timer);
108
- process.stdout?.removeListener('data', onData);
109
- process.stderr?.removeListener('data', onData);
118
+ // process.stdout?.removeListener('data', onData);
119
+ // process.stderr?.removeListener('data', onData);
110
120
  // Additional wait for HTTP servers to be fully ready
111
121
  if (mode !== TransportMode.STDIO) {
112
122
  setTimeout(resolve, 1000);
@@ -121,10 +131,16 @@ async function waitForServerStart(process, mode, port, timeout) {
121
131
  reject(new Error(`Server process error: ${error.message}`));
122
132
  };
123
133
  const onExit = (code) => {
134
+ console.log(`[Launcher DEBUG] Process ${process.pid} exited with code ${code}`);
135
+ // We don't reject here immediately, we wait for 'close' to ensure streams are flushed
136
+ };
137
+ const onClose = (code) => {
138
+ console.log(`[Launcher DEBUG] Process ${process.pid} closed with code ${code}`);
124
139
  clearTimeout(timer);
125
140
  reject(new Error(`Server process exited with code ${code} before starting`));
126
141
  };
127
142
  process.stdout?.on('data', onData);
143
+ process.on('close', onClose);
128
144
  process.stderr?.on('data', onData);
129
145
  process.on('error', onError);
130
146
  process.on('exit', onExit);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zereight/mcp-gitlab",
3
- "version": "2.0.13",
3
+ "version": "2.0.18",
4
4
  "description": "MCP server for using the GitLab API",
5
5
  "license": "MIT",
6
6
  "author": "zereight",
@@ -9,6 +9,10 @@
9
9
  "files": [
10
10
  "build"
11
11
  ],
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/zereight/gitlab-mcp.git"
15
+ },
12
16
  "publishConfig": {
13
17
  "access": "public"
14
18
  },
@@ -35,7 +39,6 @@
35
39
  "format:check": "prettier --check \"**/*.{js,ts,json,md}\""
36
40
  },
37
41
  "dependencies": {
38
- "@modelcontextprotocol/sdk": "^1.10.0",
39
42
  "@types/node-fetch": "^2.6.12",
40
43
  "express": "^5.1.0",
41
44
  "fetch-cookie": "^3.1.0",
@@ -49,6 +52,8 @@
49
52
  "pkce-challenge": "^5.0.0",
50
53
  "socks-proxy-agent": "^8.0.5",
51
54
  "tough-cookie": "^5.1.2",
55
+ "zod": "^3.24.2",
56
+ "@modelcontextprotocol/sdk": "^1.24.2",
52
57
  "zod-to-json-schema": "3.24.5"
53
58
  },
54
59
  "devDependencies": {
@@ -62,7 +67,6 @@
62
67
  "prettier": "^3.4.2",
63
68
  "ts-node": "^10.9.2",
64
69
  "tsx": "^4.20.5",
65
- "typescript": "^5.8.2",
66
- "zod": "^3.24.2"
70
+ "typescript": "^5.8.2"
67
71
  }
68
72
  }