@zereight/mcp-gitlab 1.0.35 → 1.0.37
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 +29 -27
- package/build/index.js +155 -6
- package/build/schemas.js +34 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,31 +55,33 @@ When using with the Claude App, you need to set up your API key and URLs directl
|
|
|
55
55
|
11. `get_merge_request_diffs` - Get the changes/diffs of a merge request (Either mergeRequestIid or branchName must be provided)
|
|
56
56
|
12. `update_merge_request` - Update a merge request (Either mergeRequestIid or branchName must be provided)
|
|
57
57
|
13. `create_note` - Create a new note (comment) to an issue or merge request
|
|
58
|
-
14. `
|
|
59
|
-
15. `
|
|
60
|
-
16. `
|
|
61
|
-
17. `
|
|
62
|
-
18. `
|
|
63
|
-
19. `
|
|
64
|
-
20. `
|
|
65
|
-
21. `
|
|
66
|
-
22. `
|
|
67
|
-
23. `
|
|
68
|
-
24. `
|
|
69
|
-
25. `
|
|
70
|
-
26. `
|
|
71
|
-
27. `
|
|
72
|
-
28. `
|
|
73
|
-
29. `
|
|
74
|
-
30. `
|
|
75
|
-
31. `
|
|
76
|
-
32. `
|
|
77
|
-
33. `
|
|
78
|
-
34. `
|
|
79
|
-
35. `
|
|
80
|
-
36. `
|
|
81
|
-
37. `
|
|
82
|
-
38. `
|
|
83
|
-
39. `
|
|
84
|
-
40. `
|
|
58
|
+
14. `create_merge_request_thread` - Create a new thread on a merge request
|
|
59
|
+
15. `mr_discussions` - List discussion items for a merge request
|
|
60
|
+
16. `update_merge_request_note` - Modify an existing merge request thread note
|
|
61
|
+
17. `create_merge_request_note` - Add a new note to an existing merge request thread
|
|
62
|
+
18. `list_issues` - List issues in a GitLab project with filtering options
|
|
63
|
+
19. `get_issue` - Get details of a specific issue in a GitLab project
|
|
64
|
+
20. `update_issue` - Update an issue in a GitLab project
|
|
65
|
+
21. `delete_issue` - Delete an issue from a GitLab project
|
|
66
|
+
22. `list_issue_links` - List all issue links for a specific issue
|
|
67
|
+
23. `get_issue_link` - Get a specific issue link
|
|
68
|
+
24. `create_issue_link` - Create an issue link between two issues
|
|
69
|
+
25. `delete_issue_link` - Delete an issue link
|
|
70
|
+
26. `list_namespaces` - List all namespaces available to the current user
|
|
71
|
+
27. `get_namespace` - Get details of a namespace by ID or path
|
|
72
|
+
28. `verify_namespace` - Verify if a namespace path exists
|
|
73
|
+
29. `get_project` - Get details of a specific project
|
|
74
|
+
30. `list_projects` - List projects accessible by the current user
|
|
75
|
+
31. `list_labels` - List labels for a project
|
|
76
|
+
32. `get_label` - Get a single label from a project
|
|
77
|
+
33. `create_label` - Create a new label in a project
|
|
78
|
+
34. `update_label` - Update an existing label in a project
|
|
79
|
+
35. `delete_label` - Delete a label from a project
|
|
80
|
+
36. `list_group_projects` - List projects in a GitLab group with filtering options
|
|
81
|
+
37. `list_wiki_pages` - List wiki pages in a GitLab project
|
|
82
|
+
38. `get_wiki_page` - Get details of a specific wiki page
|
|
83
|
+
39. `create_wiki_page` - Create a new wiki page in a GitLab project
|
|
84
|
+
40. `update_wiki_page` - Update an existing wiki page in a GitLab project
|
|
85
|
+
41. `delete_wiki_page` - Delete a wiki page from a GitLab project
|
|
86
|
+
42. `get_repository_tree` - Get the repository tree for a GitLab project (list files and directories)
|
|
85
87
|
<!-- TOOLS-END -->
|
package/build/index.js
CHANGED
|
@@ -13,10 +13,11 @@ import { dirname } from "path";
|
|
|
13
13
|
import fs from "fs";
|
|
14
14
|
import path from "path";
|
|
15
15
|
import { URL } from "url";
|
|
16
|
-
import { GitLabForkSchema, GitLabReferenceSchema, GitLabRepositorySchema, GitLabIssueSchema, GitLabMergeRequestSchema, GitLabContentSchema, GitLabCreateUpdateFileResponseSchema, GitLabSearchResponseSchema, GitLabTreeSchema, GitLabCommitSchema, GitLabNamespaceSchema, GitLabNamespaceExistsResponseSchema, GitLabProjectSchema, CreateOrUpdateFileSchema, SearchRepositoriesSchema, CreateRepositorySchema, GetFileContentsSchema, PushFilesSchema, CreateIssueSchema, CreateMergeRequestSchema, ForkRepositorySchema, CreateBranchSchema, GitLabMergeRequestDiffSchema, GetMergeRequestSchema, GetMergeRequestDiffsSchema, UpdateMergeRequestSchema, ListIssuesSchema, GetIssueSchema, UpdateIssueSchema, DeleteIssueSchema, GitLabIssueLinkSchema, GitLabIssueWithLinkDetailsSchema, ListIssueLinksSchema, GetIssueLinkSchema, CreateIssueLinkSchema, DeleteIssueLinkSchema, ListNamespacesSchema, GetNamespaceSchema, VerifyNamespaceSchema, GetProjectSchema, ListProjectsSchema, ListLabelsSchema, GetLabelSchema, CreateLabelSchema, UpdateLabelSchema, DeleteLabelSchema, CreateNoteSchema, ListGroupProjectsSchema, ListWikiPagesSchema, GetWikiPageSchema, CreateWikiPageSchema, UpdateWikiPageSchema, DeleteWikiPageSchema, GitLabWikiPageSchema,
|
|
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, CreateMergeRequestThreadSchema, ListGroupProjectsSchema, ListWikiPagesSchema, GetWikiPageSchema, CreateWikiPageSchema, UpdateWikiPageSchema, DeleteWikiPageSchema, GitLabWikiPageSchema,
|
|
17
17
|
// Discussion Schemas
|
|
18
18
|
GitLabDiscussionNoteSchema, // Added
|
|
19
19
|
GitLabDiscussionSchema, UpdateMergeRequestNoteSchema, // Added
|
|
20
|
+
CreateMergeRequestNoteSchema, // Added
|
|
20
21
|
ListMergeRequestDiscussionsSchema, GitLabTreeItemSchema, GetRepositoryTreeSchema, } from "./schemas.js";
|
|
21
22
|
/**
|
|
22
23
|
* Read version from package.json
|
|
@@ -150,6 +151,11 @@ const allTools = [
|
|
|
150
151
|
description: "Create a new note (comment) to an issue or merge request",
|
|
151
152
|
inputSchema: zodToJsonSchema(CreateNoteSchema),
|
|
152
153
|
},
|
|
154
|
+
{
|
|
155
|
+
name: "create_merge_request_thread",
|
|
156
|
+
description: "Create a new thread on a merge request",
|
|
157
|
+
inputSchema: zodToJsonSchema(CreateMergeRequestThreadSchema),
|
|
158
|
+
},
|
|
153
159
|
{
|
|
154
160
|
name: "mr_discussions",
|
|
155
161
|
description: "List discussion items for a merge request",
|
|
@@ -160,6 +166,11 @@ const allTools = [
|
|
|
160
166
|
description: "Modify an existing merge request thread note",
|
|
161
167
|
inputSchema: zodToJsonSchema(UpdateMergeRequestNoteSchema),
|
|
162
168
|
},
|
|
169
|
+
{
|
|
170
|
+
name: "create_merge_request_note",
|
|
171
|
+
description: "Add a new note to an existing merge request thread",
|
|
172
|
+
inputSchema: zodToJsonSchema(CreateMergeRequestNoteSchema),
|
|
173
|
+
},
|
|
163
174
|
{
|
|
164
175
|
name: "list_issues",
|
|
165
176
|
description: "List issues in a GitLab project with filtering options",
|
|
@@ -373,6 +384,7 @@ async function handleGitLabError(response) {
|
|
|
373
384
|
* @returns {Promise<GitLabFork>} The created fork
|
|
374
385
|
*/
|
|
375
386
|
async function forkProject(projectId, namespace) {
|
|
387
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
376
388
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/fork`);
|
|
377
389
|
if (namespace) {
|
|
378
390
|
url.searchParams.append("namespace", namespace);
|
|
@@ -398,6 +410,7 @@ async function forkProject(projectId, namespace) {
|
|
|
398
410
|
* @returns {Promise<GitLabReference>} The created branch reference
|
|
399
411
|
*/
|
|
400
412
|
async function createBranch(projectId, options) {
|
|
413
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
401
414
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/branches`);
|
|
402
415
|
const response = await fetch(url.toString(), {
|
|
403
416
|
...DEFAULT_FETCH_CONFIG,
|
|
@@ -418,6 +431,7 @@ async function createBranch(projectId, options) {
|
|
|
418
431
|
* @returns {Promise<string>} The name of the default branch
|
|
419
432
|
*/
|
|
420
433
|
async function getDefaultBranchRef(projectId) {
|
|
434
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
421
435
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}`);
|
|
422
436
|
const response = await fetch(url.toString(), {
|
|
423
437
|
...DEFAULT_FETCH_CONFIG,
|
|
@@ -436,6 +450,7 @@ async function getDefaultBranchRef(projectId) {
|
|
|
436
450
|
* @returns {Promise<GitLabContent>} The file content
|
|
437
451
|
*/
|
|
438
452
|
async function getFileContents(projectId, filePath, ref) {
|
|
453
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
439
454
|
const encodedPath = encodeURIComponent(filePath);
|
|
440
455
|
// ref가 없는 경우 default branch를 가져옴
|
|
441
456
|
if (!ref) {
|
|
@@ -469,6 +484,7 @@ async function getFileContents(projectId, filePath, ref) {
|
|
|
469
484
|
* @returns {Promise<GitLabIssue>} The created issue
|
|
470
485
|
*/
|
|
471
486
|
async function createIssue(projectId, options) {
|
|
487
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
472
488
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues`);
|
|
473
489
|
const response = await fetch(url.toString(), {
|
|
474
490
|
...DEFAULT_FETCH_CONFIG,
|
|
@@ -499,6 +515,7 @@ async function createIssue(projectId, options) {
|
|
|
499
515
|
* @returns {Promise<GitLabIssue[]>} List of issues
|
|
500
516
|
*/
|
|
501
517
|
async function listIssues(projectId, options = {}) {
|
|
518
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
502
519
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues`);
|
|
503
520
|
// Add all query parameters
|
|
504
521
|
Object.entries(options).forEach(([key, value]) => {
|
|
@@ -528,6 +545,7 @@ async function listIssues(projectId, options = {}) {
|
|
|
528
545
|
* @returns {Promise<GitLabIssue>} The issue
|
|
529
546
|
*/
|
|
530
547
|
async function getIssue(projectId, issueIid) {
|
|
548
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
531
549
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}`);
|
|
532
550
|
const response = await fetch(url.toString(), {
|
|
533
551
|
...DEFAULT_FETCH_CONFIG,
|
|
@@ -546,6 +564,7 @@ async function getIssue(projectId, issueIid) {
|
|
|
546
564
|
* @returns {Promise<GitLabIssue>} The updated issue
|
|
547
565
|
*/
|
|
548
566
|
async function updateIssue(projectId, issueIid, options) {
|
|
567
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
549
568
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}`);
|
|
550
569
|
// Convert labels array to comma-separated string if present
|
|
551
570
|
const body = { ...options };
|
|
@@ -570,6 +589,7 @@ async function updateIssue(projectId, issueIid, options) {
|
|
|
570
589
|
* @returns {Promise<void>}
|
|
571
590
|
*/
|
|
572
591
|
async function deleteIssue(projectId, issueIid) {
|
|
592
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
573
593
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}`);
|
|
574
594
|
const response = await fetch(url.toString(), {
|
|
575
595
|
...DEFAULT_FETCH_CONFIG,
|
|
@@ -586,6 +606,7 @@ async function deleteIssue(projectId, issueIid) {
|
|
|
586
606
|
* @returns {Promise<GitLabIssueWithLinkDetails[]>} List of issues with link details
|
|
587
607
|
*/
|
|
588
608
|
async function listIssueLinks(projectId, issueIid) {
|
|
609
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
589
610
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/links`);
|
|
590
611
|
const response = await fetch(url.toString(), {
|
|
591
612
|
...DEFAULT_FETCH_CONFIG,
|
|
@@ -604,6 +625,7 @@ async function listIssueLinks(projectId, issueIid) {
|
|
|
604
625
|
* @returns {Promise<GitLabIssueLink>} The issue link
|
|
605
626
|
*/
|
|
606
627
|
async function getIssueLink(projectId, issueIid, issueLinkId) {
|
|
628
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
607
629
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/links/${issueLinkId}`);
|
|
608
630
|
const response = await fetch(url.toString(), {
|
|
609
631
|
...DEFAULT_FETCH_CONFIG,
|
|
@@ -624,6 +646,8 @@ async function getIssueLink(projectId, issueIid, issueLinkId) {
|
|
|
624
646
|
* @returns {Promise<GitLabIssueLink>} The created issue link
|
|
625
647
|
*/
|
|
626
648
|
async function createIssueLink(projectId, issueIid, targetProjectId, targetIssueIid, linkType = "relates_to") {
|
|
649
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
650
|
+
targetProjectId = decodeURIComponent(targetProjectId); // Decode target project ID as well
|
|
627
651
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/links`);
|
|
628
652
|
const response = await fetch(url.toString(), {
|
|
629
653
|
...DEFAULT_FETCH_CONFIG,
|
|
@@ -648,6 +672,7 @@ async function createIssueLink(projectId, issueIid, targetProjectId, targetIssue
|
|
|
648
672
|
* @returns {Promise<void>}
|
|
649
673
|
*/
|
|
650
674
|
async function deleteIssueLink(projectId, issueIid, issueLinkId) {
|
|
675
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
651
676
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/links/${issueLinkId}`);
|
|
652
677
|
const response = await fetch(url.toString(), {
|
|
653
678
|
...DEFAULT_FETCH_CONFIG,
|
|
@@ -664,6 +689,7 @@ async function deleteIssueLink(projectId, issueIid, issueLinkId) {
|
|
|
664
689
|
* @returns {Promise<GitLabMergeRequest>} The created merge request
|
|
665
690
|
*/
|
|
666
691
|
async function createMergeRequest(projectId, options) {
|
|
692
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
667
693
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests`);
|
|
668
694
|
const response = await fetch(url.toString(), {
|
|
669
695
|
...DEFAULT_FETCH_CONFIG,
|
|
@@ -697,6 +723,7 @@ async function createMergeRequest(projectId, options) {
|
|
|
697
723
|
* @returns {Promise<GitLabDiscussion[]>} List of discussions
|
|
698
724
|
*/
|
|
699
725
|
async function listMergeRequestDiscussions(projectId, mergeRequestIid) {
|
|
726
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
700
727
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}/discussions`);
|
|
701
728
|
const response = await fetch(url.toString(), {
|
|
702
729
|
...DEFAULT_FETCH_CONFIG,
|
|
@@ -719,9 +746,14 @@ async function listMergeRequestDiscussions(projectId, mergeRequestIid) {
|
|
|
719
746
|
* @returns {Promise<GitLabDiscussionNote>} The updated note
|
|
720
747
|
*/
|
|
721
748
|
async function updateMergeRequestNote(projectId, mergeRequestIid, discussionId, noteId, body, resolved) {
|
|
749
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
722
750
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}/discussions/${discussionId}/notes/${noteId}`);
|
|
723
|
-
|
|
724
|
-
|
|
751
|
+
// Only one of body or resolved can be sent according to GitLab API
|
|
752
|
+
const payload = {};
|
|
753
|
+
if (body !== undefined) {
|
|
754
|
+
payload.body = body;
|
|
755
|
+
}
|
|
756
|
+
else if (resolved !== undefined) {
|
|
725
757
|
payload.resolved = resolved;
|
|
726
758
|
}
|
|
727
759
|
const response = await fetch(url.toString(), {
|
|
@@ -733,6 +765,33 @@ async function updateMergeRequestNote(projectId, mergeRequestIid, discussionId,
|
|
|
733
765
|
const data = await response.json();
|
|
734
766
|
return GitLabDiscussionNoteSchema.parse(data);
|
|
735
767
|
}
|
|
768
|
+
/**
|
|
769
|
+
* Add a new note to an existing merge request thread
|
|
770
|
+
* 기존 병합 요청 스레드에 새 노트 추가
|
|
771
|
+
*
|
|
772
|
+
* @param {string} projectId - The ID or URL-encoded path of the project
|
|
773
|
+
* @param {number} mergeRequestIid - The IID of a merge request
|
|
774
|
+
* @param {string} discussionId - The ID of a thread
|
|
775
|
+
* @param {string} body - The content of the new note
|
|
776
|
+
* @param {string} [createdAt] - The creation date of the note (ISO 8601 format)
|
|
777
|
+
* @returns {Promise<GitLabDiscussionNote>} The created note
|
|
778
|
+
*/
|
|
779
|
+
async function createMergeRequestNote(projectId, mergeRequestIid, discussionId, body, createdAt) {
|
|
780
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
781
|
+
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}/discussions/${discussionId}/notes`);
|
|
782
|
+
const payload = { body };
|
|
783
|
+
if (createdAt) {
|
|
784
|
+
payload.created_at = createdAt;
|
|
785
|
+
}
|
|
786
|
+
const response = await fetch(url.toString(), {
|
|
787
|
+
...DEFAULT_FETCH_CONFIG,
|
|
788
|
+
method: "POST",
|
|
789
|
+
body: JSON.stringify(payload),
|
|
790
|
+
});
|
|
791
|
+
await handleGitLabError(response);
|
|
792
|
+
const data = await response.json();
|
|
793
|
+
return GitLabDiscussionNoteSchema.parse(data);
|
|
794
|
+
}
|
|
736
795
|
/**
|
|
737
796
|
* Create or update a file in a GitLab project
|
|
738
797
|
* 파일 생성 또는 업데이트
|
|
@@ -746,6 +805,7 @@ async function updateMergeRequestNote(projectId, mergeRequestIid, discussionId,
|
|
|
746
805
|
* @returns {Promise<GitLabCreateUpdateFileResponse>} The file update response
|
|
747
806
|
*/
|
|
748
807
|
async function createOrUpdateFile(projectId, filePath, content, commitMessage, branch, previousPath, last_commit_id, commit_id) {
|
|
808
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
749
809
|
const encodedPath = encodeURIComponent(filePath);
|
|
750
810
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/files/${encodedPath}`);
|
|
751
811
|
const body = {
|
|
@@ -813,6 +873,7 @@ async function createOrUpdateFile(projectId, filePath, content, commitMessage, b
|
|
|
813
873
|
* @returns {Promise<GitLabTree>} The created tree
|
|
814
874
|
*/
|
|
815
875
|
async function createTree(projectId, files, ref) {
|
|
876
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
816
877
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/tree`);
|
|
817
878
|
if (ref) {
|
|
818
879
|
url.searchParams.append("ref", ref);
|
|
@@ -850,6 +911,7 @@ async function createTree(projectId, files, ref) {
|
|
|
850
911
|
* @returns {Promise<GitLabCommit>} The created commit
|
|
851
912
|
*/
|
|
852
913
|
async function createCommit(projectId, message, branch, actions) {
|
|
914
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
853
915
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/commits`);
|
|
854
916
|
const response = await fetch(url.toString(), {
|
|
855
917
|
...DEFAULT_FETCH_CONFIG,
|
|
@@ -948,6 +1010,7 @@ async function createRepository(options) {
|
|
|
948
1010
|
* @returns {Promise<GitLabMergeRequest>} The merge request details
|
|
949
1011
|
*/
|
|
950
1012
|
async function getMergeRequest(projectId, mergeRequestIid, branchName) {
|
|
1013
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
951
1014
|
let url;
|
|
952
1015
|
if (mergeRequestIid) {
|
|
953
1016
|
url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}`);
|
|
@@ -980,6 +1043,7 @@ async function getMergeRequest(projectId, mergeRequestIid, branchName) {
|
|
|
980
1043
|
* @returns {Promise<GitLabMergeRequestDiff[]>} The merge request diffs
|
|
981
1044
|
*/
|
|
982
1045
|
async function getMergeRequestDiffs(projectId, mergeRequestIid, branchName, view) {
|
|
1046
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
983
1047
|
if (!mergeRequestIid && !branchName) {
|
|
984
1048
|
throw new Error("Either mergeRequestIid or branchName must be provided");
|
|
985
1049
|
}
|
|
@@ -1009,6 +1073,7 @@ async function getMergeRequestDiffs(projectId, mergeRequestIid, branchName, view
|
|
|
1009
1073
|
* @returns {Promise<GitLabMergeRequest>} The updated merge request
|
|
1010
1074
|
*/
|
|
1011
1075
|
async function updateMergeRequest(projectId, options, mergeRequestIid, branchName) {
|
|
1076
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
1012
1077
|
if (!mergeRequestIid && !branchName) {
|
|
1013
1078
|
throw new Error("Either mergeRequestIid or branchName must be provided");
|
|
1014
1079
|
}
|
|
@@ -1038,6 +1103,7 @@ async function updateMergeRequest(projectId, options, mergeRequestIid, branchNam
|
|
|
1038
1103
|
*/
|
|
1039
1104
|
async function createNote(projectId, noteableType, // 'issue' 또는 'merge_request' 타입 명시
|
|
1040
1105
|
noteableIid, body) {
|
|
1106
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
1041
1107
|
// ⚙️ 응답 타입은 GitLab API 문서에 따라 조정 가능
|
|
1042
1108
|
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
|
|
1043
1109
|
);
|
|
@@ -1052,6 +1118,43 @@ noteableIid, body) {
|
|
|
1052
1118
|
}
|
|
1053
1119
|
return await response.json();
|
|
1054
1120
|
}
|
|
1121
|
+
/**
|
|
1122
|
+
* Create a new thread on a merge request
|
|
1123
|
+
* 📦 새로운 함수: createMergeRequestThread - 병합 요청에 새로운 스레드(토론)를 생성하는 함수
|
|
1124
|
+
* (New function: createMergeRequestThread - Function to create a new thread (discussion) on a merge request)
|
|
1125
|
+
*
|
|
1126
|
+
* This function provides more capabilities than createNote, including the ability to:
|
|
1127
|
+
* - Create diff notes (comments on specific lines of code)
|
|
1128
|
+
* - Specify exact positions for comments
|
|
1129
|
+
* - Set creation timestamps
|
|
1130
|
+
*
|
|
1131
|
+
* @param {string} projectId - The ID or URL-encoded path of the project
|
|
1132
|
+
* @param {number} mergeRequestIid - The internal ID of the merge request
|
|
1133
|
+
* @param {string} body - The content of the thread
|
|
1134
|
+
* @param {MergeRequestThreadPosition} [position] - Position information for diff notes
|
|
1135
|
+
* @param {string} [createdAt] - ISO 8601 formatted creation date
|
|
1136
|
+
* @returns {Promise<GitLabDiscussion>} The created discussion thread
|
|
1137
|
+
*/
|
|
1138
|
+
async function createMergeRequestThread(projectId, mergeRequestIid, body, position, createdAt) {
|
|
1139
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
1140
|
+
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}/discussions`);
|
|
1141
|
+
const payload = { body };
|
|
1142
|
+
// Add optional parameters if provided
|
|
1143
|
+
if (position) {
|
|
1144
|
+
payload.position = position;
|
|
1145
|
+
}
|
|
1146
|
+
if (createdAt) {
|
|
1147
|
+
payload.created_at = createdAt;
|
|
1148
|
+
}
|
|
1149
|
+
const response = await fetch(url.toString(), {
|
|
1150
|
+
...DEFAULT_FETCH_CONFIG,
|
|
1151
|
+
method: "POST",
|
|
1152
|
+
body: JSON.stringify(payload),
|
|
1153
|
+
});
|
|
1154
|
+
await handleGitLabError(response);
|
|
1155
|
+
const data = await response.json();
|
|
1156
|
+
return GitLabDiscussionSchema.parse(data);
|
|
1157
|
+
}
|
|
1055
1158
|
/**
|
|
1056
1159
|
* List all namespaces
|
|
1057
1160
|
* 사용 가능한 모든 네임스페이스 목록 조회
|
|
@@ -1128,6 +1231,7 @@ async function verifyNamespaceExistence(namespacePath, parentId) {
|
|
|
1128
1231
|
* @returns {Promise<GitLabProject>} Project details
|
|
1129
1232
|
*/
|
|
1130
1233
|
async function getProject(projectId, options = {}) {
|
|
1234
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
1131
1235
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}`);
|
|
1132
1236
|
if (options.license) {
|
|
1133
1237
|
url.searchParams.append("license", "true");
|
|
@@ -1183,6 +1287,7 @@ async function listProjects(options = {}) {
|
|
|
1183
1287
|
* @returns Array of GitLab labels
|
|
1184
1288
|
*/
|
|
1185
1289
|
async function listLabels(projectId, options = {}) {
|
|
1290
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
1186
1291
|
// Construct the URL with project path
|
|
1187
1292
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels`);
|
|
1188
1293
|
// Add query parameters
|
|
@@ -1215,6 +1320,7 @@ async function listLabels(projectId, options = {}) {
|
|
|
1215
1320
|
* @returns GitLab label
|
|
1216
1321
|
*/
|
|
1217
1322
|
async function getLabel(projectId, labelId, includeAncestorGroups) {
|
|
1323
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
1218
1324
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels/${encodeURIComponent(String(labelId))}`);
|
|
1219
1325
|
// Add query parameters
|
|
1220
1326
|
if (includeAncestorGroups !== undefined) {
|
|
@@ -1238,6 +1344,7 @@ async function getLabel(projectId, labelId, includeAncestorGroups) {
|
|
|
1238
1344
|
* @returns Created GitLab label
|
|
1239
1345
|
*/
|
|
1240
1346
|
async function createLabel(projectId, options) {
|
|
1347
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
1241
1348
|
// Make the API request
|
|
1242
1349
|
const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels`, {
|
|
1243
1350
|
...DEFAULT_FETCH_CONFIG,
|
|
@@ -1259,6 +1366,7 @@ async function createLabel(projectId, options) {
|
|
|
1259
1366
|
* @returns Updated GitLab label
|
|
1260
1367
|
*/
|
|
1261
1368
|
async function updateLabel(projectId, labelId, options) {
|
|
1369
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
1262
1370
|
// Make the API request
|
|
1263
1371
|
const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels/${encodeURIComponent(String(labelId))}`, {
|
|
1264
1372
|
...DEFAULT_FETCH_CONFIG,
|
|
@@ -1278,6 +1386,7 @@ async function updateLabel(projectId, labelId, options) {
|
|
|
1278
1386
|
* @param labelId The ID or name of the label to delete
|
|
1279
1387
|
*/
|
|
1280
1388
|
async function deleteLabel(projectId, labelId) {
|
|
1389
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
1281
1390
|
// Make the API request
|
|
1282
1391
|
const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels/${encodeURIComponent(String(labelId))}`, {
|
|
1283
1392
|
...DEFAULT_FETCH_CONFIG,
|
|
@@ -1339,6 +1448,7 @@ async function listGroupProjects(options) {
|
|
|
1339
1448
|
* List wiki pages in a project
|
|
1340
1449
|
*/
|
|
1341
1450
|
async function listWikiPages(projectId, options = {}) {
|
|
1451
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
1342
1452
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis`);
|
|
1343
1453
|
if (options.page)
|
|
1344
1454
|
url.searchParams.append("page", options.page.toString());
|
|
@@ -1355,6 +1465,7 @@ async function listWikiPages(projectId, options = {}) {
|
|
|
1355
1465
|
* Get a specific wiki page
|
|
1356
1466
|
*/
|
|
1357
1467
|
async function getWikiPage(projectId, slug) {
|
|
1468
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
1358
1469
|
const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis/${encodeURIComponent(slug)}`, { ...DEFAULT_FETCH_CONFIG });
|
|
1359
1470
|
await handleGitLabError(response);
|
|
1360
1471
|
const data = await response.json();
|
|
@@ -1364,6 +1475,7 @@ async function getWikiPage(projectId, slug) {
|
|
|
1364
1475
|
* Create a new wiki page
|
|
1365
1476
|
*/
|
|
1366
1477
|
async function createWikiPage(projectId, title, content, format) {
|
|
1478
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
1367
1479
|
const body = { title, content };
|
|
1368
1480
|
if (format)
|
|
1369
1481
|
body.format = format;
|
|
@@ -1380,6 +1492,7 @@ async function createWikiPage(projectId, title, content, format) {
|
|
|
1380
1492
|
* Update an existing wiki page
|
|
1381
1493
|
*/
|
|
1382
1494
|
async function updateWikiPage(projectId, slug, title, content, format) {
|
|
1495
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
1383
1496
|
const body = {};
|
|
1384
1497
|
if (title)
|
|
1385
1498
|
body.title = title;
|
|
@@ -1400,6 +1513,7 @@ async function updateWikiPage(projectId, slug, title, content, format) {
|
|
|
1400
1513
|
* Delete a wiki page
|
|
1401
1514
|
*/
|
|
1402
1515
|
async function deleteWikiPage(projectId, slug) {
|
|
1516
|
+
projectId = decodeURIComponent(projectId); // Decode project ID
|
|
1403
1517
|
const response = await fetch(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis/${encodeURIComponent(slug)}`, {
|
|
1404
1518
|
...DEFAULT_FETCH_CONFIG,
|
|
1405
1519
|
method: "DELETE",
|
|
@@ -1413,6 +1527,7 @@ async function deleteWikiPage(projectId, slug) {
|
|
|
1413
1527
|
* @returns {Promise<GitLabTreeItem[]>}
|
|
1414
1528
|
*/
|
|
1415
1529
|
async function getRepositoryTree(options) {
|
|
1530
|
+
options.project_id = decodeURIComponent(options.project_id); // Decode project_id within options
|
|
1416
1531
|
const queryParams = new URLSearchParams();
|
|
1417
1532
|
if (options.path)
|
|
1418
1533
|
queryParams.append("path", options.path);
|
|
@@ -1447,11 +1562,29 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1447
1562
|
? allTools.filter((tool) => readOnlyTools.includes(tool.name))
|
|
1448
1563
|
: allTools;
|
|
1449
1564
|
// Toggle wiki tools by USE_GITLAB_WIKI flag
|
|
1450
|
-
|
|
1565
|
+
let tools = USE_GITLAB_WIKI
|
|
1451
1566
|
? tools0
|
|
1452
1567
|
: tools0.filter((tool) => !wikiToolNames.includes(tool.name));
|
|
1568
|
+
// <<< START: Gemini 호환성을 위해 $schema 제거 >>>
|
|
1569
|
+
tools = tools.map((tool) => {
|
|
1570
|
+
// inputSchema가 존재하고 객체인지 확인
|
|
1571
|
+
if (tool.inputSchema &&
|
|
1572
|
+
typeof tool.inputSchema === "object" &&
|
|
1573
|
+
tool.inputSchema !== null) {
|
|
1574
|
+
// $schema 키가 존재하면 삭제
|
|
1575
|
+
if ("$schema" in tool.inputSchema) {
|
|
1576
|
+
// 불변성을 위해 새로운 객체 생성 (선택적이지만 권장)
|
|
1577
|
+
const modifiedSchema = { ...tool.inputSchema };
|
|
1578
|
+
delete modifiedSchema.$schema;
|
|
1579
|
+
return { ...tool, inputSchema: modifiedSchema };
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
// 변경이 필요 없으면 그대로 반환
|
|
1583
|
+
return tool;
|
|
1584
|
+
});
|
|
1585
|
+
// <<< END: Gemini 호환성을 위해 $schema 제거 >>>
|
|
1453
1586
|
return {
|
|
1454
|
-
tools,
|
|
1587
|
+
tools, // $schema가 제거된 도구 목록 반환
|
|
1455
1588
|
};
|
|
1456
1589
|
});
|
|
1457
1590
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -1557,12 +1690,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1557
1690
|
}
|
|
1558
1691
|
case "update_merge_request_note": {
|
|
1559
1692
|
const args = UpdateMergeRequestNoteSchema.parse(request.params.arguments);
|
|
1560
|
-
const note = await updateMergeRequestNote(args.project_id, args.merge_request_iid, args.discussion_id, args.note_id, args.body,
|
|
1693
|
+
const note = await updateMergeRequestNote(args.project_id, args.merge_request_iid, args.discussion_id, args.note_id, args.body, // Now optional
|
|
1694
|
+
args.resolved // Now one of body or resolved must be provided, not both
|
|
1561
1695
|
);
|
|
1562
1696
|
return {
|
|
1563
1697
|
content: [{ type: "text", text: JSON.stringify(note, null, 2) }],
|
|
1564
1698
|
};
|
|
1565
1699
|
}
|
|
1700
|
+
case "create_merge_request_note": {
|
|
1701
|
+
const args = CreateMergeRequestNoteSchema.parse(request.params.arguments);
|
|
1702
|
+
const note = await createMergeRequestNote(args.project_id, args.merge_request_iid, args.discussion_id, args.body, args.created_at);
|
|
1703
|
+
return {
|
|
1704
|
+
content: [{ type: "text", text: JSON.stringify(note, null, 2) }],
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1566
1707
|
case "get_merge_request": {
|
|
1567
1708
|
const args = GetMergeRequestSchema.parse(request.params.arguments);
|
|
1568
1709
|
const mergeRequest = await getMergeRequest(args.project_id, args.merge_request_iid, args.source_branch);
|
|
@@ -1681,6 +1822,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1681
1822
|
content: [{ type: "text", text: JSON.stringify(note, null, 2) }],
|
|
1682
1823
|
};
|
|
1683
1824
|
}
|
|
1825
|
+
case "create_merge_request_thread": {
|
|
1826
|
+
const args = CreateMergeRequestThreadSchema.parse(request.params.arguments);
|
|
1827
|
+
const { project_id, merge_request_iid, body, position, created_at } = args;
|
|
1828
|
+
const thread = await createMergeRequestThread(project_id, merge_request_iid, body, position, created_at);
|
|
1829
|
+
return {
|
|
1830
|
+
content: [{ type: "text", text: JSON.stringify(thread, null, 2) }],
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1684
1833
|
case "list_issues": {
|
|
1685
1834
|
const args = ListIssuesSchema.parse(request.params.arguments);
|
|
1686
1835
|
const { project_id, ...options } = args;
|
package/build/schemas.js
CHANGED
|
@@ -434,8 +434,19 @@ export const UpdateMergeRequestNoteSchema = ProjectParamsSchema.extend({
|
|
|
434
434
|
merge_request_iid: z.number().describe("The IID of a merge request"),
|
|
435
435
|
discussion_id: z.string().describe("The ID of a thread"),
|
|
436
436
|
note_id: z.number().describe("The ID of a thread note"),
|
|
437
|
+
body: z.string().optional().describe("The content of the note or reply"),
|
|
438
|
+
resolved: z.boolean().optional().describe("Resolve or unresolve the note"),
|
|
439
|
+
}).refine(data => data.body !== undefined || data.resolved !== undefined, {
|
|
440
|
+
message: "At least one of 'body' or 'resolved' must be provided"
|
|
441
|
+
}).refine(data => !(data.body !== undefined && data.resolved !== undefined), {
|
|
442
|
+
message: "Only one of 'body' or 'resolved' can be provided, not both"
|
|
443
|
+
});
|
|
444
|
+
// Input schema for adding a note to an existing merge request discussion
|
|
445
|
+
export const CreateMergeRequestNoteSchema = ProjectParamsSchema.extend({
|
|
446
|
+
merge_request_iid: z.number().describe("The IID of a merge request"),
|
|
447
|
+
discussion_id: z.string().describe("The ID of a thread"),
|
|
437
448
|
body: z.string().describe("The content of the note or reply"),
|
|
438
|
-
|
|
449
|
+
created_at: z.string().optional().describe("Date the note was created at (ISO 8601 format)"),
|
|
439
450
|
});
|
|
440
451
|
// API Operation Parameter Schemas
|
|
441
452
|
export const CreateOrUpdateFileSchema = ProjectParamsSchema.extend({
|
|
@@ -919,3 +930,25 @@ export const GitLabWikiPageSchema = z.object({
|
|
|
919
930
|
created_at: z.string().optional(),
|
|
920
931
|
updated_at: z.string().optional(),
|
|
921
932
|
});
|
|
933
|
+
// Merge Request Thread position schema - used for diff notes
|
|
934
|
+
export const MergeRequestThreadPositionSchema = z.object({
|
|
935
|
+
base_sha: z.string().describe("Base commit SHA in the source branch"),
|
|
936
|
+
head_sha: z.string().describe("SHA referencing HEAD of the source branch"),
|
|
937
|
+
start_sha: z.string().describe("SHA referencing the start commit of the source branch"),
|
|
938
|
+
position_type: z.enum(["text", "image", "file"]).describe("Type of position reference"),
|
|
939
|
+
new_path: z.string().optional().describe("File path after change"),
|
|
940
|
+
old_path: z.string().optional().describe("File path before change"),
|
|
941
|
+
new_line: z.number().nullable().optional().describe("Line number after change"),
|
|
942
|
+
old_line: z.number().nullable().optional().describe("Line number before change"),
|
|
943
|
+
width: z.number().optional().describe("Width of the image (for image diffs)"),
|
|
944
|
+
height: z.number().optional().describe("Height of the image (for image diffs)"),
|
|
945
|
+
x: z.number().optional().describe("X coordinate on the image (for image diffs)"),
|
|
946
|
+
y: z.number().optional().describe("Y coordinate on the image (for image diffs)"),
|
|
947
|
+
});
|
|
948
|
+
// Schema for creating a new merge request thread
|
|
949
|
+
export const CreateMergeRequestThreadSchema = ProjectParamsSchema.extend({
|
|
950
|
+
merge_request_iid: z.number().describe("The IID of a merge request"),
|
|
951
|
+
body: z.string().describe("The content of the thread"),
|
|
952
|
+
position: MergeRequestThreadPositionSchema.optional().describe("Position when creating a diff note"),
|
|
953
|
+
created_at: z.string().optional().describe("Date the thread was created at (ISO 8601 format)"),
|
|
954
|
+
});
|