n8n-workflow-builder-mcp 0.1.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 (595) hide show
  1. package/.cursor/rules/cursor_rules.mdc +53 -0
  2. package/.cursor/rules/dev_workflow.mdc +219 -0
  3. package/.cursor/rules/mcp.mdc +430 -0
  4. package/.cursor/rules/self_improve.mdc +72 -0
  5. package/.cursor/rules/taskmaster.mdc +382 -0
  6. package/.cursorignore +1 -0
  7. package/.cursorrules +4 -0
  8. package/.env.example +23 -0
  9. package/.eslintrc.json +38 -0
  10. package/.github/workflows/npm-publish-github-packages.yml +55 -0
  11. package/.prettierrc +9 -0
  12. package/.roo/rules/dev_workflow.md +219 -0
  13. package/.roo/rules/mcp.md +430 -0
  14. package/.roo/rules/roo_rules.md +53 -0
  15. package/.roo/rules/self_improve.md +72 -0
  16. package/.roo/rules/taskmaster.md +382 -0
  17. package/.roo/rules-architect/architect-rules +93 -0
  18. package/.roo/rules-ask/ask-rules +89 -0
  19. package/.roo/rules-boomerang/boomerang-rules +181 -0
  20. package/.roo/rules-code/code-rules +61 -0
  21. package/.roo/rules-debug/debug-rules +68 -0
  22. package/.roo/rules-test/test-rules +61 -0
  23. package/.roomodes +63 -0
  24. package/.taskmasterconfig +31 -0
  25. package/.windsurfrules +2382 -0
  26. package/LICENSE +21 -0
  27. package/README.md +210 -0
  28. package/config/credentials/credentials.json +1 -0
  29. package/config/default.js +41 -0
  30. package/package.json +56 -0
  31. package/scripts/demo-n8n-integration.js +161 -0
  32. package/scripts/demo-workflow-generator.js +102 -0
  33. package/scripts/init.sh +36 -0
  34. package/scripts/prd.txt +197 -0
  35. package/src/index.ts +1440 -0
  36. package/src/middleware/auth.js +273 -0
  37. package/src/middleware/authorize.js +183 -0
  38. package/src/middleware/logging.js +64 -0
  39. package/src/middleware/mcp.js +187 -0
  40. package/src/middleware/rateLimiter.js +82 -0
  41. package/src/middleware/validation.js +241 -0
  42. package/src/models/credential.js +359 -0
  43. package/src/models/llmService.js +236 -0
  44. package/src/models/n8nIntegration.js +542 -0
  45. package/src/models/storage.js +196 -0
  46. package/src/models/tool.js +148 -0
  47. package/src/models/user.js +164 -0
  48. package/src/models/workflow.js +229 -0
  49. package/src/routes/toolDefinitions.js +62 -0
  50. package/src/routes/toolExecution.js +79 -0
  51. package/src/tools/__index.js +242 -0
  52. package/src/tools/connectionManagement.js +500 -0
  53. package/src/tools/n8nIntegration.js +370 -0
  54. package/src/tools/nodeDiscovery.js +488 -0
  55. package/src/tools/nodeManagement.js +674 -0
  56. package/src/tools/toolDefinitions.js +660 -0
  57. package/src/tools/workflowCreation.js +100 -0
  58. package/src/tools/workflowGenerator.js +152 -0
  59. package/src/tools/workflowStorage.js +113 -0
  60. package/src/tools/workflowTesting.js +285 -0
  61. package/src/utils/encryption.js +164 -0
  62. package/src/utils/logger.js +84 -0
  63. package/src/utils/mcp.js +85 -0
  64. package/src/utils/securityLogger.js +109 -0
  65. package/tests/auth.test.js +402 -0
  66. package/tests/authorize.test.js +208 -0
  67. package/tests/run-memory-tests.js +55 -0
  68. package/tests/run-tests.js +55 -0
  69. package/tests/server.test.js +203 -0
  70. package/tests/unit/add-ai-connections.test.js +385 -0
  71. package/tests/unit/connectionManagement.test.js +309 -0
  72. package/tests/unit/langchain-llm-format.test.js +259 -0
  73. package/tests/unit/memory-connection.test.js +140 -0
  74. package/tests/unit/memory-integration.test.js +253 -0
  75. package/tests/unit/n8nIntegration.test.js +291 -0
  76. package/tests/unit/nodeDiscovery.test.js +270 -0
  77. package/tests/unit/nodeManagement.test.js +522 -0
  78. package/tests/unit/utils/mcp-test-utils.js +94 -0
  79. package/tests/unit/workflowCreation.test.js +110 -0
  80. package/tests/unit/workflowTesting.test.js +269 -0
  81. package/tests/user.test.js +181 -0
  82. package/tsconfig.json +20 -0
  83. package/workflow_nodes/Brandfetch.json +85 -0
  84. package/workflow_nodes/WorkflowTrigger.json +28 -0
  85. package/workflow_nodes/actionNetwork.json +218 -0
  86. package/workflow_nodes/activeCampaign.json +722 -0
  87. package/workflow_nodes/activeCampaignTrigger.json +52 -0
  88. package/workflow_nodes/acuitySchedulingTrigger.json +8 -0
  89. package/workflow_nodes/adalo.json +123 -0
  90. package/workflow_nodes/affinity.json +203 -0
  91. package/workflow_nodes/affinityTrigger.json +124 -0
  92. package/workflow_nodes/aggregate.json +119 -0
  93. package/workflow_nodes/agileCrm.json +503 -0
  94. package/workflow_nodes/aiTransform.json +17 -0
  95. package/workflow_nodes/airtable.json +226 -0
  96. package/workflow_nodes/airtableTrigger.json +120 -0
  97. package/workflow_nodes/airtop.json +10 -0
  98. package/workflow_nodes/amqp.json +62 -0
  99. package/workflow_nodes/amqpTrigger.json +8 -0
  100. package/workflow_nodes/apiTemplateIo.json +147 -0
  101. package/workflow_nodes/asana.json +446 -0
  102. package/workflow_nodes/asanaTrigger.json +52 -0
  103. package/workflow_nodes/automizy.json +195 -0
  104. package/workflow_nodes/autopilot.json +287 -0
  105. package/workflow_nodes/autopilotTrigger.json +8 -0
  106. package/workflow_nodes/awsCertificateManager.json +223 -0
  107. package/workflow_nodes/awsComprehend.json +125 -0
  108. package/workflow_nodes/awsDynamoDb.json +251 -0
  109. package/workflow_nodes/awsElb.json +174 -0
  110. package/workflow_nodes/awsLambda.json +69 -0
  111. package/workflow_nodes/awsRekognition.json +191 -0
  112. package/workflow_nodes/awsS3.json +32 -0
  113. package/workflow_nodes/awsSes.json +302 -0
  114. package/workflow_nodes/awsSns.json +110 -0
  115. package/workflow_nodes/awsSnsTrigger.json +47 -0
  116. package/workflow_nodes/awsSqs.json +140 -0
  117. package/workflow_nodes/awsTextract.json +43 -0
  118. package/workflow_nodes/awsTranscribe.json +217 -0
  119. package/workflow_nodes/azureCosmosDb.json +8 -0
  120. package/workflow_nodes/azureStorage.json +17 -0
  121. package/workflow_nodes/bambooHr.json +8 -0
  122. package/workflow_nodes/bannerbear.json +126 -0
  123. package/workflow_nodes/baserow.json +277 -0
  124. package/workflow_nodes/beeminder.json +146 -0
  125. package/workflow_nodes/bitbucketTrigger.json +62 -0
  126. package/workflow_nodes/bitly.json +130 -0
  127. package/workflow_nodes/bitwarden.json +224 -0
  128. package/workflow_nodes/box.json +457 -0
  129. package/workflow_nodes/boxTrigger.json +8 -0
  130. package/workflow_nodes/brevo.json +41 -0
  131. package/workflow_nodes/brevoTrigger.json +145 -0
  132. package/workflow_nodes/bubble.json +212 -0
  133. package/workflow_nodes/calTrigger.json +91 -0
  134. package/workflow_nodes/calendlyTrigger.json +71 -0
  135. package/workflow_nodes/chargebee.json +217 -0
  136. package/workflow_nodes/chargebeeTrigger.json +187 -0
  137. package/workflow_nodes/circleCi.json +89 -0
  138. package/workflow_nodes/ciscoWebex.json +593 -0
  139. package/workflow_nodes/ciscoWebexTrigger.json +159 -0
  140. package/workflow_nodes/clearbit.json +138 -0
  141. package/workflow_nodes/clickUp.json +793 -0
  142. package/workflow_nodes/clickUpTrigger.json +188 -0
  143. package/workflow_nodes/clockify.json +372 -0
  144. package/workflow_nodes/clockifyTrigger.json +26 -0
  145. package/workflow_nodes/cloudflare.json +103 -0
  146. package/workflow_nodes/cockpit.json +161 -0
  147. package/workflow_nodes/coda.json +242 -0
  148. package/workflow_nodes/code.json +40 -0
  149. package/workflow_nodes/coinGecko.json +363 -0
  150. package/workflow_nodes/compareDatasets.json +14 -0
  151. package/workflow_nodes/compression.json +66 -0
  152. package/workflow_nodes/contentful.json +29 -0
  153. package/workflow_nodes/convertKit.json +159 -0
  154. package/workflow_nodes/convertKitTrigger.json +109 -0
  155. package/workflow_nodes/convertToFile.json +64 -0
  156. package/workflow_nodes/copper.json +239 -0
  157. package/workflow_nodes/copperTrigger.json +8 -0
  158. package/workflow_nodes/cortex.json +348 -0
  159. package/workflow_nodes/crateDb.json +90 -0
  160. package/workflow_nodes/cron.json +23 -0
  161. package/workflow_nodes/crowdDev.json +8 -0
  162. package/workflow_nodes/crowdDevTrigger.json +8 -0
  163. package/workflow_nodes/crypto.json +147 -0
  164. package/workflow_nodes/customerIo.json +206 -0
  165. package/workflow_nodes/customerIoTrigger.json +185 -0
  166. package/workflow_nodes/dateTime.json +39 -0
  167. package/workflow_nodes/debughelper.json +162 -0
  168. package/workflow_nodes/deepL.json +103 -0
  169. package/workflow_nodes/demio.json +187 -0
  170. package/workflow_nodes/dhl.json +53 -0
  171. package/workflow_nodes/discord.json +81 -0
  172. package/workflow_nodes/discourse.json +319 -0
  173. package/workflow_nodes/disqus.json +254 -0
  174. package/workflow_nodes/drift.json +112 -0
  175. package/workflow_nodes/dropbox.json +258 -0
  176. package/workflow_nodes/dropcontact.json +154 -0
  177. package/workflow_nodes/e2eTest.json +70 -0
  178. package/workflow_nodes/editImage.json +132 -0
  179. package/workflow_nodes/egoi.json +220 -0
  180. package/workflow_nodes/elasticSecurity.json +401 -0
  181. package/workflow_nodes/elasticsearch.json +422 -0
  182. package/workflow_nodes/emailReadImap.json +88 -0
  183. package/workflow_nodes/emailSend.json +38 -0
  184. package/workflow_nodes/emelia.json +201 -0
  185. package/workflow_nodes/emeliaTrigger.json +57 -0
  186. package/workflow_nodes/erpNext.json +139 -0
  187. package/workflow_nodes/errorTrigger.json +15 -0
  188. package/workflow_nodes/evaluationMetrics.json +21 -0
  189. package/workflow_nodes/eventbriteTrigger.json +125 -0
  190. package/workflow_nodes/executeCommand.json +25 -0
  191. package/workflow_nodes/executeWorkflow.json +102 -0
  192. package/workflow_nodes/executeWorkflowTrigger.json +65 -0
  193. package/workflow_nodes/executionData.json +35 -0
  194. package/workflow_nodes/extractFromFile.json +71 -0
  195. package/workflow_nodes/facebookGraphApi.json +234 -0
  196. package/workflow_nodes/facebookLeadAdsTrigger.json +8 -0
  197. package/workflow_nodes/facebookTrigger.json +112 -0
  198. package/workflow_nodes/figmaTrigger.json +8 -0
  199. package/workflow_nodes/filemaker.json +268 -0
  200. package/workflow_nodes/filter.json +24 -0
  201. package/workflow_nodes/flow.json +323 -0
  202. package/workflow_nodes/flowTrigger.json +52 -0
  203. package/workflow_nodes/form.json +25 -0
  204. package/workflow_nodes/formIoTrigger.json +59 -0
  205. package/workflow_nodes/formTrigger.json +8 -0
  206. package/workflow_nodes/formstackTrigger.json +8 -0
  207. package/workflow_nodes/freshdesk.json +584 -0
  208. package/workflow_nodes/freshservice.json +899 -0
  209. package/workflow_nodes/freshworksCrm.json +772 -0
  210. package/workflow_nodes/ftp.json +127 -0
  211. package/workflow_nodes/function.json +22 -0
  212. package/workflow_nodes/functionItem.json +22 -0
  213. package/workflow_nodes/gSuiteAdmin.json +562 -0
  214. package/workflow_nodes/getResponse.json +310 -0
  215. package/workflow_nodes/getResponseTrigger.json +82 -0
  216. package/workflow_nodes/ghost.json +290 -0
  217. package/workflow_nodes/git.json +184 -0
  218. package/workflow_nodes/github.json +732 -0
  219. package/workflow_nodes/githubTrigger.json +317 -0
  220. package/workflow_nodes/gitlab.json +544 -0
  221. package/workflow_nodes/gitlabTrigger.json +61 -0
  222. package/workflow_nodes/gmail.json +62 -0
  223. package/workflow_nodes/gmailTrigger.json +127 -0
  224. package/workflow_nodes/goToWebinar.json +430 -0
  225. package/workflow_nodes/gong.json +22 -0
  226. package/workflow_nodes/googleAds.json +116 -0
  227. package/workflow_nodes/googleAnalytics.json +28 -0
  228. package/workflow_nodes/googleBigQuery.json +38 -0
  229. package/workflow_nodes/googleBooks.json +154 -0
  230. package/workflow_nodes/googleBusinessProfile.json +277 -0
  231. package/workflow_nodes/googleBusinessProfileTrigger.json +55 -0
  232. package/workflow_nodes/googleCalendar.json +474 -0
  233. package/workflow_nodes/googleCalendarTrigger.json +72 -0
  234. package/workflow_nodes/googleChat.json +187 -0
  235. package/workflow_nodes/googleCloudNaturalLanguage.json +171 -0
  236. package/workflow_nodes/googleCloudStorage.json +466 -0
  237. package/workflow_nodes/googleContacts.json +481 -0
  238. package/workflow_nodes/googleDocs.json +312 -0
  239. package/workflow_nodes/googleDrive.json +920 -0
  240. package/workflow_nodes/googleDriveTrigger.json +181 -0
  241. package/workflow_nodes/googleFirebaseCloudFirestore.json +156 -0
  242. package/workflow_nodes/googleFirebaseRealtimeDatabase.json +75 -0
  243. package/workflow_nodes/googlePerspective.json +94 -0
  244. package/workflow_nodes/googleSheets.json +98 -0
  245. package/workflow_nodes/googleSheetsTrigger.json +192 -0
  246. package/workflow_nodes/googleSlides.json +186 -0
  247. package/workflow_nodes/googleTasks.json +198 -0
  248. package/workflow_nodes/googleTranslate.json +80 -0
  249. package/workflow_nodes/gotify.json +110 -0
  250. package/workflow_nodes/grafana.json +155 -0
  251. package/workflow_nodes/graphql.json +165 -0
  252. package/workflow_nodes/grist.json +13 -0
  253. package/workflow_nodes/gumroadTrigger.json +8 -0
  254. package/workflow_nodes/hackerNews.json +100 -0
  255. package/workflow_nodes/haloPSA.json +286 -0
  256. package/workflow_nodes/harvest.json +699 -0
  257. package/workflow_nodes/helpScout.json +629 -0
  258. package/workflow_nodes/helpScoutTrigger.json +8 -0
  259. package/workflow_nodes/highLevel.json +8 -0
  260. package/workflow_nodes/homeAssistant.json +201 -0
  261. package/workflow_nodes/html.json +118 -0
  262. package/workflow_nodes/htmlExtract.json +87 -0
  263. package/workflow_nodes/httpRequest.json +472 -0
  264. package/workflow_nodes/hubspot.json +62 -0
  265. package/workflow_nodes/hubspotTrigger.json +138 -0
  266. package/workflow_nodes/humanticAi.json +82 -0
  267. package/workflow_nodes/hunter.json +168 -0
  268. package/workflow_nodes/iCal.json +20 -0
  269. package/workflow_nodes/if.json +24 -0
  270. package/workflow_nodes/intercom.json +335 -0
  271. package/workflow_nodes/interval.json +8 -0
  272. package/workflow_nodes/invoiceNinja.json +882 -0
  273. package/workflow_nodes/invoiceNinjaTrigger.json +11 -0
  274. package/workflow_nodes/itemLists.json +313 -0
  275. package/workflow_nodes/iterable.json +168 -0
  276. package/workflow_nodes/jenkins.json +172 -0
  277. package/workflow_nodes/jira.json +529 -0
  278. package/workflow_nodes/jiraTrigger.json +308 -0
  279. package/workflow_nodes/jotFormTrigger.json +44 -0
  280. package/workflow_nodes/jwt.json +195 -0
  281. package/workflow_nodes/kafka.json +132 -0
  282. package/workflow_nodes/kafkaTrigger.json +11 -0
  283. package/workflow_nodes/keap.json +915 -0
  284. package/workflow_nodes/keapTrigger.json +37 -0
  285. package/workflow_nodes/kitemaker.json +153 -0
  286. package/workflow_nodes/koBoToolbox.json +337 -0
  287. package/workflow_nodes/koBoToolboxTrigger.json +8 -0
  288. package/workflow_nodes/langchain_Summarization Chain.json +60 -0
  289. package/workflow_nodes/langchain_agent.json +145 -0
  290. package/workflow_nodes/langchain_allowFileUploads.json +180 -0
  291. package/workflow_nodes/langchain_chainLlm.json +16 -0
  292. package/workflow_nodes/langchain_chainSummarization.json +119 -0
  293. package/workflow_nodes/langchain_code.json +62 -0
  294. package/workflow_nodes/langchain_documentBinaryInputLoader.json +8 -0
  295. package/workflow_nodes/langchain_documentDefaultDataLoader.json +8 -0
  296. package/workflow_nodes/langchain_documentGithubLoader.json +8 -0
  297. package/workflow_nodes/langchain_documentJsonInputLoader.json +8 -0
  298. package/workflow_nodes/langchain_embeddingDimensions.json +17 -0
  299. package/workflow_nodes/langchain_embeddingsAwsBedrock.json +8 -0
  300. package/workflow_nodes/langchain_embeddingsAzureOpenAi.json +151 -0
  301. package/workflow_nodes/langchain_embeddingsCohere.json +8 -0
  302. package/workflow_nodes/langchain_embeddingsGoogleGemini.json +8 -0
  303. package/workflow_nodes/langchain_embeddingsGoogleVertex.json +8 -0
  304. package/workflow_nodes/langchain_embeddingsHuggingFaceInference.json +8 -0
  305. package/workflow_nodes/langchain_embeddingsMistralCloud.json +8 -0
  306. package/workflow_nodes/langchain_embeddingsOllama.json +8 -0
  307. package/workflow_nodes/langchain_informationExtractor.json +81 -0
  308. package/workflow_nodes/langchain_lmChatAwsBedrock.json +8 -0
  309. package/workflow_nodes/langchain_lmChatAzureOpenAi.json +151 -0
  310. package/workflow_nodes/langchain_lmChatDeepSeek.json +10 -0
  311. package/workflow_nodes/langchain_lmChatGoogleGemini.json +31 -0
  312. package/workflow_nodes/langchain_lmChatGoogleVertex.json +32 -0
  313. package/workflow_nodes/langchain_lmChatGroq.json +8 -0
  314. package/workflow_nodes/langchain_lmChatMistralCloud.json +8 -0
  315. package/workflow_nodes/langchain_lmChatOllama.json +8 -0
  316. package/workflow_nodes/langchain_lmChatOpenAi.json +155 -0
  317. package/workflow_nodes/langchain_lmChatOpenRouter.json +10 -0
  318. package/workflow_nodes/langchain_lmChatXAiGrok.json +10 -0
  319. package/workflow_nodes/langchain_lmCohere.json +8 -0
  320. package/workflow_nodes/langchain_lmOllama.json +8 -0
  321. package/workflow_nodes/langchain_lmOpenAi.json +251 -0
  322. package/workflow_nodes/langchain_lmOpenHuggingFaceInference.json +8 -0
  323. package/workflow_nodes/langchain_manualChatTrigger.json +11 -0
  324. package/workflow_nodes/langchain_mcpClientTool.json +86 -0
  325. package/workflow_nodes/langchain_mcpTrigger.json +8 -0
  326. package/workflow_nodes/langchain_memoryBufferWindow.json +13 -0
  327. package/workflow_nodes/langchain_memoryChatRetriever.json +22 -0
  328. package/workflow_nodes/langchain_memoryManager.json +106 -0
  329. package/workflow_nodes/langchain_memoryMongoDbChat.json +10 -0
  330. package/workflow_nodes/langchain_memoryMotorhead.json +13 -0
  331. package/workflow_nodes/langchain_memoryPostgresChat.json +13 -0
  332. package/workflow_nodes/langchain_memoryRedisChat.json +15 -0
  333. package/workflow_nodes/langchain_memoryXata.json +14 -0
  334. package/workflow_nodes/langchain_memoryZep.json +13 -0
  335. package/workflow_nodes/langchain_model.json +155 -0
  336. package/workflow_nodes/langchain_mongoCollection.json +16 -0
  337. package/workflow_nodes/langchain_notice.json +22 -0
  338. package/workflow_nodes/langchain_openAiAssistant.json +132 -0
  339. package/workflow_nodes/langchain_options.json +17 -0
  340. package/workflow_nodes/langchain_outputParserAutofixing.json +8 -0
  341. package/workflow_nodes/langchain_outputParserItemList.json +8 -0
  342. package/workflow_nodes/langchain_outputParserStructured.json +12 -0
  343. package/workflow_nodes/langchain_pineconeNamespace.json +16 -0
  344. package/workflow_nodes/langchain_queryName.json +16 -0
  345. package/workflow_nodes/langchain_retrieverContextualCompression.json +8 -0
  346. package/workflow_nodes/langchain_retrieverMultiQuery.json +8 -0
  347. package/workflow_nodes/langchain_retrieverVectorStore.json +8 -0
  348. package/workflow_nodes/langchain_retrieverWorkflow.json +103 -0
  349. package/workflow_nodes/langchain_sentimentAnalysis.json +52 -0
  350. package/workflow_nodes/langchain_systemPromptTemplate.json +47 -0
  351. package/workflow_nodes/langchain_tableName.json +23 -0
  352. package/workflow_nodes/langchain_textClassifier.json +66 -0
  353. package/workflow_nodes/langchain_textSplitterCharacterTextSplitter.json +8 -0
  354. package/workflow_nodes/langchain_textSplitterRecursiveCharacterTextSplitter.json +8 -0
  355. package/workflow_nodes/langchain_textSplitterTokenSplitter.json +8 -0
  356. package/workflow_nodes/langchain_toolCalculator.json +8 -0
  357. package/workflow_nodes/langchain_toolCode.json +12 -0
  358. package/workflow_nodes/langchain_toolHttpRequest.json +232 -0
  359. package/workflow_nodes/langchain_toolSearXng.json +8 -0
  360. package/workflow_nodes/langchain_toolSerpApi.json +8 -0
  361. package/workflow_nodes/langchain_toolThink.json +8 -0
  362. package/workflow_nodes/langchain_toolVectorStore.json +11 -0
  363. package/workflow_nodes/langchain_toolWikipedia.json +8 -0
  364. package/workflow_nodes/langchain_toolWolframAlpha.json +8 -0
  365. package/workflow_nodes/langchain_toolWorkflow.json +8 -0
  366. package/workflow_nodes/langchain_vectorStoreInMemoryInsert.json +29 -0
  367. package/workflow_nodes/langchain_vectorStoreInMemoryLoad.json +8 -0
  368. package/workflow_nodes/langchain_vectorStorePineconeInsert.json +37 -0
  369. package/workflow_nodes/langchain_vectorStorePineconeLoad.json +8 -0
  370. package/workflow_nodes/langchain_vectorStoreSupabaseInsert.json +32 -0
  371. package/workflow_nodes/langchain_vectorStoreSupabaseLoad.json +8 -0
  372. package/workflow_nodes/langchain_vectorStoreZepInsert.json +46 -0
  373. package/workflow_nodes/langchain_vectorStoreZepLoad.json +8 -0
  374. package/workflow_nodes/ldap.json +182 -0
  375. package/workflow_nodes/lemlist.json +44 -0
  376. package/workflow_nodes/lemlistTrigger.json +45 -0
  377. package/workflow_nodes/limit.json +26 -0
  378. package/workflow_nodes/line.json +95 -0
  379. package/workflow_nodes/linear.json +151 -0
  380. package/workflow_nodes/linearTrigger.json +71 -0
  381. package/workflow_nodes/lingvaNex.json +66 -0
  382. package/workflow_nodes/linkedIn.json +142 -0
  383. package/workflow_nodes/localFileTrigger.json +120 -0
  384. package/workflow_nodes/lonescale.json +171 -0
  385. package/workflow_nodes/lonescaleTrigger.json +8 -0
  386. package/workflow_nodes/magento2.json +164 -0
  387. package/workflow_nodes/mailcheck.json +46 -0
  388. package/workflow_nodes/mailchimp.json +507 -0
  389. package/workflow_nodes/mailchimpTrigger.json +100 -0
  390. package/workflow_nodes/mailerLite.json +24 -0
  391. package/workflow_nodes/mailerLiteTrigger.json +74 -0
  392. package/workflow_nodes/mailgun.json +81 -0
  393. package/workflow_nodes/mailjet.json +201 -0
  394. package/workflow_nodes/mailjetTrigger.json +8 -0
  395. package/workflow_nodes/mandrill.json +372 -0
  396. package/workflow_nodes/manualTrigger.json +8 -0
  397. package/workflow_nodes/markdown.json +376 -0
  398. package/workflow_nodes/marketstack.json +126 -0
  399. package/workflow_nodes/matrix.json +264 -0
  400. package/workflow_nodes/mattermost.json +8 -0
  401. package/workflow_nodes/mautic.json +564 -0
  402. package/workflow_nodes/mauticTrigger.json +54 -0
  403. package/workflow_nodes/medium.json +209 -0
  404. package/workflow_nodes/merge.json +125 -0
  405. package/workflow_nodes/messageBird.json +182 -0
  406. package/workflow_nodes/metabase.json +175 -0
  407. package/workflow_nodes/microsoftDynamicsCrm.json +100 -0
  408. package/workflow_nodes/microsoftEntra.json +51 -0
  409. package/workflow_nodes/microsoftExcel.json +35 -0
  410. package/workflow_nodes/microsoftGraphSecurity.json +113 -0
  411. package/workflow_nodes/microsoftOneDrive.json +232 -0
  412. package/workflow_nodes/microsoftOneDriveTrigger.json +80 -0
  413. package/workflow_nodes/microsoftOutlook.json +40 -0
  414. package/workflow_nodes/microsoftOutlookTrigger.json +24 -0
  415. package/workflow_nodes/microsoftSql.json +81 -0
  416. package/workflow_nodes/microsoftTeams.json +36 -0
  417. package/workflow_nodes/microsoftToDo.json +181 -0
  418. package/workflow_nodes/mindee.json +86 -0
  419. package/workflow_nodes/misp.json +399 -0
  420. package/workflow_nodes/mocean.json +103 -0
  421. package/workflow_nodes/mondayCom.json +290 -0
  422. package/workflow_nodes/mongoDb.json +16 -0
  423. package/workflow_nodes/monicaCrm.json +543 -0
  424. package/workflow_nodes/moveBinaryData.json +121 -0
  425. package/workflow_nodes/mqtt.json +67 -0
  426. package/workflow_nodes/mqttTrigger.json +47 -0
  427. package/workflow_nodes/msg91.json +65 -0
  428. package/workflow_nodes/mySql.json +111 -0
  429. package/workflow_nodes/n8n.json +75 -0
  430. package/workflow_nodes/n8nTrigger.json +27 -0
  431. package/workflow_nodes/nasa.json +310 -0
  432. package/workflow_nodes/netlify.json +87 -0
  433. package/workflow_nodes/netlifyTrigger.json +68 -0
  434. package/workflow_nodes/netscalerAdc.json +243 -0
  435. package/workflow_nodes/nextCloud.json +312 -0
  436. package/workflow_nodes/noOp.json +8 -0
  437. package/workflow_nodes/nocoDb.json +276 -0
  438. package/workflow_nodes/notion.json +8 -0
  439. package/workflow_nodes/notionTrigger.json +75 -0
  440. package/workflow_nodes/npm.json +64 -0
  441. package/workflow_nodes/odoo.json +344 -0
  442. package/workflow_nodes/okta.json +97 -0
  443. package/workflow_nodes/oneSimpleApi.json +281 -0
  444. package/workflow_nodes/onfleet.json +316 -0
  445. package/workflow_nodes/onfleetTrigger.json +8 -0
  446. package/workflow_nodes/openAi.json +154 -0
  447. package/workflow_nodes/openThesaurus.json +81 -0
  448. package/workflow_nodes/openWeatherMap.json +129 -0
  449. package/workflow_nodes/orbit.json +375 -0
  450. package/workflow_nodes/oura.json +74 -0
  451. package/workflow_nodes/paddle.json +403 -0
  452. package/workflow_nodes/pagerDuty.json +351 -0
  453. package/workflow_nodes/payPal.json +196 -0
  454. package/workflow_nodes/payPalTrigger.json +40 -0
  455. package/workflow_nodes/peekalink.json +41 -0
  456. package/workflow_nodes/phantombuster.json +172 -0
  457. package/workflow_nodes/philipsHue.json +177 -0
  458. package/workflow_nodes/pipedrive.json +860 -0
  459. package/workflow_nodes/pipedriveTrigger.json +11 -0
  460. package/workflow_nodes/plivo.json +91 -0
  461. package/workflow_nodes/postHog.json +122 -0
  462. package/workflow_nodes/postbin.json +60 -0
  463. package/workflow_nodes/postgres.json +109 -0
  464. package/workflow_nodes/postgresTrigger.json +8 -0
  465. package/workflow_nodes/postmarkTrigger.json +72 -0
  466. package/workflow_nodes/profitWell.json +305 -0
  467. package/workflow_nodes/pushbullet.json +186 -0
  468. package/workflow_nodes/pushcut.json +75 -0
  469. package/workflow_nodes/pushcutTrigger.json +8 -0
  470. package/workflow_nodes/pushover.json +159 -0
  471. package/workflow_nodes/questDb.json +94 -0
  472. package/workflow_nodes/quickChart.json +188 -0
  473. package/workflow_nodes/quickbase.json +205 -0
  474. package/workflow_nodes/quickbooks.json +550 -0
  475. package/workflow_nodes/rabbitmq.json +165 -0
  476. package/workflow_nodes/rabbitmqTrigger.json +8 -0
  477. package/workflow_nodes/raindrop.json +216 -0
  478. package/workflow_nodes/readBinaryFile.json +26 -0
  479. package/workflow_nodes/readBinaryFiles.json +26 -0
  480. package/workflow_nodes/readPDF.json +31 -0
  481. package/workflow_nodes/readWriteFile.json +27 -0
  482. package/workflow_nodes/reddit.json +309 -0
  483. package/workflow_nodes/redis.json +183 -0
  484. package/workflow_nodes/redisTrigger.json +8 -0
  485. package/workflow_nodes/removeDuplicates.json +8 -0
  486. package/workflow_nodes/renameKeys.json +67 -0
  487. package/workflow_nodes/respondToWebhook.json +126 -0
  488. package/workflow_nodes/rocketchat.json +216 -0
  489. package/workflow_nodes/rssFeedRead.json +28 -0
  490. package/workflow_nodes/rssFeedReadTrigger.json +17 -0
  491. package/workflow_nodes/rundeck.json +79 -0
  492. package/workflow_nodes/s3.json +425 -0
  493. package/workflow_nodes/salesforce.json +1137 -0
  494. package/workflow_nodes/salesforceTrigger.json +122 -0
  495. package/workflow_nodes/salesmate.json +467 -0
  496. package/workflow_nodes/scheduleTrigger.json +270 -0
  497. package/workflow_nodes/seaTable.json +8 -0
  498. package/workflow_nodes/seaTableTrigger.json +87 -0
  499. package/workflow_nodes/securityScorecard.json +459 -0
  500. package/workflow_nodes/segment.json +219 -0
  501. package/workflow_nodes/sendGrid.json +359 -0
  502. package/workflow_nodes/sendy.json +225 -0
  503. package/workflow_nodes/sentryIo.json +426 -0
  504. package/workflow_nodes/serviceNow.json +544 -0
  505. package/workflow_nodes/set.json +124 -0
  506. package/workflow_nodes/shopify.json +707 -0
  507. package/workflow_nodes/shopifyTrigger.json +8 -0
  508. package/workflow_nodes/signl4.json +133 -0
  509. package/workflow_nodes/simulate.json +30 -0
  510. package/workflow_nodes/simulateTrigger.json +8 -0
  511. package/workflow_nodes/slack.json +62 -0
  512. package/workflow_nodes/slackTrigger.json +135 -0
  513. package/workflow_nodes/sms77.json +121 -0
  514. package/workflow_nodes/snowflake.json +65 -0
  515. package/workflow_nodes/sort.json +57 -0
  516. package/workflow_nodes/splitInBatches.json +30 -0
  517. package/workflow_nodes/splitOut.json +62 -0
  518. package/workflow_nodes/splunk.json +40 -0
  519. package/workflow_nodes/spontit.json +123 -0
  520. package/workflow_nodes/spotify.json +285 -0
  521. package/workflow_nodes/spreadsheetFile.json +8 -0
  522. package/workflow_nodes/sseTrigger.json +8 -0
  523. package/workflow_nodes/ssh.json +105 -0
  524. package/workflow_nodes/stackby.json +85 -0
  525. package/workflow_nodes/start.json +15 -0
  526. package/workflow_nodes/stickyNote.json +36 -0
  527. package/workflow_nodes/stopAndError.json +8 -0
  528. package/workflow_nodes/storyblok.json +138 -0
  529. package/workflow_nodes/strapi.json +138 -0
  530. package/workflow_nodes/strava.json +427 -0
  531. package/workflow_nodes/stravaTrigger.json +79 -0
  532. package/workflow_nodes/stripe.json +357 -0
  533. package/workflow_nodes/stripeTrigger.json +775 -0
  534. package/workflow_nodes/summarize.json +124 -0
  535. package/workflow_nodes/supabase.json +136 -0
  536. package/workflow_nodes/surveyMonkeyTrigger.json +160 -0
  537. package/workflow_nodes/switch.json +91 -0
  538. package/workflow_nodes/syncroMsp.json +8 -0
  539. package/workflow_nodes/taiga.json +340 -0
  540. package/workflow_nodes/taigaTrigger.json +81 -0
  541. package/workflow_nodes/tapfiliate.json +241 -0
  542. package/workflow_nodes/telegram.json +612 -0
  543. package/workflow_nodes/telegramTrigger.json +142 -0
  544. package/workflow_nodes/theHive.json +497 -0
  545. package/workflow_nodes/theHiveProject.json +8 -0
  546. package/workflow_nodes/theHiveProjectTrigger.json +162 -0
  547. package/workflow_nodes/theHiveTrigger.json +101 -0
  548. package/workflow_nodes/timescaleDb.json +95 -0
  549. package/workflow_nodes/todoist.json +285 -0
  550. package/workflow_nodes/togglTrigger.json +24 -0
  551. package/workflow_nodes/totp.json +86 -0
  552. package/workflow_nodes/travisCi.json +142 -0
  553. package/workflow_nodes/trello.json +609 -0
  554. package/workflow_nodes/trelloTrigger.json +8 -0
  555. package/workflow_nodes/twake.json +76 -0
  556. package/workflow_nodes/twilio.json +95 -0
  557. package/workflow_nodes/twilioTrigger.json +46 -0
  558. package/workflow_nodes/twist.json +376 -0
  559. package/workflow_nodes/twitter.json +40 -0
  560. package/workflow_nodes/typeformTrigger.json +62 -0
  561. package/workflow_nodes/unleashedSoftware.json +154 -0
  562. package/workflow_nodes/uplead.json +72 -0
  563. package/workflow_nodes/uproc.json +26 -0
  564. package/workflow_nodes/uptimeRobot.json +453 -0
  565. package/workflow_nodes/urlScanIo.json +113 -0
  566. package/workflow_nodes/venafiTlsProtectCloud.json +310 -0
  567. package/workflow_nodes/venafiTlsProtectCloudTrigger.json +38 -0
  568. package/workflow_nodes/venafiTlsProtectDatacenter.json +491 -0
  569. package/workflow_nodes/vero.json +158 -0
  570. package/workflow_nodes/vonage.json +125 -0
  571. package/workflow_nodes/wait.json +71 -0
  572. package/workflow_nodes/webflow.json +38 -0
  573. package/workflow_nodes/webflowTrigger.json +8 -0
  574. package/workflow_nodes/webhook.json +55 -0
  575. package/workflow_nodes/wekan.json +460 -0
  576. package/workflow_nodes/whatsApp.json +476 -0
  577. package/workflow_nodes/whatsAppTrigger.json +103 -0
  578. package/workflow_nodes/wise.json +330 -0
  579. package/workflow_nodes/wiseTrigger.json +8 -0
  580. package/workflow_nodes/wooCommerce.json +812 -0
  581. package/workflow_nodes/wooCommerceTrigger.json +8 -0
  582. package/workflow_nodes/wordpress.json +500 -0
  583. package/workflow_nodes/workableTrigger.json +51 -0
  584. package/workflow_nodes/writeBinaryFile.json +34 -0
  585. package/workflow_nodes/wufooTrigger.json +37 -0
  586. package/workflow_nodes/xero.json +530 -0
  587. package/workflow_nodes/xml.json +129 -0
  588. package/workflow_nodes/youTube.json +578 -0
  589. package/workflow_nodes/yourls.json +71 -0
  590. package/workflow_nodes/zammad.json +406 -0
  591. package/workflow_nodes/zendesk.json +526 -0
  592. package/workflow_nodes/zendeskTrigger.json +187 -0
  593. package/workflow_nodes/zohoCrm.json +721 -0
  594. package/workflow_nodes/zoom.json +507 -0
  595. package/workflow_nodes/zulip.json +371 -0
