@zereight/mcp-gitlab 1.0.17 β†’ 1.0.19

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
@@ -174,6 +174,13 @@ env GITLAB_PERSONAL_ACCESS_TOKEN=your_gitlab_token GITLAB_API_URL=your_gitlab_ap
174
174
  - `body` (string): Note content
175
175
  - Returns: Details of the created note
176
176
 
177
+ | **`list_projects`** | List accessible projects with rich filtering options πŸ“Š | β€’ Search/filtering: `search`, `owned`, `membership`, `archived`, `visibility`<br>β€’ Features filtering: `with_issues_enabled`, `with_merge_requests_enabled`<br>β€’ Sorting: `order_by`, `sort`<br>β€’ Access control: `min_access_level`<br>β€’ Pagination: `page`, `per_page`, `simple` | Array of projects |
178
+ | **`list_labels`** | List all labels for a project with filtering options 🏷️ | β€’ `project_id` (string): Project ID or path<br>β€’ `with_counts` (optional): Include issue and merge request counts<br>β€’ `include_ancestor_groups` (optional): Include ancestor groups<br>β€’ `search` (optional): Filter labels by keyword | Array of labels |
179
+ | **`get_label`** | Get a single label from a project 🏷️ | β€’ `project_id` (string): Project ID or path<br>β€’ `label_id` (number/string): Label ID or name<br>β€’ `include_ancestor_groups` (optional): Include ancestor groups | Label details |
180
+ | **`create_label`** | Create a new label in a project πŸ·οΈβž• | β€’ `project_id` (string): Project ID or path<br>β€’ `name` (string): Label name<br>β€’ `color` (string): Color in hex format (e.g., "#FF0000")<br>β€’ `description` (optional): Label description<br>β€’ `priority` (optional): Label priority | Created label details |
181
+ | **`update_label`** | Update an existing label in a project 🏷️✏️ | β€’ `project_id` (string): Project ID or path<br>β€’ `label_id` (number/string): Label ID or name<br>β€’ `new_name` (optional): New label name<br>β€’ `color` (optional): New color in hex format<br>β€’ `description` (optional): New description<br>β€’ `priority` (optional): New priority | Updated label details |
182
+ | **`delete_label`** | Delete a label from a project 🏷️❌ | β€’ `project_id` (string): Project ID or path<br>β€’ `label_id` (number/string): Label ID or name | Success message |
183
+
177
184
  ## Environment Variable Configuration
178
185
 
179
186
  Before running the server, you need to set the following environment variables:
package/build/index.js CHANGED
@@ -9,7 +9,7 @@ import { fileURLToPath } from "url";
9
9
  import { dirname } from "path";
10
10
  import fs from "fs";
11
11
  import path from "path";
12
- import { GitLabForkSchema, GitLabReferenceSchema, GitLabRepositorySchema, GitLabIssueSchema, GitLabMergeRequestSchema, GitLabContentSchema, GitLabCreateUpdateFileResponseSchema, GitLabSearchResponseSchema, GitLabTreeSchema, GitLabCommitSchema, GitLabNamespaceSchema, GitLabNamespaceExistsResponseSchema, CreateOrUpdateFileSchema, SearchRepositoriesSchema, CreateRepositorySchema, GetFileContentsSchema, PushFilesSchema, CreateIssueSchema, CreateMergeRequestSchema, ForkRepositorySchema, CreateBranchSchema, GitLabMergeRequestDiffSchema, GetMergeRequestSchema, GetMergeRequestDiffsSchema, UpdateMergeRequestSchema, ListNamespacesSchema, GetNamespaceSchema, VerifyNamespaceSchema, GetProjectSchema, ListProjectsSchema, CreateNoteSchema, } from "./schemas.js";
12
+ import { GitLabForkSchema, GitLabReferenceSchema, GitLabRepositorySchema, GitLabIssueSchema, GitLabMergeRequestSchema, GitLabContentSchema, GitLabCreateUpdateFileResponseSchema, GitLabSearchResponseSchema, GitLabTreeSchema, GitLabCommitSchema, GitLabNamespaceSchema, GitLabNamespaceExistsResponseSchema, GitLabProjectSchema, CreateOrUpdateFileSchema, SearchRepositoriesSchema, CreateRepositorySchema, GetFileContentsSchema, PushFilesSchema, CreateIssueSchema, CreateMergeRequestSchema, ForkRepositorySchema, CreateBranchSchema, GitLabMergeRequestDiffSchema, GetMergeRequestSchema, GetMergeRequestDiffsSchema, UpdateMergeRequestSchema, ListIssuesSchema, GetIssueSchema, UpdateIssueSchema, DeleteIssueSchema, GitLabIssueLinkSchema, GitLabIssueWithLinkDetailsSchema, ListIssueLinksSchema, GetIssueLinkSchema, CreateIssueLinkSchema, DeleteIssueLinkSchema, ListNamespacesSchema, GetNamespaceSchema, VerifyNamespaceSchema, GetProjectSchema, ListProjectsSchema, ListLabelsSchema, GetLabelSchema, CreateLabelSchema, UpdateLabelSchema, DeleteLabelSchema, CreateNoteSchema, } from "./schemas.js";
13
13
  /**
14
14
  * Read version from package.json
15
15
  */
@@ -215,6 +215,171 @@ async function createIssue(projectId, options) {
215
215
  const data = await response.json();
216
216
  return GitLabIssueSchema.parse(data);
217
217
  }
