UNPKG

mcp-product-manager

Version:

MCP Orchestrator for task and project management with web interface

176 lines 6.84 kB
import ErrorResponseBuilder from '../../api/utils/errorResponseBuilder.js'; import db from '../utils/database.js'; const errorResponseBuilder = new ErrorResponseBuilder(); // Track retry attempts per request const retryTracker = new Map(); function getRetryCount(requestKey) { const count = retryTracker.get(requestKey) || 0; return count; } function incrementRetryCount(requestKey) { const current = getRetryCount(requestKey); retryTracker.set(requestKey, current + 1); // Clean up old entries after 5 minutes setTimeout(() => { retryTracker.delete(requestKey); }, 5 * 60 * 1000); return current + 1; } function getRequestKey(req) { // Create a unique key based on agent, operation, and main parameters const agent = req.headers['x-agent-name'] || req.body?.agent || 'unknown'; const operation = req.path + ':' + req.method; const resourceId = req.params?.id || req.body?.task_id || req.body?.project || ''; return `${agent}:${operation}:${resourceId}`; } export const errorHandler = async (err, req, res, next) => { // Skip if response already sent if (res.headersSent) { next(err); return; } const requestKey = getRequestKey(req); const retryCount = err.retry ? incrementRetryCount(requestKey) : getRetryCount(requestKey); // Build context for error response const context = { agentName: req.headers['x-agent-name'] || req.body?.agent || 'unknown', agentType: req.headers['x-agent-type'] || req.agentType || 'unknown', retryCount: retryCount, operation: `${req.method} ${req.path}`, project: req.params?.project || req.body?.project || null, requestId: req.id || null, requiredPermission: err.requiredPermission || null, validationErrors: err.validationErrors || null, failedTasks: err.failedTasks || null }; // Map common errors to our error codes let mappedError = err; if (err.message?.includes('Permission denied') || err.message?.includes('403')) { mappedError = { ...err, code: 'PERMISSION_DENIED' }; } else if (err.message?.includes('not found') || err.message?.includes('404')) { if (err.message.includes('task')) { mappedError = { ...err, code: 'TASK_NOT_FOUND' }; } else if (err.message.includes('project')) { mappedError = { ...err, code: 'PROJECT_NOT_FOUND' }; } } else if (err.message?.includes('Database') || err.message?.includes('SQLITE')) { mappedError = { ...err, code: 'DATABASE_ERROR' }; } else if (err.message?.includes('already claimed') || err.message?.includes('concurrent')) { mappedError = { ...err, code: 'CONCURRENT_CLAIM' }; } else if (err.message?.includes('bundle') && err.message?.includes('claim')) { mappedError = { ...err, code: 'BUNDLE_CLAIM_FAILED' }; } else if (err.message?.includes('validation') || err.message?.includes('Invalid')) { mappedError = { ...err, code: 'VALIDATION_ERROR' }; } // Build the error response const errorResponse = errorResponseBuilder.build(mappedError, context); // Log error pattern to database await logErrorPattern(mappedError, context, errorResponse); // Send response res.status(errorResponse.metadata.httpStatus).json(errorResponse); }; async function logErrorPattern(error, context, response) { try { await db.run(` INSERT INTO error_patterns ( error_id, error_code, agent_name, agent_type, retry_count, operation, project, timestamp, http_status, category ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `, [ response.metadata.errorId, response.error.code, context.agentName, context.agentType, context.retryCount, context.operation, context.project, response.error.timestamp, response.metadata.httpStatus, response.error.category ]); // Check for intervention patterns (5+ same errors) const recentErrors = await db.query(` SELECT COUNT(*) as count FROM error_patterns WHERE error_code = ? AND agent_name = ? AND timestamp > datetime('now', '-30 minutes') `, [response.error.code, context.agentName]); const errorCount = recentErrors[0]?.count; if (errorCount && errorCount >= 5) { // Flag for intervention console.log(`INTERVENTION NEEDED: Agent ${context.agentName} has ${errorCount} ${response.error.code} errors`); } } catch (dbError) { // Don't fail the main error handler if logging fails console.error('Failed to log error pattern:', dbError); } } // Express error wrapper for async routes export const asyncHandler = (fn) => { return (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); }; }; // Permission checking middleware export const requirePermission = (permission) => { return (req, res, next) => { const agentType = req.headers['x-agent-type'] || req.agentType || 'unknown'; const permissions = { orchestrator: ['orch_read', 'orch_write', 'agent_read', 'shared_read', 'shared_write'], basic: ['agent_read', 'agent_write', 'shared_read', 'shared_write'], bundle: ['agent_read', 'agent_write', 'shared_read', 'shared_write'], critical: ['agent_read', 'agent_write', 'shared_read', 'shared_write'], maintenance: ['agent_read', 'agent_write', 'shared_read', 'shared_write'] }; if (!permissions[agentType]?.includes(permission)) { const error = new Error(`Permission denied: ${agentType} agents cannot access ${permission} operations`); error.code = 'PERMISSION_DENIED'; error.requiredPermission = permission; return next(error); } next(); }; }; // 404 handler export const notFoundHandler = (req, res) => { const context = { agentName: req.headers['x-agent-name'] || 'unknown', agentType: req.headers['x-agent-type'] || 'unknown', operation: `${req.method} ${req.path}`, retryCount: 0, project: null, requestId: null, requiredPermission: null, validationErrors: null, failedTasks: null }; const error = { code: 'ENDPOINT_NOT_FOUND', message: `Endpoint ${req.path} not found` }; const errorResponse = errorResponseBuilder.build(error, context); res.status(404).json(errorResponse); }; export default { errorHandler, asyncHandler, requirePermission, notFoundHandler }; //# sourceMappingURL=errorHandler.js.map