@zereight/mcp-gitlab 1.0.32 → 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,16 +3,16 @@ 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';
15
+ import { URL } from "url";
16
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
@@ -52,7 +52,7 @@ const HTTPS_PROXY = process.env.HTTPS_PROXY;
52
52
  let httpAgent = undefined;
53
53
  let httpsAgent = undefined;
54
54
  if (HTTP_PROXY) {
55
- if (HTTP_PROXY.startsWith('socks')) {
55
+ if (HTTP_PROXY.startsWith("socks")) {
56
56
  httpAgent = new SocksProxyAgent(HTTP_PROXY);
57
57
  }
58
58
  else {
@@ -60,7 +60,7 @@ if (HTTP_PROXY) {
60
60
  }
61
61
  }
62
62
  if (HTTPS_PROXY) {
63
- if (HTTPS_PROXY.startsWith('socks')) {
63
+ if (HTTPS_PROXY.startsWith("socks")) {
64
64
  httpsAgent = new SocksProxyAgent(HTTPS_PROXY);
65
65
  }
66
66
  else {
@@ -77,11 +77,11 @@ const DEFAULT_HEADERS = {
77
77
  const DEFAULT_FETCH_CONFIG = {
78
78
  headers: DEFAULT_HEADERS,
79
79
  agent: (parsedUrl) => {
80
- if (parsedUrl.protocol === 'https:') {
80
+ if (parsedUrl.protocol === "https:") {
81
81
  return httpsAgent;
82
82
  }
83
83
  return httpAgent;
84
- }
84
+ },
85
85
  };
86
86
  // Define all available tools
87
87
  const allTools = [
@@ -132,17 +132,17 @@ const allTools = [
132
132
  },
133
133
  {
134
134
  name: "get_merge_request",
135
- description: "Get details of a merge request",
135
+ description: "Get details of a merge request (Either mergeRequestIid or branchName must be provided)",
136
136
  inputSchema: zodToJsonSchema(GetMergeRequestSchema),
137
137
  },
138
138
  {
139
139
  name: "get_merge_request_diffs",
140
- 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)",
141
141
  inputSchema: zodToJsonSchema(GetMergeRequestDiffsSchema),
142
142
  },
143
143
  {
144
144
  name: "update_merge_request",
145
- description: "Update a merge request",
145
+ description: "Update a merge request (Either mergeRequestIid or branchName must be provided)",
146
146
  inputSchema: zodToJsonSchema(UpdateMergeRequestSchema),
147
147
  },
148
148
  {
@@ -279,7 +279,7 @@ const allTools = [
279
279
  name: "delete_wiki_page",
280
280
  description: "Delete a wiki page from a GitLab project",
281
281
  inputSchema: zodToJsonSchema(DeleteWikiPageSchema),
282
- }
282
+ },
283
283
  ];
284
284
  // Define which tools are read-only
285
285
  const readOnlyTools = [
@@ -347,7 +347,8 @@ async function handleGitLabError(response) {
347
347
  if (!response.ok) {
348
348
  const errorBody = await response.text();
349
349
  // Check specifically for Rate Limit error
350
- 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")) {
351
352
  console.error("GitLab API Rate Limit Exceeded:", errorBody);
352
353
  console.log("User API Key Rate limit exceeded. Please try again later.");
353
354
  throw new Error(`GitLab API Rate Limit Exceeded: ${errorBody}`);
@@ -937,27 +938,50 @@ async function createRepository(options) {
937
938
  * MR ėĄ°íšŒ í•¨ėˆ˜ (Function to retrieve merge request)
938
939
  *
939
940
  * @param {string} projectId - The ID or URL-encoded path of the project
940
- * @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)
941
943
  * @returns {Promise<GitLabMergeRequest>} The merge request details
942
944
  */
943
- async function getMergeRequest(projectId, mergeRequestIid) {
944
- 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
+ }
945
956
  const response = await fetch(url.toString(), {
946
957
  ...DEFAULT_FETCH_CONFIG,
947
958
  });
948
959
  await handleGitLabError(response);
949
- 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);
950
966
  }
951
967
  /**
952
968
  * Get merge request changes/diffs
953
969
  * MR ëŗ€ę˛Ŋė‚Ŧ항 ėĄ°íšŒ í•¨ėˆ˜ (Function to retrieve merge request changes)
954
970
  *
955
971
  * @param {string} projectId - The ID or URL-encoded path of the project
956
- * @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)
957
974
  * @param {string} [view] - The view type for the diff (inline or parallel)
958
975
  * @returns {Promise<GitLabMergeRequestDiff[]>} The merge request diffs
959
976
  */
960
- 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
+ }
961
985
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}/changes`);
962
986
  if (view) {
963
987
  url.searchParams.append("view", view);
@@ -974,11 +998,19 @@ async function getMergeRequestDiffs(projectId, mergeRequestIid, view) {
974
998
  * MR ė—…ë°ė´íŠ¸ í•¨ėˆ˜ (Function to update merge request)
975
999
  *
976
1000
  * @param {string} projectId - The ID or URL-encoded path of the project
977
- * @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)
978
1003
  * @param {Object} options - The update options
979
1004
  * @returns {Promise<GitLabMergeRequest>} The updated merge request
980
1005
  */
981
- 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
+ }
982
1014
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}`);
983
1015
  const response = await fetch(url.toString(), {
984
1016
  ...DEFAULT_FETCH_CONFIG,
@@ -1304,9 +1336,9 @@ async function listGroupProjects(options) {
1304
1336
  async function listWikiPages(projectId, options = {}) {
1305
1337
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis`);
1306
1338
  if (options.page)
1307
- url.searchParams.append('page', options.page.toString());
1339
+ url.searchParams.append("page", options.page.toString());
1308
1340
  if (options.per_page)
1309
- url.searchParams.append('per_page', options.per_page.toString());
1341
+ url.searchParams.append("per_page", options.per_page.toString());
1310
1342
  const response = await fetch(url.toString(), {
1311
1343
  ...DEFAULT_FETCH_CONFIG,
1312
1344
  });
@@ -1332,7 +1364,7 @@ async function createWikiPage(projectId, title, content, format) {
1332
1364
  body.format = format;
1333
1365
  const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis`, {
1334
1366
  ...DEFAULT_FETCH_CONFIG,
1335
- method: 'POST',
1367
+ method: "POST",
1336
1368
  body: JSON.stringify(body),
1337
1369
  });
1338
1370
  await handleGitLabError(response);
@@ -1352,7 +1384,7 @@ async function updateWikiPage(projectId, slug, title, content, format) {
1352
1384
  body.format = format;
1353
1385
  const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis/${encodeURIComponent(slug)}`, {
1354
1386
  ...DEFAULT_FETCH_CONFIG,
1355
- method: 'PUT',
1387
+ method: "PUT",
1356
1388
  body: JSON.stringify(body),
1357
1389
  });
1358
1390
  await handleGitLabError(response);
@@ -1365,7 +1397,7 @@ async function updateWikiPage(projectId, slug, title, content, format) {
1365
1397
  async function deleteWikiPage(projectId, slug) {
1366
1398
  const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis/${encodeURIComponent(slug)}`, {
1367
1399
  ...DEFAULT_FETCH_CONFIG,
1368
- method: 'DELETE',
1400
+ method: "DELETE",
1369
1401
  });
1370
1402
  await handleGitLabError(response);
1371
1403
  }
@@ -1493,7 +1525,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1493
1525
  }
1494
1526
  case "get_merge_request": {
1495
1527
  const args = GetMergeRequestSchema.parse(request.params.arguments);
1496
- 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);
1497
1529
  return {
1498
1530
  content: [
1499
1531
  { type: "text", text: JSON.stringify(mergeRequest, null, 2) },
@@ -1502,15 +1534,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1502
1534
  }
1503
1535
  case "get_merge_request_diffs": {
1504
1536
  const args = GetMergeRequestDiffsSchema.parse(request.params.arguments);
1505
- 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);
1506
1538
  return {
1507
1539
  content: [{ type: "text", text: JSON.stringify(diffs, null, 2) }],
1508
1540
  };
1509
1541
  }
1510
1542
  case "update_merge_request": {
1511
1543
  const args = UpdateMergeRequestSchema.parse(request.params.arguments);
1512
- const { project_id, merge_request_iid, ...options } = args;
1513
- 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);
1514
1546
  return {
1515
1547
  content: [
1516
1548
  { type: "text", text: JSON.stringify(mergeRequest, null, 2) },
@@ -1731,27 +1763,45 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1731
1763
  case "list_wiki_pages": {
1732
1764
  const { project_id, page, per_page } = ListWikiPagesSchema.parse(request.params.arguments);
1733
1765
  const wikiPages = await listWikiPages(project_id, { page, per_page });
1734
- return { content: [{ type: "text", text: JSON.stringify(wikiPages, null, 2) }] };
1766
+ return {
1767
+ content: [{ type: "text", text: JSON.stringify(wikiPages, null, 2) }],
1768
+ };
1735
1769
  }
1736
1770
  case "get_wiki_page": {
1737
1771
  const { project_id, slug } = GetWikiPageSchema.parse(request.params.arguments);
1738
1772
  const wikiPage = await getWikiPage(project_id, slug);
1739
- return { content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }] };
1773
+ return {
1774
+ content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }],
1775
+ };
1740
1776
  }
1741
1777
  case "create_wiki_page": {
1742
1778
  const { project_id, title, content, format } = CreateWikiPageSchema.parse(request.params.arguments);
1743
1779
  const wikiPage = await createWikiPage(project_id, title, content, format);
1744
- return { content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }] };
1780
+ return {
1781
+ content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }],
1782
+ };
1745
1783
  }
