n8n-mcp 2.7.21 → 2.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +69 -2
  2. package/data/nodes.db +0 -0
  3. package/dist/database/node-repository.d.ts +12 -1
  4. package/dist/database/node-repository.d.ts.map +1 -1
  5. package/dist/database/node-repository.js +122 -2
  6. package/dist/database/node-repository.js.map +1 -1
  7. package/dist/http-server-single-session.d.ts +21 -1
  8. package/dist/http-server-single-session.d.ts.map +1 -1
  9. package/dist/http-server-single-session.js +515 -44
  10. package/dist/http-server-single-session.js.map +1 -1
  11. package/dist/http-server.d.ts.map +1 -1
  12. package/dist/http-server.js +6 -3
  13. package/dist/http-server.js.map +1 -1
  14. package/dist/mcp/server.d.ts +5 -0
  15. package/dist/mcp/server.d.ts.map +1 -1
  16. package/dist/mcp/server.js +413 -26
  17. package/dist/mcp/server.js.map +1 -1
  18. package/dist/mcp/tools-n8n-friendly.d.ts +6 -0
  19. package/dist/mcp/tools-n8n-friendly.d.ts.map +1 -0
  20. package/dist/mcp/tools-n8n-friendly.js +131 -0
  21. package/dist/mcp/tools-n8n-friendly.js.map +1 -0
  22. package/dist/mcp/tools.d.ts.map +1 -1
  23. package/dist/mcp/tools.js +187 -11
  24. package/dist/mcp/tools.js.map +1 -1
  25. package/dist/mcp/workflow-examples.d.ts +76 -0
  26. package/dist/mcp/workflow-examples.d.ts.map +1 -0
  27. package/dist/mcp/workflow-examples.js +111 -0
  28. package/dist/mcp/workflow-examples.js.map +1 -0
  29. package/dist/mcp-engine.d.ts +1 -1
  30. package/dist/mcp-engine.d.ts.map +1 -1
  31. package/dist/mcp-tools-engine.d.ts +48 -0
  32. package/dist/mcp-tools-engine.d.ts.map +1 -0
  33. package/dist/mcp-tools-engine.js +92 -0
  34. package/dist/mcp-tools-engine.js.map +1 -0
  35. package/dist/parsers/node-parser.d.ts.map +1 -1
  36. package/dist/parsers/node-parser.js +16 -10
  37. package/dist/parsers/node-parser.js.map +1 -1
  38. package/dist/parsers/simple-parser.d.ts.map +1 -1
  39. package/dist/parsers/simple-parser.js +11 -0
  40. package/dist/parsers/simple-parser.js.map +1 -1
  41. package/dist/scripts/test-protocol-negotiation.d.ts +3 -0
  42. package/dist/scripts/test-protocol-negotiation.d.ts.map +1 -0
  43. package/dist/scripts/test-protocol-negotiation.js +154 -0
  44. package/dist/scripts/test-protocol-negotiation.js.map +1 -0
  45. package/dist/services/config-validator.d.ts +6 -1
  46. package/dist/services/config-validator.d.ts.map +1 -1
  47. package/dist/services/config-validator.js +99 -25
  48. package/dist/services/config-validator.js.map +1 -1
  49. package/dist/services/enhanced-config-validator.d.ts +4 -0
  50. package/dist/services/enhanced-config-validator.d.ts.map +1 -1
  51. package/dist/services/enhanced-config-validator.js +90 -2
  52. package/dist/services/enhanced-config-validator.js.map +1 -1
  53. package/dist/services/expression-validator.d.ts.map +1 -1
  54. package/dist/services/expression-validator.js +36 -15
  55. package/dist/services/expression-validator.js.map +1 -1
  56. package/dist/services/n8n-validation.d.ts +5 -5
  57. package/dist/services/n8n-validation.d.ts.map +1 -1
  58. package/dist/services/n8n-validation.js +16 -12
  59. package/dist/services/n8n-validation.js.map +1 -1
  60. package/dist/services/property-filter.d.ts.map +1 -1
  61. package/dist/services/property-filter.js +35 -11
  62. package/dist/services/property-filter.js.map +1 -1
  63. package/dist/services/sqlite-storage-service.d.ts +11 -0
  64. package/dist/services/sqlite-storage-service.d.ts.map +1 -0
  65. package/dist/services/sqlite-storage-service.js +74 -0
  66. package/dist/services/sqlite-storage-service.js.map +1 -0
  67. package/dist/services/workflow-validator.d.ts +3 -1
  68. package/dist/services/workflow-validator.d.ts.map +1 -1
  69. package/dist/services/workflow-validator.js +296 -202
  70. package/dist/services/workflow-validator.js.map +1 -1
  71. package/dist/templates/template-repository.js +1 -1
  72. package/dist/templates/template-repository.js.map +1 -1
  73. package/dist/types/index.d.ts +6 -0
  74. package/dist/types/index.d.ts.map +1 -1
  75. package/dist/utils/fixed-collection-validator.d.ts +35 -0
  76. package/dist/utils/fixed-collection-validator.d.ts.map +1 -0
  77. package/dist/utils/fixed-collection-validator.js +358 -0
  78. package/dist/utils/fixed-collection-validator.js.map +1 -0
  79. package/dist/utils/logger.d.ts +1 -0
  80. package/dist/utils/logger.d.ts.map +1 -1
  81. package/dist/utils/logger.js +8 -4
  82. package/dist/utils/logger.js.map +1 -1
  83. package/dist/utils/protocol-version.d.ts +19 -0
  84. package/dist/utils/protocol-version.d.ts.map +1 -0
  85. package/dist/utils/protocol-version.js +95 -0
  86. package/dist/utils/protocol-version.js.map +1 -0
  87. package/dist/utils/template-sanitizer.d.ts.map +1 -1
  88. package/dist/utils/template-sanitizer.js +10 -1
  89. package/dist/utils/template-sanitizer.js.map +1 -1
  90. package/package.json +1 -1
