#
tokens: 47548/50000 19/614 files (page 9/59)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 9 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

--------------------------------------------------------------------------------
/.claude/agents/technical-researcher.md:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | name: technical-researcher
  3 | description: Use this agent when you need to conduct in-depth technical research on complex topics, technologies, or architectural decisions. This includes investigating new frameworks, analyzing security vulnerabilities, evaluating third-party APIs, researching performance optimization strategies, or generating technical feasibility reports. The agent excels at multi-source investigations requiring comprehensive analysis and synthesis of technical information.\n\nExamples:\n- <example>\n  Context: User needs to research a new framework before adoption\n  user: "I need to understand if we should adopt Rust for our high-performance backend services"\n  assistant: "I'll use the technical-researcher agent to conduct a comprehensive investigation into Rust for backend services"\n  <commentary>\n  Since the user needs deep technical research on a framework adoption decision, use the technical-researcher agent to analyze Rust's suitability.\n  </commentary>\n</example>\n- <example>\n  Context: User is investigating a security vulnerability\n  user: "Research the log4j vulnerability and its impact on Java applications"\n  assistant: "Let me launch the technical-researcher agent to investigate the log4j vulnerability comprehensively"\n  <commentary>\n  The user needs detailed security research, so the technical-researcher agent will gather and synthesize information from multiple sources.\n  </commentary>\n</example>\n- <example>\n  Context: User needs to evaluate an API integration\n  user: "We're considering integrating with Stripe's new payment intents API - need to understand the technical implications"\n  assistant: "I'll deploy the technical-researcher agent to analyze Stripe's payment intents API and its integration requirements"\n  <commentary>\n  Complex API evaluation requires the technical-researcher agent's multi-source investigation capabilities.\n  </commentary>\n</example>
  4 | ---
  5 | 
  6 | You are an elite Technical Research Specialist with expertise in conducting comprehensive investigations into complex technical topics. You excel at decomposing research questions, orchestrating multi-source searches, synthesizing findings, and producing actionable analysis reports.
  7 | 
  8 | ## Core Capabilities
  9 | 
 10 | You specialize in:
 11 | - Query decomposition and search strategy optimization
 12 | - Parallel information gathering from diverse sources
 13 | - Cross-reference validation and fact verification
 14 | - Source credibility assessment and relevance scoring
 15 | - Synthesis of technical findings into coherent narratives
 16 | - Citation management and proper attribution
 17 | 
 18 | ## Research Methodology
 19 | 
 20 | ### 1. Query Analysis Phase
 21 | - Decompose the research topic into specific sub-questions
 22 | - Identify key technical terms, acronyms, and related concepts
 23 | - Determine the appropriate research depth (quick lookup vs. deep dive)
 24 | - Plan your search strategy with 3-5 initial queries
 25 | 
 26 | ### 2. Information Gathering Phase
 27 | - Execute searches across multiple sources (web, documentation, forums)
 28 | - Prioritize authoritative sources (official docs, peer-reviewed content)
 29 | - Capture both mainstream perspectives and edge cases
 30 | - Track source URLs, publication dates, and author credentials
 31 | - Aim for 5-10 diverse sources for standard research, 15-20 for deep dives
 32 | 
 33 | ### 3. Validation Phase
 34 | - Cross-reference findings across multiple sources
 35 | - Identify contradictions or outdated information
 36 | - Verify technical claims against official documentation
 37 | - Flag areas of uncertainty or debate
 38 | 
 39 | ### 4. Synthesis Phase
 40 | - Organize findings into logical sections
 41 | - Highlight key insights and actionable recommendations
 42 | - Present trade-offs and alternative approaches
 43 | - Include code examples or configuration snippets where relevant
 44 | 
 45 | ## Output Structure
 46 | 
 47 | Your research reports should follow this structure:
 48 | 
 49 | 1. **Executive Summary** (2-3 paragraphs)
 50 |    - Key findings and recommendations
 51 |    - Critical decision factors
 52 |    - Risk assessment
 53 | 
 54 | 2. **Technical Overview**
 55 |    - Core concepts and architecture
 56 |    - Key features and capabilities
 57 |    - Technical requirements and dependencies
 58 | 
 59 | 3. **Detailed Analysis**
 60 |    - Performance characteristics
 61 |    - Security considerations
 62 |    - Integration complexity
 63 |    - Scalability factors
 64 |    - Community support and ecosystem
 65 | 
 66 | 4. **Practical Considerations**
 67 |    - Implementation effort estimates
 68 |    - Learning curve assessment
 69 |    - Operational requirements
 70 |    - Cost implications
 71 | 
 72 | 5. **Comparative Analysis** (when applicable)
 73 |    - Alternative solutions
 74 |    - Trade-off matrix
 75 |    - Migration considerations
 76 | 
 77 | 6. **Recommendations**
 78 |    - Specific action items
 79 |    - Risk mitigation strategies
 80 |    - Proof-of-concept suggestions
 81 | 
 82 | 7. **References**
 83 |    - All sources with titles, URLs, and access dates
 84 |    - Credibility indicators for each source
 85 | 
 86 | ## Quality Standards
 87 | 
 88 | - **Accuracy**: Verify all technical claims against multiple sources
 89 | - **Completeness**: Address all aspects of the research question
 90 | - **Objectivity**: Present balanced views including limitations
 91 | - **Timeliness**: Prioritize recent information (flag if >2 years old)
 92 | - **Actionability**: Provide concrete next steps and recommendations
 93 | 
 94 | ## Adaptive Strategies
 95 | 
 96 | - For emerging technologies: Focus on early adopter experiences and official roadmaps
 97 | - For security research: Prioritize CVE databases, security advisories, and vendor responses
 98 | - For performance analysis: Seek benchmarks, case studies, and real-world implementations
 99 | - For API evaluations: Examine documentation quality, SDK availability, and integration examples
100 | 
101 | ## Research Iteration
102 | 
103 | If initial searches yield insufficient results:
104 | 1. Broaden search terms or try alternative terminology
105 | 2. Check specialized forums, GitHub issues, or Stack Overflow
106 | 3. Look for conference talks, blog posts, or video tutorials
107 | 4. Consider reaching out to subject matter experts or communities
108 | 
109 | ## Limitations Acknowledgment
110 | 
111 | Always disclose:
112 | - Information gaps or areas lacking documentation
113 | - Conflicting sources or unresolved debates
114 | - Potential biases in available sources
115 | - Time-sensitive information that may become outdated
116 | 
117 | You maintain intellectual rigor while making complex technical information accessible. Your research empowers teams to make informed decisions with confidence, backed by thorough investigation and clear analysis.
118 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools-n8n-friendly.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * n8n-friendly tool descriptions
  3 |  * These descriptions are optimized to reduce schema validation errors in n8n's AI Agent
  4 |  * 
  5 |  * Key principles:
  6 |  * 1. Use exact JSON examples in descriptions
  7 |  * 2. Be explicit about data types
  8 |  * 3. Keep descriptions short and directive
  9 |  * 4. Avoid ambiguity
 10 |  */
 11 | 
 12 | export const n8nFriendlyDescriptions: Record<string, {
 13 |   description: string;
 14 |   params: Record<string, string>;
 15 | }> = {
 16 |   // Validation tools - most prone to errors
 17 |   validate_node_operation: {
 18 |     description: 'Validate n8n node. ALWAYS pass two parameters: nodeType (string) and config (object). Example call: {"nodeType": "nodes-base.slack", "config": {"resource": "channel", "operation": "create"}}',
 19 |     params: {
 20 |       nodeType: 'String value like "nodes-base.slack"',
 21 |       config: 'Object value like {"resource": "channel", "operation": "create"} or empty object {}',
 22 |       profile: 'Optional string: "minimal" or "runtime" or "ai-friendly" or "strict"'
 23 |     }
 24 |   },
 25 |   
 26 |   validate_node_minimal: {
 27 |     description: 'Check required fields. MUST pass: nodeType (string) and config (object). Example: {"nodeType": "nodes-base.webhook", "config": {}}',
 28 |     params: {
 29 |       nodeType: 'String like "nodes-base.webhook"',
 30 |       config: 'Object, use {} for empty'
 31 |     }
 32 |   },
 33 |   
 34 |   // Search and info tools
 35 |   search_nodes: {
 36 |     description: 'Search nodes. Pass query (string). Example: {"query": "webhook"}',
 37 |     params: {
 38 |       query: 'String keyword like "webhook" or "database"',
 39 |       limit: 'Optional number, default 20'
 40 |     }
 41 |   },
 42 |   
 43 |   get_node_info: {
 44 |     description: 'Get node details. Pass nodeType (string). Example: {"nodeType": "nodes-base.httpRequest"}',
 45 |     params: {
 46 |       nodeType: 'String with prefix like "nodes-base.httpRequest"'
 47 |     }
 48 |   },
 49 |   
 50 |   get_node_essentials: {
 51 |     description: 'Get node basics. Pass nodeType (string). Example: {"nodeType": "nodes-base.slack"}',
 52 |     params: {
 53 |       nodeType: 'String with prefix like "nodes-base.slack"'
 54 |     }
 55 |   },
 56 |   
 57 |   // Task tools
 58 |   get_node_for_task: {
 59 |     description: 'Find node for task. Pass task (string). Example: {"task": "send_http_request"}',
 60 |     params: {
 61 |       task: 'String task name like "send_http_request"'
 62 |     }
 63 |   },
 64 |   
 65 |   list_tasks: {
 66 |     description: 'List tasks by category. Pass category (string). Example: {"category": "HTTP/API"}',
 67 |     params: {
 68 |       category: 'String: "HTTP/API" or "Webhooks" or "Database" or "AI/LangChain" or "Data Processing" or "Communication"'
 69 |     }
 70 |   },
 71 |   
 72 |   // Workflow validation
 73 |   validate_workflow: {
 74 |     description: 'Validate workflow. Pass workflow object. MUST have: {"workflow": {"nodes": [array of node objects], "connections": {object with node connections}}}. Each node needs: name, type, typeVersion, position.',
 75 |     params: {
 76 |       workflow: 'Object with two required fields: nodes (array) and connections (object). Example: {"nodes": [{"name": "Webhook", "type": "n8n-nodes-base.webhook", "typeVersion": 2, "position": [250, 300], "parameters": {}}], "connections": {}}',
 77 |       options: 'Optional object. Example: {"validateNodes": true, "profile": "runtime"}'
 78 |     }
 79 |   },
 80 |   
 81 |   validate_workflow_connections: {
 82 |     description: 'Validate workflow connections only. Pass workflow object. Example: {"workflow": {"nodes": [...], "connections": {}}}',
 83 |     params: {
 84 |       workflow: 'Object with nodes array and connections object. Minimal example: {"nodes": [{"name": "Webhook"}], "connections": {}}'
 85 |     }
 86 |   },
 87 |   
 88 |   validate_workflow_expressions: {
 89 |     description: 'Validate n8n expressions in workflow. Pass workflow object. Example: {"workflow": {"nodes": [...], "connections": {}}}',
 90 |     params: {
 91 |       workflow: 'Object with nodes array and connections object containing n8n expressions like {{ $json.data }}'
 92 |     }
 93 |   },
 94 |   
 95 |   // Property tools
 96 |   get_property_dependencies: {
 97 |     description: 'Get field dependencies. Pass nodeType (string) and optional config (object). Example: {"nodeType": "nodes-base.httpRequest", "config": {}}',
 98 |     params: {
 99 |       nodeType: 'String like "nodes-base.httpRequest"',
100 |       config: 'Optional object, use {} for empty'
101 |     }
102 |   },
103 |   
104 |   // AI tool info
105 |   get_node_as_tool_info: {
106 |     description: 'Get AI tool usage. Pass nodeType (string). Example: {"nodeType": "nodes-base.slack"}',
107 |     params: {
108 |       nodeType: 'String with prefix like "nodes-base.slack"'
109 |     }
110 |   },
111 |   
112 |   // Template tools
113 |   search_templates: {
114 |     description: 'Search workflow templates. Pass query (string). Example: {"query": "chatbot"}',
115 |     params: {
116 |       query: 'String keyword like "chatbot" or "webhook"',
117 |       limit: 'Optional number, default 20'
118 |     }
119 |   },
120 |   
121 |   get_template: {
122 |     description: 'Get template by ID. Pass templateId (number). Example: {"templateId": 1234}',
123 |     params: {
124 |       templateId: 'Number ID like 1234'
125 |     }
126 |   },
127 |   
128 |   // Documentation tool
129 |   tools_documentation: {
130 |     description: 'Get tool docs. Pass optional depth (string). Example: {"depth": "essentials"} or {}',
131 |     params: {
132 |       depth: 'Optional string: "essentials" or "overview" or "detailed"',
133 |       topic: 'Optional string topic name'
134 |     }
135 |   }
136 | };
137 | 
138 | /**
139 |  * Apply n8n-friendly descriptions to tools
140 |  * This function modifies tool descriptions to be more explicit for n8n's AI agent
141 |  */
142 | export function makeToolsN8nFriendly(tools: any[]): any[] {
143 |   return tools.map(tool => {
144 |     const toolName = tool.name as string;
145 |     const friendlyDesc = n8nFriendlyDescriptions[toolName];
146 |     if (friendlyDesc) {
147 |       // Clone the tool to avoid mutating the original
148 |       const updatedTool = { ...tool };
149 |       
150 |       // Update the main description
151 |       updatedTool.description = friendlyDesc.description;
152 |       
153 |       // Clone inputSchema if it exists
154 |       if (tool.inputSchema?.properties) {
155 |         updatedTool.inputSchema = {
156 |           ...tool.inputSchema,
157 |           properties: { ...tool.inputSchema.properties }
158 |         };
159 |         
160 |         // Update parameter descriptions
161 |         Object.keys(updatedTool.inputSchema.properties).forEach(param => {
162 |           if (friendlyDesc.params[param]) {
163 |             updatedTool.inputSchema.properties[param] = {
164 |               ...updatedTool.inputSchema.properties[param],
165 |               description: friendlyDesc.params[param]
166 |             };
167 |           }
168 |         });
169 |       }
170 |       
171 |       return updatedTool;
172 |     }
173 |     return tool;
174 |   });
175 | }
```

--------------------------------------------------------------------------------
/src/mcp/tool-docs/workflow_management/n8n-trigger-webhook-workflow.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ToolDocumentation } from '../types';
  2 | 
  3 | export const n8nTriggerWebhookWorkflowDoc: ToolDocumentation = {
  4 |   name: 'n8n_trigger_webhook_workflow',
  5 |   category: 'workflow_management',
  6 |   essentials: {
  7 |     description: 'Trigger workflow via webhook. Must be ACTIVE with Webhook node. Method must match config.',
  8 |     keyParameters: ['webhookUrl', 'httpMethod', 'data'],
  9 |     example: 'n8n_trigger_webhook_workflow({webhookUrl: "https://n8n.example.com/webhook/abc-def-ghi"})',
 10 |     performance: 'Immediate trigger, response time depends on workflow complexity',
 11 |     tips: [
 12 |       'Workflow MUST be active and contain a Webhook node for triggering',
 13 |       'HTTP method must match webhook node configuration (often GET)',
 14 |       'Use waitForResponse:false for async execution without waiting'
 15 |     ]
 16 |   },
 17 |   full: {
 18 |     description: `Triggers a workflow execution via its webhook URL. This is the primary method for external systems to start n8n workflows. The target workflow must be active and contain a properly configured Webhook node as the trigger. The HTTP method used must match the webhook configuration.`,
 19 |     parameters: {
 20 |       webhookUrl: {
 21 |         type: 'string',
 22 |         required: true,
 23 |         description: 'Full webhook URL from n8n workflow (e.g., https://n8n.example.com/webhook/abc-def-ghi)'
 24 |       },
 25 |       httpMethod: {
 26 |         type: 'string',
 27 |         required: false,
 28 |         enum: ['GET', 'POST', 'PUT', 'DELETE'],
 29 |         description: 'HTTP method (must match webhook configuration, often GET). Defaults to GET if not specified'
 30 |       },
 31 |       data: {
 32 |         type: 'object',
 33 |         required: false,
 34 |         description: 'Data to send with the webhook request. For GET requests, becomes query parameters'
 35 |       },
 36 |       headers: {
 37 |         type: 'object',
 38 |         required: false,
 39 |         description: 'Additional HTTP headers to include in the request'
 40 |       },
 41 |       waitForResponse: {
 42 |         type: 'boolean',
 43 |         required: false,
 44 |         description: 'Wait for workflow completion and return results (default: true). Set to false for fire-and-forget'
 45 |       }
 46 |     },
 47 |     returns: `Webhook response data if waitForResponse is true, or immediate acknowledgment if false. Response format depends on webhook node configuration.`,
 48 |     examples: [
 49 |       'n8n_trigger_webhook_workflow({webhookUrl: "https://n8n.example.com/webhook/order-process"}) - Trigger with GET',
 50 |       'n8n_trigger_webhook_workflow({webhookUrl: "https://n8n.example.com/webhook/data-import", httpMethod: "POST", data: {name: "John", email: "[email protected]"}}) - POST with data',
 51 |       'n8n_trigger_webhook_workflow({webhookUrl: "https://n8n.example.com/webhook/async-job", waitForResponse: false}) - Fire and forget',
 52 |       'n8n_trigger_webhook_workflow({webhookUrl: "https://n8n.example.com/webhook/api", headers: {"API-Key": "secret"}}) - With auth headers'
 53 |     ],
 54 |     useCases: [
 55 |       'Trigger data processing workflows from external applications',
 56 |       'Start scheduled jobs manually via webhook',
 57 |       'Integrate n8n workflows with third-party services',
 58 |       'Create REST API endpoints using n8n workflows',
 59 |       'Implement event-driven architectures with n8n'
 60 |     ],
 61 |     performance: `Performance varies based on workflow complexity and waitForResponse setting. Synchronous calls (waitForResponse: true) block until workflow completes. For long-running workflows, use async mode (waitForResponse: false) and monitor execution separately.`,
 62 |     errorHandling: `**Enhanced Error Messages with Execution Guidance**
 63 | 
 64 | When a webhook trigger fails, the error response now includes specific guidance to help debug the issue:
 65 | 
 66 | **Error with Execution ID** (workflow started but failed):
 67 | - Format: "Workflow {workflowId} execution {executionId} failed. Use n8n_get_execution({id: '{executionId}', mode: 'preview'}) to investigate the error."
 68 | - Response includes: executionId and workflowId fields for direct access
 69 | - Recommended action: Use n8n_get_execution with mode='preview' for fast, efficient error inspection
 70 | 
 71 | **Error without Execution ID** (workflow didn't start):
 72 | - Format: "Workflow failed to execute. Use n8n_list_executions to find recent executions, then n8n_get_execution with mode='preview' to investigate."
 73 | - Recommended action: Check recent executions with n8n_list_executions
 74 | 
 75 | **Why mode='preview'?**
 76 | - Fast: <50ms response time
 77 | - Efficient: ~500 tokens (vs 50K+ for full mode)
 78 | - Safe: No timeout or token limit risks
 79 | - Informative: Shows structure, counts, and error details
 80 | - Provides recommendations for fetching more data if needed
 81 | 
 82 | **Example Error Responses**:
 83 | \`\`\`json
 84 | {
 85 |   "success": false,
 86 |   "error": "Workflow wf_123 execution exec_456 failed. Use n8n_get_execution({id: 'exec_456', mode: 'preview'}) to investigate the error.",
 87 |   "executionId": "exec_456",
 88 |   "workflowId": "wf_123",
 89 |   "code": "SERVER_ERROR"
 90 | }
 91 | \`\`\`
 92 | 
 93 | **Investigation Workflow**:
 94 | 1. Trigger returns error with execution ID
 95 | 2. Call n8n_get_execution({id: executionId, mode: 'preview'}) to see structure and error
 96 | 3. Based on preview recommendation, fetch more data if needed
 97 | 4. Fix issues in workflow and retry`,
 98 |     bestPractices: [
 99 |       'Always verify workflow is active before attempting webhook triggers',
100 |       'Match HTTP method exactly with webhook node configuration',
101 |       'Use async mode (waitForResponse: false) for long-running workflows',
102 |       'Include authentication headers when webhook requires them',
103 |       'Test webhook URL manually first to ensure it works',
104 |       'When errors occur, use n8n_get_execution with mode="preview" first for efficient debugging',
105 |       'Store execution IDs from error responses for later investigation'
106 |     ],
107 |     pitfalls: [
108 |       'Workflow must be ACTIVE - inactive workflows cannot be triggered',
109 |       'HTTP method mismatch returns 404 even if URL is correct',
110 |       'Webhook node must be the trigger node in the workflow',
111 |       'Timeout errors occur with long workflows in sync mode',
112 |       'Data format must match webhook node expectations',
113 |       'Error messages always include n8n_get_execution guidance - follow the suggested steps for efficient debugging',
114 |       'Execution IDs in error responses are crucial for debugging - always check for and use them'
115 |     ],
116 |     relatedTools: ['n8n_get_execution', 'n8n_list_executions', 'n8n_get_workflow', 'n8n_create_workflow']
117 |   }
118 | };
```

--------------------------------------------------------------------------------
/versioned-nodes.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Versioned Nodes in n8n
  2 | 
  3 | This document lists all nodes that have `version` defined as an array in their description.
  4 | 
  5 | ## From n8n-nodes-base package:
  6 | 
  7 | 1. **Airtop** - `Airtop.node.js`
  8 | 2. **Cal Trigger** - `CalTrigger.node.js`
  9 | 3. **Coda** - `Coda.node.js`
 10 | 4. **Code** - `Code.node.js` - version: [1, 2]
 11 | 5. **Compare Datasets** - `CompareDatasets.node.js`
 12 | 6. **Compression** - `Compression.node.js`
 13 | 7. **Convert To File** - `ConvertToFile.node.js`
 14 | 8. **Email Send V2** - `EmailSendV2.node.js`
 15 | 9. **Execute Workflow** - `ExecuteWorkflow.node.js`
 16 | 10. **Execute Workflow Trigger** - `ExecuteWorkflowTrigger.node.js`
 17 | 11. **Filter V2** - `FilterV2.node.js`
 18 | 12. **Form Trigger V2** - `FormTriggerV2.node.js`
 19 | 13. **GitHub** - `Github.node.js`
 20 | 14. **Gmail Trigger** - `GmailTrigger.node.js`
 21 | 15. **Gmail V2** - `GmailV2.node.js`
 22 | 16. **Google Books** - `GoogleBooks.node.js`
 23 | 17. **Google Calendar** - `GoogleCalendar.node.js`
 24 | 18. **Google Docs** - `GoogleDocs.node.js`
 25 | 19. **Google Drive V1** - `GoogleDriveV1.node.js`
 26 | 20. **Google Firebase Cloud Firestore** - `GoogleFirebaseCloudFirestore.node.js`
 27 | 21. **Google Slides** - `GoogleSlides.node.js`
 28 | 22. **Google Translate** - `GoogleTranslate.node.js`
 29 | 23. **GraphQL** - `GraphQL.node.js`
 30 | 24. **HTML** - `Html.node.js`
 31 | 25. **HTTP Request V3** - `HttpRequestV3.node.js` - version: [3, 4, 4.1, 4.2]
 32 | 26. **HubSpot V2** - `HubspotV2.node.js`
 33 | 27. **If V2** - `IfV2.node.js`
 34 | 28. **Invoice Ninja** - `InvoiceNinja.node.js`
 35 | 29. **Invoice Ninja Trigger** - `InvoiceNinjaTrigger.node.js`
 36 | 30. **Item Lists V2** - `ItemListsV2.node.js`
 37 | 31. **Jira Trigger** - `JiraTrigger.node.js`
 38 | 32. **Kafka Trigger** - `KafkaTrigger.node.js`
 39 | 33. **MailerLite Trigger V2** - `MailerLiteTriggerV2.node.js`
 40 | 34. **MailerLite V2** - `MailerLiteV2.node.js`
 41 | 35. **Merge V2** - `MergeV2.node.js`
 42 | 36. **Microsoft SQL** - `MicrosoftSql.node.js`
 43 | 37. **Microsoft Teams V1** - `MicrosoftTeamsV1.node.js`
 44 | 38. **Mindee** - `Mindee.node.js`
 45 | 39. **MongoDB** - `MongoDb.node.js`
 46 | 40. **Move Binary Data** - `MoveBinaryData.node.js`
 47 | 41. **NocoDB** - `NocoDB.node.js`
 48 | 42. **OpenAI** - `OpenAi.node.js`
 49 | 43. **Pipedrive Trigger** - `PipedriveTrigger.node.js`
 50 | 44. **RabbitMQ** - `RabbitMQ.node.js`
 51 | 45. **Remove Duplicates V1** - `RemoveDuplicatesV1.node.js`
 52 | 46. **Remove Duplicates V2** - `RemoveDuplicatesV2.node.js`
 53 | 47. **Respond To Webhook** - `RespondToWebhook.node.js`
 54 | 48. **RSS Feed Read** - `RssFeedRead.node.js`
 55 | 49. **Schedule Trigger** - `ScheduleTrigger.node.js`
 56 | 50. **Set V1** - `SetV1.node.js`
 57 | 51. **Set V2** - `SetV2.node.js`
 58 | 52. **Slack V2** - `SlackV2.node.js`
 59 | 53. **Strava** - `Strava.node.js`
 60 | 54. **Summarize** - `Summarize.node.js`
 61 | 55. **Switch V1** - `SwitchV1.node.js`
 62 | 56. **Switch V2** - `SwitchV2.node.js`
 63 | 57. **Switch V3** - `SwitchV3.node.js`
 64 | 58. **Telegram** - `Telegram.node.js`
 65 | 59. **Telegram Trigger** - `TelegramTrigger.node.js`
 66 | 60. **The Hive Trigger** - `TheHiveTrigger.node.js`
 67 | 61. **Todoist V2** - `TodoistV2.node.js`
 68 | 62. **Twilio Trigger** - `TwilioTrigger.node.js`
 69 | 63. **Typeform Trigger** - `TypeformTrigger.node.js`
 70 | 64. **Wait** - `Wait.node.js`
 71 | 65. **Webhook** - `Webhook.node.js` - version: [1, 1.1, 2]
 72 | 
 73 | ## From @n8n/n8n-nodes-langchain package:
 74 | 
 75 | 1. **Agent V1** - `AgentV1.node.js`
 76 | 2. **Chain LLM** - `ChainLlm.node.js`
 77 | 3. **Chain Retrieval QA** - `ChainRetrievalQa.node.js`
 78 | 4. **Chain Summarization V2** - `ChainSummarizationV2.node.js`
 79 | 5. **Chat Trigger** - `ChatTrigger.node.js`
 80 | 6. **Document Default Data Loader** - `DocumentDefaultDataLoader.node.js`
 81 | 7. **Document GitHub Loader** - `DocumentGithubLoader.node.js`
 82 | 8. **Embeddings OpenAI** - `EmbeddingsOpenAi.node.js`
 83 | 9. **Information Extractor** - `InformationExtractor.node.js`
 84 | 10. **LM Chat Anthropic** - `LmChatAnthropic.node.js`
 85 | 11. **LM Chat DeepSeek** - `LmChatDeepSeek.node.js`
 86 | 12. **LM Chat OpenAI** - `LmChatOpenAi.node.js`
 87 | 13. **LM Chat OpenRouter** - `LmChatOpenRouter.node.js`
 88 | 14. **LM Chat xAI Grok** - `LmChatXAiGrok.node.js`
 89 | 15. **Manual Chat Trigger** - `ManualChatTrigger.node.js`
 90 | 16. **MCP Trigger** - `McpTrigger.node.js`
 91 | 17. **Memory Buffer Window** - `MemoryBufferWindow.node.js`
 92 | 18. **Memory Manager** - `MemoryManager.node.js`
 93 | 19. **Memory MongoDB Chat** - `MemoryMongoDbChat.node.js`
 94 | 20. **Memory Motorhead** - `MemoryMotorhead.node.js`
 95 | 21. **Memory Postgres Chat** - `MemoryPostgresChat.node.js`
 96 | 22. **Memory Redis Chat** - `MemoryRedisChat.node.js`
 97 | 23. **Memory Xata** - `MemoryXata.node.js`
 98 | 24. **Memory Zep** - `MemoryZep.node.js`
 99 | 25. **OpenAI Assistant** - `OpenAiAssistant.node.js`
100 | 26. **Output Parser Structured** - `OutputParserStructured.node.js`
101 | 27. **Retriever Workflow** - `RetrieverWorkflow.node.js`
102 | 28. **Sentiment Analysis** - `SentimentAnalysis.node.js`
103 | 29. **Text Classifier** - `TextClassifier.node.js`
104 | 30. **Tool Code** - `ToolCode.node.js`
105 | 31. **Tool HTTP Request** - `ToolHttpRequest.node.js`
106 | 32. **Tool Vector Store** - `ToolVectorStore.node.js`
107 | 
108 | ## Examples of Version Arrays Found
109 | 
110 | Here are some specific examples of version arrays from actual nodes:
111 | 
112 | ### n8n-nodes-base:
113 | - **Code**: `version: [1, 2]`
114 | - **HTTP Request V3**: `version: [3, 4, 4.1, 4.2]`
115 | - **Webhook**: `version: [1, 1.1, 2]`
116 | - **Wait**: `version: [1, 1.1]`
117 | - **Schedule Trigger**: `version: [1, 1.1, 1.2]`
118 | - **Switch V3**: `version: [3, 3.1, 3.2]`
119 | - **Set V2**: `version: [3, 3.1, 3.2, 3.3, 3.4]`
120 | 
121 | ### @n8n/n8n-nodes-langchain:
122 | - **LM Chat OpenAI**: `version: [1, 1.1, 1.2]`
123 | - **Chain LLM**: `version: [1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7]`
124 | - **Tool HTTP Request**: `version: [1, 1.1]`
125 | 
126 | ## Summary
127 | 
128 | Total nodes with version arrays: **97 nodes**
129 | - From n8n-nodes-base: 65 nodes
130 | - From @n8n/n8n-nodes-langchain: 32 nodes
131 | 
132 | These nodes use versioning to maintain backward compatibility while introducing new features or changes to their interface. The version array pattern allows n8n to:
133 | 1. Support multiple versions of the same node
134 | 2. Maintain backward compatibility with existing workflows
135 | 3. Introduce breaking changes in newer versions while keeping old versions functional
136 | 4. Use `defaultVersion` to specify which version new instances should use
137 | 
138 | Common version patterns observed:
139 | - Simple incremental: `[1, 2]`, `[1, 2, 3]`
140 | - Minor versions: `[1, 1.1, 1.2]` (common for bug fixes)
141 | - Patch versions: `[3, 4, 4.1, 4.2]` (detailed version tracking)
142 | - Extended versions: `[1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7]` (Chain LLM has the most versions)
```

