#
tokens: 49266/50000 23/614 files (page 8/45)
lines: off (toggle) GitHub
raw markdown copy
This is page 8 of 45. Use http://codebase.md/czlonkowski/n8n-mcp?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

--------------------------------------------------------------------------------
/scripts/extract-from-docker.js:
--------------------------------------------------------------------------------

```javascript
#!/usr/bin/env node
const dotenv = require('dotenv');
const { NodeDocumentationService } = require('../dist/services/node-documentation-service');
const { NodeSourceExtractor } = require('../dist/utils/node-source-extractor');
const { logger } = require('../dist/utils/logger');
const fs = require('fs').promises;
const path = require('path');

// Load environment variables
dotenv.config();

async function extractNodesFromDocker() {
  logger.info('🐳 Starting Docker-based node extraction...');
  
  // Add Docker volume paths to environment for NodeSourceExtractor
  const dockerVolumePaths = [
    process.env.N8N_MODULES_PATH || '/n8n-modules',
    process.env.N8N_CUSTOM_PATH || '/n8n-custom',
  ];
  
  logger.info(`Docker volume paths: ${dockerVolumePaths.join(', ')}`);
  
  // Check if volumes are mounted
  for (const volumePath of dockerVolumePaths) {
    try {
      await fs.access(volumePath);
      logger.info(`✅ Volume mounted: ${volumePath}`);
      
      // List what's in the volume
      const entries = await fs.readdir(volumePath);
      logger.info(`Contents of ${volumePath}: ${entries.slice(0, 10).join(', ')}${entries.length > 10 ? '...' : ''}`);
    } catch (error) {
      logger.warn(`❌ Volume not accessible: ${volumePath}`);
    }
  }
  
  // Initialize services
  const docService = new NodeDocumentationService();
  const extractor = new NodeSourceExtractor();
  
  // Extend the extractor's search paths with Docker volumes
  extractor.n8nBasePaths.unshift(...dockerVolumePaths);
  
  // Clear existing nodes to ensure we only have latest versions
  logger.info('🧹 Clearing existing nodes...');
  const db = docService.db;
  db.prepare('DELETE FROM nodes').run();
  
  logger.info('🔍 Searching for n8n nodes in Docker volumes...');
  
  // Known n8n packages to extract
  const n8nPackages = [
    'n8n-nodes-base',
    '@n8n/n8n-nodes-langchain',
    'n8n-nodes-extras',
  ];
  
  let totalExtracted = 0;
  let ifNodeVersion = null;
  
  for (const packageName of n8nPackages) {
    logger.info(`\n📦 Processing package: ${packageName}`);
    
    try {
      // Find package in Docker volumes
      let packagePath = null;
      
      for (const volumePath of dockerVolumePaths) {
        const possiblePaths = [
          path.join(volumePath, packageName),
          path.join(volumePath, '.pnpm', `${packageName}@*`, 'node_modules', packageName),
        ];
        
        for (const testPath of possiblePaths) {
          try {
            // Use glob pattern to find pnpm packages
            if (testPath.includes('*')) {
              const baseDir = path.dirname(testPath.split('*')[0]);
              const entries = await fs.readdir(baseDir);
              
              for (const entry of entries) {
                if (entry.includes(packageName.replace('/', '+'))) {
                  const fullPath = path.join(baseDir, entry, 'node_modules', packageName);
                  try {
                    await fs.access(fullPath);
                    packagePath = fullPath;
                    break;
                  } catch {}
                }
              }
            } else {
              await fs.access(testPath);
              packagePath = testPath;
              break;
            }
          } catch {}
        }
        
        if (packagePath) break;
      }
      
      if (!packagePath) {
        logger.warn(`Package ${packageName} not found in Docker volumes`);
        continue;
      }
      
      logger.info(`Found package at: ${packagePath}`);
      
      // Check package version
      try {
        const packageJsonPath = path.join(packagePath, 'package.json');
        const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
        logger.info(`Package version: ${packageJson.version}`);
      } catch {}
      
      // Find nodes directory
      const nodesPath = path.join(packagePath, 'dist', 'nodes');
      
      try {
        await fs.access(nodesPath);
        logger.info(`Scanning nodes directory: ${nodesPath}`);
        
        // Extract all nodes from this package
        const nodeEntries = await scanForNodes(nodesPath);
        logger.info(`Found ${nodeEntries.length} nodes in ${packageName}`);
        
        for (const nodeEntry of nodeEntries) {
          try {
            const nodeName = nodeEntry.name.replace('.node.js', '');
            const nodeType = `${packageName}.${nodeName}`;
            
            logger.info(`Extracting: ${nodeType}`);
            
            // Extract source info
            const sourceInfo = await extractor.extractNodeSource(nodeType);
            
            // Check if this is the If node
            if (nodeName === 'If') {
              // Look for version in the source code
              const versionMatch = sourceInfo.sourceCode.match(/version:\s*(\d+)/);
              if (versionMatch) {
                ifNodeVersion = versionMatch[1];
                logger.info(`📍 Found If node version: ${ifNodeVersion}`);
              }
            }
            
            // Store in database
            await docService.storeNode({
              nodeType: nodeType,
              name: nodeName,
              displayName: nodeName,
              description: `${nodeName} node from ${packageName}`,
              sourceCode: sourceInfo.sourceCode,
              credentialCode: sourceInfo.credentialCode,
              packageName: packageName,
              version: ifNodeVersion || '1',
              hasCredentials: !!sourceInfo.credentialCode,
              isTrigger: sourceInfo.sourceCode.includes('trigger: true') || nodeName.toLowerCase().includes('trigger'),
              isWebhook: sourceInfo.sourceCode.includes('webhook: true') || nodeName.toLowerCase().includes('webhook'),
            });
            
            totalExtracted++;
          } catch (error) {
            logger.error(`Failed to extract ${nodeEntry.name}: ${error}`);
          }
        }
      } catch (error) {
        logger.error(`Failed to scan nodes directory: ${error}`);
      }
    } catch (error) {
      logger.error(`Failed to process package ${packageName}: ${error}`);
    }
  }
  
  logger.info(`\n✅ Extraction complete!`);
  logger.info(`📊 Total nodes extracted: ${totalExtracted}`);
  
  if (ifNodeVersion) {
    logger.info(`📍 If node version: ${ifNodeVersion}`);
    if (ifNodeVersion === '2' || ifNodeVersion === '2.2') {
      logger.info('✅ Successfully extracted latest If node (v2+)!');
    } else {
      logger.warn(`⚠️ If node version is ${ifNodeVersion}, expected v2 or higher`);
    }
  }
  
  // Close database
  docService.close();
}

async function scanForNodes(dirPath) {
  const nodes = [];
  
  async function scan(currentPath) {
    try {
      const entries = await fs.readdir(currentPath, { withFileTypes: true });
      
      for (const entry of entries) {
        const fullPath = path.join(currentPath, entry.name);
        
        if (entry.isFile() && entry.name.endsWith('.node.js')) {
          nodes.push({ name: entry.name, path: fullPath });
        } else if (entry.isDirectory() && entry.name !== 'node_modules') {
          await scan(fullPath);
        }
      }
    } catch (error) {
      logger.debug(`Failed to scan directory ${currentPath}: ${error}`);
    }
  }
  
  await scan(dirPath);
  return nodes;
}

// Run extraction
extractNodesFromDocker().catch(error => {
  logger.error('Extraction failed:', error);
  process.exit(1);
});
```

--------------------------------------------------------------------------------
/docs/MCP_ESSENTIALS_README.md:
--------------------------------------------------------------------------------

```markdown
# n8n MCP Essentials Tools - User Guide

## Overview

The n8n MCP has been enhanced with new tools that dramatically improve the AI agent experience when building n8n workflows. The key improvement is the `get_node_essentials` tool which reduces response sizes by 95% while providing all the information needed for basic configuration.

## New Tools

### 1. `get_node_essentials`

**Purpose**: Get only the 10-20 most important properties for a node instead of 200+

**When to use**: 
- Starting to configure a new node
- Need quick access to common properties
- Want working examples
- Building basic workflows

**Example usage**:
```json
{
  "name": "get_node_essentials",
  "arguments": {
    "nodeType": "nodes-base.httpRequest"
  }
}
```

**Response structure**:
```json
{
  "nodeType": "nodes-base.httpRequest",
  "displayName": "HTTP Request",
  "description": "Makes HTTP requests and returns the response data",
  "requiredProperties": [
    {
      "name": "url",
      "displayName": "URL",
      "type": "string",
      "description": "The URL to make the request to",
      "placeholder": "https://api.example.com/endpoint"
    }
  ],
  "commonProperties": [
    {
      "name": "method",
      "type": "options",
      "options": [
        { "value": "GET", "label": "GET" },
        { "value": "POST", "label": "POST" }
      ],
      "default": "GET"
    }
    // ... 4-5 more common properties
  ],
  "examples": {
    "minimal": {
      "url": "https://api.example.com/data"
    },
    "common": {
      "method": "POST",
      "url": "https://api.example.com/users",
      "sendBody": true,
      "contentType": "json",
      "jsonBody": "{ \"name\": \"John\" }"
    }
  },
  "metadata": {
    "totalProperties": 245,
    "isAITool": false,
    "isTrigger": false
  }
}
```

**Benefits**:
- 95% smaller response (5KB vs 100KB+)
- Only shows properties you actually need
- Includes working examples
- No duplicate or confusing properties
- Clear indication of what's required

### 2. `search_node_properties`

**Purpose**: Find specific properties within a node without downloading everything

**When to use**:
- Looking for authentication options
- Finding specific configuration like headers or body
- Exploring what options are available
- Need to configure advanced features

**Example usage**:
```json
{
  "name": "search_node_properties",
  "arguments": {
    "nodeType": "nodes-base.httpRequest",
    "query": "auth"
  }
}
```

**Response structure**:
```json
{
  "nodeType": "nodes-base.httpRequest",
  "query": "auth",
  "matches": [
    {
      "name": "authentication",
      "displayName": "Authentication",
      "type": "options",
      "description": "Method of authentication to use",
      "path": "authentication",
      "options": [
        { "value": "none", "label": "None" },
        { "value": "basicAuth", "label": "Basic Auth" }
      ]
    },
    {
      "name": "genericAuthType",
      "path": "genericAuthType",
      "showWhen": { "authentication": "genericCredentialType" }
    }
  ],
  "totalMatches": 5,
  "searchedIn": "245 properties"
}
```

## Recommended Workflow

### For Basic Configuration:

1. **Start with essentials**:
   ```
   get_node_essentials("nodes-base.httpRequest")
   ```
   
2. **Use the provided examples**:
   - Start with `minimal` example
   - Upgrade to `common` for typical use cases
   - Modify based on your needs

3. **Search for specific features** (if needed):
   ```
   search_node_properties("nodes-base.httpRequest", "header")
   ```

### For Complex Configuration:

1. **Get documentation first**:
   ```
   get_node_documentation("nodes-base.httpRequest")
   ```

2. **Get essentials for the basics**:
   ```
   get_node_essentials("nodes-base.httpRequest")
   ```

3. **Search for advanced properties**:
   ```
   search_node_properties("nodes-base.httpRequest", "proxy")
   ```

4. **Only use get_node_info if absolutely necessary**:
   ```
   get_node_info("nodes-base.httpRequest")  // Last resort - 100KB+ response
   ```

## Common Patterns

### Making API Calls:
```javascript
// Start with essentials
const essentials = get_node_essentials("nodes-base.httpRequest");

// Use the POST example
const config = essentials.examples.common;

// Modify for your needs
config.url = "https://api.myservice.com/endpoint";
config.jsonBody = JSON.stringify({ my: "data" });
```

### Setting up Webhooks:
```javascript
// Get webhook essentials
const essentials = get_node_essentials("nodes-base.webhook");

// Start with minimal
const config = essentials.examples.minimal;
config.path = "my-webhook-endpoint";
```

### Database Operations:
```javascript
// Get database essentials
const essentials = get_node_essentials("nodes-base.postgres");

// Check available operations
const operations = essentials.operations;

// Use appropriate example
const config = essentials.examples.common;
```

## Tips for AI Agents

1. **Always start with get_node_essentials** - It has everything needed for 90% of use cases

2. **Use examples as templates** - They're tested, working configurations

3. **Search before diving deep** - Use search_node_properties to find specific options

4. **Check metadata** - Know if you need credentials, if it's a trigger, etc.

5. **Progressive disclosure** - Start simple, add complexity only when needed

## Supported Nodes

The essentials tool has optimized configurations for 20+ commonly used nodes:

- **Core**: httpRequest, webhook, code, set, if, merge, splitInBatches
- **Databases**: postgres, mysql, mongodb, redis
- **Communication**: slack, email, discord
- **Files**: ftp, ssh, googleSheets
- **AI**: openAi, agent
- **Utilities**: executeCommand, function

For other nodes, the tool automatically extracts the most important properties.

## Performance Metrics

Based on testing with top 10 nodes:

- **Average size reduction**: 94.3%
- **Response time improvement**: 78%
- **Properties shown**: 10-20 (vs 200+)
- **Usability improvement**: Dramatic

## Migration Guide

If you're currently using `get_node_info`, here's how to migrate:

### Before:
```javascript
const node = get_node_info("nodes-base.httpRequest");
// Parse through 200+ properties
// Figure out what's required
// Deal with duplicates and conditionals
```

### After:
```javascript
const essentials = get_node_essentials("nodes-base.httpRequest");
// Use essentials.requiredProperties
// Use essentials.commonProperties  
// Start with essentials.examples.common
```

## Troubleshooting

**Q: The tool says node not found**
A: Use the full node type with prefix: `nodes-base.httpRequest` not just `httpRequest`

**Q: I need a property that's not in essentials**
A: Use `search_node_properties` to find it, or `get_node_info` as last resort

**Q: The examples don't cover my use case**
A: Start with the closest example and modify. Use search to find additional properties.

**Q: How do I know what properties are available?**
A: Check `metadata.totalProperties` to see how many are available, then search for what you need

## Future Improvements

Planned enhancements:
- Task-based configurations (e.g., "post_json_with_auth")
- Configuration validation
- Property dependency resolution
- More node coverage

## Summary

The new essentials tools make n8n workflow building with AI agents actually practical. Instead of overwhelming agents with hundreds of properties, we provide just what's needed, when it's needed. This results in faster, more accurate workflow creation with fewer errors.
```

--------------------------------------------------------------------------------
/src/scripts/extract-from-docker.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node
import * as dotenv from 'dotenv';
import { NodeDocumentationService } from '../services/node-documentation-service';
import { NodeSourceExtractor } from '../utils/node-source-extractor';
import { logger } from '../utils/logger';
import * as fs from 'fs/promises';
import * as path from 'path';

// Load environment variables
dotenv.config();

async function extractNodesFromDocker() {
  logger.info('🐳 Starting Docker-based node extraction...');
  
  // Add Docker volume paths to environment for NodeSourceExtractor
  const dockerVolumePaths = [
    process.env.N8N_MODULES_PATH || '/n8n-modules',
    process.env.N8N_CUSTOM_PATH || '/n8n-custom',
  ];
  
  logger.info(`Docker volume paths: ${dockerVolumePaths.join(', ')}`);
  
  // Check if volumes are mounted
  for (const volumePath of dockerVolumePaths) {
    try {
      await fs.access(volumePath);
      logger.info(`✅ Volume mounted: ${volumePath}`);
      
      // List what's in the volume
      const entries = await fs.readdir(volumePath);
      logger.info(`Contents of ${volumePath}: ${entries.slice(0, 10).join(', ')}${entries.length > 10 ? '...' : ''}`);
    } catch (error) {
      logger.warn(`❌ Volume not accessible: ${volumePath}`);
    }
  }
  
  // Initialize services
  const docService = new NodeDocumentationService();
  const extractor = new NodeSourceExtractor();
  
  // Extend the extractor's search paths with Docker volumes
  (extractor as any).n8nBasePaths.unshift(...dockerVolumePaths);
  
  // Clear existing nodes to ensure we only have latest versions
  logger.info('🧹 Clearing existing nodes...');
  const db = (docService as any).db;
  db.prepare('DELETE FROM nodes').run();
  
  logger.info('🔍 Searching for n8n nodes in Docker volumes...');
  
  // Known n8n packages to extract
  const n8nPackages = [
    'n8n-nodes-base',
    '@n8n/n8n-nodes-langchain',
    'n8n-nodes-extras',
  ];
  
  let totalExtracted = 0;
  let ifNodeVersion = null;
  
  for (const packageName of n8nPackages) {
    logger.info(`\n📦 Processing package: ${packageName}`);
    
    try {
      // Find package in Docker volumes
      let packagePath = null;
      
      for (const volumePath of dockerVolumePaths) {
        const possiblePaths = [
          path.join(volumePath, packageName),
          path.join(volumePath, '.pnpm', `${packageName}@*`, 'node_modules', packageName),
        ];
        
        for (const testPath of possiblePaths) {
          try {
            // Use glob pattern to find pnpm packages
            if (testPath.includes('*')) {
              const baseDir = path.dirname(testPath.split('*')[0]);
              const entries = await fs.readdir(baseDir);
              
              for (const entry of entries) {
                if (entry.includes(packageName.replace('/', '+'))) {
                  const fullPath = path.join(baseDir, entry, 'node_modules', packageName);
                  try {
                    await fs.access(fullPath);
                    packagePath = fullPath;
                    break;
                  } catch {}
                }
              }
            } else {
              await fs.access(testPath);
              packagePath = testPath;
              break;
            }
          } catch {}
        }
        
        if (packagePath) break;
      }
      
      if (!packagePath) {
        logger.warn(`Package ${packageName} not found in Docker volumes`);
        continue;
      }
      
      logger.info(`Found package at: ${packagePath}`);
      
      // Check package version
      try {
        const packageJsonPath = path.join(packagePath, 'package.json');
        const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
        logger.info(`Package version: ${packageJson.version}`);
      } catch {}
      
      // Find nodes directory
      const nodesPath = path.join(packagePath, 'dist', 'nodes');
      
      try {
        await fs.access(nodesPath);
        logger.info(`Scanning nodes directory: ${nodesPath}`);
        
        // Extract all nodes from this package
        const nodeEntries = await scanForNodes(nodesPath);
        logger.info(`Found ${nodeEntries.length} nodes in ${packageName}`);
        
        for (const nodeEntry of nodeEntries) {
          try {
            const nodeName = nodeEntry.name.replace('.node.js', '');
            const nodeType = `${packageName}.${nodeName}`;
            
            logger.info(`Extracting: ${nodeType}`);
            
            // Extract source info
            const sourceInfo = await extractor.extractNodeSource(nodeType);
            
            // Check if this is the If node
            if (nodeName === 'If') {
              // Look for version in the source code
              const versionMatch = sourceInfo.sourceCode.match(/version:\s*(\d+)/);
              if (versionMatch) {
                ifNodeVersion = versionMatch[1];
                logger.info(`📍 Found If node version: ${ifNodeVersion}`);
              }
            }
            
            // Store in database
            await docService.storeNode({
              nodeType: nodeType,
              name: nodeName,
              displayName: nodeName,
              description: `${nodeName} node from ${packageName}`,
              sourceCode: sourceInfo.sourceCode,
              credentialCode: sourceInfo.credentialCode,
              packageName: packageName,
              version: ifNodeVersion || '1',
              hasCredentials: !!sourceInfo.credentialCode,
              isTrigger: sourceInfo.sourceCode.includes('trigger: true') || nodeName.toLowerCase().includes('trigger'),
              isWebhook: sourceInfo.sourceCode.includes('webhook: true') || nodeName.toLowerCase().includes('webhook'),
            });
            
            totalExtracted++;
          } catch (error) {
            logger.error(`Failed to extract ${nodeEntry.name}: ${error}`);
          }
        }
      } catch (error) {
        logger.error(`Failed to scan nodes directory: ${error}`);
      }
    } catch (error) {
      logger.error(`Failed to process package ${packageName}: ${error}`);
    }
  }
  
  logger.info(`\n✅ Extraction complete!`);
  logger.info(`📊 Total nodes extracted: ${totalExtracted}`);
  
  if (ifNodeVersion) {
    logger.info(`📍 If node version: ${ifNodeVersion}`);
    if (ifNodeVersion === '2' || ifNodeVersion === '2.2') {
      logger.info('✅ Successfully extracted latest If node (v2+)!');
    } else {
      logger.warn(`⚠️ If node version is ${ifNodeVersion}, expected v2 or higher`);
    }
  }
  
  // Close database
  await docService.close();
}

async function scanForNodes(dirPath: string): Promise<{ name: string; path: string }[]> {
  const nodes: { name: string; path: string }[] = [];
  
  async function scan(currentPath: string) {
    try {
      const entries = await fs.readdir(currentPath, { withFileTypes: true });
      
      for (const entry of entries) {
        const fullPath = path.join(currentPath, entry.name);
        
        if (entry.isFile() && entry.name.endsWith('.node.js')) {
          nodes.push({ name: entry.name, path: fullPath });
        } else if (entry.isDirectory() && entry.name !== 'node_modules') {
          await scan(fullPath);
        }
      }
    } catch (error) {
      logger.debug(`Failed to scan directory ${currentPath}: ${error}`);
    }
  }
  
  await scan(dirPath);
  return nodes;
}

