n8n-mcp
Version:
Integration between n8n workflow automation and Model Context Protocol (MCP)
1,237 lines (1,236 loc) • 119 kB
JavaScript
"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.tryParseJson = tryParseJson;
exports.handleCreateWorkflow = handleCreateWorkflow;
exports.handleGetWorkflow = handleGetWorkflow;
exports.handleGetWorkflowDetails = handleGetWorkflowDetails;
exports.handleGetWorkflowStructure = handleGetWorkflowStructure;
exports.handleGetWorkflowMinimal = handleGetWorkflowMinimal;
exports.handleGetWorkflowFiltered = handleGetWorkflowFiltered;
exports.handleGetWorkflowActive = handleGetWorkflowActive;
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.handleListTestRuns = handleListTestRuns;
exports.handleGetTestRun = handleGetTestRun;
exports.handleListTestCases = handleListTestCases;
exports.handleHealthCheck = handleHealthCheck;
exports.handleDiagnostic = handleDiagnostic;
exports.handleWorkflowVersions = handleWorkflowVersions;
exports.handleDeployTemplate = handleDeployTemplate;
exports.handleTriggerWebhookWorkflow = handleTriggerWebhookWorkflow;
exports.handleCreateTable = handleCreateTable;
exports.handleListTables = handleListTables;
exports.handleGetTable = handleGetTable;
exports.handleUpdateTable = handleUpdateTable;
exports.handleDeleteTable = handleDeleteTable;
exports.handleGetRows = handleGetRows;
exports.handleInsertRows = handleInsertRows;
exports.handleUpdateRows = handleUpdateRows;
exports.handleUpsertRows = handleUpsertRows;
exports.handleDeleteRows = handleDeleteRows;
exports.handleListCredentials = handleListCredentials;
exports.handleGetCredential = handleGetCredential;
exports.handleCreateCredential = handleCreateCredential;
exports.handleUpdateCredential = handleUpdateCredential;
exports.handleDeleteCredential = handleDeleteCredential;
exports.handleGetCredentialSchema = handleGetCredentialSchema;
exports.handleAuditInstance = handleAuditInstance;
const crypto_1 = require("crypto");
const n8n_api_client_1 = require("../services/n8n-api-client");
const workflow_security_scanner_1 = require("../services/workflow-security-scanner");
const audit_report_builder_1 = require("../services/audit-report-builder");
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");
const mcp_input_normalizer_1 = require("../utils/mcp-input-normalizer");
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;
}
if (process.env.ENABLE_MULTI_TENANT === 'true') {
logger_1.logger.warn('Refusing env-credential fallback in multi-tenant mode');
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;
}
function resolveN8nApiConfigForResponse(context) {
const fromContext = context ? (0, n8n_api_1.getN8nApiConfigFromContext)(context) : null;
if (fromContext) {
return fromContext;
}
if (process.env.ENABLE_MULTI_TENANT === 'true') {
return null;
}
return (0, n8n_api_1.getN8nApiConfig)();
}
function tryParseJson(val) {
if (typeof val !== 'string')
return val;
try {
return JSON.parse(val);
}
catch {
return val;
}
}
function stripActiveVersion(workflow) {
const { activeVersion, ...rest } = workflow;
return rest;
}
const emptyToUndefined = (v) => typeof v === 'string' && v.trim() === '' ? undefined : v;
const optionalEmptyAware = (schema) => zod_1.z.preprocess(emptyToUndefined, schema.optional());
const createWorkflowSchema = zod_1.z.object({
name: zod_1.z.string(),
nodes: zod_1.z.preprocess(mcp_input_normalizer_1.normalizeMcpWorkflowNodes, zod_1.z.array(zod_1.z.any())),
connections: zod_1.z.preprocess(mcp_input_normalizer_1.normalizeMcpWorkflowConnections, zod_1.z.record(zod_1.z.string(), zod_1.z.any())),
settings: zod_1.z.preprocess(mcp_input_normalizer_1.normalizeMcpJsonValue, 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(),
projectId: zod_1.z.string().optional(),
});
const updateWorkflowSchema = zod_1.z.object({
id: zod_1.z.string(),
name: zod_1.z.string().optional(),
nodes: zod_1.z.preprocess(mcp_input_normalizer_1.normalizeMcpWorkflowNodes, zod_1.z.array(zod_1.z.any())).optional(),
connections: zod_1.z.preprocess(mcp_input_normalizer_1.normalizeMcpWorkflowConnections, zod_1.z.record(zod_1.z.string(), zod_1.z.any())).optional(),
settings: zod_1.z.preprocess(mcp_input_normalizer_1.normalizeMcpJsonValue, 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: optionalEmptyAware(zod_1.z.string()),
active: zod_1.z.boolean().optional(),
tags: zod_1.z.preprocess(mcp_input_normalizer_1.normalizeMcpJsonValue, zod_1.z.array(zod_1.z.string())).optional(),
projectId: optionalEmptyAware(zod_1.z.string()),
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',
'tool-variant-correction',
'connection-numeric-keys',
'connection-invalid-type',
'connection-id-to-name',
'connection-duplicate-removal',
'connection-input-index'
])).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: optionalEmptyAware(zod_1.z.enum(['webhook', 'form', 'chat'])),
httpMethod: optionalEmptyAware(zod_1.z.enum(['GET', 'POST', 'PUT', 'DELETE'])),
webhookPath: optionalEmptyAware(zod_1.z.string()),
message: optionalEmptyAware(zod_1.z.string()),
sessionId: optionalEmptyAware(zod_1.z.string()),
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: optionalEmptyAware(zod_1.z.string()),
workflowId: optionalEmptyAware(zod_1.z.string()),
projectId: optionalEmptyAware(zod_1.z.string()),
status: optionalEmptyAware(zod_1.z.enum(['success', 'error', 'waiting'])),
includeData: zod_1.z.boolean().optional(),
});
const listTestRunsSchema = zod_1.z.object({
workflowId: zod_1.z.string(),
status: optionalEmptyAware(zod_1.z.enum(['new', 'running', 'completed', 'error', 'cancelled'])),
limit: zod_1.z.number().min(1).max(250).optional(),
cursor: optionalEmptyAware(zod_1.z.string()),
});
const getTestRunSchema = zod_1.z.object({
workflowId: zod_1.z.string(),
runId: zod_1.z.string(),
});
const listTestCasesSchema = zod_1.z.object({
workflowId: zod_1.z.string(),
runId: zod_1.z.string(),
limit: zod_1.z.number().min(1).max(250).optional(),
cursor: optionalEmptyAware(zod_1.z.string()),
});
const workflowVersionsSchema = zod_1.z.object({
mode: zod_1.z.enum(['list', 'get', 'rollback', 'delete', 'prune']),
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(),
});
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);
if (!workflow || !workflow.id) {
return {
success: false,
error: 'Workflow creation failed: n8n API returned an empty or invalid response. Verify your N8N_API_URL points to the correct /api/v1 endpoint and that the n8n instance supports workflow creation.',
details: {
response: workflow ? { keys: Object.keys(workflow) } : null
}
};
}
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: stripActiveVersion(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: stripActiveVersion(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 handleGetWorkflowFiltered(args, context) {
try {
const client = ensureApiConfigured(context);
const { id, nodeNames } = zod_1.z.object({
id: zod_1.z.string(),
nodeNames: zod_1.z.array(zod_1.z.string()).min(1)
}).parse(args);
const workflow = await client.getWorkflow(id);
const requested = new Set(nodeNames);
const matchedNodes = workflow.nodes.filter(node => requested.has(node.name) || requested.has(node.id));
const matchedKeys = new Set(matchedNodes.flatMap(node => [node.name, node.id]));
const notFound = nodeNames.filter(key => !matchedKeys.has(key));
return {
success: true,
data: {
id: workflow.id,
name: workflow.name,
active: workflow.active,
isArchived: workflow.isArchived,
nodes: matchedNodes,
nodeCount: workflow.nodes.length,
returnedCount: matchedNodes.length,
...(notFound.length > 0 ? { notFound } : {})
}
};
}
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 handleGetWorkflowActive(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 activeVersion = workflow.activeVersion;
const baseMeta = {
id: workflow.id,
name: workflow.name,
active: workflow.active,
isArchived: workflow.isArchived,
tags: workflow.tags || [],
settings: workflow.settings,
createdAt: workflow.createdAt,
updatedAt: workflow.updatedAt,
};
if (workflow.activeVersionId && activeVersion) {
return {
success: true,
data: {
...baseMeta,
activeVersionId: workflow.activeVersionId,
versionCreatedAt: activeVersion.createdAt ?? null,
versionName: activeVersion.name ?? null,
nodes: activeVersion.nodes,
connections: activeVersion.connections,
}
};
}
if (workflow.active === true) {
return {
success: true,
data: {
...baseMeta,
activeVersionId: null,
versionCreatedAt: null,
versionName: null,
nodes: workflow.nodes,
connections: workflow.connections,
}
};
}
return {
success: false,
error: 'No published version. Workflow is inactive and has never been activated. Use mode="full" to see the draft.',
code: 'NO_ACTIVE_VERSION'
};
}
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()}_${(0, crypto_1.randomUUID)()}`;
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';
const current = await client.getWorkflow(id);
workflowBefore = JSON.parse(JSON.stringify(current));
if (updateData.nodes && current.nodes) {
const currentById = new Map();
const currentByName = new Map();
for (const node of current.nodes) {
if (node.id)
currentById.set(node.id, node);
currentByName.set(node.name, node);
}
for (const node of updateData.nodes) {
const hasCredentials = node.credentials && typeof node.credentials === 'object' && Object.keys(node.credentials).length > 0;
if (!hasCredentials) {
const match = (node.id && currentById.get(node.id)) || currentByName.get(node.name);
if (match?.credentials) {
node.credentials = match.credentials;
}
}
}
}
const { settings: settingsUpdate, ...nonSettingsUpdate } = updateData;
const fullWorkflow = {
...current,
...nonSettingsUpdate
};
if (settingsUpdate && typeof settingsUpdate === 'object') {
fullWorkflow.settings = {
...(current.settings ?? {}),
...settingsUpdate,
};
}
if (updateData.nodes || updateData.connections) {
if (createBackup !== false) {
try {
const versioningService = new workflow_versioning_service_1.WorkflowVersioningService(repository, client, (0, instance_context_1.getInstanceScopeId)(context));
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 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, fullWorkflow);
if (workflowBefore) {
void 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) {
void 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 },
};
}