--------------------------------------------------------------------------------
/src/types/node-types.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * TypeScript type definitions for n8n node parsing
  3 |  *
  4 |  * This file provides strong typing for node classes and instances,
  5 |  * preventing bugs like the v2.17.4 baseDescription issue where
  6 |  * TypeScript couldn't catch property name mistakes due to `any` types.
  7 |  *
  8 |  * @module types/node-types
  9 |  * @since 2.17.5
 10 |  */
 11 | 
 12 | // Import n8n's official interfaces
 13 | import type {
 14 |   IVersionedNodeType,
 15 |   INodeType,
 16 |   INodeTypeBaseDescription,
 17 |   INodeTypeDescription
 18 | } from 'n8n-workflow';
 19 | 
 20 | /**
 21 |  * Represents a node class that can be either:
 22 |  * - A constructor function that returns INodeType
 23 |  * - A constructor function that returns IVersionedNodeType
 24 |  * - An already-instantiated node instance
 25 |  *
 26 |  * This covers all patterns we encounter when loading nodes from n8n packages.
 27 |  */
 28 | export type NodeClass =
 29 |   | (new () => INodeType)
 30 |   | (new () => IVersionedNodeType)
 31 |   | INodeType
 32 |   | IVersionedNodeType;
 33 | 
 34 | /**
 35 |  * Instance of a versioned node type with all properties accessible.
 36 |  *
 37 |  * This represents nodes that use n8n's VersionedNodeType pattern,
 38 |  * such as AI Agent, HTTP Request, Slack, etc.
 39 |  *
 40 |  * @property currentVersion - The computed current version (defaultVersion ?? max(nodeVersions))
 41 |  * @property description - Base description stored as 'description' (NOT 'baseDescription')
 42 |  * @property nodeVersions - Map of version numbers to INodeType implementations
 43 |  *
 44 |  * @example
 45 |  * ```typescript
 46 |  * const aiAgent = new AIAgentNode() as VersionedNodeInstance;
 47 |  * console.log(aiAgent.currentVersion); // 2.2
 48 |  * console.log(aiAgent.description.defaultVersion); // 2.2
 49 |  * console.log(aiAgent.nodeVersions[1]); // INodeType for version 1
 50 |  * ```
 51 |  */
 52 | export interface VersionedNodeInstance extends IVersionedNodeType {
 53 |   currentVersion: number;
 54 |   description: INodeTypeBaseDescription;
 55 |   nodeVersions: {
 56 |     [version: number]: INodeType;
 57 |   };
 58 | }
 59 | 
 60 | /**
 61 |  * Instance of a regular (non-versioned) node type.
 62 |  *
 63 |  * This represents simple nodes that don't use versioning,
 64 |  * such as Edit Fields, Set, Code (v1), etc.
 65 |  */
 66 | export interface RegularNodeInstance extends INodeType {
 67 |   description: INodeTypeDescription;
 68 | }
 69 | 
 70 | /**
 71 |  * Union type for any node instance (versioned or regular).
 72 |  *
 73 |  * Use this when you need to handle both types of nodes.
 74 |  */
 75 | export type NodeInstance = VersionedNodeInstance | RegularNodeInstance;
 76 | 
 77 | /**
 78 |  * Type guard to check if a node is a VersionedNodeType instance.
 79 |  *
 80 |  * This provides runtime type safety and enables TypeScript to narrow
 81 |  * the type within conditional blocks.
 82 |  *
 83 |  * @param node - The node instance to check
 84 |  * @returns True if node is a VersionedNodeInstance
 85 |  *
 86 |  * @example
 87 |  * ```typescript
 88 |  * const instance = new nodeClass();
 89 |  * if (isVersionedNodeInstance(instance)) {
 90 |  *   // TypeScript knows instance is VersionedNodeInstance here
 91 |  *   console.log(instance.currentVersion);
 92 |  *   console.log(instance.nodeVersions);
 93 |  * }
 94 |  * ```
 95 |  */
 96 | export function isVersionedNodeInstance(node: any): node is VersionedNodeInstance {
 97 |   return (
 98 |     node !== null &&
 99 |     typeof node === 'object' &&
100 |     'nodeVersions' in node &&
101 |     'currentVersion' in node &&
102 |     'description' in node &&
103 |     typeof node.currentVersion === 'number'
104 |   );
105 | }
106 | 
107 | /**
108 |  * Type guard to check if a value is a VersionedNodeType class.
109 |  *
110 |  * This checks the constructor name pattern used by n8n's VersionedNodeType.
111 |  *
112 |  * @param nodeClass - The class or value to check
113 |  * @returns True if nodeClass is a VersionedNodeType constructor
114 |  *
115 |  * @example
116 |  * ```typescript
117 |  * if (isVersionedNodeClass(nodeClass)) {
118 |  *   // It's a VersionedNodeType class
119 |  *   const instance = new nodeClass() as VersionedNodeInstance;
120 |  * }
121 |  * ```
122 |  */
123 | export function isVersionedNodeClass(nodeClass: any): boolean {
124 |   return (
125 |     typeof nodeClass === 'function' &&
126 |     nodeClass.prototype?.constructor?.name === 'VersionedNodeType'
127 |   );
128 | }
129 | 
130 | /**
131 |  * Safely instantiate a node class with proper error handling.
132 |  *
133 |  * Some nodes require specific parameters or environment setup to instantiate.
134 |  * This helper provides safe instantiation with fallback to null on error.
135 |  *
136 |  * @param nodeClass - The node class or instance to instantiate
137 |  * @returns The instantiated node or null if instantiation fails
138 |  *
139 |  * @example
140 |  * ```typescript
141 |  * const instance = instantiateNode(nodeClass);
142 |  * if (instance) {
143 |  *   // Successfully instantiated
144 |  *   const version = isVersionedNodeInstance(instance)
145 |  *     ? instance.currentVersion
146 |  *     : instance.description.version;
147 |  * }
148 |  * ```
149 |  */
150 | export function instantiateNode(nodeClass: NodeClass): NodeInstance | null {
151 |   try {
152 |     if (typeof nodeClass === 'function') {
153 |       return new nodeClass();
154 |     }
155 |     // Already an instance
156 |     return nodeClass;
157 |   } catch (e) {
158 |     // Some nodes require parameters to instantiate
159 |     return null;
160 |   }
161 | }
162 | 
163 | /**
164 |  * Safely get a node instance, handling both classes and instances.
165 |  *
166 |  * This is a non-throwing version that returns undefined on failure.
167 |  *
168 |  * @param nodeClass - The node class or instance
169 |  * @returns The node instance or undefined
170 |  */
171 | export function getNodeInstance(nodeClass: NodeClass): NodeInstance | undefined {
172 |   const instance = instantiateNode(nodeClass);
173 |   return instance ?? undefined;
174 | }
175 | 
176 | /**
177 |  * Extract description from a node class or instance.
178 |  *
179 |  * Handles both versioned and regular nodes, with fallback logic.
180 |  *
181 |  * @param nodeClass - The node class or instance
182 |  * @returns The node description or empty object on failure
183 |  */
184 | export function getNodeDescription(
185 |   nodeClass: NodeClass
186 | ): INodeTypeBaseDescription | INodeTypeDescription {
187 |   // Try to get description from instance first
188 |   try {
189 |     const instance = instantiateNode(nodeClass);
190 | 
191 |     if (instance) {
192 |       // For VersionedNodeType, description is the baseDescription
193 |       if (isVersionedNodeInstance(instance)) {
194 |         return instance.description;
195 |       }
196 |       // For regular nodes, description is the full INodeTypeDescription
197 |       return instance.description;
198 |     }
199 |   } catch (e) {
200 |     // Ignore instantiation errors
201 |   }
202 | 
203 |   // Fallback to static properties
204 |   if (typeof nodeClass === 'object' && 'description' in nodeClass) {
205 |     return nodeClass.description;
206 |   }
207 | 
208 |   // Last resort: empty description
209 |   return {
210 |     displayName: '',
211 |     name: '',
212 |     group: [],
213 |     description: '',
214 |     version: 1,
215 |     defaults: { name: '', color: '' },
216 |     inputs: [],
217 |     outputs: [],
218 |     properties: []
219 |   } as any; // Type assertion needed for fallback case
220 | }
221 | 
```

--------------------------------------------------------------------------------
/.claude/agents/deployment-engineer.md:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | name: deployment-engineer
  3 | description: Use this agent when you need to set up CI/CD pipelines, containerize applications, configure cloud deployments, or automate infrastructure. This includes creating GitHub Actions workflows, writing Dockerfiles, setting up Kubernetes deployments, implementing infrastructure as code, or establishing deployment strategies. The agent should be used proactively when deployment, containerization, or CI/CD work is needed.\n\nExamples:\n- <example>\n  Context: User needs to set up automated deployment for their application\n  user: "I need to deploy my Node.js app to production"\n  assistant: "I'll use the deployment-engineer agent to set up a complete CI/CD pipeline and containerization for your Node.js application"\n  <commentary>\n  Since the user needs deployment setup, use the Task tool to launch the deployment-engineer agent to create the necessary CI/CD and container configurations.\n  </commentary>\n</example>\n- <example>\n  Context: User has just created a new web service and needs deployment automation\n  user: "I've finished building the API service"\n  assistant: "Now let me use the deployment-engineer agent to set up automated deployments for your API service"\n  <commentary>\n  Proactively use the deployment-engineer agent after development work to establish proper deployment infrastructure.\n  </commentary>\n</example>\n- <example>\n  Context: User wants to implement Kubernetes for their microservices\n  user: "How should I structure my Kubernetes deployments for these three microservices?"\n  assistant: "I'll use the deployment-engineer agent to create a complete Kubernetes deployment strategy for your microservices"\n  <commentary>\n  For Kubernetes and container orchestration questions, use the deployment-engineer agent to provide production-ready configurations.\n  </commentary>\n</example>
  4 | ---
  5 | 
  6 | You are a deployment engineer specializing in automated deployments and container orchestration. Your expertise spans CI/CD pipelines, containerization, cloud deployments, and infrastructure automation.
  7 | 
  8 | ## Core Responsibilities
  9 | 
 10 | You will create production-ready deployment configurations that emphasize automation, reliability, and maintainability. Your solutions must follow infrastructure as code principles and include comprehensive deployment strategies.
 11 | 
 12 | ## Technical Expertise
 13 | 
 14 | ### CI/CD Pipelines
 15 | - Design GitHub Actions workflows with matrix builds, caching, and artifact management
 16 | - Implement GitLab CI pipelines with proper stages and dependencies
 17 | - Configure Jenkins pipelines with shared libraries and parallel execution
 18 | - Set up automated testing, security scanning, and quality gates
 19 | - Implement semantic versioning and automated release management
 20 | 
 21 | ### Container Engineering
 22 | - Write multi-stage Dockerfiles optimized for size and security
 23 | - Implement proper layer caching and build optimization
 24 | - Configure container security scanning and vulnerability management
 25 | - Design docker-compose configurations for local development
 26 | - Implement container registry strategies with proper tagging
 27 | 
 28 | ### Kubernetes Orchestration
 29 | - Create deployments with proper resource limits and requests
 30 | - Configure services, ingresses, and network policies
 31 | - Implement ConfigMaps and Secrets management
 32 | - Design horizontal pod autoscaling and cluster autoscaling
 33 | - Set up health checks, readiness probes, and liveness probes
 34 | 
 35 | ### Infrastructure as Code
 36 | - Write Terraform modules for cloud resources
 37 | - Design CloudFormation templates with proper parameters
 38 | - Implement state management and backend configuration
 39 | - Create reusable infrastructure components
 40 | - Design multi-environment deployment strategies
 41 | 
 42 | ## Operational Approach
 43 | 
 44 | 1. **Automation First**: Every deployment step must be automated. Manual interventions should only be required for approval gates.
 45 | 
 46 | 2. **Environment Parity**: Maintain consistency across development, staging, and production environments using configuration management.
 47 | 
 48 | 3. **Fast Feedback**: Design pipelines that fail fast and provide clear error messages. Run quick checks before expensive operations.
 49 | 
 50 | 4. **Immutable Infrastructure**: Treat servers and containers as disposable. Never modify running infrastructure - always replace.
 51 | 
 52 | 5. **Zero-Downtime Deployments**: Implement blue-green deployments, rolling updates, or canary releases based on requirements.
 53 | 
 54 | ## Output Requirements
 55 | 
 56 | You will provide:
 57 | 
 58 | ### CI/CD Pipeline Configuration
 59 | - Complete pipeline file with all stages defined
 60 | - Build, test, security scan, and deployment stages
 61 | - Environment-specific deployment configurations
 62 | - Secret management and variable handling
 63 | - Artifact storage and versioning strategy
 64 | 
 65 | ### Container Configuration
 66 | - Production-optimized Dockerfile with comments
 67 | - Security best practices (non-root user, minimal base images)
 68 | - Build arguments for flexibility
 69 | - Health check implementations
 70 | - Container registry push strategies
 71 | 
 72 | ### Orchestration Manifests
 73 | - Kubernetes YAML files or docker-compose configurations
 74 | - Service definitions with proper networking
 75 | - Persistent volume configurations if needed
 76 | - Ingress/load balancer setup
 77 | - Namespace and RBAC configurations
 78 | 
 79 | ### Infrastructure Code
 80 | - Complete IaC templates for required resources
 81 | - Variable definitions for environment flexibility
 82 | - Output definitions for resource discovery
 83 | - State management configuration
 84 | - Module structure for reusability
 85 | 
 86 | ### Deployment Documentation
 87 | - Step-by-step deployment runbook
 88 | - Rollback procedures with specific commands
 89 | - Monitoring and alerting setup basics
 90 | - Troubleshooting guide for common issues
 91 | - Environment variable documentation
 92 | 
 93 | ## Quality Standards
 94 | 
 95 | - Include inline comments explaining critical decisions and trade-offs
 96 | - Provide security scanning at multiple stages
 97 | - Implement proper logging and monitoring hooks
 98 | - Design for horizontal scalability from the start
 99 | - Include cost optimization considerations
100 | - Ensure all configurations are idempotent
101 | 
102 | ## Proactive Recommendations
103 | 
104 | When analyzing existing code or infrastructure, you will proactively suggest:
105 | - Pipeline optimizations to reduce build times
106 | - Security improvements for containers and deployments
107 | - Cost optimization opportunities
108 | - Monitoring and observability enhancements
109 | - Disaster recovery improvements
110 | 
111 | You will always validate that configurations work together as a complete system and provide clear instructions for implementation and testing.
112 | 
```