1746
1784
  case "update_wiki_page": {
1747
1785
  const { project_id, slug, title, content, format } = UpdateWikiPageSchema.parse(request.params.arguments);
1748
1786
  const wikiPage = await updateWikiPage(project_id, slug, title, content, format);
1749
- return { content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }] };
1787
+ return {
1788
+ content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }],
1789
+ };
1750
1790
  }
1751
1791
  case "delete_wiki_page": {
1752
1792
  const { project_id, slug } = DeleteWikiPageSchema.parse(request.params.arguments);
1753
1793
  await deleteWikiPage(project_id, slug);
1754
- return { content: [{ type: "text", text: JSON.stringify({ status: "success", message: "Wiki page deleted successfully" }, null, 2) }] };
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
+ };
1755
1805
  }
1756
1806
  default:
1757
1807
  throw new Error(`Unknown tool: ${request.params.name}`);
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,67 +707,160 @@ 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"),
684
864
  });
685
865
  // Add wiki operation schemas
686
866
  export const ListWikiPagesSchema = z.object({
@@ -696,14 +876,20 @@ export const CreateWikiPageSchema = z.object({
696
876
  project_id: z.string().describe("Project ID or URL-encoded path"),
697
877
  title: z.string().describe("Title of the wiki page"),
698
878
  content: z.string().describe("Content of the wiki page"),
699
- format: z.string().optional().describe("Content format, e.g., markdown, rdoc"),
879
+ format: z
880
+ .string()
881
+ .optional()
882
+ .describe("Content format, e.g., markdown, rdoc"),
700
883
  });
701
884
  export const UpdateWikiPageSchema = z.object({
702
885
  project_id: z.string().describe("Project ID or URL-encoded path"),
703
886
  slug: z.string().describe("URL-encoded slug of the wiki page"),
704
887
  title: z.string().optional().describe("New title of the wiki page"),
705
888
  content: z.string().optional().describe("New content of the wiki page"),
706
- format: z.string().optional().describe("Content format, e.g., markdown, rdoc"),
889
+ format: z
890
+ .string()
891
+ .optional()
892
+ .describe("Content format, e.g., markdown, rdoc"),
707
893
  });
708
894
  export const DeleteWikiPageSchema = z.object({
709
895
  project_id: z.string().describe("Project ID or URL-encoded path"),
@@ -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.32",
3
+ "version": "1.0.33",
4
4
  "description": "MCP server for using the GitLab API",
5
5
  "license": "MIT",
6
6
  "author": "zereight",
@@ -19,7 +19,8 @@
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",