Skip to main content
Glama
jakedx6
by jakedx6
workflow-automation.ts18.6 kB
import { z } from 'zod' // Local type definitions interface MCPTool { name: string description: string inputSchema: any } import { supabaseService } from '../lib/api-client.js' import { requireAuth } from '../lib/auth.js' import { logger } from '../lib/logger.js' /** * Create workflow automation rule */ export const createWorkflowRuleTool: MCPTool = { name: 'create_workflow_rule', description: 'Create an automation rule that triggers actions based on events', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Name of the automation rule' }, description: { type: 'string', description: 'Description of what this rule does' }, trigger: { type: 'object', properties: { event_type: { type: 'string', enum: ['task_status_changed', 'task_created', 'task_overdue', 'project_status_changed', 'document_created', 'document_updated'], description: 'Type of event that triggers this rule' }, conditions: { type: 'object', description: 'Conditions that must be met for trigger to fire' } }, required: ['event_type'] }, actions: { type: 'array', items: { type: 'object', properties: { action_type: { type: 'string', enum: ['create_task', 'update_task', 'send_notification', 'assign_task', 'move_task', 'create_document'], description: 'Type of action to perform' }, parameters: { type: 'object', description: 'Parameters for the action' } }, required: ['action_type', 'parameters'] }, description: 'Actions to perform when rule is triggered' }, project_id: { type: 'string', description: 'Project this rule applies to (optional for global rules)' }, enabled: { type: 'boolean', default: true, description: 'Whether this rule is active' } }, required: ['name', 'trigger', 'actions'] } } const CreateWorkflowRuleSchema = z.object({ name: z.string().min(1).max(200), description: z.string().optional(), trigger: z.object({ event_type: z.enum(['task_status_changed', 'task_created', 'task_overdue', 'project_status_changed', 'document_created', 'document_updated']), conditions: z.record(z.any()).optional() }), actions: z.array(z.object({ action_type: z.enum(['create_task', 'update_task', 'send_notification', 'assign_task', 'move_task', 'create_document']), parameters: z.record(z.any()) })).min(1), project_id: z.string().optional(), enabled: z.boolean().default(true) }) export const createWorkflowRule = requireAuth(async (args: any) => { const { name, description, trigger, actions, project_id, enabled } = CreateWorkflowRuleSchema.parse(args) logger.info('Creating workflow rule', { name, trigger: trigger.event_type, actions: actions.length }) const rule = await supabaseService.createWorkflowRule({ name, description, trigger, actions, project_id, enabled, created_at: new Date().toISOString() }) return { rule, message: `Workflow rule "${name}" created successfully` } }) /** * List workflow rules */ export const listWorkflowRulesTool: MCPTool = { name: 'list_workflow_rules', description: 'List all automation rules for a project or globally', inputSchema: { type: 'object', properties: { project_id: { type: 'string', description: 'Project ID to filter rules (optional)' }, enabled_only: { type: 'boolean', default: true, description: 'Only return enabled rules' } } } } const ListWorkflowRulesSchema = z.object({ project_id: z.string().optional(), enabled_only: z.boolean().default(true) }) export const listWorkflowRules = requireAuth(async (args: any) => { const { project_id, enabled_only } = ListWorkflowRulesSchema.parse(args) logger.info('Listing workflow rules', { project_id, enabled_only }) const rules = await supabaseService.getWorkflowRules({ project_id, enabled: enabled_only ? true : undefined }) return { rules, total_rules: rules.length, active_rules: rules.filter(r => r.enabled).length } }) /** * Execute workflow rule manually */ export const executeWorkflowRuleTool: MCPTool = { name: 'execute_workflow_rule', description: 'Manually execute a workflow rule for testing', inputSchema: { type: 'object', properties: { rule_id: { type: 'string', description: 'ID of the rule to execute' }, test_data: { type: 'object', description: 'Test data to simulate the trigger event' }, dry_run: { type: 'boolean', default: false, description: 'If true, only simulate execution without performing actions' } }, required: ['rule_id'] } } const ExecuteWorkflowRuleSchema = z.object({ rule_id: z.string().min(1), test_data: z.record(z.any()).optional(), dry_run: z.boolean().default(false) }) export const executeWorkflowRule = requireAuth(async (args: any) => { const { rule_id, test_data, dry_run } = ExecuteWorkflowRuleSchema.parse(args) logger.info('Executing workflow rule', { rule_id, dry_run }) const rule = await supabaseService.getWorkflowRule(rule_id) if (!rule) { throw new Error('Workflow rule not found') } if (!rule.enabled && !dry_run) { throw new Error('Cannot execute disabled rule (use dry_run for testing)') } const executionResults = [] for (const action of rule.actions) { try { const result = await executeAction(action, test_data, dry_run) executionResults.push({ action_type: action.action_type, success: true, result, dry_run }) } catch (error) { logger.error(`Failed to execute action ${action.action_type}:`, error) executionResults.push({ action_type: action.action_type, success: false, error: error instanceof Error ? error.message : 'Unknown error', dry_run }) } } // Log execution if not dry run if (!dry_run) { await supabaseService.logWorkflowExecution({ rule_id, trigger_data: test_data, execution_results: executionResults, executed_at: new Date().toISOString() }) } return { rule_name: rule.name, executed_actions: executionResults.length, successful_actions: executionResults.filter(r => r.success).length, failed_actions: executionResults.filter(r => !r.success).length, results: executionResults, dry_run } }) /** * Create trigger-based automation */ export const createTriggerAutomationTool: MCPTool = { name: 'create_trigger_automation', description: 'Create automated workflows triggered by specific events', inputSchema: { type: 'object', properties: { automation_name: { type: 'string', description: 'Name of the automation' }, trigger_events: { type: 'array', items: { type: 'string', enum: ['task_completed', 'task_blocked', 'deadline_approaching', 'project_milestone', 'team_member_assigned'] }, description: 'Events that trigger this automation' }, conditions: { type: 'object', properties: { priority: { type: 'string', enum: ['low', 'medium', 'high', 'urgent'] }, project_id: { type: 'string' }, assignee_id: { type: 'string' }, tags: { type: 'array', items: { type: 'string' } } }, description: 'Conditions that must be met' }, automated_actions: { type: 'array', items: { type: 'object', properties: { type: { type: 'string', enum: ['create_follow_up_task', 'notify_team', 'update_project_status', 'assign_reviewer', 'schedule_meeting'] }, config: { type: 'object' } }, required: ['type', 'config'] }, description: 'Actions to perform automatically' }, project_scope: { type: 'string', enum: ['single_project', 'all_projects', 'specific_projects'], default: 'single_project', description: 'Scope of automation' } }, required: ['automation_name', 'trigger_events', 'automated_actions'] } } const CreateTriggerAutomationSchema = z.object({ automation_name: z.string().min(1).max(200), trigger_events: z.array(z.enum(['task_completed', 'task_blocked', 'deadline_approaching', 'project_milestone', 'team_member_assigned'])).min(1), conditions: z.object({ priority: z.enum(['low', 'medium', 'high', 'urgent']).optional(), project_id: z.string().optional(), assignee_id: z.string().optional(), tags: z.array(z.string()).optional() }).optional(), automated_actions: z.array(z.object({ type: z.enum(['create_follow_up_task', 'notify_team', 'update_project_status', 'assign_reviewer', 'schedule_meeting']), config: z.record(z.any()) })).min(1), project_scope: z.enum(['single_project', 'all_projects', 'specific_projects']).default('single_project') }) export const createTriggerAutomation = requireAuth(async (args: any) => { const { automation_name, trigger_events, conditions, automated_actions, project_scope } = CreateTriggerAutomationSchema.parse(args) logger.info('Creating trigger automation', { automation_name, trigger_events, actions_count: automated_actions.length }) const automation = await supabaseService.createTriggerAutomation({ name: automation_name, trigger_events, conditions: conditions || {}, actions: automated_actions, scope: project_scope, enabled: true, created_at: new Date().toISOString() }) return { automation, trigger_count: trigger_events.length, action_count: automated_actions.length, scope: project_scope, message: `Automation "${automation_name}" created and enabled` } }) /** * Get automation analytics */ export const getAutomationAnalyticsTool: MCPTool = { name: 'get_automation_analytics', description: 'Get analytics and performance data for workflow automations', inputSchema: { type: 'object', properties: { project_id: { type: 'string', description: 'Project ID to filter analytics (optional)' }, time_range: { type: 'string', enum: ['day', 'week', 'month', 'quarter'], default: 'week', description: 'Time range for analytics' }, include_inactive: { type: 'boolean', default: false, description: 'Include disabled automations in analytics' } } } } const GetAutomationAnalyticsSchema = z.object({ project_id: z.string().optional(), time_range: z.enum(['day', 'week', 'month', 'quarter']).default('week'), include_inactive: z.boolean().default(false) }) export const getAutomationAnalytics = requireAuth(async (args: any) => { const { project_id, time_range, include_inactive } = GetAutomationAnalyticsSchema.parse(args) logger.info('Getting automation analytics', { project_id, time_range }) const [rules, executions] = await Promise.all([ supabaseService.getWorkflowRules({ project_id, enabled: include_inactive ? undefined : true }), supabaseService.getWorkflowExecutions({ project_id, time_range }) ]) // Calculate analytics const analytics = calculateAutomationAnalytics(rules, executions, time_range) return { summary: { total_rules: rules.length, active_rules: rules.filter(r => r.enabled).length, total_executions: executions.length, successful_executions: executions.filter(e => e.success).length, time_range }, performance: analytics.performance, top_performers: analytics.topPerformers, failure_analysis: analytics.failureAnalysis, recommendations: analytics.recommendations } }) // Helper functions async function executeAction(action: any, testData: any, dryRun: boolean): Promise<any> { if (dryRun) { return { simulated: true, action_type: action.action_type, parameters: action.parameters } } switch (action.action_type) { case 'create_task': return await supabaseService.createTask({ title: action.parameters.title, description: action.parameters.description, project_id: action.parameters.project_id || testData?.project_id, initiative_id: null, priority: action.parameters.priority || 'medium', assignee_id: action.parameters.assignee_id, status: 'todo', due_date: null }) case 'update_task': return await supabaseService.updateTask( action.parameters.task_id || testData?.task_id, action.parameters.updates ) case 'send_notification': // Would integrate with notification service return { notification_sent: true, recipient: action.parameters.recipient, message: action.parameters.message } case 'assign_task': return await supabaseService.updateTask( action.parameters.task_id || testData?.task_id, { assignee_id: action.parameters.assignee_id } ) case 'move_task': return await supabaseService.updateTask( action.parameters.task_id || testData?.task_id, { status: action.parameters.new_status } ) case 'create_document': return await supabaseService.createDocument({ title: action.parameters.title, content: action.parameters.content, project_id: action.parameters.project_id || testData?.project_id, document_type: action.parameters.document_type || 'note' }) default: throw new Error(`Unknown action type: ${action.action_type}`) } } function calculateAutomationAnalytics(rules: any[], executions: any[], timeRange: string): any { const now = new Date() const timeRangeMs = getTimeRangeMs(timeRange) const recentExecutions = executions.filter(e => new Date(e.executed_at).getTime() > now.getTime() - timeRangeMs ) // Performance metrics const performance = { total_executions: recentExecutions.length, success_rate: recentExecutions.length > 0 ? (recentExecutions.filter(e => e.success).length / recentExecutions.length) * 100 : 0, average_execution_time: calculateAverageExecutionTime(recentExecutions), executions_per_day: recentExecutions.length / getDaysInRange(timeRange) } // Top performing rules const rulePerformance = new Map() recentExecutions.forEach(exec => { const ruleId = exec.rule_id if (!rulePerformance.has(ruleId)) { rulePerformance.set(ruleId, { executions: 0, successes: 0 }) } const stats = rulePerformance.get(ruleId) stats.executions++ if (exec.success) stats.successes++ }) const topPerformers = Array.from(rulePerformance.entries()) .map(([ruleId, stats]) => ({ rule_id: ruleId, rule_name: rules.find(r => r.id === ruleId)?.name || 'Unknown', executions: stats.executions, success_rate: (stats.successes / stats.executions) * 100 })) .sort((a, b) => b.executions - a.executions) .slice(0, 5) // Failure analysis const failures = recentExecutions.filter(e => !e.success) const failureReasons = new Map() failures.forEach(f => { const reason = f.error_message || 'Unknown error' failureReasons.set(reason, (failureReasons.get(reason) || 0) + 1) }) const failureAnalysis = { total_failures: failures.length, failure_rate: recentExecutions.length > 0 ? (failures.length / recentExecutions.length) * 100 : 0, common_failures: Array.from(failureReasons.entries()) .map(([reason, count]) => ({ reason, count })) .sort((a, b) => b.count - a.count) .slice(0, 3) } // Recommendations const recommendations = generateAutomationRecommendations(performance, failureAnalysis, rules) return { performance, topPerformers, failureAnalysis, recommendations } } function getTimeRangeMs(timeRange: string): number { const day = 24 * 60 * 60 * 1000 switch (timeRange) { case 'day': return day case 'week': return 7 * day case 'month': return 30 * day case 'quarter': return 90 * day default: return 7 * day } } function getDaysInRange(timeRange: string): number { switch (timeRange) { case 'day': return 1 case 'week': return 7 case 'month': return 30 case 'quarter': return 90 default: return 7 } } function calculateAverageExecutionTime(executions: any[]): number { if (executions.length === 0) return 0 const totalTime = executions.reduce((sum, exec) => { const duration = exec.execution_duration || 1000 // Default 1 second return sum + duration }, 0) return Math.round(totalTime / executions.length) } function generateAutomationRecommendations(performance: any, failureAnalysis: any, rules: any[]): string[] { const recommendations = [] if (performance.success_rate < 90) { recommendations.push('Consider reviewing failed automations to improve reliability') } if (performance.executions_per_day < 1) { recommendations.push('Low automation usage - consider creating more trigger-based rules') } if (failureAnalysis.failure_rate > 10) { recommendations.push('High failure rate detected - review automation conditions and actions') } if (rules.filter(r => r.enabled).length < 3) { recommendations.push('Create more automation rules to improve workflow efficiency') } if (performance.average_execution_time > 5000) { recommendations.push('Long execution times detected - optimize automation actions') } return recommendations } // Export all workflow automation tools export const workflowAutomationTools = { createWorkflowRuleTool, listWorkflowRulesTool, executeWorkflowRuleTool, createTriggerAutomationTool, getAutomationAnalyticsTool } export const workflowAutomationHandlers = { create_workflow_rule: createWorkflowRule, list_workflow_rules: listWorkflowRules, execute_workflow_rule: executeWorkflowRule, create_trigger_automation: createTriggerAutomation, get_automation_analytics: getAutomationAnalytics }

Implementation Reference

Latest Blog Posts

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/jakedx6/helios9-MCP-Server'

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