--------------------------------------------------------------------------------
/tests/unit/utils/n8n-errors.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, it, expect } from 'vitest';
  2 | import {
  3 |   formatExecutionError,
  4 |   formatNoExecutionError,
  5 |   getUserFriendlyErrorMessage,
  6 |   N8nApiError,
  7 |   N8nAuthenticationError,
  8 |   N8nNotFoundError,
  9 |   N8nValidationError,
 10 |   N8nRateLimitError,
 11 |   N8nServerError
 12 | } from '../../../src/utils/n8n-errors';
 13 | 
 14 | describe('formatExecutionError', () => {
 15 |   it('should format error with both execution ID and workflow ID', () => {
 16 |     const result = formatExecutionError('exec_12345', 'wf_abc');
 17 | 
 18 |     expect(result).toBe("Workflow wf_abc execution exec_12345 failed. Use n8n_get_execution({id: 'exec_12345', mode: 'preview'}) to investigate the error.");
 19 |     expect(result).toContain('mode: \'preview\'');
 20 |     expect(result).toContain('exec_12345');
 21 |     expect(result).toContain('wf_abc');
 22 |   });
 23 | 
 24 |   it('should format error with only execution ID', () => {
 25 |     const result = formatExecutionError('exec_67890');
 26 | 
 27 |     expect(result).toBe("Execution exec_67890 failed. Use n8n_get_execution({id: 'exec_67890', mode: 'preview'}) to investigate the error.");
 28 |     expect(result).toContain('mode: \'preview\'');
 29 |     expect(result).toContain('exec_67890');
 30 |     expect(result).not.toContain('Workflow');
 31 |   });
 32 | 
 33 |   it('should include preview mode guidance', () => {
 34 |     const result = formatExecutionError('test_id');
 35 | 
 36 |     expect(result).toMatch(/mode:\s*'preview'/);
 37 |   });
 38 | 
 39 |   it('should format with undefined workflow ID (treated as missing)', () => {
 40 |     const result = formatExecutionError('exec_123', undefined);
 41 | 
 42 |     expect(result).toBe("Execution exec_123 failed. Use n8n_get_execution({id: 'exec_123', mode: 'preview'}) to investigate the error.");
 43 |   });
 44 | 
 45 |   it('should properly escape execution ID in suggestion', () => {
 46 |     const result = formatExecutionError('exec-with-special_chars.123');
 47 | 
 48 |     expect(result).toContain("id: 'exec-with-special_chars.123'");
 49 |   });
 50 | });
 51 | 
 52 | describe('formatNoExecutionError', () => {
 53 |   it('should provide guidance to check recent executions', () => {
 54 |     const result = formatNoExecutionError();
 55 | 
 56 |     expect(result).toBe("Workflow failed to execute. Use n8n_list_executions to find recent executions, then n8n_get_execution with mode='preview' to investigate.");
 57 |     expect(result).toContain('n8n_list_executions');
 58 |     expect(result).toContain('n8n_get_execution');
 59 |     expect(result).toContain("mode='preview'");
 60 |   });
 61 | 
 62 |   it('should include preview mode in guidance', () => {
 63 |     const result = formatNoExecutionError();
 64 | 
 65 |     expect(result).toMatch(/mode\s*=\s*'preview'/);
 66 |   });
 67 | });
 68 | 
 69 | describe('getUserFriendlyErrorMessage', () => {
 70 |   it('should handle authentication error', () => {
 71 |     const error = new N8nAuthenticationError('Invalid API key');
 72 |     const message = getUserFriendlyErrorMessage(error);
 73 | 
 74 |     expect(message).toBe('Failed to authenticate with n8n. Please check your API key.');
 75 |   });
 76 | 
 77 |   it('should handle not found error', () => {
 78 |     const error = new N8nNotFoundError('Workflow', '123');
 79 |     const message = getUserFriendlyErrorMessage(error);
 80 | 
 81 |     expect(message).toContain('not found');
 82 |   });
 83 | 
 84 |   it('should handle validation error', () => {
 85 |     const error = new N8nValidationError('Missing required field');
 86 |     const message = getUserFriendlyErrorMessage(error);
 87 | 
 88 |     expect(message).toBe('Invalid request: Missing required field');
 89 |   });
 90 | 
 91 |   it('should handle rate limit error', () => {
 92 |     const error = new N8nRateLimitError(60);
 93 |     const message = getUserFriendlyErrorMessage(error);
 94 | 
 95 |     expect(message).toBe('Too many requests. Please wait a moment and try again.');
 96 |   });
 97 | 
 98 |   it('should handle server error with custom message', () => {
 99 |     const error = new N8nServerError('Database connection failed', 503);
100 |     const message = getUserFriendlyErrorMessage(error);
101 | 
102 |     expect(message).toBe('Database connection failed');
103 |   });
104 | 
105 |   it('should handle server error without message', () => {
106 |     const error = new N8nApiError('', 500, 'SERVER_ERROR');
107 |     const message = getUserFriendlyErrorMessage(error);
108 | 
109 |     expect(message).toBe('n8n server error occurred');
110 |   });
111 | 
112 |   it('should handle no response error', () => {
113 |     const error = new N8nApiError('Network error', undefined, 'NO_RESPONSE');
114 |     const message = getUserFriendlyErrorMessage(error);
115 | 
116 |     expect(message).toBe('Unable to connect to n8n. Please check the server URL and ensure n8n is running.');
117 |   });
118 | 
119 |   it('should handle unknown error with message', () => {
120 |     const error = new N8nApiError('Custom error message');
121 |     const message = getUserFriendlyErrorMessage(error);
122 | 
123 |     expect(message).toBe('Custom error message');
124 |   });
125 | 
126 |   it('should handle unknown error without message', () => {
127 |     const error = new N8nApiError('');
128 |     const message = getUserFriendlyErrorMessage(error);
129 | 
130 |     expect(message).toBe('An unexpected error occurred');
131 |   });
132 | });
133 | 
134 | describe('Error message integration', () => {
135 |   it('should use formatExecutionError for webhook failures with execution ID', () => {
136 |     const executionId = 'exec_webhook_123';
137 |     const workflowId = 'wf_webhook_abc';
138 |     const message = formatExecutionError(executionId, workflowId);
139 | 
140 |     expect(message).toContain('Workflow wf_webhook_abc execution exec_webhook_123 failed');
141 |     expect(message).toContain('n8n_get_execution');
142 |     expect(message).toContain("mode: 'preview'");
143 |   });
144 | 
145 |   it('should use formatNoExecutionError for server errors without execution context', () => {
146 |     const message = formatNoExecutionError();
147 | 
148 |     expect(message).toContain('Workflow failed to execute');
149 |     expect(message).toContain('n8n_list_executions');
150 |     expect(message).toContain('n8n_get_execution');
151 |   });
152 | 
153 |   it('should not include "contact support" in any error message', () => {
154 |     const executionMessage = formatExecutionError('test');
155 |     const noExecutionMessage = formatNoExecutionError();
156 |     const serverError = new N8nServerError();
157 |     const serverErrorMessage = getUserFriendlyErrorMessage(serverError);
158 | 
159 |     expect(executionMessage.toLowerCase()).not.toContain('contact support');
160 |     expect(noExecutionMessage.toLowerCase()).not.toContain('contact support');
161 |     expect(serverErrorMessage.toLowerCase()).not.toContain('contact support');
162 |   });
163 | 
164 |   it('should always guide users to use preview mode first', () => {
165 |     const executionMessage = formatExecutionError('test');
166 |     const noExecutionMessage = formatNoExecutionError();
167 | 
168 |     expect(executionMessage).toContain("mode: 'preview'");
169 |     expect(noExecutionMessage).toContain("mode='preview'");
170 |   });
171 | });
172 | 
```

--------------------------------------------------------------------------------
/tests/unit/services/operation-similarity-service.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Tests for OperationSimilarityService
  3 |  */
  4 | 
  5 | import { describe, it, expect, beforeEach } from 'vitest';
  6 | import { OperationSimilarityService } from '../../../src/services/operation-similarity-service';
  7 | import { NodeRepository } from '../../../src/database/node-repository';
  8 | import { createTestDatabase } from '../../utils/database-utils';
  9 | 
 10 | describe('OperationSimilarityService', () => {
 11 |   let service: OperationSimilarityService;
 12 |   let repository: NodeRepository;
 13 |   let testDb: any;
 14 | 
 15 |   beforeEach(async () => {
 16 |     testDb = await createTestDatabase();
 17 |     repository = testDb.nodeRepository;
 18 |     service = new OperationSimilarityService(repository);
 19 | 
 20 |     // Add test node with operations
 21 |     const testNode = {
 22 |       nodeType: 'nodes-base.googleDrive',
 23 |       packageName: 'n8n-nodes-base',
 24 |       displayName: 'Google Drive',
 25 |       description: 'Access Google Drive',
 26 |       category: 'transform',
 27 |       style: 'declarative' as const,
 28 |       isAITool: false,
 29 |       isTrigger: false,
 30 |       isWebhook: false,
 31 |       isVersioned: true,
 32 |       version: '1',
 33 |       properties: [
 34 |         {
 35 |           name: 'resource',
 36 |           type: 'options',
 37 |           options: [
 38 |             { value: 'file', name: 'File' },
 39 |             { value: 'folder', name: 'Folder' },
 40 |             { value: 'drive', name: 'Shared Drive' },
 41 |           ]
 42 |         },
 43 |         {
 44 |           name: 'operation',
 45 |           type: 'options',
 46 |           displayOptions: {
 47 |             show: {
 48 |               resource: ['file']
 49 |             }
 50 |           },
 51 |           options: [
 52 |             { value: 'copy', name: 'Copy' },
 53 |             { value: 'delete', name: 'Delete' },
 54 |             { value: 'download', name: 'Download' },
 55 |             { value: 'list', name: 'List' },
 56 |             { value: 'share', name: 'Share' },
 57 |             { value: 'update', name: 'Update' },
 58 |             { value: 'upload', name: 'Upload' }
 59 |           ]
 60 |         },
 61 |         {
 62 |           name: 'operation',
 63 |           type: 'options',
 64 |           displayOptions: {
 65 |             show: {
 66 |               resource: ['folder']
 67 |             }
 68 |           },
 69 |           options: [
 70 |             { value: 'create', name: 'Create' },
 71 |             { value: 'delete', name: 'Delete' },
 72 |             { value: 'share', name: 'Share' }
 73 |           ]
 74 |         }
 75 |       ],
 76 |       operations: [],
 77 |       credentials: []
 78 |     };
 79 | 
 80 |     repository.saveNode(testNode);
 81 |   });
 82 | 
 83 |   afterEach(async () => {
 84 |     if (testDb) {
 85 |       await testDb.cleanup();
 86 |     }
 87 |   });
 88 | 
 89 |   describe('findSimilarOperations', () => {
 90 |     it('should find exact match', () => {
 91 |       const suggestions = service.findSimilarOperations(
 92 |         'nodes-base.googleDrive',
 93 |         'download',
 94 |         'file'
 95 |       );
 96 | 
 97 |       expect(suggestions).toHaveLength(0); // No suggestions for valid operation
 98 |     });
 99 | 
100 |     it('should suggest similar operations for typos', () => {
101 |       const suggestions = service.findSimilarOperations(
102 |         'nodes-base.googleDrive',
103 |         'downlod',
104 |         'file'
105 |       );
106 | 
107 |       expect(suggestions.length).toBeGreaterThan(0);
108 |       expect(suggestions[0].value).toBe('download');
109 |       expect(suggestions[0].confidence).toBeGreaterThan(0.8);
110 |     });
111 | 
112 |     it('should handle common mistakes with patterns', () => {
113 |       const suggestions = service.findSimilarOperations(
114 |         'nodes-base.googleDrive',
115 |         'uploadFile',
116 |         'file'
117 |       );
118 | 
119 |       expect(suggestions.length).toBeGreaterThan(0);
120 |       expect(suggestions[0].value).toBe('upload');
121 |       expect(suggestions[0].reason).toContain('instead of');
122 |     });
123 | 
124 |     it('should filter operations by resource', () => {
125 |       const suggestions = service.findSimilarOperations(
126 |         'nodes-base.googleDrive',
127 |         'upload',
128 |         'folder'
129 |       );
130 | 
131 |       // Upload is not valid for folder resource
132 |       expect(suggestions).toBeDefined();
133 |       expect(suggestions.find(s => s.value === 'upload')).toBeUndefined();
134 |     });
135 | 
136 |     it('should return empty array for node not found', () => {
137 |       const suggestions = service.findSimilarOperations(
138 |         'nodes-base.nonexistent',
139 |         'operation',
140 |         undefined
141 |       );
142 | 
143 |       expect(suggestions).toEqual([]);
144 |     });
145 | 
146 |     it('should handle operations without resource filtering', () => {
147 |       const suggestions = service.findSimilarOperations(
148 |         'nodes-base.googleDrive',
149 |         'updat',  // Missing 'e' at the end
150 |         undefined
151 |       );
152 | 
153 |       expect(suggestions.length).toBeGreaterThan(0);
154 |       expect(suggestions[0].value).toBe('update');
155 |     });
156 |   });
157 | 
158 |   describe('similarity calculation', () => {
159 |     it('should rank exact matches highest', () => {
160 |       const suggestions = service.findSimilarOperations(
161 |         'nodes-base.googleDrive',
162 |         'delete',
163 |         'file'
164 |       );
165 | 
166 |       expect(suggestions).toHaveLength(0); // Exact match, no suggestions needed
167 |     });
168 | 
169 |     it('should rank substring matches high', () => {
170 |       const suggestions = service.findSimilarOperations(
171 |         'nodes-base.googleDrive',
172 |         'del',
173 |         'file'
174 |       );
175 | 
176 |       expect(suggestions.length).toBeGreaterThan(0);
177 |       const deleteSuggestion = suggestions.find(s => s.value === 'delete');
178 |       expect(deleteSuggestion).toBeDefined();
179 |       expect(deleteSuggestion!.confidence).toBeGreaterThanOrEqual(0.7);
180 |     });
181 | 
182 |     it('should detect common variations', () => {
183 |       const suggestions = service.findSimilarOperations(
184 |         'nodes-base.googleDrive',
185 |         'getData',
186 |         'file'
187 |       );
188 | 
189 |       expect(suggestions.length).toBeGreaterThan(0);
190 |       // Should suggest 'download' or similar
191 |     });
192 |   });
193 | 
194 |   describe('caching', () => {
195 |     it('should cache results for repeated queries', () => {
196 |       // First call
197 |       const suggestions1 = service.findSimilarOperations(
198 |         'nodes-base.googleDrive',
199 |         'downlod',
200 |         'file'
201 |       );
202 | 
203 |       // Second call with same params
204 |       const suggestions2 = service.findSimilarOperations(
205 |         'nodes-base.googleDrive',
206 |         'downlod',
207 |         'file'
208 |       );
209 | 
210 |       expect(suggestions1).toEqual(suggestions2);
211 |     });
212 | 
213 |     it('should clear cache when requested', () => {
214 |       // Add to cache
215 |       service.findSimilarOperations(
216 |         'nodes-base.googleDrive',
217 |         'test',
218 |         'file'
219 |       );
220 | 
221 |       // Clear cache
222 |       service.clearCache();
223 | 
224 |       // This would fetch fresh data (behavior is the same, just uncached)
225 |       const suggestions = service.findSimilarOperations(
226 |         'nodes-base.googleDrive',
227 |         'test',
228 |         'file'
229 |       );
230 | 
231 |       expect(suggestions).toBeDefined();
232 |     });
233 |   });
234 | });
```

