n8n-mcp 2.14.6 → 2.15.0

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 (48) hide show
  1. package/data/nodes.db +0 -0
  2. package/dist/database/node-repository.d.ts.map +1 -1
  3. package/dist/database/node-repository.js +12 -20
  4. package/dist/database/node-repository.js.map +1 -1
  5. package/dist/mcp/handlers-n8n-manager.d.ts.map +1 -1
  6. package/dist/mcp/handlers-n8n-manager.js +10 -4
  7. package/dist/mcp/handlers-n8n-manager.js.map +1 -1
  8. package/dist/mcp/server.d.ts +0 -1
  9. package/dist/mcp/server.d.ts.map +1 -1
  10. package/dist/mcp/server.js +146 -53
  11. package/dist/mcp/server.js.map +1 -1
  12. package/dist/mcp/tool-docs/index.d.ts.map +1 -1
  13. package/dist/mcp/tool-docs/index.js +0 -1
  14. package/dist/mcp/tool-docs/index.js.map +1 -1
  15. package/dist/mcp/tool-docs/templates/index.d.ts +0 -1
  16. package/dist/mcp/tool-docs/templates/index.d.ts.map +1 -1
  17. package/dist/mcp/tool-docs/templates/index.js +1 -3
  18. package/dist/mcp/tool-docs/templates/index.js.map +1 -1
  19. package/dist/mcp/tools.d.ts.map +1 -1
  20. package/dist/mcp/tools.js +12 -16
  21. package/dist/mcp/tools.js.map +1 -1
  22. package/dist/mcp-tools-engine.d.ts +0 -1
  23. package/dist/mcp-tools-engine.d.ts.map +1 -1
  24. package/dist/mcp-tools-engine.js +0 -4
  25. package/dist/mcp-tools-engine.js.map +1 -1
  26. package/dist/scripts/fetch-templates.d.ts +1 -1
  27. package/dist/scripts/fetch-templates.d.ts.map +1 -1
  28. package/dist/scripts/fetch-templates.js +147 -4
  29. package/dist/scripts/fetch-templates.js.map +1 -1
  30. package/dist/services/enhanced-config-validator.js +2 -2
  31. package/dist/services/enhanced-config-validator.js.map +1 -1
  32. package/dist/services/n8n-validation.d.ts +3 -0
  33. package/dist/services/n8n-validation.d.ts.map +1 -1
  34. package/dist/services/n8n-validation.js +2 -0
  35. package/dist/services/n8n-validation.js.map +1 -1
  36. package/dist/services/task-templates.d.ts.map +1 -1
  37. package/dist/services/task-templates.js.map +1 -1
  38. package/dist/services/workflow-diff-engine.d.ts.map +1 -1
  39. package/dist/services/workflow-diff-engine.js +19 -2
  40. package/dist/services/workflow-diff-engine.js.map +1 -1
  41. package/dist/services/workflow-validator.d.ts.map +1 -1
  42. package/dist/services/workflow-validator.js +14 -18
  43. package/dist/services/workflow-validator.js.map +1 -1
  44. package/dist/utils/node-type-normalizer.d.ts +16 -0
  45. package/dist/utils/node-type-normalizer.d.ts.map +1 -0
  46. package/dist/utils/node-type-normalizer.js +75 -0
  47. package/dist/utils/node-type-normalizer.js.map +1 -0
  48. package/package.json +1 -1
@@ -62,6 +62,7 @@ const handlers_workflow_diff_1 = require("./handlers-workflow-diff");
62
62
  const tools_documentation_1 = require("./tools-documentation");
63
63
  const version_1 = require("../utils/version");
64
64
  const node_utils_1 = require("../utils/node-utils");
65
+ const node_type_normalizer_1 = require("../utils/node-type-normalizer");
65
66
  const validation_schemas_1 = require("../utils/validation-schemas");
66
67
  const protocol_version_1 = require("../utils/protocol-version");
67
68
  const telemetry_1 = require("../telemetry");
