UNPKG

mcp-product-manager

Version:

MCP Orchestrator for task and project management with web interface

166 lines 6.11 kB
// create.js - POST /api/bundles import { Router } from 'express'; import { run, get, query } from '../../utils/database.js'; import { success, error, asyncHandler } from '../../utils/response.js'; const router = Router(); // Create a bundle from task IDs router.post('/api/bundles', asyncHandler(async (req, res) => { const { project, bundle_type = 'shared_files', task_ids, // Array or comma-separated string reason, created_by = 'orchestrator' } = req.body; if (!project || !task_ids || !reason) { const errorResponse = error('Missing required fields: project, task_ids, reason', 400); return res.status(errorResponse.status).json(errorResponse.body); } try { // Convert to array if string const taskIdsArray = Array.isArray(task_ids) ? task_ids : task_ids.split(',').map(id => id.trim()); // Validate at least 2 tasks if (taskIdsArray.length < 2) { const errorResponse = error('Bundle must contain at least 2 tasks', 400); return res.status(errorResponse.status).json(errorResponse.body); } // Verify all tasks exist and belong to project const placeholders = taskIdsArray.map(() => '?').join(','); const tasksQuery = ` SELECT id, estimated_hours, status, description, priority FROM tasks WHERE project = ? AND id IN (${placeholders}) `; const tasks = await query(tasksQuery, [project, ...taskIdsArray]); if (tasks.length !== taskIdsArray.length) { const foundIds = tasks.map(t => t.id); const missingIds = taskIdsArray.filter(id => !foundIds.includes(id)); const errorResponse = error(`Tasks not found: ${missingIds.join(', ')}`, 404); return res.status(errorResponse.status).json(errorResponse.body); } // Calculate total hours const totalHours = tasks.reduce((sum, task) => sum + (task.estimated_hours || 0), 0); // Generate bundle ID const bundleId = `BUNDLE-${new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)}`; // Begin transaction await run('BEGIN TRANSACTION'); try { // Create bundle await run(` INSERT INTO task_bundles ( id, project, bundle_type, task_count, total_estimated_hours, status, bundle_reason, created_by ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `, [ bundleId, project, bundle_type, taskIdsArray.length, totalHours, 'proposed', reason, created_by ]); // Link tasks to bundle with position const bundleTaskInserts = taskIdsArray.map((taskId, index) => run('INSERT INTO bundle_tasks (bundle_id, task_id, position) VALUES (?, ?, ?)', [bundleId, taskId, index + 1])); await Promise.all(bundleTaskInserts); // Log creation await run(` INSERT INTO coordination_updates ( action, agent_id, task_id, data ) VALUES ( 'bundle_created', ?, ?, ? ) `, [created_by, bundleId, JSON.stringify({ bundle_type, reason, task_count: taskIdsArray.length, total_hours: totalHours })]); await run('COMMIT'); // Get full bundle details const bundleQuery = ` SELECT b.*, json_group_array( json_object( 'id', t.id, 'description', t.description, 'status', t.status, 'priority', t.priority, 'estimated_hours', t.estimated_hours ) ) as tasks FROM task_bundles b JOIN bundle_tasks bt ON b.id = bt.bundle_id JOIN tasks t ON bt.task_id = t.id WHERE b.id = ? GROUP BY b.id `; const bundle = await get(bundleQuery, [bundleId]); res.status(201).json(success({ bundle_id: bundleId, bundle: { ...bundle, tasks: JSON.parse(bundle.tasks) } }, 'Bundle created successfully')); } catch (err) { await run('ROLLBACK'); throw err; } } catch (err) { console.error('Failed to create bundle:', err); const errorResponse = error('Failed to create bundle', 500, { error: err.message }); res.status(errorResponse.status).json(errorResponse.body); } })); // MCP tool definition export const tool = { name: 'bundle_create', description: 'Create a bundle from multiple task IDs for efficient batch processing', inputSchema: { type: 'object', properties: { project: { type: 'string', description: 'Project name' }, task_ids: { type: 'array', items: { type: 'string' }, description: 'Array of task IDs to bundle together', minItems: 2 }, bundle_type: { type: 'string', enum: ['shared_files', 'sequential', 'related', 'manual'], description: 'Type of bundle', default: 'shared_files' }, reason: { type: 'string', description: 'Reason for creating this bundle' }, created_by: { type: 'string', description: 'Who is creating this bundle', default: 'orchestrator' } }, required: ['project', 'task_ids', 'reason'] } }; export { router }; export default router; //# sourceMappingURL=create.js.map