--------------------------------------------------------------------------------
/src/types/workflow-diff.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Workflow Diff Types
  3 |  * Defines the structure for partial workflow updates using diff operations
  4 |  */
  5 | 
  6 | import { WorkflowNode, WorkflowConnection } from './n8n-api';
  7 | 
  8 | // Base operation interface
  9 | export interface DiffOperation {
 10 |   type: string;
 11 |   description?: string; // Optional description for clarity
 12 | }
 13 | 
 14 | // Node Operations
 15 | export interface AddNodeOperation extends DiffOperation {
 16 |   type: 'addNode';
 17 |   node: Partial<WorkflowNode> & {
 18 |     name: string; // Name is required
 19 |     type: string; // Type is required
 20 |     position: [number, number]; // Position is required
 21 |   };
 22 | }
 23 | 
 24 | export interface RemoveNodeOperation extends DiffOperation {
 25 |   type: 'removeNode';
 26 |   nodeId?: string; // Can use either ID or name
 27 |   nodeName?: string;
 28 | }
 29 | 
 30 | export interface UpdateNodeOperation extends DiffOperation {
 31 |   type: 'updateNode';
 32 |   nodeId?: string; // Can use either ID or name
 33 |   nodeName?: string;
 34 |   updates: {
 35 |     [path: string]: any; // Dot notation paths like 'parameters.url'
 36 |   };
 37 | }
 38 | 
 39 | export interface MoveNodeOperation extends DiffOperation {
 40 |   type: 'moveNode';
 41 |   nodeId?: string;
 42 |   nodeName?: string;
 43 |   position: [number, number];
 44 | }
 45 | 
 46 | export interface EnableNodeOperation extends DiffOperation {
 47 |   type: 'enableNode';
 48 |   nodeId?: string;
 49 |   nodeName?: string;
 50 | }
 51 | 
 52 | export interface DisableNodeOperation extends DiffOperation {
 53 |   type: 'disableNode';
 54 |   nodeId?: string;
 55 |   nodeName?: string;
 56 | }
 57 | 
 58 | // Connection Operations
 59 | export interface AddConnectionOperation extends DiffOperation {
 60 |   type: 'addConnection';
 61 |   source: string; // Node name or ID
 62 |   target: string; // Node name or ID
 63 |   sourceOutput?: string; // Default: 'main'
 64 |   targetInput?: string; // Default: 'main'
 65 |   sourceIndex?: number; // Default: 0
 66 |   targetIndex?: number; // Default: 0
 67 |   // Smart parameters for multi-output nodes (Phase 1 UX improvement)
 68 |   branch?: 'true' | 'false'; // For IF nodes: maps to sourceIndex (0=true, 1=false)
 69 |   case?: number; // For Switch/multi-output nodes: maps to sourceIndex
 70 | }
 71 | 
 72 | export interface RemoveConnectionOperation extends DiffOperation {
 73 |   type: 'removeConnection';
 74 |   source: string; // Node name or ID
 75 |   target: string; // Node name or ID
 76 |   sourceOutput?: string; // Default: 'main'
 77 |   targetInput?: string; // Default: 'main'
 78 |   ignoreErrors?: boolean; // If true, don't fail when connection doesn't exist (useful for cleanup)
 79 | }
 80 | 
 81 | export interface RewireConnectionOperation extends DiffOperation {
 82 |   type: 'rewireConnection';
 83 |   source: string;      // Source node name or ID
 84 |   from: string;        // Current target to rewire FROM
 85 |   to: string;          // New target to rewire TO
 86 |   sourceOutput?: string;  // Optional: which output to rewire (default: 'main')
 87 |   targetInput?: string;   // Optional: which input type (default: 'main')
 88 |   sourceIndex?: number;   // Optional: which source index (default: 0)
 89 |   // Smart parameters for multi-output nodes (Phase 1 UX improvement)
 90 |   branch?: 'true' | 'false'; // For IF nodes: maps to sourceIndex (0=true, 1=false)
 91 |   case?: number; // For Switch/multi-output nodes: maps to sourceIndex
 92 | }
 93 | 
 94 | // Workflow Metadata Operations
 95 | export interface UpdateSettingsOperation extends DiffOperation {
 96 |   type: 'updateSettings';
 97 |   settings: {
 98 |     [key: string]: any;
 99 |   };
100 | }
101 | 
102 | export interface UpdateNameOperation extends DiffOperation {
103 |   type: 'updateName';
104 |   name: string;
105 | }
106 | 
107 | export interface AddTagOperation extends DiffOperation {
108 |   type: 'addTag';
109 |   tag: string;
110 | }
111 | 
112 | export interface RemoveTagOperation extends DiffOperation {
113 |   type: 'removeTag';
114 |   tag: string;
115 | }
116 | 
117 | // Connection Cleanup Operations
118 | export interface CleanStaleConnectionsOperation extends DiffOperation {
119 |   type: 'cleanStaleConnections';
120 |   dryRun?: boolean; // If true, return what would be removed without applying changes
121 | }
122 | 
123 | export interface ReplaceConnectionsOperation extends DiffOperation {
124 |   type: 'replaceConnections';
125 |   connections: {
126 |     [nodeName: string]: {
127 |       [outputName: string]: Array<Array<{
128 |         node: string;
129 |         type: string;
130 |         index: number;
131 |       }>>;
132 |     };
133 |   };
134 | }
135 | 
136 | // Union type for all operations
137 | export type WorkflowDiffOperation =
138 |   | AddNodeOperation
139 |   | RemoveNodeOperation
140 |   | UpdateNodeOperation
141 |   | MoveNodeOperation
142 |   | EnableNodeOperation
143 |   | DisableNodeOperation
144 |   | AddConnectionOperation
145 |   | RemoveConnectionOperation
146 |   | RewireConnectionOperation
147 |   | UpdateSettingsOperation
148 |   | UpdateNameOperation
149 |   | AddTagOperation
150 |   | RemoveTagOperation
151 |   | CleanStaleConnectionsOperation
152 |   | ReplaceConnectionsOperation;
153 | 
154 | // Main diff request structure
155 | export interface WorkflowDiffRequest {
156 |   id: string; // Workflow ID
157 |   operations: WorkflowDiffOperation[];
158 |   validateOnly?: boolean; // If true, only validate without applying
159 |   continueOnError?: boolean; // If true, apply valid operations even if some fail (default: false for atomic behavior)
160 | }
161 | 
162 | // Response types
163 | export interface WorkflowDiffValidationError {
164 |   operation: number; // Index of the operation that failed
165 |   message: string;
166 |   details?: any;
167 | }
168 | 
169 | export interface WorkflowDiffResult {
170 |   success: boolean;
171 |   workflow?: any; // Updated workflow if successful
172 |   errors?: WorkflowDiffValidationError[];
173 |   operationsApplied?: number;
174 |   message?: string;
175 |   applied?: number[]; // Indices of successfully applied operations (when continueOnError is true)
176 |   failed?: number[]; // Indices of failed operations (when continueOnError is true)
177 |   staleConnectionsRemoved?: Array<{ from: string; to: string }>; // For cleanStaleConnections operation
178 | }
179 | 
180 | // Helper type for node reference (supports both ID and name)
181 | export interface NodeReference {
182 |   id?: string;
183 |   name?: string;
184 | }
185 | 
186 | // Utility functions type guards
187 | export function isNodeOperation(op: WorkflowDiffOperation): op is 
188 |   AddNodeOperation | RemoveNodeOperation | UpdateNodeOperation | 
189 |   MoveNodeOperation | EnableNodeOperation | DisableNodeOperation {
190 |   return ['addNode', 'removeNode', 'updateNode', 'moveNode', 'enableNode', 'disableNode'].includes(op.type);
191 | }
192 | 
193 | export function isConnectionOperation(op: WorkflowDiffOperation): op is
194 |   AddConnectionOperation | RemoveConnectionOperation | RewireConnectionOperation | CleanStaleConnectionsOperation | ReplaceConnectionsOperation {
195 |   return ['addConnection', 'removeConnection', 'rewireConnection', 'cleanStaleConnections', 'replaceConnections'].includes(op.type);
196 | }
197 | 
198 | export function isMetadataOperation(op: WorkflowDiffOperation): op is 
199 |   UpdateSettingsOperation | UpdateNameOperation | AddTagOperation | RemoveTagOperation {
200 |   return ['updateSettings', 'updateName', 'addTag', 'removeTag'].includes(op.type);
201 | }
```

--------------------------------------------------------------------------------
/src/scripts/test-node-suggestions.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env npx tsx
  2 | /**
  3 |  * Test script for enhanced node type suggestions
  4 |  * Tests the NodeSimilarityService to ensure it provides helpful suggestions
  5 |  * for unknown or incorrectly typed nodes in workflows.
  6 |  */
  7 | 
  8 | import { createDatabaseAdapter } from '../database/database-adapter';
  9 | import { NodeRepository } from '../database/node-repository';
 10 | import { NodeSimilarityService } from '../services/node-similarity-service';
 11 | import { WorkflowValidator } from '../services/workflow-validator';
 12 | import { EnhancedConfigValidator } from '../services/enhanced-config-validator';
 13 | import { WorkflowAutoFixer } from '../services/workflow-auto-fixer';
 14 | import { Logger } from '../utils/logger';
 15 | import path from 'path';
 16 | 
 17 | const logger = new Logger({ prefix: '[NodeSuggestions Test]' });
 18 | const console = {
 19 |   log: (msg: string) => logger.info(msg),
 20 |   error: (msg: string, err?: any) => logger.error(msg, err)
 21 | };
 22 | 
 23 | async function testNodeSimilarity() {
 24 |   console.log('🔍 Testing Enhanced Node Type Suggestions\n');
 25 | 
 26 |   // Initialize database and services
 27 |   const dbPath = path.join(process.cwd(), 'data/nodes.db');
 28 |   const db = await createDatabaseAdapter(dbPath);
 29 |   const repository = new NodeRepository(db);
 30 |   const similarityService = new NodeSimilarityService(repository);
 31 |   const validator = new WorkflowValidator(repository, EnhancedConfigValidator);
 32 | 
 33 |   // Test cases with various invalid node types
 34 |   const testCases = [
 35 |     // Case variations
 36 |     { invalid: 'HttpRequest', expected: 'nodes-base.httpRequest' },
 37 |     { invalid: 'HTTPRequest', expected: 'nodes-base.httpRequest' },
 38 |     { invalid: 'Webhook', expected: 'nodes-base.webhook' },
 39 |     { invalid: 'WebHook', expected: 'nodes-base.webhook' },
 40 | 
 41 |     // Missing package prefix
 42 |     { invalid: 'slack', expected: 'nodes-base.slack' },
 43 |     { invalid: 'googleSheets', expected: 'nodes-base.googleSheets' },
 44 |     { invalid: 'telegram', expected: 'nodes-base.telegram' },
 45 | 
 46 |     // Common typos
 47 |     { invalid: 'htpRequest', expected: 'nodes-base.httpRequest' },
 48 |     { invalid: 'webook', expected: 'nodes-base.webhook' },
 49 |     { invalid: 'slak', expected: 'nodes-base.slack' },
 50 | 
 51 |     // Partial names
 52 |     { invalid: 'http', expected: 'nodes-base.httpRequest' },
 53 |     { invalid: 'sheet', expected: 'nodes-base.googleSheets' },
 54 | 
 55 |     // Wrong package prefix
 56 |     { invalid: 'nodes-base.openai', expected: 'nodes-langchain.openAi' },
 57 |     { invalid: 'n8n-nodes-base.httpRequest', expected: 'nodes-base.httpRequest' },
 58 | 
 59 |     // Complete unknowns
 60 |     { invalid: 'foobar', expected: null },
 61 |     { invalid: 'xyz123', expected: null },
 62 |   ];
 63 | 
 64 |   console.log('Testing individual node type suggestions:');
 65 |   console.log('=' .repeat(60));
 66 | 
 67 |   for (const testCase of testCases) {
 68 |     const suggestions = await similarityService.findSimilarNodes(testCase.invalid, 3);
 69 | 
 70 |     console.log(`\n❌ Invalid type: "${testCase.invalid}"`);
 71 | 
 72 |     if (suggestions.length > 0) {
 73 |       console.log('✨ Suggestions:');
 74 |       for (const suggestion of suggestions) {
 75 |         const confidence = Math.round(suggestion.confidence * 100);
 76 |         const marker = suggestion.nodeType === testCase.expected ? '✅' : '  ';
 77 |         console.log(
 78 |           `${marker} ${suggestion.nodeType} (${confidence}% match) - ${suggestion.reason}`
 79 |         );
 80 | 
 81 |         if (suggestion.confidence >= 0.9) {
 82 |           console.log('   💡 Can be auto-fixed!');
 83 |         }
 84 |       }
 85 | 
 86 |       // Check if expected match was found
 87 |       if (testCase.expected) {
 88 |         const found = suggestions.some(s => s.nodeType === testCase.expected);
 89 |         if (!found) {
 90 |           console.log(`   ⚠️  Expected "${testCase.expected}" was not suggested!`);
 91 |         }
 92 |       }
 93 |     } else {
 94 |       console.log('   No suggestions found');
 95 |       if (testCase.expected) {
 96 |         console.log(`   ⚠️  Expected "${testCase.expected}" was not suggested!`);
 97 |       }
 98 |     }
 99 |   }
100 | 
101 |   console.log('\n' + '='.repeat(60));
102 |   console.log('\n📋 Testing workflow validation with unknown nodes:');
103 |   console.log('='.repeat(60));
104 | 
105 |   // Test with a sample workflow
106 |   const testWorkflow = {
107 |     id: 'test-workflow',
108 |     name: 'Test Workflow',
109 |     nodes: [
110 |       {
111 |         id: '1',
112 |         name: 'Start',
113 |         type: 'nodes-base.manualTrigger',
114 |         position: [100, 100] as [number, number],
115 |         parameters: {},
116 |         typeVersion: 1
117 |       },
118 |       {
119 |         id: '2',
120 |         name: 'HTTP Request',
121 |         type: 'HTTPRequest', // Wrong capitalization
122 |         position: [300, 100] as [number, number],
123 |         parameters: {},
124 |         typeVersion: 1
125 |       },
126 |       {
127 |         id: '3',
128 |         name: 'Slack',
129 |         type: 'slack', // Missing prefix
130 |         position: [500, 100] as [number, number],
131 |         parameters: {},
132 |         typeVersion: 1
133 |       },
134 |       {
135 |         id: '4',
136 |         name: 'Unknown',
137 |         type: 'foobar', // Completely unknown
138 |         position: [700, 100] as [number, number],
139 |         parameters: {},
140 |         typeVersion: 1
141 |       }
142 |     ],
143 |     connections: {
144 |       'Start': {
145 |         main: [[{ node: 'HTTP Request', type: 'main', index: 0 }]]
146 |       },
147 |       'HTTP Request': {
148 |         main: [[{ node: 'Slack', type: 'main', index: 0 }]]
149 |       },
150 |       'Slack': {
151 |         main: [[{ node: 'Unknown', type: 'main', index: 0 }]]
152 |       }
153 |     },
154 |     settings: {}
155 |   };
156 | 
157 |   const validationResult = await validator.validateWorkflow(testWorkflow as any, {
158 |     validateNodes: true,
159 |     validateConnections: false,
160 |     validateExpressions: false,
161 |     profile: 'runtime'
162 |   });
163 | 
164 |   console.log('\nValidation Results:');
165 |   for (const error of validationResult.errors) {
166 |     if (error.message?.includes('Unknown node type:')) {
167 |       console.log(`\n🔴 ${error.nodeName}: ${error.message}`);
168 |     }
169 |   }
170 | 
171 |   console.log('\n' + '='.repeat(60));
172 |   console.log('\n🔧 Testing AutoFixer with node type corrections:');
173 |   console.log('='.repeat(60));
174 | 
175 |   const autoFixer = new WorkflowAutoFixer(repository);
176 |   const fixResult = autoFixer.generateFixes(
177 |     testWorkflow as any,
178 |     validationResult,
179 |     [],
180 |     {
181 |       applyFixes: false,
182 |       fixTypes: ['node-type-correction'],
183 |       confidenceThreshold: 'high'
184 |     }
185 |   );
186 | 
187 |   if (fixResult.fixes.length > 0) {
188 |     console.log('\n✅ Auto-fixable issues found:');
189 |     for (const fix of fixResult.fixes) {
190 |       console.log(`   • ${fix.description}`);
191 |     }
192 |     console.log(`\nSummary: ${fixResult.summary}`);
193 |   } else {
194 |     console.log('\n❌ No auto-fixable node type issues found (only high-confidence fixes are applied)');
195 |   }
196 | 
197 |   console.log('\n' + '='.repeat(60));
198 |   console.log('\n✨ Test complete!');
199 | }
200 | 
201 | // Run the test
202 | testNodeSimilarity().catch(error => {
203 |   console.error('Test failed:', error);
204 |   process.exit(1);
205 | });
```

