#
tokens: 49212/50000 3/615 files (page 35/45)
lines: off (toggle) GitHub
raw markdown copy
This is page 35 of 45. Use http://codebase.md/czlonkowski/n8n-mcp?lines=false&page={x} to view the full context.

# Directory Structure

```
├── _config.yml
├── .claude
│   └── agents
│       ├── code-reviewer.md
│       ├── context-manager.md
│       ├── debugger.md
│       ├── deployment-engineer.md
│       ├── mcp-backend-engineer.md
│       ├── n8n-mcp-tester.md
│       ├── technical-researcher.md
│       └── test-automator.md
├── .dockerignore
├── .env.docker
├── .env.example
├── .env.n8n.example
├── .env.test
├── .env.test.example
├── .github
│   ├── ABOUT.md
│   ├── BENCHMARK_THRESHOLDS.md
│   ├── FUNDING.yml
│   ├── gh-pages.yml
│   ├── secret_scanning.yml
│   └── workflows
│       ├── benchmark-pr.yml
│       ├── benchmark.yml
│       ├── docker-build-fast.yml
│       ├── docker-build-n8n.yml
│       ├── docker-build.yml
│       ├── release.yml
│       ├── test.yml
│       └── update-n8n-deps.yml
├── .gitignore
├── .npmignore
├── ATTRIBUTION.md
├── CHANGELOG.md
├── CLAUDE.md
├── codecov.yml
├── coverage.json
├── data
│   ├── .gitkeep
│   ├── nodes.db
│   ├── nodes.db-shm
│   ├── nodes.db-wal
│   └── templates.db
├── deploy
│   └── quick-deploy-n8n.sh
├── docker
│   ├── docker-entrypoint.sh
│   ├── n8n-mcp
│   ├── parse-config.js
│   └── README.md
├── docker-compose.buildkit.yml
├── docker-compose.extract.yml
├── docker-compose.n8n.yml
├── docker-compose.override.yml.example
├── docker-compose.test-n8n.yml
├── docker-compose.yml
├── Dockerfile
├── Dockerfile.railway
├── Dockerfile.test
├── docs
│   ├── AUTOMATED_RELEASES.md
│   ├── BENCHMARKS.md
│   ├── CHANGELOG.md
│   ├── CLAUDE_CODE_SETUP.md
│   ├── CLAUDE_INTERVIEW.md
│   ├── CODECOV_SETUP.md
│   ├── CODEX_SETUP.md
│   ├── CURSOR_SETUP.md
│   ├── DEPENDENCY_UPDATES.md
│   ├── DOCKER_README.md
│   ├── DOCKER_TROUBLESHOOTING.md
│   ├── FINAL_AI_VALIDATION_SPEC.md
│   ├── FLEXIBLE_INSTANCE_CONFIGURATION.md
│   ├── HTTP_DEPLOYMENT.md
│   ├── img
│   │   ├── cc_command.png
│   │   ├── cc_connected.png
│   │   ├── codex_connected.png
│   │   ├── cursor_tut.png
│   │   ├── Railway_api.png
│   │   ├── Railway_server_address.png
│   │   ├── vsc_ghcp_chat_agent_mode.png
│   │   ├── vsc_ghcp_chat_instruction_files.png
│   │   ├── vsc_ghcp_chat_thinking_tool.png
│   │   └── windsurf_tut.png
│   ├── INSTALLATION.md
│   ├── LIBRARY_USAGE.md
│   ├── local
│   │   ├── DEEP_DIVE_ANALYSIS_2025-10-02.md
│   │   ├── DEEP_DIVE_ANALYSIS_README.md
│   │   ├── Deep_dive_p1_p2.md
│   │   ├── integration-testing-plan.md
│   │   ├── integration-tests-phase1-summary.md
│   │   ├── N8N_AI_WORKFLOW_BUILDER_ANALYSIS.md
│   │   ├── P0_IMPLEMENTATION_PLAN.md
│   │   └── TEMPLATE_MINING_ANALYSIS.md
│   ├── MCP_ESSENTIALS_README.md
│   ├── MCP_QUICK_START_GUIDE.md
│   ├── N8N_DEPLOYMENT.md
│   ├── RAILWAY_DEPLOYMENT.md
│   ├── README_CLAUDE_SETUP.md
│   ├── README.md
│   ├── tools-documentation-usage.md
│   ├── VS_CODE_PROJECT_SETUP.md
│   ├── WINDSURF_SETUP.md
│   └── workflow-diff-examples.md
├── examples
│   └── enhanced-documentation-demo.js
├── fetch_log.txt
├── LICENSE
├── MEMORY_N8N_UPDATE.md
├── MEMORY_TEMPLATE_UPDATE.md
├── monitor_fetch.sh
├── N8N_HTTP_STREAMABLE_SETUP.md
├── n8n-nodes.db
├── P0-R3-TEST-PLAN.md
├── package-lock.json
├── package.json
├── package.runtime.json
├── PRIVACY.md
├── railway.json
├── README.md
├── renovate.json
├── scripts
│   ├── analyze-optimization.sh
│   ├── audit-schema-coverage.ts
│   ├── build-optimized.sh
│   ├── compare-benchmarks.js
│   ├── demo-optimization.sh
│   ├── deploy-http.sh
│   ├── deploy-to-vm.sh
│   ├── export-webhook-workflows.ts
│   ├── extract-changelog.js
│   ├── extract-from-docker.js
│   ├── extract-nodes-docker.sh
│   ├── extract-nodes-simple.sh
│   ├── format-benchmark-results.js
│   ├── generate-benchmark-stub.js
│   ├── generate-detailed-reports.js
│   ├── generate-test-summary.js
│   ├── http-bridge.js
│   ├── mcp-http-client.js
│   ├── migrate-nodes-fts.ts
│   ├── migrate-tool-docs.ts
│   ├── n8n-docs-mcp.service
│   ├── nginx-n8n-mcp.conf
│   ├── prebuild-fts5.ts
│   ├── prepare-release.js
│   ├── publish-npm-quick.sh
│   ├── publish-npm.sh
│   ├── quick-test.ts
│   ├── run-benchmarks-ci.js
│   ├── sync-runtime-version.js
│   ├── test-ai-validation-debug.ts
│   ├── test-code-node-enhancements.ts
│   ├── test-code-node-fixes.ts
│   ├── test-docker-config.sh
│   ├── test-docker-fingerprint.ts
│   ├── test-docker-optimization.sh
│   ├── test-docker.sh
│   ├── test-empty-connection-validation.ts
│   ├── test-error-message-tracking.ts
│   ├── test-error-output-validation.ts
│   ├── test-error-validation.js
│   ├── test-essentials.ts
│   ├── test-expression-code-validation.ts
│   ├── test-expression-format-validation.js
│   ├── test-fts5-search.ts
│   ├── test-fuzzy-fix.ts
│   ├── test-fuzzy-simple.ts
│   ├── test-helpers-validation.ts
│   ├── test-http-search.ts
│   ├── test-http.sh
│   ├── test-jmespath-validation.ts
│   ├── test-multi-tenant-simple.ts
│   ├── test-multi-tenant.ts
│   ├── test-n8n-integration.sh
│   ├── test-node-info.js
│   ├── test-node-type-validation.ts
│   ├── test-nodes-base-prefix.ts
│   ├── test-operation-validation.ts
│   ├── test-optimized-docker.sh
│   ├── test-release-automation.js
│   ├── test-search-improvements.ts
│   ├── test-security.ts
│   ├── test-single-session.sh
│   ├── test-sqljs-triggers.ts
│   ├── test-telemetry-debug.ts
│   ├── test-telemetry-direct.ts
│   ├── test-telemetry-env.ts
│   ├── test-telemetry-integration.ts
│   ├── test-telemetry-no-select.ts
│   ├── test-telemetry-security.ts
│   ├── test-telemetry-simple.ts
│   ├── test-typeversion-validation.ts
│   ├── test-url-configuration.ts
│   ├── test-user-id-persistence.ts
│   ├── test-webhook-validation.ts
│   ├── test-workflow-insert.ts
│   ├── test-workflow-sanitizer.ts
│   ├── test-workflow-tracking-debug.ts
│   ├── update-and-publish-prep.sh
│   ├── update-n8n-deps.js
│   ├── update-readme-version.js
│   ├── vitest-benchmark-json-reporter.js
│   └── vitest-benchmark-reporter.ts
├── SECURITY.md
├── src
│   ├── config
│   │   └── n8n-api.ts
│   ├── data
│   │   └── canonical-ai-tool-examples.json
│   ├── database
│   │   ├── database-adapter.ts
│   │   ├── migrations
│   │   │   └── add-template-node-configs.sql
│   │   ├── node-repository.ts
│   │   ├── nodes.db
│   │   ├── schema-optimized.sql
│   │   └── schema.sql
│   ├── errors
│   │   └── validation-service-error.ts
│   ├── http-server-single-session.ts
│   ├── http-server.ts
│   ├── index.ts
│   ├── loaders
│   │   └── node-loader.ts
│   ├── mappers
│   │   └── docs-mapper.ts
│   ├── mcp
│   │   ├── handlers-n8n-manager.ts
│   │   ├── handlers-workflow-diff.ts
│   │   ├── index.ts
│   │   ├── server.ts
│   │   ├── stdio-wrapper.ts
│   │   ├── tool-docs
│   │   │   ├── configuration
│   │   │   │   ├── get-node-as-tool-info.ts
│   │   │   │   ├── get-node-documentation.ts
│   │   │   │   ├── get-node-essentials.ts
│   │   │   │   ├── get-node-info.ts
│   │   │   │   ├── get-property-dependencies.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── search-node-properties.ts
│   │   │   ├── discovery
│   │   │   │   ├── get-database-statistics.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── list-ai-tools.ts
│   │   │   │   ├── list-nodes.ts
│   │   │   │   └── search-nodes.ts
│   │   │   ├── guides
│   │   │   │   ├── ai-agents-guide.ts
│   │   │   │   └── index.ts
│   │   │   ├── index.ts
│   │   │   ├── system
│   │   │   │   ├── index.ts
│   │   │   │   ├── n8n-diagnostic.ts
│   │   │   │   ├── n8n-health-check.ts
│   │   │   │   ├── n8n-list-available-tools.ts
│   │   │   │   └── tools-documentation.ts
│   │   │   ├── templates
│   │   │   │   ├── get-template.ts
│   │   │   │   ├── get-templates-for-task.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── list-node-templates.ts
│   │   │   │   ├── list-tasks.ts
│   │   │   │   ├── search-templates-by-metadata.ts
│   │   │   │   └── search-templates.ts
│   │   │   ├── types.ts
│   │   │   ├── validation
│   │   │   │   ├── index.ts
│   │   │   │   ├── validate-node-minimal.ts
│   │   │   │   ├── validate-node-operation.ts
│   │   │   │   ├── validate-workflow-connections.ts
│   │   │   │   ├── validate-workflow-expressions.ts
│   │   │   │   └── validate-workflow.ts
│   │   │   └── workflow_management
│   │   │       ├── index.ts
│   │   │       ├── n8n-autofix-workflow.ts
│   │   │       ├── n8n-create-workflow.ts
│   │   │       ├── n8n-delete-execution.ts
│   │   │       ├── n8n-delete-workflow.ts
│   │   │       ├── n8n-get-execution.ts
│   │   │       ├── n8n-get-workflow-details.ts
│   │   │       ├── n8n-get-workflow-minimal.ts
│   │   │       ├── n8n-get-workflow-structure.ts
│   │   │       ├── n8n-get-workflow.ts
│   │   │       ├── n8n-list-executions.ts
│   │   │       ├── n8n-list-workflows.ts
│   │   │       ├── n8n-trigger-webhook-workflow.ts
│   │   │       ├── n8n-update-full-workflow.ts
│   │   │       ├── n8n-update-partial-workflow.ts
│   │   │       └── n8n-validate-workflow.ts
│   │   ├── tools-documentation.ts
│   │   ├── tools-n8n-friendly.ts
│   │   ├── tools-n8n-manager.ts
│   │   ├── tools.ts
│   │   └── workflow-examples.ts
│   ├── mcp-engine.ts
│   ├── mcp-tools-engine.ts
│   ├── n8n
│   │   ├── MCPApi.credentials.ts
│   │   └── MCPNode.node.ts
│   ├── parsers
│   │   ├── node-parser.ts
│   │   ├── property-extractor.ts
│   │   └── simple-parser.ts
│   ├── scripts
│   │   ├── debug-http-search.ts
│   │   ├── extract-from-docker.ts
│   │   ├── fetch-templates-robust.ts
│   │   ├── fetch-templates.ts
│   │   ├── rebuild-database.ts
│   │   ├── rebuild-optimized.ts
│   │   ├── rebuild.ts
│   │   ├── sanitize-templates.ts
│   │   ├── seed-canonical-ai-examples.ts
│   │   ├── test-autofix-documentation.ts
│   │   ├── test-autofix-workflow.ts
│   │   ├── test-execution-filtering.ts
│   │   ├── test-node-suggestions.ts
│   │   ├── test-protocol-negotiation.ts
│   │   ├── test-summary.ts
│   │   ├── test-webhook-autofix.ts
│   │   ├── validate.ts
│   │   └── validation-summary.ts
│   ├── services
│   │   ├── ai-node-validator.ts
│   │   ├── ai-tool-validators.ts
│   │   ├── confidence-scorer.ts
│   │   ├── config-validator.ts
│   │   ├── enhanced-config-validator.ts
│   │   ├── example-generator.ts
│   │   ├── execution-processor.ts
│   │   ├── expression-format-validator.ts
│   │   ├── expression-validator.ts
│   │   ├── n8n-api-client.ts
│   │   ├── n8n-validation.ts
│   │   ├── node-documentation-service.ts
│   │   ├── node-similarity-service.ts
│   │   ├── node-specific-validators.ts
│   │   ├── operation-similarity-service.ts
│   │   ├── property-dependencies.ts
│   │   ├── property-filter.ts
│   │   ├── resource-similarity-service.ts
│   │   ├── sqlite-storage-service.ts
│   │   ├── task-templates.ts
│   │   ├── universal-expression-validator.ts
│   │   ├── workflow-auto-fixer.ts
│   │   ├── workflow-diff-engine.ts
│   │   └── workflow-validator.ts
│   ├── telemetry
│   │   ├── batch-processor.ts
│   │   ├── config-manager.ts
│   │   ├── early-error-logger.ts
│   │   ├── error-sanitization-utils.ts
│   │   ├── error-sanitizer.ts
│   │   ├── event-tracker.ts
│   │   ├── event-validator.ts
│   │   ├── index.ts
│   │   ├── performance-monitor.ts
│   │   ├── rate-limiter.ts
│   │   ├── startup-checkpoints.ts
│   │   ├── telemetry-error.ts
│   │   ├── telemetry-manager.ts
│   │   ├── telemetry-types.ts
│   │   └── workflow-sanitizer.ts
│   ├── templates
│   │   ├── batch-processor.ts
│   │   ├── metadata-generator.ts
│   │   ├── README.md
│   │   ├── template-fetcher.ts
│   │   ├── template-repository.ts
│   │   └── template-service.ts
│   ├── types
│   │   ├── index.ts
│   │   ├── instance-context.ts
│   │   ├── n8n-api.ts
│   │   ├── node-types.ts
│   │   └── workflow-diff.ts
│   └── utils
│       ├── auth.ts
│       ├── bridge.ts
│       ├── cache-utils.ts
│       ├── console-manager.ts
│       ├── documentation-fetcher.ts
│       ├── enhanced-documentation-fetcher.ts
│       ├── error-handler.ts
│       ├── example-generator.ts
│       ├── fixed-collection-validator.ts
│       ├── logger.ts
│       ├── mcp-client.ts
│       ├── n8n-errors.ts
│       ├── node-source-extractor.ts
│       ├── node-type-normalizer.ts
│       ├── node-type-utils.ts
│       ├── node-utils.ts
│       ├── npm-version-checker.ts
│       ├── protocol-version.ts
│       ├── simple-cache.ts
│       ├── ssrf-protection.ts
│       ├── template-node-resolver.ts
│       ├── template-sanitizer.ts
│       ├── url-detector.ts
│       ├── validation-schemas.ts
│       └── version.ts
├── test-output.txt
├── test-reinit-fix.sh
├── tests
│   ├── __snapshots__
│   │   └── .gitkeep
│   ├── auth.test.ts
│   ├── benchmarks
│   │   ├── database-queries.bench.ts
│   │   ├── index.ts
│   │   ├── mcp-tools.bench.ts
│   │   ├── mcp-tools.bench.ts.disabled
│   │   ├── mcp-tools.bench.ts.skip
│   │   ├── node-loading.bench.ts.disabled
│   │   ├── README.md
│   │   ├── search-operations.bench.ts.disabled
│   │   └── validation-performance.bench.ts.disabled
│   ├── bridge.test.ts
│   ├── comprehensive-extraction-test.js
│   ├── data
│   │   └── .gitkeep
│   ├── debug-slack-doc.js
│   ├── demo-enhanced-documentation.js
│   ├── docker-tests-README.md
│   ├── error-handler.test.ts
│   ├── examples
│   │   └── using-database-utils.test.ts
│   ├── extracted-nodes-db
│   │   ├── database-import.json
│   │   ├── extraction-report.json
│   │   ├── insert-nodes.sql
│   │   ├── n8n-nodes-base__Airtable.json
│   │   ├── n8n-nodes-base__Discord.json
│   │   ├── n8n-nodes-base__Function.json
│   │   ├── n8n-nodes-base__HttpRequest.json
│   │   ├── n8n-nodes-base__If.json
│   │   ├── n8n-nodes-base__Slack.json
│   │   ├── n8n-nodes-base__SplitInBatches.json
│   │   └── n8n-nodes-base__Webhook.json
│   ├── factories
│   │   ├── node-factory.ts
│   │   └── property-definition-factory.ts
│   ├── fixtures
│   │   ├── .gitkeep
│   │   ├── database
│   │   │   └── test-nodes.json
│   │   ├── factories
│   │   │   ├── node.factory.ts
│   │   │   └── parser-node.factory.ts
│   │   └── template-configs.ts
│   ├── helpers
│   │   └── env-helpers.ts
│   ├── http-server-auth.test.ts
│   ├── integration
│   │   ├── ai-validation
│   │   │   ├── ai-agent-validation.test.ts
│   │   │   ├── ai-tool-validation.test.ts
│   │   │   ├── chat-trigger-validation.test.ts
│   │   │   ├── e2e-validation.test.ts
│   │   │   ├── helpers.ts
│   │   │   ├── llm-chain-validation.test.ts
│   │   │   ├── README.md
│   │   │   └── TEST_REPORT.md
│   │   ├── ci
│   │   │   └── database-population.test.ts
│   │   ├── database
│   │   │   ├── connection-management.test.ts
│   │   │   ├── empty-database.test.ts
│   │   │   ├── fts5-search.test.ts
│   │   │   ├── node-fts5-search.test.ts
│   │   │   ├── node-repository.test.ts
│   │   │   ├── performance.test.ts
│   │   │   ├── sqljs-memory-leak.test.ts
│   │   │   ├── template-node-configs.test.ts
│   │   │   ├── template-repository.test.ts
│   │   │   ├── test-utils.ts
│   │   │   └── transactions.test.ts
│   │   ├── database-integration.test.ts
│   │   ├── docker
│   │   │   ├── docker-config.test.ts
│   │   │   ├── docker-entrypoint.test.ts
│   │   │   └── test-helpers.ts
│   │   ├── flexible-instance-config.test.ts
│   │   ├── mcp
│   │   │   └── template-examples-e2e.test.ts
│   │   ├── mcp-protocol
│   │   │   ├── basic-connection.test.ts
│   │   │   ├── error-handling.test.ts
│   │   │   ├── performance.test.ts
│   │   │   ├── protocol-compliance.test.ts
│   │   │   ├── README.md
│   │   │   ├── session-management.test.ts
│   │   │   ├── test-helpers.ts
│   │   │   ├── tool-invocation.test.ts
│   │   │   └── workflow-error-validation.test.ts
│   │   ├── msw-setup.test.ts
│   │   ├── n8n-api
│   │   │   ├── executions
│   │   │   │   ├── delete-execution.test.ts
│   │   │   │   ├── get-execution.test.ts
│   │   │   │   ├── list-executions.test.ts
│   │   │   │   └── trigger-webhook.test.ts
│   │   │   ├── scripts
│   │   │   │   └── cleanup-orphans.ts
│   │   │   ├── system
│   │   │   │   ├── diagnostic.test.ts
│   │   │   │   ├── health-check.test.ts
│   │   │   │   └── list-tools.test.ts
│   │   │   ├── test-connection.ts
│   │   │   ├── types
│   │   │   │   └── mcp-responses.ts
│   │   │   ├── utils
│   │   │   │   ├── cleanup-helpers.ts
│   │   │   │   ├── credentials.ts
│   │   │   │   ├── factories.ts
│   │   │   │   ├── fixtures.ts
│   │   │   │   ├── mcp-context.ts
│   │   │   │   ├── n8n-client.ts
│   │   │   │   ├── node-repository.ts
│   │   │   │   ├── response-types.ts
│   │   │   │   ├── test-context.ts
│   │   │   │   └── webhook-workflows.ts
│   │   │   └── workflows
│   │   │       ├── autofix-workflow.test.ts
│   │   │       ├── create-workflow.test.ts
│   │   │       ├── delete-workflow.test.ts
│   │   │       ├── get-workflow-details.test.ts
│   │   │       ├── get-workflow-minimal.test.ts
│   │   │       ├── get-workflow-structure.test.ts
│   │   │       ├── get-workflow.test.ts
│   │   │       ├── list-workflows.test.ts
│   │   │       ├── smart-parameters.test.ts
│   │   │       ├── update-partial-workflow.test.ts
│   │   │       ├── update-workflow.test.ts
│   │   │       └── validate-workflow.test.ts
│   │   ├── security
│   │   │   ├── command-injection-prevention.test.ts
│   │   │   └── rate-limiting.test.ts
│   │   ├── setup
│   │   │   ├── integration-setup.ts
│   │   │   └── msw-test-server.ts
│   │   ├── telemetry
│   │   │   ├── docker-user-id-stability.test.ts
│   │   │   └── mcp-telemetry.test.ts
│   │   ├── templates
│   │   │   └── metadata-operations.test.ts
│   │   └── workflow-creation-node-type-format.test.ts
│   ├── logger.test.ts
│   ├── MOCKING_STRATEGY.md
│   ├── mocks
│   │   ├── n8n-api
│   │   │   ├── data
│   │   │   │   ├── credentials.ts
│   │   │   │   ├── executions.ts
│   │   │   │   └── workflows.ts
│   │   │   ├── handlers.ts
│   │   │   └── index.ts
│   │   └── README.md
│   ├── node-storage-export.json
│   ├── setup
│   │   ├── global-setup.ts
│   │   ├── msw-setup.ts
│   │   ├── TEST_ENV_DOCUMENTATION.md
│   │   └── test-env.ts
│   ├── test-database-extraction.js
│   ├── test-direct-extraction.js
│   ├── test-enhanced-documentation.js
│   ├── test-enhanced-integration.js
│   ├── test-mcp-extraction.js
│   ├── test-mcp-server-extraction.js
│   ├── test-mcp-tools-integration.js
│   ├── test-node-documentation-service.js
│   ├── test-node-list.js
│   ├── test-package-info.js
│   ├── test-parsing-operations.js
│   ├── test-slack-node-complete.js
│   ├── test-small-rebuild.js
│   ├── test-sqlite-search.js
│   ├── test-storage-system.js
│   ├── unit
│   │   ├── __mocks__
│   │   │   ├── n8n-nodes-base.test.ts
│   │   │   ├── n8n-nodes-base.ts
│   │   │   └── README.md
│   │   ├── database
│   │   │   ├── __mocks__
│   │   │   │   └── better-sqlite3.ts
│   │   │   ├── database-adapter-unit.test.ts
│   │   │   ├── node-repository-core.test.ts
│   │   │   ├── node-repository-operations.test.ts
│   │   │   ├── node-repository-outputs.test.ts
│   │   │   ├── README.md
│   │   │   └── template-repository-core.test.ts
│   │   ├── docker
│   │   │   ├── config-security.test.ts
│   │   │   ├── edge-cases.test.ts
│   │   │   ├── parse-config.test.ts
│   │   │   └── serve-command.test.ts
│   │   ├── errors
│   │   │   └── validation-service-error.test.ts
│   │   ├── examples
│   │   │   └── using-n8n-nodes-base-mock.test.ts
│   │   ├── flexible-instance-security-advanced.test.ts
│   │   ├── flexible-instance-security.test.ts
│   │   ├── http-server
│   │   │   └── multi-tenant-support.test.ts
│   │   ├── http-server-n8n-mode.test.ts
│   │   ├── http-server-n8n-reinit.test.ts
│   │   ├── http-server-session-management.test.ts
│   │   ├── loaders
│   │   │   └── node-loader.test.ts
│   │   ├── mappers
│   │   │   └── docs-mapper.test.ts
│   │   ├── mcp
│   │   │   ├── get-node-essentials-examples.test.ts
│   │   │   ├── handlers-n8n-manager-simple.test.ts
│   │   │   ├── handlers-n8n-manager.test.ts
│   │   │   ├── handlers-workflow-diff.test.ts
│   │   │   ├── lru-cache-behavior.test.ts
│   │   │   ├── multi-tenant-tool-listing.test.ts.disabled
│   │   │   ├── parameter-validation.test.ts
│   │   │   ├── search-nodes-examples.test.ts
│   │   │   ├── tools-documentation.test.ts
│   │   │   └── tools.test.ts
│   │   ├── monitoring
│   │   │   └── cache-metrics.test.ts
│   │   ├── MULTI_TENANT_TEST_COVERAGE.md
│   │   ├── multi-tenant-integration.test.ts
│   │   ├── parsers
│   │   │   ├── node-parser-outputs.test.ts
│   │   │   ├── node-parser.test.ts
│   │   │   ├── property-extractor.test.ts
│   │   │   └── simple-parser.test.ts
│   │   ├── scripts
│   │   │   └── fetch-templates-extraction.test.ts
│   │   ├── services
│   │   │   ├── ai-node-validator.test.ts
│   │   │   ├── ai-tool-validators.test.ts
│   │   │   ├── confidence-scorer.test.ts
│   │   │   ├── config-validator-basic.test.ts
│   │   │   ├── config-validator-edge-cases.test.ts
│   │   │   ├── config-validator-node-specific.test.ts
│   │   │   ├── config-validator-security.test.ts
│   │   │   ├── debug-validator.test.ts
│   │   │   ├── enhanced-config-validator-integration.test.ts
│   │   │   ├── enhanced-config-validator-operations.test.ts
│   │   │   ├── enhanced-config-validator.test.ts
│   │   │   ├── example-generator.test.ts
│   │   │   ├── execution-processor.test.ts
│   │   │   ├── expression-format-validator.test.ts
│   │   │   ├── expression-validator-edge-cases.test.ts
│   │   │   ├── expression-validator.test.ts
│   │   │   ├── fixed-collection-validation.test.ts
│   │   │   ├── loop-output-edge-cases.test.ts
│   │   │   ├── n8n-api-client.test.ts
│   │   │   ├── n8n-validation.test.ts
│   │   │   ├── node-similarity-service.test.ts
│   │   │   ├── node-specific-validators.test.ts
│   │   │   ├── operation-similarity-service-comprehensive.test.ts
│   │   │   ├── operation-similarity-service.test.ts
│   │   │   ├── property-dependencies.test.ts
│   │   │   ├── property-filter-edge-cases.test.ts
│   │   │   ├── property-filter.test.ts
│   │   │   ├── resource-similarity-service-comprehensive.test.ts
│   │   │   ├── resource-similarity-service.test.ts
│   │   │   ├── task-templates.test.ts
│   │   │   ├── template-service.test.ts
│   │   │   ├── universal-expression-validator.test.ts
│   │   │   ├── validation-fixes.test.ts
│   │   │   ├── workflow-auto-fixer.test.ts
│   │   │   ├── workflow-diff-engine.test.ts
│   │   │   ├── workflow-fixed-collection-validation.test.ts
│   │   │   ├── workflow-validator-comprehensive.test.ts
│   │   │   ├── workflow-validator-edge-cases.test.ts
│   │   │   ├── workflow-validator-error-outputs.test.ts
│   │   │   ├── workflow-validator-expression-format.test.ts
│   │   │   ├── workflow-validator-loops-simple.test.ts
│   │   │   ├── workflow-validator-loops.test.ts
│   │   │   ├── workflow-validator-mocks.test.ts
│   │   │   ├── workflow-validator-performance.test.ts
│   │   │   ├── workflow-validator-with-mocks.test.ts
│   │   │   └── workflow-validator.test.ts
│   │   ├── telemetry
│   │   │   ├── batch-processor.test.ts
│   │   │   ├── config-manager.test.ts
│   │   │   ├── event-tracker.test.ts
│   │   │   ├── event-validator.test.ts
│   │   │   ├── rate-limiter.test.ts
│   │   │   ├── telemetry-error.test.ts
│   │   │   ├── telemetry-manager.test.ts
│   │   │   ├── v2.18.3-fixes-verification.test.ts
│   │   │   └── workflow-sanitizer.test.ts
│   │   ├── templates
│   │   │   ├── batch-processor.test.ts
│   │   │   ├── metadata-generator.test.ts
│   │   │   ├── template-repository-metadata.test.ts
│   │   │   └── template-repository-security.test.ts
│   │   ├── test-env-example.test.ts
│   │   ├── test-infrastructure.test.ts
│   │   ├── types
│   │   │   ├── instance-context-coverage.test.ts
│   │   │   └── instance-context-multi-tenant.test.ts
│   │   ├── utils
│   │   │   ├── auth-timing-safe.test.ts
│   │   │   ├── cache-utils.test.ts
│   │   │   ├── console-manager.test.ts
│   │   │   ├── database-utils.test.ts
│   │   │   ├── fixed-collection-validator.test.ts
│   │   │   ├── n8n-errors.test.ts
│   │   │   ├── node-type-normalizer.test.ts
│   │   │   ├── node-type-utils.test.ts
│   │   │   ├── node-utils.test.ts
│   │   │   ├── simple-cache-memory-leak-fix.test.ts
│   │   │   ├── ssrf-protection.test.ts
│   │   │   └── template-node-resolver.test.ts
│   │   └── validation-fixes.test.ts
│   └── utils
│       ├── assertions.ts
│       ├── builders
│       │   └── workflow.builder.ts
│       ├── data-generators.ts
│       ├── database-utils.ts
│       ├── README.md
│       └── test-helpers.ts
├── thumbnail.png
├── tsconfig.build.json
├── tsconfig.json
├── types
│   ├── mcp.d.ts
│   └── test-env.d.ts
├── verify-telemetry-fix.js
├── versioned-nodes.md
├── vitest.config.benchmark.ts
├── vitest.config.integration.ts
└── vitest.config.ts
```

