@zereight/mcp-gitlab 2.1.43 → 2.1.44

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.
package/README.ko.md CHANGED
@@ -80,7 +80,7 @@ npm install -g @zereight/mcp-gitlab
80
80
 
81
81
  예시는 기존 `mcp-gitlab`보다 충돌 가능성이 낮은 `zereight-mcp-gitlab` 별칭을 사용합니다. MCP 클라이언트가 찾지 못하면 `which zereight-mcp-gitlab`의 절대 경로를 사용하세요.
82
82
 
83
- 전역 설치를 쓰지 않으려면 `npx -y @zereight/mcp-gitlab@2.1.42`처럼 직전 안정 버전(문서가 권장하는 버전)으로 고정하세요. 항상 최신 버전을 원하면 `npx -y @zereight/mcp-gitlab@latest`를 사용하세요. 새 버전이 나오면 서버가 시작 시 stderr로 알려줍니다(`GITLAB_DISABLE_VERSION_CHECK=true`로 비활성화 가능).
83
+ 전역 설치를 쓰지 않으려면 `npx -y @zereight/mcp-gitlab@2.1.43`처럼 직전 안정 버전(문서가 권장하는 버전)으로 고정하세요. 항상 최신 버전을 원하면 `npx -y @zereight/mcp-gitlab@latest`를 사용하세요. 새 버전이 나오면 서버가 시작 시 stderr로 알려줍니다(`GITLAB_DISABLE_VERSION_CHECK=true`로 비활성화 가능).
84
84
 
85
85
  #### CLI 인자 사용하기(환경 변수 문제가 있는 클라이언트용)
86
86
 
package/README.md CHANGED
@@ -82,7 +82,7 @@ npm install -g @zereight/mcp-gitlab
82
82
 
83
83
  The examples use `zereight-mcp-gitlab`, a less collision-prone alias for the legacy `mcp-gitlab` binary. If your MCP client cannot find it, use the absolute path from `which zereight-mcp-gitlab`.
84
84
 
85
- No global install? Pin `npx` to the previous stable release (the version these docs recommend), for example `npx -y @zereight/mcp-gitlab@2.1.42`. If you always want the newest release, use `npx -y @zereight/mcp-gitlab@latest` instead. The server prints a notice to stderr on startup when a newer version is available (disable with `GITLAB_DISABLE_VERSION_CHECK=true`).
85
+ No global install? Pin `npx` to the previous stable release (the version these docs recommend), for example `npx -y @zereight/mcp-gitlab@2.1.43`. If you always want the newest release, use `npx -y @zereight/mcp-gitlab@latest` instead. The server prints a notice to stderr on startup when a newer version is available (disable with `GITLAB_DISABLE_VERSION_CHECK=true`).
86
86
 
87
87
  #### Using CLI Arguments (for clients with env var issues)
88
88
 
@@ -700,7 +700,7 @@ Register the skill directory in your AI client to get optimal tool usage guidanc
700
700
  156. `list_group_iterations` - List group iterations with filtering options
701
701
  157. `upload_markdown` - Upload a file to a GitLab project for use in markdown content
702
702
  158. `download_attachment` - Download an uploaded file from a GitLab project by secret and filename
703
- 159. `health_check` - Verify server status and authentication
703
+ 159. `health_check` - Verify server status and authentication; when authenticated, reports GitLab instance version from `/api/v4/version` (`version`, `revision`, `enterprise`)
704
704
  160. `list_events` - List all events for the currently authenticated user
705
705
  161. `get_project_events` - List all visible events for a specified project
706
706
  162. `list_releases` - List all releases for a project
package/README.zh-CN.md CHANGED
@@ -80,7 +80,7 @@ npm install -g @zereight/mcp-gitlab
80
80
 
81
81
  示例使用 `zereight-mcp-gitlab`,这是比旧的 `mcp-gitlab` 更不容易冲突的别名。如果 MCP 客户端找不到它,请使用 `which zereight-mcp-gitlab` 输出的绝对路径。