--------------------------------------------------------------------------------
/tests/utils/test-helpers.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { vi } from 'vitest';
  2 | import { WorkflowNode, Workflow } from '@/types/n8n-api';
  3 | 
  4 | // Use any type for INodeDefinition since it's from n8n-workflow package
  5 | type INodeDefinition = any;
  6 | 
  7 | /**
  8 |  * Common test utilities and helpers
  9 |  */
 10 | 
 11 | /**
 12 |  * Wait for a condition to be true
 13 |  */
 14 | export async function waitFor(
 15 |   condition: () => boolean | Promise<boolean>,
 16 |   options: { timeout?: number; interval?: number } = {}
 17 | ): Promise<void> {
 18 |   const { timeout = 5000, interval = 50 } = options;
 19 |   const startTime = Date.now();
 20 |   
 21 |   while (Date.now() - startTime < timeout) {
 22 |     if (await condition()) {
 23 |       return;
 24 |     }
 25 |     await new Promise(resolve => setTimeout(resolve, interval));
 26 |   }
 27 |   
 28 |   throw new Error(`Timeout waiting for condition after ${timeout}ms`);
 29 | }
 30 | 
 31 | /**
 32 |  * Create a mock node definition with default values
 33 |  */
 34 | export function createMockNodeDefinition(overrides?: Partial<INodeDefinition>): INodeDefinition {
 35 |   return {
 36 |     displayName: 'Mock Node',
 37 |     name: 'mockNode',
 38 |     group: ['transform'],
 39 |     version: 1,
 40 |     description: 'A mock node for testing',
 41 |     defaults: {
 42 |       name: 'Mock Node',
 43 |     },
 44 |     inputs: ['main'],
 45 |     outputs: ['main'],
 46 |     properties: [],
 47 |     ...overrides
 48 |   };
 49 | }
 50 | 
 51 | /**
 52 |  * Create a mock workflow node
 53 |  */
 54 | export function createMockNode(overrides?: Partial<WorkflowNode>): WorkflowNode {
 55 |   return {
 56 |     id: 'mock-node-id',
 57 |     name: 'Mock Node',
 58 |     type: 'n8n-nodes-base.mockNode',
 59 |     typeVersion: 1,
 60 |     position: [0, 0],
 61 |     parameters: {},
 62 |     ...overrides
 63 |   };
 64 | }
 65 | 
 66 | /**
 67 |  * Create a mock workflow
 68 |  */
 69 | export function createMockWorkflow(overrides?: Partial<Workflow>): Workflow {
 70 |   return {
 71 |     id: 'mock-workflow-id',
 72 |     name: 'Mock Workflow',
 73 |     active: false,
 74 |     nodes: [],
 75 |     connections: {},
 76 |     createdAt: new Date().toISOString(),
 77 |     updatedAt: new Date().toISOString(),
 78 |     ...overrides
 79 |   };
 80 | }
 81 | 
 82 | /**
 83 |  * Mock console methods for tests
 84 |  */
 85 | export function mockConsole() {
 86 |   const originalConsole = { ...console };
 87 |   
 88 |   const mocks = {
 89 |     log: vi.spyOn(console, 'log').mockImplementation(() => {}),
 90 |     error: vi.spyOn(console, 'error').mockImplementation(() => {}),
 91 |     warn: vi.spyOn(console, 'warn').mockImplementation(() => {}),
 92 |     debug: vi.spyOn(console, 'debug').mockImplementation(() => {}),
 93 |     info: vi.spyOn(console, 'info').mockImplementation(() => {})
 94 |   };
 95 |   
 96 |   return {
 97 |     mocks,
 98 |     restore: () => {
 99 |       Object.entries(mocks).forEach(([key, mock]) => {
100 |         mock.mockRestore();
101 |       });
102 |     }
103 |   };
104 | }
105 | 
106 | /**
107 |  * Create a deferred promise for testing async operations
108 |  */
109 | export function createDeferred<T>() {
110 |   let resolve: (value: T) => void;
111 |   let reject: (error: any) => void;
112 |   
113 |   const promise = new Promise<T>((res, rej) => {
114 |     resolve = res;
115 |     reject = rej;
116 |   });
117 |   
118 |   return {
119 |     promise,
120 |     resolve: resolve!,
121 |     reject: reject!
122 |   };
123 | }
124 | 
125 | /**
126 |  * Helper to test error throwing
127 |  */
128 | export async function expectToThrowAsync(
129 |   fn: () => Promise<any>,
130 |   errorMatcher?: string | RegExp | Error
131 | ) {
132 |   let thrown = false;
133 |   let error: any;
134 |   
135 |   try {
136 |     await fn();
137 |   } catch (e) {
138 |     thrown = true;
139 |     error = e;
140 |   }
141 |   
142 |   if (!thrown) {
143 |     throw new Error('Expected function to throw');
144 |   }
145 |   
146 |   if (errorMatcher) {
147 |     if (typeof errorMatcher === 'string') {
148 |       expect(error.message).toContain(errorMatcher);
149 |     } else if (errorMatcher instanceof RegExp) {
150 |       expect(error.message).toMatch(errorMatcher);
151 |     } else if (errorMatcher instanceof Error) {
152 |       expect(error).toEqual(errorMatcher);
153 |     }
154 |   }
155 |   
156 |   return error;
157 | }
158 | 
159 | /**
160 |  * Create a test database with initial data
161 |  */
162 | export function createTestDatabase(data: Record<string, any[]> = {}) {
163 |   const db = new Map<string, any[]>();
164 |   
165 |   // Initialize with default tables
166 |   db.set('nodes', data.nodes || []);
167 |   db.set('templates', data.templates || []);
168 |   db.set('tools_documentation', data.tools_documentation || []);
169 |   
170 |   // Add any additional tables from data
171 |   Object.entries(data).forEach(([table, rows]) => {
172 |     if (!db.has(table)) {
173 |       db.set(table, rows);
174 |     }
175 |   });
176 |   
177 |   return {
178 |     prepare: vi.fn((sql: string) => {
179 |       const tableName = extractTableName(sql);
180 |       const rows = db.get(tableName) || [];
181 |       
182 |       return {
183 |         all: vi.fn(() => rows),
184 |         get: vi.fn((params: any) => {
185 |           if (typeof params === 'string') {
186 |             return rows.find((r: any) => r.id === params);
187 |           }
188 |           return rows[0];
189 |         }),
190 |         run: vi.fn((params: any) => {
191 |           rows.push(params);
192 |           return { changes: 1, lastInsertRowid: rows.length };
193 |         })
194 |       };
195 |     }),
196 |     exec: vi.fn(),
197 |     close: vi.fn(),
198 |     transaction: vi.fn((fn: Function) => fn()),
199 |     pragma: vi.fn()
200 |   };
201 | }
202 | 
203 | /**
204 |  * Extract table name from SQL query
205 |  */
206 | function extractTableName(sql: string): string {
207 |   const patterns = [
208 |     /FROM\s+(\w+)/i,
209 |     /INTO\s+(\w+)/i,
210 |     /UPDATE\s+(\w+)/i,
211 |     /TABLE\s+(\w+)/i
212 |   ];
213 |   
214 |   for (const pattern of patterns) {
215 |     const match = sql.match(pattern);
216 |     if (match) {
217 |       return match[1];
218 |     }
219 |   }
220 |   
221 |   return 'nodes';
222 | }
223 | 
224 | /**
225 |  * Create a mock HTTP response
226 |  */
227 | export function createMockResponse(data: any, status = 200) {
228 |   return {
229 |     data,
230 |     status,
231 |     statusText: status === 200 ? 'OK' : 'Error',
232 |     headers: {},
233 |     config: {}
234 |   };
235 | }
236 | 
237 | /**
238 |  * Create a mock HTTP error
239 |  */
240 | export function createMockHttpError(message: string, status = 500, data?: any) {
241 |   const error: any = new Error(message);
242 |   error.isAxiosError = true;
243 |   error.response = {
244 |     data: data || { message },
245 |     status,
246 |     statusText: status === 500 ? 'Internal Server Error' : 'Error',
247 |     headers: {},
248 |     config: {}
249 |   };
250 |   return error;
251 | }
252 | 
253 | /**
254 |  * Helper to test MCP tool calls
255 |  */
256 | export async function testMCPToolCall(
257 |   tool: any,
258 |   args: any,
259 |   expectedResult?: any
260 | ) {
261 |   const result = await tool.handler(args);
262 |   
263 |   if (expectedResult !== undefined) {
264 |     expect(result).toEqual(expectedResult);
265 |   }
266 |   
267 |   return result;
268 | }
269 | 
270 | /**
271 |  * Create a mock MCP context
272 |  */
273 | export function createMockMCPContext() {
274 |   return {
275 |     request: vi.fn(),
276 |     notify: vi.fn(),
277 |     expose: vi.fn(),
278 |     onClose: vi.fn()
279 |   };
280 | }
281 | 
282 | /**
283 |  * Snapshot serializer for dates
284 |  */
285 | export const dateSerializer = {
286 |   test: (value: any) => value instanceof Date,
287 |   serialize: (value: Date) => value.toISOString()
288 | };
289 | 
290 | /**
291 |  * Snapshot serializer for functions
292 |  */
293 | export const functionSerializer = {
294 |   test: (value: any) => typeof value === 'function',
295 |   serialize: () => '[Function]'
296 | };
297 | 
298 | /**
299 |  * Clean up test environment
300 |  */
301 | export function cleanupTestEnvironment() {
302 |   vi.clearAllMocks();
303 |   vi.clearAllTimers();
304 |   vi.useRealTimers();
305 | }
```

--------------------------------------------------------------------------------
/tests/unit/services/node-similarity-service.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
  2 | import { NodeSimilarityService } from '@/services/node-similarity-service';
  3 | import { NodeRepository } from '@/database/node-repository';
  4 | import type { ParsedNode } from '@/parsers/node-parser';
  5 | 
  6 | vi.mock('@/database/node-repository');
  7 | 
  8 | describe('NodeSimilarityService', () => {
  9 |   let service: NodeSimilarityService;
 10 |   let mockRepository: NodeRepository;
 11 | 
 12 |   const createMockNode = (type: string, displayName: string, description = ''): any => ({
 13 |     nodeType: type,
 14 |     displayName,
 15 |     description,
 16 |     version: 1,
 17 |     defaults: {},
 18 |     inputs: ['main'],
 19 |     outputs: ['main'],
 20 |     properties: [],
 21 |     package: 'n8n-nodes-base',
 22 |     typeVersion: 1
 23 |   });
 24 | 
 25 |   beforeEach(() => {
 26 |     vi.clearAllMocks();
 27 |     mockRepository = new NodeRepository({} as any);
 28 |     service = new NodeSimilarityService(mockRepository);
 29 |   });
 30 | 
 31 |   afterEach(() => {
 32 |     vi.restoreAllMocks();
 33 |   });
 34 | 
 35 |   describe('Cache Management', () => {
 36 |     it('should invalidate cache when requested', () => {
 37 |       service.invalidateCache();
 38 |       expect(service['nodeCache']).toBeNull();
 39 |       expect(service['cacheVersion']).toBeGreaterThan(0);
 40 |     });
 41 | 
 42 |     it('should refresh cache with new data', async () => {
 43 |       const nodes = [
 44 |         createMockNode('nodes-base.httpRequest', 'HTTP Request'),
 45 |         createMockNode('nodes-base.webhook', 'Webhook')
 46 |       ];
 47 | 
 48 |       vi.spyOn(mockRepository, 'getAllNodes').mockReturnValue(nodes);
 49 | 
 50 |       await service.refreshCache();
 51 | 
 52 |       expect(service['nodeCache']).toEqual(nodes);
 53 |       expect(mockRepository.getAllNodes).toHaveBeenCalled();
 54 |     });
 55 | 
 56 |     it('should use stale cache on refresh error', async () => {
 57 |       const staleNodes = [createMockNode('nodes-base.slack', 'Slack')];
 58 |       service['nodeCache'] = staleNodes;
 59 |       service['cacheExpiry'] = Date.now() + 1000; // Set cache as not expired
 60 | 
 61 |       vi.spyOn(mockRepository, 'getAllNodes').mockImplementation(() => {
 62 |         throw new Error('Database error');
 63 |       });
 64 | 
 65 |       const nodes = await service['getCachedNodes']();
 66 | 
 67 |       expect(nodes).toEqual(staleNodes);
 68 |     });
 69 | 
 70 |     it('should refresh cache when expired', async () => {
 71 |       service['cacheExpiry'] = Date.now() - 1000; // Cache expired
 72 |       const nodes = [createMockNode('nodes-base.httpRequest', 'HTTP Request')];
 73 | 
 74 |       vi.spyOn(mockRepository, 'getAllNodes').mockReturnValue(nodes);
 75 | 
 76 |       const result = await service['getCachedNodes']();
 77 | 
 78 |       expect(result).toEqual(nodes);
 79 |       expect(mockRepository.getAllNodes).toHaveBeenCalled();
 80 |     });
 81 |   });
 82 | 
 83 |   describe('Edit Distance Optimization', () => {
 84 |     it('should return 0 for identical strings', () => {
 85 |       const distance = service['getEditDistance']('test', 'test');
 86 |       expect(distance).toBe(0);
 87 |     });
 88 | 
 89 |     it('should early terminate for length difference exceeding max', () => {
 90 |       const distance = service['getEditDistance']('a', 'abcdefghijk', 3);
 91 |       expect(distance).toBe(4); // maxDistance + 1
 92 |     });
 93 | 
 94 |     it('should calculate correct edit distance within threshold', () => {
 95 |       const distance = service['getEditDistance']('kitten', 'sitting', 10);
 96 |       expect(distance).toBe(3);
 97 |     });
 98 | 
 99 |     it('should use early termination when min distance exceeds max', () => {
100 |       const distance = service['getEditDistance']('abc', 'xyz', 2);
101 |       expect(distance).toBe(3); // Should terminate early and return maxDistance + 1
102 |     });
103 |   });
104 | 
105 | 
106 |   describe('Node Suggestions', () => {
107 |     beforeEach(() => {
108 |       const nodes = [
109 |         createMockNode('nodes-base.httpRequest', 'HTTP Request', 'Make HTTP requests'),
110 |         createMockNode('nodes-base.webhook', 'Webhook', 'Receive webhooks'),
111 |         createMockNode('nodes-base.slack', 'Slack', 'Send messages to Slack'),
112 |         createMockNode('nodes-langchain.openAi', 'OpenAI', 'Use OpenAI models')
113 |       ];
114 | 
115 |       vi.spyOn(mockRepository, 'getAllNodes').mockReturnValue(nodes);
116 |     });
117 | 
118 |     it('should find similar nodes for exact match', async () => {
119 |       const suggestions = await service.findSimilarNodes('httpRequest', 3);
120 | 
121 |       expect(suggestions).toHaveLength(1);
122 |       expect(suggestions[0].nodeType).toBe('nodes-base.httpRequest');
123 |       expect(suggestions[0].confidence).toBeGreaterThan(0.5); // Adjusted based on actual implementation
124 |     });
125 | 
126 |     it('should find nodes for typo queries', async () => {
127 |       const suggestions = await service.findSimilarNodes('htpRequest', 3);
128 | 
129 |       expect(suggestions.length).toBeGreaterThan(0);
130 |       expect(suggestions[0].nodeType).toBe('nodes-base.httpRequest');
131 |       expect(suggestions[0].confidence).toBeGreaterThan(0.4); // Adjusted based on actual implementation
132 |     });
133 | 
134 |     it('should find nodes for partial matches', async () => {
135 |       const suggestions = await service.findSimilarNodes('slack', 3);
136 | 
137 |       expect(suggestions.length).toBeGreaterThan(0);
138 |       expect(suggestions[0].nodeType).toBe('nodes-base.slack');
139 |     });
140 | 
141 |     it('should return empty array for no matches', async () => {
142 |       const suggestions = await service.findSimilarNodes('nonexistent', 3);
143 | 
144 |       expect(suggestions).toEqual([]);
145 |     });
146 | 
147 |     it('should respect the limit parameter', async () => {
148 |       const suggestions = await service.findSimilarNodes('request', 2);
149 | 
150 |       expect(suggestions.length).toBeLessThanOrEqual(2);
151 |     });
152 | 
153 |     it('should provide appropriate confidence levels', async () => {
154 |       const suggestions = await service.findSimilarNodes('HttpRequest', 3);
155 | 
156 |       if (suggestions.length > 0) {
157 |         expect(suggestions[0].confidence).toBeGreaterThan(0.5);
158 |         expect(suggestions[0].reason).toBeDefined();
159 |       }
160 |     });
161 | 
162 |     it('should handle package prefix normalization', async () => {
163 |       // Add a node with the exact type we're searching for
164 |       const nodes = [
165 |         createMockNode('nodes-base.httpRequest', 'HTTP Request', 'Make HTTP requests')
166 |       ];
167 |       vi.spyOn(mockRepository, 'getAllNodes').mockReturnValue(nodes);
168 | 
169 |       const suggestions = await service.findSimilarNodes('nodes-base.httpRequest', 3);
170 | 
171 |       expect(suggestions.length).toBeGreaterThan(0);
172 |       expect(suggestions[0].nodeType).toBe('nodes-base.httpRequest');
173 |     });
174 |   });
175 | 
176 |   describe('Constants Usage', () => {
177 |     it('should use proper constants for scoring', () => {
178 |       expect(NodeSimilarityService['SCORING_THRESHOLD']).toBe(50);
179 |       expect(NodeSimilarityService['TYPO_EDIT_DISTANCE']).toBe(2);
180 |       expect(NodeSimilarityService['SHORT_SEARCH_LENGTH']).toBe(5);
181 |       expect(NodeSimilarityService['CACHE_DURATION_MS']).toBe(5 * 60 * 1000);
182 |       expect(NodeSimilarityService['AUTO_FIX_CONFIDENCE']).toBe(0.9);
183 |     });
184 |   });
185 | });
```

