@zereight/mcp-gitlab 1.0.30 → 1.0.32

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Roo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/build/index.js CHANGED
@@ -3,13 +3,17 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
5
  import fetch from "node-fetch";
6
+ import { SocksProxyAgent } from 'socks-proxy-agent';
7
+ import { HttpsProxyAgent } from 'https-proxy-agent';
8
+ import { HttpProxyAgent } from 'http-proxy-agent';
6
9
  import { z } from "zod";
7
10
  import { zodToJsonSchema } from "zod-to-json-schema";
8
11
  import { fileURLToPath } from "url";
9
12
  import { dirname } from "path";
10
13
  import fs from "fs";
11
14
  import path from "path";
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, ListGroupProjectsSchema,
15
+ import { URL } from 'url';
16
+ 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, ListGroupProjectsSchema, ListWikiPagesSchema, GetWikiPageSchema, CreateWikiPageSchema, UpdateWikiPageSchema, DeleteWikiPageSchema, GitLabWikiPageSchema,
13
17
  // Discussion Schemas
14
18
  GitLabDiscussionNoteSchema, // Added
15
19
  GitLabDiscussionSchema, UpdateMergeRequestNoteSchema, // Added
@@ -40,6 +44,45 @@ const server = new Server({
40
44
  });
41
45
  const GITLAB_PERSONAL_ACCESS_TOKEN = process.env.GITLAB_PERSONAL_ACCESS_TOKEN;
42
46
  const GITLAB_READ_ONLY_MODE = process.env.GITLAB_READ_ONLY_MODE === "true";
47
+ const USE_GITLAB_WIKI = process.env.USE_GITLAB_WIKI === "true";
48
+ // Add proxy configuration
49
+ const HTTP_PROXY = process.env.HTTP_PROXY;
50
+ const HTTPS_PROXY = process.env.HTTPS_PROXY;
51
+ // Configure proxy agents if proxies are set
52
+ let httpAgent = undefined;
53
+ let httpsAgent = undefined;
54
+ if (HTTP_PROXY) {
55
+ if (HTTP_PROXY.startsWith('socks')) {
56
+ httpAgent = new SocksProxyAgent(HTTP_PROXY);
57
+ }
58
+ else {
59
+ httpAgent = new HttpProxyAgent(HTTP_PROXY);
60
+ }
61
+ }
62
+ if (HTTPS_PROXY) {
63
+ if (HTTPS_PROXY.startsWith('socks')) {
64
+ httpsAgent = new SocksProxyAgent(HTTPS_PROXY);
65
+ }
66
+ else {
67
+ httpsAgent = new HttpsProxyAgent(HTTPS_PROXY);
68
+ }
69
+ }
70
+ // Modify DEFAULT_HEADERS to include agent configuration
71
+ const DEFAULT_HEADERS = {
72
+ Accept: "application/json",
73
+ "Content-Type": "application/json",
74
+ Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`,
75
+ };
76
+ // Create a default fetch configuration object that includes proxy agents if set
77
+ const DEFAULT_FETCH_CONFIG = {
78
+ headers: DEFAULT_HEADERS,
79
+ agent: (parsedUrl) => {
80
+ if (parsedUrl.protocol === 'https:') {
81
+ return httpsAgent;
82
+ }
83
+ return httpAgent;
84
+ }
85
+ };
43
86
  // Define all available tools
44
87
  const allTools = [
45
88
  {
@@ -108,7 +151,7 @@ const allTools = [
108
151
  inputSchema: zodToJsonSchema(CreateNoteSchema),
109
152
  },
110
153
  {
111
- name: "list_merge_request_discussions",
154
+ name: "mr_discussions",
112
155
  description: "List discussion items for a merge request",
113
156
  inputSchema: zodToJsonSchema(ListMergeRequestDiscussionsSchema),
114
157
  },
@@ -212,6 +255,31 @@ const allTools = [
212
255
  description: "List projects in a GitLab group with filtering options",
213
256
  inputSchema: zodToJsonSchema(ListGroupProjectsSchema),
214
257
  },
258
+ {
259
+ name: "list_wiki_pages",
260
+ description: "List wiki pages in a GitLab project",
261
+ inputSchema: zodToJsonSchema(ListWikiPagesSchema),
262
+ },
263
+ {
264
+ name: "get_wiki_page",
265
+ description: "Get details of a specific wiki page",
266
+ inputSchema: zodToJsonSchema(GetWikiPageSchema),
267
+ },
268
+ {
269
+ name: "create_wiki_page",
270
+ description: "Create a new wiki page in a GitLab project",
271
+ inputSchema: zodToJsonSchema(CreateWikiPageSchema),
272
+ },
273
+ {
274
+ name: "update_wiki_page",
275
+ description: "Update an existing wiki page in a GitLab project",
276
+ inputSchema: zodToJsonSchema(UpdateWikiPageSchema),
277
+ },
278
+ {
279
+ name: "delete_wiki_page",
280
+ description: "Delete a wiki page from a GitLab project",
281
+ inputSchema: zodToJsonSchema(DeleteWikiPageSchema),
282
+ }
215
283
  ];
216
284
  // Define which tools are read-only
217
285
  const readOnlyTools = [
@@ -219,7 +287,7 @@ const readOnlyTools = [
219
287
  "get_file_contents",
220
288
  "get_merge_request",
221
289
  "get_merge_request_diffs",
222
- "list_merge_request_discussions",
290
+ "mr_discussions",
223
291
  "list_issues",
224
292
  "get_issue",
225
293
  "list_issue_links",
@@ -233,6 +301,15 @@ const readOnlyTools = [
233
301
  "get_label",
234
302
  "list_group_projects",
235
303
  ];
304
+ // Define which tools are related to wiki and can be toggled by USE_GITLAB_WIKI
305
+ const wikiToolNames = [
306
+ "list_wiki_pages",
307
+ "get_wiki_page",
308
+ "create_wiki_page",
309
+ "update_wiki_page",
310
+ "delete_wiki_page",
311
+ "upload_wiki_attachment",
312
+ ];
236
313
  /**
237
314
  * Smart URL handling for GitLab API
238
315
  *
@@ -259,15 +336,6 @@ if (!GITLAB_PERSONAL_ACCESS_TOKEN) {
259
336
  console.error("GITLAB_PERSONAL_ACCESS_TOKEN environment variable is not set");
260
337
  process.exit(1);
261
338
  }
262
- /**
263
- * Common headers for GitLab API requests
264
- * GitLab API 공통 헤더 (Common headers for GitLab API)
265
- */
266
- const DEFAULT_HEADERS = {
267
- Accept: "application/json",
268
- "Content-Type": "application/json",
269
- Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`,
270
- };
271
339
  /**
272
340
  * Utility function for handling GitLab API errors
273
341
  * API 에러 처리를 위한 유틸리티 함수 (Utility function for handling API errors)
@@ -299,14 +367,13 @@ async function handleGitLabError(response) {
299
367
  * @returns {Promise<GitLabFork>} The created fork
300
368
  */