@@ -555,7 +556,7 @@ class N8NDocumentationMCPServer {
555
556
  case 'search_nodes':
556
557
  this.validateToolParams(name, args, ['query']);
557
558
  const limit = args.limit !== undefined ? Number(args.limit) || 20 : 20;
558
- return this.searchNodes(args.query, limit, { mode: args.mode });
559
+ return this.searchNodes(args.query, limit, { mode: args.mode, includeExamples: args.includeExamples });
559
560
  case 'list_ai_tools':
560
561
  return this.listAITools();
561
562
  case 'get_node_documentation':
@@ -565,14 +566,11 @@ class N8NDocumentationMCPServer {
565
566
  return this.getDatabaseStatistics();
566
567
  case 'get_node_essentials':
567
568
  this.validateToolParams(name, args, ['nodeType']);
568
- return this.getNodeEssentials(args.nodeType);
569
+ return this.getNodeEssentials(args.nodeType, args.includeExamples);
569
570
  case 'search_node_properties':
570
571
  this.validateToolParams(name, args, ['nodeType', 'query']);
571
572
  const maxResults = args.maxResults !== undefined ? Number(args.maxResults) || 20 : 20;
572
573
  return this.searchNodeProperties(args.nodeType, args.query, maxResults);
573
- case 'get_node_for_task':
574
- this.validateToolParams(name, args, ['task']);
575
- return this.getNodeForTask(args.task);
576
574
  case 'list_tasks':
577
575
  return this.listTasks(args.category);
578
576
  case 'validate_node_operation':
@@ -783,7 +781,7 @@ class N8NDocumentationMCPServer {
783
781
  await this.ensureInitialized();
784
782
  if (!this.repository)
785
783
  throw new Error('Repository not initialized');
786
- const normalizedType = (0, node_utils_1.normalizeNodeType)(nodeType);
784
+ const normalizedType = node_type_normalizer_1.NodeTypeNormalizer.normalizeToFullForm(nodeType);
787
785
  let node = this.repository.getNode(normalizedType);
788
786
  if (!node && normalizedType !== nodeType) {
789
787
  node = this.repository.getNode(nodeType);
@@ -846,13 +844,15 @@ class N8NDocumentationMCPServer {
846
844
  WHERE type='table' AND name='nodes_fts'
847
845
  `).get();
848
846
  if (ftsExists) {
849
- return this.searchNodesFTS(normalizedQuery, limit, searchMode);
847
+ logger_1.logger.debug(`Using FTS5 search with includeExamples=${options?.includeExamples}`);
848
+ return this.searchNodesFTS(normalizedQuery, limit, searchMode, options);
850
849
  }
851
850
  else {
852
- return this.searchNodesLIKE(normalizedQuery, limit);
851
+ logger_1.logger.debug('Using LIKE search (no FTS5)');
852
+ return this.searchNodesLIKE(normalizedQuery, limit, options);
853
853
  }
854
854
  }
855
- async searchNodesFTS(query, limit, mode) {
855
+ async searchNodesFTS(query, limit, mode, options) {
856
856
  if (!this.db)
857
857
  throw new Error('Database not initialized');
858
858
  const cleanedQuery = query.trim();
@@ -932,6 +932,32 @@ class N8NDocumentationMCPServer {
932
932
  if (mode !== 'OR') {
933
933
  result.mode = mode;
934
934
  }
935
+ if (options && options.includeExamples) {
936
+ try {
937
+ for (const nodeResult of result.results) {
938
+ const examples = this.db.prepare(`
939
+ SELECT
940
+ parameters_json,
941
+ template_name,
942
+ template_views
943
+ FROM template_node_configs
944
+ WHERE node_type = ?
945
+ ORDER BY rank
946
+ LIMIT 2
947
+ `).all(nodeResult.workflowNodeType);
948
+ if (examples.length > 0) {
949
+ nodeResult.examples = examples.map((ex) => ({
950
+ configuration: JSON.parse(ex.parameters_json),
951
+ template: ex.template_name,
952
+ views: ex.template_views
953
+ }));
954
+ }
955
+ }
956
+ }
957
+ catch (error) {
958
+ logger_1.logger.error(`Failed to add examples:`, error);
959
+ }
960
+ }
935
961
  telemetry_1.telemetry.trackSearchQuery(query, scoredNodes.length, mode ?? 'OR');
936
962
  return result;
937
963
  }
@@ -1059,18 +1085,18 @@ class N8NDocumentationMCPServer {
1059
1085
  }
1060
1086
  return dp[m][n];
1061
1087
  }
1062
- async searchNodesLIKE(query, limit) {
1088
+ async searchNodesLIKE(query, limit, options) {
1063
1089
  if (!this.db)
1064
1090
  throw new Error('Database not initialized');
1065
1091
  if (query.startsWith('"') && query.endsWith('"')) {
1066
1092
  const exactPhrase = query.slice(1, -1);
1067
1093
  const nodes = this.db.prepare(`
1068
- SELECT * FROM nodes
1094
+ SELECT * FROM nodes
1069
1095
  WHERE node_type LIKE ? OR display_name LIKE ? OR description LIKE ?
1070
1096
  LIMIT ?
1071
1097
  `).all(`%${exactPhrase}%`, `%${exactPhrase}%`, `%${exactPhrase}%`, limit * 3);
1072
1098
  const rankedNodes = this.rankSearchResults(nodes, exactPhrase, limit);
1073
- return {
1099
+ const result = {
1074
1100
  query,
1075
1101
  results: rankedNodes.map(node => ({
1076
1102
  nodeType: node.node_type,
@@ -1082,6 +1108,33 @@ class N8NDocumentationMCPServer {
1082
1108
  })),
1083
1109
  totalCount: rankedNodes.length
1084
1110
  };
1111
+ if (options?.includeExamples) {
1112
+ for (const nodeResult of result.results) {
1113
+ try {
1114
+ const examples = this.db.prepare(`
1115
+ SELECT
1116
+ parameters_json,
1117
+ template_name,
1118
+ template_views
1119
+ FROM template_node_configs
1120
+ WHERE node_type = ?
1121
+ ORDER BY rank
1122
+ LIMIT 2
1123
+ `).all(nodeResult.workflowNodeType);
1124
+ if (examples.length > 0) {
1125
+ nodeResult.examples = examples.map((ex) => ({
1126
+ configuration: JSON.parse(ex.parameters_json),
1127
+ template: ex.template_name,
1128
+ views: ex.template_views
1129
+ }));
1130
+ }
1131
+ }
1132
+ catch (error) {
1133
+ logger_1.logger.warn(`Failed to fetch examples for ${nodeResult.nodeType}:`, error.message);
1134
+ }
1135
+ }
1136
+ }
1137
+ return result;
1085
1138
  }
1086
1139
  const words = query.toLowerCase().split(/\s+/).filter(w => w.length > 0);
1087
1140
  if (words.length === 0) {
@@ -1096,7 +1149,7 @@ class N8NDocumentationMCPServer {
1096
1149
  LIMIT ?
1097
1150
  `).all(...params);
1098
1151
  const rankedNodes = this.rankSearchResults(nodes, query, limit);
1099
- return {
1152
+ const result = {
1100
1153
  query,
1101
1154
  results: rankedNodes.map(node => ({
1102
1155
  nodeType: node.node_type,
@@ -1108,6 +1161,33 @@ class N8NDocumentationMCPServer {
1108
1161
  })),
1109
1162
  totalCount: rankedNodes.length
1110
1163
  };
1164
+ if (options?.includeExamples) {
1165
+ for (const nodeResult of result.results) {
1166
+ try {
1167
+ const examples = this.db.prepare(`
1168
+ SELECT
1169
+ parameters_json,
1170
+ template_name,
1171
+ template_views
1172
+ FROM template_node_configs
1173
+ WHERE node_type = ?
1174
+ ORDER BY rank
1175
+ LIMIT 2
1176
+ `).all(nodeResult.workflowNodeType);
1177
+ if (examples.length > 0) {
1178
+ nodeResult.examples = examples.map((ex) => ({
1179
+ configuration: JSON.parse(ex.parameters_json),
1180
+ template: ex.template_name,
1181
+ views: ex.template_views
1182
+ }));
1183
+ }
1184
+ }
1185
+ catch (error) {
1186
+ logger_1.logger.warn(`Failed to fetch examples for ${nodeResult.nodeType}:`, error.message);
1187
+ }
1188
+ }
1189
+ }
1190
+ return result;
1111
1191
  }
1112
1192
  calculateRelevance(node, query) {
1113
1193
  const lowerQuery = query.toLowerCase();
@@ -1253,7 +1333,7 @@ class N8NDocumentationMCPServer {
1253
1333
  await this.ensureInitialized();
1254
1334
  if (!this.db)
1255
1335
  throw new Error('Database not initialized');
1256
- const normalizedType = (0, node_utils_1.normalizeNodeType)(nodeType);
1336
+ const normalizedType = node_type_normalizer_1.NodeTypeNormalizer.normalizeToFullForm(nodeType);
1257
1337
  let node = this.db.prepare(`
1258
1338
  SELECT node_type, display_name, documentation, description
1259
1339
  FROM nodes
@@ -1362,15 +1442,15 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
1362
1442
  })),
1363
1443
  };
1364
1444
  }
1365
- async getNodeEssentials(nodeType) {
1445
+ async getNodeEssentials(nodeType, includeExamples) {
1366
1446
  await this.ensureInitialized();
1367
1447
  if (!this.repository)
1368
1448
  throw new Error('Repository not initialized');
1369
- const cacheKey = `essentials:${nodeType}`;
1449
+ const cacheKey = `essentials:${nodeType}:${includeExamples ? 'withExamples' : 'basic'}`;
1370
1450
  const cached = this.cache.get(cacheKey);
1371
1451
  if (cached)
1372
1452
  return cached;
1373
- const normalizedType = (0, node_utils_1.normalizeNodeType)(nodeType);
1453
+ const normalizedType = node_type_normalizer_1.NodeTypeNormalizer.normalizeToFullForm(nodeType);
1374
1454
  let node = this.repository.getNode(normalizedType);
1375
1455
  if (!node && normalizedType !== nodeType) {
1376
1456
  node = this.repository.getNode(nodeType);
@@ -1417,6 +1497,50 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
1417
1497
  developmentStyle: node.developmentStyle ?? 'programmatic'
1418
1498
  }
1419
1499
  };
1500
+ if (includeExamples) {
1501
+ try {
1502
+ const fullNodeType = (0, node_utils_1.getWorkflowNodeType)(node.package ?? 'n8n-nodes-base', node.nodeType);
1503
+ const examples = this.db.prepare(`
1504
+ SELECT
1505
+ parameters_json,
1506
+ template_name,
1507
+ template_views,
1508
+ complexity,
1509
+ use_cases,
1510
+ has_credentials,
1511
+ has_expressions
1512
+ FROM template_node_configs
1513
+ WHERE node_type = ?
1514
+ ORDER BY rank
1515
+ LIMIT 3
1516
+ `).all(fullNodeType);
1517
+ if (examples.length > 0) {
1518
+ result.examples = examples.map((ex) => ({
1519
+ configuration: JSON.parse(ex.parameters_json),
1520
+ source: {
1521
+ template: ex.template_name,
1522
+ views: ex.template_views,
1523
+ complexity: ex.complexity
1524
+ },
1525
+ useCases: ex.use_cases ? JSON.parse(ex.use_cases).slice(0, 2) : [],
1526
+ metadata: {
1527
+ hasCredentials: ex.has_credentials === 1,
1528
+ hasExpressions: ex.has_expressions === 1
1529
+ }
1530
+ }));
1531
+ result.examplesCount = examples.length;
1532
+ }
1533
+ else {
1534
+ result.examples = [];
1535
+ result.examplesCount = 0;
1536
+ }
1537
+ }
1538
+ catch (error) {
1539
+ logger_1.logger.warn(`Failed to fetch examples for ${nodeType}:`, error.message);
1540
+ result.examples = [];
1541
+ result.examplesCount = 0;
1542
+ }
1543
+ }
1420
1544
  this.cache.set(cacheKey, result, 3600);
1421
1545
  return result;
1422
1546
  }
@@ -1424,7 +1548,7 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
1424
1548
  await this.ensureInitialized();
1425
1549
  if (!this.repository)
1426
1550
  throw new Error('Repository not initialized');
1427
- const normalizedType = (0, node_utils_1.normalizeNodeType)(nodeType);
1551
+ const normalizedType = node_type_normalizer_1.NodeTypeNormalizer.normalizeToFullForm(nodeType);
1428
1552
  let node = this.repository.getNode(normalizedType);
1429
1553
  if (!node && normalizedType !== nodeType) {
1430
1554
  node = this.repository.getNode(nodeType);
@@ -1462,37 +1586,6 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
1462
1586
  searchedIn: allProperties.length + ' properties'
1463
1587
  };
1464
1588
  }
1465
- async getNodeForTask(task) {
1466
- const template = task_templates_1.TaskTemplates.getTaskTemplate(task);
1467
- if (!template) {
1468
- const similar = task_templates_1.TaskTemplates.searchTasks(task);
1469
- throw new Error(`Unknown task: ${task}. ` +
1470
- (similar.length > 0
1471
- ? `Did you mean: ${similar.slice(0, 3).join(', ')}?`
1472
- : `Use 'list_tasks' to see available tasks.`));
1473
- }
1474
- return {
1475
- task: template.task,
1476
- description: template.description,
1477
- nodeType: template.nodeType,
1478
- configuration: template.configuration,
1479
- userMustProvide: template.userMustProvide,
1480
- optionalEnhancements: template.optionalEnhancements || [],
1481
- notes: template.notes || [],
1482
- example: {
1483
- node: {
1484
- type: template.nodeType,
1485
- parameters: template.configuration
1486
- },
1487
- userInputsNeeded: template.userMustProvide.map(p => ({
1488
- property: p.property,
1489
- currentValue: this.getPropertyValue(template.configuration, p.property),
1490
- description: p.description,
1491
- example: p.example
1492
- }))
1493
- }
1494
- };
1495
- }
1496
1589
  getPropertyValue(config, path) {
1497
1590
  const parts = path.split('.');
1498
1591
  let value = config;
@@ -1547,7 +1640,7 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
1547
1640
  await this.ensureInitialized();
1548
1641
  if (!this.repository)
1549
1642
  throw new Error('Repository not initialized');
1550
- const normalizedType = (0, node_utils_1.normalizeNodeType)(nodeType);
1643
+ const normalizedType = node_type_normalizer_1.NodeTypeNormalizer.normalizeToFullForm(nodeType);
1551
1644
  let node = this.repository.getNode(normalizedType);
1552
1645
  if (!node && normalizedType !== nodeType) {
1553
1646
  node = this.repository.getNode(nodeType);
@@ -1584,7 +1677,7 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
1584
1677
  await this.ensureInitialized();
1585
1678
  if (!this.repository)
1586
1679
  throw new Error('Repository not initialized');
1587
- const normalizedType = (0, node_utils_1.normalizeNodeType)(nodeType);
1680
+ const normalizedType = node_type_normalizer_1.NodeTypeNormalizer.normalizeToFullForm(nodeType);
1588
1681
  let node = this.repository.getNode(normalizedType);
1589
1682
  if (!node && normalizedType !== nodeType) {
1590
1683
  node = this.repository.getNode(nodeType);
@@ -1622,7 +1715,7 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
1622
1715
  await this.ensureInitialized();
1623
1716
  if (!this.repository)
1624
1717
  throw new Error('Repository not initialized');
1625
- const normalizedType = (0, node_utils_1.normalizeNodeType)(nodeType);
1718
+ const normalizedType = node_type_normalizer_1.NodeTypeNormalizer.normalizeToFullForm(nodeType);
1626
1719
  let node = this.repository.getNode(normalizedType);
1627
1720
  if (!node && normalizedType !== nodeType) {
1628
1721
  node = this.repository.getNode(nodeType);
@@ -1815,7 +1908,7 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
1815
1908
  await this.ensureInitialized();
1816
1909
  if (!this.repository)
1817
1910
  throw new Error('Repository not initialized');
1818
- const normalizedType = (0, node_utils_1.normalizeNodeType)(nodeType);
1911
+ const normalizedType = node_type_normalizer_1.NodeTypeNormalizer.normalizeToFullForm(nodeType);
1819
1912
  let node = this.repository.getNode(normalizedType);
1820
1913
  if (!node && normalizedType !== nodeType) {
1821
1914
  node = this.repository.getNode(nodeType);