Skip to main content
Glama
mlaurel

Structured Workflow Engine MCP Server

by mlaurel
smart-workflow-engine.ts8.76 kB
import { WorkflowConfig, WorkflowStep, ExecutionContext, GetNextStepResponse, WorkflowExecutionPlan, MiniPrompt } from '../types/workflow-types'; import { WorkflowValidator, MCPRegistry } from '../validation/workflow-validator'; import { MiniPromptLoader } from '../loaders/mini-prompt-loader'; export class SmartWorkflowEngine { private validator: WorkflowValidator; private miniPromptLoader: MiniPromptLoader; private context: ExecutionContext; constructor( context: ExecutionContext, mcpRegistry: MCPRegistry, miniPromptLoader: MiniPromptLoader ) { this.context = context; this.validator = new WorkflowValidator(context, mcpRegistry); this.miniPromptLoader = miniPromptLoader; } /** * Execute smart workflow planning and get execution summary */ async planWorkflow(workflow: WorkflowConfig): Promise<WorkflowExecutionPlan> { console.log(`[SmartEngine] Planning workflow: ${workflow.name}`); const plan = this.validator.planWorkflowExecution(workflow); console.log(`[SmartEngine] Execution plan: - Total steps: ${plan.total_steps} - Executable steps: ${plan.executable_steps} - Skipped steps: ${plan.skipped_steps.length} - Execution rate: ${plan.execution_rate}%`); if (plan.skipped_steps.length > 0) { console.log(`[SmartEngine] Skipped steps:`); plan.skipped_steps.forEach(skip => { console.log(` - ${skip.id}: ${skip.reason}`); }); } return plan; } /** * Get next step with smart skipping */ async getNextStep( workflow: WorkflowConfig, currentStep: number ): Promise<GetNextStepResponse | null> { console.log(`[SmartEngine] Getting next step after ${currentStep}`); // Get next executable step const nextStep = this.validator.getNextExecutableStep(workflow, currentStep); if (!nextStep) { console.log(`[SmartEngine] Workflow complete`); return { workflow: workflow.name, currentPhase: { name: 'Complete', stepInPhase: 0, totalInPhase: 0 }, currentStep: { stepNumber: 0, totalSteps: 0, miniPrompt: {} as MiniPrompt, validation: { hasRequiredMCP: true, hasRequiredContext: true, hasOptionalContext: [], canExecute: false, skipReasons: ['Workflow complete'], missingMCP: [], missingContext: [] }, progress: '100% complete' }, skippedSteps: this.context.skipped_steps, isComplete: true }; } // Load mini-prompt for the step const miniPrompt = await this.miniPromptLoader.loadMiniPrompt( nextStep.phase, nextStep.id ); nextStep.miniPrompt = miniPrompt; // Calculate phase progress const phaseInfo = this.calculatePhaseProgress(workflow, nextStep); // Calculate total progress const totalExecutable = this.validator.planWorkflowExecution(workflow).executable_steps; const completedExecutable = this.context.completed_steps.length; const progressPercent = Math.round((completedExecutable / totalExecutable) * 100); const response: GetNextStepResponse = { workflow: workflow.name, currentPhase: { name: nextStep.phase, stepInPhase: phaseInfo.stepInPhase, totalInPhase: phaseInfo.totalInPhase }, currentStep: { stepNumber: completedExecutable + 1, totalSteps: totalExecutable, miniPrompt: nextStep.miniPrompt, validation: nextStep.validation, progress: `${progressPercent}% complete (Step ${completedExecutable + 1}/${totalExecutable})`, note: this.generateProgressNote(nextStep, this.context.skipped_steps.length) }, skippedSteps: this.context.skipped_steps, isComplete: false }; console.log(`[SmartEngine] Next step: ${nextStep.id} (${nextStep.phase})`); return response; } /** * Mark step as completed and update context */ async completeStep( stepId: string, outputs: Record<string, any> ): Promise<void> { console.log(`[SmartEngine] Completing step: ${stepId}`); this.validator.updateContextAfterStep(stepId, outputs); console.log(`[SmartEngine] Step ${stepId} completed. Context updated with outputs.`); } /** * Skip step manually with reason */ async skipStep(stepId: string, reason: string): Promise<void> { console.log(`[SmartEngine] Manually skipping step: ${stepId} - ${reason}`); this.context.skipped_steps.push({ id: stepId, reason: `Manual skip: ${reason}`, step_title: stepId }); } /** * Get skip suggestions based on skip conditions */ getSkipSuggestions(skipConditions: string[], context: ExecutionContext): { canSkip: boolean; reasons: string[]; } { return this.validator.checkSkipSuggestions(skipConditions); } /** * Get current execution summary */ getExecutionSummary(workflow?: WorkflowConfig): { completed: number; skipped: number; remaining: number; completionRate: number; } { let executableSteps = 1; // Default to 1 to avoid division by zero if (workflow) { const plan = this.validator.planWorkflowExecution(workflow); executableSteps = plan.executable_steps; } return { completed: this.context.completed_steps.length, skipped: this.context.skipped_steps.length, remaining: Math.max(0, executableSteps - this.context.completed_steps.length), completionRate: this.context.completed_steps.length / executableSteps }; } /** * Calculate phase progress information */ private calculatePhaseProgress( workflow: WorkflowConfig, currentStep: WorkflowStep ): { stepInPhase: number; totalInPhase: number } { let stepInPhase = 0; let totalInPhase = 0; // Find the current phase const currentPhase = workflow.phases.find(phase => phase.name === currentStep.phase); if (!currentPhase) { return { stepInPhase: 1, totalInPhase: 1 }; } // Count executable steps in this phase for (const stepConfig of currentPhase.steps) { const mockStep: WorkflowStep = { id: stepConfig.id, miniPrompt: {} as MiniPrompt, phase: currentPhase.name, stepNumber: 0, totalSteps: 0, validation: {} as any, prerequisites: stepConfig.prerequisites, skipConditions: stepConfig.skip_conditions || [] }; const validation = this.validator.validateStep(mockStep); if (validation.canExecute) { totalInPhase++; if (stepConfig.id === currentStep.id) { stepInPhase = totalInPhase; } } } return { stepInPhase, totalInPhase }; } /** * Generate progress note for current step */ private generateProgressNote( currentStep: WorkflowStep, totalSkipped: number ): string | undefined { if (totalSkipped === 0) { return undefined; } if (totalSkipped === 1) { return "Auto-skipped 1 step due to missing prerequisites"; } return `Auto-skipped ${totalSkipped} steps due to missing prerequisites`; } /** * Validate workflow configuration */ validateWorkflowConfig(workflow: WorkflowConfig): { isValid: boolean; errors: string[]; warnings: string[]; } { const errors: string[] = []; const warnings: string[] = []; // Check workflow structure if (!workflow.name || !workflow.description) { errors.push("Workflow must have name and description"); } if (!workflow.phases || workflow.phases.length === 0) { errors.push("Workflow must have at least one phase"); } // Check phases for (const phase of workflow.phases || []) { if (!phase.name) { errors.push("Each phase must have a name"); } if (!phase.steps || phase.steps.length === 0) { warnings.push(`Phase "${phase.name}" has no steps`); } // Check steps for (const step of phase.steps || []) { if (!step.id || !step.mini_prompt) { errors.push(`Step in phase "${phase.name}" missing id or mini_prompt`); } if (!step.prerequisites) { warnings.push(`Step "${step.id}" has no prerequisites defined`); } } } // Calculate execution rate const plan = this.validator.planWorkflowExecution(workflow); if (plan.execution_rate < 30) { warnings.push(`Low execution rate: ${plan.execution_rate}% (${plan.executable_steps}/${plan.total_steps} steps executable)`); } return { isValid: errors.length === 0, errors, warnings }; } }

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/mlaurel/mcp-workflow-engine'

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