# Files

--------------------------------------------------------------------------------
/src/mcp/handlers-n8n-manager.ts:
--------------------------------------------------------------------------------

```typescript
import { N8nApiClient } from '../services/n8n-api-client';
import { getN8nApiConfig, getN8nApiConfigFromContext } from '../config/n8n-api';
import {
  Workflow,
  WorkflowNode,
  WorkflowConnection,
  ExecutionStatus,
  WebhookRequest,
  McpToolResponse,
  ExecutionFilterOptions,
  ExecutionMode
} from '../types/n8n-api';
import {
  validateWorkflowStructure,
  hasWebhookTrigger,
  getWebhookUrl
} from '../services/n8n-validation';
import {
  N8nApiError,
  N8nNotFoundError,
  getUserFriendlyErrorMessage,
  formatExecutionError,
  formatNoExecutionError
} from '../utils/n8n-errors';
import { logger } from '../utils/logger';
import { z } from 'zod';
import { WorkflowValidator } from '../services/workflow-validator';
import { EnhancedConfigValidator } from '../services/enhanced-config-validator';
import { NodeRepository } from '../database/node-repository';
import { InstanceContext, validateInstanceContext } from '../types/instance-context';
import { NodeTypeNormalizer } from '../utils/node-type-normalizer';
import { WorkflowAutoFixer, AutoFixConfig } from '../services/workflow-auto-fixer';
import { ExpressionFormatValidator, ExpressionFormatIssue } from '../services/expression-format-validator';
import { handleUpdatePartialWorkflow } from './handlers-workflow-diff';
import { telemetry } from '../telemetry';
import {
  createCacheKey,
  createInstanceCache,
  CacheMutex,
  cacheMetrics,
  withRetry,
  getCacheStatistics
} from '../utils/cache-utils';
import { processExecution } from '../services/execution-processor';
import { checkNpmVersion, formatVersionMessage } from '../utils/npm-version-checker';

// ========================================================================
// TypeScript Interfaces for Type Safety
// ========================================================================

/**
 * Health Check Response Data Structure
 */
interface HealthCheckResponseData {
  status: string;
  instanceId?: string;
  n8nVersion?: string;
  features?: Record<string, unknown>;
  apiUrl?: string;
  mcpVersion: string;
  supportedN8nVersion?: string;
  versionCheck: {
    current: string;
    latest: string | null;
    upToDate: boolean;
    message: string;
    updateCommand?: string;
  };
  performance: {
    responseTimeMs: number;
    cacheHitRate: string;
    cachedInstances: number;
  };
  nextSteps?: string[];
  updateWarning?: string;
}

/**
 * Cloud Platform Guide Structure
 */
interface CloudPlatformGuide {
  name: string;
  troubleshooting: string[];
}

/**
 * Workflow Validation Response Data
 */
interface WorkflowValidationResponse {
  valid: boolean;
  workflowId?: string;
  workflowName?: string;
  summary: {
    totalNodes: number;
    enabledNodes: number;
    triggerNodes: number;
    validConnections: number;
    invalidConnections: number;
    expressionsValidated: number;
    errorCount: number;
    warningCount: number;
  };
  errors?: Array<{
    node: string;
    nodeName?: string;
    message: string;
    details?: Record<string, unknown>;
  }>;
  warnings?: Array<{
    node: string;
    nodeName?: string;
    message: string;
    details?: Record<string, unknown>;
  }>;
  suggestions?: unknown[];
}

/**
 * Diagnostic Response Data Structure
 */
interface DiagnosticResponseData {
  timestamp: string;
  environment: {
    N8N_API_URL: string | null;
    N8N_API_KEY: string | null;
    NODE_ENV: string;
    MCP_MODE: string;
    isDocker: boolean;
    cloudPlatform: string | null;
    nodeVersion: string;
    platform: string;
  };
  apiConfiguration: {
    configured: boolean;
    status: {
      configured: boolean;
      connected: boolean;
      error: string | null;
      version: string | null;
    };
    config: {
      baseUrl: string;
      timeout: number;
      maxRetries: number;
    } | null;
  };
  versionInfo: {
    current: string;
    latest: string | null;
    upToDate: boolean;
    message: string;
    updateCommand?: string;
  };
  toolsAvailability: {
    documentationTools: {
      count: number;
      enabled: boolean;
      description: string;
    };
    managementTools: {
      count: number;
      enabled: boolean;
      description: string;
    };
    totalAvailable: number;
  };
  performance: {
    diagnosticResponseTimeMs: number;
    cacheHitRate: string;
    cachedInstances: number;
  };
  modeSpecificDebug: Record<string, unknown>;
  dockerDebug?: Record<string, unknown>;
  cloudPlatformDebug?: CloudPlatformGuide;
  nextSteps?: Record<string, unknown>;
  troubleshooting?: Record<string, unknown>;
  setupGuide?: Record<string, unknown>;
  updateWarning?: Record<string, unknown>;
  debug?: Record<string, unknown>;
  [key: string]: unknown; // Allow dynamic property access for optional fields
}

// ========================================================================
// Singleton n8n API client instance (backward compatibility)
let defaultApiClient: N8nApiClient | null = null;
let lastDefaultConfigUrl: string | null = null;

// Mutex for cache operations to prevent race conditions
const cacheMutex = new CacheMutex();

// Instance-specific API clients cache with LRU eviction and TTL
const instanceClients = createInstanceCache<N8nApiClient>((client, key) => {
  // Clean up when evicting from cache
  logger.debug('Evicting API client from cache', {
    cacheKey: key.substring(0, 8) + '...' // Only log partial key for security
  });
});

/**
 * Get or create API client with flexible instance support
 * Supports both singleton mode (using environment variables) and instance-specific mode.
 * Uses LRU cache with mutex protection for thread-safe operations.
 *
 * @param context - Optional instance context for instance-specific configuration
 * @returns API client configured for the instance or environment, or null if not configured
 *
 * @example
 * // Using environment variables (singleton mode)
 * const client = getN8nApiClient();
 *
 * @example
 * // Using instance context
 * const client = getN8nApiClient({
 *   n8nApiUrl: 'https://customer.n8n.cloud',
 *   n8nApiKey: 'api-key-123',
 *   instanceId: 'customer-1'
 * });
 */
/**
 * Get cache statistics for monitoring
 * @returns Formatted cache statistics string
 */
export function getInstanceCacheStatistics(): string {
  return getCacheStatistics();
}

/**
 * Get raw cache metrics for detailed monitoring
 * @returns Raw cache metrics object
 */
export function getInstanceCacheMetrics() {
  return cacheMetrics.getMetrics();
}

/**
 * Clear the instance cache for testing or maintenance
 */
export function clearInstanceCache(): void {
  instanceClients.clear();
  cacheMetrics.recordClear();
  cacheMetrics.updateSize(0, instanceClients.max);
}

export function getN8nApiClient(context?: InstanceContext): N8nApiClient | null {
  // If context provided with n8n config, use instance-specific client
  if (context?.n8nApiUrl && context?.n8nApiKey) {
    // Validate context before using
    const validation = validateInstanceContext(context);
    if (!validation.valid) {
      logger.warn('Invalid instance context provided', {
        instanceId: context.instanceId,
        errors: validation.errors
      });
      return null;
    }
    // Create secure hash of credentials for cache key using memoization
    const cacheKey = createCacheKey(
      `${context.n8nApiUrl}:${context.n8nApiKey}:${context.instanceId || ''}`
    );

    // Check cache first
    if (instanceClients.has(cacheKey)) {
      cacheMetrics.recordHit();
      return instanceClients.get(cacheKey) || null;
    }

    cacheMetrics.recordMiss();

    // Check if already being created (simple lock check)
    if (cacheMutex.isLocked(cacheKey)) {
      // Wait briefly and check again
      const waitTime = 100; // 100ms
      const start = Date.now();
      while (cacheMutex.isLocked(cacheKey) && (Date.now() - start) < 1000) {
        // Busy wait for up to 1 second
      }
      // Check if it was created while waiting
      if (instanceClients.has(cacheKey)) {
        cacheMetrics.recordHit();
        return instanceClients.get(cacheKey) || null;
      }
    }

    const config = getN8nApiConfigFromContext(context);
    if (config) {
      // Sanitized logging - never log API keys
      logger.info('Creating instance-specific n8n API client', {
        url: config.baseUrl.replace(/^(https?:\/\/[^\/]+).*/, '$1'), // Only log domain
        instanceId: context.instanceId,
        cacheKey: cacheKey.substring(0, 8) + '...' // Only log partial hash
      });

      const client = new N8nApiClient(config);
      instanceClients.set(cacheKey, client);
      cacheMetrics.recordSet();
      cacheMetrics.updateSize(instanceClients.size, instanceClients.max);
      return client;
    }

    return null;
  }

  // Fall back to default singleton from environment
  logger.info('Falling back to environment configuration for n8n API client');
  const config = getN8nApiConfig();

  if (!config) {
    if (defaultApiClient) {
      logger.info('n8n API configuration removed, clearing default client');
      defaultApiClient = null;
      lastDefaultConfigUrl = null;
    }
    return null;
  }

  // Check if config has changed
  if (!defaultApiClient || lastDefaultConfigUrl !== config.baseUrl) {
    logger.info('n8n API client initialized from environment', { url: config.baseUrl });
    defaultApiClient = new N8nApiClient(config);
    lastDefaultConfigUrl = config.baseUrl;
  }

  return defaultApiClient;
}

/**
 * Helper to ensure API is configured
 * @param context - Optional instance context
 * @returns Configured API client
 * @throws Error if API is not configured
 */
function ensureApiConfigured(context?: InstanceContext): N8nApiClient {
  const client = getN8nApiClient(context);
  if (!client) {
    if (context?.instanceId) {
      throw new Error(`n8n API not configured for instance ${context.instanceId}. Please provide n8nApiUrl and n8nApiKey in the instance context.`);
    }
    throw new Error('n8n API not configured. Please set N8N_API_URL and N8N_API_KEY environment variables.');
  }
  return client;
}

// Zod schemas for input validation
const createWorkflowSchema = z.object({
  name: z.string(),
  nodes: z.array(z.any()),
  connections: z.record(z.any()),
  settings: z.object({
    executionOrder: z.enum(['v0', 'v1']).optional(),
    timezone: z.string().optional(),
    saveDataErrorExecution: z.enum(['all', 'none']).optional(),
    saveDataSuccessExecution: z.enum(['all', 'none']).optional(),
    saveManualExecutions: z.boolean().optional(),
    saveExecutionProgress: z.boolean().optional(),
    executionTimeout: z.number().optional(),
    errorWorkflow: z.string().optional(),
  }).optional(),
});

const updateWorkflowSchema = z.object({
  id: z.string(),
  name: z.string().optional(),
  nodes: z.array(z.any()).optional(),
  connections: z.record(z.any()).optional(),
  settings: z.any().optional(),
});

const listWorkflowsSchema = z.object({
  limit: z.number().min(1).max(100).optional(),
  cursor: z.string().optional(),
  active: z.boolean().optional(),
  tags: z.array(z.string()).optional(),
  projectId: z.string().optional(),
  excludePinnedData: z.boolean().optional(),
});

const validateWorkflowSchema = z.object({
  id: z.string(),
  options: z.object({
    validateNodes: z.boolean().optional(),
    validateConnections: z.boolean().optional(),
    validateExpressions: z.boolean().optional(),
    profile: z.enum(['minimal', 'runtime', 'ai-friendly', 'strict']).optional(),
  }).optional(),
});

const autofixWorkflowSchema = z.object({
  id: z.string(),
  applyFixes: z.boolean().optional().default(false),
  fixTypes: z.array(z.enum([
    'expression-format',
    'typeversion-correction',
    'error-output-config',
    'node-type-correction',
    'webhook-missing-path'
  ])).optional(),
  confidenceThreshold: z.enum(['high', 'medium', 'low']).optional().default('medium'),
  maxFixes: z.number().optional().default(50)
});

const triggerWebhookSchema = z.object({
  webhookUrl: z.string().url(),
  httpMethod: z.enum(['GET', 'POST', 'PUT', 'DELETE']).optional(),
  data: z.record(z.unknown()).optional(),
  headers: z.record(z.string()).optional(),
  waitForResponse: z.boolean().optional(),
});

const listExecutionsSchema = z.object({
  limit: z.number().min(1).max(100).optional(),
  cursor: z.string().optional(),
  workflowId: z.string().optional(),
  projectId: z.string().optional(),
  status: z.enum(['success', 'error', 'waiting']).optional(),
  includeData: z.boolean().optional(),
});

// Workflow Management Handlers

export async function handleCreateWorkflow(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
  try {
    const client = ensureApiConfigured(context);
    const input = createWorkflowSchema.parse(args);

    // Proactively detect SHORT form node types (common mistake)
    const shortFormErrors: string[] = [];
    input.nodes?.forEach((node: any, index: number) => {
      if (node.type?.startsWith('nodes-base.') || node.type?.startsWith('nodes-langchain.')) {
        const fullForm = node.type.startsWith('nodes-base.')
          ? node.type.replace('nodes-base.', 'n8n-nodes-base.')
          : node.type.replace('nodes-langchain.', '@n8n/n8n-nodes-langchain.');
        shortFormErrors.push(
          `Node ${index} ("${node.name}") uses SHORT form "${node.type}". ` +
          `The n8n API requires FULL form. Change to "${fullForm}"`
        );
      }
    });

    if (shortFormErrors.length > 0) {
      telemetry.trackWorkflowCreation(input, false);
      return {
        success: false,
        error: 'Node type format error: n8n API requires FULL form node types',
        details: {
          errors: shortFormErrors,
          hint: 'Use n8n-nodes-base.* instead of nodes-base.* for standard nodes'
        }
      };
    }

    // Validate workflow structure (n8n API expects FULL form: n8n-nodes-base.*)
    const errors = validateWorkflowStructure(input);
    if (errors.length > 0) {
      // Track validation failure
      telemetry.trackWorkflowCreation(input, false);

      return {
        success: false,
        error: 'Workflow validation failed',
        details: { errors }
      };
    }

    // Create workflow (n8n API expects node types in FULL form)
    const workflow = await client.createWorkflow(input);

    // Track successful workflow creation
    telemetry.trackWorkflowCreation(workflow, true);

    return {
      success: true,
      data: workflow,
      message: `Workflow "${workflow.name}" created successfully with ID: ${workflow.id}`
    };
  } catch (error) {
    if (error instanceof z.ZodError) {
      return {
        success: false,
        error: 'Invalid input',
        details: { errors: error.errors }
      };
    }
    
    if (error instanceof N8nApiError) {
      return {
        success: false,
        error: getUserFriendlyErrorMessage(error),
        code: error.code,
        details: error.details as Record<string, unknown> | undefined
      };
    }
    
    return {
      success: false,
      error: error instanceof Error ? error.message : 'Unknown error occurred'
    };
  }
}

export async function handleGetWorkflow(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
  try {
    const client = ensureApiConfigured(context);
    const { id } = z.object({ id: z.string() }).parse(args);
    
    const workflow = await client.getWorkflow(id);
    
    return {
      success: true,
      data: workflow
    };
  } catch (error) {
    if (error instanceof z.ZodError) {
      return {
        success: false,
        error: 'Invalid input',
        details: { errors: error.errors }
      };
    }
    
    if (error instanceof N8nApiError) {
      return {
        success: false,
        error: getUserFriendlyErrorMessage(error),
        code: error.code
      };
    }
    
    return {
      success: false,
      error: error instanceof Error ? error.message : 'Unknown error occurred'
    };
  }
}

export async function handleGetWorkflowDetails(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
  try {
    const client = ensureApiConfigured(context);
    const { id } = z.object({ id: z.string() }).parse(args);
    
    const workflow = await client.getWorkflow(id);
    
    // Get recent executions for this workflow
    const executions = await client.listExecutions({
      workflowId: id,
      limit: 10
    });
    
    // Calculate execution statistics
    const stats = {
      totalExecutions: executions.data.length,
      successCount: executions.data.filter(e => e.status === ExecutionStatus.SUCCESS).length,
      errorCount: executions.data.filter(e => e.status === ExecutionStatus.ERROR).length,
      lastExecutionTime: executions.data[0]?.startedAt || null
    };
    
    return {
      success: true,
      data: {
        workflow,
        executionStats: stats,
        hasWebhookTrigger: hasWebhookTrigger(workflow),
        webhookPath: getWebhookUrl(workflow)
      }
    };
  } catch (error) {
    if (error instanceof z.ZodError) {
      return {
        success: false,
        error: 'Invalid input',
        details: { errors: error.errors }
      };
    }
    
    if (error instanceof N8nApiError) {
      return {
        success: false,
        error: getUserFriendlyErrorMessage(error),
        code: error.code
      };
    }
    
    return {
      success: false,
      error: error instanceof Error ? error.message : 'Unknown error occurred'
    };
  }
}

export async function handleGetWorkflowStructure(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
  try {
    const client = ensureApiConfigured(context);
    const { id } = z.object({ id: z.string() }).parse(args);
    
    const workflow = await client.getWorkflow(id);
    
    // Simplify nodes to just essential structure
    const simplifiedNodes = workflow.nodes.map(node => ({
      id: node.id,
      name: node.name,
      type: node.type,
      position: node.position,
      disabled: node.disabled || false
    }));
    
    return {
      success: true,
      data: {
        id: workflow.id,
        name: workflow.name,
        active: workflow.active,
        isArchived: workflow.isArchived,
        nodes: simplifiedNodes,
        connections: workflow.connections,
        nodeCount: workflow.nodes.length,
        connectionCount: Object.keys(workflow.connections).length
      }
    };
  } catch (error) {
    if (error instanceof z.ZodError) {
      return {
        success: false,
        error: 'Invalid input',
        details: { errors: error.errors }
      };
    }
    
    if (error instanceof N8nApiError) {
      return {
        success: false,
        error: getUserFriendlyErrorMessage(error),
        code: error.code
      };
    }
    
    return {
      success: false,
      error: error instanceof Error ? error.message : 'Unknown error occurred'
    };
  }
}

export async function handleGetWorkflowMinimal(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
  try {
    const client = ensureApiConfigured(context);
    const { id } = z.object({ id: z.string() }).parse(args);
    
    const workflow = await client.getWorkflow(id);
    
    return {
      success: true,
      data: {
        id: workflow.id,
        name: workflow.name,
        active: workflow.active,
        isArchived: workflow.isArchived,
        tags: workflow.tags || [],
        createdAt: workflow.createdAt,
        updatedAt: workflow.updatedAt
      }
    };
  } catch (error) {
    if (error instanceof z.ZodError) {
      return {
        success: false,
        error: 'Invalid input',
        details: { errors: error.errors }
      };
    }
    
    if (error instanceof N8nApiError) {
      return {
        success: false,
        error: getUserFriendlyErrorMessage(error),
        code: error.code
      };
    }
    
    return {
      success: false,
      error: error instanceof Error ? error.message : 'Unknown error occurred'
    };
  }
}

export async function handleUpdateWorkflow(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
  try {
    const client = ensureApiConfigured(context);
    const input = updateWorkflowSchema.parse(args);
    const { id, ...updateData } = input;

    // If nodes/connections are being updated, validate the structure
    if (updateData.nodes || updateData.connections) {
      // Always fetch current workflow for validation (need all fields like name)
      const current = await client.getWorkflow(id);
      const fullWorkflow = {
        ...current,
        ...updateData
      };

      // Validate workflow structure (n8n API expects FULL form: n8n-nodes-base.*)
      const errors = validateWorkflowStructure(fullWorkflow);
      if (errors.length > 0) {
        return {
          success: false,
          error: 'Workflow validation failed',
          details: { errors }
        };
      }
    }
    
    // Update workflow
    const workflow = await client.updateWorkflow(id, updateData);
    
    return {
      success: true,
      data: workflow,
      message: `Workflow "${workflow.name}" updated successfully`
    };
  } catch (error) {
    if (error instanceof z.ZodError) {
      return {
        success: false,
        error: 'Invalid input',
        details: { errors: error.errors }
      };
    }
    
    if (error instanceof N8nApiError) {
      return {
        success: false,
        error: getUserFriendlyErrorMessage(error),
        code: error.code,
        details: error.details as Record<string, unknown> | undefined
      };
    }
    
    return {
      success: false,
      error: error instanceof Error ? error.message : 'Unknown error occurred'
    };
  }
}

export async function handleDeleteWorkflow(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
  try {
    const client = ensureApiConfigured(context);
    const { id } = z.object({ id: z.string() }).parse(args);

    const deleted = await client.deleteWorkflow(id);

    return {
      success: true,
      data: deleted,
      message: `Workflow ${id} deleted successfully`
    };
  } catch (error) {
    if (error instanceof z.ZodError) {
      return {
        success: false,
        error: 'Invalid input',
        details: { errors: error.errors }
      };
    }
    
    if (error instanceof N8nApiError) {
      return {
        success: false,
        error: getUserFriendlyErrorMessage(error),
        code: error.code
      };
    }
    
    return {
      success: false,
      error: error instanceof Error ? error.message : 'Unknown error occurred'
    };
  }
}

export async function handleListWorkflows(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
  try {
    const client = ensureApiConfigured(context);
    const input = listWorkflowsSchema.parse(args || {});

    // Convert tags array to comma-separated string (n8n API format)
    const tagsParam = input.tags && input.tags.length > 0
      ? input.tags.join(',')
      : undefined;

    const response = await client.listWorkflows({
      limit: input.limit || 100,
      cursor: input.cursor,
      active: input.active,
      tags: tagsParam as any,  // API expects string, not array
      projectId: input.projectId,
      excludePinnedData: input.excludePinnedData ?? true
    });
    
    // Strip down workflows to only essential metadata
    const minimalWorkflows = response.data.map(workflow => ({
      id: workflow.id,
      name: workflow.name,
      active: workflow.active,
      isArchived: workflow.isArchived,
      createdAt: workflow.createdAt,
      updatedAt: workflow.updatedAt,
      tags: workflow.tags || [],
      nodeCount: workflow.nodes?.length || 0
    }));

    return {
      success: true,
      data: {
        workflows: minimalWorkflows,
        returned: minimalWorkflows.length,
        nextCursor: response.nextCursor,
        hasMore: !!response.nextCursor,
        ...(response.nextCursor ? { 
          _note: "More workflows available. Use cursor to get next page." 
        } : {})
      }
    };
  } catch (error) {
    if (error instanceof z.ZodError) {
      return {
        success: false,
        error: 'Invalid input',
        details: { errors: error.errors }
      };
    }
    
    if (error instanceof N8nApiError) {
      return {
        success: false,
        error: getUserFriendlyErrorMessage(error),
        code: error.code
      };
    }
    
    return {
      success: false,
      error: error instanceof Error ? error.message : 'Unknown error occurred'
    };
  }
}

export async function handleValidateWorkflow(
  args: unknown,
  repository: NodeRepository,
  context?: InstanceContext
): Promise<McpToolResponse> {
  try {
    const client = ensureApiConfigured(context);
    const input = validateWorkflowSchema.parse(args);
    
    // First, fetch the workflow from n8n
    const workflowResponse = await handleGetWorkflow({ id: input.id });
    
    if (!workflowResponse.success) {
      return workflowResponse; // Return the error from fetching
    }
    
    const workflow = workflowResponse.data as Workflow;
    
    // Create validator instance using the provided repository
    const validator = new WorkflowValidator(repository, EnhancedConfigValidator);
    
    // Run validation
    const validationResult = await validator.validateWorkflow(workflow, input.options);
    
    // Format the response (same format as the regular validate_workflow tool)
    const response: WorkflowValidationResponse = {
      valid: validationResult.valid,
      workflowId: workflow.id,
      workflowName: workflow.name,
      summary: {
        totalNodes: validationResult.statistics.totalNodes,
        enabledNodes: validationResult.statistics.enabledNodes,
        triggerNodes: validationResult.statistics.triggerNodes,
        validConnections: validationResult.statistics.validConnections,
        invalidConnections: validationResult.statistics.invalidConnections,
        expressionsValidated: validationResult.statistics.expressionsValidated,
        errorCount: validationResult.errors.length,
        warningCount: validationResult.warnings.length
      }
    };
    
    if (validationResult.errors.length > 0) {
      response.errors = validationResult.errors.map(e => ({
        node: e.nodeName || 'workflow',
        nodeName: e.nodeName, // Also set nodeName for compatibility
        message: e.message,
        details: e.details
      }));
    }

    if (validationResult.warnings.length > 0) {
      response.warnings = validationResult.warnings.map(w => ({
        node: w.nodeName || 'workflow',
        nodeName: w.nodeName, // Also set nodeName for compatibility
        message: w.message,
        details: w.details
      }));
    }
    
    if (validationResult.suggestions.length > 0) {
      response.suggestions = validationResult.suggestions;
    }

    // Track successfully validated workflows in telemetry
    if (validationResult.valid) {
      telemetry.trackWorkflowCreation(workflow, true);
    }

    return {
      success: true,
      data: response
    };
  } catch (error) {
    if (error instanceof z.ZodError) {
      return {
        success: false,
        error: 'Invalid input',
        details: { errors: error.errors }
      };
    }
    
    if (error instanceof N8nApiError) {
      return {
        success: false,
        error: getUserFriendlyErrorMessage(error),
        code: error.code
      };
    }
    
    return {
      success: false,
      error: error instanceof Error ? error.message : 'Unknown error occurred'
    };
  }
}

export async function handleAutofixWorkflow(
  args: unknown,
  repository: NodeRepository,
  context?: InstanceContext
): Promise<McpToolResponse> {
  try {
    const client = ensureApiConfigured(context);
    const input = autofixWorkflowSchema.parse(args);

    // First, fetch the workflow from n8n
    const workflowResponse = await handleGetWorkflow({ id: input.id }, context);

    if (!workflowResponse.success) {
      return workflowResponse; // Return the error from fetching
    }

    const workflow = workflowResponse.data as Workflow;

    // Create validator instance using the provided repository
    const validator = new WorkflowValidator(repository, EnhancedConfigValidator);

    // Run validation to identify issues
    const validationResult = await validator.validateWorkflow(workflow, {
      validateNodes: true,
      validateConnections: true,
      validateExpressions: true,
      profile: 'ai-friendly'
    });

    // Check for expression format issues
    const allFormatIssues: ExpressionFormatIssue[] = [];
    for (const node of workflow.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);
    }

    // Generate fixes using WorkflowAutoFixer
    const autoFixer = new WorkflowAutoFixer(repository);
    const fixResult = autoFixer.generateFixes(
      workflow,
      validationResult,
      allFormatIssues,
      {
        applyFixes: input.applyFixes,
        fixTypes: input.fixTypes,
        confidenceThreshold: input.confidenceThreshold,
        maxFixes: input.maxFixes
      }
    );

    // If no fixes available
    if (fixResult.fixes.length === 0) {
      return {
        success: true,
        data: {
          workflowId: workflow.id,
          workflowName: workflow.name,
          message: 'No automatic fixes available for this workflow',
          validationSummary: {
            errors: validationResult.errors.length,
            warnings: validationResult.warnings.length
          }
        }
      };
    }

    // If preview mode (applyFixes = false)
    if (!input.applyFixes) {
      return {
        success: true,
        data: {
          workflowId: workflow.id,
          workflowName: workflow.name,
          preview: true,
          fixesAvailable: fixResult.fixes.length,
          fixes: fixResult.fixes,
          summary: fixResult.summary,
          stats: fixResult.stats,
          message: `${fixResult.fixes.length} fixes available. Set applyFixes=true to apply them.`
        }
      };
    }

    // Apply fixes using the diff engine
    if (fixResult.operations.length > 0) {
      const updateResult = await handleUpdatePartialWorkflow(
        {
          id: workflow.id,
          operations: fixResult.operations
        },
        context
      );

      if (!updateResult.success) {
        return {
          success: false,
          error: 'Failed to apply fixes',
          details: {
            fixes: fixResult.fixes,
            updateError: updateResult.error
          }
        };
      }

      return {
        success: true,
        data: {
          workflowId: workflow.id,
          workflowName: workflow.name,
          fixesApplied: fixResult.fixes.length,
          fixes: fixResult.fixes,
          summary: fixResult.summary,
          stats: fixResult.stats,
          message: `Successfully applied ${fixResult.fixes.length} fixes to workflow "${workflow.name}"`
        }
      };
    }

    return {
      success: true,
      data: {
        workflowId: workflow.id,
        workflowName: workflow.name,
        message: 'No fixes needed'
      }
    };

  } catch (error) {
    if (error instanceof z.ZodError) {
      return {
        success: false,
        error: 'Invalid input',
        details: { errors: error.errors }
      };
    }

    if (error instanceof N8nApiError) {
      return {
        success: false,
        error: getUserFriendlyErrorMessage(error),
        code: error.code
      };
    }

    return {
      success: false,
      error: error instanceof Error ? error.message : 'Unknown error occurred'
    };
  }
}

// Execution Management Handlers

export async function handleTriggerWebhookWorkflow(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
  try {
    const client = ensureApiConfigured(context);
    const input = triggerWebhookSchema.parse(args);

    const webhookRequest: WebhookRequest = {
      webhookUrl: input.webhookUrl,
      httpMethod: input.httpMethod || 'POST',
      data: input.data,
      headers: input.headers,
      waitForResponse: input.waitForResponse ?? true
    };

    const response = await client.triggerWebhook(webhookRequest);

    return {
      success: true,
      data: response,
      message: 'Webhook triggered successfully'
    };
  } catch (error) {
    if (error instanceof z.ZodError) {
      return {
        success: false,
        error: 'Invalid input',
        details: { errors: error.errors }
      };
    }

    if (error instanceof N8nApiError) {
      // Try to extract execution context from error response
      const errorData = error.details as any;
      const executionId = errorData?.executionId || errorData?.id || errorData?.execution?.id;
      const workflowId = errorData?.workflowId || errorData?.workflow?.id;

      // If we have execution ID, provide specific guidance with n8n_get_execution
      if (executionId) {
        return {
          success: false,
          error: formatExecutionError(executionId, workflowId),
          code: error.code,
          executionId,
          workflowId: workflowId || undefined
        };
      }

      // No execution ID available - workflow likely didn't start
      // Provide guidance to check recent executions
      if (error.code === 'SERVER_ERROR' || error.statusCode && error.statusCode >= 500) {
        return {
          success: false,
          error: formatNoExecutionError(),
          code: error.code
        };
      }

      // For other errors (auth, validation, etc), use standard message
      return {
        success: false,
        error: getUserFriendlyErrorMessage(error),
        code: error.code,
        details: error.details as Record<string, unknown> | undefined
      };
    }

    return {
      success: false,
      error: error instanceof Error ? error.message : 'Unknown error occurred'
    };
  }
}

export async function handleGetExecution(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
  try {
    const client = ensureApiConfigured(context);

    // Parse and validate input with new parameters
    const schema = z.object({
      id: z.string(),
      // New filtering parameters
      mode: z.enum(['preview', 'summary', 'filtered', 'full']).optional(),
      nodeNames: z.array(z.string()).optional(),
      itemsLimit: z.number().optional(),
      includeInputData: z.boolean().optional(),
      // Legacy parameter (backward compatibility)
      includeData: z.boolean().optional()
    });

    const params = schema.parse(args);
    const { id, mode, nodeNames, itemsLimit, includeInputData, includeData } = params;

    /**
     * Map legacy includeData parameter to mode for backward compatibility
     *
     * Legacy behavior:
     * - includeData: undefined -> minimal execution summary (no data)
     * - includeData: false -> minimal execution summary (no data)
     * - includeData: true -> full execution data
     *
     * New behavior mapping:
     * - includeData: undefined -> no mode (minimal)
     * - includeData: false -> no mode (minimal)
     * - includeData: true -> mode: 'summary' (2 items per node, not full)
     *
     * Note: Legacy true behavior returned ALL data, which could exceed token limits.
     * New behavior caps at 2 items for safety. Users can use mode: 'full' for old behavior.
     */
    let effectiveMode = mode;
    if (!effectiveMode && includeData !== undefined) {
      effectiveMode = includeData ? 'summary' : undefined;
    }

    // Determine if we need to fetch full data from API
    // We fetch full data if any mode is specified (including preview) or legacy includeData is true
    // Preview mode needs the data to analyze structure and generate recommendations
    const fetchFullData = effectiveMode !== undefined || includeData === true;

    // Fetch execution from n8n API
    const execution = await client.getExecution(id, fetchFullData);

    // If no filtering options specified, return original execution (backward compatibility)
    if (!effectiveMode && !nodeNames && itemsLimit === undefined) {
      return {
        success: true,
        data: execution
      };
    }

    // Apply filtering using ExecutionProcessor
    const filterOptions: ExecutionFilterOptions = {
      mode: effectiveMode,
      nodeNames,
      itemsLimit,
      includeInputData
    };

    const processedExecution = processExecution(execution, filterOptions);

    return {
      success: true,
      data: processedExecution
    };
  } catch (error) {
    if (error instanceof z.ZodError) {
      return {
        success: false,
        error: 'Invalid input',
        details: { errors: error.errors }
      };
    }

    if (error instanceof N8nApiError) {
      return {
        success: false,
        error: getUserFriendlyErrorMessage(error),
        code: error.code
      };
    }

    return {
      success: false,
      error: error instanceof Error ? error.message : 'Unknown error occurred'
    };
  }
}

export async function handleListExecutions(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
  try {
    const client = ensureApiConfigured(context);
    const input = listExecutionsSchema.parse(args || {});
    
    const response = await client.listExecutions({
      limit: input.limit || 100,
      cursor: input.cursor,
      workflowId: input.workflowId,
      projectId: input.projectId,
      status: input.status as ExecutionStatus | undefined,
      includeData: input.includeData || false
    });
    
    return {
      success: true,
      data: {
        executions: response.data,
        returned: response.data.length,
        nextCursor: response.nextCursor,
        hasMore: !!response.nextCursor,
        ...(response.nextCursor ? { 
          _note: "More executions available. Use cursor to get next page." 
        } : {})
      }
    };
  } catch (error) {
    if (error instanceof z.ZodError) {
      return {
        success: false,
        error: 'Invalid input',
        details: { errors: error.errors }
      };
    }
    
    if (error instanceof N8nApiError) {
      return {
        success: false,
        error: getUserFriendlyErrorMessage(error),
        code: error.code
      };
    }
    
    return {
      success: false,
      error: error instanceof Error ? error.message : 'Unknown error occurred'
    };
  }
}

export async function handleDeleteExecution(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
  try {
    const client = ensureApiConfigured(context);
    const { id } = z.object({ id: z.string() }).parse(args);
    
    await client.deleteExecution(id);
    
    return {
      success: true,
      message: `Execution ${id} deleted successfully`
    };
  } catch (error) {
    if (error instanceof z.ZodError) {
      return {
        success: false,
        error: 'Invalid input',
        details: { errors: error.errors }
      };
    }
    
    if (error instanceof N8nApiError) {
      return {
        success: false,
        error: getUserFriendlyErrorMessage(error),
        code: error.code
      };
    }
    
    return {
      success: false,
      error: error instanceof Error ? error.message : 'Unknown error occurred'
    };
  }
}

// System Tools Handlers

export async function handleHealthCheck(context?: InstanceContext): Promise<McpToolResponse> {
  const startTime = Date.now();

  try {
    const client = ensureApiConfigured(context);
    const health = await client.healthCheck();

    // Get MCP version from package.json
    const packageJson = require('../../package.json');
    const mcpVersion = packageJson.version;
    const supportedN8nVersion = packageJson.dependencies?.n8n?.replace(/[^0-9.]/g, '');

    // Check npm for latest version (async, non-blocking)
    const versionCheck = await checkNpmVersion();

    // Get cache metrics for performance monitoring
    const cacheMetricsData = getInstanceCacheMetrics();

    // Calculate response time
    const responseTime = Date.now() - startTime;

    // Build response data
    const responseData: HealthCheckResponseData = {
      status: health.status,
      instanceId: health.instanceId,
      n8nVersion: health.n8nVersion,
      features: health.features,
      apiUrl: getN8nApiConfig()?.baseUrl,
      mcpVersion,
      supportedN8nVersion,
      versionCheck: {
        current: versionCheck.currentVersion,
        latest: versionCheck.latestVersion,
        upToDate: !versionCheck.isOutdated,
        message: formatVersionMessage(versionCheck),
        ...(versionCheck.updateCommand ? { updateCommand: versionCheck.updateCommand } : {})
      },
      performance: {
        responseTimeMs: responseTime,
        cacheHitRate: (cacheMetricsData.hits + cacheMetricsData.misses) > 0
          ? ((cacheMetricsData.hits / (cacheMetricsData.hits + cacheMetricsData.misses)) * 100).toFixed(2) + '%'
          : 'N/A',
        cachedInstances: cacheMetricsData.size
      }
    };

    // Add next steps guidance based on telemetry insights
    responseData.nextSteps = [
      '• Create workflow: n8n_create_workflow',
      '• List workflows: n8n_list_workflows',
      '• Search nodes: search_nodes',
      '• Browse templates: search_templates'
    ];

    // Add update warning if outdated
    if (versionCheck.isOutdated && versionCheck.latestVersion) {
      responseData.updateWarning = `⚠️  n8n-mcp v${versionCheck.latestVersion} is available (you have v${versionCheck.currentVersion}). Update recommended.`;
    }

    // Track result in telemetry
    telemetry.trackEvent('health_check_completed', {
      success: true,
      responseTimeMs: responseTime,
      upToDate: !versionCheck.isOutdated,
      apiConnected: true
    });

    return {
      success: true,
      data: responseData
    };
  } catch (error) {
    const responseTime = Date.now() - startTime;

    // Track failure in telemetry
    telemetry.trackEvent('health_check_failed', {
      success: false,
      responseTimeMs: responseTime,
      errorType: error instanceof N8nApiError ? error.code : 'unknown'
    });

    if (error instanceof N8nApiError) {
      return {
        success: false,
        error: getUserFriendlyErrorMessage(error),
        code: error.code,
        details: {
          apiUrl: getN8nApiConfig()?.baseUrl,
          hint: 'Check if n8n is running and API is enabled',
          troubleshooting: [
            '1. Verify n8n instance is running',
            '2. Check N8N_API_URL is correct',
            '3. Verify N8N_API_KEY has proper permissions',
            '4. Run n8n_diagnostic for detailed analysis'
          ]
        }
      };
    }

    return {
      success: false,
      error: error instanceof Error ? error.message : 'Unknown error occurred'
    };
  }
}

export async function handleListAvailableTools(context?: InstanceContext): Promise<McpToolResponse> {
  const tools = [
    {
      category: 'Workflow Management',
      tools: [
        { name: 'n8n_create_workflow', description: 'Create new workflows' },
        { name: 'n8n_get_workflow', description: 'Get workflow by ID' },
        { name: 'n8n_get_workflow_details', description: 'Get detailed workflow info with stats' },
        { name: 'n8n_get_workflow_structure', description: 'Get simplified workflow structure' },
        { name: 'n8n_get_workflow_minimal', description: 'Get minimal workflow info' },
        { name: 'n8n_update_workflow', description: 'Update existing workflows' },
        { name: 'n8n_delete_workflow', description: 'Delete workflows' },
        { name: 'n8n_list_workflows', description: 'List workflows with filters' },
        { name: 'n8n_validate_workflow', description: 'Validate workflow from n8n instance' },
        { name: 'n8n_autofix_workflow', description: 'Automatically fix common workflow errors' }
      ]
    },
    {
      category: 'Execution Management',
      tools: [
        { name: 'n8n_trigger_webhook_workflow', description: 'Trigger workflows via webhook' },
        { name: 'n8n_get_execution', description: 'Get execution details' },
        { name: 'n8n_list_executions', description: 'List executions with filters' },
        { name: 'n8n_delete_execution', description: 'Delete execution records' }
      ]
    },
    {
      category: 'System',
      tools: [
        { name: 'n8n_health_check', description: 'Check API connectivity' },
        { name: 'n8n_list_available_tools', description: 'List all available tools' }
      ]
    }
  ];
  
  const config = getN8nApiConfig();
  const apiConfigured = config !== null;
  
  return {
    success: true,
    data: {
      tools,
      apiConfigured,
      configuration: config ? {
        apiUrl: config.baseUrl,
        timeout: config.timeout,
        maxRetries: config.maxRetries
      } : null,
      limitations: [
        'Cannot activate/deactivate workflows via API',
        'Cannot execute workflows directly (must use webhooks)',
        'Cannot stop running executions',
        'Tags and credentials have limited API support'
      ]
    }
  };
}

// Environment-aware debugging helpers

/**
 * Detect cloud platform from environment variables
 * Returns platform name or null if not in cloud
 */
function detectCloudPlatform(): string | null {
  if (process.env.RAILWAY_ENVIRONMENT) return 'railway';
  if (process.env.RENDER) return 'render';
  if (process.env.FLY_APP_NAME) return 'fly';
  if (process.env.HEROKU_APP_NAME) return 'heroku';
  if (process.env.AWS_EXECUTION_ENV) return 'aws';
  if (process.env.KUBERNETES_SERVICE_HOST) return 'kubernetes';
  if (process.env.GOOGLE_CLOUD_PROJECT) return 'gcp';
  if (process.env.AZURE_FUNCTIONS_ENVIRONMENT) return 'azure';
  return null;
}

/**
 * Get mode-specific debugging suggestions
 */
function getModeSpecificDebug(mcpMode: string) {
  if (mcpMode === 'http') {
    const port = process.env.MCP_PORT || process.env.PORT || 3000;
    return {
      mode: 'HTTP Server',
      port,
      authTokenConfigured: !!(process.env.MCP_AUTH_TOKEN || process.env.AUTH_TOKEN),
      corsEnabled: true,
      serverUrl: `http://localhost:${port}`,
      healthCheckUrl: `http://localhost:${port}/health`,
      troubleshooting: [
        `1. Test server health: curl http://localhost:${port}/health`,
        '2. Check browser console for CORS errors',
        '3. Verify MCP_AUTH_TOKEN or AUTH_TOKEN if authentication enabled',
        `4. Ensure port ${port} is not in use: lsof -i :${port} (macOS/Linux) or netstat -ano | findstr :${port} (Windows)`,
        '5. Check firewall settings for port access',
        '6. Review server logs for connection errors'
      ],
      commonIssues: [
        'CORS policy blocking browser requests',
        'Port already in use by another application',
        'Authentication token mismatch',
        'Network firewall blocking connections'
      ]
    };
  } else {
    // stdio mode
    const configLocation = process.platform === 'darwin'
      ? '~/Library/Application Support/Claude/claude_desktop_config.json'
      : process.platform === 'win32'
      ? '%APPDATA%\\Claude\\claude_desktop_config.json'
      : '~/.config/Claude/claude_desktop_config.json';

    return {
      mode: 'Standard I/O (Claude Desktop)',
      configLocation,
      troubleshooting: [
        '1. Verify Claude Desktop config file exists and is valid JSON',
        '2. Check MCP server entry: {"mcpServers": {"n8n": {"command": "npx", "args": ["-y", "n8n-mcp"]}}}',
        '3. Restart Claude Desktop after config changes',
        '4. Check Claude Desktop logs for startup errors',
        '5. Test npx can run: npx -y n8n-mcp --version',
        '6. Verify executable permissions if using local installation'
      ],
      commonIssues: [
        'Invalid JSON in claude_desktop_config.json',
        'Incorrect command or args in MCP server config',
        'Claude Desktop not restarted after config changes',
        'npx unable to download or run package',
        'Missing execute permissions on local binary'
      ]
    };
  }
}