package/src/index.ts ADDED
@@ -0,0 +1,1440 @@
1
+ #!/usr/bin/env node
2
+
3
+ // N8N Workflow Builder MCP Server
4
+ // Using the official MCP SDK as required
5
+
6
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
8
+ import { z } from 'zod';
9
+ import fs from 'fs/promises';
10
+ import path from 'path';
11
+
12
+ // Define types for workflow and node structures
13
+ // Old Workflow interface (kept for reference or other tools not yet updated)
14
+ interface WorkflowNode {
15
+ id: string;
16
+ type: string;
17
+ position: { x: number; y: number };
18
+ parameters: Record<string, any>;
19
+ }
20
+
21
+ interface WorkflowConnection {
22
+ sourceNodeId: string;
23
+ targetNodeId: string;
24
+ }
25
+
26
+ interface Workflow {
27
+ id: string;
28
+ name: string;
29
+ description: string;
30
+ created: string;
31
+ nodes: WorkflowNode[];
32
+ connections: WorkflowConnection[];
33
+ }
34
+
35
+ // New N8N specific interfaces
36
+ interface N8nWorkflowNode {
37
+ parameters: Record<string, any>;
38
+ type: string;
39
+ typeVersion: number;
40
+ position: [number, number]; // [x, y]
41
+ id: string; // UUID
42
+ name: string;
43
+ webhookId?: string; // Added for nodes like ChatTrigger
44
+ }
45
+
46
+ interface N8nConnectionDetail {
47
+ node: string; // Name of the target node
48
+ type: string; // e.g., "main"
49
+ index: number;
50
+ }
51
+
52
+ interface N8nConnections {
53
+ [sourceNodeName: string]: {
54
+ [outputType: string]: N8nConnectionDetail[][];
55
+ };
56
+ }
57
+
58
+ interface N8nWorkflowSettings {
59
+ executionOrder: "v1";
60
+ // Future settings can be added here
61
+ }
62
+
63
+ interface N8nWorkflowMeta {
64
+ instanceId: string;
65
+ // Future meta fields can be added here
66
+ }
67
+
68
+ interface N8nWorkflow {
69
+ name: string;
70
+ nodes: N8nWorkflowNode[];
71
+ pinData: Record<string, any>;
72
+ connections: N8nConnections;
73
+ active: boolean;
74
+ settings: N8nWorkflowSettings;
75
+ versionId: string; // UUID
76
+ meta?: N8nWorkflowMeta;
77
+ id: string; // Short, unique ID (e.g., "Y6sBMxxyJQtgCCBQ")
78
+ tags: string[];
79
+ }
80
+
81
+ // Global workspace configuration
82
+ let WORKSPACE_DIR: string = process.cwd();
83
+ console.error(`[DEBUG] Default workspace directory: ${WORKSPACE_DIR}`);
84
+
85
+ // Store for known node type casings (lowercase -> CorrectCase)
86
+ // let knownNodeBaseCasings: Map<string, string> = new Map(); // OLD MAP
87
+
88
+ interface CachedNodeInfo {
89
+ officialType: string; // The correctly cased, full type string (e.g., "n8n-nodes-base.HttpRequest" or "@n8n/n8n-nodes-langchain.allowFileUploads")
90
+ version: number | number[]; // The version information from the node's definition file
91
+ }
92
+ let nodeInfoCache: Map<string, CachedNodeInfo> = new Map();
93
+
94
+ // Helper function to normalize LLM parameters from various possible inputs
95
+ function normalizeLLMParameters(params: Record<string, any>): Record<string, any> {
96
+ const normalized = { ...params };
97
+
98
+ // Handle model/modelName variation
99
+ if (normalized.modelName && !normalized.model) {
100
+ console.error(`[DEBUG] Normalizing 'modelName' to 'model'`);
101
+ normalized.model = normalized.modelName;
102
+ delete normalized.modelName;
103
+ }
104
+
105
+ // Convert model string to required format
106
+ if (normalized.model && typeof normalized.model === 'string') {
107
+ console.error(`[DEBUG] Converting model string to required format: ${normalized.model}`);
108
+ const modelValue = normalized.model;
109
+ normalized.model = {
110
+ "__rl": true,
111
+ "value": modelValue,
112
+ "mode": "list",
113
+ "cachedResultName": modelValue
114
+ };
115
+ }
116
+
117
+ // Handle credentials formatting
118
+ // Handle at options level
119
+ if (normalized.options?.credentials?.providerType) {
120
+ const credType = normalized.options.credentials.providerType;
121
+ console.error(`[DEBUG] Found credentials in options with type: ${credType}`);
122
+ delete normalized.options.credentials;
123
+
124
+ // Set credentials properly based on provider type
125
+ if (credType === 'openAi' || credType === 'openAiApi') {
126
+ normalized.credentials = {
127
+ "openAiApi": {
128
+ "id": generateN8nId(),
129
+ "name": "OpenAi account"
130
+ }
131
+ };
132
+ }
133
+ }
134
+
135
+ // Handle at root level
136
+ if (normalized.credentialsType && !normalized.credentials) {
137
+ const credType = normalized.credentialsType;
138
+ console.error(`[DEBUG] Found credentialsType at root level: ${credType}`);
139
+
140
+ // Set credentials properly based on provider type
141
+ if (credType === 'openAi' || credType === 'openAiApi') {
142
+ normalized.credentials = {
143
+ "openAiApi": {
144
+ "id": generateN8nId(),
145
+ "name": "OpenAi account"
146
+ }
147
+ };
148
+ }
149
+
150
+ // Remove root level parameter
151
+ delete normalized.credentialsType;
152
+ }
153
+
154
+ return normalized;
155
+ }
156
+
157
+ async function loadKnownNodeBaseTypes(): Promise<void> {
158
+ // Corrected path: relative to this script's location, assuming workflow_nodes is at project root
159
+ const workflowNodesDir = path.resolve(__dirname, '../workflow_nodes');
160
+ try {
161
+ console.error(`[DEBUG] Attempting to load known node types from server resource path: ${workflowNodesDir}`);
162
+ const files = await fs.readdir(workflowNodesDir);
163
+ const suffix = ".json";
164
+ // knownNodeBaseCasings.clear(); // Clear any previous entries // OLD MAP
165
+ nodeInfoCache.clear();
166
+
167
+ for (const file of files) {
168
+ if (file.endsWith(suffix)) {
169
+ try {
170
+ // Extract base type from filename like "MyNode.json" -> "MyNode"
171
+ // const correctCaseBaseType = file.substring(0, file.length - suffix.length); // Not directly used for cache keys anymore
172
+
173
+ // Also read the JSON file to get the nodeType property with correct casing
174
+ const filePath = path.join(workflowNodesDir, file);
175
+ const fileContent = await fs.readFile(filePath, 'utf8');
176
+ const nodeDefinition = JSON.parse(fileContent);
177
+
178
+ if (nodeDefinition.nodeType) {
179
+ const officialType = nodeDefinition.nodeType;
180
+ const version = nodeDefinition.version || 1; // Default to 1 if version is missing/falsy
181
+
182
+ // Map by lowercase full official type
183
+ nodeInfoCache.set(officialType.toLowerCase(), { officialType, version });
184
+ console.error(`[DEBUG] Cached node info for key '${officialType.toLowerCase()}': { officialType: '${officialType}', version: ${JSON.stringify(version)} }`);
185
+
186
+ // If it's a prefixed type (n8n-nodes-base), also map by its lowercase base name
187
+ const prefix = "n8n-nodes-base.";
188
+ if (officialType.startsWith(prefix)) {
189
+ const baseName = officialType.substring(prefix.length);
190
+ if (baseName) { // Ensure baseName is not empty
191
+ nodeInfoCache.set(baseName.toLowerCase(), { officialType, version });
192
+ console.error(`[DEBUG] Cached node info for base key '${baseName.toLowerCase()}': { officialType: '${officialType}', version: ${JSON.stringify(version)} }`);
193
+ }
194
+ }
195
+ }
196
+ } catch (parseError) {
197
+ console.warn(`[WARN] Error parsing node definition in ${file}:`, parseError);
198
+ // Continue with other files if one fails to parse
199
+ }
200
+ }
201
+ }
202
+
203
+ console.error(`[DEBUG] Loaded ${nodeInfoCache.size} cache entries for node types.`);
204
+ if (nodeInfoCache.size === 0) {
205
+ console.warn("[WARN] No node type information loaded into cache. Check 'workflow_nodes' directory and naming convention.");
206
+ }
207
+ } catch (error: any) {
208
+ console.warn(`[WARN] Could not load known node types from ${workflowNodesDir}: ${error.message}. Node type normalization might rely on defaults.`);
209
+ nodeInfoCache = new Map(); // Ensure map is empty if loading fails
210
+ }
211
+ }
212
+
213
+ // Helper function to normalize node types (OLD - to be replaced)
214
+ // function normalizeNodeType(inputType: string): string { ... } // OLD FUNCTION
215
+
216
+ // New function to get normalized type and version
217
+ function normalizeNodeTypeAndVersion(inputType: string, inputVersion?: number): { finalNodeType: string; finalTypeVersion: number } {
218
+ if (nodeInfoCache.size === 0 && WORKSPACE_DIR !== process.cwd()) { // Check if cache is empty and workspace might have changed
219
+ console.warn("[WARN] nodeInfoCache is empty in normalizeNodeTypeAndVersion. Attempting to reload based on current WORKSPACE_DIR.");
220
+ // This reload should be awaited if called from an async context.
221
+ // For now, assuming loadKnownNodeBaseTypes was called at startup or by a previous async tool.
222
+ // Synchronous reload attempt (not ideal but matches previous knownNodeBaseCasings logic):
223
+ // await loadKnownNodeBaseTypes(); // Making this async would require normalizeNodeTypeAndVersion to be async too.
224
+ // For now, we proceed, and if cache is empty, warnings will be issued.
225
+ }
226
+
227
+ const lowerInputType = inputType.toLowerCase();
228
+ const prefix = "n8n-nodes-base.";
229
+ const cacheEntry = nodeInfoCache.get(lowerInputType);
230
+
231
+ let finalNodeType: string;
232
+ let versionSource: number | number[] = 1; // Default version if not found in cache
233
+
234
+ if (cacheEntry) {
235
+ finalNodeType = cacheEntry.officialType; // This is the correctly cased, full type name
236
+ versionSource = cacheEntry.version;
237
+ } else {
238
+ // Not in cache. Determine type based on structure.
239
+ if (inputType.includes('/') && !lowerInputType.startsWith(prefix)) {
240
+ // Likely a namespaced type not in cache (e.g. user typed it, or it's new)
241
+ finalNodeType = inputType; // Use user's casing
242
+ console.warn(`[WARN] Namespaced node type ${inputType} not in cache. Using as-is with default version.`);
243
+ } else {
244
+ // Assumed to be a base type needing a prefix, or a prefixed type not in cache.
245
+ // Use inputType for casing if it already seems prefixed, otherwise prefix the original inputType
246
+ finalNodeType = lowerInputType.startsWith(prefix) ? inputType : `${prefix}${inputType}`;
247
+ console.warn(`[WARN] Node type ${inputType} (assumed base/prefixed) not in cache. Result: ${finalNodeType} with default version.`);
248
+ }
249
+ // versionSource remains 1 (default)
250
+ }
251
+
252
+ let finalTypeVersion: number;
253
+ if (inputVersion !== undefined && !isNaN(Number(inputVersion))) {
254
+ finalTypeVersion = Number(inputVersion);
255
+ } else { // inputVersion was not provided or was NaN, use versionSource
256
+ if (inputVersion !== undefined && isNaN(Number(inputVersion))) {
257
+ console.warn(`[WARN] Provided inputVersion '${inputVersion}' is NaN for node ${finalNodeType}. Determining from cache/default.`);
258
+ }
259
+ if (Array.isArray(versionSource)) {
260
+ if (versionSource.length > 0) {
261
+ const numericVersions = versionSource.map(v => Number(v)).filter(v => !isNaN(v));
262
+ finalTypeVersion = numericVersions.length > 0 ? Math.max(...numericVersions) : 1;
263
+ } else {
264
+ finalTypeVersion = 1; // Empty array in cache
265
+ }
266
+ } else { // It's a number or was defaulted to 1
267
+ finalTypeVersion = Number(versionSource);
268
+ }
269
+ if (isNaN(finalTypeVersion)) finalTypeVersion = 1; // Fallback for bad data from cache
270
+ }
271
+ if (isNaN(finalTypeVersion)) finalTypeVersion = 1; // Final check, e.g. if inputVersion was NaN and versionSource also led to NaN.
272
+
273
+ console.error(`[DEBUG] normalizeNodeTypeAndVersion: input='${inputType}', inputVersion=${inputVersion} -> finalNodeType='${finalNodeType}', finalTypeVersion=${finalTypeVersion}`);
274
+ return { finalNodeType, finalTypeVersion };
275
+ }
276
+
277
+ // Helper function to resolve paths against workspace
278
+ // Always treat paths as relative to WORKSPACE_DIR by stripping leading slashes
279
+ function resolvePath(filepath: string): string {
280
+ // Remove any leading path separators to prevent absolute path resolution
281
+ const relativePath = filepath.replace(/^[\\/]+/, '');
282
+ return path.join(WORKSPACE_DIR, relativePath);
283
+ }
284
+
285
+ // ID Generation Helpers
286
+ function generateN8nId(length: number = 16): string {
287
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
288
+ let result = '';
289
+ for (let i = 0; i < length; i++) {
290
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
291
+ }
292
+ return result;
293
+ }
294
+
295
+ function generateUUID(): string {
296
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
297
+ const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
298
+ return v.toString(16);
299
+ });
300
+ }
301
+
302
+ function generateInstanceId(length: number = 64): string {
303
+ const chars = 'abcdef0123456789';
304
+ let result = '';
305
+ for (let i = 0; i < length; i++) {
306
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
307
+ }
308
+ return result;
309
+ }
310
+
311
+ // Constants
312
+ const WORKFLOW_DATA_DIR_NAME = 'workflow_data';
313
+ const WORKFLOWS_FILE_NAME = 'workflows.json'; // Kept for now, but create_workflow won't use it.
314
+
315
+ // Helper functions
316
+ async function ensureWorkflowDir(): Promise<void> {
317
+ try {
318
+ const resolvedDir = resolvePath(WORKFLOW_DATA_DIR_NAME);
319
+ console.error("[DEBUG] Ensuring workflow directory at:", resolvedDir);
320
+ await fs.mkdir(resolvedDir, { recursive: true });
321
+ // Removed creation of workflows.json as each workflow is a separate file now.
322
+ } catch (error) {
323
+ console.error('[ERROR] Failed to ensure workflow directory:', error);
324
+ throw error;
325
+ }
326
+ }
327
+
328
+ async function loadWorkflows(): Promise<Workflow[]> {
329
+ // This function will need to be updated if list_workflows is to work with the new format.
330
+ // For now, it's related to the old format.
331
+ const resolvedFile = resolvePath(path.join(WORKFLOW_DATA_DIR_NAME, WORKFLOWS_FILE_NAME));
332
+ try {
333
+ await ensureWorkflowDir(); // Ensures dir exists, doesn't create workflows.json anymore unless called by old logic
334
+ const data = await fs.readFile(resolvedFile, 'utf8');
335
+ console.error("[DEBUG] Loaded workflows (old format):", data);
336
+ return JSON.parse(data) as Workflow[];
337
+ } catch (error: any) {
338
+ if (error.code === 'ENOENT') {
339
+ console.error("[DEBUG] No workflows.json file found (old format), returning empty array");
340
+ // If workflows.json is truly deprecated, this might try to create it.
341
+ // For now, let's assume ensureWorkflowDir handles directory creation.
342
+ // And if the file doesn't exist, it means no workflows (in old format).
343
+ await fs.writeFile(resolvedFile, JSON.stringify([], null, 2)); // Create if not exists for old logic
344
+ return [];
345
+ }
346
+ console.error('[ERROR] Failed to load workflows (old format):', error);
347
+ throw error;
348
+ }
349
+ }
350
+
351
+ async function saveWorkflows(workflows: Workflow[]): Promise<void> {
352
+ // This function is for the old format (saving an array to workflows.json).
353
+ try {
354
+ await ensureWorkflowDir();
355
+ const resolvedFile = resolvePath(path.join(WORKFLOW_DATA_DIR_NAME, WORKFLOWS_FILE_NAME));
356
+ console.error("[DEBUG] Saving workflows (old format):", JSON.stringify(workflows, null, 2));
357
+ await fs.writeFile(resolvedFile, JSON.stringify(workflows, null, 2));
358
+ } catch (error) {
359
+ console.error('[ERROR] Failed to save workflows (old format):', error);
360
+ throw error;
361
+ }
362
+ }
363
+
364
+ // Create the MCP server
365
+ const server = new McpServer({
366
+ name: "n8n-workflow-builder",
367
+ version: "1.0.0"
368
+ });
369
+
370
+ // Tool definitions
371
+
372
+ // Create Workflow
373
+ const createWorkflowParamsSchema = z.object({
374
+ workflow_name: z.string().describe("The name for the new workflow"),
375
+ workspace_dir: z.string().describe("Absolute path to the project root directory where workflow_data will be stored")
376
+ });
377
+ server.tool(
378
+ "create_workflow",
379
+ createWorkflowParamsSchema.shape,
380
+ async (params: z.infer<typeof createWorkflowParamsSchema>, _extra) => {
381
+ console.error("[DEBUG] create_workflow called with params:", params);
382
+ const workflowName = params.workflow_name;
383
+ const workspaceDir = params.workspace_dir;
384
+
385
+ if (!workflowName || workflowName.trim() === "") {
386
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: "Parameter 'workflow_name' is required." }) }] };
387
+ }
388
+ if (!workspaceDir || workspaceDir.trim() === "") {
389
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: "Parameter 'workspace_dir' is required." }) }] };
390
+ }
391
+
392
+ try {
393
+ const stat = await fs.stat(workspaceDir);
394
+ if (!stat.isDirectory()) {
395
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: "Provided 'workspace_dir' is not a directory." }) }] };
396
+ }
397
+
398
+ // Check if the workspaceDir is the root directory
399
+ if (path.resolve(workspaceDir) === path.resolve('/')) {
400
+ console.error("[ERROR] 'workspace_dir' cannot be the root directory ('/'). Please specify a valid project subdirectory.");
401
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: "'workspace_dir' cannot be the root directory. Please specify a project subdirectory." }) }] };
402
+ }
403
+
404
+ WORKSPACE_DIR = workspaceDir; // Set current workspace for resolvePath
405
+ await ensureWorkflowDir(); // Ensures WORKFLOW_DATA_DIR_NAME exists
406
+
407
+ const newN8nWorkflow: N8nWorkflow = {
408
+ name: workflowName,
409
+ id: generateN8nId(), // e.g., "Y6sBMxxyJQtgCCBQ"
410
+ nodes: [], // Initialize with empty nodes array
411
+ connections: {}, // Initialize with empty connections object
412
+ active: false,
413
+ pinData: {},
414
+ settings: {
415
+ executionOrder: "v1"
416
+ },
417
+ versionId: generateUUID(),
418
+ meta: {
419
+ instanceId: generateInstanceId()
420
+ },
421
+ tags: []
422
+ };
423
+
424
+ // Sanitize workflowName for filename or ensure it's safe.
425
+ // For now, using directly. Consider a sanitization function for production.
426
+ const filename = `${workflowName.replace(/[^a-z0-9_.-]/gi, '_')}.json`;
427
+ const filePath = resolvePath(path.join(WORKFLOW_DATA_DIR_NAME, filename));
428
+
429
+ await fs.writeFile(filePath, JSON.stringify(newN8nWorkflow, null, 2));
430
+ console.error("[DEBUG] Workflow created and saved to:", filePath);
431
+ return { content: [{ type: "text", text: JSON.stringify({ success: true, workflow: newN8nWorkflow, recommended_next_step: "YOU NEED TO CALL 'list_available_nodes' TOOL BEFORE starting adding nodes. SEARCH BY SPECIFIC TOPIC USING 'search_term' parameter'. To search AI nodes you can use 'langchain' as the search term and 'ai" }) }] };
432
+
433
+ } catch (error: any) {
434
+ console.error("[ERROR] Failed to create workflow:", error);
435
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: "Failed to create workflow: " + error.message }) }] };
436
+ }
437
+ }
438
+ );
439
+
440
+ // List Workflows
441
+ // NOTE: This tool will need to be updated to read individual .json files
442
+ // from the workflow_data directory and parse them into N8nWorkflow[]
443
+ server.tool(
444
+ "list_workflows",
445
+ {},
446
+ async (_params: Record<string, never>, _extra) => {
447
+ console.error("[DEBUG] list_workflows called - (current impl uses old format and might be broken)");
448
+ try {
449
+ // This implementation needs to change to scan directory for .json files
450
+ // and aggregate them. For now, it will likely fail or return empty
451
+ // if workflows.json doesn't exist or is empty.
452
+ await ensureWorkflowDir(); // Ensures directory exists
453
+ const workflowDataDir = resolvePath(WORKFLOW_DATA_DIR_NAME);
454
+ const files = await fs.readdir(workflowDataDir);
455
+ const workflowFiles = files.filter(file => file.endsWith('.json') && file !== WORKFLOWS_FILE_NAME);
456
+
457
+ const workflows: N8nWorkflow[] = [];
458
+ for (const file of workflowFiles) {
459
+ try {
460
+ const data = await fs.readFile(path.join(workflowDataDir, file), 'utf8');
461
+ workflows.push(JSON.parse(data) as N8nWorkflow);
462
+ } catch (err) {
463
+ console.error(`[ERROR] Failed to read or parse workflow file ${file}:`, err);
464
+ // Decide how to handle: skip, error out, etc.
465
+ }
466
+ }
467
+ console.error(`[DEBUG] Retrieved ${workflows.length} workflows from individual files.`);
468
+ return { content: [{ type: "text", text: JSON.stringify({ success: true, workflows }) }] };
469
+ } catch (error: any) {
470
+ console.error("[ERROR] Failed to list workflows:", error);
471
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: "Failed to list workflows: " + error.message }) }] };
472
+ }
473
+ }
474
+ );
475
+
476
+ // Get Workflow Details
477
+ // NOTE: This tool will need to be updated. It currently assumes workflow_id is
478
+ // an ID found in the old workflows.json structure. It should now probably
479
+ // expect workflow_id to be the workflow name (to form the filename) or the new N8n ID.
480
+ const getWorkflowDetailsParamsSchema = z.object({
481
+ workflow_name: z.string().describe("The Name of the workflow to get details for")
482
+ });
483
+ server.tool(
484
+ "get_workflow_details",
485
+ getWorkflowDetailsParamsSchema.shape,
486
+ async (params: z.infer<typeof getWorkflowDetailsParamsSchema>, _extra) => {
487
+ const workflowName = params.workflow_name;
488
+ console.error("[DEBUG] get_workflow_details called with name:", workflowName);
489
+ try {
490
+ await ensureWorkflowDir();
491
+ const sanitizedName = workflowName.replace(/[^a-z0-9_.-]/gi, '_');
492
+ const filePath = resolvePath(path.join(WORKFLOW_DATA_DIR_NAME, `${sanitizedName}.json`));
493
+
494
+ try {
495
+ const data = await fs.readFile(filePath, 'utf8');
496
+ const workflow = JSON.parse(data) as N8nWorkflow;
497
+ console.error("[DEBUG] Found workflow by name in file:", filePath);
498
+ return { content: [{ type: "text", text: JSON.stringify({ success: true, workflow }) }] };
499
+ } catch (error: any) {
500
+ if (error.code === 'ENOENT') {
501
+ console.warn(`[DEBUG] Workflow file ${filePath} not found using name: ${workflowName}.`);
502
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Workflow with name ${workflowName} not found` }) }] };
503
+ } else {
504
+ throw error; // Re-throw other read errors
505
+ }
506
+ }
507
+ } catch (error: any) {
508
+ console.error("[ERROR] Failed to get workflow details:", error);
509
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: "Failed to get workflow details: " + error.message }) }] };
510
+ }
511
+ }
512
+ );
513
+
514
+ // Add Node
515
+ // NOTE: This tool will need significant updates to load the specific workflow file,
516
+ // add the node to its 'nodes' array, and save the file.
517
+ const addNodeParamsSchema = z.object({
518
+ workflow_name: z.string().describe("The Name of the workflow to add the node to"),
519
+ node_type: z.string().describe("The type of node to add (e.g., 'gmail', 'slack', 'openAi'). You can specify with or without the 'n8n-nodes-base.' prefix. The system will handle proper casing (e.g., 'openai' will be converted to 'openAi' if that's the correct casing)."),
520
+ position: z.object({
521
+ x: z.number(),
522
+ y: z.number()
523
+ }).optional().describe("The position of the node {x,y} - will be converted to [x,y] for N8nWorkflowNode"),
524
+ parameters: z.record(z.string(), z.any()).optional().describe("The parameters for the node"),
525
+ node_name: z.string().optional().describe("The name for the new node (e.g., 'My Gmail Node')"),
526
+ typeVersion: z.number().optional().describe("The type version for the node (e.g., 1, 1.1). Defaults to 1 if not specified."),
527
+ webhookId: z.string().optional().describe("Optional webhook ID for certain node types like triggers.")
528
+ });
529
+ server.tool(
530
+ "add_node",
531
+ addNodeParamsSchema.shape,
532
+ async (params: z.infer<typeof addNodeParamsSchema>, _extra) => {
533
+ console.error("[DEBUG] add_node called with:", params);
534
+ const workflowName = params.workflow_name;
535
+ try {
536
+ // Attempt to reload node types if cache is empty and WORKSPACE_DIR is set by a previous call (e.g. create_workflow)
537
+ // This helps if server started with default WORKSPACE_DIR and cache was empty.
538
+ if (nodeInfoCache.size === 0 && WORKSPACE_DIR !== process.cwd()) {
539
+ console.warn("[WARN] nodeInfoCache is empty in add_node. Attempting to reload based on current WORKSPACE_DIR.");
540
+ await loadKnownNodeBaseTypes();
541
+ }
542
+
543
+ await ensureWorkflowDir();
544
+ const sanitizedName = workflowName.replace(/[^a-z0-9_.-]/gi, '_');
545
+ const filePath = resolvePath(path.join(WORKFLOW_DATA_DIR_NAME, `${sanitizedName}.json`));
546
+
547
+ let workflow: N8nWorkflow;
548
+ try {
549
+ const data = await fs.readFile(filePath, 'utf8');
550
+ workflow = JSON.parse(data) as N8nWorkflow;
551
+ } catch (readError: any) {
552
+ if (readError.code === 'ENOENT') {
553
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Workflow with name ${workflowName} not found at ${filePath}` }) }] };
554
+ }
555
+ throw readError;
556
+ }
557
+
558
+ // Ensure workflow.nodes exists
559
+ if (!Array.isArray(workflow.nodes)) {
560
+ workflow.nodes = [];
561
+ }
562
+
563
+ const defaultPos = params.position || { x: Math.floor(Math.random() * 500), y: Math.floor(Math.random() * 500) };
564
+
565
+ const { finalNodeType, finalTypeVersion } = normalizeNodeTypeAndVersion(params.node_type, params.typeVersion);
566
+ // console.error(`[DEBUG] Node type normalized: "${params.node_type}" -> "${finalNodeType}"`); // Covered by normalizeNodeTypeAndVersion's own log
567
+
568
+ // Process parameters for LangChain LLM nodes
569
+ let nodeParameters = params.parameters || {};
570
+
571
+ // Check if this is a LangChain LLM node
572
+ const isLangChainLLM = finalNodeType.includes('@n8n/n8n-nodes-langchain') &&
573
+ (finalNodeType.includes('lmChat') || finalNodeType.includes('llm'));
574
+
575
+ // Apply normalization for LangChain LLM nodes
576
+ if (isLangChainLLM) {
577
+ console.error(`[DEBUG] Applying parameter normalization for LangChain LLM node`);
578
+ nodeParameters = normalizeLLMParameters(nodeParameters);
579
+ } else {
580
+ // Handle OpenAI credentials specifically for non-LangChain nodes
581
+ if (params.parameters?.options?.credentials?.providerType === 'openAi') {
582
+ console.error(`[DEBUG] Setting up proper OpenAI credentials format for standard node`);
583
+
584
+ // Remove credentials from options and set at node level
585
+ if (nodeParameters.options?.credentials) {
586
+ const credentialsType = nodeParameters.options.credentials.providerType;
587
+ delete nodeParameters.options.credentials;
588
+
589
+ // Set a placeholder for credentials that would be filled in the n8n UI
590
+ if (!nodeParameters.credentials) {
591
+ nodeParameters.credentials = {};
592
+ }
593
+
594
+ // Add credentials in the proper format for OpenAI
595
+ nodeParameters.credentials = {
596
+ "openAiApi": {
597
+ "id": generateN8nId(),
598
+ "name": "OpenAi account"
599
+ }
600
+ };
601
+ }
602
+ }
603
+ }
604
+
605
+ const newNode: N8nWorkflowNode = {
606
+ id: generateUUID(),
607
+ type: finalNodeType,
608
+ typeVersion: finalTypeVersion, // Use version from normalizeNodeTypeAndVersion
609
+ position: [defaultPos.x, defaultPos.y],
610
+ parameters: nodeParameters,
611
+ name: params.node_name || `${finalNodeType} Node`, // Use finalNodeType for default name
612
+ ...(params.webhookId && { webhookId: params.webhookId }) // Add webhookId if provided
613
+ };
614
+ workflow.nodes.push(newNode);
615
+ await fs.writeFile(filePath, JSON.stringify(workflow, null, 2));
616
+ console.error(`[DEBUG] Added node ${newNode.id} to workflow ${workflowName} in file ${filePath}`);
617
+ return { content: [{ type: "text", text: JSON.stringify({ success: true, node: newNode, workflowId: workflow.id }) }] };
618
+ } catch (error: any) {
619
+ console.error("[ERROR] Failed to add node:", error);
620
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: "Failed to add node: " + error.message }) }] };
621
+ }
622
+ }
623
+ );
624
+
625
+ // Edit Node
626
+ // NOTE: This tool also needs updates for single-file workflow management.
627
+ const editNodeParamsSchema = z.object({
628
+ workflow_name: z.string().describe("The Name of the workflow containing the node"),
629
+ node_id: z.string().describe("The ID of the node to edit"),
630
+ node_type: z.string().optional().describe("The new type for the node (e.g., 'gmail', 'slack', 'openAi'). You can specify with or without the 'n8n-nodes-base.' prefix. The system will handle proper casing (e.g., 'openai' will be converted to 'openAi' if that's the correct casing)."),
631
+ node_name: z.string().optional().describe("The new name for the node"),
632
+ position: z.object({ // API still takes {x,y}
633
+ x: z.number(),
634
+ y: z.number()
635
+ }).optional().describe("The new position {x,y} - will be converted to [x,y]"),
636
+ parameters: z.record(z.string(), z.any()).optional().describe("The new parameters"),
637
+ typeVersion: z.number().optional().describe("The new type version for the node"),
638
+ webhookId: z.string().optional().describe("Optional new webhook ID for the node.")
639
+ });
640
+ server.tool(
641
+ "edit_node",
642
+ editNodeParamsSchema.shape,
643
+ async (params: z.infer<typeof editNodeParamsSchema>, _extra) => {
644
+ console.error("[DEBUG] edit_node called with:", params);
645
+ const workflowName = params.workflow_name;
646
+ try {
647
+ // Similar cache reload logic as in add_node
648
+ if (nodeInfoCache.size === 0 && WORKSPACE_DIR !== process.cwd()) {
649
+ console.warn("[WARN] nodeInfoCache is empty in edit_node. Attempting to reload based on current WORKSPACE_DIR.");
650
+ await loadKnownNodeBaseTypes();
651
+ }
652
+
653
+ await ensureWorkflowDir();
654
+ const sanitizedName = workflowName.replace(/[^a-z0-9_.-]/gi, '_');
655
+ const filePath = resolvePath(path.join(WORKFLOW_DATA_DIR_NAME, `${sanitizedName}.json`));
656
+
657
+ let workflow: N8nWorkflow;
658
+ try {
659
+ const data = await fs.readFile(filePath, 'utf8');
660
+ workflow = JSON.parse(data) as N8nWorkflow;
661
+ } catch (readError: any) {
662
+ if (readError.code === 'ENOENT') {
663
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Workflow with name ${workflowName} not found at ${filePath}` }) }] };
664
+ }
665
+ throw readError;
666
+ }
667
+
668
+ const nodeIndex = workflow.nodes.findIndex(n => n.id === params.node_id);
669
+ if (nodeIndex === -1) {
670
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Node with id ${params.node_id} not found in workflow ${workflowName}` }) }] };
671
+ }
672
+
673
+ const nodeToEdit = workflow.nodes[nodeIndex];
674
+
675
+ let newType = nodeToEdit.type;
676
+ let newTypeVersion = nodeToEdit.typeVersion;
677
+
678
+ if (params.node_type) {
679
+ // If node_type is changing, typeVersion should be re-evaluated based on the new type,
680
+ // unless a specific params.typeVersion is also given for this edit.
681
+ const { finalNodeType, finalTypeVersion: determinedVersionForNewType } = normalizeNodeTypeAndVersion(params.node_type, params.typeVersion);
682
+ newType = finalNodeType;
683
+ newTypeVersion = determinedVersionForNewType; // This uses params.typeVersion if valid, else default for new type.
684
+ } else if (params.typeVersion !== undefined && !isNaN(Number(params.typeVersion))) {
685
+ // Only typeVersion is being changed, node_type remains the same.
686
+ newTypeVersion = Number(params.typeVersion);
687
+ } else if (params.typeVersion !== undefined && isNaN(Number(params.typeVersion))) {
688
+ console.warn(`[WARN] Provided typeVersion '${params.typeVersion}' for editing node ${nodeToEdit.id} is NaN. typeVersion will not be changed.`);
689
+ }
690
+
691
+ nodeToEdit.type = newType;
692
+ nodeToEdit.typeVersion = newTypeVersion;
693
+
694
+ if (params.node_name) nodeToEdit.name = params.node_name;
695
+ if (params.position) nodeToEdit.position = [params.position.x, params.position.y];
696
+
697
+ // Process new parameters if provided
698
+ if (params.parameters) {
699
+ let newParameters = params.parameters;
700
+
701
+ // Check if this is a LangChain LLM node
702
+ const isLangChainLLM = newType.includes('@n8n/n8n-nodes-langchain') &&
703
+ (newType.includes('lmChat') || newType.includes('llm'));
704
+
705
+ // Apply normalization for LangChain LLM nodes
706
+ if (isLangChainLLM) {
707
+ console.error(`[DEBUG] Applying parameter normalization for LangChain LLM node during edit`);
708
+ newParameters = normalizeLLMParameters(newParameters);
709
+ } else {
710
+ // Handle OpenAI credentials specifically for non-LangChain nodes
711
+ if (newParameters.options?.credentials?.providerType === 'openAi') {
712
+ console.error(`[DEBUG] Setting up proper OpenAI credentials format for standard node during edit`);
713
+
714
+ // Remove credentials from options and set at node level
715
+ if (newParameters.options?.credentials) {
716
+ const credentialsType = newParameters.options.credentials.providerType;
717
+ delete newParameters.options.credentials;
718
+
719
+ // Set a placeholder for credentials that would be filled in the n8n UI
720
+ if (!newParameters.credentials) {
721
+ newParameters.credentials = {};
722
+ }
723
+
724
+ // Add credentials in the proper format for OpenAI
725
+ newParameters.credentials = {
726
+ "openAiApi": {
727
+ "id": generateN8nId(),
728
+ "name": "OpenAi account"
729
+ }
730
+ };
731
+ }
732
+ }
733
+ }
734
+
735
+ nodeToEdit.parameters = newParameters;
736
+ }
737
+
738
+ if (params.webhookId !== undefined) { // Allow setting or unsetting webhookId
739
+ if (params.webhookId === null || params.webhookId === "") { // Check for explicit clear
740
+ delete nodeToEdit.webhookId;
741
+ } else {
742
+ nodeToEdit.webhookId = params.webhookId;
743
+ }
744
+ }
745
+
746
+ workflow.nodes[nodeIndex] = nodeToEdit;
747
+
748
+ await fs.writeFile(filePath, JSON.stringify(workflow, null, 2));
749
+ console.error(`[DEBUG] Edited node ${params.node_id} in workflow ${workflowName} in file ${filePath}`);
750
+ return { content: [{ type: "text", text: JSON.stringify({ success: true, node: nodeToEdit }) }] };
751
+ } catch (error: any) {
752
+ console.error("[ERROR] Failed to edit node:", error);
753
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: "Failed to edit node: " + error.message }) }] };
754
+ }
755
+ }
756
+ );
757
+
758
+ // Delete Node
759
+ // NOTE: This tool also needs updates for single-file workflow management.
760
+ const deleteNodeParamsSchema = z.object({
761
+ workflow_name: z.string().describe("The Name of the workflow containing the node"),
762
+ node_id: z.string().describe("The ID of the node to delete")
763
+ });
764
+ server.tool(
765
+ "delete_node",
766
+ deleteNodeParamsSchema.shape,
767
+ async (params: z.infer<typeof deleteNodeParamsSchema>, _extra) => {
768
+ console.error("[DEBUG] delete_node called with:", params);
769
+ const workflowName = params.workflow_name;
770
+ try {
771
+ await ensureWorkflowDir();
772
+ const sanitizedName = workflowName.replace(/[^a-z0-9_.-]/gi, '_');
773
+ const filePath = resolvePath(path.join(WORKFLOW_DATA_DIR_NAME, `${sanitizedName}.json`));
774
+
775
+ let workflow: N8nWorkflow;
776
+ try {
777
+ const data = await fs.readFile(filePath, 'utf8');
778
+ workflow = JSON.parse(data) as N8nWorkflow;
779
+ } catch (readError: any) {
780
+ if (readError.code === 'ENOENT') {
781
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Workflow with name ${workflowName} not found at ${filePath}` }) }] };
782
+ }
783
+ throw readError;
784
+ }
785
+
786
+ const nodeIndex = workflow.nodes.findIndex(n => n.id === params.node_id);
787
+ if (nodeIndex === -1) {
788
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Node with id ${params.node_id} not found in workflow ${workflowName}` }) }] };
789
+ }
790
+
791
+ const deletedNodeName = workflow.nodes[nodeIndex].name;
792
+ workflow.nodes.splice(nodeIndex, 1);
793
+
794
+ // Also remove connections related to this node
795
+ // This is a simplified connection removal. n8n's logic might be more complex.
796
+ const newConnections: N8nConnections = {};
797
+ for (const sourceNodeName in workflow.connections) {
798
+ if (sourceNodeName === deletedNodeName) continue; // Skip connections FROM the deleted node
799
+
800
+ const outputConnections = workflow.connections[sourceNodeName];
801
+ const newOutputConnectionsForSource: N8nConnections[string] = {};
802
+
803
+ for (const outputKey in outputConnections) {
804
+ const connectionChains = outputConnections[outputKey];
805
+ const newConnectionChains: N8nConnectionDetail[][] = [];
806
+
807
+ for (const chain of connectionChains) {
808
+ const newChain = chain.filter(connDetail => connDetail.node !== deletedNodeName);
809
+ if (newChain.length > 0) {
810
+ newConnectionChains.push(newChain);
811
+ }
812
+ }
813
+ if (newConnectionChains.length > 0) {
814
+ newOutputConnectionsForSource[outputKey] = newConnectionChains;
815
+ }
816
+ }
817
+ if (Object.keys(newOutputConnectionsForSource).length > 0) {
818
+ newConnections[sourceNodeName] = newOutputConnectionsForSource;
819
+ }
820
+ }
821
+ workflow.connections = newConnections;
822
+
823
+ await fs.writeFile(filePath, JSON.stringify(workflow, null, 2));
824
+ console.error(`[DEBUG] Deleted node ${params.node_id} from workflow ${workflowName} in file ${filePath}`);
825
+ return { content: [{ type: "text", text: JSON.stringify({ success: true, message: `Node ${params.node_id} deleted successfully from workflow ${workflowName}` }) }] };
826
+ } catch (error: any) {
827
+ console.error("[ERROR] Failed to delete node:", error);
828
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: "Failed to delete node: " + error.message }) }] };
829
+ }
830
+ }
831
+ );
832
+
833
+ // Add Connection
834
+ const addConnectionParamsSchema = z.object({
835
+ workflow_name: z.string().describe("The Name of the workflow to add the connection to"),
836
+ source_node_id: z.string().describe("The ID of the source node for the connection"),
837
+ source_node_output_name: z.string().describe("The name of the output handle on the source node (e.g., 'main')"),
838
+ target_node_id: z.string().describe("The ID of the target node for the connection"),
839
+ target_node_input_name: z.string().describe("The name of the input handle on the target node (e.g., 'main')"),
840
+ target_node_input_index: z.number().optional().default(0).describe("The index for the target node's input handle (default: 0)")
841
+ });
842
+
843
+ server.tool(
844
+ "add_connection",
845
+ addConnectionParamsSchema.shape,
846
+ async (params: z.infer<typeof addConnectionParamsSchema>, _extra) => {
847
+ console.error("[DEBUG] add_connection called with:", params);
848
+ const { workflow_name, source_node_id, source_node_output_name, target_node_id, target_node_input_name, target_node_input_index } = params;
849
+
850
+ try {
851
+ await ensureWorkflowDir();
852
+ const sanitizedWorkflowName = workflow_name.replace(/[^a-z0-9_.-]/gi, '_');
853
+ const filePath = resolvePath(path.join(WORKFLOW_DATA_DIR_NAME, `${sanitizedWorkflowName}.json`));
854
+
855
+ let workflow: N8nWorkflow;
856
+ try {
857
+ const data = await fs.readFile(filePath, 'utf8');
858
+ workflow = JSON.parse(data) as N8nWorkflow;
859
+ } catch (readError: any) {
860
+ if (readError.code === 'ENOENT') {
861
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Workflow with name ${workflow_name} not found at ${filePath}` }) }] };
862
+ }
863
+ throw readError;
864
+ }
865
+
866
+ const sourceNode = workflow.nodes.find(node => node.id === source_node_id);
867
+ const targetNode = workflow.nodes.find(node => node.id === target_node_id);
868
+
869
+ if (!sourceNode) {
870
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Source node with ID ${source_node_id} not found in workflow ${workflow_name}` }) }] };
871
+ }
872
+ if (!targetNode) {
873
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Target node with ID ${target_node_id} not found in workflow ${workflow_name}` }) }] };
874
+ }
875
+
876
+ const sourceNodeNameKey = sourceNode.name; // n8n connections are keyed by node *name*
877
+ const targetNodeNameValue = targetNode.name;
878
+
879
+ // Detect if we're working with LangChain AI nodes that require special connection handling
880
+ const isLangChainSource = sourceNode.type.includes('@n8n/n8n-nodes-langchain');
881
+ const isLangChainTarget = targetNode.type.includes('@n8n/n8n-nodes-langchain');
882
+ const isAIConnection = source_node_output_name.startsWith('ai_') || target_node_input_name.startsWith('ai_');
883
+
884
+ let connectionDirection = "forward"; // Default: source -> target
885
+
886
+ // Check if we need to reverse connection direction for AI nodes
887
+ // This handles the special case for LangChain nodes where tools and models
888
+ // connect TO the agent rather than the agent connecting to them
889
+ if ((isLangChainSource || isLangChainTarget) && isAIConnection) {
890
+ // Check if this might be a case where direction needs to be reversed
891
+ // - Models/Tools point TO Agent (reversed)
892
+ // - Agent points to regular nodes (forward)
893
+ // - Triggers point to any node (forward)
894
+ // - Memory nodes point TO Agent (reversed)
895
+ if (
896
+ // If it's a LLM, Tool, or Memory node pointing to an agent
897
+ (sourceNode.type.includes('lmChat') ||
898
+ sourceNode.type.includes('tool') ||
899
+ sourceNode.type.toLowerCase().includes('request') ||
900
+ sourceNode.type.includes('memory'))
901
+ && targetNode.type.includes('agent')
902
+ ) {
903
+ console.warn("[WARN] LangChain AI connection detected. N8n often expects models, tools, and memory to connect TO agents rather than agents connecting to them.");
904
+ console.warn("[WARN] Connections will be created as specified, but if they don't appear correctly in n8n UI, try reversing the source and target.");
905
+
906
+ // Special hint for memory connections
907
+ if (sourceNode.type.includes('memory')) {
908
+ if (source_node_output_name !== 'ai_memory') {
909
+ console.warn("[WARN] Memory nodes should usually connect to agents using 'ai_memory' output, not '" + source_node_output_name + "'.");
910
+ }
911
+ if (target_node_input_name !== 'ai_memory') {
912
+ console.warn("[WARN] Agents should receive memory connections on 'ai_memory' input, not '" + target_node_input_name + "'.");
913
+ }
914
+ }
915
+ }
916
+ }
917
+
918
+ const newConnectionObject: N8nConnectionDetail = {
919
+ node: targetNodeNameValue,
920
+ type: target_node_input_name,
921
+ index: target_node_input_index
922
+ };
923
+
924
+ if (!workflow.connections) {
925
+ workflow.connections = {};
926
+ }
927
+
928
+ if (!workflow.connections[sourceNodeNameKey]) {
929
+ workflow.connections[sourceNodeNameKey] = {};
930
+ }
931
+
932
+ if (!workflow.connections[sourceNodeNameKey][source_node_output_name]) {
933
+ workflow.connections[sourceNodeNameKey][source_node_output_name] = [];
934
+ }
935
+
936
+ // n8n expects an array of connection arrays for each output handle.
937
+ // Each inner array represents a set of connections originating from the same output point if it splits.
938
+ // For a simple new connection, we add it as a new chain: [newConnectionObject]
939
+ workflow.connections[sourceNodeNameKey][source_node_output_name].push([newConnectionObject]);
940
+
941
+ await fs.writeFile(filePath, JSON.stringify(workflow, null, 2));
942
+ console.error(`[DEBUG] Added connection from ${sourceNodeNameKey}:${source_node_output_name} to ${targetNodeNameValue}:${target_node_input_name} in workflow ${workflow_name}`);
943
+
944
+ // Add a special note for AI connections
945
+ let message = "Connection added successfully";
946
+ if ((isLangChainSource || isLangChainTarget) && isAIConnection) {
947
+ message += ". Note: For LangChain nodes, connections might need specific output/input names and connection direction. If connections don't appear in n8n UI, check that:";
948
+ message += "\n- Models connect TO the agent using 'ai_languageModel' ports";
949
+ message += "\n- Tools connect TO the agent using 'ai_tool' ports";
950
+ message += "\n- Memory nodes connect TO the agent using 'ai_memory' ports";
951
+ }
952
+
953
+ return { content: [{ type: "text", text: JSON.stringify({ success: true, message, workflow }) }] };
954
+
955
+ } catch (error: any) {
956
+ console.error("[ERROR] Failed to add connection:", error);
957
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: "Failed to add connection: " + error.message }) }] };
958
+ }
959
+ }
960
+ );
961
+
962
+ // Add AI Connections (special case for LangChain nodes)
963
+ const addAIConnectionsParamsSchema = z.object({
964
+ workflow_name: z.string().describe("The Name of the workflow to add the AI connections to"),
965
+ agent_node_id: z.string().describe("The ID of the agent node that will use the model and tools"),
966
+ model_node_id: z.string().optional().describe("The ID of the language model node (optional)"),
967
+ tool_node_ids: z.array(z.string()).optional().describe("Array of tool node IDs to connect to the agent (optional)"),
968
+ memory_node_id: z.string().optional().describe("The ID of the memory node (optional)")
969
+ });
970
+
971
+ server.tool(
972
+ "add_ai_connections",
973
+ addAIConnectionsParamsSchema.shape,
974
+ async (params: z.infer<typeof addAIConnectionsParamsSchema>, _extra) => {
975
+ console.error("[DEBUG] add_ai_connections called with:", params);
976
+ const { workflow_name, agent_node_id, model_node_id, tool_node_ids, memory_node_id } = params;
977
+
978
+ if (!model_node_id && (!tool_node_ids || tool_node_ids.length === 0) && !memory_node_id) {
979
+ return {
980
+ content: [{
981
+ type: "text",
982
+ text: JSON.stringify({
983
+ success: false,
984
+ error: "At least one of model_node_id, memory_node_id, or tool_node_ids must be provided"
985
+ })
986
+ }]
987
+ };
988
+ }
989
+
990
+ try {
991
+ await ensureWorkflowDir();
992
+ const sanitizedWorkflowName = workflow_name.replace(/[^a-z0-9_.-]/gi, '_');
993
+ const filePath = resolvePath(path.join(WORKFLOW_DATA_DIR_NAME, `${sanitizedWorkflowName}.json`));
994
+
995
+ let workflow: N8nWorkflow;
996
+ try {
997
+ const data = await fs.readFile(filePath, 'utf8');
998
+ workflow = JSON.parse(data) as N8nWorkflow;
999
+ } catch (readError: any) {
1000
+ if (readError.code === 'ENOENT') {
1001
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Workflow with name ${workflow_name} not found at ${filePath}` }) }] };
1002
+ }
1003
+ throw readError;
1004
+ }
1005
+
1006
+ // First verify all nodes exist
1007
+ const agentNode = workflow.nodes.find(node => node.id === agent_node_id);
1008
+ if (!agentNode) {
1009
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Agent node with ID ${agent_node_id} not found in workflow ${workflow_name}` }) }] };
1010
+ }
1011
+
1012
+ let modelNode = null;
1013
+ if (model_node_id) {
1014
+ modelNode = workflow.nodes.find(node => node.id === model_node_id);
1015
+ if (!modelNode) {
1016
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Model node with ID ${model_node_id} not found in workflow ${workflow_name}` }) }] };
1017
+ }
1018
+ }
1019
+
1020
+ let memoryNode = null;
1021
+ if (memory_node_id) {
1022
+ memoryNode = workflow.nodes.find(node => node.id === memory_node_id);
1023
+ if (!memoryNode) {
1024
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Memory node with ID ${memory_node_id} not found in workflow ${workflow_name}` }) }] };
1025
+ }
1026
+ }
1027
+
1028
+ const toolNodes: N8nWorkflowNode[] = [];
1029
+ if (tool_node_ids && tool_node_ids.length > 0) {
1030
+ for (const toolId of tool_node_ids) {
1031
+ const toolNode = workflow.nodes.find(node => node.id === toolId);
1032
+ if (!toolNode) {
1033
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Tool node with ID ${toolId} not found in workflow ${workflow_name}` }) }] };
1034
+ }
1035
+ toolNodes.push(toolNode);
1036
+ }
1037
+ }
1038
+
1039
+ if (!workflow.connections) {
1040
+ workflow.connections = {};
1041
+ }
1042
+
1043
+ // For AI nodes in n8n, we need to:
1044
+ // 1. Language model connects TO the agent using ai_languageModel ports
1045
+ // 2. Tools connect TO the agent using ai_tool ports
1046
+ // 3. Memory nodes connect TO the agent using ai_memory ports
1047
+
1048
+ // Create the language model connection if a model node was provided
1049
+ if (modelNode) {
1050
+ const modelNodeName = modelNode.name;
1051
+
1052
+ // Initialize model node's connections if needed
1053
+ if (!workflow.connections[modelNodeName]) {
1054
+ workflow.connections[modelNodeName] = {};
1055
+ }
1056
+
1057
+ // Add the AI language model output
1058
+ if (!workflow.connections[modelNodeName]["ai_languageModel"]) {
1059
+ workflow.connections[modelNodeName]["ai_languageModel"] = [];
1060
+ }
1061
+
1062
+ // Add connection from model to agent
1063
+ const modelConnection: N8nConnectionDetail = {
1064
+ node: agentNode.name,
1065
+ type: "ai_languageModel",
1066
+ index: 0
1067
+ };
1068
+
1069
+ // Check if this connection already exists
1070
+ const existingModelConnection = workflow.connections[modelNodeName]["ai_languageModel"].some(
1071
+ conn => conn.some(detail => detail.node === agentNode.name && detail.type === "ai_languageModel")
1072
+ );
1073
+
1074
+ if (!existingModelConnection) {
1075
+ workflow.connections[modelNodeName]["ai_languageModel"].push([modelConnection]);
1076
+ console.error(`[DEBUG] Added AI language model connection from ${modelNodeName} to ${agentNode.name}`);
1077
+ } else {
1078
+ console.error(`[DEBUG] AI language model connection from ${modelNodeName} to ${agentNode.name} already exists`);
1079
+ }
1080
+ }
1081
+
1082
+ // Create memory connection if a memory node was provided
1083
+ if (memoryNode) {
1084
+ const memoryNodeName = memoryNode.name;
1085
+
1086
+ // Initialize memory node's connections if needed
1087
+ if (!workflow.connections[memoryNodeName]) {
1088
+ workflow.connections[memoryNodeName] = {};
1089
+ }
1090
+
1091
+ // Add the AI memory output
1092
+ if (!workflow.connections[memoryNodeName]["ai_memory"]) {
1093
+ workflow.connections[memoryNodeName]["ai_memory"] = [];
1094
+ }
1095
+
1096
+ // Add connection from memory to agent
1097
+ const memoryConnection: N8nConnectionDetail = {
1098
+ node: agentNode.name,
1099
+ type: "ai_memory",
1100
+ index: 0
1101
+ };
1102
+
1103
+ // Check if this connection already exists
1104
+ const existingMemoryConnection = workflow.connections[memoryNodeName]["ai_memory"].some(
1105
+ conn => conn.some(detail => detail.node === agentNode.name && detail.type === "ai_memory")
1106
+ );
1107
+
1108
+ if (!existingMemoryConnection) {
1109
+ workflow.connections[memoryNodeName]["ai_memory"].push([memoryConnection]);
1110
+ console.error(`[DEBUG] Added AI memory connection from ${memoryNodeName} to ${agentNode.name}`);
1111
+ } else {
1112
+ console.error(`[DEBUG] AI memory connection from ${memoryNodeName} to ${agentNode.name} already exists`);
1113
+ }
1114
+ }
1115
+
1116
+ // Create tool connections if tool nodes were provided
1117
+ if (toolNodes.length > 0) {
1118
+ for (const toolNode of toolNodes) {
1119
+ const toolNodeName = toolNode.name;
1120
+
1121
+ // Initialize tool node's connections if needed
1122
+ if (!workflow.connections[toolNodeName]) {
1123
+ workflow.connections[toolNodeName] = {};
1124
+ }
1125
+
1126
+ // Add the AI tool output
1127
+ if (!workflow.connections[toolNodeName]["ai_tool"]) {
1128
+ workflow.connections[toolNodeName]["ai_tool"] = [];
1129
+ }
1130
+
1131
+ // Add connection from tool to agent
1132
+ const toolConnection: N8nConnectionDetail = {
1133
+ node: agentNode.name,
1134
+ type: "ai_tool",
1135
+ index: 0
1136
+ };
1137
+
1138
+ // Check if this connection already exists
1139
+ const existingToolConnection = workflow.connections[toolNodeName]["ai_tool"].some(
1140
+ conn => conn.some(detail => detail.node === agentNode.name && detail.type === "ai_tool")
1141
+ );
1142
+
1143
+ if (!existingToolConnection) {
1144
+ workflow.connections[toolNodeName]["ai_tool"].push([toolConnection]);
1145
+ console.error(`[DEBUG] Added AI tool connection from ${toolNodeName} to ${agentNode.name}`);
1146
+ } else {
1147
+ console.error(`[DEBUG] AI tool connection from ${toolNodeName} to ${agentNode.name} already exists`);
1148
+ }
1149
+ }
1150
+ }
1151
+
1152
+ // Save the updated workflow
1153
+ await fs.writeFile(filePath, JSON.stringify(workflow, null, 2));
1154
+
1155
+ return {
1156
+ content: [{
1157
+ type: "text",
1158
+ text: JSON.stringify({
1159
+ success: true,
1160
+ message: "AI connections added successfully",
1161
+ workflow
1162
+ })
1163
+ }]
1164
+ };
1165
+ } catch (error: any) {
1166
+ console.error("[ERROR] Failed to add AI connections:", error);
1167
+ return {
1168
+ content: [{
1169
+ type: "text",
1170
+ text: JSON.stringify({
1171
+ success: false,
1172
+ error: "Failed to add AI connections: " + error.message
1173
+ })
1174
+ }]
1175
+ };
1176
+ }
1177
+ }
1178
+ );
1179
+
1180
+ // List Available Nodes
1181
+ const listAvailableNodesParamsSchema = z.object({
1182
+ search_term: z.string().optional().describe("An optional search term to filter nodes by their name, type, or description.")
1183
+ });
1184
+
1185
+ server.tool(
1186
+ "list_available_nodes",
1187
+ listAvailableNodesParamsSchema.shape,
1188
+ async (params: z.infer<typeof listAvailableNodesParamsSchema>, _extra) => {
1189
+ console.error("[DEBUG] list_available_nodes called with params:", params);
1190
+ let availableNodes: any[] = [];
1191
+
1192
+ // Corrected path: relative to this script's location
1193
+ const workflowNodesDir = path.resolve(__dirname, '../workflow_nodes');
1194
+
1195
+ try {
1196
+ // knownNodeBaseCasings should ideally be populated at startup by loadKnownNodeBaseTypes.
1197
+ // If it's empty here, it means initial load failed or directory wasn't found then.
1198
+ // We might not need to reload it here if startup handles it, but a check doesn't hurt.
1199
+ if (nodeInfoCache.size === 0 && WORKSPACE_DIR !== process.cwd()) {
1200
+ console.warn("[WARN] nodeInfoCache is empty in list_available_nodes. Attempting to reload node type information.");
1201
+ // For now, if cache is empty, it means startup failed to load them.
1202
+ // The function will proceed and likely return an empty list or whatever it finds if workflowNodesDir is accessible now.
1203
+ }
1204
+
1205
+ console.error(`[DEBUG] Reading node definitions from server resource path: ${workflowNodesDir}`);
1206
+ const files = await fs.readdir(workflowNodesDir);
1207
+ const suffix = ".json";
1208
+ const allParsedNodes: any[] = []; // Temporary array to hold all nodes before filtering
1209
+
1210
+ for (const file of files) {
1211
+ if (file.endsWith(suffix) && file !== WORKFLOWS_FILE_NAME /* ignore old combined file */) {
1212
+ const filePath = path.join(workflowNodesDir, file);
1213
+ try {
1214
+ const fileContent = await fs.readFile(filePath, 'utf8');
1215
+ const nodeDefinition = JSON.parse(fileContent);
1216
+
1217
+ if (nodeDefinition.nodeType && nodeDefinition.displayName && nodeDefinition.properties) {
1218
+ allParsedNodes.push({
1219
+ nodeType: nodeDefinition.nodeType,
1220
+ displayName: nodeDefinition.displayName,
1221
+ description: nodeDefinition.description || "",
1222
+ version: nodeDefinition.version || 1,
1223
+ properties: nodeDefinition.properties,
1224
+ credentialsConfig: nodeDefinition.credentialsConfig || [],
1225
+ categories: nodeDefinition.categories || [],
1226
+ // Also add simplified versions of the node type for reference
1227
+ simpleName: nodeDefinition.nodeType.includes('n8n-nodes-base.')
1228
+ ? nodeDefinition.nodeType.split('n8n-nodes-base.')[1]
1229
+ : nodeDefinition.nodeType
1230
+ });
1231
+ } else {
1232
+ console.warn(`[WARN] File ${file} does not seem to be a valid node definition. Skipping.`);
1233
+ }
1234
+ } catch (parseError: any) {
1235
+ console.warn(`[WARN] Failed to parse ${file}: ${parseError.message}. Skipping.`);
1236
+ }
1237
+ }
1238
+ }
1239
+
1240
+ if (params.search_term && params.search_term.trim() !== "") {
1241
+ const searchTermLower = params.search_term.toLowerCase();
1242
+ availableNodes = allParsedNodes.filter(node => {
1243
+ let found = false;
1244
+ if (node.displayName && node.displayName.toLowerCase().includes(searchTermLower)) {
1245
+ found = true;
1246
+ }
1247
+ if (!found && node.nodeType && node.nodeType.toLowerCase().includes(searchTermLower)) {
1248
+ found = true;
1249
+ }
1250
+ if (!found && node.description && node.description.toLowerCase().includes(searchTermLower)) {
1251
+ found = true;
1252
+ }
1253
+ if (!found && node.simpleName && node.simpleName.toLowerCase().includes(searchTermLower)) {
1254
+ found = true;
1255
+ }
1256
+ if (!found && node.properties && Array.isArray(node.properties)) {
1257
+ for (const prop of node.properties) {
1258
+ if (prop.name && prop.name.toLowerCase().includes(searchTermLower)) {
1259
+ found = true;
1260
+ break;
1261
+ }
1262
+ if (prop.displayName && prop.displayName.toLowerCase().includes(searchTermLower)) {
1263
+ found = true;
1264
+ break;
1265
+ }
1266
+ // Optionally search prop.description as well
1267
+ // if (prop.description && prop.description.toLowerCase().includes(searchTermLower)) {
1268
+ // found = true;
1269
+ // break;
1270
+ // }
1271
+ }
1272
+ }
1273
+ if (!found && node.categories && Array.isArray(node.categories)) {
1274
+ for (const category of node.categories) {
1275
+ if (typeof category === 'string' && category.toLowerCase().includes(searchTermLower)) {
1276
+ found = true;
1277
+ break;
1278
+ }
1279
+ }
1280
+ }
1281
+ return found;
1282
+ });
1283
+ console.log(`[DEBUG] Filtered nodes by '${params.search_term}'. Found ${availableNodes.length} of ${allParsedNodes.length}.`);
1284
+ } else {
1285
+ availableNodes = allParsedNodes; // No search term, return all nodes
1286
+ }
1287
+
1288
+ if (availableNodes.length === 0 && allParsedNodes.length > 0 && params.search_term) {
1289
+ console.warn(`[WARN] No nodes matched the search term: '${params.search_term}'.`);
1290
+ } else if (allParsedNodes.length === 0) {
1291
+ console.warn("[WARN] No node definitions found in workflow_nodes. Ensure the directory is populated with JSON files from the scraper.");
1292
+ }
1293
+
1294
+ // Format the results to be more user-friendly and informative
1295
+ const formattedNodes = availableNodes.map(node => {
1296
+ return {
1297
+ // Keep only the most relevant information
1298
+ nodeType: node.nodeType, // Full node type with correct casing
1299
+ displayName: node.displayName,
1300
+ description: node.description,
1301
+ simpleName: node.simpleName, // The part after n8n-nodes-base
1302
+ categories: node.categories || [],
1303
+ version: node.version,
1304
+ // Count parameters but don't include details to keep response size manageable
1305
+ parameterCount: node.properties ? node.properties.length : 0
1306
+ };
1307
+ });
1308
+
1309
+ // Include usage guidance in the response
1310
+ const usageGuidance = {
1311
+ title: "Node Type Usage Guide",
1312
+ description: "When using the add_node or replace_node tools, you can specify the node type in any of these formats:",
1313
+ formats: [
1314
+ `Full Type (with correct casing): "${formattedNodes.length > 0 ? formattedNodes[0].nodeType : 'n8n-nodes-base.nodeTypeName'}"`,
1315
+ `Simple Name (with correct casing): "${formattedNodes.length > 0 ? formattedNodes[0].simpleName : 'nodeTypeName'}"`,
1316
+ `Simple Name (lowercase): "${formattedNodes.length > 0 ? formattedNodes[0].simpleName.toLowerCase() : 'nodetypename'}"`
1317
+ ],
1318
+ note: "The system will automatically handle proper casing and prefixing for you based on the official node definitions."
1319
+ };
1320
+
1321
+ // Return the formatted response
1322
+ return {
1323
+ content: [{
1324
+ type: "text", text: JSON.stringify({
1325
+ success: true,
1326
+ nodes: formattedNodes,
1327
+ total: formattedNodes.length,
1328
+ usageGuidance: usageGuidance
1329
+ })
1330
+ }]
1331
+ };
1332
+
1333
+ } catch (error: any) {
1334
+ console.error("[ERROR] Failed to list available nodes:", error);
1335
+ if (error.code === 'ENOENT') {
1336
+ console.warn("[WARN] workflow_nodes directory not found. Cannot list available nodes.");
1337
+ return { content: [{ type: "text", text: JSON.stringify({ success: true, nodes: [], message: "workflow_nodes directory not found." }) }] };
1338
+ }
1339
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: "Failed to list available nodes: " + error.message }) }] };
1340
+ }
1341
+ }
1342
+ );
1343
+
1344
+ // Create and configure the transport
1345
+ const transport = new StdioServerTransport();
1346
+
1347
+ // Start the server
1348
+ async function main(): Promise<void> {
1349
+ try {
1350
+ // Note: loadKnownNodeBaseTypes uses resolvePath, which depends on WORKSPACE_DIR.
1351
+ // WORKSPACE_DIR is typically set by create_workflow.
1352
+ // If called before create_workflow, it might use process.cwd() or fail if workflow_nodes isn't there.
1353
+ // This is a known limitation for now; ideally, WORKSPACE_DIR is configured at MCP server init more globally.
1354
+ await loadKnownNodeBaseTypes(); // Attempt to load node types at startup
1355
+
1356
+ await server.connect(transport);
1357
+ console.error("[DEBUG] N8N Workflow Builder MCP Server started (TypeScript version)");
1358
+
1359
+ // Debugging tool schemas might need update if params changed significantly for other tools
1360
+ const toolSchemasForDebug = {
1361
+ create_workflow: createWorkflowParamsSchema,
1362
+ list_workflows: z.object({}), // Updated to reflect empty params
1363
+ get_workflow_details: getWorkflowDetailsParamsSchema,
1364
+ add_node: addNodeParamsSchema,
1365
+ edit_node: editNodeParamsSchema,
1366
+ delete_node: deleteNodeParamsSchema,
1367
+ add_connection: addConnectionParamsSchema
1368
+ };
1369
+
1370
+ const manuallyConstructedToolList = Object.entries(toolSchemasForDebug).map(([name, schema]) => {
1371
+ let toolDefinition: any = { name };
1372
+ // Attempt to get description from the schema if available, or use a default.
1373
+ // Note: .describe() on Zod schemas is for properties, not usually the whole schema for tool description.
1374
+ // The description passed in the options object to server.tool() is what the MCP client sees.
1375
+ // This reconstruction is for local debugging of what the SDK *might* send.
1376
+
1377
+ if (name === "create_workflow") toolDefinition.description = "Create a new n8n workflow";
1378
+ else if (name === "list_workflows") toolDefinition.description = "List all n8n workflows";
1379
+ else if (name === "get_workflow_details") toolDefinition.description = "Get details of a specific n8n workflow";
1380
+ else if (name === "add_node") toolDefinition.description = "Add a new node to a workflow";
1381
+ else if (name === "edit_node") toolDefinition.description = "Edit an existing node in a workflow";
1382
+ else if (name === "delete_node") toolDefinition.description = "Delete a node from a workflow";
1383
+ else if (name === "add_connection") toolDefinition.description = "Add a new connection between nodes in a workflow";
1384
+ else toolDefinition.description = `Description for ${name}`;
1385
+
1386
+ if (schema) {
1387
+ // This is a simplified mock of how zod-to-json-schema might convert it
1388
+ // Actual conversion by SDK might be more complex.
1389
+ const properties: Record<string, any> = {};
1390
+ const required: string[] = [];
1391
+ const shape = schema.shape as Record<string, z.ZodTypeAny>;
1392
+ for (const key in shape) {
1393
+ const field = shape[key];
1394
+ properties[key] = { type: field._def.typeName.replace('Zod', '').toLowerCase(), description: field.description };
1395
+ if (!field.isOptional()) {
1396
+ required.push(key);
1397
+ }
1398
+ }
1399
+ toolDefinition.inputSchema = { type: "object", properties, required };
1400
+ } else {
1401
+ toolDefinition.inputSchema = { type: "object", properties: {}, required: [] };
1402
+ }
1403
+ return toolDefinition;
1404
+ });
1405
+
1406
+ console.error("[DEBUG] Server's expected 'tools' array for tools/list response (with detailed inputSchemas):");
1407
+ console.error(JSON.stringify(manuallyConstructedToolList, null, 2));
1408
+
1409
+ // Keep the process alive
1410
+ return new Promise<void>((resolve, reject) => {
1411
+ process.on('SIGINT', () => {
1412
+ console.error("[DEBUG] Received SIGINT, shutting down...");
1413
+ server.close().then(resolve).catch(reject);
1414
+ });
1415
+ process.on('SIGTERM', () => {
1416
+ console.error("[DEBUG] Received SIGTERM, shutting down...");
1417
+ server.close().then(resolve).catch(reject);
1418
+ });
1419
+ });
1420
+ } catch (error) {
1421
+ console.error("[ERROR] Failed to start server:", error);
1422
+ process.exit(1);
1423
+ }
1424
+ }
1425
+
1426
+ main().catch(error => {
1427
+ console.error("[ERROR] Unhandled error in main:", error);
1428
+ process.exit(1);
1429
+ });
1430
+
1431
+ process.on('uncaughtException', (error) => {
1432
+ console.error("[ERROR] Uncaught exception:", error);
1433
+ // Consider whether to exit or attempt graceful shutdown
1434
+ });
1435
+
1436
+ process.on('unhandledRejection', (reason, promise) => {
1437
+ console.error("[ERROR] Unhandled promise rejection at:", promise, "reason:", reason);
1438
+ // Consider whether to exit or attempt graceful shutdown
1439
+ });
1440
+