@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.
@@ -11,226 +11,235 @@ import { StreamableHTTPTestClient } from './clients/streamable-http-client.js';
11
11
  const MOCK_TOKEN = process.env.GITLAB_TOKEN_TEST || 'glpat-mock-token-12345';
12
12
  const DEFAULT_PROJECT_ID = '123';
13
13
  const OTHER_PROJECT_ID = '456';
14
+ // Ensure GITLAB_TOKEN_TEST is set for launchServer() validation
15
+ if (!process.env.GITLAB_TOKEN_TEST && !process.env.GITLAB_TOKEN) {
16
+ process.env.GITLAB_TOKEN_TEST = MOCK_TOKEN;
17
+ }
18
+ if (!process.env.TEST_PROJECT_ID) {
19
+ process.env.TEST_PROJECT_ID = DEFAULT_PROJECT_ID;
20
+ }
14
21
  console.log('🔍 Testing getEffectiveProjectId functionality');
15
22
  console.log('');
16
- describe('getEffectiveProjectId - No GITLAB_ALLOWED_PROJECT_IDS', () => {
17
- let mcpUrl;
18
- let mockGitLab;
19
- let servers = [];
20
- let client;
21
- before(async () => {
22
- // Start mock GitLab server
23
- const mockPort = await findMockServerPort(9100);
24
- mockGitLab = new MockGitLabServer({
25
- port: mockPort,
26
- validTokens: [MOCK_TOKEN]
23
+ describe('getEffectiveProjectId', { concurrency: 1 }, () => {
24
+ describe('getEffectiveProjectId - No GITLAB_ALLOWED_PROJECT_IDS', () => {
25
+ let mcpUrl;
26
+ let mockGitLab;
27
+ let servers = [];
28
+ let client;
29
+ before(async () => {
30
+ // Start mock GitLab server
31
+ const mockPort = await findMockServerPort(9100);
32
+ mockGitLab = new MockGitLabServer({
33
+ port: mockPort,
34
+ validTokens: [MOCK_TOKEN]
35
+ });
36
+ await mockGitLab.start();
37
+ const mockGitLabUrl = mockGitLab.getUrl();
38
+ // Start MCP server WITHOUT GITLAB_ALLOWED_PROJECT_IDS
39
+ const mcpPort = await findAvailablePort(3100);
40
+ const server = await launchServer({
41
+ mode: TransportMode.STREAMABLE_HTTP,
42
+ port: mcpPort,
43
+ timeout: 5000,
44
+ env: {
45
+ STREAMABLE_HTTP: 'true',
46
+ GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
47
+ GITLAB_PROJECT_ID: DEFAULT_PROJECT_ID,
48
+ GITLAB_READ_ONLY_MODE: 'true',
49
+ }
50
+ });
51
+ servers.push(server);
52
+ mcpUrl = `http://${HOST}:${mcpPort}/mcp`;
53
+ client = new StreamableHTTPTestClient();
54
+ await client.connect(mcpUrl);
55
+ console.log(`Mock GitLab: ${mockGitLabUrl}`);
56
+ console.log(`MCP Server: ${mcpUrl}`);
57
+ console.log(`Default Project: ${DEFAULT_PROJECT_ID}`);
27
58
  });
28
- await mockGitLab.start();
29
- const mockGitLabUrl = mockGitLab.getUrl();
30
- // Start MCP server WITHOUT GITLAB_ALLOWED_PROJECT_IDS
31
- const mcpPort = await findAvailablePort(3100);
32
- const server = await launchServer({
33
- mode: TransportMode.STREAMABLE_HTTP,
34
- port: mcpPort,
35
- timeout: 5000,
36
- env: {
37
- STREAMABLE_HTTP: 'true',
38
- GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
39
- GITLAB_PROJECT_ID: DEFAULT_PROJECT_ID,
40
- GITLAB_READ_ONLY_MODE: 'true',
59
+ after(async () => {
60
+ if (client) {
61
+ await client.disconnect();
62
+ }
63
+ cleanupServers(servers);
64
+ if (mockGitLab) {
65
+ await mockGitLab.stop();
41
66
  }
42
67
  });
43
- servers.push(server);
44
- mcpUrl = `http://${HOST}:${mcpPort}/mcp`;
45
- client = new StreamableHTTPTestClient();
46
- await client.connect(mcpUrl);
47
- console.log(`Mock GitLab: ${mockGitLabUrl}`);
48
- console.log(`MCP Server: ${mcpUrl}`);
49
- console.log(`Default Project: ${DEFAULT_PROJECT_ID}`);
50
- });
51
- after(async () => {
52
- if (client) {
53
- await client.disconnect();
54
- }
55
- cleanupServers(servers);
56
- if (mockGitLab) {
57
- await mockGitLab.stop();
58
- }
59
- });
60
- test('should use GITLAB_PROJECT_ID when no project_id is provided', async () => {
61
- // Call get_project without specifying project_id
62
- const result = await client.callTool('get_project', {
63
- project_id: ''
68
+ test('should use GITLAB_PROJECT_ID when no project_id is provided', async () => {
69
+ // Call get_project without specifying project_id
70
+ const result = await client.callTool('get_project', {
71
+ project_id: ''
72
+ });
73
+ assert.ok(result.content, 'Should have content');
74
+ const content = result.content[0];
75
+ assert.ok('text' in content, 'Content should have text');
76
+ const project = JSON.parse(content.text);
77
+ // The mock server should receive a request for the default project
78
+ assert.strictEqual(project.id.toString(), DEFAULT_PROJECT_ID, 'Should use GITLAB_PROJECT_ID as default');
79
+ console.log(` ✓ Used default project ${DEFAULT_PROJECT_ID} when no project_id provided`);
64
80
  });
65
- assert.ok(result.content, 'Should have content');
66
- const content = result.content[0];
67
- assert.ok('text' in content, 'Content should have text');
68
- const project = JSON.parse(content.text);
69
- // The mock server should receive a request for the default project
70
- assert.strictEqual(project.id.toString(), DEFAULT_PROJECT_ID, 'Should use GITLAB_PROJECT_ID as default');
71
- console.log(` ✓ Used default project ${DEFAULT_PROJECT_ID} when no project_id provided`);
72
- });
73
- test('should prioritize passed project_id over GITLAB_PROJECT_ID', async () => {
74
- // Call get_project with a different project_id
75
- const result = await client.callTool('get_project', {
76
- project_id: OTHER_PROJECT_ID
81
+ test('should prioritize passed project_id over GITLAB_PROJECT_ID', async () => {
82
+ // Call get_project with a different project_id
83
+ const result = await client.callTool('get_project', {
84
+ project_id: OTHER_PROJECT_ID
85
+ });
86
+ assert.ok(result.content, 'Should have content');
87
+ const content = result.content[0];
88
+ assert.ok('text' in content, 'Content should have text');
89
+ const project = JSON.parse(content.text);
90
+ // Should use the passed project_id, not GITLAB_PROJECT_ID
91
+ assert.strictEqual(project.id.toString(), OTHER_PROJECT_ID, 'Should use passed project_id');
92
+ console.log(` ✓ Used passed project_id ${OTHER_PROJECT_ID} instead of default ${DEFAULT_PROJECT_ID}`);
77
93
  });
78
- assert.ok(result.content, 'Should have content');
79
- const content = result.content[0];
80
- assert.ok('text' in content, 'Content should have text');
81
- const project = JSON.parse(content.text);
82
- // Should use the passed project_id, not GITLAB_PROJECT_ID
83
- assert.strictEqual(project.id.toString(), OTHER_PROJECT_ID, 'Should use passed project_id');
84
- console.log(` ✓ Used passed project_id ${OTHER_PROJECT_ID} instead of default ${DEFAULT_PROJECT_ID}`);
85
94
  });
86
- });
87
- describe('getEffectiveProjectId - With single GITLAB_ALLOWED_PROJECT_IDS', () => {
88
- let mcpUrl;
89
- let mockGitLab;
90
- let servers = [];
91
- let client;
92
- before(async () => {
93
- // Start mock GitLab server
94
- const mockPort = await findMockServerPort(9200);
95
- mockGitLab = new MockGitLabServer({
96
- port: mockPort,
97
- validTokens: [MOCK_TOKEN]
95
+ describe('getEffectiveProjectId - With single GITLAB_ALLOWED_PROJECT_IDS', () => {
96
+ let mcpUrl;
97
+ let mockGitLab;
98
+ let servers = [];
99
+ let client;
100
+ before(async () => {
101
+ // Start mock GitLab server
102
+ const mockPort = await findMockServerPort(9200);
103
+ mockGitLab = new MockGitLabServer({
104
+ port: mockPort,
105
+ validTokens: [MOCK_TOKEN]
106
+ });
107
+ await mockGitLab.start();
108
+ const mockGitLabUrl = mockGitLab.getUrl();
109
+ // Start MCP server WITH single GITLAB_ALLOWED_PROJECT_IDS
110
+ const mcpPort = await findAvailablePort(3200);
111
+ const server = await launchServer({
112
+ mode: TransportMode.STREAMABLE_HTTP,
113
+ port: mcpPort,
114
+ timeout: 5000,
115
+ env: {
116
+ STREAMABLE_HTTP: 'true',
117
+ GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
118
+ GITLAB_PROJECT_ID: DEFAULT_PROJECT_ID,
119
+ GITLAB_ALLOWED_PROJECT_IDS: DEFAULT_PROJECT_ID,
120
+ GITLAB_READ_ONLY_MODE: 'true',
121
+ }
122
+ });
123
+ servers.push(server);
124
+ mcpUrl = `http://${HOST}:${mcpPort}/mcp`;
125
+ client = new StreamableHTTPTestClient();
126
+ await client.connect(mcpUrl);
127
+ console.log(`Mock GitLab: ${mockGitLabUrl}`);
128
+ console.log(`MCP Server: ${mcpUrl}`);
129
+ console.log(`Allowed Project: ${DEFAULT_PROJECT_ID}`);
98
130
  });
99
- await mockGitLab.start();
100
- const mockGitLabUrl = mockGitLab.getUrl();
101
- // Start MCP server WITH single GITLAB_ALLOWED_PROJECT_IDS
102
- const mcpPort = await findAvailablePort(3200);
103
- const server = await launchServer({
104
- mode: TransportMode.STREAMABLE_HTTP,
105
- port: mcpPort,
106
- timeout: 5000,
107
- env: {
108
- STREAMABLE_HTTP: 'true',
109
- GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
110
- GITLAB_PROJECT_ID: DEFAULT_PROJECT_ID,
111
- GITLAB_ALLOWED_PROJECT_IDS: DEFAULT_PROJECT_ID,
112
- GITLAB_READ_ONLY_MODE: 'true',
131
+ after(async () => {
132
+ if (client) {
133
+ await client.disconnect();
134
+ }
135
+ cleanupServers(servers);
136
+ if (mockGitLab) {
137
+ await mockGitLab.stop();
113
138
  }
114
139
  });
115
- servers.push(server);
116
- mcpUrl = `http://${HOST}:${mcpPort}/mcp`;
117
- client = new StreamableHTTPTestClient();
118
- await client.connect(mcpUrl);
119
- console.log(`Mock GitLab: ${mockGitLabUrl}`);
120
- console.log(`MCP Server: ${mcpUrl}`);
121
- console.log(`Allowed Project: ${DEFAULT_PROJECT_ID}`);
122
- });
123
- after(async () => {
124
- if (client) {
125
- await client.disconnect();
126
- }
127
- cleanupServers(servers);
128
- if (mockGitLab) {
129
- await mockGitLab.stop();
130
- }
131
- });
132
- test('should use single allowed project as default', async () => {
133
- const result = await client.callTool('get_project', {
134
- project_id: ''
140
+ test('should use single allowed project as default', async () => {
141
+ const result = await client.callTool('get_project', {
142
+ project_id: ''
143
+ });
144
+ assert.ok(result.content, 'Should have content');
145
+ const content = result.content[0];
146
+ assert.ok('text' in content, 'Content should have text');
147
+ const project = JSON.parse(content.text);
148
+ assert.strictEqual(project.id.toString(), DEFAULT_PROJECT_ID, 'Should use allowed project as default');
149
+ console.log(` ✓ Used allowed project ${DEFAULT_PROJECT_ID} as default`);
150
+ });
151
+ test('should reject access to non-allowed project', async () => {
152
+ try {
153
+ await client.callTool('get_project', {
154
+ project_id: OTHER_PROJECT_ID
155
+ });
156
+ assert.fail('Should have rejected access to non-allowed project');
157
+ }
158
+ catch (error) {
159
+ assert.ok(error instanceof Error);
160
+ assert.ok(error.message.includes('Access denied'), 'Should indicate access denied');
161
+ console.log(' ✓ Correctly rejected access to non-allowed project');
162
+ }
135
163
  });
136
- assert.ok(result.content, 'Should have content');
137
- const content = result.content[0];
138
- assert.ok('text' in content, 'Content should have text');
139
- const project = JSON.parse(content.text);
140
- assert.strictEqual(project.id.toString(), DEFAULT_PROJECT_ID, 'Should use allowed project as default');
141
- console.log(` ✓ Used allowed project ${DEFAULT_PROJECT_ID} as default`);
142
164
  });
143
- test('should reject access to non-allowed project', async () => {
144
- try {
145
- await client.callTool('get_project', {
146
- project_id: OTHER_PROJECT_ID
165
+ describe('getEffectiveProjectId - With multiple GITLAB_ALLOWED_PROJECT_IDS', () => {
166
+ let mcpUrl;
167
+ let mockGitLab;
168
+ let servers = [];
169
+ let client;
170
+ before(async () => {
171
+ // Start mock GitLab server
172
+ const mockPort = await findMockServerPort(9300);
173
+ mockGitLab = new MockGitLabServer({
174
+ port: mockPort,
175
+ validTokens: [MOCK_TOKEN]
147
176
  });
148
- assert.fail('Should have rejected access to non-allowed project');
149
- }
150
- catch (error) {
151
- assert.ok(error instanceof Error);
152
- assert.ok(error.message.includes('Access denied'), 'Should indicate access denied');
153
- console.log(' ✓ Correctly rejected access to non-allowed project');
154
- }
155
- });
156
- });
157
- describe('getEffectiveProjectId - With multiple GITLAB_ALLOWED_PROJECT_IDS', () => {
158
- let mcpUrl;
159
- let mockGitLab;
160
- let servers = [];
161
- let client;
162
- before(async () => {
163
- // Start mock GitLab server
164
- const mockPort = await findMockServerPort(9300);
165
- mockGitLab = new MockGitLabServer({
166
- port: mockPort,
167
- validTokens: [MOCK_TOKEN]
177
+ await mockGitLab.start();
178
+ const mockGitLabUrl = mockGitLab.getUrl();
179
+ // Start MCP server WITH multiple GITLAB_ALLOWED_PROJECT_IDS
180
+ const mcpPort = await findAvailablePort(3300);
181
+ const server = await launchServer({
182
+ mode: TransportMode.STREAMABLE_HTTP,
183
+ port: mcpPort,
184
+ timeout: 5000,
185
+ env: {
186
+ STREAMABLE_HTTP: 'true',
187
+ GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
188
+ GITLAB_ALLOWED_PROJECT_IDS: `${DEFAULT_PROJECT_ID},${OTHER_PROJECT_ID}`,
189
+ GITLAB_READ_ONLY_MODE: 'true',
190
+ }
191
+ });
192
+ servers.push(server);
193
+ mcpUrl = `http://${HOST}:${mcpPort}/mcp`;
194
+ client = new StreamableHTTPTestClient();
195
+ await client.connect(mcpUrl);
196
+ console.log(`Mock GitLab: ${mockGitLabUrl}`);
197
+ console.log(`MCP Server: ${mcpUrl}`);
198
+ console.log(`Allowed Projects: ${DEFAULT_PROJECT_ID},${OTHER_PROJECT_ID}`);
168
199
  });
169
- await mockGitLab.start();
170
- const mockGitLabUrl = mockGitLab.getUrl();
171
- // Start MCP server WITH multiple GITLAB_ALLOWED_PROJECT_IDS
172
- const mcpPort = await findAvailablePort(3300);
173
- const server = await launchServer({
174
- mode: TransportMode.STREAMABLE_HTTP,
175
- port: mcpPort,
176
- timeout: 5000,
177
- env: {
178
- STREAMABLE_HTTP: 'true',
179
- GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
180
- GITLAB_ALLOWED_PROJECT_IDS: `${DEFAULT_PROJECT_ID},${OTHER_PROJECT_ID}`,
181
- GITLAB_READ_ONLY_MODE: 'true',
200
+ after(async () => {
201
+ if (client) {
202
+ await client.disconnect();
203
+ }
204
+ cleanupServers(servers);
205
+ if (mockGitLab) {
206
+ await mockGitLab.stop();
182
207
  }
183
208
  });
184
- servers.push(server);
185
- mcpUrl = `http://${HOST}:${mcpPort}/mcp`;
186
- client = new StreamableHTTPTestClient();
187
- await client.connect(mcpUrl);
188
- console.log(`Mock GitLab: ${mockGitLabUrl}`);
189
- console.log(`MCP Server: ${mcpUrl}`);
190
- console.log(`Allowed Projects: ${DEFAULT_PROJECT_ID},${OTHER_PROJECT_ID}`);
191
- });
192
- after(async () => {
193
- if (client) {
194
- await client.disconnect();
195
- }
196
- cleanupServers(servers);
197
- if (mockGitLab) {
198
- await mockGitLab.stop();
199
- }
200
- });
201
- test('should require explicit project_id when multiple projects allowed', async () => {
202
- try {
203
- await client.callTool('get_project', {
204
- project_id: ''
209
+ test('should require explicit project_id when multiple projects allowed', async () => {
210
+ try {
211
+ await client.callTool('get_project', {
212
+ project_id: ''
213
+ });
214
+ assert.fail('Should have required explicit project_id');
215
+ }
216
+ catch (error) {
217
+ assert.ok(error instanceof Error);
218
+ assert.ok(error.message.includes('Please specify a project ID'), 'Should require project ID');
219
+ console.log(' ✓ Correctly required explicit project_id');
220
+ }
221
+ });
222
+ test('should allow access to first allowed project', async () => {
223
+ const result = await client.callTool('get_project', {
224
+ project_id: DEFAULT_PROJECT_ID
205
225
  });
206
- assert.fail('Should have required explicit project_id');
207
- }
208
- catch (error) {
209
- assert.ok(error instanceof Error);
210
- assert.ok(error.message.includes('Please specify a project ID'), 'Should require project ID');
211
- console.log('Correctly required explicit project_id');
212
- }
213
- });
214
- test('should allow access to first allowed project', async () => {
215
- const result = await client.callTool('get_project', {
216
- project_id: DEFAULT_PROJECT_ID
226
+ assert.ok(result.content, 'Should have content');
227
+ const content = result.content[0];
228
+ assert.ok('text' in content, 'Content should have text');
229
+ const project = JSON.parse(content.text);
230
+ assert.strictEqual(project.id.toString(), DEFAULT_PROJECT_ID, 'Should allow first project');
231
+ console.log(`Allowed access to first project ${DEFAULT_PROJECT_ID}`);
217
232
  });
218
- assert.ok(result.content, 'Should have content');
219
- const content = result.content[0];
220
- assert.ok('text' in content, 'Content should have text');
221
- const project = JSON.parse(content.text);
222
- assert.strictEqual(project.id.toString(), DEFAULT_PROJECT_ID, 'Should allow first project');
223
- console.log(` ✓ Allowed access to first project ${DEFAULT_PROJECT_ID}`);
224
- });
225
- test('should allow access to second allowed project', async () => {
226
- const result = await client.callTool('get_project', {
227
- project_id: OTHER_PROJECT_ID
233
+ test('should allow access to second allowed project', async () => {
234
+ const result = await client.callTool('get_project', {
235
+ project_id: OTHER_PROJECT_ID
236
+ });
237
+ assert.ok(result.content, 'Should have content');
238
+ const content = result.content[0];
239
+ assert.ok('text' in content, 'Content should have text');
240
+ const project = JSON.parse(content.text);
241
+ assert.strictEqual(project.id.toString(), OTHER_PROJECT_ID, 'Should allow second project');
242
+ console.log(` ✓ Allowed access to second project ${OTHER_PROJECT_ID}`);
228
243
  });
229
- assert.ok(result.content, 'Should have content');
230
- const content = result.content[0];
231
- assert.ok('text' in content, 'Content should have text');
232
- const project = JSON.parse(content.text);
233
- assert.strictEqual(project.id.toString(), OTHER_PROJECT_ID, 'Should allow second project');
234
- console.log(` ✓ Allowed access to second project ${OTHER_PROJECT_ID}`);
235
244
  });
236
- });
245
+ }); // end wrapper describe