/**
 * Get Docker-specific debugging suggestions
 */
function getDockerDebug(isDocker: boolean) {
  if (!isDocker) return null;

  return {
    containerDetected: true,
    troubleshooting: [
      '1. Verify volume mounts for data/nodes.db',
      '2. Check network connectivity to n8n instance',
      '3. Ensure ports are correctly mapped',
      '4. Review container logs: docker logs <container-name>',
      '5. Verify environment variables passed to container',
      '6. Check IS_DOCKER=true is set correctly'
    ],
    commonIssues: [
      'Volume mount not persisting database',
      'Network isolation preventing n8n API access',
      'Port mapping conflicts',
      'Missing environment variables in container'
    ]
  };
}

/**
 * Get cloud platform-specific suggestions
 */
function getCloudPlatformDebug(cloudPlatform: string | null) {
  if (!cloudPlatform) return null;

  const platformGuides: Record<string, CloudPlatformGuide> = {
    railway: {
      name: 'Railway',
      troubleshooting: [
        '1. Check Railway environment variables are set',
        '2. Verify deployment logs in Railway dashboard',
        '3. Ensure PORT matches Railway assigned port (automatic)',
        '4. Check networking configuration for external access'
      ]
    },
    render: {
      name: 'Render',
      troubleshooting: [
        '1. Verify Render environment variables',
        '2. Check Render logs for startup errors',
        '3. Ensure health check endpoint is responding',
        '4. Verify instance type has sufficient resources'
      ]
    },
    fly: {
      name: 'Fly.io',
      troubleshooting: [
        '1. Check Fly.io logs: flyctl logs',
        '2. Verify fly.toml configuration',
        '3. Ensure volumes are properly mounted',
        '4. Check app status: flyctl status'
      ]
    },
    heroku: {
      name: 'Heroku',
      troubleshooting: [
        '1. Check Heroku logs: heroku logs --tail',
        '2. Verify Procfile configuration',
        '3. Ensure dynos are running: heroku ps',
        '4. Check environment variables: heroku config'
      ]
    },
    kubernetes: {
      name: 'Kubernetes',
      troubleshooting: [
        '1. Check pod logs: kubectl logs <pod-name>',
        '2. Verify service and ingress configuration',
        '3. Check persistent volume claims',
        '4. Verify resource limits and requests'
      ]
    },
    aws: {
      name: 'AWS',
      troubleshooting: [
        '1. Check CloudWatch logs',
        '2. Verify IAM roles and permissions',
        '3. Check security groups and networking',
        '4. Verify environment variables in service config'
      ]
    }
  };

  return platformGuides[cloudPlatform] || {
    name: cloudPlatform.toUpperCase(),
    troubleshooting: [
      '1. Check cloud platform logs',
      '2. Verify environment variables are set',
      '3. Check networking and port configuration',
      '4. Review platform-specific documentation'
    ]
  };
}

