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
JavaScript
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);