@zereight/mcp-gitlab 2.0.6 → 2.0.7
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/build/index.js +55 -1
- package/build/schemas.js +9 -2
- package/package.json +1 -1
- package/build/test/comprehensive-mcp-tests.js +0 -378
- package/build/test/simple-mcp-tests.js +0 -190
package/build/index.js
CHANGED
|
@@ -29,7 +29,7 @@ GitLabDiscussionNoteSchema, // Added
|
|
|
29
29
|
GitLabDiscussionSchema,
|
|
30
30
|
// Draft Notes Schemas
|
|
31
31
|
GitLabDraftNoteSchema, GitLabForkSchema, GitLabIssueLinkSchema, GitLabIssueSchema, GitLabIssueWithLinkDetailsSchema, GitLabMarkdownUploadSchema, GitLabMergeRequestSchema, GitLabMilestonesSchema, GitLabNamespaceExistsResponseSchema, GitLabNamespaceSchema, GitLabPipelineJobSchema, GitLabPipelineSchema, GitLabPipelineTriggerJobSchema, GitLabProjectMemberSchema, GitLabProjectSchema, GitLabReferenceSchema, GitLabRepositorySchema, GitLabSearchResponseSchema, GitLabTreeItemSchema, GitLabTreeSchema, GitLabUserSchema, GitLabUsersResponseSchema, GitLabWikiPageSchema, GroupIteration, ListCommitsSchema, ListDraftNotesSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListLabelsSchema, ListMergeRequestDiffsSchema, // Added
|
|
32
|
-
ListMergeRequestDiscussionsSchema, ListMergeRequestsSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelinesSchema, ListPipelineTriggerJobsSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListWikiPagesSchema, MarkdownUploadSchema, DownloadAttachmentSchema, MergeMergeRequestSchema, MyIssuesSchema, PaginatedDiscussionsResponseSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PlayPipelineJobSchema, PushFilesSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchRepositoriesSchema, UpdateDraftNoteSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateLabelSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestSchema, UpdateWikiPageSchema, VerifyNamespaceSchema, GitLabEventSchema, ListEventsSchema, GetProjectEventsSchema } from "./schemas.js";
|
|
32
|
+
ListMergeRequestDiscussionsSchema, ListMergeRequestsSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelinesSchema, ListPipelineTriggerJobsSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListWikiPagesSchema, MarkdownUploadSchema, DownloadAttachmentSchema, MergeMergeRequestSchema, MyIssuesSchema, PaginatedDiscussionsResponseSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PlayPipelineJobSchema, PushFilesSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchRepositoriesSchema, UpdateDraftNoteSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateLabelSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestSchema, UpdateWikiPageSchema, VerifyNamespaceSchema, GitLabEventSchema, ListEventsSchema, GetProjectEventsSchema, ExecuteGraphQLSchema } from "./schemas.js";
|
|
33
33
|
import { randomUUID } from "crypto";
|
|
34
34
|
import { pino } from "pino";
|
|
35
35
|
const logger = pino({
|
|
@@ -221,6 +221,11 @@ const allTools = [
|
|
|
221
221
|
description: "Merge a merge request in a GitLab project",
|
|
222
222
|
inputSchema: toJSONSchema(MergeMergeRequestSchema),
|
|
223
223
|
},
|
|
224
|
+
{
|
|
225
|
+
name: "execute_graphql",
|
|
226
|
+
description: "Execute a GitLab GraphQL query",
|
|
227
|
+
inputSchema: zodToJsonSchema(ExecuteGraphQLSchema),
|
|
228
|
+
},
|
|
224
229
|
{
|
|
225
230
|
name: "create_or_update_file",
|
|
226
231
|
description: "Create or update a single file in a GitLab project",
|
|
@@ -660,6 +665,7 @@ const allTools = [
|
|
|
660
665
|
// Define which tools are read-only
|
|
661
666
|
const readOnlyTools = [
|
|
662
667
|
"search_repositories",
|
|
668
|
+
"execute_graphql",
|
|
663
669
|
"get_file_contents",
|
|
664
670
|
"get_merge_request",
|
|
665
671
|
"get_merge_request_diffs",
|
|
@@ -3308,6 +3314,54 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3308
3314
|
}
|
|
3309
3315
|
logger.info(request.params.name);
|
|
3310
3316
|
switch (request.params.name) {
|
|
3317
|
+
case "execute_graphql": {
|
|
3318
|
+
const args = ExecuteGraphQLSchema.parse(request.params.arguments);
|
|
3319
|
+
const apiUrl = new URL(GITLAB_API_URL);
|
|
3320
|
+
// Build GraphQL endpoint preserving any instance subpath (e.g. /gitlab)
|
|
3321
|
+
const restPath = apiUrl.pathname || ""; // e.g. /api/v4 or /gitlab/api/v4
|
|
3322
|
+
const idx = restPath.lastIndexOf("/api/v4");
|
|
3323
|
+
const prefix = idx >= 0 ? restPath.slice(0, idx) : "";
|
|
3324
|
+
const graphqlUrl = process.env.GITLAB_GRAPHQL_URL || `${apiUrl.origin}${prefix}/api/graphql`;
|
|
3325
|
+
// Add timeout to avoid hanging requests
|
|
3326
|
+
const controller = new AbortController();
|
|
3327
|
+
const timeoutMs = 45000;
|
|
3328
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
3329
|
+
logger.info({ endpoint: graphqlUrl }, "execute_graphql request");
|
|
3330
|
+
try {
|
|
3331
|
+
const response = await fetch(graphqlUrl, {
|
|
3332
|
+
...DEFAULT_FETCH_CONFIG,
|
|
3333
|
+
method: "POST",
|
|
3334
|
+
headers: {
|
|
3335
|
+
...DEFAULT_HEADERS,
|
|
3336
|
+
"Content-Type": "application/json",
|
|
3337
|
+
Accept: "application/json",
|
|
3338
|
+
},
|
|
3339
|
+
body: JSON.stringify({ query: args.query, variables: args.variables || {} }),
|
|
3340
|
+
signal: controller.signal,
|
|
3341
|
+
});
|
|
3342
|
+
if (!response.ok) {
|
|
3343
|
+
await handleGitLabError(response);
|
|
3344
|
+
}
|
|
3345
|
+
const json = await response.json();
|
|
3346
|
+
return {
|
|
3347
|
+
content: [{ type: "text", text: JSON.stringify(json, null, 2) }],
|
|
3348
|
+
};
|
|
3349
|
+
}
|
|
3350
|
+
catch (err) {
|
|
3351
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3352
|
+
return {
|
|
3353
|
+
content: [
|
|
3354
|
+
{
|
|
3355
|
+
type: "text",
|
|
3356
|
+
text: JSON.stringify({ error: `GraphQL request failed: ${message}` }, null, 2),
|
|
3357
|
+
},
|
|
3358
|
+
],
|
|
3359
|
+
};
|
|
3360
|
+
}
|
|
3361
|
+
finally {
|
|
3362
|
+
clearTimeout(timeout);
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3311
3365
|
case "fork_repository": {
|
|
3312
3366
|
if (GITLAB_PROJECT_ID) {
|
|
3313
3367
|
throw new Error("Direct project ID is set. So fork_repository is not allowed");
|
package/build/schemas.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { flexibleBoolean } from "./customSchemas.js";
|
|
3
2
|
// Base schemas for common types
|
|
4
3
|
export const GitLabAuthorSchema = z.object({
|
|
5
4
|
name: z.string(),
|
|
@@ -1600,7 +1599,7 @@ export const GetCommitSchema = z.object({
|
|
|
1600
1599
|
export const GetCommitDiffSchema = z.object({
|
|
1601
1600
|
project_id: z.coerce.string().describe("Project ID or complete URL-encoded path to project"),
|
|
1602
1601
|
sha: z.string().describe("The commit hash or name of a repository branch or tag"),
|
|
1603
|
-
full_diff:
|
|
1602
|
+
full_diff: z.boolean().optional().describe("Whether to return the full diff or only first page (default: false)"),
|
|
1604
1603
|
});
|
|
1605
1604
|
// Schema for listing issues assigned to the current user
|
|
1606
1605
|
export const MyIssuesSchema = z.object({
|
|
@@ -1751,3 +1750,11 @@ export const GetProjectEventsSchema = z.object({
|
|
|
1751
1750
|
page: z.number().optional().describe("Returns the specified results page. Default: 1"),
|
|
1752
1751
|
per_page: z.number().optional().describe("Number of results per page. Default: 20"),
|
|
1753
1752
|
});
|
|
1753
|
+
// GraphQL generic execution schema
|
|
1754
|
+
export const ExecuteGraphQLSchema = z.object({
|
|
1755
|
+
query: z.string().describe("GraphQL query string"),
|
|
1756
|
+
variables: z
|
|
1757
|
+
.record(z.any())
|
|
1758
|
+
.optional()
|
|
1759
|
+
.describe("Variables object for the GraphQL query"),
|
|
1760
|
+
});
|
package/package.json
CHANGED
|
@@ -1,378 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ts-node
|
|
2
|
-
/**
|
|
3
|
-
* 포괄적인 GitLab MCP 도구 테스트 스크립트 (TypeScript)
|
|
4
|
-
* 모든 GitLab MCP 도구들을 자동으로 테스트합니다.
|
|
5
|
-
*/
|
|
6
|
-
import { spawn } from 'child_process';
|
|
7
|
-
import * as fs from 'fs';
|
|
8
|
-
// 환경 변수 설정
|
|
9
|
-
const GITLAB_API_URL = process.env.GITLAB_API_URL || "https://gitlab.com/api/v4";
|
|
10
|
-
const GITLAB_TOKEN = process.env.GITLAB_PERSONAL_ACCESS_TOKEN || process.env.GITLAB_TOKEN;
|
|
11
|
-
const TEST_PROJECT_ID = process.env.GITLAB_PROJECT_ID || process.env.TEST_PROJECT_ID;
|
|
12
|
-
// 테스트 결과 저장
|
|
13
|
-
const testResults = {
|
|
14
|
-
passed: 0,
|
|
15
|
-
failed: 0,
|
|
16
|
-
skipped: 0,
|
|
17
|
-
total: 0,
|
|
18
|
-
details: []
|
|
19
|
-
};
|
|
20
|
-
// 테스트 도구 목록 (GitLab MCP에서 제공하는 모든 도구들)
|
|
21
|
-
const mcpTools = [
|
|
22
|
-
// 프로젝트 관련
|
|
23
|
-
{ name: 'list_projects', category: 'project', required: false },
|
|
24
|
-
{ name: 'search_repositories', category: 'project', required: false },
|
|
25
|
-
{ name: 'get_project', category: 'project', required: true },
|
|
26
|
-
{ name: 'list_project_members', category: 'project', required: true },
|
|
27
|
-
{ name: 'list_group_projects', category: 'project', required: false },
|
|
28
|
-
// 이슈 관련
|
|
29
|
-
{ name: 'list_issues', category: 'issue', required: true },
|
|
30
|
-
{ name: 'my_issues', category: 'issue', required: false },
|
|
31
|
-
{ name: 'get_issue', category: 'issue', required: true },
|
|
32
|
-
{ name: 'list_issue_discussions', category: 'issue', required: true },
|
|
33
|
-
{ name: 'list_issue_links', category: 'issue', required: true },
|
|
34
|
-
// 머지 리퀘스트 관련
|
|
35
|
-
{ name: 'list_merge_requests', category: 'merge_request', required: true },
|
|
36
|
-
{ name: 'get_merge_request', category: 'merge_request', required: true },
|
|
37
|
-
{ name: 'get_merge_request_diffs', category: 'merge_request', required: true },
|
|
38
|
-
{ name: 'list_merge_request_diffs', category: 'merge_request', required: true },
|
|
39
|
-
{ name: 'get_branch_diffs', category: 'merge_request', required: true },
|
|
40
|
-
{ name: 'mr_discussions', category: 'merge_request', required: true },
|
|
41
|
-
// 파이프라인 관련
|
|
42
|
-
{ name: 'list_pipelines', category: 'pipeline', required: true },
|
|
43
|
-
{ name: 'get_pipeline', category: 'pipeline', required: true },
|
|
44
|
-
{ name: 'list_pipeline_jobs', category: 'pipeline', required: true },
|
|
45
|
-
{ name: 'list_pipeline_trigger_jobs', category: 'pipeline', required: true },
|
|
46
|
-
{ name: 'get_pipeline_job', category: 'pipeline', required: true },
|
|
47
|
-
{ name: 'get_pipeline_job_output', category: 'pipeline', required: true },
|
|
48
|
-
// 파일 관리
|
|
49
|
-
{ name: 'get_file_contents', category: 'file', required: true },
|
|
50
|
-
{ name: 'get_repository_tree', category: 'file', required: true },
|
|
51
|
-
// 커밋 관련
|
|
52
|
-
{ name: 'list_commits', category: 'commit', required: true },
|
|
53
|
-
{ name: 'get_commit', category: 'commit', required: true },
|
|
54
|
-
{ name: 'get_commit_diff', category: 'commit', required: true },
|
|
55
|
-
// 라벨 관련
|
|
56
|
-
{ name: 'list_labels', category: 'label', required: true },
|
|
57
|
-
{ name: 'get_label', category: 'label', required: true },
|
|
58
|
-
// 네임스페이스 관련
|
|
59
|
-
{ name: 'list_namespaces', category: 'namespace', required: false },
|
|
60
|
-
{ name: 'get_namespace', category: 'namespace', required: false },
|
|
61
|
-
{ name: 'verify_namespace', category: 'namespace', required: false },
|
|
62
|
-
// 사용자 관련
|
|
63
|
-
{ name: 'get_users', category: 'user', required: false },
|
|
64
|
-
// 이벤트 관련
|
|
65
|
-
{ name: 'list_events', category: 'event', required: false },
|
|
66
|
-
{ name: 'get_project_events', category: 'event', required: true },
|
|
67
|
-
// 마일스톤 관련 (선택적)
|
|
68
|
-
{ name: 'list_milestones', category: 'milestone', required: true },
|
|
69
|
-
{ name: 'get_milestone', category: 'milestone', required: true },
|
|
70
|
-
// 위키 관련 (선택적)
|
|
71
|
-
{ name: 'list_wiki_pages', category: 'wiki', required: true },
|
|
72
|
-
{ name: 'get_wiki_page', category: 'wiki', required: true },
|
|
73
|
-
// 그룹 이터레이션 관련
|
|
74
|
-
{ name: 'list_group_iterations', category: 'iteration', required: false }
|
|
75
|
-
];
|
|
76
|
-
// MCP 응답 파싱 함수
|
|
77
|
-
function parseMCPResponse(output) {
|
|
78
|
-
const lines = output.trim().split('\n');
|
|
79
|
-
for (const line of lines) {
|
|
80
|
-
if (line.startsWith('{')) {
|
|
81
|
-
try {
|
|
82
|
-
const result = JSON.parse(line);
|
|
83
|
-
if (result.result !== undefined) {
|
|
84
|
-
return result.result;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
catch (e) {
|
|
88
|
-
// JSON 파싱 실패 시 계속 시도
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return { success: true, raw: output };
|
|
93
|
-
}
|
|
94
|
-
// MCP 서버와 통신하는 함수
|
|
95
|
-
async function callMCPTool(toolName, parameters = {}) {
|
|
96
|
-
return new Promise((resolve, reject) => {
|
|
97
|
-
const mcpProcess = spawn('node', ['build/index.js'], {
|
|
98
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
99
|
-
env: {
|
|
100
|
-
...process.env,
|
|
101
|
-
GITLAB_PERSONAL_ACCESS_TOKEN: GITLAB_TOKEN,
|
|
102
|
-
GITLAB_API_URL: GITLAB_API_URL,
|
|
103
|
-
GITLAB_PROJECT_ID: TEST_PROJECT_ID
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
let output = '';
|
|
107
|
-
let errorOutput = '';
|
|
108
|
-
mcpProcess.stdout?.on('data', (data) => {
|
|
109
|
-
output += data.toString();
|
|
110
|
-
});
|
|
111
|
-
mcpProcess.stderr?.on('data', (data) => {
|
|
112
|
-
errorOutput += data.toString();
|
|
113
|
-
});
|
|
114
|
-
mcpProcess.on('close', (code) => {
|
|
115
|
-
if (code === 0) {
|
|
116
|
-
try {
|
|
117
|
-
const result = parseMCPResponse(output);
|
|
118
|
-
resolve(result);
|
|
119
|
-
}
|
|
120
|
-
catch (e) {
|
|
121
|
-
reject(new Error(`MCP 응답 파싱 실패: ${e.message}`));
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
reject(new Error(`MCP 프로세스 종료 코드 ${code}: ${errorOutput}`));
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
// MCP 요청 전송
|
|
129
|
-
const request = {
|
|
130
|
-
jsonrpc: "2.0",
|
|
131
|
-
id: 1,
|
|
132
|
-
method: "tools/call",
|
|
133
|
-
params: {
|
|
134
|
-
name: toolName,
|
|
135
|
-
arguments: parameters
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
mcpProcess.stdin?.write(JSON.stringify(request) + '\n');
|
|
139
|
-
mcpProcess.stdin?.end();
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
// 도구별 파라미터 설정 함수
|
|
143
|
-
async function setupToolParameters(tool) {
|
|
144
|
-
let parameters = {};
|
|
145
|
-
if (tool.required && TEST_PROJECT_ID) {
|
|
146
|
-
parameters.project_id = TEST_PROJECT_ID;
|
|
147
|
-
}
|
|
148
|
-
// 특정 도구들의 추가 파라미터 설정
|
|
149
|
-
switch (tool.name) {
|
|
150
|
-
case 'get_issue':
|
|
151
|
-
if (TEST_PROJECT_ID) {
|
|
152
|
-
const listResult = await callMCPTool('list_issues', parameters);
|
|
153
|
-
if (Array.isArray(listResult) && listResult.length > 0) {
|
|
154
|
-
parameters.issue_iid = listResult[0].iid;
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
throw new Error('No issues found to test with');
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
break;
|
|
161
|
-
case 'get_merge_request':
|
|
162
|
-
if (TEST_PROJECT_ID) {
|
|
163
|
-
const listResult = await callMCPTool('list_merge_requests', parameters);
|
|
164
|
-
if (Array.isArray(listResult) && listResult.length > 0) {
|
|
165
|
-
parameters.merge_request_iid = listResult[0].iid;
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
throw new Error('No merge requests found to test with');
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
break;
|
|
172
|
-
case 'get_pipeline':
|
|
173
|
-
if (TEST_PROJECT_ID) {
|
|
174
|
-
const pipelinesResult = await callMCPTool('list_pipelines', parameters);
|
|
175
|
-
if (Array.isArray(pipelinesResult) && pipelinesResult.length > 0) {
|
|
176
|
-
parameters.pipeline_id = pipelinesResult[0].id;
|
|
177
|
-
}
|
|
178
|
-
else {
|
|
179
|
-
throw new Error('No pipelines found to test with');
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
break;
|
|
183
|
-
case 'get_pipeline_job':
|
|
184
|
-
if (TEST_PROJECT_ID) {
|
|
185
|
-
const pipelinesResult = await callMCPTool('list_pipelines', parameters);
|
|
186
|
-
if (Array.isArray(pipelinesResult) && pipelinesResult.length > 0) {
|
|
187
|
-
parameters.pipeline_id = pipelinesResult[0].id;
|
|
188
|
-
const jobsResult = await callMCPTool('list_pipeline_jobs', parameters);
|
|
189
|
-
if (Array.isArray(jobsResult) && jobsResult.length > 0) {
|
|
190
|
-
parameters.job_id = jobsResult[0].id;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
194
|
-
throw new Error('No pipelines found to test with');
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
break;
|
|
198
|
-
case 'get_commit':
|
|
199
|
-
if (TEST_PROJECT_ID) {
|
|
200
|
-
const commitsResult = await callMCPTool('list_commits', parameters);
|
|
201
|
-
if (Array.isArray(commitsResult) && commitsResult.length > 0) {
|
|
202
|
-
parameters.sha = commitsResult[0].id;
|
|
203
|
-
}
|
|
204
|
-
else {
|
|
205
|
-
throw new Error('No commits found to test with');
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
break;
|
|
209
|
-
case 'get_label':
|
|
210
|
-
if (TEST_PROJECT_ID) {
|
|
211
|
-
const labelsResult = await callMCPTool('list_labels', parameters);
|
|
212
|
-
if (Array.isArray(labelsResult) && labelsResult.length > 0) {
|
|
213
|
-
parameters.label_id = labelsResult[0].id;
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
throw new Error('No labels found to test with');
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
break;
|
|
220
|
-
case 'get_wiki_page':
|
|
221
|
-
if (TEST_PROJECT_ID) {
|
|
222
|
-
const wikiResult = await callMCPTool('list_wiki_pages', parameters);
|
|
223
|
-
if (Array.isArray(wikiResult) && wikiResult.length > 0) {
|
|
224
|
-
parameters.slug = wikiResult[0].slug;
|
|
225
|
-
}
|
|
226
|
-
else {
|
|
227
|
-
throw new Error('No wiki pages found to test with');
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
break;
|
|
231
|
-
case 'get_milestone':
|
|
232
|
-
if (TEST_PROJECT_ID) {
|
|
233
|
-
const milestonesResult = await callMCPTool('list_milestones', parameters);
|
|
234
|
-
if (Array.isArray(milestonesResult) && milestonesResult.length > 0) {
|
|
235
|
-
parameters.milestone_id = milestonesResult[0].id;
|
|
236
|
-
}
|
|
237
|
-
else {
|
|
238
|
-
throw new Error('No milestones found to test with');
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
break;
|
|
242
|
-
case 'search_repositories':
|
|
243
|
-
parameters.search = 'test';
|
|
244
|
-
break;
|
|
245
|
-
case 'get_users':
|
|
246
|
-
parameters.usernames = ['root'];
|
|
247
|
-
break;
|
|
248
|
-
case 'verify_namespace':
|
|
249
|
-
parameters.path = 'root';
|
|
250
|
-
break;
|
|
251
|
-
}
|
|
252
|
-
return parameters;
|
|
253
|
-
}
|
|
254
|
-
// 개별 도구 테스트 함수
|
|
255
|
-
async function testTool(tool) {
|
|
256
|
-
const startTime = Date.now();
|
|
257
|
-
let result = {
|
|
258
|
-
name: tool.name,
|
|
259
|
-
category: tool.category,
|
|
260
|
-
status: 'skipped',
|
|
261
|
-
duration: 0,
|
|
262
|
-
response: null
|
|
263
|
-
};
|
|
264
|
-
try {
|
|
265
|
-
console.log(`🧪 Testing ${tool.name}...`);
|
|
266
|
-
const parameters = await setupToolParameters(tool);
|
|
267
|
-
const response = await callMCPTool(tool.name, parameters);
|
|
268
|
-
result.response = response;
|
|
269
|
-
result.status = 'passed';
|
|
270
|
-
result.duration = Date.now() - startTime;
|
|
271
|
-
console.log(`✅ ${tool.name} - PASSED (${result.duration}ms)`);
|
|
272
|
-
}
|
|
273
|
-
catch (error) {
|
|
274
|
-
result.status = 'failed';
|
|
275
|
-
result.error = error.message;
|
|
276
|
-
result.duration = Date.now() - startTime;
|
|
277
|
-
console.log(`❌ ${tool.name} - FAILED (${result.duration}ms)`);
|
|
278
|
-
console.log(` Error: ${error.message}`);
|
|
279
|
-
}
|
|
280
|
-
return result;
|
|
281
|
-
}
|
|
282
|
-
// 메인 테스트 실행 함수
|
|
283
|
-
async function runComprehensiveTests() {
|
|
284
|
-
console.log('🚀 GitLab MCP 포괄적 테스트 시작\n');
|
|
285
|
-
console.log(`📊 총 ${mcpTools.length}개의 도구를 테스트합니다.\n`);
|
|
286
|
-
// 환경 변수 확인
|
|
287
|
-
if (!GITLAB_TOKEN) {
|
|
288
|
-
console.error('❌ GITLAB_PERSONAL_ACCESS_TOKEN 환경 변수가 설정되지 않았습니다.');
|
|
289
|
-
process.exit(1);
|
|
290
|
-
}
|
|
291
|
-
if (!TEST_PROJECT_ID) {
|
|
292
|
-
console.warn('⚠️ GITLAB_PROJECT_ID가 설정되지 않았습니다. 일부 테스트가 건너뛰어질 수 있습니다.');
|
|
293
|
-
}
|
|
294
|
-
// MCP 서버 빌드 확인
|
|
295
|
-
if (!fs.existsSync('build/index.js')) {
|
|
296
|
-
console.log('🔨 MCP 서버를 빌드합니다...');
|
|
297
|
-
const buildProcess = spawn('npm', ['run', 'build'], { stdio: 'inherit' });
|
|
298
|
-
await new Promise((resolve, reject) => {
|
|
299
|
-
buildProcess.on('close', (code) => {
|
|
300
|
-
if (code === 0)
|
|
301
|
-
resolve();
|
|
302
|
-
else
|
|
303
|
-
reject(new Error(`Build failed with code ${code}`));
|
|
304
|
-
});
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
// 각 도구 테스트 실행
|
|
308
|
-
for (const tool of mcpTools) {
|
|
309
|
-
const result = await testTool(tool);
|
|
310
|
-
testResults.details.push(result);
|
|
311
|
-
testResults.total++;
|
|
312
|
-
if (result.status === 'passed') {
|
|
313
|
-
testResults.passed++;
|
|
314
|
-
}
|
|
315
|
-
else if (result.status === 'failed') {
|
|
316
|
-
testResults.failed++;
|
|
317
|
-
}
|
|
318
|
-
else {
|
|
319
|
-
testResults.skipped++;
|
|
320
|
-
}
|
|
321
|
-
// 요청 간 간격 (API 제한 방지)
|
|
322
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
323
|
-
}
|
|
324
|
-
// 결과 출력
|
|
325
|
-
console.log('\n📊 테스트 결과 요약');
|
|
326
|
-
console.log('='.repeat(50));
|
|
327
|
-
console.log(`총 테스트: ${testResults.total}`);
|
|
328
|
-
console.log(`✅ 성공: ${testResults.passed}`);
|
|
329
|
-
console.log(`❌ 실패: ${testResults.failed}`);
|
|
330
|
-
console.log(`⏭️ 건너뜀: ${testResults.skipped}`);
|
|
331
|
-
console.log(`성공률: ${((testResults.passed / testResults.total) * 100).toFixed(1)}%`);
|
|
332
|
-
// 카테고리별 결과
|
|
333
|
-
const categoryResults = {};
|
|
334
|
-
testResults.details.forEach(result => {
|
|
335
|
-
if (!categoryResults[result.category]) {
|
|
336
|
-
categoryResults[result.category] = { passed: 0, failed: 0, total: 0 };
|
|
337
|
-
}
|
|
338
|
-
categoryResults[result.category].total++;
|
|
339
|
-
if (result.status === 'passed') {
|
|
340
|
-
categoryResults[result.category].passed++;
|
|
341
|
-
}
|
|
342
|
-
else if (result.status === 'failed') {
|
|
343
|
-
categoryResults[result.category].failed++;
|
|
344
|
-
}
|
|
345
|
-
});
|
|
346
|
-
console.log('\n📈 카테고리별 결과');
|
|
347
|
-
console.log('-'.repeat(30));
|
|
348
|
-
Object.entries(categoryResults).forEach(([category, stats]) => {
|
|
349
|
-
const successRate = ((stats.passed / stats.total) * 100).toFixed(1);
|
|
350
|
-
console.log(`${category.padEnd(15)}: ${stats.passed}/${stats.total} (${successRate}%)`);
|
|
351
|
-
});
|
|
352
|
-
// 실패한 테스트 상세 정보
|
|
353
|
-
const failedTests = testResults.details.filter(r => r.status === 'failed');
|
|
354
|
-
if (failedTests.length > 0) {
|
|
355
|
-
console.log('\n❌ 실패한 테스트 상세 정보');
|
|
356
|
-
console.log('-'.repeat(40));
|
|
357
|
-
failedTests.forEach(test => {
|
|
358
|
-
console.log(`${test.name}: ${test.error}`);
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
// 결과를 JSON 파일로 저장
|
|
362
|
-
const reportPath = 'test-results-comprehensive.json';
|
|
363
|
-
fs.writeFileSync(reportPath, JSON.stringify(testResults, null, 2));
|
|
364
|
-
console.log(`\n📄 상세 결과가 ${reportPath}에 저장되었습니다.`);
|
|
365
|
-
return testResults.failed === 0;
|
|
366
|
-
}
|
|
367
|
-
// 테스트 실행
|
|
368
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
369
|
-
runComprehensiveTests()
|
|
370
|
-
.then(success => {
|
|
371
|
-
process.exit(success ? 0 : 1);
|
|
372
|
-
})
|
|
373
|
-
.catch(error => {
|
|
374
|
-
console.error('테스트 실행 중 오류 발생:', error);
|
|
375
|
-
process.exit(1);
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
export { runComprehensiveTests, mcpTools, testResults };
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ts-node
|
|
2
|
-
/**
|
|
3
|
-
* 간단한 GitLab MCP 도구 테스트 스크립트 (TypeScript)
|
|
4
|
-
* MCP 서버를 직접 호출하여 기본 도구들을 테스트합니다.
|
|
5
|
-
*/
|
|
6
|
-
import { spawn } from 'child_process';
|
|
7
|
-
import * as fs from 'fs';
|
|
8
|
-
// 환경 변수
|
|
9
|
-
const GITLAB_TOKEN = process.env.GITLAB_PERSONAL_ACCESS_TOKEN || process.env.GITLAB_TOKEN;
|
|
10
|
-
const GITLAB_API_URL = process.env.GITLAB_API_URL || "https://gitlab.com/api/v4";
|
|
11
|
-
const TEST_PROJECT_ID = process.env.GITLAB_PROJECT_ID || process.env.TEST_PROJECT_ID;
|
|
12
|
-
// 테스트 결과
|
|
13
|
-
const results = {
|
|
14
|
-
passed: 0,
|
|
15
|
-
failed: 0,
|
|
16
|
-
total: 0,
|
|
17
|
-
tests: []
|
|
18
|
-
};
|
|
19
|
-
// MCP 응답 파싱 함수
|
|
20
|
-
function parseMCPResponse(output) {
|
|
21
|
-
const lines = output.trim().split('\n');
|
|
22
|
-
for (const line of lines) {
|
|
23
|
-
if (line.startsWith('{')) {
|
|
24
|
-
try {
|
|
25
|
-
const result = JSON.parse(line);
|
|
26
|
-
if (result.result !== undefined) {
|
|
27
|
-
return result.result;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
catch (e) {
|
|
31
|
-
// JSON 파싱 실패 시 계속 시도
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return { success: true, raw: output };
|
|
36
|
-
}
|
|
37
|
-
// MCP 서버와 통신하는 함수
|
|
38
|
-
function callMCPTool(toolName, parameters = {}) {
|
|
39
|
-
return new Promise((resolve, reject) => {
|
|
40
|
-
const mcpProcess = spawn('node', ['build/index.js'], {
|
|
41
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
42
|
-
env: {
|
|
43
|
-
...process.env,
|
|
44
|
-
GITLAB_PERSONAL_ACCESS_TOKEN: GITLAB_TOKEN,
|
|
45
|
-
GITLAB_API_URL: GITLAB_API_URL,
|
|
46
|
-
GITLAB_PROJECT_ID: TEST_PROJECT_ID
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
let output = '';
|
|
50
|
-
let errorOutput = '';
|
|
51
|
-
mcpProcess.stdout?.on('data', (data) => {
|
|
52
|
-
output += data.toString();
|
|
53
|
-
});
|
|
54
|
-
mcpProcess.stderr?.on('data', (data) => {
|
|
55
|
-
errorOutput += data.toString();
|
|
56
|
-
});
|
|
57
|
-
mcpProcess.on('close', (code) => {
|
|
58
|
-
if (code === 0) {
|
|
59
|
-
try {
|
|
60
|
-
const result = parseMCPResponse(output);
|
|
61
|
-
resolve(result);
|
|
62
|
-
}
|
|
63
|
-
catch (e) {
|
|
64
|
-
reject(new Error(`응답 파싱 실패: ${e.message}`));
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
reject(new Error(`MCP 프로세스 종료 코드 ${code}: ${errorOutput}`));
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
// MCP 요청 전송
|
|
72
|
-
const request = {
|
|
73
|
-
jsonrpc: "2.0",
|
|
74
|
-
id: 1,
|
|
75
|
-
method: "tools/call",
|
|
76
|
-
params: {
|
|
77
|
-
name: toolName,
|
|
78
|
-
arguments: parameters
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
mcpProcess.stdin?.write(JSON.stringify(request) + '\n');
|
|
82
|
-
mcpProcess.stdin?.end();
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
// 개별 테스트 실행
|
|
86
|
-
async function runTest(testName, toolName, parameters = {}) {
|
|
87
|
-
const startTime = Date.now();
|
|
88
|
-
console.log(`🧪 ${testName} 테스트 중...`);
|
|
89
|
-
try {
|
|
90
|
-
const result = await callMCPTool(toolName, parameters);
|
|
91
|
-
const duration = Date.now() - startTime;
|
|
92
|
-
console.log(`✅ ${testName} - 성공 (${duration}ms)`);
|
|
93
|
-
results.passed++;
|
|
94
|
-
results.tests.push({
|
|
95
|
-
name: testName,
|
|
96
|
-
tool: toolName,
|
|
97
|
-
status: 'passed',
|
|
98
|
-
duration,
|
|
99
|
-
result: typeof result === 'object' ? 'object' : typeof result
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
catch (error) {
|
|
103
|
-
const duration = Date.now() - startTime;
|
|
104
|
-
console.log(`❌ ${testName} - 실패 (${duration}ms): ${error.message}`);
|
|
105
|
-
results.failed++;
|
|
106
|
-
results.tests.push({
|
|
107
|
-
name: testName,
|
|
108
|
-
tool: toolName,
|
|
109
|
-
status: 'failed',
|
|
110
|
-
duration,
|
|
111
|
-
error: error.message
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
results.total++;
|
|
115
|
-
}
|
|
116
|
-
// 메인 테스트 함수
|
|
117
|
-
async function runTests() {
|
|
118
|
-
console.log('🚀 GitLab MCP 도구 테스트 시작\n');
|
|
119
|
-
// 환경 변수 확인
|
|
120
|
-
if (!GITLAB_TOKEN) {
|
|
121
|
-
console.error('❌ GITLAB_PERSONAL_ACCESS_TOKEN 환경 변수가 필요합니다.');
|
|
122
|
-
process.exit(1);
|
|
123
|
-
}
|
|
124
|
-
if (!TEST_PROJECT_ID) {
|
|
125
|
-
console.warn('⚠️ GITLAB_PROJECT_ID가 설정되지 않았습니다. 일부 테스트가 건너뛰어질 수 있습니다.');
|
|
126
|
-
}
|
|
127
|
-
// MCP 서버 빌드 확인
|
|
128
|
-
if (!fs.existsSync('build/index.js')) {
|
|
129
|
-
console.log('🔨 MCP 서버를 빌드합니다...');
|
|
130
|
-
const buildProcess = spawn('npm', ['run', 'build'], { stdio: 'inherit' });
|
|
131
|
-
await new Promise((resolve, reject) => {
|
|
132
|
-
buildProcess.on('close', (code) => {
|
|
133
|
-
if (code === 0)
|
|
134
|
-
resolve();
|
|
135
|
-
else
|
|
136
|
-
reject(new Error(`빌드 실패: ${code}`));
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
console.log('📋 기본 테스트 실행 중...\n');
|
|
141
|
-
// 기본 읽기 전용 테스트들
|
|
142
|
-
await runTest('프로젝트 목록 조회', 'list_projects', { per_page: 5 });
|
|
143
|
-
await runTest('프로젝트 검색', 'search_repositories', { search: 'gitlab' });
|
|
144
|
-
if (TEST_PROJECT_ID) {
|
|
145
|
-
await runTest('프로젝트 정보 조회', 'get_project', { project_id: TEST_PROJECT_ID });
|
|
146
|
-
await runTest('프로젝트 멤버 목록', 'list_project_members', { project_id: TEST_PROJECT_ID });
|
|
147
|
-
await runTest('이슈 목록 조회', 'list_issues', { project_id: TEST_PROJECT_ID, per_page: 5 });
|
|
148
|
-
await runTest('머지 리퀘스트 목록', 'list_merge_requests', { project_id: TEST_PROJECT_ID, per_page: 5 });
|
|
149
|
-
await runTest('파이프라인 목록', 'list_pipelines', { project_id: TEST_PROJECT_ID, per_page: 5 });
|
|
150
|
-
await runTest('커밋 목록', 'list_commits', { project_id: TEST_PROJECT_ID, per_page: 5 });
|
|
151
|
-
await runTest('라벨 목록', 'list_labels', { project_id: TEST_PROJECT_ID });
|
|
152
|
-
await runTest('파일 트리 조회', 'get_repository_tree', { project_id: TEST_PROJECT_ID });
|
|
153
|
-
await runTest('프로젝트 이벤트', 'get_project_events', { project_id: TEST_PROJECT_ID, per_page: 5 });
|
|
154
|
-
}
|
|
155
|
-
// 네임스페이스 관련 테스트
|
|
156
|
-
await runTest('네임스페이스 목록', 'list_namespaces', { per_page: 5 });
|
|
157
|
-
await runTest('사용자 정보 조회', 'get_users', { usernames: ['root'] });
|
|
158
|
-
await runTest('전체 이벤트 조회', 'list_events', { per_page: 5 });
|
|
159
|
-
// 결과 출력
|
|
160
|
-
console.log('\n📊 테스트 결과');
|
|
161
|
-
console.log('='.repeat(40));
|
|
162
|
-
console.log(`총 테스트: ${results.total}`);
|
|
163
|
-
console.log(`✅ 성공: ${results.passed}`);
|
|
164
|
-
console.log(`❌ 실패: ${results.failed}`);
|
|
165
|
-
console.log(`성공률: ${((results.passed / results.total) * 100).toFixed(1)}%`);
|
|
166
|
-
// 실패한 테스트 상세 정보
|
|
167
|
-
const failedTests = results.tests.filter(t => t.status === 'failed');
|
|
168
|
-
if (failedTests.length > 0) {
|
|
169
|
-
console.log('\n❌ 실패한 테스트:');
|
|
170
|
-
failedTests.forEach(test => {
|
|
171
|
-
console.log(` - ${test.name}: ${test.error}`);
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
// 결과를 파일로 저장
|
|
175
|
-
fs.writeFileSync('test-results-simple.json', JSON.stringify(results, null, 2));
|
|
176
|
-
console.log('\n📄 상세 결과가 test-results-simple.json에 저장되었습니다.');
|
|
177
|
-
return results.failed === 0;
|
|
178
|
-
}
|
|
179
|
-
// 실행
|
|
180
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
181
|
-
runTests()
|
|
182
|
-
.then(success => {
|
|
183
|
-
process.exit(success ? 0 : 1);
|
|
184
|
-
})
|
|
185
|
-
.catch(error => {
|
|
186
|
-
console.error('테스트 실행 오류:', error);
|
|
187
|
-
process.exit(1);
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
export { runTests, callMCPTool };
|