gitlab-mcp 1.0.0 → 1.2.0

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.
Files changed (104) hide show
  1. package/LICENSE +21 -0
  2. package/dist/config/env.d.ts +56 -0
  3. package/dist/config/env.js +163 -0
  4. package/dist/config/env.js.map +1 -0
  5. package/dist/http-app.d.ts +45 -0
  6. package/dist/http-app.js +550 -0
  7. package/dist/http-app.js.map +1 -0
  8. package/dist/http.d.ts +2 -0
  9. package/dist/http.js +65 -0
  10. package/dist/http.js.map +1 -0
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.js +65 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/lib/auth-context.d.ts +9 -0
  15. package/dist/lib/auth-context.js +9 -0
  16. package/dist/lib/auth-context.js.map +1 -0
  17. package/dist/lib/gitlab-client.d.ts +331 -0
  18. package/dist/lib/gitlab-client.js +1025 -0
  19. package/dist/lib/gitlab-client.js.map +1 -0
  20. package/dist/lib/logger.d.ts +2 -0
  21. package/dist/lib/logger.js +13 -0
  22. package/dist/lib/logger.js.map +1 -0
  23. package/dist/lib/network.d.ts +3 -0
  24. package/dist/lib/network.js +38 -0
  25. package/dist/lib/network.js.map +1 -0
  26. package/dist/lib/oauth.d.ts +29 -0
  27. package/dist/lib/oauth.js +220 -0
  28. package/dist/lib/oauth.js.map +1 -0
  29. package/dist/lib/output.d.ts +14 -0
  30. package/dist/lib/output.js +38 -0
  31. package/dist/lib/output.js.map +1 -0
  32. package/dist/lib/policy.d.ts +25 -0
  33. package/dist/lib/policy.js +48 -0
  34. package/dist/lib/policy.js.map +1 -0
  35. package/dist/lib/request-runtime.d.ts +26 -0
  36. package/dist/lib/request-runtime.js +323 -0
  37. package/dist/lib/request-runtime.js.map +1 -0
  38. package/dist/lib/sanitize.d.ts +1 -0
  39. package/dist/lib/sanitize.js +21 -0
  40. package/dist/lib/sanitize.js.map +1 -0
  41. package/dist/lib/session-capacity.d.ts +8 -0
  42. package/dist/lib/session-capacity.js +7 -0
  43. package/dist/lib/session-capacity.js.map +1 -0
  44. package/dist/server/build-server.d.ts +3 -0
  45. package/dist/server/build-server.js +13 -0
  46. package/dist/server/build-server.js.map +1 -0
  47. package/dist/tools/gitlab.d.ts +9 -0
  48. package/dist/tools/gitlab.js +2576 -0
  49. package/dist/tools/gitlab.js.map +1 -0
  50. package/dist/tools/health.d.ts +2 -0
  51. package/dist/tools/health.js +21 -0
  52. package/dist/tools/health.js.map +1 -0
  53. package/dist/tools/mr-code-context.d.ts +38 -0
  54. package/dist/tools/mr-code-context.js +330 -0
  55. package/dist/tools/mr-code-context.js.map +1 -0
  56. package/{src/types/context.ts → dist/types/context.d.ts} +5 -6
  57. package/dist/types/context.js +2 -0
  58. package/dist/types/context.js.map +1 -0
  59. package/docs/configuration.md +6 -6
  60. package/docs/mcp-integration-testing-best-practices.md +981 -0
  61. package/package.json +21 -1
  62. package/.dockerignore +0 -7
  63. package/.editorconfig +0 -9
  64. package/.env.example +0 -75
  65. package/.github/workflows/nodejs.yml +0 -31
  66. package/.github/workflows/npm-publish.yml +0 -31
  67. package/.husky/pre-commit +0 -1
  68. package/.nvmrc +0 -1
  69. package/.prettierrc.json +0 -6
  70. package/Dockerfile +0 -20
  71. package/docker-compose.yml +0 -10
  72. package/eslint.config.js +0 -23
  73. package/scripts/get-oauth-token.example.sh +0 -15
  74. package/src/config/env.ts +0 -171
  75. package/src/http.ts +0 -605
  76. package/src/index.ts +0 -77
  77. package/src/lib/auth-context.ts +0 -19
  78. package/src/lib/gitlab-client.ts +0 -1810
  79. package/src/lib/logger.ts +0 -17
  80. package/src/lib/network.ts +0 -45
  81. package/src/lib/oauth.ts +0 -287
  82. package/src/lib/output.ts +0 -51
  83. package/src/lib/policy.ts +0 -78
  84. package/src/lib/request-runtime.ts +0 -376
  85. package/src/lib/sanitize.ts +0 -25
  86. package/src/server/build-server.ts +0 -17
  87. package/src/tools/gitlab.ts +0 -3128
  88. package/src/tools/health.ts +0 -27
  89. package/src/tools/mr-code-context.ts +0 -473
  90. package/tests/auth-context.test.ts +0 -102
  91. package/tests/gitlab-client.test.ts +0 -674
  92. package/tests/graphql-guard.test.ts +0 -121
  93. package/tests/integration/agent-loop.integration.test.ts +0 -552
  94. package/tests/integration/server.integration.test.ts +0 -543
  95. package/tests/mr-code-context.test.ts +0 -600
  96. package/tests/oauth.test.ts +0 -43
  97. package/tests/output.test.ts +0 -186
  98. package/tests/policy.test.ts +0 -324
  99. package/tests/request-runtime.test.ts +0 -252
  100. package/tests/sanitize.test.ts +0 -123
  101. package/tests/upload-reference.test.ts +0 -84
  102. package/tsconfig.build.json +0 -11
  103. package/tsconfig.json +0 -21
  104. package/vitest.config.ts +0 -12