--------------------------------------------------------------------------------
/tests/integration/database/empty-database.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Integration tests for empty database scenarios
  3 |  * Ensures we detect and handle empty database situations that caused production failures
  4 |  */
  5 | import { describe, it, expect, beforeEach, afterEach } from 'vitest';
  6 | import { createDatabaseAdapter } from '../../../src/database/database-adapter';
  7 | import { NodeRepository } from '../../../src/database/node-repository';
  8 | import * as fs from 'fs';
  9 | import * as path from 'path';
 10 | import * as os from 'os';
 11 | 
 12 | describe('Empty Database Detection Tests', () => {
 13 |   let tempDbPath: string;
 14 |   let db: any;
 15 |   let repository: NodeRepository;
 16 | 
 17 |   beforeEach(async () => {
 18 |     // Create a temporary database file
 19 |     tempDbPath = path.join(os.tmpdir(), `test-empty-${Date.now()}.db`);
 20 |     db = await createDatabaseAdapter(tempDbPath);
 21 | 
 22 |     // Initialize schema
 23 |     const schemaPath = path.join(__dirname, '../../../src/database/schema.sql');
 24 |     const schema = fs.readFileSync(schemaPath, 'utf-8');
 25 |     db.exec(schema);
 26 | 
 27 |     repository = new NodeRepository(db);
 28 |   });
 29 | 
 30 |   afterEach(() => {
 31 |     if (db) {
 32 |       db.close();
 33 |     }
 34 |     // Clean up temp file
 35 |     if (fs.existsSync(tempDbPath)) {
 36 |       fs.unlinkSync(tempDbPath);
 37 |     }
 38 |   });
 39 | 
 40 |   describe('Empty Nodes Table Detection', () => {
 41 |     it('should detect empty nodes table', () => {
 42 |       const count = db.prepare('SELECT COUNT(*) as count FROM nodes').get();
 43 |       expect(count.count).toBe(0);
 44 |     });
 45 | 
 46 |     it('should detect empty FTS5 index', () => {
 47 |       const count = db.prepare('SELECT COUNT(*) as count FROM nodes_fts').get();
 48 |       expect(count.count).toBe(0);
 49 |     });
 50 | 
 51 |     it('should return empty results for critical node searches', () => {
 52 |       const criticalSearches = ['webhook', 'merge', 'split', 'code', 'http'];
 53 | 
 54 |       for (const search of criticalSearches) {
 55 |         const results = db.prepare(`
 56 |           SELECT node_type FROM nodes_fts
 57 |           WHERE nodes_fts MATCH ?
 58 |         `).all(search);
 59 | 
 60 |         expect(results).toHaveLength(0);
 61 |       }
 62 |     });
 63 | 
 64 |     it('should fail validation with empty database', () => {
 65 |       const validation = validateEmptyDatabase(repository);
 66 | 
 67 |       expect(validation.passed).toBe(false);
 68 |       expect(validation.issues.length).toBeGreaterThan(0);
 69 |       expect(validation.issues[0]).toMatch(/CRITICAL.*no nodes found/i);
 70 |     });
 71 |   });
 72 | 
 73 |   describe('LIKE Fallback with Empty Database', () => {
 74 |     it('should return empty results for LIKE searches', () => {
 75 |       const results = db.prepare(`
 76 |         SELECT node_type FROM nodes
 77 |         WHERE node_type LIKE ? OR display_name LIKE ? OR description LIKE ?
 78 |       `).all('%webhook%', '%webhook%', '%webhook%');
 79 | 
 80 |       expect(results).toHaveLength(0);
 81 |     });
 82 | 
 83 |     it('should return empty results for multi-word LIKE searches', () => {
 84 |       const results = db.prepare(`
 85 |         SELECT node_type FROM nodes
 86 |         WHERE (node_type LIKE ? OR display_name LIKE ? OR description LIKE ?)
 87 |         OR (node_type LIKE ? OR display_name LIKE ? OR description LIKE ?)
 88 |       `).all('%split%', '%split%', '%split%', '%batch%', '%batch%', '%batch%');
 89 | 
 90 |       expect(results).toHaveLength(0);
 91 |     });
 92 |   });
 93 | 
 94 |   describe('Repository Methods with Empty Database', () => {
 95 |     it('should return null for getNode() with empty database', () => {
 96 |       const node = repository.getNode('nodes-base.webhook');
 97 |       expect(node).toBeNull();
 98 |     });
 99 | 
100 |     it('should return empty array for searchNodes() with empty database', () => {
101 |       const results = repository.searchNodes('webhook');
102 |       expect(results).toHaveLength(0);
103 |     });
104 | 
105 |     it('should return empty array for getAITools() with empty database', () => {
106 |       const tools = repository.getAITools();
107 |       expect(tools).toHaveLength(0);
108 |     });
109 | 
110 |     it('should return 0 for getNodeCount() with empty database', () => {
111 |       const count = repository.getNodeCount();
112 |       expect(count).toBe(0);
113 |     });
114 |   });
115 | 
116 |   describe('Validation Messages for Empty Database', () => {
117 |     it('should provide clear error message for empty database', () => {
118 |       const validation = validateEmptyDatabase(repository);
119 | 
120 |       const criticalError = validation.issues.find(issue =>
121 |         issue.includes('CRITICAL') && issue.includes('empty')
122 |       );
123 | 
124 |       expect(criticalError).toBeDefined();
125 |       expect(criticalError).toContain('no nodes found');
126 |     });
127 | 
128 |     it('should suggest rebuild command in error message', () => {
129 |       const validation = validateEmptyDatabase(repository);
130 | 
131 |       const errorWithSuggestion = validation.issues.find(issue =>
132 |         issue.toLowerCase().includes('rebuild')
133 |       );
134 | 
135 |       // This expectation documents that we should add rebuild suggestions
136 |       // Currently validation doesn't include this, but it should
137 |       if (!errorWithSuggestion) {
138 |         console.warn('TODO: Add rebuild suggestion to validation error messages');
139 |       }
140 |     });
141 |   });
142 | 
143 |   describe('Empty Template Data', () => {
144 |     it('should detect empty templates table', () => {
145 |       const count = db.prepare('SELECT COUNT(*) as count FROM templates').get();
146 |       expect(count.count).toBe(0);
147 |     });
148 | 
149 |     it('should handle missing template data gracefully', () => {
150 |       const templates = db.prepare('SELECT * FROM templates LIMIT 10').all();
151 |       expect(templates).toHaveLength(0);
152 |     });
153 |   });
154 | });
155 | 
156 | /**
157 |  * Validation function matching rebuild.ts logic
158 |  */
159 | function validateEmptyDatabase(repository: NodeRepository): { passed: boolean; issues: string[] } {
160 |   const issues: string[] = [];
161 | 
162 |   try {
163 |     const db = (repository as any).db;
164 | 
165 |     // Check if database has any nodes
166 |     const nodeCount = db.prepare('SELECT COUNT(*) as count FROM nodes').get() as { count: number };
167 |     if (nodeCount.count === 0) {
168 |       issues.push('CRITICAL: Database is empty - no nodes found! Rebuild failed or was interrupted.');
169 |       return { passed: false, issues };
170 |     }
171 | 
172 |     // Check minimum expected node count
173 |     if (nodeCount.count < 500) {
174 |       issues.push(`WARNING: Only ${nodeCount.count} nodes found - expected at least 500 (both n8n packages)`);
175 |     }
176 | 
177 |     // Check FTS5 table
178 |     const ftsTableCheck = db.prepare(`
179 |       SELECT name FROM sqlite_master
180 |       WHERE type='table' AND name='nodes_fts'
181 |     `).get();
182 | 
183 |     if (!ftsTableCheck) {
184 |       issues.push('CRITICAL: FTS5 table (nodes_fts) does not exist - searches will fail or be very slow');
185 |     } else {
186 |       const ftsCount = db.prepare('SELECT COUNT(*) as count FROM nodes_fts').get() as { count: number };
187 | 
188 |       if (ftsCount.count === 0) {
189 |         issues.push('CRITICAL: FTS5 index is empty - searches will return zero results');
190 |       }
191 |     }
192 |   } catch (error) {
193 |     issues.push(`Validation error: ${(error as Error).message}`);
194 |   }
195 | 
196 |   return {
197 |     passed: issues.length === 0,
198 |     issues
199 |   };
200 | }
201 | 
```

--------------------------------------------------------------------------------
/src/utils/ssrf-protection.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { URL } from 'url';
  2 | import { lookup } from 'dns/promises';
  3 | import { logger } from './logger';
  4 | 
  5 | /**
  6 |  * SSRF Protection Utility with Configurable Security Modes
  7 |  *
  8 |  * Validates URLs to prevent Server-Side Request Forgery attacks including DNS rebinding
  9 |  * See: https://github.com/czlonkowski/n8n-mcp/issues/265 (HIGH-03)
 10 |  *
 11 |  * Security Modes:
 12 |  * - strict (default): Block localhost + private IPs + cloud metadata (production)
 13 |  * - moderate: Allow localhost, block private IPs + cloud metadata (local dev)
 14 |  * - permissive: Allow localhost + private IPs, block cloud metadata (testing only)
 15 |  */
 16 | 
 17 | // Security mode type
 18 | type SecurityMode = 'strict' | 'moderate' | 'permissive';
 19 | 
 20 | // Cloud metadata endpoints (ALWAYS blocked in all modes)
 21 | const CLOUD_METADATA = new Set([
 22 |   // AWS/Azure
 23 |   '169.254.169.254', // AWS/Azure metadata
 24 |   '169.254.170.2',   // AWS ECS metadata
 25 |   // Google Cloud
 26 |   'metadata.google.internal', // GCP metadata
 27 |   'metadata',
 28 |   // Alibaba Cloud
 29 |   '100.100.100.200', // Alibaba Cloud metadata
 30 |   // Oracle Cloud
 31 |   '192.0.0.192',     // Oracle Cloud metadata
 32 | ]);
 33 | 
 34 | // Localhost patterns
 35 | const LOCALHOST_PATTERNS = new Set([
 36 |   'localhost',
 37 |   '127.0.0.1',
 38 |   '::1',
 39 |   '0.0.0.0',
 40 |   'localhost.localdomain',
 41 | ]);
 42 | 
 43 | // Private IP ranges (regex for IPv4)
 44 | const PRIVATE_IP_RANGES = [
 45 |   /^10\./,                          // 10.0.0.0/8
 46 |   /^192\.168\./,                    // 192.168.0.0/16
 47 |   /^172\.(1[6-9]|2[0-9]|3[0-1])\./, // 172.16.0.0/12
 48 |   /^169\.254\./,                    // 169.254.0.0/16 (Link-local)
 49 |   /^127\./,                         // 127.0.0.0/8 (Loopback)
 50 |   /^0\./,                           // 0.0.0.0/8 (Invalid)
 51 | ];
 52 | 
 53 | export class SSRFProtection {
 54 |   /**
 55 |    * Validate webhook URL for SSRF protection with configurable security modes
 56 |    *
 57 |    * @param urlString - URL to validate
 58 |    * @returns Promise with validation result
 59 |    *
 60 |    * @security Uses DNS resolution to prevent DNS rebinding attacks
 61 |    *
 62 |    * @example
 63 |    * // Production (default strict mode)
 64 |    * const result = await SSRFProtection.validateWebhookUrl('http://localhost:5678');
 65 |    * // { valid: false, reason: 'Localhost not allowed' }
 66 |    *
 67 |    * @example
 68 |    * // Local development (moderate mode)
 69 |    * process.env.WEBHOOK_SECURITY_MODE = 'moderate';
 70 |    * const result = await SSRFProtection.validateWebhookUrl('http://localhost:5678');
 71 |    * // { valid: true }
 72 |    */
 73 |   static async validateWebhookUrl(urlString: string): Promise<{
 74 |     valid: boolean;
 75 |     reason?: string
 76 |   }> {
 77 |     try {
 78 |       const url = new URL(urlString);
 79 |       const mode: SecurityMode = (process.env.WEBHOOK_SECURITY_MODE || 'strict') as SecurityMode;
 80 | 
 81 |       // Step 1: Must be HTTP/HTTPS (all modes)
 82 |       if (!['http:', 'https:'].includes(url.protocol)) {
 83 |         return { valid: false, reason: 'Invalid protocol. Only HTTP/HTTPS allowed.' };
 84 |       }
 85 | 
 86 |       // Get hostname and strip IPv6 brackets if present
 87 |       let hostname = url.hostname.toLowerCase();
 88 |       // Remove IPv6 brackets for consistent comparison
 89 |       if (hostname.startsWith('[') && hostname.endsWith(']')) {
 90 |         hostname = hostname.slice(1, -1);
 91 |       }
 92 | 
 93 |       // Step 2: ALWAYS block cloud metadata endpoints (all modes)
 94 |       if (CLOUD_METADATA.has(hostname)) {
 95 |         logger.warn('SSRF blocked: Cloud metadata endpoint', { hostname, mode });
 96 |         return { valid: false, reason: 'Cloud metadata endpoint blocked' };
 97 |       }
 98 | 
 99 |       // Step 3: Resolve DNS to get actual IP address
100 |       // This prevents DNS rebinding attacks where hostname resolves to different IPs
101 |       let resolvedIP: string;
102 |       try {
103 |         const { address } = await lookup(hostname);
104 |         resolvedIP = address;
105 | 
106 |         logger.debug('DNS resolved for SSRF check', { hostname, resolvedIP, mode });
107 |       } catch (error) {
108 |         logger.warn('DNS resolution failed for webhook URL', {
109 |           hostname,
110 |           error: error instanceof Error ? error.message : String(error)
111 |         });
112 |         return { valid: false, reason: 'DNS resolution failed' };
113 |       }
114 | 
115 |       // Step 4: ALWAYS block cloud metadata IPs (all modes)
116 |       if (CLOUD_METADATA.has(resolvedIP)) {
117 |         logger.warn('SSRF blocked: Hostname resolves to cloud metadata IP', {
118 |           hostname,
119 |           resolvedIP,
120 |           mode
121 |         });
122 |         return { valid: false, reason: 'Hostname resolves to cloud metadata endpoint' };
123 |       }
124 | 
125 |       // Step 5: Mode-specific validation
126 | 
127 |       // MODE: permissive - Allow everything except cloud metadata
128 |       if (mode === 'permissive') {
129 |         logger.warn('SSRF protection in permissive mode (localhost and private IPs allowed)', {
130 |           hostname,
131 |           resolvedIP
132 |         });
133 |         return { valid: true };
134 |       }
135 | 
136 |       // Check if target is localhost
137 |       const isLocalhost = LOCALHOST_PATTERNS.has(hostname) ||
138 |                         resolvedIP === '::1' ||
139 |                         resolvedIP.startsWith('127.');
140 | 
141 |       // MODE: strict - Block localhost and private IPs
142 |       if (mode === 'strict' && isLocalhost) {
143 |         logger.warn('SSRF blocked: Localhost not allowed in strict mode', {
144 |           hostname,
145 |           resolvedIP
146 |         });
147 |         return { valid: false, reason: 'Localhost access is blocked in strict mode' };
148 |       }
149 | 
150 |       // MODE: moderate - Allow localhost, block private IPs
151 |       if (mode === 'moderate' && isLocalhost) {
152 |         logger.info('Localhost webhook allowed (moderate mode)', { hostname, resolvedIP });
153 |         return { valid: true };
154 |       }
155 | 
156 |       // Step 6: Check private IPv4 ranges (strict & moderate modes)
157 |       if (PRIVATE_IP_RANGES.some(regex => regex.test(resolvedIP))) {
158 |         logger.warn('SSRF blocked: Private IP address', { hostname, resolvedIP, mode });
159 |         return {
160 |           valid: false,
161 |           reason: mode === 'strict'
162 |             ? 'Private IP addresses not allowed'
163 |             : 'Private IP addresses not allowed (use WEBHOOK_SECURITY_MODE=permissive if needed)'
164 |         };
165 |       }
166 | 
167 |       // Step 7: IPv6 private address check (strict & moderate modes)
168 |       if (resolvedIP === '::1' ||         // Loopback
169 |           resolvedIP === '::' ||          // Unspecified address
170 |           resolvedIP.startsWith('fe80:') || // Link-local
171 |           resolvedIP.startsWith('fc00:') || // Unique local (fc00::/7)
172 |           resolvedIP.startsWith('fd00:') || // Unique local (fd00::/8)
173 |           resolvedIP.startsWith('::ffff:')) { // IPv4-mapped IPv6
174 |         logger.warn('SSRF blocked: IPv6 private address', {
175 |           hostname,
176 |           resolvedIP,
177 |           mode
178 |         });
179 |         return { valid: false, reason: 'IPv6 private address not allowed' };
180 |       }
181 | 
182 |       return { valid: true };
183 |     } catch (error) {
184 |       return { valid: false, reason: 'Invalid URL format' };
185 |     }
186 |   }
187 | }
188 | 
```