// Handler: n8n_diagnostic
export async function handleDiagnostic(request: any, context?: InstanceContext): Promise<McpToolResponse> {
  const startTime = Date.now();
  const verbose = request.params?.arguments?.verbose || false;

  // Detect environment for targeted debugging
  const mcpMode = process.env.MCP_MODE || 'stdio';
  const isDocker = process.env.IS_DOCKER === 'true';
  const cloudPlatform = detectCloudPlatform();

  // Check environment variables
  const envVars = {
    N8N_API_URL: process.env.N8N_API_URL || null,
    N8N_API_KEY: process.env.N8N_API_KEY ? '***configured***' : null,
    NODE_ENV: process.env.NODE_ENV || 'production',
    MCP_MODE: mcpMode,
    isDocker,
    cloudPlatform,
    nodeVersion: process.version,
    platform: process.platform
  };

  // Check API configuration
  const apiConfig = getN8nApiConfig();
  const apiConfigured = apiConfig !== null;
  const apiClient = getN8nApiClient(context);

  // Test API connectivity if configured
  let apiStatus = {
    configured: apiConfigured,
    connected: false,
    error: null as string | null,
    version: null as string | null
  };

  if (apiClient) {
    try {
      const health = await apiClient.healthCheck();
      apiStatus.connected = true;
      apiStatus.version = health.n8nVersion || 'unknown';
    } catch (error) {
      apiStatus.error = error instanceof Error ? error.message : 'Unknown error';
    }
  }

  // Check which tools are available
  const documentationTools = 22; // Base documentation tools
  const managementTools = apiConfigured ? 16 : 0;
  const totalTools = documentationTools + managementTools;

  // Check npm version
  const versionCheck = await checkNpmVersion();

  // Get performance metrics
  const cacheMetricsData = getInstanceCacheMetrics();
  const responseTime = Date.now() - startTime;

  // Build diagnostic report
  const diagnostic: DiagnosticResponseData = {
    timestamp: new Date().toISOString(),
    environment: envVars,
    apiConfiguration: {
      configured: apiConfigured,
      status: apiStatus,
      config: apiConfig ? {
        baseUrl: apiConfig.baseUrl,
        timeout: apiConfig.timeout,
        maxRetries: apiConfig.maxRetries
      } : null
    },
    versionInfo: {
      current: versionCheck.currentVersion,
      latest: versionCheck.latestVersion,
      upToDate: !versionCheck.isOutdated,
      message: formatVersionMessage(versionCheck),
      ...(versionCheck.updateCommand ? { updateCommand: versionCheck.updateCommand } : {})
    },
    toolsAvailability: {
      documentationTools: {
        count: documentationTools,
        enabled: true,
        description: 'Always available - node info, search, validation, etc.'
      },
      managementTools: {
        count: managementTools,
        enabled: apiConfigured,
        description: apiConfigured ?
          'Management tools are ENABLED - create, update, execute workflows' :
          'Management tools are DISABLED - configure N8N_API_URL and N8N_API_KEY to enable'
      },
      totalAvailable: totalTools
    },
    performance: {
      diagnosticResponseTimeMs: responseTime,
      cacheHitRate: (cacheMetricsData.hits + cacheMetricsData.misses) > 0
        ? ((cacheMetricsData.hits / (cacheMetricsData.hits + cacheMetricsData.misses)) * 100).toFixed(2) + '%'
        : 'N/A',
      cachedInstances: cacheMetricsData.size
    },
    modeSpecificDebug: getModeSpecificDebug(mcpMode)
  };

  // Enhanced guidance based on telemetry insights
  if (apiConfigured && apiStatus.connected) {
    // API is working - provide next steps
    diagnostic.nextSteps = {
      message: '✓ API connected! Here\'s what you can do:',
      recommended: [
        {
          action: 'n8n_list_workflows',
          description: 'See your existing workflows',
          timing: 'Fast (6 seconds median)'
        },
        {
          action: 'n8n_create_workflow',
          description: 'Create a new workflow',
          timing: 'Typically 6-14 minutes to build'
        },
        {
          action: 'search_nodes',
          description: 'Discover available nodes',
          timing: 'Fast - explore 500+ nodes'
        },
        {
          action: 'search_templates',
          description: 'Browse pre-built workflows',
          timing: 'Find examples quickly'
        }
      ],
      tips: [
        '82% of users start creating workflows after diagnostics - you\'re ready to go!',
        'Most common first action: n8n_update_partial_workflow (managing existing workflows)',
        'Use n8n_validate_workflow before deploying to catch issues early'
      ]
    };
  } else if (apiConfigured && !apiStatus.connected) {
    // API configured but not connecting - troubleshooting
    diagnostic.troubleshooting = {
      issue: '⚠️ API configured but connection failed',
      error: apiStatus.error,
      steps: [
        '1. Verify n8n instance is running and accessible',
        '2. Check N8N_API_URL is correct (currently: ' + apiConfig?.baseUrl + ')',
        '3. Test URL in browser: ' + apiConfig?.baseUrl + '/healthz',
        '4. Verify N8N_API_KEY has proper permissions',
        '5. Check firewall/network settings if using remote n8n',
        '6. Try running n8n_health_check again after fixes'
      ],
      commonIssues: [
        'Wrong port number in N8N_API_URL',
        'API key doesn\'t have sufficient permissions',
        'n8n instance not running or crashed',
        'Network firewall blocking connection'
      ],
      documentation: 'https://github.com/czlonkowski/n8n-mcp?tab=readme-ov-file#n8n-management-tools-optional---requires-api-configuration'
    };
  } else {
    // API not configured - setup guidance
    diagnostic.setupGuide = {
      message: 'n8n API not configured. You can still use documentation tools!',
      whatYouCanDoNow: {
        documentation: [
          {
            tool: 'search_nodes',
            description: 'Search 500+ n8n nodes',
            example: 'search_nodes({query: "slack"})'
          },
          {
            tool: 'get_node_essentials',
            description: 'Get node configuration details',
            example: 'get_node_essentials({nodeType: "nodes-base.httpRequest"})'
          },
          {
            tool: 'search_templates',
            description: 'Browse workflow templates',
            example: 'search_templates({query: "chatbot"})'
          },
          {
            tool: 'validate_workflow',
            description: 'Validate workflow JSON',
            example: 'validate_workflow({workflow: {...}})'
          }
        ],
        note: '22 documentation tools available without API configuration'
      },
      whatYouCannotDo: [
        '✗ Create/update workflows in n8n instance',
        '✗ List your workflows',
        '✗ Execute workflows',
        '✗ View execution results'
      ],
      howToEnable: {
        steps: [
          '1. Get your n8n API key: [Your n8n instance]/settings/api',
          '2. Set environment variables:',
          '   N8N_API_URL=https://your-n8n-instance.com',
          '   N8N_API_KEY=your_api_key_here',
          '3. Restart the MCP server',
          '4. Run n8n_diagnostic again to verify',
          '5. All 38 tools will be available!'
        ],
        documentation: 'https://github.com/czlonkowski/n8n-mcp?tab=readme-ov-file#n8n-management-tools-optional---requires-api-configuration'
      }
    };
  }

  // Add version warning if outdated
  if (versionCheck.isOutdated && versionCheck.latestVersion) {
    diagnostic.updateWarning = {
      message: `⚠️ Update available: v${versionCheck.currentVersion} → v${versionCheck.latestVersion}`,
      command: versionCheck.updateCommand,
      benefits: [
        'Latest bug fixes and improvements',
        'New features and tools',
        'Better performance and reliability'
      ]
    };
  }

  // Add Docker-specific debugging if in container
  const dockerDebug = getDockerDebug(isDocker);
  if (dockerDebug) {
    diagnostic.dockerDebug = dockerDebug;
  }

  // Add cloud platform-specific debugging if detected
  const cloudDebug = getCloudPlatformDebug(cloudPlatform);
  if (cloudDebug) {
    diagnostic.cloudPlatformDebug = cloudDebug;
  }

  // Add verbose debug info if requested
  if (verbose) {
    diagnostic.debug = {
      processEnv: Object.keys(process.env).filter(key =>
        key.startsWith('N8N_') || key.startsWith('MCP_')
      ),
      nodeVersion: process.version,
      platform: process.platform,
      workingDirectory: process.cwd(),
      cacheMetrics: cacheMetricsData
    };
  }

  // Track diagnostic usage with result data
  telemetry.trackEvent('diagnostic_completed', {
    success: true,
    apiConfigured,
    apiConnected: apiStatus.connected,
    toolsAvailable: totalTools,
    responseTimeMs: responseTime,
    upToDate: !versionCheck.isOutdated,
    verbose
  });

  return {
    success: true,
    data: diagnostic
  };
}

