@zereight/mcp-gitlab 2.1.69 → 2.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 (155) hide show
  1. package/README.ko.md +10 -4
  2. package/README.md +238 -233
  3. package/README.pt-BR.md +4 -4
  4. package/README.zh-CN.md +6 -4
  5. package/build/config.js +12 -4
  6. package/build/deprecated-env.js +47 -0
  7. package/build/index.js +456 -237
  8. package/build/oauth-device-flow.js +80 -0
  9. package/build/oauth.js +135 -18
  10. package/build/schemas.js +1 -1
  11. package/build/server/remote-http-stack.js +39 -0
  12. package/build/tools/discover-activation.js +38 -0
  13. package/build/tools/exposed-tools.js +35 -0
  14. package/build/tools/listed-tools.js +76 -0
  15. package/build/tools/registry.js +226 -57
  16. package/build/tools/tool-profile.js +66 -0
  17. package/build/utils/compact-tool-allowlist.js +14 -0
  18. package/build/utils/logger.js +34 -12
  19. package/build/utils/url.js +15 -0
  20. package/package.json +3 -3
  21. package/build/scripts/check-skill-sync.js +0 -143
  22. package/build/scripts/generate-server-card.js +0 -39
  23. package/build/scripts/generate-tool-coverage.js +0 -58
  24. package/build/scripts/generate-tool-docs.js +0 -430
  25. package/build/scripts/tool-coverage/coverage.js +0 -341
  26. package/build/scripts/tool-coverage/coverage.test.js +0 -142
  27. package/build/test/callback-proxy-tests.js +0 -321
  28. package/build/test/cli/args.test.js +0 -175
  29. package/build/test/cli/boolean-flags.test.js +0 -106
  30. package/build/test/cli/compact-mcp-result.test.js +0 -93
  31. package/build/test/cli/exposure.test.js +0 -104
  32. package/build/test/cli/load-config-snapshot.js +0 -5
  33. package/build/test/cli/output.test.js +0 -74
  34. package/build/test/cli/replay-command.test.js +0 -37
  35. package/build/test/cli/router.test.js +0 -183
  36. package/build/test/client-pool-test.js +0 -109
  37. package/build/test/clients/client.js +0 -34
  38. package/build/test/clients/custom-header-client.js +0 -122
  39. package/build/test/clients/sse-client.js +0 -95
  40. package/build/test/clients/stdio-client.js +0 -113
  41. package/build/test/clients/streamable-http-client.js +0 -95
  42. package/build/test/config-allowed-groups.test.js +0 -97
  43. package/build/test/create-commit-actions.test.js +0 -102
  44. package/build/test/draft-note-schema.test.js +0 -44
  45. package/build/test/dynamic-api-url-allowlist.test.js +0 -121
  46. package/build/test/dynamic-api-url-test.js +0 -304
  47. package/build/test/dynamic-routing-tests.js +0 -495
  48. package/build/test/get-merge-request-discussion-schema.test.js +0 -30
  49. package/build/test/gitlab-artifact-entry-schema.test.js +0 -44
  50. package/build/test/gitlab-diff-schema.test.js +0 -36
  51. package/build/test/gitlab-pipeline-schedule-schema.test.js +0 -32
  52. package/build/test/gitlab-tag-schema.test.js +0 -51
  53. package/build/test/group-milestone-schema.test.js +0 -45
  54. package/build/test/mcp-oauth-tests.js +0 -905
  55. package/build/test/mcp-server-name.test.js +0 -87
  56. package/build/test/merge-merge-request-schema.test.js +0 -75
  57. package/build/test/multi-server-test.js +0 -190
  58. package/build/test/no-proxy-integration-test.js +0 -183
  59. package/build/test/no-proxy-test.js +0 -149
  60. package/build/test/nullable-gitlab-response-fields.test.js +0 -37
  61. package/build/test/nullish-tool-arguments-schema.test.js +0 -118
  62. package/build/test/oauth-device-flow-tests.js +0 -450
  63. package/build/test/oauth-startup.test.js +0 -204
  64. package/build/test/oauth-tests.js +0 -428
  65. package/build/test/path-segment-encoding.test.js +0 -98
  66. package/build/test/remote-auth-simple-test.js +0 -277
  67. package/build/test/remote-auth-tests.js +0 -314
  68. package/build/test/response-masking.test.js +0 -445
  69. package/build/test/schema-tests.js +0 -1679
  70. package/build/test/server/metrics.test.js +0 -47
  71. package/build/test/server/request-helpers.test.js +0 -48
  72. package/build/test/server/version.test.js +0 -13
  73. package/build/test/sse-auth-guard.test.js +0 -96
  74. package/build/test/sse-session-limits.test.js +0 -273
  75. package/build/test/stateless/callback-proxy.test.js +0 -510
  76. package/build/test/stateless/client-id.test.js +0 -177
  77. package/build/test/stateless/codec.test.js +0 -328
  78. package/build/test/stateless/config-ttl.test.js +0 -149
  79. package/build/test/stateless/consumed-proxy-code-cache.test.js +0 -150
  80. package/build/test/stateless/session-id-integration.test.js +0 -675
  81. package/build/test/stateless/session-id.test.js +0 -157
  82. package/build/test/streamable-http-concurrent-session.test.js +0 -171
  83. package/build/test/streamable-http-dns-rebinding.test.js +0 -185
  84. package/build/test/streamable-http-sse-stream.test.js +0 -350
  85. package/build/test/streamable-http-static-token-auth.test.js +0 -157
  86. package/build/test/streamable-http-unauthenticated-discovery.test.js +0 -207
  87. package/build/test/test-all-transport-server.js +0 -255
  88. package/build/test/test-auth-retry.js +0 -195
  89. package/build/test/test-blank-filters.js +0 -156
  90. package/build/test/test-ci-catalog.js +0 -177
  91. package/build/test/test-ci-lint.js +0 -191
  92. package/build/test/test-ci-variables.js +0 -309
  93. package/build/test/test-create-repository.js +0 -120
  94. package/build/test/test-dependency-proxy.js +0 -195
  95. package/build/test/test-deployment-tools.js +0 -463
  96. package/build/test/test-download-attachment.js +0 -190
  97. package/build/test/test-dynamic-project-scope.js +0 -524
  98. package/build/test/test-empty-toolsets.js +0 -535
  99. package/build/test/test-get-file-blame.js +0 -145
  100. package/build/test/test-get-file-contents.js +0 -128
  101. package/build/test/test-geteffectiveprojectid.js +0 -812
  102. package/build/test/test-health-check.js +0 -115
  103. package/build/test/test-issue-description-patch.js +0 -256
  104. package/build/test/test-job-artifacts.js +0 -275
  105. package/build/test/test-json-schema.js +0 -166
  106. package/build/test/test-list-group-members.js +0 -150
  107. package/build/test/test-list-group-merge-requests.js +0 -165
  108. package/build/test/test-list-issues.js +0 -170
  109. package/build/test/test-list-merge-requests.js +0 -201
  110. package/build/test/test-list-milestones.js +0 -145
  111. package/build/test/test-list-project-members.js +0 -132
  112. package/build/test/test-merge-request-approval-state-tools.js +0 -213
  113. package/build/test/test-merge-request-approvals.js +0 -187
  114. package/build/test/test-merge-request-pipelines.js +0 -106
  115. package/build/test/test-mr-diffs-filter.js +0 -132
  116. package/build/test/test-mr-file-diffs.js +0 -251
  117. package/build/test/test-oauth-proxy-rate-limit.js +0 -133
  118. package/build/test/test-orbit.js +0 -155
  119. package/build/test/test-permission-mode.js +0 -424
  120. package/build/test/test-pipeline-schedules.js +0 -482
  121. package/build/test/test-protected-branches.js +0 -174
  122. package/build/test/test-remote-downloads.js +0 -510
  123. package/build/test/test-search-code.js +0 -275
  124. package/build/test/test-snippets.js +0 -735
  125. package/build/test/test-tags.js +0 -220
  126. package/build/test/test-todos.js +0 -195
  127. package/build/test/test-token-optimizations.js +0 -691
  128. package/build/test/test-toolset-filtering.js +0 -499
  129. package/build/test/test-update-issue-slim.js +0 -141
  130. package/build/test/test-update-project.js +0 -112
  131. package/build/test/test-upload-markdown.js +0 -206
  132. package/build/test/test-vulnerabilities.js +0 -399
  133. package/build/test/test-webhooks.js +0 -218
  134. package/build/test/tool-description-quality.test.js +0 -50
  135. package/build/test/utils/bulk-publish-options.test.js +0 -32
  136. package/build/test/utils/download-token.test.js +0 -35
  137. package/build/test/utils/forwarded-public-base-url.test.js +0 -46
  138. package/build/test/utils/gitlab-instance-version.test.js +0 -32
  139. package/build/test/utils/gitlab-version-gate.test.js +0 -74
  140. package/build/test/utils/graphql-query.test.js +0 -168
  141. package/build/test/utils/helpers-path.test.js +0 -104
  142. package/build/test/utils/jmespath-tool-result.test.js +0 -91
  143. package/build/test/utils/masking.test.js +0 -384
  144. package/build/test/utils/merge-request-position.test.js +0 -57
  145. package/build/test/utils/mock-gitlab-server.js +0 -614
  146. package/build/test/utils/proxy-client-ip.test.js +0 -28
  147. package/build/test/utils/redact-sensitive.test.js +0 -32
  148. package/build/test/utils/safe-redirect-fetch.test.js +0 -463
  149. package/build/test/utils/server-launcher.js +0 -231
  150. package/build/test/utils/tool-args.test.js +0 -210
  151. package/build/test/utils/url.test.js +0 -128
  152. package/build/test/utils/version-check.test.js +0 -239
  153. package/build/test/utils/wiki-title.test.js +0 -21
  154. package/build/test-note.js +0 -54
  155. package/build/test-resolve-issue-note.js +0 -127
