Skip to main content
Glama

gitlab mcp

readonly-mcp-tests.ts14.8 kB
#!/usr/bin/env ts-node /** * 읽기 전용 GitLab MCP 도구 테스트 스크립트 (TypeScript) * 읽기 전용 GitLab MCP 도구들을 자동으로 테스트합니다. */ import { spawn, ChildProcess } from 'child_process'; import * as fs from 'fs'; import * as dotenv from 'dotenv'; // .env 파일 로드 dotenv.config(); // 환경 변수 설정 const GITLAB_API_URL = process.env.GITLAB_API_URL || "https://gitlab.com/api/v4"; const GITLAB_TOKEN = process.env.GITLAB_PERSONAL_ACCESS_TOKEN || process.env.GITLAB_TOKEN; const TEST_PROJECT_ID = process.env.GITLAB_PROJECT_ID || process.env.TEST_PROJECT_ID; // 타입 정의 interface TestResult { name: string; category: string; status: 'passed' | 'failed' | 'skipped'; duration: number; error?: string; response?: any; } interface TestResults { passed: number; failed: number; skipped: number; total: number; details: TestResult[]; } interface MCPTool { name: string; category: string; required: boolean; } interface MCPResponse { result?: any; error?: any; id?: number; } // 테스트 결과 저장 const testResults: TestResults = { passed: 0, failed: 0, skipped: 0, total: 0, details: [] }; // 테스트 도구 목록 (GitLab MCP에서 제공하는 모든 도구들) const mcpTools: MCPTool[] = [ // 프로젝트 관련 { name: 'list_projects', category: 'project', required: false }, { name: 'search_repositories', category: 'project', required: false }, { name: 'get_project', category: 'project', required: true }, { name: 'list_project_members', category: 'project', required: true }, { name: 'list_group_projects', category: 'project', required: false }, // 이슈 관련 { name: 'list_issues', category: 'issue', required: true }, { name: 'my_issues', category: 'issue', required: false }, { name: 'get_issue', category: 'issue', required: true }, { name: 'list_issue_discussions', category: 'issue', required: true }, { name: 'list_issue_links', category: 'issue', required: true }, // 머지 리퀘스트 관련 { name: 'list_merge_requests', category: 'merge_request', required: true }, { name: 'get_merge_request', category: 'merge_request', required: true }, { name: 'get_merge_request_diffs', category: 'merge_request', required: true }, { name: 'list_merge_request_diffs', category: 'merge_request', required: true }, { name: 'get_branch_diffs', category: 'merge_request', required: true }, { name: 'mr_discussions', category: 'merge_request', required: true }, // 파이프라인 관련 { name: 'list_pipelines', category: 'pipeline', required: true }, { name: 'get_pipeline', category: 'pipeline', required: true }, { name: 'list_pipeline_jobs', category: 'pipeline', required: true }, { name: 'list_pipeline_trigger_jobs', category: 'pipeline', required: true }, { name: 'get_pipeline_job', category: 'pipeline', required: true }, { name: 'get_pipeline_job_output', category: 'pipeline', required: true }, // 파일 관리 { name: 'get_file_contents', category: 'file', required: true }, { name: 'get_repository_tree', category: 'file', required: true }, // 커밋 관련 { name: 'list_commits', category: 'commit', required: true }, { name: 'get_commit', category: 'commit', required: true }, { name: 'get_commit_diff', category: 'commit', required: true }, // 라벨 관련 { name: 'list_labels', category: 'label', required: true }, { name: 'get_label', category: 'label', required: true }, // 네임스페이스 관련 { name: 'list_namespaces', category: 'namespace', required: false }, { name: 'get_namespace', category: 'namespace', required: false }, { name: 'verify_namespace', category: 'namespace', required: false }, // 사용자 관련 { name: 'get_users', category: 'user', required: false }, // 이벤트 관련 { name: 'list_events', category: 'event', required: false }, { name: 'get_project_events', category: 'event', required: true }, // 마일스톤 관련 (선택적) { name: 'list_milestones', category: 'milestone', required: true }, { name: 'get_milestone', category: 'milestone', required: true }, // 위키 관련 (선택적) { name: 'list_wiki_pages', category: 'wiki', required: true }, { name: 'get_wiki_page', category: 'wiki', required: true }, // 그룹 이터레이션 관련 { name: 'list_group_iterations', category: 'iteration', required: false } ]; // MCP 응답 파싱 함수 function parseMCPResponse(output: string): any { const lines = output.trim().split('\n'); for (const line of lines) { if (line.startsWith('{')) { try { const result: MCPResponse = JSON.parse(line); if (result.result !== undefined) { return result.result; } } catch (e) { // JSON 파싱 실패 시 계속 시도 } } } return { success: true, raw: output }; } // MCP 서버와 통신하는 함수 async function callMCPTool(toolName: string, parameters: Record<string, any> = {}): Promise<any> { return new Promise((resolve, reject) => { const mcpProcess: ChildProcess = spawn('node', ['build/index.js'], { stdio: ['pipe', 'pipe', 'pipe'], env: { ...process.env, GITLAB_PERSONAL_ACCESS_TOKEN: GITLAB_TOKEN, GITLAB_API_URL: GITLAB_API_URL, GITLAB_PROJECT_ID: TEST_PROJECT_ID } }); let output = ''; let errorOutput = ''; mcpProcess.stdout?.on('data', (data: Buffer) => { output += data.toString(); }); mcpProcess.stderr?.on('data', (data: Buffer) => { errorOutput += data.toString(); }); mcpProcess.on('close', (code: number | null) => { if (code === 0) { try { const result = parseMCPResponse(output); resolve(result); } catch (e) { reject(new Error(`MCP 응답 파싱 실패: ${(e as Error).message}`)); } } else { reject(new Error(`MCP 프로세스 종료 코드 ${code}: ${errorOutput}`)); } }); // MCP 요청 전송 const request = { jsonrpc: "2.0", id: 1, method: "tools/call", params: { name: toolName, arguments: parameters } }; mcpProcess.stdin?.write(JSON.stringify(request) + '\n'); mcpProcess.stdin?.end(); }); } // 도구별 파라미터 설정 함수 async function setupToolParameters(tool: MCPTool): Promise<Record<string, any>> { let parameters: Record<string, any> = {}; if (tool.required && TEST_PROJECT_ID) { parameters.project_id = TEST_PROJECT_ID; } // 특정 도구들의 추가 파라미터 설정 switch (tool.name) { case 'get_issue': if (TEST_PROJECT_ID) { const listResult = await callMCPTool('list_issues', parameters); if (Array.isArray(listResult) && listResult.length > 0) { parameters.issue_iid = listResult[0].iid; } else { throw new Error('No issues found to test with'); } } break; case 'get_merge_request': if (TEST_PROJECT_ID) { const listResult = await callMCPTool('list_merge_requests', parameters); if (Array.isArray(listResult) && listResult.length > 0) { parameters.merge_request_iid = listResult[0].iid; } else { throw new Error('No merge requests found to test with'); } } break; case 'get_pipeline': if (TEST_PROJECT_ID) { const pipelinesResult = await callMCPTool('list_pipelines', parameters); if (Array.isArray(pipelinesResult) && pipelinesResult.length > 0) { parameters.pipeline_id = pipelinesResult[0].id; } else { throw new Error('No pipelines found to test with'); } } break; case 'get_pipeline_job': if (TEST_PROJECT_ID) { const pipelinesResult = await callMCPTool('list_pipelines', parameters); if (Array.isArray(pipelinesResult) && pipelinesResult.length > 0) { parameters.pipeline_id = pipelinesResult[0].id; const jobsResult = await callMCPTool('list_pipeline_jobs', parameters); if (Array.isArray(jobsResult) && jobsResult.length > 0) { parameters.job_id = jobsResult[0].id; } } else { throw new Error('No pipelines found to test with'); } } break; case 'get_commit': if (TEST_PROJECT_ID) { const commitsResult = await callMCPTool('list_commits', parameters); if (Array.isArray(commitsResult) && commitsResult.length > 0) { parameters.sha = commitsResult[0].id; } else { throw new Error('No commits found to test with'); } } break; case 'get_label': if (TEST_PROJECT_ID) { const labelsResult = await callMCPTool('list_labels', parameters); if (Array.isArray(labelsResult) && labelsResult.length > 0) { parameters.label_id = labelsResult[0].id; } else { throw new Error('No labels found to test with'); } } break; case 'get_wiki_page': if (TEST_PROJECT_ID) { const wikiResult = await callMCPTool('list_wiki_pages', parameters); if (Array.isArray(wikiResult) && wikiResult.length > 0) { parameters.slug = wikiResult[0].slug; } else { throw new Error('No wiki pages found to test with'); } } break; case 'get_milestone': if (TEST_PROJECT_ID) { const milestonesResult = await callMCPTool('list_milestones', parameters); if (Array.isArray(milestonesResult) && milestonesResult.length > 0) { parameters.milestone_id = milestonesResult[0].id; } else { throw new Error('No milestones found to test with'); } } break; case 'search_repositories': parameters.search = 'test'; break; case 'get_users': parameters.usernames = ['root']; break; case 'verify_namespace': parameters.path = 'root'; break; } return parameters; } // 개별 도구 테스트 함수 async function testTool(tool: MCPTool): Promise<TestResult> { const startTime = Date.now(); let result: TestResult = { name: tool.name, category: tool.category, status: 'skipped', duration: 0, response: null }; try { console.log(`🧪 Testing ${tool.name}...`); const parameters = await setupToolParameters(tool); const response = await callMCPTool(tool.name, parameters); result.response = response; result.status = 'passed'; result.duration = Date.now() - startTime; console.log(`✅ ${tool.name} - PASSED (${result.duration}ms)`); } catch (error) { result.status = 'failed'; result.error = (error as Error).message; result.duration = Date.now() - startTime; console.log(`❌ ${tool.name} - FAILED (${result.duration}ms)`); console.log(` Error: ${(error as Error).message}`); } return result; } // 메인 테스트 실행 함수 async function runReadOnlyTests(): Promise<boolean> { console.log('🚀 GitLab MCP 읽기 전용 도구 테스트 시작\n'); console.log(`📊 총 ${mcpTools.length}개의 읽기 전용 도구를 테스트합니다.\n`); // 환경 변수 확인 if (!GITLAB_TOKEN) { console.error('❌ GITLAB_PERSONAL_ACCESS_TOKEN 환경 변수가 설정되지 않았습니다.'); process.exit(1); } if (!TEST_PROJECT_ID) { console.warn('⚠️ GITLAB_PROJECT_ID가 설정되지 않았습니다. 일부 테스트가 건너뛰어질 수 있습니다.'); } // MCP 서버 빌드 확인 if (!fs.existsSync('build/index.js')) { console.log('🔨 MCP 서버를 빌드합니다...'); const buildProcess = spawn('npm', ['run', 'build'], { stdio: 'inherit' }); await new Promise<void>((resolve, reject) => { buildProcess.on('close', (code: number | null) => { if (code === 0) resolve(); else reject(new Error(`Build failed with code ${code}`)); }); }); } // 각 도구 테스트 실행 for (const tool of mcpTools) { const result = await testTool(tool); testResults.details.push(result); testResults.total++; if (result.status === 'passed') { testResults.passed++; } else if (result.status === 'failed') { testResults.failed++; } else { testResults.skipped++; } // 요청 간 간격 (API 제한 방지) await new Promise(resolve => setTimeout(resolve, 100)); } // 결과 출력 console.log('\n📊 테스트 결과 요약'); console.log('='.repeat(50)); console.log(`총 테스트: ${testResults.total}`); console.log(`✅ 성공: ${testResults.passed}`); console.log(`❌ 실패: ${testResults.failed}`); console.log(`⏭️ 건너뜀: ${testResults.skipped}`); console.log(`성공률: ${((testResults.passed / testResults.total) * 100).toFixed(1)}%`); // 카테고리별 결과 const categoryResults: Record<string, { passed: number; failed: number; total: number }> = {}; testResults.details.forEach(result => { if (!categoryResults[result.category]) { categoryResults[result.category] = { passed: 0, failed: 0, total: 0 }; } categoryResults[result.category].total++; if (result.status === 'passed') { categoryResults[result.category].passed++; } else if (result.status === 'failed') { categoryResults[result.category].failed++; } }); console.log('\n📈 카테고리별 결과'); console.log('-'.repeat(30)); Object.entries(categoryResults).forEach(([category, stats]) => { const successRate = ((stats.passed / stats.total) * 100).toFixed(1); console.log(`${category.padEnd(15)}: ${stats.passed}/${stats.total} (${successRate}%)`); }); // 실패한 테스트 상세 정보 const failedTests = testResults.details.filter(r => r.status === 'failed'); if (failedTests.length > 0) { console.log('\n❌ 실패한 테스트 상세 정보'); console.log('-'.repeat(40)); failedTests.forEach(test => { console.log(`${test.name}: ${test.error}`); }); } // 결과를 JSON 파일로 저장 const reportPath = 'test-results-readonly.json'; fs.writeFileSync(reportPath, JSON.stringify(testResults, null, 2)); console.log(`\n📄 상세 결과가 ${reportPath}에 저장되었습니다.`); return testResults.failed === 0; } // 테스트 실행 if (import.meta.url === `file://${process.argv[1]}`) { runReadOnlyTests() .then(success => { process.exit(success ? 0 : 1); }) .catch(error => { console.error('테스트 실행 중 오류 발생:', error); process.exit(1); }); } export { runReadOnlyTests, mcpTools, testResults };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/zereight/gitlab-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server