"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.getInstanceCacheStatistics = getInstanceCacheStatistics; exports.getInstanceCacheMetrics = getInstanceCacheMetrics; exports.clearInstanceCache = clearInstanceCache; exports.getN8nApiClient = getN8nApiClient; exports.handleCreateWorkflow = handleCreateWorkflow; exports.handleGetWorkflow = handleGetWorkflow; exports.handleGetWorkflowDetails = handleGetWorkflowDetails; exports.handleGetWorkflowStructure = handleGetWorkflowStructure; exports.handleGetWorkflowMinimal = handleGetWorkflowMinimal; exports.handleUpdateWorkflow = handleUpdateWorkflow; exports.handleDeleteWorkflow = handleDeleteWorkflow; exports.handleListWorkflows = handleListWorkflows; exports.handleValidateWorkflow = handleValidateWorkflow; exports.handleAutofixWorkflow = handleAutofixWorkflow; exports.handleTestWorkflow = handleTestWorkflow; exports.handleGetExecution = handleGetExecution; exports.handleListExecutions = handleListExecutions; exports.handleDeleteExecution = handleDeleteExecution; exports.handleHealthCheck = handleHealthCheck; exports.handleDiagnostic = handleDiagnostic; exports.handleWorkflowVersions = handleWorkflowVersions; exports.handleDeployTemplate = handleDeployTemplate; exports.handleTriggerWebhookWorkflow = handleTriggerWebhookWorkflow; const n8n_api_client_1 = require("../services/n8n-api-client"); const n8n_api_1 = require("../config/n8n-api"); const n8n_api_2 = require("../types/n8n-api"); const n8n_validation_1 = require("../services/n8n-validation"); const n8n_errors_1 = require("../utils/n8n-errors"); const logger_1 = require("../utils/logger"); const zod_1 = require("zod"); const workflow_validator_1 = require("../services/workflow-validator"); const enhanced_config_validator_1 = require("../services/enhanced-config-validator"); const instance_context_1 = require("../types/instance-context"); const workflow_auto_fixer_1 = require("../services/workflow-auto-fixer"); const expression_format_validator_1 = require("../services/expression-format-validator"); const workflow_versioning_service_1 = require("../services/workflow-versioning-service"); const handlers_workflow_diff_1 = require("./handlers-workflow-diff"); const telemetry_1 = require("../telemetry"); const cache_utils_1 = require("../utils/cache-utils"); const execution_processor_1 = require("../services/execution-processor"); const npm_version_checker_1 = require("../utils/npm-version-checker"); let defaultApiClient = null; let lastDefaultConfigUrl = null; const cacheMutex = new cache_utils_1.CacheMutex(); const instanceClients = (0, cache_utils_1.createInstanceCache)((client, key) => { logger_1.logger.debug('Evicting API client from cache', { cacheKey: key.substring(0, 8) + '...' }); }); function getInstanceCacheStatistics() { return (0, cache_utils_1.getCacheStatistics)(); } function getInstanceCacheMetrics() { return cache_utils_1.cacheMetrics.getMetrics(); } function clearInstanceCache() { instanceClients.clear(); cache_utils_1.cacheMetrics.recordClear(); cache_utils_1.cacheMetrics.updateSize(0, instanceClients.max); } function getN8nApiClient(context) { if (context?.n8nApiUrl && context?.n8nApiKey) { const validation = (0, instance_context_1.validateInstanceContext)(context); if (!validation.valid) { logger_1.logger.warn('Invalid instance context provided', { instanceId: context.instanceId, errors: validation.errors }); return null; } const cacheKey = (0, cache_utils_1.createCacheKey)(`${context.n8nApiUrl}:${context.n8nApiKey}:${context.instanceId || ''}`); if (instanceClients.has(cacheKey)) { cache_utils_1.cacheMetrics.recordHit(); return instanceClients.get(cacheKey) || null; } cache_utils_1.cacheMetrics.recordMiss(); if (cacheMutex.isLocked(cacheKey)) { const waitTime = 100; const start = Date.now(); while (cacheMutex.isLocked(cacheKey) && (Date.now() - start) < 1000) { } if (instanceClients.has(cacheKey)) { cache_utils_1.cacheMetrics.recordHit(); return instanceClients.get(cacheKey) || null; } } const config = (0, n8n_api_1.getN8nApiConfigFromContext)(context); if (config) { logger_1.logger.info('Creating instance-specific n8n API client', { url: config.baseUrl.replace(/^(https?:\/\/[^\/]+).*/, '$1'), instanceId: context.instanceId, cacheKey: cacheKey.substring(0, 8) + '...' }); const client = new n8n_api_client_1.N8nApiClient(config); instanceClients.set(cacheKey, client); cache_utils_1.cacheMetrics.recordSet(); cache_utils_1.cacheMetrics.updateSize(instanceClients.size, instanceClients.max); return client; } return null; } logger_1.logger.info('Falling back to environment configuration for n8n API client'); const config = (0, n8n_api_1.getN8nApiConfig)(); if (!config) { if (defaultApiClient) { logger_1.logger.info('n8n API configuration removed, clearing default client'); defaultApiClient = null; lastDefaultConfigUrl = null; } return null; } if (!defaultApiClient || lastDefaultConfigUrl !== config.baseUrl) { logger_1.logger.info('n8n API client initialized from environment', { url: config.baseUrl }); defaultApiClient = new n8n_api_client_1.N8nApiClient(config); lastDefaultConfigUrl = config.baseUrl; } return defaultApiClient; } function ensureApiConfigured(context) { const client = getN8nApiClient(context); if (!client) { if (context?.instanceId) { throw new Error(`n8n API not configured for instance ${context.instanceId}. Please provide n8nApiUrl and n8nApiKey in the instance context.`); } throw new Error('n8n API not configured. Please set N8N_API_URL and N8N_API_KEY environment variables.'); } return client; } const createWorkflowSchema = zod_1.z.object({ name: zod_1.z.string(), nodes: zod_1.z.array(zod_1.z.any()), connections: zod_1.z.record(zod_1.z.any()), settings: zod_1.z.object({ executionOrder: zod_1.z.enum(['v0', 'v1']).optional(), timezone: zod_1.z.string().optional(), saveDataErrorExecution: zod_1.z.enum(['all', 'none']).optional(), saveDataSuccessExecution: zod_1.z.enum(['all', 'none']).optional(), saveManualExecutions: zod_1.z.boolean().optional(), saveExecutionProgress: zod_1.z.boolean().optional(), executionTimeout: zod_1.z.number().optional(), errorWorkflow: zod_1.z.string().optional(), }).optional(), }); const updateWorkflowSchema = zod_1.z.object({ id: zod_1.z.string(), name: zod_1.z.string().optional(), nodes: zod_1.z.array(zod_1.z.any()).optional(), connections: zod_1.z.record(zod_1.z.any()).optional(), settings: zod_1.z.any().optional(), createBackup: zod_1.z.boolean().optional(), intent: zod_1.z.string().optional(), }); const listWorkflowsSchema = zod_1.z.object({ limit: zod_1.z.number().min(1).max(100).optional(), cursor: zod_1.z.string().optional(), active: zod_1.z.boolean().optional(), tags: zod_1.z.array(zod_1.z.string()).optional(), projectId: zod_1.z.string().optional(), excludePinnedData: zod_1.z.boolean().optional(), }); const validateWorkflowSchema = zod_1.z.object({ id: zod_1.z.string(), options: zod_1.z.object({ validateNodes: zod_1.z.boolean().optional(), validateConnections: zod_1.z.boolean().optional(), validateExpressions: zod_1.z.boolean().optional(), profile: zod_1.z.enum(['minimal', 'runtime', 'ai-friendly', 'strict']).optional(), }).optional(), }); const autofixWorkflowSchema = zod_1.z.object({ id: zod_1.z.string(), applyFixes: zod_1.z.boolean().optional().default(false), fixTypes: zod_1.z.array(zod_1.z.enum([ 'expression-format', 'typeversion-correction', 'error-output-config', 'node-type-correction', 'webhook-missing-path', 'typeversion-upgrade', 'version-migration' ])).optional(), confidenceThreshold: zod_1.z.enum(['high', 'medium', 'low']).optional().default('medium'), maxFixes: zod_1.z.number().optional().default(50) }); const testWorkflowSchema = zod_1.z.object({ workflowId: zod_1.z.string(), triggerType: zod_1.z.enum(['webhook', 'form', 'chat']).optional(), httpMethod: zod_1.z.enum(['GET', 'POST', 'PUT', 'DELETE']).optional(), webhookPath: zod_1.z.string().optional(), message: zod_1.z.string().optional(), sessionId: zod_1.z.string().optional(), data: zod_1.z.record(zod_1.z.unknown()).optional(), headers: zod_1.z.record(zod_1.z.string()).optional(), timeout: zod_1.z.number().optional(), waitForResponse: zod_1.z.boolean().optional(), }); const listExecutionsSchema = zod_1.z.object({ limit: zod_1.z.number().min(1).max(100).optional(), cursor: zod_1.z.string().optional(), workflowId: zod_1.z.string().optional(), projectId: zod_1.z.string().optional(), status: zod_1.z.enum(['success', 'error', 'waiting']).optional(), includeData: zod_1.z.boolean().optional(), }); const workflowVersionsSchema = zod_1.z.object({ mode: zod_1.z.enum(['list', 'get', 'rollback', 'delete', 'prune', 'truncate']), workflowId: zod_1.z.string().optional(), versionId: zod_1.z.number().optional(), limit: zod_1.z.number().default(10).optional(), validateBefore: zod_1.z.boolean().default(true).optional(), deleteAll: zod_1.z.boolean().default(false).optional(), maxVersions: zod_1.z.number().default(10).optional(), confirmTruncate: zod_1.z.boolean().default(false).optional(), }); async function handleCreateWorkflow(args, context) { try { const client = ensureApiConfigured(context); const input = createWorkflowSchema.parse(args); const shortFormErrors = []; input.nodes?.forEach((node, index) => { if (node.type?.startsWith('nodes-base.') || node.type?.startsWith('nodes-langchain.')) { const fullForm = node.type.startsWith('nodes-base.') ? node.type.replace('nodes-base.', 'n8n-nodes-base.') : node.type.replace('nodes-langchain.', '@n8n/n8n-nodes-langchain.'); shortFormErrors.push(`Node ${index} ("${node.name}") uses SHORT form "${node.type}". ` + `The n8n API requires FULL form. Change to "${fullForm}"`); } }); if (shortFormErrors.length > 0) { telemetry_1.telemetry.trackWorkflowCreation(input, false); return { success: false, error: 'Node type format error: n8n API requires FULL form node types', details: { errors: shortFormErrors, hint: 'Use n8n-nodes-base.* instead of nodes-base.* for standard nodes' } }; } const errors = (0, n8n_validation_1.validateWorkflowStructure)(input); if (errors.length > 0) { telemetry_1.telemetry.trackWorkflowCreation(input, false); return { success: false, error: 'Workflow validation failed', details: { errors } }; } const workflow = await client.createWorkflow(input); telemetry_1.telemetry.trackWorkflowCreation(workflow, true); return { success: true, data: { id: workflow.id, name: workflow.name, active: workflow.active, nodeCount: workflow.nodes?.length || 0 }, message: `Workflow "${workflow.name}" created successfully with ID: ${workflow.id}. Use n8n_get_workflow with mode 'structure' to verify current state.` }; } catch (error) { if (error instanceof zod_1.z.ZodError) { return { success: false, error: 'Invalid input', details: { errors: error.errors } }; } if (error instanceof n8n_errors_1.N8nApiError) { return { success: false, error: (0, n8n_errors_1.getUserFriendlyErrorMessage)(error), code: error.code, details: error.details }; } return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } } async function handleGetWorkflow(args, context) { try { const client = ensureApiConfigured(context); const { id } = zod_1.z.object({ id: zod_1.z.string() }).parse(args); const workflow = await client.getWorkflow(id); return { success: true, data: workflow }; } catch (error) { if (error instanceof zod_1.z.ZodError) { return { success: false, error: 'Invalid input', details: { errors: error.errors } }; } if (error instanceof n8n_errors_1.N8nApiError) { return { success: false, error: (0, n8n_errors_1.getUserFriendlyErrorMessage)(error), code: error.code }; } return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } } async function handleGetWorkflowDetails(args, context) { try { const client = ensureApiConfigured(context); const { id } = zod_1.z.object({ id: zod_1.z.string() }).parse(args); const workflow = await client.getWorkflow(id); const executions = await client.listExecutions({ workflowId: id, limit: 10 }); const stats = { totalExecutions: executions.data.length, successCount: executions.data.filter(e => e.status === n8n_api_2.ExecutionStatus.SUCCESS).length, errorCount: executions.data.filter(e => e.status === n8n_api_2.ExecutionStatus.ERROR).length, lastExecutionTime: executions.data[0]?.startedAt || null }; return { success: true, data: { workflow, executionStats: stats, hasWebhookTrigger: (0, n8n_validation_1.hasWebhookTrigger)(workflow), webhookPath: (0, n8n_validation_1.getWebhookUrl)(workflow) } }; } catch (error) { if (error instanceof zod_1.z.ZodError) { return { success: false, error: 'Invalid input', details: { errors: error.errors } }; } if (error instanceof n8n_errors_1.N8nApiError) { return { success: false, error: (0, n8n_errors_1.getUserFriendlyErrorMessage)(error), code: error.code }; } return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } } async function handleGetWorkflowStructure(args, context) { try { const client = ensureApiConfigured(context); const { id } = zod_1.z.object({ id: zod_1.z.string() }).parse(args); const workflow = await client.getWorkflow(id); const simplifiedNodes = workflow.nodes.map(node => ({ id: node.id, name: node.name, type: node.type, position: node.position, disabled: node.disabled || false })); return { success: true, data: { id: workflow.id, name: workflow.name, active: workflow.active, isArchived: workflow.isArchived, nodes: simplifiedNodes, connections: workflow.connections, nodeCount: workflow.nodes.length, connectionCount: Object.keys(workflow.connections).length } }; } catch (error) { if (error instanceof zod_1.z.ZodError) { return { success: false, error: 'Invalid input', details: { errors: error.errors } }; } if (error instanceof n8n_errors_1.N8nApiError) { return { success: false, error: (0, n8n_errors_1.getUserFriendlyErrorMessage)(error), code: error.code }; } return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } } async function handleGetWorkflowMinimal(args, context) { try { const client = ensureApiConfigured(context); const { id } = zod_1.z.object({ id: zod_1.z.string() }).parse(args); const workflow = await client.getWorkflow(id); return { success: true, data: { id: workflow.id, name: workflow.name, active: workflow.active, isArchived: workflow.isArchived, tags: workflow.tags || [], createdAt: workflow.createdAt, updatedAt: workflow.updatedAt } }; } catch (error) { if (error instanceof zod_1.z.ZodError) { return { success: false, error: 'Invalid input', details: { errors: error.errors } }; } if (error instanceof n8n_errors_1.N8nApiError) { return { success: false, error: (0, n8n_errors_1.getUserFriendlyErrorMessage)(error), code: error.code }; } return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } } async function handleUpdateWorkflow(args, repository, context) { const startTime = Date.now(); const sessionId = `mutation_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`; let workflowBefore = null; let userIntent = 'Full workflow update'; try { const client = ensureApiConfigured(context); const input = updateWorkflowSchema.parse(args); const { id, createBackup, intent, ...updateData } = input; userIntent = intent || 'Full workflow update'; if (updateData.nodes || updateData.connections) { const current = await client.getWorkflow(id); workflowBefore = JSON.parse(JSON.stringify(current)); if (createBackup !== false) { try { const versioningService = new workflow_versioning_service_1.WorkflowVersioningService(repository, client); const backupResult = await versioningService.createBackup(id, current, { trigger: 'full_update' }); logger_1.logger.info('Workflow backup created', { workflowId: id, versionId: backupResult.versionId, versionNumber: backupResult.versionNumber, pruned: backupResult.pruned }); } catch (error) { logger_1.logger.warn('Failed to create workflow backup', { workflowId: id, error: error.message }); } } const fullWorkflow = { ...current, ...updateData }; const errors = (0, n8n_validation_1.validateWorkflowStructure)(fullWorkflow); if (errors.length > 0) { return { success: false, error: 'Workflow validation failed', details: { errors } }; } } const workflow = await client.updateWorkflow(id, updateData); if (workflowBefore) { trackWorkflowMutationForFullUpdate({ sessionId, toolName: 'n8n_update_full_workflow', userIntent, operations: [], workflowBefore, workflowAfter: workflow, mutationSuccess: true, durationMs: Date.now() - startTime, }).catch(err => { logger_1.logger.warn('Failed to track mutation telemetry:', err); }); } return { success: true, data: { id: workflow.id, name: workflow.name, active: workflow.active, nodeCount: workflow.nodes?.length || 0 }, message: `Workflow "${workflow.name}" updated successfully. Use n8n_get_workflow with mode 'structure' to verify current state.` }; } catch (error) { if (workflowBefore) { trackWorkflowMutationForFullUpdate({ sessionId, toolName: 'n8n_update_full_workflow', userIntent, operations: [], workflowBefore, workflowAfter: workflowBefore, mutationSuccess: false, mutationError: error instanceof Error ? error.message : 'Unknown error', durationMs: Date.now() - startTime, }).catch(err => { logger_1.logger.warn('Failed to track mutation telemetry for failed operation:', err); }); } if (error instanceof zod_1.z.ZodError) { return { success: false, error: 'Invalid input', details: { errors: error.errors } }; } if (error instanceof n8n_errors_1.N8nApiError) { return { success: false, error: (0, n8n_errors_1.getUserFriendlyErrorMessage)(error), code: error.code, details: error.details }; } return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } } async function trackWorkflowMutationForFullUpdate(data) { try { const { telemetry } = await Promise.resolve().then(() => __importStar(require('../telemetry/telemetry-manager.js'))); await telemetry.trackWorkflowMutation(data); } catch (error) { logger_1.logger.debug('Telemetry tracking failed:', error); } } async function handleDeleteWorkflow(args, context) { try { const client = ensureApiConfigured(context); const { id } = zod_1.z.object({ id: zod_1.z.string() }).parse(args); const deleted = await client.deleteWorkflow(id); return { success: true, data: { id: deleted?.id || id, name: deleted?.name, deleted: true }, message: `Workflow "${deleted?.name || id}" deleted successfully.` }; } catch (error) { if (error instanceof zod_1.z.ZodError) { return { success: false, error: 'Invalid input', details: { errors: error.errors } }; } if (error instanceof n8n_errors_1.N8nApiError) { return { success: false, error: (0, n8n_errors_1.getUserFriendlyErrorMessage)(error), code: error.code }; } return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } } async function handleListWorkflows(args, context) { try { const client = ensureApiConfigured(context); const input = listWorkflowsSchema.parse(args || {}); const tagsParam = input.tags && input.tags.length > 0 ? input.tags.join(',') : undefined; const response = await client.listWorkflows({ limit: input.limit || 100, cursor: input.cursor, active: input.active, tags: tagsParam, projectId: input.projectId, excludePinnedData: input.excludePinnedData ?? true }); const minimalWorkflows = response.data.map(workflow => ({ id: workflow.id, name: workflow.name, active: workflow.active, isArchived: workflow.isArchived, createdAt: workflow.createdAt, updatedAt: workflow.updatedAt, tags: workflow.tags || [], nodeCount: workflow.nodes?.length || 0 })); return { success: true, data: { workflows: minimalWorkflows, returned: minimalWorkflows.length, nextCursor: response.nextCursor, hasMore: !!response.nextCursor, ...(response.nextCursor ? { _note: "More workflows available. Use cursor to get next page." } : {}) } }; } catch (error) { if (error instanceof zod_1.z.ZodError) { return { success: false, error: 'Invalid input', details: { errors: error.errors } }; } if (error instanceof n8n_errors_1.N8nApiError) { return { success: false, error: (0, n8n_errors_1.getUserFriendlyErrorMessage)(error), code: error.code }; } return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } } async function handleValidateWorkflow(args, repository, context) { try { const client = ensureApiConfigured(context); const input = validateWorkflowSchema.parse(args); const workflowResponse = await handleGetWorkflow({ id: input.id }, context); if (!workflowResponse.success) { return workflowResponse; } const workflow = workflowResponse.data; const validator = new workflow_validator_1.WorkflowValidator(repository, enhanced_config_validator_1.EnhancedConfigValidator); const validationResult = await validator.validateWorkflow(workflow, input.options); const response = { valid: validationResult.valid, workflowId: workflow.id, workflowName: workflow.name, summary: { totalNodes: validationResult.statistics.totalNodes, enabledNodes: validationResult.statistics.enabledNodes, triggerNodes: validationResult.statistics.triggerNodes, validConnections: validationResult.statistics.validConnections, invalidConnections: validationResult.statistics.invalidConnections, expressionsValidated: validationResult.statistics.expressionsValidated, errorCount: validationResult.errors.length, warningCount: validationResult.warnings.length } }; if (validationResult.errors.length > 0) { response.errors = validationResult.errors.map(e => ({ node: e.nodeName || 'workflow', nodeName: e.nodeName, message: e.message, details: e.details })); } if (validationResult.warnings.length > 0) { response.warnings = validationResult.warnings.map(w => ({ node: w.nodeName || 'workflow', nodeName: w.nodeName, message: w.message, details: w.details })); } if (validationResult.suggestions.length > 0) { response.suggestions = validationResult.suggestions; } if (validationResult.valid) { telemetry_1.telemetry.trackWorkflowCreation(workflow, true); } return { success: true, data: response }; } catch (error) { if (error instanceof zod_1.z.ZodError) { return { success: false, error: 'Invalid input', details: { errors: error.errors } }; } if (error instanceof n8n_errors_1.N8nApiError) { return { success: false, error: (0, n8n_errors_1.getUserFriendlyErrorMessage)(error), code: error.code }; } return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } } async function handleAutofixWorkflow(args, repository, context) { try { const client = ensureApiConfigured(context); const input = autofixWorkflowSchema.parse(args); const workflowResponse = await handleGetWorkflow({ id: input.id }, context); if (!workflowResponse.success) { return workflowResponse; } const workflow = workflowResponse.data; const validator = new workflow_validator_1.WorkflowValidator(repository, enhanced_config_validator_1.EnhancedConfigValidator); const validationResult = await validator.validateWorkflow(workflow, { validateNodes: true, validateConnections: true, validateExpressions: true, profile: 'ai-friendly' }); const allFormatIssues = []; for (const node of workflow.nodes) { const formatContext = { nodeType: node.type, nodeName: node.name, nodeId: node.id }; const nodeFormatIssues = expression_format_validator_1.ExpressionFormatValidator.validateNodeParameters(node.parameters, formatContext); const enrichedIssues = nodeFormatIssues.map(issue => ({ ...issue, nodeName: node.name, nodeId: node.id })); allFormatIssues.push(...enrichedIssues); } const autoFixer = new workflow_auto_fixer_1.WorkflowAutoFixer(repository); const fixResult = await autoFixer.generateFixes(workflow, validationResult, allFormatIssues, { applyFixes: input.applyFixes, fixTypes: input.fixTypes, confidenceThreshold: input.confidenceThreshold, maxFixes: input.maxFixes }); if (fixResult.fixes.length === 0) { return { success: true, data: { workflowId: workflow.id, workflowName: workflow.name, message: 'No automatic fixes available for this workflow', validationSummary: { errors: validationResult.errors.length, warnings: validationResult.warnings.length } } }; } if (!input.applyFixes) { return { success: true, data: { workflowId: workflow.id, workflowName: workflow.name, preview: true, fixesAvailable: fixResult.fixes.length, fixes: fixResult.fixes, summary: fixResult.summary, stats: fixResult.stats, message: `${fixResult.fixes.length} fixes available. Set applyFixes=true to apply them.` } }; } if (fixResult.operations.length > 0) { const updateResult = await (0, handlers_workflow_diff_1.handleUpdatePartialWorkflow)({ id: workflow.id, operations: fixResult.operations, createBackup: true }, repository, context); if (!updateResult.success) { return { success: false, error: 'Failed to apply fixes', details: { fixes: fixResult.fixes, updateError: updateResult.error } }; } return { success: true, data: { workflowId: workflow.id, workflowName: workflow.name, fixesApplied: fixResult.fixes.length, fixes: fixResult.fixes, summary: fixResult.summary, stats: fixResult.stats, message: `Successfully applied ${fixResult.fixes.length} fixes to workflow "${workflow.name}"` } }; } return { success: true, data: { workflowId: workflow.id, workflowName: workflow.name, message: 'No fixes needed' } }; } catch (error) { if (error instanceof zod_1.z.ZodError) { return { success: false, error: 'Invalid input', details: { errors: error.errors } }; } if (error instanceof n8n_errors_1.N8nApiError) { return { success: false, error: (0, n8n_errors_1.getUserFriendlyErrorMessage)(error), code: error.code }; } return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } } async function handleTestWorkflow(args, context) { try { const client = ensureApiConfigured(context); const input = testWorkflowSchema.parse(args); const { detectTriggerFromWorkflow, ensureRegistryInitialized, TriggerRegistry, } = await Promise.resolve().then(() => __importStar(require('../triggers'))); await ensureRegistryInitialized(); const workflow = await client.getWorkflow(input.workflowId); let triggerType = input.triggerType; let triggerInfo; const detection = detectTriggerFromWorkflow(workflow); if (!triggerType) { if (detection.detected && detection.trigger) { triggerType = detection.trigger.type; triggerInfo = detection.trigger; } else { return { success: false, error: 'Workflow cannot be triggered externally', details: { workflowId: input.workflowId, reason: detection.reason, hint: 'Only workflows with webhook, form, or chat triggers can be executed via the API. Add one of these trigger nodes to your workflow.', }, }; } } else { if (detection.detected && detection.trigger?.type === triggerType) { triggerInfo = detection.trigger; } else if (!detection.detected || detection.trigger?.type !== triggerType) { return { success: false, error: `Workflow does not have a ${triggerType} trigger`, details: { workflowId: input.workflowId, requestedTrigger: triggerType, detectedTrigger: detection.trigger?.type || 'none', hint: detection.detected ? `Workflow has a ${detection.trigger?.type} trigger. Either use that type or omit triggerType for auto-detection.` : 'Workflow has no externally-triggerable triggers (webhook, form, or chat).', }, }; } } const handler = TriggerRegistry.getHandler(triggerType, client, context); if (!handler) { return { success: false, error: `No handler registered for trigger type: ${triggerType}`, details: { supportedTypes: TriggerRegistry.getRegisteredTypes(), }, }; } if (handler.capabilities.requiresActiveWorkflow && !workflow.active) { return { success: false, error: 'Workflow must be active to trigger via this method', details: { workflowId: input.workflowId, triggerType, hint: 'Activate the workflow in n8n using n8n_update_partial_workflow with [{type: "activateWorkflow"}]', }, }; } if (triggerType === 'chat' && !input.message) { return { success: false, error: 'Chat trigger requires a message parameter', details: { hint: 'Provide message="your message" for chat triggers', }, }; } const triggerInput = { workflowId: input.workflowId, triggerType, httpMethod: input.httpMethod, webhookPath: input.webhookPath, message: input.message || '', sessionId: input.sessionId, data: input.data, formData: input.data, headers: input.headers, timeout: input.timeout, waitForResponse: input.waitForResponse, }; const response = await handler.execute(triggerInput, workflow, triggerInfo); return { success: response.success, data: response.data, message: response.success ? `Workflow triggered successfully via ${triggerType}` : response.error, executionId: response.executionId, workflowId: input.workflowId, details: { triggerType, metadata: response.metadata, ...(response.details || {}), }, }; } catch (error) { if (error instanceof zod_1.z.ZodError) { return { success: false, error: 'Invalid input', details: { errors: error.errors }, }; } if (error instanceof n8n_errors_1.N8nApiError) { return { success: false, error: (0, n8n_errors_1.getUserFriendlyErrorMessage)(error), code: error.code, details: error.details, }; } return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred', }; } } async function handleGetExecution(args, context) { try { const client = ensureApiConfigured(context); const schema = zod_1.z.object({ id: zod_1.z.string(), mode: zod_1.z.enum(['preview', 'summary', 'filtered', 'full', 'error']).optional(), nodeNames: zod_1.z.array(zod_1.z.string()).optional(), itemsLimit: zod_1.z.number().optional(), includeInputData: zod_1.z.boolean().optional(), includeData: zod_1.z.boolean().optional(), errorItemsLimit: zod_1.z.number().min(0).max(100).optional(), includeStackTrace: zod_1.z.boolean().optional(), includeExecutionPath: zod_1.z.boolean().optional(), fetchWorkflow: zod_1.z.boolean().optional() }); const params = schema.parse(args); const { id, mode, nodeNames, itemsLimit, includeInputData, includeData, errorItemsLimit, includeStackTrace, includeExecutionPath, fetchWorkflow } = params; let effectiveMode = mode; if (!effectiveMode && includeData !== undefined) { effectiveMode = includeData ? 'summary' : undefined; } const fetchFullData = effectiveMode !== undefined || includeData === true; const execution = await client.getExecution(id, fetchFullData); if (!effectiveMode && !nodeNames && itemsLimit === undefined) { return { success: true, data: execution }; } let workflow; if (effectiveMode === 'error' && fetchWorkflow !== false && execution.workflowId) { try { workflow = await client.getWorkflow(execution.workflowId); } catch (e) { logger_1.logger.debug('Could not fetch workflow for error analysis', { workflowId: execution.workflowId, error: e instanceof Error ? e.message : 'Unknown error' }); } } const filterOptions = { mode: effectiveMode, nodeNames, itemsLimit, includeInputData, errorItemsLimit, includeStackTrace, includeExecutionPath }; const processedExecution = (0, execution_processor_1.processExecution)(execution, filterOptions, workflow); return { success: true, data: processedExecution }; } catch (error) { if (error instanceof zod_1.z.ZodError) { return { success: false, error: 'Invalid input', details: { errors: error.errors } }; } if (error instanceof n8n_errors_1.N8nApiError) { return { success: false, error: (0, n8n_errors_1.getUserFriendlyErrorMessage)(error), code: error.code }; } return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } } async function handleListExecutions(args, context) { try { const client = ensureApiConfigured(context); const input = listExecutionsSchema.parse(args || {}); const response = await client.listExecutions({ limit: input.limit || 100, cursor: input.cursor, workflowId: input.workflowId, projectId: input.projectId, status: input.status, includeData: input.includeData || false }); return { success: true, data: { executions: response.data, returned: response.data.length, nextCursor: response.nextCursor, hasMore: !!response.nextCursor, ...(response.nextCursor ? { _note: "More executions available. Use cursor to get next page." } : {}) } }; } catch (error) { if (error instanceof zod_1.z.ZodError) { return { success: false, error: 'Invalid input', details: { errors: error.errors } }; } if (error instanceof n8n_errors_1.N8nApiError) { return { success: false, error: (0, n8n_errors_1.getUserFriendlyErrorMessage)(error), code: error.code }; } return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } } async function handleDeleteExecution(args, context) { try { const client = ensureApiConfigured(context); const { id } = zod_1.z.object({ id: zod_1.z.string() }).parse(args); await client.deleteExecution(id); return { success: true, message: `Execution ${id} deleted successfully` }; } catch (error) { if (error instanceof zod_1.z.ZodError) { return { success: false, error: 'Invalid input', details: { errors: error.errors } }; } if (error instanceof n8n_errors_1.N8nApiError) { return { success: false, error: (0, n8n_errors_1.getUserFriendlyErrorMessage)(error), code: error.code }; } return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } } async function handleHealthCheck(context) { const startTime = Date.now(); try { const client = ensureApiConfigured(context); const health = await client.healthCheck(); const packageJson = require('../../package.json'); const mcpVersion = packageJson.version; const supportedN8nVersion = packageJson.dependencies?.n8n?.replace(/[^0-9.]/g, ''); const versionCheck = await (0, npm_version_checker_1.checkNpmVersion)(); const cacheMetricsData = getInstanceCacheMetrics(); const responseTime = Date.now() - startTime; const responseData = { status: health.status, instanceId: health.instanceId, n8nVersion: health.n8nVersion, features: health.features, apiUrl: (0, n8n_api_1.getN8nApiConfig)()?.baseUrl, mcpVersion, supportedN8nVersion, versionCheck: { current: versionCheck.currentVersion, latest: versionCheck.latestVersion, upToDate: !versionCheck.isOutdated, message: (0, npm_version_checker_1.formatVersionMessage)(versionCheck), ...(versionCheck.updateCommand ? { updateCommand: versionCheck.updateCommand } : {}) }, performance: { responseTimeMs: responseTime, cacheHitRate: (cacheMetricsData.hits + cacheMetricsData.misses) > 0 ? ((cacheMetricsData.hits / (cacheMetricsData.hits + cacheMetricsData.misses)) * 100).toFixed(2) + '%' : 'N/A', cachedInstances: cacheMetricsData.size } }; responseData.nextSteps = [ '• Create workflow: n8n_create_workflow', '• List workflows: n8n_list_workflows', '• Search nodes: search_nodes', '• Browse templates: search_templates' ]; if (versionCheck.isOutdated && versionCheck.latestVersion) { responseData.updateWarning = `⚠️ n8n-mcp v${versionCheck.latestVersion} is available (you have v${versionCheck.currentVersion}). Update recommended.`; } telemetry_1.telemetry.trackEvent('health_check_completed', { success: true, responseTimeMs: responseTime, upToDate: !versionCheck.isOutdated, apiConnected: true }); return { success: true, data: responseData }; } catch (error) { const responseTime = Date.now() - startTime; telemetry_1.telemetry.trackEvent('health_check_failed', { success: false, responseTimeMs: responseTime, errorType: error instanceof n8n_errors_1.N8nApiError ? error.code : 'unknown' }); if (error instanceof n8n_errors_1.N8nApiError) { return { success: false, error: (0, n8n_errors_1.getUserFriendlyErrorMessage)(error), code: error.code, details: { apiUrl: (0, n8n_api_1.getN8nApiConfig)()?.baseUrl, hint: 'Check if n8n is running and API is enabled', troubleshooting: [ '1. Verify n8n instance is running', '2. Check N8N_API_URL is correct', '3. Verify N8N_API_KEY has proper permissions', '4. Run n8n_health_check with mode="diagnostic" for detailed analysis' ] } }; } return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } } function detectCloudPlatform() { if (process.env.RAILWAY_ENVIRONMENT) return 'railway'; if (process.env.RENDER) return 'render'; if (process.env.FLY_APP_NAME) return 'fly'; if (process.env.HEROKU_APP_NAME) return 'heroku'; if (process.env.AWS_EXECUTION_ENV) return 'aws'; if (process.env.KUBERNETES_SERVICE_HOST) return 'kubernetes'; if (process.env.GOOGLE_CLOUD_PROJECT) return 'gcp'; if (process.env.AZURE_FUNCTIONS_ENVIRONMENT) return 'azure'; return null; } function getModeSpecificDebug(mcpMode) { if (mcpMode === 'http') { const port = process.env.MCP_PORT || process.env.PORT || 3000; return { mode: 'HTTP Server', port, authTokenConfigured: !!(process.env.MCP_AUTH_TOKEN || process.env.AUTH_TOKEN), corsEnabled: true, serverUrl: `http://localhost:${port}`, healthCheckUrl: `http://localhost:${port}/health`, troubleshooting: [ `1. Test server health: curl http://localhost:${port}/health`, '2. Check browser console for CORS errors', '3. Verify MCP_AUTH_TOKEN or AUTH_TOKEN if authentication enabled', `4. Ensure port ${port} is not in use: lsof -i :${port} (macOS/Linux) or netstat -ano | findstr :${port} (Windows)`, '5. Check firewall settings for port access', '6. Review server logs for connection errors' ], commonIssues: [ 'CORS policy blocking browser requests', 'Port already in use by another application', 'Authentication token mismatch', 'Network firewall blocking connections' ] }; } else { const configLocation = process.platform === 'darwin' ? '~/Library/Application Support/Claude/claude_desktop_config.json' : process.platform === 'win32' ? '%APPDATA%\\Claude\\claude_desktop_config.json' : '~/.config/Claude/claude_desktop_config.json'; return { mode: 'Standard I/O (Claude Desktop)', configLocation, troubleshooting: [ '1. Verify Claude Desktop config file exists and is valid JSON', '2. Check MCP server entry: {"mcpServers": {"n8n": {"command": "npx", "args": ["-y", "n8n-mcp"]}}}', '3. Restart Claude Desktop after config changes', '4. Check Claude Desktop logs for startup errors', '5. Test npx can run: npx -y n8n-mcp --version', '6. Verify executable permissions if using local installation' ], commonIssues: [ 'Invalid JSON in claude_desktop_config.json', 'Incorrect command or args in MCP server config', 'Claude Desktop not restarted after config changes', 'npx unable to download or run package', 'Missing execute permissions on local binary' ] }; } } function getDockerDebug(isDocker) { if (!isDocker) return null; return { containerDetected: true, troubleshooting: [ '1. Verify volume mounts for data/nodes.db', '2. Check network connectivity to n8n instance', '3. Ensure ports are correctly mapped', '4. Review container logs: docker logs ', '5. Verify environment variables passed to container', '6. Check IS_DOCKER=true is set correctly' ], commonIssues: [ 'Volume mount not persisting database', 'Network isolation preventing n8n API access', 'Port mapping conflicts', 'Missing environment variables in container' ] }; } function getCloudPlatformDebug(cloudPlatform) { if (!cloudPlatform) return null; const platformGuides = { railway: { name: 'Railway', troubleshooting: [ '1. Check Railway environment variables are set', '2. Verify deployment logs in Railway dashboard', '3. Ensure PORT matches Railway assigned port (automatic)', '4. Check networking configuration for external access' ] }, render: { name: 'Render', troubleshooting: [ '1. Verify Render environment variables', '2. Check Render logs for startup errors', '3. Ensure health check endpoint is responding', '4. Verify instance type has sufficient resources' ] }, fly: { name: 'Fly.io', troubleshooting: [ '1. Check Fly.io logs: flyctl logs', '2. Verify fly.toml configuration', '3. Ensure volumes are properly mounted', '4. Check app status: flyctl status' ] }, heroku: { name: 'Heroku', troubleshooting: [ '1. Check Heroku logs: heroku logs --tail', '2. Verify Procfile configuration', '3. Ensure dynos are running: heroku ps', '4. Check environment variables: heroku config' ] }, kubernetes: { name: 'Kubernetes', troubleshooting: [ '1. Check pod logs: kubectl logs ', '2. Verify service and ingress configuration', '3. Check persistent volume claims', '4. Verify resource limits and requests' ] }, aws: { name: 'AWS', troubleshooting: [ '1. Check CloudWatch logs', '2. Verify IAM roles and permissions', '3. Check security groups and networking', '4. Verify environment variables in service config' ] } }; return platformGuides[cloudPlatform] || { name: cloudPlatform.toUpperCase(), troubleshooting: [ '1. Check cloud platform logs', '2. Verify environment variables are set', '3. Check networking and port configuration', '4. Review platform-specific documentation' ] }; } async function handleDiagnostic(request, context) { const startTime = Date.now(); const verbose = request.params?.arguments?.verbose || false; const mcpMode = process.env.MCP_MODE || 'stdio'; const isDocker = process.env.IS_DOCKER === 'true'; const cloudPlatform = detectCloudPlatform(); const envVars = { N8N_API_URL: process.env.N8N_API_URL || null, N8N_API_KEY: process.env.N8N_API_KEY ? '***configured***' : null, NODE_ENV: process.env.NODE_ENV || 'production', MCP_MODE: mcpMode, isDocker, cloudPlatform, nodeVersion: process.version, platform: process.platform }; const apiConfig = (0, n8n_api_1.getN8nApiConfig)(); const apiConfigured = apiConfig !== null; const apiClient = getN8nApiClient(context); let apiStatus = { configured: apiConfigured, connected: false, error: null, version: null }; if (apiClient) { try { const health = await apiClient.healthCheck(); apiStatus.connected = true; apiStatus.version = health.n8nVersion || 'unknown'; } catch (error) { apiStatus.error = error instanceof Error ? error.message : 'Unknown error'; } } const documentationTools = 7; const managementTools = apiConfigured ? 13 : 0; const totalTools = documentationTools + managementTools; const versionCheck = await (0, npm_version_checker_1.checkNpmVersion)(); const cacheMetricsData = getInstanceCacheMetrics(); const responseTime = Date.now() - startTime; const diagnostic = { timestamp: new Date().toISOString(), environment: envVars, apiConfiguration: { configured: apiConfigured, status: apiStatus, config: apiConfig ? { baseUrl: apiConfig.baseUrl, timeout: apiConfig.timeout, maxRetries: apiConfig.maxRetries } : null }, versionInfo: { current: versionCheck.currentVersion, latest: versionCheck.latestVersion, upToDate: !versionCheck.isOutdated, message: (0, npm_version_checker_1.formatVersionMessage)(versionCheck), ...(versionCheck.updateCommand ? { updateCommand: versionCheck.updateCommand } : {}) }, toolsAvailability: { documentationTools: { count: documentationTools, enabled: true, description: 'Always available - node info, search, validation, etc.' }, managementTools: { count: managementTools, enabled: apiConfigured, description: apiConfigured ? 'Management tools are ENABLED - create, update, execute workflows' : 'Management tools are DISABLED - configure N8N_API_URL and N8N_API_KEY to enable' }, totalAvailable: totalTools }, performance: { diagnosticResponseTimeMs: responseTime, cacheHitRate: (cacheMetricsData.hits + cacheMetricsData.misses) > 0 ? ((cacheMetricsData.hits / (cacheMetricsData.hits + cacheMetricsData.misses)) * 100).toFixed(2) + '%' : 'N/A', cachedInstances: cacheMetricsData.size }, modeSpecificDebug: getModeSpecificDebug(mcpMode) }; if (apiConfigured && apiStatus.connected) { diagnostic.nextSteps = { message: '✓ API connected! Here\'s what you can do:', recommended: [ { action: 'n8n_list_workflows', description: 'See your existing workflows', timing: 'Fast (6 seconds median)' }, { action: 'n8n_create_workflow', description: 'Create a new workflow', timing: 'Typically 6-14 minutes to build' }, { action: 'search_nodes', description: 'Discover available nodes', timing: 'Fast - explore 500+ nodes' }, { action: 'search_templates', description: 'Browse pre-built workflows', timing: 'Find examples quickly' } ], tips: [ '82% of users start creating workflows after diagnostics - you\'re ready to go!', 'Most common first action: n8n_update_partial_workflow (managing existing workflows)', 'Use n8n_validate_workflow before deploying to catch issues early' ] }; } else if (apiConfigured && !apiStatus.connected) { diagnostic.troubleshooting = { issue: '⚠️ API configured but connection failed', error: apiStatus.error, steps: [ '1. Verify n8n instance is running and accessible', '2. Check N8N_API_URL is correct (currently: ' + apiConfig?.baseUrl + ')', '3. Test URL in browser: ' + apiConfig?.baseUrl + '/healthz', '4. Verify N8N_API_KEY has proper permissions', '5. Check firewall/network settings if using remote n8n', '6. Try running n8n_health_check again after fixes' ], commonIssues: [ 'Wrong port number in N8N_API_URL', 'API key doesn\'t have sufficient permissions', 'n8n instance not running or crashed', 'Network firewall blocking connection' ], documentation: 'https://github.com/czlonkowski/n8n-mcp?tab=readme-ov-file#n8n-management-tools-optional---requires-api-configuration' }; } else { diagnostic.setupGuide = { message: 'n8n API not configured. You can still use documentation tools!', whatYouCanDoNow: { documentation: [ { tool: 'search_nodes', description: 'Search 500+ n8n nodes', example: 'search_nodes({query: "slack"})' }, { tool: 'get_node_essentials', description: 'Get node configuration details', example: 'get_node_essentials({nodeType: "nodes-base.httpRequest"})' }, { tool: 'search_templates', description: 'Browse workflow templates', example: 'search_templates({query: "chatbot"})' }, { tool: 'validate_workflow', description: 'Validate workflow JSON', example: 'validate_workflow({workflow: {...}})' } ], note: '14 documentation tools available without API configuration' }, whatYouCannotDo: [ '✗ Create/update workflows in n8n instance', '✗ List your workflows', '✗ Execute workflows', '✗ View execution results' ], howToEnable: { steps: [ '1. Get your n8n API key: [Your n8n instance]/settings/api', '2. Set environment variables:', ' N8N_API_URL=https://your-n8n-instance.com', ' N8N_API_KEY=your_api_key_here', '3. Restart the MCP server', '4. Run n8n_health_check with mode="diagnostic" to verify', '5. All 19 tools will be available!' ], documentation: 'https://github.com/czlonkowski/n8n-mcp?tab=readme-ov-file#n8n-management-tools-optional---requires-api-configuration' } }; } if (versionCheck.isOutdated && versionCheck.latestVersion) { diagnostic.updateWarning = { message: `⚠️ Update available: v${versionCheck.currentVersion} → v${versionCheck.latestVersion}`, command: versionCheck.updateCommand, benefits: [ 'Latest bug fixes and improvements', 'New features and tools', 'Better performance and reliability' ] }; } const dockerDebug = getDockerDebug(isDocker); if (dockerDebug) { diagnostic.dockerDebug = dockerDebug; } const cloudDebug = getCloudPlatformDebug(cloudPlatform); if (cloudDebug) { diagnostic.cloudPlatformDebug = cloudDebug; } if (verbose) { diagnostic.debug = { processEnv: Object.keys(process.env).filter(key => key.startsWith('N8N_') || key.startsWith('MCP_')), nodeVersion: process.version, platform: process.platform, workingDirectory: process.cwd(), cacheMetrics: cacheMetricsData }; } telemetry_1.telemetry.trackEvent('diagnostic_completed', { success: true, apiConfigured, apiConnected: apiStatus.connected, toolsAvailable: totalTools, responseTimeMs: responseTime, upToDate: !versionCheck.isOutdated, verbose }); return { success: true, data: diagnostic }; } async function handleWorkflowVersions(args, repository, context) { try { const input = workflowVersionsSchema.parse(args); const client = context ? getN8nApiClient(context) : null; const versioningService = new workflow_versioning_service_1.WorkflowVersioningService(repository, client || undefined); switch (input.mode) { case 'list': { if (!input.workflowId) { return { success: false, error: 'workflowId is required for list mode' }; } const versions = await versioningService.getVersionHistory(input.workflowId, input.limit); return { success: true, data: { workflowId: input.workflowId, versions, count: versions.length, message: `Found ${versions.length} version(s) for workflow ${input.workflowId}` } }; } case 'get': { if (!input.versionId) { return { success: false, error: 'versionId is required for get mode' }; } const version = await versioningService.getVersion(input.versionId); if (!version) { return { success: false, error: `Version ${input.versionId} not found` }; } return { success: true, data: version }; } case 'rollback': { if (!input.workflowId) { return { success: false, error: 'workflowId is required for rollback mode' }; } if (!client) { return { success: false, error: 'n8n API not configured. Cannot perform rollback without API access.' }; } const result = await versioningService.restoreVersion(input.workflowId, input.versionId, input.validateBefore); return { success: result.success, data: result.success ? result : undefined, error: result.success ? undefined : result.message, details: result.success ? undefined : { validationErrors: result.validationErrors } }; } case 'delete': { if (input.deleteAll) { if (!input.workflowId) { return { success: false, error: 'workflowId is required for deleteAll mode' }; } const result = await versioningService.deleteAllVersions(input.workflowId); return { success: true, data: { workflowId: input.workflowId, deleted: result.deleted, message: result.message } }; } else { if (!input.versionId) { return { success: false, error: 'versionId is required for single version delete' }; } const result = await versioningService.deleteVersion(input.versionId); return { success: result.success, data: result.success ? { message: result.message } : undefined, error: result.success ? undefined : result.message }; } } case 'prune': { if (!input.workflowId) { return { success: false, error: 'workflowId is required for prune mode' }; } const result = await versioningService.pruneVersions(input.workflowId, input.maxVersions || 10); return { success: true, data: { workflowId: input.workflowId, pruned: result.pruned, remaining: result.remaining, message: `Pruned ${result.pruned} old version(s), ${result.remaining} version(s) remaining` } }; } case 'truncate': { if (!input.confirmTruncate) { return { success: false, error: 'confirmTruncate must be true to truncate all versions. This action cannot be undone.' }; } const result = await versioningService.truncateAllVersions(true); return { success: true, data: { deleted: result.deleted, message: result.message } }; } default: return { success: false, error: `Unknown mode: ${input.mode}` }; } } catch (error) { if (error instanceof zod_1.z.ZodError) { return { success: false, error: 'Invalid input', details: { errors: error.errors } }; } return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } } const deployTemplateSchema = zod_1.z.object({ templateId: zod_1.z.number().positive().int(), name: zod_1.z.string().optional(), autoUpgradeVersions: zod_1.z.boolean().default(true), autoFix: zod_1.z.boolean().default(true), stripCredentials: zod_1.z.boolean().default(true) }); async function handleDeployTemplate(args, templateService, repository, context) { try { const client = ensureApiConfigured(context); const input = deployTemplateSchema.parse(args); const template = await templateService.getTemplate(input.templateId, 'full'); if (!template) { return { success: false, error: `Template ${input.templateId} not found`, details: { hint: 'Use search_templates to find available templates', templateUrl: `https://n8n.io/workflows/${input.templateId}` } }; } const workflow = JSON.parse(JSON.stringify(template.workflow)); if (!workflow || !workflow.nodes) { return { success: false, error: 'Template has invalid workflow structure', details: { templateId: input.templateId } }; } const workflowName = input.name || template.name; const requiredCredentials = []; for (const node of workflow.nodes) { if (node.credentials && typeof node.credentials === 'object') { for (const [credType] of Object.entries(node.credentials)) { requiredCredentials.push({ nodeType: node.type, nodeName: node.name, credentialType: credType }); } } } if (input.stripCredentials) { workflow.nodes = workflow.nodes.map((node) => { const { credentials, ...rest } = node; return rest; }); } if (input.autoUpgradeVersions) { const autoFixer = new workflow_auto_fixer_1.WorkflowAutoFixer(repository); const validator = new workflow_validator_1.WorkflowValidator(repository, enhanced_config_validator_1.EnhancedConfigValidator); const validationResult = await validator.validateWorkflow(workflow, { validateNodes: true, validateConnections: false, validateExpressions: false, profile: 'runtime' }); const fixResult = await autoFixer.generateFixes(workflow, validationResult, [], { fixTypes: ['typeversion-upgrade', 'typeversion-correction'] }); if (fixResult.operations.length > 0) { for (const op of fixResult.operations) { if (op.type === 'updateNode' && op.updates) { const node = workflow.nodes.find((n) => n.id === op.nodeId || n.name === op.nodeName); if (node) { for (const [path, value] of Object.entries(op.updates)) { if (path === 'typeVersion') { node.typeVersion = value; } } } } } } } const triggerNode = workflow.nodes.find((n) => n.type?.includes('Trigger') || n.type?.includes('webhook') || n.type === 'n8n-nodes-base.webhook'); const triggerType = triggerNode?.type?.split('.').pop() || 'manual'; const createdWorkflow = await client.createWorkflow({ name: workflowName, nodes: workflow.nodes, connections: workflow.connections, settings: workflow.settings || { executionOrder: 'v1' } }); const apiConfig = context ? (0, n8n_api_1.getN8nApiConfigFromContext)(context) : (0, n8n_api_1.getN8nApiConfig)(); const baseUrl = apiConfig?.baseUrl?.replace('/api/v1', '') || ''; let fixesApplied = []; let fixSummary = ''; let autoFixStatus = 'skipped'; if (input.autoFix) { try { const autofixResult = await handleAutofixWorkflow({ id: createdWorkflow.id, applyFixes: true, fixTypes: ['expression-format', 'typeversion-upgrade'], confidenceThreshold: 'medium' }, repository, context); if (autofixResult.success && autofixResult.data) { const fixData = autofixResult.data; autoFixStatus = 'success'; if (fixData.fixesApplied && fixData.fixesApplied > 0) { fixesApplied = fixData.fixes || []; fixSummary = ` Auto-fixed ${fixData.fixesApplied} issue(s).`; } } } catch (fixError) { autoFixStatus = 'failed'; logger_1.logger.warn('Auto-fix failed after template deployment', { workflowId: createdWorkflow.id, error: fixError instanceof Error ? fixError.message : 'Unknown error' }); fixSummary = ' Auto-fix failed (workflow deployed successfully).'; } } return { success: true, data: { workflowId: createdWorkflow.id, name: createdWorkflow.name, active: false, nodeCount: workflow.nodes.length, triggerType, requiredCredentials: requiredCredentials.length > 0 ? requiredCredentials : undefined, url: baseUrl ? `${baseUrl}/workflow/${createdWorkflow.id}` : undefined, templateId: input.templateId, templateUrl: template.url || `https://n8n.io/workflows/${input.templateId}`, autoFixStatus, fixesApplied: fixesApplied.length > 0 ? fixesApplied : undefined }, message: `Workflow "${createdWorkflow.name}" deployed successfully from template ${input.templateId}.${fixSummary} ${requiredCredentials.length > 0 ? `Configure ${requiredCredentials.length} credential(s) in n8n to activate.` : ''}` }; } catch (error) { if (error instanceof zod_1.z.ZodError) { return { success: false, error: 'Invalid input', details: { errors: error.errors } }; } if (error instanceof n8n_errors_1.N8nApiError) { return { success: false, error: (0, n8n_errors_1.getUserFriendlyErrorMessage)(error), code: error.code, details: error.details }; } return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } } async function handleTriggerWebhookWorkflow(args, context) { const triggerWebhookSchema = zod_1.z.object({ webhookUrl: zod_1.z.string().url(), httpMethod: zod_1.z.enum(['GET', 'POST', 'PUT', 'DELETE']).optional(), data: zod_1.z.record(zod_1.z.unknown()).optional(), headers: zod_1.z.record(zod_1.z.string()).optional(), waitForResponse: zod_1.z.boolean().optional(), }); try { const client = ensureApiConfigured(context); const input = triggerWebhookSchema.parse(args); const webhookRequest = { webhookUrl: input.webhookUrl, httpMethod: input.httpMethod || 'POST', data: input.data, headers: input.headers, waitForResponse: input.waitForResponse ?? true }; const response = await client.triggerWebhook(webhookRequest); return { success: true, data: response, message: 'Webhook triggered successfully' }; } catch (error) { if (error instanceof zod_1.z.ZodError) { return { success: false, error: 'Invalid input', details: { errors: error.errors } }; } if (error instanceof n8n_errors_1.N8nApiError) { const errorData = error.details; const executionId = errorData?.executionId || errorData?.id || errorData?.execution?.id; const workflowId = errorData?.workflowId || errorData?.workflow?.id; if (executionId) { return { success: false, error: (0, n8n_errors_1.formatExecutionError)(executionId, workflowId), code: error.code, executionId, workflowId: workflowId || undefined }; } if (error.code === 'SERVER_ERROR' || error.statusCode && error.statusCode >= 500) { return { success: false, error: (0, n8n_errors_1.formatNoExecutionError)(), code: error.code }; } return { success: false, error: (0, n8n_errors_1.getUserFriendlyErrorMessage)(error), code: error.code, details: error.details }; } return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } } //# sourceMappingURL=handlers-n8n-manager.js.map