@zereight/mcp-gitlab 2.1.62 → 2.1.64
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 +178 -235
- 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 +6 -2
- package/build/scripts/generate-tool-coverage.js +58 -0
- package/build/scripts/generate-tool-docs.js +3 -0
- package/build/scripts/tool-coverage/coverage.js +339 -0
- package/build/scripts/tool-coverage/coverage.test.js +142 -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-blank-filters.js +156 -0
- package/build/test/test-empty-toolsets.js +535 -0
- package/build/test/test-list-issues.js +47 -0
- package/build/test/test-list-merge-requests.js +76 -0
- package/build/test/test-list-milestones.js +145 -0
- package/build/test/test-toolset-filtering.js +1 -1
- package/build/test/utils/masking.test.js +384 -0
- package/build/test/utils/tool-args.test.js +57 -1
- package/build/tools/registry.js +8 -1
- package/build/tools/tool-descriptions.js +1 -0
- package/build/utils/tool-args.js +35 -0
- package/package.json +2 -1
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,11 +95,12 @@ 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";
|
|
101
102
|
import { buildBulkPublishDraftNotesBody, needsGitLab19_2BulkPublish, } from "./utils/bulk-publish-options.js";
|
|
102
|
-
import { cleanMutuallyExclusiveIdUsernameOptions, LIST_MERGE_REQUESTS_ID_USERNAME_PAIRS, sanitizeToolArguments, } from "./utils/tool-args.js";
|
|
103
|
+
import { appendFilterParam, appendFilterParams, cleanMutuallyExclusiveIdUsernameOptions, dropBlankArrayEntries, isBlankFilterValue, LIST_MERGE_REQUESTS_ID_USERNAME_PAIRS, sanitizeToolArguments, } from "./utils/tool-args.js";
|
|
103
104
|
import { parseSearchReplaceBlocks, applySearchReplace, applyUnifiedDiff, } from "./utils/patch-helper.js";
|
|
104
105
|
import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
|
|
105
106
|
import { GitLabClientPool } from "./gitlab-client-pool.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
|
}
|
|
@@ -1450,23 +1506,26 @@ async function listIssues(projectId, options = {}) {
|
|
|
1450
1506
|
}
|
|
1451
1507
|
// Add all query parameters
|
|
1452
1508
|
Object.entries(options).forEach(([key, value]) => {
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
url.searchParams.append(`${key}[]`,
|
|
1464
|
-
}
|
|
1509
|
+
// Drop blank entries inside arrays so ["", "bug"] behaves like the scalar guard below
|
|
1510
|
+
const normalized = dropBlankArrayEntries(value);
|
|
1511
|
+
if (isBlankFilterValue(normalized)) {
|
|
1512
|
+
return;
|
|
1513
|
+
}
|
|
1514
|
+
const keys = ["labels", "assignee_username"];
|
|
1515
|
+
if (keys.includes(key)) {
|
|
1516
|
+
if (Array.isArray(normalized)) {
|
|
1517
|
+
// Handle array of labels
|
|
1518
|
+
normalized.forEach(label => {
|
|
1519
|
+
url.searchParams.append(`${key}[]`, String(label));
|
|
1520
|
+
});
|
|
1465
1521
|
}
|
|
1466
1522
|
else {
|
|
1467
|
-
url.searchParams.append(key
|
|
1523
|
+
url.searchParams.append(`${key}[]`, String(normalized));
|
|
1468
1524
|
}
|
|
1469
1525
|
}
|
|
1526
|
+
else {
|
|
1527
|
+
url.searchParams.append(key, String(normalized));
|
|
1528
|
+
}
|
|
1470
1529
|
});
|
|
1471
1530
|
const response = await fetch(url.toString(), {
|
|
1472
1531
|
...getFetchConfig(),
|
|
@@ -1477,11 +1536,7 @@ async function listIssues(projectId, options = {}) {
|
|
|
1477
1536
|
}
|
|
1478
1537
|
async function listTodos(options = {}) {
|
|
1479
1538
|
const url = new URL(`${getEffectiveApiUrl()}/todos`);
|
|
1480
|
-
|
|
1481
|
-
if (value !== undefined) {
|
|
1482
|
-
url.searchParams.append(key, String(value));
|
|
1483
|
-
}
|
|
1484
|
-
});
|
|
1539
|
+
appendFilterParams(url.searchParams, options);
|
|
1485
1540
|
const response = await fetch(url.toString(), {
|
|
1486
1541
|
...getFetchConfig(),
|
|
1487
1542
|
});
|
|
@@ -1510,20 +1565,22 @@ async function markAllTodosDone() {
|
|
|
1510
1565
|
*/
|
|
1511
1566
|
function appendMergeRequestFilters(url, options) {
|
|
1512
1567
|
Object.entries(options).forEach(([key, value]) => {
|
|
1513
|
-
|
|
1568
|
+
// Drop blank entries inside arrays so ["", "bug"] behaves like the scalar guard below
|
|
1569
|
+
const normalized = dropBlankArrayEntries(value);
|
|
1570
|
+
if (isBlankFilterValue(normalized)) {
|
|
1514
1571
|
return;
|
|
1515
1572
|
}
|
|
1516
|
-
if (key === "labels" && Array.isArray(
|
|
1517
|
-
url.searchParams.append(key,
|
|
1573
|
+
if (key === "labels" && Array.isArray(normalized)) {
|
|
1574
|
+
url.searchParams.append(key, normalized.join(","));
|
|
1518
1575
|
}
|
|
1519
|
-
else if (key === "approved_by_usernames" && Array.isArray(
|
|
1576
|
+
else if (key === "approved_by_usernames" && Array.isArray(normalized)) {
|
|
1520
1577
|
// GitLab expects array-bracket form: approved_by_usernames[]=alice&approved_by_usernames[]=bob
|
|
1521
|
-
for (const v of
|
|
1578
|
+
for (const v of normalized) {
|
|
1522
1579
|
url.searchParams.append(`${key}[]`, String(v));
|
|
1523
1580
|
}
|
|
1524
1581
|
}
|
|
1525
1582
|
else {
|
|
1526
|
-
url.searchParams.append(key, String(
|
|
1583
|
+
url.searchParams.append(key, String(normalized));
|
|
1527
1584
|
}
|
|
1528
1585
|
});
|
|
1529
1586
|
}
|
|
@@ -3408,6 +3465,17 @@ async function getMergeRequestNote(projectId, mergeRequestIid, noteId) {
|
|
|
3408
3465
|
const data = await response.json();
|
|
3409
3466
|
return GitLabDiscussionNoteSchema.parse(data);
|
|
3410
3467
|
}
|
|
3468
|
+
async function getMergeRequestDiscussion(projectId, mergeRequestIid, discussionId) {
|
|
3469
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3470
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/discussions/${encodeGitLabPathSegment(discussionId)}`);
|
|
3471
|
+
const response = await fetch(url.toString(), {
|
|
3472
|
+
...getFetchConfig(),
|
|
3473
|
+
method: "GET",
|
|
3474
|
+
});
|
|
3475
|
+
await handleGitLabError(response);
|
|
3476
|
+
const data = await response.json();
|
|
3477
|
+
return GitLabDiscussionSchema.parse(data);
|
|
3478
|
+
}
|
|
3411
3479
|
async function getMergeRequestNotes(projectId, mergeRequestIid, sort, order_by, per_page, page) {
|
|
3412
3480
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
3413
3481
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/notes`);
|
|
@@ -3733,11 +3801,7 @@ async function getMergeRequestSourceCommitCount(projectId, mergeRequestIid) {
|
|
|
3733
3801
|
async function listMergeRequestPipelines(projectId, mergeRequestIid, options = {}) {
|
|
3734
3802
|
projectId = decodeURIComponent(projectId);
|
|
3735
3803
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${encodeGitLabPathSegment(mergeRequestIid)}/pipelines`);
|
|
3736
|
-
|
|
3737
|
-
if (value !== undefined) {
|
|
3738
|
-
url.searchParams.append(key, value.toString());
|
|
3739
|
-
}
|
|
3740
|
-
});
|
|
3804
|
+
appendFilterParams(url.searchParams, options);
|
|
3741
3805
|
const response = await fetch(url.toString(), {
|
|
3742
3806
|
...getFetchConfig(),
|
|
3743
3807
|
});
|
|
@@ -4667,16 +4731,7 @@ async function getMergeRequestVersion(projectId, mergeRequestIid, versionId, uni
|
|
|
4667
4731
|
async function listProjects(options = {}) {
|
|
4668
4732
|
// Construct the query parameters
|
|
4669
4733
|
const params = new URLSearchParams();
|
|
4670
|
-
|
|
4671
|
-
if (value !== undefined && value !== null) {
|
|
4672
|
-
if (typeof value === "boolean") {
|
|
4673
|
-
params.append(key, value ? "true" : "false");
|
|
4674
|
-
}
|
|
4675
|
-
else {
|
|
4676
|
-
params.append(key, String(value));
|
|
4677
|
-
}
|
|
4678
|
-
}
|
|
4679
|
-
}
|
|
4734
|
+
appendFilterParams(params, options);
|
|
4680
4735
|
// Make the API request
|
|
4681
4736
|
const response = await fetch(`${getEffectiveApiUrl()}/projects?${params.toString()}`, {
|
|
4682
4737
|
...getFetchConfig(),
|
|
@@ -4699,16 +4754,7 @@ async function listLabels(projectId, options = {}) {
|
|
|
4699
4754
|
// Construct the URL with project path
|
|
4700
4755
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/labels`);
|
|
4701
4756
|
// Add query parameters
|
|
4702
|
-
|
|
4703
|
-
if (value !== undefined) {
|
|
4704
|
-
if (typeof value === "boolean") {
|
|
4705
|
-
url.searchParams.append(key, value ? "true" : "false");
|
|
4706
|
-
}
|
|
4707
|
-
else {
|
|
4708
|
-
url.searchParams.append(key, String(value));
|
|
4709
|
-
}
|
|
4710
|
-
}
|
|
4711
|
-
});
|
|
4757
|
+
appendFilterParams(url.searchParams, options);
|
|
4712
4758
|
// Make the API request
|
|
4713
4759
|
const response = await fetch(url.toString(), {
|
|
4714
4760
|
...getFetchConfig(),
|
|
@@ -5165,11 +5211,7 @@ async function listPipelines(projectId, options = {}) {
|
|
|
5165
5211
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5166
5212
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines`);
|
|
5167
5213
|
// Add all query parameters
|
|
5168
|
-
|
|
5169
|
-
if (value !== undefined) {
|
|
5170
|
-
url.searchParams.append(key, value.toString());
|
|
5171
|
-
}
|
|
5172
|
-
});
|
|
5214
|
+
appendFilterParams(url.searchParams, options);
|
|
5173
5215
|
const response = await fetch(url.toString(), {
|
|
5174
5216
|
...getFetchConfig(),
|
|
5175
5217
|
});
|
|
@@ -5243,11 +5285,7 @@ async function updatePipelineMetadata(projectId, pipelineId, name) {
|
|
|
5243
5285
|
async function listDeployments(projectId, options = {}) {
|
|
5244
5286
|
projectId = decodeURIComponent(projectId);
|
|
5245
5287
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/deployments`);
|
|
5246
|
-
|
|
5247
|
-
if (value !== undefined) {
|
|
5248
|
-
url.searchParams.append(key, value.toString());
|
|
5249
|
-
}
|
|
5250
|
-
});
|
|
5288
|
+
appendFilterParams(url.searchParams, options);
|
|
5251
5289
|
const response = await fetch(url.toString(), {
|
|
5252
5290
|
...getFetchConfig(),
|
|
5253
5291
|
});
|
|
@@ -5299,11 +5337,7 @@ async function deploymentRequest(projectId, path = "", method = "GET", body, que
|
|
|
5299
5337
|
async function listEnvironments(projectId, options = {}) {
|
|
5300
5338
|
projectId = decodeURIComponent(projectId);
|
|
5301
5339
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/environments`);
|
|
5302
|
-
|
|
5303
|
-
if (value !== undefined) {
|
|
5304
|
-
url.searchParams.append(key, value.toString());
|
|
5305
|
-
}
|
|
5306
|
-
});
|
|
5340
|
+
appendFilterParams(url.searchParams, options);
|
|
5307
5341
|
const response = await fetch(url.toString(), {
|
|
5308
5342
|
...getFetchConfig(),
|
|
5309
5343
|
});
|
|
@@ -5373,16 +5407,7 @@ async function listPipelineJobs(projectId, pipelineId, options = {}) {
|
|
|
5373
5407
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5374
5408
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/jobs`);
|
|
5375
5409
|
// Add all query parameters
|
|
5376
|
-
|
|
5377
|
-
if (value !== undefined) {
|
|
5378
|
-
if (typeof value === "boolean") {
|
|
5379
|
-
url.searchParams.append(key, value ? "true" : "false");
|
|
5380
|
-
}
|
|
5381
|
-
else {
|
|
5382
|
-
url.searchParams.append(key, value.toString());
|
|
5383
|
-
}
|
|
5384
|
-
}
|
|
5385
|
-
});
|
|
5410
|
+
appendFilterParams(url.searchParams, options);
|
|
5386
5411
|
const response = await fetch(url.toString(), {
|
|
5387
5412
|
...getFetchConfig(),
|
|
5388
5413
|
});
|
|
@@ -5405,16 +5430,7 @@ async function listPipelineTriggerJobs(projectId, pipelineId, options = {}) {
|
|
|
5405
5430
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5406
5431
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${encodeGitLabPathSegment(pipelineId)}/bridges`);
|
|
5407
5432
|
// Add all query parameters
|
|
5408
|
-
|
|
5409
|
-
if (value !== undefined) {
|
|
5410
|
-
if (typeof value === "boolean") {
|
|
5411
|
-
url.searchParams.append(key, value ? "true" : "false");
|
|
5412
|
-
}
|
|
5413
|
-
else {
|
|
5414
|
-
url.searchParams.append(key, value.toString());
|
|
5415
|
-
}
|
|
5416
|
-
}
|
|
5417
|
-
});
|
|
5433
|
+
appendFilterParams(url.searchParams, options);
|
|
5418
5434
|
const response = await fetch(url.toString(), {
|
|
5419
5435
|
...getFetchConfig(),
|
|
5420
5436
|
});
|
|
@@ -5499,16 +5515,7 @@ async function validateCiLint(projectId, options) {
|
|
|
5499
5515
|
async function validateProjectCiLint(projectId, options) {
|
|
5500
5516
|
projectId = decodeURIComponent(projectId);
|
|
5501
5517
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/ci/lint`);
|
|
5502
|
-
|
|
5503
|
-
if (value !== undefined) {
|
|
5504
|
-
if (typeof value === "boolean") {
|
|
5505
|
-
url.searchParams.append(key, value ? "true" : "false");
|
|
5506
|
-
}
|
|
5507
|
-
else {
|
|
5508
|
-
url.searchParams.append(key, value.toString());
|
|
5509
|
-
}
|
|
5510
|
-
}
|
|
5511
|
-
});
|
|
5518
|
+
appendFilterParams(url.searchParams, options);
|
|
5512
5519
|
const response = await fetch(url.toString(), {
|
|
5513
5520
|
...getFetchConfig(),
|
|
5514
5521
|
});
|
|
@@ -5527,16 +5534,7 @@ async function validateProjectCiLint(projectId, options) {
|
|
|
5527
5534
|
async function listJobArtifacts(projectId, jobId, options = {}) {
|
|
5528
5535
|
projectId = decodeURIComponent(projectId);
|
|
5529
5536
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/jobs/${encodeGitLabPathSegment(jobId)}/artifacts/tree`);
|
|
5530
|
-
|
|
5531
|
-
if (value !== undefined) {
|
|
5532
|
-
if (typeof value === "boolean") {
|
|
5533
|
-
url.searchParams.append(key, value ? "true" : "false");
|
|
5534
|
-
}
|
|
5535
|
-
else {
|
|
5536
|
-
url.searchParams.append(key, value.toString());
|
|
5537
|
-
}
|
|
5538
|
-
}
|
|
5539
|
-
});
|
|
5537
|
+
appendFilterParams(url.searchParams, options);
|
|
5540
5538
|
const response = await fetch(url.toString(), {
|
|
5541
5539
|
...getFetchConfig(),
|
|
5542
5540
|
});
|
|
@@ -5673,11 +5671,7 @@ async function cancelPipeline(projectId, pipelineId) {
|
|
|
5673
5671
|
async function listPipelineSchedules(projectId, options = {}) {
|
|
5674
5672
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5675
5673
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipeline_schedules`);
|
|
5676
|
-
|
|
5677
|
-
if (value !== undefined) {
|
|
5678
|
-
url.searchParams.append(key, value.toString());
|
|
5679
|
-
}
|
|
5680
|
-
});
|
|
5674
|
+
appendFilterParams(url.searchParams, options);
|
|
5681
5675
|
const response = await fetch(url.toString(), {
|
|
5682
5676
|
...getFetchConfig(),
|
|
5683
5677
|
});
|
|
@@ -5713,11 +5707,7 @@ async function getPipelineSchedule(projectId, pipelineScheduleId) {
|
|
|
5713
5707
|
async function listPipelineSchedulePipelines(projectId, pipelineScheduleId, options = {}) {
|
|
5714
5708
|
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
5715
5709
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipeline_schedules/${encodeGitLabPathSegment(pipelineScheduleId)}/pipelines`);
|
|
5716
|
-
|
|
5717
|
-
if (value !== undefined) {
|
|
5718
|
-
url.searchParams.append(key, value.toString());
|
|
5719
|
-
}
|
|
5720
|
-
});
|
|
5710
|
+
appendFilterParams(url.searchParams, options);
|
|
5721
5711
|
const response = await fetch(url.toString(), {
|
|
5722
5712
|
...getFetchConfig(),
|
|
5723
5713
|
});
|
|
@@ -6040,15 +6030,18 @@ async function listProjectMilestones(projectId, options) {
|
|
|
6040
6030
|
projectId = decodeURIComponent(projectId);
|
|
6041
6031
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones`);
|
|
6042
6032
|
Object.entries(options).forEach(([key, value]) => {
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
url.searchParams.append(
|
|
6051
|
-
}
|
|
6033
|
+
// Drop blank entries inside arrays so they behave like the scalar blank guard below
|
|
6034
|
+
const normalized = dropBlankArrayEntries(value);
|
|
6035
|
+
if (isBlankFilterValue(normalized)) {
|
|
6036
|
+
return;
|
|
6037
|
+
}
|
|
6038
|
+
if (key === "iids" && Array.isArray(normalized)) {
|
|
6039
|
+
normalized.forEach(iid => {
|
|
6040
|
+
url.searchParams.append("iids[]", String(iid));
|
|
6041
|
+
});
|
|
6042
|
+
}
|
|
6043
|
+
else {
|
|
6044
|
+
url.searchParams.append(key, String(normalized));
|
|
6052
6045
|
}
|
|
6053
6046
|
});
|
|
6054
6047
|
const response = await fetch(url.toString(), {
|
|
@@ -6135,11 +6128,7 @@ async function deleteProjectMilestone(projectId, milestoneId) {
|
|
|
6135
6128
|
async function getMilestoneIssues(projectId, milestoneId, options = {}) {
|
|
6136
6129
|
projectId = decodeURIComponent(projectId);
|
|
6137
6130
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}/issues`);
|
|
6138
|
-
|
|
6139
|
-
if (value !== undefined) {
|
|
6140
|
-
url.searchParams.append(key, String(value));
|
|
6141
|
-
}
|
|
6142
|
-
});
|
|
6131
|
+
appendFilterParams(url.searchParams, options);
|
|
6143
6132
|
const response = await fetch(url.toString(), {
|
|
6144
6133
|
...getFetchConfig(),
|
|
6145
6134
|
});
|
|
@@ -6156,11 +6145,7 @@ async function getMilestoneIssues(projectId, milestoneId, options = {}) {
|
|
|
6156
6145
|
async function getMilestoneMergeRequests(projectId, milestoneId, options = {}) {
|
|
6157
6146
|
projectId = decodeURIComponent(projectId);
|
|
6158
6147
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}/merge_requests`);
|
|
6159
|
-
|
|
6160
|
-
if (value !== undefined) {
|
|
6161
|
-
url.searchParams.append(key, String(value));
|
|
6162
|
-
}
|
|
6163
|
-
});
|
|
6148
|
+
appendFilterParams(url.searchParams, options);
|
|
6164
6149
|
const response = await fetch(url.toString(), {
|
|
6165
6150
|
...getFetchConfig(),
|
|
6166
6151
|
});
|
|
@@ -6194,11 +6179,7 @@ async function promoteProjectMilestone(projectId, milestoneId) {
|
|
|
6194
6179
|
async function getMilestoneBurndownEvents(projectId, milestoneId, options = {}) {
|
|
6195
6180
|
projectId = decodeURIComponent(projectId);
|
|
6196
6181
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${encodeGitLabPathSegment(milestoneId)}/burndown_events`);
|
|
6197
|
-
|
|
6198
|
-
if (value !== undefined) {
|
|
6199
|
-
url.searchParams.append(key, String(value));
|
|
6200
|
-
}
|
|
6201
|
-
});
|
|
6182
|
+
appendFilterParams(url.searchParams, options);
|
|
6202
6183
|
const response = await fetch(url.toString(), {
|
|
6203
6184
|
...getFetchConfig(),
|
|
6204
6185
|
});
|
|
@@ -6213,15 +6194,18 @@ async function listGroupMilestones(groupId, options) {
|
|
|
6213
6194
|
groupId = decodeURIComponent(groupId);
|
|
6214
6195
|
const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(groupId)}/milestones`);
|
|
6215
6196
|
Object.entries(options).forEach(([key, value]) => {
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
url.searchParams.append(
|
|
6224
|
-
}
|
|
6197
|
+
// Drop blank entries inside arrays so they behave like the scalar blank guard below
|
|
6198
|
+
const normalized = dropBlankArrayEntries(value);
|
|
6199
|
+
if (isBlankFilterValue(normalized)) {
|
|
6200
|
+
return;
|
|
6201
|
+
}
|
|
6202
|
+
if (key === "iids" && Array.isArray(normalized)) {
|
|
6203
|
+
normalized.forEach(iid => {
|
|
6204
|
+
url.searchParams.append("iids[]", String(iid));
|
|
6205
|
+
});
|
|
6206
|
+
}
|
|
6207
|
+
else {
|
|
6208
|
+
url.searchParams.append(key, String(normalized));
|
|
6225
6209
|
}
|
|
6226
6210
|
});
|
|
6227
6211
|
const response = await fetch(url.toString(), {
|
|
@@ -6292,11 +6276,7 @@ async function deleteGroupMilestone(groupId, milestoneId) {
|
|
|
6292
6276
|
async function getGroupMilestoneIssues(groupId, milestoneId, options = {}) {
|
|
6293
6277
|
groupId = decodeURIComponent(groupId);
|
|
6294
6278
|
const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(groupId)}/milestones/${encodeGitLabPathSegment(milestoneId)}/issues`);
|
|
6295
|
-
|
|
6296
|
-
if (value !== undefined) {
|
|
6297
|
-
url.searchParams.append(key, String(value));
|
|
6298
|
-
}
|
|
6299
|
-
});
|
|
6279
|
+
appendFilterParams(url.searchParams, options);
|
|
6300
6280
|
const response = await fetch(url.toString(), {
|
|
6301
6281
|
...getFetchConfig(),
|
|
6302
6282
|
});
|
|
@@ -6310,11 +6290,7 @@ async function getGroupMilestoneIssues(groupId, milestoneId, options = {}) {
|
|
|
6310
6290
|
async function getGroupMilestoneMergeRequests(groupId, milestoneId, options = {}) {
|
|
6311
6291
|
groupId = decodeURIComponent(groupId);
|
|
6312
6292
|
const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(groupId)}/milestones/${encodeGitLabPathSegment(milestoneId)}/merge_requests`);
|
|
6313
|
-
|
|
6314
|
-
if (value !== undefined) {
|
|
6315
|
-
url.searchParams.append(key, String(value));
|
|
6316
|
-
}
|
|
6317
|
-
});
|
|
6293
|
+
appendFilterParams(url.searchParams, options);
|
|
6318
6294
|
const response = await fetch(url.toString(), {
|
|
6319
6295
|
...getFetchConfig(),
|
|
6320
6296
|
});
|
|
@@ -6328,11 +6304,7 @@ async function getGroupMilestoneMergeRequests(groupId, milestoneId, options = {}
|
|
|
6328
6304
|
async function getGroupMilestoneBurndownEvents(groupId, milestoneId, options = {}) {
|
|
6329
6305
|
groupId = decodeURIComponent(groupId);
|
|
6330
6306
|
const url = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(groupId)}/milestones/${encodeGitLabPathSegment(milestoneId)}/burndown_events`);
|
|
6331
|
-
|
|
6332
|
-
if (value !== undefined) {
|
|
6333
|
-
url.searchParams.append(key, String(value));
|
|
6334
|
-
}
|
|
6335
|
-
});
|
|
6307
|
+
appendFilterParams(url.searchParams, options);
|
|
6336
6308
|
const response = await fetch(url.toString(), {
|
|
6337
6309
|
...getFetchConfig(),
|
|
6338
6310
|
});
|
|
@@ -6531,11 +6503,7 @@ async function getFileBlame(projectId, options) {
|
|
|
6531
6503
|
async function listCommitStatuses(projectId, sha, options = {}) {
|
|
6532
6504
|
projectId = decodeURIComponent(projectId);
|
|
6533
6505
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/commits/${encodeURIComponent(sha)}/statuses`);
|
|
6534
|
-
|
|
6535
|
-
if (value !== undefined) {
|
|
6536
|
-
url.searchParams.append(key, value.toString());
|
|
6537
|
-
}
|
|
6538
|
-
});
|
|
6506
|
+
appendFilterParams(url.searchParams, options);
|
|
6539
6507
|
const response = await fetch(url.toString(), {
|
|
6540
6508
|
...getFetchConfig(),
|
|
6541
6509
|
});
|
|
@@ -6557,11 +6525,7 @@ async function createCommitStatus(projectId, sha, options) {
|
|
|
6557
6525
|
}
|
|
6558
6526
|
projectId = decodeURIComponent(projectId);
|
|
6559
6527
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/statuses/${encodeURIComponent(sha)}`);
|
|
6560
|
-
|
|
6561
|
-
if (value !== undefined) {
|
|
6562
|
-
url.searchParams.append(key, value.toString());
|
|
6563
|
-
}
|
|
6564
|
-
});
|
|
6528
|
+
appendFilterParams(url.searchParams, options);
|
|
6565
6529
|
const response = await fetch(url.toString(), {
|
|
6566
6530
|
...getFetchConfig(),
|
|
6567
6531
|
method: "POST",
|
|
@@ -6739,19 +6703,13 @@ async function listProjectVariables(projectId, options = {}) {
|
|
|
6739
6703
|
projectId = decodeURIComponent(projectId);
|
|
6740
6704
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/variables`);
|
|
6741
6705
|
Object.entries(options).forEach(([key, value]) => {
|
|
6742
|
-
if (value === undefined)
|
|
6743
|
-
return;
|
|
6744
6706
|
if (key === "filter" && typeof value === "object" && value !== null) {
|
|
6745
|
-
Object.entries(value).forEach(([
|
|
6746
|
-
url.searchParams
|
|
6707
|
+
Object.entries(value).forEach(([filterKey, filterValue]) => {
|
|
6708
|
+
appendFilterParam(url.searchParams, `filter[${filterKey}]`, filterValue);
|
|
6747
6709
|
});
|
|
6710
|
+
return;
|
|
6748
6711
|
}
|
|
6749
|
-
|
|
6750
|
-
url.searchParams.append(key, value ? "true" : "false");
|
|
6751
|
-
}
|
|
6752
|
-
else {
|
|
6753
|
-
url.searchParams.append(key, String(value));
|
|
6754
|
-
}
|
|
6712
|
+
appendFilterParam(url.searchParams, key, value);
|
|
6755
6713
|
});
|
|
6756
6714
|
const response = await fetch(url.toString(), getFetchConfig());
|
|
6757
6715
|
await handleGitLabError(response);
|
|
@@ -6805,19 +6763,13 @@ async function listGroupVariables(groupId, options = {}) {
|
|
|
6805
6763
|
const encoded = encodeURIComponent(decodeURIComponent(groupId));
|
|
6806
6764
|
const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/variables`);
|
|
6807
6765
|
Object.entries(options).forEach(([key, value]) => {
|
|
6808
|
-
if (value === undefined)
|
|
6809
|
-
return;
|
|
6810
6766
|
if (key === "filter" && typeof value === "object" && value !== null) {
|
|
6811
|
-
Object.entries(value).forEach(([
|
|
6812
|
-
url.searchParams
|
|
6767
|
+
Object.entries(value).forEach(([filterKey, filterValue]) => {
|
|
6768
|
+
appendFilterParam(url.searchParams, `filter[${filterKey}]`, filterValue);
|
|
6813
6769
|
});
|
|
6770
|
+
return;
|
|
6814
6771
|
}
|
|
6815
|
-
|
|
6816
|
-
url.searchParams.append(key, value ? "true" : "false");
|
|
6817
|
-
}
|
|
6818
|
-
else {
|
|
6819
|
-
url.searchParams.append(key, String(value));
|
|
6820
|
-
}
|
|
6772
|
+
appendFilterParam(url.searchParams, key, value);
|
|
6821
6773
|
});
|
|
6822
6774
|
const response = await fetch(url.toString(), getFetchConfig());
|
|
6823
6775
|
await handleGitLabError(response);
|
|
@@ -7364,11 +7316,7 @@ async function downloadAttachment(projectId, secret, filename, localPath) {
|
|
|
7364
7316
|
async function listEvents(options = {}) {
|
|
7365
7317
|
const url = new URL(`${getEffectiveApiUrl()}/events`);
|
|
7366
7318
|
// Add all query parameters
|
|
7367
|
-
|
|
7368
|
-
if (value !== undefined) {
|
|
7369
|
-
url.searchParams.append(key, value.toString());
|
|
7370
|
-
}
|
|
7371
|
-
});
|
|
7319
|
+
appendFilterParams(url.searchParams, options);
|
|
7372
7320
|
const response = await fetch(url.toString(), { ...getFetchConfig() });
|
|
7373
7321
|
if (!response.ok) {
|
|
7374
7322
|
await handleGitLabError(response);
|
|
@@ -7386,11 +7334,7 @@ async function getProjectEvents(projectId, options = {}) {
|
|
|
7386
7334
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
7387
7335
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/events`);
|
|
7388
7336
|
// Add all query parameters
|
|
7389
|
-
|
|
7390
|
-
if (value !== undefined) {
|
|
7391
|
-
url.searchParams.append(key, value.toString());
|
|
7392
|
-
}
|
|
7393
|
-
});
|
|
7337
|
+
appendFilterParams(url.searchParams, options);
|
|
7394
7338
|
const response = await fetch(url.toString(), { ...getFetchConfig() });
|
|
7395
7339
|
if (!response.ok) {
|
|
7396
7340
|
await handleGitLabError(response);
|
|
@@ -7409,11 +7353,7 @@ async function listReleases(projectId, options = {}) {
|
|
|
7409
7353
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
7410
7354
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/releases`);
|
|
7411
7355
|
// Add query parameters
|
|
7412
|
-
|
|
7413
|
-
if (value !== undefined) {
|
|
7414
|
-
url.searchParams.append(key, value.toString());
|
|
7415
|
-
}
|
|
7416
|
-
});
|
|
7356
|
+
appendFilterParams(url.searchParams, options);
|
|
7417
7357
|
const response = await fetch(url.toString(), {
|
|
7418
7358
|
...getFetchConfig(),
|
|
7419
7359
|
});
|
|
@@ -7536,11 +7476,7 @@ async function downloadReleaseAsset(projectId, tagName, directAssetPath) {
|
|
|
7536
7476
|
async function listTags(projectId, options = {}) {
|
|
7537
7477
|
const effectiveProjectId = getEffectiveProjectId(projectId);
|
|
7538
7478
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}/repository/tags`);
|
|
7539
|
-
|
|
7540
|
-
if (value !== undefined) {
|
|
7541
|
-
url.searchParams.append(key, value.toString());
|
|
7542
|
-
}
|
|
7543
|
-
});
|
|
7479
|
+
appendFilterParams(url.searchParams, options);
|
|
7544
7480
|
const response = await fetch(url.toString(), {
|
|
7545
7481
|
...getFetchConfig(),
|
|
7546
7482
|
});
|
|
@@ -7957,6 +7893,13 @@ async function handleToolCall(params) {
|
|
|
7957
7893
|
content: [{ type: "text", text: JSON.stringify(note) }],
|
|
7958
7894
|
};
|
|
7959
7895
|
}
|
|
7896
|
+
case "get_merge_request_discussion": {
|
|
7897
|
+
const args = GetMergeRequestDiscussionSchema.parse(params.arguments);
|
|
7898
|
+
const discussion = await getMergeRequestDiscussion(args.project_id, args.merge_request_iid, args.discussion_id);
|
|
7899
|
+
return {
|
|
7900
|
+
content: [{ type: "text", text: JSON.stringify(discussion) }],
|
|
7901
|
+
};
|
|
7902
|
+
}
|
|
7960
7903
|
case "get_merge_request_notes": {
|
|
7961
7904
|
const args = GetMergeRequestNotesSchema.parse(params.arguments);
|
|
7962
7905
|
const notes = await getMergeRequestNotes(args.project_id, args.merge_request_iid, args.sort, args.order_by, args.per_page, args.page);
|
|
@@ -8773,8 +8716,8 @@ async function handleToolCall(params) {
|
|
|
8773
8716
|
};
|
|
8774
8717
|
}
|
|
8775
8718
|
case "list_labels": {
|
|
8776
|
-
const
|
|
8777
|
-
const labels = await listLabels(
|
|
8719
|
+
const { project_id, ...options } = ListLabelsSchema.parse(params.arguments);
|
|
8720
|
+
const labels = await listLabels(project_id, options);
|
|
8778
8721
|
return {
|
|
8779
8722
|
content: [{ type: "text", text: JSON.stringify(labels) }],
|
|
8780
8723
|
};
|