@zereight/mcp-gitlab 1.0.31 → 1.0.33

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
@@ -23,7 +23,8 @@ When using with the Claude App, you need to set up your API key and URLs directl
23
23
  "env": {
24
24
  "GITLAB_PERSONAL_ACCESS_TOKEN": "your_gitlab_token",
25
25
  "GITLAB_API_URL": "your_gitlab_api_url",
26
- "GITLAB_READ_ONLY_MODE": "true"
26
+ "GITLAB_READ_ONLY_MODE": "false",
27
+ "USE_GITLAB_WIKI":"true"
27
28
  }
28
29
  }
29
30
  }
@@ -36,242 +37,48 @@ When using with the Claude App, you need to set up your API key and URLs directl
36
37
  - `GITLAB_PERSONAL_ACCESS_TOKEN`: Your GitLab personal access token.
37
38
  - `GITLAB_API_URL`: Your GitLab API URL. (Default: `https://gitlab.com/api/v4`)
38
39
  - `GITLAB_READ_ONLY_MODE`: When set to 'true', restricts the server to only expose read-only operations. Useful for enhanced security or when write access is not needed. Also useful for using with Cursor and it's 40 tool limit.
40
+ - `USE_GITLAB_WIKI`: When set to 'true', enables the wiki-related tools (list_wiki_pages, get_wiki_page, create_wiki_page, update_wiki_page, delete_wiki_page). By default, wiki features are disabled.
39
41
 
40
42
  ## Tools đŸ› ī¸
41
43
 