// Run extraction
extractNodesFromDocker().catch(error => {
  logger.error('Extraction failed:', error);
  process.exit(1);
});
```

--------------------------------------------------------------------------------
/src/scripts/test-autofix-workflow.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Test script for n8n_autofix_workflow functionality
 *
 * Tests the automatic fixing of common workflow validation errors:
 * 1. Expression format errors (missing = prefix)
 * 2. TypeVersion corrections
 * 3. Error output configuration issues
 */

import { WorkflowAutoFixer } from '../services/workflow-auto-fixer';
import { WorkflowValidator } from '../services/workflow-validator';
import { EnhancedConfigValidator } from '../services/enhanced-config-validator';
import { ExpressionFormatValidator } from '../services/expression-format-validator';
import { NodeRepository } from '../database/node-repository';
import { Logger } from '../utils/logger';
import { createDatabaseAdapter } from '../database/database-adapter';
import * as path from 'path';

const logger = new Logger({ prefix: '[TestAutofix]' });

async function testAutofix() {
  // Initialize database and repository
  const dbPath = path.join(__dirname, '../../data/nodes.db');
  const dbAdapter = await createDatabaseAdapter(dbPath);
  const repository = new NodeRepository(dbAdapter);

  // Test workflow with various issues
  const testWorkflow = {
    id: 'test_workflow_1',
    name: 'Test Workflow for Autofix',
    nodes: [
      {
        id: 'webhook_1',
        name: 'Webhook',
        type: 'n8n-nodes-base.webhook',
        typeVersion: 1.1,
        position: [250, 300],
        parameters: {
          httpMethod: 'GET',
          path: 'test-webhook',
          responseMode: 'onReceived',
          responseData: 'firstEntryJson'
        }
      },
      {
        id: 'http_1',
        name: 'HTTP Request',
        type: 'n8n-nodes-base.httpRequest',
        typeVersion: 5.0, // Invalid - max is 4.2
        position: [450, 300],
        parameters: {
          method: 'GET',
          url: '{{ $json.webhookUrl }}', // Missing = prefix
          sendHeaders: true,
          headerParameters: {
            parameters: [
              {
                name: 'Authorization',
                value: '{{ $json.token }}' // Missing = prefix
              }
            ]
          }
        },
        onError: 'continueErrorOutput' // Has onError but no error connections
      },
      {
        id: 'set_1',
        name: 'Set',
        type: 'n8n-nodes-base.set',
        typeVersion: 3.5, // Invalid version
        position: [650, 300],
        parameters: {
          mode: 'manual',
          duplicateItem: false,
          values: {
            values: [
              {
                name: 'status',
                value: '{{ $json.success }}' // Missing = prefix
              }
            ]
          }
        }
      }
    ],
    connections: {
      'Webhook': {
        main: [
          [
            {
              node: 'HTTP Request',
              type: 'main',
              index: 0
            }
          ]
        ]
      },
      'HTTP Request': {
        main: [
          [
            {
              node: 'Set',
              type: 'main',
              index: 0
            }
          ]
          // Missing error output connection for onError: 'continueErrorOutput'
        ]
      }
    }
  };

  logger.info('=== Testing Workflow Auto-Fixer ===\n');

  // Step 1: Validate the workflow to identify issues
  logger.info('Step 1: Validating workflow to identify issues...');
  const validator = new WorkflowValidator(repository, EnhancedConfigValidator);
  const validationResult = await validator.validateWorkflow(testWorkflow as any, {
    validateNodes: true,
    validateConnections: true,
    validateExpressions: true,
    profile: 'ai-friendly'
  });

  logger.info(`Found ${validationResult.errors.length} errors and ${validationResult.warnings.length} warnings`);

  // Step 2: Check for expression format issues
  logger.info('\nStep 2: Checking for expression format issues...');
  const allFormatIssues: any[] = [];
  for (const node of testWorkflow.nodes) {
    const formatContext = {
      nodeType: node.type,
      nodeName: node.name,
      nodeId: node.id
    };

    const nodeFormatIssues = ExpressionFormatValidator.validateNodeParameters(
      node.parameters,
      formatContext
    );

    // Add node information to each format issue
    const enrichedIssues = nodeFormatIssues.map(issue => ({
      ...issue,
      nodeName: node.name,
      nodeId: node.id
    }));

    allFormatIssues.push(...enrichedIssues);
  }

  logger.info(`Found ${allFormatIssues.length} expression format issues`);

  // Debug: Show the actual format issues
  if (allFormatIssues.length > 0) {
    logger.info('\nExpression format issues found:');
    for (const issue of allFormatIssues) {
      logger.info(`  - ${issue.fieldPath}: ${issue.issueType} (${issue.severity})`);
      logger.info(`    Current: ${JSON.stringify(issue.currentValue)}`);
      logger.info(`    Fixed: ${JSON.stringify(issue.correctedValue)}`);
    }
  }

  // Step 3: Generate fixes in preview mode
  logger.info('\nStep 3: Generating fixes (preview mode)...');
  const autoFixer = new WorkflowAutoFixer();
  const previewResult = autoFixer.generateFixes(
    testWorkflow as any,
    validationResult,
    allFormatIssues,
    {
      applyFixes: false, // Preview mode
      confidenceThreshold: 'medium'
    }
  );

  logger.info(`\nGenerated ${previewResult.fixes.length} fixes:`);
  logger.info(`Summary: ${previewResult.summary}`);
  logger.info('\nFixes by type:');
  for (const [type, count] of Object.entries(previewResult.stats.byType)) {
    if (count > 0) {
      logger.info(`  - ${type}: ${count}`);
    }
  }

  logger.info('\nFixes by confidence:');
  for (const [confidence, count] of Object.entries(previewResult.stats.byConfidence)) {
    if (count > 0) {
      logger.info(`  - ${confidence}: ${count}`);
    }
  }

  // Step 4: Display individual fixes
  logger.info('\nDetailed fixes:');
  for (const fix of previewResult.fixes) {
    logger.info(`\n[${fix.confidence.toUpperCase()}] ${fix.node}.${fix.field} (${fix.type})`);
    logger.info(`  Before: ${JSON.stringify(fix.before)}`);
    logger.info(`  After:  ${JSON.stringify(fix.after)}`);
    logger.info(`  Description: ${fix.description}`);
  }

  // Step 5: Display generated operations
  logger.info('\n\nGenerated diff operations:');
  for (const op of previewResult.operations) {
    logger.info(`\nOperation: ${op.type}`);
    logger.info(`  Details: ${JSON.stringify(op, null, 2)}`);
  }

  // Step 6: Test with different confidence thresholds
  logger.info('\n\n=== Testing Different Confidence Thresholds ===');

  for (const threshold of ['high', 'medium', 'low'] as const) {
    const result = autoFixer.generateFixes(
      testWorkflow as any,
      validationResult,
      allFormatIssues,
      {
        applyFixes: false,
        confidenceThreshold: threshold
      }
    );
    logger.info(`\nThreshold "${threshold}": ${result.fixes.length} fixes`);
  }

  // Step 7: Test with specific fix types
  logger.info('\n\n=== Testing Specific Fix Types ===');

  const fixTypes = ['expression-format', 'typeversion-correction', 'error-output-config'] as const;
  for (const fixType of fixTypes) {
    const result = autoFixer.generateFixes(
      testWorkflow as any,
      validationResult,
      allFormatIssues,
      {
        applyFixes: false,
        fixTypes: [fixType]
      }
    );
    logger.info(`\nFix type "${fixType}": ${result.fixes.length} fixes`);
  }

  logger.info('\n\n✅ Autofix test completed successfully!');

  await dbAdapter.close();
}

// Run the test
testAutofix().catch(error => {
  logger.error('Test failed:', error);
  process.exit(1);
});
```

--------------------------------------------------------------------------------
/src/parsers/property-extractor.ts:
--------------------------------------------------------------------------------

```typescript
import type { NodeClass } from '../types/node-types';

export class PropertyExtractor {
  /**
   * Extract properties with proper handling of n8n's complex structures
   */
  extractProperties(nodeClass: NodeClass): any[] {
    const properties: any[] = [];
    
    // First try to get instance-level properties
    let instance: any;
    try {
      instance = typeof nodeClass === 'function' ? new nodeClass() : nodeClass;
    } catch (e) {
      // Failed to instantiate
    }
    
    // Handle versioned nodes - check instance for nodeVersions
    if (instance?.nodeVersions) {
      const versions = Object.keys(instance.nodeVersions).map(Number);
      if (versions.length > 0) {
        const latestVersion = Math.max(...versions);
        if (!isNaN(latestVersion)) {
          const versionedNode = instance.nodeVersions[latestVersion];

          if (versionedNode?.description?.properties) {
            return this.normalizeProperties(versionedNode.description.properties);
          }
        }
      }
    }
    
    // Check for description with properties
    const description = instance?.description || instance?.baseDescription || 
                       this.getNodeDescription(nodeClass);
    
    if (description?.properties) {
      return this.normalizeProperties(description.properties);
    }
    
    return properties;
  }
  
  private getNodeDescription(nodeClass: NodeClass): any {
    // Try to get description from the class first
    let description: any;

    if (typeof nodeClass === 'function') {
      // Try to instantiate to get description
      try {
        const instance = new nodeClass();
        // Strategic any assertion for instance properties
        const inst = instance as any;
        description = inst.description || inst.baseDescription || {};
      } catch (e) {
        // Some nodes might require parameters to instantiate
        // Strategic any assertion for class-level properties
        const nodeClassAny = nodeClass as any;
        description = nodeClassAny.description || {};
      }
    } else {
      // Strategic any assertion for instance properties
      const inst = nodeClass as any;
      description = inst.description || {};
    }

    return description;
  }
  
  /**
   * Extract operations from both declarative and programmatic nodes
   */
  extractOperations(nodeClass: NodeClass): any[] {
    const operations: any[] = [];
    
    // First try to get instance-level data
    let instance: any;
    try {
      instance = typeof nodeClass === 'function' ? new nodeClass() : nodeClass;
    } catch (e) {
      // Failed to instantiate
    }
    
    // Handle versioned nodes
    if (instance?.nodeVersions) {
      const versions = Object.keys(instance.nodeVersions).map(Number);
      if (versions.length > 0) {
        const latestVersion = Math.max(...versions);
        if (!isNaN(latestVersion)) {
          const versionedNode = instance.nodeVersions[latestVersion];

          if (versionedNode?.description) {
            return this.extractOperationsFromDescription(versionedNode.description);
          }
        }
      }
    }
    
    // Get description
    const description = instance?.description || instance?.baseDescription || 
                       this.getNodeDescription(nodeClass);
    
    return this.extractOperationsFromDescription(description);
  }
  
  private extractOperationsFromDescription(description: any): any[] {
    const operations: any[] = [];
    
    if (!description) return operations;
    
    // Declarative nodes (with routing)
    if (description.routing) {
      const routing = description.routing;
      
      // Extract from request.resource and request.operation
      if (routing.request?.resource) {
        const resources = routing.request.resource.options || [];
        const operationOptions = routing.request.operation?.options || {};
        
        resources.forEach((resource: any) => {
          const resourceOps = operationOptions[resource.value] || [];
          resourceOps.forEach((op: any) => {
            operations.push({
              resource: resource.value,
              operation: op.value,
              name: `${resource.name} - ${op.name}`,
              action: op.action
            });
          });
        });
      }
    }
    
    // Programmatic nodes - look for operation property in properties
    if (description.properties && Array.isArray(description.properties)) {
      const operationProp = description.properties.find(
        (p: any) => p.name === 'operation' || p.name === 'action'
      );
      
      if (operationProp?.options) {
        operationProp.options.forEach((op: any) => {
          operations.push({
            operation: op.value,
            name: op.name,
            description: op.description
          });
        });
      }
    }
    
    return operations;
  }
  
  /**
   * Deep search for AI tool capability
   */
  detectAIToolCapability(nodeClass: NodeClass): boolean {
    const description = this.getNodeDescription(nodeClass);

    // Direct property check
    if (description?.usableAsTool === true) return true;

    // Check in actions for declarative nodes
    if (description?.actions?.some((a: any) => a.usableAsTool === true)) return true;

    // Check versioned nodes
    // Strategic any assertion for nodeVersions property
    const nodeClassAny = nodeClass as any;
    if (nodeClassAny.nodeVersions) {
      for (const version of Object.values(nodeClassAny.nodeVersions)) {
        if ((version as any).description?.usableAsTool === true) return true;
      }
    }

    // Check for specific AI-related properties
    const aiIndicators = ['openai', 'anthropic', 'huggingface', 'cohere', 'ai'];
    const nodeName = description?.name?.toLowerCase() || '';

    return aiIndicators.some(indicator => nodeName.includes(indicator));
  }
  
  /**
   * Extract credential requirements with proper structure
   */
  extractCredentials(nodeClass: NodeClass): any[] {
    const credentials: any[] = [];
    
    // First try to get instance-level data
    let instance: any;
    try {
      instance = typeof nodeClass === 'function' ? new nodeClass() : nodeClass;
    } catch (e) {
      // Failed to instantiate
    }
    
    // Handle versioned nodes
    if (instance?.nodeVersions) {
      const versions = Object.keys(instance.nodeVersions).map(Number);
      if (versions.length > 0) {
        const latestVersion = Math.max(...versions);
        if (!isNaN(latestVersion)) {
          const versionedNode = instance.nodeVersions[latestVersion];

          if (versionedNode?.description?.credentials) {
            return versionedNode.description.credentials;
          }
        }
      }
    }
    
    // Check for description with credentials
    const description = instance?.description || instance?.baseDescription || 
                       this.getNodeDescription(nodeClass);
    
    if (description?.credentials) {
      return description.credentials;
    }
    
    return credentials;
  }
  
  private normalizeProperties(properties: any[]): any[] {
    // Ensure all properties have consistent structure
    return properties.map(prop => ({
      displayName: prop.displayName,
      name: prop.name,
      type: prop.type,
      default: prop.default,
      description: prop.description,
      options: prop.options,
      required: prop.required,
      displayOptions: prop.displayOptions,
      typeOptions: prop.typeOptions,
      modes: prop.modes, // For resourceLocator type properties - modes are at top level
      noDataExpression: prop.noDataExpression
    }));
  }
}
```

--------------------------------------------------------------------------------
/docs/local/DEEP_DIVE_ANALYSIS_README.md:
--------------------------------------------------------------------------------

```markdown
# N8N-MCP Deep Dive Analysis - October 2, 2025

## Overview

This directory contains a comprehensive deep-dive analysis of n8n-mcp usage data from September 26 - October 2, 2025.

**Data Volume Analyzed:**
- 212,375 telemetry events
- 5,751 workflow creations
- 2,119 unique users
- 6 days of usage data

## Report Structure


###: `DEEP_DIVE_ANALYSIS_2025-10-02.md` (Main Report)

**Sections Covered:**
1. **Executive Summary** - Key findings and recommendations
2. **Tool Performance Analysis** - Success rates, performance metrics, critical findings
3. **Validation Catastrophe** - The node type prefix disaster analysis
4. **Usage Patterns & User Segmentation** - User distribution, daily trends
5. **Tool Sequence Analysis** - How AI agents use tools together
6. **Workflow Creation Patterns** - Complexity distribution, popular nodes
7. **Platform & Version Distribution** - OS, architecture, version adoption
8. **Error Patterns & Root Causes** - TypeErrors, validation errors, discovery failures
9. **P0-P1 Refactoring Recommendations** - Detailed implementation guides

**Sections Covered:**
- Remaining P1 and P2 recommendations
- Architectural refactoring suggestions
- Telemetry enhancements
- CHANGELOG integration
- Final recommendations summary

## Key Findings Summary

### Critical Issues (P0 - Fix Immediately)

1. **Node Type Prefix Validation Catastrophe**
   - 5,000+ validation errors from single root cause
   - `nodes-base.X` vs `n8n-nodes-base.X` confusion
   - **Solution**: Auto-normalize prefixes (2-4 hours effort)

2. **TypeError in Node Information Tools**
   - 10-18% failure rate in get_node_essentials/info
   - 1,000+ failures affecting hundreds of users
   - **Solution**: Complete null-safety audit (1 day effort)

3. **Task Discovery Failures**
   - `get_node_for_task` failing 28% of the time
   - Worst-performing tool in entire system
   - **Solution**: Expand task library + fuzzy matching (3 days effort)

### Performance Metrics

**Excellent Reliability (96-100% success):**
- n8n_update_partial_workflow: 98.7%
- search_nodes: 99.8%
- n8n_create_workflow: 96.1%
- All workflow management tools: 100%

**User Distribution:**
- Power Users (12): 2,112 events/user, 33 workflows
- Heavy Users (47): 673 events/user, 18 workflows
- Regular Users (516): 199 events/user, 7 workflows (CORE AUDIENCE)
- Active Users (919): 52 events/user, 2 workflows
- Casual Users (625): 8 events/user, 1 workflow

### Usage Insights

**Most Used Tools:**
1. n8n_update_partial_workflow: 10,177 calls (iterative refinement)
2. search_nodes: 8,839 calls (node discovery)
3. n8n_create_workflow: 6,046 calls (workflow creation)

**Most Common Tool Sequences:**
1. update → update → update (549x) - Iterative refinement pattern
2. create → update (297x) - Create then refine
3. update → get_workflow (265x) - Update then verify

**Most Popular Nodes:**
1. code (53% of workflows) - AI agents love programmatic control
2. httpRequest (47%) - Integration-heavy usage
3. webhook (32%) - Event-driven automation

## SQL Analytical Views Created

15 comprehensive views were created in Supabase for ongoing analysis:

1. `vw_tool_performance` - Performance metrics per tool
2. `vw_error_analysis` - Error patterns and frequencies
3. `vw_validation_analysis` - Validation failure details
4. `vw_tool_sequences` - Tool-to-tool transition patterns
5. `vw_workflow_creation_patterns` - Workflow characteristics
6. `vw_node_usage_analysis` - Node popularity and complexity
7. `vw_node_cooccurrence` - Which nodes are used together
8. `vw_user_activity` - Per-user activity metrics
9. `vw_session_analysis` - Platform/version distribution
10. `vw_workflow_validation_failures` - Workflow validation issues
11. `vw_temporal_patterns` - Time-based usage patterns
12. `vw_tool_funnel` - User progression through tools
13. `vw_search_analysis` - Search behavior
14. `vw_tool_success_summary` - Success/failure rates
15. `vw_user_journeys` - Complete user session reconstruction

## Priority Recommendations

### Immediate Actions (This Week)

✅ **P0-R1**: Auto-normalize node type prefixes → Eliminate 4,800 errors
✅ **P0-R2**: Complete null-safety audit → Fix 10-18% TypeError failures
✅ **P0-R3**: Expand get_node_for_task library → 72% → 95% success rate

**Expected Impact**: Reduce error rate from 5-10% to <2% overall

### Next Release (2-3 Weeks)

✅ **P1-R4**: Batch workflow operations → Save 30-50% tokens
✅ **P1-R5**: Proactive node suggestions → Reduce search iterations
✅ **P1-R6**: Auto-fix suggestions in errors → Self-service recovery

**Expected Impact**: 40% faster workflow creation, better UX

### Future Roadmap (1-3 Months)

✅ **A1**: Service layer consolidation → Cleaner architecture
✅ **A2**: Repository caching → 50% faster node operations
✅ **R10**: Workflow template library from usage → 80% coverage
✅ **T1-T3**: Enhanced telemetry → Better observability

**Expected Impact**: Scalable foundation for 10x growth

## Methodology

### Data Sources

1. **Supabase Telemetry Database**
   - `telemetry_events` table: 212,375 rows
   - `telemetry_workflows` table: 5,751 rows

2. **Analytical Views**
   - Created 15 SQL views for multi-dimensional analysis
   - Enabled complex queries and pattern recognition

3. **CHANGELOG Review**
   - Analyzed recent changes (v2.14.0 - v2.14.6)
   - Correlated fixes with error patterns

### Analysis Approach

1. **Quantitative Analysis**
   - Success/failure rates per tool
   - Performance metrics (avg, median, p95, p99)
   - User segmentation and cohort analysis
   - Temporal trends and growth patterns

2. **Pattern Recognition**
   - Tool sequence analysis (Markov chains)
   - Node co-occurrence patterns
   - Workflow complexity distribution
   - Error clustering and root cause analysis

3. **Qualitative Insights**
   - CHANGELOG integration
   - Error message analysis
   - User journey reconstruction
   - Best practice identification

## How to Use This Analysis

### For Development Priorities

1. Review **P0 Critical Recommendations** (Section 8)
2. Check estimated effort and impact
3. Prioritize based on ROI (impact/effort ratio)
4. Follow implementation guides with code examples

### For Architecture Decisions

1. Review **Architectural Recommendations** (Section 9)
2. Consider service layer consolidation
3. Evaluate repository caching opportunities
4. Plan for 10x scale

### For Product Strategy

1. Review **Usage Patterns** (Section 3 & 5)
2. Understand user segments (power vs casual)
3. Identify high-value features (most-used tools)
4. Focus on reliability over features (96% success rate target)

### For Telemetry Enhancement

1. Review **Telemetry Enhancements** (Section 10)
2. Add fine-grained timing metrics
3. Track workflow creation funnels
4. Monitor node-level analytics

## Contact & Feedback

For questions about this analysis or to request additional insights:
- Data Analyst: Claude Code with Supabase MCP
- Analysis Date: October 2, 2025
- Data Period: September 26 - October 2, 2025

## Change Log

- **2025-10-02**: Initial comprehensive analysis completed
  - 15 SQL analytical views created
  - 13 sections of detailed findings
  - P0/P1/P2 recommendations with implementation guides
  - Code examples and effort estimates provided

## Next Steps

1. ✅ Review findings with development team
2. ✅ Prioritize P0 recommendations for immediate implementation
3. ✅ Plan P1 features for next release cycle
4. ✅ Set up monitoring for key metrics
5. ✅ Schedule follow-up analysis (weekly recommended)

---

*This analysis represents a snapshot of n8n-mcp usage during early adoption phase. Patterns may evolve as the user base grows and matures.*

```