82
82
 
83
- 如果不想全局安装,请将 `npx` 固定到上一个稳定版本(即文档推荐的版本),例如 `npx -y @zereight/mcp-gitlab@2.1.42`。如果始终想使用最新版本,请改用 `npx -y @zereight/mcp-gitlab@latest`。有新版本发布时,服务器会在启动时通过 stderr 提示(可用 `GITLAB_DISABLE_VERSION_CHECK=true` 关闭)。
83
+ 如果不想全局安装,请将 `npx` 固定到上一个稳定版本(即文档推荐的版本),例如 `npx -y @zereight/mcp-gitlab@2.1.43`。如果始终想使用最新版本,请改用 `npx -y @zereight/mcp-gitlab@latest`。有新版本发布时,服务器会在启动时通过 stderr 提示(可用 `GITLAB_DISABLE_VERSION_CHECK=true` 关闭)。
84
84
 
85
85
  #### 使用 CLI 参数(适用于环境变量有问题的客户端)
86
86
 
package/build/index.js CHANGED
@@ -91,6 +91,7 @@ import { resolveNestedWikiUpdateTitle } from "./utils/wiki-title.js";
91
91
  import { redactSensitiveGitLabFields } from "./utils/redact-sensitive.js";
92
92
  import { checkForNewVersion } from "./utils/version-check.js";
93
93
  import { assertGitLabVersionAtLeast } from "./utils/gitlab-version-gate.js";
94
+ import { parseGitLabVersionApiResponse, } from "./utils/gitlab-instance-version.js";
94
95
  import { buildBulkPublishDraftNotesBody, needsGitLab19_2BulkPublish, } from "./utils/bulk-publish-options.js";
95
96
  import { cleanMutuallyExclusiveIdUsernameOptions, LIST_MERGE_REQUESTS_ID_USERNAME_PAIRS, sanitizeToolArguments, } from "./utils/tool-args.js";
96
97
  import { parseSearchReplaceBlocks, applySearchReplace, applyUnifiedDiff, } from "./utils/patch-helper.js";
@@ -4391,7 +4392,7 @@ async function publishDraftNote(projectId, mergeRequestIid, draftNoteId) {
4391
4392
  };
4392
4393
  }
4393
4394
  }