```

--------------------------------------------------------------------------------
/tests/unit/services/workflow-validator-comprehensive.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
import { WorkflowValidator } from '@/services/workflow-validator';
import { NodeRepository } from '@/database/node-repository';
import { EnhancedConfigValidator } from '@/services/enhanced-config-validator';
import { ExpressionValidator } from '@/services/expression-validator';
import { createWorkflow } from '@tests/utils/builders/workflow.builder';
import type { WorkflowNode, Workflow } from '@/types/n8n-api';

// Mock dependencies
vi.mock('@/database/node-repository');
vi.mock('@/services/enhanced-config-validator');
vi.mock('@/services/expression-validator');
vi.mock('@/utils/logger');

describe('WorkflowValidator - Comprehensive Tests', () => {
  let validator: WorkflowValidator;
  let mockNodeRepository: NodeRepository;
  let mockEnhancedConfigValidator: typeof EnhancedConfigValidator;

  beforeEach(() => {
    vi.clearAllMocks();

    // Create mock instances
    mockNodeRepository = new NodeRepository({} as any) as any;
    mockEnhancedConfigValidator = EnhancedConfigValidator as any;

    // Ensure the mock repository has all necessary methods
    if (!mockNodeRepository.getAllNodes) {
      mockNodeRepository.getAllNodes = vi.fn();
    }
    if (!mockNodeRepository.getNode) {
      mockNodeRepository.getNode = vi.fn();
    }

    // Mock common node types data
    const nodeTypes: Record<string, any> = {
      'nodes-base.webhook': {
        type: 'nodes-base.webhook',
        displayName: 'Webhook',
        package: 'n8n-nodes-base',
        version: 2,
        isVersioned: true,
        properties: [],
        category: 'trigger'
      },
      'nodes-base.httpRequest': {
        type: 'nodes-base.httpRequest',
        displayName: 'HTTP Request',
        package: 'n8n-nodes-base',
        version: 4,
        isVersioned: true,
        properties: [],
        category: 'network'
      },
      'nodes-base.set': {
        type: 'nodes-base.set',
        displayName: 'Set',
        package: 'n8n-nodes-base',
        version: 3,
        isVersioned: true,
        properties: [],
        category: 'data'
      },
      'nodes-base.code': {
        type: 'nodes-base.code',
        displayName: 'Code',
        package: 'n8n-nodes-base',
        version: 2,
        isVersioned: true,
        properties: [],
        category: 'code'
      },
      'nodes-base.manualTrigger': {
        type: 'nodes-base.manualTrigger',
        displayName: 'Manual Trigger',
        package: 'n8n-nodes-base',
        version: 1,
        isVersioned: true,
        properties: [],
        category: 'trigger'
      },
      'nodes-base.if': {
        type: 'nodes-base.if',
        displayName: 'IF',
        package: 'n8n-nodes-base',
        version: 2,
        isVersioned: true,
        properties: [],
        category: 'logic'
      },
      'nodes-base.slack': {
        type: 'nodes-base.slack',
        displayName: 'Slack',
        package: 'n8n-nodes-base',
        version: 2,
        isVersioned: true,
        properties: [],
        category: 'communication'
      },
      'nodes-base.googleSheets': {
        type: 'nodes-base.googleSheets',
        displayName: 'Google Sheets',
        package: 'n8n-nodes-base',
        version: 4,
        isVersioned: true,
        properties: [],
        category: 'data'
      },
      'nodes-langchain.agent': {
        type: 'nodes-langchain.agent',
        displayName: 'AI Agent',
        package: '@n8n/n8n-nodes-langchain',
        version: 1,
        isVersioned: true,
        properties: [],
        isAITool: true,
        category: 'ai'
      },
      'nodes-base.postgres': {
        type: 'nodes-base.postgres',
        displayName: 'Postgres',
        package: 'n8n-nodes-base',
        version: 2,
        isVersioned: true,
        properties: [],
        category: 'database'
      },
      'community.customNode': {
        type: 'community.customNode',
        displayName: 'Custom Node',
        package: 'n8n-nodes-custom',
        version: 1,
        isVersioned: false,
        properties: [],
        isAITool: false,
        category: 'custom'
      }
    };

    // Set up default mock behaviors
    vi.mocked(mockNodeRepository.getNode).mockImplementation((nodeType: string) => {
      // Handle normalization for custom nodes
      if (nodeType === 'n8n-nodes-custom.customNode') {
        return {
          type: 'n8n-nodes-custom.customNode',
          displayName: 'Custom Node',
          package: 'n8n-nodes-custom',
          version: 1,
          isVersioned: false,
          properties: [],
          isAITool: false
        };
      }

      return nodeTypes[nodeType] || null;
    });

    // Mock getAllNodes for NodeSimilarityService
    vi.mocked(mockNodeRepository.getAllNodes).mockReturnValue(Object.values(nodeTypes));

    vi.mocked(mockEnhancedConfigValidator.validateWithMode).mockReturnValue({
      errors: [],
      warnings: [],
      suggestions: [],
      mode: 'operation' as const,
      valid: true,
      visibleProperties: [],
      hiddenProperties: []
    } as any);

    vi.mocked(ExpressionValidator.validateNodeExpressions).mockReturnValue({
      valid: true,
      errors: [],
      warnings: [],
      usedVariables: new Set(),
      usedNodes: new Set()
    });

    // Create validator instance
    validator = new WorkflowValidator(mockNodeRepository, mockEnhancedConfigValidator);
  });

  describe('validateWorkflow', () => {
    it('should validate a minimal valid workflow', async () => {
      const workflow = createWorkflow('Test Workflow')
        .addWebhookNode({ name: 'Webhook' })
        .build();

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.valid).toBe(true);
      expect(result.errors).toHaveLength(0);
      expect(result.statistics.totalNodes).toBe(1);
      expect(result.statistics.enabledNodes).toBe(1);
      expect(result.statistics.triggerNodes).toBe(1);
    });

    it('should validate a workflow with all options disabled', async () => {
      const workflow = createWorkflow('Test Workflow')
        .addWebhookNode({ name: 'Webhook' })
        .build();

      const result = await validator.validateWorkflow(workflow as any, {
        validateNodes: false,
        validateConnections: false,
        validateExpressions: false
      });

      expect(result.valid).toBe(true);
      expect(mockNodeRepository.getNode).not.toHaveBeenCalled();
      expect(ExpressionValidator.validateNodeExpressions).not.toHaveBeenCalled();
    });

    it('should handle validation errors gracefully', async () => {
      const workflow = createWorkflow('Test Workflow')
        .addWebhookNode({ name: 'Webhook' })
        .build();

      // Make the validation throw an error
      vi.mocked(mockNodeRepository.getNode).mockImplementation(() => {
        throw new Error('Database error');
      });

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.valid).toBe(false);
      expect(result.errors.length).toBeGreaterThan(0);
      expect(result.errors.some(e => e.message.includes('Database error'))).toBe(true);
    });

    it('should use different validation profiles', async () => {
      const workflow = createWorkflow('Test Workflow')
        .addWebhookNode({ name: 'Webhook' })
        .build();

      const profiles = ['minimal', 'runtime', 'ai-friendly', 'strict'] as const;

      for (const profile of profiles) {
        const result = await validator.validateWorkflow(workflow as any, { profile });
        expect(result).toBeDefined();
        expect(mockEnhancedConfigValidator.validateWithMode).toHaveBeenCalledWith(
          expect.any(String),
          expect.any(Object),
          expect.any(Array),
          'operation',
          profile
        );
      }
    });
  });

  describe('validateWorkflowStructure', () => {
    it('should error when nodes array is missing', async () => {
      const workflow = { connections: {} } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.valid).toBe(false);
      expect(result.errors.some(e => e.message === 'Workflow must have a nodes array')).toBe(true);
    });

    it('should error when connections object is missing', async () => {
      const workflow = { nodes: [] } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.valid).toBe(false);
      expect(result.errors.some(e => e.message === 'Workflow must have a connections object')).toBe(true);
    });

    it('should warn when workflow has no nodes', async () => {
      const workflow = { nodes: [], connections: {} } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.valid).toBe(true); // Empty workflows are valid but get a warning
      expect(result.warnings).toHaveLength(1);
      expect(result.warnings[0].message).toBe('Workflow is empty - no nodes defined');
    });

    it('should error for single non-webhook node workflow', async () => {
      const workflow = {
        nodes: [{
          id: '1',
          name: 'Set',
          type: 'n8n-nodes-base.set',
          position: [100, 100],
          parameters: {}
        }],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.valid).toBe(false);
      expect(result.errors.some(e => e.message.includes('Single-node workflows are only valid for webhook endpoints'))).toBe(true);
    });

    it('should warn for webhook without connections', async () => {
      const workflow = {
        nodes: [{
          id: '1',
          name: 'Webhook',
          type: 'n8n-nodes-base.webhook',
          position: [100, 100],
          parameters: {},
          typeVersion: 2
        }],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.valid).toBe(true);
      expect(result.warnings.some(w => w.message.includes('Webhook node has no connections'))).toBe(true);
    });

    it('should error for multi-node workflow without connections', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Webhook',
            type: 'n8n-nodes-base.webhook',
            position: [100, 100],
            parameters: {}
          },
          {
            id: '2',
            name: 'Set',
            type: 'n8n-nodes-base.set',
            position: [300, 100],
            parameters: {}
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.valid).toBe(false);
      expect(result.errors.some(e => e.message.includes('Multi-node workflow has no connections'))).toBe(true);
    });

    it('should detect duplicate node names', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Webhook',
            type: 'n8n-nodes-base.webhook',
            position: [100, 100],
            parameters: {}
          },
          {
            id: '2',
            name: 'Webhook',
            type: 'n8n-nodes-base.webhook',
            position: [300, 100],
            parameters: {}
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.errors.some(e => e.message.includes('Duplicate node name: "Webhook"'))).toBe(true);
    });

    it('should detect duplicate node IDs', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Webhook1',
            type: 'n8n-nodes-base.webhook',
            position: [100, 100],
            parameters: {}
          },
          {
            id: '1',
            name: 'Webhook2',
            type: 'n8n-nodes-base.webhook',
            position: [300, 100],
            parameters: {}
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.errors.some(e => e.message.includes('Duplicate node ID: "1"'))).toBe(true);
    });

    it('should count trigger nodes correctly', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Webhook',
            type: 'n8n-nodes-base.webhook',
            position: [100, 100],
            parameters: {}
          },
          {
            id: '2',
            name: 'Schedule',
            type: 'n8n-nodes-base.scheduleTrigger',
            position: [100, 300],
            parameters: {}
          },
          {
            id: '3',
            name: 'Manual',
            type: 'n8n-nodes-base.manualTrigger',
            position: [100, 500],
            parameters: {}
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.statistics.triggerNodes).toBe(3);
    });

    it('should warn when no trigger nodes exist', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Set',
            type: 'n8n-nodes-base.set',
            position: [100, 100],
            parameters: {}
          },
          {
            id: '2',
            name: 'Code',
            type: 'n8n-nodes-base.code',
            position: [300, 100],
            parameters: {}
          }
        ],
        connections: {
          'Set': {
            main: [[{ node: 'Code', type: 'main', index: 0 }]]
          }
        }
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.warnings.some(w => w.message.includes('Workflow has no trigger nodes'))).toBe(true);
    });

    it('should not count disabled nodes in enabledNodes count', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Webhook',
            type: 'n8n-nodes-base.webhook',
            position: [100, 100],
            parameters: {},
            disabled: true
          },
          {
            id: '2',
            name: 'Set',
            type: 'n8n-nodes-base.set',
            position: [300, 100],
            parameters: {}
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.statistics.totalNodes).toBe(2);
      expect(result.statistics.enabledNodes).toBe(1);
    });
  });

  describe('validateAllNodes', () => {
    it('should skip disabled nodes', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Webhook',
            type: 'n8n-nodes-base.webhook',
            position: [100, 100],
            parameters: {},
            disabled: true
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(mockNodeRepository.getNode).not.toHaveBeenCalled();
    });

    it('should accept both nodes-base and n8n-nodes-base prefixes as valid', async () => {
      // This test verifies the fix for false positives - both prefixes are valid
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Webhook',
            type: 'nodes-base.webhook', // This is now valid (normalized internally)
            position: [100, 100],
            parameters: {}
          }
        ],
        connections: {}
      } as any;

      // Mock the normalized node lookup
      (mockNodeRepository.getNode as any) = vi.fn((type: string) => {
        if (type === 'nodes-base.webhook') {
          return {
            nodeType: 'nodes-base.webhook',
            displayName: 'Webhook',
            properties: [],
            isVersioned: false
          };
        }
        return null;
      });

      const result = await validator.validateWorkflow(workflow as any);

      // Should NOT error for nodes-base prefix - it's valid!
      expect(result.valid).toBe(true);
      expect(result.errors.some(e => e.message.includes('Invalid node type'))).toBe(false);
    });

    it.skip('should handle unknown node types with suggestions', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'HTTP',
            type: 'httpRequest', // Missing package prefix
            position: [100, 100],
            parameters: {}
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.valid).toBe(false);
      expect(result.errors.some(e => e.message.includes('Unknown node type: "httpRequest"'))).toBe(true);
      expect(result.errors.some(e => e.message.includes('Did you mean "n8n-nodes-base.httpRequest"?'))).toBe(true);
    });

    it('should try normalized types for n8n-nodes-base', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Webhook',
            type: 'n8n-nodes-base.webhook',
            position: [100, 100],
            parameters: {}
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(mockNodeRepository.getNode).toHaveBeenCalledWith('nodes-base.webhook');
    });

    it('should validate typeVersion but skip parameter validation for langchain nodes', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Agent',
            type: '@n8n/n8n-nodes-langchain.agent',
            typeVersion: 1,
            position: [100, 100],
            parameters: {}
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      // After v2.17.4 fix: Langchain nodes SHOULD call getNode for typeVersion validation
      // This prevents invalid typeVersion values from bypassing validation
      // But they skip parameter validation (handled by dedicated AI validators)
      expect(mockNodeRepository.getNode).toHaveBeenCalledWith('nodes-langchain.agent');

      // Should not have typeVersion validation errors (other AI-specific errors may exist)
      const typeVersionErrors = result.errors.filter(e => e.message.includes('typeVersion'));
      expect(typeVersionErrors).toEqual([]);
    });

    it('should catch invalid typeVersion for langchain nodes (v2.17.4 bug fix)', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Agent',
            type: '@n8n/n8n-nodes-langchain.agent',
            typeVersion: 99999, // Invalid - exceeds maximum
            position: [100, 100],
            parameters: {}
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      // Critical: Before v2.17.4, this would pass validation but fail at runtime
      // After v2.17.4: Invalid typeVersion is caught during validation
      expect(result.valid).toBe(false);
      expect(result.errors.some(e =>
        e.message.includes('typeVersion 99999 exceeds maximum')
      )).toBe(true);
    });

    it('should validate typeVersion for versioned nodes', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Webhook',
            type: 'n8n-nodes-base.webhook',
            position: [100, 100],
            parameters: {}
            // Missing typeVersion
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.errors.some(e => e.message.includes('Missing required property \'typeVersion\''))).toBe(true);
    });

    it('should error for invalid typeVersion', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Webhook',
            type: 'n8n-nodes-base.webhook',
            position: [100, 100],
            parameters: {},
            typeVersion: 'invalid' as any
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.errors.some(e => e.message.includes('Invalid typeVersion: invalid'))).toBe(true);
    });

    it('should warn for outdated typeVersion', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Webhook',
            type: 'n8n-nodes-base.webhook',
            position: [100, 100],
            parameters: {},
            typeVersion: 1 // Current version is 2
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.warnings.some(w => w.message.includes('Outdated typeVersion: 1. Latest is 2'))).toBe(true);
    });

    it('should error for typeVersion exceeding maximum', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Webhook',
            type: 'n8n-nodes-base.webhook',
            position: [100, 100],
            parameters: {},
            typeVersion: 10 // Max is 2
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.errors.some(e => e.message.includes('typeVersion 10 exceeds maximum supported version 2'))).toBe(true);
    });

    it('should add node validation errors and warnings', async () => {
      vi.mocked(mockEnhancedConfigValidator.validateWithMode).mockReturnValue({
        errors: [{ type: 'missing_required', property: 'url', message: 'Missing required field: url' }],
        warnings: [{ type: 'security', property: 'url', message: 'Consider using HTTPS' }],
        suggestions: [],
        mode: 'operation' as const,
        valid: false,
        visibleProperties: [],
        hiddenProperties: []
      } as any);

      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'HTTP',
            type: 'n8n-nodes-base.httpRequest',
            position: [100, 100],
            parameters: {},
            typeVersion: 4
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.errors.some(e => e.message.includes('Missing required field: url'))).toBe(true);
      expect(result.warnings.some(w => w.message.includes('Consider using HTTPS'))).toBe(true);
    });

    it('should handle node validation failures gracefully', async () => {
      vi.mocked(mockEnhancedConfigValidator.validateWithMode).mockImplementation(() => {
        throw new Error('Validation error');
      });

      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'HTTP',
            type: 'n8n-nodes-base.httpRequest',
            position: [100, 100],
            parameters: {},
            typeVersion: 4
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.errors.some(e => e.message.includes('Failed to validate node: Validation error'))).toBe(true);
    });
  });

  describe('validateConnections', () => {
    it('should validate valid connections', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Webhook',
            type: 'n8n-nodes-base.webhook',
            position: [100, 100],
            parameters: {}
          },
          {
            id: '2',
            name: 'Set',
            type: 'n8n-nodes-base.set',
            position: [300, 100],
            parameters: {}
          }
        ],
        connections: {
          'Webhook': {
            main: [[{ node: 'Set', type: 'main', index: 0 }]]
          }
        }
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.statistics.validConnections).toBe(1);
      expect(result.statistics.invalidConnections).toBe(0);
    });

    it('should error for connection from non-existent node', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Webhook',
            type: 'n8n-nodes-base.webhook',
            position: [100, 100],
            parameters: {}
          }
        ],
        connections: {
          'NonExistent': {
            main: [[{ node: 'Webhook', type: 'main', index: 0 }]]
          }
        }
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.errors.some(e => e.message.includes('Connection from non-existent node: "NonExistent"'))).toBe(true);
      expect(result.statistics.invalidConnections).toBe(1);
    });

    it('should error when using node ID instead of name in source', async () => {
      const workflow = {
        nodes: [
          {
            id: 'webhook-id',
            name: 'Webhook',
            type: 'n8n-nodes-base.webhook',
            position: [100, 100],
            parameters: {}
          },
          {
            id: 'set-id',
            name: 'Set',
            type: 'n8n-nodes-base.set',
            position: [300, 100],
            parameters: {}
          }
        ],
        connections: {
          'webhook-id': { // Using ID instead of name
            main: [[{ node: 'Set', type: 'main', index: 0 }]]
          }
        }
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.errors.some(e => e.message.includes('Connection uses node ID \'webhook-id\' instead of node name \'Webhook\''))).toBe(true);
    });

    it('should error for connection to non-existent node', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Webhook',
            type: 'n8n-nodes-base.webhook',
            position: [100, 100],
            parameters: {}
          }
        ],
        connections: {
          'Webhook': {
            main: [[{ node: 'NonExistent', type: 'main', index: 0 }]]
          }
        }
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.errors.some(e => e.message.includes('Connection to non-existent node: "NonExistent"'))).toBe(true);
      expect(result.statistics.invalidConnections).toBe(1);
    });

    it('should error when using node ID instead of name in target', async () => {
      const workflow = {
        nodes: [
          {
            id: 'webhook-id',
            name: 'Webhook',
            type: 'n8n-nodes-base.webhook',
            position: [100, 100],
            parameters: {}
          },
          {
            id: 'set-id',
            name: 'Set',
            type: 'n8n-nodes-base.set',
            position: [300, 100],
            parameters: {}
          }
        ],
        connections: {
          'Webhook': {
            main: [[{ node: 'set-id', type: 'main', index: 0 }]] // Using ID instead of name
          }
        }
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.errors.some(e => e.message.includes('Connection target uses node ID \'set-id\' instead of node name \'Set\''))).toBe(true);
    });

    it('should warn for connection to disabled node', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Webhook',
            type: 'n8n-nodes-base.webhook',
            position: [100, 100],
            parameters: {}
          },
          {
            id: '2',
            name: 'Set',
            type: 'n8n-nodes-base.set',
            position: [300, 100],
            parameters: {},
            disabled: true
          }
        ],
        connections: {
          'Webhook': {
            main: [[{ node: 'Set', type: 'main', index: 0 }]]
          }
        }
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.warnings.some(w => w.message.includes('Connection to disabled node: "Set"'))).toBe(true);
    });

    it('should validate error outputs', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'HTTP',
            type: 'n8n-nodes-base.httpRequest',
            position: [100, 100],
            parameters: {}
          },
          {
            id: '2',
            name: 'Error Handler',
            type: 'n8n-nodes-base.set',
            position: [300, 100],
            parameters: {}
          }
        ],
        connections: {
          'HTTP': {
            error: [[{ node: 'Error Handler', type: 'main', index: 0 }]]
          }
        }
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.statistics.validConnections).toBe(1);
    });

    it('should validate AI tool connections', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Agent',
            type: '@n8n/n8n-nodes-langchain.agent',
            position: [100, 100],
            parameters: {}
          },
          {
            id: '2',
            name: 'Tool',
            type: 'n8n-nodes-base.httpRequest',
            position: [300, 100],
            parameters: {}
          }
        ],
        connections: {
          'Agent': {
            ai_tool: [[{ node: 'Tool', type: 'main', index: 0 }]]
          }
        }
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.statistics.validConnections).toBe(1);
    });

    it('should warn for community nodes used as AI tools', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Agent',
            type: '@n8n/n8n-nodes-langchain.agent',
            position: [100, 100],
            parameters: {},
            typeVersion: 1
          },
          {
            id: '2',
            name: 'CustomTool',
            type: 'n8n-nodes-custom.customNode',
            position: [300, 100],
            parameters: {},
            typeVersion: 1
          }
        ],
        connections: {
          'Agent': {
            ai_tool: [[{ node: 'CustomTool', type: 'main', index: 0 }]]
          }
        }
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.warnings.some(w => w.message.includes('Community node "CustomTool" is being used as an AI tool'))).toBe(true);
    });

    it('should warn for orphaned nodes', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Webhook',
            type: 'n8n-nodes-base.webhook',
            position: [100, 100],
            parameters: {}
          },
          {
            id: '2',
            name: 'Set',
            type: 'n8n-nodes-base.set',
            position: [300, 100],
            parameters: {}
          },
          {
            id: '3',
            name: 'Orphaned',
            type: 'n8n-nodes-base.code',
            position: [500, 100],
            parameters: {}
          }
        ],
        connections: {
          'Webhook': {
            main: [[{ node: 'Set', type: 'main', index: 0 }]]
          }
        }
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.warnings.some(w => w.message.includes('Node is not connected to any other nodes') && w.nodeName === 'Orphaned')).toBe(true);
    });

    it('should detect cycles in workflow', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Node1',
            type: 'n8n-nodes-base.set',
            position: [100, 100],
            parameters: {}
          },
          {
            id: '2',
            name: 'Node2',
            type: 'n8n-nodes-base.set',
            position: [300, 100],
            parameters: {}
          },
          {
            id: '3',
            name: 'Node3',
            type: 'n8n-nodes-base.set',
            position: [500, 100],
            parameters: {}
          }
        ],
        connections: {
          'Node1': {
            main: [[{ node: 'Node2', type: 'main', index: 0 }]]
          },
          'Node2': {
            main: [[{ node: 'Node3', type: 'main', index: 0 }]]
          },
          'Node3': {
            main: [[{ node: 'Node1', type: 'main', index: 0 }]] // Creates cycle
          }
        }
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.errors.some(e => e.message.includes('Workflow contains a cycle'))).toBe(true);
    });

    it('should handle null connections properly', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'IF',
            type: 'n8n-nodes-base.if',
            position: [100, 100],
            parameters: {},
            typeVersion: 2
          },
          {
            id: '2',
            name: 'True Branch',
            type: 'n8n-nodes-base.set',
            position: [300, 50],
            parameters: {},
            typeVersion: 3
          }
        ],
        connections: {
          'IF': {
            main: [
              [{ node: 'True Branch', type: 'main', index: 0 }],
              null // False branch not connected
            ]
          }
        }
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.statistics.validConnections).toBe(1);
      expect(result.valid).toBe(true);
    });
  });

  describe('validateExpressions', () => {
    it('should validate expressions in node parameters', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Webhook',
            type: 'n8n-nodes-base.webhook',
            position: [100, 100],
            parameters: {}
          },
          {
            id: '2',
            name: 'Set',
            type: 'n8n-nodes-base.set',
            position: [300, 100],
            parameters: {
              values: {
                string: [
                  {
                    name: 'field',
                    value: '={{ $json.data }}'
                  }
                ]
              }
            }
          }
        ],
        connections: {
          'Webhook': {
            main: [[{ node: 'Set', type: 'main', index: 0 }]]
          }
        }
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(ExpressionValidator.validateNodeExpressions).toHaveBeenCalledWith(
        expect.objectContaining({ values: expect.any(Object) }),
        expect.objectContaining({
          availableNodes: expect.arrayContaining(['Webhook']),
          currentNodeName: 'Set',
          hasInputData: true
        })
      );
    });

    it('should add expression errors to result', async () => {
      vi.mocked(ExpressionValidator.validateNodeExpressions).mockReturnValue({
        valid: false,
        errors: ['Invalid expression syntax'],
        warnings: ['Deprecated variable usage'],
        usedVariables: new Set(['$json']),
        usedNodes: new Set()
      });

      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Set',
            type: 'n8n-nodes-base.set',
            position: [100, 100],
            parameters: {
              value: '={{ invalid }}'
            }
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.errors.some(e => e.message.includes('Expression error: Invalid expression syntax'))).toBe(true);
      expect(result.warnings.some(w => w.message.includes('Expression warning: Deprecated variable usage'))).toBe(true);
      expect(result.statistics.expressionsValidated).toBe(1);
    });

    it('should skip expression validation for disabled nodes', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Set',
            type: 'n8n-nodes-base.set',
            position: [100, 100],
            parameters: {
              value: '={{ $json.data }}'
            },
            disabled: true
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(ExpressionValidator.validateNodeExpressions).not.toHaveBeenCalled();
    });
  });

  describe('checkWorkflowPatterns', () => {
    it('should suggest error handling for large workflows', async () => {
      const builder = createWorkflow('Large Workflow');
      
      // Add more than 3 nodes
      for (let i = 0; i < 5; i++) {
        builder.addCustomNode('n8n-nodes-base.set', 3, {}, { name: `Set${i}` });
      }

      const workflow = builder.build() as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.warnings.some(w => w.message.includes('Consider adding error handling'))).toBe(true);
    });

    it('should warn about long linear chains', async () => {
      const builder = createWorkflow('Linear Workflow');
      
      // Create a chain of 12 nodes
      const nodeNames: string[] = [];
      for (let i = 0; i < 12; i++) {
        const nodeName = `Node${i}`;
        builder.addCustomNode('n8n-nodes-base.set', 3, {}, { name: nodeName });
        nodeNames.push(nodeName);
      }

      // Connect them sequentially
      builder.connectSequentially(nodeNames);

      const workflow = builder.build() as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.warnings.some(w => w.message.includes('Long linear chain detected'))).toBe(true);
    });

    it('should warn about missing credentials', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Slack',
            type: 'n8n-nodes-base.slack',
            position: [100, 100],
            parameters: {},
            credentials: {
              slackApi: {} // Missing id
            }
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.warnings.some(w => w.message.includes('Missing credentials configuration for slackApi'))).toBe(true);
    });

    it('should warn about AI agents without tools', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Agent',
            type: '@n8n/n8n-nodes-langchain.agent',
            position: [100, 100],
            parameters: {}
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.warnings.some(w => w.message.includes('AI Agent has no tools connected'))).toBe(true);
    });

    it('should suggest community package setting for AI tools', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Agent',
            type: '@n8n/n8n-nodes-langchain.agent',
            position: [100, 100],
            parameters: {}
          },
          {
            id: '2',
            name: 'Tool',
            type: 'n8n-nodes-base.httpRequest',
            position: [300, 100],
            parameters: {}
          }
        ],
        connections: {
          'Agent': {
            ai_tool: [[{ node: 'Tool', type: 'main', index: 0 }]]
          }
        }
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.suggestions.some(s => s.includes('N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE'))).toBe(true);
    });
  });

  describe('checkNodeErrorHandling', () => {
    it('should error when node-level properties are inside parameters', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'HTTP',
            type: 'n8n-nodes-base.httpRequest',
            position: [100, 100],
            typeVersion: 4,
            parameters: {
              url: 'https://api.example.com',
              onError: 'continueRegularOutput', // Wrong location!
              retryOnFail: true, // Wrong location!
              credentials: {} // Wrong location!
            }
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.errors.some(e => e.message.includes('Node-level properties onError, retryOnFail, credentials are in the wrong location'))).toBe(true);
      expect(result.errors.some(e => e.details?.fix?.includes('Move these properties from node.parameters to the node level'))).toBe(true);
    });

    it('should validate onError property values', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'HTTP',
            type: 'n8n-nodes-base.httpRequest',
            position: [100, 100],
            parameters: {},
            onError: 'invalidValue' as any
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.errors.some(e => e.message.includes('Invalid onError value: "invalidValue"'))).toBe(true);
    });

    it('should warn about deprecated continueOnFail', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'HTTP',
            type: 'n8n-nodes-base.httpRequest',
            position: [100, 100],
            parameters: {},
            continueOnFail: true
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.warnings.some(w => w.message.includes('Using deprecated "continueOnFail: true"'))).toBe(true);
    });

    it('should error for conflicting error handling properties', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'HTTP',
            type: 'n8n-nodes-base.httpRequest',
            position: [100, 100],
            parameters: {},
            continueOnFail: true,
            onError: 'continueRegularOutput'
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.errors.some(e => e.message.includes('Cannot use both "continueOnFail" and "onError" properties'))).toBe(true);
    });

    it('should validate retry configuration', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'HTTP',
            type: 'n8n-nodes-base.httpRequest',
            position: [100, 100],
            parameters: {},
            retryOnFail: true,
            maxTries: 'invalid' as any,
            waitBetweenTries: -1000
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.errors.some(e => e.message.includes('maxTries must be a positive number'))).toBe(true);
      expect(result.errors.some(e => e.message.includes('waitBetweenTries must be a non-negative number'))).toBe(true);
    });

    it('should warn about excessive retry values', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'HTTP',
            type: 'n8n-nodes-base.httpRequest',
            position: [100, 100],
            parameters: {},
            retryOnFail: true,
            maxTries: 15,
            waitBetweenTries: 400000
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.warnings.some(w => w.message.includes('maxTries is set to 15'))).toBe(true);
      expect(result.warnings.some(w => w.message.includes('waitBetweenTries is set to 400000ms'))).toBe(true);
    });

    it('should warn about retryOnFail without maxTries', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'HTTP',
            type: 'n8n-nodes-base.httpRequest',
            position: [100, 100],
            parameters: {},
            retryOnFail: true
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.warnings.some(w => w.message.includes('retryOnFail is enabled but maxTries is not specified'))).toBe(true);
    });

    it('should validate other node-level properties', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Set',
            type: 'n8n-nodes-base.set',
            position: [100, 100],
            parameters: {},
            typeVersion: 3,
            alwaysOutputData: 'invalid' as any,
            executeOnce: 'invalid' as any,
            disabled: 'invalid' as any,
            notesInFlow: 'invalid' as any,
            notes: 123 as any
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);


      expect(result.errors.some(e => e.message.includes('alwaysOutputData must be a boolean'))).toBe(true);
      expect(result.errors.some(e => e.message.includes('executeOnce must be a boolean'))).toBe(true);
      expect(result.errors.some(e => e.message.includes('disabled must be a boolean'))).toBe(true);
      expect(result.errors.some(e => e.message.includes('notesInFlow must be a boolean'))).toBe(true);
      expect(result.errors.some(e => e.message.includes('notes must be a string'))).toBe(true);
    });

    it('should warn about executeOnce', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Set',
            type: 'n8n-nodes-base.set',
            position: [100, 100],
            parameters: {},
            executeOnce: true
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.warnings.some(w => w.message.includes('executeOnce is enabled'))).toBe(true);
    });

    it('should warn error-prone nodes without error handling', async () => {
      const errorProneNodes = [
        { type: 'n8n-nodes-base.httpRequest', message: 'HTTP Request', version: 4 },
        { type: 'n8n-nodes-base.webhook', message: 'Webhook', version: 2 },
        { type: 'n8n-nodes-base.postgres', message: 'Database operation', version: 2 },
        { type: 'n8n-nodes-base.slack', message: 'slack node', version: 2 }
      ];

      for (const nodeInfo of errorProneNodes) {
        const workflow = {
          nodes: [
            {
              id: '1',
              name: 'Node',
              type: nodeInfo.type,
              position: [100, 100],
              parameters: {},
              typeVersion: nodeInfo.version
            }
          ],
          connections: {}
        } as any;

        const result = await validator.validateWorkflow(workflow as any);

        expect(result.warnings.some(w => w.message.includes(nodeInfo.message) && w.message.includes('without error handling'))).toBe(true);
      }
    });

    it('should warn about conflicting error handling', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'HTTP',
            type: 'n8n-nodes-base.httpRequest',
            position: [100, 100],
            parameters: {},
            continueOnFail: true,
            retryOnFail: true
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.warnings.some(w => w.message.includes('Both continueOnFail and retryOnFail are enabled'))).toBe(true);
    });

    it('should suggest alwaysOutputData for debugging', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'HTTP',
            type: 'n8n-nodes-base.httpRequest',
            position: [100, 100],
            parameters: {},
            retryOnFail: true
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.suggestions.some(s => s.includes('Consider enabling alwaysOutputData'))).toBe(true);
    });

    it('should provide general error handling suggestions', async () => {
      const builder = createWorkflow('No Error Handling');
      
      // Add 6 nodes without error handling
      for (let i = 0; i < 6; i++) {
        builder.addCustomNode('n8n-nodes-base.httpRequest', 4, {}, { name: `HTTP${i}` });
      }

      const workflow = builder.build() as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.suggestions.some(s => s.includes('Most nodes lack error handling'))).toBe(true);
    });

    it('should suggest replacing deprecated error handling', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'HTTP',
            type: 'n8n-nodes-base.httpRequest',
            position: [100, 100],
            parameters: {},
            continueOnFail: true
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.suggestions.some(s => s.includes('Replace "continueOnFail: true" with "onError:'))).toBe(true);
    });
  });

  describe('generateSuggestions', () => {
    it('should suggest adding trigger for workflows without triggers', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Set',
            type: 'n8n-nodes-base.set',
            position: [100, 100],
            parameters: {}
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.suggestions.some(s => s.includes('Add a trigger node'))).toBe(true);
    });

    it('should provide connection examples for connection errors', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Webhook',
            type: 'n8n-nodes-base.webhook',
            position: [100, 100],
            parameters: {}
          },
          {
            id: '2',
            name: 'Set',
            type: 'n8n-nodes-base.set',
            position: [300, 100],
            parameters: {}
          }
        ],
        connections: {} // Missing connections
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.suggestions.some(s => s.includes('Example connection structure'))).toBe(true);
      expect(result.suggestions.some(s => s.includes('Use node NAMES (not IDs) in connections'))).toBe(true);
    });

    it('should suggest error handling when missing', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Webhook',
            type: 'n8n-nodes-base.webhook',
            position: [100, 100],
            parameters: {}
          },
          {
            id: '2',
            name: 'HTTP',
            type: 'n8n-nodes-base.httpRequest',
            position: [300, 100],
            parameters: {}
          }
        ],
        connections: {
          'Webhook': {
            main: [[{ node: 'HTTP', type: 'main', index: 0 }]]
          }
        }
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.suggestions.some(s => s.includes('Add error handling'))).toBe(true);
    });

    it('should suggest breaking up large workflows', async () => {
      const builder = createWorkflow('Large Workflow');
      
      // Add 25 nodes
      for (let i = 0; i < 25; i++) {
        builder.addCustomNode('n8n-nodes-base.set', 3, {}, { name: `Node${i}` });
      }

      const workflow = builder.build() as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.suggestions.some(s => s.includes('Consider breaking this workflow into smaller sub-workflows'))).toBe(true);
    });

    it('should suggest Code node for complex expressions', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Complex',
            type: 'n8n-nodes-base.set',
            position: [100, 100],
            parameters: {
              field1: '={{ $json.a }}',
              field2: '={{ $json.b }}',
              field3: '={{ $json.c }}',
              field4: '={{ $json.d }}',
              field5: '={{ $json.e }}',
              field6: '={{ $json.f }}'
            }
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.suggestions.some(s => s.includes('Consider using a Code node for complex data transformations'))).toBe(true);
    });

    it('should suggest minimal workflow structure', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Set',
            type: 'n8n-nodes-base.set',
            position: [100, 100],
            parameters: {}
          }
        ],
        connections: {}
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.suggestions.some(s => s.includes('A minimal workflow needs'))).toBe(true);
    });
  });

  describe('findSimilarNodeTypes', () => {
    it.skip('should find similar node types for common mistakes', async () => {
      // Test that webhook without prefix gets suggestions
      const webhookWorkflow = {
        nodes: [
          {
            id: '1',
            name: 'Node',
            type: 'webhook',
            position: [100, 100],
            parameters: {}
          }
        ],
        connections: {}
      } as any;

      const webhookResult = await validator.validateWorkflow(webhookWorkflow);

      // Check that we get an unknown node error with suggestions
      const unknownNodeError = webhookResult.errors.find(e =>
        e.message && e.message.includes('Unknown node type')
      );
      expect(unknownNodeError).toBeDefined();

      // For webhook, it should definitely suggest nodes-base.webhook
      expect(unknownNodeError?.message).toContain('nodes-base.webhook');

      // Test that slack without prefix gets suggestions
      const slackWorkflow = {
        nodes: [
          {
            id: '1',
            name: 'Node',
            type: 'slack',
            position: [100, 100],
            parameters: {}
          }
        ],
        connections: {}
      } as any;

      const slackResult = await validator.validateWorkflow(slackWorkflow);
      const slackError = slackResult.errors.find(e =>
        e.message && e.message.includes('Unknown node type')
      );
      expect(slackError).toBeDefined();
      expect(slackError?.message).toContain('nodes-base.slack');
    });
  });

  describe('Integration Tests', () => {
    it('should validate a complex workflow with multiple issues', async () => {
      const workflow = {
        nodes: [
          // Valid trigger
          {
            id: '1',
            name: 'Webhook',
            type: 'n8n-nodes-base.webhook',
            position: [100, 100],
            parameters: {},
            typeVersion: 2
          },
          // Node with valid alternative prefix (no longer an error)
          {
            id: '2',
            name: 'HTTP1',
            type: 'nodes-base.httpRequest', // Valid prefix (normalized internally)
            position: [300, 100],
            parameters: {}
          },
          // Node with missing typeVersion
          {
            id: '3',
            name: 'Slack',
            type: 'n8n-nodes-base.slack',
            position: [500, 100],
            parameters: {}
          },
          // Disabled node
          {
            id: '4',
            name: 'Disabled',
            type: 'n8n-nodes-base.set',
            position: [700, 100],
            parameters: {},
            disabled: true
          },
          // Node with error handling in wrong place
          {
            id: '5',
            name: 'HTTP2',
            type: 'n8n-nodes-base.httpRequest',
            position: [900, 100],
            parameters: {
              onError: 'continueRegularOutput'
            },
            typeVersion: 4
          },
          // Orphaned node
          {
            id: '6',
            name: 'Orphaned',
            type: 'n8n-nodes-base.code',
            position: [1100, 100],
            parameters: {},
            typeVersion: 2
          },
          // AI Agent without tools
          {
            id: '7',
            name: 'Agent',
            type: '@n8n/n8n-nodes-langchain.agent',
            position: [100, 300],
            parameters: {},
            typeVersion: 1
          }
        ],
        connections: {
          'Webhook': {
            main: [[{ node: 'HTTP1', type: 'main', index: 0 }]]
          },
          'HTTP1': {
            main: [[{ node: 'Slack', type: 'main', index: 0 }]]
          },
          'Slack': {
            main: [[{ node: 'Disabled', type: 'main', index: 0 }]]
          },
          // Using ID instead of name
          '5': {
            main: [[{ node: 'Agent', type: 'main', index: 0 }]]
          }
        }
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      // Should have multiple errors (but not for the nodes-base prefix)
      expect(result.valid).toBe(false);
      expect(result.errors.length).toBeGreaterThan(2); // Reduced by 1 since nodes-base prefix is now valid

      // Specific errors (removed the invalid node type error as it's no longer invalid)
      expect(result.errors.some(e => e.message.includes('Missing required property \'typeVersion\''))).toBe(true);
      expect(result.errors.some(e => e.message.includes('Node-level properties onError are in the wrong location'))).toBe(true);
      expect(result.errors.some(e => e.message.includes('Connection uses node ID \'5\' instead of node name'))).toBe(true);

      // Warnings
      expect(result.warnings.some(w => w.message.includes('Connection to disabled node'))).toBe(true);
      expect(result.warnings.some(w => w.message.includes('Node is not connected') && w.nodeName === 'Orphaned')).toBe(true);
      expect(result.warnings.some(w => w.message.includes('AI Agent has no tools connected'))).toBe(true);

      // Statistics
      expect(result.statistics.totalNodes).toBe(7);
      expect(result.statistics.enabledNodes).toBe(6);
      expect(result.statistics.triggerNodes).toBe(1);
      expect(result.statistics.invalidConnections).toBeGreaterThan(0);

      // Suggestions
      expect(result.suggestions.length).toBeGreaterThan(0);
    });

    it('should validate a perfect workflow', async () => {
      const workflow = {
        nodes: [
          {
            id: '1',
            name: 'Manual Trigger',
            type: 'n8n-nodes-base.manualTrigger',
            position: [250, 300],
            parameters: {},
            typeVersion: 1
          },
          {
            id: '2',
            name: 'HTTP Request',
            type: 'n8n-nodes-base.httpRequest',
            position: [450, 300],
            parameters: {
              url: 'https://api.example.com/data',
              method: 'GET'
            },
            typeVersion: 4,
            onError: 'continueErrorOutput',
            retryOnFail: true,
            maxTries: 3,
            waitBetweenTries: 1000
          },
          {
            id: '3',
            name: 'Process Data',
            type: 'n8n-nodes-base.code',
            position: [650, 300],
            parameters: {
              jsCode: 'return items;'
            },
            typeVersion: 2
          },
          {
            id: '4',
            name: 'Error Handler',
            type: 'n8n-nodes-base.set',
            position: [650, 500],
            parameters: {
              values: {
                string: [
                  {
                    name: 'error',
                    value: 'An error occurred'
                  }
                ]
              }
            },
            typeVersion: 3
          }
        ],
        connections: {
          'Manual Trigger': {
            main: [[{ node: 'HTTP Request', type: 'main', index: 0 }]]
          },
          'HTTP Request': {
            main: [
              [{ node: 'Process Data', type: 'main', index: 0 }],
              [{ node: 'Error Handler', type: 'main', index: 0 }]
            ]
          }
        }
      } as any;

      const result = await validator.validateWorkflow(workflow as any);

      expect(result.valid).toBe(true);
      expect(result.errors).toHaveLength(0);
      expect(result.warnings).toHaveLength(0);
      expect(result.statistics.validConnections).toBe(3);
      expect(result.statistics.invalidConnections).toBe(0);
    });
  });
});
```