--------------------------------------------------------------------------------
/tests/integration/n8n-api/utils/webhook-workflows.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Webhook Workflow Configuration
 *
 * Provides configuration and setup instructions for webhook workflows
 * required for integration testing.
 *
 * These workflows must be created manually in n8n and activated because
 * the n8n API doesn't support workflow activation.
 */

import { Workflow, WorkflowNode } from '../../../../src/types/n8n-api';

export interface WebhookWorkflowConfig {
  name: string;
  description: string;
  httpMethod: 'GET' | 'POST' | 'PUT' | 'DELETE';
  path: string;
  nodes: Array<Partial<WorkflowNode>>;
  connections: Record<string, any>;
}

/**
 * Configuration for required webhook workflows
 */
export const WEBHOOK_WORKFLOW_CONFIGS: Record<string, WebhookWorkflowConfig> = {
  GET: {
    name: '[MCP-TEST] Webhook GET',
    description: 'Pre-activated webhook for GET method testing',
    httpMethod: 'GET',
    path: 'mcp-test-get',
    nodes: [
      {
        name: 'Webhook',
        type: 'n8n-nodes-base.webhook',
        typeVersion: 2,
        position: [250, 300],
        parameters: {
          httpMethod: 'GET',
          path: 'mcp-test-get',
          responseMode: 'lastNode',
          options: {}
        }
      },
      {
        name: 'Respond to Webhook',
        type: 'n8n-nodes-base.respondToWebhook',
        typeVersion: 1.1,
        position: [450, 300],
        parameters: {
          options: {}
        }
      }
    ],
    connections: {
      Webhook: {
        main: [[{ node: 'Respond to Webhook', type: 'main', index: 0 }]]
      }
    }
  },
  POST: {
    name: '[MCP-TEST] Webhook POST',
    description: 'Pre-activated webhook for POST method testing',
    httpMethod: 'POST',
    path: 'mcp-test-post',
    nodes: [
      {
        name: 'Webhook',
        type: 'n8n-nodes-base.webhook',
        typeVersion: 2,
        position: [250, 300],
        parameters: {
          httpMethod: 'POST',
          path: 'mcp-test-post',
          responseMode: 'lastNode',
          options: {}
        }
      },
      {
        name: 'Respond to Webhook',
        type: 'n8n-nodes-base.respondToWebhook',
        typeVersion: 1.1,
        position: [450, 300],
        parameters: {
          options: {}
        }
      }
    ],
    connections: {
      Webhook: {
        main: [[{ node: 'Respond to Webhook', type: 'main', index: 0 }]]
      }
    }
  },
  PUT: {
    name: '[MCP-TEST] Webhook PUT',
    description: 'Pre-activated webhook for PUT method testing',
    httpMethod: 'PUT',
    path: 'mcp-test-put',
    nodes: [
      {
        name: 'Webhook',
        type: 'n8n-nodes-base.webhook',
        typeVersion: 2,
        position: [250, 300],
        parameters: {
          httpMethod: 'PUT',
          path: 'mcp-test-put',
          responseMode: 'lastNode',
          options: {}
        }
      },
      {
        name: 'Respond to Webhook',
        type: 'n8n-nodes-base.respondToWebhook',
        typeVersion: 1.1,
        position: [450, 300],
        parameters: {
          options: {}
        }
      }
    ],
    connections: {
      Webhook: {
        main: [[{ node: 'Respond to Webhook', type: 'main', index: 0 }]]
      }
    }
  },
  DELETE: {
    name: '[MCP-TEST] Webhook DELETE',
    description: 'Pre-activated webhook for DELETE method testing',
    httpMethod: 'DELETE',
    path: 'mcp-test-delete',
    nodes: [
      {
        name: 'Webhook',
        type: 'n8n-nodes-base.webhook',
        typeVersion: 2,
        position: [250, 300],
        parameters: {
          httpMethod: 'DELETE',
          path: 'mcp-test-delete',
          responseMode: 'lastNode',
          options: {}
        }
      },
      {
        name: 'Respond to Webhook',
        type: 'n8n-nodes-base.respondToWebhook',
        typeVersion: 1.1,
        position: [450, 300],
        parameters: {
          options: {}
        }
      }
    ],
    connections: {
      Webhook: {
        main: [[{ node: 'Respond to Webhook', type: 'main', index: 0 }]]
      }
    }
  }
};

/**
 * Print setup instructions for webhook workflows
 */
export function printSetupInstructions(): void {
  console.log(`
╔════════════════════════════════════════════════════════════════╗
║  WEBHOOK WORKFLOW SETUP REQUIRED                               ║
╠════════════════════════════════════════════════════════════════╣
║                                                                ║
║  Integration tests require 4 pre-activated webhook workflows:  ║
║                                                                ║
║  1. Create workflows manually in n8n UI                        ║
║  2. Use the configurations shown below                         ║
║  3. ACTIVATE each workflow in n8n UI                           ║
║  4. Copy workflow IDs to .env file                             ║
║                                                                ║
╚════════════════════════════════════════════════════════════════╝

Required workflows:
`);

  Object.entries(WEBHOOK_WORKFLOW_CONFIGS).forEach(([method, config]) => {
    console.log(`
${method} Method:
  Name: ${config.name}
  Path: ${config.path}
  .env variable: N8N_TEST_WEBHOOK_${method}_ID

  Workflow Structure:
    1. Webhook node (${method} method, path: ${config.path})
    2. Respond to Webhook node

  After creating:
    1. Save the workflow
    2. ACTIVATE the workflow (toggle in UI)
    3. Copy the workflow ID
    4. Add to .env: N8N_TEST_WEBHOOK_${method}_ID=<workflow-id>
`);
  });

  console.log(`
See docs/local/integration-testing-plan.md for detailed instructions.
`);
}

/**
 * Generate workflow JSON for a webhook workflow
 *
 * @param method - HTTP method
 * @returns Partial workflow ready to create
 */
export function generateWebhookWorkflowJson(
  method: 'GET' | 'POST' | 'PUT' | 'DELETE'
): Partial<Workflow> {
  const config = WEBHOOK_WORKFLOW_CONFIGS[method];

  return {
    name: config.name,
    nodes: config.nodes as any,
    connections: config.connections,
    active: false, // Will need to be activated manually
    settings: {
      executionOrder: 'v1'
    },
    tags: ['mcp-integration-test', 'webhook-test']
  };
}

/**
 * Export all webhook workflow JSONs
 *
 * Returns an object with all 4 webhook workflow configurations
 * ready to be created in n8n.
 *
 * @returns Object with workflow configurations
 */
export function exportAllWebhookWorkflows(): Record<string, Partial<Workflow>> {
  return {
    GET: generateWebhookWorkflowJson('GET'),
    POST: generateWebhookWorkflowJson('POST'),
    PUT: generateWebhookWorkflowJson('PUT'),
    DELETE: generateWebhookWorkflowJson('DELETE')
  };
}

/**
 * Get webhook URL for a given n8n instance and HTTP method
 *
 * @param n8nUrl - n8n instance URL
 * @param method - HTTP method
 * @returns Webhook URL
 */
export function getWebhookUrl(
  n8nUrl: string,
  method: 'GET' | 'POST' | 'PUT' | 'DELETE'
): string {
  const config = WEBHOOK_WORKFLOW_CONFIGS[method];
  const baseUrl = n8nUrl.replace(/\/$/, ''); // Remove trailing slash
  return `${baseUrl}/webhook/${config.path}`;
}

/**
 * Validate webhook workflow structure
 *
 * Checks if a workflow matches the expected webhook workflow structure.
 *
 * @param workflow - Workflow to validate
 * @param method - Expected HTTP method
 * @returns true if valid
 */
export function isValidWebhookWorkflow(
  workflow: Partial<Workflow>,
  method: 'GET' | 'POST' | 'PUT' | 'DELETE'
): boolean {
  if (!workflow.nodes || workflow.nodes.length < 1) {
    return false;
  }

  const webhookNode = workflow.nodes.find(n => n.type === 'n8n-nodes-base.webhook');
  if (!webhookNode) {
    return false;
  }

  const params = webhookNode.parameters as any;
  return params.httpMethod === method;
}

```

--------------------------------------------------------------------------------
/tests/unit/utils/template-node-resolver.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect } from 'vitest';
import { resolveTemplateNodeTypes } from '../../../src/utils/template-node-resolver';

describe('Template Node Resolver', () => {
  describe('resolveTemplateNodeTypes', () => {
    it('should handle bare node names', () => {
      const result = resolveTemplateNodeTypes(['slack']);
      
      expect(result).toContain('n8n-nodes-base.slack');
      expect(result).toContain('n8n-nodes-base.slackTrigger');
    });
    
    it('should handle HTTP variations', () => {
      const result = resolveTemplateNodeTypes(['http']);
      
      expect(result).toContain('n8n-nodes-base.httpRequest');
      expect(result).toContain('n8n-nodes-base.webhook');
    });
    
    it('should handle httpRequest variations', () => {
      const result = resolveTemplateNodeTypes(['httprequest']);
      
      expect(result).toContain('n8n-nodes-base.httpRequest');
    });
    
    it('should handle partial prefix formats', () => {
      const result = resolveTemplateNodeTypes(['nodes-base.webhook']);
      
      expect(result).toContain('n8n-nodes-base.webhook');
      expect(result).not.toContain('nodes-base.webhook');
    });
    
    it('should handle langchain nodes', () => {
      const result = resolveTemplateNodeTypes(['nodes-langchain.agent']);
      
      expect(result).toContain('@n8n/n8n-nodes-langchain.agent');
      expect(result).not.toContain('nodes-langchain.agent');
    });
    
    it('should handle already correct formats', () => {
      const input = ['n8n-nodes-base.slack', '@n8n/n8n-nodes-langchain.agent'];
      const result = resolveTemplateNodeTypes(input);
      
      expect(result).toContain('n8n-nodes-base.slack');
      expect(result).toContain('@n8n/n8n-nodes-langchain.agent');
    });
    
    it('should handle Google services', () => {
      const result = resolveTemplateNodeTypes(['google']);
      
      expect(result).toContain('n8n-nodes-base.googleSheets');
      expect(result).toContain('n8n-nodes-base.googleDrive');
      expect(result).toContain('n8n-nodes-base.googleCalendar');
    });
    
    it('should handle database variations', () => {
      const result = resolveTemplateNodeTypes(['database']);
      
      expect(result).toContain('n8n-nodes-base.postgres');
      expect(result).toContain('n8n-nodes-base.mysql');
      expect(result).toContain('n8n-nodes-base.mongoDb');
      expect(result).toContain('n8n-nodes-base.postgresDatabase');
      expect(result).toContain('n8n-nodes-base.mysqlDatabase');
    });
    
    it('should handle AI/LLM variations', () => {
      const result = resolveTemplateNodeTypes(['ai']);
      
      expect(result).toContain('n8n-nodes-base.openAi');
      expect(result).toContain('@n8n/n8n-nodes-langchain.agent');
      expect(result).toContain('@n8n/n8n-nodes-langchain.lmChatOpenAi');
    });
    
    it('should handle email variations', () => {
      const result = resolveTemplateNodeTypes(['email']);
      
      expect(result).toContain('n8n-nodes-base.emailSend');
      expect(result).toContain('n8n-nodes-base.emailReadImap');
      expect(result).toContain('n8n-nodes-base.gmail');
      expect(result).toContain('n8n-nodes-base.gmailTrigger');
    });
    
    it('should handle schedule/cron variations', () => {
      const result = resolveTemplateNodeTypes(['schedule']);
      
      expect(result).toContain('n8n-nodes-base.scheduleTrigger');
      expect(result).toContain('n8n-nodes-base.cron');
    });
    
    it('should handle multiple inputs', () => {
      const result = resolveTemplateNodeTypes(['slack', 'webhook', 'http']);
      
      expect(result).toContain('n8n-nodes-base.slack');
      expect(result).toContain('n8n-nodes-base.slackTrigger');
      expect(result).toContain('n8n-nodes-base.webhook');
      expect(result).toContain('n8n-nodes-base.httpRequest');
    });
    
    it('should not duplicate entries', () => {
      const result = resolveTemplateNodeTypes(['slack', 'n8n-nodes-base.slack']);
      
      const slackCount = result.filter(r => r === 'n8n-nodes-base.slack').length;
      expect(slackCount).toBe(1);
    });
    
    it('should handle mixed case inputs', () => {
      const result = resolveTemplateNodeTypes(['Slack', 'WEBHOOK', 'HttpRequest']);
      
      expect(result).toContain('n8n-nodes-base.slack');
      expect(result).toContain('n8n-nodes-base.webhook');
      expect(result).toContain('n8n-nodes-base.httpRequest');
    });
    
    it('should handle common misspellings', () => {
      const result = resolveTemplateNodeTypes(['postgres', 'postgresql']);
      
      expect(result).toContain('n8n-nodes-base.postgres');
      expect(result).toContain('n8n-nodes-base.postgresDatabase');
    });
    
    it('should handle code/javascript/python variations', () => {
      const result = resolveTemplateNodeTypes(['javascript', 'python', 'js']);
      
      result.forEach(() => {
        expect(result).toContain('n8n-nodes-base.code');
      });
    });
    
    it('should handle trigger suffix variations', () => {
      const result = resolveTemplateNodeTypes(['slacktrigger', 'gmailtrigger']);
      
      expect(result).toContain('n8n-nodes-base.slackTrigger');
      expect(result).toContain('n8n-nodes-base.gmailTrigger');
    });
    
    it('should handle sheet/sheets variations', () => {
      const result = resolveTemplateNodeTypes(['googlesheet', 'googlesheets']);
      
      result.forEach(() => {
        expect(result).toContain('n8n-nodes-base.googleSheets');
      });
    });
    
    it('should return empty array for empty input', () => {
      const result = resolveTemplateNodeTypes([]);
      
      expect(result).toEqual([]);
    });
  });
  
  describe('Edge cases', () => {
    it('should handle undefined-like strings gracefully', () => {
      const result = resolveTemplateNodeTypes(['undefined', 'null', '']);
      
      // Should process them as regular strings
      expect(result).toBeDefined();
      expect(Array.isArray(result)).toBe(true);
    });
    
    it('should handle very long node names', () => {
      const longName = 'a'.repeat(100);
      const result = resolveTemplateNodeTypes([longName]);
      
      expect(result).toBeDefined();
      expect(Array.isArray(result)).toBe(true);
    });
    
    it('should handle special characters in node names', () => {
      const result = resolveTemplateNodeTypes(['node-with-dashes', 'node_with_underscores']);
      
      expect(result).toBeDefined();
      expect(Array.isArray(result)).toBe(true);
    });
  });
  
  describe('Real-world scenarios from AI agents', () => {
    it('should handle common AI agent queries', () => {
      // These are actual queries that AI agents commonly try
      const testCases = [
        { input: ['slack'], shouldContain: 'n8n-nodes-base.slack' },
        { input: ['webhook'], shouldContain: 'n8n-nodes-base.webhook' },
        { input: ['http'], shouldContain: 'n8n-nodes-base.httpRequest' },
        { input: ['email'], shouldContain: 'n8n-nodes-base.gmail' },
        { input: ['gpt'], shouldContain: 'n8n-nodes-base.openAi' },
        { input: ['chatgpt'], shouldContain: 'n8n-nodes-base.openAi' },
        { input: ['agent'], shouldContain: '@n8n/n8n-nodes-langchain.agent' },
        { input: ['sql'], shouldContain: 'n8n-nodes-base.postgres' },
        { input: ['api'], shouldContain: 'n8n-nodes-base.httpRequest' },
        { input: ['csv'], shouldContain: 'n8n-nodes-base.spreadsheetFile' },
      ];
      
      testCases.forEach(({ input, shouldContain }) => {
        const result = resolveTemplateNodeTypes(input);
        expect(result).toContain(shouldContain);
      });
    });
  });
});
```

--------------------------------------------------------------------------------
/tests/integration/setup/msw-test-server.ts:
--------------------------------------------------------------------------------

```typescript
import { setupServer } from 'msw/node';
import { HttpResponse, http } from 'msw';
import type { RequestHandler } from 'msw';
import { handlers as defaultHandlers } from '../../mocks/n8n-api/handlers';

/**
 * MSW server instance for integration tests
 * This is separate from the global MSW setup to allow for more control
 * in integration tests that may need specific handler configurations
 */
export const integrationTestServer = setupServer(...defaultHandlers);

/**
 * Enhanced server controls for integration tests
 */
export const mswTestServer = {
  /**
   * Start the server with specific options
   */
  start: (options?: {
    onUnhandledRequest?: 'error' | 'warn' | 'bypass';
    quiet?: boolean;
  }) => {
    integrationTestServer.listen({
      onUnhandledRequest: options?.onUnhandledRequest || 'warn',
    });

    if (!options?.quiet && process.env.MSW_DEBUG === 'true') {
      integrationTestServer.events.on('request:start', ({ request }) => {
        console.log('[Integration MSW] %s %s', request.method, request.url);
      });
    }
  },

  /**
   * Stop the server
   */
  stop: () => {
    integrationTestServer.close();
  },

  /**
   * Reset handlers to defaults
   */
  reset: () => {
    integrationTestServer.resetHandlers();
  },

  /**
   * Add handlers for a specific test
   */
  use: (...handlers: RequestHandler[]) => {
    integrationTestServer.use(...handlers);
  },

  /**
   * Replace all handlers (useful for isolated test scenarios)
   */
  replaceAll: (...handlers: RequestHandler[]) => {
    integrationTestServer.resetHandlers(...handlers);
  },

  /**
   * Wait for a specific number of requests to be made
   */
  waitForRequests: (count: number, timeout = 5000): Promise<Request[]> => {
    return new Promise((resolve, reject) => {
      const requests: Request[] = [];
      let timeoutId: NodeJS.Timeout | null = null;
      
      // Event handler function to allow cleanup
      const handleRequest = ({ request }: { request: Request }) => {
        requests.push(request);
        if (requests.length === count) {
          cleanup();
          resolve(requests);
        }
      };
      
      // Cleanup function to remove listener and clear timeout
      const cleanup = () => {
        if (timeoutId) {
          clearTimeout(timeoutId);
          timeoutId = null;
        }
        integrationTestServer.events.removeListener('request:match', handleRequest);
      };
      
      // Set timeout
      timeoutId = setTimeout(() => {
        cleanup();
        reject(new Error(`Timeout waiting for ${count} requests. Got ${requests.length}`));
      }, timeout);
      
      // Add event listener
      integrationTestServer.events.on('request:match', handleRequest);
    });
  },

  /**
   * Verify no unhandled requests were made
   */
  verifyNoUnhandledRequests: (): Promise<void> => {
    return new Promise((resolve, reject) => {
      let hasUnhandled = false;
      let timeoutId: NodeJS.Timeout | null = null;
      
      const handleUnhandled = ({ request }: { request: Request }) => {
        hasUnhandled = true;
        cleanup();
        reject(new Error(`Unhandled request: ${request.method} ${request.url}`));
      };
      
      const cleanup = () => {
        if (timeoutId) {
          clearTimeout(timeoutId);
          timeoutId = null;
        }
        integrationTestServer.events.removeListener('request:unhandled', handleUnhandled);
      };
      
      // Add event listener
      integrationTestServer.events.on('request:unhandled', handleUnhandled);

      // Give a small delay to allow any pending requests
      timeoutId = setTimeout(() => {
        cleanup();
        if (!hasUnhandled) {
          resolve();
        }
      }, 100);
    });
  },

  /**
   * Create a scoped server for a specific test
   * Automatically starts and stops the server
   */
  withScope: async <T>(
    handlers: RequestHandler[],
    testFn: () => Promise<T>
  ): Promise<T> => {
    // Save current handlers
    const currentHandlers = [...defaultHandlers];
    
    try {
      // Replace with scoped handlers
      integrationTestServer.resetHandlers(...handlers);
      
      // Run the test
      return await testFn();
    } finally {
      // Restore original handlers
      integrationTestServer.resetHandlers(...currentHandlers);
    }
  }
};

/**
 * Integration test utilities for n8n API mocking
 */
export const n8nApiMock = {
  /**
   * Mock a successful workflow creation
   */
  mockWorkflowCreate: (response?: any) => {
    return http.post('*/api/v1/workflows', async ({ request }) => {
      const body = await request.json() as Record<string, any>;
      return HttpResponse.json({
        data: {
          id: 'test-workflow-id',
          ...body,
          ...response,
          createdAt: new Date().toISOString(),
          updatedAt: new Date().toISOString()
        }
      }, { status: 201 });
    });
  },

  /**
   * Mock a workflow validation endpoint
   */
  mockWorkflowValidate: (validationResult: { valid: boolean; errors?: any[] }) => {
    return http.post('*/api/v1/workflows/validate', async () => {
      return HttpResponse.json(validationResult);
    });
  },

  /**
   * Mock webhook execution
   */
  mockWebhookExecution: (webhookPath: string, response: any) => {
    return http.all(`*/webhook/${webhookPath}`, async ({ request }) => {
      const body = request.body ? await request.json() : undefined;
      
      // Simulate webhook processing
      return HttpResponse.json({
        ...response,
        webhookReceived: {
          path: webhookPath,
          method: request.method,
          body,
          timestamp: new Date().toISOString()
        }
      });
    });
  },

  /**
   * Mock API error responses
   */
  mockError: (endpoint: string, error: { status: number; message: string; code?: string }) => {
    return http.all(endpoint, () => {
      return HttpResponse.json(
        {
          message: error.message,
          code: error.code || 'ERROR',
          timestamp: new Date().toISOString()
        },
        { status: error.status }
      );
    });
  },

  /**
   * Mock rate limiting
   */
  mockRateLimit: (endpoint: string) => {
    let requestCount = 0;
    const limit = 5;
    
    return http.all(endpoint, () => {
      requestCount++;
      
      if (requestCount > limit) {
        return HttpResponse.json(
          {
            message: 'Rate limit exceeded',
            code: 'RATE_LIMIT',
            retryAfter: 60
          },
          {
            status: 429,
            headers: {
              'X-RateLimit-Limit': String(limit),
              'X-RateLimit-Remaining': '0',
              'X-RateLimit-Reset': String(Date.now() + 60000)
            }
          }
        );
      }
      
      return HttpResponse.json({ success: true });
    });
  }
};

/**
 * Test data builders for integration tests
 */
export const testDataBuilders = {
  /**
   * Build a workflow for testing
   */
  workflow: (overrides?: any) => ({
    name: 'Integration Test Workflow',
    nodes: [
      {
        id: 'start',
        name: 'Start',
        type: 'n8n-nodes-base.start',
        typeVersion: 1,
        position: [250, 300],
        parameters: {}
      }
    ],
    connections: {},
    settings: {},
    active: false,
    ...overrides
  }),

  /**
   * Build an execution result
   */
  execution: (workflowId: string, overrides?: any) => ({
    id: `exec_${Date.now()}`,
    workflowId,
    status: 'success',
    mode: 'manual',
    startedAt: new Date().toISOString(),
    stoppedAt: new Date().toISOString(),
    data: {
      resultData: {
        runData: {}
      }
    },
    ...overrides
  })
};
```

