@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 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.61`처럼 직전 안정 버전(문서가 권장하는 버전)으로 고정하세요. 항상 최신 버전을 원하면 `npx -y @zereight/mcp-gitlab@latest`를 사용하세요. 새 버전이 나오면 서버가 시작 시 stderr로 알려줍니다(`GITLAB_DISABLE_VERSION_CHECK=true`로 비활성화 가능).
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.61`. 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`).
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.61`。如果始终想使用最新版本,请改用 `npx -y @zereight/mcp-gitlab@latest`。有新版本发布时,服务器会在启动时通过 stderr 提示(可用 `GITLAB_DISABLE_VERSION_CHECK=true` 关闭)。
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
- return result;
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: error instanceof Error ? error.message : String(error),
322
+ error: safeError instanceof Error ? safeError.message : String(safeError),
307
323
  }, `tool_call_error: ${toolName} (${durationMs}ms)`);
308
- throw error;
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 ((REMOTE_AUTHORIZATION || GITLAB_MCP_OAUTH) && sessionId && authBySession[sessionId]) {
425
- const authData = authBySession[sessionId];
426
- const sessionContext = {
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
- logger.error({ err: errorBody }, "GitLab API Rate Limit Exceeded");
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
- throw new Error(`GitLab API Rate Limit Exceeded: ${errorBody}`);
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
- throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`);
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);