@zereight/mcp-gitlab 2.1.16 → 2.1.18

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.md CHANGED
@@ -647,6 +647,20 @@ Register the skill directory in your AI client to get optimal tool usage guidanc
647
647
  154. `search_group_code` - Search for code within a specific GitLab group (requires advanced search or exact code search to be enabled)
648
648
  155. `execute_graphql` - Execute a GitLab GraphQL query
649
649
  156. `list_merge_request_pipelines` - List pipelines for a merge request with pagination support
650
+ 157. `list_project_variables` - List CI/CD variables for a project with optional environment scope filter
651
+ 158. `get_project_variable` - Get a single CI/CD variable from a project by key, with optional environment scope filter
652
+ 159. `create_project_variable` - Create a new CI/CD variable in a project
653
+ 160. `update_project_variable` - Update an existing CI/CD variable in a project, with optional filter to disambiguate by environment scope
654
+ 161. `delete_project_variable` - Delete a CI/CD variable from a project, with optional filter to disambiguate by environment scope
655
+ 162. `list_group_variables` - List CI/CD variables for a group with optional environment scope filter
656
+ 163. `get_group_variable` - Get a single CI/CD variable from a group by key, with optional environment scope filter
657
+ 164. `create_group_variable` - Create a new CI/CD variable in a group
658
+ 165. `update_group_variable` - Update an existing CI/CD variable in a group, with optional filter to disambiguate by environment scope
659
+ 166. `delete_group_variable` - Delete a CI/CD variable from a group, with optional filter to disambiguate by environment scope
660
+ 167. `get_dependency_proxy_settings` - Get dependency proxy settings for a group (enabled status, blob count, total size, image prefix, TTL policy)
661
+ 168. `update_dependency_proxy_settings` - Update dependency proxy settings for a group (enable/disable, credentials for authenticated Docker Hub pulls)
662
+ 169. `list_dependency_proxy_blobs` - List cached dependency proxy blobs for a group with cursor-based pagination
663
+ 170. `purge_dependency_proxy_cache` - Schedule purge of all cached dependency proxy blobs for a group
650
664
 
651
665
  <!-- TOOLS-END -->
652
666
 
package/build/index.js CHANGED
@@ -139,7 +139,7 @@ CreateMergeRequestNoteSchema, CreateMergeRequestDiscussionNoteSchema, CreateMerg
139
139
  GitLabDiscussionNoteSchema, // Added
140
140
  GitLabDiscussionSchema,
141
141
  // Draft Notes Schemas