--------------------------------------------------------------------------------
/scripts/test-expression-format-validation.js:
--------------------------------------------------------------------------------

```javascript
#!/usr/bin/env node

/**
 * Test script for expression format validation
 * Tests the validation of expression prefixes and resource locator formats
 */

const { WorkflowValidator } = require('../dist/services/workflow-validator.js');
const { NodeRepository } = require('../dist/database/node-repository.js');
const { EnhancedConfigValidator } = require('../dist/services/enhanced-config-validator.js');
const { createDatabaseAdapter } = require('../dist/database/database-adapter.js');
const path = require('path');

async function runTests() {
  // Initialize database
  const dbPath = path.join(__dirname, '..', 'data', 'nodes.db');
  const adapter = await createDatabaseAdapter(dbPath);
  const db = adapter;

  const nodeRepository = new NodeRepository(db);
  const validator = new WorkflowValidator(nodeRepository, EnhancedConfigValidator);

  console.log('\n🧪 Testing Expression Format Validation\n');
  console.log('=' .repeat(60));

  // Test 1: Email node with missing = prefix
  console.log('\n📝 Test 1: Email Send node - Missing = prefix');
  console.log('-'.repeat(40));

  const emailWorkflowIncorrect = {
    nodes: [
      {
        id: 'b9dd1cfd-ee66-4049-97e7-1af6d976a4e0',
        name: 'Error Handler',
        type: 'n8n-nodes-base.emailSend',
        typeVersion: 2.1,
        position: [-128, 400],
        parameters: {
          fromEmail: '{{ $env.ADMIN_EMAIL }}',  // INCORRECT - missing =
          toEmail: '[email protected]',
          subject: 'GitHub Issue Workflow Error - HIGH PRIORITY',
          options: {}
        },
        credentials: {
          smtp: {
            id: '7AQ08VMFHubmfvzR',
            name: '[email protected]'
          }
        }
      }
    ],
    connections: {}
  };

  const result1 = await validator.validateWorkflow(emailWorkflowIncorrect);

  if (result1.errors.some(e => e.message.includes('Expression format'))) {
    console.log('✅ ERROR DETECTED (correct behavior):');
    const formatError = result1.errors.find(e => e.message.includes('Expression format'));
    console.log('\n' + formatError.message);
  } else {
    console.log('❌ No expression format error detected (should have detected missing prefix)');
  }

  // Test 2: Email node with correct = prefix
  console.log('\n📝 Test 2: Email Send node - Correct = prefix');
  console.log('-'.repeat(40));

  const emailWorkflowCorrect = {
    nodes: [
      {
        id: 'b9dd1cfd-ee66-4049-97e7-1af6d976a4e0',
        name: 'Error Handler',
        type: 'n8n-nodes-base.emailSend',
        typeVersion: 2.1,
        position: [-128, 400],
        parameters: {
          fromEmail: '={{ $env.ADMIN_EMAIL }}',  // CORRECT - has =
          toEmail: '[email protected]',
          subject: 'GitHub Issue Workflow Error - HIGH PRIORITY',
          options: {}
        }
      }
    ],
    connections: {}
  };

  const result2 = await validator.validateWorkflow(emailWorkflowCorrect);

  if (result2.errors.some(e => e.message.includes('Expression format'))) {
    console.log('❌ Unexpected expression format error (should accept = prefix)');
  } else {
    console.log('✅ No expression format errors (correct!)');
  }

  // Test 3: GitHub node without resource locator format
  console.log('\n📝 Test 3: GitHub node - Missing resource locator format');
  console.log('-'.repeat(40));

  const githubWorkflowIncorrect = {
    nodes: [
      {
        id: '3c742ca1-af8f-4d80-a47e-e68fb1ced491',
        name: 'Send Welcome Comment',
        type: 'n8n-nodes-base.github',
        typeVersion: 1.1,
        position: [-240, 96],
        parameters: {
          operation: 'createComment',
          owner: '{{ $vars.GITHUB_OWNER }}',  // INCORRECT - needs RL format
          repository: '{{ $vars.GITHUB_REPO }}',  // INCORRECT - needs RL format
          issueNumber: null,
          body: '👋 Hi @{{ $(\'Extract Issue Data\').first().json.author }}!'  // INCORRECT - missing =
        },
        credentials: {
          githubApi: {
            id: 'edgpwh6ldYN07MXx',
            name: 'GitHub account'
          }
        }
      }
    ],
    connections: {}
  };

  const result3 = await validator.validateWorkflow(githubWorkflowIncorrect);

  const formatErrors = result3.errors.filter(e => e.message.includes('Expression format'));
  console.log(`\nFound ${formatErrors.length} expression format errors:`);

  if (formatErrors.length >= 3) {
    console.log('✅ All format issues detected:');
    formatErrors.forEach((error, index) => {
      const field = error.message.match(/Field '([^']+)'/)?.[1] || 'unknown';
      console.log(`  ${index + 1}. Field '${field}' - ${error.message.includes('resource locator') ? 'Needs RL format' : 'Missing = prefix'}`);
    });
  } else {
    console.log('❌ Not all format issues detected');
  }

  // Test 4: GitHub node with correct resource locator format
  console.log('\n📝 Test 4: GitHub node - Correct resource locator format');
  console.log('-'.repeat(40));

  const githubWorkflowCorrect = {
    nodes: [
      {
        id: '3c742ca1-af8f-4d80-a47e-e68fb1ced491',
        name: 'Send Welcome Comment',
        type: 'n8n-nodes-base.github',
        typeVersion: 1.1,
        position: [-240, 96],
        parameters: {
          operation: 'createComment',
          owner: {
            __rl: true,
            value: '={{ $vars.GITHUB_OWNER }}',  // CORRECT - RL format with =
            mode: 'expression'
          },
          repository: {
            __rl: true,
            value: '={{ $vars.GITHUB_REPO }}',  // CORRECT - RL format with =
            mode: 'expression'
          },
          issueNumber: 123,
          body: '=👋 Hi @{{ $(\'Extract Issue Data\').first().json.author }}!'  // CORRECT - has =
        }
      }
    ],
    connections: {}
  };

  const result4 = await validator.validateWorkflow(githubWorkflowCorrect);

  const formatErrors4 = result4.errors.filter(e => e.message.includes('Expression format'));
  if (formatErrors4.length === 0) {
    console.log('✅ No expression format errors (correct!)');
  } else {
    console.log(`❌ Unexpected expression format errors: ${formatErrors4.length}`);
    formatErrors4.forEach(e => console.log('  - ' + e.message.split('\n')[0]));
  }

  // Test 5: Mixed content expressions
  console.log('\n📝 Test 5: Mixed content with expressions');
  console.log('-'.repeat(40));

  const mixedContentWorkflow = {
    nodes: [
      {
        id: '1',
        name: 'HTTP Request',
        type: 'n8n-nodes-base.httpRequest',
        typeVersion: 4,
        position: [0, 0],
        parameters: {
          url: 'https://api.example.com/users/{{ $json.userId }}',  // INCORRECT
          headers: {
            'Authorization': '=Bearer {{ $env.API_TOKEN }}'  // CORRECT
          }
        }
      }
    ],
    connections: {}
  };

  const result5 = await validator.validateWorkflow(mixedContentWorkflow);

  const urlError = result5.errors.find(e => e.message.includes('url') && e.message.includes('Expression format'));
  if (urlError) {
    console.log('✅ Mixed content error detected for URL field');
    console.log('  Should be: "=https://api.example.com/users/{{ $json.userId }}"');
  } else {
    console.log('❌ Mixed content error not detected');
  }

  console.log('\n' + '='.repeat(60));
  console.log('\n✨ Expression Format Validation Summary:');
  console.log('  - Detects missing = prefix in expressions');
  console.log('  - Identifies fields needing resource locator format');
  console.log('  - Provides clear correction examples');
  console.log('  - Handles mixed literal and expression content');

  // Close database
  db.close();
}

runTests().catch(error => {
  console.error('Test failed:', error);
  process.exit(1);
});
```

--------------------------------------------------------------------------------
/tests/integration/n8n-api/workflows/get-workflow-details.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Integration Tests: handleGetWorkflowDetails
 *
 * Tests workflow details retrieval against a real n8n instance.
 * Covers basic workflows, metadata, version history, and execution stats.
 */

import { describe, it, expect, beforeEach, afterEach, afterAll } from 'vitest';
import { createTestContext, TestContext, createTestWorkflowName } from '../utils/test-context';
import { getTestN8nClient } from '../utils/n8n-client';
import { N8nApiClient } from '../../../../src/services/n8n-api-client';
import { SIMPLE_WEBHOOK_WORKFLOW } from '../utils/fixtures';
import { cleanupOrphanedWorkflows } from '../utils/cleanup-helpers';
import { createMcpContext } from '../utils/mcp-context';
import { InstanceContext } from '../../../../src/types/instance-context';
import { handleGetWorkflowDetails } from '../../../../src/mcp/handlers-n8n-manager';

describe('Integration: handleGetWorkflowDetails', () => {
  let context: TestContext;
  let client: N8nApiClient;
  let mcpContext: InstanceContext;

  beforeEach(() => {
    context = createTestContext();
    client = getTestN8nClient();
    mcpContext = createMcpContext();
  });

  afterEach(async () => {
    await context.cleanup();
  });

  afterAll(async () => {
    if (!process.env.CI) {
      await cleanupOrphanedWorkflows();
    }
  });

  // ======================================================================
  // Basic Workflow Details
  // ======================================================================

  describe('Basic Workflow', () => {
    it('should retrieve workflow with basic details', async () => {
      // Create a simple workflow
      const workflow = {
        ...SIMPLE_WEBHOOK_WORKFLOW,
        name: createTestWorkflowName('Get Details - Basic'),
        tags: ['mcp-integration-test']
      };

      const created = await client.createWorkflow(workflow);
      expect(created).toBeDefined();
      expect(created.id).toBeTruthy();

      if (!created.id) throw new Error('Workflow ID is missing');
      context.trackWorkflow(created.id);

      // Retrieve detailed workflow information using MCP handler
      const response = await handleGetWorkflowDetails({ id: created.id }, mcpContext);

      // Verify MCP response structure
      expect(response.success).toBe(true);
      expect(response.data).toBeDefined();

      // handleGetWorkflowDetails returns { workflow, executionStats, hasWebhookTrigger, webhookPath }
      const details = (response.data as any).workflow;

      // Verify basic details
      expect(details).toBeDefined();
      expect(details.id).toBe(created.id);
      expect(details.name).toBe(workflow.name);
      expect(details.createdAt).toBeDefined();
      expect(details.updatedAt).toBeDefined();
      expect(details.active).toBeDefined();

      // Verify metadata fields
      expect(details.versionId).toBeDefined();
    });
  });

  // ======================================================================
  // Workflow with Metadata
  // ======================================================================

  describe('Workflow with Metadata', () => {
    it('should retrieve workflow with tags and settings metadata', async () => {
      // Create workflow with rich metadata
      const workflow = {
        ...SIMPLE_WEBHOOK_WORKFLOW,
        name: createTestWorkflowName('Get Details - With Metadata'),
        tags: [
          'mcp-integration-test',
          'test-category',
          'integration'
        ],
        settings: {
          executionOrder: 'v1' as const,
          timezone: 'America/New_York'
        }
      };

      const created = await client.createWorkflow(workflow);
      expect(created).toBeDefined();
      expect(created.id).toBeTruthy();

      if (!created.id) throw new Error('Workflow ID is missing');
      context.trackWorkflow(created.id);

      // Retrieve workflow details using MCP handler
      const response = await handleGetWorkflowDetails({ id: created.id }, mcpContext);
      expect(response.success).toBe(true);
      const details = (response.data as any).workflow;

      // Verify metadata is present (tags may be undefined in API response)
      // Note: n8n API behavior for tags varies - they may not be returned
      // in GET requests even if set during creation
      if (details.tags) {
        expect(details.tags.length).toBeGreaterThanOrEqual(0);
      }

      // Verify settings
      expect(details.settings).toBeDefined();
      expect(details.settings!.executionOrder).toBe('v1');
      expect(details.settings!.timezone).toBe('America/New_York');
    });
  });

  // ======================================================================
  // Version History
  // ======================================================================

  describe('Version History', () => {
    it('should track version changes after updates', async () => {
      // Create initial workflow
      const workflow = {
        ...SIMPLE_WEBHOOK_WORKFLOW,
        name: createTestWorkflowName('Get Details - Version History'),
        tags: ['mcp-integration-test']
      };

      const created = await client.createWorkflow(workflow);
      expect(created).toBeDefined();
      expect(created.id).toBeTruthy();

      if (!created.id) throw new Error('Workflow ID is missing');
      context.trackWorkflow(created.id);

      // Get initial version using MCP handler
      const initialResponse = await handleGetWorkflowDetails({ id: created.id }, mcpContext);
      expect(initialResponse.success).toBe(true);
      const initialDetails = (initialResponse.data as any).workflow;
      const initialVersionId = initialDetails.versionId;
      const initialUpdatedAt = initialDetails.updatedAt;

      // Update the workflow
      await client.updateWorkflow(created.id, {
        name: createTestWorkflowName('Get Details - Version History (Updated)'),
        nodes: workflow.nodes,
        connections: workflow.connections
      });

      // Get updated details using MCP handler
      const updatedResponse = await handleGetWorkflowDetails({ id: created.id }, mcpContext);
      expect(updatedResponse.success).toBe(true);
      const updatedDetails = (updatedResponse.data as any).workflow;

      // Verify version changed
      expect(updatedDetails.versionId).toBeDefined();
      expect(updatedDetails.updatedAt).not.toBe(initialUpdatedAt);

      // Version ID should have changed after update
      expect(updatedDetails.versionId).not.toBe(initialVersionId);
    });
  });

  // ======================================================================
  // Execution Statistics
  // ======================================================================

  describe('Execution Statistics', () => {
    it('should include execution-related fields in details', async () => {
      // Create workflow
      const workflow = {
        ...SIMPLE_WEBHOOK_WORKFLOW,
        name: createTestWorkflowName('Get Details - Execution Stats'),
        tags: ['mcp-integration-test']
      };

      const created = await client.createWorkflow(workflow);
      expect(created).toBeDefined();
      expect(created.id).toBeTruthy();

      if (!created.id) throw new Error('Workflow ID is missing');
      context.trackWorkflow(created.id);

      // Retrieve workflow details using MCP handler
      const response = await handleGetWorkflowDetails({ id: created.id }, mcpContext);
      expect(response.success).toBe(true);
      const details = (response.data as any).workflow;

      // Verify execution-related fields exist
      // Note: New workflows won't have executions, but fields should be present
      expect(details).toHaveProperty('active');

      // The workflow should start inactive
      expect(details.active).toBe(false);
    });
  });
});

```

--------------------------------------------------------------------------------
/tests/unit/test-env-example.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Example test demonstrating test environment configuration usage
 */

import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { 
  getTestConfig, 
  getTestTimeout, 
  isFeatureEnabled,
  isTestMode,
  loadTestEnvironment 
} from '@tests/setup/test-env';
import {
  withEnvOverrides,
  createTestDatabasePath,
  getMockApiUrl,
  measurePerformance,
  createTestLogger,
  waitForCondition
} from '@tests/helpers/env-helpers';

describe('Test Environment Configuration Example', () => {
  let config: ReturnType<typeof getTestConfig>;
  let logger: ReturnType<typeof createTestLogger>;
  
  beforeAll(() => {
    // Initialize config inside beforeAll to ensure environment is loaded
    config = getTestConfig();
    logger = createTestLogger('test-env-example');
    
    logger.info('Test suite starting with configuration:', {
      environment: config.nodeEnv,
      database: config.database.path,
      apiUrl: config.api.url
    });
  });
  
  afterAll(() => {
    logger.info('Test suite completed');
  });
  
  it('should be in test mode', () => {
    const testConfig = getTestConfig();
    expect(isTestMode()).toBe(true);
    expect(testConfig.nodeEnv).toBe('test');
    expect(testConfig.isTest).toBe(true);
  });
  
  it('should have proper database configuration', () => {
    const testConfig = getTestConfig();
    expect(testConfig.database.path).toBeDefined();
    expect(testConfig.database.rebuildOnStart).toBe(false);
    expect(testConfig.database.seedData).toBe(true);
  });
  
  it.skip('should have mock API configuration', () => {
    const testConfig = getTestConfig();
    // Add debug logging for CI
    if (process.env.CI) {
      console.log('CI Environment Debug:', {
        NODE_ENV: process.env.NODE_ENV,
        N8N_API_URL: process.env.N8N_API_URL,
        N8N_API_KEY: process.env.N8N_API_KEY,
        configUrl: testConfig.api.url,
        configKey: testConfig.api.key
      });
    }
    expect(testConfig.api.url).toMatch(/mock-api/);
    expect(testConfig.api.key).toBe('test-api-key-12345');
  });
  
  it('should respect test timeouts', { timeout: getTestTimeout('unit') }, async () => {
    const timeout = getTestTimeout('unit');
    expect(timeout).toBe(5000);
    
    // Simulate async operation
    await new Promise(resolve => setTimeout(resolve, 100));
  });
  
  it('should support environment overrides', () => {
    const testConfig = getTestConfig();
    const originalLogLevel = testConfig.logging.level;
    
    const result = withEnvOverrides({
      LOG_LEVEL: 'debug',
      DEBUG: 'true'
    }, () => {
      const newConfig = getTestConfig();
      expect(newConfig.logging.level).toBe('debug');
      expect(newConfig.logging.debug).toBe(true);
      return 'success';
    });
    
    expect(result).toBe('success');
    const configAfter = getTestConfig();
    expect(configAfter.logging.level).toBe(originalLogLevel);
  });
  
  it('should generate unique test database paths', () => {
    const path1 = createTestDatabasePath('feature1');
    const path2 = createTestDatabasePath('feature1');
    
    if (path1 !== ':memory:') {
      expect(path1).not.toBe(path2);
      expect(path1).toMatch(/test-feature1-\d+-\w+\.db$/);
    }
  });
  
  it('should construct mock API URLs', () => {
    const testConfig = getTestConfig();
    const baseUrl = getMockApiUrl();
    const endpointUrl = getMockApiUrl('/nodes');
    
    expect(baseUrl).toBe(testConfig.api.url);
    expect(endpointUrl).toBe(`${testConfig.api.url}/nodes`);
  });
  
  it.skipIf(!isFeatureEnabled('mockExternalApis'))('should check feature flags', () => {
    const testConfig = getTestConfig();
    expect(testConfig.features.mockExternalApis).toBe(true);
    expect(isFeatureEnabled('mockExternalApis')).toBe(true);
  });
  
  it('should measure performance', () => {
    const measure = measurePerformance('test-operation');
    
    // Test the performance measurement utility structure and behavior
    // rather than relying on timing precision which is unreliable in CI
    
    // Capture initial state
    const startTime = performance.now();
    
    // Add some marks
    measure.mark('start-processing');
    
    // Do some minimal synchronous work
    let sum = 0;
    for (let i = 0; i < 10000; i++) {
      sum += i;
    }
    
    measure.mark('mid-processing');
    
    // Do a bit more work
    for (let i = 0; i < 10000; i++) {
      sum += i * 2;
    }
    
    const results = measure.end();
    const endTime = performance.now();
    
    // Test the utility's correctness rather than exact timing
    expect(results).toHaveProperty('total');
    expect(results).toHaveProperty('marks');
    expect(typeof results.total).toBe('number');
    expect(results.total).toBeGreaterThan(0);
    
    // Verify marks structure
    expect(results.marks).toHaveProperty('start-processing');
    expect(results.marks).toHaveProperty('mid-processing');
    expect(typeof results.marks['start-processing']).toBe('number');
    expect(typeof results.marks['mid-processing']).toBe('number');
    
    // Verify logical order of marks (this should always be true)
    expect(results.marks['start-processing']).toBeLessThan(results.marks['mid-processing']);
    expect(results.marks['start-processing']).toBeGreaterThanOrEqual(0);
    expect(results.marks['mid-processing']).toBeLessThan(results.total);
    
    // Verify the total time is reasonable (should be between manual measurements)
    const manualTotal = endTime - startTime;
    expect(results.total).toBeLessThanOrEqual(manualTotal + 1); // Allow 1ms tolerance
    
    // Verify work was actually done
    expect(sum).toBeGreaterThan(0);
  });
  
  it('should wait for conditions', async () => {
    let counter = 0;
    const incrementCounter = setInterval(() => counter++, 100);
    
    try {
      await waitForCondition(
        () => counter >= 3,
        { 
          timeout: 1000, 
          interval: 50,
          message: 'Counter did not reach 3'
        }
      );
      
      expect(counter).toBeGreaterThanOrEqual(3);
    } finally {
      clearInterval(incrementCounter);
    }
  });
  
  it('should have proper logging configuration', () => {
    const testConfig = getTestConfig();
    expect(testConfig.logging.level).toBe('error');
    expect(testConfig.logging.debug).toBe(false);
    expect(testConfig.logging.showStack).toBe(true);
    
    // Logger should respect configuration
    logger.debug('This should not appear in test output');
    logger.error('This should appear in test output');
  });
  
  it('should have performance thresholds', () => {
    const testConfig = getTestConfig();
    expect(testConfig.performance.thresholds.apiResponse).toBe(100);
    expect(testConfig.performance.thresholds.dbQuery).toBe(50);
    expect(testConfig.performance.thresholds.nodeParse).toBe(200);
  });
  
  it('should disable caching and rate limiting in tests', () => {
    const testConfig = getTestConfig();
    expect(testConfig.cache.enabled).toBe(false);
    expect(testConfig.cache.ttl).toBe(0);
    expect(testConfig.rateLimiting.max).toBe(0);
    expect(testConfig.rateLimiting.window).toBe(0);
  });
  
  it('should configure test paths', () => {
    const testConfig = getTestConfig();
    expect(testConfig.paths.fixtures).toBe('./tests/fixtures');
    expect(testConfig.paths.data).toBe('./tests/data');
    expect(testConfig.paths.snapshots).toBe('./tests/__snapshots__');
  });
  
  it('should support MSW configuration', () => {
    // Ensure test environment is loaded
    if (!process.env.MSW_ENABLED) {
      loadTestEnvironment();
    }
    
    const testConfig = getTestConfig();
    expect(testConfig.mocking.msw.enabled).toBe(true);
    expect(testConfig.mocking.msw.apiDelay).toBe(0);
  });
});
```