--------------------------------------------------------------------------------
/src/mcp/tool-docs/workflow_management/n8n-autofix-workflow.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ToolDocumentation } from '../types';
  2 | 
  3 | export const n8nAutofixWorkflowDoc: ToolDocumentation = {
  4 |   name: 'n8n_autofix_workflow',
  5 |   category: 'workflow_management',
  6 |   essentials: {
  7 |     description: 'Automatically fix common workflow validation errors - expression formats, typeVersions, error outputs, webhook paths',
  8 |     keyParameters: ['id', 'applyFixes'],
  9 |     example: 'n8n_autofix_workflow({id: "wf_abc123", applyFixes: false})',
 10 |     performance: 'Network-dependent (200-1000ms) - fetches, validates, and optionally updates workflow',
 11 |     tips: [
 12 |       'Use applyFixes: false to preview changes before applying',
 13 |       'Set confidenceThreshold to control fix aggressiveness (high/medium/low)',
 14 |       'Supports fixing expression formats, typeVersion issues, error outputs, node type corrections, and webhook paths',
 15 |       'High-confidence fixes (≥90%) are safe for auto-application'
 16 |     ]
 17 |   },
 18 |   full: {
 19 |     description: `Automatically detects and fixes common workflow validation errors in n8n workflows. This tool:
 20 | 
 21 | - Fetches the workflow from your n8n instance
 22 | - Runs comprehensive validation to detect issues
 23 | - Generates targeted fixes for common problems
 24 | - Optionally applies the fixes back to the workflow
 25 | 
 26 | The auto-fixer can resolve:
 27 | 1. **Expression Format Issues**: Missing '=' prefix in n8n expressions (e.g., {{ $json.field }} → ={{ $json.field }})
 28 | 2. **TypeVersion Corrections**: Downgrades nodes with unsupported typeVersions to maximum supported
 29 | 3. **Error Output Configuration**: Removes conflicting onError settings when error connections are missing
 30 | 4. **Node Type Corrections**: Intelligently fixes unknown node types using similarity matching:
 31 |    - Handles deprecated package prefixes (n8n-nodes-base. → nodes-base.)
 32 |    - Corrects capitalization mistakes (HttpRequest → httpRequest)
 33 |    - Suggests correct packages (nodes-base.openai → nodes-langchain.openAi)
 34 |    - Uses multi-factor scoring: name similarity, category match, package match, pattern match
 35 |    - Only auto-fixes suggestions with ≥90% confidence
 36 |    - Leverages NodeSimilarityService with 5-minute caching for performance
 37 | 5. **Webhook Path Generation**: Automatically generates UUIDs for webhook nodes missing path configuration:
 38 |    - Generates a unique UUID for webhook path
 39 |    - Sets both 'path' parameter and 'webhookId' field to the same UUID
 40 |    - Ensures webhook nodes become functional with valid endpoints
 41 |    - High confidence fix as UUID generation is deterministic
 42 | 
 43 | The tool uses a confidence-based system to ensure safe fixes:
 44 | - **High (≥90%)**: Safe to auto-apply (exact matches, known patterns)
 45 | - **Medium (70-89%)**: Generally safe but review recommended
 46 | - **Low (<70%)**: Manual review strongly recommended
 47 | 
 48 | Requires N8N_API_URL and N8N_API_KEY environment variables to be configured.`,
 49 |     parameters: {
 50 |       id: {
 51 |         type: 'string',
 52 |         required: true,
 53 |         description: 'The workflow ID to fix in your n8n instance'
 54 |       },
 55 |       applyFixes: {
 56 |         type: 'boolean',
 57 |         required: false,
 58 |         description: 'Whether to apply fixes to the workflow (default: false - preview mode). When false, returns proposed fixes without modifying the workflow.'
 59 |       },
 60 |       fixTypes: {
 61 |         type: 'array',
 62 |         required: false,
 63 |         description: 'Types of fixes to apply. Options: ["expression-format", "typeversion-correction", "error-output-config", "node-type-correction", "webhook-missing-path"]. Default: all types.'
 64 |       },
 65 |       confidenceThreshold: {
 66 |         type: 'string',
 67 |         required: false,
 68 |         description: 'Minimum confidence level for fixes: "high" (≥90%), "medium" (≥70%), "low" (any). Default: "medium".'
 69 |       },
 70 |       maxFixes: {
 71 |         type: 'number',
 72 |         required: false,
 73 |         description: 'Maximum number of fixes to apply (default: 50). Useful for limiting scope of changes.'
 74 |       }
 75 |     },
 76 |     returns: `AutoFixResult object containing:
 77 | - operations: Array of diff operations that will be/were applied
 78 | - fixes: Detailed list of individual fixes with before/after values
 79 | - summary: Human-readable summary of fixes
 80 | - stats: Statistics by fix type and confidence level
 81 | - applied: Boolean indicating if fixes were applied (when applyFixes: true)`,
 82 |     examples: [
 83 |       'n8n_autofix_workflow({id: "wf_abc123"}) - Preview all possible fixes',
 84 |       'n8n_autofix_workflow({id: "wf_abc123", applyFixes: true}) - Apply all medium+ confidence fixes',
 85 |       'n8n_autofix_workflow({id: "wf_abc123", applyFixes: true, confidenceThreshold: "high"}) - Only apply high-confidence fixes',
 86 |       'n8n_autofix_workflow({id: "wf_abc123", fixTypes: ["expression-format"]}) - Only fix expression format issues',
 87 |       'n8n_autofix_workflow({id: "wf_abc123", fixTypes: ["webhook-missing-path"]}) - Only fix webhook path issues',
 88 |       'n8n_autofix_workflow({id: "wf_abc123", applyFixes: true, maxFixes: 10}) - Apply up to 10 fixes'
 89 |     ],
 90 |     useCases: [
 91 |       'Fixing workflows imported from older n8n versions',
 92 |       'Correcting expression syntax after manual edits',
 93 |       'Resolving typeVersion conflicts after n8n upgrades',
 94 |       'Cleaning up workflows before production deployment',
 95 |       'Batch fixing common issues across multiple workflows',
 96 |       'Migrating workflows between n8n instances with different versions',
 97 |       'Repairing webhook nodes that lost their path configuration'
 98 |     ],
 99 |     performance: 'Depends on workflow size and number of issues. Preview mode: 200-500ms. Apply mode: 500-1000ms for medium workflows. Node similarity matching is cached for 5 minutes for improved performance on repeated validations.',
100 |     bestPractices: [
101 |       'Always preview fixes first (applyFixes: false) before applying',
102 |       'Start with high confidence threshold for production workflows',
103 |       'Review the fix summary to understand what changed',
104 |       'Test workflows after auto-fixing to ensure expected behavior',
105 |       'Use fixTypes parameter to target specific issue categories',
106 |       'Keep maxFixes reasonable to avoid too many changes at once'
107 |     ],
108 |     pitfalls: [
109 |       'Some fixes may change workflow behavior - always test after fixing',
110 |       'Low confidence fixes might not be the intended solution',
111 |       'Expression format fixes assume standard n8n syntax requirements',
112 |       'Node type corrections only work for known node types in the database',
113 |       'Cannot fix structural issues like missing nodes or invalid connections',
114 |       'TypeVersion downgrades might remove node features added in newer versions',
115 |       'Generated webhook paths are new UUIDs - existing webhook URLs will change'
116 |     ],
117 |     relatedTools: [
118 |       'n8n_validate_workflow',
119 |       'validate_workflow',
120 |       'n8n_update_partial_workflow',
121 |       'validate_workflow_expressions',
122 |       'validate_node_operation'
123 |     ]
124 |   }
125 | };
```

--------------------------------------------------------------------------------
/tests/unit/MULTI_TENANT_TEST_COVERAGE.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Multi-Tenant Support Test Coverage Summary
  2 | 
  3 | This document summarizes the comprehensive test suites created for the multi-tenant support implementation in n8n-mcp.
  4 | 
  5 | ## Test Files Created
  6 | 
  7 | ### 1. `tests/unit/mcp/multi-tenant-tool-listing.test.ts`
  8 | **Focus**: MCP Server ListToolsRequestSchema handler multi-tenant logic
  9 | 
 10 | **Coverage Areas**:
 11 | - Environment variable configuration (backward compatibility)
 12 | - Instance context configuration (multi-tenant support)
 13 | - ENABLE_MULTI_TENANT flag support
 14 | - shouldIncludeManagementTools logic truth table
 15 | - Tool availability logic with different configurations
 16 | - Combined configuration scenarios
 17 | - Edge cases and security validation
 18 | - Tool count validation and structure consistency
 19 | 
 20 | **Key Test Scenarios**:
 21 | - ✅ Environment variables only (N8N_API_URL, N8N_API_KEY)
 22 | - ✅ Instance context only (runtime configuration)
 23 | - ✅ Multi-tenant flag only (ENABLE_MULTI_TENANT=true)
 24 | - ✅ No configuration (documentation tools only)
 25 | - ✅ All combinations of the above
 26 | - ✅ Malformed instance context handling
 27 | - ✅ Security logging verification
 28 | 
 29 | ### 2. `tests/unit/types/instance-context-multi-tenant.test.ts`
 30 | **Focus**: Enhanced URL validation in instance-context.ts
 31 | 
 32 | **Coverage Areas**:
 33 | - IPv4 address validation (valid and invalid ranges)
 34 | - IPv6 address validation (various formats)
 35 | - Localhost and development URLs
 36 | - Port validation (1-65535 range)
 37 | - Domain name validation (subdomains, TLDs)
 38 | - Protocol validation (http/https only)
 39 | - Edge cases and malformed URLs
 40 | - Real-world n8n deployment patterns
 41 | - Security and XSS prevention
 42 | - URL encoding handling
 43 | 
 44 | **Key Test Scenarios**:
 45 | - ✅ Valid IPv4: private networks, public IPs, localhost
 46 | - ✅ Invalid IPv4: out-of-range octets, malformed addresses
 47 | - ✅ Valid IPv6: loopback, documentation prefix, full addresses
 48 | - ✅ Valid ports: 1-65535 range, common development ports
 49 | - ✅ Invalid ports: negative, above 65535, non-numeric
 50 | - ✅ Domain patterns: subdomains, enterprise domains, development URLs
 51 | - ✅ Security validation: XSS attempts, file protocols, injection attempts
 52 | - ✅ Real n8n URLs: cloud, tenant, self-hosted patterns
 53 | 
 54 | ### 3. `tests/unit/http-server/multi-tenant-support.test.ts`
 55 | **Focus**: HTTP server multi-tenant functions and session management
 56 | 
 57 | **Coverage Areas**:
 58 | - Header extraction and type safety
 59 | - Instance context creation from headers
 60 | - Session ID generation with configuration hashing
 61 | - Context switching between tenants
 62 | - Security logging with sanitization
 63 | - Session management and cleanup
 64 | - Race condition prevention
 65 | - Memory management
 66 | 
 67 | **Key Test Scenarios**:
 68 | - ✅ Multi-tenant header extraction (x-n8n-url, x-n8n-key, etc.)
 69 | - ✅ Instance context validation from headers
 70 | - ✅ Session isolation between tenants
 71 | - ✅ Configuration-based session ID generation
 72 | - ✅ Header type safety (arrays, non-strings)
 73 | - ✅ Missing/corrupt session data handling
 74 | - ✅ Memory pressure and cleanup strategies
 75 | 
 76 | ### 4. `tests/unit/multi-tenant-integration.test.ts`
 77 | **Focus**: End-to-end integration testing of multi-tenant features
 78 | 
 79 | **Coverage Areas**:
 80 | - Real-world URL patterns and validation
 81 | - Environment variable handling
 82 | - Header processing simulation
 83 | - Configuration priority logic
 84 | - Session management concepts
 85 | - Error scenarios and recovery
 86 | - Security validation across components
 87 | 
 88 | **Key Test Scenarios**:
 89 | - ✅ Complete n8n deployment URL patterns
 90 | - ✅ API key validation (valid/invalid patterns)
 91 | - ✅ Environment flag handling (ENABLE_MULTI_TENANT)
 92 | - ✅ Header processing edge cases
 93 | - ✅ Configuration priority matrix
 94 | - ✅ Session isolation concepts
 95 | - ✅ Comprehensive error handling
 96 | - ✅ Specific validation error messages
 97 | 
 98 | ## Test Coverage Metrics
 99 | 
100 | ### Instance Context Validation
101 | - **Statements**: 83.78% (93/111)
102 | - **Branches**: 81.53% (53/65)
103 | - **Functions**: 100% (4/4)
104 | - **Lines**: 83.78% (93/111)
105 | 
106 | ### Test Quality Metrics
107 | - **Total Test Cases**: 200+ individual test scenarios
108 | - **Error Scenarios Covered**: 50+ edge cases and error conditions
109 | - **Security Tests**: 15+ XSS, injection, and protocol abuse tests
110 | - **Integration Scenarios**: 40+ end-to-end validation tests
111 | 
112 | ## Key Features Tested
113 | 
114 | ### Backward Compatibility
115 | - ✅ Environment variable configuration (N8N_API_URL, N8N_API_KEY)
116 | - ✅ Existing tool listing behavior preserved
117 | - ✅ Graceful degradation when multi-tenant features are disabled
118 | 
119 | ### Multi-Tenant Support
120 | - ✅ Runtime instance context configuration
121 | - ✅ HTTP header-based tenant identification
122 | - ✅ Session isolation between tenants
123 | - ✅ Dynamic tool registration based on context
124 | 
125 | ### Security
126 | - ✅ URL validation against XSS and injection attempts
127 | - ✅ API key validation with placeholder detection
128 | - ✅ Sensitive data sanitization in logs
129 | - ✅ Protocol restriction (http/https only)
130 | 
131 | ### Error Handling
132 | - ✅ Graceful handling of malformed configurations
133 | - ✅ Specific error messages for debugging
134 | - ✅ Non-throwing validation functions
135 | - ✅ Recovery from invalid session data
136 | 
137 | ## Test Patterns Used
138 | 
139 | ### Arrange-Act-Assert
140 | All tests follow the clear AAA pattern for maintainability and readability.
141 | 
142 | ### Comprehensive Mocking
143 | - Logger mocking for isolation
144 | - Environment variable mocking for clean state
145 | - Dependency injection for testability
146 | 
147 | ### Data-Driven Testing
148 | - Parameterized tests for URL patterns
149 | - Truth table testing for configuration logic
150 | - Matrix testing for scenario combinations
151 | 
152 | ### Edge Case Coverage
153 | - Boundary value testing (ports, IP ranges)
154 | - Invalid input testing (malformed URLs, empty strings)
155 | - Security testing (XSS, injection attempts)
156 | 
157 | ## Running the Tests
158 | 
159 | ```bash
160 | # Run all multi-tenant tests
161 | npm test tests/unit/mcp/multi-tenant-tool-listing.test.ts
162 | npm test tests/unit/types/instance-context-multi-tenant.test.ts
163 | npm test tests/unit/http-server/multi-tenant-support.test.ts
164 | npm test tests/unit/multi-tenant-integration.test.ts
165 | 
166 | # Run with coverage
167 | npm run test:coverage
168 | 
169 | # Run specific test patterns
170 | npm test -- --grep "multi-tenant"
171 | ```
172 | 
173 | ## Test Maintenance Notes
174 | 
175 | ### Mock Updates
176 | When updating the logger or other core utilities, ensure mocks are updated accordingly.
177 | 
178 | ### Environment Variables
179 | Tests properly isolate environment variables to prevent cross-test pollution.
180 | 
181 | ### Real-World Patterns
182 | URL validation tests are based on actual n8n deployment patterns and should be updated as new deployment methods are supported.
183 | 
184 | ### Security Tests
185 | Security-focused tests should be regularly reviewed and updated as new attack vectors are discovered.
186 | 
187 | ## Future Test Enhancements
188 | 
189 | ### Performance Testing
190 | - Session management under load
191 | - Memory usage during high tenant count
192 | - Configuration validation performance
193 | 
194 | ### End-to-End Testing
195 | - Full HTTP request/response cycles
196 | - Multi-tenant workflow execution
197 | - Session persistence across requests
198 | 
199 | ### Integration Testing
200 | - Database adapter integration with multi-tenant contexts
201 | - MCP protocol compliance with dynamic tool sets
202 | - Error propagation across component boundaries
```

