@zereight/mcp-gitlab 2.1.62 → 2.1.63
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 +1 -1
- package/README.md +5 -1
- package/README.zh-CN.md +5 -1
- package/build/config.js +7 -0
- package/build/index.js +94 -20
- package/build/masking/engine.js +404 -0
- package/build/masking/index.js +2 -0
- package/build/masking/project-scope.js +53 -0
- package/build/schemas.js +4 -0
- package/build/test/get-merge-request-discussion-schema.test.js +30 -0
- package/build/test/response-masking.test.js +412 -0
- package/build/test/test-toolset-filtering.js +1 -1
- package/build/test/utils/masking.test.js +384 -0
- package/build/tools/registry.js +8 -1
- package/build/tools/tool-descriptions.js +1 -0
- package/package.json +1 -1
package/README.ko.md
CHANGED
|
@@ -110,7 +110,7 @@ command = lib.getExe inputs.gitlab-mcp.packages.${system}.default;
|
|
|
110
110
|
|
|
111
111
|
예시는 기존 `mcp-gitlab`보다 충돌 가능성이 낮은 `zereight-mcp-gitlab` 별칭을 사용합니다. MCP 클라이언트가 찾지 못하면 `which zereight-mcp-gitlab`의 절대 경로를 사용하세요.
|
|
112
112
|
|
|
113
|
-
전역 설치를 쓰지 않으려면 `npx -y @zereight/mcp-gitlab@2.1.
|
|
113
|
+
전역 설치를 쓰지 않으려면 `npx -y @zereight/mcp-gitlab@2.1.62`처럼 직전 안정 버전(문서가 권장하는 버전)으로 고정하세요. 항상 최신 버전을 원하면 `npx -y @zereight/mcp-gitlab@latest`를 사용하세요. 새 버전이 나오면 서버가 시작 시 stderr로 알려줍니다(`GITLAB_DISABLE_VERSION_CHECK=true`로 비활성화 가능).
|
|
114
114
|
|
|
115
115
|
#### CLI 인자 사용하기(환경 변수 문제가 있는 클라이언트용)
|
|
116
116
|
|
package/README.md
CHANGED
|
@@ -115,7 +115,7 @@ The store path is pinned by your lock file; update it with `nix flake update git
|
|
|
115
115
|
|
|
116
116
|
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`.
|
|
117
117
|
|
|
118
|
-
No global install? Pin `npx` to the previous stable release (the version these docs recommend), for example `npx -y @zereight/mcp-gitlab@2.1.
|
|
118
|
+
No global install? Pin `npx` to the previous stable release (the version these docs recommend), for example `npx -y @zereight/mcp-gitlab@2.1.62`. 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`).
|
|
119
119
|
|
|
120
120
|
#### Using CLI Arguments (for clients with env var issues)
|
|
121
121
|
|
|
@@ -143,6 +143,10 @@ Some MCP clients (like GitHub Copilot CLI) have issues with environment variable
|
|
|
143
143
|
- `--use-milestone=true` - Enable milestone API (replaces `USE_MILESTONE`, legacy — prefer `GITLAB_TOOLSETS=milestones`)
|
|
144
144
|
- `--use-pipeline=true` - Enable pipeline API (replaces `USE_PIPELINE`, legacy — prefer `GITLAB_TOOLSETS=pipelines`)
|
|
145
145
|
- `--disable-version-check=true` - Disable the startup new-version notice (replaces `GITLAB_DISABLE_VERSION_CHECK`)
|
|
146
|
+
- `--masking-enabled=true` - Enable text-response masking (replaces `GITLAB_MASKING_ENABLED`)
|
|
147
|
+
- `--masking-config` - Path to a masking configuration file (replaces `GITLAB_MASKING_CONFIG`)
|
|
148
|
+
- `--masking-policy-file` - Path to a protected managed-policy file (replaces `GITLAB_MASKING_POLICY_FILE`)
|
|
149
|
+
- `--masking-workspace-dir` - Directory used to resolve masking files (replaces `GITLAB_MASKING_WORKSPACE_DIR`)
|
|
146
150
|
|
|
147
151
|
CLI arguments take precedence over environment variables.
|
|
148
152
|
|
package/README.zh-CN.md
CHANGED
|
@@ -110,7 +110,7 @@ command = lib.getExe inputs.gitlab-mcp.packages.${system}.default;
|
|
|
110
110
|
|
|
111
111
|
示例使用 `zereight-mcp-gitlab`,这是比旧的 `mcp-gitlab` 更不容易冲突的别名。如果 MCP 客户端找不到它,请使用 `which zereight-mcp-gitlab` 输出的绝对路径。
|
|
112
112
|
|
|
113
|
-
如果不想全局安装,请将 `npx` 固定到上一个稳定版本(即文档推荐的版本),例如 `npx -y @zereight/mcp-gitlab@2.1.
|
|
113
|
+
如果不想全局安装,请将 `npx` 固定到上一个稳定版本(即文档推荐的版本),例如 `npx -y @zereight/mcp-gitlab@2.1.62`。如果始终想使用最新版本,请改用 `npx -y @zereight/mcp-gitlab@latest`。有新版本发布时,服务器会在启动时通过 stderr 提示(可用 `GITLAB_DISABLE_VERSION_CHECK=true` 关闭)。
|
|
114
114
|
|
|
115
115
|
#### 使用 CLI 参数(适用于环境变量有问题的客户端)
|
|
116
116
|
|
|
@@ -138,6 +138,10 @@ command = lib.getExe inputs.gitlab-mcp.packages.${system}.default;
|
|
|
138
138
|
- `--use-milestone=true` - 启用里程碑 API(替代 `USE_MILESTONE`,旧版 — 推荐 `GITLAB_TOOLSETS=milestones`)
|
|
139
139
|
- `--use-pipeline=true` - 启用流水线 API(替代 `USE_PIPELINE`,旧版 — 推荐 `GITLAB_TOOLSETS=pipelines`)
|
|
140
140
|
- `--disable-version-check=true` - 关闭启动时的新版本提示(替代 `GITLAB_DISABLE_VERSION_CHECK`)
|
|
141
|
+
- `--masking-enabled=true` - 启用文本响应掩码(替代 `GITLAB_MASKING_ENABLED`)
|
|
142
|
+
- `--masking-config` - 掩码配置文件路径(替代 `GITLAB_MASKING_CONFIG`)
|
|
143
|
+
- `--masking-policy-file` - 受保护的托管策略文件路径(替代 `GITLAB_MASKING_POLICY_FILE`)
|
|
144
|
+
- `--masking-workspace-dir` - 用于解析掩码文件的目录(替代 `GITLAB_MASKING_WORKSPACE_DIR`)
|
|
141
145
|
|
|
142
146
|
CLI 参数优先于环境变量。
|
|
143
147
|
|
package/build/config.js
CHANGED
|
@@ -28,6 +28,13 @@ export const IS_OLD = getConfig("is-old", "GITLAB_IS_OLD") === "true";
|
|
|
28
28
|
// Behavior flags
|
|
29
29
|
// ---------------------------------------------------------------------------
|
|
30
30
|
export const GITLAB_READ_ONLY_MODE = getConfig("read-only", "GITLAB_READ_ONLY_MODE") === "true";
|
|
31
|
+
// Optional response masking. Disabled by default so existing deployments keep
|
|
32
|
+
// the exact original response behavior unless explicitly opted in.
|
|
33
|
+
export const GITLAB_MASKING_ENABLED = getConfig("masking-enabled", "GITLAB_MASKING_ENABLED") === "true";
|
|
34
|
+
export const GITLAB_MASKING_CONFIG = getConfig("masking-config", "GITLAB_MASKING_CONFIG");
|
|
35
|
+
/** Server-owned policy groups and project bindings for managed response masking. */
|
|
36
|
+
export const GITLAB_MASKING_POLICY_FILE = getConfig("masking-policy-file", "GITLAB_MASKING_POLICY_FILE");
|
|
37
|
+
export const GITLAB_MASKING_WORKSPACE_DIR = getConfig("masking-workspace-dir", "GITLAB_MASKING_WORKSPACE_DIR");
|
|
31
38
|
const PERMISSION_MODES = ["readonly", "modify", "full"];
|
|
32
39
|
export const GITLAB_PERMISSION_MODE = (() => {
|
|
33
40
|
const raw = getConfig("permission-mode", "GITLAB_PERMISSION_MODE");
|
package/build/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { getConfig, ENABLE_DYNAMIC_API_URL, ENABLE_DYNAMIC_PROJECT_SCOPE, ENABLE_STRICT_PROJECT_SCOPE, GITLAB_AUTH_COOKIE_PATH, GITLAB_ALLOW_UNAUTHENTICATED_TOOL_DISCOVERY, GITLAB_CA_CERT_PATH, GITLAB_JOB_TOKEN, GITLAB_MCP_OAUTH, GITLAB_OAUTH_APP_ID, GITLAB_OAUTH_SCOPES, GITLAB_OAUTH_CALLBACK_PROXY, GITLAB_PERSONAL_ACCESS_TOKEN, GITLAB_POOL_MAX_SIZE, GITLAB_DISABLE_VERSION_CHECK, GITLAB_READ_ONLY_MODE, GITLAB_PERMISSION_MODE, GITLAB_TOOLSETS_RAW, GITLAB_TOOLS_RAW, HOST, HTTP_PROXY, HTTPS_PROXY, IS_OLD, MCP_SERVER_URL, NODE_TLS_REJECT_UNAUTHORIZED, NO_PROXY, OAUTH_STATELESS_CLIENT_TTL_SECONDS, OAUTH_STATELESS_MODE, OAUTH_STATELESS_PENDING_TTL_SECONDS, OAUTH_STATELESS_SESSION_TTL_SECONDS, OAUTH_STATELESS_STORED_TTL_SECONDS, PORT, REMOTE_AUTHORIZATION, SESSION_TIMEOUT_SECONDS, SSE, STREAMABLE_HTTP, MCP_TRUST_PROXY, USE_GITLAB_WIKI, USE_MILESTONE, USE_OAUTH, USE_PIPELINE, GITLAB_TOOL_POLICY_APPROVE_RAW, GITLAB_TOOL_POLICY_HIDDEN_RAW, GITLAB_OAUTH_ALLOWED_GROUPS_RAW, GITLAB_ALLOWED_GROUPS_RAW, GITLAB_OAUTH_ALLOWED_GROUPS, } from "./config.js";
|
|
2
|
+
import { getConfig, ENABLE_DYNAMIC_API_URL, ENABLE_DYNAMIC_PROJECT_SCOPE, ENABLE_STRICT_PROJECT_SCOPE, GITLAB_AUTH_COOKIE_PATH, GITLAB_ALLOW_UNAUTHENTICATED_TOOL_DISCOVERY, GITLAB_CA_CERT_PATH, GITLAB_JOB_TOKEN, GITLAB_MCP_OAUTH, GITLAB_OAUTH_APP_ID, GITLAB_OAUTH_SCOPES, GITLAB_OAUTH_CALLBACK_PROXY, GITLAB_PERSONAL_ACCESS_TOKEN, GITLAB_POOL_MAX_SIZE, GITLAB_DISABLE_VERSION_CHECK, GITLAB_READ_ONLY_MODE, GITLAB_MASKING_ENABLED, GITLAB_MASKING_CONFIG, GITLAB_MASKING_POLICY_FILE, GITLAB_MASKING_WORKSPACE_DIR, GITLAB_PERMISSION_MODE, GITLAB_TOOLSETS_RAW, GITLAB_TOOLS_RAW, HOST, HTTP_PROXY, HTTPS_PROXY, IS_OLD, MCP_SERVER_URL, NODE_TLS_REJECT_UNAUTHORIZED, NO_PROXY, OAUTH_STATELESS_CLIENT_TTL_SECONDS, OAUTH_STATELESS_MODE, OAUTH_STATELESS_PENDING_TTL_SECONDS, OAUTH_STATELESS_SESSION_TTL_SECONDS, OAUTH_STATELESS_STORED_TTL_SECONDS, PORT, REMOTE_AUTHORIZATION, SESSION_TIMEOUT_SECONDS, SSE, STREAMABLE_HTTP, MCP_TRUST_PROXY, USE_GITLAB_WIKI, USE_MILESTONE, USE_OAUTH, USE_PIPELINE, GITLAB_TOOL_POLICY_APPROVE_RAW, GITLAB_TOOL_POLICY_HIDDEN_RAW, GITLAB_OAUTH_ALLOWED_GROUPS_RAW, GITLAB_ALLOWED_GROUPS_RAW, GITLAB_OAUTH_ALLOWED_GROUPS, } from "./config.js";
|
|
3
3
|
/** True when the server is running in remote/network mode (SSE or StreamableHTTP transport). */
|
|
4
4
|
const IS_REMOTE = SSE || STREAMABLE_HTTP;
|
|
5
5
|
const STREAMABLE_HTTP_AUTH_TOKEN = getConfig("streamable-http-auth-token", "STREAMABLE_HTTP_AUTH_TOKEN");
|
|
@@ -95,6 +95,7 @@ import { graphqlQueryContainsWriteOperation, graphqlQueryContainsDeleteOperation
|
|
|
95
95
|
import { resolveNestedWikiUpdateTitle } from "./utils/wiki-title.js";
|
|
96
96
|
import { encodeRepoFilePayloadContent, fileOperationsIncludeDeleteOrMove, toGitLabCommitActions, } from "./utils/gitlab-commit-actions.js";
|
|
97
97
|
import { redactSensitiveGitLabFields } from "./utils/redact-sensitive.js";
|
|
98
|
+
import { createMaskingPolicyResolver, getManagedMaskingProjectIds, } from "./masking/index.js";
|
|
98
99
|
import { checkForNewVersion } from "./utils/version-check.js";
|
|
99
100
|
import { assertGitLabVersionAtLeast } from "./utils/gitlab-version-gate.js";
|
|
100
101
|
import { parseGitLabVersionApiResponse, } from "./utils/gitlab-instance-version.js";
|
|
@@ -111,10 +112,18 @@ GitLabDiscussionNoteSchema, // Added
|
|
|
111
112
|
GitLabDiscussionSchema,
|
|
112
113
|
// Draft Notes Schemas
|
|
113
114
|
GitLabDraftNoteSchema, GitLabForkSchema, GitLabBranchSchema, GitLabProtectedBranchSchema, GitLabGroupSchema, GitLabIssueLinkSchema, GitLabIssueSchema, GitLabIssueWithLinkDetailsSchema, GitLabMarkdownUploadSchema, GitLabMergeRequestPipelineSchema, GitLabMergeRequestSchema, GitLabMilestonesSchema, GitLabGroupMilestonesSchema, GitLabNamespaceExistsResponseSchema, GitLabNamespaceSchema, GitLabPipelineJobSchema, GitLabDeploymentSchema, GitLabEnvironmentSchema, GitLabPipelineSchema, GitLabPipelineScheduleSchema, GitLabPipelineScheduleVariableSchema, GitLabPipelineSchedulePlayResultSchema, ListPipelineSchedulesSchema, GetPipelineScheduleSchema, ListPipelineSchedulePipelinesSchema, CreatePipelineScheduleSchema, UpdatePipelineScheduleSchema, DeletePipelineScheduleSchema, PlayPipelineScheduleSchema, TakeOwnershipPipelineScheduleSchema, GetPipelineScheduleVariableSchema, CreatePipelineScheduleVariableSchema, UpdatePipelineScheduleVariableSchema, DeletePipelineScheduleVariableSchema, GitLabPipelineTriggerJobSchema, GitLabProjectMemberSchema, GitLabProjectSchema, GitLabTodoSchema, GitLabReferenceSchema, GitLabRepositorySchema, GitLabSearchBlobResultSchema, GitLabSearchResponseSchema, GitLabTreeItemSchema, GitLabUserSchema, GitLabUsersResponseSchema, GitLabWikiPageSchema, GroupIteration, ListCommitStatusesSchema, ListBranchesSchema, ListCommitsSchema, ListDraftNotesSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, GitLabCiVariableSchema, ListProjectVariablesSchema, GetProjectVariableSchema, CreateProjectVariableSchema, UpdateProjectVariableSchema, DeleteProjectVariableSchema, ListGroupVariablesSchema, GetGroupVariableSchema, CreateGroupVariableSchema, UpdateGroupVariableSchema, DeleteGroupVariableSchema, GitLabDependencyProxySchema, GitLabDependencyProxyBlobSchema, GetDependencyProxySettingsSchema, UpdateDependencyProxySettingsSchema, ListDependencyProxyBlobsSchema, PurgeDependencyProxyCacheSchema, ListProjectVulnerabilitiesSchema, GetVulnerabilitySchema, DismissVulnerabilitySchema, ConfirmVulnerabilitySchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListTodosSchema, ListLabelsSchema, ListMergeRequestDiffsSchema, // Added
|
|
114
|
-
GetMergeRequestFileDiffSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestPipelinesSchema, ListMergeRequestsSchema, ListGroupMergeRequestsSchema, ListMergeRequestVersionsSchema, GetMergeRequestVersionSchema, GitLabMergeRequestVersionSchema, GitLabMergeRequestVersionDetailSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelinesSchema, ListDeploymentsSchema, ListEnvironmentsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListCiCatalogResourcesSchema, GetCiCatalogResourceSchema, ListProjectMembersSchema, ListGroupMembersSchema, ListProjectMilestonesSchema, ListGroupMilestonesSchema, ListProjectsSchema, ListWikiPagesSchema, GetGroupWikiPageSchema, ListGroupWikiPagesSchema, UpdateGroupWikiPageSchema, MarkdownUploadSchema, MarkdownUploadRemoteSchema, DownloadAttachmentSchema, DownloadJobArtifactsSchema, GetJobArtifactFileSchema, GitLabArtifactEntrySchema, ListJobArtifactsSchema, MergeMergeRequestSchema, ApproveMergeRequestSchema, UnapproveMergeRequestSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GitLabMergeRequestApprovalsResponseSchema, GitLabMergeRequestApprovalStateSchema, MyIssuesSchema, OrbitQuerySchema, OrbitSchemaSchema, OrbitStatusSchema, OrbitToolsSchema, MarkAllTodosDoneSchema, MarkTodoDoneSchema, PaginatedDiscussionsResponseSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PlayPipelineJobSchema, PushFilesSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UpdateDraftNoteSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateIssueDescriptionPatchSchema, UpdateLabelSchema, UpdateProjectSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestSchema, UpdateWikiPageSchema, VerifyNamespaceSchema, GitLabEventSchema, ListEventsSchema, GetProjectEventsSchema, ExecuteGraphQLSchema, GitLabReleaseSchema, ListReleasesSchema, GetReleaseSchema, CreateReleaseSchema, UpdateReleaseSchema, DeleteReleaseSchema, CreateReleaseEvidenceSchema, DownloadReleaseAssetSchema, ListTagsSchema, GetTagSchema, CreateTagSchema, DeleteTagSchema, GetTagSignatureSchema, GitLabTagSchema, GitLabTagSignatureSchema, GetMergeRequestNotesSchema, GetMergeRequestNoteSchema, DeleteMergeRequestDiscussionNoteSchema, ResolveMergeRequestThreadSchema, GetWorkItemSchema, ListWorkItemsSchema, CreateWorkItemSchema, UpdateWorkItemSchema, ConvertWorkItemTypeSchema, ListWorkItemStatusesSchema, ListWorkItemNotesSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, MoveWorkItemSchema, ListCustomFieldDefinitionsSchema, GetTimelineEventsSchema, CreateTimelineEventSchema, ListWebhooksSchema, CreateWebhookSchema, UpdateWebhookSchema, DeleteWebhookSchema, ListWebhookEventsSchema, GetWebhookEventSchema, HealthCheckSchema, } from "./schemas.js";
|
|
115
|
+
GetMergeRequestFileDiffSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestPipelinesSchema, ListMergeRequestsSchema, ListGroupMergeRequestsSchema, ListMergeRequestVersionsSchema, GetMergeRequestVersionSchema, GitLabMergeRequestVersionSchema, GitLabMergeRequestVersionDetailSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelinesSchema, ListDeploymentsSchema, ListEnvironmentsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListCiCatalogResourcesSchema, GetCiCatalogResourceSchema, ListProjectMembersSchema, ListGroupMembersSchema, ListProjectMilestonesSchema, ListGroupMilestonesSchema, ListProjectsSchema, ListWikiPagesSchema, GetGroupWikiPageSchema, ListGroupWikiPagesSchema, UpdateGroupWikiPageSchema, MarkdownUploadSchema, MarkdownUploadRemoteSchema, DownloadAttachmentSchema, DownloadJobArtifactsSchema, GetJobArtifactFileSchema, GitLabArtifactEntrySchema, ListJobArtifactsSchema, MergeMergeRequestSchema, ApproveMergeRequestSchema, UnapproveMergeRequestSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GitLabMergeRequestApprovalsResponseSchema, GitLabMergeRequestApprovalStateSchema, MyIssuesSchema, OrbitQuerySchema, OrbitSchemaSchema, OrbitStatusSchema, OrbitToolsSchema, MarkAllTodosDoneSchema, MarkTodoDoneSchema, PaginatedDiscussionsResponseSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PlayPipelineJobSchema, PushFilesSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UpdateDraftNoteSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateIssueDescriptionPatchSchema, UpdateLabelSchema, UpdateProjectSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestSchema, UpdateWikiPageSchema, VerifyNamespaceSchema, GitLabEventSchema, ListEventsSchema, GetProjectEventsSchema, ExecuteGraphQLSchema, GitLabReleaseSchema, ListReleasesSchema, GetReleaseSchema, CreateReleaseSchema, UpdateReleaseSchema, DeleteReleaseSchema, CreateReleaseEvidenceSchema, DownloadReleaseAssetSchema, ListTagsSchema, GetTagSchema, CreateTagSchema, DeleteTagSchema, GetTagSignatureSchema, GitLabTagSchema, GitLabTagSignatureSchema, GetMergeRequestNotesSchema, GetMergeRequestNoteSchema, GetMergeRequestDiscussionSchema, DeleteMergeRequestDiscussionNoteSchema, ResolveMergeRequestThreadSchema, GetWorkItemSchema, ListWorkItemsSchema, CreateWorkItemSchema, UpdateWorkItemSchema, ConvertWorkItemTypeSchema, ListWorkItemStatusesSchema, ListWorkItemNotesSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, MoveWorkItemSchema, ListCustomFieldDefinitionsSchema, GetTimelineEventsSchema, CreateTimelineEventSchema, ListWebhooksSchema, CreateWebhookSchema, UpdateWebhookSchema, DeleteWebhookSchema, ListWebhookEventsSchema, GetWebhookEventSchema, HealthCheckSchema, } from "./schemas.js";
|
|
115
116
|
import { randomUUID, timingSafeEqual, } from "node:crypto";
|
|
116
117
|
import { createLogger } from "./utils/logger.js";
|
|
117
118
|
const logger = createLogger();
|
|
119
|
+
// Construct once at startup. When disabled this is undefined and the result
|
|
120
|
+
// path below returns the original objects without reading a config file.
|
|
121
|
+
const maskingPolicyResolver = createMaskingPolicyResolver({
|
|
122
|
+
enabled: GITLAB_MASKING_ENABLED,
|
|
123
|
+
configPath: GITLAB_MASKING_CONFIG,
|
|
124
|
+
policyFilePath: GITLAB_MASKING_POLICY_FILE,
|
|
125
|
+
workspaceDir: GITLAB_MASKING_WORKSPACE_DIR,
|
|
126
|
+
});
|
|
118
127
|
const SERVER_NAME = process.env.MCP_SERVER_NAME?.trim() || "zereight-gitlab-mcp-server";
|
|
119
128
|
/**
|
|
120
129
|
* Static MCP prompt catalog. These wrap the multi-step tool workflows documented in
|
|
@@ -292,22 +301,74 @@ function createServer() {
|
|
|
292
301
|
const sessionId = request.params.sessionId;
|
|
293
302
|
const toolName = request.params.name;
|
|
294
303
|
const start = Date.now();
|
|
304
|
+
let maskingEngine;
|
|
295
305
|
const logCompletion = (result) => {
|
|
296
306
|
const durationMs = Date.now() - start;
|
|
297
307
|
logger.info({ tool: toolName, event: "tool_call_done", durationMs }, `tool_call_done: ${toolName} (${durationMs}ms)`);
|
|
298
|
-
|
|
308
|
+
const isPlainTextResult = toolName === "get_pipeline_job_output" ||
|
|
309
|
+
toolName === "get_job_artifact_file" ||
|
|
310
|
+
(toolName === "download_release_asset" && !IS_REMOTE);
|
|
311
|
+
return maskingEngine
|
|
312
|
+
? maskingEngine.maskToolResult(result, { textFormat: isPlainTextResult ? "plain" : "json" })
|
|
313
|
+
: result;
|
|
299
314
|
};
|
|
300
315
|
const logError = (error) => {
|
|
301
316
|
const durationMs = Date.now() - start;
|
|
317
|
+
const safeError = maskingEngine ? maskingEngine.maskError(error) : error;
|
|
302
318
|
logger.error({
|
|
303
319
|
tool: toolName,
|
|
304
320
|
event: "tool_call_error",
|
|
305
321
|
durationMs,
|
|
306
|
-
error:
|
|
322
|
+
error: safeError instanceof Error ? safeError.message : String(safeError),
|
|
307
323
|
}, `tool_call_error: ${toolName} (${durationMs}ms)`);
|
|
308
|
-
throw
|
|
324
|
+
throw safeError;
|
|
309
325
|
};
|
|
310
326
|
try {
|
|
327
|
+
const authData = (REMOTE_AUTHORIZATION || GITLAB_MCP_OAUTH) && sessionId
|
|
328
|
+
? authBySession[sessionId]
|
|
329
|
+
: undefined;
|
|
330
|
+
const sessionContext = authData
|
|
331
|
+
? {
|
|
332
|
+
sessionId,
|
|
333
|
+
header: authData.header,
|
|
334
|
+
token: authData.token,
|
|
335
|
+
lastUsed: authData.lastUsed,
|
|
336
|
+
apiUrl: authData.apiUrl,
|
|
337
|
+
publicBaseUrl: authData.publicBaseUrl,
|
|
338
|
+
allowedProjectIds: authData.allowedProjectIds,
|
|
339
|
+
}
|
|
340
|
+
: undefined;
|
|
341
|
+
const selectMaskingPolicy = () => {
|
|
342
|
+
const isToolDiscovery = toolName === "discover_tools";
|
|
343
|
+
const inputSchema = allTools.find(tool => tool.name === toolName)?.inputSchema;
|
|
344
|
+
// Optional project fields can be filters on global queries. Only supply
|
|
345
|
+
// the default when the corresponding handler actually uses it. Keep this
|
|
346
|
+
// list synchronized with getFileContents, listMyIssues, listIssues, and
|
|
347
|
+
// listMergeRequests: those are the handlers with optional project input
|
|
348
|
+
// that may call getEffectiveProjectId("") at runtime.
|
|
349
|
+
const usesDefaultProject = inputSchema?.required?.includes("project_id") ||
|
|
350
|
+
toolName === "get_file_contents" ||
|
|
351
|
+
(toolName === "my_issues" &&
|
|
352
|
+
(Boolean(GITLAB_PROJECT_ID) || getEffectiveAllowedProjectIds().length > 0)) ||
|
|
353
|
+
((toolName === "list_issues" || toolName === "list_merge_requests") &&
|
|
354
|
+
ENABLE_STRICT_PROJECT_SCOPE &&
|
|
355
|
+
getEffectiveAllowedProjectIds().length > 0);
|
|
356
|
+
const maskingScope = maskingPolicyResolver
|
|
357
|
+
? getManagedMaskingProjectIds(inputSchema, request.params.arguments, maskingPolicyResolver.managed && usesDefaultProject
|
|
358
|
+
? () => getEffectiveProjectId("")
|
|
359
|
+
: undefined)
|
|
360
|
+
: undefined;
|
|
361
|
+
return maskingPolicyResolver?.select({
|
|
362
|
+
gitlabInstance: getEffectiveApiUrl(),
|
|
363
|
+
...maskingScope,
|
|
364
|
+
// Tool discovery does not access GitLab data and must remain available
|
|
365
|
+
// under the default managed-policy setting.
|
|
366
|
+
allowBuiltinForUnscoped: isToolDiscovery,
|
|
367
|
+
});
|
|
368
|
+
};
|
|
369
|
+
maskingEngine = sessionContext
|
|
370
|
+
? sessionAuthStore.run(sessionContext, selectMaskingPolicy)
|
|
371
|
+
: selectMaskingPolicy();
|
|
311
372
|
// Handle discover_tools meta-tool directly (needs access to mcpServer and filteredTools)
|
|
312
373
|
if (toolName === "discover_tools") {
|
|
313
374
|
const category = request.params.arguments?.category?.trim()?.toLowerCase();
|
|
@@ -421,18 +482,9 @@ function createServer() {
|
|
|
421
482
|
const { _confirmed, ...cleanArgs } = request.params.arguments || {};
|
|
422
483
|
request.params.arguments = cleanArgs;
|
|
423
484
|
}
|
|
424
|
-
if (
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
sessionId,
|
|
428
|
-
header: authData.header,
|
|
429
|
-
token: authData.token,
|
|
430
|
-
lastUsed: authData.lastUsed,
|
|
431
|
-
apiUrl: authData.apiUrl,
|
|
432
|
-
publicBaseUrl: authData.publicBaseUrl,
|
|
433
|
-
allowedProjectIds: authData.allowedProjectIds,
|
|
434
|
-
};
|
|
435
|
-
// Run the handler within the retrieved context
|
|
485
|
+
if (sessionContext) {
|
|
486
|
+
// Policy selection and the tool handler must use the same request-local
|
|
487
|
+
// API URL and project allowlist.
|
|
436
488
|
const result = await sessionAuthStore.run(sessionContext, () => handleToolCall(request.params));
|
|
437
489
|
return logCompletion(result);
|
|
438
490
|
}
|
|
@@ -1248,13 +1300,17 @@ async function handleGitLabError(response) {
|
|
|
1248
1300
|
const errorBody = await response.text();
|
|
1249
1301
|
// Check specifically for Rate Limit error
|
|
1250
1302
|
if (response.status === 403 && errorBody.includes("User API Key Rate limit exceeded")) {
|
|
1251
|
-
|
|
1303
|
+
// Do not log the upstream body here; it is masked on the error object
|
|
1304
|
+
// before the common tool-call boundary returns/logs it.
|
|
1305
|
+
logger.error("GitLab API Rate Limit Exceeded");
|
|
1252
1306
|
logger.error("User API Key Rate limit exceeded. Please try again later.");
|
|
1253
|
-
|
|
1307
|
+
const error = new Error(`GitLab API Rate Limit Exceeded: ${errorBody}`);
|
|
1308
|
+
throw error;
|
|
1254
1309
|
}
|
|
1255
1310
|
else {
|
|
1256
1311
|
// Handle other API errors
|
|
1257
|
-
|
|
1312
|
+
const error = new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`);
|
|
1313
|
+
throw error;
|
|
1258
1314
|
}
|
|
1259
1315
|
}
|
|
1260
1316
|
}
|
|
@@ -3408,6 +3464,17 @@ async function getMergeRequestNote(projectId, mergeRequestIid, noteId) {
|
|
|
3408
3464
|
const data = await response.json();
|
|
3409
3465
|
return GitLabDiscussionNoteSchema.parse(data);
|
|
3410
3466
|
}
|
|
3467
|
+
async function getMergeRequestDiscussion(projectId, mergeRequestIid, discussionId) {
|
|
3468
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3469
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/discussions/${encodeGitLabPathSegment(discussionId)}`);
|
|
3470
|
+
const response = await fetch(url.toString(), {
|
|
3471
|
+
...getFetchConfig(),
|
|
3472
|
+
method: "GET",
|
|
3473
|
+
});
|
|
3474
|
+
await handleGitLabError(response);
|
|
3475
|
+
const data = await response.json();
|
|
3476
|
+
return GitLabDiscussionSchema.parse(data);
|
|
3477
|
+
}
|
|
3411
3478
|
async function getMergeRequestNotes(projectId, mergeRequestIid, sort, order_by, per_page, page) {
|
|
3412
3479
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3413
3480
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/notes`);
|
|
@@ -7957,6 +8024,13 @@ async function handleToolCall(params) {
|
|
|
7957
8024
|
content: [{ type: "text", text: JSON.stringify(note) }],
|
|
7958
8025
|
};
|
|
7959
8026
|
}
|
|
8027
|
+
case "get_merge_request_discussion": {
|
|
8028
|
+
const args = GetMergeRequestDiscussionSchema.parse(params.arguments);
|
|
8029
|
+
const discussion = await getMergeRequestDiscussion(args.project_id, args.merge_request_iid, args.discussion_id);
|
|
8030
|
+
return {
|
|
8031
|
+
content: [{ type: "text", text: JSON.stringify(discussion) }],
|
|
8032
|
+
};
|
|
8033
|
+
}
|
|
7960
8034
|
case "get_merge_request_notes": {
|
|
7961
8035
|
const args = GetMergeRequestNotesSchema.parse(params.arguments);
|
|
7962
8036
|
const notes = await getMergeRequestNotes(args.project_id, args.merge_request_iid, args.sort, args.order_by, args.per_page, args.page);
|