--------------------------------------------------------------------------------
/tests/unit/examples/using-n8n-nodes-base-mock.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { getNodeTypes, mockNodeBehavior, resetAllMocks } from '../__mocks__/n8n-nodes-base';

// Example service that uses n8n-nodes-base
class WorkflowService {
  async getNodeDescription(nodeName: string) {
    const nodeTypes = getNodeTypes();
    const node = nodeTypes.getByName(nodeName);
    return node?.description;
  }

  async executeNode(nodeName: string, context: any) {
    const nodeTypes = getNodeTypes();
    const node = nodeTypes.getByName(nodeName);
    
    if (!node?.execute) {
      throw new Error(`Node ${nodeName} does not have an execute method`);
    }
    
    return node.execute.call(context);
  }

  async validateSlackMessage(channel: string, text: string) {
    if (!channel || !text) {
      throw new Error('Channel and text are required');
    }
    
    const nodeTypes = getNodeTypes();
    const slackNode = nodeTypes.getByName('slack');
    
    if (!slackNode) {
      throw new Error('Slack node not found');
    }
    
    // Check if required properties exist
    const channelProp = slackNode.description.properties.find(p => p.name === 'channel');
    const textProp = slackNode.description.properties.find(p => p.name === 'text');
    
    return !!(channelProp && textProp);
  }
}

// Mock the module at the top level
vi.mock('n8n-nodes-base', () => {
  const { getNodeTypes: mockGetNodeTypes } = require('../__mocks__/n8n-nodes-base');
  return {
    getNodeTypes: mockGetNodeTypes
  };
});

describe('WorkflowService with n8n-nodes-base mock', () => {
  let service: WorkflowService;

  beforeEach(() => {
    resetAllMocks();
    service = new WorkflowService();
  });

  describe('getNodeDescription', () => {
    it('should get webhook node description', async () => {
      const description = await service.getNodeDescription('webhook');
      
      expect(description).toBeDefined();
      expect(description?.name).toBe('webhook');
      expect(description?.group).toContain('trigger');
      expect(description?.webhooks).toBeDefined();
    });

    it('should get httpRequest node description', async () => {
      const description = await service.getNodeDescription('httpRequest');
      
      expect(description).toBeDefined();
      expect(description?.name).toBe('httpRequest');
      expect(description?.version).toBe(3);
      
      const methodProp = description?.properties.find(p => p.name === 'method');
      expect(methodProp).toBeDefined();
      expect(methodProp?.options).toHaveLength(6);
    });
  });

  describe('executeNode', () => {
    it('should execute httpRequest node with custom response', async () => {
      // Override the httpRequest node behavior for this test
      mockNodeBehavior('httpRequest', {
        execute: vi.fn(async function(this: any) {
          const url = this.getNodeParameter('url', 0);
          return [[{ 
            json: { 
              statusCode: 200,
              url,
              customData: 'mocked response' 
            } 
          }]];
        })
      });

      const mockContext = {
        getInputData: vi.fn(() => [{ json: { input: 'data' } }]),
        getNodeParameter: vi.fn((name: string) => {
          if (name === 'url') return 'https://test.com/api';
          return '';
        })
      };

      const result = await service.executeNode('httpRequest', mockContext);
      
      expect(result).toBeDefined();
      expect(result[0][0].json).toMatchObject({
        statusCode: 200,
        url: 'https://test.com/api',
        customData: 'mocked response'
      });
    });

    it('should execute slack node and track calls', async () => {
      const mockContext = {
        getInputData: vi.fn(() => [{ json: { message: 'test' } }]),
        getNodeParameter: vi.fn((name: string, index: number) => {
          const params: Record<string, string> = {
            resource: 'message',
            operation: 'post',
            channel: '#general',
            text: 'Hello from test!'
          };
          return params[name] || '';
        }),
        getCredentials: vi.fn(async () => ({ token: 'mock-token' }))
      };

      const result = await service.executeNode('slack', mockContext);
      
      expect(result).toBeDefined();
      expect(result[0][0].json).toMatchObject({
        ok: true,
        channel: '#general',
        message: {
          text: 'Hello from test!'
        }
      });
      
      // Verify the mock was called
      expect(mockContext.getNodeParameter).toHaveBeenCalledWith('channel', 0, '');
      expect(mockContext.getNodeParameter).toHaveBeenCalledWith('text', 0, '');
    });

    it('should throw error for non-executable node', async () => {
      // Create a trigger-only node
      mockNodeBehavior('webhook', {
        execute: undefined // Remove execute method
      });

      await expect(
        service.executeNode('webhook', {})
      ).rejects.toThrow('Node webhook does not have an execute method');
    });
  });

  describe('validateSlackMessage', () => {
    it('should validate slack message parameters', async () => {
      const isValid = await service.validateSlackMessage('#general', 'Hello');
      expect(isValid).toBe(true);
    });

    it('should throw error for missing parameters', async () => {
      await expect(
        service.validateSlackMessage('', 'Hello')
      ).rejects.toThrow('Channel and text are required');

      await expect(
        service.validateSlackMessage('#general', '')
      ).rejects.toThrow('Channel and text are required');
    });

    it('should handle missing slack node', async () => {
      // Save the original mock implementation
      const originalImplementation = vi.mocked(getNodeTypes).getMockImplementation();
      
      // Override getNodeTypes to return undefined for slack
      vi.mocked(getNodeTypes).mockImplementation(() => ({
        getByName: vi.fn((name: string) => {
          if (name === 'slack') return undefined;
          // Return the actual mock implementation for other nodes
          const actualRegistry = originalImplementation ? originalImplementation() : getNodeTypes();
          return actualRegistry.getByName(name);
        }),
        getByNameAndVersion: vi.fn()
      }));

      await expect(
        service.validateSlackMessage('#general', 'Hello')
      ).rejects.toThrow('Slack node not found');
      
      // Restore the original implementation
      if (originalImplementation) {
        vi.mocked(getNodeTypes).mockImplementation(originalImplementation);
      }
    });
  });

  describe('complex workflow scenarios', () => {
    it('should handle if node branching', async () => {
      const mockContext = {
        getInputData: vi.fn(() => [
          { json: { status: 'active' } },
          { json: { status: 'inactive' } },
          { json: { status: 'active' } },
        ]),
        getNodeParameter: vi.fn()
      };

      const result = await service.executeNode('if', mockContext);
      
      expect(result).toHaveLength(2); // true and false branches
      expect(result[0]).toHaveLength(2); // items at index 0 and 2
      expect(result[1]).toHaveLength(1); // item at index 1
    });

    it('should handle merge node combining inputs', async () => {
      const mockContext = {
        getInputData: vi.fn((inputIndex?: number) => {
          if (inputIndex === 0) return [{ json: { source: 'input1' } }];
          if (inputIndex === 1) return [{ json: { source: 'input2' } }];
          return [{ json: { source: 'input1' } }];
        }),
        getNodeParameter: vi.fn(() => 'append')
      };

      const result = await service.executeNode('merge', mockContext);
      
      expect(result).toBeDefined();
      expect(result[0]).toHaveLength(1);
    });
  });
});
```

--------------------------------------------------------------------------------
/tests/unit/services/universal-expression-validator.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect } from 'vitest';
import { UniversalExpressionValidator } from '../../../src/services/universal-expression-validator';

describe('UniversalExpressionValidator', () => {
  describe('validateExpressionPrefix', () => {
    it('should detect missing prefix in pure expression', () => {
      const result = UniversalExpressionValidator.validateExpressionPrefix('{{ $json.value }}');

      expect(result.isValid).toBe(false);
      expect(result.hasExpression).toBe(true);
      expect(result.needsPrefix).toBe(true);
      expect(result.isMixedContent).toBe(false);
      expect(result.confidence).toBe(1.0);
      expect(result.suggestion).toBe('={{ $json.value }}');
    });

    it('should detect missing prefix in mixed content', () => {
      const result = UniversalExpressionValidator.validateExpressionPrefix(
        'Hello {{ $json.name }}'
      );

      expect(result.isValid).toBe(false);
      expect(result.hasExpression).toBe(true);
      expect(result.needsPrefix).toBe(true);
      expect(result.isMixedContent).toBe(true);
      expect(result.confidence).toBe(1.0);
      expect(result.suggestion).toBe('=Hello {{ $json.name }}');
    });

    it('should accept properly prefixed expression', () => {
      const result = UniversalExpressionValidator.validateExpressionPrefix('={{ $json.value }}');

      expect(result.isValid).toBe(true);
      expect(result.hasExpression).toBe(true);
      expect(result.needsPrefix).toBe(false);
      expect(result.confidence).toBe(1.0);
    });

    it('should accept properly prefixed mixed content', () => {
      const result = UniversalExpressionValidator.validateExpressionPrefix(
        '=Hello {{ $json.name }}!'
      );

      expect(result.isValid).toBe(true);
      expect(result.hasExpression).toBe(true);
      expect(result.isMixedContent).toBe(true);
      expect(result.confidence).toBe(1.0);
    });

    it('should ignore non-string values', () => {
      const result = UniversalExpressionValidator.validateExpressionPrefix(123);

      expect(result.isValid).toBe(true);
      expect(result.hasExpression).toBe(false);
      expect(result.confidence).toBe(1.0);
    });

    it('should ignore strings without expressions', () => {
      const result = UniversalExpressionValidator.validateExpressionPrefix('plain text');

      expect(result.isValid).toBe(true);
      expect(result.hasExpression).toBe(false);
      expect(result.confidence).toBe(1.0);
    });
  });

  describe('validateExpressionSyntax', () => {
    it('should detect unclosed brackets', () => {
      const result = UniversalExpressionValidator.validateExpressionSyntax('={{ $json.value }');

      expect(result.isValid).toBe(false);
      expect(result.explanation).toContain('Unmatched expression brackets');
    });

    it('should detect empty expressions', () => {
      const result = UniversalExpressionValidator.validateExpressionSyntax('={{  }}');

      expect(result.isValid).toBe(false);
      expect(result.explanation).toContain('Empty expression');
    });

    it('should accept valid syntax', () => {
      const result = UniversalExpressionValidator.validateExpressionSyntax('={{ $json.value }}');

      expect(result.isValid).toBe(true);
      expect(result.hasExpression).toBe(true);
    });

    it('should handle multiple expressions', () => {
      const result = UniversalExpressionValidator.validateExpressionSyntax(
        '={{ $json.first }} and {{ $json.second }}'
      );

      expect(result.isValid).toBe(true);
      expect(result.hasExpression).toBe(true);
      expect(result.isMixedContent).toBe(true);
    });
  });

  describe('validateCommonPatterns', () => {
    it('should detect template literal syntax', () => {
      const result = UniversalExpressionValidator.validateCommonPatterns('={{ ${json.value} }}');

      expect(result.isValid).toBe(false);
      expect(result.explanation).toContain('Template literal syntax');
    });

    it('should detect double prefix', () => {
      const result = UniversalExpressionValidator.validateCommonPatterns('={{ =$json.value }}');

      expect(result.isValid).toBe(false);
      expect(result.explanation).toContain('Double prefix');
    });

    it('should detect nested brackets', () => {
      const result = UniversalExpressionValidator.validateCommonPatterns(
        '={{ $json.items[{{ $json.index }}] }}'
      );

      expect(result.isValid).toBe(false);
      expect(result.explanation).toContain('Nested brackets');
    });

    it('should accept valid patterns', () => {
      const result = UniversalExpressionValidator.validateCommonPatterns(
        '={{ $json.items[$json.index] }}'
      );

      expect(result.isValid).toBe(true);
    });
  });

  describe('validate (comprehensive)', () => {
    it('should return all validation issues', () => {
      const results = UniversalExpressionValidator.validate('{{ ${json.value} }}');

      expect(results.length).toBeGreaterThan(0);
      const issues = results.filter(r => !r.isValid);
      expect(issues.length).toBeGreaterThan(0);

      // Should detect both missing prefix and template literal syntax
      const prefixIssue = issues.find(i => i.needsPrefix);
      const patternIssue = issues.find(i => i.explanation.includes('Template literal'));

      expect(prefixIssue).toBeTruthy();
      expect(patternIssue).toBeTruthy();
    });

    it('should return success for valid expression', () => {
      const results = UniversalExpressionValidator.validate('={{ $json.value }}');

      expect(results).toHaveLength(1);
      expect(results[0].isValid).toBe(true);
      expect(results[0].confidence).toBe(1.0);
    });

    it('should handle non-expression strings', () => {
      const results = UniversalExpressionValidator.validate('plain text');

      expect(results).toHaveLength(1);
      expect(results[0].isValid).toBe(true);
      expect(results[0].hasExpression).toBe(false);
    });
  });

  describe('getCorrectedValue', () => {
    it('should add prefix to expression', () => {
      const corrected = UniversalExpressionValidator.getCorrectedValue('{{ $json.value }}');
      expect(corrected).toBe('={{ $json.value }}');
    });

    it('should add prefix to mixed content', () => {
      const corrected = UniversalExpressionValidator.getCorrectedValue(
        'Hello {{ $json.name }}'
      );
      expect(corrected).toBe('=Hello {{ $json.name }}');
    });

    it('should not modify already prefixed expressions', () => {
      const corrected = UniversalExpressionValidator.getCorrectedValue('={{ $json.value }}');
      expect(corrected).toBe('={{ $json.value }}');
    });

    it('should not modify non-expressions', () => {
      const corrected = UniversalExpressionValidator.getCorrectedValue('plain text');
      expect(corrected).toBe('plain text');
    });
  });

  describe('hasMixedContent', () => {
    it('should detect URLs with expressions', () => {
      const result = UniversalExpressionValidator.validateExpressionPrefix(
        'https://api.example.com/users/{{ $json.id }}'
      );
      expect(result.isMixedContent).toBe(true);
    });

    it('should detect text with expressions', () => {
      const result = UniversalExpressionValidator.validateExpressionPrefix(
        'Welcome {{ $json.name }} to our service'
      );
      expect(result.isMixedContent).toBe(true);
    });

    it('should identify pure expressions', () => {
      const result = UniversalExpressionValidator.validateExpressionPrefix('{{ $json.value }}');
      expect(result.isMixedContent).toBe(false);
    });

    it('should identify pure expressions with spaces', () => {
      const result = UniversalExpressionValidator.validateExpressionPrefix(
        '  {{ $json.value }}  '
      );
      expect(result.isMixedContent).toBe(false);
    });
  });
});
```

--------------------------------------------------------------------------------
/tests/integration/mcp-protocol/test-helpers.ts:
--------------------------------------------------------------------------------

```typescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
import { 
  CallToolRequestSchema, 
  ListToolsRequestSchema,
  InitializeRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
import { N8NDocumentationMCPServer } from '../../../src/mcp/server';

let sharedMcpServer: N8NDocumentationMCPServer | null = null;

export class TestableN8NMCPServer {
  private mcpServer: N8NDocumentationMCPServer;
  private server: Server;
  private transports = new Set<Transport>();
  private connections = new Set<any>();
  private static instanceCount = 0;
  private testDbPath: string;

  constructor() {
    // Use a unique test database for each instance to avoid conflicts
    // This prevents concurrent test issues with database locking
    const instanceId = TestableN8NMCPServer.instanceCount++;
    this.testDbPath = `/tmp/n8n-mcp-test-${process.pid}-${instanceId}.db`;
    process.env.NODE_DB_PATH = this.testDbPath;
    
    this.server = new Server({
      name: 'n8n-documentation-mcp',
      version: '1.0.0'
    }, {
      capabilities: {
        tools: {}
      }
    });
    
    this.mcpServer = new N8NDocumentationMCPServer();
    this.setupHandlers();
  }

  private setupHandlers() {
    // Initialize handler
    this.server.setRequestHandler(InitializeRequestSchema, async () => {
      return {
        protocolVersion: '2024-11-05',
        capabilities: {
          tools: {}
        },
        serverInfo: {
          name: 'n8n-documentation-mcp',
          version: '1.0.0'
        }
      };
    });

    // List tools handler
    this.server.setRequestHandler(ListToolsRequestSchema, async () => {
      // Import the tools directly from the tools module
      const { n8nDocumentationToolsFinal } = await import('../../../src/mcp/tools');
      const { n8nManagementTools } = await import('../../../src/mcp/tools-n8n-manager');
      const { isN8nApiConfigured } = await import('../../../src/config/n8n-api');
      
      // Combine documentation tools with management tools if API is configured
      const tools = [...n8nDocumentationToolsFinal];
      if (isN8nApiConfigured()) {
        tools.push(...n8nManagementTools);
      }
      
      return { tools };
    });

    // Call tool handler
    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      try {
        // The mcpServer.executeTool returns raw data, we need to wrap it in the MCP response format
        const result = await this.mcpServer.executeTool(request.params.name, request.params.arguments || {});
        
        return {
          content: [
            {
              type: 'text' as const,
              text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
            }
          ]
        };
      } catch (error: any) {
        // If it's already an MCP error, throw it as is
        if (error.code && error.message) {
          throw error;
        }
        // Otherwise, wrap it in an MCP error
        throw new McpError(
          ErrorCode.InternalError,
          error.message || 'Unknown error'
        );
      }
    });
  }

  async initialize(): Promise<void> {
    // Copy production database to test location for realistic testing
    try {
      const fs = await import('fs');
      const path = await import('path');
      const prodDbPath = path.join(process.cwd(), 'data', 'nodes.db');
      
      if (await fs.promises.access(prodDbPath).then(() => true).catch(() => false)) {
        await fs.promises.copyFile(prodDbPath, this.testDbPath);
      }
    } catch (error) {
      // Ignore copy errors, database will be created fresh
    }
    
    // The MCP server initializes its database lazily
    // We can trigger initialization by calling executeTool
    try {
      await this.mcpServer.executeTool('get_database_statistics', {});
    } catch (error) {
      // Ignore errors, we just want to trigger initialization
    }
  }

  async connectToTransport(transport: Transport): Promise<void> {
    // Ensure transport has required properties before connecting
    if (!transport || typeof transport !== 'object') {
      throw new Error('Invalid transport provided');
    }
    
    // Set up any missing transport handlers to prevent "Cannot set properties of undefined" errors
    if (transport && typeof transport === 'object') {
      const transportAny = transport as any;
      if (transportAny.serverTransport && !transportAny.serverTransport.onclose) {
        transportAny.serverTransport.onclose = () => {};
      }
    }
    
    // Track this transport for cleanup
    this.transports.add(transport);
    
    const connection = await this.server.connect(transport);
    this.connections.add(connection);
  }

  async close(): Promise<void> {
    // Use a timeout to prevent hanging during cleanup
    const closeTimeout = new Promise<void>((resolve) => {
      setTimeout(() => {
        console.warn('TestableN8NMCPServer close timeout - forcing cleanup');
        resolve();
      }, 3000);
    });

    const performClose = async () => {
      // Close all connections first with timeout protection
      const connectionPromises = Array.from(this.connections).map(async (connection) => {
        const connTimeout = new Promise<void>((resolve) => setTimeout(resolve, 500));
        
        try {
          if (connection && typeof connection.close === 'function') {
            await Promise.race([connection.close(), connTimeout]);
          }
        } catch (error) {
          // Ignore errors during connection cleanup
        }
      });
      
      await Promise.allSettled(connectionPromises);
      this.connections.clear();
      
      // Close all tracked transports with timeout protection
      const transportPromises: Promise<void>[] = [];
      
      for (const transport of this.transports) {
        const transportTimeout = new Promise<void>((resolve) => setTimeout(resolve, 500));
        
        try {
          // Force close all transports
          const transportAny = transport as any;
          
          // Try different close methods
          if (transportAny.close && typeof transportAny.close === 'function') {
            transportPromises.push(
              Promise.race([transportAny.close(), transportTimeout])
            );
          }
          if (transportAny.serverTransport?.close) {
            transportPromises.push(
              Promise.race([transportAny.serverTransport.close(), transportTimeout])
            );
          }
          if (transportAny.clientTransport?.close) {
            transportPromises.push(
              Promise.race([transportAny.clientTransport.close(), transportTimeout])
            );
          }
        } catch (error) {
          // Ignore errors during transport cleanup
        }
      }
      
      // Wait for all transports to close with timeout
      await Promise.allSettled(transportPromises);
      
      // Clear the transports set
      this.transports.clear();
      
      // Don't shut down the shared MCP server instance
    };

    // Race between actual close and timeout
    await Promise.race([performClose(), closeTimeout]);
    
    // Clean up test database
    if (this.testDbPath) {
      try {
        const fs = await import('fs');
        await fs.promises.unlink(this.testDbPath).catch(() => {});
        await fs.promises.unlink(`${this.testDbPath}-shm`).catch(() => {});
        await fs.promises.unlink(`${this.testDbPath}-wal`).catch(() => {});
      } catch (error) {
        // Ignore cleanup errors
      }
    }
  }
  
  static async shutdownShared(): Promise<void> {
    if (sharedMcpServer) {
      await sharedMcpServer.shutdown();
      sharedMcpServer = null;
    }
  }
}
```

--------------------------------------------------------------------------------
/scripts/test-error-output-validation.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env npx tsx

/**
 * Test script for error output validation improvements
 * Tests both incorrect and correct error output configurations
 */

import { WorkflowValidator } from '../dist/services/workflow-validator.js';
import { NodeRepository } from '../dist/database/node-repository.js';
import { EnhancedConfigValidator } from '../dist/services/enhanced-config-validator.js';
import { DatabaseAdapter } from '../dist/database/database-adapter.js';
import { Logger } from '../dist/utils/logger.js';
import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const logger = new Logger({ prefix: '[TestErrorValidation]' });

async function runTests() {
  // Initialize database
  const dbPath = path.join(__dirname, '..', 'data', 'n8n-nodes.db');
  const adapter = new DatabaseAdapter();
  adapter.initialize({
    type: 'better-sqlite3',
    filename: dbPath
  });
  const db = adapter.getDatabase();

  const nodeRepository = new NodeRepository(db);
  const validator = new WorkflowValidator(nodeRepository, EnhancedConfigValidator);

  console.log('\n🧪 Testing Error Output Validation Improvements\n');
  console.log('=' .repeat(60));

  // Test 1: Incorrect configuration - multiple nodes in same array
  console.log('\n📝 Test 1: INCORRECT - Multiple nodes in main[0]');
  console.log('-'.repeat(40));

  const incorrectWorkflow = {
    nodes: [
      {
        id: '132ef0dc-87af-41de-a95d-cabe3a0a5342',
        name: 'Validate Input',
        type: 'n8n-nodes-base.set',
        typeVersion: 3.4,
        position: [-400, 64] as [number, number],
        parameters: {}
      },
      {
        id: '5dedf217-63f9-409f-b34e-7780b22e199a',
        name: 'Filter URLs',
        type: 'n8n-nodes-base.filter',
        typeVersion: 2.2,
        position: [-176, 64] as [number, number],
        parameters: {}
      },
      {
        id: '9d5407cc-ca5a-4966-b4b7-0e5dfbf54ad3',
        name: 'Error Response1',
        type: 'n8n-nodes-base.respondToWebhook',
        typeVersion: 1.5,
        position: [-160, 240] as [number, number],
        parameters: {}
      }
    ],
    connections: {
      'Validate Input': {
        main: [
          [
            { node: 'Filter URLs', type: 'main', index: 0 },
            { node: 'Error Response1', type: 'main', index: 0 }  // WRONG!
          ]
        ]
      }
    }
  };

  const result1 = await validator.validateWorkflow(incorrectWorkflow);

  if (result1.errors.length > 0) {
    console.log('❌ ERROR DETECTED (as expected):');
    const errorMessage = result1.errors.find(e =>
      e.message.includes('Incorrect error output configuration')
    );
    if (errorMessage) {
      console.log('\n' + errorMessage.message);
    }
  } else {
    console.log('✅ No errors found (but should have detected the issue!)');
  }

  // Test 2: Correct configuration - separate arrays
  console.log('\n📝 Test 2: CORRECT - Separate main[0] and main[1]');
  console.log('-'.repeat(40));

  const correctWorkflow = {
    nodes: [
      {
        id: '132ef0dc-87af-41de-a95d-cabe3a0a5342',
        name: 'Validate Input',
        type: 'n8n-nodes-base.set',
        typeVersion: 3.4,
        position: [-400, 64] as [number, number],
        parameters: {},
        onError: 'continueErrorOutput' as const
      },
      {
        id: '5dedf217-63f9-409f-b34e-7780b22e199a',
        name: 'Filter URLs',
        type: 'n8n-nodes-base.filter',
        typeVersion: 2.2,
        position: [-176, 64] as [number, number],
        parameters: {}
      },
      {
        id: '9d5407cc-ca5a-4966-b4b7-0e5dfbf54ad3',
        name: 'Error Response1',
        type: 'n8n-nodes-base.respondToWebhook',
        typeVersion: 1.5,
        position: [-160, 240] as [number, number],
        parameters: {}
      }
    ],
    connections: {
      'Validate Input': {
        main: [
          [
            { node: 'Filter URLs', type: 'main', index: 0 }
          ],
          [
            { node: 'Error Response1', type: 'main', index: 0 }  // CORRECT!
          ]
        ]
      }
    }
  };

  const result2 = await validator.validateWorkflow(correctWorkflow);

  const hasIncorrectError = result2.errors.some(e =>
    e.message.includes('Incorrect error output configuration')
  );

  if (!hasIncorrectError) {
    console.log('✅ No error output configuration issues (correct!)');
  } else {
    console.log('❌ Unexpected error found');
  }

  // Test 3: onError without error connections
  console.log('\n📝 Test 3: onError without error connections');
  console.log('-'.repeat(40));

  const mismatchWorkflow = {
    nodes: [
      {
        id: '1',
        name: 'HTTP Request',
        type: 'n8n-nodes-base.httpRequest',
        typeVersion: 4,
        position: [100, 100] as [number, number],
        parameters: {},
        onError: 'continueErrorOutput' as const
      },
      {
        id: '2',
        name: 'Process Data',
        type: 'n8n-nodes-base.set',
        typeVersion: 2,
        position: [300, 100] as [number, number],
        parameters: {}
      }
    ],
    connections: {
      'HTTP Request': {
        main: [
          [
            { node: 'Process Data', type: 'main', index: 0 }
          ]
          // No main[1] for error output
        ]
      }
    }
  };

  const result3 = await validator.validateWorkflow(mismatchWorkflow);

  const mismatchError = result3.errors.find(e =>
    e.message.includes("has onError: 'continueErrorOutput' but no error output connections")
  );

  if (mismatchError) {
    console.log('❌ ERROR DETECTED (as expected):');
    console.log(`Node: ${mismatchError.nodeName}`);
    console.log(`Message: ${mismatchError.message}`);
  } else {
    console.log('✅ No mismatch detected (but should have!)');
  }

  // Test 4: Error connections without onError
  console.log('\n📝 Test 4: Error connections without onError property');
  console.log('-'.repeat(40));

  const missingOnErrorWorkflow = {
    nodes: [
      {
        id: '1',
        name: 'HTTP Request',
        type: 'n8n-nodes-base.httpRequest',
        typeVersion: 4,
        position: [100, 100] as [number, number],
        parameters: {}
        // Missing onError property
      },
      {
        id: '2',
        name: 'Process Data',
        type: 'n8n-nodes-base.set',
        position: [300, 100] as [number, number],
        parameters: {}
      },
      {
        id: '3',
        name: 'Error Handler',
        type: 'n8n-nodes-base.set',
        position: [300, 300] as [number, number],
        parameters: {}
      }
    ],
    connections: {
      'HTTP Request': {
        main: [
          [
            { node: 'Process Data', type: 'main', index: 0 }
          ],
          [
            { node: 'Error Handler', type: 'main', index: 0 }
          ]
        ]
      }
    }
  };

  const result4 = await validator.validateWorkflow(missingOnErrorWorkflow);

  const missingOnErrorWarning = result4.warnings.find(w =>
    w.message.includes('error output connections in main[1] but missing onError')
  );

  if (missingOnErrorWarning) {
    console.log('⚠️  WARNING DETECTED (as expected):');
    console.log(`Node: ${missingOnErrorWarning.nodeName}`);
    console.log(`Message: ${missingOnErrorWarning.message}`);
  } else {
    console.log('✅ No warning (but should have warned!)');
  }

  console.log('\n' + '='.repeat(60));
  console.log('\n📊 Summary:');
  console.log('- Error output validation is working correctly');
  console.log('- Detects incorrect configurations (multiple nodes in main[0])');
  console.log('- Validates onError property matches connections');
  console.log('- Provides clear error messages with fix examples');

  // Close database
  adapter.close();
}

runTests().catch(error => {
  console.error('Test failed:', error);
  process.exit(1);
});
```

--------------------------------------------------------------------------------
/tests/integration/n8n-api/utils/factories.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Test Data Factories
 *
 * Provides factory functions for generating test data dynamically.
 * Useful for creating variations of workflows, nodes, and parameters.
 */

import { Workflow, WorkflowNode } from '../../../../src/types/n8n-api';
import { createTestWorkflowName } from './test-context';

/**
 * Create a webhook node with custom parameters
 *
 * @param options - Node options
 * @returns WorkflowNode
 */
export function createWebhookNode(options: {
  id?: string;
  name?: string;
  httpMethod?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
  path?: string;
  position?: [number, number];
  responseMode?: 'onReceived' | 'lastNode';
}): WorkflowNode {
  return {
    id: options.id || `webhook-${Date.now()}`,
    name: options.name || 'Webhook',
    type: 'n8n-nodes-base.webhook',
    typeVersion: 2,
    position: options.position || [250, 300],
    parameters: {
      httpMethod: options.httpMethod || 'GET',
      path: options.path || `test-${Date.now()}`,
      responseMode: options.responseMode || 'lastNode'
    }
  };
}

/**
 * Create an HTTP Request node with custom parameters
 *
 * @param options - Node options
 * @returns WorkflowNode
 */
export function createHttpRequestNode(options: {
  id?: string;
  name?: string;
  url?: string;
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
  position?: [number, number];
  authentication?: string;
}): WorkflowNode {
  return {
    id: options.id || `http-${Date.now()}`,
    name: options.name || 'HTTP Request',
    type: 'n8n-nodes-base.httpRequest',
    typeVersion: 4.2,
    position: options.position || [450, 300],
    parameters: {
      url: options.url || 'https://httpbin.org/get',
      method: options.method || 'GET',
      authentication: options.authentication || 'none'
    }
  };
}

/**
 * Create a Set node with custom assignments
 *
 * @param options - Node options
 * @returns WorkflowNode
 */
export function createSetNode(options: {
  id?: string;
  name?: string;
  position?: [number, number];
  assignments?: Array<{
    name: string;
    value: any;
    type?: 'string' | 'number' | 'boolean' | 'object' | 'array';
  }>;
}): WorkflowNode {
  const assignments = options.assignments || [
    { name: 'key', value: 'value', type: 'string' as const }
  ];

  return {
    id: options.id || `set-${Date.now()}`,
    name: options.name || 'Set',
    type: 'n8n-nodes-base.set',
    typeVersion: 3.4,
    position: options.position || [450, 300],
    parameters: {
      assignments: {
        assignments: assignments.map((a, idx) => ({
          id: `assign-${idx}`,
          name: a.name,
          value: a.value,
          type: a.type || 'string'
        }))
      },
      options: {}
    }
  };
}

/**
 * Create a Manual Trigger node
 *
 * @param options - Node options
 * @returns WorkflowNode
 */
export function createManualTriggerNode(options: {
  id?: string;
  name?: string;
  position?: [number, number];
} = {}): WorkflowNode {
  return {
    id: options.id || `manual-${Date.now()}`,
    name: options.name || 'When clicking "Test workflow"',
    type: 'n8n-nodes-base.manualTrigger',
    typeVersion: 1,
    position: options.position || [250, 300],
    parameters: {}
  };
}

/**
 * Create a simple connection between two nodes
 *
 * @param from - Source node name
 * @param to - Target node name
 * @param options - Connection options
 * @returns Connection object
 */
export function createConnection(
  from: string,
  to: string,
  options: {
    sourceOutput?: string;
    targetInput?: string;
    sourceIndex?: number;
    targetIndex?: number;
  } = {}
): Record<string, any> {
  const sourceOutput = options.sourceOutput || 'main';
  const targetInput = options.targetInput || 'main';
  const sourceIndex = options.sourceIndex || 0;
  const targetIndex = options.targetIndex || 0;

  return {
    [from]: {
      [sourceOutput]: [
        [
          {
            node: to,
            type: targetInput,
            index: targetIndex
          }
        ]
      ]
    }
  };
}

/**
 * Create a workflow from nodes with automatic connections
 *
 * Connects nodes in sequence: node1 -> node2 -> node3, etc.
 *
 * @param name - Workflow name
 * @param nodes - Array of nodes
 * @returns Partial workflow
 */
export function createSequentialWorkflow(
  name: string,
  nodes: WorkflowNode[]
): Partial<Workflow> {
  const connections: Record<string, any> = {};

  // Create connections between sequential nodes
  for (let i = 0; i < nodes.length - 1; i++) {
    const currentNode = nodes[i];
    const nextNode = nodes[i + 1];

    connections[currentNode.name] = {
      main: [[{ node: nextNode.name, type: 'main', index: 0 }]]
    };
  }

  return {
    name: createTestWorkflowName(name),
    nodes,
    connections,
    settings: {
      executionOrder: 'v1'
    }
  };
}

/**
 * Create a workflow with parallel branches
 *
 * Creates a workflow with one trigger node that splits into multiple
 * parallel execution paths.
 *
 * @param name - Workflow name
 * @param trigger - Trigger node
 * @param branches - Array of branch nodes
 * @returns Partial workflow
 */
export function createParallelWorkflow(
  name: string,
  trigger: WorkflowNode,
  branches: WorkflowNode[]
): Partial<Workflow> {
  const connections: Record<string, any> = {
    [trigger.name]: {
      main: [branches.map(node => ({ node: node.name, type: 'main', index: 0 }))]
    }
  };

  return {
    name: createTestWorkflowName(name),
    nodes: [trigger, ...branches],
    connections,
    settings: {
      executionOrder: 'v1'
    }
  };
}

/**
 * Generate a random string for test data
 *
 * @param length - String length (default: 8)
 * @returns Random string
 */
export function randomString(length: number = 8): string {
  const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
  let result = '';
  for (let i = 0; i < length; i++) {
    result += chars.charAt(Math.floor(Math.random() * chars.length));
  }
  return result;
}

/**
 * Generate a unique ID for testing
 *
 * @param prefix - Optional prefix
 * @returns Unique ID
 */
export function uniqueId(prefix: string = 'test'): string {
  return `${prefix}-${Date.now()}-${randomString(4)}`;
}

/**
 * Create a workflow with error handling
 *
 * @param name - Workflow name
 * @param mainNode - Main processing node
 * @param errorNode - Error handling node
 * @returns Partial workflow with error handling configured
 */
export function createErrorHandlingWorkflow(
  name: string,
  mainNode: WorkflowNode,
  errorNode: WorkflowNode
): Partial<Workflow> {
  const trigger = createWebhookNode({
    name: 'Trigger',
    position: [250, 300]
  });

  // Configure main node for error handling
  const mainNodeWithError = {
    ...mainNode,
    continueOnFail: true,
    onError: 'continueErrorOutput' as const
  };

  const connections: Record<string, any> = {
    [trigger.name]: {
      main: [[{ node: mainNode.name, type: 'main', index: 0 }]]
    },
    [mainNode.name]: {
      error: [[{ node: errorNode.name, type: 'main', index: 0 }]]
    }
  };

  return {
    name: createTestWorkflowName(name),
    nodes: [trigger, mainNodeWithError, errorNode],
    connections,
    settings: {
      executionOrder: 'v1'
    }
  };
}

/**
 * Create test workflow tags
 *
 * @param additional - Additional tags to include
 * @returns Array of tags for test workflows
 */
export function createTestTags(additional: string[] = []): string[] {
  return ['mcp-integration-test', ...additional];
}

/**
 * Create workflow settings with common test configurations
 *
 * @param overrides - Settings to override
 * @returns Workflow settings object
 */
export function createWorkflowSettings(overrides: Record<string, any> = {}): Record<string, any> {
  return {
    executionOrder: 'v1',
    saveDataErrorExecution: 'all',
    saveDataSuccessExecution: 'all',
    saveManualExecutions: true,
    ...overrides
  };
}

```

--------------------------------------------------------------------------------
/tests/http-server-auth.test.ts:
--------------------------------------------------------------------------------

