@zereight/mcp-gitlab 1.0.49 → 1.0.51
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 +17 -2
- package/build/index.js +364 -45
- package/build/schemas.js +210 -241
- package/build/scripts/generate-tools-readme.js +12 -12
- package/build/tests/integration.test.js +151 -0
- package/build/tests/unit.test.js +122 -0
- package/package.json +14 -3
package/README.md
CHANGED
|
@@ -26,7 +26,8 @@ When using with the Claude App, you need to set up your API key and URLs directl
|
|
|
26
26
|
"GITLAB_PERSONAL_ACCESS_TOKEN": "your_gitlab_token",
|
|
27
27
|
"GITLAB_API_URL": "your_gitlab_api_url",
|
|
28
28
|
"GITLAB_READ_ONLY_MODE": "false",
|
|
29
|
-
"USE_GITLAB_WIKI": "
|
|
29
|
+
"USE_GITLAB_WIKI": "false",
|
|
30
|
+
"USE_MILESTONE": "false"
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
33
|
}
|
|
@@ -52,13 +53,16 @@ When using with the Claude App, you need to set up your API key and URLs directl
|
|
|
52
53
|
"GITLAB_READ_ONLY_MODE",
|
|
53
54
|
"-e",
|
|
54
55
|
"USE_GITLAB_WIKI",
|
|
56
|
+
"-e",
|
|
57
|
+
"USE_MILESTONE",
|
|
55
58
|
"iwakitakuma/gitlab-mcp"
|
|
56
59
|
],
|
|
57
60
|
"env": {
|
|
58
61
|
"GITLAB_PERSONAL_ACCESS_TOKEN": "your_gitlab_token",
|
|
59
62
|
"GITLAB_API_URL": "https://gitlab.com/api/v4", // Optional, for self-hosted GitLab
|
|
60
63
|
"GITLAB_READ_ONLY_MODE": "false",
|
|
61
|
-
"USE_GITLAB_WIKI": "true"
|
|
64
|
+
"USE_GITLAB_WIKI": "true",
|
|
65
|
+
"USE_MILESTONE": "true"
|
|
62
66
|
}
|
|
63
67
|
}
|
|
64
68
|
}
|
|
@@ -77,10 +81,12 @@ $ sh scripts/image_push.sh docker_user_name
|
|
|
77
81
|
- `GITLAB_API_URL`: Your GitLab API URL. (Default: `https://gitlab.com/api/v4`)
|
|
78
82
|
- `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.
|
|
79
83
|
- `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.
|
|
84
|
+
- `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.
|
|
80
85
|
|
|
81
86
|
## Tools 🛠️
|
|
82
87
|
|
|
83
88
|
+<!-- TOOLS-START -->
|
|
89
|
+
|
|
84
90
|
1. `create_or_update_file` - Create or update a single file in a GitLab project
|
|
85
91
|
2. `search_repositories` - Search for GitLab projects
|
|
86
92
|
3. `create_repository` - Create a new GitLab project
|
|
@@ -132,4 +138,13 @@ $ sh scripts/image_push.sh docker_user_name
|
|
|
132
138
|
49. `get_pipeline_job` - Get details of a GitLab pipeline job number
|
|
133
139
|
50. `get_pipeline_job_output` - Get the output/trace of a GitLab pipeline job number
|
|
134
140
|
51. `list_merge_requests` - List merge requests in a GitLab project with filtering options
|
|
141
|
+
52. `list_milestones` - List milestones in a GitLab project with filtering options
|
|
142
|
+
53. `get_milestone` - Get details of a specific milestone
|
|
143
|
+
54. `create_milestone` - Create a new milestone in a GitLab project
|
|
144
|
+
55. `edit_milestone ` - Edit an existing milestone in a GitLab project
|
|
145
|
+
56. `delete_milestone` - Delete a milestone from a GitLab project
|
|
146
|
+
57. `get_milestone_issue` - Get issues associated with a specific milestone
|
|
147
|
+
58. `get_milestone_merge_requests` - Get merge requests associated with a specific milestone
|
|
148
|
+
59. `promote_milestone` - Promote a milestone to the next stage
|
|
149
|
+
60. `get_milestone_burndown_events` - Get burndown events for a specific milestone
|
|
135
150
|
<!-- TOOLS-END -->
|
package/build/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import { CallToolRequestSchema, ListToolsRequestSchema
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
5
5
|
import fetch from "node-fetch";
|
|
6
6
|
import { SocksProxyAgent } from "socks-proxy-agent";
|
|
7
7
|
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
@@ -20,7 +20,7 @@ GetPipelineJobOutputSchema, GitLabPipelineJobSchema,
|
|
|
20
20
|
GitLabDiscussionNoteSchema, // Added
|
|
21
21
|
GitLabDiscussionSchema, UpdateMergeRequestNoteSchema, // Added
|
|
22
22
|
CreateMergeRequestNoteSchema, // Added
|
|
23
|
-
ListMergeRequestDiscussionsSchema, UpdateIssueNoteSchema, CreateIssueNoteSchema, ListMergeRequestsSchema, } from "./schemas.js";
|
|
23
|
+
ListMergeRequestDiscussionsSchema, UpdateIssueNoteSchema, CreateIssueNoteSchema, ListMergeRequestsSchema, GitLabMilestonesSchema, ListProjectMilestonesSchema, GetProjectMilestoneSchema, CreateProjectMilestoneSchema, EditProjectMilestoneSchema, DeleteProjectMilestoneSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, PromoteProjectMilestoneSchema, GetMilestoneBurndownEventsSchema, } from "./schemas.js";
|
|
24
24
|
/**
|
|
25
25
|
* Read version from package.json
|
|
26
26
|
*/
|
|
@@ -48,6 +48,7 @@ const server = new Server({
|
|
|
48
48
|
const GITLAB_PERSONAL_ACCESS_TOKEN = process.env.GITLAB_PERSONAL_ACCESS_TOKEN;
|
|
49
49
|
const GITLAB_READ_ONLY_MODE = process.env.GITLAB_READ_ONLY_MODE === "true";
|
|
50
50
|
const USE_GITLAB_WIKI = process.env.USE_GITLAB_WIKI === "true";
|
|
51
|
+
const USE_MILESTONE = process.env.USE_MILESTONE === "true";
|
|
51
52
|
// Add proxy configuration
|
|
52
53
|
const HTTP_PROXY = process.env.HTTP_PROXY;
|
|
53
54
|
const HTTPS_PROXY = process.env.HTTPS_PROXY;
|
|
@@ -343,6 +344,51 @@ const allTools = [
|
|
|
343
344
|
description: "List merge requests in a GitLab project with filtering options",
|
|
344
345
|
inputSchema: zodToJsonSchema(ListMergeRequestsSchema),
|
|
345
346
|
},
|
|
347
|
+
{
|
|
348
|
+
name: "list_milestones",
|
|
349
|
+
description: "List milestones in a GitLab project with filtering options",
|
|
350
|
+
inputSchema: zodToJsonSchema(ListProjectMilestonesSchema),
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
name: "get_milestone",
|
|
354
|
+
description: "Get details of a specific milestone",
|
|
355
|
+
inputSchema: zodToJsonSchema(GetProjectMilestoneSchema),
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
name: "create_milestone",
|
|
359
|
+
description: "Create a new milestone in a GitLab project",
|
|
360
|
+
inputSchema: zodToJsonSchema(CreateProjectMilestoneSchema),
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
name: "edit_milestone",
|
|
364
|
+
description: "Edit an existing milestone in a GitLab project",
|
|
365
|
+
inputSchema: zodToJsonSchema(EditProjectMilestoneSchema),
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
name: "delete_milestone",
|
|
369
|
+
description: "Delete a milestone from a GitLab project",
|
|
370
|
+
inputSchema: zodToJsonSchema(DeleteProjectMilestoneSchema),
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
name: "get_milestone_issue",
|
|
374
|
+
description: "Get issues associated with a specific milestone",
|
|
375
|
+
inputSchema: zodToJsonSchema(GetMilestoneIssuesSchema),
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
name: "get_milestone_merge_requests",
|
|
379
|
+
description: "Get merge requests associated with a specific milestone",
|
|
380
|
+
inputSchema: zodToJsonSchema(GetMilestoneMergeRequestsSchema),
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
name: "promote_milestone",
|
|
384
|
+
description: "Promote a milestone to the next stage",
|
|
385
|
+
inputSchema: zodToJsonSchema(PromoteProjectMilestoneSchema),
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
name: "get_milestone_burndown_events",
|
|
389
|
+
description: "Get burndown events for a specific milestone",
|
|
390
|
+
inputSchema: zodToJsonSchema(GetMilestoneBurndownEventsSchema),
|
|
391
|
+
},
|
|
346
392
|
];
|
|
347
393
|
// Define which tools are read-only
|
|
348
394
|
const readOnlyTools = [
|
|
@@ -371,6 +417,13 @@ const readOnlyTools = [
|
|
|
371
417
|
"get_label",
|
|
372
418
|
"list_group_projects",
|
|
373
419
|
"get_repository_tree",
|
|
420
|
+
"list_milestones",
|
|
421
|
+
"get_milestone",
|
|
422
|
+
"get_milestone_issue",
|
|
423
|
+
"get_milestone_merge_requests",
|
|
424
|
+
"get_milestone_burndown_events",
|
|
425
|
+
"list_wiki_pages",
|
|
426
|
+
"get_wiki_page",
|
|
374
427
|
];
|
|
375
428
|
// Define which tools are related to wiki and can be toggled by USE_GITLAB_WIKI
|
|
376
429
|
const wikiToolNames = [
|
|
@@ -381,6 +434,18 @@ const wikiToolNames = [
|
|
|
381
434
|
"delete_wiki_page",
|
|
382
435
|
"upload_wiki_attachment",
|
|
383
436
|
];
|
|
437
|
+
// Define which tools are related to milestones and can be toggled by USE_MILESTONE
|
|
438
|
+
const milestoneToolNames = [
|
|
439
|
+
"list_milestones",
|
|
440
|
+
"get_milestone",
|
|
441
|
+
"create_milestone",
|
|
442
|
+
"edit_milestone",
|
|
443
|
+
"delete_milestone",
|
|
444
|
+
"get_milestone_issue",
|
|
445
|
+
"get_milestone_merge_requests",
|
|
446
|
+
"promote_milestone",
|
|
447
|
+
"get_milestone_burndown_events",
|
|
448
|
+
];
|
|
384
449
|
/**
|
|
385
450
|
* Smart URL handling for GitLab API
|
|
386
451
|
*
|
|
@@ -394,8 +459,7 @@ function normalizeGitLabApiUrl(url) {
|
|
|
394
459
|
// Remove trailing slash if present
|
|
395
460
|
let normalizedUrl = url.endsWith("/") ? url.slice(0, -1) : url;
|
|
396
461
|
// Check if URL already has /api/v4
|
|
397
|
-
if (!normalizedUrl.endsWith("/api/v4") &&
|
|
398
|
-
!normalizedUrl.endsWith("/api/v4/")) {
|
|
462
|
+
if (!normalizedUrl.endsWith("/api/v4") && !normalizedUrl.endsWith("/api/v4/")) {
|
|
399
463
|
// Append /api/v4 if not already present
|
|
400
464
|
normalizedUrl = `${normalizedUrl}/api/v4`;
|
|
401
465
|
}
|
|
@@ -418,8 +482,7 @@ async function handleGitLabError(response) {
|
|
|
418
482
|
if (!response.ok) {
|
|
419
483
|
const errorBody = await response.text();
|
|
420
484
|
// Check specifically for Rate Limit error
|
|
421
|
-
if (response.status === 403 &&
|
|
422
|
-
errorBody.includes("User API Key Rate limit exceeded")) {
|
|
485
|
+
if (response.status === 403 && errorBody.includes("User API Key Rate limit exceeded")) {
|
|
423
486
|
console.error("GitLab API Rate Limit Exceeded:", errorBody);
|
|
424
487
|
console.log("User API Key Rate limit exceeded. Please try again later.");
|
|
425
488
|
throw new Error(`GitLab API Rate Limit Exceeded: ${errorBody}`);
|
|
@@ -1045,7 +1108,7 @@ async function createTree(projectId, files, ref) {
|
|
|
1045
1108
|
...DEFAULT_FETCH_CONFIG,
|
|
1046
1109
|
method: "POST",
|
|
1047
1110
|
body: JSON.stringify({
|
|
1048
|
-
files: files.map(
|
|
1111
|
+
files: files.map(file => ({
|
|
1049
1112
|
file_path: file.path,
|
|
1050
1113
|
content: file.content,
|
|
1051
1114
|
encoding: "text",
|
|
@@ -1082,7 +1145,7 @@ async function createCommit(projectId, message, branch, actions) {
|
|
|
1082
1145
|
body: JSON.stringify({
|
|
1083
1146
|
branch,
|
|
1084
1147
|
commit_message: message,
|
|
1085
|
-
actions: actions.map(
|
|
1148
|
+
actions: actions.map(action => ({
|
|
1086
1149
|
action: "create",
|
|
1087
1150
|
file_path: action.path,
|
|
1088
1151
|
content: action.content,
|
|
@@ -1830,21 +1893,184 @@ async function getRepositoryTree(options) {
|
|
|
1830
1893
|
const data = await response.json();
|
|
1831
1894
|
return z.array(GitLabTreeItemSchema).parse(data);
|
|
1832
1895
|
}
|
|
1896
|
+
/**
|
|
1897
|
+
* List project milestones in a GitLab project
|
|
1898
|
+
* @param {string} projectId - The ID or URL-encoded path of the project
|
|
1899
|
+
* @param {Object} options - Options for listing milestones
|
|
1900
|
+
* @returns {Promise<GitLabMilestones[]>} List of milestones
|
|
1901
|
+
*/
|
|
1902
|
+
async function listProjectMilestones(projectId, options) {
|
|
1903
|
+
projectId = decodeURIComponent(projectId);
|
|
1904
|
+
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones`);
|
|
1905
|
+
Object.entries(options).forEach(([key, value]) => {
|
|
1906
|
+
if (value !== undefined) {
|
|
1907
|
+
if (key === "iids" && Array.isArray(value) && value.length > 0) {
|
|
1908
|
+
value.forEach(iid => {
|
|
1909
|
+
url.searchParams.append("iids[]", iid.toString());
|
|
1910
|
+
});
|
|
1911
|
+
}
|
|
1912
|
+
else if (value !== undefined) {
|
|
1913
|
+
url.searchParams.append(key, value.toString());
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
});
|
|
1917
|
+
const response = await fetch(url.toString(), {
|
|
1918
|
+
...DEFAULT_FETCH_CONFIG,
|
|
1919
|
+
});
|
|
1920
|
+
await handleGitLabError(response);
|
|
1921
|
+
const data = await response.json();
|
|
1922
|
+
return z.array(GitLabMilestonesSchema).parse(data);
|
|
1923
|
+
}
|
|
1924
|
+
/**
|
|
1925
|
+
* Get a single milestone in a GitLab project
|
|
1926
|
+
* @param {string} projectId - The ID or URL-encoded path of the project
|
|
1927
|
+
* @param {number} milestoneId - The ID of the milestone
|
|
1928
|
+
* @returns {Promise<GitLabMilestones>} Milestone details
|
|
1929
|
+
*/
|
|
1930
|
+
async function getProjectMilestone(projectId, milestoneId) {
|
|
1931
|
+
projectId = decodeURIComponent(projectId);
|
|
1932
|
+
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones/${milestoneId}`);
|
|
1933
|
+
const response = await fetch(url.toString(), {
|
|
1934
|
+
...DEFAULT_FETCH_CONFIG,
|
|
1935
|
+
});
|
|
1936
|
+
await handleGitLabError(response);
|
|
1937
|
+
const data = await response.json();
|
|
1938
|
+
return GitLabMilestonesSchema.parse(data);
|
|
1939
|
+
}
|
|
1940
|
+
/**
|
|
1941
|
+
* Create a new milestone in a GitLab project
|
|
1942
|
+
* @param {string} projectId - The ID or URL-encoded path of the project
|
|
1943
|
+
* @param {Object} options - Options for creating a milestone
|
|
1944
|
+
* @returns {Promise<GitLabMilestones>} Created milestone
|
|
1945
|
+
*/
|
|
1946
|
+
async function createProjectMilestone(projectId, options) {
|
|
1947
|
+
projectId = decodeURIComponent(projectId);
|
|
1948
|
+
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones`);
|
|
1949
|
+
const response = await fetch(url.toString(), {
|
|
1950
|
+
...DEFAULT_FETCH_CONFIG,
|
|
1951
|
+
method: "POST",
|
|
1952
|
+
body: JSON.stringify(options),
|
|
1953
|
+
});
|
|
1954
|
+
await handleGitLabError(response);
|
|
1955
|
+
const data = await response.json();
|
|
1956
|
+
return GitLabMilestonesSchema.parse(data);
|
|
1957
|
+
}
|
|
1958
|
+
/**
|
|
1959
|
+
* Edit an existing milestone in a GitLab project
|
|
1960
|
+
* @param {string} projectId - The ID or URL-encoded path of the project
|
|
1961
|
+
* @param {number} milestoneId - The ID of the milestone
|
|
1962
|
+
* @param {Object} options - Options for editing a milestone
|
|
1963
|
+
* @returns {Promise<GitLabMilestones>} Updated milestone
|
|
1964
|
+
*/
|
|
1965
|
+
async function editProjectMilestone(projectId, milestoneId, options) {
|
|
1966
|
+
projectId = decodeURIComponent(projectId);
|
|
1967
|
+
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones/${milestoneId}`);
|
|
1968
|
+
const response = await fetch(url.toString(), {
|
|
1969
|
+
...DEFAULT_FETCH_CONFIG,
|
|
1970
|
+
method: "PUT",
|
|
1971
|
+
body: JSON.stringify(options),
|
|
1972
|
+
});
|
|
1973
|
+
await handleGitLabError(response);
|
|
1974
|
+
const data = await response.json();
|
|
1975
|
+
return GitLabMilestonesSchema.parse(data);
|
|
1976
|
+
}
|
|
1977
|
+
/**
|
|
1978
|
+
* Delete a milestone from a GitLab project
|
|
1979
|
+
* @param {string} projectId - The ID or URL-encoded path of the project
|
|
1980
|
+
* @param {number} milestoneId - The ID of the milestone
|
|
1981
|
+
* @returns {Promise<void>}
|
|
1982
|
+
*/
|
|
1983
|
+
async function deleteProjectMilestone(projectId, milestoneId) {
|
|
1984
|
+
projectId = decodeURIComponent(projectId);
|
|
1985
|
+
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones/${milestoneId}`);
|
|
1986
|
+
const response = await fetch(url.toString(), {
|
|
1987
|
+
...DEFAULT_FETCH_CONFIG,
|
|
1988
|
+
method: "DELETE",
|
|
1989
|
+
});
|
|
1990
|
+
await handleGitLabError(response);
|
|
1991
|
+
}
|
|
1992
|
+
/**
|
|
1993
|
+
* Get all issues assigned to a single milestone
|
|
1994
|
+
* @param {string} projectId - The ID or URL-encoded path of the project
|
|
1995
|
+
* @param {number} milestoneId - The ID of the milestone
|
|
1996
|
+
* @returns {Promise<GitLabIssue[]>} List of issues
|
|
1997
|
+
*/
|
|
1998
|
+
async function getMilestoneIssues(projectId, milestoneId) {
|
|
1999
|
+
projectId = decodeURIComponent(projectId);
|
|
2000
|
+
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones/${milestoneId}/issues`);
|
|
2001
|
+
const response = await fetch(url.toString(), {
|
|
2002
|
+
...DEFAULT_FETCH_CONFIG,
|
|
2003
|
+
});
|
|
2004
|
+
await handleGitLabError(response);
|
|
2005
|
+
const data = await response.json();
|
|
2006
|
+
return z.array(GitLabIssueSchema).parse(data);
|
|
2007
|
+
}
|
|
2008
|
+
/**
|
|
2009
|
+
* Get all merge requests assigned to a single milestone
|
|
2010
|
+
* @param {string} projectId - The ID or URL-encoded path of the project
|
|
2011
|
+
* @param {number} milestoneId - The ID of the milestone
|
|
2012
|
+
* @returns {Promise<GitLabMergeRequest[]>} List of merge requests
|
|
2013
|
+
*/
|
|
2014
|
+
async function getMilestoneMergeRequests(projectId, milestoneId) {
|
|
2015
|
+
projectId = decodeURIComponent(projectId);
|
|
2016
|
+
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones/${milestoneId}/merge_requests`);
|
|
2017
|
+
const response = await fetch(url.toString(), {
|
|
2018
|
+
...DEFAULT_FETCH_CONFIG,
|
|
2019
|
+
});
|
|
2020
|
+
await handleGitLabError(response);
|
|
2021
|
+
const data = await response.json();
|
|
2022
|
+
return z.array(GitLabMergeRequestSchema).parse(data);
|
|
2023
|
+
}
|
|
2024
|
+
/**
|
|
2025
|
+
* Promote a project milestone to a group milestone
|
|
2026
|
+
* @param {string} projectId - The ID or URL-encoded path of the project
|
|
2027
|
+
* @param {number} milestoneId - The ID of the milestone
|
|
2028
|
+
* @returns {Promise<GitLabMilestones>} Promoted milestone
|
|
2029
|
+
*/
|
|
2030
|
+
async function promoteProjectMilestone(projectId, milestoneId) {
|
|
2031
|
+
projectId = decodeURIComponent(projectId);
|
|
2032
|
+
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones/${milestoneId}/promote`);
|
|
2033
|
+
const response = await fetch(url.toString(), {
|
|
2034
|
+
...DEFAULT_FETCH_CONFIG,
|
|
2035
|
+
method: "POST",
|
|
2036
|
+
});
|
|
2037
|
+
await handleGitLabError(response);
|
|
2038
|
+
const data = await response.json();
|
|
2039
|
+
return GitLabMilestonesSchema.parse(data);
|
|
2040
|
+
}
|
|
2041
|
+
/**
|
|
2042
|
+
* Get all burndown chart events for a single milestone
|
|
2043
|
+
* @param {string} projectId - The ID or URL-encoded path of the project
|
|
2044
|
+
* @param {number} milestoneId - The ID of the milestone
|
|
2045
|
+
* @returns {Promise<any[]>} Burndown chart events
|
|
2046
|
+
*/
|
|
2047
|
+
async function getMilestoneBurndownEvents(projectId, milestoneId) {
|
|
2048
|
+
projectId = decodeURIComponent(projectId);
|
|
2049
|
+
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/milestones/${milestoneId}/burndown_events`);
|
|
2050
|
+
const response = await fetch(url.toString(), {
|
|
2051
|
+
...DEFAULT_FETCH_CONFIG,
|
|
2052
|
+
});
|
|
2053
|
+
await handleGitLabError(response);
|
|
2054
|
+
const data = await response.json();
|
|
2055
|
+
return data;
|
|
2056
|
+
}
|
|
1833
2057
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
1834
2058
|
// Apply read-only filter first
|
|
1835
2059
|
const tools0 = GITLAB_READ_ONLY_MODE
|
|
1836
|
-
? allTools.filter(
|
|
2060
|
+
? allTools.filter(tool => readOnlyTools.includes(tool.name))
|
|
1837
2061
|
: allTools;
|
|
1838
2062
|
// Toggle wiki tools by USE_GITLAB_WIKI flag
|
|
1839
|
-
|
|
2063
|
+
const tools1 = USE_GITLAB_WIKI
|
|
1840
2064
|
? tools0
|
|
1841
|
-
: tools0.filter(
|
|
2065
|
+
: tools0.filter(tool => !wikiToolNames.includes(tool.name));
|
|
2066
|
+
// Toggle milestone tools by USE_MILESTONE flag
|
|
2067
|
+
let tools = USE_MILESTONE
|
|
2068
|
+
? tools1
|
|
2069
|
+
: tools1.filter(tool => !milestoneToolNames.includes(tool.name));
|
|
1842
2070
|
// <<< START: Gemini 호환성을 위해 $schema 제거 >>>
|
|
1843
|
-
tools = tools.map(
|
|
2071
|
+
tools = tools.map(tool => {
|
|
1844
2072
|
// inputSchema가 존재하고 객체인지 확인
|
|
1845
|
-
if (tool.inputSchema &&
|
|
1846
|
-
typeof tool.inputSchema === "object" &&
|
|
1847
|
-
tool.inputSchema !== null) {
|
|
2073
|
+
if (tool.inputSchema && typeof tool.inputSchema === "object" && tool.inputSchema !== null) {
|
|
1848
2074
|
// $schema 키가 존재하면 삭제
|
|
1849
2075
|
if ("$schema" in tool.inputSchema) {
|
|
1850
2076
|
// 불변성을 위해 새로운 객체 생성 (선택적이지만 권장)
|
|
@@ -1872,9 +2098,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1872
2098
|
try {
|
|
1873
2099
|
const forkedProject = await forkProject(forkArgs.project_id, forkArgs.namespace);
|
|
1874
2100
|
return {
|
|
1875
|
-
content: [
|
|
1876
|
-
{ type: "text", text: JSON.stringify(forkedProject, null, 2) },
|
|
1877
|
-
],
|
|
2101
|
+
content: [{ type: "text", text: JSON.stringify(forkedProject, null, 2) }],
|
|
1878
2102
|
};
|
|
1879
2103
|
}
|
|
1880
2104
|
catch (forkError) {
|
|
@@ -1918,9 +2142,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1918
2142
|
const args = CreateRepositorySchema.parse(request.params.arguments);
|
|
1919
2143
|
const repository = await createRepository(args);
|
|
1920
2144
|
return {
|
|
1921
|
-
content: [
|
|
1922
|
-
{ type: "text", text: JSON.stringify(repository, null, 2) },
|
|
1923
|
-
],
|
|
2145
|
+
content: [{ type: "text", text: JSON.stringify(repository, null, 2) }],
|
|
1924
2146
|
};
|
|
1925
2147
|
}
|
|
1926
2148
|
case "get_file_contents": {
|
|
@@ -1939,7 +2161,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1939
2161
|
}
|
|
1940
2162
|
case "push_files": {
|
|
1941
2163
|
const args = PushFilesSchema.parse(request.params.arguments);
|
|
1942
|
-
const result = await createCommit(args.project_id, args.commit_message, args.branch, args.files.map(
|
|
2164
|
+
const result = await createCommit(args.project_id, args.commit_message, args.branch, args.files.map(f => ({ path: f.file_path, content: f.content })));
|
|
1943
2165
|
return {
|
|
1944
2166
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1945
2167
|
};
|
|
@@ -1957,9 +2179,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1957
2179
|
const { project_id, ...options } = args;
|
|
1958
2180
|
const mergeRequest = await createMergeRequest(project_id, options);
|
|
1959
2181
|
return {
|
|
1960
|
-
content: [
|
|
1961
|
-
{ type: "text", text: JSON.stringify(mergeRequest, null, 2) },
|
|
1962
|
-
],
|
|
2182
|
+
content: [{ type: "text", text: JSON.stringify(mergeRequest, null, 2) }],
|
|
1963
2183
|
};
|
|
1964
2184
|
}
|
|
1965
2185
|
case "update_merge_request_note": {
|
|
@@ -1996,9 +2216,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1996
2216
|
const args = GetMergeRequestSchema.parse(request.params.arguments);
|
|
1997
2217
|
const mergeRequest = await getMergeRequest(args.project_id, args.merge_request_iid, args.source_branch);
|
|
1998
2218
|
return {
|
|
1999
|
-
content: [
|
|
2000
|
-
{ type: "text", text: JSON.stringify(mergeRequest, null, 2) },
|
|
2001
|
-
],
|
|
2219
|
+
content: [{ type: "text", text: JSON.stringify(mergeRequest, null, 2) }],
|
|
2002
2220
|
};
|
|
2003
2221
|
}
|
|
2004
2222
|
case "get_merge_request_diffs": {
|
|
@@ -2013,18 +2231,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2013
2231
|
const { project_id, merge_request_iid, source_branch, ...options } = args;
|
|
2014
2232
|
const mergeRequest = await updateMergeRequest(project_id, options, merge_request_iid, source_branch);
|
|
2015
2233
|
return {
|
|
2016
|
-
content: [
|
|
2017
|
-
{ type: "text", text: JSON.stringify(mergeRequest, null, 2) },
|
|
2018
|
-
],
|
|
2234
|
+
content: [{ type: "text", text: JSON.stringify(mergeRequest, null, 2) }],
|
|
2019
2235
|
};
|
|
2020
2236
|
}
|
|
2021
2237
|
case "mr_discussions": {
|
|
2022
2238
|
const args = ListMergeRequestDiscussionsSchema.parse(request.params.arguments);
|
|
2023
2239
|
const discussions = await listMergeRequestDiscussions(args.project_id, args.merge_request_iid);
|
|
2024
2240
|
return {
|
|
2025
|
-
content: [
|
|
2026
|
-
{ type: "text", text: JSON.stringify(discussions, null, 2) },
|
|
2027
|
-
],
|
|
2241
|
+
content: [{ type: "text", text: JSON.stringify(discussions, null, 2) }],
|
|
2028
2242
|
};
|
|
2029
2243
|
}
|
|
2030
2244
|
case "list_namespaces": {
|
|
@@ -2049,9 +2263,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2049
2263
|
const data = await response.json();
|
|
2050
2264
|
const namespaces = z.array(GitLabNamespaceSchema).parse(data);
|
|
2051
2265
|
return {
|
|
2052
|
-
content: [
|
|
2053
|
-
{ type: "text", text: JSON.stringify(namespaces, null, 2) },
|
|
2054
|
-
],
|
|
2266
|
+
content: [{ type: "text", text: JSON.stringify(namespaces, null, 2) }],
|
|
2055
2267
|
};
|
|
2056
2268
|
}
|
|
2057
2269
|
case "get_namespace": {
|
|
@@ -2077,9 +2289,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2077
2289
|
const data = await response.json();
|
|
2078
2290
|
const namespaceExists = GitLabNamespaceExistsResponseSchema.parse(data);
|
|
2079
2291
|
return {
|
|
2080
|
-
content: [
|
|
2081
|
-
{ type: "text", text: JSON.stringify(namespaceExists, null, 2) },
|
|
2082
|
-
],
|
|
2292
|
+
content: [{ type: "text", text: JSON.stringify(namespaceExists, null, 2) }],
|
|
2083
2293
|
};
|
|
2084
2294
|
}
|
|
2085
2295
|
case "get_project": {
|
|
@@ -2165,9 +2375,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2165
2375
|
const { project_id, issue_iid, ...options } = args;
|
|
2166
2376
|
const discussions = await listIssueDiscussions(project_id, issue_iid, options);
|
|
2167
2377
|
return {
|
|
2168
|
-
content: [
|
|
2169
|
-
{ type: "text", text: JSON.stringify(discussions, null, 2) },
|
|
2170
|
-
],
|
|
2378
|
+
content: [{ type: "text", text: JSON.stringify(discussions, null, 2) }],
|
|
2171
2379
|
};
|
|
2172
2380
|
}
|
|
2173
2381
|
case "get_issue_link": {
|
|
@@ -2360,6 +2568,117 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2360
2568
|
content: [{ type: "text", text: JSON.stringify(mergeRequests, null, 2) }],
|
|
2361
2569
|
};
|
|
2362
2570
|
}
|
|
2571
|
+
case "list_milestones": {
|
|
2572
|
+
const { project_id, ...options } = ListProjectMilestonesSchema.parse(request.params.arguments);
|
|
2573
|
+
const milestones = await listProjectMilestones(project_id, options);
|
|
2574
|
+
return {
|
|
2575
|
+
content: [
|
|
2576
|
+
{
|
|
2577
|
+
type: "text",
|
|
2578
|
+
text: JSON.stringify(milestones, null, 2),
|
|
2579
|
+
},
|
|
2580
|
+
],
|
|
2581
|
+
};
|
|
2582
|
+
}
|
|
2583
|
+
case "get_milestone": {
|
|
2584
|
+
const { project_id, milestone_id } = GetProjectMilestoneSchema.parse(request.params.arguments);
|
|
2585
|
+
const milestone = await getProjectMilestone(project_id, milestone_id);
|
|
2586
|
+
return {
|
|
2587
|
+
content: [
|
|
2588
|
+
{
|
|
2589
|
+
type: "text",
|
|
2590
|
+
text: JSON.stringify(milestone, null, 2),
|
|
2591
|
+
},
|
|
2592
|
+
],
|
|
2593
|
+
};
|
|
2594
|
+
}
|
|
2595
|
+
case "create_milestone": {
|
|
2596
|
+
const { project_id, ...options } = CreateProjectMilestoneSchema.parse(request.params.arguments);
|
|
2597
|
+
const milestone = await createProjectMilestone(project_id, options);
|
|
2598
|
+
return {
|
|
2599
|
+
content: [
|
|
2600
|
+
{
|
|
2601
|
+
type: "text",
|
|
2602
|
+
text: JSON.stringify(milestone, null, 2),
|
|
2603
|
+
},
|
|
2604
|
+
],
|
|
2605
|
+
};
|
|
2606
|
+
}
|
|
2607
|
+
case "edit_milestone": {
|
|
2608
|
+
const { project_id, milestone_id, ...options } = EditProjectMilestoneSchema.parse(request.params.arguments);
|
|
2609
|
+
const milestone = await editProjectMilestone(project_id, milestone_id, options);
|
|
2610
|
+
return {
|
|
2611
|
+
content: [
|
|
2612
|
+
{
|
|
2613
|
+
type: "text",
|
|
2614
|
+
text: JSON.stringify(milestone, null, 2),
|
|
2615
|
+
},
|
|
2616
|
+
],
|
|
2617
|
+
};
|
|
2618
|
+
}
|
|
2619
|
+
case "delete_milestone": {
|
|
2620
|
+
const { project_id, milestone_id } = DeleteProjectMilestoneSchema.parse(request.params.arguments);
|
|
2621
|
+
await deleteProjectMilestone(project_id, milestone_id);
|
|
2622
|
+
return {
|
|
2623
|
+
content: [
|
|
2624
|
+
{
|
|
2625
|
+
type: "text",
|
|
2626
|
+
text: JSON.stringify({
|
|
2627
|
+
status: "success",
|
|
2628
|
+
message: "Milestone deleted successfully",
|
|
2629
|
+
}, null, 2),
|
|
2630
|
+
},
|
|
2631
|
+
],
|
|
2632
|
+
};
|
|
2633
|
+
}
|
|
2634
|
+
case "get_milestone_issue": {
|
|
2635
|
+
const { project_id, milestone_id } = GetMilestoneIssuesSchema.parse(request.params.arguments);
|
|
2636
|
+
const issues = await getMilestoneIssues(project_id, milestone_id);
|
|
2637
|
+
return {
|
|
2638
|
+
content: [
|
|
2639
|
+
{
|
|
2640
|
+
type: "text",
|
|
2641
|
+
text: JSON.stringify(issues, null, 2),
|
|
2642
|
+
},
|
|
2643
|
+
],
|
|
2644
|
+
};
|
|
2645
|
+
}
|
|
2646
|
+
case "get_milestone_merge_requests": {
|
|
2647
|
+
const { project_id, milestone_id } = GetMilestoneMergeRequestsSchema.parse(request.params.arguments);
|
|
2648
|
+
const mergeRequests = await getMilestoneMergeRequests(project_id, milestone_id);
|
|
2649
|
+
return {
|
|
2650
|
+
content: [
|
|
2651
|
+
{
|
|
2652
|
+
type: "text",
|
|
2653
|
+
text: JSON.stringify(mergeRequests, null, 2),
|
|
2654
|
+
},
|
|
2655
|
+
],
|
|
2656
|
+
};
|
|
2657
|
+
}
|
|
2658
|
+
case "promote_milestone": {
|
|
2659
|
+
const { project_id, milestone_id } = PromoteProjectMilestoneSchema.parse(request.params.arguments);
|
|
2660
|
+
const milestone = await promoteProjectMilestone(project_id, milestone_id);
|
|
2661
|
+
return {
|
|
2662
|
+
content: [
|
|
2663
|
+
{
|
|
2664
|
+
type: "text",
|
|
2665
|
+
text: JSON.stringify(milestone, null, 2),
|
|
2666
|
+
},
|
|
2667
|
+
],
|
|
2668
|
+
};
|
|
2669
|
+
}
|
|
2670
|
+
case "get_milestone_burndown_events": {
|
|
2671
|
+
const { project_id, milestone_id } = GetMilestoneBurndownEventsSchema.parse(request.params.arguments);
|
|
2672
|
+
const events = await getMilestoneBurndownEvents(project_id, milestone_id);
|
|
2673
|
+
return {
|
|
2674
|
+
content: [
|
|
2675
|
+
{
|
|
2676
|
+
type: "text",
|
|
2677
|
+
text: JSON.stringify(events, null, 2),
|
|
2678
|
+
},
|
|
2679
|
+
],
|
|
2680
|
+
};
|
|
2681
|
+
}
|
|
2363
2682
|
default:
|
|
2364
2683
|
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
2365
2684
|
}
|
|
@@ -2367,7 +2686,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2367
2686
|
catch (error) {
|
|
2368
2687
|
if (error instanceof z.ZodError) {
|
|
2369
2688
|
throw new Error(`Invalid arguments: ${error.errors
|
|
2370
|
-
.map(
|
|
2689
|
+
.map(e => `${e.path.join(".")}: ${e.message}`)
|
|
2371
2690
|
.join(", ")}`);
|
|
2372
2691
|
}
|
|
2373
2692
|
throw error;
|
|
@@ -2392,7 +2711,7 @@ async function runServer() {
|
|
|
2392
2711
|
process.exit(1);
|
|
2393
2712
|
}
|
|
2394
2713
|
}
|
|
2395
|
-
runServer().catch(
|
|
2714
|
+
runServer().catch(error => {
|
|
2396
2715
|
console.error("Fatal error in main():", error);
|
|
2397
2716
|
process.exit(1);
|
|
2398
2717
|
});
|