42
- 1. `create_or_update_file`
43
-
44
- - Create or update a single file in a GitLab project. 📝
45
- - Inputs:
46
- - `project_id` (string): Project ID or namespace/project_path
47
- - `file_path` (string): Path to create/update the file
48
- - `content` (string): File content
49
- - `commit_message` (string): Commit message
50
- - `branch` (string): Branch to create/update the file in
51
- - `previous_path` (optional string): Previous file path when renaming a file
52
- - Returns: File content and commit details
53
-
54
- 2. `push_files`
55
-
56
- - Push multiple files in a single commit. 📤
57
- - Inputs:
58
- - `project_id` (string): Project ID or namespace/project_path
59
- - `branch` (string): Branch to push to
60
- - `files` (array): Array of files to push, each with `file_path` and `content` properties
61
- - `commit_message` (string): Commit message
62
- - Returns: Updated branch reference
63
-
64
- 3. `search_repositories`
65
-
66
- - Search for GitLab projects. 🔍
67
- - Inputs:
68
- - `search` (string): Search query
69
- - `page` (optional number): Page number (default: 1)
70
- - `per_page` (optional number): Results per page (default: 20, max: 100)
71
- - Returns: Project search results
72
-
73
- 4. `create_repository`
74
-
75
- - Create a new GitLab project. ➕
76
- - Inputs:
77
- - `name` (string): Project name
78
- - `description` (optional string): Project description
79
- - `visibility` (optional string): Project visibility level (public, private, internal)
80
- - `initialize_with_readme` (optional boolean): Initialize with README
81
- - Returns: Details of the created project
82
-
83
- 5. `get_file_contents`
84
-
85
- - Get the contents of a file or directory. 📂
86
- - Inputs:
87
- - `project_id` (string): Project ID or namespace/project_path
88
- - `file_path` (string): Path to the file/directory
89
- - `ref` (optional string): Branch, tag, or commit SHA (default: default branch)
90
- - Returns: File/directory content
91
-
92
- 6. `create_issue`
93
-
94
- - Create a new issue. 🐛
95
- - Inputs:
96
- - `project_id` (string): Project ID or namespace/project_path
97
- - `title` (string): Issue title
98
- - `description` (string): Issue description
99
- - `assignee_ids` (optional number[]): Array of assignee IDs
100
- - `milestone_id` (optional number): Milestone ID
101
- - `labels` (optional string[]): Array of labels
102
- - Returns: Details of the created issue
103
-
104
- 7. `create_merge_request`
105
-
106
- - Create a new merge request. 🚀
107
- - Inputs:
108
- - `project_id` (string): Project ID or namespace/project_path
109
- - `title` (string): Merge request title
110
- - `description` (string): Merge request description
111
- - `source_branch` (string): Branch with changes
112
- - `target_branch` (string): Branch to merge into
113
- - `allow_collaboration` (optional boolean): Allow collaborators to push commits to the source branch
114
- - `draft` (optional boolean): Create as a draft merge request
115
- - Returns: Details of the created merge request
116
-
117
- 8. `fork_repository`
118
-
119
- - Fork a project. 🍴
120
- - Inputs:
121
- - `project_id` (string): Project ID or namespace/project_path to fork
122
- - `namespace` (optional string): Namespace to fork into (default: user namespace)
123
- - Returns: Details of the forked project
124
-
125
- 9. `create_branch`
126
-
127
- - Create a new branch. đŸŒŋ
128
- - Inputs:
129
- - `project_id` (string): Project ID or namespace/project_path
130
- - `name` (string): New branch name
131
- - `ref` (optional string): Ref to create the branch from (branch, tag, commit SHA, default: default branch)
132
- - Returns: Created branch reference
133
-
134
- 10. `get_merge_request`
135
-
136
- - Get details of a merge request. â„šī¸
137
- - Inputs:
138
- - `project_id` (string): Project ID or namespace/project_path
139
- - `merge_request_iid` (number): Merge request IID
140
- - Returns: Merge request details
141
-
142
- 11. `get_merge_request_diffs`
143
-
144
- - Get changes (diffs) of a merge request. diff
145
- - Inputs:
146
- - `project_id` (string): Project ID or namespace/project_path
147
- - `merge_request_iid` (number): Merge request IID
148
- - `view` (optional string): Diff view type ('inline' or 'parallel')
149
- - Returns: Array of merge request diff information
150
-
151
- 12. `update_merge_request`
152
-
153
- - Update a merge request. 🔄
154
- - Inputs:
155
- - `project_id` (string): Project ID or namespace/project_path
156
- - `merge_request_iid` (number): Merge request IID
157
- - `title` (optional string): New title
158
- - `description` (string): New description
159
- - `target_branch` (optional string): New target branch
160
- - `state_event` (optional string): Merge request state change event ('close', 'reopen')
161
- - `remove_source_branch` (optional boolean): Remove source branch after merge
162
- - `allow_collaboration` (optional boolean): Allow collaborators to push commits to the source branch
163
- - Returns: Updated merge request details
164
-
165
- 13. `create_note`
166
- - Create a new note (comment) to an issue or merge request. đŸ’Ŧ
167
- - Inputs:
168
- - `project_id` (string): Project ID or namespace/project_path
169
- - `noteable_type` (string): Type of noteable ("issue" or "merge_request")
170
- - `noteable_iid` (number): IID of the issue or merge request
171
- - `body` (string): Note content
172
- - Returns: Details of the created note
173
-
174
- 14. `list_projects`
175
- - List accessible projects with rich filtering options 📊
176
- - Inputs:
177
- - Search/filtering:
178
- - `search`
179
- - `owned`
180
- - `membership`
181
- - `archived`
182
- - `visibility`
183
- - Features filtering:
184
- - `with_issues_enabled`
185
- - `with_merge_requests_enabled`
186
- - Sorting:
187
- - `order_by`
188
- - `sort`
189
- - Access control:
190
- - `min_access_level`
191
- - Pagination:
192
- - `page`
193
- - `per_page`
194
- - `simple`
195
- - Returns: Array of projects
196
- 15. `list_labels`
197
- - List all labels for a project with filtering options đŸˇī¸
198
- - Inputs:
199
- - `project_id` (string): Project ID or path
200
- - `with_counts` (optional): Include issue and merge request counts
201
- - `include_ancestor_groups` (optional): Include ancestor groups
202
- - `search` (optional): Filter labels by keyword
203
- - Returns: Array of labels
204
- 16. `get_label`
205
- - Get a single label from a project
206
- - Inputs:
207
- - `project_id` (string): Project ID or path
208
- - `label_id` (number/string): Label ID or name
209
- - `include_ancestor_groups` (optional): Include ancestor groups
210
- - Returns: label details
211
- 17. `create_label`
212
- - Create a new label in an object đŸˇī¸âž•
213
- - Inputs:
214
- - `project_id` (string): Project ID or path
215
- - `name` (string): Label name
216
- - `color` (string): Color in hex format (e.g., "#FF0000")
217
- - `description` (optional): Label description
218
- - `priority` (optional): Label priority
219
- - Returns: Created label details
220
- 18. `update_label`
221
- - Update an existing label in a project đŸˇī¸âœī¸
222
- - Inputs:
223
- - `project_id` (string): Project ID or path
224
- - `label_id` (number/string): Label ID or name
225
- - `new_name` (optional): New label name
226
- - `color` (optional): New color in hex format
227
- - `description` (optional): New description
228
- - `priority` (optional): New priority
229
- - Returns: Updated label details
230
- 19. `delete_label`
231
- - Delete a label from a project đŸˇī¸âŒ
232
- - Inputs:
233
- - `project_id` (string): Project ID or path
234
- - `label_id` (number/string): Label ID or name
235
- - Returns: Success message
236
-
237
- 14. `list_group_projects`
238
-
239
- - List all projects in a GitLab group. 📂
240
- - Inputs:
241
- - `group_id` (string): Project ID or namespace/project_path
242
- - Filtering options:
243
- - `include_subgroups` (optional boolean): Include projects from subgroups
244
- - `search` (optional string): Search term to filter projects
245
- - `archived` (optional boolean): Filter for archived projects
246
- - `visibility` (optional string): Filter by project visibility (public/internal/private)
247
- - `with_programming_language` (optional string): Filter by programming language
248
- - `starred` (optional boolean): Filter by starred projects
249
- - Feature filtering:
250
- - `with_issues_enabled` (optional boolean): Filter projects with issues feature enabled
251
- - `with_merge_requests_enabled` (optional boolean): Filter projects with merge requests feature enabled
252
- - `min_access_level` (optional number): Filter by minimum access level
253
- - Pagination:
254
- - `page` (optional number): Page number
255
- - `per_page` (optional number): Results per page
256
- - Sorting:
257
- - `order_by` (optional string): Field to sort by
258
- - `sort` (optional string): Sort direction (asc/desc)
259
- - Additional data:
260
- - `statistics` (optional boolean): Include project statistics
261
- - `with_custom_attributes` (optional boolean): Include custom attributes
262
- - `with_security_reports` (optional boolean): Include security reports
263
- - Returns: List of projects
264
-
265
- ## Environment Variable Configuration
266
-
267
- Before running the server, you need to set the following environment variables:
268
-
269
- ```
270
- GITLAB_PERSONAL_ACCESS_TOKEN=your_gitlab_token
271
- GITLAB_API_URL=your_gitlab_api_url # Default: https://gitlab.com/api/v4
272
- GITLAB_READ_ONLY_MODE=true # Optional: Enable read-only mode
273
- ```
274
-
275
- ## License
276
-
277
- MIT License
44
+ +<!-- TOOLS-START -->
45
+ 1. `create_or_update_file` - Create or update a single file in a GitLab project
46
+ 2. `search_repositories` - Search for GitLab projects
47
+ 3. `create_repository` - Create a new GitLab project
48
+ 4. `get_file_contents` - Get the contents of a file or directory from a GitLab project
49
+ 5. `push_files` - Push multiple files to a GitLab project in a single commit
50
+ 6. `create_issue` - Create a new issue in a GitLab project
51
+ 7. `create_merge_request` - Create a new merge request in a GitLab project
52
+ 8. `fork_repository` - Fork a GitLab project to your account or specified namespace
53
+ 9. `create_branch` - Create a new branch in a GitLab project
54
+ 10. `get_merge_request` - Get details of a merge request
55
+ 11. `get_merge_request_diffs` - Get the changes/diffs of a merge request
56
+ 12. `update_merge_request` - Update a merge request
57
+ 13. `create_note` - Create a new note (comment) to an issue or merge request
58
+ 14. `mr_discussions` - List discussion items for a merge request
59
+ 15. `update_merge_request_note` - Modify an existing merge request thread note
60
+ 16. `list_issues` - List issues in a GitLab project with filtering options
61
+ 17. `get_issue` - Get details of a specific issue in a GitLab project
62
+ 18. `update_issue` - Update an issue in a GitLab project
63
+ 19. `delete_issue` - Delete an issue from a GitLab project
64
+ 20. `list_issue_links` - List all issue links for a specific issue
65
+ 21. `get_issue_link` - Get a specific issue link
66
+ 22. `create_issue_link` - Create an issue link between two issues
67
+ 23. `delete_issue_link` - Delete an issue link
68
+ 24. `list_namespaces` - List all namespaces available to the current user
69
+ 25. `get_namespace` - Get details of a namespace by ID or path
70
+ 26. `verify_namespace` - Verify if a namespace path exists
71
+ 27. `get_project` - Get details of a specific project
72
+ 28. `list_projects` - List projects accessible by the current user
73
+ 29. `list_labels` - List labels for a project
74
+ 30. `get_label` - Get a single label from a project
75
+ 31. `create_label` - Create a new label in a project
76
+ 32. `update_label` - Update an existing label in a project
77
+ 33. `delete_label` - Delete a label from a project
78
+ 34. `list_group_projects` - List projects in a GitLab group with filtering options
79
+ 35. `list_wiki_pages` - List wiki pages in a GitLab project
80
+ 36. `get_wiki_page` - Get details of a specific wiki page
81
+ 37. `create_wiki_page` - Create a new wiki page in a GitLab project
82
+ 38. `update_wiki_page` - Update an existing wiki page in a GitLab project
83
+ 39. `delete_wiki_page` - Delete a wiki page from a GitLab project
84
+ <!-- TOOLS-END -->
package/build/index.js CHANGED
@@ -3,17 +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
+ import { SocksProxyAgent } from "socks-proxy-agent";
7
+ import { HttpsProxyAgent } from "https-proxy-agent";
8
+ import { HttpProxyAgent } from "http-proxy-agent";
9
9
  import { z } from "zod";
10
10
  import { zodToJsonSchema } from "zod-to-json-schema";
11
11
  import { fileURLToPath } from "url";
12
12
  import { dirname } from "path";
13
13
  import fs from "fs";
14
14
  import path from "path";
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,
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,
17
17
  // Discussion Schemas
18
18
  GitLabDiscussionNoteSchema, // Added
19
19
  GitLabDiscussionSchema, UpdateMergeRequestNoteSchema, // Added
@@ -44,6 +44,7 @@ const server = new Server({
44
44
  });
45
45
  const GITLAB_PERSONAL_ACCESS_TOKEN = process.env.GITLAB_PERSONAL_ACCESS_TOKEN;
46
46
  const GITLAB_READ_ONLY_MODE = process.env.GITLAB_READ_ONLY_MODE === "true";
47
+ const USE_GITLAB_WIKI = process.env.USE_GITLAB_WIKI === "true";
47
48
  // Add proxy configuration
48
49
  const HTTP_PROXY = process.env.HTTP_PROXY;
49
50
  const HTTPS_PROXY = process.env.HTTPS_PROXY;
@@ -51,7 +52,7 @@ const HTTPS_PROXY = process.env.HTTPS_PROXY;
51
52
  let httpAgent = undefined;