```typescript
import { readFileSync, writeFileSync, mkdirSync, rmSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import type { MockedFunction } from 'vitest';

// Import the actual functions we'll be testing
import { loadAuthToken, startFixedHTTPServer } from '../src/http-server';

// Mock dependencies
vi.mock('../src/utils/logger', () => ({
  logger: {
    info: vi.fn(),
    error: vi.fn(),
    warn: vi.fn(),
    debug: vi.fn()
  },
  Logger: vi.fn().mockImplementation(() => ({
    info: vi.fn(),
    error: vi.fn(),
    warn: vi.fn(),
    debug: vi.fn()
  })),
  LogLevel: {
    ERROR: 0,
    WARN: 1,
    INFO: 2,
    DEBUG: 3
  }
}));

vi.mock('dotenv');

// Mock other dependencies to prevent side effects
vi.mock('../src/mcp/server', () => ({
  N8NDocumentationMCPServer: vi.fn().mockImplementation(() => ({
    executeTool: vi.fn()
  }))
}));

vi.mock('../src/mcp/tools', () => ({
  n8nDocumentationToolsFinal: []
}));

vi.mock('../src/mcp/tools-n8n-manager', () => ({
  n8nManagementTools: []
}));

vi.mock('../src/utils/version', () => ({
  PROJECT_VERSION: '2.7.4'
}));

vi.mock('../src/config/n8n-api', () => ({
  isN8nApiConfigured: vi.fn().mockReturnValue(false)
}));

vi.mock('../src/utils/url-detector', () => ({
  getStartupBaseUrl: vi.fn().mockReturnValue('http://localhost:3000'),
  formatEndpointUrls: vi.fn().mockReturnValue({
    health: 'http://localhost:3000/health',
    mcp: 'http://localhost:3000/mcp'
  }),
  detectBaseUrl: vi.fn().mockReturnValue('http://localhost:3000')
}));

// Create mock server instance
const mockServer = {
  on: vi.fn(),
  close: vi.fn((callback) => callback())
};

// Mock Express to prevent server from starting
const mockExpressApp = {
  use: vi.fn(),
  get: vi.fn(),
  post: vi.fn(),
  listen: vi.fn((port: any, host: any, callback: any) => {
    // Call the callback immediately to simulate server start
    if (callback) callback();
    return mockServer;
  }),
  set: vi.fn()
};

vi.mock('express', () => {
  const express: any = vi.fn(() => mockExpressApp);
  express.json = vi.fn();
  express.urlencoded = vi.fn();
  express.static = vi.fn();
  express.Request = {};
  express.Response = {};
  express.NextFunction = {};
  return { default: express };
});

describe('HTTP Server Authentication', () => {
  const originalEnv = process.env;
  let tempDir: string;
  let authTokenFile: string;

  beforeEach(() => {
    // Reset modules and environment
    vi.clearAllMocks();
    vi.resetModules();
    process.env = { ...originalEnv };
    
    // Create temporary directory for test files
    tempDir = join(tmpdir(), `http-server-auth-test-${Date.now()}`);
    mkdirSync(tempDir, { recursive: true });
    authTokenFile = join(tempDir, 'auth-token');
  });

  afterEach(() => {
    // Restore original environment
    process.env = originalEnv;
    
    // Clean up temporary directory
    try {
      rmSync(tempDir, { recursive: true, force: true });
    } catch (error) {
      // Ignore cleanup errors
    }
  });

  describe('loadAuthToken', () => {
    it('should load token when AUTH_TOKEN environment variable is set', () => {
      process.env.AUTH_TOKEN = 'test-token-from-env';
      delete process.env.AUTH_TOKEN_FILE;

      const token = loadAuthToken();
      expect(token).toBe('test-token-from-env');
    });

    it('should load token from file when only AUTH_TOKEN_FILE is set', () => {
      delete process.env.AUTH_TOKEN;
      process.env.AUTH_TOKEN_FILE = authTokenFile;
      
      // Write test token to file
      writeFileSync(authTokenFile, 'test-token-from-file\n');

      const token = loadAuthToken();
      expect(token).toBe('test-token-from-file');
    });

    it('should trim whitespace when reading token from file', () => {
      delete process.env.AUTH_TOKEN;
      process.env.AUTH_TOKEN_FILE = authTokenFile;
      
      // Write token with whitespace
      writeFileSync(authTokenFile, '  test-token-with-spaces  \n\n');

      const token = loadAuthToken();
      expect(token).toBe('test-token-with-spaces');
    });

    it('should prefer AUTH_TOKEN when both variables are set', () => {
      process.env.AUTH_TOKEN = 'env-token';
      process.env.AUTH_TOKEN_FILE = authTokenFile;
      writeFileSync(authTokenFile, 'file-token');

      const token = loadAuthToken();
      expect(token).toBe('env-token');
    });

    it('should return null when AUTH_TOKEN_FILE points to non-existent file', async () => {
      delete process.env.AUTH_TOKEN;
      process.env.AUTH_TOKEN_FILE = join(tempDir, 'non-existent-file');

      // Import logger to check calls
      const { logger } = await import('../src/utils/logger');
      
      // Clear any previous mock calls
      vi.clearAllMocks();
      
      const token = loadAuthToken();
      expect(token).toBeNull();
      expect(logger.error).toHaveBeenCalled();
      const errorCall = (logger.error as MockedFunction<any>).mock.calls[0];
      expect(errorCall[0]).toContain('Failed to read AUTH_TOKEN_FILE');
      // Check that the second argument exists and is truthy (the error object)
      expect(errorCall[1]).toBeTruthy();
    });

    it('should return null when no auth variables are set', () => {
      delete process.env.AUTH_TOKEN;
      delete process.env.AUTH_TOKEN_FILE;

      const token = loadAuthToken();
      expect(token).toBeNull();
    });
  });

  describe('validateEnvironment', () => {
    it('should exit process when no auth token is available', async () => {
      delete process.env.AUTH_TOKEN;
      delete process.env.AUTH_TOKEN_FILE;

      const mockExit = vi.spyOn(process, 'exit').mockImplementation((code?: string | number | null | undefined) => {
        throw new Error('Process exited');
      });

      // validateEnvironment is called when starting the server
      await expect(async () => {
        await startFixedHTTPServer();
      }).rejects.toThrow('Process exited');

      expect(mockExit).toHaveBeenCalledWith(1);
      mockExit.mockRestore();
    });

    it('should warn when token length is less than 32 characters', async () => {
      process.env.AUTH_TOKEN = 'short-token';

      // Import logger to check calls
      const { logger } = await import('../src/utils/logger');
      
      // Clear any previous mock calls
      vi.clearAllMocks();
      
      // Ensure the mock server is properly configured
      mockExpressApp.listen.mockReturnValue(mockServer);
      mockServer.on.mockReturnValue(undefined);
      
      // Start the server which will trigger validateEnvironment
      await startFixedHTTPServer();
      
      expect(logger.warn).toHaveBeenCalledWith(
        'AUTH_TOKEN should be at least 32 characters for security'
      );
    });
  });

  describe('Integration test scenarios', () => {
    it('should authenticate successfully when token is loaded from file', () => {
      // This is more of an integration test placeholder
      // In a real scenario, you'd start the server and make HTTP requests
      
      writeFileSync(authTokenFile, 'very-secure-token-with-more-than-32-characters');
      process.env.AUTH_TOKEN_FILE = authTokenFile;
      delete process.env.AUTH_TOKEN;

      const token = loadAuthToken();
      expect(token).toBe('very-secure-token-with-more-than-32-characters');
    });

    it('should load token when using Docker secrets pattern', () => {
      // Docker secrets are typically mounted at /run/secrets/
      const dockerSecretPath = join(tempDir, 'run', 'secrets', 'auth_token');
      mkdirSync(join(tempDir, 'run', 'secrets'), { recursive: true });
      writeFileSync(dockerSecretPath, 'docker-secret-token');
      
      process.env.AUTH_TOKEN_FILE = dockerSecretPath;
      delete process.env.AUTH_TOKEN;

      const token = loadAuthToken();
      expect(token).toBe('docker-secret-token');
    });
  });
});
```

--------------------------------------------------------------------------------
/src/n8n/MCPNode.node.ts:
--------------------------------------------------------------------------------

```typescript
import {
  IExecuteFunctions,
  INodeExecutionData,
  INodeType,
  INodeTypeDescription,
  NodeOperationError,
} from 'n8n-workflow';
import { MCPClient } from '../utils/mcp-client';
import { N8NMCPBridge } from '../utils/bridge';

export class MCPNode implements INodeType {
  description: INodeTypeDescription = {
    displayName: 'MCP',
    name: 'mcp',
    icon: 'file:mcp.svg',
    group: ['transform'],
    version: 1,
    description: 'Interact with Model Context Protocol (MCP) servers',
    defaults: {
      name: 'MCP',
    },
    inputs: ['main'],
    outputs: ['main'],
    credentials: [
      {
        name: 'mcpApi',
        required: true,
      },
    ],
    properties: [
      {
        displayName: 'Operation',
        name: 'operation',
        type: 'options',
        noDataExpression: true,
        options: [
          {
            name: 'Call Tool',
            value: 'callTool',
            description: 'Execute an MCP tool',
          },
          {
            name: 'List Tools',
            value: 'listTools',
            description: 'List available MCP tools',
          },
          {
            name: 'Read Resource',
            value: 'readResource',
            description: 'Read an MCP resource',
          },
          {
            name: 'List Resources',
            value: 'listResources',
            description: 'List available MCP resources',
          },
          {
            name: 'Get Prompt',
            value: 'getPrompt',
            description: 'Get an MCP prompt',
          },
          {
            name: 'List Prompts',
            value: 'listPrompts',
            description: 'List available MCP prompts',
          },
        ],
        default: 'callTool',
      },
      // Tool-specific fields
      {
        displayName: 'Tool Name',
        name: 'toolName',
        type: 'string',
        required: true,
        displayOptions: {
          show: {
            operation: ['callTool'],
          },
        },
        default: '',
        description: 'Name of the MCP tool to execute',
      },
      {
        displayName: 'Tool Arguments',
        name: 'toolArguments',
        type: 'json',
        required: false,
        displayOptions: {
          show: {
            operation: ['callTool'],
          },
        },
        default: '{}',
        description: 'Arguments to pass to the MCP tool',
      },
      // Resource-specific fields
      {
        displayName: 'Resource URI',
        name: 'resourceUri',
        type: 'string',
        required: true,
        displayOptions: {
          show: {
            operation: ['readResource'],
          },
        },
        default: '',
        description: 'URI of the MCP resource to read',
      },
      // Prompt-specific fields
      {
        displayName: 'Prompt Name',
        name: 'promptName',
        type: 'string',
        required: true,
        displayOptions: {
          show: {
            operation: ['getPrompt'],
          },
        },
        default: '',
        description: 'Name of the MCP prompt to retrieve',
      },
      {
        displayName: 'Prompt Arguments',
        name: 'promptArguments',
        type: 'json',
        required: false,
        displayOptions: {
          show: {
            operation: ['getPrompt'],
          },
        },
        default: '{}',
        description: 'Arguments to pass to the MCP prompt',
      },
    ],
  };

  async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
    const items = this.getInputData();
    const returnData: INodeExecutionData[] = [];
    const operation = this.getNodeParameter('operation', 0) as string;

    // Get credentials
    const credentials = await this.getCredentials('mcpApi');
    
    for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
      try {
        let result: any;

        switch (operation) {
          case 'callTool':
            const toolName = this.getNodeParameter('toolName', itemIndex) as string;
            const toolArgumentsJson = this.getNodeParameter('toolArguments', itemIndex) as string;
            const toolArguments = JSON.parse(toolArgumentsJson);
            
            result = await (this as any).callMCPTool(credentials, toolName, toolArguments);
            break;

          case 'listTools':
            result = await (this as any).listMCPTools(credentials);
            break;

          case 'readResource':
            const resourceUri = this.getNodeParameter('resourceUri', itemIndex) as string;
            result = await (this as any).readMCPResource(credentials, resourceUri);
            break;

          case 'listResources':
            result = await (this as any).listMCPResources(credentials);
            break;

          case 'getPrompt':
            const promptName = this.getNodeParameter('promptName', itemIndex) as string;
            const promptArgumentsJson = this.getNodeParameter('promptArguments', itemIndex) as string;
            const promptArguments = JSON.parse(promptArgumentsJson);
            
            result = await (this as any).getMCPPrompt(credentials, promptName, promptArguments);
            break;

          case 'listPrompts':
            result = await (this as any).listMCPPrompts(credentials);
            break;

          default:
            throw new NodeOperationError(this.getNode(), `Unknown operation: ${operation}`);
        }

        returnData.push({
          json: result,
          pairedItem: itemIndex,
        });
      } catch (error) {
        if (this.continueOnFail()) {
          returnData.push({
            json: {
              error: error instanceof Error ? error.message : 'Unknown error',
            },
            pairedItem: itemIndex,
          });
          continue;
        }
        throw error;
      }
    }

    return [returnData];
  }

  // MCP client methods
  private async getMCPClient(credentials: any): Promise<MCPClient> {
    const client = new MCPClient({
      serverUrl: credentials.serverUrl,
      authToken: credentials.authToken,
      connectionType: credentials.connectionType || 'websocket',
    });
    await client.connect();
    return client;
  }

  private async callMCPTool(credentials: any, toolName: string, args: any): Promise<any> {
    const client = await this.getMCPClient(credentials);
    try {
      const result = await client.callTool(toolName, args);
      return N8NMCPBridge.mcpToN8NExecutionData(result).json;
    } finally {
      await client.disconnect();
    }
  }

  private async listMCPTools(credentials: any): Promise<any> {
    const client = await this.getMCPClient(credentials);
    try {
      return await client.listTools();
    } finally {
      await client.disconnect();
    }
  }

  private async readMCPResource(credentials: any, uri: string): Promise<any> {
    const client = await this.getMCPClient(credentials);
    try {
      const result = await client.readResource(uri);
      return N8NMCPBridge.mcpToN8NExecutionData(result).json;
    } finally {
      await client.disconnect();
    }
  }

  private async listMCPResources(credentials: any): Promise<any> {
    const client = await this.getMCPClient(credentials);
    try {
      return await client.listResources();
    } finally {
      await client.disconnect();
    }
  }

  private async getMCPPrompt(credentials: any, promptName: string, args: any): Promise<any> {
    const client = await this.getMCPClient(credentials);
    try {
      const result = await client.getPrompt(promptName, args);
      return N8NMCPBridge.mcpPromptArgsToN8N(result);
    } finally {
      await client.disconnect();
    }
  }

  private async listMCPPrompts(credentials: any): Promise<any> {
    const client = await this.getMCPClient(credentials);
    try {
      return await client.listPrompts();
    } finally {
      await client.disconnect();
    }
  }
}
```

--------------------------------------------------------------------------------
/tests/integration/n8n-api/utils/fixtures.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Workflow Fixtures for Integration Tests
 *
 * Provides reusable workflow templates for testing.
 * All fixtures use FULL node type format (n8n-nodes-base.*)
 * as required by the n8n API.
 */

import { Workflow, WorkflowNode } from '../../../../src/types/n8n-api';

/**
 * Simple webhook workflow with a single Webhook node
 *
 * Use this for basic workflow creation tests.
 */
export const SIMPLE_WEBHOOK_WORKFLOW: Partial<Workflow> = {
  nodes: [
    {
      id: 'webhook-1',
      name: 'Webhook',
      type: 'n8n-nodes-base.webhook',
      typeVersion: 2,
      position: [250, 300],
      parameters: {
        httpMethod: 'GET',
        path: 'test-webhook'
      }
    }
  ],
  connections: {},
  settings: {
    executionOrder: 'v1'
  }
};

/**
 * Simple HTTP request workflow
 *
 * Contains a Webhook trigger and an HTTP Request node.
 * Tests basic workflow connections.
 */
export const SIMPLE_HTTP_WORKFLOW: Partial<Workflow> = {
  nodes: [
    {
      id: 'webhook-1',
      name: 'Webhook',
      type: 'n8n-nodes-base.webhook',
      typeVersion: 2,
      position: [250, 300],
      parameters: {
        httpMethod: 'GET',
        path: 'trigger'
      }
    },
    {
      id: 'http-1',
      name: 'HTTP Request',
      type: 'n8n-nodes-base.httpRequest',
      typeVersion: 4.2,
      position: [450, 300],
      parameters: {
        url: 'https://httpbin.org/get',
        method: 'GET'
      }
    }
  ],
  connections: {
    Webhook: {
      main: [[{ node: 'HTTP Request', type: 'main', index: 0 }]]
    }
  },
  settings: {
    executionOrder: 'v1'
  }
};

/**
 * Multi-node workflow with branching
 *
 * Tests complex connections and multiple execution paths.
 */
export const MULTI_NODE_WORKFLOW: Partial<Workflow> = {
  nodes: [
    {
      id: 'webhook-1',
      name: 'Webhook',
      type: 'n8n-nodes-base.webhook',
      typeVersion: 2,
      position: [250, 300],
      parameters: {
        httpMethod: 'POST',
        path: 'multi-node'
      }
    },
    {
      id: 'set-1',
      name: 'Set 1',
      type: 'n8n-nodes-base.set',
      typeVersion: 3.4,
      position: [450, 200],
      parameters: {
        assignments: {
          assignments: [
            {
              id: 'assign-1',
              name: 'branch',
              value: 'top',
              type: 'string'
            }
          ]
        },
        options: {}
      }
    },
    {
      id: 'set-2',
      name: 'Set 2',
      type: 'n8n-nodes-base.set',
      typeVersion: 3.4,
      position: [450, 400],
      parameters: {
        assignments: {
          assignments: [
            {
              id: 'assign-2',
              name: 'branch',
              value: 'bottom',
              type: 'string'
            }
          ]
        },
        options: {}
      }
    },
    {
      id: 'merge-1',
      name: 'Merge',
      type: 'n8n-nodes-base.merge',
      typeVersion: 3,
      position: [650, 300],
      parameters: {
        mode: 'append',
        options: {}
      }
    }
  ],
  connections: {
    Webhook: {
      main: [
        [
          { node: 'Set 1', type: 'main', index: 0 },
          { node: 'Set 2', type: 'main', index: 0 }
        ]
      ]
    },
    'Set 1': {
      main: [[{ node: 'Merge', type: 'main', index: 0 }]]
    },
    'Set 2': {
      main: [[{ node: 'Merge', type: 'main', index: 1 }]]
    }
  },
  settings: {
    executionOrder: 'v1'
  }
};

/**
 * Workflow with error handling
 *
 * Tests error output configuration and error workflows.
 */
export const ERROR_HANDLING_WORKFLOW: Partial<Workflow> = {
  nodes: [
    {
      id: 'webhook-1',
      name: 'Webhook',
      type: 'n8n-nodes-base.webhook',
      typeVersion: 2,
      position: [250, 300],
      parameters: {
        httpMethod: 'GET',
        path: 'error-test'
      }
    },
    {
      id: 'http-1',
      name: 'HTTP Request',
      type: 'n8n-nodes-base.httpRequest',
      typeVersion: 4.2,
      position: [450, 300],
      parameters: {
        url: 'https://httpbin.org/status/500',
        method: 'GET'
      },
      continueOnFail: true,
      onError: 'continueErrorOutput'
    },
    {
      id: 'set-error',
      name: 'Handle Error',
      type: 'n8n-nodes-base.set',
      typeVersion: 3.4,
      position: [650, 400],
      parameters: {
        assignments: {
          assignments: [
            {
              id: 'error-assign',
              name: 'error_handled',
              value: 'true',
              type: 'boolean'
            }
          ]
        },
        options: {}
      }
    }
  ],
  connections: {
    Webhook: {
      main: [[{ node: 'HTTP Request', type: 'main', index: 0 }]]
    },
    'HTTP Request': {
      main: [[{ node: 'Handle Error', type: 'main', index: 0 }]],
      error: [[{ node: 'Handle Error', type: 'main', index: 0 }]]
    }
  },
  settings: {
    executionOrder: 'v1'
  }
};

/**
 * AI Agent workflow (langchain nodes)
 *
 * Tests langchain node support.
 */
export const AI_AGENT_WORKFLOW: Partial<Workflow> = {
  nodes: [
    {
      id: 'manual-1',
      name: 'When clicking "Test workflow"',
      type: 'n8n-nodes-base.manualTrigger',
      typeVersion: 1,
      position: [250, 300],
      parameters: {}
    },
    {
      id: 'agent-1',
      name: 'AI Agent',
      type: '@n8n/n8n-nodes-langchain.agent',
      typeVersion: 1.7,
      position: [450, 300],
      parameters: {
        promptType: 'define',
        text: '={{ $json.input }}',
        options: {}
      }
    }
  ],
  connections: {
    'When clicking "Test workflow"': {
      main: [[{ node: 'AI Agent', type: 'main', index: 0 }]]
    }
  },
  settings: {
    executionOrder: 'v1'
  }
};

/**
 * Workflow with n8n expressions
 *
 * Tests expression validation.
 */
export const EXPRESSION_WORKFLOW: Partial<Workflow> = {
  nodes: [
    {
      id: 'manual-1',
      name: 'Manual Trigger',
      type: 'n8n-nodes-base.manualTrigger',
      typeVersion: 1,
      position: [250, 300],
      parameters: {}
    },
    {
      id: 'set-1',
      name: 'Set Variables',
      type: 'n8n-nodes-base.set',
      typeVersion: 3.4,
      position: [450, 300],
      parameters: {
        assignments: {
          assignments: [
            {
              id: 'expr-1',
              name: 'timestamp',
              value: '={{ $now }}',
              type: 'string'
            },
            {
              id: 'expr-2',
              name: 'item_count',
              value: '={{ $json.items.length }}',
              type: 'number'
            },
            {
              id: 'expr-3',
              name: 'first_item',
              value: '={{ $node["Manual Trigger"].json }}',
              type: 'object'
            }
          ]
        },
        options: {}
      }
    }
  ],
  connections: {
    'Manual Trigger': {
      main: [[{ node: 'Set Variables', type: 'main', index: 0 }]]
    }
  },
  settings: {
    executionOrder: 'v1'
  }
};

/**
 * Get a fixture by name
 *
 * @param name - Fixture name
 * @returns Workflow fixture
 */
export function getFixture(
  name:
    | 'simple-webhook'
    | 'simple-http'
    | 'multi-node'
    | 'error-handling'
    | 'ai-agent'
    | 'expression'
): Partial<Workflow> {
  const fixtures = {
    'simple-webhook': SIMPLE_WEBHOOK_WORKFLOW,
    'simple-http': SIMPLE_HTTP_WORKFLOW,
    'multi-node': MULTI_NODE_WORKFLOW,
    'error-handling': ERROR_HANDLING_WORKFLOW,
    'ai-agent': AI_AGENT_WORKFLOW,
    expression: EXPRESSION_WORKFLOW
  };

  return JSON.parse(JSON.stringify(fixtures[name])); // Deep clone
}

/**
 * Create a minimal workflow with custom nodes
 *
 * @param nodes - Array of workflow nodes
 * @param connections - Optional connections object
 * @returns Workflow fixture
 */
export function createCustomWorkflow(
  nodes: WorkflowNode[],
  connections: Record<string, any> = {}
): Partial<Workflow> {
  return {
    nodes,
    connections,
    settings: {
      executionOrder: 'v1'
    }
  };
}