218
+ /**
219
+ * List issues in a GitLab project
220
+ * ν”„λ‘œμ νŠΈμ˜ 이슈 λͺ©λ‘ 쑰회
221
+ *
222
+ * @param {string} projectId - The ID or URL-encoded path of the project
223
+ * @param {Object} options - Options for listing issues
224
+ * @returns {Promise<GitLabIssue[]>} List of issues
225
+ */
226
+ async function listIssues(projectId, options = {}) {
227
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues`);
228
+ // Add all query parameters
229
+ Object.entries(options).forEach(([key, value]) => {
230
+ if (value !== undefined) {
231
+ if (key === 'label_name' && Array.isArray(value)) {
232
+ // Handle array of labels
233
+ url.searchParams.append(key, value.join(','));
234
+ }
235
+ else {
236
+ url.searchParams.append(key, value.toString());
237
+ }
238
+ }
239
+ });
240
+ const response = await fetch(url.toString(), {
241
+ headers: DEFAULT_HEADERS,
242
+ });
243
+ await handleGitLabError(response);
244
+ const data = await response.json();
245
+ return z.array(GitLabIssueSchema).parse(data);
246
+ }
247
+ /**
248
+ * Get a single issue from a GitLab project
249
+ * 단일 이슈 쑰회
250
+ *
251
+ * @param {string} projectId - The ID or URL-encoded path of the project
252
+ * @param {number} issueIid - The internal ID of the project issue
253
+ * @returns {Promise<GitLabIssue>} The issue
254
+ */
255
+ async function getIssue(projectId, issueIid) {
256
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}`);
257
+ const response = await fetch(url.toString(), {
258
+ headers: DEFAULT_HEADERS,
259
+ });
260
+ await handleGitLabError(response);
261
+ const data = await response.json();
262
+ return GitLabIssueSchema.parse(data);
263
+ }
264
+ /**
265
+ * Update an issue in a GitLab project
266
+ * 이슈 μ—…λ°μ΄νŠΈ
267
+ *
268
+ * @param {string} projectId - The ID or URL-encoded path of the project
269
+ * @param {number} issueIid - The internal ID of the project issue
270
+ * @param {Object} options - Update options for the issue
271
+ * @returns {Promise<GitLabIssue>} The updated issue
272
+ */
273
+ async function updateIssue(projectId, issueIid, options) {
274
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}`);
275
+ // Convert labels array to comma-separated string if present
276
+ const body = { ...options };
277
+ if (body.labels && Array.isArray(body.labels)) {
278
+ body.labels = body.labels.join(',');
279
+ }
280
+ const response = await fetch(url.toString(), {
281
+ method: "PUT",
282
+ headers: DEFAULT_HEADERS,
283
+ body: JSON.stringify(body),
284
+ });
285
+ await handleGitLabError(response);
286
+ const data = await response.json();
287
+ return GitLabIssueSchema.parse(data);
288
+ }
289
+ /**
290
+ * Delete an issue from a GitLab project
291
+ * 이슈 μ‚­μ œ
292
+ *
293
+ * @param {string} projectId - The ID or URL-encoded path of the project
294
+ * @param {number} issueIid - The internal ID of the project issue
295
+ * @returns {Promise<void>}
296
+ */
297
+ async function deleteIssue(projectId, issueIid) {
298
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}`);
299
+ const response = await fetch(url.toString(), {
300
+ method: "DELETE",
301
+ headers: DEFAULT_HEADERS,
302
+ });
303
+ await handleGitLabError(response);
304
+ }
305
+ /**
306
+ * List all issue links for a specific issue
307
+ * 이슈 관계 λͺ©λ‘ 쑰회
308
+ *
309
+ * @param {string} projectId - The ID or URL-encoded path of the project
310
+ * @param {number} issueIid - The internal ID of the project issue
311
+ * @returns {Promise<GitLabIssueWithLinkDetails[]>} List of issues with link details
312
+ */
313
+ async function listIssueLinks(projectId, issueIid) {
314
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/links`);
315
+ const response = await fetch(url.toString(), {
316
+ headers: DEFAULT_HEADERS,
317
+ });
318
+ await handleGitLabError(response);
319
+ const data = await response.json();
320
+ return z.array(GitLabIssueWithLinkDetailsSchema).parse(data);
321
+ }
322
+ /**
323
+ * Get a specific issue link
324
+ * νŠΉμ • 이슈 관계 쑰회
325
+ *
326
+ * @param {string} projectId - The ID or URL-encoded path of the project
327
+ * @param {number} issueIid - The internal ID of the project issue
328
+ * @param {number} issueLinkId - The ID of the issue link
329
+ * @returns {Promise<GitLabIssueLink>} The issue link
330
+ */
331
+ async function getIssueLink(projectId, issueIid, issueLinkId) {
332
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/links/${issueLinkId}`);
333
+ const response = await fetch(url.toString(), {
334
+ headers: DEFAULT_HEADERS,
335
+ });
336
+ await handleGitLabError(response);
337
+ const data = await response.json();
338
+ return GitLabIssueLinkSchema.parse(data);
339
+ }
340
+ /**
341
+ * Create an issue link between two issues
342
+ * 이슈 관계 생성
343
+ *
344
+ * @param {string} projectId - The ID or URL-encoded path of the project
345
+ * @param {number} issueIid - The internal ID of the project issue
346
+ * @param {string} targetProjectId - The ID or URL-encoded path of the target project
347
+ * @param {number} targetIssueIid - The internal ID of the target project issue
348
+ * @param {string} linkType - The type of the relation (relates_to, blocks, is_blocked_by)
349
+ * @returns {Promise<GitLabIssueLink>} The created issue link
350
+ */
351
+ async function createIssueLink(projectId, issueIid, targetProjectId, targetIssueIid, linkType = 'relates_to') {
352
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/links`);
353
+ const response = await fetch(url.toString(), {
354
+ method: "POST",
355
+ headers: DEFAULT_HEADERS,
356
+ body: JSON.stringify({
357
+ target_project_id: targetProjectId,
358
+ target_issue_iid: targetIssueIid,
359
+ link_type: linkType
360
+ }),
361
+ });
362
+ await handleGitLabError(response);
363
+ const data = await response.json();
364
+ return GitLabIssueLinkSchema.parse(data);
365
+ }
366
+ /**
367
+ * Delete an issue link
368
+ * 이슈 관계 μ‚­μ œ
369
+ *
370
+ * @param {string} projectId - The ID or URL-encoded path of the project
371
+ * @param {number} issueIid - The internal ID of the project issue
372
+ * @param {number} issueLinkId - The ID of the issue link
373
+ * @returns {Promise<void>}
374
+ */
375
+ async function deleteIssueLink(projectId, issueIid, issueLinkId) {
376
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/links/${issueLinkId}`);
377
+ const response = await fetch(url.toString(), {
378
+ method: "DELETE",
379
+ headers: DEFAULT_HEADERS,
380
+ });
381
+ await handleGitLabError(response);
382
+ }
218
383
  /**
219
384
  * Create a new merge request in a GitLab project
220
385
  * 병합 μš”μ²­ 생성
@@ -661,19 +826,139 @@ async function getProject(projectId, options = {}) {
661
826
  * @returns {Promise<GitLabProject[]>} List of projects
662
827
  */