--------------------------------------------------------------------------------
/src/services/workflow-validator.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Workflow Validator for n8n workflows
 * Validates complete workflow structure, connections, and node configurations
 */

import { NodeRepository } from '../database/node-repository';
import { EnhancedConfigValidator } from './enhanced-config-validator';
import { ExpressionValidator } from './expression-validator';
import { ExpressionFormatValidator } from './expression-format-validator';
import { NodeSimilarityService, NodeSuggestion } from './node-similarity-service';
import { NodeTypeNormalizer } from '../utils/node-type-normalizer';
import { Logger } from '../utils/logger';
import { validateAISpecificNodes, hasAINodes } from './ai-node-validator';
const logger = new Logger({ prefix: '[WorkflowValidator]' });

interface WorkflowNode {
  id: string;
  name: string;
  type: string;
  position: [number, number];
  parameters: any;
  credentials?: any;
  disabled?: boolean;
  notes?: string;
  notesInFlow?: boolean;
  typeVersion?: number;
  continueOnFail?: boolean;
  onError?: 'continueRegularOutput' | 'continueErrorOutput' | 'stopWorkflow';
  retryOnFail?: boolean;
  maxTries?: number;
  waitBetweenTries?: number;
  alwaysOutputData?: boolean;
  executeOnce?: boolean;
}

interface WorkflowConnection {
  [sourceNode: string]: {
    main?: Array<Array<{ node: string; type: string; index: number }>>;
    error?: Array<Array<{ node: string; type: string; index: number }>>;
    ai_tool?: Array<Array<{ node: string; type: string; index: number }>>;
  };
}

interface WorkflowJson {
  name?: string;
  nodes: WorkflowNode[];
  connections: WorkflowConnection;
  settings?: any;
  staticData?: any;
  pinData?: any;
  meta?: any;
}

interface ValidationIssue {
  type: 'error' | 'warning';
  nodeId?: string;
  nodeName?: string;
  message: string;
  details?: any;
}

export interface WorkflowValidationResult {
  valid: boolean;
  errors: ValidationIssue[];
  warnings: ValidationIssue[];
  statistics: {
    totalNodes: number;
    enabledNodes: number;
    triggerNodes: number;
    validConnections: number;
    invalidConnections: number;
    expressionsValidated: number;
  };
  suggestions: string[];
}

export class WorkflowValidator {
  private currentWorkflow: WorkflowJson | null = null;
  private similarityService: NodeSimilarityService;

  constructor(
    private nodeRepository: NodeRepository,
    private nodeValidator: typeof EnhancedConfigValidator
  ) {
    this.similarityService = new NodeSimilarityService(nodeRepository);
  }

  /**
   * Check if a node is a Sticky Note or other non-executable node
   */
  private isStickyNote(node: WorkflowNode): boolean {
    const stickyNoteTypes = [
      'n8n-nodes-base.stickyNote',
      'nodes-base.stickyNote',
      '@n8n/n8n-nodes-base.stickyNote'
    ];
    return stickyNoteTypes.includes(node.type);
  }

  /**
   * Validate a complete workflow
   */
  async validateWorkflow(
    workflow: WorkflowJson,
    options: {
      validateNodes?: boolean;
      validateConnections?: boolean;
      validateExpressions?: boolean;
      profile?: 'minimal' | 'runtime' | 'ai-friendly' | 'strict';
    } = {}
  ): Promise<WorkflowValidationResult> {
    // Store current workflow for access in helper methods
    this.currentWorkflow = workflow;

    const {
      validateNodes = true,
      validateConnections = true,
      validateExpressions = true,
      profile = 'runtime'
    } = options;

    const result: WorkflowValidationResult = {
      valid: true,
      errors: [],
      warnings: [],
      statistics: {
        totalNodes: 0,
        enabledNodes: 0,
        triggerNodes: 0,
        validConnections: 0,
        invalidConnections: 0,
        expressionsValidated: 0,
      },
      suggestions: []
    };

    try {
      // Handle null/undefined workflow
      if (!workflow) {
        result.errors.push({
          type: 'error',
          message: 'Invalid workflow structure: workflow is null or undefined'
        });
        result.valid = false;
        return result;
      }

      // Update statistics after null check (exclude sticky notes from counts)
      const executableNodes = Array.isArray(workflow.nodes) ? workflow.nodes.filter(n => !this.isStickyNote(n)) : [];
      result.statistics.totalNodes = executableNodes.length;
      result.statistics.enabledNodes = executableNodes.filter(n => !n.disabled).length;

      // Basic workflow structure validation
      this.validateWorkflowStructure(workflow, result);

      // Only continue if basic structure is valid
      if (workflow.nodes && Array.isArray(workflow.nodes) && workflow.connections && typeof workflow.connections === 'object') {
        // Validate each node if requested
        if (validateNodes && workflow.nodes.length > 0) {
          await this.validateAllNodes(workflow, result, profile);
        }

        // Validate connections if requested
        if (validateConnections) {
          this.validateConnections(workflow, result, profile);
        }

        // Validate expressions if requested
        if (validateExpressions && workflow.nodes.length > 0) {
          this.validateExpressions(workflow, result, profile);
        }

        // Check workflow patterns and best practices
        if (workflow.nodes.length > 0) {
          this.checkWorkflowPatterns(workflow, result, profile);
        }

        // Validate AI-specific nodes (AI Agent, Chat Trigger, AI tools)
        if (workflow.nodes.length > 0 && hasAINodes(workflow)) {
          const aiIssues = validateAISpecificNodes(workflow);
          // Convert AI validation issues to workflow validation format
          for (const issue of aiIssues) {
            const validationIssue: ValidationIssue = {
              type: issue.severity === 'error' ? 'error' : 'warning',
              nodeId: issue.nodeId,
              nodeName: issue.nodeName,
              message: issue.message,
              details: issue.code ? { code: issue.code } : undefined
            };

            if (issue.severity === 'error') {
              result.errors.push(validationIssue);
            } else {
              result.warnings.push(validationIssue);
            }
          }
        }

        // Add suggestions based on findings
        this.generateSuggestions(workflow, result);

        // Add AI-specific recovery suggestions if there are errors
        if (result.errors.length > 0) {
          this.addErrorRecoverySuggestions(result);
        }
      }

    } catch (error) {
      logger.error('Error validating workflow:', error);
      result.errors.push({
        type: 'error',
        message: `Workflow validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`
      });
    }

    result.valid = result.errors.length === 0;
    return result;
  }

  /**
   * Validate basic workflow structure
   */
  private validateWorkflowStructure(
    workflow: WorkflowJson,
    result: WorkflowValidationResult
  ): void {
    // Check for required fields
    if (!workflow.nodes) {
      result.errors.push({
        type: 'error',
        message: workflow.nodes === null ? 'nodes must be an array' : 'Workflow must have a nodes array'
      });
      return;
    }

    if (!Array.isArray(workflow.nodes)) {
      result.errors.push({
        type: 'error',
        message: 'nodes must be an array'
      });
      return;
    }

    if (!workflow.connections) {
      result.errors.push({
        type: 'error',
        message: workflow.connections === null ? 'connections must be an object' : 'Workflow must have a connections object'
      });
      return;
    }

    if (typeof workflow.connections !== 'object' || Array.isArray(workflow.connections)) {
      result.errors.push({
        type: 'error',
        message: 'connections must be an object'
      });
      return;
    }

    // Check for empty workflow - this should be a warning, not an error
    if (workflow.nodes.length === 0) {
      result.warnings.push({
        type: 'warning',
        message: 'Workflow is empty - no nodes defined'
      });
      return;
    }

    // Check for minimum viable workflow
    if (workflow.nodes.length === 1) {
      const singleNode = workflow.nodes[0];
      const normalizedType = NodeTypeNormalizer.normalizeToFullForm(singleNode.type);
      const isWebhook = normalizedType === 'nodes-base.webhook' ||
                       normalizedType === 'nodes-base.webhookTrigger';
      const isLangchainNode = normalizedType.startsWith('nodes-langchain.');

      // Langchain nodes can be validated standalone for AI tool purposes
      if (!isWebhook && !isLangchainNode) {
        result.errors.push({
          type: 'error',
          message: 'Single-node workflows are only valid for webhook endpoints. Add at least one more connected node to create a functional workflow.'
        });
      } else if (isWebhook && Object.keys(workflow.connections).length === 0) {
        result.warnings.push({
          type: 'warning',
          message: 'Webhook node has no connections. Consider adding nodes to process the webhook data.'
        });
      }
    }

    // Check for empty connections in multi-node workflows
    if (workflow.nodes.length > 1) {
      const hasEnabledNodes = workflow.nodes.some(n => !n.disabled);
      const hasConnections = Object.keys(workflow.connections).length > 0;
      
      if (hasEnabledNodes && !hasConnections) {
        result.errors.push({
          type: 'error',
          message: 'Multi-node workflow has no connections. Nodes must be connected to create a workflow. Use connections: { "Source Node Name": { "main": [[{ "node": "Target Node Name", "type": "main", "index": 0 }]] } }'
        });
      }
    }

    // Check for duplicate node names
    const nodeNames = new Set<string>();
    const nodeIds = new Set<string>();
    
    for (const node of workflow.nodes) {
      if (nodeNames.has(node.name)) {
        result.errors.push({
          type: 'error',
          nodeId: node.id,
          nodeName: node.name,
          message: `Duplicate node name: "${node.name}"`
        });
      }
      nodeNames.add(node.name);

      if (nodeIds.has(node.id)) {
        result.errors.push({
          type: 'error',
          nodeId: node.id,
          message: `Duplicate node ID: "${node.id}"`
        });
      }
      nodeIds.add(node.id);
    }

    // Count trigger nodes - normalize type names first
    const triggerNodes = workflow.nodes.filter(n => {
      const normalizedType = NodeTypeNormalizer.normalizeToFullForm(n.type);
      const lowerType = normalizedType.toLowerCase();
      return lowerType.includes('trigger') ||
             (lowerType.includes('webhook') && !lowerType.includes('respond')) ||
             normalizedType === 'nodes-base.start' ||
             normalizedType === 'nodes-base.manualTrigger' ||
             normalizedType === 'nodes-base.formTrigger';
    });
    result.statistics.triggerNodes = triggerNodes.length;

    // Check for at least one trigger node
    if (triggerNodes.length === 0 && workflow.nodes.filter(n => !n.disabled).length > 0) {
      result.warnings.push({
        type: 'warning',
        message: 'Workflow has no trigger nodes. It can only be executed manually.'
      });
    }
  }