```

--------------------------------------------------------------------------------
/tests/integration/n8n-api/system/list-tools.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Integration Tests: handleListAvailableTools
 *
 * Tests tool listing functionality.
 * Covers tool discovery and configuration status.
 */

import { describe, it, expect, beforeEach } from 'vitest';
import { createMcpContext } from '../utils/mcp-context';
import { InstanceContext } from '../../../../src/types/instance-context';
import { handleListAvailableTools } from '../../../../src/mcp/handlers-n8n-manager';
import { ListToolsResponse } from '../utils/response-types';

describe('Integration: handleListAvailableTools', () => {
  let mcpContext: InstanceContext;

  beforeEach(() => {
    mcpContext = createMcpContext();
  });

  // ======================================================================
  // List All Tools
  // ======================================================================

  describe('Tool Listing', () => {
    it('should list all available tools organized by category', async () => {
      const response = await handleListAvailableTools(mcpContext);

      expect(response.success).toBe(true);
      expect(response.data).toBeDefined();

      const data = response.data as ListToolsResponse;

      // Verify tools array exists
      expect(data).toHaveProperty('tools');
      expect(Array.isArray(data.tools)).toBe(true);
      expect(data.tools.length).toBeGreaterThan(0);

      // Verify tool categories
      const categories = data.tools.map((cat: any) => cat.category);
      expect(categories).toContain('Workflow Management');
      expect(categories).toContain('Execution Management');
      expect(categories).toContain('System');

      // Verify each category has tools
      data.tools.forEach(category => {
        expect(category).toHaveProperty('category');
        expect(category).toHaveProperty('tools');
        expect(Array.isArray(category.tools)).toBe(true);
        expect(category.tools.length).toBeGreaterThan(0);

        // Verify each tool has required fields
        category.tools.forEach(tool => {
          expect(tool).toHaveProperty('name');
          expect(tool).toHaveProperty('description');
          expect(typeof tool.name).toBe('string');
          expect(typeof tool.description).toBe('string');
        });
      });
    });

    it('should include API configuration status', async () => {
      const response = await handleListAvailableTools(mcpContext);

      expect(response.success).toBe(true);
      const data = response.data as ListToolsResponse;

      // Verify configuration status
      expect(data).toHaveProperty('apiConfigured');
      expect(typeof data.apiConfigured).toBe('boolean');

      // Since tests run with API configured, should be true
      expect(data.apiConfigured).toBe(true);

      // Verify configuration details are present when configured
      if (data.apiConfigured) {
        expect(data).toHaveProperty('configuration');
        expect(data.configuration).toBeDefined();
        expect(data.configuration).toHaveProperty('apiUrl');
        expect(data.configuration).toHaveProperty('timeout');
        expect(data.configuration).toHaveProperty('maxRetries');
      }
    });

    it('should include API limitations information', async () => {
      const response = await handleListAvailableTools(mcpContext);

      expect(response.success).toBe(true);
      const data = response.data as ListToolsResponse;

      // Verify limitations are documented
      expect(data).toHaveProperty('limitations');
      expect(Array.isArray(data.limitations)).toBe(true);
      expect(data.limitations.length).toBeGreaterThan(0);

      // Verify limitations are informative strings
      data.limitations.forEach(limitation => {
        expect(typeof limitation).toBe('string');
        expect(limitation.length).toBeGreaterThan(0);
      });

      // Common known limitations
      const limitationsText = data.limitations.join(' ');
      expect(limitationsText).toContain('Cannot activate');
      expect(limitationsText).toContain('Cannot execute workflows directly');
    });
  });

  // ======================================================================
  // Workflow Management Tools
  // ======================================================================

  describe('Workflow Management Tools', () => {
    it('should include all workflow management tools', async () => {
      const response = await handleListAvailableTools(mcpContext);
      const data = response.data as ListToolsResponse;

      const workflowCategory = data.tools.find(cat => cat.category === 'Workflow Management');
      expect(workflowCategory).toBeDefined();

      const toolNames = workflowCategory!.tools.map(t => t.name);

      // Core workflow tools
      expect(toolNames).toContain('n8n_create_workflow');
      expect(toolNames).toContain('n8n_get_workflow');
      expect(toolNames).toContain('n8n_update_workflow');
      expect(toolNames).toContain('n8n_delete_workflow');
      expect(toolNames).toContain('n8n_list_workflows');

      // Enhanced workflow tools
      expect(toolNames).toContain('n8n_get_workflow_details');
      expect(toolNames).toContain('n8n_get_workflow_structure');
      expect(toolNames).toContain('n8n_get_workflow_minimal');
      expect(toolNames).toContain('n8n_validate_workflow');
      expect(toolNames).toContain('n8n_autofix_workflow');
    });
  });

  // ======================================================================
  // Execution Management Tools
  // ======================================================================

  describe('Execution Management Tools', () => {
    it('should include all execution management tools', async () => {
      const response = await handleListAvailableTools(mcpContext);
      const data = response.data as ListToolsResponse;

      const executionCategory = data.tools.find(cat => cat.category === 'Execution Management');
      expect(executionCategory).toBeDefined();

      const toolNames = executionCategory!.tools.map(t => t.name);

      expect(toolNames).toContain('n8n_trigger_webhook_workflow');
      expect(toolNames).toContain('n8n_get_execution');
      expect(toolNames).toContain('n8n_list_executions');
      expect(toolNames).toContain('n8n_delete_execution');
    });
  });

  // ======================================================================
  // System Tools
  // ======================================================================

  describe('System Tools', () => {
    it('should include system tools', async () => {
      const response = await handleListAvailableTools(mcpContext);
      const data = response.data as ListToolsResponse;

      const systemCategory = data.tools.find(cat => cat.category === 'System');
      expect(systemCategory).toBeDefined();

      const toolNames = systemCategory!.tools.map(t => t.name);

      expect(toolNames).toContain('n8n_health_check');
      expect(toolNames).toContain('n8n_list_available_tools');
    });
  });

  // ======================================================================
  // Response Format Verification
  // ======================================================================

  describe('Response Format', () => {
    it('should return complete tool list response structure', async () => {
      const response = await handleListAvailableTools(mcpContext);

      expect(response.success).toBe(true);
      expect(response.data).toBeDefined();

      const data = response.data as ListToolsResponse;

      // Verify all required fields
      expect(data).toHaveProperty('tools');
      expect(data).toHaveProperty('apiConfigured');
      expect(data).toHaveProperty('limitations');

      // Verify optional configuration field
      if (data.apiConfigured) {
        expect(data).toHaveProperty('configuration');
      }

      // Verify data types
      expect(Array.isArray(data.tools)).toBe(true);
      expect(typeof data.apiConfigured).toBe('boolean');
      expect(Array.isArray(data.limitations)).toBe(true);
    });
  });
});

```

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

```typescript
/**
 * Tests for ResourceSimilarityService
 */

import { describe, it, expect, beforeEach } from 'vitest';
import { ResourceSimilarityService } from '../../../src/services/resource-similarity-service';
import { NodeRepository } from '../../../src/database/node-repository';
import { createTestDatabase } from '../../utils/database-utils';

describe('ResourceSimilarityService', () => {
  let service: ResourceSimilarityService;
  let repository: NodeRepository;
  let testDb: any;

  beforeEach(async () => {
    testDb = await createTestDatabase();
    repository = testDb.nodeRepository;
    service = new ResourceSimilarityService(repository);

    // Add test node with resources
    const testNode = {
      nodeType: 'nodes-base.googleDrive',
      packageName: 'n8n-nodes-base',
      displayName: 'Google Drive',
      description: 'Access Google Drive',
      category: 'transform',
      style: 'declarative' as const,
      isAITool: false,
      isTrigger: false,
      isWebhook: false,
      isVersioned: true,
      version: '1',
      properties: [
        {
          name: 'resource',
          type: 'options',
          options: [
            { value: 'file', name: 'File' },
            { value: 'folder', name: 'Folder' },
            { value: 'drive', name: 'Shared Drive' },
            { value: 'fileFolder', name: 'File & Folder' }
          ]
        }
      ],
      operations: [],
      credentials: []
    };

    repository.saveNode(testNode);

    // Add Slack node for testing different patterns
    const slackNode = {
      nodeType: 'nodes-base.slack',
      packageName: 'n8n-nodes-base',
      displayName: 'Slack',
      description: 'Send messages to Slack',
      category: 'communication',
      style: 'declarative' as const,
      isAITool: false,
      isTrigger: false,
      isWebhook: false,
      isVersioned: true,
      version: '2',
      properties: [
        {
          name: 'resource',
          type: 'options',
          options: [
            { value: 'channel', name: 'Channel' },
            { value: 'message', name: 'Message' },
            { value: 'user', name: 'User' },
            { value: 'file', name: 'File' },
            { value: 'star', name: 'Star' }
          ]
        }
      ],
      operations: [],
      credentials: []
    };

    repository.saveNode(slackNode);
  });

  afterEach(async () => {
    if (testDb) {
      await testDb.cleanup();
    }
  });

  describe('findSimilarResources', () => {
    it('should find exact match', () => {
      const suggestions = service.findSimilarResources(
        'nodes-base.googleDrive',
        'file',
        5
      );

      expect(suggestions).toHaveLength(0); // No suggestions for valid resource
    });

    it('should suggest singular form for plural input', () => {
      const suggestions = service.findSimilarResources(
        'nodes-base.googleDrive',
        'files',
        5
      );

      expect(suggestions.length).toBeGreaterThan(0);
      expect(suggestions[0].value).toBe('file');
      expect(suggestions[0].confidence).toBeGreaterThanOrEqual(0.9);
      expect(suggestions[0].reason).toContain('singular');
    });

    it('should suggest singular form for folders', () => {
      const suggestions = service.findSimilarResources(
        'nodes-base.googleDrive',
        'folders',
        5
      );

      expect(suggestions.length).toBeGreaterThan(0);
      expect(suggestions[0].value).toBe('folder');
      expect(suggestions[0].confidence).toBeGreaterThanOrEqual(0.9);
    });

    it('should handle typos with Levenshtein distance', () => {
      const suggestions = service.findSimilarResources(
        'nodes-base.googleDrive',
        'flie',
        5
      );

      expect(suggestions.length).toBeGreaterThan(0);
      expect(suggestions[0].value).toBe('file');
      expect(suggestions[0].confidence).toBeGreaterThan(0.7);
    });

    it('should handle combined resources', () => {
      const suggestions = service.findSimilarResources(
        'nodes-base.googleDrive',
        'fileAndFolder',
        5
      );

      expect(suggestions.length).toBeGreaterThan(0);
      // Should suggest 'fileFolder' (the actual combined resource)
      const fileFolderSuggestion = suggestions.find(s => s.value === 'fileFolder');
      expect(fileFolderSuggestion).toBeDefined();
    });

    it('should return empty array for node not found', () => {
      const suggestions = service.findSimilarResources(
        'nodes-base.nonexistent',
        'resource',
        5
      );

      expect(suggestions).toEqual([]);
    });
  });

  describe('plural/singular detection', () => {
    it('should handle regular plurals (s)', () => {
      const suggestions = service.findSimilarResources(
        'nodes-base.slack',
        'channels',
        5
      );

      expect(suggestions.length).toBeGreaterThan(0);
      expect(suggestions[0].value).toBe('channel');
    });

    it('should handle plural ending in es', () => {
      const suggestions = service.findSimilarResources(
        'nodes-base.slack',
        'messages',
        5
      );

      expect(suggestions.length).toBeGreaterThan(0);
      expect(suggestions[0].value).toBe('message');
    });

    it('should handle plural ending in ies', () => {
      // Test with a hypothetical 'entities' -> 'entity' conversion
      const suggestions = service.findSimilarResources(
        'nodes-base.googleDrive',
        'entities',
        5
      );

      // Should not crash and provide some suggestions
      expect(suggestions).toBeDefined();
    });
  });

  describe('node-specific patterns', () => {
    it('should apply Google Drive specific patterns', () => {
      const suggestions = service.findSimilarResources(
        'nodes-base.googleDrive',
        'sharedDrives',
        5
      );

      expect(suggestions.length).toBeGreaterThan(0);
      const driveSuggestion = suggestions.find(s => s.value === 'drive');
      expect(driveSuggestion).toBeDefined();
    });

    it('should apply Slack specific patterns', () => {
      const suggestions = service.findSimilarResources(
        'nodes-base.slack',
        'users',
        5
      );

      expect(suggestions.length).toBeGreaterThan(0);
      expect(suggestions[0].value).toBe('user');
    });
  });

  describe('similarity calculation', () => {
    it('should rank exact matches highest', () => {
      const suggestions = service.findSimilarResources(
        'nodes-base.googleDrive',
        'file',
        5
      );

      expect(suggestions).toHaveLength(0); // Exact match, no suggestions
    });

    it('should rank substring matches high', () => {
      const suggestions = service.findSimilarResources(
        'nodes-base.googleDrive',
        'fil',
        5
      );

      expect(suggestions.length).toBeGreaterThan(0);
      const fileSuggestion = suggestions.find(s => s.value === 'file');
      expect(fileSuggestion).toBeDefined();
      expect(fileSuggestion!.confidence).toBeGreaterThanOrEqual(0.7);
    });
  });

  describe('caching', () => {
    it('should cache results for repeated queries', () => {
      // First call
      const suggestions1 = service.findSimilarResources(
        'nodes-base.googleDrive',
        'files',
        5
      );

      // Second call with same params
      const suggestions2 = service.findSimilarResources(
        'nodes-base.googleDrive',
        'files',
        5
      );

      expect(suggestions1).toEqual(suggestions2);
    });

    it('should clear cache when requested', () => {
      // Add to cache
      service.findSimilarResources(
        'nodes-base.googleDrive',
        'test',
        5
      );

      // Clear cache
      service.clearCache();

      // This would fetch fresh data (behavior is the same, just uncached)
      const suggestions = service.findSimilarResources(
        'nodes-base.googleDrive',
        'test',
        5
      );

      expect(suggestions).toBeDefined();
    });
  });
});
```

--------------------------------------------------------------------------------
/tests/unit/__mocks__/n8n-nodes-base.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { getNodeTypes, mockNodeBehavior, resetAllMocks, registerMockNode } from './n8n-nodes-base';

describe('n8n-nodes-base mock', () => {
  beforeEach(() => {
    resetAllMocks();
  });

  describe('getNodeTypes', () => {
    it('should return node types registry', () => {
      const registry = getNodeTypes();
      expect(registry).toBeDefined();
      expect(registry.getByName).toBeDefined();
      expect(registry.getByNameAndVersion).toBeDefined();
    });

    it('should retrieve webhook node', () => {
      const registry = getNodeTypes();
      const webhookNode = registry.getByName('webhook');
      
      expect(webhookNode).toBeDefined();
      expect(webhookNode?.description.name).toBe('webhook');
      expect(webhookNode?.description.group).toContain('trigger');
      expect(webhookNode?.webhook).toBeDefined();
    });

    it('should retrieve httpRequest node', () => {
      const registry = getNodeTypes();
      const httpNode = registry.getByName('httpRequest');
      
      expect(httpNode).toBeDefined();
      expect(httpNode?.description.name).toBe('httpRequest');
      expect(httpNode?.description.version).toBe(3);
      expect(httpNode?.execute).toBeDefined();
    });

    it('should retrieve slack node', () => {
      const registry = getNodeTypes();
      const slackNode = registry.getByName('slack');
      
      expect(slackNode).toBeDefined();
      expect(slackNode?.description.credentials).toHaveLength(1);
      expect(slackNode?.description.credentials?.[0].name).toBe('slackApi');
    });
  });

  describe('node execution', () => {
    it('should execute webhook node', async () => {
      const registry = getNodeTypes();
      const webhookNode = registry.getByName('webhook');
      
      const mockContext = {
        getWebhookName: vi.fn(() => 'default'),
        getBodyData: vi.fn(() => ({ test: 'data' })),
        getHeaderData: vi.fn(() => ({ 'content-type': 'application/json' })),
        getQueryData: vi.fn(() => ({ query: 'param' })),
        getRequestObject: vi.fn(),
        getResponseObject: vi.fn(),
        helpers: {
          returnJsonArray: vi.fn((data) => [{ json: data }]),
        },
      };

      const result = await webhookNode?.webhook?.call(mockContext as any);
      
      expect(result).toBeDefined();
      expect(result?.workflowData).toBeDefined();
      expect(result?.workflowData[0]).toHaveLength(1);
      expect(result?.workflowData[0][0].json).toMatchObject({
        headers: { 'content-type': 'application/json' },
        params: { query: 'param' },
        body: { test: 'data' },
      });
    });

    it('should execute httpRequest node', async () => {
      const registry = getNodeTypes();
      const httpNode = registry.getByName('httpRequest');
      
      const mockContext = {
        getInputData: vi.fn(() => [{ json: { test: 'input' } }]),
        getNodeParameter: vi.fn((name: string) => {
          if (name === 'method') return 'POST';
          if (name === 'url') return 'https://api.example.com';
          return '';
        }),
        getCredentials: vi.fn(),
        helpers: {
          returnJsonArray: vi.fn((data) => [{ json: data }]),
          httpRequest: vi.fn(),
          webhook: vi.fn(),
        },
      };

      const result = await httpNode?.execute?.call(mockContext as any);
      
      expect(result).toBeDefined();
      expect(result!).toHaveLength(1);
      expect(result![0]).toHaveLength(1);
      expect(result![0][0].json).toMatchObject({
        statusCode: 200,
        body: {
          success: true,
          method: 'POST',
          url: 'https://api.example.com',
        },
      });
    });
  });

  describe('mockNodeBehavior', () => {
    it('should override node execution behavior', async () => {
      const customExecute = vi.fn(async function() {
        return [[{ json: { custom: 'response' } }]];
      });

      mockNodeBehavior('httpRequest', {
        execute: customExecute,
      });

      const registry = getNodeTypes();
      const httpNode = registry.getByName('httpRequest');
      
      const mockContext = {
        getInputData: vi.fn(() => []),
        getNodeParameter: vi.fn(),
        getCredentials: vi.fn(),
        helpers: {
          returnJsonArray: vi.fn(),
          httpRequest: vi.fn(),
          webhook: vi.fn(),
        },
      };

      const result = await httpNode?.execute?.call(mockContext as any);
      
      expect(customExecute).toHaveBeenCalled();
      expect(result).toEqual([[{ json: { custom: 'response' } }]]);
    });

    it('should override node description', () => {
      mockNodeBehavior('slack', {
        description: {
          displayName: 'Custom Slack',
          version: 3,
          name: 'slack',
          group: ['output'],
          description: 'Send messages to Slack',
          defaults: { name: 'Slack' },
          inputs: ['main'],
          outputs: ['main'],
          properties: [],
        },
      });

      const registry = getNodeTypes();
      const slackNode = registry.getByName('slack');
      
      expect(slackNode?.description.displayName).toBe('Custom Slack');
      expect(slackNode?.description.version).toBe(3);
      expect(slackNode?.description.name).toBe('slack'); // Original preserved
    });
  });

  describe('registerMockNode', () => {
    it('should register custom node', () => {
      const customNode = {
        description: {
          displayName: 'Custom Node',
          name: 'customNode',
          group: ['transform'],
          version: 1,
          description: 'A custom test node',
          defaults: { name: 'Custom' },
          inputs: ['main'],
          outputs: ['main'],
          properties: [],
        },
        execute: vi.fn(async function() {
          return [[{ json: { custom: true } }]];
        }),
      };

      registerMockNode('customNode', customNode);

      const registry = getNodeTypes();
      const retrievedNode = registry.getByName('customNode');
      
      expect(retrievedNode).toBe(customNode);
      expect(retrievedNode?.description.name).toBe('customNode');
    });
  });

  describe('conditional nodes', () => {
    it('should execute if node with two outputs', async () => {
      const registry = getNodeTypes();
      const ifNode = registry.getByName('if');
      
      const mockContext = {
        getInputData: vi.fn(() => [
          { json: { value: 1 } },
          { json: { value: 2 } },
          { json: { value: 3 } },
          { json: { value: 4 } },
        ]),
        getNodeParameter: vi.fn(),
        getCredentials: vi.fn(),
        helpers: {
          returnJsonArray: vi.fn(),
          httpRequest: vi.fn(),
          webhook: vi.fn(),
        },
      };

      const result = await ifNode?.execute?.call(mockContext as any);
      
      expect(result!).toHaveLength(2); // true and false outputs
      expect(result![0]).toHaveLength(2); // even indices
      expect(result![1]).toHaveLength(2); // odd indices
    });

    it('should execute switch node with multiple outputs', async () => {
      const registry = getNodeTypes();
      const switchNode = registry.getByName('switch');
      
      const mockContext = {
        getInputData: vi.fn(() => [
          { json: { value: 1 } },
          { json: { value: 2 } },
          { json: { value: 3 } },
          { json: { value: 4 } },
        ]),
        getNodeParameter: vi.fn(),
        getCredentials: vi.fn(),
        helpers: {
          returnJsonArray: vi.fn(),
          httpRequest: vi.fn(),
          webhook: vi.fn(),
        },
      };

      const result = await switchNode?.execute?.call(mockContext as any);
      
      expect(result!).toHaveLength(4); // 4 outputs
      expect(result![0]).toHaveLength(1); // item 0
      expect(result![1]).toHaveLength(1); // item 1
      expect(result![2]).toHaveLength(1); // item 2
      expect(result![3]).toHaveLength(1); // item 3
    });
  });
});
```
Page 8/45FirstPrevNextLast