@zereight/mcp-gitlab 1.0.32 → 1.0.34

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,21 +3,21 @@ 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
19
19
  GitLabDiscussionSchema, UpdateMergeRequestNoteSchema, // Added
20
- ListMergeRequestDiscussionsSchema, } from "./schemas.js";
20
+ ListMergeRequestDiscussionsSchema, GitLabTreeItemSchema, GetRepositoryTreeSchema, } from "./schemas.js";
21
21
  /**
22
22
  * Read version from package.json
23
23
  */
@@ -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,12 @@ 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
+ {
284
+ name: "get_repository_tree",
285
+ description: "Get the repository tree for a GitLab project (list files and directories)",
286
+ inputSchema: zodToJsonSchema(GetRepositoryTreeSchema),
287
+ },
283
288
  ];
284
289
  // Define which tools are read-only
285
290
  const readOnlyTools = [
@@ -347,7 +352,8 @@ async function handleGitLabError(response) {
347
352
  if (!response.ok) {
348
353
  const errorBody = await response.text();
349
354
  // Check specifically for Rate Limit error
350
- if (response.status === 403 && errorBody.includes("User API Key Rate limit exceeded")) {
355
+ if (response.status === 403 &&
356
+ errorBody.includes("User API Key Rate limit exceeded")) {
351
357
  console.error("GitLab API Rate Limit Exceeded:", errorBody);
352
358
  console.log("User API Key Rate limit exceeded. Please try again later.");
353
359
  throw new Error(`GitLab API Rate Limit Exceeded: ${errorBody}`);
@@ -937,27 +943,50 @@ async function createRepository(options) {
937
943
  * MR ėĄ°íšŒ í•¨ėˆ˜ (Function to retrieve merge request)
938
944
  *
939
945
  * @param {string} projectId - The ID or URL-encoded path of the project
940
- * @param {number} mergeRequestIid - The internal ID of the merge request
946
+ * @param {number} mergeRequestIid - The internal ID of the merge request (Optional)
947
+ * @param {string} [branchName] - The name of the branch to search for merge request by branch name (Optional)
941
948
  * @returns {Promise<GitLabMergeRequest>} The merge request details
942
949
  */
943
- async function getMergeRequest(projectId, mergeRequestIid) {
944
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}`);
950
+ async function getMergeRequest(projectId, mergeRequestIid, branchName) {
951
+ let url;
952
+ if (mergeRequestIid) {
953
+ url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}`);
954
+ }
955
+ else if (branchName) {
956
+ url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests?source_branch=${encodeURIComponent(branchName)}`);
957
+ }
958
+ else {
959
+ throw new Error("Either mergeRequestIid or branchName must be provided");
960
+ }
945
961
  const response = await fetch(url.toString(), {
946
962
  ...DEFAULT_FETCH_CONFIG,
947
963
  });
948
964
  await handleGitLabError(response);
949
- return GitLabMergeRequestSchema.parse(await response.json());
965
+ const data = await response.json();
966
+ // If response is an array (Comes from branchName search), return the first item if exist
967
+ if (Array.isArray(data) && data.length > 0) {
968
+ return GitLabMergeRequestSchema.parse(data[0]);
969
+ }
970
+ return GitLabMergeRequestSchema.parse(data);
950
971
  }
951
972
  /**
952
973
  * Get merge request changes/diffs
953
974
  * MR ëŗ€ę˛Ŋė‚Ŧ항 ėĄ°íšŒ í•¨ėˆ˜ (Function to retrieve merge request changes)
954
975
  *
955
976
  * @param {string} projectId - The ID or URL-encoded path of the project
956
- * @param {number} mergeRequestIid - The internal ID of the merge request
977
+ * @param {number} mergeRequestIid - The internal ID of the merge request (Either mergeRequestIid or branchName must be provided)
978
+ * @param {string} [branchName] - The name of the branch to search for merge request by branch name (Either mergeRequestIid or branchName must be provided)
957
979
  * @param {string} [view] - The view type for the diff (inline or parallel)
958
980
  * @returns {Promise<GitLabMergeRequestDiff[]>} The merge request diffs
959
981
  */
960
- async function getMergeRequestDiffs(projectId, mergeRequestIid, view) {
982
+ async function getMergeRequestDiffs(projectId, mergeRequestIid, branchName, view) {
983
+ if (!mergeRequestIid && !branchName) {
984
+ throw new Error("Either mergeRequestIid or branchName must be provided");
985
+ }
986
+ if (branchName && !mergeRequestIid) {
987
+ const mergeRequest = await getMergeRequest(projectId, undefined, branchName);
988
+ mergeRequestIid = mergeRequest.iid;
989
+ }
961
990
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}/changes`);
962
991
  if (view) {
963
992
  url.searchParams.append("view", view);
@@ -974,11 +1003,19 @@ async function getMergeRequestDiffs(projectId, mergeRequestIid, view) {
974
1003
  * MR ė—…ë°ė´íŠ¸ í•¨ėˆ˜ (Function to update merge request)
975
1004
  *
976
1005
  * @param {string} projectId - The ID or URL-encoded path of the project
977
- * @param {number} mergeRequestIid - The internal ID of the merge request
1006
+ * @param {number} mergeRequestIid - The internal ID of the merge request (Optional)
1007
+ * @param {string} branchName - The name of the branch to search for merge request by branch name (Optional)
978
1008
  * @param {Object} options - The update options
979
1009
  * @returns {Promise<GitLabMergeRequest>} The updated merge request
980
1010
  */
981
- async function updateMergeRequest(projectId, mergeRequestIid, options) {
1011
+ async function updateMergeRequest(projectId, options, mergeRequestIid, branchName) {
1012
+ if (!mergeRequestIid && !branchName) {
1013
+ throw new Error("Either mergeRequestIid or branchName must be provided");
1014
+ }
1015
+ if (branchName && !mergeRequestIid) {
1016
+ const mergeRequest = await getMergeRequest(projectId, undefined, branchName);
1017
+ mergeRequestIid = mergeRequest.iid;
1018
+ }
982
1019
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}`);
983
1020
  const response = await fetch(url.toString(), {
984
1021
  ...DEFAULT_FETCH_CONFIG,
@@ -1304,9 +1341,9 @@ async function listGroupProjects(options) {
1304
1341
  async function listWikiPages(projectId, options = {}) {
1305
1342
  const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis`);
1306
1343
  if (options.page)
1307
- url.searchParams.append('page', options.page.toString());
1344
+ url.searchParams.append("page", options.page.toString());
1308
1345
  if (options.per_page)
1309
- url.searchParams.append('per_page', options.per_page.toString());
1346
+ url.searchParams.append("per_page", options.per_page.toString());
1310
1347
  const response = await fetch(url.toString(), {
1311
1348
  ...DEFAULT_FETCH_CONFIG,
1312
1349
  });
@@ -1332,7 +1369,7 @@ async function createWikiPage(projectId, title, content, format) {
1332
1369
  body.format = format;
1333
1370
  const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis`, {
1334
1371
  ...DEFAULT_FETCH_CONFIG,
1335
- method: 'POST',
1372
+ method: "POST",
1336
1373
  body: JSON.stringify(body),
1337
1374
  });
1338
1375
  await handleGitLabError(response);
@@ -1352,7 +1389,7 @@ async function updateWikiPage(projectId, slug, title, content, format) {
1352
1389
  body.format = format;
1353
1390
  const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis/${encodeURIComponent(slug)}`, {
1354
1391
  ...DEFAULT_FETCH_CONFIG,
1355
- method: 'PUT',
1392
+ method: "PUT",
1356
1393
  body: JSON.stringify(body),
1357
1394
  });
1358
1395
  await handleGitLabError(response);
@@ -1365,10 +1402,45 @@ async function updateWikiPage(projectId, slug, title, content, format) {
1365
1402
  async function deleteWikiPage(projectId, slug) {
1366
1403
  const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis/${encodeURIComponent(slug)}`, {
1367
1404
  ...DEFAULT_FETCH_CONFIG,
1368
- method: 'DELETE',
1405
+ method: "DELETE",
1369
1406
  });
1370
1407
  await handleGitLabError(response);
1371
1408
  }
1409
+ /**
1410
+ * Get the repository tree for a project
1411
+ * @param {string} projectId - The ID or URL-encoded path of the project
1412
+ * @param {GetRepositoryTreeOptions} options - Options for the tree
1413
+ * @returns {Promise<GitLabTreeItem[]>}
1414
+ */
1415
+ async function getRepositoryTree(options) {
1416
+ const queryParams = new URLSearchParams();
1417
+ if (options.path)
1418
+ queryParams.append("path", options.path);
1419
+ if (options.ref)
1420
+ queryParams.append("ref", options.ref);
1421
+ if (options.recursive)
1422
+ queryParams.append("recursive", "true");
1423
+ if (options.per_page)
1424
+ queryParams.append("per_page", options.per_page.toString());
1425
+ if (options.page_token)
1426
+ queryParams.append("page_token", options.page_token);
1427
+ if (options.pagination)
1428
+ queryParams.append("pagination", options.pagination);
1429
+ const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(options.project_id)}/repository/tree?${queryParams.toString()}`, {
1430
+ headers: {
1431
+ Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`,
1432
+ "Content-Type": "application/json",
1433
+ },
1434
+ });
1435
+ if (response.status === 404) {
1436
+ throw new Error("Repository or path not found");
1437
+ }
1438
+ if (!response.ok) {
1439
+ throw new Error(`Failed to get repository tree: ${response.statusText}`);
1440
+ }
1441
+ const data = await response.json();
1442
+ return z.array(GitLabTreeItemSchema).parse(data);
1443
+ }
1372
1444
  server.setRequestHandler(ListToolsRequestSchema, async () => {
1373
1445
  // Apply read-only filter first
1374
1446
  const tools0 = GITLAB_READ_ONLY_MODE
@@ -1493,7 +1565,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1493
1565
  }
1494
1566
  case "get_merge_request": {
1495
1567
  const args = GetMergeRequestSchema.parse(request.params.arguments);
1496
- const mergeRequest = await getMergeRequest(args.project_id, args.merge_request_iid);
1568
+ const mergeRequest = await getMergeRequest(args.project_id, args.merge_request_iid, args.source_branch);
1497
1569
  return {
1498
1570
  content: [
1499
1571
  { type: "text", text: JSON.stringify(mergeRequest, null, 2) },
@@ -1502,15 +1574,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1502
1574
  }
1503
1575
  case "get_merge_request_diffs": {
1504
1576
  const args = GetMergeRequestDiffsSchema.parse(request.params.arguments);
1505
- const diffs = await getMergeRequestDiffs(args.project_id, args.merge_request_iid, args.view);
1577
+ const diffs = await getMergeRequestDiffs(args.project_id, args.merge_request_iid, args.source_branch, args.view);
1506
1578
  return {
1507
1579
  content: [{ type: "text", text: JSON.stringify(diffs, null, 2) }],
1508
1580
  };
1509
1581
  }
1510
1582
  case "update_merge_request": {
1511
1583
  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);
1584
+ const { project_id, merge_request_iid, source_branch, ...options } = args;
1585
+ const mergeRequest = await updateMergeRequest(project_id, options, merge_request_iid, source_branch);
1514
1586
  return {
1515
1587
  content: [
1516
1588
  { type: "text", text: JSON.stringify(mergeRequest, null, 2) },
@@ -1731,27 +1803,52 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1731
1803
  case "list_wiki_pages": {
1732
1804
  const { project_id, page, per_page } = ListWikiPagesSchema.parse(request.params.arguments);
1733
1805
  const wikiPages = await listWikiPages(project_id, { page, per_page });
1734
- return { content: [{ type: "text", text: JSON.stringify(wikiPages, null, 2) }] };
1806
+ return {
1807
+ content: [{ type: "text", text: JSON.stringify(wikiPages, null, 2) }],
1808
+ };
1735
1809
  }
1736
1810
  case "get_wiki_page": {
1737
1811
  const { project_id, slug } = GetWikiPageSchema.parse(request.params.arguments);
1738
1812
  const wikiPage = await getWikiPage(project_id, slug);
1739
- return { content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }] };
1813
+ return {
1814
+ content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }],
1815
+ };
1740
1816
  }
1741
1817
  case "create_wiki_page": {
1742
1818
  const { project_id, title, content, format } = CreateWikiPageSchema.parse(request.params.arguments);
1743
1819
  const wikiPage = await createWikiPage(project_id, title, content, format);
1744
- return { content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }] };
1820
+ return {
1821
+ content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }],
1822
+ };
1745
1823
  }
1746
1824
  case "update_wiki_page": {
1747
1825
  const { project_id, slug, title, content, format } = UpdateWikiPageSchema.parse(request.params.arguments);
1748
1826
  const wikiPage = await updateWikiPage(project_id, slug, title, content, format);
1749
- return { content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }] };
1827
+ return {
1828
+ content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }],
1829
+ };
1750
1830
  }
1751
1831
  case "delete_wiki_page": {
1752
1832
  const { project_id, slug } = DeleteWikiPageSchema.parse(request.params.arguments);
1753
1833
  await deleteWikiPage(project_id, slug);
1754
- return { content: [{ type: "text", text: JSON.stringify({ status: "success", message: "Wiki page deleted successfully" }, null, 2) }] };
1834
+ return {
1835
+ content: [
1836
+ {
1837
+ type: "text",
1838
+ text: JSON.stringify({
1839
+ status: "success",
1840
+ message: "Wiki page deleted successfully",
1841
+ }, null, 2),
1842
+ },
1843
+ ],
1844
+ };
1845
+ }
1846
+ case "get_repository_tree": {
1847
+ const args = GetRepositoryTreeSchema.parse(request.params.arguments);
1848
+ const tree = await getRepositoryTree(args);
1849
+ return {
1850
+ content: [{ type: "text", text: JSON.stringify(tree, null, 2) }],
1851
+ };
1755
1852
  }
1756
1853
  default:
1757
1854
  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;
@@ -134,16 +146,25 @@ export const FileOperationSchema = z.object({
134
146
  content: z.string(),
135
147
  });
136
148
  // Tree and commit schemas
137
- export const GitLabTreeEntrySchema = z.object({
138
- id: z.string(), // Changed from sha to match GitLab API
149
+ export const GitLabTreeItemSchema = z.object({
150
+ id: z.string(),
139
151
  name: z.string(),
140
- type: z.enum(["blob", "tree"]),
152
+ type: z.enum(["tree", "blob"]),
141
153
  path: z.string(),
142
154
  mode: z.string(),
143
155
  });
156
+ export const GetRepositoryTreeSchema = z.object({
157
+ project_id: z.string().describe("The ID or URL-encoded path of the project"),
158
+ path: z.string().optional().describe("The path inside the repository"),
159
+ ref: z.string().optional().describe("The name of a repository branch or tag. Defaults to the default branch."),
160
+ recursive: z.boolean().optional().describe("Boolean value to get a recursive tree"),
161
+ per_page: z.number().optional().describe("Number of results to show per page"),
162
+ page_token: z.string().optional().describe("The tree record ID for pagination"),
163
+ pagination: z.string().optional().describe("Pagination method (keyset)")
164
+ });
144
165
  export const GitLabTreeSchema = z.object({
145
166
  id: z.string(), // Changed from sha to match GitLab API
146
- tree: z.array(GitLabTreeEntrySchema),
167
+ tree: z.array(GitLabTreeItemSchema),
147
168
  });
148
169
  export const GitLabCommitSchema = z.object({
149
170
  id: z.string(), // Changed from sha to match GitLab API
@@ -251,17 +272,21 @@ export const GitLabIssueSchema = z.object({
251
272
  updated_at: z.string(),
252
273
  closed_at: z.string().nullable(),
253
274
  web_url: z.string(), // Changed from html_url to match GitLab API
254
- references: z.object({
275
+ references: z
276
+ .object({
255
277
  short: z.string(),
256
278
  relative: z.string(),
257
279
  full: z.string(),
258
- }).optional(),
259
- time_stats: z.object({
280
+ })
281
+ .optional(),
282
+ time_stats: z
283
+ .object({
260
284
  time_estimate: z.number(),
261
285
  total_time_spent: z.number(),
262
286
  human_time_estimate: z.string().nullable(),
263
287
  human_total_time_spent: z.string().nullable(),
264
- }).optional(),
288
+ })
289
+ .optional(),
265
290
  confidential: z.boolean().optional(),
266
291
  due_date: z.string().nullable().optional(),
267
292
  discussion_locked: z.boolean().nullable().optional(),
@@ -270,7 +295,7 @@ export const GitLabIssueSchema = z.object({
270
295
  // NEW SCHEMA: For issue with link details (used in listing issue links)
271
296
  export const GitLabIssueWithLinkDetailsSchema = GitLabIssueSchema.extend({
272
297
  issue_link_id: z.number(),
273
- link_type: z.enum(['relates_to', 'blocks', 'is_blocked_by']),
298
+ link_type: z.enum(["relates_to", "blocks", "is_blocked_by"]),
274
299
  link_created_at: z.string(),
275
300
  link_updated_at: z.string(),
276
301
  });
@@ -278,11 +303,13 @@ export const GitLabIssueWithLinkDetailsSchema = GitLabIssueSchema.extend({
278
303
  export const GitLabForkParentSchema = z.object({
279
304
  name: z.string(),
280
305
  path_with_namespace: z.string(), // Changed from full_name to match GitLab API
281
- owner: z.object({
306
+ owner: z
307
+ .object({
282
308
  username: z.string(), // Changed from login to match GitLab API
283
309
  id: z.number(),
284
310
  avatar_url: z.string(),
285
- }).optional(), // Made optional to handle cases where GitLab API doesn't include it
311
+ })
312
+ .optional(), // Made optional to handle cases where GitLab API doesn't include it
286
313
  web_url: z.string(), // Changed from html_url to match GitLab API
287
314
  });
288
315
  export const GitLabForkSchema = GitLabRepositorySchema.extend({
@@ -346,7 +373,9 @@ export const GitLabDiscussionNoteSchema = z.object({
346
373
  resolved: z.boolean().optional(),
347
374
  resolved_by: GitLabUserSchema.nullable().optional(),
348
375
  resolved_at: z.string().nullable().optional(),
349
- position: z.object({
376
+ position: z
377
+ .object({
378
+ // Only present for DiffNote
350
379
  base_sha: z.string(),
351
380
  start_sha: z.string(),
352
381
  head_sha: z.string(),
@@ -355,7 +384,8 @@ export const GitLabDiscussionNoteSchema = z.object({
355
384
  position_type: z.enum(["text", "image", "file"]),
356
385
  old_line: z.number().nullable(),
357
386
  new_line: z.number().nullable(),
358
- line_range: z.object({
387
+ line_range: z
388
+ .object({
359
389
  start: z.object({
360
390
  line_code: z.string(),
361
391
  type: z.enum(["new", "old"]),
@@ -368,12 +398,15 @@ export const GitLabDiscussionNoteSchema = z.object({
368
398
  old_line: z.number().nullable(),
369
399
  new_line: z.number().nullable(),
370
400
  }),
371
- }).nullable().optional(), // For multi-line diff notes
401
+ })
402
+ .nullable()
403
+ .optional(), // For multi-line diff notes
372
404
  width: z.number().optional(), // For image diff notes
373
405
  height: z.number().optional(), // For image diff notes
374
406
  x: z.number().optional(), // For image diff notes
375
407
  y: z.number().optional(), // For image diff notes
376
- }).optional(),
408
+ })
409
+ .optional(),
377
410
  });
378
411
  export const GitLabDiscussionSchema = z.object({
379
412
  id: z.string(),
@@ -402,10 +435,7 @@ export const CreateOrUpdateFileSchema = ProjectParamsSchema.extend({
402
435
  .string()
403
436
  .optional()
404
437
  .describe("Path of the file to move/rename"),
405
- last_commit_id: z
406
- .string()
407
- .optional()
408
- .describe("Last known file commit ID"),
438
+ last_commit_id: z.string().optional().describe("Last known file commit ID"),
409
439
  commit_id: z
410
440
  .string()
411
441
  .optional()
@@ -489,7 +519,9 @@ export const GitLabMergeRequestDiffSchema = z.object({
489
519
  export const GetMergeRequestSchema = ProjectParamsSchema.extend({
490
520
  merge_request_iid: z
491
521
  .number()
492
- .describe("The internal ID of the merge request"),
522
+ .optional()
523
+ .describe("The IID of a merge request"),
524
+ source_branch: z.string().optional().describe("Source branch name"),
493
525
  });
494
526
  export const UpdateMergeRequestSchema = GetMergeRequestSchema.extend({
495
527
  title: z.string().optional().describe("The title of the merge request"),
@@ -531,22 +563,61 @@ export const CreateNoteSchema = z.object({
531
563
  // Issues API operation schemas
532
564
  export const ListIssuesSchema = z.object({
533
565
  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"),
566
+ assignee_id: z
567
+ .number()
568
+ .optional()
569
+ .describe("Return issues assigned to the given user ID"),
570
+ assignee_username: z
571
+ .string()
572
+ .optional()
573
+ .describe("Return issues assigned to the given username"),
574
+ author_id: z
575
+ .number()
576
+ .optional()
577
+ .describe("Return issues created by the given user ID"),
578
+ author_username: z
579
+ .string()
580
+ .optional()
581
+ .describe("Return issues created by the given username"),
582
+ confidential: z
583
+ .boolean()
584
+ .optional()
585
+ .describe("Filter confidential or public issues"),
586
+ created_after: z
587
+ .string()
588
+ .optional()
589
+ .describe("Return issues created after the given time"),
590
+ created_before: z
591
+ .string()
592
+ .optional()
593
+ .describe("Return issues created before the given time"),
594
+ due_date: z
595
+ .string()
596
+ .optional()
597
+ .describe("Return issues that have the due date"),
542
598
  label_name: z.array(z.string()).optional().describe("Array of label names"),
543
599
  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"),
600
+ scope: z
601
+ .enum(["created-by-me", "assigned-to-me", "all"])
602
+ .optional()
603
+ .describe("Return issues from a specific scope"),
545
604
  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"),
605
+ state: z
606
+ .enum(["opened", "closed", "all"])
607
+ .optional()
608
+ .describe("Return issues with a specific state"),
609
+ updated_after: z
610
+ .string()
611
+ .optional()
612
+ .describe("Return issues updated after the given time"),
613
+ updated_before: z
614
+ .string()
615
+ .optional()
616
+ .describe("Return issues updated before the given time"),
617
+ with_labels_details: z
618
+ .boolean()
619
+ .optional()
620
+ .describe("Return more details for each label"),
550
621
  page: z.number().optional().describe("Page number for pagination"),
551
622
  per_page: z.number().optional().describe("Number of items per page"),
552
623
  });
@@ -559,13 +630,28 @@ export const UpdateIssueSchema = z.object({
559
630
  issue_iid: z.number().describe("The internal ID of the project issue"),
560
631
  title: z.string().optional().describe("The title of the issue"),
561
632
  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)"),
633
+ assignee_ids: z
634
+ .array(z.number())
635
+ .optional()
636
+ .describe("Array of user IDs to assign issue to"),
637
+ confidential: z
638
+ .boolean()
639
+ .optional()
640
+ .describe("Set the issue to be confidential"),
641
+ discussion_locked: z
642
+ .boolean()
643
+ .optional()
644
+ .describe("Flag to lock discussions"),
645
+ due_date: z
646
+ .string()
647
+ .optional()
648
+ .describe("Date the issue is due (YYYY-MM-DD)"),
566
649
  labels: z.array(z.string()).optional().describe("Array of label names"),
567
650
  milestone_id: z.number().optional().describe("Milestone ID to assign"),
568
- state_event: z.enum(['close', 'reopen']).optional().describe("Update issue state (close/reopen)"),
651
+ state_event: z
652
+ .enum(["close", "reopen"])
653
+ .optional()
654
+ .describe("Update issue state (close/reopen)"),
569
655
  weight: z.number().optional().describe("Weight of the issue (0-9)"),
570
656
  });
571
657
  export const DeleteIssueSchema = z.object({
@@ -576,7 +662,7 @@ export const DeleteIssueSchema = z.object({
576
662
  export const GitLabIssueLinkSchema = z.object({
577
663
  source_issue: GitLabIssueSchema,
578
664
  target_issue: GitLabIssueSchema,
579
- link_type: z.enum(['relates_to', 'blocks', 'is_blocked_by']),
665
+ link_type: z.enum(["relates_to", "blocks", "is_blocked_by"]),
580
666
  });
581
667
  export const ListIssueLinksSchema = z.object({
582
668
  project_id: z.string().describe("Project ID or URL-encoded path"),
@@ -590,9 +676,16 @@ export const GetIssueLinkSchema = z.object({
590
676
  export const CreateIssueLinkSchema = z.object({
591
677
  project_id: z.string().describe("Project ID or URL-encoded path"),
592
678
  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"),
679
+ target_project_id: z
680
+ .string()
681
+ .describe("The ID or URL-encoded path of a target project"),
682
+ target_issue_iid: z
683
+ .number()
684
+ .describe("The internal ID of a target project's issue"),
685
+ link_type: z
686
+ .enum(["relates_to", "blocks", "is_blocked_by"])
687
+ .optional()
688
+ .describe("The type of the relation, defaults to relates_to"),
596
689
  });
597
690
  export const DeleteIssueLinkSchema = z.object({
598
691
  project_id: z.string().describe("Project ID or URL-encoded path"),
@@ -604,7 +697,10 @@ export const ListNamespacesSchema = z.object({
604
697
  search: z.string().optional().describe("Search term for namespaces"),
605
698
  page: z.number().optional().describe("Page number for pagination"),
606
699
  per_page: z.number().optional().describe("Number of items per page"),
607
- owned: z.boolean().optional().describe("Filter for namespaces owned by current user"),
700
+ owned: z
701
+ .boolean()
702
+ .optional()
703
+ .describe("Filter for namespaces owned by current user"),
608
704
  });
609
705
  export const GetNamespaceSchema = z.object({
610
706
  namespace_id: z.string().describe("Namespace ID or full path"),
@@ -620,67 +716,160 @@ export const ListProjectsSchema = z.object({
620
716
  search: z.string().optional().describe("Search term for projects"),
621
717
  page: z.number().optional().describe("Page number for pagination"),
622
718
  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"),
719
+ search_namespaces: z
720
+ .boolean()
721
+ .optional()
722
+ .describe("Needs to be true if search is full path"),
723
+ owned: z
724
+ .boolean()
725
+ .optional()
726
+ .describe("Filter for projects owned by current user"),
727
+ membership: z
728
+ .boolean()
729
+ .optional()
730
+ .describe("Filter for projects where current user is a member"),
625
731
  simple: z.boolean().optional().describe("Return only limited fields"),
626
732
  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"),
733
+ visibility: z
734
+ .enum(["public", "internal", "private"])
735
+ .optional()
736
+ .describe("Filter by project visibility"),
737
+ order_by: z
738
+ .enum([
739
+ "id",
740
+ "name",
741
+ "path",
742
+ "created_at",
743
+ "updated_at",
744
+ "last_activity_at",
745
+ ])
746
+ .optional()
747
+ .describe("Return projects ordered by field"),
748
+ sort: z
749
+ .enum(["asc", "desc"])
750
+ .optional()
751
+ .describe("Return projects sorted in ascending or descending order"),
752
+ with_issues_enabled: z
753
+ .boolean()
754
+ .optional()
755
+ .describe("Filter projects with issues feature enabled"),
756
+ with_merge_requests_enabled: z
757
+ .boolean()
758
+ .optional()
759
+ .describe("Filter projects with merge requests feature enabled"),
760
+ min_access_level: z
761
+ .number()
762
+ .optional()
763
+ .describe("Filter by minimum access level"),
633
764
  });
634
765
  // Label operation schemas
635
766
  export const ListLabelsSchema = z.object({
636
767
  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"),
768
+ with_counts: z
769
+ .boolean()
770
+ .optional()
771
+ .describe("Whether or not to include issue and merge request counts"),
772
+ include_ancestor_groups: z
773
+ .boolean()
774
+ .optional()
775
+ .describe("Include ancestor groups"),
639
776
  search: z.string().optional().describe("Keyword to filter labels by"),
640
777
  });
641
778
  export const GetLabelSchema = z.object({
642
779
  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"),
780
+ label_id: z
781
+ .union([z.number(), z.string()])
782
+ .describe("The ID or title of a project's label"),
783
+ include_ancestor_groups: z
784
+ .boolean()
785
+ .optional()
786
+ .describe("Include ancestor groups"),
645
787
  });
646
788
  export const CreateLabelSchema = z.object({
647
789
  project_id: z.string().describe("Project ID or URL-encoded path"),
648
790
  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"),
791
+ color: z
792
+ .string()
793
+ .describe("The color of the label given in 6-digit hex notation with leading '#' sign"),
650
794
  description: z.string().optional().describe("The description of the label"),
651
- priority: z.number().nullable().optional().describe("The priority of the label"),
795
+ priority: z
796
+ .number()
797
+ .nullable()
798
+ .optional()
799
+ .describe("The priority of the label"),
652
800
  });
653
801
  export const UpdateLabelSchema = z.object({
654
802
  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"),
803
+ label_id: z
804
+ .union([z.number(), z.string()])
805
+ .describe("The ID or title of a project's label"),
656
806
  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"),
807
+ color: z
808
+ .string()
809
+ .optional()
810
+ .describe("The color of the label given in 6-digit hex notation with leading '#' sign"),
811
+ description: z
812
+ .string()
813
+ .optional()
814
+ .describe("The new description of the label"),
815
+ priority: z
816
+ .number()
817
+ .nullable()
818
+ .optional()
819
+ .describe("The new priority of the label"),
660
820
  });
661
821
  export const DeleteLabelSchema = z.object({
662
822
  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"),
823
+ label_id: z
824
+ .union([z.number(), z.string()])
825
+ .describe("The ID or title of a project's label"),
664
826
  });
665
827
  // Group projects schema
666
828
  export const ListGroupProjectsSchema = z.object({
667
829
  group_id: z.string().describe("Group ID or path"),
668
- include_subgroups: z.boolean().optional().describe("Include projects from subgroups"),
830
+ include_subgroups: z
831
+ .boolean()
832
+ .optional()
833
+ .describe("Include projects from subgroups"),
669
834
  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"),
835
+ order_by: z
836
+ .enum(["name", "path", "created_at", "updated_at", "last_activity_at"])
837
+ .optional()
838
+ .describe("Field to sort by"),
839
+ sort: z.enum(["asc", "desc"]).optional().describe("Sort direction"),
672
840
  page: z.number().optional().describe("Page number"),
673
841
  per_page: z.number().optional().describe("Number of results per page"),
674
842
  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"),
843
+ visibility: z
844
+ .enum(["public", "internal", "private"])
845
+ .optional()
846
+ .describe("Filter by project visibility"),
847
+ with_issues_enabled: z
848
+ .boolean()
849
+ .optional()
850
+ .describe("Filter projects with issues feature enabled"),
851
+ with_merge_requests_enabled: z
852
+ .boolean()
853
+ .optional()
854
+ .describe("Filter projects with merge requests feature enabled"),
855
+ min_access_level: z
856
+ .number()
857
+ .optional()
858
+ .describe("Filter by minimum access level"),
859
+ with_programming_language: z
860
+ .string()
861
+ .optional()
862
+ .describe("Filter by programming language"),
680
863
  starred: z.boolean().optional().describe("Filter by starred projects"),
681
864
  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")
865
+ with_custom_attributes: z
866
+ .boolean()
867
+ .optional()
868
+ .describe("Include custom attributes"),
869
+ with_security_reports: z
870
+ .boolean()
871
+ .optional()
872
+ .describe("Include security reports"),
684
873
  });
685
874
  // Add wiki operation schemas
686
875
  export const ListWikiPagesSchema = z.object({
@@ -696,14 +885,20 @@ export const CreateWikiPageSchema = z.object({
696
885
  project_id: z.string().describe("Project ID or URL-encoded path"),
697
886
  title: z.string().describe("Title of the wiki page"),
698
887
  content: z.string().describe("Content of the wiki page"),
699
- format: z.string().optional().describe("Content format, e.g., markdown, rdoc"),
888
+ format: z
889
+ .string()
890
+ .optional()
891
+ .describe("Content format, e.g., markdown, rdoc"),
700
892
  });
701
893
  export const UpdateWikiPageSchema = z.object({
702
894
  project_id: z.string().describe("Project ID or URL-encoded path"),
703
895
  slug: z.string().describe("URL-encoded slug of the wiki page"),
704
896
  title: z.string().optional().describe("New title of the wiki page"),
705
897
  content: z.string().optional().describe("New content of the wiki page"),
706
- format: z.string().optional().describe("Content format, e.g., markdown, rdoc"),
898
+ format: z
899
+ .string()
900
+ .optional()
901
+ .describe("Content format, e.g., markdown, rdoc"),
707
902
  });
708
903
  export const DeleteWikiPageSchema = z.object({
709
904
  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.34",
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",