4394
- async function fetchGitLabInstanceVersion() {
4395
+ async function fetchGitLabVersionMetadata() {
4395
4396
  try {
4396
4397
  const response = await fetch(`${getEffectiveApiUrl()}/version`, {
4397
4398
  ...getFetchConfig(),
@@ -4399,18 +4400,16 @@ async function fetchGitLabInstanceVersion() {
4399
4400
  if (!response.ok)
4400
4401
  return null;
4401
4402
  const data = await response.json();
4402
- if (typeof data === "object" &&
4403
- data !== null &&
4404
- "version" in data &&
4405
- typeof data.version === "string") {
4406
- return data.version;
4407
- }
4408
- return null;
4403
+ return parseGitLabVersionApiResponse(data);
4409
4404
  }
4410
4405
  catch {
4411
4406
  return null;
4412
4407
  }
4413
4408
  }
4409
+ async function fetchGitLabInstanceVersion() {
4410
+ const metadata = await fetchGitLabVersionMetadata();
4411
+ return metadata?.version ?? null;
4412
+ }
4414
4413
  /**
4415
4414
  * Publish all draft notes for a merge request
4416
4415
  * @param {string} projectId - The ID or URL-encoded path of the project
@@ -9339,6 +9338,9 @@ async function handleToolCall(params) {
9339
9338
  const jobResponse = await fetch(jobUrl.toString(), getFetchConfig());
9340
9339
  authenticated = jobResponse.ok;
9341
9340
  }
9341
+ const versionMetadata = authenticated
9342
+ ? await fetchGitLabVersionMetadata()
9343
+ : null;
9342
9344
  return {
9343
9345
  content: [
9344
9346
  {
@@ -9347,6 +9349,7 @@ async function handleToolCall(params) {
9347
9349
  status: authenticated ? "ok" : "error",
9348
9350
  authenticated,
9349
9351
  gitlab_url: getEffectiveApiUrl(),
9352
+ ...(versionMetadata ?? {}),
9350
9353
  }),
9351
9354
  },
9352
9355
  ],
@@ -0,0 +1,106 @@
1
+ import { describe, test } from "node:test";
2
+ import assert from "node:assert";
3
+ import { spawn } from "child_process";
4
+ import { MockGitLabServer, findMockServerPort } from "./utils/mock-gitlab-server.js";
5
+ const MOCK_TOKEN = "glpat-mock-token-12345";
6
+ function createMockGitLabServer(port) {
7
+ return new MockGitLabServer({
8
+ port,
9
+ validTokens: [MOCK_TOKEN],
10
+ });
11
+ }
12
+ function baseEnv(mockGitLabUrl) {
13
+ return {
14
+ GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
15
+ GITLAB_PERSONAL_ACCESS_TOKEN: MOCK_TOKEN,
16
+ GITLAB_TOOLSETS: "projects",
17
+ };
18
+ }
19
+ async function callHealthCheckAsync(env) {
20
+ return new Promise((resolve, reject) => {
21
+ const proc = spawn("node", ["build/index.js"], {
22
+ stdio: ["pipe", "pipe", "pipe"],
23
+ env: {
24
+ ...process.env,
25
+ ...env,
26
+ USE_PIPELINE: "true",
27
+ },
28
+ });
29
+ let output = "";
30
+ let errorOutput = "";
31
+ proc.stdout?.on("data", (chunk) => (output += chunk));
32
+ proc.stderr?.on("data", (chunk) => (errorOutput += chunk));
33
+ proc.on("close", code => {
34
+ if (code !== 0) {
35
+ return reject(new Error(`Process exited with code ${code}: ${errorOutput}`));
36
+ }
37
+ const line = output.split("\n").find(entry => entry.startsWith("{"));
38
+ if (!line) {
39
+ return reject(new Error("No JSON output found"));
40
+ }
41
+ try {
42
+ const response = JSON.parse(line);
43
+ if (response.error) {
44
+ reject(response.error);
45
+ return;
46
+ }
47
+ const content = response.result?.content?.[0]?.text;
48
+ if (!content) {
49
+ reject(new Error("No tool result content"));
50
+ return;
51
+ }
52
+ resolve(JSON.parse(content));
53
+ }
54
+ catch (error) {
55
+ reject(error);
56
+ }
57
+ });
58
+ proc.stdin?.end(JSON.stringify({
59
+ jsonrpc: "2.0",
60
+ id: 1,
61
+ method: "tools/call",
62
+ params: { name: "health_check", arguments: {} },
63
+ }) + "\n");
64
+ });
65
+ }
66
+ describe("When health_check runs", () => {
67
+ describe("with authenticated mock GitLab", () => {
68
+ test("should include GitLab instance version metadata", async () => {
69
+ const mockPort = await findMockServerPort();
70
+ const mockGitLab = createMockGitLabServer(mockPort);
71
+ await mockGitLab.start();
72
+ try {
73
+ const result = await callHealthCheckAsync(baseEnv(mockGitLab.getUrl()));
74
+ assert.equal(result.status, "ok");
75
+ assert.equal(result.authenticated, true);
76
+ assert.equal(result.version, "18.3.1-ee");
77
+ assert.equal(result.revision, "abc1234");
78
+ assert.equal(result.enterprise, true);
79
+ }
80
+ finally {
81
+ await mockGitLab.stop();
82
+ }
83
+ });
84
+ });
85
+ describe("when GitLab /version lookup fails", () => {
86
+ test("should keep health check ok without version fields", async () => {
87
+ const mockPort = await findMockServerPort();
88
+ const mockGitLab = createMockGitLabServer(mockPort);
89
+ mockGitLab.addMockHandler("get", "/version", (_req, res) => {
90
+ res.status(500).json({ message: "version unavailable" });
91
+ });
92
+ await mockGitLab.start();
93
+ try {
94
+ const result = await callHealthCheckAsync(baseEnv(mockGitLab.getUrl()));
95
+ assert.equal(result.status, "ok");
96
+ assert.equal(result.authenticated, true);
97
+ assert.equal("version" in result, false);
98
+ assert.equal("revision" in result, false);
99
+ assert.equal("enterprise" in result, false);
100
+ }
101
+ finally {
102
+ await mockGitLab.stop();
103
+ }
104
+ });
105
+ });
106
+ });
@@ -0,0 +1,32 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, test } from "node:test";
3
+ import { parseGitLabVersionApiResponse } from "../../utils/gitlab-instance-version.js";
4
+ describe("When parseGitLabVersionApiResponse runs", () => {
5
+ describe("with a full GitLab /version payload", () => {
6
+ test("should return version, revision, and enterprise", () => {
7
+ assert.deepEqual(parseGitLabVersionApiResponse({
8
+ version: "18.3.1-ee",
9
+ revision: "abc1234",
10
+ enterprise: true,
11
+ }), {
12
+ version: "18.3.1-ee",
13
+ revision: "abc1234",
14
+ enterprise: true,
15
+ });
16
+ });
17
+ });
18
+ describe("with only a version field", () => {
19
+ test("should return version without optional fields", () => {
20
+ assert.deepEqual(parseGitLabVersionApiResponse({ version: "17.5.0" }), {
21
+ version: "17.5.0",
22
+ });
23
+ });
24
+ });
25
+ describe("with invalid payloads", () => {
26
+ test("should return null", () => {
27
+ assert.equal(parseGitLabVersionApiResponse(null), null);
28
+ assert.equal(parseGitLabVersionApiResponse({}), null);
29
+ assert.equal(parseGitLabVersionApiResponse({ version: 18 }), null);
30
+ });
31
+ });
32
+ });
@@ -150,6 +150,14 @@ export class MockGitLabServer {
150
150
  state: "active",
151
151
  });
152
152
  });
153
+ // GET /api/v4/version - GitLab instance version
154
+ this.app.get("/api/v4/version", (_req, res) => {
155
+ res.json({
156
+ version: "18.3.1-ee",
157
+ revision: "abc1234",
158
+ enterprise: true,
159
+ });
160
+ });
153
161
  // GET /api/v4/projects/:projectId - Get project
154
162
  this.app.get("/api/v4/projects/:projectId", (req, res) => {
155
163
  const projectId = req.params.projectId;
@@ -813,7 +813,7 @@ export const allTools = [
813
813
  },
814
814
  {
815
815
  name: "health_check",
816
- description: "Verify server status and authentication",
816
+ description: "Verify server status and authentication. When authenticated, also reports the GitLab instance version from GET /api/v4/version (version, revision, enterprise). Version lookup failures do not fail the health check — those fields are omitted.",
817
817
  inputSchema: toJSONSchema(HealthCheckSchema),
818
818
  },
819
819
  {
@@ -0,0 +1,16 @@
1
+ export function parseGitLabVersionApiResponse(data) {
2
+ if (typeof data !== "object" || data === null || !("version" in data)) {
3
+ return null;
4
+ }
5
+ if (typeof data.version !== "string") {
6
+ return null;
7
+ }
8
+ const metadata = { version: data.version };
9
+ if ("revision" in data && typeof data.revision === "string") {
10
+ metadata.revision = data.revision;
11
+ }
12
+ if ("enterprise" in data && typeof data.enterprise === "boolean") {
13
+ metadata.enterprise = data.enterprise;
14
+ }
15
+ return metadata;
16
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zereight/mcp-gitlab",
3
- "version": "2.1.43",
3
+ "version": "2.1.44",
4
4
  "mcpName": "io.github.zereight/gitlab-mcp",
5
5
  "description": "GitLab MCP server for projects, merge requests, issues, pipelines, wiki, releases, and more",
6
6
  "keywords": [