301
369
  async function forkProject(projectId, namespace) {
302
- // API 엔드포인트 URL 생성
303
370
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/fork`);
304
371
  if (namespace) {
305
372
  url.searchParams.append("namespace", namespace);
306
373
  }
307
374
  const response = await fetch(url.toString(), {
375
+ ...DEFAULT_FETCH_CONFIG,
308
376
  method: "POST",
309
- headers: DEFAULT_HEADERS,
310
377
  });
311
378
  // 이미 존재하는 프로젝트인 경우 처리
312
379
  if (response.status === 409) {
@@ -327,8 +394,8 @@ async function forkProject(projectId, namespace) {
327
394
  async function createBranch(projectId, options) {
328
395
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/branches`);
329
396
  const response = await fetch(url.toString(), {
397
+ ...DEFAULT_FETCH_CONFIG,
330
398
  method: "POST",
331
- headers: DEFAULT_HEADERS,
332
399
  body: JSON.stringify({
333
400
  branch: options.name,
334
401
  ref: options.ref,
@@ -347,7 +414,7 @@ async function createBranch(projectId, options) {
347
414
  async function getDefaultBranchRef(projectId) {
348
415
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}`);
349
416
  const response = await fetch(url.toString(), {
350
- headers: DEFAULT_HEADERS,
417
+ ...DEFAULT_FETCH_CONFIG,
351
418
  });
352
419
  await handleGitLabError(response);
353
420
  const project = GitLabRepositorySchema.parse(await response.json());
@@ -371,7 +438,7 @@ async function getFileContents(projectId, filePath, ref) {
371
438
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/files/${encodedPath}`);
372
439
  url.searchParams.append("ref", ref);
373
440
  const response = await fetch(url.toString(), {
374
- headers: DEFAULT_HEADERS,
441
+ ...DEFAULT_FETCH_CONFIG,
375
442
  });
376
443
  // 파일을 찾을 수 없는 경우 처리
377
444
  if (response.status === 404) {
@@ -398,8 +465,8 @@ async function getFileContents(projectId, filePath, ref) {
398
465
  async function createIssue(projectId, options) {
399
466
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues`);
400
467
  const response = await fetch(url.toString(), {
468
+ ...DEFAULT_FETCH_CONFIG,
401
469
  method: "POST",
402
- headers: DEFAULT_HEADERS,
403
470
  body: JSON.stringify({
404
471
  title: options.title,
405
472
  description: options.description,
@@ -440,7 +507,7 @@ async function listIssues(projectId, options = {}) {
440
507
  }
441
508
  });
442
509
  const response = await fetch(url.toString(), {
443
- headers: DEFAULT_HEADERS,
510
+ ...DEFAULT_FETCH_CONFIG,
444
511
  });
445
512
  await handleGitLabError(response);
446
513
  const data = await response.json();
@@ -457,7 +524,7 @@ async function listIssues(projectId, options = {}) {
457
524
  async function getIssue(projectId, issueIid) {
458
525
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}`);
459
526
  const response = await fetch(url.toString(), {
460
- headers: DEFAULT_HEADERS,
527
+ ...DEFAULT_FETCH_CONFIG,
461
528
  });
462
529
  await handleGitLabError(response);
463
530
  const data = await response.json();
@@ -480,8 +547,8 @@ async function updateIssue(projectId, issueIid, options) {
480
547
  body.labels = body.labels.join(",");
481
548
  }
482
549
  const response = await fetch(url.toString(), {
550
+ ...DEFAULT_FETCH_CONFIG,
483
551
  method: "PUT",
484
- headers: DEFAULT_HEADERS,
485
552
  body: JSON.stringify(body),
486
553
  });
487
554
  await handleGitLabError(response);
@@ -499,8 +566,8 @@ async function updateIssue(projectId, issueIid, options) {
499
566
  async function deleteIssue(projectId, issueIid) {
500
567
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}`);
501
568
  const response = await fetch(url.toString(), {
569
+ ...DEFAULT_FETCH_CONFIG,
502
570
  method: "DELETE",
503
- headers: DEFAULT_HEADERS,
504
571
  });
505
572
  await handleGitLabError(response);
506
573
  }
@@ -515,7 +582,7 @@ async function deleteIssue(projectId, issueIid) {
515
582
  async function listIssueLinks(projectId, issueIid) {
516
583
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/links`);
517
584
  const response = await fetch(url.toString(), {
518
- headers: DEFAULT_HEADERS,
585
+ ...DEFAULT_FETCH_CONFIG,
519
586
  });
520
587
  await handleGitLabError(response);
521
588
  const data = await response.json();
@@ -533,7 +600,7 @@ async function listIssueLinks(projectId, issueIid) {
533
600
  async function getIssueLink(projectId, issueIid, issueLinkId) {
534
601
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/links/${issueLinkId}`);
535
602
  const response = await fetch(url.toString(), {
536
- headers: DEFAULT_HEADERS,
603
+ ...DEFAULT_FETCH_CONFIG,
537
604
  });
538
605
  await handleGitLabError(response);
539
606
  const data = await response.json();
@@ -553,8 +620,8 @@ async function getIssueLink(projectId, issueIid, issueLinkId) {
553
620
  async function createIssueLink(projectId, issueIid, targetProjectId, targetIssueIid, linkType = "relates_to") {
554
621
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/links`);
555
622
  const response = await fetch(url.toString(), {
623
+ ...DEFAULT_FETCH_CONFIG,
556
624
  method: "POST",
557
- headers: DEFAULT_HEADERS,
558
625
  body: JSON.stringify({
559
626
  target_project_id: targetProjectId,
560
627
  target_issue_iid: targetIssueIid,
@@ -577,8 +644,8 @@ async function createIssueLink(projectId, issueIid, targetProjectId, targetIssue
577
644
  async function deleteIssueLink(projectId, issueIid, issueLinkId) {
578
645
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/links/${issueLinkId}`);
579
646
  const response = await fetch(url.toString(), {
647
+ ...DEFAULT_FETCH_CONFIG,
580
648
  method: "DELETE",
581
- headers: DEFAULT_HEADERS,
582
649
  });
583
650
  await handleGitLabError(response);
584
651
  }
@@ -593,12 +660,8 @@ async function deleteIssueLink(projectId, issueIid, issueLinkId) {
593
660
  async function createMergeRequest(projectId, options) {
594
661
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests`);
595
662
  const response = await fetch(url.toString(), {
663
+ ...DEFAULT_FETCH_CONFIG,
596
664
  method: "POST",
597
- headers: {
598
- Accept: "application/json",
599
- "Content-Type": "application/json",
600
- Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`,
601
- },
602
665
  body: JSON.stringify({
603
666
  title: options.title,
604
667
  description: options.description,
@@ -630,7 +693,7 @@ async function createMergeRequest(projectId, options) {
630
693
  async function listMergeRequestDiscussions(projectId, mergeRequestIid) {
631
694
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}/discussions`);
632
695
  const response = await fetch(url.toString(), {
633
- headers: DEFAULT_HEADERS,
696
+ ...DEFAULT_FETCH_CONFIG,
634
697
  });
635
698
  await handleGitLabError(response);
636
699
  const data = await response.json();
@@ -656,8 +719,8 @@ async function updateMergeRequestNote(projectId, mergeRequestIid, discussionId,
656
719
  payload.resolved = resolved;
657
720
  }
658
721
  const response = await fetch(url.toString(), {
722
+ ...DEFAULT_FETCH_CONFIG,
659
723
  method: "PUT",
660
- headers: DEFAULT_HEADERS,
661
724
  body: JSON.stringify(payload),
662
725
  });
663
726
  await handleGitLabError(response);
@@ -723,12 +786,8 @@ async function createOrUpdateFile(projectId, filePath, content, commitMessage, b
723
786
  }
724
787
  }
725
788
  const response = await fetch(url.toString(), {
789
+ ...DEFAULT_FETCH_CONFIG,
726
790
  method,
727
- headers: {
728
- Accept: "application/json",
729
- "Content-Type": "application/json",
730
- Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`,
731
- },
732
791
  body: JSON.stringify(body),
733
792
  });
734
793
  if (!response.ok) {
@@ -753,12 +812,8 @@ async function createTree(projectId, files, ref) {
753
812
  url.searchParams.append("ref", ref);
754
813
  }
755
814
  const response = await fetch(url.toString(), {
815
+ ...DEFAULT_FETCH_CONFIG,
756
816
  method: "POST",
757
- headers: {
758
- Accept: "application/json",
759
- "Content-Type": "application/json",
760
- Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`,
761
- },
762
817
  body: JSON.stringify({
763
818
  files: files.map((file) => ({
764
819
  file_path: file.path,
@@ -791,12 +846,8 @@ async function createTree(projectId, files, ref) {
791
846
  async function createCommit(projectId, message, branch, actions) {
792
847
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/commits`);
793
848
  const response = await fetch(url.toString(), {
849
+ ...DEFAULT_FETCH_CONFIG,
794
850
  method: "POST",
795
- headers: {
796
- Accept: "application/json",
797
- "Content-Type": "application/json",
798
- Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`,
799
- },
800
851
  body: JSON.stringify({
801
852
  branch,
802
853
  commit_message: message,
@@ -836,11 +887,7 @@ async function searchProjects(query, page = 1, perPage = 20) {
836
887
  url.searchParams.append("order_by", "id");
837
888
  url.searchParams.append("sort", "desc");
838
889
  const response = await fetch(url.toString(), {
839
- headers: {
840
- Accept: "application/json",
841
- "Content-Type": "application/json",
842
- Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`,
843
- },
890
+ ...DEFAULT_FETCH_CONFIG,
844
891
  });
845
892
  if (!response.ok) {
846
893
  const errorBody = await response.text();
@@ -867,12 +914,8 @@ async function searchProjects(query, page = 1, perPage = 20) {
867
914
  */
868
915
  async function createRepository(options) {
869
916
  const response = await fetch(`${GITLAB_API_URL}/projects`, {
917
+ ...DEFAULT_FETCH_CONFIG,
870
918
  method: "POST",
871
- headers: {
872
- Accept: "application/json",
873
- "Content-Type": "application/json",
874
- Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`,
875
- },
876
919
  body: JSON.stringify({
877
920
  name: options.name,
878
921
  description: options.description,
@@ -900,7 +943,7 @@ async function createRepository(options) {
900
943
  async function getMergeRequest(projectId, mergeRequestIid) {
901
944
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}`);
902
945
  const response = await fetch(url.toString(), {
903
- headers: DEFAULT_HEADERS,
946
+ ...DEFAULT_FETCH_CONFIG,
904
947
  });
905
948
  await handleGitLabError(response);
906
949
  return GitLabMergeRequestSchema.parse(await response.json());
@@ -920,7 +963,7 @@ async function getMergeRequestDiffs(projectId, mergeRequestIid, view) {
920
963
  url.searchParams.append("view", view);
921
964
  }
922
965
  const response = await fetch(url.toString(), {
923
- headers: DEFAULT_HEADERS,
966
+ ...DEFAULT_FETCH_CONFIG,
924
967
  });
925
968
  await handleGitLabError(response);
926
969
  const data = (await response.json());
@@ -938,8 +981,8 @@ async function getMergeRequestDiffs(projectId, mergeRequestIid, view) {
938
981
  async function updateMergeRequest(projectId, mergeRequestIid, options) {
939
982
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}`);
940
983
  const response = await fetch(url.toString(), {
984
+ ...DEFAULT_FETCH_CONFIG,
941
985
  method: "PUT",
942
- headers: DEFAULT_HEADERS,
943
986
  body: JSON.stringify(options),
944
987
  });
945
988
  await handleGitLabError(response);
@@ -962,8 +1005,8 @@ noteableIid, body) {
962
1005
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/${noteableType}s/${noteableIid}/notes` // Using plural form (issues/merge_requests) as per GitLab API documentation
963
1006
  );
964
1007
  const response = await fetch(url.toString(), {
1008
+ ...DEFAULT_FETCH_CONFIG,
965
1009
  method: "POST",
966
- headers: DEFAULT_HEADERS,
967
1010
  body: JSON.stringify({ body }),
968
1011
  });
969
1012
  if (!response.ok) {
@@ -994,7 +1037,7 @@ async function listNamespaces(options) {
994
1037
  url.searchParams.append("top_level_only", "true");
995
1038
  }
996
1039
  const response = await fetch(url.toString(), {
997
- headers: DEFAULT_HEADERS,
1040
+ ...DEFAULT_FETCH_CONFIG,
998
1041
  });
999
1042
  await handleGitLabError(response);
1000
1043
  const data = await response.json();
@@ -1010,7 +1053,7 @@ async function listNamespaces(options) {
1010
1053
  async function getNamespace(id) {
1011
1054
  const url = new URL(`${GITLAB_API_URL}/namespaces/${encodeURIComponent(id)}`);
1012
1055
  const response = await fetch(url.toString(), {
1013
- headers: DEFAULT_HEADERS,
1056
+ ...DEFAULT_FETCH_CONFIG,
1014
1057
  });
1015
1058
  await handleGitLabError(response);
1016
1059
  const data = await response.json();
@@ -1030,7 +1073,7 @@ async function verifyNamespaceExistence(namespacePath, parentId) {
1030
1073
  url.searchParams.append("parent_id", parentId.toString());
1031
1074
  }
1032
1075
  const response = await fetch(url.toString(), {
1033
- headers: DEFAULT_HEADERS,
1076
+ ...DEFAULT_FETCH_CONFIG,
1034
1077
  });
1035
1078
  await handleGitLabError(response);
1036
1079
  const data = await response.json();
@@ -1059,7 +1102,7 @@ async function getProject(projectId, options = {}) {
1059
1102
  url.searchParams.append("with_custom_attributes", "true");
1060
1103
  }
1061
1104
  const response = await fetch(url.toString(), {
1062
- headers: DEFAULT_HEADERS,
1105
+ ...DEFAULT_FETCH_CONFIG,
1063
1106
  });
1064
1107
  await handleGitLabError(response);
1065
1108
  const data = await response.json();
@@ -1087,8 +1130,7 @@ async function listProjects(options = {}) {
1087
1130
  }
1088
1131
  // Make the API request
1089
1132
  const response = await fetch(`${GITLAB_API_URL}/projects?${params.toString()}`, {
1090
- method: "GET",
1091
- headers: DEFAULT_HEADERS,
1133
+ ...DEFAULT_FETCH_CONFIG,
1092
1134
  });
1093
1135
  // Handle errors
1094
1136
  await handleGitLabError(response);
@@ -1119,7 +1161,7 @@ async function listLabels(projectId, options = {}) {
1119
1161
  });
1120
1162
  // Make the API request
1121
1163
  const response = await fetch(url.toString(), {
1122
- headers: DEFAULT_HEADERS,
1164
+ ...DEFAULT_FETCH_CONFIG,
1123
1165
  });
1124
1166
  // Handle errors
1125
1167
  await handleGitLabError(response);
@@ -1143,7 +1185,7 @@ async function getLabel(projectId, labelId, includeAncestorGroups) {
1143
1185
  }
1144
1186
  // Make the API request
1145
1187
  const response = await fetch(url.toString(), {
1146
- headers: DEFAULT_HEADERS,
1188
+ ...DEFAULT_FETCH_CONFIG,
1147
1189
  });
1148
1190
  // Handle errors
1149
1191
  await handleGitLabError(response);
@@ -1161,8 +1203,8 @@ async function getLabel(projectId, labelId, includeAncestorGroups) {
1161
1203
  async function createLabel(projectId, options) {
1162
1204
  // Make the API request
1163
1205
  const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels`, {
1206
+ ...DEFAULT_FETCH_CONFIG,
1164
1207
  method: "POST",
1165
- headers: DEFAULT_HEADERS,
1166
1208
  body: JSON.stringify(options),
1167
1209
  });
1168
1210
  // Handle errors
@@ -1182,8 +1224,8 @@ async function createLabel(projectId, options) {
1182
1224
  async function updateLabel(projectId, labelId, options) {
1183
1225
  // Make the API request
1184
1226
  const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels/${encodeURIComponent(String(labelId))}`, {
1227
+ ...DEFAULT_FETCH_CONFIG,
1185
1228
  method: "PUT",
1186
- headers: DEFAULT_HEADERS,
1187
1229
  body: JSON.stringify(options),
1188
1230
  });
1189
1231
  // Handle errors
@@ -1201,8 +1243,8 @@ async function updateLabel(projectId, labelId, options) {
1201
1243
  async function deleteLabel(projectId, labelId) {
1202
1244
  // Make the API request
1203
1245
  const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels/${encodeURIComponent(String(labelId))}`, {
1246
+ ...DEFAULT_FETCH_CONFIG,
1204
1247
  method: "DELETE",
1205
- headers: DEFAULT_HEADERS,
1206
1248
  });
1207
1249
  // Handle errors
1208
1250
  await handleGitLabError(response);
@@ -1249,18 +1291,93 @@ async function listGroupProjects(options) {
1249
1291
  if (options.with_security_reports !== undefined)
1250
1292
  url.searchParams.append("with_security_reports", options.with_security_reports.toString());
1251
1293
  const response = await fetch(url.toString(), {
1252
- method: "GET",
1253
- headers: DEFAULT_HEADERS,
1294
+ ...DEFAULT_FETCH_CONFIG,
1254
1295
  });
1255
1296
  await handleGitLabError(response);
1256
1297
  const projects = await response.json();
1257
1298
  return GitLabProjectSchema.array().parse(projects);
1258
1299
  }
1300
+ // Wiki API helper functions
1301
+ /**
1302
+ * List wiki pages in a project
1303
+ */
1304
+ async function listWikiPages(projectId, options = {}) {
1305
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis`);
1306
+ if (options.page)
1307
+ url.searchParams.append('page', options.page.toString());
1308
+ if (options.per_page)
1309
+ url.searchParams.append('per_page', options.per_page.toString());
1310
+ const response = await fetch(url.toString(), {
1311
+ ...DEFAULT_FETCH_CONFIG,
1312
+ });
1313
+ await handleGitLabError(response);
1314
+ const data = await response.json();
1315
+ return GitLabWikiPageSchema.array().parse(data);
1316
+ }
1317
+ /**
1318
+ * Get a specific wiki page
1319
+ */
1320
+ async function getWikiPage(projectId, slug) {
1321
+ const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis/${encodeURIComponent(slug)}`, { ...DEFAULT_FETCH_CONFIG });
1322
+ await handleGitLabError(response);
1323
+ const data = await response.json();
1324
+ return GitLabWikiPageSchema.parse(data);
1325
+ }
1326
+ /**
1327
+ * Create a new wiki page
1328
+ */
1329
+ async function createWikiPage(projectId, title, content, format) {
1330
+ const body = { title, content };
1331
+ if (format)
1332
+ body.format = format;
1333
+ const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis`, {
1334
+ ...DEFAULT_FETCH_CONFIG,
1335
+ method: 'POST',
1336
+ body: JSON.stringify(body),
1337
+ });
1338
+ await handleGitLabError(response);
1339
+ const data = await response.json();
1340
+ return GitLabWikiPageSchema.parse(data);
1341
+ }
1342
+ /**
1343
+ * Update an existing wiki page
1344
+ */
1345
+ async function updateWikiPage(projectId, slug, title, content, format) {
1346
+ const body = {};
1347
+ if (title)
1348
+ body.title = title;
1349
+ if (content)
1350
+ body.content = content;
1351
+ if (format)
1352
+ body.format = format;
1353
+ const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis/${encodeURIComponent(slug)}`, {
1354
+ ...DEFAULT_FETCH_CONFIG,
1355
+ method: 'PUT',
1356
+ body: JSON.stringify(body),
1357
+ });
1358
+ await handleGitLabError(response);
1359
+ const data = await response.json();
1360
+ return GitLabWikiPageSchema.parse(data);
1361
+ }
1362
+ /**
1363
+ * Delete a wiki page
1364
+ */
1365
+ async function deleteWikiPage(projectId, slug) {
1366
+ const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis/${encodeURIComponent(slug)}`, {
1367
+ ...DEFAULT_FETCH_CONFIG,
1368
+ method: 'DELETE',
1369
+ });
1370
+ await handleGitLabError(response);
1371
+ }
1259
1372
  server.setRequestHandler(ListToolsRequestSchema, async () => {
1260
- // If read-only mode is enabled, filter out write operations
1261
- const tools = GITLAB_READ_ONLY_MODE
1373
+ // Apply read-only filter first
1374
+ const tools0 = GITLAB_READ_ONLY_MODE
1262
1375
  ? allTools.filter((tool) => readOnlyTools.includes(tool.name))
1263
1376
  : allTools;
1377
+ // Toggle wiki tools by USE_GITLAB_WIKI flag
1378
+ const tools = USE_GITLAB_WIKI
1379
+ ? tools0
1380
+ : tools0.filter((tool) => !wikiToolNames.includes(tool.name));
1264
1381
  return {
1265
1382
  tools,
1266
1383
  };
@@ -1400,7 +1517,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1400
1517
  ],
1401
1518
  };
1402
1519
  }
1403
- case "list_merge_request_discussions": {
1520
+ case "mr_discussions": {
1404
1521
  const args = ListMergeRequestDiscussionsSchema.parse(request.params.arguments);
1405
1522
  const discussions = await listMergeRequestDiscussions(args.project_id, args.merge_request_iid);
1406
1523
  return {
@@ -1425,7 +1542,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1425
1542
  url.searchParams.append("owned", args.owned.toString());
1426
1543
  }
1427
1544
  const response = await fetch(url.toString(), {
1428
- headers: DEFAULT_HEADERS,
1545
+ ...DEFAULT_FETCH_CONFIG,
1429
1546
  });
1430
1547
  await handleGitLabError(response);
1431
1548
  const data = await response.json();
@@ -1440,7 +1557,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1440
1557
  const args = GetNamespaceSchema.parse(request.params.arguments);
1441
1558
  const url = new URL(`${GITLAB_API_URL}/namespaces/${encodeURIComponent(args.namespace_id)}`);
1442
1559
  const response = await fetch(url.toString(), {
1443
- headers: DEFAULT_HEADERS,
1560
+ ...DEFAULT_FETCH_CONFIG,
1444
1561
  });
1445
1562
  await handleGitLabError(response);
1446
1563
  const data = await response.json();
@@ -1453,7 +1570,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1453
1570
  const args = VerifyNamespaceSchema.parse(request.params.arguments);
1454
1571
  const url = new URL(`${GITLAB_API_URL}/namespaces/${encodeURIComponent(args.path)}/exists`);
1455
1572
  const response = await fetch(url.toString(), {
1456
- headers: DEFAULT_HEADERS,
1573
+ ...DEFAULT_FETCH_CONFIG,
1457
1574
  });
1458
1575
  await handleGitLabError(response);
1459
1576
  const data = await response.json();
@@ -1468,7 +1585,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1468
1585
  const args = GetProjectSchema.parse(request.params.arguments);
1469
1586
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(args.project_id)}`);
1470
1587
  const response = await fetch(url.toString(), {
1471
- headers: DEFAULT_HEADERS,
1588
+ ...DEFAULT_FETCH_CONFIG,
1472
1589
  });
1473
1590
  await handleGitLabError(response);
1474
1591
  const data = await response.json();
@@ -1611,6 +1728,31 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1611
1728
  content: [{ type: "text", text: JSON.stringify(projects, null, 2) }],
1612
1729
  };
1613
1730
  }
1731
+ case "list_wiki_pages": {
1732
+ const { project_id, page, per_page } = ListWikiPagesSchema.parse(request.params.arguments);
1733
+ const wikiPages = await listWikiPages(project_id, { page, per_page });
1734
+ return { content: [{ type: "text", text: JSON.stringify(wikiPages, null, 2) }] };
1735
+ }
1736
+ case "get_wiki_page": {
1737
+ const { project_id, slug } = GetWikiPageSchema.parse(request.params.arguments);
1738
+ const wikiPage = await getWikiPage(project_id, slug);
1739
+ return { content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }] };
1740
+ }
1741
+ case "create_wiki_page": {
1742
+ const { project_id, title, content, format } = CreateWikiPageSchema.parse(request.params.arguments);
1743
+ const wikiPage = await createWikiPage(project_id, title, content, format);
1744
+ return { content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }] };
1745
+ }
1746
+ case "update_wiki_page": {
1747
+ const { project_id, slug, title, content, format } = UpdateWikiPageSchema.parse(request.params.arguments);
1748
+ const wikiPage = await updateWikiPage(project_id, slug, title, content, format);
1749
+ return { content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }] };
1750
+ }
1751
+ case "delete_wiki_page": {
1752
+ const { project_id, slug } = DeleteWikiPageSchema.parse(request.params.arguments);
1753
+ await deleteWikiPage(project_id, slug);
1754
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", message: "Wiki page deleted successfully" }, null, 2) }] };
1755
+ }
1614
1756
  default:
1615
1757
  throw new Error(`Unknown tool: ${request.params.name}`);
1616
1758
  }
package/build/schemas.js CHANGED
@@ -682,3 +682,39 @@ export const ListGroupProjectsSchema = z.object({
682
682
  with_custom_attributes: z.boolean().optional().describe("Include custom attributes"),
683
683
  with_security_reports: z.boolean().optional().describe("Include security reports")
684
684
  });
685
+ // Add wiki operation schemas
686
+ export const ListWikiPagesSchema = z.object({
687
+ project_id: z.string().describe("Project ID or URL-encoded path"),
688
+ page: z.number().optional().describe("Page number for pagination"),
689
+ per_page: z.number().optional().describe("Number of items per page"),
690
+ });
691
+ export const GetWikiPageSchema = z.object({
692
+ project_id: z.string().describe("Project ID or URL-encoded path"),
693
+ slug: z.string().describe("URL-encoded slug of the wiki page"),
694
+ });
695
+ export const CreateWikiPageSchema = z.object({
696
+ project_id: z.string().describe("Project ID or URL-encoded path"),
697
+ title: z.string().describe("Title of the wiki page"),
698
+ content: z.string().describe("Content of the wiki page"),
699
+ format: z.string().optional().describe("Content format, e.g., markdown, rdoc"),
700
+ });
701
+ export const UpdateWikiPageSchema = z.object({
702
+ project_id: z.string().describe("Project ID or URL-encoded path"),
703
+ slug: z.string().describe("URL-encoded slug of the wiki page"),
704
+ title: z.string().optional().describe("New title of the wiki page"),
705
+ content: z.string().optional().describe("New content of the wiki page"),
706
+ format: z.string().optional().describe("Content format, e.g., markdown, rdoc"),
707
+ });
708
+ export const DeleteWikiPageSchema = z.object({
709
+ project_id: z.string().describe("Project ID or URL-encoded path"),
710
+ slug: z.string().describe("URL-encoded slug of the wiki page"),
711
+ });
712
+ // Define wiki response schemas
713
+ export const GitLabWikiPageSchema = z.object({
714
+ title: z.string(),
715
+ slug: z.string(),
716
+ format: z.string(),
717
+ content: z.string(),
718
+ created_at: z.string().optional(),
719
+ updated_at: z.string().optional(),
720
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zereight/mcp-gitlab",
3
- "version": "1.0.30",
3
+ "version": "1.0.32",
4
4
  "description": "MCP server for using the GitLab API",
5
5
  "license": "MIT",
6
6
  "author": "zereight",
@@ -23,8 +23,12 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "@modelcontextprotocol/sdk": "1.8.0",
26
+ "form-data": "^4.0.0",
26
27
  "@types/node-fetch": "^2.6.12",
28
+ "http-proxy-agent": "^7.0.2",
29
+ "https-proxy-agent": "^7.0.6",
27
30
  "node-fetch": "^3.3.2",
31
+ "socks-proxy-agent": "^8.0.5",
28
32
  "zod-to-json-schema": "^3.23.5"
29
33
  },
30
34
  "devDependencies": {