@zereight/mcp-gitlab 2.1.28 → 2.1.30
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 +82 -28
- package/README.md +56 -23
- package/README.zh-CN.md +82 -28
- package/build/config.js +13 -0
- package/build/downloads/proxy.js +199 -0
- package/build/index.js +564 -482
- package/build/oauth-proxy.js +18 -4
- package/build/schemas.js +47 -9
- package/build/scripts/check-skill-sync.js +137 -0
- package/build/scripts/generate-tool-docs.js +7 -2
- package/build/server/metrics.js +84 -0
- package/build/server/transport-mode.js +25 -0
- package/build/test/callback-proxy-tests.js +1 -1
- package/build/test/client-pool-test.js +1 -1
- package/build/test/dynamic-api-url-allowlist.test.js +2 -2
- package/build/test/dynamic-api-url-test.js +4 -4
- package/build/test/dynamic-routing-tests.js +4 -4
- package/build/test/mcp-oauth-tests.js +17 -4
- package/build/test/multi-server-test.js +3 -3
- package/build/test/no-proxy-integration-test.js +1 -1
- package/build/test/path-segment-encoding.test.js +27 -0
- package/build/test/remote-auth-simple-test.js +226 -199
- package/build/test/server/metrics.test.js +47 -0
- package/build/test/sse-auth-guard.test.js +2 -2
- package/build/test/stateless/callback-proxy.test.js +8 -0
- package/build/test/stateless/session-id-integration.test.js +3 -3
- package/build/test/streamable-http-concurrent-session.test.js +1 -1
- package/build/test/streamable-http-dns-rebinding.test.js +185 -0
- package/build/test/streamable-http-static-token-auth.test.js +25 -22
- package/build/test/streamable-http-unauthenticated-discovery.test.js +28 -15
- package/build/test/test-ci-catalog.js +1 -1
- package/build/test/test-ci-lint.js +1 -1
- package/build/test/test-ci-variables.js +8 -5
- package/build/test/test-dependency-proxy.js +11 -7
- package/build/test/test-deployment-tools.js +16 -2
- package/build/test/test-download-attachment.js +1 -1
- package/build/test/test-get-file-blame.js +1 -1
- package/build/test/test-geteffectiveprojectid.js +7 -7
- package/build/test/test-issue-description-patch.js +1 -1
- package/build/test/test-job-artifacts.js +1 -1
- package/build/test/test-list-issues.js +1 -1
- package/build/test/test-list-merge-requests.js +1 -1
- package/build/test/test-list-project-members.js +1 -1
- package/build/test/test-merge-request-approval-state-tools.js +1 -1
- package/build/test/test-merge-request-pipelines.js +1 -1
- package/build/test/test-mr-diffs-filter.js +1 -1
- package/build/test/test-mr-file-diffs.js +2 -2
- package/build/test/test-oauth-proxy-rate-limit.js +1 -1
- package/build/test/test-permission-mode.js +215 -0
- package/build/test/test-protected-branches.js +1 -1
- package/build/test/test-remote-downloads.js +2 -2
- package/build/test/test-search-code.js +1 -1
- package/build/test/test-tags.js +1 -1
- package/build/test/test-todos.js +1 -1
- package/build/test/test-token-optimizations.js +3 -3
- package/build/test/test-toolset-filtering.js +1 -1
- package/build/test/test-update-issue-slim.js +141 -0
- package/build/test/test-upload-markdown.js +1 -1
- package/build/test/utils/download-token.test.js +35 -0
- package/build/test/utils/forwarded-public-base-url.test.js +9 -1
- package/build/test/utils/graphql-query.test.js +64 -1
- package/build/test/utils/mock-gitlab-server.js +24 -31
- package/build/test/utils/redact-sensitive.test.js +32 -0
- package/build/test/utils/server-launcher.js +1 -2
- package/build/test/utils/version-check.test.js +52 -0
- package/build/tools/registry.js +30 -5
- package/build/utils/download-token.js +71 -0
- package/build/utils/forwarded-public-base-url.js +22 -0
- package/build/utils/graphql-query.js +109 -12
- package/build/utils/redact-sensitive.js +21 -0
- package/build/utils/version-check.js +40 -0
- package/package.json +5 -4
|
@@ -342,7 +342,16 @@ export class MockGitLabServer {
|
|
|
342
342
|
},
|
|
343
343
|
assignees: [],
|
|
344
344
|
labels: [],
|
|
345
|
-
milestone:
|
|
345
|
+
milestone: issueIid === 100
|
|
346
|
+
? {
|
|
347
|
+
id: 42,
|
|
348
|
+
iid: 7,
|
|
349
|
+
title: "Sprint 42",
|
|
350
|
+
description: "A very long milestone description ".repeat(50),
|
|
351
|
+
state: "active",
|
|
352
|
+
web_url: `https://gitlab.mock/project/${projectId}/milestones/7`,
|
|
353
|
+
}
|
|
354
|
+
: null,
|
|
346
355
|
});
|
|
347
356
|
});
|
|
348
357
|
// PUT /api/v4/projects/:projectId/issues/:issue_iid - Update issue
|
|
@@ -545,11 +554,16 @@ export class MockGitLabServer {
|
|
|
545
554
|
});
|
|
546
555
|
}
|
|
547
556
|
async start() {
|
|
548
|
-
return new Promise(resolve => {
|
|
557
|
+
return new Promise((resolve, reject) => {
|
|
549
558
|
this.server = this.app.listen(this.config.port, "127.0.0.1", () => {
|
|
559
|
+
const address = this.server?.address();
|
|
560
|
+
if (typeof address === "object" && address) {
|
|
561
|
+
this.config.port = address.port;
|
|
562
|
+
}
|
|
550
563
|
console.log(`Mock GitLab API listening on http://127.0.0.1:${this.config.port}`);
|
|
551
564
|
resolve();
|
|
552
565
|
});
|
|
566
|
+
this.server.once("error", reject);
|
|
553
567
|
});
|
|
554
568
|
}
|
|
555
569
|
async stop() {
|
|
@@ -572,38 +586,17 @@ export class MockGitLabServer {
|
|
|
572
586
|
getUrl() {
|
|
573
587
|
return `http://127.0.0.1:${this.config.port}`;
|
|
574
588
|
}
|
|
589
|
+
getPort() {
|
|
590
|
+
return this.config.port;
|
|
591
|
+
}
|
|
575
592
|
}
|
|
576
593
|
/**
|
|
577
|
-
* Helper to find available port for mock server
|
|
594
|
+
* Helper to find available port for mock server (OS-assigned ephemeral port).
|
|
595
|
+
* Returns 0 to signal MockGitLabServer.start() to use OS-assigned port,
|
|
596
|
+
* avoiding TOCTOU race where another test grabs the port before binding.
|
|
578
597
|
*/
|
|
579
|
-
export async function findMockServerPort(
|
|
580
|
-
|
|
581
|
-
const tryPort = async (port, attemptsLeft) => {
|
|
582
|
-
if (attemptsLeft === 0) {
|
|
583
|
-
throw new Error(`Could not find available port after ${maxAttempts} attempts starting from ${basePort}`);
|
|
584
|
-
}
|
|
585
|
-
return new Promise((resolve, reject) => {
|
|
586
|
-
const server = net.createServer();
|
|
587
|
-
server.unref();
|
|
588
|
-
server.on("error", async () => {
|
|
589
|
-
try {
|
|
590
|
-
const nextPort = await tryPort(port + 1, attemptsLeft - 1);
|
|
591
|
-
resolve(nextPort);
|
|
592
|
-
}
|
|
593
|
-
catch (err) {
|
|
594
|
-
reject(err);
|
|
595
|
-
}
|
|
596
|
-
});
|
|
597
|
-
server.listen(port, "127.0.0.1", () => {
|
|
598
|
-
const addr = server.address();
|
|
599
|
-
const actualPort = typeof addr === "object" && addr ? addr.port : port;
|
|
600
|
-
server.close(() => {
|
|
601
|
-
resolve(actualPort);
|
|
602
|
-
});
|
|
603
|
-
});
|
|
604
|
-
});
|
|
605
|
-
};
|
|
606
|
-
return tryPort(basePort, maxAttempts);
|
|
598
|
+
export async function findMockServerPort() {
|
|
599
|
+
return 0;
|
|
607
600
|
}
|
|
608
601
|
/**
|
|
609
602
|
* Reset request counter (useful for rate limit testing)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import { redactSensitiveGitLabFields } from "../../utils/redact-sensitive.js";
|
|
4
|
+
describe("When redactSensitiveGitLabFields runs", () => {
|
|
5
|
+
describe("with a project response containing credentials", () => {
|
|
6
|
+
test("should remove runners_token and import_url while keeping other fields", () => {
|
|
7
|
+
const data = {
|
|
8
|
+
id: 42,
|
|
9
|
+
name: "demo",
|
|
10
|
+
runners_token: "GR1348941secret",
|
|
11
|
+
import_url: "https://user:pass@example.com/repo.git",
|
|
12
|
+
};
|
|
13
|
+
const result = redactSensitiveGitLabFields(data);
|
|
14
|
+
assert.equal("runners_token" in result, false);
|
|
15
|
+
assert.equal("import_url" in result, false);
|
|
16
|
+
assert.equal(result.id, 42);
|
|
17
|
+
assert.equal(result.name, "demo");
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
describe("with a response that has no sensitive fields", () => {
|
|
21
|
+
test("should return the object unchanged", () => {
|
|
22
|
+
const data = { id: 1, name: "safe" };
|
|
23
|
+
assert.deepEqual(redactSensitiveGitLabFields(data), { id: 1, name: "safe" });
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
describe("with a non-object value", () => {
|
|
27
|
+
test("should return the value as-is", () => {
|
|
28
|
+
assert.equal(redactSensitiveGitLabFields(null), null);
|
|
29
|
+
assert.equal(redactSensitiveGitLabFields("raw"), "raw");
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -58,8 +58,7 @@ export async function launchServer(config) {
|
|
|
58
58
|
throw new Error(`${TransportMode.STDIO} mode is not supported for server testing, because it uses process communication.`);
|
|
59
59
|
}
|
|
60
60
|
const serverPath = path.resolve(process.cwd(), "build/index.js");
|
|
61
|
-
console.log("Launcher: Spawning server process
|
|
62
|
-
console.log("Launcher: Spawning server process with env:", serverEnv);
|
|
61
|
+
console.log("Launcher: Spawning server process", { mode, port });
|
|
63
62
|
const serverProcess = spawn("node", [serverPath], {
|
|
64
63
|
env: serverEnv,
|
|
65
64
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import { checkForNewVersion, isNewerVersion } from "../../utils/version-check.js";
|
|
4
|
+
function fetchReturning(status, body) {
|
|
5
|
+
return (async () => new Response(JSON.stringify(body), { status }));
|
|
6
|
+
}
|
|
7
|
+
describe("When isNewerVersion compares versions", () => {
|
|
8
|
+
test("should detect newer patch, minor, and major versions", () => {
|
|
9
|
+
assert.equal(isNewerVersion("2.1.30", "2.1.29"), true);
|
|
10
|
+
assert.equal(isNewerVersion("2.2.0", "2.1.29"), true);
|
|
11
|
+
assert.equal(isNewerVersion("3.0.0", "2.1.29"), true);
|
|
12
|
+
});
|
|
13
|
+
test("should return false for equal or older versions", () => {
|
|
14
|
+
assert.equal(isNewerVersion("2.1.29", "2.1.29"), false);
|
|
15
|
+
assert.equal(isNewerVersion("2.1.28", "2.1.29"), false);
|
|
16
|
+
assert.equal(isNewerVersion("1.9.9", "2.0.0"), false);
|
|
17
|
+
});
|
|
18
|
+
test("should return false for malformed versions", () => {
|
|
19
|
+
assert.equal(isNewerVersion("unknown", "2.1.29"), false);
|
|
20
|
+
assert.equal(isNewerVersion("2.1.30", "unknown"), false);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
describe("When checkForNewVersion runs", () => {
|
|
24
|
+
test("should return the latest version when the registry has a newer one", async () => {
|
|
25
|
+
const latest = await checkForNewVersion("2.1.29", fetchReturning(200, { version: "2.1.31" }));
|
|
26
|
+
assert.equal(latest, "2.1.31");
|
|
27
|
+
});
|
|
28
|
+
test("should return null when already on the latest version", async () => {
|
|
29
|
+
const latest = await checkForNewVersion("2.1.31", fetchReturning(200, { version: "2.1.31" }));
|
|
30
|
+
assert.equal(latest, null);
|
|
31
|
+
});
|
|
32
|
+
test("should return null without fetching when current version is unknown", async () => {
|
|
33
|
+
let called = false;
|
|
34
|
+
const spyFetch = (async () => {
|
|
35
|
+
called = true;
|
|
36
|
+
return new Response("{}", { status: 200 });
|
|
37
|
+
});
|
|
38
|
+
assert.equal(await checkForNewVersion("unknown", spyFetch), null);
|
|
39
|
+
assert.equal(called, false);
|
|
40
|
+
});
|
|
41
|
+
test("should return null on registry errors instead of throwing", async () => {
|
|
42
|
+
const failingFetch = (async () => {
|
|
43
|
+
throw new Error("network down");
|
|
44
|
+
});
|
|
45
|
+
assert.equal(await checkForNewVersion("2.1.29", failingFetch), null);
|
|
46
|
+
assert.equal(await checkForNewVersion("2.1.29", fetchReturning(500, {})), null);
|
|
47
|
+
});
|
|
48
|
+
test("should return null when the registry returns a non-release version", async () => {
|
|
49
|
+
const latest = await checkForNewVersion("2.1.29", fetchReturning(200, { version: "2.2.0-beta.1" }));
|
|
50
|
+
assert.equal(latest, null);
|
|
51
|
+
});
|
|
52
|
+
});
|
package/build/tools/registry.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
2
2
|
import { toJSONSchema } from "../utils/schema.js";
|
|
3
3
|
import { USE_GITLAB_WIKI, USE_MILESTONE, USE_PIPELINE, SSE, STREAMABLE_HTTP, } from "../config.js";
|
|
4
|
-
import { ApproveMergeRequestSchema, BulkPublishDraftNotesSchema, CancelPipelineJobSchema, CancelPipelineSchema, ConvertWorkItemTypeSchema, CreateBranchSchema, CreateDraftNoteSchema, CreateGroupSchema, CreateGroupWikiPageSchema, CreateIssueLinkSchema, CreateIssueNoteSchema, CreateIssueSchema, CreateIssueEmojiReactionSchema, CreateIssueNoteEmojiReactionSchema, ListIssueEmojiReactionsSchema, ListIssueNoteEmojiReactionsSchema, CreateLabelSchema, MarkAllTodosDoneSchema, ListTodosSchema, MarkTodoDoneSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestNoteSchema, CreateMergeRequestNoteEmojiReactionSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateCommitStatusSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateReleaseEvidenceSchema, CreateReleaseSchema, CreateRepositorySchema, CreateTagSchema, CreateTimelineEventSchema, CreateWikiPageSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, CreateWorkItemSchema, DeleteBranchSchema, GetProtectedBranchSchema, ListProtectedBranchesSchema, ProtectBranchSchema, UnprotectBranchSchema, UpdateDefaultBranchSchema, DeleteDraftNoteSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteMergeRequestDiscussionNoteSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, DeleteProjectMilestoneSchema, DeleteReleaseSchema, DeleteTagSchema, DeleteWikiPageSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, DownloadAttachmentSchema, DownloadAttachmentRemoteSchema, DownloadJobArtifactsSchema, DownloadJobArtifactsRemoteSchema, DownloadReleaseAssetSchema, EditProjectMilestoneSchema, ExecuteGraphQLSchema, ForkRepositorySchema, HealthCheckSchema, GetBranchSchema, GetBranchDiffsSchema, GetCommitDiffSchema, GetCommitSchema, GetFileBlameSchema, GetDeploymentSchema, GetDraftNoteSchema, GetEnvironmentSchema, GetFileContentsSchema, GetGroupWikiPageSchema, GetIssueLinkSchema, GetIssueSchema, GetJobArtifactFileSchema, GetLabelSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GetMergeRequestDiffsSchema, GetMergeRequestFileDiffSchema, GetMergeRequestNoteSchema, GetMergeRequestNotesSchema, GetMergeRequestSchema, GetMergeRequestVersionSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetNamespaceSchema, GetPipelineJobOutputSchema, GetPipelineSchema, GetProjectEventsSchema, GetProjectMilestoneSchema, GetProjectSchema, GetReleaseSchema, GetRepositoryTreeSchema, GetTagSchema, GetTagSignatureSchema, GetTimelineEventsSchema, GetUsersSchema, GetUserSchema, WhoAmISchema, GetWebhookEventSchema, GetWikiPageSchema, GetWorkItemSchema, ListBranchesSchema, ListCommitsSchema, ListCommitStatusesSchema, ListCustomFieldDefinitionsSchema, ListDeploymentsSchema, ListDraftNotesSchema, ListEnvironmentsSchema, ListEventsSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, ListGroupWikiPagesSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListJobArtifactsSchema, ListLabelsSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiffsSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestPipelinesSchema, ListMergeRequestVersionsSchema, ListMergeRequestsSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListCiCatalogResourcesSchema, GetCiCatalogResourceSchema, ListPipelinesSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListReleasesSchema, ListTagsSchema, ListWebhookEventsSchema, ListWebhooksSchema, ListWikiPagesSchema, ListWorkItemNotesSchema, ListWorkItemStatusesSchema, ListWorkItemsSchema, MarkdownUploadSchema, MarkdownUploadRemoteSchema, MergeMergeRequestSchema, MoveWorkItemSchema, MyIssuesSchema, PlayPipelineJobSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PushFilesSchema, ResolveMergeRequestThreadSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UnapproveMergeRequestSchema, UpdateDraftNoteSchema, UpdateGroupWikiPageSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateIssueDescriptionPatchSchema, UpdateLabelSchema, UpdateProjectSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestSchema, UpdateReleaseSchema, UpdateWikiPageSchema, UpdateWorkItemSchema, VerifyNamespaceSchema, ListProjectVariablesSchema, GetProjectVariableSchema, CreateProjectVariableSchema, UpdateProjectVariableSchema, DeleteProjectVariableSchema, ListGroupVariablesSchema, GetGroupVariableSchema, CreateGroupVariableSchema, UpdateGroupVariableSchema, DeleteGroupVariableSchema, GetDependencyProxySettingsSchema, UpdateDependencyProxySettingsSchema, ListDependencyProxyBlobsSchema, PurgeDependencyProxyCacheSchema, } from "../schemas.js";
|
|
4
|
+
import { ApproveMergeRequestSchema, BulkPublishDraftNotesSchema, CancelPipelineJobSchema, CancelPipelineSchema, ConvertWorkItemTypeSchema, CreateBranchSchema, CreateDraftNoteSchema, CreateGroupSchema, CreateGroupWikiPageSchema, CreateIssueLinkSchema, CreateIssueNoteSchema, CreateIssueSchema, CreateIssueEmojiReactionSchema, CreateIssueNoteEmojiReactionSchema, ListIssueEmojiReactionsSchema, ListIssueNoteEmojiReactionsSchema, CreateLabelSchema, MarkAllTodosDoneSchema, ListTodosSchema, MarkTodoDoneSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestNoteSchema, CreateMergeRequestNoteEmojiReactionSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateCommitStatusSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateReleaseEvidenceSchema, CreateReleaseSchema, CreateRepositorySchema, CreateTagSchema, CreateTimelineEventSchema, CreateWikiPageSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, CreateWorkItemSchema, DeleteBranchSchema, GetProtectedBranchSchema, ListProtectedBranchesSchema, ProtectBranchSchema, UnprotectBranchSchema, UpdateDefaultBranchSchema, DeleteDraftNoteSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteMergeRequestDiscussionNoteSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, DeleteProjectMilestoneSchema, DeleteReleaseSchema, DeleteTagSchema, DeleteWikiPageSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, DownloadAttachmentSchema, DownloadAttachmentRemoteSchema, DownloadJobArtifactsSchema, DownloadJobArtifactsRemoteSchema, DownloadReleaseAssetSchema, EditProjectMilestoneSchema, ExecuteGraphQLSchema, ForkRepositorySchema, HealthCheckSchema, GetBranchSchema, GetBranchDiffsSchema, GetCommitDiffSchema, GetCommitSchema, GetFileBlameSchema, GetDeploymentSchema, GetDraftNoteSchema, GetEnvironmentSchema, GetFileContentsSchema, GetGroupWikiPageSchema, GetIssueLinkSchema, GetIssueSchema, GetJobArtifactFileSchema, GetLabelSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GetMergeRequestDiffsSchema, GetMergeRequestFileDiffSchema, GetMergeRequestNoteSchema, GetMergeRequestNotesSchema, GetMergeRequestSchema, GetMergeRequestVersionSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetNamespaceSchema, GetPipelineJobOutputSchema, PipelineJobControlSchema, GetPipelineSchema, GetProjectEventsSchema, GetProjectMilestoneSchema, GetProjectSchema, GetReleaseSchema, GetRepositoryTreeSchema, GetTagSchema, GetTagSignatureSchema, GetTimelineEventsSchema, GetUsersSchema, GetUserSchema, WhoAmISchema, GetWebhookEventSchema, GetWikiPageSchema, GetWorkItemSchema, ListBranchesSchema, ListCommitsSchema, ListCommitStatusesSchema, ListCustomFieldDefinitionsSchema, ListDeploymentsSchema, ListDraftNotesSchema, ListEnvironmentsSchema, ListEventsSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, ListGroupWikiPagesSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListJobArtifactsSchema, ListLabelsSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiffsSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestPipelinesSchema, ListMergeRequestVersionsSchema, ListMergeRequestsSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListCiCatalogResourcesSchema, GetCiCatalogResourceSchema, ListPipelinesSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListReleasesSchema, ListTagsSchema, ListWebhookEventsSchema, ListWebhooksSchema, ListWikiPagesSchema, ListWorkItemNotesSchema, ListWorkItemStatusesSchema, ListWorkItemsSchema, MarkdownUploadSchema, MarkdownUploadRemoteSchema, MergeMergeRequestSchema, MoveWorkItemSchema, MyIssuesSchema, PlayPipelineJobSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PushFilesSchema, ResolveMergeRequestThreadSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UnapproveMergeRequestSchema, UpdateDraftNoteSchema, UpdateGroupWikiPageSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateIssueDescriptionPatchSchema, UpdateLabelSchema, UpdateProjectSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestSchema, UpdateReleaseSchema, UpdateWikiPageSchema, UpdateWorkItemSchema, VerifyNamespaceSchema, ListProjectVariablesSchema, GetProjectVariableSchema, CreateProjectVariableSchema, UpdateProjectVariableSchema, DeleteProjectVariableSchema, ListGroupVariablesSchema, GetGroupVariableSchema, CreateGroupVariableSchema, UpdateGroupVariableSchema, DeleteGroupVariableSchema, GetDependencyProxySettingsSchema, UpdateDependencyProxySettingsSchema, ListDependencyProxyBlobsSchema, PurgeDependencyProxyCacheSchema, } from "../schemas.js";
|
|
5
5
|
const IS_REMOTE = SSE || STREAMABLE_HTTP;
|
|
6
6
|
// Define all available tools
|
|
7
7
|
export const allTools = [
|
|
@@ -132,7 +132,7 @@ export const allTools = [
|
|
|
132
132
|
},
|
|
133
133
|
{
|
|
134
134
|
name: "get_merge_request",
|
|
135
|
-
description: "Get details of a merge request (mergeRequestIid or branchName required)",
|
|
135
|
+
description: "Get details of a merge request (mergeRequestIid or branchName required). Set include_summaries=true for deployment/commit/approval summaries",
|
|
136
136
|
inputSchema: toJSONSchema(GetMergeRequestSchema),
|
|
137
137
|
},
|
|
138
138
|
{
|
|
@@ -354,12 +354,12 @@ export const allTools = [
|
|
|
354
354
|
},
|
|
355
355
|
{
|
|
356
356
|
name: "get_issue",
|
|
357
|
-
description: "Get details of a specific issue",
|
|
357
|
+
description: "Get details of a specific issue. Returns a slim milestone by default; set full_response=true for the complete milestone object",
|
|
358
358
|
inputSchema: toJSONSchema(GetIssueSchema),
|
|
359
359
|
},
|
|
360
360
|
{
|
|
361
361
|
name: "update_issue",
|
|
362
|
-
description: "Update an issue",
|
|
362
|
+
description: "Update an issue. Returns a slim confirmation by default; set full_response=true for the complete updated issue object",
|
|
363
363
|
inputSchema: toJSONSchema(UpdateIssueSchema),
|
|
364
364
|
},
|
|
365
365
|
{
|
|
@@ -577,7 +577,7 @@ export const allTools = [
|
|
|
577
577
|
{
|
|
578
578
|
name: "get_pipeline_job",
|
|
579
579
|
description: "Get details of a GitLab pipeline job number",
|
|
580
|
-
inputSchema: toJSONSchema(
|
|
580
|
+
inputSchema: toJSONSchema(PipelineJobControlSchema),
|
|
581
581
|
},
|
|
582
582
|
{
|
|
583
583
|
name: "get_pipeline_job_output",
|
|
@@ -1202,6 +1202,31 @@ export const destructiveTools = new Set([
|
|
|
1202
1202
|
"delete_group_variable",
|
|
1203
1203
|
"purge_dependency_proxy_cache",
|
|
1204
1204
|
]);
|
|
1205
|
+
// Tools that permanently delete resources — blocked in "modify" permission mode.
|
|
1206
|
+
// Narrower than destructiveTools: merge/protect/push are modifications, not deletions.
|
|
1207
|
+
export const deleteTools = new Set([
|
|
1208
|
+
"delete_branch",
|
|
1209
|
+
"delete_draft_note",
|
|
1210
|
+
"delete_group_variable",
|
|
1211
|
+
"delete_group_wiki_page",
|
|
1212
|
+
"delete_issue",
|
|
1213
|
+
"delete_issue_emoji_reaction",
|
|
1214
|
+
"delete_issue_link",
|
|
1215
|
+
"delete_issue_note_emoji_reaction",
|
|
1216
|
+
"delete_label",
|
|
1217
|
+
"delete_merge_request_discussion_note",
|
|
1218
|
+
"delete_merge_request_emoji_reaction",
|
|
1219
|
+
"delete_merge_request_note",
|
|
1220
|
+
"delete_merge_request_note_emoji_reaction",
|
|
1221
|
+
"delete_milestone",
|
|
1222
|
+
"delete_project_variable",
|
|
1223
|
+
"delete_release",
|
|
1224
|
+
"delete_tag",
|
|
1225
|
+
"delete_wiki_page",
|
|
1226
|
+
"delete_work_item_emoji_reaction",
|
|
1227
|
+
"delete_work_item_note_emoji_reaction",
|
|
1228
|
+
"purge_dependency_proxy_cache",
|
|
1229
|
+
]);
|
|
1205
1230
|
// Define which tools are related to wiki and can be toggled by USE_GITLAB_WIKI
|
|
1206
1231
|
export const wikiToolNames = new Set([
|
|
1207
1232
|
"list_wiki_pages",
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Encryption key for download tokens. When DOWNLOAD_TOKEN_SECRET is set
|
|
4
|
+
* (recommended for HA deployments behind a load balancer) the key is
|
|
5
|
+
* derived from that value so all replicas share the same key. Otherwise
|
|
6
|
+
* a random key is generated per process (tokens are not portable across
|
|
7
|
+
* restarts or replicas).
|
|
8
|
+
*/
|
|
9
|
+
const DOWNLOAD_TOKEN_KEY = (() => {
|
|
10
|
+
const secret = process.env.DOWNLOAD_TOKEN_SECRET;
|
|
11
|
+
if (secret) {
|
|
12
|
+
return createHash("sha256").update(secret).digest();
|
|
13
|
+
}
|
|
14
|
+
return randomBytes(32);
|
|
15
|
+
})();
|
|
16
|
+
/** Download token TTL in seconds (default 5 minutes). */
|
|
17
|
+
const DEFAULT_DOWNLOAD_TOKEN_TTL = 300;
|
|
18
|
+
const parsedDownloadTokenTtl = Number.parseInt(process.env.DOWNLOAD_TOKEN_TTL || "", 10);
|
|
19
|
+
const DOWNLOAD_TOKEN_TTL = Number.isFinite(parsedDownloadTokenTtl) && parsedDownloadTokenTtl > 0
|
|
20
|
+
? parsedDownloadTokenTtl
|
|
21
|
+
: DEFAULT_DOWNLOAD_TOKEN_TTL;
|
|
22
|
+
/**
|
|
23
|
+
* Create a self-contained encrypted download token for a specific GitLab resource.
|
|
24
|
+
*/
|
|
25
|
+
export function createDownloadToken(header, token, apiUrl, resource) {
|
|
26
|
+
const iv = randomBytes(12);
|
|
27
|
+
const cipher = createCipheriv("aes-256-gcm", DOWNLOAD_TOKEN_KEY, iv);
|
|
28
|
+
const payload = JSON.stringify({
|
|
29
|
+
h: header,
|
|
30
|
+
t: token,
|
|
31
|
+
e: Math.floor(Date.now() / 1000) + DOWNLOAD_TOKEN_TTL,
|
|
32
|
+
...(apiUrl ? { u: apiUrl } : {}),
|
|
33
|
+
...(resource ? { r: resource.type, p: resource.params } : {}),
|
|
34
|
+
});
|
|
35
|
+
const encrypted = Buffer.concat([cipher.update(payload, "utf8"), cipher.final()]);
|
|
36
|
+
const tag = cipher.getAuthTag();
|
|
37
|
+
return Buffer.concat([iv, tag, encrypted]).toString("base64url");
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Decrypt and validate a download token, returning null when it is invalid or expired.
|
|
41
|
+
*/
|
|
42
|
+
export function decryptDownloadToken(tokenStr) {
|
|
43
|
+
try {
|
|
44
|
+
const buf = Buffer.from(tokenStr, "base64url");
|
|
45
|
+
if (buf.length < 29)
|
|
46
|
+
return null;
|
|
47
|
+
const iv = buf.subarray(0, 12);
|
|
48
|
+
const tag = buf.subarray(12, 28);
|
|
49
|
+
const encrypted = buf.subarray(28);
|
|
50
|
+
const decipher = createDecipheriv("aes-256-gcm", DOWNLOAD_TOKEN_KEY, iv);
|
|
51
|
+
decipher.setAuthTag(tag);
|
|
52
|
+
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
53
|
+
const payload = JSON.parse(decrypted.toString("utf8"));
|
|
54
|
+
if (payload.e && Math.floor(Date.now() / 1000) > payload.e) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
if (typeof payload.h !== "string" || payload.h.length === 0)
|
|
58
|
+
return null;
|
|
59
|
+
if (typeof payload.t !== "string" || payload.t.length === 0)
|
|
60
|
+
return null;
|
|
61
|
+
return {
|
|
62
|
+
header: payload.h,
|
|
63
|
+
token: payload.t,
|
|
64
|
+
...(payload.u ? { apiUrl: payload.u } : {}),
|
|
65
|
+
...(payload.r ? { resourceType: payload.r, resourceParams: payload.p } : {}),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
export function getForwardedRequestHost(req, trustProxy) {
|
|
2
|
+
if (!trustProxy)
|
|
3
|
+
return undefined;
|
|
4
|
+
const forwarded = getLastHeaderValue(req.headers.forwarded);
|
|
5
|
+
if (forwarded) {
|
|
6
|
+
for (const part of forwarded.split(";")) {
|
|
7
|
+
const separator = part.indexOf("=");
|
|
8
|
+
if (separator <= 0)
|
|
9
|
+
continue;
|
|
10
|
+
const key = part.slice(0, separator).trim().toLowerCase();
|
|
11
|
+
const value = unquoteHeaderValue(part.slice(separator + 1).trim());
|
|
12
|
+
if (key === "host" && value && !/[\s/\\]/.test(value)) {
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const host = getLastHeaderValue(req.headers["x-forwarded-host"]);
|
|
18
|
+
if (host && !/[\s/\\]/.test(host)) {
|
|
19
|
+
return host;
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
1
23
|
function getLastHeaderValue(value) {
|
|
2
24
|
const raw = Array.isArray(value) ? value[value.length - 1] : value;
|
|
3
25
|
if (!raw)
|
|
@@ -10,6 +10,21 @@ function stripGraphQLCommentsAndStrings(source) {
|
|
|
10
10
|
result += " ";
|
|
11
11
|
continue;
|
|
12
12
|
}
|
|
13
|
+
if (source.slice(i, i + 3) === '"""') {
|
|
14
|
+
i += 3;
|
|
15
|
+
while (i < source.length && source.slice(i, i + 3) !== '"""') {
|
|
16
|
+
if (source[i] === "\\" && source.slice(i, i + 4) === '\\"""') {
|
|
17
|
+
i += 4;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
i++;
|
|
21
|
+
}
|
|
22
|
+
if (i < source.length) {
|
|
23
|
+
i += 3;
|
|
24
|
+
}
|
|
25
|
+
result += " ";
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
13
28
|
if (ch === '"' || ch === "'") {
|
|
14
29
|
const quote = ch;
|
|
15
30
|
i++;
|
|
@@ -27,17 +42,6 @@ function stripGraphQLCommentsAndStrings(source) {
|
|
|
27
42
|
result += " ";
|
|
28
43
|
continue;
|
|
29
44
|
}
|
|
30
|
-
if (source.slice(i, i + 3) === '"""') {
|
|
31
|
-
i += 3;
|
|
32
|
-
while (i < source.length && source.slice(i, i + 3) !== '"""') {
|
|
33
|
-
i++;
|
|
34
|
-
}
|
|
35
|
-
if (i < source.length) {
|
|
36
|
-
i += 3;
|
|
37
|
-
}
|
|
38
|
-
result += " ";
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
45
|
result += ch;
|
|
42
46
|
i++;
|
|
43
47
|
}
|
|
@@ -48,5 +52,98 @@ export function graphqlQueryContainsWriteOperation(query) {
|
|
|
48
52
|
if (!normalized) {
|
|
49
53
|
return false;
|
|
50
54
|
}
|
|
51
|
-
|
|
55
|
+
const operationTypePattern = /\s*(?:,\s*)?(query|mutation|subscription)\b/y;
|
|
56
|
+
let depth = 0;
|
|
57
|
+
let expectingOperationType = true;
|
|
58
|
+
for (let i = 0; i < normalized.length; i++) {
|
|
59
|
+
const ch = normalized[i];
|
|
60
|
+
if (ch === "{") {
|
|
61
|
+
depth++;
|
|
62
|
+
expectingOperationType = false;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (ch === "}") {
|
|
66
|
+
depth = Math.max(0, depth - 1);
|
|
67
|
+
if (depth === 0) {
|
|
68
|
+
expectingOperationType = true;
|
|
69
|
+
}
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (depth === 0 && (ch === ";" || ch === ",")) {
|
|
73
|
+
expectingOperationType = true;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (depth === 0 && expectingOperationType) {
|
|
77
|
+
operationTypePattern.lastIndex = i;
|
|
78
|
+
const operationMatch = operationTypePattern.exec(normalized);
|
|
79
|
+
if (operationMatch) {
|
|
80
|
+
const operationType = operationMatch[1];
|
|
81
|
+
expectingOperationType = false;
|
|
82
|
+
if (operationType === "mutation" || operationType === "subscription") {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
i = operationTypePattern.lastIndex - 1;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
const DELETE_FIELD_PATTERN = /delete|destroy|remove|prune|purge/i;
|
|
92
|
+
// Collects top-level selection field names (and aliases) of every mutation operation.
|
|
93
|
+
// Content inside parentheses (arguments) is skipped so argument names like
|
|
94
|
+
// removeSourceBranch do not count as delete fields. Returns null when a top-level
|
|
95
|
+
// fragment spread is present, since the spread could hide a delete field.
|
|
96
|
+
function extractTopLevelMutationFields(normalized) {
|
|
97
|
+
const fields = [];
|
|
98
|
+
const mutationRegex = /(?:^|[};]\s*)mutation\b[^({]*(?:\([^)]*\))?\s*\{/g;
|
|
99
|
+
let match;
|
|
100
|
+
while ((match = mutationRegex.exec(normalized)) !== null) {
|
|
101
|
+
let i = mutationRegex.lastIndex;
|
|
102
|
+
let braceDepth = 1;
|
|
103
|
+
let parenDepth = 0;
|
|
104
|
+
let current = "";
|
|
105
|
+
while (i < normalized.length && braceDepth > 0) {
|
|
106
|
+
const ch = normalized[i];
|
|
107
|
+
if (ch === "(")
|
|
108
|
+
parenDepth++;
|
|
109
|
+
else if (ch === ")")
|
|
110
|
+
parenDepth = Math.max(0, parenDepth - 1);
|
|
111
|
+
else if (ch === "{")
|
|
112
|
+
braceDepth++;
|
|
113
|
+
else if (ch === "}")
|
|
114
|
+
braceDepth--;
|
|
115
|
+
if (braceDepth === 1 && parenDepth === 0) {
|
|
116
|
+
if (normalized.startsWith("...", i)) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
if (/[A-Za-z0-9_]/.test(ch)) {
|
|
120
|
+
current += ch;
|
|
121
|
+
i++;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (current) {
|
|
126
|
+
fields.push(current);
|
|
127
|
+
current = "";
|
|
128
|
+
}
|
|
129
|
+
i++;
|
|
130
|
+
}
|
|
131
|
+
if (current) {
|
|
132
|
+
fields.push(current);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return fields;
|
|
136
|
+
}
|
|
137
|
+
export function graphqlQueryContainsDeleteOperation(query) {
|
|
138
|
+
const normalized = stripGraphQLCommentsAndStrings(query).trim();
|
|
139
|
+
if (!normalized || !/(?:^|[};]\s*)mutation\b/.test(normalized)) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
const fields = extractTopLevelMutationFields(normalized);
|
|
143
|
+
if (fields === null || fields.length === 0) {
|
|
144
|
+
// Fragment spread at mutation top level, or a mutation whose selection set
|
|
145
|
+
// could not be located (exotic syntax): be conservative and treat as delete
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
return fields.some(field => DELETE_FIELD_PATTERN.test(field));
|
|
52
149
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fields GitLab returns that are live credentials or carry embedded secrets.
|
|
3
|
+
* MCP tool results become part of the AI conversation context, so these must
|
|
4
|
+
* never be forwarded to the model.
|
|
5
|
+
*/
|
|
6
|
+
const SENSITIVE_GITLAB_FIELDS = ["runners_token", "import_url"];
|
|
7
|
+
/**
|
|
8
|
+
* Strip known sensitive fields from a GitLab API response object before it is
|
|
9
|
+
* returned as an MCP tool result. Mutates and returns the same object.
|
|
10
|
+
*
|
|
11
|
+
* Used for raw passthrough responses (get_project / update_project) that do not
|
|
12
|
+
* go through a Zod schema, since Zod would otherwise strip unknown keys for us.
|
|
13
|
+
*/
|
|
14
|
+
export function redactSensitiveGitLabFields(data) {
|
|
15
|
+
if (data && typeof data === "object") {
|
|
16
|
+
for (const key of SENSITIVE_GITLAB_FIELDS) {
|
|
17
|
+
delete data[key];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return data;
|
|
21
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const NPM_REGISTRY_LATEST_URL = "https://registry.npmjs.org/@zereight/mcp-gitlab/latest";
|
|
2
|
+
const RELEASE_VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
3
|
+
export function isNewerVersion(candidate, current) {
|
|
4
|
+
const parse = (version) => version.split(".").map(part => Number.parseInt(part, 10));
|
|
5
|
+
const a = parse(candidate);
|
|
6
|
+
const b = parse(current);
|
|
7
|
+
for (let i = 0; i < 3; i++) {
|
|
8
|
+
if (!Number.isFinite(a[i]) || !Number.isFinite(b[i]))
|
|
9
|
+
return false;
|
|
10
|
+
if (a[i] !== b[i])
|
|
11
|
+
return a[i] > b[i];
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
export async function fetchLatestVersion(fetchFn = fetch, timeoutMs = 3000) {
|
|
16
|
+
try {
|
|
17
|
+
const response = await fetchFn(NPM_REGISTRY_LATEST_URL, {
|
|
18
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
19
|
+
headers: { accept: "application/json" },
|
|
20
|
+
});
|
|
21
|
+
if (!response.ok)
|
|
22
|
+
return null;
|
|
23
|
+
const data = (await response.json());
|
|
24
|
+
return typeof data.version === "string" ? data.version : null;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Fail silent: the update check must never break or delay server startup
|
|
28
|
+
// (offline machines, air-gapped networks, registry outages).
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Returns the latest published version when it is newer than `currentVersion`, otherwise null. */
|
|
33
|
+
export async function checkForNewVersion(currentVersion, fetchFn = fetch) {
|
|
34
|
+
if (!RELEASE_VERSION_PATTERN.test(currentVersion))
|
|
35
|
+
return null;
|
|
36
|
+
const latest = await fetchLatestVersion(fetchFn);
|
|
37
|
+
if (!latest || !RELEASE_VERSION_PATTERN.test(latest))
|
|
38
|
+
return null;
|
|
39
|
+
return isNewerVersion(latest, currentVersion) ? latest : null;
|
|
40
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zereight/mcp-gitlab",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.30",
|
|
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": [
|
|
@@ -52,18 +52,19 @@
|
|
|
52
52
|
"changelog": "auto-changelog -p",
|
|
53
53
|
"test": "npm run test:all",
|
|
54
54
|
"test:all": "npm run build && npm run test:mock && npm run test:live",
|
|
55
|
-
"test:mock": "
|
|
56
|
-
"test:stateless": "npm run build && node --import tsx/esm --test test/stateless/codec.test.ts test/stateless/client-id.test.ts test/stateless/callback-proxy.test.ts test/stateless/session-id.test.ts test/stateless/session-id-integration.test.ts test/stateless/config-ttl.test.ts",
|
|
55
|
+
"test:mock": "bash scripts/run-mock-tests.sh",
|
|
56
|
+
"test:stateless": "npm run build && node --import tsx/esm --test --experimental-test-isolation=none test/stateless/codec.test.ts test/stateless/client-id.test.ts test/stateless/callback-proxy.test.ts test/stateless/session-id.test.ts test/stateless/session-id-integration.test.ts test/stateless/config-ttl.test.ts",
|
|
57
57
|
"test:mcp-oauth": "npm run build && node --import tsx/esm --test test/mcp-oauth-tests.ts",
|
|
58
58
|
"test:live": "node test/validate-api.js",
|
|
59
59
|
"test:consumer-smoke": "bash scripts/consumer-install-smoke.sh",
|
|
60
|
-
"test:remote-auth": "npm run build && node --import tsx/esm --test test/remote-auth-simple-test.ts",
|
|
60
|
+
"test:remote-auth": "npm run build && node --import tsx/esm --test --experimental-test-isolation=none test/remote-auth-simple-test.ts",
|
|
61
61
|
"test:schema": "tsx test/schema-tests.ts && tsx test/test-json-schema.ts",
|
|
62
62
|
"test:oauth": "tsx test/oauth-tests.ts",
|
|
63
63
|
"test:list-merge-requests": "npm run build && tsx test/test-list-merge-requests.ts",
|
|
64
64
|
"test:approvals": "npm run build && tsx test/test-merge-request-approvals.ts",
|
|
65
65
|
"lint": "eslint . --ext .ts",
|
|
66
66
|
"check:runtime-deps": "node scripts/check-runtime-deps.mjs",
|
|
67
|
+
"check:skill-sync": "tsx scripts/check-skill-sync.ts",
|
|
67
68
|
"lint:fix": "eslint . --ext .ts --fix",
|
|
68
69
|
"release": "bash scripts/release.sh",
|
|
69
70
|
"release:mcp-registry": "bash scripts/publish_mcp_registry.sh",
|