@zereight/mcp-gitlab 1.0.64 → 1.0.66

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
@@ -35,9 +35,39 @@ When using with the Claude App, you need to set up your API key and URLs directl
35
35
  }
36
36
  ```
37
37
 
38
+ #### vscode .vscode/mcp.json
39
+
40
+ ```json
41
+ {
42
+ "inputs": [
43
+ {
44
+ "type": "promptString",
45
+ "id": "gitlab-token",
46
+ "description": "Gitlab Token to read API",
47
+ "password": true
48
+ }
49
+ ],
50
+ "servers": {
51
+ "GitLab-MCP": {
52
+ "type": "stdio",
53
+ "command": "npx",
54
+ "args": ["-y", "@zereight/mcp-gitlab"],
55
+ "env": {
56
+ "GITLAB_PERSONAL_ACCESS_TOKEN": "${input:gitlab-token}",
57
+ "GITLAB_API_URL": "your-fancy-gitlab-url",
58
+ "GITLAB_READ_ONLY_MODE": "true",
59
+ ...
60
+ }
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
38
66
  #### Docker
39
- - stdio
40
- ```mcp.json
67
+
68
+ - stdio mcp.json
69
+
70
+ ```json
41
71
  {
42
72
  "mcpServers": {
43
73
  "GitLab communication server": {
@@ -74,6 +104,7 @@ When using with the Claude App, you need to set up your API key and URLs directl
74
104
  ```
75
105
 
76
106
  - sse
107
+
77
108
  ```shell
78
109
  docker run -i --rm \
79
110
  -e GITLAB_PERSONAL_ACCESS_TOKEN=your_gitlab_token \
@@ -91,6 +122,7 @@ docker run -i --rm \
91
122
  {
92
123
  "mcpServers": {
93
124
  "GitLab communication server": {
125
+ "type": "sse",
94
126
  "url": "http://localhost:3333/sse"
95
127
  }
96
128
  }
@@ -107,15 +139,19 @@ $ sh scripts/image_push.sh docker_user_name
107
139
 
108
140
  - `GITLAB_PERSONAL_ACCESS_TOKEN`: Your GitLab personal access token.
109
141
  - `GITLAB_API_URL`: Your GitLab API URL. (Default: `https://gitlab.com/api/v4`)
142
+ - `GITLAB_PROJECT_ID`: Default project ID. If set, Overwrite this value when making an API request.
110
143
  - `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.
111
144
  - `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.
112
145
  - `USE_MILESTONE`: When set to 'true', enables the milestone-related tools (list_milestones, get_milestone, create_milestone, edit_milestone, delete_milestone, get_milestone_issue, get_milestone_merge_requests, promote_milestone, get_milestone_burndown_events). By default, milestone features are disabled.
113
146
  - `USE_PIPELINE`: When set to 'true', enables the pipeline-related tools (list_pipelines, get_pipeline, list_pipeline_jobs, get_pipeline_job, get_pipeline_job_output, create_pipeline, retry_pipeline, cancel_pipeline). By default, pipeline features are disabled.
114
147
  - `GITLAB_AUTH_COOKIE_PATH`: Path to an authentication cookie file for GitLab instances that require cookie-based authentication. When provided, the cookie will be included in all GitLab API requests.
115
148
 
149
+ [![Star History Chart](https://api.star-history.com/svg?repos=zereight/gitlab-mcp&type=Date)](https://www.star-history.com/#zereight/gitlab-mcp&Date)
150
+
116
151
  ## Tools 🛠️
117
152
 
118
153
  +<!-- TOOLS-START -->
154
+
119
155
  1. `create_or_update_file` - Create or update a single file in a GitLab project
120
156
  2. `search_repositories` - Search for GitLab projects
121
157
  3. `create_repository` - Create a new GitLab project
@@ -127,58 +163,59 @@ $ sh scripts/image_push.sh docker_user_name
127
163
  9. `create_branch` - Create a new branch in a GitLab project
128
164
  10. `get_merge_request` - Get details of a merge request (Either mergeRequestIid or branchName must be provided)
129
165
  11. `get_merge_request_diffs` - Get the changes/diffs of a merge request (Either mergeRequestIid or branchName must be provided)
130
- 12. `get_branch_diffs` - Get the changes/diffs between two branches or commits in a GitLab project
131
- 13. `update_merge_request` - Update a merge request (Either mergeRequestIid or branchName must be provided)
132
- 14. `create_note` - Create a new note (comment) to an issue or merge request
133
- 15. `create_merge_request_thread` - Create a new thread on a merge request
134
- 16. `mr_discussions` - List discussion items for a merge request
135
- 17. `update_merge_request_note` - Modify an existing merge request thread note
136
- 18. `create_merge_request_note` - Add a new note to an existing merge request thread
137
- 19. `update_issue_note` - Modify an existing issue thread note
138
- 20. `create_issue_note` - Add a new note to an existing issue thread
139
- 21. `list_issues` - List issues in a GitLab project with filtering options
140
- 22. `get_issue` - Get details of a specific issue in a GitLab project
141
- 23. `update_issue` - Update an issue in a GitLab project
142
- 24. `delete_issue` - Delete an issue from a GitLab project
143
- 25. `list_issue_links` - List all issue links for a specific issue
144
- 26. `list_issue_discussions` - List discussions for an issue in a GitLab project
145
- 27. `get_issue_link` - Get a specific issue link
146
- 28. `create_issue_link` - Create an issue link between two issues
147
- 29. `delete_issue_link` - Delete an issue link
148
- 30. `list_namespaces` - List all namespaces available to the current user
149
- 31. `get_namespace` - Get details of a namespace by ID or path
150
- 32. `verify_namespace` - Verify if a namespace path exists
151
- 33. `get_project` - Get details of a specific project
152
- 34. `list_projects` - List projects accessible by the current user
153
- 35. `list_labels` - List labels for a project
154
- 36. `get_label` - Get a single label from a project
155
- 37. `create_label` - Create a new label in a project
156
- 38. `update_label` - Update an existing label in a project
157
- 39. `delete_label` - Delete a label from a project
158
- 40. `list_group_projects` - List projects in a GitLab group with filtering options
159
- 41. `list_wiki_pages` - List wiki pages in a GitLab project
160
- 42. `get_wiki_page` - Get details of a specific wiki page
161
- 43. `create_wiki_page` - Create a new wiki page in a GitLab project
162
- 44. `update_wiki_page` - Update an existing wiki page in a GitLab project
163
- 45. `delete_wiki_page` - Delete a wiki page from a GitLab project
164
- 46. `get_repository_tree` - Get the repository tree for a GitLab project (list files and directories)
165
- 47. `list_pipelines` - List pipelines in a GitLab project with filtering options
166
- 48. `get_pipeline` - Get details of a specific pipeline in a GitLab project
167
- 49. `list_pipeline_jobs` - List all jobs in a specific pipeline
168
- 50. `get_pipeline_job` - Get details of a GitLab pipeline job number
169
- 51. `get_pipeline_job_output` - Get the output/trace of a GitLab pipeline job number
170
- 52. `create_pipeline` - Create a new pipeline for a branch or tag
171
- 53. `retry_pipeline` - Retry a failed or canceled pipeline
172
- 54. `cancel_pipeline` - Cancel a running pipeline
173
- 55. `list_merge_requests` - List merge requests in a GitLab project with filtering options
174
- 56. `list_milestones` - List milestones in a GitLab project with filtering options
175
- 57. `get_milestone` - Get details of a specific milestone
176
- 58. `create_milestone` - Create a new milestone in a GitLab project
177
- 59. `edit_milestone` - Edit an existing milestone in a GitLab project
178
- 60. `delete_milestone` - Delete a milestone from a GitLab project
179
- 61. `get_milestone_issue` - Get issues associated with a specific milestone
180
- 62. `get_milestone_merge_requests` - Get merge requests associated with a specific milestone
181
- 63. `promote_milestone` - Promote a milestone to the next stage
182
- 64. `get_milestone_burndown_events` - Get burndown events for a specific milestone
183
- 65. `get_users` - Get GitLab user details by usernames
166
+ 12. `list_merge_request_diffs` - List merge request diffs with pagination support (Either mergeRequestIid or branchName must be provided)
167
+ 13. `get_branch_diffs` - Get the changes/diffs between two branches or commits in a GitLab project
168
+ 14. `update_merge_request` - Update a merge request (Either mergeRequestIid or branchName must be provided)
169
+ 15. `create_note` - Create a new note (comment) to an issue or merge request
170
+ 16. `create_merge_request_thread` - Create a new thread on a merge request
171
+ 17. `mr_discussions` - List discussion items for a merge request
172
+ 18. `update_merge_request_note` - Modify an existing merge request thread note
173
+ 19. `create_merge_request_note` - Add a new note to an existing merge request thread
174
+ 20. `update_issue_note` - Modify an existing issue thread note
175
+ 21. `create_issue_note` - Add a new note to an existing issue thread
176
+ 22. `list_issues` - List issues in a GitLab project with filtering options
177
+ 23. `get_issue` - Get details of a specific issue in a GitLab project
178
+ 24. `update_issue` - Update an issue in a GitLab project
179
+ 25. `delete_issue` - Delete an issue from a GitLab project
180
+ 26. `list_issue_links` - List all issue links for a specific issue
181
+ 27. `list_issue_discussions` - List discussions for an issue in a GitLab project
182
+ 28. `get_issue_link` - Get a specific issue link
183
+ 29. `create_issue_link` - Create an issue link between two issues
184
+ 30. `delete_issue_link` - Delete an issue link
185
+ 31. `list_namespaces` - List all namespaces available to the current user
186
+ 32. `get_namespace` - Get details of a namespace by ID or path
187
+ 33. `verify_namespace` - Verify if a namespace path exists
188
+ 34. `get_project` - Get details of a specific project
189
+ 35. `list_projects` - List projects accessible by the current user
190
+ 36. `list_labels` - List labels for a project
191
+ 37. `get_label` - Get a single label from a project
192
+ 38. `create_label` - Create a new label in a project
193
+ 39. `update_label` - Update an existing label in a project
194
+ 40. `delete_label` - Delete a label from a project
195
+ 41. `list_group_projects` - List projects in a GitLab group with filtering options
196
+ 42. `list_wiki_pages` - List wiki pages in a GitLab project
197
+ 43. `get_wiki_page` - Get details of a specific wiki page
198
+ 44. `create_wiki_page` - Create a new wiki page in a GitLab project
199
+ 45. `update_wiki_page` - Update an existing wiki page in a GitLab project
200
+ 46. `delete_wiki_page` - Delete a wiki page from a GitLab project
201
+ 47. `get_repository_tree` - Get the repository tree for a GitLab project (list files and directories)
202
+ 48. `list_pipelines` - List pipelines in a GitLab project with filtering options
203
+ 49. `get_pipeline` - Get details of a specific pipeline in a GitLab project
204
+ 50. `list_pipeline_jobs` - List all jobs in a specific pipeline
205
+ 51. `get_pipeline_job` - Get details of a GitLab pipeline job number
206
+ 52. `get_pipeline_job_output` - Get the output/trace of a GitLab pipeline job number
207
+ 53. `create_pipeline` - Create a new pipeline for a branch or tag
208
+ 54. `retry_pipeline` - Retry a failed or canceled pipeline
209
+ 55. `cancel_pipeline` - Cancel a running pipeline
210
+ 56. `list_merge_requests` - List merge requests in a GitLab project with filtering options
211
+ 57. `list_milestones` - List milestones in a GitLab project with filtering options
212
+ 58. `get_milestone` - Get details of a specific milestone
213
+ 59. `create_milestone` - Create a new milestone in a GitLab project
214
+ 60. `edit_milestone` - Edit an existing milestone in a GitLab project
215
+ 61. `delete_milestone` - Delete a milestone from a GitLab project
216
+ 62. `get_milestone_issue` - Get issues associated with a specific milestone
217
+ 63. `get_milestone_merge_requests` - Get merge requests associated with a specific milestone
218
+ 64. `promote_milestone` - Promote a milestone to the next stage
219
+ 65. `get_milestone_burndown_events` - Get burndown events for a specific milestone
220
+ 66. `get_users` - Get GitLab user details by usernames
184
221
  <!-- TOOLS-END -->
package/build/index.js CHANGED
@@ -27,7 +27,7 @@ GetPipelineJobOutputSchema, GitLabPipelineJobSchema,
27
27
  GitLabDiscussionNoteSchema, // Added
28
28
  GitLabDiscussionSchema, PaginatedDiscussionsResponseSchema, UpdateMergeRequestNoteSchema, // Added
29
29
  CreateMergeRequestNoteSchema, // Added
30
- ListMergeRequestDiscussionsSchema, UpdateIssueNoteSchema, CreateIssueNoteSchema, ListMergeRequestsSchema, GitLabMilestonesSchema, ListProjectMilestonesSchema, GetProjectMilestoneSchema, CreateProjectMilestoneSchema, EditProjectMilestoneSchema, DeleteProjectMilestoneSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, PromoteProjectMilestoneSchema, GetMilestoneBurndownEventsSchema, GitLabCompareResultSchema, GetBranchDiffsSchema, } from "./schemas.js";
30
+ ListMergeRequestDiscussionsSchema, UpdateIssueNoteSchema, CreateIssueNoteSchema, ListMergeRequestsSchema, GitLabMilestonesSchema, ListProjectMilestonesSchema, GetProjectMilestoneSchema, CreateProjectMilestoneSchema, EditProjectMilestoneSchema, DeleteProjectMilestoneSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, PromoteProjectMilestoneSchema, GetMilestoneBurndownEventsSchema, GitLabCompareResultSchema, GetBranchDiffsSchema, ListCommitsSchema, GetCommitSchema, GetCommitDiffSchema, ListMergeRequestDiffsSchema, } from "./schemas.js";
31
31
  /**
32
32
  * Read version from package.json
33
33
  */
@@ -242,6 +242,11 @@ const allTools = [
242
242
  description: "Get the changes/diffs of a merge request (Either mergeRequestIid or branchName must be provided)",
243
243
  inputSchema: zodToJsonSchema(GetMergeRequestDiffsSchema),
244
244
  },
245
+ {
246
+ name: "list_merge_request_diffs",
247
+ description: "List merge request diffs with pagination support (Either mergeRequestIid or branchName must be provided)",
248
+ inputSchema: zodToJsonSchema(ListMergeRequestDiffsSchema),
249
+ },
245
250
  {
246
251
  name: "get_branch_diffs",
247
252
  description: "Get the changes/diffs between two branches or commits in a GitLab project",
@@ -512,6 +517,21 @@ const allTools = [
512
517
  description: "Get GitLab user details by usernames",
513
518
  inputSchema: zodToJsonSchema(GetUsersSchema),
514
519
  },
520
+ {
521
+ name: "list_commits",
522
+ description: "List repository commits with filtering options",
523
+ inputSchema: zodToJsonSchema(ListCommitsSchema),
524
+ },
525
+ {
526
+ name: "get_commit",
527
+ description: "Get details of a specific commit",
528
+ inputSchema: zodToJsonSchema(GetCommitSchema),
529
+ },
530
+ {
531
+ name: "get_commit_diff",
532
+ description: "Get changes/diffs of a specific commit",
533
+ inputSchema: zodToJsonSchema(GetCommitDiffSchema),
534
+ },
515
535
  ];
516
536
  // Define which tools are read-only
517
537
  const readOnlyTools = [
@@ -549,6 +569,9 @@ const readOnlyTools = [
549
569
  "list_wiki_pages",
550
570
  "get_wiki_page",
551
571
  "get_users",
572
+ "list_commits",
573
+ "get_commit",
574
+ "get_commit_diff",
552
575
  ];
553
576
  // Define which tools are related to wiki and can be toggled by USE_GITLAB_WIKI
554
577
  const wikiToolNames = [
@@ -603,6 +626,7 @@ function normalizeGitLabApiUrl(url) {
603
626
  }
604
627
  // Use the normalizeGitLabApiUrl function to handle various URL formats
605
628
  const GITLAB_API_URL = normalizeGitLabApiUrl(process.env.GITLAB_API_URL || "");
629
+ const GITLAB_PROJECT_ID = process.env.GITLAB_PROJECT_ID;
606
630
  if (!GITLAB_PERSONAL_ACCESS_TOKEN) {
607
631
  console.error("GITLAB_PERSONAL_ACCESS_TOKEN environment variable is not set");
608
632
  process.exit(1);
@@ -629,6 +653,13 @@ async function handleGitLabError(response) {
629
653
  }
630
654
  }
631
655
  }
656
+ /**
657
+ * @param {string} projectId - The project ID parameter passed to the function
658
+ * @returns {string} The project ID to use for the API call
659
+ */
660
+ function getEffectiveProjectId(projectId) {
661
+ return GITLAB_PROJECT_ID || projectId;
662
+ }
632
663
  /**
633
664
  * Create a fork of a GitLab project
634
665
  * 프로젝트 포크 생성 (Create a project fork)
@@ -639,7 +670,8 @@ async function handleGitLabError(response) {
639
670
  */
640
671
  async function forkProject(projectId, namespace) {
641
672
  projectId = decodeURIComponent(projectId); // Decode project ID
642
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/fork`);
673
+ const effectiveProjectId = getEffectiveProjectId(projectId);
674
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/fork`);
643
675
  if (namespace) {
644
676
  url.searchParams.append("namespace", namespace);
645
677
  }
@@ -665,7 +697,8 @@ async function forkProject(projectId, namespace) {
665
697
  */
666
698
  async function createBranch(projectId, options) {
667
699
  projectId = decodeURIComponent(projectId); // Decode project ID
668
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/branches`);
700
+ const effectiveProjectId = getEffectiveProjectId(projectId);
701
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/repository/branches`);
669
702
  const response = await fetch(url.toString(), {
670
703
  ...DEFAULT_FETCH_CONFIG,
671
704
  method: "POST",
@@ -686,7 +719,8 @@ async function createBranch(projectId, options) {
686
719
  */
687
720
  async function getDefaultBranchRef(projectId) {
688
721
  projectId = decodeURIComponent(projectId); // Decode project ID
689
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}`);
722
+ const effectiveProjectId = getEffectiveProjectId(projectId);
723
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}`);
690
724
  const response = await fetch(url.toString(), {
691
725
  ...DEFAULT_FETCH_CONFIG,
692
726
  });
@@ -705,12 +739,13 @@ async function getDefaultBranchRef(projectId) {
705
739
  */
706
740
  async function getFileContents(projectId, filePath, ref) {
707
741
  projectId = decodeURIComponent(projectId); // Decode project ID
742
+ const effectiveProjectId = getEffectiveProjectId(projectId);
708
743
  const encodedPath = encodeURIComponent(filePath);
709
744
  // ref가 없는 경우 default branch를 가져옴
710
745
  if (!ref) {
711
746
  ref = await getDefaultBranchRef(projectId);
712
747
  }
713
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/files/${encodedPath}`);
748
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/repository/files/${encodedPath}`);
714
749
  url.searchParams.append("ref", ref);
715
750
  const response = await fetch(url.toString(), {
716
751
  ...DEFAULT_FETCH_CONFIG,
@@ -739,7 +774,8 @@ async function getFileContents(projectId, filePath, ref) {
739
774
  */
740
775
  async function createIssue(projectId, options) {
741
776
  projectId = decodeURIComponent(projectId); // Decode project ID
742
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues`);
777
+ const effectiveProjectId = getEffectiveProjectId(projectId);
778
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/issues`);
743
779
  const response = await fetch(url.toString(), {
744
780
  ...DEFAULT_FETCH_CONFIG,
745
781
  method: "POST",
@@ -770,7 +806,8 @@ async function createIssue(projectId, options) {
770
806
  */
771
807
  async function listIssues(projectId, options = {}) {
772
808
  projectId = decodeURIComponent(projectId); // Decode project ID
773
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues`);
809
+ const effectiveProjectId = getEffectiveProjectId(projectId);
810
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/issues`);
774
811
  // Add all query parameters
775
812
  Object.entries(options).forEach(([key, value]) => {
776
813
  if (value !== undefined) {
@@ -807,7 +844,7 @@ async function listIssues(projectId, options = {}) {
807
844
  */
808
845
  async function listMergeRequests(projectId, options = {}) {
809
846
  projectId = decodeURIComponent(projectId); // Decode project ID
810
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests`);
847
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests`);
811
848
  // Add all query parameters
812
849
  Object.entries(options).forEach(([key, value]) => {
813
850
  if (value !== undefined) {
@@ -837,7 +874,7 @@ async function listMergeRequests(projectId, options = {}) {
837
874
  */
838
875
  async function getIssue(projectId, issueIid) {
839
876
  projectId = decodeURIComponent(projectId); // Decode project ID
840
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}`);
877
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${issueIid}`);
841
878
  const response = await fetch(url.toString(), {
842
879
  ...DEFAULT_FETCH_CONFIG,
843
880
  });
@@ -856,7 +893,7 @@ async function getIssue(projectId, issueIid) {
856
893
  */
857
894
  async function updateIssue(projectId, issueIid, options) {
858
895
  projectId = decodeURIComponent(projectId); // Decode project ID
859
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}`);
896
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${issueIid}`);
860
897
  // Convert labels array to comma-separated string if present
861
898
  const body = { ...options };
862
899
  if (body.labels && Array.isArray(body.labels)) {
@@ -881,7 +918,7 @@ async function updateIssue(projectId, issueIid, options) {
881
918
  */
882
919
  async function deleteIssue(projectId, issueIid) {
883
920
  projectId = decodeURIComponent(projectId); // Decode project ID
884
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}`);
921
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${issueIid}`);
885
922
  const response = await fetch(url.toString(), {
886
923
  ...DEFAULT_FETCH_CONFIG,
887
924
  method: "DELETE",
@@ -898,7 +935,7 @@ async function deleteIssue(projectId, issueIid) {
898
935
  */
899
936
  async function listIssueLinks(projectId, issueIid) {
900
937
  projectId = decodeURIComponent(projectId); // Decode project ID
901
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/links`);
938
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${issueIid}/links`);
902
939
  const response = await fetch(url.toString(), {
903
940
  ...DEFAULT_FETCH_CONFIG,
904
941
  });
@@ -917,7 +954,7 @@ async function listIssueLinks(projectId, issueIid) {
917
954
  */
918
955
  async function getIssueLink(projectId, issueIid, issueLinkId) {
919
956
  projectId = decodeURIComponent(projectId); // Decode project ID
920
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/links/${issueLinkId}`);
957
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${issueIid}/links/${issueLinkId}`);
921
958
  const response = await fetch(url.toString(), {
922
959
  ...DEFAULT_FETCH_CONFIG,
923
960
  });
@@ -939,7 +976,7 @@ async function getIssueLink(projectId, issueIid, issueLinkId) {
939
976
  async function createIssueLink(projectId, issueIid, targetProjectId, targetIssueIid, linkType = "relates_to") {
940
977
  projectId = decodeURIComponent(projectId); // Decode project ID
941
978
  targetProjectId = decodeURIComponent(targetProjectId); // Decode target project ID as well
942
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/links`);
979
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${issueIid}/links`);
943
980
  const response = await fetch(url.toString(), {
944
981
  ...DEFAULT_FETCH_CONFIG,
945
982
  method: "POST",
@@ -964,7 +1001,7 @@ async function createIssueLink(projectId, issueIid, targetProjectId, targetIssue
964
1001
  */
965
1002
  async function deleteIssueLink(projectId, issueIid, issueLinkId) {
966
1003
  projectId = decodeURIComponent(projectId); // Decode project ID
967
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/links/${issueLinkId}`);
1004
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${issueIid}/links/${issueLinkId}`);
968
1005
  const response = await fetch(url.toString(), {
969
1006
  ...DEFAULT_FETCH_CONFIG,
970
1007
  method: "DELETE",
@@ -981,7 +1018,7 @@ async function deleteIssueLink(projectId, issueIid, issueLinkId) {
981
1018
  */
982
1019
  async function createMergeRequest(projectId, options) {
983
1020
  projectId = decodeURIComponent(projectId); // Decode project ID
984
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests`);
1021
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests`);
985
1022
  const response = await fetch(url.toString(), {
986
1023
  ...DEFAULT_FETCH_CONFIG,
987
1024
  method: "POST",
@@ -1022,7 +1059,7 @@ async function createMergeRequest(projectId, options) {
1022
1059
  */
1023
1060
  async function listDiscussions(projectId, resourceType, resourceIid, options = {}) {
1024
1061
  projectId = decodeURIComponent(projectId); // Decode project ID
1025
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/${resourceType}/${resourceIid}/discussions`);
1062
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/${resourceType}/${resourceIid}/discussions`);
1026
1063
  // Add query parameters for pagination and sorting
1027
1064
  if (options.page) {
1028
1065
  url.searchParams.append("page", options.page.toString());
@@ -1094,7 +1131,7 @@ async function listIssueDiscussions(projectId, issueIid, options = {}) {
1094
1131
  */
1095
1132
  async function updateMergeRequestNote(projectId, mergeRequestIid, discussionId, noteId, body, resolved) {
1096
1133
  projectId = decodeURIComponent(projectId); // Decode project ID
1097
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}/discussions/${discussionId}/notes/${noteId}`);
1134
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}/discussions/${discussionId}/notes/${noteId}`);
1098
1135
  // Only one of body or resolved can be sent according to GitLab API
1099
1136
  const payload = {};
1100
1137
  if (body !== undefined) {
@@ -1123,7 +1160,7 @@ async function updateMergeRequestNote(projectId, mergeRequestIid, discussionId,
1123
1160
  */
1124
1161
  async function updateIssueNote(projectId, issueIid, discussionId, noteId, body) {
1125
1162
  projectId = decodeURIComponent(projectId); // Decode project ID
1126
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/discussions/${discussionId}/notes/${noteId}`);
1163
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${issueIid}/discussions/${discussionId}/notes/${noteId}`);
1127
1164
  const payload = { body };
1128
1165
  const response = await fetch(url.toString(), {
1129
1166
  ...DEFAULT_FETCH_CONFIG,
@@ -1145,7 +1182,7 @@ async function updateIssueNote(projectId, issueIid, discussionId, noteId, body)
1145
1182
  */
1146
1183
  async function createIssueNote(projectId, issueIid, discussionId, body, createdAt) {
1147
1184
  projectId = decodeURIComponent(projectId); // Decode project ID
1148
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/discussions/${discussionId}/notes`);
1185
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${issueIid}/discussions/${discussionId}/notes`);
1149
1186
  const payload = { body };
1150
1187
  if (createdAt) {
1151
1188
  payload.created_at = createdAt;
@@ -1172,7 +1209,7 @@ async function createIssueNote(projectId, issueIid, discussionId, body, createdA
1172
1209
  */
1173
1210
  async function createMergeRequestNote(projectId, mergeRequestIid, discussionId, body, createdAt) {
1174
1211
  projectId = decodeURIComponent(projectId); // Decode project ID
1175
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}/discussions/${discussionId}/notes`);
1212
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}/discussions/${discussionId}/notes`);
1176
1213
  const payload = { body };
1177
1214
  if (createdAt) {
1178
1215
  payload.created_at = createdAt;
@@ -1201,7 +1238,7 @@ async function createMergeRequestNote(projectId, mergeRequestIid, discussionId,
1201
1238
  async function createOrUpdateFile(projectId, filePath, content, commitMessage, branch, previousPath, last_commit_id, commit_id) {
1202
1239
  projectId = decodeURIComponent(projectId); // Decode project ID
1203
1240
  const encodedPath = encodeURIComponent(filePath);
1204
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/files/${encodedPath}`);
1241
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/files/${encodedPath}`);
1205
1242
  const body = {
1206
1243
  branch,
1207
1244
  content,
@@ -1268,7 +1305,7 @@ async function createOrUpdateFile(projectId, filePath, content, commitMessage, b
1268
1305
  */
1269
1306
  async function createTree(projectId, files, ref) {
1270
1307
  projectId = decodeURIComponent(projectId); // Decode project ID
1271
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/tree`);
1308
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/tree`);
1272
1309
  if (ref) {
1273
1310
  url.searchParams.append("ref", ref);
1274
1311
  }
@@ -1306,7 +1343,7 @@ async function createTree(projectId, files, ref) {
1306
1343
  */
1307
1344
  async function createCommit(projectId, message, branch, actions) {
1308
1345
  projectId = decodeURIComponent(projectId); // Decode project ID
1309
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/commits`);
1346
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/commits`);
1310
1347
  const response = await fetch(url.toString(), {
1311
1348
  ...DEFAULT_FETCH_CONFIG,
1312
1349
  method: "POST",
@@ -1407,10 +1444,10 @@ async function getMergeRequest(projectId, mergeRequestIid, branchName) {
1407
1444
  projectId = decodeURIComponent(projectId); // Decode project ID
1408
1445
  let url;
1409
1446
  if (mergeRequestIid) {
1410
- url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}`);
1447
+ url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}`);
1411
1448
  }
1412
1449
  else if (branchName) {
1413
- url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests?source_branch=${encodeURIComponent(branchName)}`);
1450
+ url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests?source_branch=${encodeURIComponent(branchName)}`);
1414
1451
  }
1415
1452
  else {
1416
1453
  throw new Error("Either mergeRequestIid or branchName must be provided");
@@ -1445,7 +1482,7 @@ async function getMergeRequestDiffs(projectId, mergeRequestIid, branchName, view
1445
1482
  const mergeRequest = await getMergeRequest(projectId, undefined, branchName);
1446
1483
  mergeRequestIid = mergeRequest.iid;
1447
1484
  }
1448
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}/changes`);
1485
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}/changes`);
1449
1486
  if (view) {
1450
1487
  url.searchParams.append("view", view);
1451
1488
  }
@@ -1456,6 +1493,41 @@ async function getMergeRequestDiffs(projectId, mergeRequestIid, branchName, view
1456
1493
  const data = (await response.json());
1457
1494
  return z.array(GitLabDiffSchema).parse(data.changes);
1458
1495
  }
1496
+ /**
1497
+ * Get merge request changes with detailed information including commits, diff_refs, and more
1498
+ * 마지막으로 추가된 상세한 MR 변경사항 조회 함수 (Detailed merge request changes retrieval function)
1499
+ *
1500
+ * @param {string} projectId - The ID or URL-encoded path of the project
1501
+ * @param {number} mergeRequestIid - The internal ID of the merge request (Either mergeRequestIid or branchName must be provided)
1502
+ * @param {string} [branchName] - The name of the branch to search for merge request by branch name (Either mergeRequestIid or branchName must be provided)
1503
+ * @param {boolean} [unidiff] - Return diff in unidiff format
1504
+ * @returns {Promise<any>} The complete merge request changes response
1505
+ */
1506
+ async function listMergeRequestDiffs(projectId, mergeRequestIid, branchName, page, perPage, unidiff) {
1507
+ projectId = decodeURIComponent(projectId); // Decode project ID
1508
+ if (!mergeRequestIid && !branchName) {
1509
+ throw new Error("Either mergeRequestIid or branchName must be provided");
1510
+ }
1511
+ if (branchName && !mergeRequestIid) {
1512
+ const mergeRequest = await getMergeRequest(projectId, undefined, branchName);
1513
+ mergeRequestIid = mergeRequest.iid;
1514
+ }
1515
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}/diffs`);
1516
+ if (page) {
1517
+ url.searchParams.append("page", page.toString());
1518
+ }
1519
+ if (perPage) {
1520
+ url.searchParams.append("per_page", perPage.toString());
1521
+ }
1522
+ if (unidiff) {
1523
+ url.searchParams.append("unidiff", "true");
1524
+ }
1525
+ const response = await fetch(url.toString(), {
1526
+ ...DEFAULT_FETCH_CONFIG,
1527
+ });
1528
+ await handleGitLabError(response);
1529
+ return await response.json(); // Return full response including commits, diff_refs, changes, etc.
1530
+ }
1459
1531
  /**
1460
1532
  * Get branch comparison diffs
1461
1533
  *
@@ -1467,7 +1539,7 @@ async function getMergeRequestDiffs(projectId, mergeRequestIid, branchName, view
1467
1539
  */
1468
1540
  async function getBranchDiffs(projectId, from, to, straight) {
1469
1541
  projectId = decodeURIComponent(projectId); // Decode project ID
1470
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/compare`);
1542
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/compare`);
1471
1543
  url.searchParams.append("from", from);
1472
1544
  url.searchParams.append("to", to);
1473
1545
  if (straight !== undefined) {
@@ -1502,7 +1574,7 @@ async function updateMergeRequest(projectId, options, mergeRequestIid, branchNam
1502
1574
  const mergeRequest = await getMergeRequest(projectId, undefined, branchName);
1503
1575
  mergeRequestIid = mergeRequest.iid;
1504
1576
  }
1505
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}`);
1577
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}`);
1506
1578
  const response = await fetch(url.toString(), {
1507
1579
  ...DEFAULT_FETCH_CONFIG,
1508
1580
  method: "PUT",
@@ -1526,7 +1598,7 @@ async function createNote(projectId, noteableType, // 'issue' 또는 'merge_requ
1526
1598
  noteableIid, body) {
1527
1599
  projectId = decodeURIComponent(projectId); // Decode project ID
1528
1600
  // ⚙️ 응답 타입은 GitLab API 문서에 따라 조정 가능
1529
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/${noteableType}s/${noteableIid}/notes` // Using plural form (issues/merge_requests) as per GitLab API documentation
1601
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/${noteableType}s/${noteableIid}/notes` // Using plural form (issues/merge_requests) as per GitLab API documentation
1530
1602
  );
1531
1603
  const response = await fetch(url.toString(), {
1532
1604
  ...DEFAULT_FETCH_CONFIG,
@@ -1558,7 +1630,7 @@ noteableIid, body) {
1558
1630
  */
1559
1631
  async function createMergeRequestThread(projectId, mergeRequestIid, body, position, createdAt) {
1560
1632
  projectId = decodeURIComponent(projectId); // Decode project ID
1561
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}/discussions`);
1633
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}/discussions`);
1562
1634
  const payload = { body };
1563
1635
  // Add optional parameters if provided
1564
1636
  if (position) {
@@ -1653,7 +1725,7 @@ async function verifyNamespaceExistence(namespacePath, parentId) {
1653
1725
  */
1654
1726
  async function getProject(projectId, options = {}) {
1655
1727
  projectId = decodeURIComponent(projectId); // Decode project ID
1656
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}`);
1728
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}`);
1657
1729
  if (options.license) {
1658
1730
  url.searchParams.append("license", "true");
1659
1731
  }
@@ -1710,7 +1782,7 @@ async function listProjects(options = {}) {
1710
1782
  async function listLabels(projectId, options = {}) {
1711
1783
  projectId = decodeURIComponent(projectId); // Decode project ID
1712
1784
  // Construct the URL with project path
1713
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels`);
1785
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/labels`);
1714
1786
  // Add query parameters
1715
1787
  Object.entries(options).forEach(([key, value]) => {
1716
1788
  if (value !== undefined) {
@@ -1742,7 +1814,7 @@ async function listLabels(projectId, options = {}) {
1742
1814
  */
1743
1815
  async function getLabel(projectId, labelId, includeAncestorGroups) {
1744
1816
  projectId = decodeURIComponent(projectId); // Decode project ID
1745
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels/${encodeURIComponent(String(labelId))}`);
1817
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/labels/${encodeURIComponent(String(labelId))}`);
1746
1818
  // Add query parameters
1747
1819
  if (includeAncestorGroups !== undefined) {
1748
1820
  url.searchParams.append("include_ancestor_groups", includeAncestorGroups ? "true" : "false");
@@ -1767,7 +1839,7 @@ async function getLabel(projectId, labelId, includeAncestorGroups) {
1767
1839
  async function createLabel(projectId, options) {
1768
1840
  projectId = decodeURIComponent(projectId); // Decode project ID
1769
1841
  // Make the API request
1770
- const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels`, {
1842
+ const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/labels`, {
1771
1843
  ...DEFAULT_FETCH_CONFIG,
1772
1844
  method: "POST",
1773
1845
  body: JSON.stringify(options),
@@ -1789,7 +1861,7 @@ async function createLabel(projectId, options) {
1789
1861
  async function updateLabel(projectId, labelId, options) {
1790
1862
  projectId = decodeURIComponent(projectId); // Decode project ID
1791
1863
  // Make the API request
1792
- const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels/${encodeURIComponent(String(labelId))}`, {
1864
+ const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/labels/${encodeURIComponent(String(labelId))}`, {
1793
1865
  ...DEFAULT_FETCH_CONFIG,
1794
1866
  method: "PUT",
1795
1867
  body: JSON.stringify(options),
@@ -1809,7 +1881,7 @@ async function updateLabel(projectId, labelId, options) {
1809
1881
  async function deleteLabel(projectId, labelId) {
1810
1882
  projectId = decodeURIComponent(projectId); // Decode project ID
1811
1883
  // Make the API request
1812
- const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels/${encodeURIComponent(String(labelId))}`, {
1884
+ const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/labels/${encodeURIComponent(String(labelId))}`, {
1813
1885
  ...DEFAULT_FETCH_CONFIG,
1814
1886
  method: "DELETE",
1815
1887
  });
@@ -1870,7 +1942,7 @@ async function listGroupProjects(options) {
1870
1942
  */
1871
1943
  async function listWikiPages(projectId, options = {}) {
1872
1944
  projectId = decodeURIComponent(projectId); // Decode project ID
1873
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis`);
1945
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/wikis`);
1874
1946
  if (options.page)
1875
1947
  url.searchParams.append("page", options.page.toString());
1876
1948
  if (options.per_page)
@@ -1889,7 +1961,7 @@ async function listWikiPages(projectId, options = {}) {
1889
1961
  */
1890
1962
  async function getWikiPage(projectId, slug) {
1891
1963
  projectId = decodeURIComponent(projectId); // Decode project ID
1892
- const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis/${encodeURIComponent(slug)}`, { ...DEFAULT_FETCH_CONFIG });
1964
+ const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/wikis/${encodeURIComponent(slug)}`, { ...DEFAULT_FETCH_CONFIG });
1893
1965
  await handleGitLabError(response);
1894
1966
  const data = await response.json();
1895
1967
  return GitLabWikiPageSchema.parse(data);
@@ -1902,7 +1974,7 @@ async function createWikiPage(projectId, title, content, format) {
1902
1974
  const body = { title, content };
1903
1975
  if (format)
1904
1976
  body.format = format;
1905
- const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis`, {
1977
+ const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/wikis`, {
1906
1978
  ...DEFAULT_FETCH_CONFIG,
1907
1979
  method: "POST",
1908
1980
  body: JSON.stringify(body),
@@ -1923,7 +1995,7 @@ async function updateWikiPage(projectId, slug, title, content, format) {
1923
1995
  body.content = content;
1924
1996
  if (format)
1925
1997
  body.format = format;
1926
- const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis/${encodeURIComponent(slug)}`, {
1998
+ const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/wikis/${encodeURIComponent(slug)}`, {
1927
1999
  ...DEFAULT_FETCH_CONFIG,
1928
2000
  method: "PUT",
1929
2001
  body: JSON.stringify(body),
@@ -1937,7 +2009,7 @@ async function updateWikiPage(projectId, slug, title, content, format) {
1937
2009
  */
1938
2010
  async function deleteWikiPage(projectId, slug) {
1939
2011
  projectId = decodeURIComponent(projectId); // Decode project ID
1940
- const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis/${encodeURIComponent(slug)}`, {
2012
+ const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/wikis/${encodeURIComponent(slug)}`, {
1941
2013
  ...DEFAULT_FETCH_CONFIG,
1942
2014
  method: "DELETE",
1943
2015
  });
@@ -1952,7 +2024,7 @@ async function deleteWikiPage(projectId, slug) {
1952
2024
  */
1953
2025
  async function listPipelines(projectId, options = {}) {
1954
2026
  projectId = decodeURIComponent(projectId); // Decode project ID
1955
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/pipelines`);
2027
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines`);
1956
2028
  // Add all query parameters
1957
2029
  Object.entries(options).forEach(([key, value]) => {
1958
2030
  if (value !== undefined) {
@@ -1975,7 +2047,7 @@ async function listPipelines(projectId, options = {}) {
1975
2047
  */
1976
2048
  async function getPipeline(projectId, pipelineId) {
1977
2049
  projectId = decodeURIComponent(projectId); // Decode project ID
1978
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}`);
2050
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${pipelineId}`);
1979
2051
  const response = await fetch(url.toString(), {
1980
2052
  ...DEFAULT_FETCH_CONFIG,
1981
2053
  });
@@ -1996,7 +2068,7 @@ async function getPipeline(projectId, pipelineId) {
1996
2068
  */
1997
2069
  async function listPipelineJobs(projectId, pipelineId, options = {}) {
1998
2070
  projectId = decodeURIComponent(projectId); // Decode project ID
1999
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}/jobs`);
2071
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${pipelineId}/jobs`);
2000
2072
  // Add all query parameters
2001
2073
  Object.entries(options).forEach(([key, value]) => {
2002
2074
  if (value !== undefined) {
@@ -2020,7 +2092,7 @@ async function listPipelineJobs(projectId, pipelineId, options = {}) {
2020
2092
  }
2021
2093
  async function getPipelineJob(projectId, jobId) {
2022
2094
  projectId = decodeURIComponent(projectId); // Decode project ID
2023
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/jobs/${jobId}`);
2095
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/jobs/${jobId}`);
2024
2096
  const response = await fetch(url.toString(), {
2025
2097
  ...DEFAULT_FETCH_CONFIG,
2026
2098
  });
@@ -2042,7 +2114,7 @@ async function getPipelineJob(projectId, jobId) {
2042
2114
  */
2043
2115
  async function getPipelineJobOutput(projectId, jobId, limit, offset) {
2044
2116
  projectId = decodeURIComponent(projectId); // Decode project ID
2045
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/jobs/${jobId}/trace`);
2117
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/jobs/${jobId}/trace`);
2046
2118
  const response = await fetch(url.toString(), {
2047
2119
  ...DEFAULT_FETCH_CONFIG,
2048
2120
  headers: {
@@ -2087,7 +2159,7 @@ async function getPipelineJobOutput(projectId, jobId, limit, offset) {
2087
2159
  */
2088
2160
  async function createPipeline(projectId, ref, variables) {
2089
2161
  projectId = decodeURIComponent(projectId); // Decode project ID
2090
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/pipeline`);
2162
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipeline`);
2091
2163
  const body = { ref };
2092
2164
  if (variables && variables.length > 0) {
2093
2165
  body.variables = variables.reduce((acc, { key, value }) => {
@@ -2113,7 +2185,7 @@ async function createPipeline(projectId, ref, variables) {
2113
2185
  */
2114
2186
  async function retryPipeline(projectId, pipelineId) {
2115
2187
  projectId = decodeURIComponent(projectId); // Decode project ID
2116
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}/retry`);
2188
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${pipelineId}/retry`);
2117
2189
  const response = await fetch(url.toString(), {
2118
2190
  method: "POST",
2119
2191
  headers: DEFAULT_HEADERS,
@@ -2131,7 +2203,7 @@ async function retryPipeline(projectId, pipelineId) {
2131
2203
  */
2132
2204
  async function cancelPipeline(projectId, pipelineId) {
2133
2205
  projectId = decodeURIComponent(projectId); // Decode project ID
2134
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}/cancel`);
2206
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${pipelineId}/cancel`);
2135
2207
  const response = await fetch(url.toString(), {
2136
2208
  method: "POST",
2137
2209
  headers: DEFAULT_HEADERS,
@@ -2190,7 +2262,7 @@ async function getRepositoryTree(options) {
2190
2262
  */
2191
2263
  async function listProjectMilestones(projectId, options) {
2192
2264
  projectId = decodeURIComponent(projectId);
2193
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones`);
2265
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones`);
2194
2266
  Object.entries(options).forEach(([key, value]) => {
2195
2267
  if (value !== undefined) {
2196
2268
  if (key === "iids" && Array.isArray(value) && value.length > 0) {
@@ -2218,7 +2290,7 @@ async function listProjectMilestones(projectId, options) {
2218
2290
  */
2219
2291
  async function getProjectMilestone(projectId, milestoneId) {
2220
2292
  projectId = decodeURIComponent(projectId);
2221
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones/${milestoneId}`);
2293
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${milestoneId}`);
2222
2294
  const response = await fetch(url.toString(), {
2223
2295
  ...DEFAULT_FETCH_CONFIG,
2224
2296
  });
@@ -2234,7 +2306,7 @@ async function getProjectMilestone(projectId, milestoneId) {
2234
2306
  */
2235
2307
  async function createProjectMilestone(projectId, options) {
2236
2308
  projectId = decodeURIComponent(projectId);
2237
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones`);
2309
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones`);
2238
2310
  const response = await fetch(url.toString(), {
2239
2311
  ...DEFAULT_FETCH_CONFIG,
2240
2312
  method: "POST",
@@ -2253,7 +2325,7 @@ async function createProjectMilestone(projectId, options) {
2253
2325
  */
2254
2326
  async function editProjectMilestone(projectId, milestoneId, options) {
2255
2327
  projectId = decodeURIComponent(projectId);
2256
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones/${milestoneId}`);
2328
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${milestoneId}`);
2257
2329
  const response = await fetch(url.toString(), {
2258
2330
  ...DEFAULT_FETCH_CONFIG,
2259
2331
  method: "PUT",
@@ -2271,7 +2343,7 @@ async function editProjectMilestone(projectId, milestoneId, options) {
2271
2343
  */
2272
2344
  async function deleteProjectMilestone(projectId, milestoneId) {
2273
2345
  projectId = decodeURIComponent(projectId);
2274
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones/${milestoneId}`);
2346
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${milestoneId}`);
2275
2347
  const response = await fetch(url.toString(), {
2276
2348
  ...DEFAULT_FETCH_CONFIG,
2277
2349
  method: "DELETE",
@@ -2286,7 +2358,7 @@ async function deleteProjectMilestone(projectId, milestoneId) {
2286
2358
  */
2287
2359
  async function getMilestoneIssues(projectId, milestoneId) {
2288
2360
  projectId = decodeURIComponent(projectId);
2289
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones/${milestoneId}/issues`);
2361
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${milestoneId}/issues`);
2290
2362
  const response = await fetch(url.toString(), {
2291
2363
  ...DEFAULT_FETCH_CONFIG,
2292
2364
  });
@@ -2302,7 +2374,7 @@ async function getMilestoneIssues(projectId, milestoneId) {
2302
2374
  */
2303
2375
  async function getMilestoneMergeRequests(projectId, milestoneId) {
2304
2376
  projectId = decodeURIComponent(projectId);
2305
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones/${milestoneId}/merge_requests`);
2377
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${milestoneId}/merge_requests`);
2306
2378
  const response = await fetch(url.toString(), {
2307
2379
  ...DEFAULT_FETCH_CONFIG,
2308
2380
  });
@@ -2318,7 +2390,7 @@ async function getMilestoneMergeRequests(projectId, milestoneId) {
2318
2390
  */
2319
2391
  async function promoteProjectMilestone(projectId, milestoneId) {
2320
2392
  projectId = decodeURIComponent(projectId);
2321
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones/${milestoneId}/promote`);
2393
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${milestoneId}/promote`);
2322
2394
  const response = await fetch(url.toString(), {
2323
2395
  ...DEFAULT_FETCH_CONFIG,
2324
2396
  method: "POST",
@@ -2335,7 +2407,7 @@ async function promoteProjectMilestone(projectId, milestoneId) {
2335
2407
  */
2336
2408
  async function getMilestoneBurndownEvents(projectId, milestoneId) {
2337
2409
  projectId = decodeURIComponent(projectId);
2338
- const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones/${milestoneId}/burndown_events`);
2410
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${milestoneId}/burndown_events`);
2339
2411
  const response = await fetch(url.toString(), {
2340
2412
  ...DEFAULT_FETCH_CONFIG,
2341
2413
  });
@@ -2395,6 +2467,89 @@ async function getUsers(usernames) {
2395
2467
  }
2396
2468
  return GitLabUsersResponseSchema.parse(users);
2397
2469
  }
2470
+ /**
2471
+ * List repository commits
2472
+ * 저장소 커밋 목록 조회
2473
+ *
2474
+ * @param {string} projectId - Project ID or URL-encoded path
2475
+ * @param {ListCommitsOptions} options - List commits options
2476
+ * @returns {Promise<GitLabCommit[]>} List of commits
2477
+ */
2478
+ async function listCommits(projectId, options = {}) {
2479
+ projectId = decodeURIComponent(projectId);
2480
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/commits`);
2481
+ // Add query parameters
2482
+ if (options.ref_name)
2483
+ url.searchParams.append("ref_name", options.ref_name);
2484
+ if (options.since)
2485
+ url.searchParams.append("since", options.since);
2486
+ if (options.until)
2487
+ url.searchParams.append("until", options.until);
2488
+ if (options.path)
2489
+ url.searchParams.append("path", options.path);
2490
+ if (options.author)
2491
+ url.searchParams.append("author", options.author);
2492
+ if (options.all)
2493
+ url.searchParams.append("all", options.all.toString());
2494
+ if (options.with_stats)
2495
+ url.searchParams.append("with_stats", options.with_stats.toString());
2496
+ if (options.first_parent)
2497
+ url.searchParams.append("first_parent", options.first_parent.toString());
2498
+ if (options.order)
2499
+ url.searchParams.append("order", options.order);
2500
+ if (options.trailers)
2501
+ url.searchParams.append("trailers", options.trailers.toString());
2502
+ if (options.page)
2503
+ url.searchParams.append("page", options.page.toString());
2504
+ if (options.per_page)
2505
+ url.searchParams.append("per_page", options.per_page.toString());
2506
+ const response = await fetch(url.toString(), {
2507
+ ...DEFAULT_FETCH_CONFIG,
2508
+ });
2509
+ await handleGitLabError(response);
2510
+ const data = await response.json();
2511
+ return z.array(GitLabCommitSchema).parse(data);
2512
+ }
2513
+ /**
2514
+ * Get a single commit
2515
+ * 단일 커밋 정보 조회
2516
+ *
2517
+ * @param {string} projectId - Project ID or URL-encoded path
2518
+ * @param {string} sha - The commit hash or name of a repository branch or tag
2519
+ * @param {boolean} [stats] - Include commit stats
2520
+ * @returns {Promise<GitLabCommit>} The commit details
2521
+ */
2522
+ async function getCommit(projectId, sha, stats) {
2523
+ projectId = decodeURIComponent(projectId);
2524
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/commits/${encodeURIComponent(sha)}`);
2525
+ if (stats) {
2526
+ url.searchParams.append("stats", "true");
2527
+ }
2528
+ const response = await fetch(url.toString(), {
2529
+ ...DEFAULT_FETCH_CONFIG,
2530
+ });
2531
+ await handleGitLabError(response);
2532
+ const data = await response.json();
2533
+ return GitLabCommitSchema.parse(data);
2534
+ }
2535
+ /**
2536
+ * Get commit diff
2537
+ * 커밋 변경사항 조회
2538
+ *
2539
+ * @param {string} projectId - Project ID or URL-encoded path
2540
+ * @param {string} sha - The commit hash or name of a repository branch or tag
2541
+ * @returns {Promise<GitLabMergeRequestDiff[]>} The commit diffs
2542
+ */
2543
+ async function getCommitDiff(projectId, sha) {
2544
+ projectId = decodeURIComponent(projectId);
2545
+ const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/commits/${encodeURIComponent(sha)}/diff`);
2546
+ const response = await fetch(url.toString(), {
2547
+ ...DEFAULT_FETCH_CONFIG,
2548
+ });
2549
+ await handleGitLabError(response);
2550
+ const data = await response.json();
2551
+ return z.array(GitLabDiffSchema).parse(data);
2552
+ }
2398
2553
  server.setRequestHandler(ListToolsRequestSchema, async () => {
2399
2554
  // Apply read-only filter first
2400
2555
  const tools0 = GITLAB_READ_ONLY_MODE
@@ -2591,6 +2746,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2591
2746
  content: [{ type: "text", text: JSON.stringify(diffs, null, 2) }],
2592
2747
  };
2593
2748
  }
2749
+ case "list_merge_request_diffs": {
2750
+ const args = ListMergeRequestDiffsSchema.parse(request.params.arguments);
2751
+ const changes = await listMergeRequestDiffs(args.project_id, args.merge_request_iid, args.source_branch, args.page, args.per_page, args.unidiff);
2752
+ return {
2753
+ content: [{ type: "text", text: JSON.stringify(changes, null, 2) }],
2754
+ };
2755
+ }
2594
2756
  case "update_merge_request": {
2595
2757
  const args = UpdateMergeRequestSchema.parse(request.params.arguments);
2596
2758
  const { project_id, merge_request_iid, source_branch, ...options } = args;
@@ -3088,6 +3250,27 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3088
3250
  ],
3089
3251
  };
3090
3252
  }
3253
+ case "list_commits": {
3254
+ const args = ListCommitsSchema.parse(request.params.arguments);
3255
+ const commits = await listCommits(args.project_id, args);
3256
+ return {
3257
+ content: [{ type: "text", text: JSON.stringify(commits, null, 2) }],
3258
+ };
3259
+ }
3260
+ case "get_commit": {
3261
+ const args = GetCommitSchema.parse(request.params.arguments);
3262
+ const commit = await getCommit(args.project_id, args.sha, args.stats);
3263
+ return {
3264
+ content: [{ type: "text", text: JSON.stringify(commit, null, 2) }],
3265
+ };
3266
+ }
3267
+ case "get_commit_diff": {
3268
+ const args = GetCommitDiffSchema.parse(request.params.arguments);
3269
+ const diff = await getCommitDiff(args.project_id, args.sha);
3270
+ return {
3271
+ content: [{ type: "text", text: JSON.stringify(diff, null, 2) }],
3272
+ };
3273
+ }
3091
3274
  default:
3092
3275
  throw new Error(`Unknown tool: ${request.params.name}`);
3093
3276
  }
@@ -3136,6 +3319,12 @@ async function runServer() {
3136
3319
  res.status(400).send("No transport found for sessionId");
3137
3320
  }
3138
3321
  });
3322
+ app.get("/health", (_, res) => {
3323
+ res.status(200).json({
3324
+ status: "healthy",
3325
+ version: process.env.npm_package_version || "unknown",
3326
+ });
3327
+ });
3139
3328
  const PORT = process.env.PORT || 3002;
3140
3329
  app.listen(PORT, () => {
3141
3330
  console.log(`Server is running on port ${PORT}`);
package/build/schemas.js CHANGED
@@ -374,8 +374,17 @@ export const GitLabCommitSchema = z.object({
374
374
  committer_name: z.string(),
375
375
  committer_email: z.string(),
376
376
  committed_date: z.string(),
377
+ created_at: z.string().optional(), // Add created_at field
378
+ message: z.string().optional(), // Add full message field
377
379
  web_url: z.string(), // Changed from html_url to match GitLab API
378
380
  parent_ids: z.array(z.string()), // Changed from parents to match GitLab API
381
+ stats: z.object({
382
+ additions: z.number().optional().nullable(),
383
+ deletions: z.number().optional().nullable(),
384
+ total: z.number().optional().nullable(),
385
+ }).optional(), // Only present when with_stats=true
386
+ trailers: z.record(z.string()).optional().default({}), // Git trailers, may be empty object
387
+ extended_trailers: z.record(z.array(z.string())).optional().default({}), // Extended trailers, may be empty object
379
388
  });
380
389
  // Reference schema
381
390
  export const GitLabReferenceSchema = z.object({
@@ -596,6 +605,20 @@ export const GitLabMergeRequestSchema = z.object({
596
605
  squash: z.boolean().optional(),
597
606
  labels: z.array(z.string()).optional(),
598
607
  });
608
+ export const LineRangeSchema = z.object({
609
+ start: z.object({
610
+ line_code: z.string().nullable().optional().describe("CRITICAL: Line identifier in format '{file_path_sha1_hash}_{old_line_number}_{new_line_number}'. USUALLY REQUIRED for GitLab diff comments despite being optional in schema. Example: 'a1b2c3d4e5f6_10_15'. Get this from GitLab diff API response, never fabricate."),
611
+ type: z.enum(["new", "old", "expanded"]).nullable().optional().describe("Line type: 'old' = deleted/original line, 'new' = added/modified line, null = unchanged context. MUST match the line_code format and old_line/new_line values."),
612
+ old_line: z.number().nullable().optional().describe("Line number in original file (before changes). REQUIRED when type='old', NULL when type='new' (for purely added lines), can be present for context lines."),
613
+ new_line: z.number().nullable().optional().describe("Line number in modified file (after changes). REQUIRED when type='new', NULL when type='old' (for purely deleted lines), can be present for context lines."),
614
+ }).describe("Start line position for multiline comment range. MUST specify either old_line OR new_line (or both for context), never neither."),
615
+ end: z.object({
616
+ line_code: z.string().nullable().optional().describe("CRITICAL: Line identifier in format '{file_path_sha1_hash}_{old_line_number}_{new_line_number}'. USUALLY REQUIRED for GitLab diff comments despite being optional in schema. Example: 'a1b2c3d4e5f6_12_17'. Must be from same file as start.line_code."),
617
+ type: z.enum(["new", "old", "expanded"]).nullable().optional().describe("Line type: 'old' = deleted/original line, 'new' = added/modified line, null = unchanged context. SHOULD MATCH start.type for consistent ranges (don't mix old/new types)."),
618
+ old_line: z.number().nullable().optional().describe("Line number in original file (before changes). REQUIRED when type='old', NULL when type='new' (for purely added lines), can be present for context lines. MUST be >= start.old_line if both specified."),
619
+ new_line: z.number().nullable().optional().describe("Line number in modified file (after changes). REQUIRED when type='new', NULL when type='old' (for purely deleted lines), can be present for context lines. MUST be >= start.new_line if both specified."),
620
+ }).describe("End line position for multiline comment range. MUST specify either old_line OR new_line (or both for context), never neither. Range must be valid (end >= start)."),
621
+ }).describe("Line range for multiline comments on GitLab merge request diffs. VALIDATION RULES: 1) line_code is critical for GitLab API success, 2) start/end must have consistent types, 3) line numbers must form valid range, 4) get line_code from GitLab diff API, never generate manually.");
599
622
  // Discussion related schemas
600
623
  export const GitLabDiscussionNoteSchema = z.object({
601
624
  id: z.number(),
@@ -620,28 +643,12 @@ export const GitLabDiscussionNoteSchema = z.object({
620
643
  base_sha: z.string(),
621
644
  start_sha: z.string(),
622
645
  head_sha: z.string(),
623
- old_path: z.string(),
624
- new_path: z.string(),
646
+ old_path: z.string().nullable().optional().describe("File path before change"),
647
+ new_path: z.string().nullable().optional().describe("File path after change"),
625
648
  position_type: z.enum(["text", "image", "file"]),
626
- old_line: z.number().nullish(), // This is missing for image diffs
627
- new_line: z.number().nullish(), // This is missing for image diffs
628
- line_range: z
629
- .object({
630
- start: z.object({
631
- line_code: z.string(),
632
- type: z.enum(["new", "old", "expanded"]),
633
- old_line: z.number().nullish(), // This is missing for image diffs
634
- new_line: z.number().nullish(), // This is missing for image diffs
635
- }),
636
- end: z.object({
637
- line_code: z.string(),
638
- type: z.enum(["new", "old", "expanded"]),
639
- old_line: z.number().nullish(), // This is missing for image diffs
640
- new_line: z.number().nullish(), // This is missing for image diffs
641
- }),
642
- })
643
- .nullable()
644
- .optional(), // For multi-line diff notes
649
+ new_line: z.number().nullable().optional().describe("Line number in the modified file (after changes). Used for added lines and context lines. Null for deleted lines."),
650
+ old_line: z.number().nullable().optional().describe("Line number in the original file (before changes). Used for deleted lines and context lines. Null for newly added lines."),
651
+ line_range: LineRangeSchema.nullable().optional(), // For multi-line diff notes
645
652
  width: z.number().optional(), // For image diff notes
646
653
  height: z.number().optional(), // For image diff notes
647
654
  x: z.number().optional(), // For image diff notes
@@ -818,6 +825,11 @@ export const UpdateMergeRequestSchema = GetMergeRequestSchema.extend({
818
825
  export const GetMergeRequestDiffsSchema = GetMergeRequestSchema.extend({
819
826
  view: z.enum(["inline", "parallel"]).optional().describe("Diff view type"),
820
827
  });
828
+ export const ListMergeRequestDiffsSchema = GetMergeRequestSchema.extend({
829
+ page: z.number().optional().describe("Page number for pagination (default: 1)"),
830
+ per_page: z.number().optional().describe("Number of items per page (max: 100, default: 20)"),
831
+ unidiff: z.boolean().optional().describe("Present diffs in the unified diff format. Default is false. Introduced in GitLab 16.5."),
832
+ });
821
833
  export const CreateNoteSchema = z.object({
822
834
  project_id: z.string().describe("Project ID or namespace/project_path"),
823
835
  noteable_type: z
@@ -1124,18 +1136,19 @@ export const GitLabWikiPageSchema = z.object({
1124
1136
  });
1125
1137
  // Merge Request Thread position schema - used for diff notes
1126
1138
  export const MergeRequestThreadPositionSchema = z.object({
1127
- base_sha: z.string().describe("Base commit SHA in the source branch"),
1128
- head_sha: z.string().describe("SHA referencing HEAD of the source branch"),
1129
- start_sha: z.string().describe("SHA referencing the start commit of the source branch"),
1130
- position_type: z.enum(["text", "image", "file"]).describe("Type of position reference"),
1131
- new_path: z.string().optional().describe("File path after change"),
1132
- old_path: z.string().optional().describe("File path before change"),
1133
- new_line: z.number().nullable().optional().describe("Line number after change"),
1134
- old_line: z.number().nullable().optional().describe("Line number before change"),
1135
- width: z.number().optional().describe("Width of the image (for image diffs)"),
1136
- height: z.number().optional().describe("Height of the image (for image diffs)"),
1137
- x: z.number().optional().describe("X coordinate on the image (for image diffs)"),
1138
- y: z.number().optional().describe("Y coordinate on the image (for image diffs)"),
1139
+ base_sha: z.string().describe("REQUIRED: Base commit SHA in the source branch. Get this from merge request diff_refs.base_sha."),
1140
+ head_sha: z.string().describe("REQUIRED: SHA referencing HEAD of the source branch. Get this from merge request diff_refs.head_sha."),
1141
+ start_sha: z.string().describe("REQUIRED: SHA referencing the start commit of the source branch. Get this from merge request diff_refs.start_sha."),
1142
+ position_type: z.enum(["text", "image", "file"]).describe("REQUIRED: Position type. Use 'text' for code diffs, 'image' for image diffs, 'file' for file-level comments."),
1143
+ new_path: z.string().nullable().optional().describe("File path after changes. REQUIRED for most diff comments. Use same as old_path if file wasn't renamed."),
1144
+ old_path: z.string().nullable().optional().describe("File path before changes. REQUIRED for most diff comments. Use same as new_path if file wasn't renamed."),
1145
+ new_line: z.number().nullable().optional().describe("Line number in modified file (after changes). Use for added lines or context lines. NULL for deleted lines. For single-line comments on new lines."),
1146
+ old_line: z.number().nullable().optional().describe("Line number in original file (before changes). Use for deleted lines or context lines. NULL for added lines. For single-line comments on old lines."),
1147
+ line_range: LineRangeSchema.optional().describe("MULTILINE COMMENTS: Specify start/end line positions for commenting on multiple lines. Alternative to single old_line/new_line."),
1148
+ width: z.number().optional().describe("IMAGE DIFFS ONLY: Width of the image (for position_type='image')."),
1149
+ height: z.number().optional().describe("IMAGE DIFFS ONLY: Height of the image (for position_type='image')."),
1150
+ x: z.number().optional().describe("IMAGE DIFFS ONLY: X coordinate on the image (for position_type='image')."),
1151
+ y: z.number().optional().describe("IMAGE DIFFS ONLY: Y coordinate on the image (for position_type='image')."),
1139
1152
  });
1140
1153
  // Schema for creating a new merge request thread
1141
1154
  export const CreateMergeRequestThreadSchema = ProjectParamsSchema.extend({
@@ -1202,3 +1215,28 @@ export const GetMilestoneMergeRequestsSchema = GetProjectMilestoneSchema.merge(P
1202
1215
  export const PromoteProjectMilestoneSchema = GetProjectMilestoneSchema;
1203
1216
  // Schema for getting burndown chart events for a milestone
1204
1217
  export const GetMilestoneBurndownEventsSchema = GetProjectMilestoneSchema.merge(PaginationOptionsSchema);
1218
+ // Add schemas for commit operations
1219
+ export const ListCommitsSchema = z.object({
1220
+ project_id: z.string().describe("Project ID or complete URL-encoded path to project"),
1221
+ ref_name: z.string().optional().describe("The name of a repository branch, tag or revision range, or if not given the default branch"),
1222
+ since: z.string().optional().describe("Only commits after or on this date are returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ"),
1223
+ until: z.string().optional().describe("Only commits before or on this date are returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ"),
1224
+ path: z.string().optional().describe("The file path"),
1225
+ author: z.string().optional().describe("Search commits by commit author"),
1226
+ all: z.boolean().optional().describe("Retrieve every commit from the repository"),
1227
+ with_stats: z.boolean().optional().describe("Stats about each commit are added to the response"),
1228
+ first_parent: z.boolean().optional().describe("Follow only the first parent commit upon seeing a merge commit"),
1229
+ order: z.enum(["default", "topo"]).optional().describe("List commits in order"),
1230
+ trailers: z.boolean().optional().describe("Parse and include Git trailers for every commit"),
1231
+ page: z.number().optional().describe("Page number for pagination (default: 1)"),
1232
+ per_page: z.number().optional().describe("Number of items per page (max: 100, default: 20)"),
1233
+ });
1234
+ export const GetCommitSchema = z.object({
1235
+ project_id: z.string().describe("Project ID or complete URL-encoded path to project"),
1236
+ sha: z.string().describe("The commit hash or name of a repository branch or tag"),
1237
+ stats: z.boolean().optional().describe("Include commit stats"),
1238
+ });
1239
+ export const GetCommitDiffSchema = z.object({
1240
+ project_id: z.string().describe("Project ID or complete URL-encoded path to project"),
1241
+ sha: z.string().describe("The commit hash or name of a repository branch or tag"),
1242
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zereight/mcp-gitlab",
3
- "version": "1.0.64",
3
+ "version": "1.0.66",
4
4
  "description": "MCP server for using the GitLab API",
5
5
  "license": "MIT",
6
6
  "author": "zereight",