  /**
   * Validate all nodes in the workflow
   */
  private async validateAllNodes(
    workflow: WorkflowJson,
    result: WorkflowValidationResult,
    profile: string
  ): Promise<void> {
    for (const node of workflow.nodes) {
      if (node.disabled || this.isStickyNote(node)) continue;

      try {
        // Validate node name length
        if (node.name && node.name.length > 255) {
          result.warnings.push({
            type: 'warning',
            nodeId: node.id,
            nodeName: node.name,
            message: `Node name is very long (${node.name.length} characters). Consider using a shorter name for better readability.`
          });
        }

        // Validate node position
        if (!Array.isArray(node.position) || node.position.length !== 2) {
          result.errors.push({
            type: 'error',
            nodeId: node.id,
            nodeName: node.name,
            message: 'Node position must be an array with exactly 2 numbers [x, y]'
          });
        } else {
          const [x, y] = node.position;
          if (typeof x !== 'number' || typeof y !== 'number' || 
              !isFinite(x) || !isFinite(y)) {
            result.errors.push({
              type: 'error',
              nodeId: node.id,
              nodeName: node.name,
              message: 'Node position values must be finite numbers'
            });
          }
        }
        // Normalize node type FIRST to ensure consistent lookup
        const normalizedType = NodeTypeNormalizer.normalizeToFullForm(node.type);

        // Update node type in place if it was normalized
        if (normalizedType !== node.type) {
          node.type = normalizedType;
        }

        // Get node definition using normalized type (needed for typeVersion validation)
        const nodeInfo = this.nodeRepository.getNode(normalizedType);

        if (!nodeInfo) {

          // Use NodeSimilarityService to find suggestions
          const suggestions = await this.similarityService.findSimilarNodes(node.type, 3);

          let message = `Unknown node type: "${node.type}".`;

          if (suggestions.length > 0) {
            message += '\n\nDid you mean one of these?';
            for (const suggestion of suggestions) {
              const confidence = Math.round(suggestion.confidence * 100);
              message += `\n• ${suggestion.nodeType} (${confidence}% match)`;
              if (suggestion.displayName) {
                message += ` - ${suggestion.displayName}`;
              }
              message += `\n  → ${suggestion.reason}`;
              if (suggestion.confidence >= 0.9) {
                message += ' (can be auto-fixed)';
              }
            }
          } else {
            message += ' No similar nodes found. Node types must include the package prefix (e.g., "n8n-nodes-base.webhook").';
          }

          const error: any = {
            type: 'error',
            nodeId: node.id,
            nodeName: node.name,
            message
          };

          // Add suggestions as metadata for programmatic access
          if (suggestions.length > 0) {
            error.suggestions = suggestions.map(s => ({
              nodeType: s.nodeType,
              confidence: s.confidence,
              reason: s.reason
            }));
          }

          result.errors.push(error);
          continue;
        }

        // Validate typeVersion for ALL versioned nodes (including langchain nodes)
        // CRITICAL: This MUST run BEFORE the langchain skip below!
        // Otherwise, langchain nodes with invalid typeVersion (e.g., 99999) would pass validation
        // but fail at runtime in n8n. This was the bug fixed in v2.17.4.
        if (nodeInfo.isVersioned) {
          // Check if typeVersion is missing
          if (!node.typeVersion) {
            result.errors.push({
              type: 'error',
              nodeId: node.id,
              nodeName: node.name,
              message: `Missing required property 'typeVersion'. Add typeVersion: ${nodeInfo.version || 1}`
            });
          }
          // Check if typeVersion is invalid (must be non-negative number, version 0 is valid)
          else if (typeof node.typeVersion !== 'number' || node.typeVersion < 0) {
            result.errors.push({
              type: 'error',
              nodeId: node.id,
              nodeName: node.name,
              message: `Invalid typeVersion: ${node.typeVersion}. Must be a non-negative number`
            });
          }
          // Check if typeVersion is outdated (less than latest)
          else if (nodeInfo.version && node.typeVersion < nodeInfo.version) {
            result.warnings.push({
              type: 'warning',
              nodeId: node.id,
              nodeName: node.name,
              message: `Outdated typeVersion: ${node.typeVersion}. Latest is ${nodeInfo.version}`
            });
          }
          // Check if typeVersion exceeds maximum supported
          else if (nodeInfo.version && node.typeVersion > nodeInfo.version) {
            result.errors.push({
              type: 'error',
              nodeId: node.id,
              nodeName: node.name,
              message: `typeVersion ${node.typeVersion} exceeds maximum supported version ${nodeInfo.version}`
            });
          }
        }

        // Skip PARAMETER validation for langchain nodes (but NOT typeVersion validation above!)
        // Langchain nodes have dedicated AI-specific validators in validateAISpecificNodes()
        // which handle their unique parameter structures (AI connections, tool ports, etc.)
        if (normalizedType.startsWith('nodes-langchain.')) {
          continue;
        }

        // Validate node configuration
        const nodeValidation = this.nodeValidator.validateWithMode(
          node.type,
          node.parameters,
          nodeInfo.properties || [],
          'operation',
          profile as any
        );

        // Add node-specific errors and warnings
        nodeValidation.errors.forEach((error: any) => {
          result.errors.push({
            type: 'error',
            nodeId: node.id,
            nodeName: node.name,
            message: typeof error === 'string' ? error : error.message || String(error)
          });
        });

        nodeValidation.warnings.forEach((warning: any) => {
          result.warnings.push({
            type: 'warning',
            nodeId: node.id,
            nodeName: node.name,
            message: typeof warning === 'string' ? warning : warning.message || String(warning)
          });
        });

      } catch (error) {
        result.errors.push({
          type: 'error',
          nodeId: node.id,
          nodeName: node.name,
          message: `Failed to validate node: ${error instanceof Error ? error.message : 'Unknown error'}`
        });
      }
    }
  }

  /**
   * Validate workflow connections
   */
  private validateConnections(
    workflow: WorkflowJson,
    result: WorkflowValidationResult,
    profile: string = 'runtime'
  ): void {
    const nodeMap = new Map(workflow.nodes.map(n => [n.name, n]));
    const nodeIdMap = new Map(workflow.nodes.map(n => [n.id, n]));

    // Check all connections
    for (const [sourceName, outputs] of Object.entries(workflow.connections)) {
      const sourceNode = nodeMap.get(sourceName);
      
      if (!sourceNode) {
        // Check if this is an ID being used instead of a name
        const nodeById = nodeIdMap.get(sourceName);
        if (nodeById) {
          result.errors.push({
            type: 'error',
            nodeId: nodeById.id,
            nodeName: nodeById.name,
            message: `Connection uses node ID '${sourceName}' instead of node name '${nodeById.name}'. In n8n, connections must use node names, not IDs.`
          });
        } else {
          result.errors.push({
            type: 'error',
            message: `Connection from non-existent node: "${sourceName}"`
          });
        }
        result.statistics.invalidConnections++;
        continue;
      }

      // Check main outputs
      if (outputs.main) {
        this.validateConnectionOutputs(
          sourceName,
          outputs.main,
          nodeMap,
          nodeIdMap,
          result,
          'main'
        );
      }

      // Check error outputs
      if (outputs.error) {
        this.validateConnectionOutputs(
          sourceName,
          outputs.error,
          nodeMap,
          nodeIdMap,
          result,
          'error'
        );
      }

      // Check AI tool outputs
      if (outputs.ai_tool) {
        this.validateConnectionOutputs(
          sourceName,
          outputs.ai_tool,
          nodeMap,
          nodeIdMap,
          result,
          'ai_tool'
        );
      }
    }

    // Check for orphaned nodes (not connected and not triggers)
    const connectedNodes = new Set<string>();
    
    // Add all source nodes
    Object.keys(workflow.connections).forEach(name => connectedNodes.add(name));
    
    // Add all target nodes
    Object.values(workflow.connections).forEach(outputs => {
      if (outputs.main) {
        outputs.main.flat().forEach(conn => {
          if (conn) connectedNodes.add(conn.node);
        });
      }
      if (outputs.error) {
        outputs.error.flat().forEach(conn => {
          if (conn) connectedNodes.add(conn.node);
        });
      }
      if (outputs.ai_tool) {
        outputs.ai_tool.flat().forEach(conn => {
          if (conn) connectedNodes.add(conn.node);
        });
      }
    });

    // Check for orphaned nodes (exclude sticky notes)
    for (const node of workflow.nodes) {
      if (node.disabled || this.isStickyNote(node)) continue;

      const normalizedType = NodeTypeNormalizer.normalizeToFullForm(node.type);
      const isTrigger = normalizedType.toLowerCase().includes('trigger') ||
                       normalizedType.toLowerCase().includes('webhook') ||
                       normalizedType === 'nodes-base.start' ||
                       normalizedType === 'nodes-base.manualTrigger' ||
                       normalizedType === 'nodes-base.formTrigger';
      
      if (!connectedNodes.has(node.name) && !isTrigger) {
        result.warnings.push({
          type: 'warning',
          nodeId: node.id,
          nodeName: node.name,
          message: 'Node is not connected to any other nodes'
        });
      }
    }

    // Check for cycles (skip in minimal profile to reduce false positives)
    if (profile !== 'minimal' && this.hasCycle(workflow)) {
      result.errors.push({
        type: 'error',
        message: 'Workflow contains a cycle (infinite loop)'
      });
    }
  }

  /**
   * Validate connection outputs
   */
  private validateConnectionOutputs(
    sourceName: string,
    outputs: Array<Array<{ node: string; type: string; index: number }>>,
    nodeMap: Map<string, WorkflowNode>,
    nodeIdMap: Map<string, WorkflowNode>,
    result: WorkflowValidationResult,
    outputType: 'main' | 'error' | 'ai_tool'
  ): void {
    // Get source node for special validation
    const sourceNode = nodeMap.get(sourceName);

    // Special validation for main outputs with error handling
    if (outputType === 'main' && sourceNode) {
      this.validateErrorOutputConfiguration(sourceName, sourceNode, outputs, nodeMap, result);
    }
    
    outputs.forEach((outputConnections, outputIndex) => {
      if (!outputConnections) return;
      
      outputConnections.forEach(connection => {
        // Check for negative index
        if (connection.index < 0) {
          result.errors.push({
            type: 'error',
            message: `Invalid connection index ${connection.index} from "${sourceName}". Connection indices must be non-negative.`
          });
          result.statistics.invalidConnections++;
          return;
        }

        // Special validation for SplitInBatches node
        if (sourceNode && sourceNode.type === 'nodes-base.splitInBatches') {
          this.validateSplitInBatchesConnection(
            sourceNode,
            outputIndex,
            connection,
            nodeMap,
            result
          );
        }

        // Check for self-referencing connections
        if (connection.node === sourceName) {
          // This is only a warning for non-loop nodes
          if (sourceNode && sourceNode.type !== 'nodes-base.splitInBatches') {
            result.warnings.push({
              type: 'warning',
              message: `Node "${sourceName}" has a self-referencing connection. This can cause infinite loops.`
            });
          }
        }

        const targetNode = nodeMap.get(connection.node);
        
        if (!targetNode) {
          // Check if this is an ID being used instead of a name
          const nodeById = nodeIdMap.get(connection.node);
          if (nodeById) {
            result.errors.push({
              type: 'error',
              nodeId: nodeById.id,
              nodeName: nodeById.name,
              message: `Connection target uses node ID '${connection.node}' instead of node name '${nodeById.name}' (from ${sourceName}). In n8n, connections must use node names, not IDs.`
            });
          } else {
            result.errors.push({
              type: 'error',
              message: `Connection to non-existent node: "${connection.node}" from "${sourceName}"`
            });
          }
          result.statistics.invalidConnections++;
        } else if (targetNode.disabled) {
          result.warnings.push({
            type: 'warning',
            message: `Connection to disabled node: "${connection.node}" from "${sourceName}"`
          });
        } else {
          result.statistics.validConnections++;
          
          // Additional validation for AI tool connections
          if (outputType === 'ai_tool') {
            this.validateAIToolConnection(sourceName, targetNode, result);
          }
        }
      });
    });
  }

  /**
   * Validate error output configuration
   */
  private validateErrorOutputConfiguration(
    sourceName: string,
    sourceNode: WorkflowNode,
    outputs: Array<Array<{ node: string; type: string; index: number }>>,
    nodeMap: Map<string, WorkflowNode>,
    result: WorkflowValidationResult
  ): void {
    // Check if node has onError: 'continueErrorOutput'
    const hasErrorOutputSetting = sourceNode.onError === 'continueErrorOutput';
    const hasErrorConnections = outputs.length > 1 && outputs[1] && outputs[1].length > 0;

    // Validate mismatch between onError setting and connections
    if (hasErrorOutputSetting && !hasErrorConnections) {
      result.errors.push({
        type: 'error',
        nodeId: sourceNode.id,
        nodeName: sourceNode.name,
        message: `Node has onError: 'continueErrorOutput' but no error output connections in main[1]. Add error handler connections to main[1] or change onError to 'continueRegularOutput' or 'stopWorkflow'.`
      });
    }

    if (!hasErrorOutputSetting && hasErrorConnections) {
      result.warnings.push({
        type: 'warning',
        nodeId: sourceNode.id,
        nodeName: sourceNode.name,
        message: `Node has error output connections in main[1] but missing onError: 'continueErrorOutput'. Add this property to properly handle errors.`
      });
    }

    // Check for common mistake: multiple nodes in main[0] when error handling is intended
    if (outputs.length >= 1 && outputs[0] && outputs[0].length > 1) {
      // Check if any of the nodes in main[0] look like error handlers
      const potentialErrorHandlers = outputs[0].filter(conn => {
        const targetNode = nodeMap.get(conn.node);
        if (!targetNode) return false;

        const nodeName = targetNode.name.toLowerCase();
        const nodeType = targetNode.type.toLowerCase();

        // Common patterns for error handler nodes
        return nodeName.includes('error') ||
               nodeName.includes('fail') ||
               nodeName.includes('catch') ||
               nodeName.includes('exception') ||
               nodeType.includes('respondtowebhook') ||
               nodeType.includes('emailsend');
      });

      if (potentialErrorHandlers.length > 0) {
        const errorHandlerNames = potentialErrorHandlers.map(conn => `"${conn.node}"`).join(', ');
        result.errors.push({
          type: 'error',
          nodeId: sourceNode.id,
          nodeName: sourceNode.name,
          message: `Incorrect error output configuration. Nodes ${errorHandlerNames} appear to be error handlers but are in main[0] (success output) along with other nodes.\n\n` +
                   `INCORRECT (current):\n` +
                   `"${sourceName}": {\n` +
                   `  "main": [\n` +
                   `    [  // main[0] has multiple nodes mixed together\n` +
                   outputs[0].map(conn => `      {"node": "${conn.node}", "type": "${conn.type}", "index": ${conn.index}}`).join(',\n') + '\n' +
                   `    ]\n` +
                   `  ]\n` +
                   `}\n\n` +
                   `CORRECT (should be):\n` +
                   `"${sourceName}": {\n` +
                   `  "main": [\n` +
                   `    [  // main[0] = success output\n` +
                   outputs[0].filter(conn => !potentialErrorHandlers.includes(conn)).map(conn => `      {"node": "${conn.node}", "type": "${conn.type}", "index": ${conn.index}}`).join(',\n') + '\n' +
                   `    ],\n` +
                   `    [  // main[1] = error output\n` +
                   potentialErrorHandlers.map(conn => `      {"node": "${conn.node}", "type": "${conn.type}", "index": ${conn.index}}`).join(',\n') + '\n' +
                   `    ]\n` +
                   `  ]\n` +
                   `}\n\n` +
                   `Also add: "onError": "continueErrorOutput" to the "${sourceName}" node.`
        });
      }
    }
  }

  /**
   * Validate AI tool connections
   */
  private validateAIToolConnection(
    sourceName: string,
    targetNode: WorkflowNode,
    result: WorkflowValidationResult
  ): void {
    // For AI tool connections, we just need to check if this is being used as a tool
    // The source should be an AI Agent connecting to this target node as a tool
    
    // Get target node info to check if it can be used as a tool
    const normalizedType = NodeTypeNormalizer.normalizeToFullForm(targetNode.type);
    let targetNodeInfo = this.nodeRepository.getNode(normalizedType);

    // Try original type if normalization didn't help (fallback for edge cases)
    if (!targetNodeInfo && normalizedType !== targetNode.type) {
      targetNodeInfo = this.nodeRepository.getNode(targetNode.type);
    }
    
    if (targetNodeInfo && !targetNodeInfo.isAITool && targetNodeInfo.package !== 'n8n-nodes-base') {
      // It's a community node being used as a tool
      result.warnings.push({
        type: 'warning',
        nodeId: targetNode.id,
        nodeName: targetNode.name,
        message: `Community node "${targetNode.name}" is being used as an AI tool. Ensure N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true is set.`
      });
    }
  }

  /**
   * Check if workflow has cycles
   * Allow legitimate loops for SplitInBatches and similar loop nodes
   */
  private hasCycle(workflow: WorkflowJson): boolean {
    const visited = new Set<string>();
    const recursionStack = new Set<string>();
    const nodeTypeMap = new Map<string, string>();
    
    // Build node type map (exclude sticky notes)
    workflow.nodes.forEach(node => {
      if (!this.isStickyNote(node)) {
        nodeTypeMap.set(node.name, node.type);
      }
    });
    
    // Known legitimate loop node types
    const loopNodeTypes = [
      'n8n-nodes-base.splitInBatches',
      'nodes-base.splitInBatches',
      'n8n-nodes-base.itemLists',
      'nodes-base.itemLists',
      'n8n-nodes-base.loop',
      'nodes-base.loop'
    ];

    const hasCycleDFS = (nodeName: string, pathFromLoopNode: boolean = false): boolean => {
      visited.add(nodeName);
      recursionStack.add(nodeName);

      const connections = workflow.connections[nodeName];
      if (connections) {
        const allTargets: string[] = [];
        
        if (connections.main) {
          connections.main.flat().forEach(conn => {
            if (conn) allTargets.push(conn.node);
          });
        }
        
        if (connections.error) {
          connections.error.flat().forEach(conn => {
            if (conn) allTargets.push(conn.node);
          });
        }
        
        if (connections.ai_tool) {
          connections.ai_tool.flat().forEach(conn => {
            if (conn) allTargets.push(conn.node);
          });
        }

        const currentNodeType = nodeTypeMap.get(nodeName);
        const isLoopNode = loopNodeTypes.includes(currentNodeType || '');
        
        for (const target of allTargets) {
          if (!visited.has(target)) {
            if (hasCycleDFS(target, pathFromLoopNode || isLoopNode)) return true;
          } else if (recursionStack.has(target)) {
            // Allow cycles that involve legitimate loop nodes
            const targetNodeType = nodeTypeMap.get(target);
            const isTargetLoopNode = loopNodeTypes.includes(targetNodeType || '');
            
            // If this cycle involves a loop node, it's legitimate
            if (isTargetLoopNode || pathFromLoopNode || isLoopNode) {
              continue; // Allow this cycle
            }
            
            return true; // Reject other cycles
          }
        }
      }

      recursionStack.delete(nodeName);
      return false;
    };

    // Check from all executable nodes (exclude sticky notes)
    for (const node of workflow.nodes) {
      if (!this.isStickyNote(node) && !visited.has(node.name)) {
        if (hasCycleDFS(node.name)) return true;
      }
    }

    return false;
  }

  /**
   * Validate expressions in the workflow
   */
  private validateExpressions(
    workflow: WorkflowJson,
    result: WorkflowValidationResult,
    profile: string = 'runtime'
  ): void {
    const nodeNames = workflow.nodes.map(n => n.name);

    for (const node of workflow.nodes) {
      if (node.disabled || this.isStickyNote(node)) continue;

      // Skip expression validation for langchain nodes
      // They have AI-specific validators and different expression rules
      const normalizedType = NodeTypeNormalizer.normalizeToFullForm(node.type);
      if (normalizedType.startsWith('nodes-langchain.')) {
        continue;
      }

      // Create expression context
      const context = {
        availableNodes: nodeNames.filter(n => n !== node.name),
        currentNodeName: node.name,
        hasInputData: this.nodeHasInput(node.name, workflow),
        isInLoop: false // Could be enhanced to detect loop nodes
      };

      // Validate expressions in parameters
      const exprValidation = ExpressionValidator.validateNodeExpressions(
        node.parameters,
        context
      );

      // Count actual expressions found, not just unique variables
      const expressionCount = this.countExpressionsInObject(node.parameters);
      result.statistics.expressionsValidated += expressionCount;

      // Add expression errors and warnings
      exprValidation.errors.forEach(error => {
        result.errors.push({
          type: 'error',
          nodeId: node.id,
          nodeName: node.name,
          message: `Expression error: ${error}`
        });
      });

      exprValidation.warnings.forEach(warning => {
        result.warnings.push({
          type: 'warning',
          nodeId: node.id,
          nodeName: node.name,
          message: `Expression warning: ${warning}`
        });
      });

      // Validate expression format (check for missing = prefix and resource locator format)
      const formatContext = {
        nodeType: node.type,
        nodeName: node.name,
        nodeId: node.id
      };

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

      // Add format errors and warnings
      formatIssues.forEach(issue => {
        const formattedMessage = ExpressionFormatValidator.formatErrorMessage(issue, formatContext);

        if (issue.severity === 'error') {
          result.errors.push({
            type: 'error',
            nodeId: node.id,
            nodeName: node.name,
            message: formattedMessage
          });
        } else {
          result.warnings.push({
            type: 'warning',
            nodeId: node.id,
            nodeName: node.name,
            message: formattedMessage
          });
        }
      });
    }
  }

  /**
   * Count expressions in an object recursively
   */
  private countExpressionsInObject(obj: any): number {
    let count = 0;
    
    if (typeof obj === 'string') {
      // Count expressions in string
      const matches = obj.match(/\{\{[\s\S]+?\}\}/g);
      if (matches) {
        count += matches.length;
      }
    } else if (Array.isArray(obj)) {
      // Recursively count in arrays
      for (const item of obj) {
        count += this.countExpressionsInObject(item);
      }
    } else if (obj && typeof obj === 'object') {
      // Recursively count in objects
      for (const value of Object.values(obj)) {
        count += this.countExpressionsInObject(value);
      }
    }
    
    return count;
  }

  /**
   * Check if a node has input connections
   */
  private nodeHasInput(nodeName: string, workflow: WorkflowJson): boolean {
    for (const [sourceName, outputs] of Object.entries(workflow.connections)) {
      if (outputs.main) {
        for (const outputConnections of outputs.main) {
          if (outputConnections?.some(conn => conn.node === nodeName)) {
            return true;
          }
        }
      }
    }
    return false;
  }