--------------------------------------------------------------------------------
/src/services/confidence-scorer.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Confidence Scorer for node-specific validations
  3 |  *
  4 |  * Provides confidence scores for node-specific recommendations,
  5 |  * allowing users to understand the reliability of suggestions.
  6 |  */
  7 | 
  8 | export interface ConfidenceScore {
  9 |   value: number; // 0.0 to 1.0
 10 |   reason: string;
 11 |   factors: ConfidenceFactor[];
 12 | }
 13 | 
 14 | export interface ConfidenceFactor {
 15 |   name: string;
 16 |   weight: number;
 17 |   matched: boolean;
 18 |   description: string;
 19 | }
 20 | 
 21 | export class ConfidenceScorer {
 22 |   /**
 23 |    * Calculate confidence score for resource locator recommendation
 24 |    */
 25 |   static scoreResourceLocatorRecommendation(
 26 |     fieldName: string,
 27 |     nodeType: string,
 28 |     value: string
 29 |   ): ConfidenceScore {
 30 |     const factors: ConfidenceFactor[] = [];
 31 |     let totalWeight = 0;
 32 |     let matchedWeight = 0;
 33 | 
 34 |     // Factor 1: Exact field name match (highest confidence)
 35 |     const exactFieldMatch = this.checkExactFieldMatch(fieldName, nodeType);
 36 |     factors.push({
 37 |       name: 'exact-field-match',
 38 |       weight: 0.5,
 39 |       matched: exactFieldMatch,
 40 |       description: `Field name '${fieldName}' is known to use resource locator in ${nodeType}`
 41 |     });
 42 | 
 43 |     // Factor 2: Field name pattern (medium confidence)
 44 |     const patternMatch = this.checkFieldPattern(fieldName);
 45 |     factors.push({
 46 |       name: 'field-pattern',
 47 |       weight: 0.3,
 48 |       matched: patternMatch,
 49 |       description: `Field name '${fieldName}' matches common resource locator patterns`
 50 |     });
 51 | 
 52 |     // Factor 3: Value pattern (low confidence)
 53 |     const valuePattern = this.checkValuePattern(value);
 54 |     factors.push({
 55 |       name: 'value-pattern',
 56 |       weight: 0.1,
 57 |       matched: valuePattern,
 58 |       description: 'Value contains patterns typical of resource identifiers'
 59 |     });
 60 | 
 61 |     // Factor 4: Node type category (medium confidence)
 62 |     const nodeCategory = this.checkNodeCategory(nodeType);
 63 |     factors.push({
 64 |       name: 'node-category',
 65 |       weight: 0.1,
 66 |       matched: nodeCategory,
 67 |       description: `Node type '${nodeType}' typically uses resource locators`
 68 |     });
 69 | 
 70 |     // Calculate final score
 71 |     for (const factor of factors) {
 72 |       totalWeight += factor.weight;
 73 |       if (factor.matched) {
 74 |         matchedWeight += factor.weight;
 75 |       }
 76 |     }
 77 | 
 78 |     const score = totalWeight > 0 ? matchedWeight / totalWeight : 0;
 79 | 
 80 |     // Determine reason based on score
 81 |     let reason: string;
 82 |     if (score >= 0.8) {
 83 |       reason = 'High confidence: Multiple strong indicators suggest resource locator format';
 84 |     } else if (score >= 0.5) {
 85 |       reason = 'Medium confidence: Some indicators suggest resource locator format';
 86 |     } else if (score >= 0.3) {
 87 |       reason = 'Low confidence: Weak indicators for resource locator format';
 88 |     } else {
 89 |       reason = 'Very low confidence: Minimal evidence for resource locator format';
 90 |     }
 91 | 
 92 |     return {
 93 |       value: score,
 94 |       reason,
 95 |       factors
 96 |     };
 97 |   }
 98 | 
 99 |   /**
100 |    * Known field mappings with exact matches
101 |    */
102 |   private static readonly EXACT_FIELD_MAPPINGS: Record<string, string[]> = {
103 |     'github': ['owner', 'repository', 'user', 'organization'],
104 |     'googlesheets': ['sheetId', 'documentId', 'spreadsheetId'],
105 |     'googledrive': ['fileId', 'folderId', 'driveId'],
106 |     'slack': ['channel', 'user', 'channelId', 'userId'],
107 |     'notion': ['databaseId', 'pageId', 'blockId'],
108 |     'airtable': ['baseId', 'tableId', 'viewId']
109 |   };
110 | 
111 |   private static checkExactFieldMatch(fieldName: string, nodeType: string): boolean {
112 |     const nodeBase = nodeType.split('.').pop()?.toLowerCase() || '';
113 | 
114 |     for (const [pattern, fields] of Object.entries(this.EXACT_FIELD_MAPPINGS)) {
115 |       if (nodeBase === pattern || nodeBase.startsWith(`${pattern}-`)) {
116 |         return fields.includes(fieldName);
117 |       }
118 |     }
119 | 
120 |     return false;
121 |   }
122 | 
123 |   /**
124 |    * Common patterns in field names that suggest resource locators
125 |    */
126 |   private static readonly FIELD_PATTERNS = [
127 |     /^.*Id$/i,           // ends with Id
128 |     /^.*Ids$/i,          // ends with Ids
129 |     /^.*Key$/i,          // ends with Key
130 |     /^.*Name$/i,         // ends with Name
131 |     /^.*Path$/i,         // ends with Path
132 |     /^.*Url$/i,          // ends with Url
133 |     /^.*Uri$/i,          // ends with Uri
134 |     /^(table|database|collection|bucket|folder|file|document|sheet|board|project|issue|user|channel|team|organization|repository|owner)$/i
135 |   ];
136 | 
137 |   private static checkFieldPattern(fieldName: string): boolean {
138 |     return this.FIELD_PATTERNS.some(pattern => pattern.test(fieldName));
139 |   }
140 | 
141 |   /**
142 |    * Check if the value looks like it contains identifiers
143 |    */
144 |   private static checkValuePattern(value: string): boolean {
145 |     // Remove = prefix if present for analysis
146 |     const content = value.startsWith('=') ? value.substring(1) : value;
147 | 
148 |     // Skip if not an expression
149 |     if (!content.includes('{{') || !content.includes('}}')) {
150 |       return false;
151 |     }
152 | 
153 |     // Check for patterns that suggest IDs or resource references
154 |     const patterns = [
155 |       /\{\{.*\.(id|Id|ID|key|Key|name|Name|path|Path|url|Url|uri|Uri).*\}\}/i,
156 |       /\{\{.*_(id|Id|ID|key|Key|name|Name|path|Path|url|Url|uri|Uri).*\}\}/i,
157 |       /\{\{.*(id|Id|ID|key|Key|name|Name|path|Path|url|Url|uri|Uri).*\}\}/i
158 |     ];
159 | 
160 |     return patterns.some(pattern => pattern.test(content));
161 |   }
162 | 
163 |   /**
164 |    * Node categories that commonly use resource locators
165 |    */
166 |   private static readonly RESOURCE_HEAVY_NODES = [
167 |     'github', 'gitlab', 'bitbucket',           // Version control
168 |     'googlesheets', 'googledrive', 'dropbox',  // Cloud storage
169 |     'slack', 'discord', 'telegram',            // Communication
170 |     'notion', 'airtable', 'baserow',          // Databases
171 |     'jira', 'asana', 'trello', 'monday',      // Project management
172 |     'salesforce', 'hubspot', 'pipedrive',     // CRM
173 |     'stripe', 'paypal', 'square',             // Payment
174 |     'aws', 'gcp', 'azure',                    // Cloud providers
175 |     'mysql', 'postgres', 'mongodb', 'redis'   // Databases
176 |   ];
177 | 
178 |   private static checkNodeCategory(nodeType: string): boolean {
179 |     const nodeBase = nodeType.split('.').pop()?.toLowerCase() || '';
180 | 
181 |     return this.RESOURCE_HEAVY_NODES.some(category =>
182 |       nodeBase.includes(category)
183 |     );
184 |   }
185 | 
186 |   /**
187 |    * Get confidence level as a string
188 |    */
189 |   static getConfidenceLevel(score: number): 'high' | 'medium' | 'low' | 'very-low' {
190 |     if (score >= 0.8) return 'high';
191 |     if (score >= 0.5) return 'medium';
192 |     if (score >= 0.3) return 'low';
193 |     return 'very-low';
194 |   }
195 | 
196 |   /**
197 |    * Should apply recommendation based on confidence and threshold
198 |    */
199 |   static shouldApplyRecommendation(
200 |     score: number,
201 |     threshold: 'strict' | 'normal' | 'relaxed' = 'normal'
202 |   ): boolean {
203 |     const thresholds = {
204 |       strict: 0.8,   // Only apply high confidence recommendations
205 |       normal: 0.5,   // Apply medium and high confidence
206 |       relaxed: 0.3   // Apply low, medium, and high confidence
207 |     };
208 | 
209 |     return score >= thresholds[threshold];
210 |   }
211 | }
```

--------------------------------------------------------------------------------
/tests/test-mcp-tools-integration.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * End-to-end test for MCP server tools integration
  5 |  * Tests both get_node_source_code and list_available_nodes tools
  6 |  */
  7 | 
  8 | const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
  9 | const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
 10 | const { N8NMCPServer } = require('../dist/mcp/server');
 11 | 
 12 | // Test configuration
 13 | const TEST_CONFIG = {
 14 |   mcp: {
 15 |     port: 3000,
 16 |     host: '0.0.0.0',
 17 |     authToken: 'test-token'
 18 |   },
 19 |   n8n: {
 20 |     apiUrl: 'http://localhost:5678',
 21 |     apiKey: 'test-key'
 22 |   }
 23 | };
 24 | 
 25 | // Mock tool calls
 26 | const TEST_REQUESTS = [
 27 |   {
 28 |     name: 'list_available_nodes',
 29 |     description: 'List all available n8n nodes',
 30 |     request: {
 31 |       name: 'list_available_nodes',
 32 |       arguments: {}
 33 |     }
 34 |   },
 35 |   {
 36 |     name: 'list_ai_nodes',
 37 |     description: 'List AI/LangChain nodes',
 38 |     request: {
 39 |       name: 'list_available_nodes',
 40 |       arguments: {
 41 |         category: 'ai'
 42 |       }
 43 |     }
 44 |   },
 45 |   {
 46 |     name: 'get_function_node',
 47 |     description: 'Extract Function node source',
 48 |     request: {
 49 |       name: 'get_node_source_code',
 50 |       arguments: {
 51 |         nodeType: 'n8n-nodes-base.Function',
 52 |         includeCredentials: true
 53 |       }
 54 |     }
 55 |   },
 56 |   {
 57 |     name: 'get_ai_agent_node',
 58 |     description: 'Extract AI Agent node source',
 59 |     request: {
 60 |       name: 'get_node_source_code',
 61 |       arguments: {
 62 |         nodeType: '@n8n/n8n-nodes-langchain.Agent',
 63 |         includeCredentials: true
 64 |       }
 65 |     }
 66 |   },
 67 |   {
 68 |     name: 'get_webhook_node',
 69 |     description: 'Extract Webhook node source',
 70 |     request: {
 71 |       name: 'get_node_source_code',
 72 |       arguments: {
 73 |         nodeType: 'n8n-nodes-base.Webhook',
 74 |         includeCredentials: false
 75 |       }
 76 |     }
 77 |   }
 78 | ];
 79 | 
 80 | async function simulateToolCall(server, toolRequest) {
 81 |   console.log(`\n📋 Testing: ${toolRequest.description}`);
 82 |   console.log(`   Tool: ${toolRequest.request.name}`);
 83 |   console.log(`   Args:`, JSON.stringify(toolRequest.request.arguments, null, 2));
 84 |   
 85 |   try {
 86 |     const startTime = Date.now();
 87 |     
 88 |     // Directly call the tool handler
 89 |     const handler = server.toolHandlers[toolRequest.request.name];
 90 |     if (!handler) {
 91 |       throw new Error(`Tool handler not found: ${toolRequest.request.name}`);
 92 |     }
 93 |     
 94 |     const result = await handler(toolRequest.request.arguments);
 95 |     const elapsed = Date.now() - startTime;
 96 |     
 97 |     console.log(`   ✅ Success (${elapsed}ms)`);
 98 |     
 99 |     // Analyze results based on tool type
100 |     if (toolRequest.request.name === 'list_available_nodes') {
101 |       console.log(`   📊 Found ${result.nodes.length} nodes`);
102 |       if (result.nodes.length > 0) {
103 |         console.log(`   Sample nodes:`);
104 |         result.nodes.slice(0, 3).forEach(node => {
105 |           console.log(`     - ${node.name} (${node.packageName || 'unknown'})`);
106 |         });
107 |       }
108 |     } else if (toolRequest.request.name === 'get_node_source_code') {
109 |       console.log(`   📦 Node: ${result.nodeType}`);
110 |       console.log(`   📏 Code size: ${result.sourceCode.length} bytes`);
111 |       console.log(`   📍 Location: ${result.location}`);
112 |       console.log(`   🔐 Has credentials: ${!!result.credentialCode}`);
113 |       console.log(`   📄 Has package info: ${!!result.packageInfo}`);
114 |       
115 |       if (result.packageInfo) {
116 |         console.log(`   📦 Package: ${result.packageInfo.name} v${result.packageInfo.version}`);
117 |       }
118 |     }
119 |     
120 |     return { success: true, result, elapsed };
121 |   } catch (error) {
122 |     console.log(`   ❌ Failed: ${error.message}`);
123 |     return { success: false, error: error.message };
124 |   }
125 | }
126 | 
127 | async function main() {
128 |   console.log('=== MCP Server Tools Integration Test ===\n');
129 |   
130 |   // Create MCP server instance
131 |   console.log('🚀 Initializing MCP server...');
132 |   const server = new N8NMCPServer(TEST_CONFIG.mcp, TEST_CONFIG.n8n);
133 |   
134 |   // Store tool handlers for direct access
135 |   server.toolHandlers = {};
136 |   
137 |   // Override handler setup to capture handlers
138 |   const originalSetup = server.setupHandlers.bind(server);
139 |   server.setupHandlers = function() {
140 |     originalSetup();
141 |     
142 |     // Capture tool call handler
143 |     const originalHandler = this.server.setRequestHandler;
144 |     this.server.setRequestHandler = function(schema, handler) {
145 |       if (schema.parse && schema.parse({method: 'tools/call'}).method === 'tools/call') {
146 |         // This is the tool call handler
147 |         const toolCallHandler = handler;
148 |         server.handleToolCall = async (args) => {
149 |           const response = await toolCallHandler({ method: 'tools/call', params: args });
150 |           return response.content[0];
151 |         };
152 |       }
153 |       return originalHandler.call(this, schema, handler);
154 |     };
155 |   };
156 |   
157 |   // Re-setup handlers
158 |   server.setupHandlers();
159 |   
160 |   // Extract individual tool handlers
161 |   server.toolHandlers = {
162 |     list_available_nodes: async (args) => server.listAvailableNodes(args),
163 |     get_node_source_code: async (args) => server.getNodeSourceCode(args)
164 |   };
165 |   
166 |   console.log('✅ MCP server initialized\n');
167 |   
168 |   // Test statistics
169 |   const stats = {
170 |     total: 0,
171 |     passed: 0,
172 |     failed: 0,
173 |     results: []
174 |   };
175 |   
176 |   // Run all test requests
177 |   for (const testRequest of TEST_REQUESTS) {
178 |     stats.total++;
179 |     const result = await simulateToolCall(server, testRequest);
180 |     stats.results.push({
181 |       name: testRequest.name,
182 |       ...result
183 |     });
184 |     
185 |     if (result.success) {
186 |       stats.passed++;
187 |     } else {
188 |       stats.failed++;
189 |     }
190 |   }
191 |   
192 |   // Summary
193 |   console.log('\n' + '='.repeat(60));
194 |   console.log('TEST SUMMARY');
195 |   console.log('='.repeat(60));
196 |   console.log(`Total tests: ${stats.total}`);
197 |   console.log(`Passed: ${stats.passed} ✅`);
198 |   console.log(`Failed: ${stats.failed} ❌`);
199 |   console.log(`Success rate: ${((stats.passed / stats.total) * 100).toFixed(1)}%`);
200 |   
201 |   // Detailed results
202 |   console.log('\nDetailed Results:');
203 |   stats.results.forEach(result => {
204 |     const status = result.success ? '✅' : '❌';
205 |     const time = result.elapsed ? ` (${result.elapsed}ms)` : '';
206 |     console.log(`  ${status} ${result.name}${time}`);
207 |     if (!result.success) {
208 |       console.log(`     Error: ${result.error}`);
209 |     }
210 |   });
211 |   
212 |   console.log('\n✨ MCP tools integration test completed!');
213 |   
214 |   // Test database storage capability
215 |   console.log('\n📊 Database Storage Capability:');
216 |   const sampleExtraction = stats.results.find(r => r.success && r.result && r.result.sourceCode);
217 |   if (sampleExtraction) {
218 |     console.log('✅ Node extraction produces database-ready structure');
219 |     console.log('✅ Includes source code, hash, location, and metadata');
220 |     console.log('✅ Ready for bulk extraction and storage');
221 |   } else {
222 |     console.log('⚠️  No successful extraction to verify database structure');
223 |   }
224 |   
225 |   process.exit(stats.failed > 0 ? 1 : 0);
226 | }
227 | 
228 | // Handle errors
229 | process.on('unhandledRejection', (error) => {
230 |   console.error('\n💥 Unhandled error:', error);
231 |   process.exit(1);
232 | });
233 | 
234 | // Run the test
235 | main();
```

--------------------------------------------------------------------------------
/docker/docker-entrypoint.sh:
--------------------------------------------------------------------------------

```bash
  1 | #!/bin/sh
  2 | set -e
  3 | 
  4 | # Load configuration from JSON file if it exists
  5 | if [ -f "/app/config.json" ] && [ -f "/app/docker/parse-config.js" ]; then
  6 |     # Use Node.js to generate shell-safe export commands
  7 |     eval $(node /app/docker/parse-config.js /app/config.json)
  8 | fi
  9 | 
 10 | # Helper function for safe logging (prevents stdio mode corruption)
 11 | log_message() {
 12 |     [ "$MCP_MODE" != "stdio" ] && echo "$@"
 13 | }
 14 | 
 15 | # Environment variable validation
 16 | if [ "$MCP_MODE" = "http" ] && [ -z "$AUTH_TOKEN" ] && [ -z "$AUTH_TOKEN_FILE" ]; then
 17 |     log_message "ERROR: AUTH_TOKEN or AUTH_TOKEN_FILE is required for HTTP mode" >&2
 18 |     exit 1
 19 | fi
 20 | 
 21 | # Validate AUTH_TOKEN_FILE if provided
 22 | if [ -n "$AUTH_TOKEN_FILE" ] && [ ! -f "$AUTH_TOKEN_FILE" ]; then
 23 |     log_message "ERROR: AUTH_TOKEN_FILE specified but file not found: $AUTH_TOKEN_FILE" >&2
 24 |     exit 1
 25 | fi
 26 | 
 27 | # Database path configuration - respect NODE_DB_PATH if set
 28 | if [ -n "$NODE_DB_PATH" ]; then
 29 |     # Basic validation - must end with .db
 30 |     case "$NODE_DB_PATH" in
 31 |         *.db) ;;
 32 |         *) log_message "ERROR: NODE_DB_PATH must end with .db" >&2; exit 1 ;;
 33 |     esac
 34 |     
 35 |     # Use the path as-is (Docker paths should be absolute anyway)
 36 |     DB_PATH="$NODE_DB_PATH"
 37 | else
 38 |     DB_PATH="/app/data/nodes.db"
 39 | fi
 40 | 
 41 | DB_DIR=$(dirname "$DB_PATH")
 42 | 
 43 | # Ensure database directory exists with correct ownership
 44 | if [ ! -d "$DB_DIR" ]; then
 45 |     log_message "Creating database directory: $DB_DIR"
 46 |     if [ "$(id -u)" = "0" ]; then
 47 |         # Create as root but immediately fix ownership
 48 |         mkdir -p "$DB_DIR" && chown nodejs:nodejs "$DB_DIR"
 49 |     else
 50 |         mkdir -p "$DB_DIR"
 51 |     fi
 52 | fi
 53 | 
 54 | # Database initialization with file locking to prevent race conditions
 55 | if [ ! -f "$DB_PATH" ]; then
 56 |     log_message "Database not found at $DB_PATH. Initializing..."
 57 |     
 58 |     # Ensure lock directory exists before attempting to create lock
 59 |     mkdir -p "$DB_DIR"
 60 |     
 61 |     # Check if flock is available
 62 |     if command -v flock >/dev/null 2>&1; then
 63 |         # Use a lock file to prevent multiple containers from initializing simultaneously
 64 |         # Try to create lock file, handle permission errors gracefully
 65 |         LOCK_FILE="$DB_DIR/.db.lock"
 66 |         
 67 |         # Ensure we can create the lock file - fix permissions if running as root
 68 |         if [ "$(id -u)" = "0" ] && [ ! -w "$DB_DIR" ]; then
 69 |             chown nodejs:nodejs "$DB_DIR" 2>/dev/null || true
 70 |             chmod 755 "$DB_DIR" 2>/dev/null || true
 71 |         fi
 72 |         
 73 |         # Try to create lock file with proper error handling
 74 |         if touch "$LOCK_FILE" 2>/dev/null; then
 75 |             (
 76 |                 flock -x 200
 77 |                 # Double-check inside the lock
 78 |                 if [ ! -f "$DB_PATH" ]; then
 79 |                     log_message "Initializing database at $DB_PATH..."
 80 |                     cd /app && NODE_DB_PATH="$DB_PATH" node dist/scripts/rebuild.js || {
 81 |                         log_message "ERROR: Database initialization failed" >&2
 82 |                         exit 1
 83 |                     }
 84 |                 fi
 85 |             ) 200>"$LOCK_FILE"
 86 |         else
 87 |             log_message "WARNING: Cannot create lock file at $LOCK_FILE, proceeding without file locking"
 88 |             # Fallback without locking if we can't create the lock file
 89 |             if [ ! -f "$DB_PATH" ]; then
 90 |                 log_message "Initializing database at $DB_PATH..."
 91 |                 cd /app && NODE_DB_PATH="$DB_PATH" node dist/scripts/rebuild.js || {
 92 |                     log_message "ERROR: Database initialization failed" >&2
 93 |                     exit 1
 94 |                 }
 95 |             fi
 96 |         fi
 97 |     else
 98 |         # Fallback without locking (log warning)
 99 |         log_message "WARNING: flock not available, database initialization may have race conditions"
100 |         if [ ! -f "$DB_PATH" ]; then
101 |             log_message "Initializing database at $DB_PATH..."
102 |             cd /app && NODE_DB_PATH="$DB_PATH" node dist/scripts/rebuild.js || {
103 |                 log_message "ERROR: Database initialization failed" >&2
104 |                 exit 1
105 |             }
106 |         fi
107 |     fi
108 | fi
109 | 
110 | # Fix permissions if running as root (for development)
111 | if [ "$(id -u)" = "0" ]; then
112 |     log_message "Running as root, fixing permissions..."
113 |     chown -R nodejs:nodejs "$DB_DIR"
114 |     # Also ensure /app/data exists for backward compatibility
115 |     if [ -d "/app/data" ]; then
116 |         chown -R nodejs:nodejs /app/data
117 |     fi
118 |     # Switch to nodejs user with proper exec chain for signal propagation
119 |     # Build the command to execute
120 |     if [ $# -eq 0 ]; then
121 |         # No arguments provided, use default CMD from Dockerfile
122 |         set -- node /app/dist/mcp/index.js
123 |     fi
124 |     # Export all needed environment variables
125 |     export MCP_MODE="$MCP_MODE"
126 |     export NODE_DB_PATH="$NODE_DB_PATH"
127 |     export AUTH_TOKEN="$AUTH_TOKEN"
128 |     export AUTH_TOKEN_FILE="$AUTH_TOKEN_FILE"
129 |     
130 |     # Ensure AUTH_TOKEN_FILE has restricted permissions for security
131 |     if [ -n "$AUTH_TOKEN_FILE" ] && [ -f "$AUTH_TOKEN_FILE" ]; then
132 |         chmod 600 "$AUTH_TOKEN_FILE" 2>/dev/null || true
133 |         chown nodejs:nodejs "$AUTH_TOKEN_FILE" 2>/dev/null || true
134 |     fi
135 |     # Use exec with su-exec for proper signal handling (Alpine Linux)
136 |     # su-exec advantages:
137 |     # - Proper signal forwarding (critical for container shutdown)
138 |     # - No intermediate shell process
139 |     # - Designed for privilege dropping in containers
140 |     if command -v su-exec >/dev/null 2>&1; then
141 |         exec su-exec nodejs "$@"
142 |     else
143 |         # Fallback to su with preserved environment
144 |         # Use safer approach to prevent command injection
145 |         exec su -p nodejs -s /bin/sh -c 'exec "$0" "$@"' -- sh -c 'exec "$@"' -- "$@"
146 |     fi
147 | fi
148 | 
149 | # Handle special commands
150 | if [ "$1" = "n8n-mcp" ] && [ "$2" = "serve" ]; then
151 |     # Set HTTP mode for "n8n-mcp serve" command
152 |     export MCP_MODE="http"
153 |     shift 2  # Remove "n8n-mcp serve" from arguments
154 |     set -- node /app/dist/mcp/index.js "$@"
155 | fi
156 | 
157 | # Export NODE_DB_PATH so it's visible to child processes
158 | if [ -n "$DB_PATH" ]; then
159 |     export NODE_DB_PATH="$DB_PATH"
160 | fi
161 | 
162 | # Execute the main command directly with exec
163 | # This ensures our Node.js process becomes PID 1 and receives signals directly
164 | if [ "$MCP_MODE" = "stdio" ]; then
165 |     # Debug: Log to stderr to check if wrapper exists
166 |     if [ "$DEBUG_DOCKER" = "true" ]; then
167 |         echo "MCP_MODE is stdio, checking for wrapper..." >&2
168 |         ls -la /app/dist/mcp/stdio-wrapper.js >&2 || echo "Wrapper not found!" >&2
169 |     fi
170 |     
171 |     if [ -f "/app/dist/mcp/stdio-wrapper.js" ]; then
172 |         # Use the stdio wrapper for clean JSON-RPC output
173 |         # exec replaces the shell with node process as PID 1
174 |         exec node /app/dist/mcp/stdio-wrapper.js
175 |     else
176 |         # Fallback: run with explicit environment
177 |         exec env MCP_MODE=stdio DISABLE_CONSOLE_OUTPUT=true LOG_LEVEL=error node /app/dist/mcp/index.js
178 |     fi
179 | else
180 |     # HTTP mode or other
181 |     if [ $# -eq 0 ]; then
182 |         # No arguments provided, use default
183 |         exec node /app/dist/mcp/index.js
184 |     else
185 |         exec "$@"
186 |     fi
187 | fi
```
Page 9/59FirstPrevNextLast