UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

104 lines (103 loc) 6.32 kB
import { workflowRunnerInputSchema } from './schema.js'; import { registerTool } from '../../services/routing/toolRegistry.js'; import { executeWorkflow } from '../../services/workflows/workflowExecutor.js'; import { AppError, ToolExecutionError } from '../../utils/errors.js'; import logger from '../../logger.js'; import { jobManager, JobStatus } from '../../services/job-manager/index.js'; import { sseNotifier } from '../../services/sse-notifier/index.js'; import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { formatBackgroundJobInitiationResponse } from '../../services/job-response-formatter/index.js'; function formatWorkflowResult(result) { let output = `## Workflow Execution: ${result.success ? 'Completed' : 'Failed'}\n\n`; output += `**Status:** ${result.message}\n\n`; if (result.success && result.outputs && Object.keys(result.outputs).length > 0) { output += `**Workflow Output Summary:**\n`; for (const [key, value] of Object.entries(result.outputs)) { let formattedValue = '(Not available)'; if (value !== undefined && value !== null) { const valueString = typeof value === 'string' ? value : JSON.stringify(value); formattedValue = valueString.length > 200 ? valueString.substring(0, 200) + '...' : valueString; } output += `- ${key}: ${formattedValue}\n`; } output += `\n`; } if (result.error) { output += `**Error Details:**\n`; output += `- Step ID: ${result.error.stepId || 'N/A'}\n`; output += `- Tool: ${result.error.toolName || 'N/A'}\n`; output += `- Message: ${result.error.message || 'No specific error message provided.'}\n`; if (result.error.details) { try { output += `- Context: ${JSON.stringify(result.error.details, null, 2)}\n`; } catch { output += `- Context: (Could not serialize error details)\n`; } } output += `\n`; } output += `\n*Note: Detailed step results might be available in server logs or session history for debugging.*`; return output; } export const runWorkflowTool = async (params, config, context) => { const sessionIdForSse = context?.sessionId || `no-session-${Math.random().toString(36).substring(2)}`; if (sessionIdForSse.startsWith('no-session')) { logger.warn({ tool: 'runWorkflowTool' }, 'Executing workflow tool without a valid sessionId. SSE progress updates might be limited.'); } logger.debug({ configReceived: true, hasLlmMapping: Boolean(config.llm_mapping), mappingKeys: config.llm_mapping ? Object.keys(config.llm_mapping) : [] }, 'runWorkflowTool executor received config'); const validatedParams = params; const { workflowName, workflowInput } = validatedParams; const jobId = jobManager.createJob('run-workflow', params); logger.info({ jobId, tool: 'runWorkflowTool', sessionId: sessionIdForSse, workflowName }, 'Starting background job for workflow.'); const initialResponse = formatBackgroundJobInitiationResponse(jobId, `Workflow '${workflowName}' Execution`, `Your request to run workflow '${workflowName}' has been submitted. You can retrieve the result using the job ID.`); setImmediate(async () => { const logs = []; try { jobManager.updateJobStatus(jobId, JobStatus.RUNNING, `Starting workflow '${workflowName}'...`); sseNotifier.sendProgress(sessionIdForSse, jobId, JobStatus.RUNNING, `Starting workflow '${workflowName}'...`); logs.push(`[${new Date().toISOString()}] Starting workflow '${workflowName}'.`); const workflowResult = await executeWorkflow(workflowName, workflowInput || {}, config, context); const completionStatus = workflowResult.success ? JobStatus.COMPLETED : JobStatus.FAILED; const completionMessage = `Workflow '${workflowName}' finished with status: ${completionStatus}. ${workflowResult.message}`; logger.info({ jobId, workflowName, status: completionStatus }, completionMessage); logs.push(`[${new Date().toISOString()}] ${completionMessage}`); sseNotifier.sendProgress(sessionIdForSse, jobId, completionStatus, completionMessage); const formattedText = formatWorkflowResult(workflowResult); const finalResult = { content: [{ type: 'text', text: formattedText }], isError: !workflowResult.success, errorDetails: workflowResult.error ? new McpError(ErrorCode.InternalError, workflowResult.error.message, workflowResult.error.details) : undefined }; jobManager.setJobResult(jobId, finalResult); } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); logger.error({ err: error, jobId, tool: 'run-workflow', workflowName }, `Unexpected error running workflow tool: ${errorMsg}`); logs.push(`[${new Date().toISOString()}] Unexpected Error: ${errorMsg}`); const appError = error instanceof AppError ? error : new ToolExecutionError(`Unexpected error running workflow '${workflowName}': ${errorMsg}`, { workflowName }, error instanceof Error ? error : undefined); const mcpError = new McpError(ErrorCode.InternalError, appError.message, appError.context); const errorResult = { content: [{ type: 'text', text: `Error during background job ${jobId}: ${mcpError.message}\n\nLogs:\n${logs.join('\n')}` }], isError: true, errorDetails: mcpError }; jobManager.setJobResult(jobId, errorResult); sseNotifier.sendProgress(sessionIdForSse, jobId, JobStatus.FAILED, `Job failed: ${mcpError.message}`); } }); return initialResponse; }; const workflowRunnerToolDefinition = { name: "run-workflow", description: "Runs a predefined sequence of tool calls (a workflow) based on a workflow name and input parameters defined in workflows.json.", inputSchema: workflowRunnerInputSchema.shape, executor: runWorkflowTool }; registerTool(workflowRunnerToolDefinition);