142
- GitLabDraftNoteSchema, GitLabForkSchema, GitLabBranchSchema, GitLabGroupSchema, GitLabIssueLinkSchema, GitLabIssueSchema, GitLabIssueWithLinkDetailsSchema, GitLabMarkdownUploadSchema, GitLabMergeRequestPipelineSchema, GitLabMergeRequestSchema, GitLabMilestonesSchema, GitLabNamespaceExistsResponseSchema, GitLabNamespaceSchema, GitLabPipelineJobSchema, GitLabDeploymentSchema, GitLabEnvironmentSchema, GitLabPipelineSchema, GitLabPipelineTriggerJobSchema, GitLabProjectMemberSchema, GitLabProjectSchema, GitLabTodoSchema, GitLabReferenceSchema, GitLabRepositorySchema, GitLabSearchBlobResultSchema, GitLabSearchResponseSchema, GitLabTreeItemSchema, GitLabUserSchema, GitLabUsersResponseSchema, GitLabWikiPageSchema, GroupIteration, ListCommitStatusesSchema, ListBranchesSchema, ListCommitsSchema, ListDraftNotesSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListTodosSchema, ListLabelsSchema, ListMergeRequestDiffsSchema, // Added
142
+ GitLabDraftNoteSchema, GitLabForkSchema, GitLabBranchSchema, GitLabGroupSchema, GitLabIssueLinkSchema, GitLabIssueSchema, GitLabIssueWithLinkDetailsSchema, GitLabMarkdownUploadSchema, GitLabMergeRequestPipelineSchema, GitLabMergeRequestSchema, GitLabMilestonesSchema, GitLabNamespaceExistsResponseSchema, GitLabNamespaceSchema, GitLabPipelineJobSchema, GitLabDeploymentSchema, GitLabEnvironmentSchema, GitLabPipelineSchema, 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, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListTodosSchema, ListLabelsSchema, ListMergeRequestDiffsSchema, // Added
143
143
  GetMergeRequestFileDiffSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestPipelinesSchema, ListMergeRequestsSchema, ListMergeRequestVersionsSchema, GetMergeRequestVersionSchema, GitLabMergeRequestVersionSchema, GitLabMergeRequestVersionDetailSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelinesSchema, ListDeploymentsSchema, ListEnvironmentsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListWikiPagesSchema, GetGroupWikiPageSchema, ListGroupWikiPagesSchema, UpdateGroupWikiPageSchema, MarkdownUploadSchema, MarkdownUploadRemoteSchema, DownloadAttachmentSchema, DownloadJobArtifactsSchema, GetJobArtifactFileSchema, GitLabArtifactEntrySchema, ListJobArtifactsSchema, MergeMergeRequestSchema, ApproveMergeRequestSchema, UnapproveMergeRequestSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GitLabMergeRequestApprovalsResponseSchema, GitLabMergeRequestApprovalStateSchema, MyIssuesSchema, MarkAllTodosDoneSchema, MarkTodoDoneSchema, PaginatedDiscussionsResponseSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PlayPipelineJobSchema, PushFilesSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UpdateDraftNoteSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateIssueDescriptionPatchSchema, UpdateLabelSchema, 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, ListWebhookEventsSchema, GetWebhookEventSchema, HealthCheckSchema, } from "./schemas.js";
144
144
  import { randomUUID, createCipheriv, createDecipheriv, randomBytes, createHash } from "node:crypto";
145
145
  import { pino } from "pino";
