Skip to main content
Glama

mcp-github-project-manager

#!/usr/bin/env node /** * MCP Test Client - CLI tool for testing all MCP server tools * * This script provides a simple command-line interface to test all tools * provided by the MCP GitHub Project Manager server. * * Usage: * node scripts/mcp-test-client.js [command] [options] * * Commands: * list-tools List all available tools * test-tool <name> Test a specific tool interactively * test-all Test all tools with sample data * interactive Interactive mode for testing tools * * Environment Variables: * GITHUB_TOKEN GitHub personal access token (required) * GITHUB_OWNER GitHub repository owner (required) * GITHUB_REPO GitHub repository name (required) * MCP_SERVER_PATH Path to the MCP server executable (optional) */ import { spawn } from 'child_process'; import { readFileSync } from 'fs'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; import readline from 'readline'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Configuration const CONFIG = { serverPath: process.env.MCP_SERVER_PATH || join(__dirname, '../build/index.js'), githubToken: process.env.GITHUB_TOKEN, githubOwner: process.env.GITHUB_OWNER, githubRepo: process.env.GITHUB_REPO, timeout: 30000, // 30 seconds timeout for tool calls }; // Colors for console output const colors = { reset: '\x1b[0m', bright: '\x1b[1m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m', }; function colorize(text, color) { return `${colors[color]}${text}${colors.reset}`; } // MCP Client class class MCPTestClient { constructor() { this.serverProcess = null; this.requestId = 0; this.pendingRequests = new Map(); this.tools = []; } async start() { console.log(colorize('🚀 Starting MCP GitHub Project Manager server...', 'blue')); // Validate environment variables if (!CONFIG.githubToken || !CONFIG.githubOwner || !CONFIG.githubRepo) { throw new Error('Missing required environment variables: GITHUB_TOKEN, GITHUB_OWNER, GITHUB_REPO'); } // Start server process this.serverProcess = spawn('node', [CONFIG.serverPath], { env: { ...process.env, GITHUB_TOKEN: CONFIG.githubToken, GITHUB_OWNER: CONFIG.githubOwner, GITHUB_REPO: CONFIG.githubRepo, }, stdio: ['pipe', 'pipe', 'pipe'] }); // Handle server output this.serverProcess.stderr.on('data', (data) => { const message = data.toString().trim(); if (message) { console.log(colorize(`Server: ${message}`, 'yellow')); } }); // Handle server responses with proper buffering let responseBuffer = ''; this.serverProcess.stdout.on('data', (data) => { responseBuffer += data.toString(); // Process complete JSON messages let newlineIndex; while ((newlineIndex = responseBuffer.indexOf('\n')) !== -1) { const line = responseBuffer.slice(0, newlineIndex).trim(); responseBuffer = responseBuffer.slice(newlineIndex + 1); if (line) { try { const response = JSON.parse(line); this.handleResponse(response); } catch (error) { // Ignore non-JSON output console.log(colorize(`Non-JSON output: ${line.substring(0, 100)}...`, 'yellow')); } } } }); // Wait for server to initialize await new Promise(resolve => setTimeout(resolve, 2000)); // Initialize connection await this.sendRequest({ jsonrpc: '2.0', id: this.getNextId(), method: 'initialize', params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'mcp-test-client', version: '1.0.0' } } }); console.log(colorize('✅ MCP server started successfully', 'green')); } async stop() { if (this.serverProcess) { this.serverProcess.kill(); this.serverProcess = null; console.log(colorize('🛑 MCP server stopped', 'yellow')); } } getNextId() { return ++this.requestId; } async sendRequest(request) { return new Promise((resolve, reject) => { const timeout = setTimeout(() => { this.pendingRequests.delete(request.id); reject(new Error(`Request timeout: ${request.method}`)); }, CONFIG.timeout); this.pendingRequests.set(request.id, { resolve, reject, timeout }); const message = JSON.stringify(request) + '\n'; this.serverProcess.stdin.write(message); }); } handleResponse(response) { if (response.id && this.pendingRequests.has(response.id)) { const { resolve, reject, timeout } = this.pendingRequests.get(response.id); clearTimeout(timeout); this.pendingRequests.delete(response.id); if (response.error) { reject(new Error(`${response.error.code}: ${response.error.message}`)); } else { resolve(response.result); } } } async listTools() { const result = await this.sendRequest({ jsonrpc: '2.0', id: this.getNextId(), method: 'tools/list' }); this.tools = result.tools || []; return this.tools; } async callTool(name, args = {}) { return await this.sendRequest({ jsonrpc: '2.0', id: this.getNextId(), method: 'tools/call', params: { name, arguments: args } }); } } // Function to get sample data with dynamic values function getSampleData() { const githubOwner = CONFIG.githubOwner; const githubRepo = CONFIG.githubRepo; return { // Project tools create_project: { title: 'Test Project', shortDescription: 'A test project created by MCP test client', owner: githubOwner, // Use dynamic owner visibility: 'private' }, // Milestone tools create_milestone: { title: 'Test Milestone', description: 'A test milestone for validation', dueDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString() // 30 days from now }, // Issue tools create_issue: { title: 'Test Issue', description: 'This is a test issue created by the MCP test client', labels: ['test', 'mcp-client'] }, // Sprint tools create_sprint: { title: 'Test Sprint', description: 'A test sprint for validation', startDate: new Date().toISOString(), endDate: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000).toISOString(), // 14 days from now issueIds: ['1', '2'] // Array of issue IDs }, // Label tools create_label: { name: 'test-label', color: '0052cc', description: 'A test label created by MCP test client' }, // Parse PRD tool parse_prd: { prdContent: `# Task Management App PRD ## Overview Build a modern task management application for small teams. ## Features - User authentication and authorization - Task creation and management - Real-time collaboration - Mobile-responsive design - Team workspace management ## Technical Requirements - Web-based application - Real-time updates using WebSockets - RESTful API design - Mobile-first responsive design`, maxTasks: 15, includeSubtasks: true, autoEstimate: true, autoPrioritize: true, autoDetectDependencies: true, createLifecycle: true, createTraceabilityMatrix: true, includeUseCases: true, enhancedGeneration: true, contextLevel: 'standard', includeBusinessContext: true, includeTechnicalContext: true, includeImplementationGuidance: true }, // Analytics and metrics tools list_projects: { limit: 10, status: 'active' }, list_milestones: { status: 'open', sort: 'due_date', direction: 'asc' }, list_issues: { status: 'open', sort: 'updated', direction: 'desc', limit: 10 }, list_sprints: { status: 'active' }, get_upcoming_milestones: { daysAhead: 30, limit: 5, includeIssues: true }, get_overdue_milestones: { limit: 5, includeIssues: true }, // AI task tools with more examples get_next_task: { teamSkills: ['typescript', 'react', 'node.js', 'postgresql'], sprintCapacity: 40, maxComplexity: 8, excludeBlocked: true, includeAnalysis: true, limit: 3 }, analyze_task_complexity: { taskTitle: 'Implement real-time notifications', taskDescription: 'Build a WebSocket-based notification system with push notifications, email fallback, and user preference management', currentEstimate: 16, teamExperience: 'mixed', // Fix enum value includeBreakdown: true, includeRisks: true, includeRecommendations: true }, expand_task: { taskTitle: 'User authentication system', taskDescription: 'Implement complete user authentication with login, registration, password reset, and session management', currentComplexity: 8, maxSubtasks: 8, maxDepth: 3, targetComplexity: 5, includeEstimates: true, includeDependencies: true, includeAcceptanceCriteria: true }, add_feature: { featureIdea: 'Real-time Chat System', description: 'Add real-time messaging capabilities to the application with support for text messages, file sharing, and user presence indicators', requestedBy: 'test-user', autoApprove: false, expandToTasks: true, createLifecycle: true }, enhance_prd: { prdContent: `# Basic Task App PRD ## Overview Simple task management app. ## Features - Create tasks - Mark as complete`, enhancementType: 'comprehensive', includeResearch: true, targetAudience: 'technical', addMissingElements: true, improveExisting: true, validateQuality: true }, create_traceability_matrix: { projectId: 'test-project', prdContent: `# Task Management System PRD ## Business Requirements - BR001: Users must be able to create and manage tasks - BR002: System must support team collaboration - BR003: Real-time updates are required ## Features - User authentication - Task CRUD operations - Team workspaces - Real-time notifications`, features: [], tasks: [], includeUseCases: true, includeTraceabilityLinks: true, includeCoverageAnalysis: true, validateCompleteness: true }, // Generate PRD tool generate_prd: { projectIdea: 'A simple task management application with user authentication and real-time updates', projectName: 'TaskMaster Pro', complexity: 'medium', author: 'test-user', includeResearch: true }, // Create roadmap tool create_roadmap: { project: { title: 'Test Roadmap Project', shortDescription: 'A test project for roadmap validation', owner: githubOwner, // Add required owner field visibility: 'private' }, milestones: [ { milestone: { title: 'Phase 1: Foundation', description: 'Basic setup and core features', dueDate: new Date(Date.now() + 60 * 24 * 60 * 60 * 1000).toISOString() }, issues: [ { title: 'Setup project structure', description: 'Initialize project with proper folder structure and configuration', priority: 'high', type: 'feature', assignees: [], labels: ['setup', 'foundation'] }, { title: 'Implement authentication', description: 'Add user authentication and authorization system', priority: 'high', type: 'feature', assignees: [], labels: ['auth', 'security'] } ] } ] } }; } // Command handlers async function listToolsCommand(client) { console.log(colorize('\n📋 Available Tools:', 'cyan')); console.log(colorize('=' .repeat(50), 'cyan')); const tools = await client.listTools(); if (tools.length === 0) { console.log(colorize('No tools available', 'yellow')); return; } // Group tools by category const categories = { 'Project Management': [], 'Planning & Analytics': [], 'AI Task Management': [], 'Other': [] }; tools.forEach(tool => { if (tool.name.includes('project') || tool.name.includes('milestone') || tool.name.includes('issue') || tool.name.includes('sprint')) { categories['Project Management'].push(tool); } else if (tool.name.includes('roadmap') || tool.name.includes('metrics') || tool.name.includes('overdue') || tool.name.includes('upcoming')) { categories['Planning & Analytics'].push(tool); } else if (tool.name.includes('prd') || tool.name.includes('task') || tool.name.includes('feature') || tool.name.includes('traceability')) { categories['AI Task Management'].push(tool); } else { categories['Other'].push(tool); } }); Object.entries(categories).forEach(([category, categoryTools]) => { if (categoryTools.length > 0) { console.log(colorize(`\n${category}:`, 'magenta')); categoryTools.forEach(tool => { console.log(` ${colorize(tool.name, 'green')} - ${tool.description}`); }); } }); console.log(colorize(`\nTotal: ${tools.length} tools`, 'blue')); } async function testToolCommand(client, toolName) { console.log(colorize(`\n🔧 Testing tool: ${toolName}`, 'cyan')); console.log(colorize('=' .repeat(50), 'cyan')); const tools = await client.listTools(); const tool = tools.find(t => t.name === toolName); if (!tool) { console.log(colorize(`❌ Tool '${toolName}' not found`, 'red')); return; } console.log(colorize(`Description: ${tool.description}`, 'blue')); // Check if we have sample data for this tool const sampleData = getSampleData(); const sampleArgs = sampleData[toolName]; if (sampleArgs) { console.log(colorize('\n📝 Using sample data:', 'yellow')); console.log(JSON.stringify(sampleArgs, null, 2)); try { const result = await client.callTool(toolName, sampleArgs); console.log(colorize('\n✅ Tool executed successfully:', 'green')); console.log(JSON.stringify(result, null, 2)); } catch (error) { console.log(colorize(`\n❌ Tool execution failed: ${error.message}`, 'red')); } } else { console.log(colorize('\n⚠️ No sample data available for this tool', 'yellow')); console.log(colorize('Tool schema:', 'blue')); if (tool.inputSchema) { console.log(JSON.stringify(tool.inputSchema, null, 2)); } } } async function testAllCommand(client) { console.log(colorize('\n🧪 Testing All Tools', 'cyan')); console.log(colorize('=' .repeat(50), 'cyan')); const tools = await client.listTools(); const results = { passed: [], failed: [], skipped: [] }; const sampleData = getSampleData(); for (const tool of tools) { const sampleArgs = sampleData[tool.name]; if (!sampleArgs) { console.log(colorize(`⏭️ Skipping ${tool.name} (no sample data)`, 'yellow')); results.skipped.push(tool.name); continue; } console.log(colorize(`\n🔧 Testing ${tool.name}...`, 'blue')); try { const result = await client.callTool(tool.name, sampleArgs); console.log(colorize(`✅ ${tool.name} - PASSED`, 'green')); results.passed.push(tool.name); } catch (error) { console.log(colorize(`❌ ${tool.name} - FAILED: ${error.message}`, 'red')); results.failed.push({ name: tool.name, error: error.message }); } // Small delay between tests await new Promise(resolve => setTimeout(resolve, 500)); } // Print summary console.log(colorize('\n📊 Test Summary', 'cyan')); console.log(colorize('=' .repeat(50), 'cyan')); console.log(colorize(`✅ Passed: ${results.passed.length}`, 'green')); console.log(colorize(`❌ Failed: ${results.failed.length}`, 'red')); console.log(colorize(`⏭️ Skipped: ${results.skipped.length}`, 'yellow')); if (results.failed.length > 0) { console.log(colorize('\nFailed Tests:', 'red')); results.failed.forEach(failure => { console.log(` - ${failure.name}: ${failure.error}`); }); } if (results.skipped.length > 0) { console.log(colorize('\nSkipped Tests (no sample data):', 'yellow')); results.skipped.forEach(name => { console.log(` - ${name}`); }); } } async function interactiveMode(client) { console.log(colorize('\n🎮 Interactive Mode', 'cyan')); console.log(colorize('=' .repeat(50), 'cyan')); console.log('Type "help" for available commands, "exit" to quit'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const tools = await client.listTools(); while (true) { const input = await new Promise(resolve => { rl.question(colorize('\n> ', 'cyan'), resolve); }); const [command, ...args] = input.trim().split(' '); switch (command.toLowerCase()) { case 'help': console.log(colorize('\nAvailable commands:', 'blue')); console.log(' help - Show this help message'); console.log(' list - List all available tools'); console.log(' test <tool-name> - Test a specific tool'); console.log(' call <tool-name> - Call a tool with custom arguments'); console.log(' schema <tool-name> - Show tool schema'); console.log(' exit - Exit interactive mode'); break; case 'list': tools.forEach(tool => { console.log(` ${colorize(tool.name, 'green')} - ${tool.description}`); }); break; case 'test': if (args.length === 0) { console.log(colorize('Usage: test <tool-name>', 'yellow')); break; } await testToolCommand(client, args[0]); break; case 'call': if (args.length === 0) { console.log(colorize('Usage: call <tool-name>', 'yellow')); break; } await callToolInteractive(client, args[0], rl); break; case 'schema': if (args.length === 0) { console.log(colorize('Usage: schema <tool-name>', 'yellow')); break; } const tool = tools.find(t => t.name === args[0]); if (tool) { console.log(JSON.stringify(tool.inputSchema, null, 2)); } else { console.log(colorize(`Tool '${args[0]}' not found`, 'red')); } break; case 'exit': rl.close(); return; default: if (command) { console.log(colorize(`Unknown command: ${command}`, 'red')); console.log('Type "help" for available commands'); } } } } async function callToolInteractive(client, toolName, rl) { const tools = await client.listTools(); const tool = tools.find(t => t.name === toolName); if (!tool) { console.log(colorize(`Tool '${toolName}' not found`, 'red')); return; } console.log(colorize(`\nCalling tool: ${toolName}`, 'blue')); console.log('Enter arguments as JSON (or press Enter for empty object):'); const argsInput = await new Promise(resolve => { rl.question('Arguments: ', resolve); }); let args = {}; if (argsInput.trim()) { try { args = JSON.parse(argsInput); } catch (error) { console.log(colorize(`Invalid JSON: ${error.message}`, 'red')); return; } } try { const result = await client.callTool(toolName, args); console.log(colorize('\n✅ Result:', 'green')); console.log(JSON.stringify(result, null, 2)); } catch (error) { console.log(colorize(`\n❌ Error: ${error.message}`, 'red')); } } // Main function async function main() { const args = process.argv.slice(2); const command = args[0]; // Show help if no command provided if (!command || command === 'help' || command === '--help' || command === '-h') { showHelp(); return; } // Validate environment variables if (!CONFIG.githubToken || !CONFIG.githubOwner || !CONFIG.githubRepo) { console.log(colorize('❌ Error: Missing required environment variables', 'red')); console.log('Please set the following environment variables:'); console.log(' GITHUB_TOKEN - Your GitHub personal access token'); console.log(' GITHUB_OWNER - GitHub repository owner'); console.log(' GITHUB_REPO - GitHub repository name'); console.log('\nExample:'); console.log(' export GITHUB_TOKEN=ghp_xxxxxxxxxxxx'); console.log(' export GITHUB_OWNER=myusername'); console.log(' export GITHUB_REPO=myrepo'); process.exit(1); } const client = new MCPTestClient(); try { await client.start(); switch (command) { case 'list-tools': await listToolsCommand(client); break; case 'test-tool': if (args.length < 2) { console.log(colorize('❌ Error: Tool name required', 'red')); console.log('Usage: node scripts/mcp-test-client.js test-tool <tool-name>'); process.exit(1); } await testToolCommand(client, args[1]); break; case 'test-all': await testAllCommand(client); break; case 'interactive': await interactiveMode(client); break; default: console.log(colorize(`❌ Unknown command: ${command}`, 'red')); showHelp(); process.exit(1); } } catch (error) { console.log(colorize(`❌ Fatal error: ${error.message}`, 'red')); process.exit(1); } finally { await client.stop(); } } function showHelp() { console.log(colorize('MCP Test Client - CLI tool for testing MCP GitHub Project Manager', 'cyan')); console.log(colorize('=' .repeat(70), 'cyan')); console.log('\nUsage:'); console.log(' node scripts/mcp-test-client.js <command> [options]'); console.log('\nCommands:'); console.log(colorize(' list-tools', 'green') + ' List all available tools with descriptions'); console.log(colorize(' test-tool <name>', 'green') + ' Test a specific tool with sample data'); console.log(colorize(' test-all', 'green') + ' Test all tools with sample data'); console.log(colorize(' interactive', 'green') + ' Enter interactive mode for manual testing'); console.log(colorize(' help', 'green') + ' Show this help message'); console.log('\nEnvironment Variables (Required):'); console.log(' GITHUB_TOKEN GitHub personal access token'); console.log(' GITHUB_OWNER GitHub repository owner'); console.log(' GITHUB_REPO GitHub repository name'); console.log('\nOptional Environment Variables:'); console.log(' MCP_SERVER_PATH Path to MCP server executable (default: ../build/index.js)'); console.log('\nExamples:'); console.log(' # List all available tools'); console.log(' node scripts/mcp-test-client.js list-tools'); console.log(''); console.log(' # Test a specific tool'); console.log(' node scripts/mcp-test-client.js test-tool create_project'); console.log(''); console.log(' # Test all tools'); console.log(' node scripts/mcp-test-client.js test-all'); console.log(''); console.log(' # Interactive mode'); console.log(' node scripts/mcp-test-client.js interactive'); } // Handle process termination gracefully process.on('SIGINT', () => { console.log(colorize('\n\n👋 Goodbye!', 'yellow')); process.exit(0); }); process.on('SIGTERM', () => { console.log(colorize('\n\n👋 Goodbye!', 'yellow')); process.exit(0); }); // Run the main function main().catch(error => { console.log(colorize(`❌ Unhandled error: ${error.message}`, 'red')); console.error(error.stack); process.exit(1); });

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/kunwarVivek/mcp-github-project-manager'

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