@@ -1,543 +0,0 @@
1
- /**
2
- * Integration tests for the MCP server using InMemoryTransport.
3
- *
4
- * These tests exercise the full server lifecycle: creating an MCP server,
5
- * connecting a real MCP Client via InMemoryTransport, and verifying
6
- * tools/list, tools/call, and protocol-level behavior.
7
- *
8
- * No external network calls are made; the GitLabClient is stubbed.
9
- */
10
- import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
11
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
12
- import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
13
- import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14
-
15
- import { createMcpServer } from "../../src/server/build-server.js";
16
- import { OutputFormatter } from "../../src/lib/output.js";
17
- import { ToolPolicyEngine } from "../../src/lib/policy.js";
18
- import type { AppContext } from "../../src/types/context.js";
19
-
20
- /* ------------------------------------------------------------------ */
21
- /* Helpers */
22
- /* ------------------------------------------------------------------ */
23
-
24
- const defaultFeatures = {
25
- wiki: true,
26
- milestone: true,
27
- pipeline: true,
28
- release: true
29
- };
30
-
31
- /** Build a minimal AppContext with a stubbed GitLabClient. */
32
- function buildContext(overrides?: {
33
- readOnlyMode?: boolean;
34
- allowedTools?: string[];
35
- deniedToolsRegex?: RegExp;
36
- enabledFeatures?: typeof defaultFeatures;
37
- token?: string | null; // null = no token; undefined = use default
38
- allowedProjectIds?: string[];
39
- allowGraphqlWithProjectScope?: boolean;
40
- }): AppContext {
41
- const features = overrides?.enabledFeatures ?? defaultFeatures;
42
- const readOnlyMode = overrides?.readOnlyMode ?? false;
43
- const token = overrides?.token === null ? undefined : (overrides?.token ?? "test-token");
44
-
45
- return {
46
- env: {
47
- NODE_ENV: "test",
48
- LOG_LEVEL: "silent",
49
- MCP_SERVER_NAME: "test-gitlab-mcp",
50
- MCP_SERVER_VERSION: "0.0.1",
51
- GITLAB_API_URL: "https://gitlab.example.com/api/v4",
52
- GITLAB_API_URLS: ["https://gitlab.example.com/api/v4"],
53
- GITLAB_PERSONAL_ACCESS_TOKEN: token,
54
- GITLAB_USE_OAUTH: false,
55
- GITLAB_OAUTH_AUTO_OPEN_BROWSER: false,
56
- GITLAB_OAUTH_SCOPES: "api",
57
- GITLAB_READ_ONLY_MODE: readOnlyMode,
58
- GITLAB_ALLOWED_PROJECT_IDS: overrides?.allowedProjectIds ?? [],
59
- GITLAB_ALLOWED_TOOLS: overrides?.allowedTools ?? [],
60
- GITLAB_ALLOW_GRAPHQL_WITH_PROJECT_SCOPE: overrides?.allowGraphqlWithProjectScope ?? false,
61
- GITLAB_RESPONSE_MODE: "json",
62
- GITLAB_MAX_RESPONSE_BYTES: 200_000,
63
- GITLAB_HTTP_TIMEOUT_MS: 20_000,
64
- GITLAB_ERROR_DETAIL_MODE: "full",
65
- GITLAB_CLOUDFLARE_BYPASS: false,
66
- GITLAB_ALLOW_INSECURE_TOKEN_FILE: false,
67
- GITLAB_ALLOW_INSECURE_TLS: false,
68
- GITLAB_COOKIE_WARMUP_PATH: "/user",
69
- USE_GITLAB_WIKI: features.wiki,
70
- USE_MILESTONE: features.milestone,
71
- USE_PIPELINE: features.pipeline,
72
- USE_RELEASE: features.release,
73
- REMOTE_AUTHORIZATION: false,
74
- ENABLE_DYNAMIC_API_URL: false,
75
- HTTP_JSON_ONLY: false,
76
- SSE: false,
77
- SESSION_TIMEOUT_SECONDS: 3600,
78
- MAX_SESSIONS: 1000,
79
- MAX_REQUESTS_PER_MINUTE: 300,
80
- HTTP_HOST: "127.0.0.1",
81
- HTTP_PORT: 3333,
82
- GITLAB_TOKEN_CACHE_SECONDS: 300,
83
- GITLAB_TOKEN_SCRIPT_TIMEOUT_MS: 10_000,
84
- GITLAB_OAUTH_GITLAB_URL: undefined,
85
- GITLAB_OAUTH_CLIENT_ID: undefined,
86
- GITLAB_OAUTH_CLIENT_SECRET: undefined,
87
- GITLAB_OAUTH_REDIRECT_URI: undefined,
88
- GITLAB_OAUTH_TOKEN_PATH: undefined,
89
- GITLAB_AUTH_COOKIE_PATH: undefined,
90
- GITLAB_USER_AGENT: undefined,
91
- GITLAB_ACCEPT_LANGUAGE: undefined,
92
- GITLAB_TOKEN_SCRIPT: undefined,
93
- GITLAB_TOKEN_FILE: undefined,
94
- GITLAB_CA_CERT_PATH: undefined,
95
- GITLAB_DENIED_TOOLS_REGEX: undefined,
96
- NODE_TLS_REJECT_UNAUTHORIZED: undefined,
97
- HTTP_PROXY: undefined,
98
- HTTPS_PROXY: undefined
99
- } as AppContext["env"],
100
- logger: {
101
- info: vi.fn(),
102
- warn: vi.fn(),
103
- error: vi.fn(),
104
- debug: vi.fn(),
105
- trace: vi.fn(),
106
- fatal: vi.fn(),
107
- child: () => ({}) as never
108
- } as unknown as AppContext["logger"],
109
- gitlab: {} as AppContext["gitlab"], // Stubbed — individual tools/call tests mock as needed
110
- policy: new ToolPolicyEngine({
111
- readOnlyMode,
112
- allowedTools: overrides?.allowedTools ?? [],
113
- deniedToolsRegex: overrides?.deniedToolsRegex,
114
- enabledFeatures: features
115
- }),
116
- formatter: new OutputFormatter({
117
- responseMode: "json",
118
- maxBytes: 200_000
119
- })
120
- };
121
- }
122
-
123
- /** Create linked Client + Server pair and connect them. */
124
- async function createLinkedPair(context: AppContext): Promise<{
125
- client: Client;
126
- server: McpServer;
127
- clientTransport: InMemoryTransport;
128
- serverTransport: InMemoryTransport;
129
- }> {
130
- const server = createMcpServer(context);
131
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
132
-
133
- await server.connect(serverTransport);
134
-
135
- const client = new Client(
136
- { name: "integration-test-client", version: "0.0.1" },
137
- { capabilities: {} }
138
- );
139
- await client.connect(clientTransport);
140
-
141
- return { client, server, clientTransport, serverTransport };
142
- }
143
-
144
- /* ------------------------------------------------------------------ */
145
- /* Tests: Server lifecycle & tools/list */
146
- /* ------------------------------------------------------------------ */
147
-
148
- describe("MCP Server Integration (InMemoryTransport)", () => {
149
- let client: Client;
150
- let clientTransport: InMemoryTransport;
151
- let serverTransport: InMemoryTransport;
152
-
153
- beforeAll(async () => {
154
- const context = buildContext();
155
- const pair = await createLinkedPair(context);
156
- client = pair.client;
157
- clientTransport = pair.clientTransport;
158
- serverTransport = pair.serverTransport;
159
- });
160
-
161
- afterAll(async () => {
162
- await clientTransport.close();
163
- await serverTransport.close();
164
- });
165
-
166
- describe("protocol basics", () => {
167
- it("completes initialization handshake", () => {
168
- const serverVersion = client.getServerVersion();
169
- expect(serverVersion).toBeDefined();
170
- expect(serverVersion!.name).toBe("test-gitlab-mcp");
171
- expect(serverVersion!.version).toBe("0.0.1");
172
- });
173
-
174
- it("reports server capabilities including tools", () => {
175
- const caps = client.getServerCapabilities();
176
- expect(caps).toBeDefined();
177
- expect(caps!.tools).toBeDefined();
178
- });
179
-
180
- it("responds to ping", async () => {
181
- const result = await client.ping();
182
- expect(result).toBeDefined();
183
- });
184
- });
185
-
186
- describe("tools/list", () => {
187
- it("returns a non-empty list of tools", async () => {
188
- const result = await client.listTools();
189
- expect(result.tools.length).toBeGreaterThan(0);
190
- });
191
-
192
- it("includes health_check tool", async () => {
193
- const result = await client.listTools();
194
- const names = result.tools.map((t) => t.name);
195
- expect(names).toContain("health_check");
196
- });
197
-
198
- it("includes core gitlab tools", async () => {
199
- const result = await client.listTools();
200
- const names = result.tools.map((t) => t.name);
201
-
202
- // Spot-check a few essential tools
203
- expect(names).toContain("gitlab_get_project");
204
- expect(names).toContain("gitlab_list_projects");
205
- expect(names).toContain("gitlab_get_file_contents");
206
- expect(names).toContain("gitlab_create_merge_request");
207
- expect(names).toContain("gitlab_list_issues");
208
- });
209
-
210
- it("every tool has a name and inputSchema", async () => {
211
- const result = await client.listTools();
212
- for (const tool of result.tools) {
213
- expect(tool.name).toBeTruthy();
214
- expect(tool.inputSchema).toBeDefined();
215
- expect(tool.inputSchema.type).toBe("object");
216
- }
217
- });
218
-
219
- it("every tool has a description", async () => {
220
- const result = await client.listTools();
221
- for (const tool of result.tools) {
222
- expect(tool.description).toBeTruthy();
223
- }
224
- });
225
-
226
- it("tool names follow naming convention", async () => {
227
- const result = await client.listTools();
228
- for (const tool of result.tools) {
229
- // All tools should be either health_check or gitlab_*
230
- expect(tool.name === "health_check" || tool.name.startsWith("gitlab_")).toBe(true);
231
- }
232
- });
233
- });
234
-
235
- describe("tools/call - health_check", () => {
236
- it("returns ok status with timestamp", async () => {
237
- const result = await client.callTool({ name: "health_check", arguments: {} });
238
- expect(result.isError).toBeFalsy();
239
- expect(result.content).toBeDefined();
240
- expect(Array.isArray(result.content)).toBe(true);
241
-
242
- const textContent = (result.content as Array<{ type: string; text: string }>).find(
243
- (c) => c.type === "text"
244
- );
245
- expect(textContent).toBeDefined();
246
- expect(textContent!.text).toContain("ok");
247
- });
248
-
249
- it("returns structuredContent with status and timestamp", async () => {
250
- const result = await client.callTool({ name: "health_check", arguments: {} });
251
- const structured = (result as { structuredContent?: Record<string, unknown> })
252
- .structuredContent;
253
- expect(structured).toBeDefined();
254
- expect(structured!.status).toBe("ok");
255
- expect(structured!.timestamp).toBeTruthy();
256
-
257
- // Timestamp should be valid ISO 8601
258
- const ts = structured!.timestamp as string;
259
- expect(new Date(ts).toISOString()).toBe(ts);
260
- });
261
- });
262
- });
263
-
264
- /* ------------------------------------------------------------------ */
265
- /* Tests: Policy enforcement through full MCP protocol */
266
- /* ------------------------------------------------------------------ */
267
-
268
- describe("MCP Server Integration - Read-only mode", () => {
269
- let client: Client;
270
- let clientTransport: InMemoryTransport;
271
- let serverTransport: InMemoryTransport;
272
-
273
- beforeAll(async () => {
274
- const context = buildContext({ readOnlyMode: true });
275
- const pair = await createLinkedPair(context);
276
- client = pair.client;
277
- clientTransport = pair.clientTransport;
278
- serverTransport = pair.serverTransport;
279
- });
280
-
281
- afterAll(async () => {
282
- await clientTransport.close();
283
- await serverTransport.close();
284
- });
285
-
286
- it("excludes mutating tools from tools/list", async () => {
287
- const result = await client.listTools();
288
- const names = result.tools.map((t) => t.name);
289
-
290
- // These are mutating tools that should be excluded in read-only mode
291
- expect(names).not.toContain("gitlab_create_merge_request");
292
- expect(names).not.toContain("gitlab_create_issue");
293
- expect(names).not.toContain("gitlab_delete_issue");
294
- expect(names).not.toContain("gitlab_create_or_update_file");
295
- expect(names).not.toContain("gitlab_push_files");
296
- });
297
-
298
- it("still includes read-only tools", async () => {
299
- const result = await client.listTools();
300
- const names = result.tools.map((t) => t.name);
301
-
302
- expect(names).toContain("health_check");
303
- expect(names).toContain("gitlab_get_project");
304
- expect(names).toContain("gitlab_list_projects");
305
- expect(names).toContain("gitlab_get_file_contents");
306
- });
307
- });
308
-
309
- describe("MCP Server Integration - Feature flag filtering", () => {
310
- it("excludes wiki tools when wiki feature is disabled", async () => {
311
- const context = buildContext({
312
- enabledFeatures: { wiki: false, milestone: true, pipeline: true, release: true }
313
- });
314
- const { client, clientTransport, serverTransport } = await createLinkedPair(context);
315
-
316
- try {
317
- const result = await client.listTools();
318
- const names = result.tools.map((t) => t.name);
319
-
320
- expect(names).not.toContain("gitlab_list_wiki_pages");
321
- expect(names).not.toContain("gitlab_get_wiki_page");
322
- expect(names).not.toContain("gitlab_create_wiki_page");
323
- // But other tools remain
324
- expect(names).toContain("gitlab_get_project");
325
- } finally {
326
- await clientTransport.close();
327
- await serverTransport.close();
328
- }
329
- });
330
-
331
- it("excludes pipeline tools when pipeline feature is disabled", async () => {
332
- const context = buildContext({
333
- enabledFeatures: { wiki: true, milestone: true, pipeline: false, release: true }
334
- });
335
- const { client, clientTransport, serverTransport } = await createLinkedPair(context);
336
-
337
- try {
338
- const result = await client.listTools();
339
- const names = result.tools.map((t) => t.name);
340
-
341
- expect(names).not.toContain("gitlab_list_pipelines");
342
- expect(names).not.toContain("gitlab_get_pipeline");
343
- } finally {
344
- await clientTransport.close();
345
- await serverTransport.close();
346
- }
347
- });
348
-
349
- it("excludes release tools when release feature is disabled", async () => {
350
- const context = buildContext({
351
- enabledFeatures: { wiki: true, milestone: true, pipeline: true, release: false }
352
- });
353
- const { client, clientTransport, serverTransport } = await createLinkedPair(context);
354
-
355
- try {
356
- const result = await client.listTools();
357
- const names = result.tools.map((t) => t.name);
358
-
359
- expect(names).not.toContain("gitlab_list_releases");
360
- expect(names).not.toContain("gitlab_get_release");
361
- expect(names).not.toContain("gitlab_create_release");
362
- } finally {
363
- await clientTransport.close();
364
- await serverTransport.close();
365
- }
366
- });
367
- });
368
-
369
- describe("MCP Server Integration - Allowlist filtering", () => {
370
- it("only exposes tools in the allowlist (plus health_check)", async () => {
371
- const context = buildContext({
372
- allowedTools: ["get_project", "list_projects"]
373
- });
374
- const { client, clientTransport, serverTransport } = await createLinkedPair(context);
375
-
376
- try {
377
- const result = await client.listTools();
378
- const names = result.tools.map((t) => t.name);
379
-
380
- expect(names).toContain("health_check"); // always present
381
- expect(names).toContain("gitlab_get_project");
382
- expect(names).toContain("gitlab_list_projects");
383
-
384
- // Other tools should be excluded
385
- expect(names).not.toContain("gitlab_create_issue");
386
- expect(names).not.toContain("gitlab_get_file_contents");
387
- } finally {
388
- await clientTransport.close();
389
- await serverTransport.close();
390
- }
391
- });
392
- });
393
-
394
- describe("MCP Server Integration - Denied tools regex", () => {
395
- it("excludes tools matching denied regex pattern", async () => {
396
- const context = buildContext({
397
- deniedToolsRegex: /.*wiki.*/
398
- });
399
- const { client, clientTransport, serverTransport } = await createLinkedPair(context);
400
-
401
- try {
402
- const result = await client.listTools();
403
- const names = result.tools.map((t) => t.name);
404
-
405
- // Wiki tools should be excluded by regex
406
- const wikiTools = names.filter((n) => n.includes("wiki"));
407
- expect(wikiTools).toHaveLength(0);
408
-
409
- // Other tools remain
410
- expect(names).toContain("gitlab_get_project");
411
- } finally {
412
- await clientTransport.close();
413
- await serverTransport.close();
414
- }
415
- });
416
- });
417
-
418
- /* ------------------------------------------------------------------ */
419
- /* Tests: Error handling through full MCP protocol */
420
- /* ------------------------------------------------------------------ */
421
-
422
- describe("MCP Server Integration - Error handling", () => {
423
- let client: Client;
424
- let clientTransport: InMemoryTransport;
425
- let serverTransport: InMemoryTransport;
426
-
427
- beforeAll(async () => {
428
- const context = buildContext();
429
- const pair = await createLinkedPair(context);
430
- client = pair.client;
431
- clientTransport = pair.clientTransport;
432
- serverTransport = pair.serverTransport;
433
- });
434
-
435
- afterAll(async () => {
436
- await clientTransport.close();
437
- await serverTransport.close();
438
- });
439
-
440
- it("returns error for unknown tool", async () => {
441
- const result = await client.callTool({ name: "nonexistent_tool", arguments: {} });
442
- expect(result.isError).toBe(true);
443
- const textContent = (result.content as Array<{ type: string; text: string }>).find(
444
- (c) => c.type === "text"
445
- );
446
- expect(textContent!.text).toContain("not found");
447
- });
448
-
449
- it("gitlab tools return error content when API call fails", async () => {
450
- // gitlab_get_project will attempt to call context.gitlab.getProject
451
- // which is a stub ({}) and will throw a TypeError.
452
- // The tool handler catches errors and returns isError: true
453
- const result = await client.callTool({
454
- name: "gitlab_get_project",
455
- arguments: { project_id: "test/project" }
456
- });
457
-
458
- expect(result.isError).toBe(true);
459
- expect(result.content).toBeDefined();
460
- });
461
- });
462
-
463
- /* ------------------------------------------------------------------ */
464
- /* Tests: Multiple server configurations */
465
- /* ------------------------------------------------------------------ */
466
-
467
- describe("MCP Server Integration - No auth configured", () => {
468
- it("returns error when calling tool that requires auth without token", async () => {
469
- const context = buildContext({ token: null });
470
- const { client, clientTransport, serverTransport } = await createLinkedPair(context);
471
-
472
- try {
473
- const result = await client.callTool({
474
- name: "gitlab_get_project",
475
- arguments: { project_id: "test/project" }
476
- });
477
-
478
- expect(result.isError).toBe(true);
479
- const textContent = (result.content as Array<{ type: string; text: string }>).find(
480
- (c) => c.type === "text"
481
- );
482
- expect(textContent!.text).toContain("Authentication required");
483
- } finally {
484
- await clientTransport.close();
485
- await serverTransport.close();
486
- }
487
- });
488
-
489
- it("health_check still works without auth", async () => {
490
- const context = buildContext({ token: null });
491
- const { client, clientTransport, serverTransport } = await createLinkedPair(context);
492
-
493
- try {
494
- const result = await client.callTool({ name: "health_check", arguments: {} });
495
- expect(result.isError).toBeFalsy();
496
- } finally {
497
- await clientTransport.close();
498
- await serverTransport.close();
499
- }
500
- });
501
- });
502
-
503
- /* ------------------------------------------------------------------ */
504
- /* Tests: GraphQL tool filtering */
505
- /* ------------------------------------------------------------------ */
506
-
507
- describe("MCP Server Integration - GraphQL tool filtering", () => {
508
- it("disables graphql tools when project scope is set without override", async () => {
509
- const context = buildContext({
510
- allowedProjectIds: ["group/project"],
511
- allowGraphqlWithProjectScope: false
512
- });
513
- const { client, clientTransport, serverTransport } = await createLinkedPair(context);
514
-
515
- try {
516
- const result = await client.listTools();
517
- const names = result.tools.map((t) => t.name);
518
-
519
- expect(names).not.toContain("gitlab_execute_graphql");
520
- } finally {
521
- await clientTransport.close();
522
- await serverTransport.close();
523
- }
524
- });
525
-
526
- it("keeps graphql tools when project scope is set with override", async () => {
527
- const context = buildContext({
528
- allowedProjectIds: ["group/project"],
529
- allowGraphqlWithProjectScope: true
530
- });
531
- const { client, clientTransport, serverTransport } = await createLinkedPair(context);
532
-
533
- try {
534
- const result = await client.listTools();
535
- const names = result.tools.map((t) => t.name);
536
-
537
- expect(names).toContain("gitlab_execute_graphql");
538
- } finally {
539
- await clientTransport.close();
540
- await serverTransport.close();
541
- }
542
- });
543
- });