@@ -44,6 +44,8 @@ const fs_1 = require("fs");
44
44
  const path_1 = __importDefault(require("path"));
45
45
  const tools_1 = require("./tools");
46
46
  const tools_n8n_manager_1 = require("./tools-n8n-manager");
47
+ const tools_n8n_friendly_1 = require("./tools-n8n-friendly");
48
+ const workflow_examples_1 = require("./workflow-examples");
47
49
  const logger_1 = require("../utils/logger");
48
50
  const node_repository_1 = require("../database/node-repository");
49
51
  const database_adapter_1 = require("../database/database-adapter");
@@ -60,22 +62,31 @@ const handlers_workflow_diff_1 = require("./handlers-workflow-diff");
60
62
  const tools_documentation_1 = require("./tools-documentation");
61
63
  const version_1 = require("../utils/version");
62
64
  const node_utils_1 = require("../utils/node-utils");
65
+ const protocol_version_1 = require("../utils/protocol-version");
63
66
  class N8NDocumentationMCPServer {
64
67
  constructor() {
65
68
  this.db = null;
66
69
  this.repository = null;
67
70
  this.templateService = null;
68
71
  this.cache = new simple_cache_1.SimpleCache();
69
- const possiblePaths = [
70
- path_1.default.join(process.cwd(), 'data', 'nodes.db'),
71
- path_1.default.join(__dirname, '../../data', 'nodes.db'),
72
- './data/nodes.db'
73
- ];
72
+ this.clientInfo = null;
73
+ const envDbPath = process.env.NODE_DB_PATH;
74
74
  let dbPath = null;
75
- for (const p of possiblePaths) {
76
- if ((0, fs_1.existsSync)(p)) {
77
- dbPath = p;
78
- break;
75
+ let possiblePaths = [];
76
+ if (envDbPath && (envDbPath === ':memory:' || (0, fs_1.existsSync)(envDbPath))) {
77
+ dbPath = envDbPath;
78
+ }
79
+ else {
80
+ possiblePaths = [
81
+ path_1.default.join(process.cwd(), 'data', 'nodes.db'),
82
+ path_1.default.join(__dirname, '../../data', 'nodes.db'),
83
+ './data/nodes.db'
84
+ ];
85
+ for (const p of possiblePaths) {
86
+ if ((0, fs_1.existsSync)(p)) {
87
+ dbPath = p;
88
+ break;
89
+ }
79
90
  }
80
91
  }
81
92
  if (!dbPath) {
@@ -102,6 +113,9 @@ class N8NDocumentationMCPServer {
102
113
  async initializeDatabase(dbPath) {
103
114
  try {
104
115
  this.db = await (0, database_adapter_1.createDatabaseAdapter)(dbPath);
116
+ if (dbPath === ':memory:') {
117
+ await this.initializeInMemorySchema();
118
+ }
105
119
  this.repository = new node_repository_1.NodeRepository(this.db);
106
120
  this.templateService = new template_service_1.TemplateService(this.db);
107
121
  logger_1.logger.info(`Initialized database from: ${dbPath}`);
@@ -111,6 +125,18 @@ class N8NDocumentationMCPServer {
111
125
  throw new Error(`Failed to open database: ${error instanceof Error ? error.message : 'Unknown error'}`);
112
126
  }
113
127
  }
128
+ async initializeInMemorySchema() {
129
+ if (!this.db)
130
+ return;
131
+ const schemaPath = path_1.default.join(__dirname, '../../src/database/schema.sql');
132
+ const schema = await fs_1.promises.readFile(schemaPath, 'utf-8');
133
+ const statements = schema.split(';').filter(stmt => stmt.trim());
134
+ for (const statement of statements) {
135
+ if (statement.trim()) {
136
+ this.db.exec(statement);
137
+ }
138
+ }
139
+ }
114
140
  async ensureInitialized() {
115
141
  await this.initialized;
116
142
  if (!this.db || !this.repository) {
@@ -118,9 +144,25 @@ class N8NDocumentationMCPServer {
118
144
  }
119
145
  }
120
146
  setupHandlers() {
121
- this.server.setRequestHandler(types_js_1.InitializeRequestSchema, async () => {
147
+ this.server.setRequestHandler(types_js_1.InitializeRequestSchema, async (request) => {
148
+ const clientVersion = request.params.protocolVersion;
149
+ const clientCapabilities = request.params.capabilities;
150
+ const clientInfo = request.params.clientInfo;
151
+ logger_1.logger.info('MCP Initialize request received', {
152
+ clientVersion,
153
+ clientCapabilities,
154
+ clientInfo
155
+ });
156
+ this.clientInfo = clientInfo;
157
+ const negotiationResult = (0, protocol_version_1.negotiateProtocolVersion)(clientVersion, clientInfo, undefined, undefined);
158
+ (0, protocol_version_1.logProtocolNegotiation)(negotiationResult, logger_1.logger, 'MCP_INITIALIZE');
159
+ if (clientVersion && clientVersion !== negotiationResult.version) {
160
+ logger_1.logger.warn(`Protocol version negotiated: client requested ${clientVersion}, server will use ${negotiationResult.version}`, {
161
+ reasoning: negotiationResult.reasoning
162
+ });
163
+ }
122
164
  const response = {
123
- protocolVersion: '2024-11-05',
165
+ protocolVersion: negotiationResult.version,
124
166
  capabilities: {
125
167
  tools: {},
126
168
  },
@@ -129,13 +171,11 @@ class N8NDocumentationMCPServer {
129
171
  version: version_1.PROJECT_VERSION,
130
172
  },
131
173
  };
132
- if (process.env.DEBUG_MCP === 'true') {
133
- logger_1.logger.debug('Initialize handler called', { response });
134
- }
174
+ logger_1.logger.info('MCP Initialize response', { response });
135
175
  return response;
136
176
  });
137
- this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
138
- const tools = [...tools_1.n8nDocumentationToolsFinal];
177
+ this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async (request) => {
178
+ let tools = [...tools_1.n8nDocumentationToolsFinal];
139
179
  const isConfigured = (0, n8n_api_1.isN8nApiConfigured)();
140
180
  if (isConfigured) {
141
181
  tools.push(...tools_n8n_manager_1.n8nManagementTools);
@@ -144,30 +184,124 @@ class N8NDocumentationMCPServer {
144
184
  else {
145
185
  logger_1.logger.debug(`Tool listing: ${tools.length} tools available (documentation only)`);
146
186
  }
187
+ const clientInfo = this.clientInfo;
188
+ const isN8nClient = clientInfo?.name?.includes('n8n') ||
189
+ clientInfo?.name?.includes('langchain');
190
+ if (isN8nClient) {
191
+ logger_1.logger.info('Detected n8n client, using n8n-friendly tool descriptions');
192
+ tools = (0, tools_n8n_friendly_1.makeToolsN8nFriendly)(tools);
193
+ }
194
+ const validationTools = tools.filter(t => t.name.startsWith('validate_'));
195
+ validationTools.forEach(tool => {
196
+ logger_1.logger.info('Validation tool schema', {
197
+ toolName: tool.name,
198
+ inputSchema: JSON.stringify(tool.inputSchema, null, 2),
199
+ hasOutputSchema: !!tool.outputSchema,
200
+ description: tool.description
201
+ });
202
+ });
147
203
  return { tools };
148
204
  });
149
205
  this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
150
206
  const { name, arguments: args } = request.params;
207
+ logger_1.logger.info('Tool call received - DETAILED DEBUG', {
208
+ toolName: name,
209
+ arguments: JSON.stringify(args, null, 2),
210
+ argumentsType: typeof args,
211
+ argumentsKeys: args ? Object.keys(args) : [],
212
+ hasNodeType: args && 'nodeType' in args,
213
+ hasConfig: args && 'config' in args,
214
+ configType: args && args.config ? typeof args.config : 'N/A',
215
+ rawRequest: JSON.stringify(request.params)
216
+ });
217
+ let processedArgs = args;
218
+ if (args && typeof args === 'object' && 'output' in args) {
219
+ try {
220
+ const possibleNestedData = args.output;
221
+ if (typeof possibleNestedData === 'string' && possibleNestedData.trim().startsWith('{')) {
222
+ const parsed = JSON.parse(possibleNestedData);
223
+ if (parsed && typeof parsed === 'object') {
224
+ logger_1.logger.warn('Detected n8n nested output bug, attempting to extract actual arguments', {
225
+ originalArgs: args,
226
+ extractedArgs: parsed
227
+ });
228
+ if (this.validateExtractedArgs(name, parsed)) {
229
+ processedArgs = parsed;
230
+ }
231
+ else {
232
+ logger_1.logger.warn('Extracted arguments failed validation, using original args', {
233
+ toolName: name,
234
+ extractedArgs: parsed
235
+ });
236
+ }
237
+ }
238
+ }
239
+ }
240
+ catch (parseError) {
241
+ logger_1.logger.debug('Failed to parse nested output, continuing with original args', {
242
+ error: parseError instanceof Error ? parseError.message : String(parseError)
243
+ });
244
+ }
245
+ }
151
246
  try {
152
- logger_1.logger.debug(`Executing tool: ${name}`, { args });
153
- const result = await this.executeTool(name, args);
247
+ logger_1.logger.debug(`Executing tool: ${name}`, { args: processedArgs });
248
+ const result = await this.executeTool(name, processedArgs);
154
249
  logger_1.logger.debug(`Tool ${name} executed successfully`);
155
- return {
250
+ let responseText;
251
+ let structuredContent = null;
252
+ try {
253
+ if (name.startsWith('validate_') && typeof result === 'object' && result !== null) {
254
+ const cleanResult = this.sanitizeValidationResult(result, name);
255
+ structuredContent = cleanResult;
256
+ responseText = JSON.stringify(cleanResult, null, 2);
257
+ }
258
+ else {
259
+ responseText = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
260
+ }
261
+ }
262
+ catch (jsonError) {
263
+ logger_1.logger.warn(`Failed to stringify tool result for ${name}:`, jsonError);
264
+ responseText = String(result);
265
+ }
266
+ if (responseText.length > 1000000) {
267
+ logger_1.logger.warn(`Tool ${name} response is very large (${responseText.length} chars), truncating`);
268
+ responseText = responseText.substring(0, 999000) + '\n\n[Response truncated due to size limits]';
269
+ structuredContent = null;
270
+ }
271
+ const mcpResponse = {
156
272
  content: [
157
273
  {
158
274
  type: 'text',
159
- text: JSON.stringify(result, null, 2),
275
+ text: responseText,
160
276
  },
161
277
  ],
162
278
  };
279
+ if (name.startsWith('validate_') && structuredContent !== null) {
280
+ mcpResponse.structuredContent = structuredContent;
281
+ }
282
+ return mcpResponse;
163
283
  }
164
284
  catch (error) {
165
285
  logger_1.logger.error(`Error executing tool ${name}`, error);
286
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
287
+ let helpfulMessage = `Error executing tool ${name}: ${errorMessage}`;
288
+ if (errorMessage.includes('required') || errorMessage.includes('missing')) {
289
+ helpfulMessage += '\n\nNote: This error often occurs when the AI agent sends incomplete or incorrectly formatted parameters. Please ensure all required fields are provided with the correct types.';
290
+ }
291
+ else if (errorMessage.includes('type') || errorMessage.includes('expected')) {
292
+ helpfulMessage += '\n\nNote: This error indicates a type mismatch. The AI agent may be sending data in the wrong format (e.g., string instead of object).';
293
+ }
294
+ else if (errorMessage.includes('Unknown category') || errorMessage.includes('not found')) {
295
+ helpfulMessage += '\n\nNote: The requested resource or category was not found. Please check the available options.';
296
+ }
297
+ if (name.startsWith('validate_') && (errorMessage.includes('config') || errorMessage.includes('nodeType'))) {
298
+ helpfulMessage += '\n\nFor validation tools:\n- nodeType should be a string (e.g., "nodes-base.webhook")\n- config should be an object (e.g., {})';
299
+ }
166
300
  return {
167
301
  content: [
168
302
  {
169
303
  type: 'text',
170
- text: `Error executing tool ${name}: ${error instanceof Error ? error.message : 'Unknown error'}`,
304
+ text: helpfulMessage,
171
305
  },
172
306
  ],
173
307
  isError: true,
@@ -175,82 +309,291 @@ class N8NDocumentationMCPServer {
175
309
  }
176
310
  });
177
311
  }
312
+ sanitizeValidationResult(result, toolName) {
313
+ if (!result || typeof result !== 'object') {
314
+ return result;
315
+ }
316
+ const sanitized = { ...result };
317
+ if (toolName === 'validate_node_minimal') {
318
+ const filtered = {
319
+ nodeType: String(sanitized.nodeType || ''),
320
+ displayName: String(sanitized.displayName || ''),
321
+ valid: Boolean(sanitized.valid),
322
+ missingRequiredFields: Array.isArray(sanitized.missingRequiredFields)
323
+ ? sanitized.missingRequiredFields.map(String)
324
+ : []
325
+ };
326
+ return filtered;
327
+ }
328
+ else if (toolName === 'validate_node_operation') {
329
+ let summary = sanitized.summary;
330
+ if (!summary || typeof summary !== 'object') {
331
+ summary = {
332
+ hasErrors: Array.isArray(sanitized.errors) ? sanitized.errors.length > 0 : false,
333
+ errorCount: Array.isArray(sanitized.errors) ? sanitized.errors.length : 0,
334
+ warningCount: Array.isArray(sanitized.warnings) ? sanitized.warnings.length : 0,
335
+ suggestionCount: Array.isArray(sanitized.suggestions) ? sanitized.suggestions.length : 0
336
+ };
337
+ }
338
+ const filtered = {
339
+ nodeType: String(sanitized.nodeType || ''),
340
+ workflowNodeType: String(sanitized.workflowNodeType || sanitized.nodeType || ''),
341
+ displayName: String(sanitized.displayName || ''),
342
+ valid: Boolean(sanitized.valid),
343
+ errors: Array.isArray(sanitized.errors) ? sanitized.errors : [],
344
+ warnings: Array.isArray(sanitized.warnings) ? sanitized.warnings : [],
345
+ suggestions: Array.isArray(sanitized.suggestions) ? sanitized.suggestions : [],
346
+ summary: summary
347
+ };
348
+ return filtered;
349
+ }
350
+ else if (toolName.startsWith('validate_workflow')) {
351
+ sanitized.valid = Boolean(sanitized.valid);
352
+ sanitized.errors = Array.isArray(sanitized.errors) ? sanitized.errors : [];
353
+ sanitized.warnings = Array.isArray(sanitized.warnings) ? sanitized.warnings : [];
354
+ if (toolName === 'validate_workflow') {
355
+ if (!sanitized.summary || typeof sanitized.summary !== 'object') {
356
+ sanitized.summary = {
357
+ totalNodes: 0,
358
+ enabledNodes: 0,
359
+ triggerNodes: 0,
360
+ validConnections: 0,
361
+ invalidConnections: 0,
362
+ expressionsValidated: 0,
363
+ errorCount: sanitized.errors.length,
364
+ warningCount: sanitized.warnings.length
365
+ };
366
+ }
367
+ }
368
+ else {
369
+ if (!sanitized.statistics || typeof sanitized.statistics !== 'object') {
370
+ sanitized.statistics = {
371
+ totalNodes: 0,
372
+ triggerNodes: 0,
373
+ validConnections: 0,
374
+ invalidConnections: 0,
375
+ expressionsValidated: 0
376
+ };
377
+ }
378
+ }
379
+ }
380
+ return JSON.parse(JSON.stringify(sanitized));
381
+ }
382
+ validateToolParams(toolName, args, requiredParams) {
383
+ const missing = [];
384
+ for (const param of requiredParams) {
385
+ if (!(param in args) || args[param] === undefined || args[param] === null) {
386
+ missing.push(param);
387
+ }
388
+ }
389
+ if (missing.length > 0) {
390
+ throw new Error(`Missing required parameters for ${toolName}: ${missing.join(', ')}. Please provide the required parameters to use this tool.`);
391
+ }
392
+ }
393
+ validateExtractedArgs(toolName, args) {
394
+ if (!args || typeof args !== 'object') {
395
+ return false;
396
+ }
397
+ const allTools = [...tools_1.n8nDocumentationToolsFinal, ...tools_n8n_manager_1.n8nManagementTools];
398
+ const tool = allTools.find(t => t.name === toolName);
399
+ if (!tool || !tool.inputSchema) {
400
+ return true;
401
+ }
402
+ const schema = tool.inputSchema;
403
+ const required = schema.required || [];
404
+ const properties = schema.properties || {};
405
+ for (const requiredField of required) {
406
+ if (!(requiredField in args)) {
407
+ logger_1.logger.debug(`Extracted args missing required field: ${requiredField}`, {
408
+ toolName,
409
+ extractedArgs: args,
410
+ required
411
+ });
412
+ return false;
413
+ }
414
+ }
415
+ for (const [fieldName, fieldValue] of Object.entries(args)) {
416
+ if (properties[fieldName]) {
417
+ const expectedType = properties[fieldName].type;
418
+ const actualType = Array.isArray(fieldValue) ? 'array' : typeof fieldValue;
419
+ if (expectedType && expectedType !== actualType) {
420
+ if (expectedType === 'number' && actualType === 'string' && !isNaN(Number(fieldValue))) {
421
+ continue;
422
+ }
423
+ logger_1.logger.debug(`Extracted args field type mismatch: ${fieldName}`, {
424
+ toolName,
425
+ expectedType,
426
+ actualType,
427
+ fieldValue
428
+ });
429
+ return false;
430
+ }
431
+ }
432
+ }
433
+ if (schema.additionalProperties === false) {
434
+ const allowedFields = Object.keys(properties);
435
+ const extraFields = Object.keys(args).filter(field => !allowedFields.includes(field));
436
+ if (extraFields.length > 0) {
437
+ logger_1.logger.debug(`Extracted args have extra fields`, {
438
+ toolName,
439
+ extraFields,
440
+ allowedFields
441
+ });
442
+ }
443
+ }
444
+ return true;
445
+ }
178
446
  async executeTool(name, args) {
447
+ args = args || {};
448
+ logger_1.logger.info(`Tool execution: ${name}`, {
449
+ args: typeof args === 'object' ? JSON.stringify(args) : args,
450
+ argsType: typeof args,
451
+ argsKeys: typeof args === 'object' ? Object.keys(args) : 'not-object'
452
+ });
453
+ if (typeof args !== 'object' || args === null) {
454
+ throw new Error(`Invalid arguments for tool ${name}: expected object, got ${typeof args}`);
455
+ }
179
456
  switch (name) {
180
457
  case 'tools_documentation':
181
458
  return this.getToolsDocumentation(args.topic, args.depth);
182
459
  case 'list_nodes':
183
460
  return this.listNodes(args);
184
461
  case 'get_node_info':
462
+ this.validateToolParams(name, args, ['nodeType']);
185
463
  return this.getNodeInfo(args.nodeType);
186
464
  case 'search_nodes':
187
- return this.searchNodes(args.query, args.limit, { mode: args.mode });
465
+ this.validateToolParams(name, args, ['query']);
466
+ const limit = args.limit !== undefined ? Number(args.limit) || 20 : 20;
467
+ return this.searchNodes(args.query, limit, { mode: args.mode });
188
468
  case 'list_ai_tools':
189
469
  return this.listAITools();
190
470
  case 'get_node_documentation':
471
+ this.validateToolParams(name, args, ['nodeType']);
191
472
  return this.getNodeDocumentation(args.nodeType);
192
473
  case 'get_database_statistics':
193
474
  return this.getDatabaseStatistics();
194
475
  case 'get_node_essentials':
476
+ this.validateToolParams(name, args, ['nodeType']);
195
477
  return this.getNodeEssentials(args.nodeType);
196
478
  case 'search_node_properties':
197
- return this.searchNodeProperties(args.nodeType, args.query, args.maxResults);
479
+ this.validateToolParams(name, args, ['nodeType', 'query']);
480
+ const maxResults = args.maxResults !== undefined ? Number(args.maxResults) || 20 : 20;
481
+ return this.searchNodeProperties(args.nodeType, args.query, maxResults);
198
482
  case 'get_node_for_task':
483
+ this.validateToolParams(name, args, ['task']);
199
484
  return this.getNodeForTask(args.task);
200
485
  case 'list_tasks':
201
486
  return this.listTasks(args.category);
202
487
  case 'validate_node_operation':
488
+ this.validateToolParams(name, args, ['nodeType', 'config']);
489
+ if (typeof args.config !== 'object' || args.config === null) {
490
+ logger_1.logger.warn(`validate_node_operation called with invalid config type: ${typeof args.config}`);
491
+ return {
492
+ nodeType: args.nodeType || 'unknown',
493
+ workflowNodeType: args.nodeType || 'unknown',
494
+ displayName: 'Unknown Node',
495
+ valid: false,
496
+ errors: [{
497
+ type: 'config',
498
+ property: 'config',
499
+ message: 'Invalid config format - expected object',
500
+ fix: 'Provide config as an object with node properties'
501
+ }],
502
+ warnings: [],
503
+ suggestions: [],
504
+ summary: {
505
+ hasErrors: true,
506
+ errorCount: 1,
507
+ warningCount: 0,
508
+ suggestionCount: 0
509
+ }
510
+ };
511
+ }
203
512
  return this.validateNodeConfig(args.nodeType, args.config, 'operation', args.profile);
204
513
  case 'validate_node_minimal':
514
+ this.validateToolParams(name, args, ['nodeType', 'config']);
515
+ if (typeof args.config !== 'object' || args.config === null) {
516
+ logger_1.logger.warn(`validate_node_minimal called with invalid config type: ${typeof args.config}`);
517
+ return {
518
+ nodeType: args.nodeType || 'unknown',
519
+ displayName: 'Unknown Node',
520
+ valid: false,
521
+ missingRequiredFields: ['Invalid config format - expected object']
522
+ };
523
+ }
205
524
  return this.validateNodeMinimal(args.nodeType, args.config);
206
525
  case 'get_property_dependencies':
526
+ this.validateToolParams(name, args, ['nodeType']);
207
527
  return this.getPropertyDependencies(args.nodeType, args.config);
208
528
  case 'get_node_as_tool_info':
529
+ this.validateToolParams(name, args, ['nodeType']);
209
530
  return this.getNodeAsToolInfo(args.nodeType);
210
531
  case 'list_node_templates':
211
- return this.listNodeTemplates(args.nodeTypes, args.limit);
532
+ this.validateToolParams(name, args, ['nodeTypes']);
533
+ const templateLimit = args.limit !== undefined ? Number(args.limit) || 10 : 10;
534
+ return this.listNodeTemplates(args.nodeTypes, templateLimit);
212
535
  case 'get_template':
213
- return this.getTemplate(args.templateId);
536
+ this.validateToolParams(name, args, ['templateId']);
537
+ const templateId = Number(args.templateId);
538
+ return this.getTemplate(templateId);
214
539
  case 'search_templates':
215
- return this.searchTemplates(args.query, args.limit);
540
+ this.validateToolParams(name, args, ['query']);
541
+ const searchLimit = args.limit !== undefined ? Number(args.limit) || 20 : 20;
542
+ return this.searchTemplates(args.query, searchLimit);
216
543
  case 'get_templates_for_task':
544
+ this.validateToolParams(name, args, ['task']);
217
545
  return this.getTemplatesForTask(args.task);
218
546
  case 'validate_workflow':
547
+ this.validateToolParams(name, args, ['workflow']);
219
548
  return this.validateWorkflow(args.workflow, args.options);
220
549
  case 'validate_workflow_connections':
550
+ this.validateToolParams(name, args, ['workflow']);
221
551
  return this.validateWorkflowConnections(args.workflow);
222
552
  case 'validate_workflow_expressions':
553
+ this.validateToolParams(name, args, ['workflow']);
223
554
  return this.validateWorkflowExpressions(args.workflow);
224
555
  case 'n8n_create_workflow':
556
+ this.validateToolParams(name, args, ['name', 'nodes', 'connections']);
225
557
  return n8nHandlers.handleCreateWorkflow(args);
226
558
  case 'n8n_get_workflow':
559
+ this.validateToolParams(name, args, ['id']);
227
560
  return n8nHandlers.handleGetWorkflow(args);
228
561
  case 'n8n_get_workflow_details':
562
+ this.validateToolParams(name, args, ['id']);
229
563
  return n8nHandlers.handleGetWorkflowDetails(args);
230
564
  case 'n8n_get_workflow_structure':
565
+ this.validateToolParams(name, args, ['id']);
231
566
  return n8nHandlers.handleGetWorkflowStructure(args);
232
567
  case 'n8n_get_workflow_minimal':
568
+ this.validateToolParams(name, args, ['id']);
233
569
  return n8nHandlers.handleGetWorkflowMinimal(args);
234
570
  case 'n8n_update_full_workflow':
571
+ this.validateToolParams(name, args, ['id']);
235
572
  return n8nHandlers.handleUpdateWorkflow(args);
236
573
  case 'n8n_update_partial_workflow':
574
+ this.validateToolParams(name, args, ['id', 'operations']);
237
575
  return (0, handlers_workflow_diff_1.handleUpdatePartialWorkflow)(args);
238
576
  case 'n8n_delete_workflow':
577
+ this.validateToolParams(name, args, ['id']);
239
578
  return n8nHandlers.handleDeleteWorkflow(args);
240
579
  case 'n8n_list_workflows':
241
580
  return n8nHandlers.handleListWorkflows(args);
242
581
  case 'n8n_validate_workflow':
582
+ this.validateToolParams(name, args, ['id']);
243
583
  await this.ensureInitialized();
244
584
  if (!this.repository)
245
585
  throw new Error('Repository not initialized');
246
586
  return n8nHandlers.handleValidateWorkflow(args, this.repository);
247
587
  case 'n8n_trigger_webhook_workflow':
588
+ this.validateToolParams(name, args, ['webhookUrl']);
248
589
  return n8nHandlers.handleTriggerWebhookWorkflow(args);
249
590
  case 'n8n_get_execution':
591
+ this.validateToolParams(name, args, ['id']);
250
592
  return n8nHandlers.handleGetExecution(args);
251
593
  case 'n8n_list_executions':
252
594
  return n8nHandlers.handleListExecutions(args);
253
595
  case 'n8n_delete_execution':
596
+ this.validateToolParams(name, args, ['id']);
254
597
  return n8nHandlers.handleDeleteExecution(args);
255
598
  case 'n8n_health_check':
256
599
  return n8nHandlers.handleHealthCheck();
@@ -1441,6 +1784,50 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
1441
1784
  await this.ensureInitialized();
1442
1785
  if (!this.repository)
1443
1786
  throw new Error('Repository not initialized');
1787
+ logger_1.logger.info('Workflow validation requested', {
1788
+ hasWorkflow: !!workflow,
1789
+ workflowType: typeof workflow,
1790
+ hasNodes: workflow?.nodes !== undefined,
1791
+ nodesType: workflow?.nodes ? typeof workflow.nodes : 'undefined',
1792
+ nodesIsArray: Array.isArray(workflow?.nodes),
1793
+ nodesCount: Array.isArray(workflow?.nodes) ? workflow.nodes.length : 0,
1794
+ hasConnections: workflow?.connections !== undefined,
1795
+ connectionsType: workflow?.connections ? typeof workflow.connections : 'undefined',
1796
+ options: options
1797
+ });
1798
+ if (!workflow || typeof workflow !== 'object') {
1799
+ return {
1800
+ valid: false,
1801
+ errors: [{
1802
+ node: 'workflow',
1803
+ message: 'Workflow must be an object with nodes and connections',
1804
+ details: 'Expected format: ' + (0, workflow_examples_1.getWorkflowExampleString)()
1805
+ }],
1806
+ summary: { errorCount: 1 }
1807
+ };
1808
+ }
1809
+ if (!workflow.nodes || !Array.isArray(workflow.nodes)) {
1810
+ return {
1811
+ valid: false,
1812
+ errors: [{
1813
+ node: 'workflow',
1814
+ message: 'Workflow must have a nodes array',
1815
+ details: 'Expected: workflow.nodes = [array of node objects]. ' + (0, workflow_examples_1.getWorkflowExampleString)()
1816
+ }],
1817
+ summary: { errorCount: 1 }
1818
+ };
1819
+ }
1820
+ if (!workflow.connections || typeof workflow.connections !== 'object') {
1821
+ return {
1822
+ valid: false,
1823
+ errors: [{
1824
+ node: 'workflow',
1825
+ message: 'Workflow must have a connections object',
1826
+ details: 'Expected: workflow.connections = {} (can be empty object). ' + (0, workflow_examples_1.getWorkflowExampleString)()
1827
+ }],
1828
+ summary: { errorCount: 1 }
1829
+ };
1830
+ }
1444
1831
  const validator = new workflow_validator_1.WorkflowValidator(this.repository, enhanced_config_validator_1.EnhancedConfigValidator);
1445
1832
  try {
1446
1833
  const result = await validator.validateWorkflow(workflow, options);