663
828
  async function listProjects(options = {}) {
664
- const url = new URL(`${GITLAB_API_URL}/projects`);
665
- // Add all the query parameters from options
829
+ // Construct the query parameters
830
+ const params = new URLSearchParams();
831
+ for (const [key, value] of Object.entries(options)) {
832
+ if (value !== undefined && value !== null) {
833
+ if (typeof value === "boolean") {
834
+ params.append(key, value ? "true" : "false");
835
+ }
836
+ else {
837
+ params.append(key, String(value));
838
+ }
839
+ }
840
+ }
841
+ // Make the API request
842
+ const response = await fetch(`${GITLAB_API_URL}/projects?${params.toString()}`, {
843
+ method: "GET",
844
+ headers: DEFAULT_HEADERS,
845
+ });
846
+ // Handle errors
847
+ await handleGitLabError(response);
848
+ // Parse and return the data
849
+ const data = await response.json();
850
+ return z.array(GitLabProjectSchema).parse(data);
851
+ }
852
+ /**
853
+ * List labels for a project
854
+ *
855
+ * @param projectId The ID or URL-encoded path of the project
856
+ * @param options Optional parameters for listing labels
857
+ * @returns Array of GitLab labels
858
+ */
859
+ async function listLabels(projectId, options = {}) {
860
+ // Construct the URL with project path
861
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels`);
862
+ // Add query parameters
666
863
  Object.entries(options).forEach(([key, value]) => {
667
864
  if (value !== undefined) {
668
- url.searchParams.append(key, value.toString());
865
+ if (typeof value === "boolean") {
866
+ url.searchParams.append(key, value ? "true" : "false");
867
+ }
868
+ else {
869
+ url.searchParams.append(key, String(value));
870
+ }
669
871
  }
670
872
  });
873
+ // Make the API request
671
874
  const response = await fetch(url.toString(), {
672
875
  headers: DEFAULT_HEADERS,
673
876
  });
877
+ // Handle errors
674
878
  await handleGitLabError(response);
879
+ // Parse and return the data
675
880
  const data = await response.json();
676
- return z.array(GitLabRepositorySchema).parse(data);
881
+ return data;
882
+ }
883
+ /**
884
+ * Get a single label from a project
885
+ *
886
+ * @param projectId The ID or URL-encoded path of the project
887
+ * @param labelId The ID or name of the label
888
+ * @param includeAncestorGroups Whether to include ancestor groups
889
+ * @returns GitLab label
890
+ */
891
+ async function getLabel(projectId, labelId, includeAncestorGroups) {
892
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels/${encodeURIComponent(String(labelId))}`);
893
+ // Add query parameters
894
+ if (includeAncestorGroups !== undefined) {
895
+ url.searchParams.append("include_ancestor_groups", includeAncestorGroups ? "true" : "false");
896
+ }
897
+ // Make the API request
898
+ const response = await fetch(url.toString(), {
899
+ headers: DEFAULT_HEADERS,
900
+ });
901
+ // Handle errors
902
+ await handleGitLabError(response);
903
+ // Parse and return the data
904
+ const data = await response.json();
905
+ return data;
906
+ }
907
+ /**
908
+ * Create a new label in a project
909
+ *
910
+ * @param projectId The ID or URL-encoded path of the project
911
+ * @param options Options for creating the label
912
+ * @returns Created GitLab label
913
+ */
914
+ async function createLabel(projectId, options) {
915
+ // Make the API request
916
+ const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels`, {
917
+ method: "POST",
918
+ headers: DEFAULT_HEADERS,
919
+ body: JSON.stringify(options),
920
+ });
921
+ // Handle errors
922
+ await handleGitLabError(response);
923
+ // Parse and return the data
924
+ const data = await response.json();
925
+ return data;
926
+ }
927
+ /**
928
+ * Update an existing label in a project
929
+ *
930
+ * @param projectId The ID or URL-encoded path of the project
931
+ * @param labelId The ID or name of the label to update
932
+ * @param options Options for updating the label
933
+ * @returns Updated GitLab label
934
+ */
935
+ async function updateLabel(projectId, labelId, options) {
936
+ // Make the API request
937
+ const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels/${encodeURIComponent(String(labelId))}`, {
938
+ method: "PUT",
939
+ headers: DEFAULT_HEADERS,
940
+ body: JSON.stringify(options),
941
+ });
942
+ // Handle errors
943
+ await handleGitLabError(response);
944
+ // Parse and return the data
945
+ const data = await response.json();
946
+ return data;
947
+ }
948
+ /**
949
+ * Delete a label from a project
950
+ *
951
+ * @param projectId The ID or URL-encoded path of the project
952
+ * @param labelId The ID or name of the label to delete
953
+ */
954
+ async function deleteLabel(projectId, labelId) {
955
+ // Make the API request
956
+ const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels/${encodeURIComponent(String(labelId))}`, {
957
+ method: "DELETE",
958
+ headers: DEFAULT_HEADERS,
959
+ });
960
+ // Handle errors
961
+ await handleGitLabError(response);
677
962
  }
678
963
  server.setRequestHandler(ListToolsRequestSchema, async () => {
679
964
  return {
@@ -743,6 +1028,46 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
743
1028
  description: "Create a new note (comment) to an issue or merge request",
744
1029
  inputSchema: zodToJsonSchema(CreateNoteSchema),
745
1030
  },
1031
+ {
1032
+ name: "list_issues",
1033
+ description: "List issues in a GitLab project with filtering options",
1034
+ inputSchema: zodToJsonSchema(ListIssuesSchema),
1035
+ },
1036
+ {
1037
+ name: "get_issue",
1038
+ description: "Get details of a specific issue in a GitLab project",
1039
+ inputSchema: zodToJsonSchema(GetIssueSchema),
1040
+ },
1041
+ {
1042
+ name: "update_issue",
1043
+ description: "Update an issue in a GitLab project",
1044
+ inputSchema: zodToJsonSchema(UpdateIssueSchema),
1045
+ },
1046
+ {
1047
+ name: "delete_issue",
1048
+ description: "Delete an issue from a GitLab project",
1049
+ inputSchema: zodToJsonSchema(DeleteIssueSchema),
1050
+ },
1051
+ {
1052
+ name: "list_issue_links",
1053
+ description: "List all issue links for a specific issue",
1054
+ inputSchema: zodToJsonSchema(ListIssueLinksSchema),
1055
+ },
1056
+ {
1057
+ name: "get_issue_link",
1058
+ description: "Get a specific issue link",
1059
+ inputSchema: zodToJsonSchema(GetIssueLinkSchema),
1060
+ },
1061
+ {
1062
+ name: "create_issue_link",
1063
+ description: "Create an issue link between two issues",
1064
+ inputSchema: zodToJsonSchema(CreateIssueLinkSchema),
1065
+ },
1066
+ {
1067
+ name: "delete_issue_link",
1068
+ description: "Delete an issue link",
1069
+ inputSchema: zodToJsonSchema(DeleteIssueLinkSchema),
1070
+ },
746
1071
  {
747
1072
  name: "list_namespaces",
748
1073
  description: "List all namespaces available to the current user",
@@ -750,17 +1075,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
750
1075
  },
751
1076
  {
752
1077
  name: "get_namespace",
753
- description: "Get details on a specified namespace",
1078
+ description: "Get details of a namespace by ID or path",
754
1079
  inputSchema: zodToJsonSchema(GetNamespaceSchema),
755
1080
  },
756
1081
  {
757
1082
  name: "verify_namespace",
758
- description: "Verify if a specified namespace already exists",
1083
+ description: "Verify if a namespace path exists",
759
1084
  inputSchema: zodToJsonSchema(VerifyNamespaceSchema),
760
1085
  },
761
1086
  {
762
1087
  name: "get_project",
763
- description: "Get details on a specified project",
1088
+ description: "Get details of a specific project",
764
1089
  inputSchema: zodToJsonSchema(GetProjectSchema),
765
1090
  },
766
1091
  {
@@ -768,6 +1093,31 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
768
1093
  description: "List projects accessible by the current user",
769
1094
  inputSchema: zodToJsonSchema(ListProjectsSchema),
770
1095
  },
1096
+ {
1097
+ name: "list_labels",
1098
+ description: "List labels for a project",
1099
+ inputSchema: zodToJsonSchema(ListLabelsSchema),
1100
+ },
1101
+ {
1102
+ name: "get_label",
1103
+ description: "Get a single label from a project",
1104
+ inputSchema: zodToJsonSchema(GetLabelSchema),
1105
+ },
1106
+ {
1107
+ name: "create_label",
1108
+ description: "Create a new label in a project",
1109
+ inputSchema: zodToJsonSchema(CreateLabelSchema),
1110
+ },
1111
+ {
1112
+ name: "update_label",
1113
+ description: "Update an existing label in a project",
1114
+ inputSchema: zodToJsonSchema(UpdateLabelSchema),
1115
+ },
1116
+ {
1117
+ name: "delete_label",
1118
+ description: "Delete a label from a project",
1119
+ inputSchema: zodToJsonSchema(DeleteLabelSchema),
1120
+ },
771
1121
  ],
772
1122
  };
773
1123
  });
@@ -893,36 +1243,86 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
893
1243
  }
894
1244
  case "list_namespaces": {
895
1245
  const args = ListNamespacesSchema.parse(request.params.arguments);
896
- const namespaces = await listNamespaces(args);
1246
+ const url = new URL(`${GITLAB_API_URL}/namespaces`);
1247
+ if (args.search) {
1248
+ url.searchParams.append("search", args.search);
1249
+ }
1250
+ if (args.page) {
1251
+ url.searchParams.append("page", args.page.toString());
1252
+ }
1253
+ if (args.per_page) {
1254
+ url.searchParams.append("per_page", args.per_page.toString());
1255
+ }
1256
+ if (args.owned) {
1257
+ url.searchParams.append("owned", args.owned.toString());
1258
+ }
1259
+ const response = await fetch(url.toString(), {
1260
+ headers: DEFAULT_HEADERS,
1261
+ });
1262
+ await handleGitLabError(response);
1263
+ const data = await response.json();
1264
+ const namespaces = z.array(GitLabNamespaceSchema).parse(data);
897
1265
  return {
898
1266
  content: [{ type: "text", text: JSON.stringify(namespaces, null, 2) }],
899
1267
  };
900
1268
  }
901
1269
  case "get_namespace": {
902
1270
  const args = GetNamespaceSchema.parse(request.params.arguments);
903
- const namespace = await getNamespace(args.id);
1271
+ const url = new URL(`${GITLAB_API_URL}/namespaces/${encodeURIComponent(args.namespace_id)}`);
1272
+ const response = await fetch(url.toString(), {
1273
+ headers: DEFAULT_HEADERS,
1274
+ });
1275
+ await handleGitLabError(response);
1276
+ const data = await response.json();
1277
+ const namespace = GitLabNamespaceSchema.parse(data);
904
1278
  return {
905
1279
  content: [{ type: "text", text: JSON.stringify(namespace, null, 2) }],
906
1280
  };
907
1281
  }
908
1282
  case "verify_namespace": {
909
1283
  const args = VerifyNamespaceSchema.parse(request.params.arguments);
910
- const result = await verifyNamespaceExistence(args.namespace, args.parent_id);
1284
+ const url = new URL(`${GITLAB_API_URL}/namespaces/${encodeURIComponent(args.path)}/exists`);
1285
+ const response = await fetch(url.toString(), {
1286
+ headers: DEFAULT_HEADERS,
1287
+ });
1288
+ await handleGitLabError(response);
1289
+ const data = await response.json();
1290
+ const namespaceExists = GitLabNamespaceExistsResponseSchema.parse(data);
911
1291
  return {
912
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
1292
+ content: [{ type: "text", text: JSON.stringify(namespaceExists, null, 2) }],
913
1293
  };
914
1294
  }
915
1295
  case "get_project": {
916
1296
  const args = GetProjectSchema.parse(request.params.arguments);
917
- const { id, ...options } = args;
918
- const project = await getProject(id, options);
1297
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(args.project_id)}`);
1298
+ const response = await fetch(url.toString(), {
1299
+ headers: DEFAULT_HEADERS,
1300
+ });
1301
+ await handleGitLabError(response);
1302
+ const data = await response.json();
1303
+ const project = GitLabProjectSchema.parse(data);
919
1304
  return {
920
1305
  content: [{ type: "text", text: JSON.stringify(project, null, 2) }],
921
1306
  };
