This is page 45 of 59. Use http://codebase.md/czlonkowski/n8n-mcp?lines=true&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-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 │ │ │ ├── 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-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 -------------------------------------------------------------------------------- /tests/extracted-nodes-db/n8n-nodes-base__HttpRequest.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "node_type": "n8n-nodes-base.HttpRequest", 3 | "name": "HttpRequest", 4 | "package_name": "n8n-nodes-base", 5 | "code_hash": "5b5e2328474b7e85361c940dfe942e167b3f0057f38062f56d6b693f0a7ffe7e", 6 | "code_length": 1343, 7 | "source_location": "node_modules/n8n-nodes-base/dist/nodes/HttpRequest/HttpRequest.node.js", 8 | "has_credentials": false, 9 | "source_code": "\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.HttpRequest = void 0;\nconst n8n_workflow_1 = require(\"n8n-workflow\");\nconst HttpRequestV1_node_1 = require(\"./V1/HttpRequestV1.node\");\nconst HttpRequestV2_node_1 = require(\"./V2/HttpRequestV2.node\");\nconst HttpRequestV3_node_1 = require(\"./V3/HttpRequestV3.node\");\nclass HttpRequest extends n8n_workflow_1.VersionedNodeType {\n constructor() {\n const baseDescription = {\n displayName: 'HTTP Request',\n name: 'httpRequest',\n icon: 'fa:at',\n group: ['output'],\n subtitle: '={{$parameter[\"requestMethod\"] + \": \" + $parameter[\"url\"]}}',\n description: 'Makes an HTTP request and returns the response data',\n defaultVersion: 4.1,\n };\n const nodeVersions = {\n 1: new HttpRequestV1_node_1.HttpRequestV1(baseDescription),\n 2: new HttpRequestV2_node_1.HttpRequestV2(baseDescription),\n 3: new HttpRequestV3_node_1.HttpRequestV3(baseDescription),\n 4: new HttpRequestV3_node_1.HttpRequestV3(baseDescription),\n 4.1: new HttpRequestV3_node_1.HttpRequestV3(baseDescription),\n };\n super(nodeVersions, baseDescription);\n }\n}\nexports.HttpRequest = HttpRequest;\n//# sourceMappingURL=HttpRequest.node.js.map", 10 | "package_info": { 11 | "name": "n8n-nodes-base", 12 | "version": "1.14.1", 13 | "description": "Base nodes of n8n", 14 | "license": "SEE LICENSE IN LICENSE.md", 15 | "homepage": "https://n8n.io", 16 | "author": { 17 | "name": "Jan Oberhauser", 18 | "email": "[email protected]" 19 | }, 20 | "main": "index.js", 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/n8n-io/n8n.git" 24 | }, 25 | "files": [ 26 | "dist" 27 | ], 28 | "n8n": { 29 | "credentials": [ 30 | "dist/credentials/ActionNetworkApi.credentials.js", 31 | "dist/credentials/ActiveCampaignApi.credentials.js", 32 | "dist/credentials/AcuitySchedulingApi.credentials.js", 33 | "dist/credentials/AcuitySchedulingOAuth2Api.credentials.js", 34 | "dist/credentials/AdaloApi.credentials.js", 35 | "dist/credentials/AffinityApi.credentials.js", 36 | "dist/credentials/AgileCrmApi.credentials.js", 37 | "dist/credentials/AirtableApi.credentials.js", 38 | "dist/credentials/AirtableOAuth2Api.credentials.js", 39 | "dist/credentials/AirtableTokenApi.credentials.js", 40 | "dist/credentials/AlienVaultApi.credentials.js", 41 | "dist/credentials/Amqp.credentials.js", 42 | "dist/credentials/ApiTemplateIoApi.credentials.js", 43 | "dist/credentials/AsanaApi.credentials.js", 44 | "dist/credentials/AsanaOAuth2Api.credentials.js", 45 | "dist/credentials/Auth0ManagementApi.credentials.js", 46 | "dist/credentials/AutomizyApi.credentials.js", 47 | "dist/credentials/AutopilotApi.credentials.js", 48 | "dist/credentials/Aws.credentials.js", 49 | "dist/credentials/BambooHrApi.credentials.js", 50 | "dist/credentials/BannerbearApi.credentials.js", 51 | "dist/credentials/BaserowApi.credentials.js", 52 | "dist/credentials/BeeminderApi.credentials.js", 53 | "dist/credentials/BitbucketApi.credentials.js", 54 | "dist/credentials/BitlyApi.credentials.js", 55 | "dist/credentials/BitlyOAuth2Api.credentials.js", 56 | "dist/credentials/BitwardenApi.credentials.js", 57 | "dist/credentials/BoxOAuth2Api.credentials.js", 58 | "dist/credentials/BrandfetchApi.credentials.js", 59 | "dist/credentials/BubbleApi.credentials.js", 60 | "dist/credentials/CalApi.credentials.js", 61 | "dist/credentials/CalendlyApi.credentials.js", 62 | "dist/credentials/CarbonBlackApi.credentials.js", 63 | "dist/credentials/ChargebeeApi.credentials.js", 64 | "dist/credentials/CircleCiApi.credentials.js", 65 | "dist/credentials/CiscoMerakiApi.credentials.js", 66 | "dist/credentials/CiscoSecureEndpointApi.credentials.js", 67 | "dist/credentials/CiscoWebexOAuth2Api.credentials.js", 68 | "dist/credentials/CiscoUmbrellaApi.credentials.js", 69 | "dist/credentials/CitrixAdcApi.credentials.js", 70 | "dist/credentials/CloudflareApi.credentials.js", 71 | "dist/credentials/ClearbitApi.credentials.js", 72 | "dist/credentials/ClickUpApi.credentials.js", 73 | "dist/credentials/ClickUpOAuth2Api.credentials.js", 74 | "dist/credentials/ClockifyApi.credentials.js", 75 | "dist/credentials/CockpitApi.credentials.js", 76 | "dist/credentials/CodaApi.credentials.js", 77 | "dist/credentials/ContentfulApi.credentials.js", 78 | "dist/credentials/ConvertKitApi.credentials.js", 79 | "dist/credentials/CopperApi.credentials.js", 80 | "dist/credentials/CortexApi.credentials.js", 81 | "dist/credentials/CrateDb.credentials.js", 82 | "dist/credentials/CrowdStrikeOAuth2Api.credentials.js", 83 | "dist/credentials/CrowdDevApi.credentials.js", 84 | "dist/credentials/CustomerIoApi.credentials.js", 85 | "dist/credentials/DeepLApi.credentials.js", 86 | "dist/credentials/DemioApi.credentials.js", 87 | "dist/credentials/DhlApi.credentials.js", 88 | "dist/credentials/DiscourseApi.credentials.js", 89 | "dist/credentials/DisqusApi.credentials.js", 90 | "dist/credentials/DriftApi.credentials.js", 91 | "dist/credentials/DriftOAuth2Api.credentials.js", 92 | "dist/credentials/DropboxApi.credentials.js", 93 | "dist/credentials/DropboxOAuth2Api.credentials.js", 94 | "dist/credentials/DropcontactApi.credentials.js", 95 | "dist/credentials/EgoiApi.credentials.js", 96 | "dist/credentials/ElasticsearchApi.credentials.js", 97 | "dist/credentials/ElasticSecurityApi.credentials.js", 98 | "dist/credentials/EmeliaApi.credentials.js", 99 | "dist/credentials/ERPNextApi.credentials.js", 100 | "dist/credentials/EventbriteApi.credentials.js", 101 | "dist/credentials/EventbriteOAuth2Api.credentials.js", 102 | "dist/credentials/F5BigIpApi.credentials.js", 103 | "dist/credentials/FacebookGraphApi.credentials.js", 104 | "dist/credentials/FacebookGraphAppApi.credentials.js", 105 | "dist/credentials/FacebookLeadAdsOAuth2Api.credentials.js", 106 | "dist/credentials/FigmaApi.credentials.js", 107 | "dist/credentials/FileMaker.credentials.js", 108 | "dist/credentials/FlowApi.credentials.js", 109 | "dist/credentials/FormIoApi.credentials.js", 110 | "dist/credentials/FormstackApi.credentials.js", 111 | "dist/credentials/FormstackOAuth2Api.credentials.js", 112 | "dist/credentials/FortiGateApi.credentials.js", 113 | "dist/credentials/FreshdeskApi.credentials.js", 114 | "dist/credentials/FreshserviceApi.credentials.js", 115 | "dist/credentials/FreshworksCrmApi.credentials.js", 116 | "dist/credentials/Ftp.credentials.js", 117 | "dist/credentials/GetResponseApi.credentials.js", 118 | "dist/credentials/GetResponseOAuth2Api.credentials.js", 119 | "dist/credentials/GhostAdminApi.credentials.js", 120 | "dist/credentials/GhostContentApi.credentials.js", 121 | "dist/credentials/GithubApi.credentials.js", 122 | "dist/credentials/GithubOAuth2Api.credentials.js", 123 | "dist/credentials/GitlabApi.credentials.js", 124 | "dist/credentials/GitlabOAuth2Api.credentials.js", 125 | "dist/credentials/GitPassword.credentials.js", 126 | "dist/credentials/GmailOAuth2Api.credentials.js", 127 | "dist/credentials/GoogleAdsOAuth2Api.credentials.js", 128 | "dist/credentials/GoogleAnalyticsOAuth2Api.credentials.js", 129 | "dist/credentials/GoogleApi.credentials.js", 130 | "dist/credentials/GoogleBigQueryOAuth2Api.credentials.js", 131 | "dist/credentials/GoogleBooksOAuth2Api.credentials.js", 132 | "dist/credentials/GoogleCalendarOAuth2Api.credentials.js", 133 | "dist/credentials/GoogleCloudNaturalLanguageOAuth2Api.credentials.js", 134 | "dist/credentials/GoogleCloudStorageOAuth2Api.credentials.js", 135 | "dist/credentials/GoogleContactsOAuth2Api.credentials.js", 136 | "dist/credentials/GoogleDocsOAuth2Api.credentials.js", 137 | "dist/credentials/GoogleDriveOAuth2Api.credentials.js", 138 | "dist/credentials/GoogleFirebaseCloudFirestoreOAuth2Api.credentials.js", 139 | "dist/credentials/GoogleFirebaseRealtimeDatabaseOAuth2Api.credentials.js", 140 | "dist/credentials/GoogleOAuth2Api.credentials.js", 141 | "dist/credentials/GooglePerspectiveOAuth2Api.credentials.js", 142 | "dist/credentials/GoogleSheetsOAuth2Api.credentials.js", 143 | "dist/credentials/GoogleSheetsTriggerOAuth2Api.credentials.js", 144 | "dist/credentials/GoogleSlidesOAuth2Api.credentials.js", 145 | "dist/credentials/GoogleTasksOAuth2Api.credentials.js", 146 | "dist/credentials/GoogleTranslateOAuth2Api.credentials.js", 147 | "dist/credentials/GotifyApi.credentials.js", 148 | "dist/credentials/GoToWebinarOAuth2Api.credentials.js", 149 | "dist/credentials/GristApi.credentials.js", 150 | "dist/credentials/GrafanaApi.credentials.js", 151 | "dist/credentials/GSuiteAdminOAuth2Api.credentials.js", 152 | "dist/credentials/GumroadApi.credentials.js", 153 | "dist/credentials/HaloPSAApi.credentials.js", 154 | "dist/credentials/HarvestApi.credentials.js", 155 | "dist/credentials/HarvestOAuth2Api.credentials.js", 156 | "dist/credentials/HelpScoutOAuth2Api.credentials.js", 157 | "dist/credentials/HighLevelApi.credentials.js", 158 | "dist/credentials/HomeAssistantApi.credentials.js", 159 | "dist/credentials/HttpBasicAuth.credentials.js", 160 | "dist/credentials/HttpDigestAuth.credentials.js", 161 | "dist/credentials/HttpHeaderAuth.credentials.js", 162 | "dist/credentials/HttpCustomAuth.credentials.js", 163 | "dist/credentials/HttpQueryAuth.credentials.js", 164 | "dist/credentials/HubspotApi.credentials.js", 165 | "dist/credentials/HubspotAppToken.credentials.js", 166 | "dist/credentials/HubspotDeveloperApi.credentials.js", 167 | "dist/credentials/HubspotOAuth2Api.credentials.js", 168 | "dist/credentials/HumanticAiApi.credentials.js", 169 | "dist/credentials/HunterApi.credentials.js", 170 | "dist/credentials/HybridAnalysisApi.credentials.js", 171 | "dist/credentials/Imap.credentials.js", 172 | "dist/credentials/ImpervaWafApi.credentials.js", 173 | "dist/credentials/IntercomApi.credentials.js", 174 | "dist/credentials/InvoiceNinjaApi.credentials.js", 175 | "dist/credentials/IterableApi.credentials.js", 176 | "dist/credentials/JenkinsApi.credentials.js", 177 | "dist/credentials/JiraSoftwareCloudApi.credentials.js", 178 | "dist/credentials/JiraSoftwareServerApi.credentials.js", 179 | "dist/credentials/JotFormApi.credentials.js", 180 | "dist/credentials/Kafka.credentials.js", 181 | "dist/credentials/KeapOAuth2Api.credentials.js", 182 | "dist/credentials/KibanaApi.credentials.js", 183 | "dist/credentials/KitemakerApi.credentials.js", 184 | "dist/credentials/KoBoToolboxApi.credentials.js", 185 | "dist/credentials/Ldap.credentials.js", 186 | "dist/credentials/LemlistApi.credentials.js", 187 | "dist/credentials/LinearApi.credentials.js", 188 | "dist/credentials/LinearOAuth2Api.credentials.js", 189 | "dist/credentials/LineNotifyOAuth2Api.credentials.js", 190 | "dist/credentials/LingvaNexApi.credentials.js", 191 | "dist/credentials/LinkedInOAuth2Api.credentials.js", 192 | "dist/credentials/LoneScaleApi.credentials.js", 193 | "dist/credentials/Magento2Api.credentials.js", 194 | "dist/credentials/MailcheckApi.credentials.js", 195 | "dist/credentials/MailchimpApi.credentials.js", 196 | "dist/credentials/MailchimpOAuth2Api.credentials.js", 197 | "dist/credentials/MailerLiteApi.credentials.js", 198 | "dist/credentials/MailgunApi.credentials.js", 199 | "dist/credentials/MailjetEmailApi.credentials.js", 200 | "dist/credentials/MailjetSmsApi.credentials.js", 201 | "dist/credentials/MandrillApi.credentials.js", 202 | "dist/credentials/MarketstackApi.credentials.js", 203 | "dist/credentials/MatrixApi.credentials.js", 204 | "dist/credentials/MattermostApi.credentials.js", 205 | "dist/credentials/MauticApi.credentials.js", 206 | "dist/credentials/MauticOAuth2Api.credentials.js", 207 | "dist/credentials/MediumApi.credentials.js", 208 | "dist/credentials/MediumOAuth2Api.credentials.js", 209 | "dist/credentials/MetabaseApi.credentials.js", 210 | "dist/credentials/MessageBirdApi.credentials.js", 211 | "dist/credentials/MetabaseApi.credentials.js", 212 | "dist/credentials/MicrosoftDynamicsOAuth2Api.credentials.js", 213 | "dist/credentials/MicrosoftEntraOAuth2Api.credentials.js", 214 | "dist/credentials/MicrosoftExcelOAuth2Api.credentials.js", 215 | "dist/credentials/MicrosoftGraphSecurityOAuth2Api.credentials.js", 216 | "dist/credentials/MicrosoftOAuth2Api.credentials.js", 217 | "dist/credentials/MicrosoftOneDriveOAuth2Api.credentials.js", 218 | "dist/credentials/MicrosoftOutlookOAuth2Api.credentials.js", 219 | "dist/credentials/MicrosoftSql.credentials.js", 220 | "dist/credentials/MicrosoftTeamsOAuth2Api.credentials.js", 221 | "dist/credentials/MicrosoftToDoOAuth2Api.credentials.js", 222 | "dist/credentials/MindeeInvoiceApi.credentials.js", 223 | "dist/credentials/MindeeReceiptApi.credentials.js", 224 | "dist/credentials/MispApi.credentials.js", 225 | "dist/credentials/MistApi.credentials.js", 226 | "dist/credentials/MoceanApi.credentials.js", 227 | "dist/credentials/MondayComApi.credentials.js", 228 | "dist/credentials/MondayComOAuth2Api.credentials.js", 229 | "dist/credentials/MongoDb.credentials.js", 230 | "dist/credentials/MonicaCrmApi.credentials.js", 231 | "dist/credentials/Mqtt.credentials.js", 232 | "dist/credentials/Msg91Api.credentials.js", 233 | "dist/credentials/MySql.credentials.js", 234 | "dist/credentials/N8nApi.credentials.js", 235 | "dist/credentials/NasaApi.credentials.js", 236 | "dist/credentials/NetlifyApi.credentials.js", 237 | "dist/credentials/NextCloudApi.credentials.js", 238 | "dist/credentials/NextCloudOAuth2Api.credentials.js", 239 | "dist/credentials/NocoDb.credentials.js", 240 | "dist/credentials/NocoDbApiToken.credentials.js", 241 | "dist/credentials/NotionApi.credentials.js", 242 | "dist/credentials/NotionOAuth2Api.credentials.js", 243 | "dist/credentials/NpmApi.credentials.js", 244 | "dist/credentials/OAuth1Api.credentials.js", 245 | "dist/credentials/OAuth2Api.credentials.js", 246 | "dist/credentials/OdooApi.credentials.js", 247 | "dist/credentials/OktaApi.credentials.js", 248 | "dist/credentials/OneSimpleApi.credentials.js", 249 | "dist/credentials/OnfleetApi.credentials.js", 250 | "dist/credentials/OpenAiApi.credentials.js", 251 | "dist/credentials/OpenCTIApi.credentials.js", 252 | "dist/credentials/OpenWeatherMapApi.credentials.js", 253 | "dist/credentials/OrbitApi.credentials.js", 254 | "dist/credentials/OuraApi.credentials.js", 255 | "dist/credentials/PaddleApi.credentials.js", 256 | "dist/credentials/PagerDutyApi.credentials.js", 257 | "dist/credentials/PagerDutyOAuth2Api.credentials.js", 258 | "dist/credentials/PayPalApi.credentials.js", 259 | "dist/credentials/PeekalinkApi.credentials.js", 260 | "dist/credentials/PhantombusterApi.credentials.js", 261 | "dist/credentials/PhilipsHueOAuth2Api.credentials.js", 262 | "dist/credentials/PipedriveApi.credentials.js", 263 | "dist/credentials/PipedriveOAuth2Api.credentials.js", 264 | "dist/credentials/PlivoApi.credentials.js", 265 | "dist/credentials/Postgres.credentials.js", 266 | "dist/credentials/PostHogApi.credentials.js", 267 | "dist/credentials/PostmarkApi.credentials.js", 268 | "dist/credentials/ProfitWellApi.credentials.js", 269 | "dist/credentials/PushbulletOAuth2Api.credentials.js", 270 | "dist/credentials/PushcutApi.credentials.js", 271 | "dist/credentials/PushoverApi.credentials.js", 272 | "dist/credentials/QRadarApi.credentials.js", 273 | "dist/credentials/QualysApi.credentials.js", 274 | "dist/credentials/QuestDb.credentials.js", 275 | "dist/credentials/QuickBaseApi.credentials.js", 276 | "dist/credentials/QuickBooksOAuth2Api.credentials.js", 277 | "dist/credentials/RabbitMQ.credentials.js", 278 | "dist/credentials/RaindropOAuth2Api.credentials.js", 279 | "dist/credentials/RecordedFutureApi.credentials.js", 280 | "dist/credentials/RedditOAuth2Api.credentials.js", 281 | "dist/credentials/Redis.credentials.js", 282 | "dist/credentials/RocketchatApi.credentials.js", 283 | "dist/credentials/RundeckApi.credentials.js", 284 | "dist/credentials/S3.credentials.js", 285 | "dist/credentials/SalesforceJwtApi.credentials.js", 286 | "dist/credentials/SalesforceOAuth2Api.credentials.js", 287 | "dist/credentials/SalesmateApi.credentials.js", 288 | "dist/credentials/SeaTableApi.credentials.js", 289 | "dist/credentials/SecurityScorecardApi.credentials.js", 290 | "dist/credentials/SegmentApi.credentials.js", 291 | "dist/credentials/SekoiaApi.credentials.js", 292 | "dist/credentials/SendGridApi.credentials.js", 293 | "dist/credentials/BrevoApi.credentials.js", 294 | "dist/credentials/SendyApi.credentials.js", 295 | "dist/credentials/SentryIoApi.credentials.js", 296 | "dist/credentials/SentryIoOAuth2Api.credentials.js", 297 | "dist/credentials/SentryIoServerApi.credentials.js", 298 | "dist/credentials/ServiceNowOAuth2Api.credentials.js", 299 | "dist/credentials/ServiceNowBasicApi.credentials.js", 300 | "dist/credentials/Sftp.credentials.js", 301 | "dist/credentials/ShopifyApi.credentials.js", 302 | "dist/credentials/ShopifyAccessTokenApi.credentials.js", 303 | "dist/credentials/ShopifyOAuth2Api.credentials.js", 304 | "dist/credentials/Signl4Api.credentials.js", 305 | "dist/credentials/SlackApi.credentials.js", 306 | "dist/credentials/SlackOAuth2Api.credentials.js", 307 | "dist/credentials/Sms77Api.credentials.js", 308 | "dist/credentials/Smtp.credentials.js", 309 | "dist/credentials/Snowflake.credentials.js", 310 | "dist/credentials/SplunkApi.credentials.js", 311 | "dist/credentials/SpontitApi.credentials.js", 312 | "dist/credentials/SpotifyOAuth2Api.credentials.js", 313 | "dist/credentials/ShufflerApi.credentials.js", 314 | "dist/credentials/SshPassword.credentials.js", 315 | "dist/credentials/SshPrivateKey.credentials.js", 316 | "dist/credentials/StackbyApi.credentials.js", 317 | "dist/credentials/StoryblokContentApi.credentials.js", 318 | "dist/credentials/StoryblokManagementApi.credentials.js", 319 | "dist/credentials/StrapiApi.credentials.js", 320 | "dist/credentials/StrapiTokenApi.credentials.js", 321 | "dist/credentials/StravaOAuth2Api.credentials.js", 322 | "dist/credentials/StripeApi.credentials.js", 323 | "dist/credentials/SupabaseApi.credentials.js", 324 | "dist/credentials/SurveyMonkeyApi.credentials.js", 325 | "dist/credentials/SurveyMonkeyOAuth2Api.credentials.js", 326 | "dist/credentials/SyncroMspApi.credentials.js", 327 | "dist/credentials/TaigaApi.credentials.js", 328 | "dist/credentials/TapfiliateApi.credentials.js", 329 | "dist/credentials/TelegramApi.credentials.js", 330 | "dist/credentials/TheHiveProjectApi.credentials.js", 331 | "dist/credentials/TheHiveApi.credentials.js", 332 | "dist/credentials/TimescaleDb.credentials.js", 333 | "dist/credentials/TodoistApi.credentials.js", 334 | "dist/credentials/TodoistOAuth2Api.credentials.js", 335 | "dist/credentials/TogglApi.credentials.js", 336 | "dist/credentials/TotpApi.credentials.js", 337 | "dist/credentials/TravisCiApi.credentials.js", 338 | "dist/credentials/TrellixEpoApi.credentials.js", 339 | "dist/credentials/TrelloApi.credentials.js", 340 | "dist/credentials/TwakeCloudApi.credentials.js", 341 | "dist/credentials/TwakeServerApi.credentials.js", 342 | "dist/credentials/TwilioApi.credentials.js", 343 | "dist/credentials/TwistOAuth2Api.credentials.js", 344 | "dist/credentials/TwitterOAuth1Api.credentials.js", 345 | "dist/credentials/TwitterOAuth2Api.credentials.js", 346 | "dist/credentials/TypeformApi.credentials.js", 347 | "dist/credentials/TypeformOAuth2Api.credentials.js", 348 | "dist/credentials/UnleashedSoftwareApi.credentials.js", 349 | "dist/credentials/UpleadApi.credentials.js", 350 | "dist/credentials/UProcApi.credentials.js", 351 | "dist/credentials/UptimeRobotApi.credentials.js", 352 | "dist/credentials/UrlScanIoApi.credentials.js", 353 | "dist/credentials/VeroApi.credentials.js", 354 | "dist/credentials/VirusTotalApi.credentials.js", 355 | "dist/credentials/VonageApi.credentials.js", 356 | "dist/credentials/VenafiTlsProtectCloudApi.credentials.js", 357 | "dist/credentials/VenafiTlsProtectDatacenterApi.credentials.js", 358 | "dist/credentials/WebflowApi.credentials.js", 359 | "dist/credentials/WebflowOAuth2Api.credentials.js", 360 | "dist/credentials/WekanApi.credentials.js", 361 | "dist/credentials/WhatsAppApi.credentials.js", 362 | "dist/credentials/WiseApi.credentials.js", 363 | "dist/credentials/WooCommerceApi.credentials.js", 364 | "dist/credentials/WordpressApi.credentials.js", 365 | "dist/credentials/WorkableApi.credentials.js", 366 | "dist/credentials/WufooApi.credentials.js", 367 | "dist/credentials/XeroOAuth2Api.credentials.js", 368 | "dist/credentials/YourlsApi.credentials.js", 369 | "dist/credentials/YouTubeOAuth2Api.credentials.js", 370 | "dist/credentials/ZammadBasicAuthApi.credentials.js", 371 | "dist/credentials/ZammadTokenAuthApi.credentials.js", 372 | "dist/credentials/ZendeskApi.credentials.js", 373 | "dist/credentials/ZendeskOAuth2Api.credentials.js", 374 | "dist/credentials/ZohoOAuth2Api.credentials.js", 375 | "dist/credentials/ZoomApi.credentials.js", 376 | "dist/credentials/ZoomOAuth2Api.credentials.js", 377 | "dist/credentials/ZscalerZiaApi.credentials.js", 378 | "dist/credentials/ZulipApi.credentials.js" 379 | ], 380 | "nodes": [ 381 | "dist/nodes/ActionNetwork/ActionNetwork.node.js", 382 | "dist/nodes/ActiveCampaign/ActiveCampaign.node.js", 383 | "dist/nodes/ActiveCampaign/ActiveCampaignTrigger.node.js", 384 | "dist/nodes/AcuityScheduling/AcuitySchedulingTrigger.node.js", 385 | "dist/nodes/Adalo/Adalo.node.js", 386 | "dist/nodes/Affinity/Affinity.node.js", 387 | "dist/nodes/Affinity/AffinityTrigger.node.js", 388 | "dist/nodes/AgileCrm/AgileCrm.node.js", 389 | "dist/nodes/Airtable/Airtable.node.js", 390 | "dist/nodes/Airtable/AirtableTrigger.node.js", 391 | "dist/nodes/Amqp/Amqp.node.js", 392 | "dist/nodes/Amqp/AmqpTrigger.node.js", 393 | "dist/nodes/ApiTemplateIo/ApiTemplateIo.node.js", 394 | "dist/nodes/Asana/Asana.node.js", 395 | "dist/nodes/Asana/AsanaTrigger.node.js", 396 | "dist/nodes/Automizy/Automizy.node.js", 397 | "dist/nodes/Autopilot/Autopilot.node.js", 398 | "dist/nodes/Autopilot/AutopilotTrigger.node.js", 399 | "dist/nodes/Aws/AwsLambda.node.js", 400 | "dist/nodes/Aws/AwsSns.node.js", 401 | "dist/nodes/Aws/AwsSnsTrigger.node.js", 402 | "dist/nodes/Aws/CertificateManager/AwsCertificateManager.node.js", 403 | "dist/nodes/Aws/Comprehend/AwsComprehend.node.js", 404 | "dist/nodes/Aws/DynamoDB/AwsDynamoDB.node.js", 405 | "dist/nodes/Aws/ELB/AwsElb.node.js", 406 | "dist/nodes/Aws/Rekognition/AwsRekognition.node.js", 407 | "dist/nodes/Aws/S3/AwsS3.node.js", 408 | "dist/nodes/Aws/SES/AwsSes.node.js", 409 | "dist/nodes/Aws/SQS/AwsSqs.node.js", 410 | "dist/nodes/Aws/Textract/AwsTextract.node.js", 411 | "dist/nodes/Aws/Transcribe/AwsTranscribe.node.js", 412 | "dist/nodes/BambooHr/BambooHr.node.js", 413 | "dist/nodes/Bannerbear/Bannerbear.node.js", 414 | "dist/nodes/Baserow/Baserow.node.js", 415 | "dist/nodes/Beeminder/Beeminder.node.js", 416 | "dist/nodes/Bitbucket/BitbucketTrigger.node.js", 417 | "dist/nodes/Bitly/Bitly.node.js", 418 | "dist/nodes/Bitwarden/Bitwarden.node.js", 419 | "dist/nodes/Box/Box.node.js", 420 | "dist/nodes/Box/BoxTrigger.node.js", 421 | "dist/nodes/Brandfetch/Brandfetch.node.js", 422 | "dist/nodes/Bubble/Bubble.node.js", 423 | "dist/nodes/Cal/CalTrigger.node.js", 424 | "dist/nodes/Calendly/CalendlyTrigger.node.js", 425 | "dist/nodes/Chargebee/Chargebee.node.js", 426 | "dist/nodes/Chargebee/ChargebeeTrigger.node.js", 427 | "dist/nodes/CircleCi/CircleCi.node.js", 428 | "dist/nodes/Cisco/Webex/CiscoWebex.node.js", 429 | "dist/nodes/Citrix/ADC/CitrixAdc.node.js", 430 | "dist/nodes/Cisco/Webex/CiscoWebexTrigger.node.js", 431 | "dist/nodes/Cloudflare/Cloudflare.node.js", 432 | "dist/nodes/Clearbit/Clearbit.node.js", 433 | "dist/nodes/ClickUp/ClickUp.node.js", 434 | "dist/nodes/ClickUp/ClickUpTrigger.node.js", 435 | "dist/nodes/Clockify/Clockify.node.js", 436 | "dist/nodes/Clockify/ClockifyTrigger.node.js", 437 | "dist/nodes/Cockpit/Cockpit.node.js", 438 | "dist/nodes/Coda/Coda.node.js", 439 | "dist/nodes/Code/Code.node.js", 440 | "dist/nodes/CoinGecko/CoinGecko.node.js", 441 | "dist/nodes/CompareDatasets/CompareDatasets.node.js", 442 | "dist/nodes/Compression/Compression.node.js", 443 | "dist/nodes/Contentful/Contentful.node.js", 444 | "dist/nodes/ConvertKit/ConvertKit.node.js", 445 | "dist/nodes/ConvertKit/ConvertKitTrigger.node.js", 446 | "dist/nodes/Copper/Copper.node.js", 447 | "dist/nodes/Copper/CopperTrigger.node.js", 448 | "dist/nodes/Cortex/Cortex.node.js", 449 | "dist/nodes/CrateDb/CrateDb.node.js", 450 | "dist/nodes/Cron/Cron.node.js", 451 | "dist/nodes/CrowdDev/CrowdDev.node.js", 452 | "dist/nodes/CrowdDev/CrowdDevTrigger.node.js", 453 | "dist/nodes/Crypto/Crypto.node.js", 454 | "dist/nodes/CustomerIo/CustomerIo.node.js", 455 | "dist/nodes/CustomerIo/CustomerIoTrigger.node.js", 456 | "dist/nodes/DateTime/DateTime.node.js", 457 | "dist/nodes/DebugHelper/DebugHelper.node.js", 458 | "dist/nodes/DeepL/DeepL.node.js", 459 | "dist/nodes/Demio/Demio.node.js", 460 | "dist/nodes/Dhl/Dhl.node.js", 461 | "dist/nodes/Discord/Discord.node.js", 462 | "dist/nodes/Discourse/Discourse.node.js", 463 | "dist/nodes/Disqus/Disqus.node.js", 464 | "dist/nodes/Drift/Drift.node.js", 465 | "dist/nodes/Dropbox/Dropbox.node.js", 466 | "dist/nodes/Dropcontact/Dropcontact.node.js", 467 | "dist/nodes/EditImage/EditImage.node.js", 468 | "dist/nodes/E2eTest/E2eTest.node.js", 469 | "dist/nodes/Egoi/Egoi.node.js", 470 | "dist/nodes/Elastic/Elasticsearch/Elasticsearch.node.js", 471 | "dist/nodes/Elastic/ElasticSecurity/ElasticSecurity.node.js", 472 | "dist/nodes/EmailReadImap/EmailReadImap.node.js", 473 | "dist/nodes/EmailSend/EmailSend.node.js", 474 | "dist/nodes/Emelia/Emelia.node.js", 475 | "dist/nodes/Emelia/EmeliaTrigger.node.js", 476 | "dist/nodes/ERPNext/ERPNext.node.js", 477 | "dist/nodes/ErrorTrigger/ErrorTrigger.node.js", 478 | "dist/nodes/Eventbrite/EventbriteTrigger.node.js", 479 | "dist/nodes/ExecuteCommand/ExecuteCommand.node.js", 480 | "dist/nodes/ExecuteWorkflow/ExecuteWorkflow.node.js", 481 | "dist/nodes/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.js", 482 | "dist/nodes/ExecutionData/ExecutionData.node.js", 483 | "dist/nodes/Facebook/FacebookGraphApi.node.js", 484 | "dist/nodes/Facebook/FacebookTrigger.node.js", 485 | "dist/nodes/FacebookLeadAds/FacebookLeadAdsTrigger.node.js", 486 | "dist/nodes/Figma/FigmaTrigger.node.js", 487 | "dist/nodes/FileMaker/FileMaker.node.js", 488 | "dist/nodes/Filter/Filter.node.js", 489 | "dist/nodes/Flow/Flow.node.js", 490 | "dist/nodes/Flow/FlowTrigger.node.js", 491 | "dist/nodes/Form/FormTrigger.node.js", 492 | "dist/nodes/FormIo/FormIoTrigger.node.js", 493 | "dist/nodes/Formstack/FormstackTrigger.node.js", 494 | "dist/nodes/Freshdesk/Freshdesk.node.js", 495 | "dist/nodes/Freshservice/Freshservice.node.js", 496 | "dist/nodes/FreshworksCrm/FreshworksCrm.node.js", 497 | "dist/nodes/Ftp/Ftp.node.js", 498 | "dist/nodes/Function/Function.node.js", 499 | "dist/nodes/FunctionItem/FunctionItem.node.js", 500 | "dist/nodes/GetResponse/GetResponse.node.js", 501 | "dist/nodes/GetResponse/GetResponseTrigger.node.js", 502 | "dist/nodes/Ghost/Ghost.node.js", 503 | "dist/nodes/Git/Git.node.js", 504 | "dist/nodes/Github/Github.node.js", 505 | "dist/nodes/Github/GithubTrigger.node.js", 506 | "dist/nodes/Gitlab/Gitlab.node.js", 507 | "dist/nodes/Gitlab/GitlabTrigger.node.js", 508 | "dist/nodes/Google/Ads/GoogleAds.node.js", 509 | "dist/nodes/Google/Analytics/GoogleAnalytics.node.js", 510 | "dist/nodes/Google/BigQuery/GoogleBigQuery.node.js", 511 | "dist/nodes/Google/Books/GoogleBooks.node.js", 512 | "dist/nodes/Google/Calendar/GoogleCalendar.node.js", 513 | "dist/nodes/Google/Calendar/GoogleCalendarTrigger.node.js", 514 | "dist/nodes/Google/Chat/GoogleChat.node.js", 515 | "dist/nodes/Google/CloudNaturalLanguage/GoogleCloudNaturalLanguage.node.js", 516 | "dist/nodes/Google/CloudStorage/GoogleCloudStorage.node.js", 517 | "dist/nodes/Google/Contacts/GoogleContacts.node.js", 518 | "dist/nodes/Google/Docs/GoogleDocs.node.js", 519 | "dist/nodes/Google/Drive/GoogleDrive.node.js", 520 | "dist/nodes/Google/Drive/GoogleDriveTrigger.node.js", 521 | "dist/nodes/Google/Firebase/CloudFirestore/GoogleFirebaseCloudFirestore.node.js", 522 | "dist/nodes/Google/Firebase/RealtimeDatabase/GoogleFirebaseRealtimeDatabase.node.js", 523 | "dist/nodes/Google/Gmail/Gmail.node.js", 524 | "dist/nodes/Google/Gmail/GmailTrigger.node.js", 525 | "dist/nodes/Google/GSuiteAdmin/GSuiteAdmin.node.js", 526 | "dist/nodes/Google/Perspective/GooglePerspective.node.js", 527 | "dist/nodes/Google/Sheet/GoogleSheets.node.js", 528 | "dist/nodes/Google/Sheet/GoogleSheetsTrigger.node.js", 529 | "dist/nodes/Google/Slides/GoogleSlides.node.js", 530 | "dist/nodes/Google/Task/GoogleTasks.node.js", 531 | "dist/nodes/Google/Translate/GoogleTranslate.node.js", 532 | "dist/nodes/Google/YouTube/YouTube.node.js", 533 | "dist/nodes/Gotify/Gotify.node.js", 534 | "dist/nodes/GoToWebinar/GoToWebinar.node.js", 535 | "dist/nodes/Grafana/Grafana.node.js", 536 | "dist/nodes/GraphQL/GraphQL.node.js", 537 | "dist/nodes/Grist/Grist.node.js", 538 | "dist/nodes/Gumroad/GumroadTrigger.node.js", 539 | "dist/nodes/HackerNews/HackerNews.node.js", 540 | "dist/nodes/HaloPSA/HaloPSA.node.js", 541 | "dist/nodes/Harvest/Harvest.node.js", 542 | "dist/nodes/HelpScout/HelpScout.node.js", 543 | "dist/nodes/HelpScout/HelpScoutTrigger.node.js", 544 | "dist/nodes/HighLevel/HighLevel.node.js", 545 | "dist/nodes/HomeAssistant/HomeAssistant.node.js", 546 | "dist/nodes/HtmlExtract/HtmlExtract.node.js", 547 | "dist/nodes/Html/Html.node.js", 548 | "dist/nodes/HttpRequest/HttpRequest.node.js", 549 | "dist/nodes/Hubspot/Hubspot.node.js", 550 | "dist/nodes/Hubspot/HubspotTrigger.node.js", 551 | "dist/nodes/HumanticAI/HumanticAi.node.js", 552 | "dist/nodes/Hunter/Hunter.node.js", 553 | "dist/nodes/ICalendar/ICalendar.node.js", 554 | "dist/nodes/If/If.node.js", 555 | "dist/nodes/Intercom/Intercom.node.js", 556 | "dist/nodes/Interval/Interval.node.js", 557 | "dist/nodes/InvoiceNinja/InvoiceNinja.node.js", 558 | "dist/nodes/InvoiceNinja/InvoiceNinjaTrigger.node.js", 559 | "dist/nodes/ItemLists/ItemLists.node.js", 560 | "dist/nodes/Iterable/Iterable.node.js", 561 | "dist/nodes/Jenkins/Jenkins.node.js", 562 | "dist/nodes/Jira/Jira.node.js", 563 | "dist/nodes/Jira/JiraTrigger.node.js", 564 | "dist/nodes/JotForm/JotFormTrigger.node.js", 565 | "dist/nodes/Kafka/Kafka.node.js", 566 | "dist/nodes/Kafka/KafkaTrigger.node.js", 567 | "dist/nodes/Keap/Keap.node.js", 568 | "dist/nodes/Keap/KeapTrigger.node.js", 569 | "dist/nodes/Kitemaker/Kitemaker.node.js", 570 | "dist/nodes/KoBoToolbox/KoBoToolbox.node.js", 571 | "dist/nodes/KoBoToolbox/KoBoToolboxTrigger.node.js", 572 | "dist/nodes/Ldap/Ldap.node.js", 573 | "dist/nodes/Lemlist/Lemlist.node.js", 574 | "dist/nodes/Lemlist/LemlistTrigger.node.js", 575 | "dist/nodes/Line/Line.node.js", 576 | "dist/nodes/Linear/Linear.node.js", 577 | "dist/nodes/Linear/LinearTrigger.node.js", 578 | "dist/nodes/LingvaNex/LingvaNex.node.js", 579 | "dist/nodes/LinkedIn/LinkedIn.node.js", 580 | "dist/nodes/LocalFileTrigger/LocalFileTrigger.node.js", 581 | "dist/nodes/LoneScale/LoneScaleTrigger.node.js", 582 | "dist/nodes/LoneScale/LoneScale.node.js", 583 | "dist/nodes/Magento/Magento2.node.js", 584 | "dist/nodes/Mailcheck/Mailcheck.node.js", 585 | "dist/nodes/Mailchimp/Mailchimp.node.js", 586 | "dist/nodes/Mailchimp/MailchimpTrigger.node.js", 587 | "dist/nodes/MailerLite/MailerLite.node.js", 588 | "dist/nodes/MailerLite/MailerLiteTrigger.node.js", 589 | "dist/nodes/Mailgun/Mailgun.node.js", 590 | "dist/nodes/Mailjet/Mailjet.node.js", 591 | "dist/nodes/Mailjet/MailjetTrigger.node.js", 592 | "dist/nodes/Mandrill/Mandrill.node.js", 593 | "dist/nodes/ManualTrigger/ManualTrigger.node.js", 594 | "dist/nodes/Markdown/Markdown.node.js", 595 | "dist/nodes/Marketstack/Marketstack.node.js", 596 | "dist/nodes/Matrix/Matrix.node.js", 597 | "dist/nodes/Mattermost/Mattermost.node.js", 598 | "dist/nodes/Mautic/Mautic.node.js", 599 | "dist/nodes/Mautic/MauticTrigger.node.js", 600 | "dist/nodes/Medium/Medium.node.js", 601 | "dist/nodes/Merge/Merge.node.js", 602 | "dist/nodes/MessageBird/MessageBird.node.js", 603 | "dist/nodes/Metabase/Metabase.node.js", 604 | "dist/nodes/Microsoft/Dynamics/MicrosoftDynamicsCrm.node.js", 605 | "dist/nodes/Microsoft/Excel/MicrosoftExcel.node.js", 606 | "dist/nodes/Microsoft/GraphSecurity/MicrosoftGraphSecurity.node.js", 607 | "dist/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.js", 608 | "dist/nodes/Microsoft/Outlook/MicrosoftOutlook.node.js", 609 | "dist/nodes/Microsoft/Sql/MicrosoftSql.node.js", 610 | "dist/nodes/Microsoft/Teams/MicrosoftTeams.node.js", 611 | "dist/nodes/Microsoft/ToDo/MicrosoftToDo.node.js", 612 | "dist/nodes/Mindee/Mindee.node.js", 613 | "dist/nodes/Misp/Misp.node.js", 614 | "dist/nodes/Mocean/Mocean.node.js", 615 | "dist/nodes/MondayCom/MondayCom.node.js", 616 | "dist/nodes/MongoDb/MongoDb.node.js", 617 | "dist/nodes/MonicaCrm/MonicaCrm.node.js", 618 | "dist/nodes/MoveBinaryData/MoveBinaryData.node.js", 619 | "dist/nodes/MQTT/Mqtt.node.js", 620 | "dist/nodes/MQTT/MqttTrigger.node.js", 621 | "dist/nodes/Msg91/Msg91.node.js", 622 | "dist/nodes/MySql/MySql.node.js", 623 | "dist/nodes/N8n/N8n.node.js", 624 | "dist/nodes/N8nTrainingCustomerDatastore/N8nTrainingCustomerDatastore.node.js", 625 | "dist/nodes/N8nTrainingCustomerMessenger/N8nTrainingCustomerMessenger.node.js", 626 | "dist/nodes/N8nTrigger/N8nTrigger.node.js", 627 | "dist/nodes/Nasa/Nasa.node.js", 628 | "dist/nodes/Netlify/Netlify.node.js", 629 | "dist/nodes/Netlify/NetlifyTrigger.node.js", 630 | "dist/nodes/NextCloud/NextCloud.node.js", 631 | "dist/nodes/NocoDB/NocoDB.node.js", 632 | "dist/nodes/Brevo/Brevo.node.js", 633 | "dist/nodes/Brevo/BrevoTrigger.node.js", 634 | "dist/nodes/StickyNote/StickyNote.node.js", 635 | "dist/nodes/NoOp/NoOp.node.js", 636 | "dist/nodes/Onfleet/Onfleet.node.js", 637 | "dist/nodes/Onfleet/OnfleetTrigger.node.js", 638 | "dist/nodes/Notion/Notion.node.js", 639 | "dist/nodes/Notion/NotionTrigger.node.js", 640 | "dist/nodes/Npm/Npm.node.js", 641 | "dist/nodes/Odoo/Odoo.node.js", 642 | "dist/nodes/OneSimpleApi/OneSimpleApi.node.js", 643 | "dist/nodes/OpenAi/OpenAi.node.js", 644 | "dist/nodes/OpenThesaurus/OpenThesaurus.node.js", 645 | "dist/nodes/OpenWeatherMap/OpenWeatherMap.node.js", 646 | "dist/nodes/Orbit/Orbit.node.js", 647 | "dist/nodes/Oura/Oura.node.js", 648 | "dist/nodes/Paddle/Paddle.node.js", 649 | "dist/nodes/PagerDuty/PagerDuty.node.js", 650 | "dist/nodes/PayPal/PayPal.node.js", 651 | "dist/nodes/PayPal/PayPalTrigger.node.js", 652 | "dist/nodes/Peekalink/Peekalink.node.js", 653 | "dist/nodes/Phantombuster/Phantombuster.node.js", 654 | "dist/nodes/PhilipsHue/PhilipsHue.node.js", 655 | "dist/nodes/Pipedrive/Pipedrive.node.js", 656 | "dist/nodes/Pipedrive/PipedriveTrigger.node.js", 657 | "dist/nodes/Plivo/Plivo.node.js", 658 | "dist/nodes/PostBin/PostBin.node.js", 659 | "dist/nodes/Postgres/Postgres.node.js", 660 | "dist/nodes/Postgres/PostgresTrigger.node.js", 661 | "dist/nodes/PostHog/PostHog.node.js", 662 | "dist/nodes/Postmark/PostmarkTrigger.node.js", 663 | "dist/nodes/ProfitWell/ProfitWell.node.js", 664 | "dist/nodes/Pushbullet/Pushbullet.node.js", 665 | "dist/nodes/Pushcut/Pushcut.node.js", 666 | "dist/nodes/Pushcut/PushcutTrigger.node.js", 667 | "dist/nodes/Pushover/Pushover.node.js", 668 | "dist/nodes/QuestDb/QuestDb.node.js", 669 | "dist/nodes/QuickBase/QuickBase.node.js", 670 | "dist/nodes/QuickBooks/QuickBooks.node.js", 671 | "dist/nodes/QuickChart/QuickChart.node.js", 672 | "dist/nodes/RabbitMQ/RabbitMQ.node.js", 673 | "dist/nodes/RabbitMQ/RabbitMQTrigger.node.js", 674 | "dist/nodes/Raindrop/Raindrop.node.js", 675 | "dist/nodes/ReadBinaryFile/ReadBinaryFile.node.js", 676 | "dist/nodes/ReadBinaryFiles/ReadBinaryFiles.node.js", 677 | "dist/nodes/ReadPdf/ReadPDF.node.js", 678 | "dist/nodes/Reddit/Reddit.node.js", 679 | "dist/nodes/Redis/Redis.node.js", 680 | "dist/nodes/Redis/RedisTrigger.node.js", 681 | "dist/nodes/RenameKeys/RenameKeys.node.js", 682 | "dist/nodes/RespondToWebhook/RespondToWebhook.node.js", 683 | "dist/nodes/Rocketchat/Rocketchat.node.js", 684 | "dist/nodes/RssFeedRead/RssFeedRead.node.js", 685 | "dist/nodes/RssFeedRead/RssFeedReadTrigger.node.js", 686 | "dist/nodes/Rundeck/Rundeck.node.js", 687 | "dist/nodes/S3/S3.node.js", 688 | "dist/nodes/Salesforce/Salesforce.node.js", 689 | "dist/nodes/Salesmate/Salesmate.node.js", 690 | "dist/nodes/Schedule/ScheduleTrigger.node.js", 691 | "dist/nodes/SeaTable/SeaTable.node.js", 692 | "dist/nodes/SeaTable/SeaTableTrigger.node.js", 693 | "dist/nodes/SecurityScorecard/SecurityScorecard.node.js", 694 | "dist/nodes/Segment/Segment.node.js", 695 | "dist/nodes/SendGrid/SendGrid.node.js", 696 | "dist/nodes/Sendy/Sendy.node.js", 697 | "dist/nodes/SentryIo/SentryIo.node.js", 698 | "dist/nodes/ServiceNow/ServiceNow.node.js", 699 | "dist/nodes/Set/Set.node.js", 700 | "dist/nodes/Shopify/Shopify.node.js", 701 | "dist/nodes/Shopify/ShopifyTrigger.node.js", 702 | "dist/nodes/Signl4/Signl4.node.js", 703 | "dist/nodes/Slack/Slack.node.js", 704 | "dist/nodes/Sms77/Sms77.node.js", 705 | "dist/nodes/Snowflake/Snowflake.node.js", 706 | "dist/nodes/SplitInBatches/SplitInBatches.node.js", 707 | "dist/nodes/Splunk/Splunk.node.js", 708 | "dist/nodes/Spontit/Spontit.node.js", 709 | "dist/nodes/Spotify/Spotify.node.js", 710 | "dist/nodes/SpreadsheetFile/SpreadsheetFile.node.js", 711 | "dist/nodes/SseTrigger/SseTrigger.node.js", 712 | "dist/nodes/Ssh/Ssh.node.js", 713 | "dist/nodes/Stackby/Stackby.node.js", 714 | "dist/nodes/Start/Start.node.js", 715 | "dist/nodes/StopAndError/StopAndError.node.js", 716 | "dist/nodes/Storyblok/Storyblok.node.js", 717 | "dist/nodes/Strapi/Strapi.node.js", 718 | "dist/nodes/Strava/Strava.node.js", 719 | "dist/nodes/Strava/StravaTrigger.node.js", 720 | "dist/nodes/Stripe/Stripe.node.js", 721 | "dist/nodes/Stripe/StripeTrigger.node.js", 722 | "dist/nodes/Supabase/Supabase.node.js", 723 | "dist/nodes/SurveyMonkey/SurveyMonkeyTrigger.node.js", 724 | "dist/nodes/Switch/Switch.node.js", 725 | "dist/nodes/SyncroMSP/SyncroMsp.node.js", 726 | "dist/nodes/Taiga/Taiga.node.js", 727 | "dist/nodes/Taiga/TaigaTrigger.node.js", 728 | "dist/nodes/Tapfiliate/Tapfiliate.node.js", 729 | "dist/nodes/Telegram/Telegram.node.js", 730 | "dist/nodes/Telegram/TelegramTrigger.node.js", 731 | "dist/nodes/TheHiveProject/TheHiveProject.node.js", 732 | "dist/nodes/TheHiveProject/TheHiveProjectTrigger.node.js", 733 | "dist/nodes/TheHive/TheHive.node.js", 734 | "dist/nodes/TheHive/TheHiveTrigger.node.js", 735 | "dist/nodes/TimescaleDb/TimescaleDb.node.js", 736 | "dist/nodes/Todoist/Todoist.node.js", 737 | "dist/nodes/Toggl/TogglTrigger.node.js", 738 | "dist/nodes/Totp/Totp.node.js", 739 | "dist/nodes/TravisCi/TravisCi.node.js", 740 | "dist/nodes/Trello/Trello.node.js", 741 | "dist/nodes/Trello/TrelloTrigger.node.js", 742 | "dist/nodes/Twake/Twake.node.js", 743 | "dist/nodes/Twilio/Twilio.node.js", 744 | "dist/nodes/Twist/Twist.node.js", 745 | "dist/nodes/Twitter/Twitter.node.js", 746 | "dist/nodes/Typeform/TypeformTrigger.node.js", 747 | "dist/nodes/UnleashedSoftware/UnleashedSoftware.node.js", 748 | "dist/nodes/Uplead/Uplead.node.js", 749 | "dist/nodes/UProc/UProc.node.js", 750 | "dist/nodes/UptimeRobot/UptimeRobot.node.js", 751 | "dist/nodes/UrlScanIo/UrlScanIo.node.js", 752 | "dist/nodes/Vero/Vero.node.js", 753 | "dist/nodes/Venafi/ProtectCloud/VenafiTlsProtectCloud.node.js", 754 | "dist/nodes/Venafi/ProtectCloud/VenafiTlsProtectCloudTrigger.node.js", 755 | "dist/nodes/Venafi/Datacenter/VenafiTlsProtectDatacenter.node.js", 756 | "dist/nodes/Vonage/Vonage.node.js", 757 | "dist/nodes/Wait/Wait.node.js", 758 | "dist/nodes/Webflow/Webflow.node.js", 759 | "dist/nodes/Webflow/WebflowTrigger.node.js", 760 | "dist/nodes/Webhook/Webhook.node.js", 761 | "dist/nodes/Wekan/Wekan.node.js", 762 | "dist/nodes/WhatsApp/WhatsApp.node.js", 763 | "dist/nodes/Wise/Wise.node.js", 764 | "dist/nodes/Wise/WiseTrigger.node.js", 765 | "dist/nodes/WooCommerce/WooCommerce.node.js", 766 | "dist/nodes/WooCommerce/WooCommerceTrigger.node.js", 767 | "dist/nodes/Wordpress/Wordpress.node.js", 768 | "dist/nodes/Workable/WorkableTrigger.node.js", 769 | "dist/nodes/WorkflowTrigger/WorkflowTrigger.node.js", 770 | "dist/nodes/WriteBinaryFile/WriteBinaryFile.node.js", 771 | "dist/nodes/Wufoo/WufooTrigger.node.js", 772 | "dist/nodes/Xero/Xero.node.js", 773 | "dist/nodes/Xml/Xml.node.js", 774 | "dist/nodes/Yourls/Yourls.node.js", 775 | "dist/nodes/Zammad/Zammad.node.js", 776 | "dist/nodes/Zendesk/Zendesk.node.js", 777 | "dist/nodes/Zendesk/ZendeskTrigger.node.js", 778 | "dist/nodes/Zoho/ZohoCrm.node.js", 779 | "dist/nodes/Zoom/Zoom.node.js", 780 | "dist/nodes/Zulip/Zulip.node.js" 781 | ] 782 | }, 783 | "devDependencies": { 784 | "@types/amqplib": "^0.10.1", 785 | "@types/aws4": "^1.5.1", 786 | "@types/basic-auth": "^1.1.3", 787 | "@types/cheerio": "^0.22.15", 788 | "@types/cron": "~1.7.1", 789 | "@types/eventsource": "^1.1.2", 790 | "@types/express": "^4.17.6", 791 | "@types/gm": "^1.25.0", 792 | "@types/imap-simple": "^4.2.0", 793 | "@types/js-nacl": "^1.3.0", 794 | "@types/jsonwebtoken": "^9.0.1", 795 | "@types/lodash": "^4.14.195", 796 | "@types/lossless-json": "^1.0.0", 797 | "@types/mailparser": "^2.7.3", 798 | "@types/mime-types": "^2.1.0", 799 | "@types/mssql": "^6.0.2", 800 | "@types/node-ssh": "^7.0.1", 801 | "@types/nodemailer": "^6.4.0", 802 | "@types/promise-ftp": "^1.3.4", 803 | "@types/redis": "^2.8.11", 804 | "@types/request-promise-native": "~1.0.15", 805 | "@types/rfc2047": "^2.0.1", 806 | "@types/showdown": "^1.9.4", 807 | "@types/snowflake-sdk": "^1.6.12", 808 | "@types/ssh2-sftp-client": "^5.1.0", 809 | "@types/tmp": "^0.2.0", 810 | "@types/uuid": "^8.3.2", 811 | "@types/xml2js": "^0.4.11", 812 | "eslint-plugin-n8n-nodes-base": "^1.16.0", 813 | "gulp": "^4.0.0", 814 | "n8n-core": "1.14.1" 815 | }, 816 | "dependencies": { 817 | "@kafkajs/confluent-schema-registry": "1.0.6", 818 | "@n8n/vm2": "^3.9.20", 819 | "amqplib": "^0.10.3", 820 | "aws4": "^1.8.0", 821 | "basic-auth": "^2.0.1", 822 | "change-case": "^4.1.1", 823 | "cheerio": "1.0.0-rc.6", 824 | "chokidar": "3.5.2", 825 | "cron": "~1.7.2", 826 | "csv-parse": "^5.5.0", 827 | "currency-codes": "^2.1.0", 828 | "eventsource": "^2.0.2", 829 | "fast-glob": "^3.2.5", 830 | "fflate": "^0.7.0", 831 | "get-system-fonts": "^2.0.2", 832 | "gm": "^1.25.0", 833 | "iconv-lite": "^0.6.2", 834 | "ics": "^2.27.0", 835 | "imap-simple": "^4.3.0", 836 | "isbot": "^3.6.13", 837 | "iso-639-1": "^2.1.3", 838 | "js-nacl": "^1.4.0", 839 | "jsonwebtoken": "^9.0.0", 840 | "kafkajs": "^1.14.0", 841 | "ldapts": "^4.2.6", 842 | "lodash": "^4.17.21", 843 | "lossless-json": "^1.0.4", 844 | "luxon": "^3.3.0", 845 | "mailparser": "^3.2.0", 846 | "minifaker": "^1.34.1", 847 | "moment": "~2.29.2", 848 | "moment-timezone": "^0.5.28", 849 | "mongodb": "^4.17.1", 850 | "mqtt": "^5.0.2", 851 | "mssql": "^8.1.2", 852 | "mysql2": "~2.3.0", 853 | "nanoid": "^3.3.6", 854 | "node-html-markdown": "^1.1.3", 855 | "node-ssh": "^12.0.0", 856 | "nodemailer": "^6.7.1", 857 | "otpauth": "^9.1.1", 858 | "pdfjs-dist": "^2.16.105", 859 | "pg": "^8.3.0", 860 | "pg-promise": "^10.5.8", 861 | "pretty-bytes": "^5.6.0", 862 | "promise-ftp": "^1.3.5", 863 | "pyodide": "^0.23.4", 864 | "redis": "^3.1.1", 865 | "rfc2047": "^4.0.1", 866 | "rhea": "^1.0.11", 867 | "rss-parser": "^3.7.0", 868 | "semver": "^7.5.4", 869 | "showdown": "^2.0.3", 870 | "simple-git": "^3.17.0", 871 | "snowflake-sdk": "^1.8.0", 872 | "ssh2-sftp-client": "^7.0.0", 873 | "tmp-promise": "^3.0.2", 874 | "typedi": "^0.10.0", 875 | "uuid": "^8.3.2", 876 | "xlsx": "https://cdn.sheetjs.com/xlsx-0.19.3/xlsx-0.19.3.tgz", 877 | "xml2js": "^0.5.0", 878 | "n8n-workflow": "1.14.1" 879 | }, 880 | "scripts": { 881 | "clean": "rimraf dist .turbo", 882 | "dev": "pnpm watch", 883 | "typecheck": "tsc", 884 | "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && gulp build:icons && gulp build:translations && pnpm build:metadata", 885 | "build:translations": "gulp build:translations", 886 | "build:metadata": "pnpm n8n-generate-known && pnpm n8n-generate-ui-types", 887 | "format": "prettier --write . --ignore-path ../../.prettierignore", 888 | "lint": "eslint . --quiet && node ./scripts/validate-load-options-methods.js", 889 | "lintfix": "eslint . --fix", 890 | "watch": "tsc-watch -p tsconfig.build.json --onCompilationComplete \"tsc-alias -p tsconfig.build.json\" --onSuccess \"pnpm n8n-generate-ui-types\"", 891 | "test": "jest" 892 | } 893 | }, 894 | "extraction_time_ms": 7, 895 | "extracted_at": "2025-06-07T17:49:22.717Z" 896 | } ``` -------------------------------------------------------------------------------- /src/http-server-single-session.ts: -------------------------------------------------------------------------------- ```typescript 1 | #!/usr/bin/env node 2 | /** 3 | * Single-Session HTTP server for n8n-MCP 4 | * Implements Hybrid Single-Session Architecture for protocol compliance 5 | * while maintaining simplicity for single-player use case 6 | */ 7 | import express from 'express'; 8 | import rateLimit from 'express-rate-limit'; 9 | import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; 10 | import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; 11 | import { N8NDocumentationMCPServer } from './mcp/server'; 12 | import { ConsoleManager } from './utils/console-manager'; 13 | import { logger } from './utils/logger'; 14 | import { AuthManager } from './utils/auth'; 15 | import { readFileSync } from 'fs'; 16 | import dotenv from 'dotenv'; 17 | import { getStartupBaseUrl, formatEndpointUrls, detectBaseUrl } from './utils/url-detector'; 18 | import { PROJECT_VERSION } from './utils/version'; 19 | import { v4 as uuidv4 } from 'uuid'; 20 | import { createHash } from 'crypto'; 21 | import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js'; 22 | import { 23 | negotiateProtocolVersion, 24 | logProtocolNegotiation, 25 | STANDARD_PROTOCOL_VERSION 26 | } from './utils/protocol-version'; 27 | import { InstanceContext, validateInstanceContext } from './types/instance-context'; 28 | 29 | dotenv.config(); 30 | 31 | // Protocol version constant - will be negotiated per client 32 | const DEFAULT_PROTOCOL_VERSION = STANDARD_PROTOCOL_VERSION; 33 | 34 | // Type-safe headers interface for multi-tenant support 35 | interface MultiTenantHeaders { 36 | 'x-n8n-url'?: string; 37 | 'x-n8n-key'?: string; 38 | 'x-instance-id'?: string; 39 | 'x-session-id'?: string; 40 | } 41 | 42 | // Session management constants 43 | const MAX_SESSIONS = 100; 44 | const SESSION_CLEANUP_INTERVAL = 5 * 60 * 1000; // 5 minutes 45 | 46 | interface Session { 47 | server: N8NDocumentationMCPServer; 48 | transport: StreamableHTTPServerTransport | SSEServerTransport; 49 | lastAccess: Date; 50 | sessionId: string; 51 | initialized: boolean; 52 | isSSE: boolean; 53 | } 54 | 55 | interface SessionMetrics { 56 | totalSessions: number; 57 | activeSessions: number; 58 | expiredSessions: number; 59 | lastCleanup: Date; 60 | } 61 | 62 | /** 63 | * Extract multi-tenant headers in a type-safe manner 64 | */ 65 | function extractMultiTenantHeaders(req: express.Request): MultiTenantHeaders { 66 | return { 67 | 'x-n8n-url': req.headers['x-n8n-url'] as string | undefined, 68 | 'x-n8n-key': req.headers['x-n8n-key'] as string | undefined, 69 | 'x-instance-id': req.headers['x-instance-id'] as string | undefined, 70 | 'x-session-id': req.headers['x-session-id'] as string | undefined, 71 | }; 72 | } 73 | 74 | export class SingleSessionHTTPServer { 75 | // Map to store transports by session ID (following SDK pattern) 76 | private transports: { [sessionId: string]: StreamableHTTPServerTransport } = {}; 77 | private servers: { [sessionId: string]: N8NDocumentationMCPServer } = {}; 78 | private sessionMetadata: { [sessionId: string]: { lastAccess: Date; createdAt: Date } } = {}; 79 | private sessionContexts: { [sessionId: string]: InstanceContext | undefined } = {}; 80 | private contextSwitchLocks: Map<string, Promise<void>> = new Map(); 81 | private session: Session | null = null; // Keep for SSE compatibility 82 | private consoleManager = new ConsoleManager(); 83 | private expressServer: any; 84 | private sessionTimeout = 30 * 60 * 1000; // 30 minutes 85 | private authToken: string | null = null; 86 | private cleanupTimer: NodeJS.Timeout | null = null; 87 | 88 | constructor() { 89 | // Validate environment on construction 90 | this.validateEnvironment(); 91 | // No longer pre-create session - will be created per initialize request following SDK pattern 92 | 93 | // Start periodic session cleanup 94 | this.startSessionCleanup(); 95 | } 96 | 97 | /** 98 | * Start periodic session cleanup 99 | */ 100 | private startSessionCleanup(): void { 101 | this.cleanupTimer = setInterval(async () => { 102 | try { 103 | await this.cleanupExpiredSessions(); 104 | } catch (error) { 105 | logger.error('Error during session cleanup', error); 106 | } 107 | }, SESSION_CLEANUP_INTERVAL); 108 | 109 | logger.info('Session cleanup started', { 110 | interval: SESSION_CLEANUP_INTERVAL / 1000 / 60, 111 | maxSessions: MAX_SESSIONS, 112 | sessionTimeout: this.sessionTimeout / 1000 / 60 113 | }); 114 | } 115 | 116 | /** 117 | * Clean up expired sessions based on last access time 118 | */ 119 | private cleanupExpiredSessions(): void { 120 | const now = Date.now(); 121 | const expiredSessions: string[] = []; 122 | 123 | // Check for expired sessions 124 | for (const sessionId in this.sessionMetadata) { 125 | const metadata = this.sessionMetadata[sessionId]; 126 | if (now - metadata.lastAccess.getTime() > this.sessionTimeout) { 127 | expiredSessions.push(sessionId); 128 | } 129 | } 130 | 131 | // Also check for orphaned contexts (sessions that were removed but context remained) 132 | for (const sessionId in this.sessionContexts) { 133 | if (!this.sessionMetadata[sessionId]) { 134 | // Context exists but session doesn't - clean it up 135 | delete this.sessionContexts[sessionId]; 136 | logger.debug('Cleaned orphaned session context', { sessionId }); 137 | } 138 | } 139 | 140 | // Remove expired sessions 141 | for (const sessionId of expiredSessions) { 142 | this.removeSession(sessionId, 'expired'); 143 | } 144 | 145 | if (expiredSessions.length > 0) { 146 | logger.info('Cleaned up expired sessions', { 147 | removed: expiredSessions.length, 148 | remaining: this.getActiveSessionCount() 149 | }); 150 | } 151 | } 152 | 153 | /** 154 | * Remove a session and clean up resources 155 | */ 156 | private async removeSession(sessionId: string, reason: string): Promise<void> { 157 | try { 158 | // Close transport if exists 159 | if (this.transports[sessionId]) { 160 | await this.transports[sessionId].close(); 161 | delete this.transports[sessionId]; 162 | } 163 | 164 | // Remove server, metadata, and context 165 | delete this.servers[sessionId]; 166 | delete this.sessionMetadata[sessionId]; 167 | delete this.sessionContexts[sessionId]; 168 | 169 | logger.info('Session removed', { sessionId, reason }); 170 | } catch (error) { 171 | logger.warn('Error removing session', { sessionId, reason, error }); 172 | } 173 | } 174 | 175 | /** 176 | * Get current active session count 177 | */ 178 | private getActiveSessionCount(): number { 179 | return Object.keys(this.transports).length; 180 | } 181 | 182 | /** 183 | * Check if we can create a new session 184 | */ 185 | private canCreateSession(): boolean { 186 | return this.getActiveSessionCount() < MAX_SESSIONS; 187 | } 188 | 189 | /** 190 | * Validate session ID format 191 | * 192 | * Accepts any non-empty string to support various MCP clients: 193 | * - UUIDv4 (internal n8n-mcp format) 194 | * - instance-{userId}-{hash}-{uuid} (multi-tenant format) 195 | * - Custom formats from mcp-remote and other proxies 196 | * 197 | * Security: Session validation happens via lookup in this.transports, 198 | * not format validation. This ensures compatibility with all MCP clients. 199 | * 200 | * @param sessionId - Session identifier from MCP client 201 | * @returns true if valid, false otherwise 202 | */ 203 | private isValidSessionId(sessionId: string): boolean { 204 | // Accept any non-empty string as session ID 205 | // This ensures compatibility with all MCP clients and proxies 206 | return Boolean(sessionId && sessionId.length > 0); 207 | } 208 | 209 | /** 210 | * Sanitize error information for client responses 211 | */ 212 | private sanitizeErrorForClient(error: unknown): { message: string; code: string } { 213 | const isProduction = process.env.NODE_ENV === 'production'; 214 | 215 | if (error instanceof Error) { 216 | // In production, only return generic messages 217 | if (isProduction) { 218 | // Map known error types to safe messages 219 | if (error.message.includes('Unauthorized') || error.message.includes('authentication')) { 220 | return { message: 'Authentication failed', code: 'AUTH_ERROR' }; 221 | } 222 | if (error.message.includes('Session') || error.message.includes('session')) { 223 | return { message: 'Session error', code: 'SESSION_ERROR' }; 224 | } 225 | if (error.message.includes('Invalid') || error.message.includes('validation')) { 226 | return { message: 'Validation error', code: 'VALIDATION_ERROR' }; 227 | } 228 | // Default generic error 229 | return { message: 'Internal server error', code: 'INTERNAL_ERROR' }; 230 | } 231 | 232 | // In development, return more details but no stack traces 233 | return { 234 | message: error.message.substring(0, 200), // Limit message length 235 | code: error.name || 'ERROR' 236 | }; 237 | } 238 | 239 | // For non-Error objects 240 | return { message: 'An error occurred', code: 'UNKNOWN_ERROR' }; 241 | } 242 | 243 | /** 244 | * Update session last access time 245 | */ 246 | private updateSessionAccess(sessionId: string): void { 247 | if (this.sessionMetadata[sessionId]) { 248 | this.sessionMetadata[sessionId].lastAccess = new Date(); 249 | } 250 | } 251 | 252 | /** 253 | * Switch session context with locking to prevent race conditions 254 | */ 255 | private async switchSessionContext(sessionId: string, newContext: InstanceContext): Promise<void> { 256 | // Check if there's already a switch in progress for this session 257 | const existingLock = this.contextSwitchLocks.get(sessionId); 258 | if (existingLock) { 259 | // Wait for the existing switch to complete 260 | await existingLock; 261 | return; 262 | } 263 | 264 | // Create a promise for this switch operation 265 | const switchPromise = this.performContextSwitch(sessionId, newContext); 266 | this.contextSwitchLocks.set(sessionId, switchPromise); 267 | 268 | try { 269 | await switchPromise; 270 | } finally { 271 | // Clean up the lock after completion 272 | this.contextSwitchLocks.delete(sessionId); 273 | } 274 | } 275 | 276 | /** 277 | * Perform the actual context switch 278 | */ 279 | private async performContextSwitch(sessionId: string, newContext: InstanceContext): Promise<void> { 280 | const existingContext = this.sessionContexts[sessionId]; 281 | 282 | // Only switch if the context has actually changed 283 | if (JSON.stringify(existingContext) !== JSON.stringify(newContext)) { 284 | logger.info('Multi-tenant shared mode: Updating instance context for session', { 285 | sessionId, 286 | oldInstanceId: existingContext?.instanceId, 287 | newInstanceId: newContext.instanceId 288 | }); 289 | 290 | // Update the session context 291 | this.sessionContexts[sessionId] = newContext; 292 | 293 | // Update the MCP server's instance context if it exists 294 | if (this.servers[sessionId]) { 295 | (this.servers[sessionId] as any).instanceContext = newContext; 296 | } 297 | } 298 | } 299 | 300 | /** 301 | * Get session metrics for monitoring 302 | */ 303 | private getSessionMetrics(): SessionMetrics { 304 | const now = Date.now(); 305 | let expiredCount = 0; 306 | 307 | for (const sessionId in this.sessionMetadata) { 308 | const metadata = this.sessionMetadata[sessionId]; 309 | if (now - metadata.lastAccess.getTime() > this.sessionTimeout) { 310 | expiredCount++; 311 | } 312 | } 313 | 314 | return { 315 | totalSessions: Object.keys(this.sessionMetadata).length, 316 | activeSessions: this.getActiveSessionCount(), 317 | expiredSessions: expiredCount, 318 | lastCleanup: new Date() 319 | }; 320 | } 321 | 322 | /** 323 | * Load auth token from environment variable or file 324 | */ 325 | private loadAuthToken(): string | null { 326 | // First, try AUTH_TOKEN environment variable 327 | if (process.env.AUTH_TOKEN) { 328 | logger.info('Using AUTH_TOKEN from environment variable'); 329 | return process.env.AUTH_TOKEN; 330 | } 331 | 332 | // Then, try AUTH_TOKEN_FILE 333 | if (process.env.AUTH_TOKEN_FILE) { 334 | try { 335 | const token = readFileSync(process.env.AUTH_TOKEN_FILE, 'utf-8').trim(); 336 | logger.info(`Loaded AUTH_TOKEN from file: ${process.env.AUTH_TOKEN_FILE}`); 337 | return token; 338 | } catch (error) { 339 | logger.error(`Failed to read AUTH_TOKEN_FILE: ${process.env.AUTH_TOKEN_FILE}`, error); 340 | console.error(`ERROR: Failed to read AUTH_TOKEN_FILE: ${process.env.AUTH_TOKEN_FILE}`); 341 | console.error(error instanceof Error ? error.message : 'Unknown error'); 342 | return null; 343 | } 344 | } 345 | 346 | return null; 347 | } 348 | 349 | /** 350 | * Validate required environment variables 351 | */ 352 | private validateEnvironment(): void { 353 | // Load auth token from env var or file 354 | this.authToken = this.loadAuthToken(); 355 | 356 | if (!this.authToken || this.authToken.trim() === '') { 357 | const message = 'No authentication token found or token is empty. Set AUTH_TOKEN environment variable or AUTH_TOKEN_FILE pointing to a file containing the token.'; 358 | logger.error(message); 359 | throw new Error(message); 360 | } 361 | 362 | // Update authToken to trimmed version 363 | this.authToken = this.authToken.trim(); 364 | 365 | if (this.authToken.length < 32) { 366 | logger.warn('AUTH_TOKEN should be at least 32 characters for security'); 367 | } 368 | 369 | // Check for default token and show prominent warnings 370 | const isDefaultToken = this.authToken === 'REPLACE_THIS_AUTH_TOKEN_32_CHARS_MIN_abcdefgh'; 371 | const isProduction = process.env.NODE_ENV === 'production'; 372 | 373 | if (isDefaultToken) { 374 | if (isProduction) { 375 | const message = 'CRITICAL SECURITY ERROR: Cannot start in production with default AUTH_TOKEN. Generate secure token: openssl rand -base64 32'; 376 | logger.error(message); 377 | console.error('\n🚨 CRITICAL SECURITY ERROR 🚨'); 378 | console.error(message); 379 | console.error('Set NODE_ENV to development for testing, or update AUTH_TOKEN for production\n'); 380 | throw new Error(message); 381 | } 382 | 383 | logger.warn('⚠️ SECURITY WARNING: Using default AUTH_TOKEN - CHANGE IMMEDIATELY!'); 384 | logger.warn('Generate secure token with: openssl rand -base64 32'); 385 | 386 | // Only show console warnings in HTTP mode 387 | if (process.env.MCP_MODE === 'http') { 388 | console.warn('\n⚠️ SECURITY WARNING ⚠️'); 389 | console.warn('Using default AUTH_TOKEN - CHANGE IMMEDIATELY!'); 390 | console.warn('Generate secure token: openssl rand -base64 32'); 391 | console.warn('Update via Railway dashboard environment variables\n'); 392 | } 393 | } 394 | } 395 | 396 | 397 | /** 398 | * Handle incoming MCP request using proper SDK pattern 399 | * 400 | * @param req - Express request object 401 | * @param res - Express response object 402 | * @param instanceContext - Optional instance-specific configuration 403 | */ 404 | async handleRequest( 405 | req: express.Request, 406 | res: express.Response, 407 | instanceContext?: InstanceContext 408 | ): Promise<void> { 409 | const startTime = Date.now(); 410 | 411 | // Wrap all operations to prevent console interference 412 | return this.consoleManager.wrapOperation(async () => { 413 | try { 414 | const sessionId = req.headers['mcp-session-id'] as string | undefined; 415 | const isInitialize = req.body ? isInitializeRequest(req.body) : false; 416 | 417 | // Log comprehensive incoming request details for debugging 418 | logger.info('handleRequest: Processing MCP request - SDK PATTERN', { 419 | requestId: req.get('x-request-id') || 'unknown', 420 | sessionId: sessionId, 421 | method: req.method, 422 | url: req.url, 423 | bodyType: typeof req.body, 424 | bodyContent: req.body ? JSON.stringify(req.body, null, 2) : 'undefined', 425 | existingTransports: Object.keys(this.transports), 426 | isInitializeRequest: isInitialize 427 | }); 428 | 429 | let transport: StreamableHTTPServerTransport; 430 | 431 | if (isInitialize) { 432 | // Check session limits before creating new session 433 | if (!this.canCreateSession()) { 434 | logger.warn('handleRequest: Session limit reached', { 435 | currentSessions: this.getActiveSessionCount(), 436 | maxSessions: MAX_SESSIONS 437 | }); 438 | 439 | res.status(429).json({ 440 | jsonrpc: '2.0', 441 | error: { 442 | code: -32000, 443 | message: `Session limit reached (${MAX_SESSIONS}). Please wait for existing sessions to expire.` 444 | }, 445 | id: req.body?.id || null 446 | }); 447 | return; 448 | } 449 | 450 | // For initialize requests: always create new transport and server 451 | logger.info('handleRequest: Creating new transport for initialize request'); 452 | 453 | // Generate session ID based on multi-tenant configuration 454 | let sessionIdToUse: string; 455 | 456 | const isMultiTenantEnabled = process.env.ENABLE_MULTI_TENANT === 'true'; 457 | const sessionStrategy = process.env.MULTI_TENANT_SESSION_STRATEGY || 'instance'; 458 | 459 | if (isMultiTenantEnabled && sessionStrategy === 'instance' && instanceContext?.instanceId) { 460 | // In multi-tenant mode with instance strategy, create session per instance 461 | // This ensures each tenant gets isolated sessions 462 | // Include configuration hash to prevent collisions with different configs 463 | const configHash = createHash('sha256') 464 | .update(JSON.stringify({ 465 | url: instanceContext.n8nApiUrl, 466 | instanceId: instanceContext.instanceId 467 | })) 468 | .digest('hex') 469 | .substring(0, 8); 470 | 471 | sessionIdToUse = `instance-${instanceContext.instanceId}-${configHash}-${uuidv4()}`; 472 | logger.info('Multi-tenant mode: Creating instance-specific session', { 473 | instanceId: instanceContext.instanceId, 474 | configHash, 475 | sessionId: sessionIdToUse 476 | }); 477 | } else { 478 | // Use client-provided session ID or generate a standard one 479 | sessionIdToUse = sessionId || uuidv4(); 480 | } 481 | 482 | const server = new N8NDocumentationMCPServer(instanceContext); 483 | 484 | transport = new StreamableHTTPServerTransport({ 485 | sessionIdGenerator: () => sessionIdToUse, 486 | onsessioninitialized: (initializedSessionId: string) => { 487 | // Store both transport and server by session ID when session is initialized 488 | logger.info('handleRequest: Session initialized, storing transport and server', { 489 | sessionId: initializedSessionId 490 | }); 491 | this.transports[initializedSessionId] = transport; 492 | this.servers[initializedSessionId] = server; 493 | 494 | // Store session metadata and context 495 | this.sessionMetadata[initializedSessionId] = { 496 | lastAccess: new Date(), 497 | createdAt: new Date() 498 | }; 499 | this.sessionContexts[initializedSessionId] = instanceContext; 500 | } 501 | }); 502 | 503 | // Set up cleanup handlers 504 | transport.onclose = () => { 505 | const sid = transport.sessionId; 506 | if (sid) { 507 | logger.info('handleRequest: Transport closed, cleaning up', { sessionId: sid }); 508 | this.removeSession(sid, 'transport_closed'); 509 | } 510 | }; 511 | 512 | // Handle transport errors to prevent connection drops 513 | transport.onerror = (error: Error) => { 514 | const sid = transport.sessionId; 515 | logger.error('Transport error', { sessionId: sid, error: error.message }); 516 | if (sid) { 517 | this.removeSession(sid, 'transport_error').catch(err => { 518 | logger.error('Error during transport error cleanup', { error: err }); 519 | }); 520 | } 521 | }; 522 | 523 | // Connect the server to the transport BEFORE handling the request 524 | logger.info('handleRequest: Connecting server to new transport'); 525 | await server.connect(transport); 526 | 527 | } else if (sessionId && this.transports[sessionId]) { 528 | // Validate session ID format 529 | if (!this.isValidSessionId(sessionId)) { 530 | logger.warn('handleRequest: Invalid session ID format', { sessionId }); 531 | res.status(400).json({ 532 | jsonrpc: '2.0', 533 | error: { 534 | code: -32602, 535 | message: 'Invalid session ID format' 536 | }, 537 | id: req.body?.id || null 538 | }); 539 | return; 540 | } 541 | 542 | // For non-initialize requests: reuse existing transport for this session 543 | logger.info('handleRequest: Reusing existing transport for session', { sessionId }); 544 | transport = this.transports[sessionId]; 545 | 546 | // In multi-tenant shared mode, update instance context if provided 547 | const isMultiTenantEnabled = process.env.ENABLE_MULTI_TENANT === 'true'; 548 | const sessionStrategy = process.env.MULTI_TENANT_SESSION_STRATEGY || 'instance'; 549 | 550 | if (isMultiTenantEnabled && sessionStrategy === 'shared' && instanceContext) { 551 | // Update the context for this session with locking to prevent race conditions 552 | await this.switchSessionContext(sessionId, instanceContext); 553 | } 554 | 555 | // Update session access time 556 | this.updateSessionAccess(sessionId); 557 | 558 | } else { 559 | // Invalid request - no session ID and not an initialize request 560 | const errorDetails = { 561 | hasSessionId: !!sessionId, 562 | isInitialize: isInitialize, 563 | sessionIdValid: sessionId ? this.isValidSessionId(sessionId) : false, 564 | sessionExists: sessionId ? !!this.transports[sessionId] : false 565 | }; 566 | 567 | logger.warn('handleRequest: Invalid request - no session ID and not initialize', errorDetails); 568 | 569 | let errorMessage = 'Bad Request: No valid session ID provided and not an initialize request'; 570 | if (sessionId && !this.isValidSessionId(sessionId)) { 571 | errorMessage = 'Bad Request: Invalid session ID format'; 572 | } else if (sessionId && !this.transports[sessionId]) { 573 | errorMessage = 'Bad Request: Session not found or expired'; 574 | } 575 | 576 | res.status(400).json({ 577 | jsonrpc: '2.0', 578 | error: { 579 | code: -32000, 580 | message: errorMessage 581 | }, 582 | id: req.body?.id || null 583 | }); 584 | return; 585 | } 586 | 587 | // Handle request with the transport 588 | logger.info('handleRequest: Handling request with transport', { 589 | sessionId: isInitialize ? 'new' : sessionId, 590 | isInitialize 591 | }); 592 | await transport.handleRequest(req, res, req.body); 593 | 594 | const duration = Date.now() - startTime; 595 | logger.info('MCP request completed', { duration, sessionId: transport.sessionId }); 596 | 597 | } catch (error) { 598 | logger.error('handleRequest: MCP request error:', { 599 | error: error instanceof Error ? error.message : error, 600 | errorName: error instanceof Error ? error.name : 'Unknown', 601 | stack: error instanceof Error ? error.stack : undefined, 602 | activeTransports: Object.keys(this.transports), 603 | requestDetails: { 604 | method: req.method, 605 | url: req.url, 606 | hasBody: !!req.body, 607 | sessionId: req.headers['mcp-session-id'] 608 | }, 609 | duration: Date.now() - startTime 610 | }); 611 | 612 | if (!res.headersSent) { 613 | // Send sanitized error to client 614 | const sanitizedError = this.sanitizeErrorForClient(error); 615 | res.status(500).json({ 616 | jsonrpc: '2.0', 617 | error: { 618 | code: -32603, 619 | message: sanitizedError.message, 620 | data: { 621 | code: sanitizedError.code 622 | } 623 | }, 624 | id: req.body?.id || null 625 | }); 626 | } 627 | } 628 | }); 629 | } 630 | 631 | 632 | /** 633 | * Reset the session for SSE - clean up old and create new SSE transport 634 | */ 635 | private async resetSessionSSE(res: express.Response): Promise<void> { 636 | // Clean up old session if exists 637 | if (this.session) { 638 | try { 639 | logger.info('Closing previous session for SSE', { sessionId: this.session.sessionId }); 640 | await this.session.transport.close(); 641 | } catch (error) { 642 | logger.warn('Error closing previous session:', error); 643 | } 644 | } 645 | 646 | try { 647 | // Create new session 648 | logger.info('Creating new N8NDocumentationMCPServer for SSE...'); 649 | const server = new N8NDocumentationMCPServer(); 650 | 651 | // Generate cryptographically secure session ID 652 | const sessionId = uuidv4(); 653 | 654 | logger.info('Creating SSEServerTransport...'); 655 | const transport = new SSEServerTransport('/mcp', res); 656 | 657 | logger.info('Connecting server to SSE transport...'); 658 | await server.connect(transport); 659 | 660 | // Note: server.connect() automatically calls transport.start(), so we don't need to call it again 661 | 662 | this.session = { 663 | server, 664 | transport, 665 | lastAccess: new Date(), 666 | sessionId, 667 | initialized: false, 668 | isSSE: true 669 | }; 670 | 671 | logger.info('Created new SSE session successfully', { sessionId: this.session.sessionId }); 672 | } catch (error) { 673 | logger.error('Failed to create SSE session:', error); 674 | throw error; 675 | } 676 | } 677 | 678 | /** 679 | * Check if current session is expired 680 | */ 681 | private isExpired(): boolean { 682 | if (!this.session) return true; 683 | return Date.now() - this.session.lastAccess.getTime() > this.sessionTimeout; 684 | } 685 | 686 | /** 687 | * Start the HTTP server 688 | */ 689 | async start(): Promise<void> { 690 | const app = express(); 691 | 692 | // Create JSON parser middleware for endpoints that need it 693 | const jsonParser = express.json({ limit: '10mb' }); 694 | 695 | // Configure trust proxy for correct IP logging behind reverse proxies 696 | const trustProxy = process.env.TRUST_PROXY ? Number(process.env.TRUST_PROXY) : 0; 697 | if (trustProxy > 0) { 698 | app.set('trust proxy', trustProxy); 699 | logger.info(`Trust proxy enabled with ${trustProxy} hop(s)`); 700 | } 701 | 702 | // DON'T use any body parser globally - StreamableHTTPServerTransport needs raw stream 703 | // Only use JSON parser for specific endpoints that need it 704 | 705 | // Security headers 706 | app.use((req, res, next) => { 707 | res.setHeader('X-Content-Type-Options', 'nosniff'); 708 | res.setHeader('X-Frame-Options', 'DENY'); 709 | res.setHeader('X-XSS-Protection', '1; mode=block'); 710 | res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains'); 711 | next(); 712 | }); 713 | 714 | // CORS configuration 715 | app.use((req, res, next) => { 716 | const allowedOrigin = process.env.CORS_ORIGIN || '*'; 717 | res.setHeader('Access-Control-Allow-Origin', allowedOrigin); 718 | res.setHeader('Access-Control-Allow-Methods', 'POST, GET, DELETE, OPTIONS'); 719 | res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, Accept, Mcp-Session-Id'); 720 | res.setHeader('Access-Control-Expose-Headers', 'Mcp-Session-Id'); 721 | res.setHeader('Access-Control-Max-Age', '86400'); 722 | 723 | if (req.method === 'OPTIONS') { 724 | res.sendStatus(204); 725 | return; 726 | } 727 | next(); 728 | }); 729 | 730 | // Request logging middleware 731 | app.use((req, res, next) => { 732 | logger.info(`${req.method} ${req.path}`, { 733 | ip: req.ip, 734 | userAgent: req.get('user-agent'), 735 | contentLength: req.get('content-length') 736 | }); 737 | next(); 738 | }); 739 | 740 | // Root endpoint with API information 741 | app.get('/', (req, res) => { 742 | const port = parseInt(process.env.PORT || '3000'); 743 | const host = process.env.HOST || '0.0.0.0'; 744 | const baseUrl = detectBaseUrl(req, host, port); 745 | const endpoints = formatEndpointUrls(baseUrl); 746 | 747 | res.json({ 748 | name: 'n8n Documentation MCP Server', 749 | version: PROJECT_VERSION, 750 | description: 'Model Context Protocol server providing comprehensive n8n node documentation and workflow management', 751 | endpoints: { 752 | health: { 753 | url: endpoints.health, 754 | method: 'GET', 755 | description: 'Health check and status information' 756 | }, 757 | mcp: { 758 | url: endpoints.mcp, 759 | method: 'GET/POST', 760 | description: 'MCP endpoint - GET for info, POST for JSON-RPC' 761 | } 762 | }, 763 | authentication: { 764 | type: 'Bearer Token', 765 | header: 'Authorization: Bearer <token>', 766 | required_for: ['POST /mcp'] 767 | }, 768 | documentation: 'https://github.com/czlonkowski/n8n-mcp' 769 | }); 770 | }); 771 | 772 | // Health check endpoint (no body parsing needed for GET) 773 | app.get('/health', (req, res) => { 774 | const activeTransports = Object.keys(this.transports); 775 | const activeServers = Object.keys(this.servers); 776 | const sessionMetrics = this.getSessionMetrics(); 777 | const isProduction = process.env.NODE_ENV === 'production'; 778 | const isDefaultToken = this.authToken === 'REPLACE_THIS_AUTH_TOKEN_32_CHARS_MIN_abcdefgh'; 779 | 780 | res.json({ 781 | status: 'ok', 782 | mode: 'sdk-pattern-transports', 783 | version: PROJECT_VERSION, 784 | environment: process.env.NODE_ENV || 'development', 785 | uptime: Math.floor(process.uptime()), 786 | sessions: { 787 | active: sessionMetrics.activeSessions, 788 | total: sessionMetrics.totalSessions, 789 | expired: sessionMetrics.expiredSessions, 790 | max: MAX_SESSIONS, 791 | usage: `${sessionMetrics.activeSessions}/${MAX_SESSIONS}`, 792 | sessionIds: activeTransports 793 | }, 794 | security: { 795 | production: isProduction, 796 | defaultToken: isDefaultToken, 797 | tokenLength: this.authToken?.length || 0 798 | }, 799 | activeTransports: activeTransports.length, // Legacy field 800 | activeServers: activeServers.length, // Legacy field 801 | legacySessionActive: !!this.session, // For SSE compatibility 802 | memory: { 803 | used: Math.round(process.memoryUsage().heapUsed / 1024 / 1024), 804 | total: Math.round(process.memoryUsage().heapTotal / 1024 / 1024), 805 | unit: 'MB' 806 | }, 807 | timestamp: new Date().toISOString() 808 | }); 809 | }); 810 | 811 | // Test endpoint for manual testing without auth 812 | app.post('/mcp/test', jsonParser, async (req: express.Request, res: express.Response): Promise<void> => { 813 | logger.info('TEST ENDPOINT: Manual test request received', { 814 | method: req.method, 815 | headers: req.headers, 816 | body: req.body, 817 | bodyType: typeof req.body, 818 | bodyContent: req.body ? JSON.stringify(req.body, null, 2) : 'undefined' 819 | }); 820 | 821 | // Negotiate protocol version for test endpoint 822 | const negotiationResult = negotiateProtocolVersion( 823 | undefined, // no client version in test 824 | undefined, // no client info 825 | req.get('user-agent'), 826 | req.headers 827 | ); 828 | 829 | logProtocolNegotiation(negotiationResult, logger, 'TEST_ENDPOINT'); 830 | 831 | // Test what a basic MCP initialize request should look like 832 | const testResponse = { 833 | jsonrpc: '2.0', 834 | id: req.body?.id || 1, 835 | result: { 836 | protocolVersion: negotiationResult.version, 837 | capabilities: { 838 | tools: {} 839 | }, 840 | serverInfo: { 841 | name: 'n8n-mcp', 842 | version: PROJECT_VERSION 843 | } 844 | } 845 | }; 846 | 847 | logger.info('TEST ENDPOINT: Sending test response', { 848 | response: testResponse 849 | }); 850 | 851 | res.json(testResponse); 852 | }); 853 | 854 | // MCP information endpoint (no auth required for discovery) and SSE support 855 | app.get('/mcp', async (req, res) => { 856 | // Handle StreamableHTTP transport requests with new pattern 857 | const sessionId = req.headers['mcp-session-id'] as string | undefined; 858 | if (sessionId && this.transports[sessionId]) { 859 | // Let the StreamableHTTPServerTransport handle the GET request 860 | try { 861 | await this.transports[sessionId].handleRequest(req, res, undefined); 862 | return; 863 | } catch (error) { 864 | logger.error('StreamableHTTP GET request failed:', error); 865 | // Fall through to standard response 866 | } 867 | } 868 | 869 | // Check Accept header for text/event-stream (SSE support) 870 | const accept = req.headers.accept; 871 | if (accept && accept.includes('text/event-stream')) { 872 | logger.info('SSE stream request received - establishing SSE connection'); 873 | 874 | try { 875 | // Create or reset session for SSE 876 | await this.resetSessionSSE(res); 877 | logger.info('SSE connection established successfully'); 878 | } catch (error) { 879 | logger.error('Failed to establish SSE connection:', error); 880 | res.status(500).json({ 881 | jsonrpc: '2.0', 882 | error: { 883 | code: -32603, 884 | message: 'Failed to establish SSE connection' 885 | }, 886 | id: null 887 | }); 888 | } 889 | return; 890 | } 891 | 892 | // In n8n mode, return protocol version and server info 893 | if (process.env.N8N_MODE === 'true') { 894 | // Negotiate protocol version for n8n mode 895 | const negotiationResult = negotiateProtocolVersion( 896 | undefined, // no client version in GET request 897 | undefined, // no client info 898 | req.get('user-agent'), 899 | req.headers 900 | ); 901 | 902 | logProtocolNegotiation(negotiationResult, logger, 'N8N_MODE_GET'); 903 | 904 | res.json({ 905 | protocolVersion: negotiationResult.version, 906 | serverInfo: { 907 | name: 'n8n-mcp', 908 | version: PROJECT_VERSION, 909 | capabilities: { 910 | tools: {} 911 | } 912 | } 913 | }); 914 | return; 915 | } 916 | 917 | // Standard response for non-n8n mode 918 | res.json({ 919 | description: 'n8n Documentation MCP Server', 920 | version: PROJECT_VERSION, 921 | endpoints: { 922 | mcp: { 923 | method: 'POST', 924 | path: '/mcp', 925 | description: 'Main MCP JSON-RPC endpoint', 926 | authentication: 'Bearer token required' 927 | }, 928 | health: { 929 | method: 'GET', 930 | path: '/health', 931 | description: 'Health check endpoint', 932 | authentication: 'None' 933 | }, 934 | root: { 935 | method: 'GET', 936 | path: '/', 937 | description: 'API information', 938 | authentication: 'None' 939 | } 940 | }, 941 | documentation: 'https://github.com/czlonkowski/n8n-mcp' 942 | }); 943 | }); 944 | 945 | // Session termination endpoint 946 | app.delete('/mcp', async (req: express.Request, res: express.Response): Promise<void> => { 947 | const mcpSessionId = req.headers['mcp-session-id'] as string; 948 | 949 | if (!mcpSessionId) { 950 | res.status(400).json({ 951 | jsonrpc: '2.0', 952 | error: { 953 | code: -32602, 954 | message: 'Mcp-Session-Id header is required' 955 | }, 956 | id: null 957 | }); 958 | return; 959 | } 960 | 961 | // Validate session ID format 962 | if (!this.isValidSessionId(mcpSessionId)) { 963 | res.status(400).json({ 964 | jsonrpc: '2.0', 965 | error: { 966 | code: -32602, 967 | message: 'Invalid session ID format' 968 | }, 969 | id: null 970 | }); 971 | return; 972 | } 973 | 974 | // Check if session exists in new transport map 975 | if (this.transports[mcpSessionId]) { 976 | logger.info('Terminating session via DELETE request', { sessionId: mcpSessionId }); 977 | try { 978 | await this.removeSession(mcpSessionId, 'manual_termination'); 979 | res.status(204).send(); // No content 980 | } catch (error) { 981 | logger.error('Error terminating session:', error); 982 | res.status(500).json({ 983 | jsonrpc: '2.0', 984 | error: { 985 | code: -32603, 986 | message: 'Error terminating session' 987 | }, 988 | id: null 989 | }); 990 | } 991 | } else { 992 | res.status(404).json({ 993 | jsonrpc: '2.0', 994 | error: { 995 | code: -32001, 996 | message: 'Session not found' 997 | }, 998 | id: null 999 | }); 1000 | } 1001 | }); 1002 | 1003 | 1004 | // SECURITY: Rate limiting for authentication endpoint 1005 | // Prevents brute force attacks and DoS 1006 | // See: https://github.com/czlonkowski/n8n-mcp/issues/265 (HIGH-02) 1007 | const authLimiter = rateLimit({ 1008 | windowMs: parseInt(process.env.AUTH_RATE_LIMIT_WINDOW || '900000'), // 15 minutes 1009 | max: parseInt(process.env.AUTH_RATE_LIMIT_MAX || '20'), // 20 authentication attempts per IP 1010 | message: { 1011 | jsonrpc: '2.0', 1012 | error: { 1013 | code: -32000, 1014 | message: 'Too many authentication attempts. Please try again later.' 1015 | }, 1016 | id: null 1017 | }, 1018 | standardHeaders: true, // Return rate limit info in `RateLimit-*` headers 1019 | legacyHeaders: false, // Disable `X-RateLimit-*` headers 1020 | handler: (req, res) => { 1021 | logger.warn('Rate limit exceeded', { 1022 | ip: req.ip, 1023 | userAgent: req.get('user-agent'), 1024 | event: 'rate_limit' 1025 | }); 1026 | res.status(429).json({ 1027 | jsonrpc: '2.0', 1028 | error: { 1029 | code: -32000, 1030 | message: 'Too many authentication attempts' 1031 | }, 1032 | id: null 1033 | }); 1034 | } 1035 | }); 1036 | 1037 | // Main MCP endpoint with authentication and rate limiting 1038 | app.post('/mcp', authLimiter, jsonParser, async (req: express.Request, res: express.Response): Promise<void> => { 1039 | // Log comprehensive debug info about the request 1040 | logger.info('POST /mcp request received - DETAILED DEBUG', { 1041 | headers: req.headers, 1042 | readable: req.readable, 1043 | readableEnded: req.readableEnded, 1044 | complete: req.complete, 1045 | bodyType: typeof req.body, 1046 | bodyContent: req.body ? JSON.stringify(req.body, null, 2) : 'undefined', 1047 | contentLength: req.get('content-length'), 1048 | contentType: req.get('content-type'), 1049 | userAgent: req.get('user-agent'), 1050 | ip: req.ip, 1051 | method: req.method, 1052 | url: req.url, 1053 | originalUrl: req.originalUrl 1054 | }); 1055 | 1056 | // Handle connection close to immediately clean up sessions 1057 | const sessionId = req.headers['mcp-session-id'] as string | undefined; 1058 | // Only add event listener if the request object supports it (not in test mocks) 1059 | if (typeof req.on === 'function') { 1060 | const closeHandler = () => { 1061 | if (!res.headersSent && sessionId) { 1062 | logger.info('Connection closed before response sent', { sessionId }); 1063 | // Schedule immediate cleanup if connection closes unexpectedly 1064 | setImmediate(() => { 1065 | if (this.sessionMetadata[sessionId]) { 1066 | const metadata = this.sessionMetadata[sessionId]; 1067 | const timeSinceAccess = Date.now() - metadata.lastAccess.getTime(); 1068 | // Only remove if it's been inactive for a bit to avoid race conditions 1069 | if (timeSinceAccess > 60000) { // 1 minute 1070 | this.removeSession(sessionId, 'connection_closed').catch(err => { 1071 | logger.error('Error during connection close cleanup', { error: err }); 1072 | }); 1073 | } 1074 | } 1075 | }); 1076 | } 1077 | }; 1078 | 1079 | req.on('close', closeHandler); 1080 | 1081 | // Clean up event listener when response ends to prevent memory leaks 1082 | res.on('finish', () => { 1083 | req.removeListener('close', closeHandler); 1084 | }); 1085 | } 1086 | 1087 | // Enhanced authentication check with specific logging 1088 | const authHeader = req.headers.authorization; 1089 | 1090 | // Check if Authorization header is missing 1091 | if (!authHeader) { 1092 | logger.warn('Authentication failed: Missing Authorization header', { 1093 | ip: req.ip, 1094 | userAgent: req.get('user-agent'), 1095 | reason: 'no_auth_header' 1096 | }); 1097 | res.status(401).json({ 1098 | jsonrpc: '2.0', 1099 | error: { 1100 | code: -32001, 1101 | message: 'Unauthorized' 1102 | }, 1103 | id: null 1104 | }); 1105 | return; 1106 | } 1107 | 1108 | // Check if Authorization header has Bearer prefix 1109 | if (!authHeader.startsWith('Bearer ')) { 1110 | logger.warn('Authentication failed: Invalid Authorization header format (expected Bearer token)', { 1111 | ip: req.ip, 1112 | userAgent: req.get('user-agent'), 1113 | reason: 'invalid_auth_format', 1114 | headerPrefix: authHeader.substring(0, Math.min(authHeader.length, 10)) + '...' // Log first 10 chars for debugging 1115 | }); 1116 | res.status(401).json({ 1117 | jsonrpc: '2.0', 1118 | error: { 1119 | code: -32001, 1120 | message: 'Unauthorized' 1121 | }, 1122 | id: null 1123 | }); 1124 | return; 1125 | } 1126 | 1127 | // Extract token and trim whitespace 1128 | const token = authHeader.slice(7).trim(); 1129 | 1130 | // SECURITY: Use timing-safe comparison to prevent timing attacks 1131 | // See: https://github.com/czlonkowski/n8n-mcp/issues/265 (CRITICAL-02) 1132 | const isValidToken = this.authToken && 1133 | AuthManager.timingSafeCompare(token, this.authToken); 1134 | 1135 | if (!isValidToken) { 1136 | logger.warn('Authentication failed: Invalid token', { 1137 | ip: req.ip, 1138 | userAgent: req.get('user-agent'), 1139 | reason: 'invalid_token' 1140 | }); 1141 | res.status(401).json({ 1142 | jsonrpc: '2.0', 1143 | error: { 1144 | code: -32001, 1145 | message: 'Unauthorized' 1146 | }, 1147 | id: null 1148 | }); 1149 | return; 1150 | } 1151 | 1152 | // Handle request with single session 1153 | logger.info('Authentication successful - proceeding to handleRequest', { 1154 | hasSession: !!this.session, 1155 | sessionType: this.session?.isSSE ? 'SSE' : 'StreamableHTTP', 1156 | sessionInitialized: this.session?.initialized 1157 | }); 1158 | 1159 | // Extract instance context from headers if present (for multi-tenant support) 1160 | const instanceContext: InstanceContext | undefined = (() => { 1161 | // Use type-safe header extraction 1162 | const headers = extractMultiTenantHeaders(req); 1163 | const hasUrl = headers['x-n8n-url']; 1164 | const hasKey = headers['x-n8n-key']; 1165 | 1166 | if (!hasUrl && !hasKey) return undefined; 1167 | 1168 | // Create context with proper type handling 1169 | const context: InstanceContext = { 1170 | n8nApiUrl: hasUrl || undefined, 1171 | n8nApiKey: hasKey || undefined, 1172 | instanceId: headers['x-instance-id'] || undefined, 1173 | sessionId: headers['x-session-id'] || undefined 1174 | }; 1175 | 1176 | // Add metadata if available 1177 | if (req.headers['user-agent'] || req.ip) { 1178 | context.metadata = { 1179 | userAgent: req.headers['user-agent'] as string | undefined, 1180 | ip: req.ip 1181 | }; 1182 | } 1183 | 1184 | // Validate the context 1185 | const validation = validateInstanceContext(context); 1186 | if (!validation.valid) { 1187 | logger.warn('Invalid instance context from headers', { 1188 | errors: validation.errors, 1189 | hasUrl: !!hasUrl, 1190 | hasKey: !!hasKey 1191 | }); 1192 | return undefined; 1193 | } 1194 | 1195 | return context; 1196 | })(); 1197 | 1198 | // Log context extraction for debugging (only if context exists) 1199 | if (instanceContext) { 1200 | // Use sanitized logging for security 1201 | logger.debug('Instance context extracted from headers', { 1202 | hasUrl: !!instanceContext.n8nApiUrl, 1203 | hasKey: !!instanceContext.n8nApiKey, 1204 | instanceId: instanceContext.instanceId ? instanceContext.instanceId.substring(0, 8) + '...' : undefined, 1205 | sessionId: instanceContext.sessionId ? instanceContext.sessionId.substring(0, 8) + '...' : undefined, 1206 | urlDomain: instanceContext.n8nApiUrl ? new URL(instanceContext.n8nApiUrl).hostname : undefined 1207 | }); 1208 | } 1209 | 1210 | await this.handleRequest(req, res, instanceContext); 1211 | 1212 | logger.info('POST /mcp request completed - checking response status', { 1213 | responseHeadersSent: res.headersSent, 1214 | responseStatusCode: res.statusCode, 1215 | responseFinished: res.finished 1216 | }); 1217 | }); 1218 | 1219 | // 404 handler 1220 | app.use((req, res) => { 1221 | res.status(404).json({ 1222 | error: 'Not found', 1223 | message: `Cannot ${req.method} ${req.path}` 1224 | }); 1225 | }); 1226 | 1227 | // Error handler 1228 | app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { 1229 | logger.error('Express error handler:', err); 1230 | 1231 | if (!res.headersSent) { 1232 | res.status(500).json({ 1233 | jsonrpc: '2.0', 1234 | error: { 1235 | code: -32603, 1236 | message: 'Internal server error', 1237 | data: process.env.NODE_ENV === 'development' ? err.message : undefined 1238 | }, 1239 | id: null 1240 | }); 1241 | } 1242 | }); 1243 | 1244 | const port = parseInt(process.env.PORT || '3000'); 1245 | const host = process.env.HOST || '0.0.0.0'; 1246 | 1247 | this.expressServer = app.listen(port, host, () => { 1248 | const isProduction = process.env.NODE_ENV === 'production'; 1249 | const isDefaultToken = this.authToken === 'REPLACE_THIS_AUTH_TOKEN_32_CHARS_MIN_abcdefgh'; 1250 | 1251 | logger.info(`n8n MCP Single-Session HTTP Server started`, { 1252 | port, 1253 | host, 1254 | environment: process.env.NODE_ENV || 'development', 1255 | maxSessions: MAX_SESSIONS, 1256 | sessionTimeout: this.sessionTimeout / 1000 / 60, 1257 | production: isProduction, 1258 | defaultToken: isDefaultToken 1259 | }); 1260 | 1261 | // Detect the base URL using our utility 1262 | const baseUrl = getStartupBaseUrl(host, port); 1263 | const endpoints = formatEndpointUrls(baseUrl); 1264 | 1265 | console.log(`n8n MCP Single-Session HTTP Server running on ${host}:${port}`); 1266 | console.log(`Environment: ${process.env.NODE_ENV || 'development'}`); 1267 | console.log(`Session Limits: ${MAX_SESSIONS} max sessions, ${this.sessionTimeout / 1000 / 60}min timeout`); 1268 | console.log(`Health check: ${endpoints.health}`); 1269 | console.log(`MCP endpoint: ${endpoints.mcp}`); 1270 | 1271 | if (isProduction) { 1272 | console.log('🔒 Running in PRODUCTION mode - enhanced security enabled'); 1273 | } else { 1274 | console.log('🛠️ Running in DEVELOPMENT mode'); 1275 | } 1276 | 1277 | console.log('\nPress Ctrl+C to stop the server'); 1278 | 1279 | // Start periodic warning timer if using default token 1280 | if (isDefaultToken && !isProduction) { 1281 | setInterval(() => { 1282 | logger.warn('⚠️ Still using default AUTH_TOKEN - security risk!'); 1283 | if (process.env.MCP_MODE === 'http') { 1284 | console.warn('⚠️ REMINDER: Still using default AUTH_TOKEN - please change it!'); 1285 | } 1286 | }, 300000); // Every 5 minutes 1287 | } 1288 | 1289 | if (process.env.BASE_URL || process.env.PUBLIC_URL) { 1290 | console.log(`\nPublic URL configured: ${baseUrl}`); 1291 | } else if (process.env.TRUST_PROXY && Number(process.env.TRUST_PROXY) > 0) { 1292 | console.log(`\nNote: TRUST_PROXY is enabled. URLs will be auto-detected from proxy headers.`); 1293 | } 1294 | }); 1295 | 1296 | // Handle server errors 1297 | this.expressServer.on('error', (error: any) => { 1298 | if (error.code === 'EADDRINUSE') { 1299 | logger.error(`Port ${port} is already in use`); 1300 | console.error(`ERROR: Port ${port} is already in use`); 1301 | process.exit(1); 1302 | } else { 1303 | logger.error('Server error:', error); 1304 | console.error('Server error:', error); 1305 | process.exit(1); 1306 | } 1307 | }); 1308 | } 1309 | 1310 | /** 1311 | * Graceful shutdown 1312 | */ 1313 | async shutdown(): Promise<void> { 1314 | logger.info('Shutting down Single-Session HTTP server...'); 1315 | 1316 | // Stop session cleanup timer 1317 | if (this.cleanupTimer) { 1318 | clearInterval(this.cleanupTimer); 1319 | this.cleanupTimer = null; 1320 | logger.info('Session cleanup timer stopped'); 1321 | } 1322 | 1323 | // Close all active transports (SDK pattern) 1324 | const sessionIds = Object.keys(this.transports); 1325 | logger.info(`Closing ${sessionIds.length} active sessions`); 1326 | 1327 | for (const sessionId of sessionIds) { 1328 | try { 1329 | logger.info(`Closing transport for session ${sessionId}`); 1330 | await this.removeSession(sessionId, 'server_shutdown'); 1331 | } catch (error) { 1332 | logger.warn(`Error closing transport for session ${sessionId}:`, error); 1333 | } 1334 | } 1335 | 1336 | // Clean up legacy session (for SSE compatibility) 1337 | if (this.session) { 1338 | try { 1339 | await this.session.transport.close(); 1340 | logger.info('Legacy session closed'); 1341 | } catch (error) { 1342 | logger.warn('Error closing legacy session:', error); 1343 | } 1344 | this.session = null; 1345 | } 1346 | 1347 | // Close Express server 1348 | if (this.expressServer) { 1349 | await new Promise<void>((resolve) => { 1350 | this.expressServer.close(() => { 1351 | logger.info('HTTP server closed'); 1352 | resolve(); 1353 | }); 1354 | }); 1355 | } 1356 | 1357 | logger.info('Single-Session HTTP server shutdown completed'); 1358 | } 1359 | 1360 | /** 1361 | * Get current session info (for testing/debugging) 1362 | */ 1363 | getSessionInfo(): { 1364 | active: boolean; 1365 | sessionId?: string; 1366 | age?: number; 1367 | sessions?: { 1368 | total: number; 1369 | active: number; 1370 | expired: number; 1371 | max: number; 1372 | sessionIds: string[]; 1373 | }; 1374 | } { 1375 | const metrics = this.getSessionMetrics(); 1376 | 1377 | // Legacy SSE session info 1378 | if (!this.session) { 1379 | return { 1380 | active: false, 1381 | sessions: { 1382 | total: metrics.totalSessions, 1383 | active: metrics.activeSessions, 1384 | expired: metrics.expiredSessions, 1385 | max: MAX_SESSIONS, 1386 | sessionIds: Object.keys(this.transports) 1387 | } 1388 | }; 1389 | } 1390 | 1391 | return { 1392 | active: true, 1393 | sessionId: this.session.sessionId, 1394 | age: Date.now() - this.session.lastAccess.getTime(), 1395 | sessions: { 1396 | total: metrics.totalSessions, 1397 | active: metrics.activeSessions, 1398 | expired: metrics.expiredSessions, 1399 | max: MAX_SESSIONS, 1400 | sessionIds: Object.keys(this.transports) 1401 | } 1402 | }; 1403 | } 1404 | } 1405 | 1406 | // Start if called directly 1407 | if (require.main === module) { 1408 | const server = new SingleSessionHTTPServer(); 1409 | 1410 | // Graceful shutdown handlers 1411 | const shutdown = async () => { 1412 | await server.shutdown(); 1413 | process.exit(0); 1414 | }; 1415 | 1416 | process.on('SIGTERM', shutdown); 1417 | process.on('SIGINT', shutdown); 1418 | 1419 | // Handle uncaught errors 1420 | process.on('uncaughtException', (error) => { 1421 | logger.error('Uncaught exception:', error); 1422 | console.error('Uncaught exception:', error); 1423 | shutdown(); 1424 | }); 1425 | 1426 | process.on('unhandledRejection', (reason, promise) => { 1427 | logger.error('Unhandled rejection:', reason); 1428 | console.error('Unhandled rejection at:', promise, 'reason:', reason); 1429 | shutdown(); 1430 | }); 1431 | 1432 | // Start server 1433 | server.start().catch(error => { 1434 | logger.error('Failed to start Single-Session HTTP server:', error); 1435 | console.error('Failed to start Single-Session HTTP server:', error); 1436 | process.exit(1); 1437 | }); 1438 | } ```