const { spawn } = require('child_process'); console.error('🔄 Starting MCP server for comprehensive endpoint testing...'); // Test configuration let currentStepIndex = 0; let messageId = 1; let testData = { projectId: null, taskId: null, createdProjectId: null, taskTitle: "Test Task from MCP Script", updatedTaskTitle: "Updated Test Task Title", secondTaskTitle: "Second Test Task", renamedTaskTitle: "Renamed Second Task", }; const testSequence = [ 'initialize', 'initialized_notification', 'list_tools', 'list_projects', 'create_project', 'list_tasks', // empty 'create_task', 'get_task', 'list_tasks', // with task 'set_task_status', 'list_tasks', // filtered 'complete_task', 'list_tasks', // completed 'create_task', // second task 'update_task', // legacy 'update_task_title', 'update_task_description', 'list_tasks', // after updates 'delete_task_by_title', 'list_tasks', // final 'summary' ]; const stepHandlers = { initialize: { description: 'Initialize MCP connection', action: () => { console.log('📤 Sending initialize request...'); mcpProcess.stdin.write(`{"jsonrpc": "2.0", "id": ${messageId++}, "method": "initialize", "params": {"protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": {"name": "test", "version": "1.0.0"}}}\n`); }, responseHandler: () => { executeNextStep(); } }, initialized_notification: { description: 'Send initialized notification', action: () => { console.log('📤 Sending initialized notification...'); mcpProcess.stdin.write('{"jsonrpc": "2.0", "method": "notifications/initialized"}\n'); // Notifications don't have responses, auto-advance setTimeout(() => executeNextStep(), 200); }, responseHandler: null }, list_tools: { description: 'List available MCP tools', action: () => { console.log('📤 Sending tools/list...'); mcpProcess.stdin.write(`{"jsonrpc": "2.0", "id": ${messageId++}, "method": "tools/list", "params": {}}\n`); }, responseHandler: () => { executeNextStep(); } }, list_projects: { description: 'List all projects', action: () => { console.log('📤 Sending list_projects...'); mcpProcess.stdin.write(`{"jsonrpc": "2.0", "id": ${messageId++}, "method": "tools/call", "params": {"name": "list_projects", "arguments": {}}}\n`); }, responseHandler: (response) => { try { const parsedResponse = JSON.parse(response); if (parsedResponse.result?.content) { const projectsResponse = JSON.parse(parsedResponse.result.content[0].text); if (projectsResponse.success && projectsResponse.projects.length > 0) { testData.projectId = projectsResponse.projects[0].id; console.log(`💾 Found existing project: ${testData.projectId}`); } } } catch (e) { console.error('⚠️ Could not parse projects response'); } executeNextStep(); } }, create_project: { description: 'Create a new test project', action: () => { console.log('📤 Sending create_project...'); mcpProcess.stdin.write(`{"jsonrpc": "2.0", "id": ${messageId++}, "method": "tools/call", "params": {"name": "create_project", "arguments": {"name": "Test Project from MCP", "git_repo_path": "/tmp/test-project", "use_existing_repo": false, "setup_script": "echo \\"Setup complete\\"", "dev_script": "echo \\"Dev server started\\""}}}\n`); }, responseHandler: (response) => { try { const parsedResponse = JSON.parse(response); if (parsedResponse.result?.content) { const createProjectResponse = JSON.parse(parsedResponse.result.content[0].text); if (createProjectResponse.success && createProjectResponse.project_id) { testData.createdProjectId = createProjectResponse.project_id; console.log(`💾 Created project: ${testData.createdProjectId}`); } } } catch (e) { console.error('⚠️ Could not parse create project response'); } executeNextStep(); } }, list_tasks: { description: 'List tasks in project', action: () => { const projectToUse = testData.createdProjectId || testData.projectId; const context = getListTasksContext(); console.log(`📤 Sending list_tasks (${context})...`); let args = { project_id: projectToUse }; // Add context-specific filters if (context === 'filtered') { args.status = 'in-progress'; } else if (context === 'completed') { args.status = 'done'; } else if (context === 'empty') { args.include_execution_status = true; } mcpProcess.stdin.write(`{"jsonrpc": "2.0", "id": ${messageId++}, "method": "tools/call", "params": {"name": "list_tasks", "arguments": ${JSON.stringify(args)}}}\n`); }, responseHandler: () => { executeNextStep(); } }, create_task: { description: 'Create a new task', action: () => { const projectToUse = testData.createdProjectId || testData.projectId; const isSecondTask = getCreateTaskContext() === 'second'; const title = isSecondTask ? testData.secondTaskTitle : testData.taskTitle; const description = isSecondTask ? "This is a second task for testing updates" : "This task was created during endpoint testing"; console.log(`📤 Sending create_task (${isSecondTask ? 'second task' : 'first task'})...`); mcpProcess.stdin.write(`{"jsonrpc": "2.0", "id": ${messageId++}, "method": "tools/call", "params": {"name": "create_task", "arguments": {"project_id": "${projectToUse}", "title": "${title}", "description": "${description}"}}}\n`); }, responseHandler: (response) => { try { const parsedResponse = JSON.parse(response); if (parsedResponse.result?.content) { const createTaskResponse = JSON.parse(parsedResponse.result.content[0].text); if (createTaskResponse.success && createTaskResponse.task_id) { testData.taskId = createTaskResponse.task_id; console.log(`💾 Created task: ${testData.taskId}`); } } } catch (e) { console.error('⚠️ Could not parse create task response'); } executeNextStep(); } }, get_task: { description: 'Get task details by ID', action: () => { const projectToUse = testData.createdProjectId || testData.projectId; console.log('📤 Sending get_task...'); mcpProcess.stdin.write(`{"jsonrpc": "2.0", "id": ${messageId++}, "method": "tools/call", "params": {"name": "get_task", "arguments": {"project_id": "${projectToUse}", "task_id": "${testData.taskId}"}}}\n`); }, responseHandler: () => { executeNextStep(); } }, set_task_status: { description: 'Set task status (agent-friendly)', action: () => { const projectToUse = testData.createdProjectId || testData.projectId; console.log('📤 Sending set_task_status (agent-friendly)...'); mcpProcess.stdin.write(`{"jsonrpc": "2.0", "id": ${messageId++}, "method": "tools/call", "params": {"name": "set_task_status", "arguments": {"project_id": "${projectToUse}", "task_title": "${testData.taskTitle}", "status": "in-progress"}}}\n`); }, responseHandler: () => { executeNextStep(); } }, complete_task: { description: 'Complete task (agent-friendly)', action: () => { const projectToUse = testData.createdProjectId || testData.projectId; console.log('📤 Sending complete_task (agent-friendly)...'); mcpProcess.stdin.write(`{"jsonrpc": "2.0", "id": ${messageId++}, "method": "tools/call", "params": {"name": "complete_task", "arguments": {"project_id": "${projectToUse}", "task_title": "${testData.taskTitle}"}}}\n`); }, responseHandler: () => { executeNextStep(); } }, update_task: { description: 'Update task (legacy UUID method)', action: () => { const projectToUse = testData.createdProjectId || testData.projectId; console.log('📤 Sending update_task (legacy method)...'); mcpProcess.stdin.write(`{"jsonrpc": "2.0", "id": ${messageId++}, "method": "tools/call", "params": {"name": "update_task", "arguments": {"project_id": "${projectToUse}", "task_id": "${testData.taskId}", "title": "${testData.updatedTaskTitle}", "description": "Updated description via legacy method", "status": "in-review"}}}\n`); }, responseHandler: () => { executeNextStep(); } }, update_task_title: { description: 'Update task title (agent-friendly)', action: () => { const projectToUse = testData.createdProjectId || testData.projectId; console.log('📤 Sending update_task_title (agent-friendly)...'); mcpProcess.stdin.write(`{"jsonrpc": "2.0", "id": ${messageId++}, "method": "tools/call", "params": {"name": "update_task_title", "arguments": {"project_id": "${projectToUse}", "current_title": "${testData.secondTaskTitle}", "new_title": "${testData.renamedTaskTitle}"}}}\n`); }, responseHandler: () => { executeNextStep(); } }, update_task_description: { description: 'Update task description (agent-friendly)', action: () => { const projectToUse = testData.createdProjectId || testData.projectId; console.log('📤 Sending update_task_description (agent-friendly)...'); mcpProcess.stdin.write(`{"jsonrpc": "2.0", "id": ${messageId++}, "method": "tools/call", "params": {"name": "update_task_description", "arguments": {"project_id": "${projectToUse}", "task_title": "${testData.renamedTaskTitle}", "description": "This description was updated using the agent-friendly endpoint"}}}\n`); }, responseHandler: () => { executeNextStep(); } }, delete_task_by_title: { description: 'Delete task by title (agent-friendly)', action: () => { const projectToUse = testData.createdProjectId || testData.projectId; console.log('📤 Sending delete_task_by_title (agent-friendly)...'); mcpProcess.stdin.write(`{"jsonrpc": "2.0", "id": ${messageId++}, "method": "tools/call", "params": {"name": "delete_task_by_title", "arguments": {"project_id": "${projectToUse}", "task_title": "${testData.renamedTaskTitle}"}}}\n`); }, responseHandler: () => { executeNextStep(); } }, summary: { description: 'Test completion summary', action: () => { console.log('✅ All endpoint tests completed successfully!'); console.log(''); console.log('📊 Test Summary:'); console.log(` - Project ID used: ${testData.projectId || 'N/A'}`); console.log(` - Created project: ${testData.createdProjectId || 'N/A'}`); console.log(` - Task ID tested: ${testData.taskId || 'N/A'}`); console.log(` - Task title: ${testData.taskTitle}`); console.log(''); console.log('🎯 Agent-Friendly Endpoints Tested:'); console.log(' ✅ set_task_status - Change task status by title'); console.log(' ✅ complete_task - Mark task done by title'); console.log(' ✅ update_task_title - Change task title'); console.log(' ✅ update_task_description - Update task description'); console.log(' ✅ delete_task_by_title - Delete task by title'); console.log(''); console.log('🔧 Legacy Endpoints Tested:'); console.log(' ✅ update_task - Update task by ID (more complex)'); console.log(' ✅ get_task - Get task details by ID'); console.log(''); console.log('🎉 All MCP endpoints are working correctly!'); console.log('💡 Agents should prefer the title-based endpoints for easier usage'); setTimeout(() => mcpProcess.kill(), 500); }, responseHandler: null } }; // Helper functions to determine context function getListTasksContext() { const prevSteps = testSequence.slice(0, currentStepIndex); if (prevSteps[prevSteps.length - 1] === 'create_project') return 'empty'; if (prevSteps[prevSteps.length - 1] === 'set_task_status') return 'filtered'; if (prevSteps[prevSteps.length - 1] === 'complete_task') return 'completed'; if (prevSteps[prevSteps.length - 1] === 'update_task_description') return 'after updates'; if (prevSteps[prevSteps.length - 1] === 'delete_task_by_title') return 'final'; return 'with task'; } function getCreateTaskContext() { const prevSteps = testSequence.slice(0, currentStepIndex); const createTaskCount = prevSteps.filter(step => step === 'create_task').length; return createTaskCount > 0 ? 'second' : 'first'; } // Execute current step function executeCurrentStep() { if (currentStepIndex >= testSequence.length) { console.log('⚠️ All steps completed'); return; } const stepName = testSequence[currentStepIndex]; const stepHandler = stepHandlers[stepName]; if (!stepHandler) { console.error(`❌ Unknown step: ${stepName}`); return; } console.log(`🔄 Step ${currentStepIndex + 1}/${testSequence.length}: ${stepHandler.description}`); setTimeout(() => { stepHandler.action(); }, 100); } // Move to next step function executeNextStep() { currentStepIndex++; executeCurrentStep(); } // Start MCP process const mcpProcess = spawn('vibe-kanban', ["--mcp"], { stdio: ['pipe', 'pipe', 'inherit'], }); mcpProcess.stdout.on('data', (data) => { const response = data.toString().trim(); const currentStepName = testSequence[currentStepIndex]; const stepHandler = stepHandlers[currentStepName]; console.log(`📥 MCP Response (${currentStepName}):`); console.log(response); if (stepHandler?.responseHandler) { stepHandler.responseHandler(response); } }); mcpProcess.on('exit', (code) => { console.error(`🔴 MCP server exited with code: ${code}`); process.exit(0); }); mcpProcess.on('error', (error) => { console.error('❌ MCP server error:', error); process.exit(1); }); // Start the sequence setTimeout(() => { executeCurrentStep(); }, 500); // Safety timeout setTimeout(() => { console.error('⏰ Test timeout - killing process'); mcpProcess.kill(); }, 45000);