52
53
  let httpsAgent = undefined;
53
54
  if (HTTP_PROXY) {
54
- if (HTTP_PROXY.startsWith('socks')) {
55
+ if (HTTP_PROXY.startsWith("socks")) {
55
56
  httpAgent = new SocksProxyAgent(HTTP_PROXY);
56
57
  }
57
58
  else {
@@ -59,7 +60,7 @@ if (HTTP_PROXY) {
59
60
  }
60
61
  }
61
62
  if (HTTPS_PROXY) {
62
- if (HTTPS_PROXY.startsWith('socks')) {
63
+ if (HTTPS_PROXY.startsWith("socks")) {
63
64
  httpsAgent = new SocksProxyAgent(HTTPS_PROXY);
64
65
  }
65
66
  else {
@@ -76,11 +77,11 @@ const DEFAULT_HEADERS = {
76
77
  const DEFAULT_FETCH_CONFIG = {
77
78
  headers: DEFAULT_HEADERS,
78
79
  agent: (parsedUrl) => {
79
- if (parsedUrl.protocol === 'https:') {
80
+ if (parsedUrl.protocol === "https:") {
80
81
  return httpsAgent;
81
82
  }
82
83
  return httpAgent;
83
- }
84
+ },
84
85
  };
85
86
  // Define all available tools
86
87
  const allTools = [
@@ -131,17 +132,17 @@ const allTools = [
131
132
  },
132
133
  {
133
134
  name: "get_merge_request",
134
- description: "Get details of a merge request",
135
+ description: "Get details of a merge request (Either mergeRequestIid or branchName must be provided)",
135
136
  inputSchema: zodToJsonSchema(GetMergeRequestSchema),
136
137
  },
137
138
  {
138
139
  name: "get_merge_request_diffs",
139
- description: "Get the changes/diffs of a merge request",
140
+ description: "Get the changes/diffs of a merge request (Either mergeRequestIid or branchName must be provided)",
140
141
  inputSchema: zodToJsonSchema(GetMergeRequestDiffsSchema),
141
142
  },
142
143
  {
143
144
  name: "update_merge_request",
144
- description: "Update a merge request",
145
+ description: "Update a merge request (Either mergeRequestIid or branchName must be provided)",
145
146
  inputSchema: zodToJsonSchema(UpdateMergeRequestSchema),
146
147
  },
147
148
  {
@@ -150,7 +151,7 @@ const allTools = [
150
151
  inputSchema: zodToJsonSchema(CreateNoteSchema),
151
152
  },
152
153
  {
153
- name: "list_merge_request_discussions",
154
+ name: "mr_discussions",
154
155
  description: "List discussion items for a merge request",
155
156
  inputSchema: zodToJsonSchema(ListMergeRequestDiscussionsSchema),
156
157
  },
@@ -254,6 +255,31 @@ const allTools = [
254
255
  description: "List projects in a GitLab group with filtering options",
255
256
  inputSchema: zodToJsonSchema(ListGroupProjectsSchema),
256
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
+ },
257
283
  ];
258
284
  // Define which tools are read-only
259
285
  const readOnlyTools = [
@@ -261,7 +287,7 @@ const readOnlyTools = [
261
287
  "get_file_contents",
262
288
  "get_merge_request",
263
289
  "get_merge_request_diffs",
264
- "list_merge_request_discussions",
290
+ "mr_discussions",
265
291
  "list_issues",
266
292
  "get_issue",
267
293
  "list_issue_links",
@@ -275,6 +301,15 @@ const readOnlyTools = [
275
301
  "get_label",
276
302
  "list_group_projects",
277
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
+ ];
278
313
  /**
279
314
  * Smart URL handling for GitLab API
280
315
  *
@@ -312,7 +347,8 @@ async function handleGitLabError(response) {
312
347
  if (!response.ok) {
313
348
  const errorBody = await response.text();
314
349
  // Check specifically for Rate Limit error
315
- if (response.status === 403 && errorBody.includes("User API Key Rate limit exceeded")) {
350
+ if (response.status === 403 &&
351
+ errorBody.includes("User API Key Rate limit exceeded")) {
316
352
  console.error("GitLab API Rate Limit Exceeded:", errorBody);
317
353
  console.log("User API Key Rate limit exceeded. Please try again later.");
318
354
  throw new Error(`GitLab API Rate Limit Exceeded: ${errorBody}`);
@@ -902,27 +938,50 @@ async function createRepository(options) {
902
938
  * MR ėĄ°íšŒ í•¨ėˆ˜ (Function to retrieve merge request)
903
939
  *
904
940
  * @param {string} projectId - The ID or URL-encoded path of the project
905
- * @param {number} mergeRequestIid - The internal ID of the merge request
941
+ * @param {number} mergeRequestIid - The internal ID of the merge request (Optional)
942
+ * @param {string} [branchName] - The name of the branch to search for merge request by branch name (Optional)
906
943
  * @returns {Promise<GitLabMergeRequest>} The merge request details
907
944
  */
908
- async function getMergeRequest(projectId, mergeRequestIid) {
909
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}`);
945
+ async function getMergeRequest(projectId, mergeRequestIid, branchName) {
946
+ let url;
947
+ if (mergeRequestIid) {
948
+ url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}`);
949
+ }
950
+ else if (branchName) {
951
+ url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests?source_branch=${encodeURIComponent(branchName)}`);
952
+ }
953
+ else {
954
+ throw new Error("Either mergeRequestIid or branchName must be provided");
955
+ }
910
956
  const response = await fetch(url.toString(), {
911
957
  ...DEFAULT_FETCH_CONFIG,
912
958
  });
913
959
  await handleGitLabError(response);
914
- return GitLabMergeRequestSchema.parse(await response.json());
960
+ const data = await response.json();
961
+ // If response is an array (Comes from branchName search), return the first item if exist
962
+ if (Array.isArray(data) && data.length > 0) {
963
+ return GitLabMergeRequestSchema.parse(data[0]);
964
+ }
965
+ return GitLabMergeRequestSchema.parse(data);
915
966
  }
916
967
  /**
917
968
  * Get merge request changes/diffs
918
969
  * MR ëŗ€ę˛Ŋė‚Ŧ항 ėĄ°íšŒ í•¨ėˆ˜ (Function to retrieve merge request changes)
919
970
  *
920
971
  * @param {string} projectId - The ID or URL-encoded path of the project
921
- * @param {number} mergeRequestIid - The internal ID of the merge request
972
+ * @param {number} mergeRequestIid - The internal ID of the merge request (Either mergeRequestIid or branchName must be provided)
973
+ * @param {string} [branchName] - The name of the branch to search for merge request by branch name (Either mergeRequestIid or branchName must be provided)
922
974
  * @param {string} [view] - The view type for the diff (inline or parallel)
923
975
  * @returns {Promise<GitLabMergeRequestDiff[]>} The merge request diffs
924
976
  */
925
- async function getMergeRequestDiffs(projectId, mergeRequestIid, view) {
977
+ async function getMergeRequestDiffs(projectId, mergeRequestIid, branchName, view) {
978
+ if (!mergeRequestIid && !branchName) {
979
+ throw new Error("Either mergeRequestIid or branchName must be provided");
980
+ }
981
+ if (branchName && !mergeRequestIid) {
982
+ const mergeRequest = await getMergeRequest(projectId, undefined, branchName);
983
+ mergeRequestIid = mergeRequest.iid;
984
+ }
926
985
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}/changes`);
927
986
  if (view) {
928
987
  url.searchParams.append("view", view);
@@ -939,11 +998,19 @@ async function getMergeRequestDiffs(projectId, mergeRequestIid, view) {
939
998
  * MR ė—…ë°ė´íŠ¸ í•¨ėˆ˜ (Function to update merge request)
940
999
  *
941
1000
  * @param {string} projectId - The ID or URL-encoded path of the project
942
- * @param {number} mergeRequestIid - The internal ID of the merge request
1001
+ * @param {number} mergeRequestIid - The internal ID of the merge request (Optional)
1002
+ * @param {string} branchName - The name of the branch to search for merge request by branch name (Optional)
943
1003
  * @param {Object} options - The update options
944
1004
  * @returns {Promise<GitLabMergeRequest>} The updated merge request
945
1005
  */
946
- async function updateMergeRequest(projectId, mergeRequestIid, options) {
1006
+ async function updateMergeRequest(projectId, options, mergeRequestIid, branchName) {
1007
+ if (!mergeRequestIid && !branchName) {
1008
+ throw new Error("Either mergeRequestIid or branchName must be provided");
1009
+ }
1010
+ if (branchName && !mergeRequestIid) {
1011
+ const mergeRequest = await getMergeRequest(projectId, undefined, branchName);
1012
+ mergeRequestIid = mergeRequest.iid;
1013
+ }
947
1014
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}`);
948
1015
  const response = await fetch(url.toString(), {
949
1016
  ...DEFAULT_FETCH_CONFIG,
@@ -1262,11 +1329,87 @@ async function listGroupProjects(options) {
1262
1329
  const projects = await response.json();
1263
1330
  return GitLabProjectSchema.array().parse(projects);
1264
1331
  }
1332
+ // Wiki API helper functions
1333
+ /**
1334
+ * List wiki pages in a project
1335
+ */
1336
+ async function listWikiPages(projectId, options = {}) {
1337
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis`);
1338
+ if (options.page)
1339
+ url.searchParams.append("page", options.page.toString());
1340
+ if (options.per_page)
1341
+ url.searchParams.append("per_page", options.per_page.toString());
1342
+ const response = await fetch(url.toString(), {
1343
+ ...DEFAULT_FETCH_CONFIG,
1344
+ });
1345
+ await handleGitLabError(response);
1346
+ const data = await response.json();
1347
+ return GitLabWikiPageSchema.array().parse(data);
1348
+ }
1349
+ /**
1350
+ * Get a specific wiki page
1351
+ */
1352
+ async function getWikiPage(projectId, slug) {
1353
+ const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis/${encodeURIComponent(slug)}`, { ...DEFAULT_FETCH_CONFIG });
1354
+ await handleGitLabError(response);
1355
+ const data = await response.json();
1356
+ return GitLabWikiPageSchema.parse(data);
1357
+ }
1358
+ /**
1359
+ * Create a new wiki page
1360
+ */
1361
+ async function createWikiPage(projectId, title, content, format) {
1362
+ const body = { title, content };
1363
+ if (format)
1364
+ body.format = format;
1365
+ const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis`, {
1366
+ ...DEFAULT_FETCH_CONFIG,
1367
+ method: "POST",
1368
+ body: JSON.stringify(body),
1369
+ });
1370
+ await handleGitLabError(response);
1371
+ const data = await response.json();
1372
+ return GitLabWikiPageSchema.parse(data);
1373
+ }
1374
+ /**
1375
+ * Update an existing wiki page
1376
+ */
1377
+ async function updateWikiPage(projectId, slug, title, content, format) {
1378
+ const body = {};
1379
+ if (title)
1380
+ body.title = title;
1381
+ if (content)
1382
+ body.content = content;
1383
+ if (format)
1384
+ body.format = format;
1385
+ const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis/${encodeURIComponent(slug)}`, {
1386
+ ...DEFAULT_FETCH_CONFIG,
1387
+ method: "PUT",
1388
+ body: JSON.stringify(body),
1389
+ });
1390
+ await handleGitLabError(response);
1391
+ const data = await response.json();
1392
+ return GitLabWikiPageSchema.parse(data);
1393
+ }
1394
+ /**
1395
+ * Delete a wiki page
1396
+ */
1397
+ async function deleteWikiPage(projectId, slug) {
1398
+ const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis/${encodeURIComponent(slug)}`, {
1399
+ ...DEFAULT_FETCH_CONFIG,
1400
+ method: "DELETE",
1401
+ });
1402
+ await handleGitLabError(response);
1403
+ }
1265
1404
  server.setRequestHandler(ListToolsRequestSchema, async () => {
1266
- // If read-only mode is enabled, filter out write operations
1267
- const tools = GITLAB_READ_ONLY_MODE
1405
+ // Apply read-only filter first
1406
+ const tools0 = GITLAB_READ_ONLY_MODE
1268
1407
  ? allTools.filter((tool) => readOnlyTools.includes(tool.name))
1269
1408
  : allTools;
1409
+ // Toggle wiki tools by USE_GITLAB_WIKI flag
1410
+ const tools = USE_GITLAB_WIKI
1411
+ ? tools0
1412
+ : tools0.filter((tool) => !wikiToolNames.includes(tool.name));
1270
1413
  return {
1271
1414
  tools,
1272
1415
  };
@@ -1382,7 +1525,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1382
1525
  }
1383
1526
  case "get_merge_request": {
1384
1527
  const args = GetMergeRequestSchema.parse(request.params.arguments);
1385
- const mergeRequest = await getMergeRequest(args.project_id, args.merge_request_iid);
1528
+ const mergeRequest = await getMergeRequest(args.project_id, args.merge_request_iid, args.source_branch);
1386
1529
  return {
1387
1530
  content: [
1388
1531
  { type: "text", text: JSON.stringify(mergeRequest, null, 2) },
@@ -1391,22 +1534,22 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1391
1534
  }
1392
1535
  case "get_merge_request_diffs": {
1393
1536
  const args = GetMergeRequestDiffsSchema.parse(request.params.arguments);
1394
- const diffs = await getMergeRequestDiffs(args.project_id, args.merge_request_iid, args.view);
1537
+ const diffs = await getMergeRequestDiffs(args.project_id, args.merge_request_iid, args.source_branch, args.view);
1395
1538
  return {
1396
1539
  content: [{ type: "text", text: JSON.stringify(diffs, null, 2) }],
1397
1540
  };
1398
1541
  }
1399
1542
  case "update_merge_request": {
1400
1543
  const args = UpdateMergeRequestSchema.parse(request.params.arguments);
1401
- const { project_id, merge_request_iid, ...options } = args;
1402
- const mergeRequest = await updateMergeRequest(project_id, merge_request_iid, options);
1544
+ const { project_id, merge_request_iid, source_branch, ...options } = args;
1545
+ const mergeRequest = await updateMergeRequest(project_id, options, merge_request_iid, source_branch);
1403
1546
  return {
1404
1547
  content: [
1405
1548
  { type: "text", text: JSON.stringify(mergeRequest, null, 2) },
1406
1549
  ],
1407
1550
  };
1408
1551
  }
1409
- case "list_merge_request_discussions": {
1552
+ case "mr_discussions": {
1410
1553
  const args = ListMergeRequestDiscussionsSchema.parse(request.params.arguments);
1411
1554
  const discussions = await listMergeRequestDiscussions(args.project_id, args.merge_request_iid);
1412
1555
  return {
@@ -1617,6 +1760,49 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1617
1760
  content: [{ type: "text", text: JSON.stringify(projects, null, 2) }],
1618
1761
  };
1619
1762
  }
1763
+ case "list_wiki_pages": {
1764
+ const { project_id, page, per_page } = ListWikiPagesSchema.parse(request.params.arguments);
1765
+ const wikiPages = await listWikiPages(project_id, { page, per_page });
1766
+ return {
1767
+ content: [{ type: "text", text: JSON.stringify(wikiPages, null, 2) }],
1768
+ };
1769
+ }
1770
+ case "get_wiki_page": {
1771
+ const { project_id, slug } = GetWikiPageSchema.parse(request.params.arguments);
1772
+ const wikiPage = await getWikiPage(project_id, slug);
1773
+ return {
1774
+ content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }],
1775
+ };
1776
+ }
1777
+ case "create_wiki_page": {
1778
+ const { project_id, title, content, format } = CreateWikiPageSchema.parse(request.params.arguments);
1779
+ const wikiPage = await createWikiPage(project_id, title, content, format);
1780
+ return {
1781
+ content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }],
1782
+ };
1783
+ }
1784
+ case "update_wiki_page": {
1785
+ const { project_id, slug, title, content, format } = UpdateWikiPageSchema.parse(request.params.arguments);
1786
+ const wikiPage = await updateWikiPage(project_id, slug, title, content, format);
1787
+ return {
1788
+ content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }],
1789
+ };
1790
+ }
1791
+ case "delete_wiki_page": {
1792
+ const { project_id, slug } = DeleteWikiPageSchema.parse(request.params.arguments);
1793
+ await deleteWikiPage(project_id, slug);
1794
+ return {
1795
+ content: [
1796
+ {
1797
+ type: "text",
1798
+ text: JSON.stringify({
1799
+ status: "success",
1800
+ message: "Wiki page deleted successfully",
1801
+ }, null, 2),
1802
+ },
1803
+ ],
1804
+ };
1805
+ }
1620
1806
  default:
1621
1807
  throw new Error(`Unknown tool: ${request.params.name}`);
1622
1808
  }
package/build/schemas.js CHANGED
@@ -57,7 +57,8 @@ export const GitLabRepositorySchema = z.object({
57
57
  created_at: z.string().optional(),
58
58
  last_activity_at: z.string().optional(),
59
59
  default_branch: z.string().optional(),
60
- namespace: z.object({
60
+ namespace: z
61
+ .object({
61
62
  id: z.number(),
62
63
  name: z.string(),
63
64
  path: z.string(),
@@ -65,7 +66,8 @@ export const GitLabRepositorySchema = z.object({
65
66
  full_path: z.string(),
66
67
  avatar_url: z.string().nullable().optional(),
67
68
  web_url: z.string().optional(),
68
- }).optional(),
69
+ })
70
+ .optional(),
69
71
  readme_url: z.string().optional().nullable(),
70
72
  topics: z.array(z.string()).optional(),
71
73
  tag_list: z.array(z.string()).optional(), // deprecated but still present
@@ -73,16 +75,24 @@ export const GitLabRepositorySchema = z.object({
73
75
  archived: z.boolean().optional(),
74
76
  forks_count: z.number().optional(),
75
77
  star_count: z.number().optional(),
76
- permissions: z.object({
77
- project_access: z.object({
78
+ permissions: z
79
+ .object({
80
+ project_access: z
81
+ .object({
78
82
  access_level: z.number(),
79
83
  notification_level: z.number().optional(),
80
- }).optional().nullable(),
81
- group_access: z.object({
84
+ })
85
+ .optional()
86
+ .nullable(),
87
+ group_access: z
88
+ .object({
82
89
  access_level: z.number(),
83
90
  notification_level: z.number().optional(),
84
- }).optional().nullable(),
85
- }).optional(),
91
+ })
92
+ .optional()
93
+ .nullable(),
94
+ })
95
+ .optional(),
86
96
  container_registry_enabled: z.boolean().optional(),
87
97
  container_registry_access_level: z.string().optional(),
88
98
  issues_enabled: z.boolean().optional(),
@@ -93,12 +103,14 @@ export const GitLabRepositorySchema = z.object({
93
103
  can_create_merge_request_in: z.boolean().optional(),
94
104
  resolve_outdated_diff_discussions: z.boolean().optional(),
95
105
  shared_runners_enabled: z.boolean().optional(),
96
- shared_with_groups: z.array(z.object({
106
+ shared_with_groups: z
107
+ .array(z.object({
97
108
  group_id: z.number(),
98
109
  group_name: z.string(),
99
110
  group_full_path: z.string(),
100
111
  group_access_level: z.number(),
101
- })).optional(),
112
+ }))
113
+ .optional(),
102
114
  });
103
115
  // Project schema (extended from repository schema)
104
116
  export const GitLabProjectSchema = GitLabRepositorySchema;
@@ -251,17 +263,21 @@ export const GitLabIssueSchema = z.object({
251
263
  updated_at: z.string(),
252
264
  closed_at: z.string().nullable(),
253
265
  web_url: z.string(), // Changed from html_url to match GitLab API
254
- references: z.object({
266
+ references: z
267
+ .object({
255
268
  short: z.string(),
256
269
  relative: z.string(),
257
270
  full: z.string(),
258
- }).optional(),
259
- time_stats: z.object({
271
+ })
272
+ .optional(),
273
+ time_stats: z
274
+ .object({
260
275
  time_estimate: z.number(),
261
276
  total_time_spent: z.number(),
262
277
  human_time_estimate: z.string().nullable(),
263
278
  human_total_time_spent: z.string().nullable(),
264
- }).optional(),
279
+ })
280
+ .optional(),
265
281
  confidential: z.boolean().optional(),
266
282
  due_date: z.string().nullable().optional(),
267
283
  discussion_locked: z.boolean().nullable().optional(),
@@ -270,7 +286,7 @@ export const GitLabIssueSchema = z.object({
270
286
  // NEW SCHEMA: For issue with link details (used in listing issue links)
271
287
  export const GitLabIssueWithLinkDetailsSchema = GitLabIssueSchema.extend({
272
288
  issue_link_id: z.number(),
273
- link_type: z.enum(['relates_to', 'blocks', 'is_blocked_by']),
289
+ link_type: z.enum(["relates_to", "blocks", "is_blocked_by"]),
274
290
  link_created_at: z.string(),
275
291
  link_updated_at: z.string(),
276
292
  });
@@ -278,11 +294,13 @@ export const GitLabIssueWithLinkDetailsSchema = GitLabIssueSchema.extend({
278
294
  export const GitLabForkParentSchema = z.object({
279
295
  name: z.string(),
280
296
  path_with_namespace: z.string(), // Changed from full_name to match GitLab API
281
- owner: z.object({
297
+ owner: z
298
+ .object({
282
299
  username: z.string(), // Changed from login to match GitLab API
283
300
  id: z.number(),
284
301
  avatar_url: z.string(),
285
- }).optional(), // Made optional to handle cases where GitLab API doesn't include it
302
+ })
303
+ .optional(), // Made optional to handle cases where GitLab API doesn't include it
286
304
  web_url: z.string(), // Changed from html_url to match GitLab API
287
305
  });
288
306
  export const GitLabForkSchema = GitLabRepositorySchema.extend({
@@ -346,7 +364,9 @@ export const GitLabDiscussionNoteSchema = z.object({
346
364
  resolved: z.boolean().optional(),
347
365
  resolved_by: GitLabUserSchema.nullable().optional(),
348
366
  resolved_at: z.string().nullable().optional(),
349
- position: z.object({
367
+ position: z
368
+ .object({
369
+ // Only present for DiffNote
350
370
  base_sha: z.string(),
351
371
  start_sha: z.string(),
352
372
  head_sha: z.string(),
@@ -355,7 +375,8 @@ export const GitLabDiscussionNoteSchema = z.object({
355
375
  position_type: z.enum(["text", "image", "file"]),
356
376
  old_line: z.number().nullable(),
357
377
  new_line: z.number().nullable(),
358
- line_range: z.object({
378
+ line_range: z
379
+ .object({
359
380
  start: z.object({
360
381
  line_code: z.string(),
361
382
  type: z.enum(["new", "old"]),
@@ -368,12 +389,15 @@ export const GitLabDiscussionNoteSchema = z.object({
368
389
  old_line: z.number().nullable(),
369
390
  new_line: z.number().nullable(),
370
391
  }),
371
- }).nullable().optional(), // For multi-line diff notes
392
+ })
393
+ .nullable()
394
+ .optional(), // For multi-line diff notes
372
395
  width: z.number().optional(), // For image diff notes
373
396
  height: z.number().optional(), // For image diff notes
374
397
  x: z.number().optional(), // For image diff notes
375
398
  y: z.number().optional(), // For image diff notes
376
- }).optional(),
399
+ })
400
+ .optional(),
377
401
  });
378
402
  export const GitLabDiscussionSchema = z.object({
379
403
  id: z.string(),
@@ -402,10 +426,7 @@ export const CreateOrUpdateFileSchema = ProjectParamsSchema.extend({
402
426
  .string()
403
427
  .optional()
404
428
  .describe("Path of the file to move/rename"),
405
- last_commit_id: z
406
- .string()
407
- .optional()
408
- .describe("Last known file commit ID"),
429
+ last_commit_id: z.string().optional().describe("Last known file commit ID"),
409
430
  commit_id: z
410
431
  .string()
411
432
  .optional()
@@ -489,7 +510,9 @@ export const GitLabMergeRequestDiffSchema = z.object({
489
510
  export const GetMergeRequestSchema = ProjectParamsSchema.extend({
490
511
  merge_request_iid: z
491
512
  .number()
492
- .describe("The internal ID of the merge request"),
513
+ .optional()
514
+ .describe("The IID of a merge request"),
515
+ source_branch: z.string().optional().describe("Source branch name"),
493
516
  });
494
517
  export const UpdateMergeRequestSchema = GetMergeRequestSchema.extend({
495
518
  title: z.string().optional().describe("The title of the merge request"),
@@ -531,22 +554,61 @@ export const CreateNoteSchema = z.object({
531
554
  // Issues API operation schemas
532
555
  export const ListIssuesSchema = z.object({
533
556
  project_id: z.string().describe("Project ID or URL-encoded path"),
534
- assignee_id: z.number().optional().describe("Return issues assigned to the given user ID"),
535
- assignee_username: z.string().optional().describe("Return issues assigned to the given username"),
536
- author_id: z.number().optional().describe("Return issues created by the given user ID"),
537
- author_username: z.string().optional().describe("Return issues created by the given username"),
538
- confidential: z.boolean().optional().describe("Filter confidential or public issues"),
539
- created_after: z.string().optional().describe("Return issues created after the given time"),
540
- created_before: z.string().optional().describe("Return issues created before the given time"),
541
- due_date: z.string().optional().describe("Return issues that have the due date"),
557
+ assignee_id: z
558
+ .number()
559
+ .optional()
560
+ .describe("Return issues assigned to the given user ID"),
561
+ assignee_username: z
562
+ .string()
563
+ .optional()
564
+ .describe("Return issues assigned to the given username"),
565
+ author_id: z
566
+ .number()
567
+ .optional()
568
+ .describe("Return issues created by the given user ID"),
569
+ author_username: z
570
+ .string()
571
+ .optional()
572
+ .describe("Return issues created by the given username"),
573
+ confidential: z
574
+ .boolean()
575
+ .optional()
576
+ .describe("Filter confidential or public issues"),
577
+ created_after: z
578
+ .string()
579
+ .optional()
580
+ .describe("Return issues created after the given time"),
581
+ created_before: z
582
+ .string()
583
+ .optional()
584
+ .describe("Return issues created before the given time"),
585
+ due_date: z
586
+ .string()
587
+ .optional()
588
+ .describe("Return issues that have the due date"),
542
589
  label_name: z.array(z.string()).optional().describe("Array of label names"),
543
590
  milestone: z.string().optional().describe("Milestone title"),
544
- scope: z.enum(['created-by-me', 'assigned-to-me', 'all']).optional().describe("Return issues from a specific scope"),
591
+ scope: z
592
+ .enum(["created-by-me", "assigned-to-me", "all"])
593
+ .optional()
594
+ .describe("Return issues from a specific scope"),
545
595
  search: z.string().optional().describe("Search for specific terms"),
546
- state: z.enum(['opened', 'closed', 'all']).optional().describe("Return issues with a specific state"),
547
- updated_after: z.string().optional().describe("Return issues updated after the given time"),
548
- updated_before: z.string().optional().describe("Return issues updated before the given time"),
549
- with_labels_details: z.boolean().optional().describe("Return more details for each label"),
596
+ state: z
597
+ .enum(["opened", "closed", "all"])
598
+ .optional()
599
+ .describe("Return issues with a specific state"),
600
+ updated_after: z
601
+ .string()
602
+ .optional()
603
+ .describe("Return issues updated after the given time"),
604
+ updated_before: z
605
+ .string()
606
+ .optional()
607
+ .describe("Return issues updated before the given time"),
608
+ with_labels_details: z
609
+ .boolean()
610
+ .optional()
611
+ .describe("Return more details for each label"),
550
612
  page: z.number().optional().describe("Page number for pagination"),
551
613
  per_page: z.number().optional().describe("Number of items per page"),
552
614
  });
@@ -559,13 +621,28 @@ export const UpdateIssueSchema = z.object({
559
621
  issue_iid: z.number().describe("The internal ID of the project issue"),
560
622
  title: z.string().optional().describe("The title of the issue"),
561
623
  description: z.string().optional().describe("The description of the issue"),
562
- assignee_ids: z.array(z.number()).optional().describe("Array of user IDs to assign issue to"),
563
- confidential: z.boolean().optional().describe("Set the issue to be confidential"),
564
- discussion_locked: z.boolean().optional().describe("Flag to lock discussions"),
565
- due_date: z.string().optional().describe("Date the issue is due (YYYY-MM-DD)"),
624
+ assignee_ids: z
625
+ .array(z.number())
626
+ .optional()
627
+ .describe("Array of user IDs to assign issue to"),
628
+ confidential: z
629
+ .boolean()
630
+ .optional()
631
+ .describe("Set the issue to be confidential"),
632
+ discussion_locked: z
633
+ .boolean()
634
+ .optional()
635
+ .describe("Flag to lock discussions"),
636
+ due_date: z
637
+ .string()
638
+ .optional()
639
+ .describe("Date the issue is due (YYYY-MM-DD)"),
566
640
  labels: z.array(z.string()).optional().describe("Array of label names"),
567
641
  milestone_id: z.number().optional().describe("Milestone ID to assign"),
568
- state_event: z.enum(['close', 'reopen']).optional().describe("Update issue state (close/reopen)"),
642
+ state_event: z
643
+ .enum(["close", "reopen"])
644
+ .optional()
645
+ .describe("Update issue state (close/reopen)"),
569
646
  weight: z.number().optional().describe("Weight of the issue (0-9)"),
570
647
  });
571
648
  export const DeleteIssueSchema = z.object({
@@ -576,7 +653,7 @@ export const DeleteIssueSchema = z.object({
576
653
  export const GitLabIssueLinkSchema = z.object({
577
654
  source_issue: GitLabIssueSchema,
578
655
  target_issue: GitLabIssueSchema,
579
- link_type: z.enum(['relates_to', 'blocks', 'is_blocked_by']),
656
+ link_type: z.enum(["relates_to", "blocks", "is_blocked_by"]),
580
657
  });
581
658
  export const ListIssueLinksSchema = z.object({
582
659
  project_id: z.string().describe("Project ID or URL-encoded path"),
@@ -590,9 +667,16 @@ export const GetIssueLinkSchema = z.object({
590
667
  export const CreateIssueLinkSchema = z.object({
591
668
  project_id: z.string().describe("Project ID or URL-encoded path"),
592
669
  issue_iid: z.number().describe("The internal ID of a project's issue"),
593
- target_project_id: z.string().describe("The ID or URL-encoded path of a target project"),
594
- target_issue_iid: z.number().describe("The internal ID of a target project's issue"),
595
- link_type: z.enum(['relates_to', 'blocks', 'is_blocked_by']).optional().describe("The type of the relation, defaults to relates_to"),
670
+ target_project_id: z
671
+ .string()
672
+ .describe("The ID or URL-encoded path of a target project"),
673
+ target_issue_iid: z
674
+ .number()
675
+ .describe("The internal ID of a target project's issue"),
676
+ link_type: z
677
+ .enum(["relates_to", "blocks", "is_blocked_by"])
678
+ .optional()
679
+ .describe("The type of the relation, defaults to relates_to"),
596
680
  });
597
681
  export const DeleteIssueLinkSchema = z.object({
598
682
  project_id: z.string().describe("Project ID or URL-encoded path"),
@@ -604,7 +688,10 @@ export const ListNamespacesSchema = z.object({
604
688
  search: z.string().optional().describe("Search term for namespaces"),
605
689
  page: z.number().optional().describe("Page number for pagination"),
606
690
  per_page: z.number().optional().describe("Number of items per page"),
607
- owned: z.boolean().optional().describe("Filter for namespaces owned by current user"),
691
+ owned: z
692
+ .boolean()
693
+ .optional()
694
+ .describe("Filter for namespaces owned by current user"),
608
695
  });
609
696
  export const GetNamespaceSchema = z.object({
610
697
  namespace_id: z.string().describe("Namespace ID or full path"),
@@ -620,65 +707,200 @@ export const ListProjectsSchema = z.object({
620
707
  search: z.string().optional().describe("Search term for projects"),
621
708
  page: z.number().optional().describe("Page number for pagination"),
622
709
  per_page: z.number().optional().describe("Number of items per page"),
623
- owned: z.boolean().optional().describe("Filter for projects owned by current user"),
624
- membership: z.boolean().optional().describe("Filter for projects where current user is a member"),
710
+ search_namespaces: z
711
+ .boolean()
712
+ .optional()
713
+ .describe("Needs to be true if search is full path"),
714
+ owned: z
715
+ .boolean()
716
+ .optional()
717
+ .describe("Filter for projects owned by current user"),
718
+ membership: z
719
+ .boolean()
720
+ .optional()
721
+ .describe("Filter for projects where current user is a member"),
625
722
  simple: z.boolean().optional().describe("Return only limited fields"),
626
723
  archived: z.boolean().optional().describe("Filter for archived projects"),
627
- visibility: z.enum(["public", "internal", "private"]).optional().describe("Filter by project visibility"),
628
- order_by: z.enum(["id", "name", "path", "created_at", "updated_at", "last_activity_at"]).optional().describe("Return projects ordered by field"),
629
- sort: z.enum(["asc", "desc"]).optional().describe("Return projects sorted in ascending or descending order"),
630
- with_issues_enabled: z.boolean().optional().describe("Filter projects with issues feature enabled"),
631
- with_merge_requests_enabled: z.boolean().optional().describe("Filter projects with merge requests feature enabled"),
632
- min_access_level: z.number().optional().describe("Filter by minimum access level"),
724
+ visibility: z
725
+ .enum(["public", "internal", "private"])
726
+ .optional()
727
+ .describe("Filter by project visibility"),
728
+ order_by: z
729
+ .enum([
730
+ "id",
731
+ "name",
732
+ "path",
733
+ "created_at",
734
+ "updated_at",
735
+ "last_activity_at",
736
+ ])
737
+ .optional()
738
+ .describe("Return projects ordered by field"),
739
+ sort: z
740
+ .enum(["asc", "desc"])
741
+ .optional()
742
+ .describe("Return projects sorted in ascending or descending order"),
743
+ with_issues_enabled: z
744
+ .boolean()
745
+ .optional()
746
+ .describe("Filter projects with issues feature enabled"),
747
+ with_merge_requests_enabled: z
748
+ .boolean()
749
+ .optional()
750
+ .describe("Filter projects with merge requests feature enabled"),
751
+ min_access_level: z
752
+ .number()
753
+ .optional()
754
+ .describe("Filter by minimum access level"),
633
755
  });
634
756
  // Label operation schemas
635
757
  export const ListLabelsSchema = z.object({
636
758
  project_id: z.string().describe("Project ID or URL-encoded path"),
637
- with_counts: z.boolean().optional().describe("Whether or not to include issue and merge request counts"),
638
- include_ancestor_groups: z.boolean().optional().describe("Include ancestor groups"),
759
+ with_counts: z
760
+ .boolean()
761
+ .optional()
762
+ .describe("Whether or not to include issue and merge request counts"),
763
+ include_ancestor_groups: z
764
+ .boolean()
765
+ .optional()
766
+ .describe("Include ancestor groups"),
639
767
  search: z.string().optional().describe("Keyword to filter labels by"),
640
768
  });
641
769
  export const GetLabelSchema = z.object({
642
770
  project_id: z.string().describe("Project ID or URL-encoded path"),
643
- label_id: z.union([z.number(), z.string()]).describe("The ID or title of a project's label"),
644
- include_ancestor_groups: z.boolean().optional().describe("Include ancestor groups"),
771
+ label_id: z
772
+ .union([z.number(), z.string()])
773
+ .describe("The ID or title of a project's label"),
774
+ include_ancestor_groups: z
775
+ .boolean()
776
+ .optional()
777
+ .describe("Include ancestor groups"),
645
778
  });
646
779
  export const CreateLabelSchema = z.object({
647
780
  project_id: z.string().describe("Project ID or URL-encoded path"),
648
781
  name: z.string().describe("The name of the label"),
649
- color: z.string().describe("The color of the label given in 6-digit hex notation with leading '#' sign"),
782
+ color: z
783
+ .string()
784
+ .describe("The color of the label given in 6-digit hex notation with leading '#' sign"),
650
785
  description: z.string().optional().describe("The description of the label"),
651
- priority: z.number().nullable().optional().describe("The priority of the label"),
786
+ priority: z
787
+ .number()
788
+ .nullable()
789
+ .optional()
790
+ .describe("The priority of the label"),
652
791
  });
653
792
  export const UpdateLabelSchema = z.object({
654
793
  project_id: z.string().describe("Project ID or URL-encoded path"),
655
- label_id: z.union([z.number(), z.string()]).describe("The ID or title of a project's label"),
794
+ label_id: z
795
+ .union([z.number(), z.string()])
796
+ .describe("The ID or title of a project's label"),
656
797
  new_name: z.string().optional().describe("The new name of the label"),
657
- color: z.string().optional().describe("The color of the label given in 6-digit hex notation with leading '#' sign"),
658
- description: z.string().optional().describe("The new description of the label"),
659
- priority: z.number().nullable().optional().describe("The new priority of the label"),
798
+ color: z
799
+ .string()
800
+ .optional()
801
+ .describe("The color of the label given in 6-digit hex notation with leading '#' sign"),
802
+ description: z
803
+ .string()
804
+ .optional()
805
+ .describe("The new description of the label"),
806
+ priority: z
807
+ .number()
808
+ .nullable()
809
+ .optional()
810
+ .describe("The new priority of the label"),
660
811
  });
661
812
  export const DeleteLabelSchema = z.object({
662
813
  project_id: z.string().describe("Project ID or URL-encoded path"),
663
- label_id: z.union([z.number(), z.string()]).describe("The ID or title of a project's label"),
814
+ label_id: z
815
+ .union([z.number(), z.string()])
816
+ .describe("The ID or title of a project's label"),
664
817
  });
665
818
  // Group projects schema
666
819
  export const ListGroupProjectsSchema = z.object({
667
820
  group_id: z.string().describe("Group ID or path"),
668
- include_subgroups: z.boolean().optional().describe("Include projects from subgroups"),
821
+ include_subgroups: z
822
+ .boolean()
823
+ .optional()
824
+ .describe("Include projects from subgroups"),
669
825
  search: z.string().optional().describe("Search term to filter projects"),
670
- order_by: z.enum(['name', 'path', 'created_at', 'updated_at', 'last_activity_at']).optional().describe("Field to sort by"),
671
- sort: z.enum(['asc', 'desc']).optional().describe("Sort direction"),
826
+ order_by: z
827
+ .enum(["name", "path", "created_at", "updated_at", "last_activity_at"])
828
+ .optional()
829
+ .describe("Field to sort by"),
830
+ sort: z.enum(["asc", "desc"]).optional().describe("Sort direction"),
672
831
  page: z.number().optional().describe("Page number"),
673
832
  per_page: z.number().optional().describe("Number of results per page"),
674
833
  archived: z.boolean().optional().describe("Filter for archived projects"),
675
- visibility: z.enum(["public", "internal", "private"]).optional().describe("Filter by project visibility"),
676
- with_issues_enabled: z.boolean().optional().describe("Filter projects with issues feature enabled"),
677
- with_merge_requests_enabled: z.boolean().optional().describe("Filter projects with merge requests feature enabled"),
678
- min_access_level: z.number().optional().describe("Filter by minimum access level"),
679
- with_programming_language: z.string().optional().describe("Filter by programming language"),
834
+ visibility: z
835
+ .enum(["public", "internal", "private"])
836
+ .optional()
837
+ .describe("Filter by project visibility"),
838
+ with_issues_enabled: z
839
+ .boolean()
840
+ .optional()
841
+ .describe("Filter projects with issues feature enabled"),
842
+ with_merge_requests_enabled: z
843
+ .boolean()
844
+ .optional()
845
+ .describe("Filter projects with merge requests feature enabled"),
846
+ min_access_level: z
847
+ .number()
848
+ .optional()
849
+ .describe("Filter by minimum access level"),
850
+ with_programming_language: z
851
+ .string()
852
+ .optional()
853
+ .describe("Filter by programming language"),
680
854
  starred: z.boolean().optional().describe("Filter by starred projects"),
681
855
  statistics: z.boolean().optional().describe("Include project statistics"),
682
- with_custom_attributes: z.boolean().optional().describe("Include custom attributes"),
683
- with_security_reports: z.boolean().optional().describe("Include security reports")
856
+ with_custom_attributes: z
857
+ .boolean()
858
+ .optional()
859
+ .describe("Include custom attributes"),
860
+ with_security_reports: z
861
+ .boolean()
862
+ .optional()
863
+ .describe("Include security reports"),
864
+ });
865
+ // Add wiki operation schemas
866
+ export const ListWikiPagesSchema = z.object({
867
+ project_id: z.string().describe("Project ID or URL-encoded path"),
868
+ page: z.number().optional().describe("Page number for pagination"),
869
+ per_page: z.number().optional().describe("Number of items per page"),
870
+ });
871
+ export const GetWikiPageSchema = z.object({
872
+ project_id: z.string().describe("Project ID or URL-encoded path"),
873
+ slug: z.string().describe("URL-encoded slug of the wiki page"),
874
+ });
875
+ export const CreateWikiPageSchema = z.object({
876
+ project_id: z.string().describe("Project ID or URL-encoded path"),
877
+ title: z.string().describe("Title of the wiki page"),
878
+ content: z.string().describe("Content of the wiki page"),
879
+ format: z
880
+ .string()
881
+ .optional()
882
+ .describe("Content format, e.g., markdown, rdoc"),
883
+ });
884
+ export const UpdateWikiPageSchema = z.object({
885
+ project_id: z.string().describe("Project ID or URL-encoded path"),
886
+ slug: z.string().describe("URL-encoded slug of the wiki page"),
887
+ title: z.string().optional().describe("New title of the wiki page"),
888
+ content: z.string().optional().describe("New content of the wiki page"),
889
+ format: z
890
+ .string()
891
+ .optional()
892
+ .describe("Content format, e.g., markdown, rdoc"),
893
+ });
894
+ export const DeleteWikiPageSchema = z.object({
895
+ project_id: z.string().describe("Project ID or URL-encoded path"),
896
+ slug: z.string().describe("URL-encoded slug of the wiki page"),
897
+ });
898
+ // Define wiki response schemas
899
+ export const GitLabWikiPageSchema = z.object({
900
+ title: z.string(),
901
+ slug: z.string(),
902
+ format: z.string(),
903
+ content: z.string(),
904
+ created_at: z.string().optional(),
905
+ updated_at: z.string().optional(),
684
906
  });
@@ -0,0 +1,41 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = path.dirname(__filename);
6
+ async function main() {
7
+ const repoRoot = path.resolve(__dirname, '..');
8
+ const indexPath = path.join(repoRoot, 'index.ts');
9
+ const readmePath = path.join(repoRoot, 'README.md');
10
+ // 1. Read index.ts
11
+ const code = fs.readFileSync(indexPath, 'utf-8');
12
+ // 2. Extract allTools array block
13
+ const match = code.match(/const allTools = \[([\s\S]*?)\];/);
14
+ if (!match) {
15
+ console.error('Unable to locate allTools array in index.ts');
16
+ process.exit(1);
17
+ }
18
+ const toolsBlock = match[1];
19
+ // 3. Parse tool entries
20
+ const toolRegex = /name:\s*"([^"]+)",[\s\S]*?description:\s*"([^"]+)"/g;
21
+ const tools = [];
22
+ let m;
23
+ while ((m = toolRegex.exec(toolsBlock)) !== null) {
24
+ tools.push({ name: m[1], description: m[2] });
25
+ }
26
+ // 4. Generate markdown
27
+ const lines = tools.map((tool, index) => {
28
+ return `${index + 1}. \`${tool.name}\` - ${tool.description}`;
29
+ });
30
+ const markdown = lines.join('\n');
31
+ // 5. Read README.md and replace between markers
32
+ const readme = fs.readFileSync(readmePath, 'utf-8');
33
+ const updated = readme.replace(/<!-- TOOLS-START -->([\s\S]*?)<!-- TOOLS-END -->/, `<!-- TOOLS-START -->\n${markdown}\n<!-- TOOLS-END -->`);
34
+ // 6. Write back
35
+ fs.writeFileSync(readmePath, updated, 'utf-8');
36
+ console.log('README.md tools section updated.');
37
+ }
38
+ main().catch(err => {
39
+ console.error(err);
40
+ process.exit(1);
41
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zereight/mcp-gitlab",
3
- "version": "1.0.31",
3
+ "version": "1.0.33",
4
4
  "description": "MCP server for using the GitLab API",
5
5
  "license": "MIT",
6
6
  "author": "zereight",
@@ -19,10 +19,12 @@
19
19
  "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
20
20
  "prepare": "npm run build",
21
21
  "watch": "tsc --watch",
22
- "deploy": "npm publish --access public"
22
+ "deploy": "npm publish --access public",
23
+ "generate-tools": "npx ts-node scripts/generate-tools-readme.ts"
23
24
  },
24
25
  "dependencies": {
25
26
  "@modelcontextprotocol/sdk": "1.8.0",
27
+ "form-data": "^4.0.0",
26
28
  "@types/node-fetch": "^2.6.12",
27
29
  "http-proxy-agent": "^7.0.2",
28
30
  "https-proxy-agent": "^7.0.6",