  /**
   * Check workflow patterns and best practices
   */
  private checkWorkflowPatterns(
    workflow: WorkflowJson,
    result: WorkflowValidationResult,
    profile: string = 'runtime'
  ): void {
    // Check for error handling (n8n uses main[1] for error outputs, not outputs.error)
    const hasErrorHandling = Object.values(workflow.connections).some(
      outputs => outputs.main && outputs.main.length > 1 && outputs.main[1] && outputs.main[1].length > 0
    );

    // Only suggest error handling in stricter profiles
    if (!hasErrorHandling && workflow.nodes.length > 3 && profile !== 'minimal') {
      result.warnings.push({
        type: 'warning',
        message: 'Consider adding error handling to your workflow'
      });
    }

    // Check node-level error handling properties for ALL executable nodes
    for (const node of workflow.nodes) {
      if (!this.isStickyNote(node)) {
        this.checkNodeErrorHandling(node, workflow, result);
      }
    }

    // Check for very long linear workflows
    const linearChainLength = this.getLongestLinearChain(workflow);
    if (linearChainLength > 10) {
      result.warnings.push({
        type: 'warning',
        message: `Long linear chain detected (${linearChainLength} nodes). Consider breaking into sub-workflows.`
      });
    }

    // Generate error handling suggestions based on all nodes
    this.generateErrorHandlingSuggestions(workflow, result);

    // Check for missing credentials
    for (const node of workflow.nodes) {
      if (node.credentials && Object.keys(node.credentials).length > 0) {
        for (const [credType, credConfig] of Object.entries(node.credentials)) {
          if (!credConfig || (typeof credConfig === 'object' && !('id' in credConfig))) {
            result.warnings.push({
              type: 'warning',
              nodeId: node.id,
              nodeName: node.name,
              message: `Missing credentials configuration for ${credType}`
            });
          }
        }
      }
    }

    // Check for AI Agent workflows
    const aiAgentNodes = workflow.nodes.filter(n => 
      n.type.toLowerCase().includes('agent') || 
      n.type.includes('langchain.agent')
    );
    
    if (aiAgentNodes.length > 0) {
      // Check if AI agents have tools connected
      for (const agentNode of aiAgentNodes) {
        const connections = workflow.connections[agentNode.name];
        if (!connections?.ai_tool || connections.ai_tool.flat().filter(c => c).length === 0) {
          result.warnings.push({
            type: 'warning',
            nodeId: agentNode.id,
            nodeName: agentNode.name,
            message: 'AI Agent has no tools connected. Consider adding tools to enhance agent capabilities.'
          });
        }
      }
      
      // Check for community nodes used as tools
      const hasAIToolConnections = Object.values(workflow.connections).some(
        outputs => outputs.ai_tool && outputs.ai_tool.length > 0
      );
      
      if (hasAIToolConnections) {
        result.suggestions.push(
          'For community nodes used as AI tools, ensure N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true is set'
        );
      }
    }
  }

  /**
   * Get the longest linear chain in the workflow
   */
  private getLongestLinearChain(workflow: WorkflowJson): number {
    const memo = new Map<string, number>();
    const visiting = new Set<string>();

    const getChainLength = (nodeName: string): number => {
      // If we're already visiting this node, we have a cycle
      if (visiting.has(nodeName)) return 0;
      
      if (memo.has(nodeName)) return memo.get(nodeName)!;

      visiting.add(nodeName);

      let maxLength = 0;
      const connections = workflow.connections[nodeName];
      
      if (connections?.main) {
        for (const outputConnections of connections.main) {
          if (outputConnections) {
            for (const conn of outputConnections) {
              const length = getChainLength(conn.node);
              maxLength = Math.max(maxLength, length);
            }
          }
        }
      }

      visiting.delete(nodeName);
      const result = maxLength + 1;
      memo.set(nodeName, result);
      return result;
    };

    let maxChain = 0;
    for (const node of workflow.nodes) {
      if (!this.nodeHasInput(node.name, workflow)) {
        maxChain = Math.max(maxChain, getChainLength(node.name));
      }
    }

    return maxChain;
  }


  /**
   * Generate suggestions based on validation results
   */
  private generateSuggestions(
    workflow: WorkflowJson,
    result: WorkflowValidationResult
  ): void {
    // Suggest adding trigger if missing
    if (result.statistics.triggerNodes === 0) {
      result.suggestions.push(
        'Add a trigger node (e.g., Webhook, Schedule Trigger) to automate workflow execution'
      );
    }

    // Suggest proper connection structure for workflows with connection errors
    const hasConnectionErrors = result.errors.some(e =>
      typeof e.message === 'string' && (
        e.message.includes('connection') ||
        e.message.includes('Connection') ||
        e.message.includes('Multi-node workflow has no connections')
      )
    );
    
    if (hasConnectionErrors) {
      result.suggestions.push(
        'Example connection structure: connections: { "Manual Trigger": { "main": [[{ "node": "Set", "type": "main", "index": 0 }]] } }'
      );
      result.suggestions.push(
        'Remember: Use node NAMES (not IDs) in connections. The name is what you see in the UI, not the node type.'
      );
    }

    // Suggest error handling
    if (!Object.values(workflow.connections).some(o => o.error)) {
      result.suggestions.push(
        'Add error handling using the error output of nodes or an Error Trigger node'
      );
    }

    // Suggest optimization for large workflows
    if (workflow.nodes.length > 20) {
      result.suggestions.push(
        'Consider breaking this workflow into smaller sub-workflows for better maintainability'
      );
    }

    // Suggest using Code node for complex logic
    const complexExpressionNodes = workflow.nodes.filter(node => {
      const jsonString = JSON.stringify(node.parameters);
      const expressionCount = (jsonString.match(/\{\{/g) || []).length;
      return expressionCount > 5;
    });

    if (complexExpressionNodes.length > 0) {
      result.suggestions.push(
        'Consider using a Code node for complex data transformations instead of multiple expressions'
      );
    }

    // Suggest minimum workflow structure
    if (workflow.nodes.length === 1 && Object.keys(workflow.connections).length === 0) {
      result.suggestions.push(
        'A minimal workflow needs: 1) A trigger node (e.g., Manual Trigger), 2) An action node (e.g., Set, HTTP Request), 3) A connection between them'
      );
    }
  }

  /**
   * Check node-level error handling configuration for a single node
   */
  private checkNodeErrorHandling(
    node: WorkflowNode,
    workflow: WorkflowJson,
    result: WorkflowValidationResult
  ): void {
    // Only skip if disabled is explicitly true (not just truthy)
    if (node.disabled === true) return;

    // Define node types that typically interact with external services (lowercase for comparison)
    const errorProneNodeTypes = [
      'httprequest',
      'webhook',
      'emailsend',
      'slack',
      'discord',
      'telegram',
      'postgres',
      'mysql',
      'mongodb',
      'redis',
      'github',
      'gitlab',
      'jira',
      'salesforce',
      'hubspot',
      'airtable',
      'googlesheets',
      'googledrive',
      'dropbox',
      's3',
      'ftp',
      'ssh',
      'mqtt',
      'kafka',
      'rabbitmq',
      'graphql',
      'openai',
      'anthropic'
    ];

    const normalizedType = node.type.toLowerCase();
    const isErrorProne = errorProneNodeTypes.some(type => normalizedType.includes(type));

    // CRITICAL: Check for node-level properties in wrong location (inside parameters)
    const nodeLevelProps = [
      // Error handling properties
      'onError', 'continueOnFail', 'retryOnFail', 'maxTries', 'waitBetweenTries', 'alwaysOutputData',
      // Other node-level properties
      'executeOnce', 'disabled', 'notes', 'notesInFlow', 'credentials'
    ];
    const misplacedProps: string[] = [];
    
    if (node.parameters) {
      for (const prop of nodeLevelProps) {
        if (node.parameters[prop] !== undefined) {
          misplacedProps.push(prop);
        }
      }
    }
    
    if (misplacedProps.length > 0) {
        result.errors.push({
          type: 'error',
          nodeId: node.id,
          nodeName: node.name,
          message: `Node-level properties ${misplacedProps.join(', ')} are in the wrong location. They must be at the node level, not inside parameters.`,
          details: {
            fix: `Move these properties from node.parameters to the node level. Example:\n` +
                 `{\n` +
                 `  "name": "${node.name}",\n` +
                 `  "type": "${node.type}",\n` +
                 `  "parameters": { /* operation-specific params */ },\n` +
                 `  "onError": "continueErrorOutput",  // ✅ Correct location\n` +
                 `  "retryOnFail": true,               // ✅ Correct location\n` +
                 `  "executeOnce": true,               // ✅ Correct location\n` +
                 `  "disabled": false,                 // ✅ Correct location\n` +
                 `  "credentials": { /* ... */ }       // ✅ Correct location\n` +
                 `}`
          }
        });
    }

    // Validate error handling properties
    
    // Check for onError property (the modern approach)
    if (node.onError !== undefined) {
        const validOnErrorValues = ['continueRegularOutput', 'continueErrorOutput', 'stopWorkflow'];
        if (!validOnErrorValues.includes(node.onError)) {
          result.errors.push({
            type: 'error',
            nodeId: node.id,
            nodeName: node.name,
            message: `Invalid onError value: "${node.onError}". Must be one of: ${validOnErrorValues.join(', ')}`
          });
        }
    }

    // Check for deprecated continueOnFail
    if (node.continueOnFail !== undefined) {
        if (typeof node.continueOnFail !== 'boolean') {
          result.errors.push({
            type: 'error',
            nodeId: node.id,
            nodeName: node.name,
            message: 'continueOnFail must be a boolean value'
          });
        } else if (node.continueOnFail === true) {
          // Warn about using deprecated property
          result.warnings.push({
            type: 'warning',
            nodeId: node.id,
            nodeName: node.name,
            message: 'Using deprecated "continueOnFail: true". Use "onError: \'continueRegularOutput\'" instead for better control and UI compatibility.'
          });
        }
    }

    // Check for conflicting error handling properties
    if (node.continueOnFail !== undefined && node.onError !== undefined) {
        result.errors.push({
          type: 'error',
          nodeId: node.id,
          nodeName: node.name,
          message: 'Cannot use both "continueOnFail" and "onError" properties. Use only "onError" for modern workflows.'
        });
    }

    if (node.retryOnFail !== undefined) {
        if (typeof node.retryOnFail !== 'boolean') {
          result.errors.push({
            type: 'error',
            nodeId: node.id,
            nodeName: node.name,
            message: 'retryOnFail must be a boolean value'
          });
        }

        // If retry is enabled, check retry configuration
        if (node.retryOnFail === true) {
          if (node.maxTries !== undefined) {
            if (typeof node.maxTries !== 'number' || node.maxTries < 1) {
              result.errors.push({
                type: 'error',
                nodeId: node.id,
                nodeName: node.name,
                message: 'maxTries must be a positive number when retryOnFail is enabled'
              });
            } else if (node.maxTries > 10) {
              result.warnings.push({
                type: 'warning',
                nodeId: node.id,
                nodeName: node.name,
                message: `maxTries is set to ${node.maxTries}. Consider if this many retries is necessary.`
              });
            }
          } else {
            // maxTries defaults to 3 if not specified
            result.warnings.push({
              type: 'warning',
              nodeId: node.id,
              nodeName: node.name,
              message: 'retryOnFail is enabled but maxTries is not specified. Default is 3 attempts.'
            });
          }

          if (node.waitBetweenTries !== undefined) {
            if (typeof node.waitBetweenTries !== 'number' || node.waitBetweenTries < 0) {
              result.errors.push({
                type: 'error',
                nodeId: node.id,
                nodeName: node.name,
                message: 'waitBetweenTries must be a non-negative number (milliseconds)'
              });
            } else if (node.waitBetweenTries > 300000) { // 5 minutes
              result.warnings.push({
                type: 'warning',
                nodeId: node.id,
                nodeName: node.name,
                message: `waitBetweenTries is set to ${node.waitBetweenTries}ms (${(node.waitBetweenTries/1000).toFixed(1)}s). This seems excessive.`
              });
            }
          }
        }
    }

    if (node.alwaysOutputData !== undefined && typeof node.alwaysOutputData !== 'boolean') {
        result.errors.push({
          type: 'error',
          nodeId: node.id,
          nodeName: node.name,
          message: 'alwaysOutputData must be a boolean value'
        });
    }

    // Warnings for error-prone nodes without error handling
    const hasErrorHandling = node.onError || node.continueOnFail || node.retryOnFail;
    
    if (isErrorProne && !hasErrorHandling) {
        const nodeTypeSimple = normalizedType.split('.').pop() || normalizedType;
        
        // Special handling for specific node types
        if (normalizedType.includes('httprequest')) {
          result.warnings.push({
            type: 'warning',
            nodeId: node.id,
            nodeName: node.name,
            message: 'HTTP Request node without error handling. Consider adding "onError: \'continueRegularOutput\'" for non-critical requests or "retryOnFail: true" for transient failures.'
          });
        } else if (normalizedType.includes('webhook')) {
          result.warnings.push({
            type: 'warning',
            nodeId: node.id,
            nodeName: node.name,
            message: 'Webhook node without error handling. Consider adding "onError: \'continueRegularOutput\'" to prevent workflow failures from blocking webhook responses.'
          });
        } else if (errorProneNodeTypes.some(db => normalizedType.includes(db) && ['postgres', 'mysql', 'mongodb'].includes(db))) {
          result.warnings.push({
            type: 'warning',
            nodeId: node.id,
            nodeName: node.name,
            message: `Database operation without error handling. Consider adding "retryOnFail: true" for connection issues or "onError: \'continueRegularOutput\'" for non-critical queries.`
          });
        } else {
          result.warnings.push({
            type: 'warning',
            nodeId: node.id,
            nodeName: node.name,
            message: `${nodeTypeSimple} node without error handling. Consider using "onError" property for better error management.`
          });
        }
    }

    // Check for problematic combinations
    if (node.continueOnFail && node.retryOnFail) {
        result.warnings.push({
          type: 'warning',
          nodeId: node.id,
          nodeName: node.name,
          message: 'Both continueOnFail and retryOnFail are enabled. The node will retry first, then continue on failure.'
        });
    }

    // Validate additional node-level properties
    
    // Check executeOnce
    if (node.executeOnce !== undefined && typeof node.executeOnce !== 'boolean') {
        result.errors.push({
          type: 'error',
          nodeId: node.id,
          nodeName: node.name,
          message: 'executeOnce must be a boolean value'
        });
    }

    // Check disabled
    if (node.disabled !== undefined && typeof node.disabled !== 'boolean') {
        result.errors.push({
          type: 'error',
          nodeId: node.id,
          nodeName: node.name,
          message: 'disabled must be a boolean value'
        });
    }

    // Check notesInFlow
    if (node.notesInFlow !== undefined && typeof node.notesInFlow !== 'boolean') {
        result.errors.push({
          type: 'error',
          nodeId: node.id,
          nodeName: node.name,
          message: 'notesInFlow must be a boolean value'
        });
    }

    // Check notes
    if (node.notes !== undefined && typeof node.notes !== 'string') {
        result.errors.push({
          type: 'error',
          nodeId: node.id,
          nodeName: node.name,
          message: 'notes must be a string value'
        });
    }

    // Provide guidance for executeOnce
    if (node.executeOnce === true) {
        result.warnings.push({
          type: 'warning',
          nodeId: node.id,
          nodeName: node.name,
          message: 'executeOnce is enabled. This node will execute only once regardless of input items.'
        });
    }

    // Suggest alwaysOutputData for debugging
    if ((node.continueOnFail || node.retryOnFail) && !node.alwaysOutputData) {
        if (normalizedType.includes('httprequest') || normalizedType.includes('webhook')) {
          result.suggestions.push(
            `Consider enabling alwaysOutputData on "${node.name}" to capture error responses for debugging`
          );
        }
      }

  }

  /**
   * Generate error handling suggestions based on all nodes
   */
  private generateErrorHandlingSuggestions(
    workflow: WorkflowJson,
    result: WorkflowValidationResult
  ): void {
    // Add general suggestions based on findings
    const nodesWithoutErrorHandling = workflow.nodes.filter(n => 
      !n.disabled && !n.onError && !n.continueOnFail && !n.retryOnFail
    ).length;

    if (nodesWithoutErrorHandling > 5 && workflow.nodes.length > 5) {
      result.suggestions.push(
        'Most nodes lack error handling. Use "onError" property for modern error handling: "continueRegularOutput" (continue on error), "continueErrorOutput" (use error output), or "stopWorkflow" (stop execution).'
      );
    }

    // Check for nodes using deprecated continueOnFail
    const nodesWithDeprecatedErrorHandling = workflow.nodes.filter(n => 
      !n.disabled && n.continueOnFail === true
    ).length;

    if (nodesWithDeprecatedErrorHandling > 0) {
      result.suggestions.push(
        'Replace "continueOnFail: true" with "onError: \'continueRegularOutput\'" for better UI compatibility and control.'
      );
    }
  }

  /**
   * Validate SplitInBatches node connections for common mistakes
   */
  private validateSplitInBatchesConnection(
    sourceNode: WorkflowNode,
    outputIndex: number,
    connection: { node: string; type: string; index: number },
    nodeMap: Map<string, WorkflowNode>,
    result: WorkflowValidationResult
  ): void {
    const targetNode = nodeMap.get(connection.node);
    if (!targetNode) return;

    // Check if connections appear to be reversed
    // Output 0 = "done", Output 1 = "loop"
    
    if (outputIndex === 0) {
      // This is the "done" output (index 0)
      // Check if target looks like it should be in the loop
      const targetType = targetNode.type.toLowerCase();
      const targetName = targetNode.name.toLowerCase();
      
      // Common patterns that suggest this node should be inside the loop
      if (targetType.includes('function') || 
          targetType.includes('code') ||
          targetType.includes('item') ||
          targetName.includes('process') ||
          targetName.includes('transform') ||
          targetName.includes('handle')) {
        
        // Check if this node connects back to the SplitInBatches
        const hasLoopBack = this.checkForLoopBack(targetNode.name, sourceNode.name, nodeMap);
        
        if (hasLoopBack) {
          result.errors.push({
            type: 'error',
            nodeId: sourceNode.id,
            nodeName: sourceNode.name,
            message: `SplitInBatches outputs appear reversed! Node "${targetNode.name}" is connected to output 0 ("done") but connects back to the loop. It should be connected to output 1 ("loop") instead. Remember: Output 0 = "done" (post-loop), Output 1 = "loop" (inside loop).`
          });
        } else {
          result.warnings.push({
            type: 'warning',
            nodeId: sourceNode.id,
            nodeName: sourceNode.name,
            message: `Node "${targetNode.name}" is connected to the "done" output (index 0) but appears to be a processing node. Consider connecting it to the "loop" output (index 1) if it should process items inside the loop.`
          });
        }
      }
    } else if (outputIndex === 1) {
      // This is the "loop" output (index 1)
      // Check if target looks like it should be after the loop
      const targetType = targetNode.type.toLowerCase();
      const targetName = targetNode.name.toLowerCase();
      
      // Common patterns that suggest this node should be after the loop
      if (targetType.includes('aggregate') ||
          targetType.includes('merge') ||
          targetType.includes('email') ||
          targetType.includes('slack') ||
          targetName.includes('final') ||
          targetName.includes('complete') ||
          targetName.includes('summary') ||
          targetName.includes('report')) {
        
        result.warnings.push({
          type: 'warning',
          nodeId: sourceNode.id,
          nodeName: sourceNode.name,
          message: `Node "${targetNode.name}" is connected to the "loop" output (index 1) but appears to be a post-processing node. Consider connecting it to the "done" output (index 0) if it should run after all iterations complete.`
        });
      }
      
      // Check if loop output doesn't eventually connect back
      const hasLoopBack = this.checkForLoopBack(targetNode.name, sourceNode.name, nodeMap);
      if (!hasLoopBack) {
        result.warnings.push({
          type: 'warning',
          nodeId: sourceNode.id,
          nodeName: sourceNode.name,
          message: `The "loop" output connects to "${targetNode.name}" but doesn't connect back to the SplitInBatches node. The last node in the loop should connect back to complete the iteration.`
        });
      }
    }
  }

  /**
   * Check if a node eventually connects back to a target node
   */
  private checkForLoopBack(
    startNode: string,
    targetNode: string,
    nodeMap: Map<string, WorkflowNode>,
    visited: Set<string> = new Set(),
    maxDepth: number = 50
  ): boolean {
    if (maxDepth <= 0) return false; // Prevent stack overflow
    if (visited.has(startNode)) return false;
    visited.add(startNode);

    const node = nodeMap.get(startNode);
    if (!node) return false;

    // Access connections from the workflow structure, not the node
    // We need to access this.currentWorkflow.connections[startNode]
    const connections = (this as any).currentWorkflow?.connections[startNode];
    if (!connections) return false;

    for (const [outputType, outputs] of Object.entries(connections)) {
      if (!Array.isArray(outputs)) continue;
      
      for (const outputConnections of outputs) {
        if (!Array.isArray(outputConnections)) continue;
        
        for (const conn of outputConnections) {
          if (conn.node === targetNode) {
            return true;
          }
          
          // Recursively check connected nodes
          if (this.checkForLoopBack(conn.node, targetNode, nodeMap, visited, maxDepth - 1)) {
            return true;
          }
        }
      }
    }

    return false;
  }

  /**
   * Add AI-specific error recovery suggestions
   */
  private addErrorRecoverySuggestions(result: WorkflowValidationResult): void {
    // Categorize errors and provide specific recovery actions
    const errorTypes = {
      nodeType: result.errors.filter(e => e.message.includes('node type') || e.message.includes('Node type')),
      connection: result.errors.filter(e => e.message.includes('connection') || e.message.includes('Connection')),
      structure: result.errors.filter(e => e.message.includes('structure') || e.message.includes('nodes must be')),
      configuration: result.errors.filter(e => e.message.includes('property') || e.message.includes('field')),
      typeVersion: result.errors.filter(e => e.message.includes('typeVersion'))
    };

    // Add recovery suggestions based on error types
    if (errorTypes.nodeType.length > 0) {
      result.suggestions.unshift(
        '🔧 RECOVERY: Invalid node types detected. Use these patterns:',
        '   • For core nodes: "n8n-nodes-base.nodeName" (e.g., "n8n-nodes-base.webhook")',
        '   • For AI nodes: "@n8n/n8n-nodes-langchain.nodeName"',
        '   • Never use just the node name without package prefix'
      );
    }

    if (errorTypes.connection.length > 0) {
      result.suggestions.unshift(
        '🔧 RECOVERY: Connection errors detected. Fix with:',
        '   • Use node NAMES in connections, not IDs or types',
        '   • Structure: { "Source Node Name": { "main": [[{ "node": "Target Node Name", "type": "main", "index": 0 }]] } }',
        '   • Ensure all referenced nodes exist in the workflow'
      );
    }

    if (errorTypes.structure.length > 0) {
      result.suggestions.unshift(
        '🔧 RECOVERY: Workflow structure errors. Fix with:',
        '   • Ensure "nodes" is an array: "nodes": [...]',
        '   • Ensure "connections" is an object: "connections": {...}',
        '   • Add at least one node to create a valid workflow'
      );
    }

    if (errorTypes.configuration.length > 0) {
      result.suggestions.unshift(
        '🔧 RECOVERY: Node configuration errors. Fix with:',
        '   • Check required fields using validate_node_minimal first',
        '   • Use get_node_essentials to see what fields are needed',
        '   • Ensure operation-specific fields match the node\'s requirements'
      );
    }

    if (errorTypes.typeVersion.length > 0) {
      result.suggestions.unshift(
        '🔧 RECOVERY: TypeVersion errors. Fix with:',
        '   • Add "typeVersion": 1 (or latest version) to each node',
        '   • Use get_node_info to check the correct version for each node type'
      );
    }

    // Add general recovery workflow
    if (result.errors.length > 3) {
      result.suggestions.push(
        '📋 SUGGESTED WORKFLOW: Too many errors detected. Try this approach:',
        '   1. Fix structural issues first (nodes array, connections object)',
        '   2. Validate node types and fix invalid ones',
        '   3. Add required typeVersion to all nodes',
        '   4. Test connections step by step',
        '   5. Use validate_node_minimal on individual nodes to verify configuration'
      );
    }
  }
}
```
Page 35/45FirstPrevNextLast