@@ -1,239 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
- import { describe, test } from "node:test";
6
- import { checkForNewVersion, fetchLatestVersion, isNewerVersion } from "../../utils/version-check.js";
7
- function fetchReturning(status, body) {
8
- return async () => new Response(JSON.stringify(body), { status });
9
- }
10
- function fetchCapturing(calls, status, body) {
11
- return async (url, init) => {
12
- const headers = new Headers(init?.headers);
13
- calls.push({
14
- url: url.toString(),
15
- authorization: headers.get("authorization"),
16
- redirect: init?.redirect,
17
- });
18
- return new Response(JSON.stringify(body), { status });
19
- };
20
- }
21
- function throwingResolver() {
22
- throw new Error("npm not found");
23
- }
24
- async function withNpmrc(contents, run, globalContents = "") {
25
- const configDir = mkdtempSync(join(tmpdir(), "gitlab-mcp-npmrc-"));
26
- const userconfigPath = join(configDir, ".npmrc");
27
- const globalconfigPath = join(configDir, "global.npmrc");
28
- writeFileSync(userconfigPath, contents);
29
- writeFileSync(globalconfigPath, globalContents);
30
- const previousUserconfig = process.env.NPM_CONFIG_USERCONFIG;
31
- const previousGlobalconfig = process.env.NPM_CONFIG_GLOBALCONFIG;
32
- process.env.NPM_CONFIG_USERCONFIG = userconfigPath;
33
- process.env.NPM_CONFIG_GLOBALCONFIG = globalconfigPath;
34
- try {
35
- await run();
36
- }
37
- finally {
38
- if (previousUserconfig === undefined)
39
- delete process.env.NPM_CONFIG_USERCONFIG;
40
- else
41
- process.env.NPM_CONFIG_USERCONFIG = previousUserconfig;
42
- if (previousGlobalconfig === undefined)
43
- delete process.env.NPM_CONFIG_GLOBALCONFIG;
44
- else
45
- process.env.NPM_CONFIG_GLOBALCONFIG = previousGlobalconfig;
46
- rmSync(configDir, { recursive: true, force: true });
47
- }
48
- }
49
- describe("When isNewerVersion compares versions", () => {
50
- test("should detect newer patch, minor, and major versions", () => {
51
- assert.equal(isNewerVersion("2.1.30", "2.1.29"), true);
52
- assert.equal(isNewerVersion("2.2.0", "2.1.29"), true);
53
- assert.equal(isNewerVersion("3.0.0", "2.1.29"), true);
54
- });
55
- test("should return false for equal or older versions", () => {
56
- assert.equal(isNewerVersion("2.1.29", "2.1.29"), false);
57
- assert.equal(isNewerVersion("2.1.28", "2.1.29"), false);
58
- assert.equal(isNewerVersion("1.9.9", "2.0.0"), false);
59
- });
60
- test("should return false for malformed versions", () => {
61
- assert.equal(isNewerVersion("unknown", "2.1.29"), false);
62
- assert.equal(isNewerVersion("2.1.30", "unknown"), false);
63
- });
64
- });
65
- describe("When checkForNewVersion runs", () => {
66
- test("should return the latest version when the registry has a newer one", async () => {
67
- const latest = await checkForNewVersion("2.1.29", fetchReturning(200, { version: "2.1.31" }));
68
- assert.equal(latest, "2.1.31");
69
- });
70
- test("should return null when already on the latest version", async () => {
71
- const latest = await checkForNewVersion("2.1.31", fetchReturning(200, { version: "2.1.31" }));
72
- assert.equal(latest, null);
73
- });
74
- test("should return null without fetching when current version is unknown", async () => {
75
- const calls = [];
76
- assert.equal(await checkForNewVersion("unknown", fetchCapturing(calls, 200, {})), null);
77
- assert.equal(calls.length, 0);
78
- });
79
- test("should return null on registry errors instead of throwing", async () => {
80
- const failingFetch = async () => {
81
- throw new Error("network down");
82
- };
83
- assert.equal(await checkForNewVersion("2.1.29", failingFetch), null);
84
- assert.equal(await checkForNewVersion("2.1.29", fetchReturning(500, {})), null);
85
- });
86
- test("should return null when the registry returns a non-release version", async () => {
87
- const latest = await checkForNewVersion("2.1.29", fetchReturning(200, { version: "2.2.0-beta.1" }));
88
- assert.equal(latest, null);
89
- });
90
- });
91
- describe("When fetchLatestVersion resolves the registry", () => {
92
- test("should fetch from the resolved registry instead of a hardcoded one", async () => {
93
- const calls = [];
94
- const latest = await fetchLatestVersion(fetchCapturing(calls, 200, { version: "2.1.31" }), 3000, () => "https://npm.internal.example/");
95
- assert.equal(latest, "2.1.31");
96
- assert.equal(calls[0]?.url, "https://npm.internal.example/@zereight/mcp-gitlab/latest");
97
- });
98
- test("should fall back to the public registry when the resolver throws", async () => {
99
- const calls = [];
100
- const latest = await fetchLatestVersion(fetchCapturing(calls, 200, { version: "2.1.31" }), 3000, throwingResolver);
101
- assert.equal(latest, null);
102
- assert.equal(calls.length, 0);
103
- });
104
- test("should fall back to the public registry when the resolved value has no hostname", async () => {
105
- const calls = [];
106
- const latest = await fetchLatestVersion(fetchCapturing(calls, 200, { version: "2.1.31" }), 3000, () => "https:///");
107
- assert.equal(latest, "2.1.31");
108
- assert.equal(calls[0]?.url, "https://registry.npmjs.org/@zereight/mcp-gitlab/latest");
109
- });
110
- test("should fall back to the public registry when the resolved value has a query string", async () => {
111
- const calls = [];
112
- const latest = await fetchLatestVersion(fetchCapturing(calls, 200, { version: "2.1.31" }), 3000, () => "https://registry.example/npm?tenant=a");
113
- assert.equal(latest, "2.1.31");
114
- assert.equal(calls[0]?.url, "https://registry.npmjs.org/@zereight/mcp-gitlab/latest");
115
- });
116
- });
117
- describe("When resolving the registry from npm config", () => {
118
- describe("with a scoped @zereight:registry", () => {
119
- test("should prefer the scoped registry over the unscoped one", async () => {
120
- await withNpmrc("registry=https://unscoped.example/\n@zereight:registry=https://scoped.example/\n", async () => {
121
- const calls = [];
122
- const latest = await fetchLatestVersion(fetchCapturing(calls, 200, { version: "2.1.31" }));
123
- assert.equal(latest, "2.1.31");
124
- assert.equal(calls[0]?.url, "https://scoped.example/@zereight/mcp-gitlab/latest");
125
- });
126
- });
127
- });
128
- });
129
- describe("When fetchLatestVersion authenticates against a protected registry", () => {
130
- describe("with a scoped _authToken in .npmrc", () => {
131
- test("should send a Bearer token only to that HTTPS registry", async () => {
132
- await withNpmrc([
133
- "registry=https://unscoped.example/",
134
- "@zereight:registry=https://scoped.example/",
135
- "//scoped.example/:_authToken=scoped-registry-token",
136
- ].join("\n"), async () => {
137
- const calls = [];
138
- const latest = await fetchLatestVersion(fetchCapturing(calls, 200, { version: "2.1.31" }));
139
- assert.equal(latest, "2.1.31");
140
- assert.equal(calls[0]?.url, "https://scoped.example/@zereight/mcp-gitlab/latest");
141
- assert.equal(calls[0]?.authorization, "Bearer scoped-registry-token");
142
- });
143
- });
144
- test("should refuse to follow redirects when a token is attached", async () => {
145
- await withNpmrc([
146
- "@zereight:registry=https://scoped.example/",
147
- "//scoped.example/:_authToken=scoped-registry-token",
148
- ].join("\n"), async () => {
149
- const calls = [];
150
- await fetchLatestVersion(fetchCapturing(calls, 200, { version: "2.1.31" }));
151
- assert.equal(calls[0]?.redirect, "error");
152
- });
153
- });
154
- });
155
- describe("without a matching token", () => {
156
- test("should omit Authorization", async () => {
157
- await withNpmrc("@zereight:registry=https://scoped.example/\n", async () => {
158
- const calls = [];
159
- await fetchLatestVersion(fetchCapturing(calls, 200, { version: "2.1.31" }));
160
- assert.equal(calls[0]?.authorization, null);
161
- assert.equal(calls[0]?.redirect, undefined);
162
- });
163
- });
164
- });
165
- describe("when the token is on a parent registry path", () => {
166
- test("should fall back to the host-level _authToken", async () => {
167
- await withNpmrc([
168
- "@zereight:registry=https://scoped.example/npm",
169
- "//scoped.example/:_authToken=host-level-token",
170
- ].join("\n"), async () => {
171
- const calls = [];
172
- const latest = await fetchLatestVersion(fetchCapturing(calls, 200, { version: "2.1.31" }));
173
- assert.equal(latest, "2.1.31");
174
- assert.equal(calls[0]?.url, "https://scoped.example/npm/@zereight/mcp-gitlab/latest");
175
- assert.equal(calls[0]?.authorization, "Bearer host-level-token");
176
- });
177
- });
178
- });
179
- describe("when both a path token and a host token exist", () => {
180
- test("should prefer the more specific path token", async () => {
181
- await withNpmrc([
182
- "@zereight:registry=https://scoped.example/npm",
183
- "//scoped.example/npm/:_authToken=path-token",
184
- "//scoped.example/:_authToken=host-level-token",
185
- ].join("\n"), async () => {
186
- const calls = [];
187
- await fetchLatestVersion(fetchCapturing(calls, 200, { version: "2.1.31" }));
188
- assert.equal(calls[0]?.authorization, "Bearer path-token");
189
- });
190
- });
191
- });
192
- describe("when the token is only in the global npmrc", () => {
193
- test("should send a Bearer token from globalconfig", async () => {
194
- await withNpmrc("@zereight:registry=https://scoped.example/\n", async () => {
195
- const calls = [];
196
- await fetchLatestVersion(fetchCapturing(calls, 200, { version: "2.1.31" }));
197
- assert.equal(calls[0]?.authorization, "Bearer global-registry-token");
198
- }, "//scoped.example/:_authToken=global-registry-token\n");
199
- });
200
- });
201
- describe("when the registry path contains underscores", () => {
202
- test("should send a Bearer token for an Azure Artifacts-style registry path", async () => {
203
- const registry = "https://pkgs.dev.azure.com/org/_packaging/feed/npm/registry";
204
- const authKey = "//pkgs.dev.azure.com/org/_packaging/feed/npm/registry/:_authToken";
205
- await withNpmrc([`@zereight:registry=${registry}`, `${authKey}=azure-feed-token`].join("\n"), async () => {
206
- const calls = [];
207
- const latest = await fetchLatestVersion(fetchCapturing(calls, 200, { version: "2.1.31" }));
208
- assert.equal(latest, "2.1.31");
209
- assert.equal(calls[0]?.url, `${registry}/@zereight/mcp-gitlab/latest`);
210
- assert.equal(calls[0]?.authorization, "Bearer azure-feed-token");
211
- });
212
- });
213
- });
214
- describe("with a token for a different host", () => {
215
- test("should omit Authorization", async () => {
216
- await withNpmrc([
217
- "@zereight:registry=https://scoped.example/",
218
- "//other.example/:_authToken=other-host-token",
219
- ].join("\n"), async () => {
220
- const calls = [];
221
- await fetchLatestVersion(fetchCapturing(calls, 200, { version: "2.1.31" }));
222
- assert.equal(calls[0]?.authorization, null);
223
- });
224
- });
225
- });
226
- describe("when the registry is HTTP", () => {
227
- test("should omit Authorization even if a token is configured", async () => {
228
- await withNpmrc([
229
- "@zereight:registry=http://insecure.example/",
230
- "//insecure.example/:_authToken=http-registry-token",
231
- ].join("\n"), async () => {
232
- const calls = [];
233
- await fetchLatestVersion(fetchCapturing(calls, 200, { version: "2.1.31" }));
234
- assert.equal(calls[0]?.url, "http://insecure.example/@zereight/mcp-gitlab/latest");
235
- assert.equal(calls[0]?.authorization, null);
236
- });
237
- });
238
- });
239
- });
@@ -1,21 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { describe, test } from "node:test";
3
- import { resolveNestedWikiUpdateTitle } from "../../utils/wiki-title.js";
4
- describe("When resolveNestedWikiUpdateTitle runs", () => {
5
- describe("with nested wiki slugs", () => {
6
- test("should prefix leaf titles using the existing hierarchical title", () => {
7
- assert.equal(resolveNestedWikiUpdateTitle("00-map/infra-servers", "infra servers v2", "00-map/infra servers"), "00-map/infra servers v2");
8
- });
9
- test("should prefix leaf titles using the slug parent when the existing title is flat", () => {
10
- assert.equal(resolveNestedWikiUpdateTitle("00-map/infra-servers", "infra servers v2", "infra servers"), "00-map/infra servers v2");
11
- });
12
- test("should keep full hierarchical titles unchanged", () => {
13
- assert.equal(resolveNestedWikiUpdateTitle("00-map/infra-servers", "00-map/infra servers v2", "00-map/infra servers"), "00-map/infra servers v2");
14
- });
15
- });
16
- describe("with flat wiki slugs", () => {
17
- test("should keep leaf titles unchanged", () => {
18
- assert.equal(resolveNestedWikiUpdateTitle("infra-servers", "infra servers v2", "infra servers"), "infra servers v2");
19
- });
20
- });
21
- });
@@ -1,54 +0,0 @@
1
- /**
2
- * This test file verifies that the createNote function works correctly
3
- * with the fixed endpoint URL construction that uses plural resource names
4
- * (issues instead of issue, merge_requests instead of merge_request).
5
- */
6
- import { encodeGitLabPathSegment } from "./utils/url.js";
7
- // GitLab API configuration (replace with actual values when testing)
8
- const GITLAB_API_URL = process.env.GITLAB_API_URL || "https://gitlab.com";
9
- const GITLAB_PERSONAL_ACCESS_TOKEN = process.env.GITLAB_TOKEN || "";
10
- const PROJECT_ID = process.env.PROJECT_ID || "your/project";
11
- const ISSUE_IID = Number(process.env.ISSUE_IID || "1");
12
- async function testCreateIssueNote() {
13
- try {
14
- // Using plural form "issues" in the URL
15
- const url = new URL(`${GITLAB_API_URL}/api/v4/projects/${encodeGitLabPathSegment(PROJECT_ID)}/issues/${ISSUE_IID}/notes`);
16
- const response = await fetch(url.toString(), {
17
- method: "POST",
18
- headers: {
19
- Accept: "application/json",
20
- "Content-Type": "application/json",
21
- Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`,
22
- },
23
- body: JSON.stringify({ body: "Test note from API - with plural endpoint" }),
24
- });
25
- if (!response.ok) {
26
- const errorBody = await response.text();
27
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`);
28
- }
29
- const data = await response.json();
30
- console.log("Successfully created note:");
31
- console.log(JSON.stringify(data, null, 2));
32
- return true;
33
- }
34
- catch (error) {
35
- console.error("Error creating note:", error);
36
- return false;
37
- }
38
- }
39
- // Only run the test if executed directly
40
- if (require.main === module) {
41
- console.log("Testing note creation with plural 'issues' endpoint...");
42
- testCreateIssueNote().then(success => {
43
- if (success) {
44
- console.log("✅ Test successful!");
45
- process.exit(0);
46
- }
47
- else {
48
- console.log("❌ Test failed!");
49
- process.exit(1);
50
- }
51
- });
52
- }
53
- // Export for use in other tests
54
- export { testCreateIssueNote };
@@ -1,127 +0,0 @@
1
- /**
2
- * This test file demonstrates the new resolve functionality for issue notes.
3
- * It shows how to use the update_issue_note tool to resolve or unresolve
4
- * issue discussion threads.
5
- */
6
- import { encodeGitLabPathSegment } from "./utils/url.js";
7
- // GitLab API configuration (replace with actual values when testing)
8
- const GITLAB_API_URL = process.env.GITLAB_API_URL || "https://gitlab.com";
9
- const GITLAB_PERSONAL_ACCESS_TOKEN = process.env.GITLAB_TOKEN || "";
10
- const PROJECT_ID = process.env.PROJECT_ID || "your/project";
11
- const ISSUE_IID = Number(process.env.ISSUE_IID || "1");
12
- const DISCUSSION_ID = process.env.DISCUSSION_ID || "your-discussion-id";
13
- const NOTE_ID = process.env.NOTE_ID || "your-note-id";
14
- /**
15
- * Test resolving an issue note
16
- */
17
- async function testResolveIssueNote() {
18
- try {
19
- const url = new URL(`${GITLAB_API_URL}/api/v4/projects/${encodeGitLabPathSegment(PROJECT_ID)}/issues/${ISSUE_IID}/discussions/${DISCUSSION_ID}/notes/${NOTE_ID}`);
20
- const response = await fetch(url.toString(), {
21
- method: "PUT",
22
- headers: {
23
- Accept: "application/json",
24
- "Content-Type": "application/json",
25
- Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`,
26
- },
27
- body: JSON.stringify({ resolved: true }),
28
- });
29
- if (!response.ok) {
30
- const errorBody = await response.text();
31
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`);
32
- }
33
- const data = await response.json();
34
- console.log("Successfully resolved issue note:");
35
- console.log(JSON.stringify(data, null, 2));
36
- return true;
37
- }
38
- catch (error) {
39
- console.error("Error resolving issue note:", error);
40
- return false;
41
- }
42
- }
43
- /**
44
- * Test unresolving an issue note
45
- */
46
- async function testUnresolveIssueNote() {
47
- try {
48
- const url = new URL(`${GITLAB_API_URL}/api/v4/projects/${encodeGitLabPathSegment(PROJECT_ID)}/issues/${ISSUE_IID}/discussions/${DISCUSSION_ID}/notes/${NOTE_ID}`);
49
- const response = await fetch(url.toString(), {
50
- method: "PUT",
51
- headers: {
52
- Accept: "application/json",
53
- "Content-Type": "application/json",
54
- Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`,
55
- },
56
- body: JSON.stringify({ resolved: false }),
57
- });
58
- if (!response.ok) {
59
- const errorBody = await response.text();
60
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`);
61
- }
62
- const data = await response.json();
63
- console.log("Successfully unresolved issue note:");
64
- console.log(JSON.stringify(data, null, 2));
65
- return true;
66
- }
67
- catch (error) {
68
- console.error("Error unresolving issue note:", error);
69
- return false;
70
- }
71
- }
72
- /**
73
- * Test updating note body (existing functionality should still work)
74
- */
75
- async function testUpdateIssueNoteBody() {
76
- try {
77
- const url = new URL(`${GITLAB_API_URL}/api/v4/projects/${encodeGitLabPathSegment(PROJECT_ID)}/issues/${ISSUE_IID}/discussions/${DISCUSSION_ID}/notes/${NOTE_ID}`);
78
- const response = await fetch(url.toString(), {
79
- method: "PUT",
80
- headers: {
81
- Accept: "application/json",
82
- "Content-Type": "application/json",
83
- Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`,
84
- },
85
- body: JSON.stringify({ body: "Updated note content" }),
86
- });
87
- if (!response.ok) {
88
- const errorBody = await response.text();
89
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`);
90
- }
91
- const data = await response.json();
92
- console.log("Successfully updated issue note body:");
93
- console.log(JSON.stringify(data, null, 2));
94
- return true;
95
- }
96
- catch (error) {
97
- console.error("Error updating issue note body:", error);
98
- return false;
99
- }
100
- }
101
- // Only run the test if executed directly
102
- if (require.main === module) {
103
- console.log("Testing issue note resolve functionality...\n");
104
- console.log("Note: This is a demonstration test file.");
105
- console.log("To run actual tests, set the following environment variables:");
106
- console.log(" - GITLAB_API_URL");
107
- console.log(" - GITLAB_TOKEN");
108
- console.log(" - PROJECT_ID");
109
- console.log(" - ISSUE_IID");
110
- console.log(" - DISCUSSION_ID");
111
- console.log(" - NOTE_ID");
112
- console.log("\nExample MCP tool usage:");
113
- console.log(`
114
- {
115
- "name": "update_issue_note",
116
- "arguments": {
117
- "project_id": "your/project",
118
- "issue_iid": "1",
119
- "discussion_id": "abc123",
120
- "note_id": "456",
121
- "resolved": true
122
- }
123
- }
124
- `);
125
- }
126
- // Export for use in other tests
127
- export { testResolveIssueNote, testUnresolveIssueNote, testUpdateIssueNoteBody };