UNPKG

@boundless-oss/atlas

Version:

Atlas - MCP Server for comprehensive startup project management

820 lines 31.9 kB
import { randomUUID } from 'crypto'; import { createTool, createSuccessResult, createErrorResult } from '../../core/tool-framework.js'; /** * Create a new automation process */ const createProcessTool = createTool({ name: 'create_process', description: 'Create a new automation process with triggers and activities', category: 'process-automation', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Process name', minLength: 1, maxLength: 200 }, description: { type: 'string', description: 'Process description', maxLength: 1000 }, version: { type: 'string', description: 'Process version', default: '1.0.0', pattern: '^\\d+\\.\\d+\\.\\d+$' }, persona: { type: 'string', description: 'Persona type for the process' }, category: { type: 'string', description: 'Process category' }, tags: { type: 'array', items: { type: 'string' }, description: 'Process tags for organization', maxItems: 20 }, triggers: { type: 'array', items: { type: 'object', properties: { type: { type: 'string', enum: ['manual', 'schedule', 'event', 'webhook', 'condition', 'chain'] }, name: { type: 'string' }, enabled: { type: 'boolean', default: true }, config: { type: 'object' } }, required: ['type', 'name', 'config'] }, description: 'Process triggers', minItems: 1 }, activities: { type: 'array', items: { type: 'object', properties: { id: { type: 'string' }, type: { type: 'string', enum: ['tool', 'human', 'agent', 'conditional', 'loop', 'parallel', 'external'] }, name: { type: 'string' }, description: { type: 'string' }, config: { type: 'object' }, retryPolicy: { type: 'object', properties: { maxRetries: { type: 'integer', minimum: 0 }, backoffMultiplier: { type: 'number' }, initialDelay: { type: 'integer' } } }, timeout: { type: 'integer', minimum: 1000 }, required: { type: 'boolean', default: true } }, required: ['id', 'type', 'name', 'config'] }, description: 'Process activities to execute', minItems: 1 }, variables: { type: 'object', description: 'Process variables' }, onSuccess: { type: 'array', description: 'Activities to run on success' }, onFailure: { type: 'array', description: 'Activities to run on failure' } }, required: ['name', 'activities'], additionalProperties: false }, async execute(input, context) { try { const processId = randomUUID(); const now = Date.now(); // Check for duplicate process names const existingProcess = await context.db.get('SELECT id FROM process_definitions WHERE name = ? AND project_id = ? AND is_active = TRUE', [input.name, context.projectId || 'default']); if (existingProcess.success && existingProcess.data) { return createErrorResult({ code: 'DUPLICATE_RESOURCE', message: 'An active process with this name already exists', category: 'validation' }); } // Start transaction for process creation await context.db.transaction(async (tx) => { // Create process definition await tx.run(`INSERT INTO process_definitions (id, project_id, name, description, version, persona, category, tags, variables, on_success, on_failure, is_active, created_by, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ processId, context.projectId || 'default', input.name, input.description || '', input.version || '1.0.0', input.persona || null, input.category || null, JSON.stringify(input.tags || []), JSON.stringify(input.variables || {}), input.onSuccess ? JSON.stringify(input.onSuccess) : null, input.onFailure ? JSON.stringify(input.onFailure) : null, true, context.userId || 'system', now, now ]); // Create activities for (let i = 0; i < input.activities.length; i++) { const activity = input.activities[i]; await tx.run(`INSERT INTO process_activities (id, process_id, activity_order, activity_id, type, name, description, config, retry_policy, timeout, required, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ randomUUID(), processId, i, activity.id, activity.type, activity.name, activity.description || '', JSON.stringify(activity.config), activity.retryPolicy ? JSON.stringify(activity.retryPolicy) : null, activity.timeout || null, activity.required !== false, now ]); } // Create triggers if provided if (input.triggers) { for (const trigger of input.triggers) { await tx.run(`INSERT INTO process_triggers (id, process_id, trigger_id, type, name, enabled, config, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ randomUUID(), processId, randomUUID(), trigger.type, trigger.name, trigger.enabled !== false, JSON.stringify(trigger.config), now, now ]); } } else { // Create default manual trigger await tx.run(`INSERT INTO process_triggers (id, process_id, trigger_id, type, name, enabled, config, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ randomUUID(), processId, randomUUID(), 'manual', 'Manual Trigger', true, JSON.stringify({}), now, now ]); } }); return createSuccessResult({ process: { id: processId, name: input.name, description: input.description || '', version: input.version || '1.0.0', persona: input.persona || null, category: input.category || null, tags: input.tags || [], activityCount: input.activities.length, triggerCount: input.triggers?.length || 1, createdAt: new Date(now).toISOString() }, message: `Process "${input.name}" created successfully`, nextSteps: [ 'Test the process with manual execution', 'Configure additional triggers if needed', 'Monitor execution results' ] }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to create process: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Execute a process */ const executeProcessTool = createTool({ name: 'execute_process', description: 'Execute an automation process manually', category: 'process-automation', inputSchema: { type: 'object', properties: { processId: { type: 'string', description: 'Process ID to execute', pattern: '^[a-zA-Z0-9-]+$' }, variables: { type: 'object', description: 'Variables to pass to the process' }, triggeredBy: { type: 'string', description: 'Who/what triggered the execution' }, triggerType: { type: 'string', description: 'Type of trigger' } }, required: ['processId'], additionalProperties: false }, async execute(input, context) { try { // Get process definition const processResult = await context.db.get('SELECT * FROM process_definitions WHERE id = ? AND project_id = ? AND is_active = TRUE', [input.processId, context.projectId || 'default']); if (!processResult.success || !processResult.data) { return createErrorResult({ code: 'RESOURCE_NOT_FOUND', message: 'Process not found or inactive', category: 'validation' }); } const process = processResult.data; const executionId = randomUUID(); const now = Date.now(); // Merge process variables with input variables const processVariables = JSON.parse(process.variables || '{}'); const executionVariables = { ...processVariables, ...input.variables }; // Create execution record const execResult = await context.db.run(`INSERT INTO process_executions (id, process_id, project_id, process_version, status, triggered_by, trigger_type, variables, started_at, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ executionId, input.processId, context.projectId || 'default', process.version, 'pending', input.triggeredBy || context.userId || 'manual', input.triggerType || 'manual', JSON.stringify(executionVariables), now, now ]); if (!execResult.success) { return createErrorResult({ code: 'DATABASE_ERROR', message: 'Failed to create execution record', category: 'system' }); } // Log execution start await context.db.run(`INSERT INTO process_execution_logs (id, execution_id, timestamp, level, message) VALUES (?, ?, ?, ?, ?)`, [ randomUUID(), executionId, now, 'info', `Process execution started for "${process.name}"` ]); // Note: Actual execution would be handled by a background worker // This is just creating the execution record return createSuccessResult({ execution: { id: executionId, processId: input.processId, processName: process.name, status: 'pending', triggeredBy: input.triggeredBy || context.userId || 'manual', startedAt: new Date(now).toISOString() }, message: `Process execution started successfully`, note: 'Execution will be processed by the automation engine' }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to execute process: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * List processes */ const listProcessesTool = createTool({ name: 'list_processes', description: 'List all automation processes', category: 'process-automation', readOnly: true, inputSchema: { type: 'object', properties: { category: { type: 'string', description: 'Filter by category' }, persona: { type: 'string', description: 'Filter by persona' }, tags: { type: 'array', items: { type: 'string' }, description: 'Filter by tags' }, isActive: { type: 'boolean', description: 'Filter by active status', default: true } }, required: [], additionalProperties: false }, async execute(input, context) { try { let query = 'SELECT * FROM process_definitions WHERE project_id = ?'; const params = [context.projectId || 'default']; if (input.isActive !== undefined) { query += ' AND is_active = ?'; params.push(input.isActive); } if (input.category) { query += ' AND category = ?'; params.push(input.category); } if (input.persona) { query += ' AND persona = ?'; params.push(input.persona); } query += ' ORDER BY created_at DESC'; const result = await context.db.query(query, params); if (!result.success) { return createErrorResult({ code: 'DATABASE_ERROR', message: 'Failed to list processes', category: 'system' }); } const processes = []; for (const proc of result.data || []) { // Filter by tags if specified if (input.tags && input.tags.length > 0) { const processTags = JSON.parse(proc.tags || '[]'); if (!input.tags.some(tag => processTags.includes(tag))) { continue; } } // Get trigger count const triggerResult = await context.db.get('SELECT COUNT(*) as count FROM process_triggers WHERE process_id = ?', [proc.id]); // Get activity count const activityResult = await context.db.get('SELECT COUNT(*) as count FROM process_activities WHERE process_id = ?', [proc.id]); processes.push({ id: proc.id, name: proc.name, description: proc.description, version: proc.version, category: proc.category, persona: proc.persona, tags: JSON.parse(proc.tags || '[]'), isActive: proc.is_active, triggerCount: triggerResult.data?.count || 0, activityCount: activityResult.data?.count || 0, createdAt: new Date(proc.created_at).toISOString(), updatedAt: new Date(proc.updated_at).toISOString() }); } return createSuccessResult({ processes, count: processes.length, filters: { category: input.category, persona: input.persona, tags: input.tags, isActive: input.isActive } }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to list processes: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * List executions */ const listExecutionsTool = createTool({ name: 'list_executions', description: 'List process executions', category: 'process-automation', readOnly: true, inputSchema: { type: 'object', properties: { processId: { type: 'string', description: 'Filter by process ID', pattern: '^[a-zA-Z0-9-]+$' }, status: { type: 'string', enum: ['pending', 'running', 'completed', 'failed', 'cancelled'], description: 'Filter by status' }, limit: { type: 'integer', description: 'Maximum number of results', minimum: 1, maximum: 100, default: 20 }, offset: { type: 'integer', description: 'Offset for pagination', minimum: 0, default: 0 } }, required: [], additionalProperties: false }, async execute(input, context) { try { let query = ` SELECT e.*, p.name as process_name FROM process_executions e JOIN process_definitions p ON e.process_id = p.id WHERE e.project_id = ? `; const params = [context.projectId || 'default']; if (input.processId) { query += ' AND e.process_id = ?'; params.push(input.processId); } if (input.status) { query += ' AND e.status = ?'; params.push(input.status); } query += ' ORDER BY e.started_at DESC LIMIT ? OFFSET ?'; params.push(input.limit || 20, input.offset || 0); const result = await context.db.query(query, params); if (!result.success) { return createErrorResult({ code: 'DATABASE_ERROR', message: 'Failed to list executions', category: 'system' }); } const executions = (result.data || []).map((exec) => ({ id: exec.id, processId: exec.process_id, processName: exec.process_name, processVersion: exec.process_version, status: exec.status, triggeredBy: exec.triggered_by, triggerType: exec.trigger_type, variables: JSON.parse(exec.variables || '{}'), errorMessage: exec.error_message, errorCode: exec.error_code, startedAt: new Date(exec.started_at).toISOString(), completedAt: exec.completed_at ? new Date(exec.completed_at).toISOString() : null, duration: exec.duration })); return createSuccessResult({ executions, count: executions.length, limit: input.limit || 20, offset: input.offset || 0 }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to list executions: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Suggest triggers for a process */ const suggestTriggersTool = createTool({ name: 'suggest_triggers', description: 'Get AI-powered trigger suggestions for a process', category: 'process-automation', readOnly: true, inputSchema: { type: 'object', properties: { processName: { type: 'string', description: 'Process name' }, activities: { type: 'array', items: { type: 'object', properties: { type: { type: 'string' }, name: { type: 'string' }, toolName: { type: 'string' } }, required: ['type', 'name'] }, description: 'Process activities' }, persona: { type: 'string', description: 'Persona type' } }, required: ['processName', 'activities'], additionalProperties: false }, async execute(input, context) { try { // This is a simplified version - in production, this would use AI const suggestions = []; // Analyze activities to suggest appropriate triggers const hasDevActivities = input.activities.some(a => a.toolName?.includes('test') || a.toolName?.includes('build') || a.toolName?.includes('deploy')); const hasDataActivities = input.activities.some(a => a.toolName?.includes('backup') || a.toolName?.includes('sync') || a.toolName?.includes('report')); const hasReviewActivities = input.activities.some(a => a.type === 'human' || a.toolName?.includes('review') || a.toolName?.includes('approve')); // Development-related triggers if (hasDevActivities) { suggestions.push({ type: 'event', name: 'On Code Push', config: { eventType: 'git.push', branch: 'main' }, reasoning: 'Automatically run tests and builds when code is pushed', confidence: 0.9 }); suggestions.push({ type: 'schedule', name: 'Nightly Build', config: { cronExpression: '0 2 * * *', timezone: 'UTC' }, reasoning: 'Run comprehensive tests during off-hours', confidence: 0.8 }); } // Data-related triggers if (hasDataActivities) { suggestions.push({ type: 'schedule', name: 'Daily Backup', config: { cronExpression: '0 3 * * *', timezone: 'UTC' }, reasoning: 'Regular backups ensure data safety', confidence: 0.95 }); suggestions.push({ type: 'condition', name: 'On Storage Threshold', config: { condition: 'storage.usage > 80', checkInterval: 3600 }, reasoning: 'Trigger cleanup or alerts when storage is high', confidence: 0.85 }); } // Review-related triggers if (hasReviewActivities) { suggestions.push({ type: 'webhook', name: 'External Request', config: { path: `/webhooks/process/${input.processName.toLowerCase().replace(/\s+/g, '-')}`, auth: 'bearer' }, reasoning: 'Allow external systems to trigger reviews', confidence: 0.7 }); } // Always suggest manual trigger suggestions.push({ type: 'manual', name: 'Manual Execution', config: {}, reasoning: 'Always useful for testing and ad-hoc runs', confidence: 1.0 }); return createSuccessResult({ suggestions: suggestions.sort((a, b) => b.confidence - a.confidence), processContext: { name: input.processName, activityCount: input.activities.length, persona: input.persona } }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to suggest triggers: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * List process templates */ const listTemplatesTool = createTool({ name: 'process_list_templates', description: 'List available process templates', category: 'process-automation', readOnly: true, inputSchema: { type: 'object', properties: {}, required: [], additionalProperties: false }, async execute(input, context) { try { const result = await context.db.query('SELECT * FROM process_templates WHERE project_id = ? OR is_system = TRUE ORDER BY category, name', [context.projectId || 'default']); if (!result.success) { return createErrorResult({ code: 'DATABASE_ERROR', message: 'Failed to list templates', category: 'system' }); } const templates = (result.data || []).map((template) => ({ id: template.id, name: template.name, description: template.description, category: template.category, persona: template.persona, isSystem: template.is_system, variables: JSON.parse(template.variables || '{}'), createdAt: new Date(template.created_at).toISOString() })); // Group by category const templatesByCategory = templates.reduce((acc, template) => { if (!acc[template.category]) { acc[template.category] = []; } acc[template.category].push(template); return acc; }, {}); return createSuccessResult({ templates, templatesByCategory, count: templates.length }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to list templates: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Create process from template */ const createFromTemplateTool = createTool({ name: 'process_create_from_template', description: 'Create a process from a template', category: 'process-automation', inputSchema: { type: 'object', properties: { templateId: { type: 'string', description: 'Template ID', pattern: '^[a-zA-Z0-9-]+$' }, name: { type: 'string', description: 'Process name', minLength: 1, maxLength: 200 }, description: { type: 'string', description: 'Process description', maxLength: 1000 }, variables: { type: 'object', description: 'Override template variables' } }, required: ['templateId', 'name'], additionalProperties: false }, async execute(input, context) { try { // Get template const templateResult = await context.db.get('SELECT * FROM process_templates WHERE id = ? AND (project_id = ? OR is_system = TRUE)', [input.templateId, context.projectId || 'default']); if (!templateResult.success || !templateResult.data) { return createErrorResult({ code: 'RESOURCE_NOT_FOUND', message: 'Template not found', category: 'validation' }); } const template = templateResult.data; const processDefinition = JSON.parse(template.process_definition); const templateVariables = JSON.parse(template.variables || '{}'); // Merge variables const finalVariables = { ...templateVariables, ...input.variables }; // Create process from template const createInput = { name: input.name, description: input.description || template.description, version: '1.0.0', persona: template.persona, category: template.category, tags: processDefinition.tags || [], triggers: processDefinition.triggers || [{ type: 'manual', name: 'Manual Trigger', enabled: true, config: {} }], activities: processDefinition.activities, variables: finalVariables, onSuccess: processDefinition.onSuccess, onFailure: processDefinition.onFailure }; // Use the create process tool return await createProcessTool.execute(createInput, context); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to create from template: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Setup process automation tools */ export async function setupProcessAutomationTools() { return { module: 'process-automation', tools: [ createProcessTool, executeProcessTool, listProcessesTool, listExecutionsTool, suggestTriggersTool, listTemplatesTool, createFromTemplateTool ] }; } //# sourceMappingURL=tools.js.map