@@ -1389,16 +1389,9 @@ async function executeGraphQL(query, variables = {}) {
1389
1389
  * Resolve a project path and issue IID to a work item GraphQL GID.
1390
1390
  */
1391
1391
  async function resolveWorkItemGID(projectId, issueIid) {
1392
- projectId = decodeURIComponent(projectId);
1393
- const effectiveProjectId = getEffectiveProjectId(projectId);
1394
- // First get the project path via REST (needed for GraphQL namespace query)
1395
- const projectUrl = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}`);
1396
- const projectResponse = await fetch(projectUrl.toString(), {
1397
- ...getFetchConfig(),
1398
- });
1399
- await handleGitLabError(projectResponse);
1400
- const project = await projectResponse.json();
1401
- const projectPath = project.path_with_namespace;
1392
+ // resolveProjectPath handles both project and group paths (including the
1393
+ // group fallback), so work item tools work for group-level namespaces too.
1394
+ const projectPath = await resolveProjectPath(projectId);
1402
1395
  // Resolve work item GID via GraphQL
1403
1396
  const data = await executeGraphQL(`query($path: ID!, $iid: String!) {
1404
1397
  namespace(fullPath: $path) {
@@ -1521,15 +1514,7 @@ async function removeIssueParent(projectId, issueIid) {
1521
1514
  * Requires Premium/Ultimate with configurable statuses enabled.
1522
1515
  */
1523
1516
  async function listIssueStatuses(projectId, workItemType = "issue") {
1524
- projectId = decodeURIComponent(projectId);
1525
- const effectiveProjectId = getEffectiveProjectId(projectId);
1526
- // Get project path
1527
- const projectUrl = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(effectiveProjectId)}`);
1528
- const projectResponse = await fetch(projectUrl.toString(), {
1529
- ...getFetchConfig(),
1530
- });
1531
- await handleGitLabError(projectResponse);
1532
- const project = await projectResponse.json();
1517
+ const projectPath = await resolveProjectPath(projectId);
1533
1518
  const typeName = WORK_ITEM_TYPE_NAMES[workItemType] || "Issue";
1534
1519
  const data = await executeGraphQL(`query($path: ID!, $typeName: IssueType) {
1535
1520
  namespace(fullPath: $path) {
@@ -1557,7 +1542,7 @@ async function listIssueStatuses(projectId, workItemType = "issue") {
1557
1542
  }
1558
1543
  }
1559
1544
  }
1560
- }`, { path: project.path_with_namespace, typeName: typeName.replace(/ /g, "_").toUpperCase() });
1545
+ }`, { path: projectPath, typeName: typeName.replace(/ /g, "_").toUpperCase() });
1561
1546
  const typeNodes = data.namespace?.workItemTypes?.nodes;
1562
1547
  if (!typeNodes || typeNodes.length === 0) {
1563
1548
  throw new Error(`Work item type '${typeName}' not found in project`);
@@ -1953,6 +1938,22 @@ async function resolveProjectPath(projectId) {
1953
1938
  const projectResponse = await fetch(projectUrl.toString(), {
1954
1939
  ...getFetchConfig(),
1955
1940
  });
1941
+ // On project 404, fall back to groups — but only for path-like identifiers.
1942
+ // Numeric IDs must not fall back: group and project IDs share no namespace,
1943
+ // so a numeric project 404 should fail immediately rather than silently
1944
+ // resolving to an unrelated group with the same integer ID.
1945
+ if (projectResponse.status === 404 && !/^\d+$/.test(effectiveProjectId)) {
1946
+ const groupUrl = new URL(`${getEffectiveApiUrl()}/groups/${encodeURIComponent(effectiveProjectId)}`);
1947
+ const groupResponse = await fetch(groupUrl.toString(), {
1948
+ ...getFetchConfig(),
1949
+ });
1950
+ if (groupResponse.ok) {
1951
+ const group = await groupResponse.json();
1952
+ return group.full_path;
1953
+ }
1954
+ // Surface the group error
1955
+ await handleGitLabError(groupResponse);
1956
+ }
1956
1957
  await handleGitLabError(projectResponse);
1957
1958
  const project = await projectResponse.json();
1958
1959
  return project.path_with_namespace;
@@ -5741,6 +5742,217 @@ async function listGroupIterations(groupId, options = {}) {
5741
5742
  const data = await response.json();
5742
5743
  return z.array(GroupIteration).parse(data);
5743
5744
  }
5745
+ // --- CI/CD Variables ---
5746
+ async function listProjectVariables(projectId, options = {}) {
5747
+ projectId = decodeURIComponent(projectId);
5748
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/variables`);
5749
+ Object.entries(options).forEach(([key, value]) => {
5750
+ if (value === undefined)
5751
+ return;
5752
+ if (key === "filter" && typeof value === "object" && value !== null) {
5753
+ Object.entries(value).forEach(([fKey, fVal]) => {
5754
+ url.searchParams.append(`filter[${fKey}]`, fVal);
5755
+ });
5756
+ }
5757
+ else if (typeof value === "boolean") {
5758
+ url.searchParams.append(key, value ? "true" : "false");
5759
+ }
5760
+ else {
5761
+ url.searchParams.append(key, String(value));
5762
+ }
5763
+ });
5764
+ const response = await fetch(url.toString(), getFetchConfig());
5765
+ await handleGitLabError(response);
5766
+ const data = await response.json();
5767
+ return z.array(GitLabCiVariableSchema).parse(data);
5768
+ }
5769
+ async function getProjectVariable(projectId, key, filter) {
5770
+ projectId = decodeURIComponent(projectId);
5771
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/variables/${encodeURIComponent(key)}`);
5772
+ if (filter?.environment_scope) {
5773
+ url.searchParams.append("filter[environment_scope]", filter.environment_scope);
5774
+ }
5775
+ const response = await fetch(url.toString(), getFetchConfig());
5776
+ await handleGitLabError(response);
5777
+ const data = await response.json();
5778
+ return GitLabCiVariableSchema.parse(data);
5779
+ }
5780
+ async function createProjectVariable(projectId, options) {
5781
+ projectId = decodeURIComponent(projectId);
5782
+ const response = await fetch(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/variables`, { ...getFetchConfig(), method: "POST", body: JSON.stringify(options) });
5783
+ await handleGitLabError(response);
5784
+ const data = await response.json();
5785
+ return GitLabCiVariableSchema.parse(data);
5786
+ }
5787
+ async function updateProjectVariable(projectId, key, options) {
5788
+ projectId = decodeURIComponent(projectId);
5789
+ const { filter, ...body } = options;
5790
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/variables/${encodeURIComponent(key)}`);
5791
+ if (filter?.environment_scope) {
5792
+ url.searchParams.append("filter[environment_scope]", filter.environment_scope);
5793
+ }
5794
+ const response = await fetch(url.toString(), {
5795
+ ...getFetchConfig(),
5796
+ method: "PUT",
5797
+ body: JSON.stringify(body),
5798
+ });
5799
+ await handleGitLabError(response);
5800
+ const data = await response.json();
5801
+ return GitLabCiVariableSchema.parse(data);
5802
+ }
5803
+ async function deleteProjectVariable(projectId, key, filter) {
5804
+ projectId = decodeURIComponent(projectId);
5805
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/variables/${encodeURIComponent(key)}`);
5806
+ if (filter?.environment_scope) {
5807
+ url.searchParams.append("filter[environment_scope]", filter.environment_scope);
5808
+ }
5809
+ const response = await fetch(url.toString(), { ...getFetchConfig(), method: "DELETE" });
5810
+ await handleGitLabError(response);
5811
+ }
5812
+ async function listGroupVariables(groupId, options = {}) {
5813
+ const encoded = encodeURIComponent(decodeURIComponent(groupId));
5814
+ const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/variables`);
5815
+ Object.entries(options).forEach(([key, value]) => {
5816
+ if (value === undefined)
5817
+ return;
5818
+ if (key === "filter" && typeof value === "object" && value !== null) {
5819
+ Object.entries(value).forEach(([fKey, fVal]) => {
5820
+ url.searchParams.append(`filter[${fKey}]`, fVal);
5821
+ });
5822
+ }
5823
+ else if (typeof value === "boolean") {
5824
+ url.searchParams.append(key, value ? "true" : "false");
5825
+ }
5826
+ else {
5827
+ url.searchParams.append(key, String(value));
5828
+ }
5829
+ });
5830
+ const response = await fetch(url.toString(), getFetchConfig());
5831
+ await handleGitLabError(response);
5832
+ const data = await response.json();
5833
+ return z.array(GitLabCiVariableSchema).parse(data);
5834
+ }
5835
+ async function getGroupVariable(groupId, key, filter) {
5836
+ const encoded = encodeURIComponent(decodeURIComponent(groupId));
5837
+ const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/variables/${encodeURIComponent(key)}`);
5838
+ if (filter?.environment_scope) {
5839
+ url.searchParams.append("filter[environment_scope]", filter.environment_scope);
5840
+ }
5841
+ const response = await fetch(url.toString(), getFetchConfig());
5842
+ await handleGitLabError(response);
5843
+ const data = await response.json();
5844
+ return GitLabCiVariableSchema.parse(data);
5845
+ }
5846
+ async function createGroupVariable(groupId, options) {
5847
+ const encoded = encodeURIComponent(decodeURIComponent(groupId));
5848
+ const response = await fetch(`${getEffectiveApiUrl()}/groups/${encoded}/variables`, {
5849
+ ...getFetchConfig(),
5850
+ method: "POST",
5851
+ body: JSON.stringify(options),
5852
+ });
5853
+ await handleGitLabError(response);
5854
+ const data = await response.json();
5855
+ return GitLabCiVariableSchema.parse(data);
5856
+ }
5857
+ async function updateGroupVariable(groupId, key, options) {
5858
+ const encoded = encodeURIComponent(decodeURIComponent(groupId));
5859
+ const { filter, ...body } = options;
5860
+ const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/variables/${encodeURIComponent(key)}`);
5861
+ if (filter?.environment_scope) {
5862
+ url.searchParams.append("filter[environment_scope]", filter.environment_scope);
5863
+ }
5864
+ const response = await fetch(url.toString(), { ...getFetchConfig(), method: "PUT", body: JSON.stringify(body) });
5865
+ await handleGitLabError(response);
5866
+ const data = await response.json();
5867
+ return GitLabCiVariableSchema.parse(data);
5868
+ }
5869
+ async function deleteGroupVariable(groupId, key, filter) {
5870
+ const encoded = encodeURIComponent(decodeURIComponent(groupId));
5871
+ const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/variables/${encodeURIComponent(key)}`);
5872
+ if (filter?.environment_scope) {
5873
+ url.searchParams.append("filter[environment_scope]", filter.environment_scope);
5874
+ }
5875
+ const response = await fetch(url.toString(), { ...getFetchConfig(), method: "DELETE" });
5876
+ await handleGitLabError(response);
5877
+ }
5878
+ // --- Dependency Proxy ---
5879
+ async function resolveGroupFullPath(groupId) {
5880
+ const decoded = decodeURIComponent(groupId);
5881
+ if (/^\d+$/.test(decoded)) {
5882
+ const response = await fetch(`${getEffectiveApiUrl()}/groups/${decoded}`, getFetchConfig());
5883
+ await handleGitLabError(response);
5884
+ const data = z.object({ full_path: z.string() }).parse(await response.json());
5885
+ return data.full_path;
5886
+ }
5887
+ return decoded;
5888
+ }
5889
+ async function getDependencyProxySettings(groupPath) {
5890
+ const fullPath = await resolveGroupFullPath(groupPath);
5891
+ const data = await executeGraphQL(`query($fullPath: ID!) {
5892
+ group(fullPath: $fullPath) {
5893
+ dependencyProxySetting { enabled }
5894
+ dependencyProxyBlobCount
5895
+ dependencyProxyTotalSize
5896
+ dependencyProxyImagePrefix
5897
+ dependencyProxyImageTtlPolicy { enabled ttl }
5898
+ }
5899
+ }`, { fullPath });
5900
+ const g = data.group;
5901
+ if (!g)
5902
+ throw new Error(`Group not found: ${fullPath}`);
5903
+ return GitLabDependencyProxySchema.parse({
5904
+ enabled: g.dependencyProxySetting?.enabled ?? false,
5905
+ blob_count: g.dependencyProxyBlobCount,
5906
+ total_size: g.dependencyProxyTotalSize,
5907
+ image_prefix: g.dependencyProxyImagePrefix,
5908
+ ttl_policy: g.dependencyProxyImageTtlPolicy,
5909
+ });
5910
+ }
5911
+ async function updateDependencyProxySettings(groupPath, options) {
5912
+ if (options.enabled === undefined && options.identity === undefined && options.secret === undefined) {
5913
+ throw new Error("At least one of enabled, identity, or secret must be provided");
5914
+ }
5915
+ const fullPath = await resolveGroupFullPath(groupPath);
5916
+ const input = { groupPath: fullPath };
5917
+ if (options.enabled !== undefined)
5918
+ input["enabled"] = options.enabled;
5919
+ if (options.identity !== undefined)
5920
+ input["identity"] = options.identity;
5921
+ if (options.secret !== undefined)
5922
+ input["secret"] = options.secret;
5923
+ const mutationResult = await executeGraphQL(`mutation($input: UpdateDependencyProxySettingsInput!) {
5924
+ updateDependencyProxySettings(input: $input) { errors }
5925
+ }`, { input });
5926
+ const errors = mutationResult.updateDependencyProxySettings?.errors;
5927
+ if (errors && errors.length > 0) {
5928
+ throw new Error(`Failed to update dependency proxy settings: ${errors.join(", ")}`);
5929
+ }
5930
+ return getDependencyProxySettings(fullPath);
5931
+ }
5932
+ async function listDependencyProxyBlobs(groupPath, options = {}) {
5933
+ const fullPath = await resolveGroupFullPath(groupPath);
5934
+ const data = await executeGraphQL(`query($fullPath: ID!, $first: Int, $after: String) {
5935
+ group(fullPath: $fullPath) {
5936
+ dependencyProxyBlobs(first: $first, after: $after) {
5937
+ nodes { fileName size createdAt }
5938
+ pageInfo { hasNextPage endCursor }
5939
+ }
5940
+ }
5941
+ }`, { fullPath, first: options.first ?? 20, after: options.after });
5942
+ const conn = data.group?.dependencyProxyBlobs;
5943
+ if (!conn)
5944
+ throw new Error(`Group not found or dependency proxy not enabled: ${fullPath}`);
5945
+ return {
5946
+ blobs: conn.nodes.map(n => GitLabDependencyProxyBlobSchema.parse({ file_name: n.fileName, size: n.size, created_at: n.createdAt })),
5947
+ pageInfo: conn.pageInfo,
5948
+ };
5949
+ }
5950
+ async function purgeDependencyProxyCache(groupId) {
5951
+ const encoded = encodeURIComponent(decodeURIComponent(groupId));
5952
+ const url = new URL(`${getEffectiveApiUrl()}/groups/${encoded}/dependency_proxy/cache`);
5953
+ const response = await fetch(url.toString(), { ...getFetchConfig(), method: "DELETE" });
5954
+ await handleGitLabError(response);
5955
+ }
5744
5956
  /**
5745
5957
  * Upload a file to a GitLab project for use in markdown content.
5746
5958
  *
@@ -7711,6 +7923,121 @@ async function handleToolCall(params) {
7711
7923
  content: [{ type: "text", text: JSON.stringify(iterations, null, 2) }],
7712
7924
  };
7713
7925
  }
7926
+ // --- CI/CD Variables ---
7927
+ case "list_project_variables": {
7928
+ const args = ListProjectVariablesSchema.parse(params.arguments);
7929
+ const { project_id, ...options } = args;
7930
+ const variables = await listProjectVariables(project_id, options);
7931
+ return { content: [{ type: "text", text: JSON.stringify(variables, null, 2) }] };
7932
+ }
7933
+ case "get_project_variable": {
7934
+ const args = GetProjectVariableSchema.parse(params.arguments);
7935
+ const variable = await getProjectVariable(args.project_id, args.key, args.filter);
7936
+ return { content: [{ type: "text", text: JSON.stringify(variable, null, 2) }] };
7937
+ }
7938
+ case "create_project_variable": {
7939
+ const args = CreateProjectVariableSchema.parse(params.arguments);
7940
+ const { project_id, ...options } = args;
7941
+ const variable = await createProjectVariable(project_id, options);
7942
+ return { content: [{ type: "text", text: JSON.stringify(variable, null, 2) }] };
7943
+ }
7944
+ case "update_project_variable": {
7945
+ const args = UpdateProjectVariableSchema.parse(params.arguments);
7946
+ const { project_id, key, ...options } = args;
7947
+ const variable = await updateProjectVariable(project_id, key, options);
7948
+ return { content: [{ type: "text", text: JSON.stringify(variable, null, 2) }] };
7949
+ }
7950
+ case "delete_project_variable": {
7951
+ const args = DeleteProjectVariableSchema.parse(params.arguments);
7952
+ await deleteProjectVariable(args.project_id, args.key, args.filter);
7953
+ return {
7954
+ content: [
7955
+ {
7956
+ type: "text",
7957
+ text: JSON.stringify({ status: "success", message: `Variable '${args.key}' deleted from project` }, null, 2),
7958
+ },
7959
+ ],
7960
+ };
7961
+ }
7962
+ case "list_group_variables": {
7963
+ rejectIfProjectScopedDeployment("list_group_variables");
7964
+ const args = ListGroupVariablesSchema.parse(params.arguments);
7965
+ const { group_id, ...options } = args;
7966
+ const variables = await listGroupVariables(group_id, options);
7967
+ return { content: [{ type: "text", text: JSON.stringify(variables, null, 2) }] };
7968
+ }
7969
+ case "get_group_variable": {
7970
+ rejectIfProjectScopedDeployment("get_group_variable");
7971
+ const args = GetGroupVariableSchema.parse(params.arguments);
7972
+ const variable = await getGroupVariable(args.group_id, args.key, args.filter);
7973
+ return { content: [{ type: "text", text: JSON.stringify(variable, null, 2) }] };
7974
+ }
7975
+ case "create_group_variable": {
7976
+ rejectIfProjectScopedDeployment("create_group_variable");
7977
+ const args = CreateGroupVariableSchema.parse(params.arguments);
7978
+ const { group_id, ...options } = args;
7979
+ const variable = await createGroupVariable(group_id, options);
7980
+ return { content: [{ type: "text", text: JSON.stringify(variable, null, 2) }] };
7981
+ }
7982
+ case "update_group_variable": {
7983
+ rejectIfProjectScopedDeployment("update_group_variable");
7984
+ const args = UpdateGroupVariableSchema.parse(params.arguments);
7985
+ const { group_id, key, ...options } = args;
7986
+ const variable = await updateGroupVariable(group_id, key, options);
7987
+ return { content: [{ type: "text", text: JSON.stringify(variable, null, 2) }] };
7988
+ }
7989
+ case "delete_group_variable": {
7990
+ rejectIfProjectScopedDeployment("delete_group_variable");
7991
+ const args = DeleteGroupVariableSchema.parse(params.arguments);
7992
+ await deleteGroupVariable(args.group_id, args.key, args.filter);
7993
+ return {
7994
+ content: [
7995
+ {
7996
+ type: "text",
7997
+ text: JSON.stringify({ status: "success", message: `Variable '${args.key}' deleted from group` }, null, 2),
7998
+ },
7999
+ ],
8000
+ };
8001
+ }
8002
+ case "get_dependency_proxy_settings": {
8003
+ rejectIfProjectScopedDeployment("get_dependency_proxy_settings");
8004
+ const args = GetDependencyProxySettingsSchema.parse(params.arguments);
8005
+ const settings = await getDependencyProxySettings(args.group_id);
8006
+ return {
8007
+ content: [{ type: "text", text: JSON.stringify(settings, null, 2) }],
8008
+ };
8009
+ }
8010
+ case "update_dependency_proxy_settings": {
8011
+ rejectIfProjectScopedDeployment("update_dependency_proxy_settings");
8012
+ const args = UpdateDependencyProxySettingsSchema.parse(params.arguments);
8013
+ const { group_id, ...options } = args;
8014
+ const settings = await updateDependencyProxySettings(group_id, options);
8015
+ return {
8016
+ content: [{ type: "text", text: JSON.stringify(settings, null, 2) }],
8017
+ };
8018
+ }
8019
+ case "list_dependency_proxy_blobs": {
8020
+ rejectIfProjectScopedDeployment("list_dependency_proxy_blobs");
8021
+ const args = ListDependencyProxyBlobsSchema.parse(params.arguments);
8022
+ const { group_id, ...options } = args;
8023
+ const result = await listDependencyProxyBlobs(group_id, options);
8024
+ return {
8025
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
8026
+ };
8027
+ }
8028
+ case "purge_dependency_proxy_cache": {
8029
+ rejectIfProjectScopedDeployment("purge_dependency_proxy_cache");
8030
+ const args = PurgeDependencyProxyCacheSchema.parse(params.arguments);
8031
+ await purgeDependencyProxyCache(args.group_id);
8032
+ return {
8033
+ content: [
8034
+ {
8035
+ type: "text",
8036
+ text: JSON.stringify({ status: "success", message: "Dependency proxy cache purge scheduled" }, null, 2),
8037
+ },
8038
+ ],
8039
+ };
8040
+ }
7714
8041
  case "upload_markdown": {
7715
8042
  if (IS_REMOTE) {
7716
8043
  const args = MarkdownUploadRemoteSchema.parse(params.arguments);