922
1307
  }
923
1308
  case "list_projects": {
924
1309
  const args = ListProjectsSchema.parse(request.params.arguments);
925
- const projects = await listProjects(args);
1310
+ const url = new URL(`${GITLAB_API_URL}/projects`);
1311
+ // Add query parameters for filtering
1312
+ Object.entries(args).forEach(([key, value]) => {
1313
+ if (value !== undefined) {
1314
+ url.searchParams.append(key, value.toString());
1315
+ }
1316
+ });
1317
+ const response = await fetch(url.toString(), {
1318
+ method: "GET",
1319
+ headers: DEFAULT_HEADERS,
1320
+ });
1321
+ // Handle errors
1322
+ await handleGitLabError(response);
1323
+ // Parse and return the data
1324
+ const data = await response.json();
1325
+ const projects = z.array(GitLabProjectSchema).parse(data);
926
1326
  return {
927
1327
  content: [{ type: "text", text: JSON.stringify(projects, null, 2) }],
928
1328
  };
@@ -935,6 +1335,100 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
935
1335
  content: [{ type: "text", text: JSON.stringify(note, null, 2) }],
936
1336
  };
937
1337
  }
1338
+ case "list_issues": {
1339
+ const args = ListIssuesSchema.parse(request.params.arguments);
1340
+ const { project_id, ...options } = args;
1341
+ const issues = await listIssues(project_id, options);
1342
+ return {
1343
+ content: [{ type: "text", text: JSON.stringify(issues, null, 2) }],
1344
+ };
1345
+ }
1346
+ case "get_issue": {
1347
+ const args = GetIssueSchema.parse(request.params.arguments);
1348
+ const issue = await getIssue(args.project_id, args.issue_iid);
1349
+ return {
1350
+ content: [{ type: "text", text: JSON.stringify(issue, null, 2) }],
1351
+ };
1352
+ }
1353
+ case "update_issue": {
1354
+ const args = UpdateIssueSchema.parse(request.params.arguments);
1355
+ const { project_id, issue_iid, ...options } = args;
1356
+ const issue = await updateIssue(project_id, issue_iid, options);
1357
+ return {
1358
+ content: [{ type: "text", text: JSON.stringify(issue, null, 2) }],
1359
+ };
1360
+ }
1361
+ case "delete_issue": {
1362
+ const args = DeleteIssueSchema.parse(request.params.arguments);
1363
+ await deleteIssue(args.project_id, args.issue_iid);
1364
+ return {
1365
+ content: [{ type: "text", text: JSON.stringify({ status: "success", message: "Issue deleted successfully" }, null, 2) }],
1366
+ };
1367
+ }
1368
+ case "list_issue_links": {
1369
+ const args = ListIssueLinksSchema.parse(request.params.arguments);
1370
+ const links = await listIssueLinks(args.project_id, args.issue_iid);
1371
+ return {
1372
+ content: [{ type: "text", text: JSON.stringify(links, null, 2) }],
1373
+ };
1374
+ }
1375
+ case "get_issue_link": {
1376
+ const args = GetIssueLinkSchema.parse(request.params.arguments);
1377
+ const link = await getIssueLink(args.project_id, args.issue_iid, args.issue_link_id);
1378
+ return {
1379
+ content: [{ type: "text", text: JSON.stringify(link, null, 2) }],
1380
+ };
1381
+ }
1382
+ case "create_issue_link": {
1383
+ const args = CreateIssueLinkSchema.parse(request.params.arguments);
1384
+ const link = await createIssueLink(args.project_id, args.issue_iid, args.target_project_id, args.target_issue_iid, args.link_type);
1385
+ return {
1386
+ content: [{ type: "text", text: JSON.stringify(link, null, 2) }],
1387
+ };
1388
+ }
1389
+ case "delete_issue_link": {
1390
+ const args = DeleteIssueLinkSchema.parse(request.params.arguments);
1391
+ await deleteIssueLink(args.project_id, args.issue_iid, args.issue_link_id);
1392
+ return {
1393
+ content: [{ type: "text", text: JSON.stringify({ status: "success", message: "Issue link deleted successfully" }, null, 2) }],
1394
+ };
1395
+ }
1396
+ case "list_labels": {
1397
+ const args = ListLabelsSchema.parse(request.params.arguments);
1398
+ const labels = await listLabels(args.project_id, args);
1399
+ return {
1400
+ content: [{ type: "text", text: JSON.stringify(labels, null, 2) }],
1401
+ };
1402
+ }
1403
+ case "get_label": {
1404
+ const args = GetLabelSchema.parse(request.params.arguments);
1405
+ const label = await getLabel(args.project_id, args.label_id, args.include_ancestor_groups);
1406
+ return {
1407
+ content: [{ type: "text", text: JSON.stringify(label, null, 2) }],
1408
+ };
1409
+ }
1410
+ case "create_label": {
1411
+ const args = CreateLabelSchema.parse(request.params.arguments);
1412
+ const label = await createLabel(args.project_id, args);
1413
+ return {
1414
+ content: [{ type: "text", text: JSON.stringify(label, null, 2) }],
1415
+ };
1416
+ }
1417
+ case "update_label": {
1418
+ const args = UpdateLabelSchema.parse(request.params.arguments);
1419
+ const { project_id, label_id, ...options } = args;
1420
+ const label = await updateLabel(project_id, label_id, options);
1421
+ return {
1422
+ content: [{ type: "text", text: JSON.stringify(label, null, 2) }],
1423
+ };
1424
+ }
1425
+ case "delete_label": {
1426
+ const args = DeleteLabelSchema.parse(request.params.arguments);
1427
+ await deleteLabel(args.project_id, args.label_id);
1428
+ return {
1429
+ content: [{ type: "text", text: JSON.stringify({ status: "success", message: "Label deleted successfully" }, null, 2) }],
1430
+ };
1431
+ }
938
1432
  default:
