This is page 31 of 46. Use http://codebase.md/czlonkowski/n8n-mcp?lines=false&page={x} to view the full context. # Directory Structure ``` ├── _config.yml ├── .claude │ └── agents │ ├── code-reviewer.md │ ├── context-manager.md │ ├── debugger.md │ ├── deployment-engineer.md │ ├── mcp-backend-engineer.md │ ├── n8n-mcp-tester.md │ ├── technical-researcher.md │ └── test-automator.md ├── .dockerignore ├── .env.docker ├── .env.example ├── .env.n8n.example ├── .env.test ├── .env.test.example ├── .github │ ├── ABOUT.md │ ├── BENCHMARK_THRESHOLDS.md │ ├── FUNDING.yml │ ├── gh-pages.yml │ ├── secret_scanning.yml │ └── workflows │ ├── benchmark-pr.yml │ ├── benchmark.yml │ ├── docker-build-fast.yml │ ├── docker-build-n8n.yml │ ├── docker-build.yml │ ├── release.yml │ ├── test.yml │ └── update-n8n-deps.yml ├── .gitignore ├── .npmignore ├── ATTRIBUTION.md ├── CHANGELOG.md ├── CLAUDE.md ├── codecov.yml ├── coverage.json ├── data │ ├── .gitkeep │ ├── nodes.db │ ├── nodes.db-shm │ ├── nodes.db-wal │ └── templates.db ├── deploy │ └── quick-deploy-n8n.sh ├── docker │ ├── docker-entrypoint.sh │ ├── n8n-mcp │ ├── parse-config.js │ └── README.md ├── docker-compose.buildkit.yml ├── docker-compose.extract.yml ├── docker-compose.n8n.yml ├── docker-compose.override.yml.example ├── docker-compose.test-n8n.yml ├── docker-compose.yml ├── Dockerfile ├── Dockerfile.railway ├── Dockerfile.test ├── docs │ ├── AUTOMATED_RELEASES.md │ ├── BENCHMARKS.md │ ├── CHANGELOG.md │ ├── CLAUDE_CODE_SETUP.md │ ├── CLAUDE_INTERVIEW.md │ ├── CODECOV_SETUP.md │ ├── CODEX_SETUP.md │ ├── CURSOR_SETUP.md │ ├── DEPENDENCY_UPDATES.md │ ├── DOCKER_README.md │ ├── DOCKER_TROUBLESHOOTING.md │ ├── FINAL_AI_VALIDATION_SPEC.md │ ├── FLEXIBLE_INSTANCE_CONFIGURATION.md │ ├── HTTP_DEPLOYMENT.md │ ├── img │ │ ├── cc_command.png │ │ ├── cc_connected.png │ │ ├── codex_connected.png │ │ ├── cursor_tut.png │ │ ├── Railway_api.png │ │ ├── Railway_server_address.png │ │ ├── vsc_ghcp_chat_agent_mode.png │ │ ├── vsc_ghcp_chat_instruction_files.png │ │ ├── vsc_ghcp_chat_thinking_tool.png │ │ └── windsurf_tut.png │ ├── INSTALLATION.md │ ├── LIBRARY_USAGE.md │ ├── local │ │ ├── DEEP_DIVE_ANALYSIS_2025-10-02.md │ │ ├── DEEP_DIVE_ANALYSIS_README.md │ │ ├── Deep_dive_p1_p2.md │ │ ├── integration-testing-plan.md │ │ ├── integration-tests-phase1-summary.md │ │ ├── N8N_AI_WORKFLOW_BUILDER_ANALYSIS.md │ │ ├── P0_IMPLEMENTATION_PLAN.md │ │ └── TEMPLATE_MINING_ANALYSIS.md │ ├── MCP_ESSENTIALS_README.md │ ├── MCP_QUICK_START_GUIDE.md │ ├── N8N_DEPLOYMENT.md │ ├── RAILWAY_DEPLOYMENT.md │ ├── README_CLAUDE_SETUP.md │ ├── README.md │ ├── tools-documentation-usage.md │ ├── VS_CODE_PROJECT_SETUP.md │ ├── WINDSURF_SETUP.md │ └── workflow-diff-examples.md ├── examples │ └── enhanced-documentation-demo.js ├── fetch_log.txt ├── LICENSE ├── MEMORY_N8N_UPDATE.md ├── MEMORY_TEMPLATE_UPDATE.md ├── monitor_fetch.sh ├── N8N_HTTP_STREAMABLE_SETUP.md ├── n8n-nodes.db ├── P0-R3-TEST-PLAN.md ├── package-lock.json ├── package.json ├── package.runtime.json ├── PRIVACY.md ├── railway.json ├── README.md ├── renovate.json ├── scripts │ ├── analyze-optimization.sh │ ├── audit-schema-coverage.ts │ ├── build-optimized.sh │ ├── compare-benchmarks.js │ ├── demo-optimization.sh │ ├── deploy-http.sh │ ├── deploy-to-vm.sh │ ├── export-webhook-workflows.ts │ ├── extract-changelog.js │ ├── extract-from-docker.js │ ├── extract-nodes-docker.sh │ ├── extract-nodes-simple.sh │ ├── format-benchmark-results.js │ ├── generate-benchmark-stub.js │ ├── generate-detailed-reports.js │ ├── generate-test-summary.js │ ├── http-bridge.js │ ├── mcp-http-client.js │ ├── migrate-nodes-fts.ts │ ├── migrate-tool-docs.ts │ ├── n8n-docs-mcp.service │ ├── nginx-n8n-mcp.conf │ ├── prebuild-fts5.ts │ ├── prepare-release.js │ ├── publish-npm-quick.sh │ ├── publish-npm.sh │ ├── quick-test.ts │ ├── run-benchmarks-ci.js │ ├── sync-runtime-version.js │ ├── test-ai-validation-debug.ts │ ├── test-code-node-enhancements.ts │ ├── test-code-node-fixes.ts │ ├── test-docker-config.sh │ ├── test-docker-fingerprint.ts │ ├── test-docker-optimization.sh │ ├── test-docker.sh │ ├── test-empty-connection-validation.ts │ ├── test-error-message-tracking.ts │ ├── test-error-output-validation.ts │ ├── test-error-validation.js │ ├── test-essentials.ts │ ├── test-expression-code-validation.ts │ ├── test-expression-format-validation.js │ ├── test-fts5-search.ts │ ├── test-fuzzy-fix.ts │ ├── test-fuzzy-simple.ts │ ├── test-helpers-validation.ts │ ├── test-http-search.ts │ ├── test-http.sh │ ├── test-jmespath-validation.ts │ ├── test-multi-tenant-simple.ts │ ├── test-multi-tenant.ts │ ├── test-n8n-integration.sh │ ├── test-node-info.js │ ├── test-node-type-validation.ts │ ├── test-nodes-base-prefix.ts │ ├── test-operation-validation.ts │ ├── test-optimized-docker.sh │ ├── test-release-automation.js │ ├── test-search-improvements.ts │ ├── test-security.ts │ ├── test-single-session.sh │ ├── test-sqljs-triggers.ts │ ├── test-telemetry-debug.ts │ ├── test-telemetry-direct.ts │ ├── test-telemetry-env.ts │ ├── test-telemetry-integration.ts │ ├── test-telemetry-no-select.ts │ ├── test-telemetry-security.ts │ ├── test-telemetry-simple.ts │ ├── test-typeversion-validation.ts │ ├── test-url-configuration.ts │ ├── test-user-id-persistence.ts │ ├── test-webhook-validation.ts │ ├── test-workflow-insert.ts │ ├── test-workflow-sanitizer.ts │ ├── test-workflow-tracking-debug.ts │ ├── update-and-publish-prep.sh │ ├── update-n8n-deps.js │ ├── update-readme-version.js │ ├── vitest-benchmark-json-reporter.js │ └── vitest-benchmark-reporter.ts ├── SECURITY.md ├── src │ ├── config │ │ └── n8n-api.ts │ ├── data │ │ └── canonical-ai-tool-examples.json │ ├── database │ │ ├── database-adapter.ts │ │ ├── migrations │ │ │ └── add-template-node-configs.sql │ │ ├── node-repository.ts │ │ ├── nodes.db │ │ ├── schema-optimized.sql │ │ └── schema.sql │ ├── errors │ │ └── validation-service-error.ts │ ├── http-server-single-session.ts │ ├── http-server.ts │ ├── index.ts │ ├── loaders │ │ └── node-loader.ts │ ├── mappers │ │ └── docs-mapper.ts │ ├── mcp │ │ ├── handlers-n8n-manager.ts │ │ ├── handlers-workflow-diff.ts │ │ ├── index.ts │ │ ├── server.ts │ │ ├── stdio-wrapper.ts │ │ ├── tool-docs │ │ │ ├── configuration │ │ │ │ ├── get-node-as-tool-info.ts │ │ │ │ ├── get-node-documentation.ts │ │ │ │ ├── get-node-essentials.ts │ │ │ │ ├── get-node-info.ts │ │ │ │ ├── get-property-dependencies.ts │ │ │ │ ├── index.ts │ │ │ │ └── search-node-properties.ts │ │ │ ├── discovery │ │ │ │ ├── get-database-statistics.ts │ │ │ │ ├── index.ts │ │ │ │ ├── list-ai-tools.ts │ │ │ │ ├── list-nodes.ts │ │ │ │ └── search-nodes.ts │ │ │ ├── guides │ │ │ │ ├── ai-agents-guide.ts │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── system │ │ │ │ ├── index.ts │ │ │ │ ├── n8n-diagnostic.ts │ │ │ │ ├── n8n-health-check.ts │ │ │ │ ├── n8n-list-available-tools.ts │ │ │ │ └── tools-documentation.ts │ │ │ ├── templates │ │ │ │ ├── get-template.ts │ │ │ │ ├── get-templates-for-task.ts │ │ │ │ ├── index.ts │ │ │ │ ├── list-node-templates.ts │ │ │ │ ├── list-tasks.ts │ │ │ │ ├── search-templates-by-metadata.ts │ │ │ │ └── search-templates.ts │ │ │ ├── types.ts │ │ │ ├── validation │ │ │ │ ├── index.ts │ │ │ │ ├── validate-node-minimal.ts │ │ │ │ ├── validate-node-operation.ts │ │ │ │ ├── validate-workflow-connections.ts │ │ │ │ ├── validate-workflow-expressions.ts │ │ │ │ └── validate-workflow.ts │ │ │ └── workflow_management │ │ │ ├── index.ts │ │ │ ├── n8n-autofix-workflow.ts │ │ │ ├── n8n-create-workflow.ts │ │ │ ├── n8n-delete-execution.ts │ │ │ ├── n8n-delete-workflow.ts │ │ │ ├── n8n-get-execution.ts │ │ │ ├── n8n-get-workflow-details.ts │ │ │ ├── n8n-get-workflow-minimal.ts │ │ │ ├── n8n-get-workflow-structure.ts │ │ │ ├── n8n-get-workflow.ts │ │ │ ├── n8n-list-executions.ts │ │ │ ├── n8n-list-workflows.ts │ │ │ ├── n8n-trigger-webhook-workflow.ts │ │ │ ├── n8n-update-full-workflow.ts │ │ │ ├── n8n-update-partial-workflow.ts │ │ │ └── n8n-validate-workflow.ts │ │ ├── tools-documentation.ts │ │ ├── tools-n8n-friendly.ts │ │ ├── tools-n8n-manager.ts │ │ ├── tools.ts │ │ └── workflow-examples.ts │ ├── mcp-engine.ts │ ├── mcp-tools-engine.ts │ ├── n8n │ │ ├── MCPApi.credentials.ts │ │ └── MCPNode.node.ts │ ├── parsers │ │ ├── node-parser.ts │ │ ├── property-extractor.ts │ │ └── simple-parser.ts │ ├── scripts │ │ ├── debug-http-search.ts │ │ ├── extract-from-docker.ts │ │ ├── fetch-templates-robust.ts │ │ ├── fetch-templates.ts │ │ ├── rebuild-database.ts │ │ ├── rebuild-optimized.ts │ │ ├── rebuild.ts │ │ ├── sanitize-templates.ts │ │ ├── seed-canonical-ai-examples.ts │ │ ├── test-autofix-documentation.ts │ │ ├── test-autofix-workflow.ts │ │ ├── test-execution-filtering.ts │ │ ├── test-node-suggestions.ts │ │ ├── test-protocol-negotiation.ts │ │ ├── test-summary.ts │ │ ├── test-webhook-autofix.ts │ │ ├── validate.ts │ │ └── validation-summary.ts │ ├── services │ │ ├── ai-node-validator.ts │ │ ├── ai-tool-validators.ts │ │ ├── confidence-scorer.ts │ │ ├── config-validator.ts │ │ ├── enhanced-config-validator.ts │ │ ├── example-generator.ts │ │ ├── execution-processor.ts │ │ ├── expression-format-validator.ts │ │ ├── expression-validator.ts │ │ ├── n8n-api-client.ts │ │ ├── n8n-validation.ts │ │ ├── node-documentation-service.ts │ │ ├── node-sanitizer.ts │ │ ├── node-similarity-service.ts │ │ ├── node-specific-validators.ts │ │ ├── operation-similarity-service.ts │ │ ├── property-dependencies.ts │ │ ├── property-filter.ts │ │ ├── resource-similarity-service.ts │ │ ├── sqlite-storage-service.ts │ │ ├── task-templates.ts │ │ ├── universal-expression-validator.ts │ │ ├── workflow-auto-fixer.ts │ │ ├── workflow-diff-engine.ts │ │ └── workflow-validator.ts │ ├── telemetry │ │ ├── batch-processor.ts │ │ ├── config-manager.ts │ │ ├── early-error-logger.ts │ │ ├── error-sanitization-utils.ts │ │ ├── error-sanitizer.ts │ │ ├── event-tracker.ts │ │ ├── event-validator.ts │ │ ├── index.ts │ │ ├── performance-monitor.ts │ │ ├── rate-limiter.ts │ │ ├── startup-checkpoints.ts │ │ ├── telemetry-error.ts │ │ ├── telemetry-manager.ts │ │ ├── telemetry-types.ts │ │ └── workflow-sanitizer.ts │ ├── templates │ │ ├── batch-processor.ts │ │ ├── metadata-generator.ts │ │ ├── README.md │ │ ├── template-fetcher.ts │ │ ├── template-repository.ts │ │ └── template-service.ts │ ├── types │ │ ├── index.ts │ │ ├── instance-context.ts │ │ ├── n8n-api.ts │ │ ├── node-types.ts │ │ └── workflow-diff.ts │ └── utils │ ├── auth.ts │ ├── bridge.ts │ ├── cache-utils.ts │ ├── console-manager.ts │ ├── documentation-fetcher.ts │ ├── enhanced-documentation-fetcher.ts │ ├── error-handler.ts │ ├── example-generator.ts │ ├── fixed-collection-validator.ts │ ├── logger.ts │ ├── mcp-client.ts │ ├── n8n-errors.ts │ ├── node-source-extractor.ts │ ├── node-type-normalizer.ts │ ├── node-type-utils.ts │ ├── node-utils.ts │ ├── npm-version-checker.ts │ ├── protocol-version.ts │ ├── simple-cache.ts │ ├── ssrf-protection.ts │ ├── template-node-resolver.ts │ ├── template-sanitizer.ts │ ├── url-detector.ts │ ├── validation-schemas.ts │ └── version.ts ├── test-output.txt ├── test-reinit-fix.sh ├── tests │ ├── __snapshots__ │ │ └── .gitkeep │ ├── auth.test.ts │ ├── benchmarks │ │ ├── database-queries.bench.ts │ │ ├── index.ts │ │ ├── mcp-tools.bench.ts │ │ ├── mcp-tools.bench.ts.disabled │ │ ├── mcp-tools.bench.ts.skip │ │ ├── node-loading.bench.ts.disabled │ │ ├── README.md │ │ ├── search-operations.bench.ts.disabled │ │ └── validation-performance.bench.ts.disabled │ ├── bridge.test.ts │ ├── comprehensive-extraction-test.js │ ├── data │ │ └── .gitkeep │ ├── debug-slack-doc.js │ ├── demo-enhanced-documentation.js │ ├── docker-tests-README.md │ ├── error-handler.test.ts │ ├── examples │ │ └── using-database-utils.test.ts │ ├── extracted-nodes-db │ │ ├── database-import.json │ │ ├── extraction-report.json │ │ ├── insert-nodes.sql │ │ ├── n8n-nodes-base__Airtable.json │ │ ├── n8n-nodes-base__Discord.json │ │ ├── n8n-nodes-base__Function.json │ │ ├── n8n-nodes-base__HttpRequest.json │ │ ├── n8n-nodes-base__If.json │ │ ├── n8n-nodes-base__Slack.json │ │ ├── n8n-nodes-base__SplitInBatches.json │ │ └── n8n-nodes-base__Webhook.json │ ├── factories │ │ ├── node-factory.ts │ │ └── property-definition-factory.ts │ ├── fixtures │ │ ├── .gitkeep │ │ ├── database │ │ │ └── test-nodes.json │ │ ├── factories │ │ │ ├── node.factory.ts │ │ │ └── parser-node.factory.ts │ │ └── template-configs.ts │ ├── helpers │ │ └── env-helpers.ts │ ├── http-server-auth.test.ts │ ├── integration │ │ ├── ai-validation │ │ │ ├── ai-agent-validation.test.ts │ │ │ ├── ai-tool-validation.test.ts │ │ │ ├── chat-trigger-validation.test.ts │ │ │ ├── e2e-validation.test.ts │ │ │ ├── helpers.ts │ │ │ ├── llm-chain-validation.test.ts │ │ │ ├── README.md │ │ │ └── TEST_REPORT.md │ │ ├── ci │ │ │ └── database-population.test.ts │ │ ├── database │ │ │ ├── connection-management.test.ts │ │ │ ├── empty-database.test.ts │ │ │ ├── fts5-search.test.ts │ │ │ ├── node-fts5-search.test.ts │ │ │ ├── node-repository.test.ts │ │ │ ├── performance.test.ts │ │ │ ├── sqljs-memory-leak.test.ts │ │ │ ├── template-node-configs.test.ts │ │ │ ├── template-repository.test.ts │ │ │ ├── test-utils.ts │ │ │ └── transactions.test.ts │ │ ├── database-integration.test.ts │ │ ├── docker │ │ │ ├── docker-config.test.ts │ │ │ ├── docker-entrypoint.test.ts │ │ │ └── test-helpers.ts │ │ ├── flexible-instance-config.test.ts │ │ ├── mcp │ │ │ └── template-examples-e2e.test.ts │ │ ├── mcp-protocol │ │ │ ├── basic-connection.test.ts │ │ │ ├── error-handling.test.ts │ │ │ ├── performance.test.ts │ │ │ ├── protocol-compliance.test.ts │ │ │ ├── README.md │ │ │ ├── session-management.test.ts │ │ │ ├── test-helpers.ts │ │ │ ├── tool-invocation.test.ts │ │ │ └── workflow-error-validation.test.ts │ │ ├── msw-setup.test.ts │ │ ├── n8n-api │ │ │ ├── executions │ │ │ │ ├── delete-execution.test.ts │ │ │ │ ├── get-execution.test.ts │ │ │ │ ├── list-executions.test.ts │ │ │ │ └── trigger-webhook.test.ts │ │ │ ├── scripts │ │ │ │ └── cleanup-orphans.ts │ │ │ ├── system │ │ │ │ ├── diagnostic.test.ts │ │ │ │ ├── health-check.test.ts │ │ │ │ └── list-tools.test.ts │ │ │ ├── test-connection.ts │ │ │ ├── types │ │ │ │ └── mcp-responses.ts │ │ │ ├── utils │ │ │ │ ├── cleanup-helpers.ts │ │ │ │ ├── credentials.ts │ │ │ │ ├── factories.ts │ │ │ │ ├── fixtures.ts │ │ │ │ ├── mcp-context.ts │ │ │ │ ├── n8n-client.ts │ │ │ │ ├── node-repository.ts │ │ │ │ ├── response-types.ts │ │ │ │ ├── test-context.ts │ │ │ │ └── webhook-workflows.ts │ │ │ └── workflows │ │ │ ├── autofix-workflow.test.ts │ │ │ ├── create-workflow.test.ts │ │ │ ├── delete-workflow.test.ts │ │ │ ├── get-workflow-details.test.ts │ │ │ ├── get-workflow-minimal.test.ts │ │ │ ├── get-workflow-structure.test.ts │ │ │ ├── get-workflow.test.ts │ │ │ ├── list-workflows.test.ts │ │ │ ├── smart-parameters.test.ts │ │ │ ├── update-partial-workflow.test.ts │ │ │ ├── update-workflow.test.ts │ │ │ └── validate-workflow.test.ts │ │ ├── security │ │ │ ├── command-injection-prevention.test.ts │ │ │ └── rate-limiting.test.ts │ │ ├── setup │ │ │ ├── integration-setup.ts │ │ │ └── msw-test-server.ts │ │ ├── telemetry │ │ │ ├── docker-user-id-stability.test.ts │ │ │ └── mcp-telemetry.test.ts │ │ ├── templates │ │ │ └── metadata-operations.test.ts │ │ └── workflow-creation-node-type-format.test.ts │ ├── logger.test.ts │ ├── MOCKING_STRATEGY.md │ ├── mocks │ │ ├── n8n-api │ │ │ ├── data │ │ │ │ ├── credentials.ts │ │ │ │ ├── executions.ts │ │ │ │ └── workflows.ts │ │ │ ├── handlers.ts │ │ │ └── index.ts │ │ └── README.md │ ├── node-storage-export.json │ ├── setup │ │ ├── global-setup.ts │ │ ├── msw-setup.ts │ │ ├── TEST_ENV_DOCUMENTATION.md │ │ └── test-env.ts │ ├── test-database-extraction.js │ ├── test-direct-extraction.js │ ├── test-enhanced-documentation.js │ ├── test-enhanced-integration.js │ ├── test-mcp-extraction.js │ ├── test-mcp-server-extraction.js │ ├── test-mcp-tools-integration.js │ ├── test-node-documentation-service.js │ ├── test-node-list.js │ ├── test-package-info.js │ ├── test-parsing-operations.js │ ├── test-slack-node-complete.js │ ├── test-small-rebuild.js │ ├── test-sqlite-search.js │ ├── test-storage-system.js │ ├── unit │ │ ├── __mocks__ │ │ │ ├── n8n-nodes-base.test.ts │ │ │ ├── n8n-nodes-base.ts │ │ │ └── README.md │ │ ├── database │ │ │ ├── __mocks__ │ │ │ │ └── better-sqlite3.ts │ │ │ ├── database-adapter-unit.test.ts │ │ │ ├── node-repository-core.test.ts │ │ │ ├── node-repository-operations.test.ts │ │ │ ├── node-repository-outputs.test.ts │ │ │ ├── README.md │ │ │ └── template-repository-core.test.ts │ │ ├── docker │ │ │ ├── config-security.test.ts │ │ │ ├── edge-cases.test.ts │ │ │ ├── parse-config.test.ts │ │ │ └── serve-command.test.ts │ │ ├── errors │ │ │ └── validation-service-error.test.ts │ │ ├── examples │ │ │ └── using-n8n-nodes-base-mock.test.ts │ │ ├── flexible-instance-security-advanced.test.ts │ │ ├── flexible-instance-security.test.ts │ │ ├── http-server │ │ │ └── multi-tenant-support.test.ts │ │ ├── http-server-n8n-mode.test.ts │ │ ├── http-server-n8n-reinit.test.ts │ │ ├── http-server-session-management.test.ts │ │ ├── loaders │ │ │ └── node-loader.test.ts │ │ ├── mappers │ │ │ └── docs-mapper.test.ts │ │ ├── mcp │ │ │ ├── get-node-essentials-examples.test.ts │ │ │ ├── handlers-n8n-manager-simple.test.ts │ │ │ ├── handlers-n8n-manager.test.ts │ │ │ ├── handlers-workflow-diff.test.ts │ │ │ ├── lru-cache-behavior.test.ts │ │ │ ├── multi-tenant-tool-listing.test.ts.disabled │ │ │ ├── parameter-validation.test.ts │ │ │ ├── search-nodes-examples.test.ts │ │ │ ├── tools-documentation.test.ts │ │ │ └── tools.test.ts │ │ ├── monitoring │ │ │ └── cache-metrics.test.ts │ │ ├── MULTI_TENANT_TEST_COVERAGE.md │ │ ├── multi-tenant-integration.test.ts │ │ ├── parsers │ │ │ ├── node-parser-outputs.test.ts │ │ │ ├── node-parser.test.ts │ │ │ ├── property-extractor.test.ts │ │ │ └── simple-parser.test.ts │ │ ├── scripts │ │ │ └── fetch-templates-extraction.test.ts │ │ ├── services │ │ │ ├── ai-node-validator.test.ts │ │ │ ├── ai-tool-validators.test.ts │ │ │ ├── confidence-scorer.test.ts │ │ │ ├── config-validator-basic.test.ts │ │ │ ├── config-validator-edge-cases.test.ts │ │ │ ├── config-validator-node-specific.test.ts │ │ │ ├── config-validator-security.test.ts │ │ │ ├── debug-validator.test.ts │ │ │ ├── enhanced-config-validator-integration.test.ts │ │ │ ├── enhanced-config-validator-operations.test.ts │ │ │ ├── enhanced-config-validator.test.ts │ │ │ ├── example-generator.test.ts │ │ │ ├── execution-processor.test.ts │ │ │ ├── expression-format-validator.test.ts │ │ │ ├── expression-validator-edge-cases.test.ts │ │ │ ├── expression-validator.test.ts │ │ │ ├── fixed-collection-validation.test.ts │ │ │ ├── loop-output-edge-cases.test.ts │ │ │ ├── n8n-api-client.test.ts │ │ │ ├── n8n-validation.test.ts │ │ │ ├── node-sanitizer.test.ts │ │ │ ├── node-similarity-service.test.ts │ │ │ ├── node-specific-validators.test.ts │ │ │ ├── operation-similarity-service-comprehensive.test.ts │ │ │ ├── operation-similarity-service.test.ts │ │ │ ├── property-dependencies.test.ts │ │ │ ├── property-filter-edge-cases.test.ts │ │ │ ├── property-filter.test.ts │ │ │ ├── resource-similarity-service-comprehensive.test.ts │ │ │ ├── resource-similarity-service.test.ts │ │ │ ├── task-templates.test.ts │ │ │ ├── template-service.test.ts │ │ │ ├── universal-expression-validator.test.ts │ │ │ ├── validation-fixes.test.ts │ │ │ ├── workflow-auto-fixer.test.ts │ │ │ ├── workflow-diff-engine.test.ts │ │ │ ├── workflow-fixed-collection-validation.test.ts │ │ │ ├── workflow-validator-comprehensive.test.ts │ │ │ ├── workflow-validator-edge-cases.test.ts │ │ │ ├── workflow-validator-error-outputs.test.ts │ │ │ ├── workflow-validator-expression-format.test.ts │ │ │ ├── workflow-validator-loops-simple.test.ts │ │ │ ├── workflow-validator-loops.test.ts │ │ │ ├── workflow-validator-mocks.test.ts │ │ │ ├── workflow-validator-performance.test.ts │ │ │ ├── workflow-validator-with-mocks.test.ts │ │ │ └── workflow-validator.test.ts │ │ ├── telemetry │ │ │ ├── batch-processor.test.ts │ │ │ ├── config-manager.test.ts │ │ │ ├── event-tracker.test.ts │ │ │ ├── event-validator.test.ts │ │ │ ├── rate-limiter.test.ts │ │ │ ├── telemetry-error.test.ts │ │ │ ├── telemetry-manager.test.ts │ │ │ ├── v2.18.3-fixes-verification.test.ts │ │ │ └── workflow-sanitizer.test.ts │ │ ├── templates │ │ │ ├── batch-processor.test.ts │ │ │ ├── metadata-generator.test.ts │ │ │ ├── template-repository-metadata.test.ts │ │ │ └── template-repository-security.test.ts │ │ ├── test-env-example.test.ts │ │ ├── test-infrastructure.test.ts │ │ ├── types │ │ │ ├── instance-context-coverage.test.ts │ │ │ └── instance-context-multi-tenant.test.ts │ │ ├── utils │ │ │ ├── auth-timing-safe.test.ts │ │ │ ├── cache-utils.test.ts │ │ │ ├── console-manager.test.ts │ │ │ ├── database-utils.test.ts │ │ │ ├── fixed-collection-validator.test.ts │ │ │ ├── n8n-errors.test.ts │ │ │ ├── node-type-normalizer.test.ts │ │ │ ├── node-type-utils.test.ts │ │ │ ├── node-utils.test.ts │ │ │ ├── simple-cache-memory-leak-fix.test.ts │ │ │ ├── ssrf-protection.test.ts │ │ │ └── template-node-resolver.test.ts │ │ └── validation-fixes.test.ts │ └── utils │ ├── assertions.ts │ ├── builders │ │ └── workflow.builder.ts │ ├── data-generators.ts │ ├── database-utils.ts │ ├── README.md │ └── test-helpers.ts ├── thumbnail.png ├── tsconfig.build.json ├── tsconfig.json ├── types │ ├── mcp.d.ts │ └── test-env.d.ts ├── verify-telemetry-fix.js ├── versioned-nodes.md ├── vitest.config.benchmark.ts ├── vitest.config.integration.ts └── vitest.config.ts ``` # Files -------------------------------------------------------------------------------- /src/services/task-templates.ts: -------------------------------------------------------------------------------- ```typescript /** * Task Templates Service * * @deprecated This module is deprecated as of v2.15.0 and will be removed in v2.16.0. * The get_node_for_task tool has been removed in favor of template-based configuration examples. * * Migration: * - Use `search_nodes({query: "webhook", includeExamples: true})` to find nodes with real template configs * - Use `get_node_essentials({nodeType: "nodes-base.webhook", includeExamples: true})` for top 3 examples * - New approach provides 2,646 real templates vs 31 hardcoded tasks * * Provides pre-configured node settings for common tasks. * This helps AI agents quickly configure nodes for specific use cases. */ export interface TaskTemplate { task: string; description: string; nodeType: string; configuration: Record<string, any>; userMustProvide: Array<{ property: string; description: string; example?: any; }>; optionalEnhancements?: Array<{ property: string; description: string; when?: string; }>; notes?: string[]; } export class TaskTemplates { private static templates: Record<string, TaskTemplate> = { // HTTP Request Tasks 'get_api_data': { task: 'get_api_data', description: 'Make a simple GET request to retrieve data from an API', nodeType: 'nodes-base.httpRequest', configuration: { method: 'GET', url: '', authentication: 'none', // Default error handling for API calls onError: 'continueRegularOutput', retryOnFail: true, maxTries: 3, waitBetweenTries: 1000, alwaysOutputData: true }, userMustProvide: [ { property: 'url', description: 'The API endpoint URL', example: 'https://api.example.com/users' } ], optionalEnhancements: [ { property: 'authentication', description: 'Add authentication if the API requires it', when: 'API requires authentication' }, { property: 'sendHeaders', description: 'Add custom headers if needed', when: 'API requires specific headers' }, { property: 'alwaysOutputData', description: 'Set to true to capture error responses', when: 'Need to debug API errors' } ] }, 'post_json_request': { task: 'post_json_request', description: 'Send JSON data to an API endpoint', nodeType: 'nodes-base.httpRequest', configuration: { method: 'POST', url: '', sendBody: true, contentType: 'json', specifyBody: 'json', jsonBody: '', // POST requests might modify data, so be careful with retries onError: 'continueRegularOutput', retryOnFail: true, maxTries: 2, waitBetweenTries: 1000, alwaysOutputData: true }, userMustProvide: [ { property: 'url', description: 'The API endpoint URL', example: 'https://api.example.com/users' }, { property: 'jsonBody', description: 'The JSON data to send', example: '{\n "name": "John Doe",\n "email": "[email protected]"\n}' } ], optionalEnhancements: [ { property: 'authentication', description: 'Add authentication if required' }, { property: 'onError', description: 'Set to "continueRegularOutput" for non-critical operations', when: 'Failure should not stop the workflow' } ], notes: [ 'Make sure jsonBody contains valid JSON', 'Content-Type header is automatically set to application/json', 'Be careful with retries on non-idempotent operations' ] }, 'call_api_with_auth': { task: 'call_api_with_auth', description: 'Make an authenticated API request', nodeType: 'nodes-base.httpRequest', configuration: { method: 'GET', url: '', authentication: 'genericCredentialType', genericAuthType: 'headerAuth', sendHeaders: true, // Authentication calls should handle auth failures gracefully onError: 'continueErrorOutput', retryOnFail: true, maxTries: 3, waitBetweenTries: 2000, alwaysOutputData: true, headerParameters: { parameters: [ { name: '', value: '' } ] } }, userMustProvide: [ { property: 'url', description: 'The API endpoint URL' }, { property: 'headerParameters.parameters[0].name', description: 'The header name for authentication', example: 'Authorization' }, { property: 'headerParameters.parameters[0].value', description: 'The authentication value', example: 'Bearer YOUR_API_KEY' } ], optionalEnhancements: [ { property: 'method', description: 'Change to POST/PUT/DELETE as needed' } ] }, // Webhook Tasks 'receive_webhook': { task: 'receive_webhook', description: 'Set up a webhook to receive data from external services', nodeType: 'nodes-base.webhook', configuration: { httpMethod: 'POST', path: 'webhook', responseMode: 'lastNode', responseData: 'allEntries', // Webhooks should always respond, even on error onError: 'continueRegularOutput', alwaysOutputData: true }, userMustProvide: [ { property: 'path', description: 'The webhook path (will be appended to your n8n URL)', example: 'github-webhook' } ], optionalEnhancements: [ { property: 'httpMethod', description: 'Change if the service sends GET/PUT/etc' }, { property: 'responseCode', description: 'Set custom response code (default 200)' } ], notes: [ 'The full webhook URL will be: https://your-n8n.com/webhook/[path]', 'Test URL will be different from production URL' ] }, 'webhook_with_response': { task: 'webhook_with_response', description: 'Receive webhook and send custom response', nodeType: 'nodes-base.webhook', configuration: { httpMethod: 'POST', path: 'webhook', responseMode: 'responseNode', responseData: 'firstEntryJson', responseCode: 200, // Ensure webhook always sends response onError: 'continueRegularOutput', alwaysOutputData: true }, userMustProvide: [ { property: 'path', description: 'The webhook path' } ], notes: [ 'Use with a Respond to Webhook node to send custom response', 'responseMode: responseNode requires a Respond to Webhook node' ] }, 'process_webhook_data': { task: 'process_webhook_data', description: 'Process incoming webhook data with Code node (shows correct data access)', nodeType: 'nodes-base.code', configuration: { language: 'javaScript', jsCode: `// ⚠️ CRITICAL: Webhook data is nested under 'body' property! // Connect this Code node after a Webhook node // Access webhook payload data - it's under .body, not directly under .json const webhookData = items[0].json.body; // ✅ CORRECT const headers = items[0].json.headers; // HTTP headers const query = items[0].json.query; // Query parameters // Common mistake to avoid: // const command = items[0].json.testCommand; // ❌ WRONG - will be undefined! // const command = items[0].json.body.testCommand; // ✅ CORRECT // Process the webhook data try { // Validate required fields if (!webhookData.command) { throw new Error('Missing required field: command'); } // Process based on command let result = {}; switch (webhookData.command) { case 'process': result = { status: 'processed', data: webhookData.data, processedAt: DateTime.now().toISO() }; break; case 'validate': result = { status: 'validated', isValid: true, validatedFields: Object.keys(webhookData.data || {}) }; break; default: result = { status: 'unknown_command', command: webhookData.command }; } // Return processed data return [{ json: { ...result, requestId: headers['x-request-id'] || crypto.randomUUID(), source: query.source || 'webhook', originalCommand: webhookData.command, metadata: { httpMethod: items[0].json.httpMethod, webhookPath: items[0].json.webhookPath, timestamp: DateTime.now().toISO() } } }]; } catch (error) { // Return error response return [{ json: { status: 'error', error: error.message, timestamp: DateTime.now().toISO() } }]; }`, onError: 'continueRegularOutput' }, userMustProvide: [], notes: [ '⚠️ WEBHOOK DATA IS AT items[0].json.body, NOT items[0].json', 'This is the most common webhook processing mistake', 'Headers are at items[0].json.headers', 'Query parameters are at items[0].json.query', 'Connect this Code node directly after a Webhook node' ] }, // Database Tasks 'query_postgres': { task: 'query_postgres', description: 'Query data from PostgreSQL database', nodeType: 'nodes-base.postgres', configuration: { operation: 'executeQuery', query: '', // Database reads can continue on error onError: 'continueRegularOutput', retryOnFail: true, maxTries: 3, waitBetweenTries: 1000 }, userMustProvide: [ { property: 'query', description: 'The SQL query to execute', example: 'SELECT * FROM users WHERE active = true LIMIT 10' } ], optionalEnhancements: [ { property: 'additionalFields.queryParams', description: 'Use parameterized queries for security', when: 'Using dynamic values' } ], notes: [ 'Always use parameterized queries to prevent SQL injection', 'Configure PostgreSQL credentials in n8n' ] }, 'insert_postgres_data': { task: 'insert_postgres_data', description: 'Insert data into PostgreSQL table', nodeType: 'nodes-base.postgres', configuration: { operation: 'insert', table: '', columns: '', returnFields: '*', // Database writes should stop on error by default onError: 'stopWorkflow', retryOnFail: true, maxTries: 2, waitBetweenTries: 1000 }, userMustProvide: [ { property: 'table', description: 'The table name', example: 'users' }, { property: 'columns', description: 'Comma-separated column names', example: 'name,email,created_at' } ], notes: [ 'Input data should match the column structure', 'Use expressions like {{ $json.fieldName }} to map data' ] }, // AI/LangChain Tasks 'chat_with_ai': { task: 'chat_with_ai', description: 'Send a message to an AI model and get response', nodeType: 'nodes-base.openAi', configuration: { resource: 'chat', operation: 'message', modelId: 'gpt-3.5-turbo', messages: { values: [ { role: 'user', content: '' } ] }, // AI calls should handle rate limits and API errors onError: 'continueRegularOutput', retryOnFail: true, maxTries: 3, waitBetweenTries: 5000, alwaysOutputData: true }, userMustProvide: [ { property: 'messages.values[0].content', description: 'The message to send to the AI', example: '{{ $json.userMessage }}' } ], optionalEnhancements: [ { property: 'modelId', description: 'Change to gpt-4 for better results' }, { property: 'options.temperature', description: 'Adjust creativity (0-1)' }, { property: 'options.maxTokens', description: 'Limit response length' } ] }, 'ai_agent_workflow': { task: 'ai_agent_workflow', description: 'Create an AI agent that can use tools', nodeType: 'nodes-langchain.agent', configuration: { text: '', outputType: 'output', systemMessage: 'You are a helpful assistant.' }, userMustProvide: [ { property: 'text', description: 'The input prompt for the agent', example: '{{ $json.query }}' } ], optionalEnhancements: [ { property: 'systemMessage', description: 'Customize the agent\'s behavior' } ], notes: [ 'Connect tool nodes to give the agent capabilities', 'Configure the AI model credentials' ] }, // Data Processing Tasks 'transform_data': { task: 'transform_data', description: 'Transform data structure using JavaScript', nodeType: 'nodes-base.code', configuration: { language: 'javaScript', jsCode: `// Transform each item const results = []; for (const item of items) { results.push({ json: { // Transform your data here id: item.json.id, processedAt: new Date().toISOString() } }); } return results;` }, userMustProvide: [], notes: [ 'Access input data via items array', 'Each item has a json property with the data', 'Return array of objects with json property' ] }, 'filter_data': { task: 'filter_data', description: 'Filter items based on conditions', nodeType: 'nodes-base.if', configuration: { conditions: { conditions: [ { leftValue: '', rightValue: '', operator: { type: 'string', operation: 'equals' } } ] } }, userMustProvide: [ { property: 'conditions.conditions[0].leftValue', description: 'The value to check', example: '{{ $json.status }}' }, { property: 'conditions.conditions[0].rightValue', description: 'The value to compare against', example: 'active' } ], notes: [ 'True output contains matching items', 'False output contains non-matching items' ] }, // Communication Tasks 'send_slack_message': { task: 'send_slack_message', description: 'Send a message to Slack channel', nodeType: 'nodes-base.slack', configuration: { resource: 'message', operation: 'post', channel: '', text: '', // Messaging can continue on error onError: 'continueRegularOutput', retryOnFail: true, maxTries: 2, waitBetweenTries: 2000 }, userMustProvide: [ { property: 'channel', description: 'The Slack channel', example: '#general' }, { property: 'text', description: 'The message text', example: 'New order received: {{ $json.orderId }}' } ], optionalEnhancements: [ { property: 'attachments', description: 'Add rich message attachments' }, { property: 'blocks', description: 'Use Block Kit for advanced formatting' } ] }, 'send_email': { task: 'send_email', description: 'Send an email notification', nodeType: 'nodes-base.emailSend', configuration: { fromEmail: '', toEmail: '', subject: '', text: '', // Email sending should retry on transient failures onError: 'continueRegularOutput', retryOnFail: true, maxTries: 3, waitBetweenTries: 3000, alwaysOutputData: true }, userMustProvide: [ { property: 'fromEmail', description: 'Sender email address', example: '[email protected]' }, { property: 'toEmail', description: 'Recipient email address', example: '{{ $json.customerEmail }}' }, { property: 'subject', description: 'Email subject', example: 'Order Confirmation #{{ $json.orderId }}' }, { property: 'text', description: 'Email body (plain text)', example: 'Thank you for your order!' } ], optionalEnhancements: [ { property: 'html', description: 'Use HTML for rich formatting' }, { property: 'attachments', description: 'Attach files to the email' } ] }, // AI Tool Usage Tasks 'use_google_sheets_as_tool': { task: 'use_google_sheets_as_tool', description: 'Use Google Sheets as an AI tool for reading/writing data', nodeType: 'nodes-base.googleSheets', configuration: { operation: 'append', sheetId: '={{ $fromAI("sheetId", "The Google Sheets ID") }}', range: '={{ $fromAI("range", "The range to append to, e.g. A:Z") }}', dataMode: 'autoMap' }, userMustProvide: [ { property: 'Google Sheets credentials', description: 'Configure Google Sheets API credentials in n8n' }, { property: 'Tool name in AI Agent', description: 'Give it a descriptive name like "Log Results to Sheet"' }, { property: 'Tool description', description: 'Describe when and how the AI should use this tool' } ], notes: [ 'Connect this node to the ai_tool port of an AI Agent node', 'The AI can dynamically determine sheetId and range using $fromAI', 'Works great for logging AI analysis results or reading data for processing' ] }, 'use_slack_as_tool': { task: 'use_slack_as_tool', description: 'Use Slack as an AI tool for sending notifications', nodeType: 'nodes-base.slack', configuration: { resource: 'message', operation: 'post', channel: '={{ $fromAI("channel", "The Slack channel, e.g. #general") }}', text: '={{ $fromAI("message", "The message to send") }}', attachments: [] }, userMustProvide: [ { property: 'Slack credentials', description: 'Configure Slack OAuth2 credentials in n8n' }, { property: 'Tool configuration in AI Agent', description: 'Name it something like "Send Slack Notification"' } ], notes: [ 'Perfect for AI agents that need to notify teams', 'The AI determines channel and message content dynamically', 'Can be enhanced with blocks for rich formatting' ] }, 'multi_tool_ai_agent': { task: 'multi_tool_ai_agent', description: 'AI agent with multiple tools for complex automation', nodeType: 'nodes-langchain.agent', configuration: { text: '={{ $json.query }}', outputType: 'output', systemMessage: 'You are an intelligent assistant with access to multiple tools. Use them wisely to complete tasks.' }, userMustProvide: [ { property: 'AI model credentials', description: 'OpenAI, Anthropic, or other LLM credentials' }, { property: 'Multiple tool nodes', description: 'Connect various nodes to the ai_tool port' }, { property: 'Tool descriptions', description: 'Clear descriptions for each connected tool' } ], optionalEnhancements: [ { property: 'Memory', description: 'Add memory nodes for conversation context' }, { property: 'Custom tools', description: 'Create Code nodes as custom tools' } ], notes: [ 'Connect multiple nodes: HTTP Request, Slack, Google Sheets, etc.', 'Each tool should have a clear, specific purpose', 'Test each tool individually before combining', 'Set N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true for community nodes' ] }, // Error Handling Templates 'api_call_with_retry': { task: 'api_call_with_retry', description: 'Resilient API call with automatic retry on failure', nodeType: 'nodes-base.httpRequest', configuration: { method: 'GET', url: '', // Retry configuration for transient failures retryOnFail: true, maxTries: 5, waitBetweenTries: 2000, // Always capture response for debugging alwaysOutputData: true, // Add request tracking sendHeaders: true, headerParameters: { parameters: [ { name: 'X-Request-ID', value: '={{ $workflow.id }}-{{ $itemIndex }}' } ] } }, userMustProvide: [ { property: 'url', description: 'The API endpoint to call', example: 'https://api.example.com/resource/{{ $json.id }}' } ], optionalEnhancements: [ { property: 'authentication', description: 'Add API authentication' }, { property: 'onError', description: 'Change to "stopWorkflow" for critical API calls', when: 'This is a critical API call that must succeed' } ], notes: [ 'Retries help with rate limits and transient network issues', 'waitBetweenTries prevents hammering the API', 'alwaysOutputData captures error responses for debugging', 'Consider exponential backoff for production use' ] }, 'fault_tolerant_processing': { task: 'fault_tolerant_processing', description: 'Data processing that continues despite individual item failures', nodeType: 'nodes-base.code', configuration: { language: 'javaScript', jsCode: `// Process items with error handling const results = []; for (const item of items) { try { // Your processing logic here const processed = { ...item.json, processed: true, timestamp: new Date().toISOString() }; results.push({ json: processed }); } catch (error) { // Log error but continue processing console.error('Processing failed for item:', item.json.id, error); // Add error item to results results.push({ json: { ...item.json, error: error.message, processed: false } }); } } return results;`, // Continue workflow even if code fails entirely onError: 'continueRegularOutput', alwaysOutputData: true }, userMustProvide: [ { property: 'Processing logic', description: 'Replace the comment with your data transformation logic' } ], optionalEnhancements: [ { property: 'Error notification', description: 'Add IF node after to handle error items separately' } ], notes: [ 'Individual item failures won\'t stop processing of other items', 'Error items are marked and can be handled separately', 'continueOnFail ensures workflow continues even on total failure' ] }, 'webhook_with_error_handling': { task: 'webhook_with_error_handling', description: 'Webhook that gracefully handles processing errors', nodeType: 'nodes-base.webhook', configuration: { httpMethod: 'POST', path: 'resilient-webhook', responseMode: 'responseNode', responseData: 'firstEntryJson', // Always continue to ensure response is sent onError: 'continueRegularOutput', alwaysOutputData: true }, userMustProvide: [ { property: 'path', description: 'Unique webhook path', example: 'order-processor' }, { property: 'Respond to Webhook node', description: 'Add node to send appropriate success/error responses' } ], optionalEnhancements: [ { property: 'Validation', description: 'Add IF node to validate webhook payload' }, { property: 'Error logging', description: 'Add error handler node for failed requests' } ], notes: [ 'onError: continueRegularOutput ensures webhook always sends a response', 'Use Respond to Webhook node to send appropriate status codes', 'Log errors but don\'t expose internal errors to webhook callers', 'Consider rate limiting for public webhooks' ] }, // Modern Error Handling Patterns 'modern_error_handling_patterns': { task: 'modern_error_handling_patterns', description: 'Examples of modern error handling using onError property', nodeType: 'nodes-base.httpRequest', configuration: { method: 'GET', url: '', // Modern error handling approach onError: 'continueRegularOutput', // Options: continueRegularOutput, continueErrorOutput, stopWorkflow retryOnFail: true, maxTries: 3, waitBetweenTries: 2000, alwaysOutputData: true }, userMustProvide: [ { property: 'url', description: 'The API endpoint' }, { property: 'onError', description: 'Choose error handling strategy', example: 'continueRegularOutput' } ], notes: [ 'onError replaces the deprecated continueOnFail property', 'continueRegularOutput: Continue with normal output on error', 'continueErrorOutput: Route errors to error output for special handling', 'stopWorkflow: Stop the entire workflow on error', 'Combine with retryOnFail for resilient workflows' ] }, 'database_transaction_safety': { task: 'database_transaction_safety', description: 'Database operations with proper error handling', nodeType: 'nodes-base.postgres', configuration: { operation: 'executeQuery', query: 'BEGIN; INSERT INTO orders ...; COMMIT;', // For transactions, don\'t retry automatically onError: 'continueErrorOutput', retryOnFail: false, alwaysOutputData: true }, userMustProvide: [ { property: 'query', description: 'Your SQL query or transaction' } ], notes: [ 'Transactions should not be retried automatically', 'Use continueErrorOutput to handle errors separately', 'Consider implementing compensating transactions', 'Always log transaction failures for audit' ] }, 'ai_rate_limit_handling': { task: 'ai_rate_limit_handling', description: 'AI API calls with rate limit handling', nodeType: 'nodes-base.openAi', configuration: { resource: 'chat', operation: 'message', modelId: 'gpt-4', messages: { values: [ { role: 'user', content: '' } ] }, // Handle rate limits with exponential backoff onError: 'continueRegularOutput', retryOnFail: true, maxTries: 5, waitBetweenTries: 5000, alwaysOutputData: true }, userMustProvide: [ { property: 'messages.values[0].content', description: 'The prompt for the AI' } ], notes: [ 'AI APIs often have rate limits', 'Longer wait times help avoid hitting limits', 'Consider implementing exponential backoff in Code node', 'Monitor usage to stay within quotas' ] }, // Code Node Tasks 'custom_ai_tool': { task: 'custom_ai_tool', description: 'Create a custom tool for AI agents using Code node', nodeType: 'nodes-base.code', configuration: { language: 'javaScript', mode: 'runOnceForEachItem', jsCode: `// Custom AI Tool - Example: Text Analysis // This code will be called by AI agents with $json containing the input // Access the input from the AI agent const text = $json.text || ''; const operation = $json.operation || 'analyze'; // Perform the requested operation let result = {}; switch (operation) { case 'wordCount': result = { wordCount: text.split(/\\s+/).filter(word => word.length > 0).length, characterCount: text.length, lineCount: text.split('\\n').length }; break; case 'extract': // Extract specific patterns (emails, URLs, etc.) result = { emails: text.match(/[\\w.-]+@[\\w.-]+\\.\\w+/g) || [], urls: text.match(/https?:\\/\\/[^\\s]+/g) || [], numbers: text.match(/\\b\\d+\\b/g) || [] }; break; default: result = { error: 'Unknown operation', availableOperations: ['wordCount', 'extract'] }; } return [{ json: { ...result, originalText: text, operation: operation, processedAt: DateTime.now().toISO() } }];`, onError: 'continueRegularOutput' }, userMustProvide: [], notes: [ 'Connect this to AI Agent node\'s tool input', 'AI will pass data in $json', 'Use "Run Once for Each Item" mode for AI tools', 'Return structured data the AI can understand' ] }, 'aggregate_data': { task: 'aggregate_data', description: 'Aggregate data from multiple items into summary statistics', nodeType: 'nodes-base.code', configuration: { language: 'javaScript', jsCode: `// Aggregate data from all items const stats = { count: 0, sum: 0, min: Infinity, max: -Infinity, values: [], categories: {}, errors: [] }; // Process each item for (const item of items) { try { const value = item.json.value || item.json.amount || 0; const category = item.json.category || 'uncategorized'; stats.count++; stats.sum += value; stats.min = Math.min(stats.min, value); stats.max = Math.max(stats.max, value); stats.values.push(value); // Count by category stats.categories[category] = (stats.categories[category] || 0) + 1; } catch (error) { stats.errors.push({ item: item.json, error: error.message }); } } // Calculate additional statistics const average = stats.count > 0 ? stats.sum / stats.count : 0; const sorted = [...stats.values].sort((a, b) => a - b); const median = sorted.length > 0 ? sorted[Math.floor(sorted.length / 2)] : 0; return [{ json: { totalItems: stats.count, sum: stats.sum, average: average, median: median, min: stats.min === Infinity ? 0 : stats.min, max: stats.max === -Infinity ? 0 : stats.max, categoryCounts: stats.categories, errorCount: stats.errors.length, errors: stats.errors, processedAt: DateTime.now().toISO() } }];`, onError: 'continueRegularOutput' }, userMustProvide: [], notes: [ 'Assumes items have "value" or "amount" field', 'Groups by "category" field if present', 'Returns single item with all statistics', 'Handles errors gracefully' ] }, 'batch_process_with_api': { task: 'batch_process_with_api', description: 'Process items in batches with API calls', nodeType: 'nodes-base.code', configuration: { language: 'javaScript', jsCode: `// Batch process items with API calls const BATCH_SIZE = 10; const API_URL = 'https://api.example.com/batch-process'; // USER MUST UPDATE const results = []; // Process items in batches for (let i = 0; i < items.length; i += BATCH_SIZE) { const batch = items.slice(i, i + BATCH_SIZE); try { // Prepare batch data const batchData = batch.map(item => ({ id: item.json.id, data: item.json })); // Make API request for batch const response = await $helpers.httpRequest({ method: 'POST', url: API_URL, body: { items: batchData }, headers: { 'Content-Type': 'application/json' } }); // Add results if (response.results && Array.isArray(response.results)) { response.results.forEach((result, index) => { results.push({ json: { ...batch[index].json, ...result, batchNumber: Math.floor(i / BATCH_SIZE) + 1, processedAt: DateTime.now().toISO() } }); }); } // Add delay between batches to avoid rate limits if (i + BATCH_SIZE < items.length) { await new Promise(resolve => setTimeout(resolve, 1000)); } } catch (error) { // Add failed batch items with error batch.forEach(item => { results.push({ json: { ...item.json, error: error.message, status: 'failed', batchNumber: Math.floor(i / BATCH_SIZE) + 1 } }); }); } } return results;`, onError: 'continueRegularOutput', retryOnFail: true, maxTries: 2 }, userMustProvide: [ { property: 'jsCode', description: 'Update API_URL in the code', example: 'https://your-api.com/batch' } ], notes: [ 'Processes items in batches of 10', 'Includes delay between batches', 'Handles batch failures gracefully', 'Update API_URL and adjust BATCH_SIZE as needed' ] }, 'error_safe_transform': { task: 'error_safe_transform', description: 'Transform data with comprehensive error handling', nodeType: 'nodes-base.code', configuration: { language: 'javaScript', jsCode: `// Safe data transformation with validation const results = []; const errors = []; for (const item of items) { try { // Validate required fields const required = ['id', 'name']; // USER SHOULD UPDATE const missing = required.filter(field => !item.json[field]); if (missing.length > 0) { throw new Error(\`Missing required fields: \${missing.join(', ')}\`); } // Transform data with type checking const transformed = { // Ensure ID is string id: String(item.json.id), // Clean and validate name name: String(item.json.name).trim(), // Parse numbers safely amount: parseFloat(item.json.amount) || 0, // Parse dates safely date: item.json.date ? DateTime.fromISO(item.json.date).isValid ? DateTime.fromISO(item.json.date).toISO() : null : null, // Boolean conversion isActive: Boolean(item.json.active || item.json.isActive), // Array handling tags: Array.isArray(item.json.tags) ? item.json.tags.filter(tag => typeof tag === 'string') : [], // Nested object handling metadata: typeof item.json.metadata === 'object' ? item.json.metadata : {}, // Add processing info processedAt: DateTime.now().toISO(), originalIndex: items.indexOf(item) }; results.push({ json: transformed }); } catch (error) { errors.push({ json: { error: error.message, originalData: item.json, index: items.indexOf(item), status: 'failed' } }); } } // Add summary at the end results.push({ json: { _summary: { totalProcessed: results.length - errors.length, totalErrors: errors.length, successRate: ((results.length - errors.length) / items.length * 100).toFixed(2) + '%', timestamp: DateTime.now().toISO() } } }); // Include errors at the end return [...results, ...errors];`, onError: 'continueRegularOutput' }, userMustProvide: [ { property: 'jsCode', description: 'Update required fields array', example: "const required = ['id', 'email', 'name'];" } ], notes: [ 'Validates all data types', 'Handles missing/invalid data gracefully', 'Returns both successful and failed items', 'Includes processing summary' ] }, 'async_data_processing': { task: 'async_data_processing', description: 'Process data with async operations and proper error handling', nodeType: 'nodes-base.code', configuration: { language: 'javaScript', jsCode: `// Async processing with concurrent limits const CONCURRENT_LIMIT = 5; const results = []; // Process items with concurrency control async function processItem(item, index) { try { // Simulate async operation (replace with actual logic) // Example: API call, database query, file operation await new Promise(resolve => setTimeout(resolve, 100)); // Actual processing logic here const processed = { ...item.json, processed: true, index: index, timestamp: DateTime.now().toISO() }; // Example async operation - external API call if (item.json.needsEnrichment) { const enrichment = await $helpers.httpRequest({ method: 'GET', url: \`https://api.example.com/enrich/\${item.json.id}\` }); processed.enrichment = enrichment; } return { json: processed }; } catch (error) { return { json: { ...item.json, error: error.message, status: 'failed', index: index } }; } } // Process in batches with concurrency limit for (let i = 0; i < items.length; i += CONCURRENT_LIMIT) { const batch = items.slice(i, i + CONCURRENT_LIMIT); const batchPromises = batch.map((item, batchIndex) => processItem(item, i + batchIndex) ); const batchResults = await Promise.all(batchPromises); results.push(...batchResults); } return results;`, onError: 'continueRegularOutput', retryOnFail: true, maxTries: 2 }, userMustProvide: [], notes: [ 'Processes 5 items concurrently', 'Prevents overwhelming external services', 'Each item processed independently', 'Errors don\'t affect other items' ] }, 'python_data_analysis': { task: 'python_data_analysis', description: 'Analyze data using Python with statistics', nodeType: 'nodes-base.code', configuration: { language: 'python', pythonCode: `# Python data analysis - use underscore prefix for built-in variables import json from datetime import datetime import statistics # Collect data for analysis values = [] categories = {} dates = [] # Use _input.all() to get items in Python for item in _input.all(): # Convert JsProxy to Python dict for safe access item_data = item.json.to_py() # Extract numeric values if 'value' in item_data or 'amount' in item_data: value = item_data.get('value', item_data.get('amount', 0)) if isinstance(value, (int, float)): values.append(value) # Count categories category = item_data.get('category', 'uncategorized') categories[category] = categories.get(category, 0) + 1 # Collect dates if 'date' in item_data: dates.append(item_data['date']) # Calculate statistics result = { 'itemCount': len(_input.all()), 'values': { 'count': len(values), 'sum': sum(values) if values else 0, 'mean': statistics.mean(values) if values else 0, 'median': statistics.median(values) if values else 0, 'min': min(values) if values else 0, 'max': max(values) if values else 0, 'stdev': statistics.stdev(values) if len(values) > 1 else 0 }, 'categories': categories, 'dateRange': { 'earliest': min(dates) if dates else None, 'latest': max(dates) if dates else None, 'count': len(dates) }, 'analysis': { 'hasNumericData': len(values) > 0, 'hasCategoricalData': len(categories) > 0, 'hasTemporalData': len(dates) > 0, 'dataQuality': 'good' if len(values) > len(items) * 0.8 else 'partial' }, 'processedAt': datetime.now().isoformat() } # Return single summary item return [{'json': result}]`, onError: 'continueRegularOutput' }, userMustProvide: [], notes: [ 'Uses Python statistics module', 'Analyzes numeric, categorical, and date data', 'Returns comprehensive summary', 'Handles missing data gracefully' ] } }; /** * Get all available tasks */ static getAllTasks(): string[] { return Object.keys(this.templates); } /** * Get tasks for a specific node type */ static getTasksForNode(nodeType: string): string[] { return Object.entries(this.templates) .filter(([_, template]) => template.nodeType === nodeType) .map(([task, _]) => task); } /** * Get a specific task template */ static getTaskTemplate(task: string): TaskTemplate | undefined { return this.templates[task]; } /** * Get a specific task template (alias for getTaskTemplate) */ static getTemplate(task: string): TaskTemplate | undefined { return this.getTaskTemplate(task); } /** * Search for tasks by keyword */ static searchTasks(keyword: string): string[] { const lower = keyword.toLowerCase(); return Object.entries(this.templates) .filter(([task, template]) => task.toLowerCase().includes(lower) || template.description.toLowerCase().includes(lower) || template.nodeType.toLowerCase().includes(lower) ) .map(([task, _]) => task); } /** * Get task categories */ static getTaskCategories(): Record<string, string[]> { return { 'HTTP/API': ['get_api_data', 'post_json_request', 'call_api_with_auth', 'api_call_with_retry'], 'Webhooks': ['receive_webhook', 'webhook_with_response', 'webhook_with_error_handling', 'process_webhook_data'], 'Database': ['query_postgres', 'insert_postgres_data', 'database_transaction_safety'], 'AI/LangChain': ['chat_with_ai', 'ai_agent_workflow', 'multi_tool_ai_agent', 'ai_rate_limit_handling'], 'Data Processing': ['transform_data', 'filter_data', 'fault_tolerant_processing', 'process_webhook_data'], 'Communication': ['send_slack_message', 'send_email'], 'AI Tool Usage': ['use_google_sheets_as_tool', 'use_slack_as_tool', 'multi_tool_ai_agent'], 'Error Handling': ['modern_error_handling_patterns', 'api_call_with_retry', 'fault_tolerant_processing', 'webhook_with_error_handling', 'database_transaction_safety', 'ai_rate_limit_handling'] }; } } ``` -------------------------------------------------------------------------------- /docs/local/P0_IMPLEMENTATION_PLAN.md: -------------------------------------------------------------------------------- ```markdown # P0 Priorities Implementation Plan ## Critical Fixes for n8n-mcp Based on Production Telemetry Data **Date:** October 2, 2025 **Analysis Period:** September 26 - October 2, 2025 **Data Volume:** 212,375 events | 5,751 workflows | 2,119 users **Target:** Reduce error rate from 5-10% to <2% --- ## Executive Summary This document provides a comprehensive implementation plan for the three P0 (Priority 0 - Critical) issues identified through deep analysis of production telemetry data. These fixes will eliminate **80% of all validation errors** and significantly improve the AI agent experience. ### Impact Summary | Issue | Current Failure Rate | Post-Fix Target | Affected Users | Estimated Effort | |-------|---------------------|-----------------|----------------|------------------| | **P0-R1**: Node Type Prefix Normalization | 80% of validation errors | <1% | Hundreds | 2 days | | **P0-R2**: Null-Safety Audit | 10-18% TypeError rate | <1% | 30+ | 2 days | | **P0-R3**: Pre-extract Template Configs + Remove get_node_for_task | 28% failure rate, 5.9% coverage | N/A (tool removed), 100% coverage | 197 (migrated) | 5 days | **Total Effort:** 2 weeks (v2.15.0 release) --- ## Table of Contents 1. [P0-R1: Auto-Normalize Node Type Prefixes](#p0-r1-auto-normalize-node-type-prefixes) 2. [P0-R2: Complete Null-Safety Audit](#p0-r2-complete-null-safety-audit) 3. [P0-R3: Pre-extract Template Configurations + Remove get_node_for_task](#p0-r3-pre-extract-template-configurations--remove-get_node_for_task) 4. [Implementation Order & Timeline](#implementation-order--timeline) 5. [Testing Strategy](#testing-strategy) 6. [Rollback Plan](#rollback-plan) 7. [Success Metrics](#success-metrics) --- ## P0-R1: Auto-Normalize Node Type Prefixes ### Problem Statement **Impact:** 4,800+ validation errors (80% of all validation errors) from a single root cause AI agents frequently produce `nodes-base.X` instead of `n8n-nodes-base.X`, causing validation failures. This is the single largest source of user frustration. **Example Error:** ``` Error: Invalid node type: "nodes-base.set". Use "n8n-nodes-base.set" instead. ``` ### Root Cause Analysis **Current Implementation Issues:** 1. **Existing normalization is BACKWARD:** - `src/utils/node-type-utils.ts` normalizes TO short form (`nodes-base.`) - But validation expects full form (`n8n-nodes-base.`) - This is the **opposite** of what we need 2. **Location of the bug:** ```typescript // src/utils/node-type-utils.ts:18-20 return type .replace(/^n8n-nodes-base\./, 'nodes-base.') // ❌ WRONG DIRECTION .replace(/^@n8n\/n8n-nodes-langchain\./, 'nodes-langchain.'); ``` 3. **Why AI agents produce short form:** - Token efficiency (LLMs abbreviate to save tokens) - Pattern learning from examples - Natural language preference for concise names ### Solution Architecture **Strategy:** Normalize ALL node types to FULL form before validation #### 1. Create Universal Node Type Normalizer **File:** `src/utils/node-type-normalizer.ts` (NEW) ```typescript /** * Universal Node Type Normalizer * * Converts ANY node type variation to the canonical full form expected by n8n * * Handles: * - Short form → Full form (nodes-base.X → n8n-nodes-base.X) * - Already full form → Unchanged * - LangChain nodes → Proper @n8n/ prefix */ export interface NodeTypeNormalizationResult { original: string; normalized: string; wasNormalized: boolean; package: 'base' | 'langchain' | 'community' | 'unknown'; } export class NodeTypeNormalizer { /** * Normalize node type to canonical full form * * @example * normalizeToFullForm('nodes-base.webhook') * // → 'n8n-nodes-base.webhook' * * normalizeToFullForm('n8n-nodes-base.webhook') * // → 'n8n-nodes-base.webhook' (unchanged) * * normalizeToFullForm('nodes-langchain.agent') * // → '@n8n/n8n-nodes-langchain.agent' */ static normalizeToFullForm(type: string): string { if (!type || typeof type !== 'string') { return type; } // Already in full form - return unchanged if (type.startsWith('n8n-nodes-base.')) { return type; } if (type.startsWith('@n8n/n8n-nodes-langchain.')) { return type; } // Normalize short forms to full form if (type.startsWith('nodes-base.')) { return type.replace(/^nodes-base\./, 'n8n-nodes-base.'); } if (type.startsWith('nodes-langchain.')) { return type.replace(/^nodes-langchain\./, '@n8n/n8n-nodes-langchain.'); } if (type.startsWith('n8n-nodes-langchain.')) { return type.replace(/^n8n-nodes-langchain\./, '@n8n/n8n-nodes-langchain.'); } // No prefix - might be community node or error return type; } /** * Normalize with detailed result */ static normalizeWithDetails(type: string): NodeTypeNormalizationResult { const original = type; const normalized = this.normalizeToFullForm(type); return { original, normalized, wasNormalized: original !== normalized, package: this.detectPackage(normalized) }; } /** * Detect package type from node type */ private static detectPackage(type: string): 'base' | 'langchain' | 'community' | 'unknown' { if (type.startsWith('n8n-nodes-base.')) return 'base'; if (type.startsWith('@n8n/n8n-nodes-langchain.')) return 'langchain'; if (type.includes('.')) return 'community'; return 'unknown'; } /** * Batch normalize multiple node types */ static normalizeBatch(types: string[]): Map<string, string> { const result = new Map<string, string>(); for (const type of types) { result.set(type, this.normalizeToFullForm(type)); } return result; } /** * Normalize all node types in a workflow */ static normalizeWorkflowNodeTypes(workflow: any): any { if (!workflow?.nodes || !Array.isArray(workflow.nodes)) { return workflow; } return { ...workflow, nodes: workflow.nodes.map((node: any) => ({ ...node, type: this.normalizeToFullForm(node.type) })) }; } } ``` #### 2. Apply Normalization in All Entry Points **File:** `src/services/workflow-validator.ts` **Change at line 250:** (validateWorkflowStructure method) ```typescript // BEFORE (line 250-252): const normalizedType = normalizeNodeType(singleNode.type); const isWebhook = normalizedType === 'nodes-base.webhook' || normalizedType === 'nodes-base.webhookTrigger'; // AFTER: import { NodeTypeNormalizer } from '../utils/node-type-normalizer'; const normalizedType = NodeTypeNormalizer.normalizeToFullForm(singleNode.type); const isWebhook = normalizedType === 'n8n-nodes-base.webhook' || normalizedType === 'n8n-nodes-base.webhookTrigger'; ``` **Change at line 368-376:** (validateAllNodes method) ```typescript // BEFORE: // Get node definition - try multiple formats let nodeInfo = this.nodeRepository.getNode(node.type); // If not found, try with normalized type if (!nodeInfo) { const normalizedType = normalizeNodeType(node.type); if (normalizedType !== node.type) { nodeInfo = this.nodeRepository.getNode(normalizedType); } } // AFTER: // Normalize node type FIRST const normalizedType = NodeTypeNormalizer.normalizeToFullForm(node.type); const nodeInfo = this.nodeRepository.getNode(normalizedType); // Update node type in place if normalized if (normalizedType !== node.type) { node.type = normalizedType; } ``` **File:** `src/mcp/handlers-n8n-manager.ts` **Add normalization in handleCreateWorkflow (line 281-310):** ```typescript // BEFORE validation: const input = createWorkflowSchema.parse(args); // AFTER: Add normalization const input = createWorkflowSchema.parse(args); // Normalize all node types before validation const normalizedInput = NodeTypeNormalizer.normalizeWorkflowNodeTypes(input); // Validate workflow structure const errors = validateWorkflowStructure(normalizedInput); ``` **Apply same pattern to:** - `handleUpdateWorkflow` (line 520) - `validateWorkflow` tool handler - Any other workflow creation/update entry points #### 3. Update Node Repository for Flexible Lookups **File:** `src/database/node-repository.ts` **Enhance getNode method (line 54):** ```typescript /** * Get node with automatic type normalization */ getNode(nodeType: string): any { // Try normalized type first const normalizedType = NodeTypeNormalizer.normalizeToFullForm(nodeType); const row = this.db.prepare(` SELECT * FROM nodes WHERE node_type = ? `).get(normalizedType) as any; if (!row) { // Fallback: try original type if normalization didn't help if (normalizedType !== nodeType) { const originalRow = this.db.prepare(` SELECT * FROM nodes WHERE node_type = ? `).get(nodeType) as any; if (originalRow) return this.parseNodeRow(originalRow); } return null; } return this.parseNodeRow(row); } ``` ### Testing Requirements **File:** `tests/unit/utils/node-type-normalizer.test.ts` (NEW) ```typescript describe('NodeTypeNormalizer', () => { describe('normalizeToFullForm', () => { it('should normalize short base form to full form', () => { expect(NodeTypeNormalizer.normalizeToFullForm('nodes-base.webhook')) .toBe('n8n-nodes-base.webhook'); }); it('should normalize short langchain form to full form', () => { expect(NodeTypeNormalizer.normalizeToFullForm('nodes-langchain.agent')) .toBe('@n8n/n8n-nodes-langchain.agent'); }); it('should leave full forms unchanged', () => { expect(NodeTypeNormalizer.normalizeToFullForm('n8n-nodes-base.webhook')) .toBe('n8n-nodes-base.webhook'); }); it('should handle edge cases', () => { expect(NodeTypeNormalizer.normalizeToFullForm('')).toBe(''); expect(NodeTypeNormalizer.normalizeToFullForm(null as any)).toBe(null); }); }); describe('normalizeWorkflowNodeTypes', () => { it('should normalize all nodes in workflow', () => { const workflow = { nodes: [ { type: 'nodes-base.webhook', id: '1', name: 'Webhook' }, { type: 'nodes-base.set', id: '2', name: 'Set' } ], connections: {} }; const result = NodeTypeNormalizer.normalizeWorkflowNodeTypes(workflow); expect(result.nodes[0].type).toBe('n8n-nodes-base.webhook'); expect(result.nodes[1].type).toBe('n8n-nodes-base.set'); }); }); }); ``` ### Success Criteria - [x] All workflow validation tests pass with both short and full node type forms - [x] 0 "Invalid node type" errors for variations of core nodes - [x] Telemetry shows <1% validation errors related to node type prefixes - [x] No breaking changes to existing workflows **Status:** ✅ COMPLETED (October 2, 2025) **Commit:** ed7de10 ### Estimated Effort **Total: 2-4 hours** - Implementation: 1-2 hours - Testing: 1 hour - Documentation: 30 minutes - Code review: 30 minutes --- ## P0-R2: Complete Null-Safety Audit ### Problem Statement **Impact:** 10-18% TypeError failures in node information tools affecting 1,000+ calls ``` TypeError: Cannot read property 'text' of undefined ``` **Affected Tools:** - `get_node_essentials`: 483 failures (10% of 4,909 calls) - `get_node_info`: 352 failures (18% of 1,988 calls) - `get_node_documentation`: 136 failures (7% of 1,919 calls) ### Root Cause Analysis **From CHANGELOG 2.14.0:** > "Fixed TypeErrors in get_node_info, get_node_essentials, and get_node_documentation tools" > "Added null safety checks for undefined node properties" **The fix was incomplete.** Residual issues remain in: 1. Nested property access without guards 2. Edge cases with unusual/legacy node structures 3. Missing properties in database 4. Assumptions about property structure ### Current Implementation Analysis **File:** `src/database/node-repository.ts` **Problem areas identified:** ```typescript // Line 73-78: Good - has safeJsonParse properties: this.safeJsonParse(row.properties_schema, []), operations: this.safeJsonParse(row.operations, []), credentials: this.safeJsonParse(row.credentials_required, []), // But doesn't protect against: // - properties being null after parse // - Nested properties like properties[0].description.text // - Missing fields in properties array ``` **handlers for get_node_essentials/info need to be found and audited** ### Solution Architecture #### 1. Enhanced Safe Property Access Utilities **File:** `src/utils/safe-property-access.ts` (NEW) ```typescript /** * Safe Property Access Utilities * * Provides defensive property access with fallbacks */ export class SafePropertyAccess { /** * Safely get nested property with default */ static get<T>(obj: any, path: string, defaultValue: T): T { if (!obj || typeof obj !== 'object') return defaultValue; const keys = path.split('.'); let current = obj; for (const key of keys) { if (current === null || current === undefined) { return defaultValue; } if (typeof current !== 'object') { return defaultValue; } current = current[key]; } return current !== undefined ? current : defaultValue; } /** * Safely get array with default */ static getArray<T>(obj: any, path: string, defaultValue: T[] = []): T[] { const value = this.get(obj, path, defaultValue); return Array.isArray(value) ? value : defaultValue; } /** * Safely get string with default */ static getString(obj: any, path: string, defaultValue: string = ''): string { const value = this.get(obj, path, defaultValue); return typeof value === 'string' ? value : defaultValue; } /** * Safely get number with default */ static getNumber(obj: any, path: string, defaultValue: number = 0): number { const value = this.get(obj, path, defaultValue); return typeof value === 'number' && !isNaN(value) ? value : defaultValue; } /** * Safely get boolean with default */ static getBoolean(obj: any, path: string, defaultValue: boolean = false): boolean { const value = this.get(obj, path, defaultValue); return typeof value === 'boolean' ? value : defaultValue; } /** * Extract description from multiple possible locations */ static extractDescription(obj: any): string { // Try common description locations const locations = [ 'description', 'properties.description', 'properties.description.text', 'subtitle', 'displayName' ]; for (const location of locations) { const value = this.getString(obj, location); if (value) return value; } return 'No description available'; } /** * Extract display name from multiple possible locations */ static extractDisplayName(obj: any, fallback: string = 'Unknown'): string { const locations = [ 'displayName', 'name', 'label', 'title' ]; for (const location of locations) { const value = this.getString(obj, location); if (value) return value; } return fallback; } } ``` #### 2. Null-Safe Node Repository Methods **File:** `src/database/node-repository.ts` **Refactor getNode method (line 54):** ```typescript import { SafePropertyAccess } from '../utils/safe-property-access'; /** * Get node with comprehensive null-safety */ getNode(nodeType: string): any | null { try { // Normalize type first const normalizedType = NodeTypeNormalizer.normalizeToFullForm(nodeType); const row = this.db.prepare(` SELECT * FROM nodes WHERE node_type = ? `).get(normalizedType) as any; if (!row) return null; // Use safe property access for all fields return { nodeType: SafePropertyAccess.getString(row, 'node_type', normalizedType), displayName: SafePropertyAccess.extractDisplayName(row, SafePropertyAccess.getString(row, 'display_name', 'Unknown Node')), description: SafePropertyAccess.extractDescription(row), category: SafePropertyAccess.getString(row, 'category', 'Uncategorized'), developmentStyle: SafePropertyAccess.getString(row, 'development_style', 'declarative'), package: SafePropertyAccess.getString(row, 'package_name', 'unknown'), isAITool: SafePropertyAccess.getBoolean(row, 'is_ai_tool', false), isTrigger: SafePropertyAccess.getBoolean(row, 'is_trigger', false), isWebhook: SafePropertyAccess.getBoolean(row, 'is_webhook', false), isVersioned: SafePropertyAccess.getBoolean(row, 'is_versioned', false), version: SafePropertyAccess.getNumber(row, 'version', 1), properties: this.safeParseProperties(row.properties_schema), operations: this.safeParseArray(row.operations), credentials: this.safeParseArray(row.credentials_required), hasDocumentation: !!row.documentation, outputs: row.outputs ? this.safeJsonParse(row.outputs, null) : null, outputNames: row.output_names ? this.safeJsonParse(row.output_names, null) : null }; } catch (error) { console.error(`Error getting node ${nodeType}:`, error); return null; } } /** * Safely parse properties with validation */ private safeParseProperties(json: string): any[] { try { const parsed = JSON.parse(json); if (!Array.isArray(parsed)) return []; // Validate each property has minimum required fields return parsed.map(prop => ({ name: SafePropertyAccess.getString(prop, 'name', 'unknown'), displayName: SafePropertyAccess.extractDisplayName(prop), type: SafePropertyAccess.getString(prop, 'type', 'string'), required: SafePropertyAccess.getBoolean(prop, 'required', false), default: prop.default !== undefined ? prop.default : null, description: SafePropertyAccess.extractDescription(prop), options: SafePropertyAccess.getArray(prop, 'options', []), displayOptions: prop.displayOptions || null })); } catch { return []; } } /** * Safely parse array field */ private safeParseArray(json: string): any[] { try { const parsed = JSON.parse(json); return Array.isArray(parsed) ? parsed : []; } catch { return []; } } ``` #### 3. Find and Fix Handler Functions **Action Required:** Search for handler functions that call getNode and add null checks **Pattern to search for:** ```bash grep -r "getNode\|getNodeEssentials\|getNodeInfo" src/mcp/ --include="*.ts" ``` **Add null checks like:** ```typescript const node = repository.getNode(nodeType); if (!node) { return { success: false, error: `Node type "${nodeType}" not found. Use search_nodes to find available nodes.` }; } ``` ### Testing Requirements **File:** `tests/unit/database/node-repository-null-safety.test.ts` (NEW) ```typescript describe('NodeRepository - Null Safety', () => { it('should handle node with missing description', () => { // Insert node with minimal data const node = { type: 'test.node', name: 'Test' }; db.prepare('INSERT INTO nodes (node_type, display_name) VALUES (?, ?)').run(node.type, node.name); const result = repository.getNode('test.node'); expect(result).not.toBeNull(); expect(result.description).toBe('No description available'); expect(result.properties).toEqual([]); }); it('should handle node with malformed JSON', () => { db.prepare('INSERT INTO nodes (node_type, properties_schema) VALUES (?, ?)').run('test.node', 'invalid json'); const result = repository.getNode('test.node'); expect(result).not.toBeNull(); expect(result.properties).toEqual([]); }); it('should handle non-existent node gracefully', () => { const result = repository.getNode('non.existent'); expect(result).toBeNull(); }); it('should handle null database row', () => { // Simulate database returning null const result = repository.getNode('null.node'); expect(result).toBeNull(); }); }); ``` ### Success Criteria - [ ] get_node_essentials failure rate: 10% → <1% - [ ] get_node_info failure rate: 18% → <1% - [ ] get_node_documentation failure rate: 7% → <1% - [ ] 100% test coverage for null cases - [ ] No TypeErrors in production logs ### Estimated Effort **Total: 1 day (8 hours)** - Safe property access utility: 2 hours - Repository refactoring: 3 hours - Handler updates: 2 hours - Testing: 1 hour --- ## P0-R3: Pre-extract Template Configurations + Remove get_node_for_task ### Problem Statement **Impact:** 28% failure rate (worst-performing tool) + redundant with better alternatives `get_node_for_task` failing 109 times out of 392 calls (27.8%) **Current State:** - Only 31 predefined tasks in `task-templates.ts` (5.9% node coverage) - 22.5:1 usage ratio favoring `search_nodes` (8,839 calls vs 392) - Hardcoded configurations require manual maintenance - Tool provides no unique value over `search_nodes` **Discovery:** We have 2,646 real production workflow templates from n8n.io with: - 3,820 httpRequest configurations - 1,700 googleSheets configurations - 466 webhook configurations - 100% AI-generated metadata coverage - Real-world best practices and patterns ### Architectural Decision: Pre-extraction **Analysis:** On-the-fly vs Pre-extraction (see `/docs/local/TEMPLATE_MINING_ANALYSIS.md`) **Decision:** Pre-extract node configurations into separate table **Rationale:** - **Performance:** 1ms vs 30-60ms (30-60x faster) - **Storage:** Only 513 KB for 2,625 configs (negligible) - **Simplicity:** No cache management, TTL, or eviction logic - **Features:** Enables filtering by complexity, auth (indexed queries) - **Scalability:** Handles 10,000+ templates without degradation - **Predictability:** Consistent sub-millisecond response times **Trade-offs (acceptable):** - +30-60 seconds rebuild time (rare operation) - Incremental updates needed when templates change ### Solution Architecture **Strategy:** 1. Pre-extract top 10 node configurations per node type into new table 2. Enhance `get_node_essentials` with optional examples 3. Enhance `search_nodes` with optional examples 4. **Remove** `get_node_for_task` entirely (no redirect) See `/docs/local/TEMPLATE_MINING_ANALYSIS.md` for complete analysis #### 1. Add Database Schema for Pre-extracted Configurations **File:** `src/database/schema.sql` Add new table after `templates` table: ```sql -- Pre-extracted node configurations from templates CREATE TABLE template_node_configs ( id INTEGER PRIMARY KEY, node_type TEXT NOT NULL, template_id INTEGER NOT NULL, template_name TEXT NOT NULL, template_views INTEGER DEFAULT 0, -- Node configuration (extracted from workflow) node_name TEXT, -- Node name in workflow (e.g., "HTTP Request") parameters_json TEXT NOT NULL, -- JSON: node.parameters credentials_json TEXT, -- JSON: node.credentials (if present) -- Pre-calculated metadata for filtering has_credentials INTEGER DEFAULT 0, has_expressions INTEGER DEFAULT 0, -- Contains {{...}} or $json/$node complexity TEXT CHECK(complexity IN ('simple', 'medium', 'complex')), use_cases TEXT, -- JSON array from template.metadata.use_cases -- Pre-calculated ranking (1 = best, 2 = second best, etc.) rank INTEGER DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (template_id) REFERENCES templates(id) ON DELETE CASCADE ); -- Indexes for fast queries CREATE INDEX idx_config_node_type_rank ON template_node_configs(node_type, rank); CREATE INDEX idx_config_complexity ON template_node_configs(node_type, complexity, rank); CREATE INDEX idx_config_auth ON template_node_configs(node_type, has_credentials, rank); -- View for easy querying of top configs CREATE VIEW ranked_node_configs AS SELECT node_type, template_name, template_views, parameters_json, credentials_json, has_credentials, has_expressions, complexity, use_cases, rank FROM template_node_configs WHERE rank <= 5 -- Top 5 per node type ORDER BY node_type, rank; ``` **Migration Script:** `src/database/migrations/add-template-node-configs.sql` ```sql -- Migration for existing databases -- Run during `npm run rebuild` or `npm run fetch:templates` -- Check if table exists CREATE TABLE IF NOT EXISTS template_node_configs ( -- ... schema as above ); -- Populate from existing templates -- (handled by extraction logic in fetch:templates script) ``` #### 2. Add Extraction Logic to fetch:templates Script **File:** `src/scripts/fetch-templates.ts` Add extraction function: ```typescript import gzip from 'zlib'; /** * Extract node configurations from a template workflow */ function extractNodeConfigs( templateId: number, templateName: string, templateViews: number, workflowCompressed: string, metadata: any ): Array<{ node_type: string; template_id: number; template_name: string; template_views: number; node_name: string; parameters_json: string; credentials_json: string | null; has_credentials: number; has_expressions: number; complexity: string; use_cases: string; }> { try { // Decompress workflow const decompressed = gzip.gunzipSync(Buffer.from(workflowCompressed, 'base64')); const workflow = JSON.parse(decompressed.toString('utf-8')); const configs: any[] = []; for (const node of workflow.nodes || []) { // Skip UI-only nodes if (node.type.includes('stickyNote') || !node.parameters) { continue; } configs.push({ node_type: node.type, template_id: templateId, template_name: templateName, template_views: templateViews, node_name: node.name, parameters_json: JSON.stringify(node.parameters), credentials_json: node.credentials ? JSON.stringify(node.credentials) : null, has_credentials: node.credentials ? 1 : 0, has_expressions: detectExpressions(node.parameters) ? 1 : 0, complexity: metadata?.complexity || 'medium', use_cases: JSON.stringify(metadata?.use_cases || []) }); } return configs; } catch (error) { console.error(`Error extracting configs from template ${templateId}:`, error); return []; } } /** * Detect n8n expressions in parameters */ function detectExpressions(params: any): boolean { const json = JSON.stringify(params); return json.includes('={{') || json.includes('$json') || json.includes('$node'); } /** * Insert extracted configs into database and rank them */ function insertAndRankConfigs(db: Database, configs: any[]) { // Clear old configs for these templates const templateIds = [...new Set(configs.map(c => c.template_id))]; db.prepare(`DELETE FROM template_node_configs WHERE template_id IN (${templateIds.join(',')})`).run(); // Insert new configs const insertStmt = db.prepare(` INSERT INTO template_node_configs ( node_type, template_id, template_name, template_views, node_name, parameters_json, credentials_json, has_credentials, has_expressions, complexity, use_cases ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `); for (const config of configs) { insertStmt.run( config.node_type, config.template_id, config.template_name, config.template_views, config.node_name, config.parameters_json, config.credentials_json, config.has_credentials, config.has_expressions, config.complexity, config.use_cases ); } // Rank configs per node_type by template popularity db.exec(` UPDATE template_node_configs SET rank = ( SELECT COUNT(*) + 1 FROM template_node_configs AS t2 WHERE t2.node_type = template_node_configs.node_type AND t2.template_views > template_node_configs.template_views ) `); // Keep only top 10 per node_type db.exec(` DELETE FROM template_node_configs WHERE id NOT IN ( SELECT id FROM template_node_configs WHERE rank <= 10 ) `); console.log(`Extracted and ranked ${configs.length} node configurations`); } ``` #### 3. Enhance get_node_essentials with Examples **File:** `src/mcp/handlers-*.ts` or `src/mcp/server.ts` Update `get_node_essentials` handler: ```typescript async function getNodeEssentials( nodeType: string, options?: { includeExamples?: boolean } ): Promise<any> { const node = repository.getNode(nodeType); if (!node) { return { success: false, error: `Node type "${nodeType}" not found. Use search_nodes to find available nodes.` }; } const result = { nodeType, displayName: node.displayName, description: node.description, category: node.category, // ... existing essentials fields ... }; // NEW: Add real-world examples if requested if (options?.includeExamples) { const examples = db.prepare(` SELECT parameters_json, template_name, template_views, complexity, use_cases, has_credentials, has_expressions FROM template_node_configs WHERE node_type = ? ORDER BY rank LIMIT 3 `).all(nodeType); result.examples = examples.map(ex => ({ config: JSON.parse(ex.parameters_json), source: `${ex.template_name} (${(ex.template_views / 1000).toFixed(0)}k views)`, complexity: ex.complexity, useCases: JSON.parse(ex.use_cases).slice(0, 2), hasAuth: ex.has_credentials === 1, hasExpressions: ex.has_expressions === 1 })); } return result; } ``` **Tool definition update:** ```typescript { name: 'get_node_essentials', description: 'Get essential information about a specific n8n node type...', inputSchema: { type: 'object', properties: { nodeType: { type: 'string', description: 'Full node type (e.g., "n8n-nodes-base.httpRequest")' }, includeExamples: { // NEW type: 'boolean', description: 'Include 2-3 real configuration examples from popular templates', default: false } }, required: ['nodeType'] } } ``` #### 4. Enhance search_nodes with Examples **File:** `src/mcp/handlers-*.ts` or `src/mcp/server.ts` Update `search_nodes` handler: ```typescript async function searchNodes( query: string, options?: { limit?: number; includeExamples?: boolean; } ): Promise<any> { const nodes = repository.searchNodes(query, 'OR', options?.limit || 20); const results = nodes.map(node => { const result = { nodeType: node.nodeType, displayName: node.displayName, description: node.description, category: node.category }; // NEW: Add examples if requested if (options?.includeExamples) { const examples = db.prepare(` SELECT parameters_json, template_name, complexity FROM template_node_configs WHERE node_type = ? ORDER BY rank LIMIT 2 `).all(node.nodeType); result.examples = examples.map(ex => ({ config: JSON.parse(ex.parameters_json), source: ex.template_name, complexity: ex.complexity })); } return result; }); return results; } ``` **Tool definition update:** ```typescript { name: 'search_nodes', description: 'Search for n8n nodes by keyword...', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query' }, limit: { type: 'number', default: 20 }, includeExamples: { // NEW type: 'boolean', description: 'Include 2 real configuration examples per node', default: false } }, required: ['query'] } } ``` #### 5. Remove get_node_for_task Tool Entirely **Files to modify:** 1. **`src/mcp/server.ts`** - Remove handler function 2. **`src/mcp/tools.ts`** - Remove tool definition 3. **`src/mcp/tools-documentation.ts`** - Remove from documentation 4. **`src/services/task-templates.ts`** - Can be deprecated (keep for now, remove in v2.16.0) 5. **`README.md`** - Remove from available tools list 6. **`CHANGELOG.md`** - Document removal **Steps:** ```bash # Search for all references grep -r "get_node_for_task" src/ grep -r "getNodeForTask" src/ # Remove handler # Remove tool definition # Remove from documentation # Update README ``` **Migration note for users (add to CHANGELOG):** ```markdown ### BREAKING CHANGES in v2.15.0 - **Removed:** `get_node_for_task` tool - **Replacement:** Use `search_nodes` with `includeExamples: true` - **Migration:** `get_node_for_task({task: "webhook"})` → `search_nodes({query: "webhook", includeExamples: true})` - **Benefit:** Access to 2,646 real templates vs 31 hardcoded tasks ``` ### Testing Requirements **File:** `tests/unit/services/template-config-extraction.test.ts` (NEW) ```typescript describe('Template Config Extraction', () => { it('should extract node configs from workflow', () => { const workflow = { nodes: [ { type: 'n8n-nodes-base.httpRequest', name: 'HTTP Request', parameters: { url: 'https://api.example.com', method: 'GET' } } ] }; const configs = extractNodeConfigs(1, 'Test', 1000, compressWorkflow(workflow), {}); expect(configs).toHaveLength(1); expect(configs[0].node_type).toBe('n8n-nodes-base.httpRequest'); }); it('should detect expressions in parameters', () => { const params = { url: '={{$json.api_url}}' }; expect(detectExpressions(params)).toBe(true); }); it('should rank configs by popularity', () => { // Insert configs with different views // Verify ranking order }); }); ``` **File:** `tests/integration/enhanced-tools.test.ts` (NEW) ```typescript describe('Enhanced Tools with Examples', () => { it('get_node_essentials should return examples when requested', async () => { const result = await getNodeEssentials('n8n-nodes-base.httpRequest', { includeExamples: true }); expect(result.examples).toBeDefined(); expect(result.examples.length).toBeGreaterThan(0); expect(result.examples[0].config).toHaveProperty('url'); }); it('search_nodes should return examples when requested', async () => { const result = await searchNodes('webhook', { includeExamples: true }); expect(result.length).toBeGreaterThan(0); expect(result[0].examples).toBeDefined(); }); it('get_node_for_task should not exist', async () => { expect(toolRegistry.has('get_node_for_task')).toBe(false); }); }); ``` ### Success Criteria - [ ] Extract 2,000+ node configurations from templates - [ ] Query performance: <1ms for pre-extracted configs - [ ] `get_node_essentials` with examples: <5ms total - [ ] `search_nodes` with examples: <10ms total - [ ] Database size increase: <1 MB - [ ] `get_node_for_task` completely removed from codebase - [ ] All documentation updated ### Estimated Effort **Total: 1 week (5 days)** - **Day 1:** Database schema + migration (8 hours) - Design schema - Create migration script - Test with existing database - **Day 2:** Extraction logic in fetch:templates (8 hours) - Write extraction function - Write ranking logic - Test with 2,646 templates - **Day 3:** Enhance get_node_essentials + search_nodes (8 hours) - Add includeExamples parameter - Update tool definitions - Integration testing - **Day 4:** Remove get_node_for_task + documentation (8 hours) - Remove from all files - Update README, CHANGELOG - Update tools_documentation - Migration guide - **Day 5:** Testing + optimization (8 hours) - Unit tests - Integration tests - Performance testing - Bug fixes --- ## Implementation Order & Timeline ### Version 2.15.0 - All P0 Fixes in One Release **Total Timeline:** 2 weeks (10 working days) ### Week 1: Foundation + P0-R1 + P0-R2 **Monday (Day 1-2): P0-R1 - Node Type Normalization** - AM: Create NodeTypeNormalizer utility - PM: Apply to workflow validator, handlers, and repository - Testing and validation - **Deliverable:** 80% of validation errors eliminated **Tuesday (Day 3): P0-R2 - Null-Safety Audit (Part 1)** - AM: Create SafePropertyAccess utility - PM: Refactor node repository methods - **Deliverable:** Safe property access framework **Wednesday (Day 4): P0-R2 - Null-Safety Audit (Part 2)** - AM: Find and fix all handlers - PM: Comprehensive null-safety testing - **Deliverable:** 10-18% TypeError rate → <1% **Thursday (Day 5): P0-R3 - Database Schema** - AM: Design and implement template_node_configs table - PM: Create migration script and test with existing database - **Deliverable:** Schema ready for extraction **Friday (Day 6): P0-R3 - Extraction Logic** - AM: Write extraction function in fetch:templates - PM: Write ranking logic and test with 2,646 templates - **Deliverable:** 2,000+ configs extracted and ranked ### Week 2: P0-R3 Integration + Testing + Documentation **Monday (Day 7): Tool Enhancements** - AM: Enhance get_node_essentials with includeExamples - PM: Enhance search_nodes with includeExamples - **Deliverable:** Both tools return real examples **Tuesday (Day 8): Tool Removal + Documentation** - AM: Remove get_node_for_task from all files - PM: Update README, CHANGELOG, tools_documentation - **Deliverable:** Clean removal, migration guide complete **Wednesday (Day 9): Comprehensive Testing** - AM: Unit tests for extraction and enhanced tools - PM: Integration tests for all P0 fixes - **Deliverable:** 95%+ test coverage **Thursday (Day 10): Performance + Final Testing** - AM: Performance testing and optimization - PM: E2E testing and bug fixes - **Deliverable:** All success criteria met **Friday (Day 11): Release Preparation** - AM: Code review and documentation review - PM: Prepare release notes, tag v2.15.0 - **Deliverable:** Ready for release ### Parallel Activities - **Documentation updates:** Days 1-11 - **Code reviews:** End of Days 2, 4, 6, 8, 10 - **Telemetry preparation:** Day 10-11 (prepare monitoring dashboard) --- ## Testing Strategy ### Unit Tests **Coverage Target:** 95% for new code - **Node Type Normalizer:** 20+ test cases - **Safe Property Access:** 30+ test cases - **Task Discovery Service:** 40+ test cases ### Integration Tests - Workflow validation with mixed node type forms - Node repository with edge case data - Task discovery with real node database ### E2E Tests - Create workflow with short-form node types → Should succeed - Get node info for nodes with missing properties → Should return safe defaults - Query task discovery with variations → Should find matches ### Regression Tests - All existing tests must pass - No breaking changes to public APIs ### Performance Tests - Normalization overhead: <1ms per workflow - Safe property access: <0.1ms per node - Task discovery: <50ms average --- ## Rollback Plan ### If P0-R1 Causes Issues 1. **Symptom:** Workflows fail validation after normalization 2. **Action:** Revert node-type-normalizer changes 3. **Fallback:** Use original normalizeNodeType 4. **Recovery time:** 15 minutes ### If P0-R2 Causes Performance Issues 1. **Symptom:** Node lookup becomes slow 2. **Action:** Cache safe property access results 3. **Fallback:** Keep safe parsing but reduce validation 4. **Recovery time:** 1 hour ### If P0-R3 Template Extraction Causes Issues 1. **Symptom:** Database bloat or slow queries 2. **Action:** Reduce rank limit from 10 to 5 per node 3. **Fallback:** Disable includeExamples parameter temporarily 4. **Recovery time:** 15 minutes (just disable parameter) ### If get_node_for_task Removal Causes User Issues 1. **Symptom:** Users report missing tool 2. **Action:** Add prominent migration guide to error messages 3. **Fallback:** N/A (breaking change, users must migrate) 4. **Communication:** Update docs, add migration examples --- ## Success Metrics ### Overall Goals | Metric | Current | Target | How to Measure | |--------|---------|--------|----------------| | Overall error rate | 5-10% | <2% | Telemetry events | | Validation errors | 4,800/week | <100/week | Error logs | | TypeError rate | 10-18% | <1% | Tool execution logs | | Node configs extracted | 0 | 2,000+ | Database count | | Config query performance | N/A | <1ms | Performance tests | | get_node_for_task usage | 392 calls | 0 (removed) | Tool usage stats | | search_nodes w/ examples | 0 | Monitored | New feature adoption | ### Telemetry Monitoring After deployment, monitor for 1 week: - Error rate by tool (should decrease 80-90%) - User success rate (should increase 5-10%) - Average errors per user (should decrease from 2.5 to <0.5) --- ## Dependencies ### NPM Packages No new NPM packages required - all functionality uses existing dependencies. ### Internal Dependencies - **P0-R3** requires database schema update (template_node_configs table) - **P0-R3** requires migration script for existing databases - All changes are backward compatible except removal of `get_node_for_task` --- ## Documentation Updates ### Files to Update 1. **CHANGELOG.md** - Add entries for each P0 fix + breaking changes 2. **README.md** - Remove get_node_for_task, add includeExamples parameter 3. **src/mcp/tools-documentation.ts** - Remove get_node_for_task documentation 4. **API.md** - Document enhanced tool parameters 5. **MIGRATION.md** - Add migration guide from get_node_for_task to search_nodes (NEW) ### Example CHANGELOG Entry ```markdown ## [2.15.0] - 2025-10-09 ### BREAKING CHANGES - **Removed:** `get_node_for_task` tool - **Replacement:** Use `search_nodes` with `includeExamples: true` - **Migration:** `get_node_for_task({task: "webhook"})` → `search_nodes({query: "webhook", includeExamples: true})` - **Benefit:** Access to 2,646 real templates vs 31 hardcoded tasks ### Fixed - **P0-R1:** Auto-normalize node type prefixes (eliminates 80% of validation errors) - **P0-R2:** Complete null-safety audit for node information tools (reduces TypeError failures from 10-18% to <1%) ### Added - `NodeTypeNormalizer` utility for universal node type normalization - `SafePropertyAccess` utility for defensive property access - `template_node_configs` table with 2,000+ pre-extracted configurations - `includeExamples` parameter for `get_node_essentials` (returns 2-3 real configs) - `includeExamples` parameter for `search_nodes` (returns 2 real configs per node) - Real-world configuration examples from popular n8n templates ### Performance - Node configuration queries: <1ms (30-60x faster than on-the-fly extraction) - Sub-millisecond response time for configuration examples ``` --- ## Conclusion These P0 fixes represent the highest-impact improvements we can make to n8n-mcp based on real production telemetry data. By implementing all three fixes in v2.15.0, we will: 1. **Eliminate 80% of validation errors** (P0-R1: Node type normalization) 2. **Fix the majority of TypeError failures** (P0-R2: Null-safety audit) 3. **Replace inferior tool with superior alternative** (P0-R3: Template-based configs + remove get_node_for_task) **Expected Overall Impact:** - Error rate: 5-10% → <2% - Configuration examples: 31 hardcoded → 2,000+ real templates - Query performance: 30-60ms → <1ms (30-60x faster) - User experience: Significant improvement across all tools - Support burden: Reduced by 50%+ **Key Innovation (P0-R3):** - Pre-extraction delivers 30-60x performance improvement - 2,646 real templates provide richer context than hardcoded tasks - Breaking change justified by superior replacement - Database increase: Only +513 KB for 2,625 configurations The implementation is well-architected, delivers exceptional value, and sets up future enhancements. --- **Next Steps:** 1. ✅ Review implementation plan with team (COMPLETED) 2. ✅ Finalize architectural decisions (COMPLETED - pre-extraction chosen) 3. ✅ Create feature branch: `feature/p0-priorities-fixes` (COMPLETED) 4. ✅ **P0-R1**: Auto-Normalize Node Type Prefixes (COMPLETED - commit ed7de10) 5. ⏳ **P0-R2**: Complete Null-Safety Audit (PENDING) 6. ⏳ **P0-R3**: Pre-extract Template Configs + Remove get_node_for_task (PENDING) 7. ⏳ Deploy v2.15.0 with monitoring and telemetry analysis **Target Release:** v2.15.0 (estimated 1.5 weeks remaining) ``` -------------------------------------------------------------------------------- /docs/local/DEEP_DIVE_ANALYSIS_2025-10-02.md: -------------------------------------------------------------------------------- ```markdown # **N8N-MCP DEEP DIVE ANALYSIS** ## **Usage Patterns & Refactoring Recommendations** **Analysis Period:** September 26 - October 2, 2025 (6 days) **Data Volume:** 212,375 events | 5,751 workflows | 2,119 unique users **Database:** Supabase telemetry with 15 analytical views **Analyst:** Claude Code with Supabase MCP integration **Date:** October 2, 2025 --- ## **EXECUTIVE SUMMARY** n8n-mcp has achieved **strong adoption** with 2,119 users generating 212K+ events in 6 days. The system demonstrates **excellent reliability** (96-100% success rates for most tools) but has **critical pain points** that are blocking users and degrading the AI agent experience. The upcoming refactor should focus on: 1. **Fixing the "node type prefix" validation catastrophe** (5,000+ validation errors from a single root cause) 2. **Resolving TypeError issues** in node information tools (1,000+ failures affecting 10% of calls) 3. **Streamlining the workflow update experience** (iterative updates dominate usage) 4. **Improving node discovery** (search is the #2 most-used tool but has UX gaps) 5. **Optimizing for power users** who drive 60% of activity **Key Metrics:** - **Overall Success Rate:** 96-98% across all user segments - **Daily Event Growth:** 16K → 40K events (2.5x growth in 3 days) - **Power User Concentration:** Top 3% of users generate 27% of events - **Most Used Tools:** update_partial_workflow (10,177), search_nodes (8,839), create_workflow (6,046) - **Critical Failure Rates:** get_node_for_task (28%), get_node_info (18%), get_node_essentials (10%) --- ## **TABLE OF CONTENTS** 1. [Tool Performance Analysis](#1-tool-performance-analysis) 2. [Validation Catastrophe](#2-validation-catastrophe) 3. [Usage Patterns & User Segmentation](#3-usage-patterns--user-segmentation) 4. [Tool Sequence Analysis](#4-tool-sequence-analysis) 5. [Workflow Creation Patterns](#5-workflow-creation-patterns) 6. [Platform & Version Distribution](#6-platform--version-distribution) 7. [Error Patterns & Root Causes](#7-error-patterns--root-causes) 8. [Prioritized Refactoring Recommendations](#8-prioritized-refactoring-recommendations) 9. [Architectural Recommendations](#9-architectural-recommendations) 10. [Telemetry Enhancements](#10-telemetry-enhancements) 11. [Specific Code Changes](#11-specific-code-changes) 12. [CHANGELOG Integration](#12-changelog-integration) 13. [Final Recommendations Summary](#13-final-recommendations-summary) --- ## **1. TOOL PERFORMANCE ANALYSIS** ### **1.1 Success Rate Tiers** **EXCELLENT (95-100% success):** - ✅ `n8n_update_partial_workflow` - 10,177 calls, 98.7% success, 846 users - ✅ `search_nodes` - 8,839 calls, 99.8% success, 1,283 users - ✅ `n8n_create_workflow` - 6,046 calls, 96.1% success, 1,305 users - ✅ `n8n_validate_workflow` - 3,222 calls, 99.8% success, 597 users - ✅ `n8n_get_workflow` - 3,368 calls, 99.8% success, 790 users - ✅ `n8n_update_full_workflow` - 2,640 calls, 99.4% success, 486 users - ✅ `tools_documentation` - 1,886 calls, 100% success, 879 users - ✅ `validate_workflow` - 1,667 calls, 95.4% success, 472 users - ✅ All n8n workflow management tools (list/delete/health) - 100% success **GOOD (80-95% success):** - ⚠️ `get_node_essentials` - 4,909 calls, **90.2% success**, 921 users (**9.8% failure**) - ⚠️ `get_node_documentation` - 1,919 calls, 92.9% success, 657 users (**7.1% failure**) - ⚠️ `validate_node_operation` - 998 calls, 88.6% success, 240 users (**11.4% failure**) - ⚠️ `list_ai_tools` - 234 calls, 84.2% success, 184 users (**15.8% failure**) - ⚠️ `get_node_info` - 1,988 calls, **82.3% success**, 677 users (**17.7% failure**) **POOR (50-80% success):** - 🔴 `get_node_for_task` - 392 calls, **72.2% success**, 197 users (**27.8% failure**) ### **1.2 Performance Metrics** **Ultra-Fast (<10ms avg):** - `get_node_essentials`: 3.27ms avg (median: 1ms) - `get_node_info`: 4.78ms avg (median: 1ms) - `get_node_documentation`: 2.16ms avg (median: 1ms) - `tools_documentation`: 3.42ms avg (median: 1ms) - `validate_node_minimal`: 1.79ms avg (median: 1ms) **Fast (10-100ms avg):** - `search_nodes`: 20.47ms avg (median: 5ms, p95: 84ms) - `validate_workflow`: 31.59ms avg (median: 12ms, p95: 103ms) - `list_nodes`: 41.86ms avg (median: 11ms, p95: 196ms) **Acceptable (100-500ms avg):** - `n8n_get_workflow`: 248.79ms avg (median: 111ms, p95: 830ms) - `n8n_validate_workflow`: 229.37ms avg (median: 106ms, p95: 722ms) - `n8n_update_full_workflow`: 302.70ms avg (median: 119ms, p95: 1,069ms) - `n8n_delete_workflow`: 308.85ms avg (median: 166ms, p95: 950ms) - `n8n_create_workflow`: 333.37ms avg (median: 85ms, p95: 1,251ms) - `n8n_list_workflows`: 476.05ms avg (median: 231ms, p95: 1,465ms) - `n8n_autofix_workflow`: 486.49ms avg (median: 174ms, p95: 1,152ms) **Slow (>500ms avg):** - `n8n_get_execution`: 670.35ms avg (median: 121ms, p95: 1,166ms) - `n8n_trigger_webhook_workflow`: 1,884.67ms avg (median: 157ms, p95: 4,865ms) ### **1.3 Critical Findings** **FINDING #1: Node information tools have systematic TypeError issues** - `get_node_essentials`: 483 failures (10% of calls) - `get_node_info`: 352 failures (18% of calls) - `get_node_documentation`: 136 failures (7% of calls) - **Root cause**: Accessing undefined properties on node objects (from CHANGELOG 2.14.0 fix) - **Impact**: AI agents cannot get basic node information, blocking workflow creation - **Evidence**: 400+ TypeError occurrences from error logs **FINDING #2: Task-based node discovery is failing 28% of the time** - `get_node_for_task` has the worst success rate (72%) - **Impact**: When AI agents ask "which node should I use for X task?", they fail 1 in 4 times - **Likely cause**: Limited task library or poor task-to-node mapping - **Usage pattern**: 392 calls, 197 users → high demand but low reliability **FINDING #3: Performance is excellent across the board** - Node lookup tools: <5ms average (ultra-fast SQLite queries) - Search operations: ~20ms average (efficient FTS5 indexing) - Network operations (n8n API): 200-500ms average (acceptable for HTTP calls) - Webhook triggers: 1,885ms average (expected for workflow execution) - **Conclusion**: Performance is NOT a bottleneck; reliability is **FINDING #4: Workflow management tools have perfect reliability** - `n8n_list_workflows`: 100% success (1,489 calls) - `n8n_health_check`: 100% success (1,304 calls) - `n8n_list_executions`: 100% success (1,297 calls) - `n8n_delete_workflow`: 100% success (1,230 calls) - **Takeaway**: The n8n API integration layer is rock-solid --- ## **2. VALIDATION CATASTROPHE** ### **2.1 The "Node Type Prefix" Disaster** **CRITICAL ISSUE:** 5,000+ validation errors from a single root cause ``` Error: Invalid node type: "nodes-base.set". Use "n8n-nodes-base.set" instead. ``` **Breakdown by error type:** 1. **4,800 occurrences**: "Invalid node type" prefix errors across Node0-Node19 - Pattern: `nodes-base.X` instead of `n8n-nodes-base.X` - Affected nodes: ALL node types (Set, HTTP Request, Code, etc.) - **Impact**: 1 affected user with systematic errors across entire workflow 2. **2,500 occurrences**: "Multi-node workflow has no connections" - Likely same 2 users testing/debugging - Message: "Multi-node workflow has no connections. Nodes must be connected..." - **Pattern**: Valid validation but high repetition suggests test loops 3. **58 occurrences**: "Single-node workflows are only valid for webhook endpoints" - 42 affected users - **This is good validation** - appropriate message 4. **22 occurrences**: Duplicate node ID: "undefined" - 5 affected users - **Likely bug**: Nodes being created without IDs ### **2.2 Root Cause Analysis** **Why AI agents produce `nodes-base.X` instead of `n8n-nodes-base.X`:** 1. **Token efficiency**: LLMs may abbreviate to save tokens 2. **Pattern learning**: AI may see shortened versions in docs/examples 3. **Natural language**: "nodes-base" is more concise than "n8n-nodes-base" 4. **Inconsistency**: Some tools may accept both formats, creating confusion **Why the system doesn't auto-correct:** From CHANGELOG 2.14.2: > "Fixed validation false positives for Google Drive nodes with 'fileFolder' resource > - Added node type normalization to handle both `n8n-nodes-base.` and `nodes-base.` prefixes correctly" **Analysis**: A fix was attempted in 2.14.2, but it's **incomplete or not applied universally**. The normalization logic exists but isn't being called in all validation paths. ### **2.3 Impact Assessment** **User Experience:** - **Frustration**: AI agents receive validation errors requiring manual intervention - **Token waste**: Multiple retry attempts with failed validations - **Broken flow**: Interrupts the natural workflow creation process **Quantitative Impact:** - **80% of all validation errors** stem from this single issue - **Affects ALL node types**, not specific to certain nodes - **Systematic pattern**: Once a user hits this, they hit it repeatedly **Why This Is Critical:** - Easy to fix (normalization helper already exists) - Massive impact (eliminates 4,800+ errors) - Improves AI agent experience significantly ### **2.4 Other Validation Issues** **Connection Validation:** - "Connection uses node ID instead of node name" - 1 occurrence - Good error message with clear guidance - Not a systemic issue **Node Configuration:** - Various property-specific validation errors (low frequency) - Generally well-handled with actionable messages --- ## **3. USAGE PATTERNS & USER SEGMENTATION** ### **3.1 User Distribution** | Segment | Users | % | Events | Avg Events | Workflows | Success Rate | |---------|-------|---|--------|------------|-----------|--------------| | **Power Users (1000+)** | 12 | 0.6% | 25,346 | 2,112 | 33 | 97.1% | | **Heavy Users (500-999)** | 47 | 2.2% | 31,608 | 673 | 18 | 98.0% | | **Regular Users (100-499)** | 516 | 24.3% | 102,931 | 199 | 7 | 96.5% | | **Active Users (20-99)** | 919 | 43.4% | 47,768 | 52 | 2 | 97.0% | | **Casual Users (<20)** | 625 | 29.5% | 4,958 | 8 | 1 | 97.6% | | **TOTAL** | 2,119 | 100% | 212,611 | 100 | - | 97.0% | ### **3.2 Key Insights** **INSIGHT #1: Extreme power law distribution** - **Top 59 users (3%)** generate **27% of all events** (57K events) - **Top 575 users (27%)** generate **76% of all events** (160K events) - **Bottom 625 users (30%)** generate only **2% of events** (5K events) **Implications:** - Optimize for power users → 3x impact per feature - Onboarding is good (casual users have 97.6% success rate) - Need enterprise features for heavy users (monitoring, analytics, team features) **INSIGHT #2: Regular users (516) are the core audience** - 103K events total (48% of all activity) - 7 workflows/user average (meaningful engagement) - 96.5% success rate (room for improvement) - **This is the growth segment** - convert to heavy users **INSIGHT #3: Consistent success rates across segments** - All segments: 96-98% success rate - **Paradox**: Power users have LOWER success rate (97.1% vs 97.6% casual) - **Explanation**: Power users attempt harder tasks → more edge cases - **Opportunity**: Focus reliability improvements on advanced features **INSIGHT #4: Workflow creation correlates with engagement** - Power users: 33 workflows - Heavy users: 18 workflows - Regular users: 7 workflows - Active users: 2 workflows - **Metric**: Workflows created = proxy for value delivered ### **3.3 Daily Usage Trends** | Date | Events | Users | Events/User | Growth | |------|--------|-------|-------------|--------| | Sep 26 | 16,334 | 958 | 17.0 | baseline | | Sep 27 | 26,042 | 2,075 | 12.6 | +59% events | | Sep 28 | 35,687 | 2,655 | 13.4 | +37% events | | **Sep 29** | **40,361** | **3,039** | **13.3** | **+13% events (peak)** | | Sep 30 | 39,833 | 3,319 | 12.0 | -1% events | | Oct 1 | 39,854 | 3,528 | 11.3 | 0% events | | Oct 2 | 14,500 | 1,057 | 13.7 | partial day | **Growth Analysis:** - **Rapid adoption**: 16K → 40K daily events (2.5x in 3 days) - **Plateau**: Sep 29-Oct 1 stable at ~40K events/day - **User growth**: 958 → 3,528 users (3.7x growth) - **Efficiency**: Events per user declining (17 → 11) as user base broadens **Interpretation:** - System reached **initial scale** (~40K events/day, ~3K users/day) - Now in **consolidation phase** - need to improve retention - **Next growth phase** requires solving reliability issues (see P0 recommendations) --- ## **4. TOOL SEQUENCE ANALYSIS** ### **4.1 Most Common Patterns** **Top 15 Tool Sequences (all show 300s = 5 min time delta):** | Rank | Sequence | Count | Users | Pattern | |------|----------|-------|-------|---------| | 1 | `update_partial_workflow` → `update_partial_workflow` | 549 | 153 | Iterative refinement | | 2 | `create_workflow` → `update_partial_workflow` | 297 | 118 | Create then refine | | 3 | `update_partial_workflow` → `get_workflow` | 265 | 91 | Update then verify | | 4 | `create_workflow` → `create_workflow` | 237 | 97 | Multiple attempts | | 5 | `create_workflow` → `get_workflow` | 185 | 81 | Create then inspect | | 6 | `create_workflow` → `search_nodes` | 166 | 72 | Create then discover | | 7 | `validate_workflow` → `update_partial_workflow` | 161 | 63 | Validate then fix | | 8 | `validate_workflow` → `validate_workflow` | 152 | 44 | Re-validation | | 9 | `validate_workflow` → `get_workflow` | 134 | 53 | Validate then inspect | | 10 | `update_partial_workflow` → `create_workflow` | 130 | 59 | Update then recreate | | 11 | `get_workflow` → `update_partial_workflow` | 117 | 50 | Inspect then update | | 12 | `update_full_workflow` → `update_partial_workflow` | 98 | 41 | Full to partial update | | 13 | `update_partial_workflow` → `search_nodes` | 94 | 42 | Update then discover | | 14 | `get_workflow` → `create_workflow` | 87 | 42 | Inspect then recreate | | 15 | `create_workflow` → `tools_documentation` | 85 | 36 | Create then learn | ### **4.2 Critical Insights** **INSIGHT #1: AI agents iterate heavily on workflows** - **#1 sequence**: `update → update → update` (549 occurrences) - **Pattern**: Create → Validate → Update → Validate → Update (feedback loop) - **Workflow**: 1. AI creates initial workflow 2. Validates it (finds issues) 3. Updates to fix issues 4. Validates again (finds more issues) 5. Continues iterating until success **Implication**: The diff-based update system (v2.7.0) is **CRUCIAL** for token efficiency - Without diff updates: Would need full workflow JSON each time (~10-50KB) - With diff updates: Only send changed operations (~1-5KB) - **Token savings**: 80-90% per update iteration **INSIGHT #2: All transitions show 5-minute time deltas** - **This is NOT actual elapsed time** - it's the telemetry "slow transition" threshold - All sequences are marked as `is_slow_transition: true` - **Actual insight**: AI agents take thinking time between tool calls (expected for LLMs) - **Limitation**: Cannot determine real workflow creation speed with current data **Recommendation**: Add fine-grained timing (see T1 in Telemetry Enhancements) **INSIGHT #3: Node discovery happens AFTER workflow creation** - `create_workflow → search_nodes` (166 occurrences) - **Flow**: 1. AI creates workflow with known nodes 2. Realizes it needs additional nodes 3. Searches for them 4. Updates workflow with new nodes **Opportunity**: Proactive node suggestions during creation (see P1-R5) **INSIGHT #4: Validation drives updates** - `validate_workflow → update_partial_workflow` (161 occurrences) - `validate_workflow → validate_workflow` (152 occurrences) - **Pattern**: Validation → Fix → Re-validate loop **Quality**: This is GOOD behavior (AI agents using validation to improve) **Optimization**: Better validation error messages → fewer iterations (see P1-R6) ### **4.3 Common Workflow Patterns** **Pattern A: Create-Update-Validate Loop** ``` create_workflow → update_partial_workflow → validate_workflow → update_partial_workflow ``` - Most common for new workflows - 3-5 iterations average before success **Pattern B: Inspect-Modify-Deploy** ``` get_workflow → update_partial_workflow → validate_workflow → get_workflow ``` - Common for modifying existing workflows - "Get" used to verify final state **Pattern C: Search-Create-Refine** ``` search_nodes → create_workflow → update_partial_workflow → validate_workflow ``` - Discovery-driven workflow creation - Users explore capabilities before creating ### **4.4 Tools Leading to Workflow Creation** **Tools used within 5 minutes BEFORE workflow creation:** | Tool | Occurrences | Users | Conversion Rate | |------|-------------|-------|-----------------| | `update_partial_workflow` | 6,271 | 547 | High | | `search_nodes` | 6,099 | 901 | High | | `get_node_essentials` | 3,361 | 649 | Medium | | `create_workflow` | 2,810 | 742 | Medium (re-creation) | | `get_workflow` | 2,057 | 512 | Medium | | `validate_workflow` | 2,014 | 417 | Medium | | `get_node_documentation` | 1,301 | 456 | Low | | `tools_documentation` | 1,290 | 596 | Low | **Interpretation:** - **Discovery tools** (search_nodes, get_node_essentials) → high workflow creation - **Documentation tools** → lower conversion (learning/exploring phase) - **Workflow management** (update/validate) → iterative creation process --- ## **5. WORKFLOW CREATION PATTERNS** ### **5.1 Complexity Distribution** | Complexity | Count | % | Avg Nodes | Median | Triggers | Webhooks | |------------|-------|---|-----------|--------|----------|----------| | **Simple** | 4,290 | 75% | 5.5 | 5 | 1,330 (31%) | 1,330 (31%) | | **Medium** | 1,282 | 22% | 14.0 | 13 | 424 (33%) | 424 (33%) | | **Complex** | 187 | 3% | 27.5 | 23 | 71 (38%) | 71 (38%) | | **TOTAL** | 5,759 | 100% | 8.2 | 6 | 1,825 (32%) | 1,825 (32%) | **Complexity Definitions:** - **Simple**: ≤8 nodes - **Medium**: 9-20 nodes - **Complex**: 21+ nodes ### **5.2 Key Findings** **FINDING #1: 75% of workflows are simple** - AI agents prefer minimalism (5-6 nodes average) - Small workflows are easier to reason about - Faster creation and debugging - **Implication**: Optimize for simple workflow creation experience **FINDING #2: Complex workflows are rare but important** - Only 3% of workflows (187 total) - Average 27.5 nodes (large automation) - 38% have triggers/webhooks (production use) - **User profile**: Likely power users building production systems **FINDING #3: Webhook usage is consistent across complexity** - Simple: 31% have webhooks - Medium: 33% have webhooks - Complex: 38% have webhooks - **Insight**: Webhooks are a fundamental pattern, not correlated with complexity ### **5.3 Most Popular Nodes** **Top 20 Nodes by Workflow Count:** | Rank | Node Type | Workflows | % | Users | Avg Workflow Size | |------|-----------|-----------|---|-------|-------------------| | 1 | `n8n-nodes-base.code` | 3,051 | 53% | 1,056 | 9.4 | | 2 | `n8n-nodes-base.httpRequest` | 2,686 | 47% | 1,033 | 9.6 | | 3 | `n8n-nodes-base.webhook` | 1,812 | 32% | 750 | 8.5 | | 4 | `n8n-nodes-base.set` | 1,738 | 30% | 742 | 9.2 | | 5 | `n8n-nodes-base.if` | 1,400 | 24% | 653 | 12.2 | | 6 | `n8n-nodes-base.manualTrigger` | 1,391 | 24% | 590 | 7.9 | | 7 | `n8n-nodes-base.respondToWebhook` | 1,113 | 19% | 484 | 8.7 | | 8 | `@n8n/n8n-nodes-langchain.agent` | 884 | 15% | 403 | 9.7 | | 9 | `n8n-nodes-base.scheduleTrigger` | 825 | 14% | 412 | 9.5 | | 10 | `n8n-nodes-base.googleSheets` | 732 | 13% | 324 | 10.5 | | 11 | `n8n-nodes-base.merge` | 599 | 10% | 311 | 13.8 | | 12 | `@n8n/n8n-nodes-langchain.lmChatOpenAi` | 564 | 10% | 268 | 10.6 | | 13 | `n8n-nodes-base.switch` | 534 | 9% | 262 | 13.7 | | 14 | `n8n-nodes-base.openAi` | 486 | 8% | 261 | 10.1 | | 15 | `n8n-nodes-base.splitInBatches` | 457 | 8% | 229 | 13.2 | | 16 | `n8n-nodes-base.telegram` | 416 | 7% | 168 | 10.0 | | 17 | `n8n-nodes-base.function` | 414 | 7% | 162 | 9.6 | | 18 | `n8n-nodes-base.gmail` | 400 | 7% | 212 | 9.5 | | 19 | `n8n-nodes-base.cron` | 380 | 7% | 182 | 9.2 | | 20 | `n8n-nodes-base.noOp` | 322 | 6% | 174 | 10.9 | ### **5.4 Critical Insights** **INSIGHT #1: Code node dominates (53% of workflows)** - AI agents LOVE programmatic control - Code node enables custom logic that other nodes can't provide - **Implication**: Ensure code node documentation and examples are excellent **INSIGHT #2: HTTP Request is in nearly half of workflows (47%)** - Integration-heavy usage pattern - Most workflows interact with external APIs - **Synergy**: Code + HTTP Request (see co-occurrence analysis) **INSIGHT #3: LangChain nodes show strong adoption (15%)** - AI-on-AI workflows - `langchain.agent` in 884 workflows - `lmChatOpenAi` in 564 workflows - **Trend**: AI-building-AI-workflows is a real use case **INSIGHT #4: Average workflow size correlates with node type** - Control flow nodes (if/switch): 12-14 nodes average (complex workflows) - Data nodes (set/code): 9-10 nodes average (medium workflows) - Trigger nodes (manualTrigger): 7-8 nodes average (simple workflows) ### **5.5 Node Co-occurrence Patterns** **Top 20 Node Pairs:** | Rank | Node 1 | Node 2 | Co-occurrence | Users | Avg Size | Pattern | |------|--------|--------|---------------|-------|----------|---------| | 1 | `code` | `httpRequest` | 1,646 | 686 | 10.4 | Data transformation + API | | 2 | `webhook` | `respondToWebhook` | 1,043 | 452 | 8.8 | Standard webhook pattern | | 3 | `code` | `webhook` | 1,008 | 442 | 9.6 | Custom webhook logic | | 4 | `code` | `if` | 894 | 437 | 12.9 | Conditional with custom logic | | 5 | `httpRequest` | `webhook` | 845 | 404 | 10.5 | Webhook-triggered API calls | | 6 | `httpRequest` | `set` | 841 | 431 | 10.7 | API response processing | | 7 | `httpRequest` | `if` | 815 | 420 | 13.4 | Conditional API logic | | 8 | `code` | `manualTrigger` | 731 | 321 | 9.7 | Manual testing workflows | | 9 | `httpRequest` | `manualTrigger` | 706 | 328 | 9.1 | Manual API testing | | 10 | `code` | `set` | 677 | 339 | 11.6 | Data manipulation | | 11 | `code` | `respondToWebhook` | 617 | 285 | 9.8 | Webhook responses | | 12 | `code` | `scheduleTrigger` | 585 | 290 | 10.3 | Scheduled automation | | 13 | `manualTrigger` | `set` | 569 | 290 | 8.6 | Simple manual workflows | | 14 | `if` | `set` | 545 | 291 | 13.0 | Conditional data setting | | 15 | `httpRequest` | `respondToWebhook` | 534 | 263 | 10.0 | API-based webhooks | | 16 | `webhook` | `set` | 516 | 289 | 9.9 | Webhook data processing | | 17 | `httpRequest` | `scheduleTrigger` | 511 | 270 | 10.4 | Scheduled API calls | | 18 | `webhook` | `if` | 477 | 277 | 12.7 | Conditional webhooks | | 19 | `code` | `googleSheets` | 475 | 212 | 11.4 | Sheets data processing | | 20 | `agent` | `lmChatOpenAi` | 475 | 222 | 10.1 | AI agent workflows | ### **5.6 Pattern Recognition** **Pattern A: The "Transform-and-Call" Pattern** - `code + httpRequest` (1,646 workflows) - **Flow**: Prepare data → Call API → Process response - **Use cases**: Integration automation, data synchronization **Pattern B: The "Webhook Handler" Pattern** - `webhook + respondToWebhook` (1,043 workflows) - **Flow**: Receive webhook → Process → Respond - **Use cases**: Event-driven automation, API endpoints **Pattern C: The "Conditional Integration" Pattern** - `httpRequest + if + set` (combined ~2,000 workflows) - **Flow**: Call API → Check response → Transform data - **Use cases**: Smart integrations, error handling **Pattern D: The "AI Agent" Pattern** - `agent + lmChatOpenAi + memoryBufferWindow` (475 workflows) - **Flow**: AI agent with memory and LLM - **Use cases**: Conversational AI, intelligent automation ### **5.7 Node Usage in Complex Workflows** **Top nodes in complex workflows (21+ nodes):** | Rank | Node Type | Count | Avg Size | |------|-----------|-------|----------| | 1 | `code` | 139 | 26.7 | | 2 | `httpRequest` | 125 | 27.6 | | 3 | `if` | 111 | 27.4 | | 4 | `set` | 87 | 28.0 | | 5 | `switch` | 74 | 27.9 | | 6 | `webhook` | 68 | 29.5 | | 7 | `merge` | 62 | 27.1 | | 8 | `splitInBatches` | 50 | 25.6 | **Insight**: Complex workflows use the same core nodes, just more of them - Control flow (if/switch) for complex logic - Data manipulation (code/set) for transformations - Integration (httpRequest) for external systems --- ## **6. PLATFORM & VERSION DISTRIBUTION** ### **6.1 Platform Breakdown** **Top 20 Configurations:** | Platform | Arch | Version | Sessions | Users | % | |----------|------|---------|----------|-------|---| | **Linux** | x64 | 2.14.0 | 1,488 | 242 | 34% | | Linux | arm64 | 2.14.0 | 190 | 135 | 4% | | **Windows** | x64 | 2.14.1 | 115 | 79 | 3% | | Windows | x64 | 2.14.1 | 95 | 53 | 2% | | **macOS** | arm64 | 2.14.1 | 77 | 51 | 2% | | Windows | x64 | 2.14.1 | 70 | 41 | 2% | | macOS | arm64 | 2.14.1 | 68 | 43 | 2% | | Windows | x64 | 2.14.1 | 60 | 46 | 1% | | Linux | x64 | 2.14.5 | 54 | 30 | 1% | | macOS | arm64 | 2.14.1 | 51 | 26 | 1% | **Aggregated by Platform:** - **Linux**: ~40% (1,678 sessions) - Docker, cloud VMs, CI/CD - **Windows**: ~25% (multiple versions) - **macOS**: ~15% (mostly M1/M2 Macs) - **Other/Unknown**: ~20% ### **6.2 Version Distribution** | Version | Total Sessions | Estimated Users | Release Date | |---------|----------------|-----------------|--------------| | 2.14.0 | 1,678 | 377 | Sep 26 | | 2.14.1 | 780+ | 500+ | Sep 26 | | 2.14.2 | ~50 | ~40 | Sep 29 | | 2.14.3 | ~30 | ~25 | Sep 29 | | 2.14.4 | 29 | 27 | Sep 30 | | 2.14.5 | 54 | 30 | Sep 30 | | 2.14.6 | 74 | 56 | Oct 1 | ### **6.3 Critical Findings** **FINDING #1: Majority stuck on 2.14.0 (37% of sessions)** - 1,678 sessions on v2.14.0 - **Problem**: This version has known issues: - TypeError fixes incomplete (CHANGELOG 2.14.0) - Validation false positives (fixed in 2.14.2) - Template sanitization issues (fixed in 2.14.3) **FINDING #2: Slow version adoption** - Only 74 sessions on latest v2.14.6 (Oct 1 release) - Only 54 sessions on v2.14.5 (Sep 30 release) - **Gap**: Users not upgrading despite bug fixes **FINDING #3: Linux dominates (40% of sessions)** - Likely Docker deployments - CI/CD integration - Cloud VMs (AWS, GCP, Azure) - **Implication**: Containerization is working well **FINDING #4: Node.js version fragmentation** - v22.20.0: Most common - v22.19.0: Second most common - v22.18.0, v22.17.0, v22.14.0: Long tail - **No compatibility issues reported** (good) ### **6.4 Recommendations** **R1: Version update notifications** - Add update checker to MCP server - Notify users of critical fixes - Show CHANGELOG diff between versions **R2: Docker image optimization** - Pre-build for Linux x64 + arm64 - Multi-stage builds for smaller images - Automatic version pinning **R3: Breaking change policy** - Clear migration guides for version updates - Deprecation warnings before breaking changes - Backward compatibility period (2 releases minimum) --- ## **7. ERROR PATTERNS & ROOT CAUSES** ### **7.1 TypeError Cascade in Node Tools** **Error Distribution (from error logs):** | Tool | TypeError Count | Affected Users | % Failure | |------|-----------------|----------------|-----------| | `get_node_essentials` | ~350 | 10+ | 9.8% | | `get_node_info` | ~250 | 12+ | 17.7% | | `get_node_documentation` | ~100 | 8+ | 7.1% | | **TOTAL** | ~700 | ~30 | **varies** | **From CHANGELOG 2.14.0:** > "Fixed TypeErrors in `get_node_info`, `get_node_essentials`, and `get_node_documentation` tools that were affecting 50% of calls" > "Added null safety checks for undefined node properties" **Analysis:** - Fix in 2.14.0 reduced failures from **50% → 10-18%** - **Residual issues remain** (700+ errors in 6 days) - **Root causes**: 1. Incomplete null safety for edge cases 2. Nodes with unusual/legacy structure 3. Missing properties in database 4. Nested property access without guards **Example Error Pattern:** ```javascript // Fails when node.properties.description is undefined const description = node.properties.description.text; // Should be: const description = node?.properties?.description?.text ?? 'No description'; ``` ### **7.2 ValidationError in Workflow Creation** **Pattern:** - `n8n_create_workflow`: 237 failures (3.9% failure rate) - Error type: `ValidationError` - Context: `tool_execution` **Root causes (from validation analysis):** 1. **Node type prefix errors** (80% of validation errors) - `nodes-base.X` vs `n8n-nodes-base.X` - See Section 2 for full analysis 2. **Missing connections** (10% of validation errors) - "Multi-node workflow has no connections" - Valid error, but could provide better guidance 3. **Duplicate node IDs** (5% of validation errors) - "Duplicate node ID: 'undefined'" - Likely bug in node generation 4. **Other validation issues** (5%) - Missing required properties - Invalid property values - Connection reference errors ### **7.3 Task Discovery Failures** **Pattern:** - `get_node_for_task`: 109 failures (27.8% failure rate) - Error type: Not specified (likely "No matching task found") - **Highest failure rate of any tool** **Probable causes:** 1. **Limited task library** - Only ~30-40 predefined tasks 2. **No fuzzy matching** - Exact task name required 3. **Poor task descriptions** - AI agents can't guess correct task name 4. **Missing fallback** - Doesn't suggest alternatives **Example failing queries (inferred):** - "send email notification" (might need "email_send" task) - "process json data" (might need "json_parse" task) - "schedule workflow" (might need "cron_trigger" task) ### **7.4 Other Error Patterns** **Low-frequency errors (<10 occurrences):** - Tool not found errors (misspelled tool names) - Invalid parameters (wrong types, missing required fields) - Network timeouts (n8n API unavailable) - Database errors (SQLite lock issues) **These are expected in production and handled gracefully** --- ## **8. PRIORITIZED REFACTORING RECOMMENDATIONS** ### **P0 - CRITICAL (Fix Immediately)** --- #### **P0-R1: Auto-normalize node type prefixes** **Problem**: 4,800+ validation errors from `nodes-base.X` vs `n8n-nodes-base.X` **Impact**: - Eliminates **80% of all validation errors** - Improves AI agent experience significantly - Reduces token waste from retry attempts - Unblocks hundreds of users **Solution**: ```typescript // src/services/workflow-validator.ts export function normalizeNodeTypes(workflow: any): any { const normalized = { ...workflow }; if (normalized.nodes) { normalized.nodes = normalized.nodes.map((node: any) => ({ ...node, type: normalizeNodeType(node.type) })); } return normalized; } function normalizeNodeType(type: string): string { // Fix common AI-generated abbreviations const prefixMap: Record<string, string> = { 'nodes-base.': 'n8n-nodes-base.', 'nodes-langchain.': '@n8n/n8n-nodes-langchain.', 'n8n-nodes-langchain.': '@n8n/n8n-nodes-langchain.' }; for (const [short, full] of Object.entries(prefixMap)) { if (type.startsWith(short)) { return type.replace(short, full); } } return type; } ``` **Apply in handlers**: ```typescript // src/mcp/handlers-n8n-manager.ts export async function handleCreateWorkflow(params: any): Promise<McpToolResponse> { // Normalize before validation const normalizedWorkflow = normalizeNodeTypes(params); const validation = await validateWorkflow(normalizedWorkflow); if (!validation.valid) { return { success: false, error: validation.errors }; } // Use normalized workflow return await createWorkflow(normalizedWorkflow); } export async function handleUpdateFullWorkflow(params: any): Promise<McpToolResponse> { const normalizedWorkflow = normalizeNodeTypes(params); // ... rest of handler } ``` **Testing**: ```typescript // tests/unit/services/workflow-normalizer.test.ts describe('normalizeNodeTypes', () => { it('should normalize nodes-base prefix', () => { const workflow = { nodes: [ { id: '1', type: 'nodes-base.set', parameters: {} } ] }; const result = normalizeNodeTypes(workflow); expect(result.nodes[0].type).toBe('n8n-nodes-base.set'); }); it('should handle already-normalized types', () => { const workflow = { nodes: [ { id: '1', type: 'n8n-nodes-base.set', parameters: {} } ] }; const result = normalizeNodeTypes(workflow); expect(result.nodes[0].type).toBe('n8n-nodes-base.set'); }); it('should normalize langchain nodes', () => { const workflow = { nodes: [ { id: '1', type: 'nodes-langchain.agent', parameters: {} } ] }; const result = normalizeNodeTypes(workflow); expect(result.nodes[0].type).toBe('@n8n/n8n-nodes-langchain.agent'); }); }); ``` **Effort**: 2-4 hours **Risk**: Low (only adds normalization, doesn't change validation logic) **Files**: - `src/services/workflow-validator.ts` (new helper) - `src/mcp/handlers-n8n-manager.ts` (apply in handlers) - `tests/unit/services/workflow-normalizer.test.ts` (new tests) --- #### **P0-R2: Complete null-safety audit of node information tools** **Problem**: 10-18% failure rate for `get_node_essentials`, `get_node_info`, `get_node_documentation` **Impact**: - Reduce TypeError failures from **10-18% → <1%** - Improve reliability of most-used tools - Prevent AI agent blocking on node discovery **Solution**: Comprehensive null-safety refactor **Step 1: Update repository methods** ```typescript // src/database/node-repository.ts export class NodeRepository { getNodeEssentials(nodeType: string): NodeEssentials | null { try { const node = this.db.prepare(` SELECT * FROM nodes WHERE type = ? `).get(nodeType); if (!node) { return null; } // Safe property access with defaults return { type: node.type ?? 'unknown', displayName: node.displayName ?? node.name ?? 'Unknown Node', description: this.extractDescription(node), category: node.category ?? 'Uncategorized', icon: node.icon ?? 'fa:question', inputs: this.parseJSON(node.inputs, []), outputs: this.parseJSON(node.outputs, []), properties: this.extractEssentialProperties(node) }; } catch (error) { this.logger.error('Error getting node essentials', { nodeType, error }); return null; } } private extractDescription(node: any): string { // Try multiple possible locations for description if (node.description) return node.description; if (node.properties?.description?.text) return node.properties.description.text; if (node.properties?.description) return node.properties.description; if (node.subtitle) return node.subtitle; return 'No description available'; } private parseJSON<T>(value: any, defaultValue: T): T { if (!value) return defaultValue; try { return typeof value === 'string' ? JSON.parse(value) : value; } catch { return defaultValue; } } private extractEssentialProperties(node: any): any[] { try { const props = this.parseJSON(node.properties, []); return props.map((prop: any) => ({ name: prop.name ?? 'unknown', displayName: prop.displayName ?? prop.name ?? 'Unknown', type: prop.type ?? 'string', required: prop.required ?? false, default: prop.default ?? null, description: prop.description ?? '' })); } catch { return []; } } } ``` **Step 2: Update handlers with error handling** ```typescript // src/mcp/handlers.ts export async function handleGetNodeEssentials(params: { nodeType: string }): Promise<McpToolResponse> { const { nodeType } = params; // Validate input if (!nodeType || typeof nodeType !== 'string') { return { success: false, error: 'Invalid nodeType parameter' }; } try { const essentials = await nodeRepository.getNodeEssentials(nodeType); if (!essentials) { return { success: false, error: `Node type "${nodeType}" not found. Use search_nodes to find available nodes.` }; } return { success: true, data: essentials }; } catch (error) { return { success: false, error: `Failed to get node essentials: ${error.message}` }; } } ``` **Step 3: Add comprehensive tests** ```typescript // tests/unit/database/node-repository.test.ts describe('NodeRepository - Null Safety', () => { it('should handle node with missing description', () => { const node = { type: 'test.node', name: 'Test' }; db.prepare('INSERT INTO nodes VALUES (?, ?, NULL)').run(node.type, node.name); const result = repository.getNodeEssentials('test.node'); expect(result).not.toBeNull(); expect(result.description).toBe('No description available'); }); it('should handle node with malformed JSON properties', () => { const node = { type: 'test.node', properties: 'invalid json' }; db.prepare('INSERT INTO nodes VALUES (?, ?, ?)').run(node.type, node.name, node.properties); const result = repository.getNodeEssentials('test.node'); expect(result).not.toBeNull(); expect(result.properties).toEqual([]); }); it('should return null for non-existent node', () => { const result = repository.getNodeEssentials('non.existent'); expect(result).toBeNull(); }); }); ``` **Effort**: 1 day (8 hours) **Risk**: Medium (changes core repository methods, needs thorough testing) **Files**: - `src/database/node-repository.ts` (refactor) - `src/mcp/handlers.ts` (update error handling) - `tests/unit/database/node-repository.test.ts` (comprehensive tests) - `tests/unit/mcp/handlers.test.ts` (update tests) **Success Criteria**: - `get_node_essentials` failure rate: 10% → <1% - `get_node_info` failure rate: 18% → <1% - `get_node_documentation` failure rate: 7% → <1% - 100% test coverage for null cases --- #### **P0-R3: Improve `get_node_for_task` success rate** **Problem**: 27.8% failure rate (worst-performing tool) **Impact**: - Improve from **72% → 95%+ success rate** - Enable AI agents to discover nodes by task description - Reduce frustration when agents don't know exact node names **Solution**: Multi-pronged enhancement **Step 1: Expand task library** ```typescript // src/services/task-templates.ts export const TASK_LIBRARY = { // HTTP & API 'http_request': { node: 'n8n-nodes-base.httpRequest', priority: 1 }, 'api_call': { node: 'n8n-nodes-base.httpRequest', priority: 1 }, 'make_http_request': { node: 'n8n-nodes-base.httpRequest', priority: 1 }, 'fetch_data': { node: 'n8n-nodes-base.httpRequest', priority: 2 }, // Data transformation 'transform_data': { node: 'n8n-nodes-base.code', priority: 1 }, 'process_json': { node: 'n8n-nodes-base.code', priority: 1 }, 'manipulate_data': { node: 'n8n-nodes-base.set', priority: 2 }, 'set_values': { node: 'n8n-nodes-base.set', priority: 1 }, // Email 'send_email': { node: 'n8n-nodes-base.emailSend', priority: 1 }, 'email_notification': { node: 'n8n-nodes-base.emailSend', priority: 1 }, 'receive_email': { node: 'n8n-nodes-base.emailReadImap', priority: 1 }, // Webhooks 'webhook': { node: 'n8n-nodes-base.webhook', priority: 1 }, 'receive_webhook': { node: 'n8n-nodes-base.webhook', priority: 1 }, 'respond_to_webhook': { node: 'n8n-nodes-base.respondToWebhook', priority: 1 }, // ... expand to 100+ tasks }; ``` **Step 2: Add fuzzy matching** ```typescript // src/services/discovery-service.ts import Fuse from 'fuse.js'; export class DiscoveryService { private taskIndex: Fuse<TaskDefinition>; constructor() { // Build fuzzy search index this.taskIndex = new Fuse(Object.entries(TASK_LIBRARY), { keys: ['0'], // Task name threshold: 0.4, // Allow some typos distance: 100 }); } getNodeForTask(taskDescription: string): TaskMatch[] { // 1. Try exact match const exactMatch = TASK_LIBRARY[taskDescription.toLowerCase()]; if (exactMatch) { return [{ node: exactMatch.node, confidence: 1.0, reason: 'Exact task match' }]; } // 2. Try fuzzy match const fuzzyMatches = this.taskIndex.search(taskDescription); if (fuzzyMatches.length > 0) { return fuzzyMatches.slice(0, 3).map(match => ({ node: match.item[1].node, confidence: 1 - match.score, reason: `Similar to "${match.item[0]}"` })); } // 3. Fallback to keyword search in node descriptions return this.searchNodesByKeywords(taskDescription); } private searchNodesByKeywords(query: string): TaskMatch[] { // Use existing search_nodes functionality const results = nodeRepository.searchNodes(query, { limit: 3 }); return results.map(node => ({ node: node.type, confidence: 0.5, reason: `Found by keyword search: "${query}"` })); } } ``` **Step 3: Return multiple suggestions** ```typescript // src/mcp/handlers.ts export async function handleGetNodeForTask(params: { task: string }): Promise<McpToolResponse> { const { task } = params; try { const matches = discoveryService.getNodeForTask(task); if (matches.length === 0) { return { success: false, error: `No node found for task "${task}". Try search_nodes with keywords instead.`, suggestions: [ 'Use search_nodes to explore available nodes', 'Check list_tasks to see predefined task names' ] }; } return { success: true, data: { primaryMatch: matches[0], alternativeMatches: matches.slice(1), totalMatches: matches.length } }; } catch (error) { return { success: false, error: `Failed to find node for task: ${error.message}` }; } } ``` **Step 4: Testing** ```typescript // tests/unit/services/discovery-service.test.ts describe('DiscoveryService - Task Matching', () => { it('should find exact task match', () => { const result = service.getNodeForTask('send_email'); expect(result[0].node).toBe('n8n-nodes-base.emailSend'); expect(result[0].confidence).toBe(1.0); }); it('should handle typos with fuzzy matching', () => { const result = service.getNodeForTask('send emial'); // typo expect(result[0].node).toBe('n8n-nodes-base.emailSend'); expect(result[0].confidence).toBeGreaterThan(0.7); }); it('should return multiple suggestions', () => { const result = service.getNodeForTask('process data'); expect(result.length).toBeGreaterThan(1); expect(result).toContainEqual( expect.objectContaining({ node: 'n8n-nodes-base.code' }) ); }); it('should fallback to keyword search', () => { const result = service.getNodeForTask('sheets manipulation'); expect(result.some(r => r.node.includes('googleSheets'))).toBe(true); }); }); ``` **Effort**: 3 days (24 hours) - Day 1: Expand task library (100+ tasks) - Day 2: Implement fuzzy matching - Day 3: Testing and refinement **Risk**: Low (enhances existing functionality) **Dependencies**: `fuse.js` (fuzzy search library) **Files**: - `src/services/task-templates.ts` (expand library) - `src/services/discovery-service.ts` (new service) - `src/mcp/handlers.ts` (update handler) - `tests/unit/services/discovery-service.test.ts` (comprehensive tests) **Success Criteria**: - `get_node_for_task` success rate: 72% → 95% - Average confidence score: >0.8 - Multiple suggestions returned for ambiguous queries ### **Immediate Actions (This Week) - P0** **1. Auto-normalize node type prefixes (P0-R1)** - **Impact**: Eliminate 4,800 validation errors (80% of all errors) - **Effort**: 2-4 hours - **Files**: `workflow-validator.ts`, `handlers-n8n-manager.ts` - **ROI**: ⭐⭐⭐⭐⭐ (Massive impact, minimal effort) **2. Complete null-safety audit (P0-R2)** - **Impact**: Fix 10-18% TypeError failures - **Effort**: 1 day (8 hours) - **Files**: `node-repository.ts`, `handlers.ts` - **ROI**: ⭐⭐⭐⭐⭐ (Critical reliability improvement) **3. Expand task discovery library (P0-R3)** - **Impact**: Improve 72% → 95% success rate - **Effort**: 3 days (24 hours) - **Files**: `task-templates.ts`, `discovery-service.ts` - **ROI**: ⭐⭐⭐⭐ (High value for task-based workflows) **Expected Overall Impact**: - Error rate: 5-10% → <2% - User satisfaction: Significant improvement - Support burden: Reduced by 50% ```