939
1433
  throw new Error(`Unknown tool: ${request.params.name}`);
940
1434
  }
package/build/schemas.js CHANGED
@@ -96,6 +96,8 @@ export const GitLabRepositorySchema = z.object({
96
96
  group_access_level: z.number(),
97
97
  })).optional(),
98
98
  });
99
+ // Project schema (extended from repository schema)
100
+ export const GitLabProjectSchema = GitLabRepositorySchema;
99
101
  // File content schemas
100
102
  export const GitLabFileContentSchema = z.object({
101
103
  file_name: z.string(), // Changed from name to match GitLab API
@@ -200,26 +202,20 @@ export const GitLabSearchResponseSchema = z.object({
200
202
  current_page: z.number().optional(),
201
203
  items: z.array(GitLabRepositorySchema),
202
204
  });
203
- // Fork related schemas
204
- export const GitLabForkParentSchema = z.object({
205
- name: z.string(),
206
- path_with_namespace: z.string(), // Changed from full_name to match GitLab API
207
- owner: z.object({
208
- username: z.string(), // Changed from login to match GitLab API
209
- id: z.number(),
210
- avatar_url: z.string(),
211
- }).optional(), // Made optional to handle cases where GitLab API doesn't include it
212
- web_url: z.string(), // Changed from html_url to match GitLab API
213
- });
214
- export const GitLabForkSchema = GitLabRepositorySchema.extend({
215
- forked_from_project: GitLabForkParentSchema.optional(), // Made optional to handle cases where GitLab API doesn't include it
216
- });
217
205
  // Issue related schemas
218
206
  export const GitLabLabelSchema = z.object({
219
207
  id: z.number(),
220
208
  name: z.string(),
221
209
  color: z.string(),
222
- description: z.string().optional(),
210
+ text_color: z.string(),
211
+ description: z.string().nullable(),
212
+ description_html: z.string().nullable(),
213
+ open_issues_count: z.number().optional(),
214
+ closed_issues_count: z.number().optional(),
215
+ open_merge_requests_count: z.number().optional(),
216
+ subscribed: z.boolean().optional(),
217
+ priority: z.number().nullable().optional(),
218
+ is_project_label: z.boolean().optional(),
223
219
  });
224
220
  export const GitLabUserSchema = z.object({
225
221
  username: z.string(), // Changed from login to match GitLab API
@@ -245,12 +241,48 @@ export const GitLabIssueSchema = z.object({
245
241
  state: z.string(),
246
242
  author: GitLabUserSchema,
247
243
  assignees: z.array(GitLabUserSchema),
248
- labels: z.array(GitLabLabelSchema),
244
+ labels: z.array(GitLabLabelSchema).or(z.array(z.string())), // Support both label objects and strings
249
245
  milestone: GitLabMilestoneSchema.nullable(),
250
246
  created_at: z.string(),
251
247
  updated_at: z.string(),
252
248
  closed_at: z.string().nullable(),
253
249
  web_url: z.string(), // Changed from html_url to match GitLab API
250
+ references: z.object({
251
+ short: z.string(),
252
+ relative: z.string(),
253
+ full: z.string(),
254
+ }).optional(),
255
+ time_stats: z.object({
256
+ time_estimate: z.number(),
257
+ total_time_spent: z.number(),
258
+ human_time_estimate: z.string().nullable(),
259
+ human_total_time_spent: z.string().nullable(),
260
+ }).optional(),
261
+ confidential: z.boolean().optional(),
262
+ due_date: z.string().nullable().optional(),
263
+ discussion_locked: z.boolean().nullable().optional(),
264
+ weight: z.number().nullable().optional(),
265
+ });
266
+ // NEW SCHEMA: For issue with link details (used in listing issue links)
267
+ export const GitLabIssueWithLinkDetailsSchema = GitLabIssueSchema.extend({
268
+ issue_link_id: z.number(),
269
+ link_type: z.enum(['relates_to', 'blocks', 'is_blocked_by']),
270
+ link_created_at: z.string(),
271
+ link_updated_at: z.string(),
272
+ });
273
+ // Fork related schemas
274
+ export const GitLabForkParentSchema = z.object({
275
+ name: z.string(),
276
+ path_with_namespace: z.string(), // Changed from full_name to match GitLab API
277
+ owner: z.object({
278
+ username: z.string(), // Changed from login to match GitLab API
279
+ id: z.number(),
280
+ avatar_url: z.string(),
281
+ }).optional(), // Made optional to handle cases where GitLab API doesn't include it
282
+ web_url: z.string(), // Changed from html_url to match GitLab API
283
+ });
284
+ export const GitLabForkSchema = GitLabRepositorySchema.extend({
285
+ forked_from_project: GitLabForkParentSchema.optional(), // Made optional to handle cases where GitLab API doesn't include it
254
286
  });
255
287
  // Merge Request related schemas (equivalent to Pull Request)
256
288
  export const GitLabMergeRequestDiffRefSchema = z.object({
@@ -431,44 +463,137 @@ export const CreateNoteSchema = z.object({
431
463
  noteable_iid: z.number().describe("IID of the issue or merge request"),
432
464
  body: z.string().describe("Note content"),
433
465
  });
434
- // Add namespace-related operation schemas
466
+ // Issues API operation schemas
467
+ export const ListIssuesSchema = z.object({
468
+ project_id: z.string().describe("Project ID or URL-encoded path"),
469
+ assignee_id: z.number().optional().describe("Return issues assigned to the given user ID"),
470
+ assignee_username: z.string().optional().describe("Return issues assigned to the given username"),
471
+ author_id: z.number().optional().describe("Return issues created by the given user ID"),
472
+ author_username: z.string().optional().describe("Return issues created by the given username"),
473
+ confidential: z.boolean().optional().describe("Filter confidential or public issues"),
474
+ created_after: z.string().optional().describe("Return issues created after the given time"),
475
+ created_before: z.string().optional().describe("Return issues created before the given time"),
476
+ due_date: z.string().optional().describe("Return issues that have the due date"),
477
+ label_name: z.array(z.string()).optional().describe("Array of label names"),
478
+ milestone: z.string().optional().describe("Milestone title"),
479
+ scope: z.enum(['created-by-me', 'assigned-to-me', 'all']).optional().describe("Return issues from a specific scope"),
480
+ search: z.string().optional().describe("Search for specific terms"),
481
+ state: z.enum(['opened', 'closed', 'all']).optional().describe("Return issues with a specific state"),
482
+ updated_after: z.string().optional().describe("Return issues updated after the given time"),
483
+ updated_before: z.string().optional().describe("Return issues updated before the given time"),
484
+ with_labels_details: z.boolean().optional().describe("Return more details for each label"),
485
+ page: z.number().optional().describe("Page number for pagination"),
486
+ per_page: z.number().optional().describe("Number of items per page"),
487
+ });
488
+ export const GetIssueSchema = z.object({
489
+ project_id: z.string().describe("Project ID or URL-encoded path"),
490
+ issue_iid: z.number().describe("The internal ID of the project issue"),
491
+ });
492
+ export const UpdateIssueSchema = z.object({
493
+ project_id: z.string().describe("Project ID or URL-encoded path"),
494
+ issue_iid: z.number().describe("The internal ID of the project issue"),
495
+ title: z.string().optional().describe("The title of the issue"),
496
+ description: z.string().optional().describe("The description of the issue"),
497
+ assignee_ids: z.array(z.number()).optional().describe("Array of user IDs to assign issue to"),
498
+ confidential: z.boolean().optional().describe("Set the issue to be confidential"),
499
+ discussion_locked: z.boolean().optional().describe("Flag to lock discussions"),
500
+ due_date: z.string().optional().describe("Date the issue is due (YYYY-MM-DD)"),
501
+ labels: z.array(z.string()).optional().describe("Array of label names"),
502
+ milestone_id: z.number().optional().describe("Milestone ID to assign"),
503
+ state_event: z.enum(['close', 'reopen']).optional().describe("Update issue state (close/reopen)"),
504
+ weight: z.number().optional().describe("Weight of the issue (0-9)"),
505
+ });
506
+ export const DeleteIssueSchema = z.object({
507
+ project_id: z.string().describe("Project ID or URL-encoded path"),
508
+ issue_iid: z.number().describe("The internal ID of the project issue"),
509
+ });
510
+ // Issue links related schemas
511
+ export const GitLabIssueLinkSchema = z.object({
512
+ source_issue: GitLabIssueSchema,
513
+ target_issue: GitLabIssueSchema,
514
+ link_type: z.enum(['relates_to', 'blocks', 'is_blocked_by']),
515
+ });
516
+ export const ListIssueLinksSchema = z.object({
517
+ project_id: z.string().describe("Project ID or URL-encoded path"),
518
+ issue_iid: z.number().describe("The internal ID of a project's issue"),
519
+ });
520
+ export const GetIssueLinkSchema = z.object({
521
+ project_id: z.string().describe("Project ID or URL-encoded path"),
522
+ issue_iid: z.number().describe("The internal ID of a project's issue"),
523
+ issue_link_id: z.number().describe("ID of an issue relationship"),
524
+ });
525
+ export const CreateIssueLinkSchema = z.object({
526
+ project_id: z.string().describe("Project ID or URL-encoded path"),
527
+ issue_iid: z.number().describe("The internal ID of a project's issue"),
528
+ target_project_id: z.string().describe("The ID or URL-encoded path of a target project"),
529
+ target_issue_iid: z.number().describe("The internal ID of a target project's issue"),
530
+ link_type: z.enum(['relates_to', 'blocks', 'is_blocked_by']).optional().describe("The type of the relation, defaults to relates_to"),
531
+ });
532
+ export const DeleteIssueLinkSchema = z.object({
533
+ project_id: z.string().describe("Project ID or URL-encoded path"),
534
+ issue_iid: z.number().describe("The internal ID of a project's issue"),
535
+ issue_link_id: z.number().describe("The ID of an issue relationship"),
536
+ });
537
+ // Namespace API operation schemas
435
538
  export const ListNamespacesSchema = z.object({
436
- search: z.string().optional().describe("Only returns namespaces accessible by the current user"),
437
- owned_only: z.boolean().optional().describe("If true, only returns namespaces by the current user"),
438
- top_level_only: z.boolean().optional().describe("In GitLab 16.8 and later, if true, only returns top-level namespaces"),
539
+ search: z.string().optional().describe("Search term for namespaces"),
540
+ page: z.number().optional().describe("Page number for pagination"),
541
+ per_page: z.number().optional().describe("Number of items per page"),
542
+ owned: z.boolean().optional().describe("Filter for namespaces owned by current user"),
439
543
  });
440
544
  export const GetNamespaceSchema = z.object({
441
- id: z.string().describe("ID or URL-encoded path of the namespace"),
545
+ namespace_id: z.string().describe("Namespace ID or full path"),
442
546
  });
443
547
  export const VerifyNamespaceSchema = z.object({
444
- namespace: z.string().describe("Path of the namespace"),
445
- parent_id: z.number().optional().describe("ID of the parent namespace. If unspecified, only returns top-level namespaces"),
548
+ path: z.string().describe("Namespace path to verify"),
446
549
  });
447
550
  // Project API operation schemas
448
551
  export const GetProjectSchema = z.object({
449
- id: z.string().describe("ID or URL-encoded path of the project"),
450
- license: z.boolean().optional().describe("Include project license data"),
451
- statistics: z.boolean().optional().describe("Include project statistics"),
452
- with_custom_attributes: z.boolean().optional().describe("Include custom attributes in response"),
552
+ project_id: z.string().describe("Project ID or URL-encoded path"),
453
553
  });
454
554
  export const ListProjectsSchema = z.object({
455
- archived: z.boolean().optional().describe("Limit by archived status"),
456
- id_after: z.number().optional().describe("Limit results to projects with IDs greater than the specified ID"),
457
- id_before: z.number().optional().describe("Limit results to projects with IDs less than the specified ID"),
458
- membership: z.boolean().optional().describe("Limit by projects that the current user is a member of"),
459
- min_access_level: z.number().optional().describe("Limit by minimum access level"),
460
- order_by: z.enum(['id', 'name', 'path', 'created_at', 'updated_at', 'last_activity_at']).optional().describe("Return projects ordered by field"),
461
- owned: z.boolean().optional().describe("Limit by projects explicitly owned by the current user"),
462
- search: z.string().optional().describe("Return list of projects matching the search criteria"),
463
- simple: z.boolean().optional().describe("Return only limited fields for each project"),
464
- sort: z.enum(['asc', 'desc']).optional().describe("Return projects sorted in ascending or descending order"),
465
- starred: z.boolean().optional().describe("Limit by projects starred by the current user"),
466
- visibility: z.enum(['public', 'internal', 'private']).optional().describe("Limit by visibility"),
467
- with_custom_attributes: z.boolean().optional().describe("Include custom attributes in response"),
468
- with_issues_enabled: z.boolean().optional().describe("Limit by enabled issues feature"),
469
- with_merge_requests_enabled: z.boolean().optional().describe("Limit by enabled merge requests feature"),
470
- with_programming_language: z.string().optional().describe("Limit by projects which use the given programming language"),
471
- with_shared: z.boolean().optional().describe("Include projects shared to this group"),
555
+ search: z.string().optional().describe("Search term for projects"),
472
556
  page: z.number().optional().describe("Page number for pagination"),
473
557
  per_page: z.number().optional().describe("Number of items per page"),
558
+ owned: z.boolean().optional().describe("Filter for projects owned by current user"),
559
+ membership: z.boolean().optional().describe("Filter for projects where current user is a member"),
560
+ simple: z.boolean().optional().describe("Return only limited fields"),
561
+ archived: z.boolean().optional().describe("Filter for archived projects"),
562
+ visibility: z.enum(["public", "internal", "private"]).optional().describe("Filter by project visibility"),
563
+ order_by: z.enum(["id", "name", "path", "created_at", "updated_at", "last_activity_at"]).optional().describe("Return projects ordered by field"),
564
+ sort: z.enum(["asc", "desc"]).optional().describe("Return projects sorted in ascending or descending order"),
565
+ with_issues_enabled: z.boolean().optional().describe("Filter projects with issues feature enabled"),
566
+ with_merge_requests_enabled: z.boolean().optional().describe("Filter projects with merge requests feature enabled"),
567
+ min_access_level: z.number().optional().describe("Filter by minimum access level"),
568
+ });
569
+ // Label operation schemas
570
+ export const ListLabelsSchema = z.object({
571
+ project_id: z.string().describe("Project ID or URL-encoded path"),
572
+ with_counts: z.boolean().optional().describe("Whether or not to include issue and merge request counts"),
573
+ include_ancestor_groups: z.boolean().optional().describe("Include ancestor groups"),
574
+ search: z.string().optional().describe("Keyword to filter labels by"),
575
+ });
576
+ export const GetLabelSchema = z.object({
577
+ project_id: z.string().describe("Project ID or URL-encoded path"),
578
+ label_id: z.union([z.number(), z.string()]).describe("The ID or title of a project's label"),
579
+ include_ancestor_groups: z.boolean().optional().describe("Include ancestor groups"),
580
+ });
581
+ export const CreateLabelSchema = z.object({
582
+ project_id: z.string().describe("Project ID or URL-encoded path"),
583
+ name: z.string().describe("The name of the label"),
584
+ color: z.string().describe("The color of the label given in 6-digit hex notation with leading '#' sign"),
585
+ description: z.string().optional().describe("The description of the label"),
586
+ priority: z.number().nullable().optional().describe("The priority of the label"),
587
+ });
588
+ export const UpdateLabelSchema = z.object({
589
+ project_id: z.string().describe("Project ID or URL-encoded path"),
590
+ label_id: z.union([z.number(), z.string()]).describe("The ID or title of a project's label"),
591
+ new_name: z.string().optional().describe("The new name of the label"),
592
+ color: z.string().optional().describe("The color of the label given in 6-digit hex notation with leading '#' sign"),
593
+ description: z.string().optional().describe("The new description of the label"),
594
+ priority: z.number().nullable().optional().describe("The new priority of the label"),
595
+ });
596
+ export const DeleteLabelSchema = z.object({
597
+ project_id: z.string().describe("Project ID or URL-encoded path"),
598
+ label_id: z.union([z.number(), z.string()]).describe("The ID or title of a project's label"),
474
599
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zereight/mcp-gitlab",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
4
4
  "description": "MCP server for using the GitLab API",
5
5
  "license": "MIT",
6
6
  "author": "zereight",