#
tokens: 49511/50000 31/614 files (page 5/59)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 5 of 59. Use http://codebase.md/czlonkowski/n8n-mcp?lines=true&page={x} to view the full context.

# Directory Structure

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

# Files

--------------------------------------------------------------------------------
/src/mcp/tool-docs/configuration/get-node-essentials.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ToolDocumentation } from '../types';
 2 | 
 3 | export const getNodeEssentialsDoc: ToolDocumentation = {
 4 |   name: 'get_node_essentials',
 5 |   category: 'configuration',
 6 |   essentials: {
 7 |     description: 'Returns only the most commonly-used properties for a node (10-20 fields). Response is 95% smaller than get_node_info (5KB vs 100KB+). Essential properties include required fields, common options, and authentication settings. Use validate_node_operation for working configurations.',
 8 |     keyParameters: ['nodeType'],
 9 |     example: 'get_node_essentials({nodeType: "nodes-base.slack"})',
10 |     performance: '<10ms, ~5KB response',
11 |     tips: [
12 |       'Always use this before get_node_info',
13 |       'Use validate_node_operation for examples',
14 |       'Perfect for understanding node structure'
15 |     ]
16 |   },
17 |   full: {
18 |     description: 'Returns a curated subset of node properties focusing on the most commonly-used fields. Essential properties are hand-picked for each node type and include: required fields, primary operations, authentication options, and the most frequent configuration patterns. NOTE: Examples have been removed to avoid confusion - use validate_node_operation to get working configurations with proper validation.',
19 |     parameters: {
20 |       nodeType: { type: 'string', description: 'Full node type with prefix, e.g., "nodes-base.slack", "nodes-base.httpRequest"', required: true }
21 |     },
22 |     returns: `Object containing:
23 | {
24 |   "nodeType": "nodes-base.slack",
25 |   "displayName": "Slack",
26 |   "description": "Consume Slack API",
27 |   "category": "output",
28 |   "version": "2.3",
29 |   "requiredProperties": [],  // Most nodes have no strictly required fields
30 |   "commonProperties": [
31 |     {
32 |       "name": "resource",
33 |       "displayName": "Resource",
34 |       "type": "options",
35 |       "options": ["channel", "message", "user"],
36 |       "default": "message"
37 |     },
38 |     {
39 |       "name": "operation",
40 |       "displayName": "Operation", 
41 |       "type": "options",
42 |       "options": ["post", "update", "delete"],
43 |       "default": "post"
44 |     },
45 |     // ... 10-20 most common properties
46 |   ],
47 |   "operations": [
48 |     {"name": "Post", "description": "Post a message"},
49 |     {"name": "Update", "description": "Update a message"}
50 |   ],
51 |   "metadata": {
52 |     "totalProperties": 121,
53 |     "isAITool": false,
54 |     "hasCredentials": true
55 |   }
56 | }`,
57 |     examples: [
58 |       'get_node_essentials({nodeType: "nodes-base.httpRequest"}) - HTTP configuration basics',
59 |       'get_node_essentials({nodeType: "nodes-base.slack"}) - Slack messaging essentials',
60 |       'get_node_essentials({nodeType: "nodes-base.googleSheets"}) - Sheets operations',
61 |       '// Workflow: search → essentials → validate',
62 |       'const nodes = search_nodes({query: "database"});',
63 |       'const mysql = get_node_essentials({nodeType: "nodes-base.mySql"});',
64 |       'validate_node_operation("nodes-base.mySql", {operation: "select"}, "minimal");'
65 |     ],
66 |     useCases: [
67 |       'Quickly understand node structure without information overload',
68 |       'Identify which properties are most important',
69 |       'Learn node basics before diving into advanced features',
70 |       'Build workflows faster with curated property sets'
71 |     ],
72 |     performance: '<10ms response time, ~5KB payload (vs 100KB+ for full schema)',
73 |     bestPractices: [
74 |       'Always start with essentials, only use get_node_info if needed',
75 |       'Use validate_node_operation to get working configurations',
76 |       'Check authentication requirements first',
77 |       'Use search_node_properties if specific property not in essentials'
78 |     ],
79 |     pitfalls: [
80 |       'Advanced properties not included - use get_node_info for complete schema',
81 |       'Node-specific validators may require additional fields',
82 |       'Some nodes have 50+ properties, essentials shows only top 10-20'
83 |     ],
84 |     relatedTools: ['get_node_info for complete schema', 'search_node_properties for finding specific fields', 'validate_node_minimal to check configuration']
85 |   }
86 | };
```

--------------------------------------------------------------------------------
/scripts/test-http.sh:
--------------------------------------------------------------------------------

```bash
  1 | #!/bin/bash
  2 | # Test script for n8n-MCP HTTP Server
  3 | 
  4 | set -e
  5 | 
  6 | # Configuration
  7 | URL="${1:-http://localhost:3000}"
  8 | TOKEN="${AUTH_TOKEN:-test-token}"
  9 | VERBOSE="${VERBOSE:-0}"
 10 | 
 11 | # Colors for output
 12 | RED='\033[0;31m'
 13 | GREEN='\033[0;32m'
 14 | YELLOW='\033[1;33m'
 15 | NC='\033[0m' # No Color
 16 | 
 17 | echo "🧪 Testing n8n-MCP HTTP Server"
 18 | echo "================================"
 19 | echo "Server URL: $URL"
 20 | echo ""
 21 | 
 22 | # Check if jq is installed
 23 | if ! command -v jq &> /dev/null; then
 24 |     echo -e "${YELLOW}Warning: jq not installed. Output will not be formatted.${NC}"
 25 |     echo "Install with: brew install jq (macOS) or apt-get install jq (Linux)"
 26 |     echo ""
 27 |     JQ="cat"
 28 | else
 29 |     JQ="jq ."
 30 | fi
 31 | 
 32 | # Function to make requests
 33 | make_request() {
 34 |     local method="$1"
 35 |     local endpoint="$2"
 36 |     local data="$3"
 37 |     local headers="$4"
 38 |     local expected_status="$5"
 39 |     
 40 |     if [ "$VERBOSE" = "1" ]; then
 41 |         echo -e "${YELLOW}Request:${NC} $method $URL$endpoint"
 42 |         [ -n "$data" ] && echo -e "${YELLOW}Data:${NC} $data"
 43 |     fi
 44 |     
 45 |     # Build curl command
 46 |     local cmd="curl -s -w '\n%{http_code}' -X $method '$URL$endpoint'"
 47 |     [ -n "$headers" ] && cmd="$cmd $headers"
 48 |     [ -n "$data" ] && cmd="$cmd -d '$data'"
 49 |     
 50 |     # Execute and capture response
 51 |     local response=$(eval "$cmd")
 52 |     local body=$(echo "$response" | sed '$d')
 53 |     local status=$(echo "$response" | tail -n 1)
 54 |     
 55 |     # Check status
 56 |     if [ "$status" = "$expected_status" ]; then
 57 |         echo -e "${GREEN}✓${NC} $method $endpoint - Status: $status"
 58 |     else
 59 |         echo -e "${RED}✗${NC} $method $endpoint - Expected: $expected_status, Got: $status"
 60 |     fi
 61 |     
 62 |     # Show response body
 63 |     if [ -n "$body" ]; then
 64 |         echo "$body" | $JQ
 65 |     fi
 66 |     echo ""
 67 | }
 68 | 
 69 | # Test 1: Health check
 70 | echo "1. Testing health endpoint..."
 71 | make_request "GET" "/health" "" "" "200"
 72 | 
 73 | # Test 2: OPTIONS request (CORS preflight)
 74 | echo "2. Testing CORS preflight..."
 75 | make_request "OPTIONS" "/mcp" "" "-H 'Origin: http://localhost' -H 'Access-Control-Request-Method: POST'" "204"
 76 | 
 77 | # Test 3: Authentication failure
 78 | echo "3. Testing authentication (should fail)..."
 79 | make_request "POST" "/mcp" \
 80 |     '{"jsonrpc":"2.0","method":"tools/list","id":1}' \
 81 |     "-H 'Content-Type: application/json' -H 'Authorization: Bearer wrong-token'" \
 82 |     "401"
 83 | 
 84 | # Test 4: Missing authentication
 85 | echo "4. Testing missing authentication..."
 86 | make_request "POST" "/mcp" \
 87 |     '{"jsonrpc":"2.0","method":"tools/list","id":1}' \
 88 |     "-H 'Content-Type: application/json'" \
 89 |     "401"
 90 | 
 91 | # Test 5: Valid MCP request to list tools
 92 | echo "5. Testing valid MCP request (list tools)..."
 93 | make_request "POST" "/mcp" \
 94 |     '{"jsonrpc":"2.0","method":"tools/list","id":1}' \
 95 |     "-H 'Content-Type: application/json' -H 'Authorization: Bearer $TOKEN' -H 'Accept: application/json, text/event-stream'" \
 96 |     "200"
 97 | 
 98 | # Test 6: 404 for unknown endpoint
 99 | echo "6. Testing 404 response..."
100 | make_request "GET" "/unknown" "" "" "404"
101 | 
102 | # Test 7: Invalid JSON
103 | echo "7. Testing invalid JSON..."
104 | make_request "POST" "/mcp" \
105 |     '{invalid json}' \
106 |     "-H 'Content-Type: application/json' -H 'Authorization: Bearer $TOKEN'" \
107 |     "400"
108 | 
109 | # Test 8: Request size limit
110 | echo "8. Testing request size limit..."
111 | # Use a different approach for large data
112 | echo "Skipping large payload test (would exceed bash limits)"
113 | 
114 | # Test 9: MCP initialization
115 | if [ "$VERBOSE" = "1" ]; then
116 |     echo "9. Testing MCP initialization..."
117 |     make_request "POST" "/mcp" \
118 |         '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{"roots":{}}},"id":1}' \
119 |         "-H 'Content-Type: application/json' -H 'Authorization: Bearer $TOKEN' -H 'Accept: text/event-stream'" \
120 |         "200"
121 | fi
122 | 
123 | echo "================================"
124 | echo "🎉 Tests completed!"
125 | echo ""
126 | echo "To run with verbose output: VERBOSE=1 $0"
127 | echo "To test a different server: $0 https://your-server.com"
128 | echo "To use a different token: AUTH_TOKEN=your-token $0"
```

--------------------------------------------------------------------------------
/tests/unit/services/debug-validator.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, it, expect, vi, beforeEach } from 'vitest';
  2 | import { WorkflowValidator } from '@/services/workflow-validator';
  3 | 
  4 | // Mock dependencies - don't use vi.mock for complex mocks
  5 | vi.mock('@/services/expression-validator', () => ({
  6 |   ExpressionValidator: {
  7 |     validateNodeExpressions: () => ({
  8 |       valid: true,
  9 |       errors: [],
 10 |       warnings: [],
 11 |       variables: [],
 12 |       expressions: []
 13 |     })
 14 |   }
 15 | }));
 16 | vi.mock('@/utils/logger', () => ({
 17 |   Logger: vi.fn().mockImplementation(() => ({
 18 |     error: vi.fn(),
 19 |     warn: vi.fn(),
 20 |     info: vi.fn(),
 21 |     debug: vi.fn()
 22 |   }))
 23 | }));
 24 | 
 25 | describe('Debug Validator Tests', () => {
 26 |   let validator: WorkflowValidator;
 27 |   let mockNodeRepository: any;
 28 |   let mockEnhancedConfigValidator: any;
 29 | 
 30 |   beforeEach(() => {
 31 |     // Create mock repository
 32 |     mockNodeRepository = {
 33 |       getNode: (nodeType: string) => {
 34 |         // Handle both n8n-nodes-base.set and nodes-base.set (normalized)
 35 |         if (nodeType === 'n8n-nodes-base.set' || nodeType === 'nodes-base.set') {
 36 |           return {
 37 |             name: 'Set',
 38 |             type: 'nodes-base.set',
 39 |             typeVersion: 1,
 40 |             properties: [],
 41 |             package: 'n8n-nodes-base',
 42 |             version: 1,
 43 |             displayName: 'Set'
 44 |           };
 45 |         }
 46 |         return null;
 47 |       }
 48 |     };
 49 |     
 50 |     // Create mock EnhancedConfigValidator
 51 |     mockEnhancedConfigValidator = {
 52 |       validateWithMode: () => ({
 53 |         valid: true,
 54 |         errors: [],
 55 |         warnings: [],
 56 |         suggestions: [],
 57 |         mode: 'operation',
 58 |         visibleProperties: [],
 59 |         hiddenProperties: []
 60 |       })
 61 |     };
 62 |     
 63 |     // Create validator instance
 64 |     validator = new WorkflowValidator(mockNodeRepository, mockEnhancedConfigValidator as any);
 65 |   });
 66 | 
 67 |   it('should handle nodes at extreme positions - debug', async () => {
 68 |     const workflow = {
 69 |       nodes: [
 70 |         { id: '1', name: 'FarLeft', type: 'n8n-nodes-base.set', position: [-999999, -999999] as [number, number], parameters: {} },
 71 |         { id: '2', name: 'FarRight', type: 'n8n-nodes-base.set', position: [999999, 999999] as [number, number], parameters: {} },
 72 |         { id: '3', name: 'Zero', type: 'n8n-nodes-base.set', position: [0, 0] as [number, number], parameters: {} }
 73 |       ],
 74 |       connections: {
 75 |         'FarLeft': {
 76 |           main: [[{ node: 'FarRight', type: 'main', index: 0 }]]
 77 |         },
 78 |         'FarRight': {
 79 |           main: [[{ node: 'Zero', type: 'main', index: 0 }]]
 80 |         }
 81 |       }
 82 |     };
 83 |     
 84 |     const result = await validator.validateWorkflow(workflow);
 85 |     
 86 |     
 87 |     // Test should pass with extreme positions
 88 |     expect(result.valid).toBe(true);
 89 |     expect(result.errors).toHaveLength(0);
 90 |   });
 91 | 
 92 |   it('should handle special characters in node names - debug', async () => {
 93 |     const workflow = {
 94 |       nodes: [
 95 |         { id: '1', name: 'Node@#$%', type: 'n8n-nodes-base.set', position: [0, 0] as [number, number], parameters: {} },
 96 |         { id: '2', name: 'Node 中文', type: 'n8n-nodes-base.set', position: [100, 0] as [number, number], parameters: {} },
 97 |         { id: '3', name: 'Node😊', type: 'n8n-nodes-base.set', position: [200, 0] as [number, number], parameters: {} }
 98 |       ],
 99 |       connections: {
100 |         'Node@#$%': {
101 |           main: [[{ node: 'Node 中文', type: 'main', index: 0 }]]
102 |         },
103 |         'Node 中文': {
104 |           main: [[{ node: 'Node😊', type: 'main', index: 0 }]]
105 |         }
106 |       }
107 |     };
108 |     
109 |     const result = await validator.validateWorkflow(workflow);
110 |     
111 |     
112 |     // Test should pass with special characters in node names
113 |     expect(result.valid).toBe(true);
114 |     expect(result.errors).toHaveLength(0);
115 |   });
116 | 
117 |   it('should handle non-array nodes - debug', async () => {
118 |     const workflow = {
119 |       nodes: 'not-an-array',
120 |       connections: {}
121 |     };
122 |     const result = await validator.validateWorkflow(workflow as any);
123 |     
124 |     
125 |     expect(result.valid).toBe(false);
126 |     expect(result.errors[0].message).toContain('nodes must be an array');
127 |   });
128 | });
```

--------------------------------------------------------------------------------
/src/mcp/tool-docs/configuration/search-node-properties.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ToolDocumentation } from '../types';
 2 | 
 3 | export const searchNodePropertiesDoc: ToolDocumentation = {
 4 |   name: 'search_node_properties',
 5 |   category: 'configuration',
 6 |   essentials: {
 7 |     description: 'Find specific properties in a node without downloading all 200+ properties.',
 8 |     keyParameters: ['nodeType', 'query'],
 9 |     example: 'search_node_properties({nodeType: "nodes-base.httpRequest", query: "auth"})',
10 |     performance: 'Fast - searches indexed properties',
11 |     tips: [
12 |       'Search for "auth", "header", "body", "json", "credential"',
13 |       'Returns property paths and descriptions',
14 |       'Much faster than get_node_info for finding specific fields'
15 |     ]
16 |   },
17 |   full: {
18 |     description: `Searches for specific properties within a node's configuration schema. Essential for finding authentication fields, headers, body parameters, or any specific property without downloading the entire node schema (which can be 100KB+). Returns matching properties with their paths, types, and descriptions.`,
19 |     parameters: {
20 |       nodeType: {
21 |         type: 'string',
22 |         required: true,
23 |         description: 'Full type with prefix',
24 |         examples: [
25 |           'nodes-base.httpRequest',
26 |           'nodes-base.slack',
27 |           'nodes-base.postgres',
28 |           'nodes-base.googleSheets'
29 |         ]
30 |       },
31 |       query: {
32 |         type: 'string',
33 |         required: true,
34 |         description: 'Property to find: "auth", "header", "body", "json"',
35 |         examples: [
36 |           'auth',
37 |           'header',
38 |           'body',
39 |           'json',
40 |           'credential',
41 |           'timeout',
42 |           'retry',
43 |           'pagination'
44 |         ]
45 |       },
46 |       maxResults: {
47 |         type: 'number',
48 |         required: false,
49 |         description: 'Max results (default 20)',
50 |         default: 20
51 |       }
52 |     },
53 |     returns: `Object containing:
54 | - nodeType: The searched node type
55 | - query: Your search term
56 | - matches: Array of matching properties with:
57 |   - name: Property identifier
58 |   - displayName: Human-readable name
59 |   - type: Property type (string, number, options, etc.)
60 |   - description: Property description
61 |   - path: Full path to property (for nested properties)
62 |   - required: Whether property is required
63 |   - default: Default value if any
64 |   - options: Available options for selection properties
65 |   - showWhen: Visibility conditions
66 | - totalMatches: Number of matches found
67 | - searchedIn: Total properties searched`,
68 |     examples: [
69 |       'search_node_properties({nodeType: "nodes-base.httpRequest", query: "auth"}) - Find authentication fields',
70 |       'search_node_properties({nodeType: "nodes-base.slack", query: "channel"}) - Find channel-related properties',
71 |       'search_node_properties({nodeType: "nodes-base.postgres", query: "query"}) - Find query fields',
72 |       'search_node_properties({nodeType: "nodes-base.webhook", query: "response"}) - Find response options'
73 |     ],
74 |     useCases: [
75 |       'Finding authentication/credential fields quickly',
76 |       'Locating specific parameters without full node info',
77 |       'Discovering header or body configuration options',
78 |       'Finding nested properties in complex nodes',
79 |       'Checking if a node supports specific features (retry, pagination, etc.)'
80 |     ],
81 |     performance: 'Very fast - searches pre-indexed property metadata',
82 |     bestPractices: [
83 |       'Use before get_node_info to find specific properties',
84 |       'Search for common terms: auth, header, body, credential',
85 |       'Check showWhen conditions to understand visibility',
86 |       'Use with get_property_dependencies for complete understanding',
87 |       'Limit results if you only need to check existence'
88 |     ],
89 |     pitfalls: [
90 |       'Some properties may be hidden due to visibility conditions',
91 |       'Property names may differ from display names',
92 |       'Nested properties show full path (e.g., "options.retry.limit")',
93 |       'Search is case-sensitive for property names'
94 |     ],
95 |     relatedTools: ['get_node_essentials', 'get_property_dependencies', 'get_node_info']
96 |   }
97 | };
```

--------------------------------------------------------------------------------
/scripts/vitest-benchmark-json-reporter.js:
--------------------------------------------------------------------------------

```javascript
  1 | const { writeFileSync } = require('fs');
  2 | const { resolve } = require('path');
  3 | 
  4 | class BenchmarkJsonReporter {
  5 |   constructor() {
  6 |     this.results = [];
  7 |     console.log('[BenchmarkJsonReporter] Initialized');
  8 |   }
  9 | 
 10 |   onInit(ctx) {
 11 |     console.log('[BenchmarkJsonReporter] onInit called');
 12 |   }
 13 | 
 14 |   onCollected(files) {
 15 |     console.log('[BenchmarkJsonReporter] onCollected called with', files ? files.length : 0, 'files');
 16 |   }
 17 | 
 18 |   onTaskUpdate(tasks) {
 19 |     console.log('[BenchmarkJsonReporter] onTaskUpdate called');
 20 |   }
 21 | 
 22 |   onBenchmarkResult(file, benchmark) {
 23 |     console.log('[BenchmarkJsonReporter] onBenchmarkResult called for', benchmark.name);
 24 |   }
 25 | 
 26 |   onFinished(files, errors) {
 27 |     console.log('[BenchmarkJsonReporter] onFinished called with', files ? files.length : 0, 'files');
 28 |     
 29 |     const results = {
 30 |       timestamp: new Date().toISOString(),
 31 |       files: []
 32 |     };
 33 | 
 34 |     try {
 35 |       for (const file of files || []) {
 36 |         if (!file) continue;
 37 |         
 38 |         const fileResult = {
 39 |           filepath: file.filepath || file.name || 'unknown',
 40 |           groups: []
 41 |         };
 42 | 
 43 |         // Handle both file.tasks and file.benchmarks
 44 |         const tasks = file.tasks || file.benchmarks || [];
 45 |         
 46 |         // Process tasks/benchmarks
 47 |         for (const task of tasks) {
 48 |           if (task.type === 'suite' && task.tasks) {
 49 |             // This is a suite containing benchmarks
 50 |             const group = {
 51 |               name: task.name,
 52 |               benchmarks: []
 53 |             };
 54 | 
 55 |             for (const benchmark of task.tasks) {
 56 |               if (benchmark.result?.benchmark) {
 57 |                 group.benchmarks.push({
 58 |                   name: benchmark.name,
 59 |                   result: {
 60 |                     mean: benchmark.result.benchmark.mean,
 61 |                     min: benchmark.result.benchmark.min,
 62 |                     max: benchmark.result.benchmark.max,
 63 |                     hz: benchmark.result.benchmark.hz,
 64 |                     p75: benchmark.result.benchmark.p75,
 65 |                     p99: benchmark.result.benchmark.p99,
 66 |                     p995: benchmark.result.benchmark.p995,
 67 |                     p999: benchmark.result.benchmark.p999,
 68 |                     rme: benchmark.result.benchmark.rme,
 69 |                     samples: benchmark.result.benchmark.samples
 70 |                   }
 71 |                 });
 72 |               }
 73 |             }
 74 | 
 75 |             if (group.benchmarks.length > 0) {
 76 |               fileResult.groups.push(group);
 77 |             }
 78 |           } else if (task.result?.benchmark) {
 79 |             // This is a direct benchmark (not in a suite)
 80 |             if (!fileResult.groups.length) {
 81 |               fileResult.groups.push({
 82 |                 name: 'Default',
 83 |                 benchmarks: []
 84 |               });
 85 |             }
 86 |             
 87 |             fileResult.groups[0].benchmarks.push({
 88 |               name: task.name,
 89 |               result: {
 90 |                 mean: task.result.benchmark.mean,
 91 |                 min: task.result.benchmark.min,
 92 |                 max: task.result.benchmark.max,
 93 |                 hz: task.result.benchmark.hz,
 94 |                 p75: task.result.benchmark.p75,
 95 |                 p99: task.result.benchmark.p99,
 96 |                 p995: task.result.benchmark.p995,
 97 |                 p999: task.result.benchmark.p999,
 98 |                 rme: task.result.benchmark.rme,
 99 |                 samples: task.result.benchmark.samples
100 |               }
101 |             });
102 |           }
103 |         }
104 | 
105 |         if (fileResult.groups.length > 0) {
106 |           results.files.push(fileResult);
107 |         }
108 |       }
109 | 
110 |       // Write results
111 |       const outputPath = resolve(process.cwd(), 'benchmark-results.json');
112 |       writeFileSync(outputPath, JSON.stringify(results, null, 2));
113 |       console.log(`[BenchmarkJsonReporter] Benchmark results written to ${outputPath}`);
114 |       console.log(`[BenchmarkJsonReporter] Total files processed: ${results.files.length}`);
115 |     } catch (error) {
116 |       console.error('[BenchmarkJsonReporter] Error writing results:', error);
117 |     }
118 |   }
119 | }
120 | 
121 | module.exports = BenchmarkJsonReporter;
```

--------------------------------------------------------------------------------
/scripts/migrate-tool-docs.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env tsx
  2 | import * as fs from 'fs';
  3 | import * as path from 'path';
  4 | 
  5 | // This is a helper script to migrate tool documentation to the new structure
  6 | // It creates a template file for each tool that needs to be migrated
  7 | 
  8 | const toolsByCategory = {
  9 |   discovery: [
 10 |     'search_nodes',
 11 |     'list_nodes', 
 12 |     'list_ai_tools',
 13 |     'get_database_statistics'
 14 |   ],
 15 |   configuration: [
 16 |     'get_node_info',
 17 |     'get_node_essentials',
 18 |     'get_node_documentation',
 19 |     'search_node_properties',
 20 |     'get_node_as_tool_info',
 21 |     'get_property_dependencies'
 22 |   ],
 23 |   validation: [
 24 |     'validate_node_minimal',
 25 |     'validate_node_operation',
 26 |     'validate_workflow',
 27 |     'validate_workflow_connections',
 28 |     'validate_workflow_expressions'
 29 |   ],
 30 |   templates: [
 31 |     'get_node_for_task',
 32 |     'list_tasks',
 33 |     'list_node_templates',
 34 |     'get_template',
 35 |     'search_templates',
 36 |     'get_templates_for_task'
 37 |   ],
 38 |   workflow_management: [
 39 |     'n8n_create_workflow',
 40 |     'n8n_get_workflow',
 41 |     'n8n_get_workflow_details',
 42 |     'n8n_get_workflow_structure',
 43 |     'n8n_get_workflow_minimal',
 44 |     'n8n_update_full_workflow',
 45 |     'n8n_update_partial_workflow',
 46 |     'n8n_delete_workflow',
 47 |     'n8n_list_workflows',
 48 |     'n8n_validate_workflow',
 49 |     'n8n_trigger_webhook_workflow',
 50 |     'n8n_get_execution',
 51 |     'n8n_list_executions',
 52 |     'n8n_delete_execution'
 53 |   ],
 54 |   system: [
 55 |     'tools_documentation',
 56 |     'n8n_diagnostic',
 57 |     'n8n_health_check',
 58 |     'n8n_list_available_tools'
 59 |   ],
 60 |   special: [
 61 |     'code_node_guide'
 62 |   ]
 63 | };
 64 | 
 65 | const template = (toolName: string, category: string) => `import { ToolDocumentation } from '../types';
 66 | 
 67 | export const ${toCamelCase(toolName)}Doc: ToolDocumentation = {
 68 |   name: '${toolName}',
 69 |   category: '${category}',
 70 |   essentials: {
 71 |     description: 'TODO: Add description from old file',
 72 |     keyParameters: ['TODO'],
 73 |     example: '${toolName}({TODO})',
 74 |     performance: 'TODO',
 75 |     tips: [
 76 |       'TODO: Add tips'
 77 |     ]
 78 |   },
 79 |   full: {
 80 |     description: 'TODO: Add full description',
 81 |     parameters: {
 82 |       // TODO: Add parameters
 83 |     },
 84 |     returns: 'TODO: Add return description',
 85 |     examples: [
 86 |       '${toolName}({TODO}) - TODO'
 87 |     ],
 88 |     useCases: [
 89 |       'TODO: Add use cases'
 90 |     ],
 91 |     performance: 'TODO: Add performance description',
 92 |     bestPractices: [
 93 |       'TODO: Add best practices'
 94 |     ],
 95 |     pitfalls: [
 96 |       'TODO: Add pitfalls'
 97 |     ],
 98 |     relatedTools: ['TODO']
 99 |   }
100 | };`;
101 | 
102 | function toCamelCase(str: string): string {
103 |   return str.split('_').map((part, index) => 
104 |     index === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)
105 |   ).join('');
106 | }
107 | 
108 | function toKebabCase(str: string): string {
109 |   return str.replace(/_/g, '-');
110 | }
111 | 
112 | // Create template files for tools that don't exist yet
113 | Object.entries(toolsByCategory).forEach(([category, tools]) => {
114 |   tools.forEach(toolName => {
115 |     const fileName = toKebabCase(toolName) + '.ts';
116 |     const filePath = path.join('src/mcp/tool-docs', category, fileName);
117 |     
118 |     // Skip if file already exists
119 |     if (fs.existsSync(filePath)) {
120 |       console.log(`✓ ${filePath} already exists`);
121 |       return;
122 |     }
123 |     
124 |     // Create the file with template
125 |     fs.writeFileSync(filePath, template(toolName, category));
126 |     console.log(`✨ Created ${filePath}`);
127 |   });
128 |   
129 |   // Create index file for the category
130 |   const indexPath = path.join('src/mcp/tool-docs', category, 'index.ts');
131 |   if (!fs.existsSync(indexPath)) {
132 |     const indexContent = tools.map(toolName => 
133 |       `export { ${toCamelCase(toolName)}Doc } from './${toKebabCase(toolName)}';`
134 |     ).join('\n');
135 |     
136 |     fs.writeFileSync(indexPath, indexContent);
137 |     console.log(`✨ Created ${indexPath}`);
138 |   }
139 | });
140 | 
141 | console.log('\n📝 Migration templates created!');
142 | console.log('Next steps:');
143 | console.log('1. Copy documentation from the old tools-documentation.ts file');
144 | console.log('2. Update each template file with the actual documentation');
145 | console.log('3. Update src/mcp/tool-docs/index.ts to import all tools');
146 | console.log('4. Replace the old tools-documentation.ts with the new one');
```

--------------------------------------------------------------------------------
/tests/docker-tests-README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Docker Config File Support Tests
  2 | 
  3 | This directory contains comprehensive tests for the Docker config file support feature added to n8n-mcp.
  4 | 
  5 | ## Test Structure
  6 | 
  7 | ### Unit Tests (`tests/unit/docker/`)
  8 | 
  9 | 1. **parse-config.test.ts** - Tests for the JSON config parser
 10 |    - Basic JSON parsing functionality
 11 |    - Environment variable precedence
 12 |    - Shell escaping and quoting
 13 |    - Nested object flattening
 14 |    - Error handling for invalid JSON
 15 | 
 16 | 2. **serve-command.test.ts** - Tests for "n8n-mcp serve" command
 17 |    - Command transformation logic
 18 |    - Argument preservation
 19 |    - Integration with config loading
 20 |    - Backwards compatibility
 21 | 
 22 | 3. **config-security.test.ts** - Security-focused tests
 23 |    - Command injection prevention
 24 |    - Shell metacharacter handling
 25 |    - Path traversal protection
 26 |    - Polyglot payload defense
 27 |    - Real-world attack scenarios
 28 | 
 29 | 4. **edge-cases.test.ts** - Edge case and stress tests
 30 |    - JavaScript number edge cases
 31 |    - Unicode handling
 32 |    - Deep nesting performance
 33 |    - Large config files
 34 |    - Invalid data types
 35 | 
 36 | ### Integration Tests (`tests/integration/docker/`)
 37 | 
 38 | 1. **docker-config.test.ts** - Full Docker container tests with config files
 39 |    - Config file loading and parsing
 40 |    - Environment variable precedence
 41 |    - Security in container context
 42 |    - Complex configuration scenarios
 43 | 
 44 | 2. **docker-entrypoint.test.ts** - Docker entrypoint script tests
 45 |    - MCP mode handling
 46 |    - Database initialization
 47 |    - Permission management
 48 |    - Signal handling
 49 |    - Authentication validation
 50 | 
 51 | ## Running the Tests
 52 | 
 53 | ### Prerequisites
 54 | - Node.js and npm installed
 55 | - Docker installed (for integration tests)
 56 | - Build the project first: `npm run build`
 57 | 
 58 | ### Commands
 59 | 
 60 | ```bash
 61 | # Run all Docker config tests
 62 | npm run test:docker
 63 | 
 64 | # Run only unit tests (no Docker required)
 65 | npm run test:docker:unit
 66 | 
 67 | # Run only integration tests (requires Docker)
 68 | npm run test:docker:integration
 69 | 
 70 | # Run security-focused tests
 71 | npm run test:docker:security
 72 | 
 73 | # Run with coverage
 74 | ./scripts/test-docker-config.sh coverage
 75 | ```
 76 | 
 77 | ### Individual test files
 78 | ```bash
 79 | # Run a specific test file
 80 | npm test -- tests/unit/docker/parse-config.test.ts
 81 | 
 82 | # Run with watch mode
 83 | npm run test:watch -- tests/unit/docker/
 84 | 
 85 | # Run with coverage
 86 | npm run test:coverage -- tests/unit/docker/config-security.test.ts
 87 | ```
 88 | 
 89 | ## Test Coverage
 90 | 
 91 | The tests cover:
 92 | 
 93 | 1. **Functionality**
 94 |    - JSON parsing and environment variable conversion
 95 |    - Nested object flattening with underscore separation
 96 |    - Environment variable precedence (env vars override config)
 97 |    - "n8n-mcp serve" command auto-enables HTTP mode
 98 | 
 99 | 2. **Security**
100 |    - Command injection prevention through proper shell escaping
101 |    - Protection against malicious config values
102 |    - Safe handling of special characters and Unicode
103 |    - Prevention of path traversal attacks
104 | 
105 | 3. **Edge Cases**
106 |    - Invalid JSON handling
107 |    - Missing config files
108 |    - Permission errors
109 |    - Very large config files
110 |    - Deep nesting performance
111 | 
112 | 4. **Integration**
113 |    - Full Docker container behavior
114 |    - Database initialization with file locking
115 |    - Permission handling (root vs nodejs user)
116 |    - Signal propagation and process management
117 | 
118 | ## CI/CD Considerations
119 | 
120 | Integration tests are skipped by default unless:
121 | - Running in CI (CI=true environment variable)
122 | - Explicitly enabled (RUN_DOCKER_TESTS=true)
123 | 
124 | This prevents test failures on developer machines without Docker.
125 | 
126 | ## Security Notes
127 | 
128 | The config parser implements defense in depth:
129 | 1. All values are wrapped in single quotes for shell safety
130 | 2. Single quotes within values are escaped as '"'"'
131 | 3. No variable expansion occurs within single quotes
132 | 4. Arrays and null values are ignored (not exported)
133 | 5. The parser exits silently on any error to prevent container startup issues
134 | 
135 | ## Troubleshooting
136 | 
137 | If tests fail:
138 | 1. Ensure Docker is running (for integration tests)
139 | 2. Check that the project is built (`npm run build`)
140 | 3. Verify no containers are left running: `docker ps -a | grep n8n-mcp-test`
141 | 4. Clean up test containers: `docker rm $(docker ps -aq -f name=n8n-mcp-test)`
```

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

```typescript
  1 | /**
  2 |  * Integration Tests: handleGetWorkflow
  3 |  *
  4 |  * Tests workflow retrieval against a real n8n instance.
  5 |  * Covers successful retrieval and error handling.
  6 |  */
  7 | 
  8 | import { describe, it, expect, beforeEach, afterEach, afterAll } from 'vitest';
  9 | import { createTestContext, TestContext, createTestWorkflowName } from '../utils/test-context';
 10 | import { getTestN8nClient } from '../utils/n8n-client';
 11 | import { N8nApiClient } from '../../../../src/services/n8n-api-client';
 12 | import { Workflow } from '../../../../src/types/n8n-api';
 13 | import { SIMPLE_WEBHOOK_WORKFLOW } from '../utils/fixtures';
 14 | import { cleanupOrphanedWorkflows } from '../utils/cleanup-helpers';
 15 | import { createMcpContext } from '../utils/mcp-context';
 16 | import { InstanceContext } from '../../../../src/types/instance-context';
 17 | import { handleGetWorkflow } from '../../../../src/mcp/handlers-n8n-manager';
 18 | 
 19 | describe('Integration: handleGetWorkflow', () => {
 20 |   let context: TestContext;
 21 |   let client: N8nApiClient;
 22 |   let mcpContext: InstanceContext;
 23 | 
 24 |   beforeEach(() => {
 25 |     context = createTestContext();
 26 |     client = getTestN8nClient();
 27 |     mcpContext = createMcpContext();
 28 |   });
 29 | 
 30 |   afterEach(async () => {
 31 |     await context.cleanup();
 32 |   });
 33 | 
 34 |   afterAll(async () => {
 35 |     if (!process.env.CI) {
 36 |       await cleanupOrphanedWorkflows();
 37 |     }
 38 |   });
 39 | 
 40 |   // ======================================================================
 41 |   // Successful Retrieval
 42 |   // ======================================================================
 43 | 
 44 |   describe('Successful Retrieval', () => {
 45 |     it('should retrieve complete workflow data', async () => {
 46 |       // Create a workflow first
 47 |       const workflow = {
 48 |         ...SIMPLE_WEBHOOK_WORKFLOW,
 49 |         name: createTestWorkflowName('Get Workflow - Complete Data'),
 50 |         tags: ['mcp-integration-test']
 51 |       };
 52 | 
 53 |       const created = await client.createWorkflow(workflow);
 54 |       expect(created).toBeDefined();
 55 |       expect(created.id).toBeTruthy();
 56 | 
 57 |       if (!created.id) throw new Error('Workflow ID is missing');
 58 |       context.trackWorkflow(created.id);
 59 | 
 60 |       // Retrieve the workflow using MCP handler
 61 |       const response = await handleGetWorkflow({ id: created.id }, mcpContext);
 62 | 
 63 |       // Verify MCP response structure
 64 |       expect(response.success).toBe(true);
 65 |       expect(response.data).toBeDefined();
 66 | 
 67 |       const retrieved = response.data as Workflow;
 68 | 
 69 |       // Verify all expected fields are present
 70 |       expect(retrieved).toBeDefined();
 71 |       expect(retrieved.id).toBe(created.id);
 72 |       expect(retrieved.name).toBe(workflow.name);
 73 |       expect(retrieved.nodes).toBeDefined();
 74 |       expect(retrieved.nodes).toHaveLength(workflow.nodes!.length);
 75 |       expect(retrieved.connections).toBeDefined();
 76 |       expect(retrieved.active).toBeDefined();
 77 |       expect(retrieved.createdAt).toBeDefined();
 78 |       expect(retrieved.updatedAt).toBeDefined();
 79 | 
 80 |       // Verify node data integrity
 81 |       const retrievedNode = retrieved.nodes[0];
 82 |       const originalNode = workflow.nodes![0];
 83 |       expect(retrievedNode.name).toBe(originalNode.name);
 84 |       expect(retrievedNode.type).toBe(originalNode.type);
 85 |       expect(retrievedNode.parameters).toBeDefined();
 86 |     });
 87 |   });
 88 | 
 89 |   // ======================================================================
 90 |   // Error Handling
 91 |   // ======================================================================
 92 | 
 93 |   describe('Error Handling', () => {
 94 |     it('should return error for non-existent workflow (invalid ID)', async () => {
 95 |       const invalidId = '99999999';
 96 | 
 97 |       const response = await handleGetWorkflow({ id: invalidId }, mcpContext);
 98 | 
 99 |       // MCP handlers return success: false on error
100 |       expect(response.success).toBe(false);
101 |       expect(response.error).toBeDefined();
102 |     });
103 | 
104 |     it('should return error for malformed workflow ID', async () => {
105 |       const malformedId = 'not-a-valid-id-format';
106 | 
107 |       const response = await handleGetWorkflow({ id: malformedId }, mcpContext);
108 | 
109 |       // MCP handlers return success: false on error
110 |       expect(response.success).toBe(false);
111 |       expect(response.error).toBeDefined();
112 |     });
113 |   });
114 | });
115 | 
```

--------------------------------------------------------------------------------
/src/mcp/tool-docs/configuration/get-node-info.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ToolDocumentation } from '../types';
 2 | 
 3 | export const getNodeInfoDoc: ToolDocumentation = {
 4 |   name: 'get_node_info',
 5 |   category: 'configuration',
 6 |   essentials: {
 7 |     description: 'Returns complete node schema with ALL properties (100KB+ response). Only use when you need advanced properties not in get_node_essentials. Contains 200+ properties for complex nodes like HTTP Request. Requires full prefix like "nodes-base.httpRequest".',
 8 |     keyParameters: ['nodeType'],
 9 |     example: 'get_node_info({nodeType: "nodes-base.slack"})',
10 |     performance: '100-500ms, 50-500KB response',
11 |     tips: [
12 |       'Try get_node_essentials first (95% smaller)',
13 |       'Use only for advanced configurations',
14 |       'Response may have 200+ properties'
15 |     ]
16 |   },
17 |   full: {
18 |     description: 'Returns the complete JSON schema for a node including all properties, operations, authentication methods, version information, and metadata. Response sizes range from 50KB to 500KB. Use this only when get_node_essentials doesn\'t provide the specific property you need.',
19 |     parameters: {
20 |       nodeType: { type: 'string', required: true, description: 'Full node type with prefix. Examples: "nodes-base.slack", "nodes-base.httpRequest", "nodes-langchain.openAi"' }
21 |     },
22 |     returns: `Complete node object containing:
23 | {
24 |   "displayName": "Slack",
25 |   "name": "slack",
26 |   "type": "nodes-base.slack",
27 |   "typeVersion": 2.2,
28 |   "description": "Consume Slack API",
29 |   "defaults": {"name": "Slack"},
30 |   "inputs": ["main"],
31 |   "outputs": ["main"],
32 |   "credentials": [
33 |     {
34 |       "name": "slackApi",
35 |       "required": true,
36 |       "displayOptions": {...}
37 |     }
38 |   ],
39 |   "properties": [
40 |     // 200+ property definitions including:
41 |     {
42 |       "displayName": "Resource",
43 |       "name": "resource",
44 |       "type": "options",
45 |       "options": ["channel", "message", "user", "file", ...],
46 |       "default": "message"
47 |     },
48 |     {
49 |       "displayName": "Operation", 
50 |       "name": "operation",
51 |       "type": "options",
52 |       "displayOptions": {
53 |         "show": {"resource": ["message"]}
54 |       },
55 |       "options": ["post", "update", "delete", "get", ...],
56 |       "default": "post"
57 |     },
58 |     // ... 200+ more properties with complex conditions
59 |   ],
60 |   "version": 2.2,
61 |   "subtitle": "={{$parameter[\"operation\"] + \": \" + $parameter[\"resource\"]}}",
62 |   "codex": {...},
63 |   "supportedWebhooks": [...]
64 | }`,
65 |     examples: [
66 |       'get_node_info({nodeType: "nodes-base.httpRequest"}) - 300+ properties for HTTP requests',
67 |       'get_node_info({nodeType: "nodes-base.googleSheets"}) - Complex operations and auth',
68 |       '// When to use get_node_info:',
69 |       '// 1. First try essentials',
70 |       'const essentials = get_node_essentials({nodeType: "nodes-base.slack"});',
71 |       '// 2. If property missing, search for it',
72 |       'const props = search_node_properties({nodeType: "nodes-base.slack", query: "thread"});',
73 |       '// 3. Only if needed, get full schema',
74 |       'const full = get_node_info({nodeType: "nodes-base.slack"});'
75 |     ],
76 |     useCases: [
77 |       'Analyzing all available operations for a node',
78 |       'Understanding complex property dependencies',
79 |       'Discovering all authentication methods',
80 |       'Building UI that shows all node options',
81 |       'Debugging property visibility conditions'
82 |     ],
83 |     performance: '100-500ms depending on node complexity. HTTP Request node: ~300KB, Simple nodes: ~50KB',
84 |     bestPractices: [
85 |       'Always try get_node_essentials first - it\'s 95% smaller',
86 |       'Use search_node_properties to find specific advanced properties',
87 |       'Cache results locally - schemas rarely change',
88 |       'Parse incrementally - don\'t load entire response into memory at once'
89 |     ],
90 |     pitfalls: [
91 |       'Response can exceed 500KB for complex nodes',
92 |       'Contains many rarely-used properties that add noise',
93 |       'Property conditions can be deeply nested and complex',
94 |       'Must use full node type with prefix (nodes-base.X not just X)'
95 |     ],
96 |     relatedTools: ['get_node_essentials for common properties', 'search_node_properties to find specific fields', 'get_property_dependencies to understand conditions']
97 |   }
98 | };
```

--------------------------------------------------------------------------------
/src/mcp/tool-docs/validation/validate-workflow.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ToolDocumentation } from '../types';
 2 | 
 3 | export const validateWorkflowDoc: ToolDocumentation = {
 4 |   name: 'validate_workflow',
 5 |   category: 'validation',
 6 |   essentials: {
 7 |     description: 'Full workflow validation: structure, connections, expressions, AI tools. Returns errors/warnings/fixes. Essential before deploy.',
 8 |     keyParameters: ['workflow', 'options'],
 9 |     example: 'validate_workflow({workflow: {nodes: [...], connections: {...}}})',
10 |     performance: 'Moderate (100-500ms)',
11 |     tips: [
12 |       'Always validate before n8n_create_workflow to catch errors early',
13 |       'Use options.profile="minimal" for quick checks during development',
14 |       'AI tool connections are automatically validated for proper node references'
15 |     ]
16 |   },
17 |   full: {
18 |     description: 'Performs comprehensive validation of n8n workflows including structure, node configurations, connections, and expressions. This is a three-layer validation system that catches errors before deployment, validates complex multi-node workflows, checks all n8n expressions for syntax errors, and ensures proper node connections and data flow.',
19 |     parameters: {
20 |       workflow: { 
21 |         type: 'object', 
22 |         required: true, 
23 |         description: 'The complete workflow JSON to validate. Must include nodes array and connections object.' 
24 |       },
25 |       options: { 
26 |         type: 'object', 
27 |         required: false, 
28 |         description: 'Validation options object' 
29 |       },
30 |       'options.validateNodes': { 
31 |         type: 'boolean', 
32 |         required: false, 
33 |         description: 'Validate individual node configurations. Default: true' 
34 |       },
35 |       'options.validateConnections': { 
36 |         type: 'boolean', 
37 |         required: false, 
38 |         description: 'Validate node connections and flow. Default: true' 
39 |       },
40 |       'options.validateExpressions': { 
41 |         type: 'boolean', 
42 |         required: false, 
43 |         description: 'Validate n8n expressions syntax and references. Default: true' 
44 |       },
45 |       'options.profile': { 
46 |         type: 'string', 
47 |         required: false, 
48 |         description: 'Validation profile for node validation: minimal, runtime (default), ai-friendly, strict' 
49 |       }
50 |     },
51 |     returns: 'Object with valid (boolean), errors (array), warnings (array), statistics (object), and suggestions (array)',
52 |     examples: [
53 |       'validate_workflow({workflow: myWorkflow}) - Full validation with default settings',
54 |       'validate_workflow({workflow: myWorkflow, options: {profile: "minimal"}}) - Quick validation for editing',
55 |       'validate_workflow({workflow: myWorkflow, options: {validateExpressions: false}}) - Skip expression validation'
56 |     ],
57 |     useCases: [
58 |       'Pre-deployment validation to catch all workflow issues',
59 |       'Quick validation during workflow development',
60 |       'Validate workflows with AI Agent nodes and tool connections',
61 |       'Check expression syntax before workflow execution',
62 |       'Ensure workflow structure integrity after modifications'
63 |     ],
64 |     performance: 'Moderate (100-500ms). Depends on workflow size and validation options. Expression validation adds ~50-100ms.',
65 |     bestPractices: [
66 |       'Always validate workflows before creating or updating in n8n',
67 |       'Use minimal profile during development, strict profile before production',
68 |       'Pay attention to warnings - they often indicate potential runtime issues',
69 |       'Validate after any workflow modifications, especially connection changes',
70 |       'Check statistics to understand workflow complexity'
71 |     ],
72 |     pitfalls: [
73 |       'Large workflows (100+ nodes) may take longer to validate',
74 |       'Expression validation requires proper node references to exist',
75 |       'Some warnings may be acceptable depending on use case',
76 |       'Validation cannot catch all runtime errors (e.g., API failures)',
77 |       'Profile setting only affects node validation, not connection/expression checks'
78 |     ],
79 |     relatedTools: ['validate_workflow_connections', 'validate_workflow_expressions', 'validate_node_operation', 'n8n_create_workflow', 'n8n_update_partial_workflow', 'n8n_autofix_workflow']
80 |   }
81 | };
```

--------------------------------------------------------------------------------
/scripts/test-expression-code-validation.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env npx tsx
  2 | 
  3 | /**
  4 |  * Test script for Expression vs Code Node validation
  5 |  * Tests that we properly detect and warn about expression syntax in Code nodes
  6 |  */
  7 | 
  8 | import { EnhancedConfigValidator } from '../src/services/enhanced-config-validator.js';
  9 | 
 10 | console.log('🧪 Testing Expression vs Code Node Validation\n');
 11 | 
 12 | // Test cases with expression syntax that shouldn't work in Code nodes
 13 | const testCases = [
 14 |   {
 15 |     name: 'Expression syntax in Code node',
 16 |     config: {
 17 |       language: 'javaScript',
 18 |       jsCode: `// Using expression syntax
 19 | const value = {{$json.field}};
 20 | return [{json: {value}}];`
 21 |     },
 22 |     expectedError: 'Expression syntax {{...}} is not valid in Code nodes'
 23 |   },
 24 |   {
 25 |     name: 'Wrong $node syntax',
 26 |     config: {
 27 |       language: 'javaScript',
 28 |       jsCode: `// Using expression $node syntax
 29 | const data = $node['Previous Node'].json;
 30 | return [{json: data}];`
 31 |     },
 32 |     expectedWarning: 'Use $(\'Node Name\') instead of $node[\'Node Name\'] in Code nodes'
 33 |   },
 34 |   {
 35 |     name: 'Expression-only functions',
 36 |     config: {
 37 |       language: 'javaScript',
 38 |       jsCode: `// Using expression functions
 39 | const now = $now();
 40 | const unique = items.unique();
 41 | return [{json: {now, unique}}];`
 42 |     },
 43 |     expectedWarning: '$now() is an expression-only function'
 44 |   },
 45 |   {
 46 |     name: 'Wrong JMESPath parameter order',
 47 |     config: {
 48 |       language: 'javaScript',
 49 |       jsCode: `// Wrong parameter order
 50 | const result = $jmespath("users[*].name", data);
 51 | return [{json: {result}}];`
 52 |     },
 53 |     expectedWarning: 'Code node $jmespath has reversed parameter order'
 54 |   },
 55 |   {
 56 |     name: 'Correct Code node syntax',
 57 |     config: {
 58 |       language: 'javaScript',
 59 |       jsCode: `// Correct syntax
 60 | const prevData = $('Previous Node').first();
 61 | const now = DateTime.now();
 62 | const result = $jmespath(data, "users[*].name");
 63 | return [{json: {prevData, now, result}}];`
 64 |     },
 65 |     shouldBeValid: true
 66 |   }
 67 | ];
 68 | 
 69 | // Basic node properties for Code node
 70 | const codeNodeProperties = [
 71 |   { name: 'language', type: 'options', options: ['javaScript', 'python'] },
 72 |   { name: 'jsCode', type: 'string' },
 73 |   { name: 'pythonCode', type: 'string' },
 74 |   { name: 'mode', type: 'options', options: ['runOnceForAllItems', 'runOnceForEachItem'] }
 75 | ];
 76 | 
 77 | console.log('Running validation tests...\n');
 78 | 
 79 | testCases.forEach((test, index) => {
 80 |   console.log(`Test ${index + 1}: ${test.name}`);
 81 |   console.log('─'.repeat(50));
 82 |   
 83 |   const result = EnhancedConfigValidator.validateWithMode(
 84 |     'nodes-base.code',
 85 |     test.config,
 86 |     codeNodeProperties,
 87 |     'operation',
 88 |     'ai-friendly'
 89 |   );
 90 |   
 91 |   console.log(`Valid: ${result.valid}`);
 92 |   console.log(`Errors: ${result.errors.length}`);
 93 |   console.log(`Warnings: ${result.warnings.length}`);
 94 |   
 95 |   if (test.expectedError) {
 96 |     const hasExpectedError = result.errors.some(e => 
 97 |       e.message.includes(test.expectedError)
 98 |     );
 99 |     console.log(`✅ Expected error found: ${hasExpectedError}`);
100 |     if (!hasExpectedError) {
101 |       console.log('❌ Missing expected error:', test.expectedError);
102 |       console.log('Actual errors:', result.errors.map(e => e.message));
103 |     }
104 |   }
105 |   
106 |   if (test.expectedWarning) {
107 |     const hasExpectedWarning = result.warnings.some(w => 
108 |       w.message.includes(test.expectedWarning)
109 |     );
110 |     console.log(`✅ Expected warning found: ${hasExpectedWarning}`);
111 |     if (!hasExpectedWarning) {
112 |       console.log('❌ Missing expected warning:', test.expectedWarning);
113 |       console.log('Actual warnings:', result.warnings.map(w => w.message));
114 |     }
115 |   }
116 |   
117 |   if (test.shouldBeValid) {
118 |     console.log(`✅ Should be valid: ${result.valid && result.errors.length === 0}`);
119 |     if (!result.valid || result.errors.length > 0) {
120 |       console.log('❌ Unexpected errors:', result.errors);
121 |     }
122 |   }
123 |   
124 |   // Show actual messages
125 |   if (result.errors.length > 0) {
126 |     console.log('\nErrors:');
127 |     result.errors.forEach(e => console.log(`  - ${e.message}`));
128 |   }
129 |   
130 |   if (result.warnings.length > 0) {
131 |     console.log('\nWarnings:');
132 |     result.warnings.forEach(w => console.log(`  - ${w.message}`));
133 |   }
134 |   
135 |   console.log('\n');
136 | });
137 | 
138 | console.log('✅ Expression vs Code Node validation tests completed!');
```

--------------------------------------------------------------------------------
/tests/test-sqlite-search.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Test SQLite database search functionality
  5 |  */
  6 | 
  7 | const { SQLiteStorageService } = require('../dist/services/sqlite-storage-service');
  8 | const { NodeSourceExtractor } = require('../dist/utils/node-source-extractor');
  9 | 
 10 | async function testDatabaseSearch() {
 11 |   console.log('=== SQLite Database Search Test ===\n');
 12 |   
 13 |   const storage = new SQLiteStorageService();
 14 |   const extractor = new NodeSourceExtractor();
 15 |   
 16 |   // First, ensure we have some data
 17 |   console.log('1️⃣ Checking database status...');
 18 |   let stats = await storage.getStatistics();
 19 |   
 20 |   if (stats.totalNodes === 0) {
 21 |     console.log('   Database is empty. Adding some test nodes...\n');
 22 |     
 23 |     const testNodes = [
 24 |       'n8n-nodes-base.Function',
 25 |       'n8n-nodes-base.Webhook',
 26 |       'n8n-nodes-base.HttpRequest',
 27 |       'n8n-nodes-base.If',
 28 |       'n8n-nodes-base.Slack',
 29 |       'n8n-nodes-base.Discord'
 30 |     ];
 31 |     
 32 |     for (const nodeType of testNodes) {
 33 |       try {
 34 |         const nodeInfo = await extractor.extractNodeSource(nodeType);
 35 |         await storage.storeNode(nodeInfo);
 36 |         console.log(`   ✅ Stored ${nodeType}`);
 37 |       } catch (error) {
 38 |         console.log(`   ❌ Failed to store ${nodeType}: ${error.message}`);
 39 |       }
 40 |     }
 41 |     
 42 |     stats = await storage.getStatistics();
 43 |   }
 44 |   
 45 |   console.log(`\n   Total nodes in database: ${stats.totalNodes}`);
 46 |   console.log(`   Total packages: ${stats.totalPackages}`);
 47 |   console.log(`   Database size: ${(stats.totalCodeSize / 1024).toFixed(2)} KB\n`);
 48 |   
 49 |   // Test different search scenarios
 50 |   console.log('2️⃣ Testing search functionality...\n');
 51 |   
 52 |   const searchTests = [
 53 |     {
 54 |       name: 'Search by partial name (func)',
 55 |       query: { query: 'func' }
 56 |     },
 57 |     {
 58 |       name: 'Search by partial name (web)',
 59 |       query: { query: 'web' }
 60 |     },
 61 |     {
 62 |       name: 'Search for HTTP',
 63 |       query: { query: 'http' }
 64 |     },
 65 |     {
 66 |       name: 'Search for multiple terms',
 67 |       query: { query: 'slack discord' }
 68 |     },
 69 |     {
 70 |       name: 'Filter by package',
 71 |       query: { packageName: 'n8n-nodes-base' }
 72 |     },
 73 |     {
 74 |       name: 'Search with package filter',
 75 |       query: { query: 'func', packageName: 'n8n-nodes-base' }
 76 |     },
 77 |     {
 78 |       name: 'Search by node type',
 79 |       query: { nodeType: 'Webhook' }
 80 |     },
 81 |     {
 82 |       name: 'Limit results',
 83 |       query: { query: 'node', limit: 3 }
 84 |     }
 85 |   ];
 86 |   
 87 |   for (const test of searchTests) {
 88 |     console.log(`   📍 ${test.name}:`);
 89 |     console.log(`      Query: ${JSON.stringify(test.query)}`);
 90 |     
 91 |     try {
 92 |       const results = await storage.searchNodes(test.query);
 93 |       console.log(`      Results: ${results.length} nodes found`);
 94 |       
 95 |       if (results.length > 0) {
 96 |         console.log('      Matches:');
 97 |         results.slice(0, 3).forEach(node => {
 98 |           console.log(`        - ${node.nodeType} (${node.displayName || node.name})`);
 99 |         });
100 |         if (results.length > 3) {
101 |           console.log(`        ... and ${results.length - 3} more`);
102 |         }
103 |       }
104 |     } catch (error) {
105 |       console.log(`      ❌ Error: ${error.message}`);
106 |     }
107 |     
108 |     console.log('');
109 |   }
110 |   
111 |   // Test specific node retrieval
112 |   console.log('3️⃣ Testing specific node retrieval...\n');
113 |   
114 |   const specificNode = await storage.getNode('n8n-nodes-base.Function');
115 |   if (specificNode) {
116 |     console.log(`   ✅ Found node: ${specificNode.nodeType}`);
117 |     console.log(`      Display name: ${specificNode.displayName}`);
118 |     console.log(`      Code size: ${specificNode.codeLength} bytes`);
119 |     console.log(`      Has credentials: ${specificNode.hasCredentials}`);
120 |   } else {
121 |     console.log('   ❌ Node not found');
122 |   }
123 |   
124 |   // Test package listing
125 |   console.log('\n4️⃣ Testing package listing...\n');
126 |   
127 |   const packages = await storage.getPackages();
128 |   console.log(`   Found ${packages.length} packages:`);
129 |   packages.forEach(pkg => {
130 |     console.log(`     - ${pkg.name}: ${pkg.nodeCount} nodes`);
131 |   });
132 |   
133 |   // Close database
134 |   storage.close();
135 |   
136 |   console.log('\n✅ Search functionality test completed!');
137 | }
138 | 
139 | // Run the test
140 | testDatabaseSearch().catch(error => {
141 |   console.error('Test failed:', error);
142 |   process.exit(1);
143 | });
```

--------------------------------------------------------------------------------
/src/mcp/tool-docs/validation/validate-node-operation.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ToolDocumentation } from '../types';
 2 | 
 3 | export const validateNodeOperationDoc: ToolDocumentation = {
 4 |   name: 'validate_node_operation',
 5 |   category: 'validation',
 6 |   essentials: {
 7 |     description: 'Validates node configuration with operation awareness. Checks required fields, data types, and operation-specific rules. Returns specific errors with automated fix suggestions. Different profiles for different validation needs.',
 8 |     keyParameters: ['nodeType', 'config', 'profile'],
 9 |     example: 'validate_node_operation({nodeType: "nodes-base.slack", config: {resource: "message", operation: "post", text: "Hi"}})',
10 |     performance: '<100ms',
11 |     tips: [
12 |       'Profile choices: minimal (editing), runtime (execution), ai-friendly (balanced), strict (deployment)',
13 |       'Returns fixes you can apply directly',
14 |       'Operation-aware - knows Slack post needs text'
15 |     ]
16 |   },
17 |   full: {
18 |     description: 'Comprehensive node configuration validation that understands operation context. For example, it knows Slack message posting requires text field, while channel listing doesn\'t. Provides different validation profiles for different stages of workflow development.',
19 |     parameters: {
20 |       nodeType: { type: 'string', required: true, description: 'Full node type with prefix: "nodes-base.slack", "nodes-base.httpRequest"' },
21 |       config: { type: 'object', required: true, description: 'Node configuration. Must include operation fields (resource/operation/action) if the node has multiple operations' },
22 |       profile: { type: 'string', required: false, description: 'Validation profile - controls what\'s checked. Default: "ai-friendly"' }
23 |     },
24 |     returns: `Object containing:
25 | {
26 |   "isValid": false,
27 |   "errors": [
28 |     {
29 |       "field": "channel",
30 |       "message": "Required field 'channel' is missing",
31 |       "severity": "error",
32 |       "fix": "#general"
33 |     }
34 |   ],
35 |   "warnings": [
36 |     {
37 |       "field": "retryOnFail", 
38 |       "message": "Consider enabling retry for reliability",
39 |       "severity": "warning",
40 |       "fix": true
41 |     }
42 |   ],
43 |   "suggestions": [
44 |     {
45 |       "field": "timeout",
46 |       "message": "Set timeout to prevent hanging",
47 |       "fix": 30000
48 |     }
49 |   ],
50 |   "fixes": {
51 |     "channel": "#general",
52 |     "retryOnFail": true,
53 |     "timeout": 30000
54 |   }
55 | }`,
56 |     examples: [
57 |       '// Missing required field',
58 |       'validate_node_operation({nodeType: "nodes-base.slack", config: {resource: "message", operation: "post"}})',
59 |       '// Returns: {isValid: false, errors: [{field: "text", message: "Required field missing"}], fixes: {text: "Message text"}}',
60 |       '',
61 |       '// Validate with strict profile for production',
62 |       'validate_node_operation({nodeType: "nodes-base.httpRequest", config: {method: "POST", url: "https://api.example.com"}, profile: "strict"})',
63 |       '',
64 |       '// Apply fixes automatically',
65 |       'const result = validate_node_operation({nodeType: "nodes-base.slack", config: myConfig});',
66 |       'if (!result.isValid) {',
67 |       '  myConfig = {...myConfig, ...result.fixes};',
68 |       '}'
69 |     ],
70 |     useCases: [
71 |       'Validate configuration before workflow execution',
72 |       'Debug why a node isn\'t working as expected',
73 |       'Generate configuration fixes automatically',
74 |       'Different validation for editing vs production'
75 |     ],
76 |     performance: '<100ms for most nodes, <200ms for complex nodes with many conditions',
77 |     bestPractices: [
78 |       'Use "minimal" profile during user editing for fast feedback',
79 |       'Use "runtime" profile (default) before execution',
80 |       'Use "ai-friendly" when AI configures nodes',
81 |       'Use "strict" profile before production deployment',
82 |       'Always include operation fields (resource/operation) in config',
83 |       'Apply suggested fixes to resolve issues quickly'
84 |     ],
85 |     pitfalls: [
86 |       'Must include operation fields for multi-operation nodes',
87 |       'Fixes are suggestions - review before applying',
88 |       'Profile affects what\'s validated - minimal skips many checks'
89 |     ],
90 |     relatedTools: ['validate_node_minimal for quick checks', 'get_node_essentials for valid examples', 'validate_workflow for complete workflow validation']
91 |   }
92 | };
```

--------------------------------------------------------------------------------
/src/mcp/tool-docs/system/n8n-health-check.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ToolDocumentation } from '../types';
 2 | 
 3 | export const n8nHealthCheckDoc: ToolDocumentation = {
 4 |   name: 'n8n_health_check',
 5 |   category: 'system',
 6 |   essentials: {
 7 |     description: 'Check n8n instance health, API connectivity, version status, and performance metrics',
 8 |     keyParameters: [],
 9 |     example: 'n8n_health_check({})',
10 |     performance: 'Fast - single API call (~150-200ms median)',
11 |     tips: [
12 |       'Use before starting workflow operations to ensure n8n is responsive',
13 |       'Automatically checks if n8n-mcp version is outdated',
14 |       'Returns version info, performance metrics, and next-step recommendations',
15 |       'New: Shows cache hit rate and response time for performance monitoring'
16 |     ]
17 |   },
18 |   full: {
19 |     description: `Performs a comprehensive health check of the configured n8n instance through its API.
20 | 
21 | This tool verifies:
22 | - API endpoint accessibility and response time
23 | - n8n instance version and build information
24 | - Authentication status and permissions
25 | - Available features and enterprise capabilities
26 | - Database connectivity (as reported by n8n)
27 | - Queue system status (if configured)
28 | 
29 | Health checks are crucial for:
30 | - Monitoring n8n instance availability
31 | - Detecting performance degradation
32 | - Verifying API compatibility before operations
33 | - Ensuring authentication is working correctly`,
34 |     parameters: {},
35 |     returns: `Health status object containing:
36 | - status: Overall health status ('healthy', 'degraded', 'error')
37 | - n8nVersion: n8n instance version information
38 | - instanceId: Unique identifier for the n8n instance
39 | - features: Object listing available features and their status
40 | - mcpVersion: Current n8n-mcp version
41 | - supportedN8nVersion: Recommended n8n version for compatibility
42 | - versionCheck: Version status information
43 |   - current: Current n8n-mcp version
44 |   - latest: Latest available version from npm
45 |   - upToDate: Boolean indicating if version is current
46 |   - message: Formatted version status message
47 |   - updateCommand: Command to update (if outdated)
48 | - performance: Performance metrics
49 |   - responseTimeMs: API response time in milliseconds
50 |   - cacheHitRate: Cache efficiency percentage
51 |   - cachedInstances: Number of cached API instances
52 | - nextSteps: Recommended actions after health check
53 | - updateWarning: Warning if version is outdated (if applicable)`,
54 |     examples: [
55 |       'n8n_health_check({}) - Complete health check with version and performance data',
56 |       '// Use in monitoring scripts\nconst health = await n8n_health_check({});\nif (health.status !== "ok") alert("n8n is down!");\nif (!health.versionCheck.upToDate) console.log("Update available:", health.versionCheck.updateCommand);',
57 |       '// Check before critical operations\nconst health = await n8n_health_check({});\nif (health.performance.responseTimeMs > 1000) console.warn("n8n is slow");\nif (health.versionCheck.isOutdated) console.log(health.updateWarning);'
58 |     ],
59 |     useCases: [
60 |       'Pre-flight checks before workflow deployments',
61 |       'Continuous monitoring of n8n instance health',
62 |       'Troubleshooting connectivity or performance issues',
63 |       'Verifying n8n version compatibility with workflows',
64 |       'Detecting feature availability (enterprise features, queue mode, etc.)'
65 |     ],
66 |     performance: `Fast response expected:
67 | - Single HTTP request to /health endpoint
68 | - Typically responds in <100ms for healthy instances
69 | - Timeout after 10 seconds indicates severe issues
70 | - Minimal server load - safe for frequent polling`,
71 |     bestPractices: [
72 |       'Run health checks before batch operations or deployments',
73 |       'Set up automated monitoring with regular health checks',
74 |       'Log response times to detect performance trends',
75 |       'Check version compatibility when deploying workflows',
76 |       'Use health status to implement circuit breaker patterns'
77 |     ],
78 |     pitfalls: [
79 |       'Requires N8N_API_URL and N8N_API_KEY to be configured',
80 |       'Network issues may cause false negatives',
81 |       'Does not check individual workflow health',
82 |       'Health endpoint might be cached - not real-time for all metrics'
83 |     ],
84 |     relatedTools: ['n8n_diagnostic', 'n8n_list_available_tools', 'n8n_list_workflows']
85 |   }
86 | };
```

--------------------------------------------------------------------------------
/scripts/quick-test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env ts-node
  2 | /**
  3 |  * Quick test script to validate the essentials implementation
  4 |  */
  5 | 
  6 | import { spawn } from 'child_process';
  7 | import { join } from 'path';
  8 | 
  9 | const colors = {
 10 |   reset: '\x1b[0m',
 11 |   bright: '\x1b[1m',
 12 |   green: '\x1b[32m',
 13 |   red: '\x1b[31m',
 14 |   yellow: '\x1b[33m',
 15 |   blue: '\x1b[34m',
 16 |   cyan: '\x1b[36m'
 17 | };
 18 | 
 19 | function log(message: string, color: string = colors.reset) {
 20 |   console.log(`${color}${message}${colors.reset}`);
 21 | }
 22 | 
 23 | async function runMCPCommand(toolName: string, args: any): Promise<any> {
 24 |   return new Promise((resolve, reject) => {
 25 |     const request = {
 26 |       jsonrpc: '2.0',
 27 |       method: 'tools/call',
 28 |       params: {
 29 |         name: toolName,
 30 |         arguments: args
 31 |       },
 32 |       id: 1
 33 |     };
 34 |     
 35 |     const mcp = spawn('npm', ['start'], {
 36 |       cwd: join(__dirname, '..'),
 37 |       stdio: ['pipe', 'pipe', 'pipe']
 38 |     });
 39 |     
 40 |     let output = '';
 41 |     let error = '';
 42 |     
 43 |     mcp.stdout.on('data', (data) => {
 44 |       output += data.toString();
 45 |     });
 46 |     
 47 |     mcp.stderr.on('data', (data) => {
 48 |       error += data.toString();
 49 |     });
 50 |     
 51 |     mcp.on('close', (code) => {
 52 |       if (code !== 0) {
 53 |         reject(new Error(`Process exited with code ${code}: ${error}`));
 54 |         return;
 55 |       }
 56 |       
 57 |       try {
 58 |         // Parse JSON-RPC response
 59 |         const lines = output.split('\n');
 60 |         for (const line of lines) {
 61 |           if (line.trim() && line.includes('"jsonrpc"')) {
 62 |             const response = JSON.parse(line);
 63 |             if (response.result) {
 64 |               resolve(JSON.parse(response.result.content[0].text));
 65 |               return;
 66 |             } else if (response.error) {
 67 |               reject(new Error(response.error.message));
 68 |               return;
 69 |             }
 70 |           }
 71 |         }
 72 |         reject(new Error('No valid response found'));
 73 |       } catch (err) {
 74 |         reject(err);
 75 |       }
 76 |     });
 77 |     
 78 |     // Send request
 79 |     mcp.stdin.write(JSON.stringify(request) + '\n');
 80 |     mcp.stdin.end();
 81 |   });
 82 | }
 83 | 
 84 | async function quickTest() {
 85 |   log('\n🚀 Quick Test - n8n MCP Essentials', colors.bright + colors.cyan);
 86 |   
 87 |   try {
 88 |     // Test 1: Get essentials for HTTP Request
 89 |     log('\n1️⃣  Testing get_node_essentials for HTTP Request...', colors.yellow);
 90 |     const essentials = await runMCPCommand('get_node_essentials', {
 91 |       nodeType: 'nodes-base.httpRequest'
 92 |     });
 93 |     
 94 |     log('✅ Success! Got essentials:', colors.green);
 95 |     log(`   Required properties: ${essentials.requiredProperties?.map((p: any) => p.name).join(', ') || 'None'}`);
 96 |     log(`   Common properties: ${essentials.commonProperties?.map((p: any) => p.name).join(', ') || 'None'}`);
 97 |     log(`   Examples: ${Object.keys(essentials.examples || {}).join(', ')}`);
 98 |     log(`   Response size: ${JSON.stringify(essentials).length} bytes`, colors.green);
 99 |     
100 |     // Test 2: Search properties
101 |     log('\n2️⃣  Testing search_node_properties...', colors.yellow);
102 |     const searchResults = await runMCPCommand('search_node_properties', {
103 |       nodeType: 'nodes-base.httpRequest',
104 |       query: 'auth'
105 |     });
106 |     
107 |     log('✅ Success! Found properties:', colors.green);
108 |     log(`   Matches: ${searchResults.totalMatches}`);
109 |     searchResults.matches?.slice(0, 3).forEach((match: any) => {
110 |       log(`   - ${match.name}: ${match.description}`);
111 |     });
112 |     
113 |     // Test 3: Compare sizes
114 |     log('\n3️⃣  Comparing response sizes...', colors.yellow);
115 |     const fullInfo = await runMCPCommand('get_node_info', {
116 |       nodeType: 'nodes-base.httpRequest'
117 |     });
118 |     
119 |     const fullSize = JSON.stringify(fullInfo).length;
120 |     const essentialSize = JSON.stringify(essentials).length;
121 |     const reduction = ((fullSize - essentialSize) / fullSize * 100).toFixed(1);
122 |     
123 |     log(`✅ Size comparison:`, colors.green);
124 |     log(`   Full response: ${(fullSize / 1024).toFixed(1)} KB`);
125 |     log(`   Essential response: ${(essentialSize / 1024).toFixed(1)} KB`);
126 |     log(`   Size reduction: ${reduction}% 🎉`, colors.bright + colors.green);
127 |     
128 |     log('\n✨ All tests passed!', colors.bright + colors.green);
129 |     
130 |   } catch (error) {
131 |     log(`\n❌ Test failed: ${error}`, colors.red);
132 |     process.exit(1);
133 |   }
134 | }
135 | 
136 | // Run if called directly
137 | if (require.main === module) {
138 |   quickTest().catch(console.error);
139 | }
```

--------------------------------------------------------------------------------
/src/mcp/tool-docs/workflow_management/n8n-list-executions.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ToolDocumentation } from '../types';
 2 | 
 3 | export const n8nListExecutionsDoc: ToolDocumentation = {
 4 |   name: 'n8n_list_executions',
 5 |   category: 'workflow_management',
 6 |   essentials: {
 7 |     description: 'List workflow executions with optional filters. Supports pagination for large result sets.',
 8 |     keyParameters: ['workflowId', 'status', 'limit'],
 9 |     example: 'n8n_list_executions({workflowId: "abc123", status: "error"})',
10 |     performance: 'Fast metadata retrieval, use pagination for large datasets',
11 |     tips: [
12 |       'Filter by status (success/error/waiting) to find specific execution types',
13 |       'Use workflowId to see all executions for a specific workflow',
14 |       'Pagination via cursor allows retrieving large execution histories'
15 |     ]
16 |   },
17 |   full: {
18 |     description: `Lists workflow executions with powerful filtering options. This tool is essential for monitoring workflow performance, finding failed executions, and tracking workflow activity. Supports pagination for retrieving large execution histories and filtering by workflow, status, and project.`,
19 |     parameters: {
20 |       limit: {
21 |         type: 'number',
22 |         required: false,
23 |         description: 'Number of executions to return (1-100, default: 100). Use with cursor for pagination'
24 |       },
25 |       cursor: {
26 |         type: 'string',
27 |         required: false,
28 |         description: 'Pagination cursor from previous response. Used to retrieve next page of results'
29 |       },
30 |       workflowId: {
31 |         type: 'string',
32 |         required: false,
33 |         description: 'Filter executions by specific workflow ID. Shows all executions for that workflow'
34 |       },
35 |       projectId: {
36 |         type: 'string',
37 |         required: false,
38 |         description: 'Filter by project ID (enterprise feature). Groups executions by project'
39 |       },
40 |       status: {
41 |         type: 'string',
42 |         required: false,
43 |         enum: ['success', 'error', 'waiting'],
44 |         description: 'Filter by execution status. Success = completed, Error = failed, Waiting = running'
45 |       },
46 |       includeData: {
47 |         type: 'boolean',
48 |         required: false,
49 |         description: 'Include execution data in results (default: false). Significantly increases response size'
50 |       }
51 |     },
52 |     returns: `Array of execution objects with metadata, pagination cursor for next page, and optionally execution data. Each execution includes ID, status, start/end times, and workflow reference.`,
53 |     examples: [
54 |       'n8n_list_executions({limit: 10}) - Get 10 most recent executions',
55 |       'n8n_list_executions({workflowId: "abc123"}) - All executions for specific workflow',
56 |       'n8n_list_executions({status: "error", limit: 50}) - Find failed executions',
57 |       'n8n_list_executions({status: "waiting"}) - Monitor currently running workflows',
58 |       'n8n_list_executions({cursor: "next-page-token"}) - Get next page of results'
59 |     ],
60 |     useCases: [
61 |       'Monitor workflow execution history and patterns',
62 |       'Find and debug failed workflow executions',
63 |       'Track currently running workflows (waiting status)',
64 |       'Analyze workflow performance and execution frequency',
65 |       'Generate execution reports for specific workflows'
66 |     ],
67 |     performance: `Listing executions is fast for metadata only. Including data (includeData: true) significantly impacts performance. Use pagination (limit + cursor) for large result sets. Default limit of 100 balances performance with usability.`,
68 |     bestPractices: [
69 |       'Use status filters to focus on specific execution types',
70 |       'Implement pagination for large execution histories',
71 |       'Avoid includeData unless you need execution details',
72 |       'Filter by workflowId when monitoring specific workflows',
73 |       'Check for cursor in response to detect more pages'
74 |     ],
75 |     pitfalls: [
76 |       'Large limits with includeData can cause timeouts',
77 |       'Execution retention depends on n8n configuration',
78 |       'Cursor tokens expire - use them promptly',
79 |       'Status "waiting" includes both running and queued executions',
80 |       'Deleted workflows still show in execution history'
81 |     ],
82 |     relatedTools: ['n8n_get_execution', 'n8n_trigger_webhook_workflow', 'n8n_delete_execution', 'n8n_list_workflows']
83 |   }
84 | };
```

--------------------------------------------------------------------------------
/tests/benchmarks/database-queries.bench.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { bench, describe } from 'vitest';
  2 | import { NodeRepository } from '../../src/database/node-repository';
  3 | import { SQLiteStorageService } from '../../src/services/sqlite-storage-service';
  4 | import { NodeFactory } from '../factories/node-factory';
  5 | import { PropertyDefinitionFactory } from '../factories/property-definition-factory';
  6 | 
  7 | /**
  8 |  * Database Query Performance Benchmarks
  9 |  *
 10 |  * NOTE: These benchmarks use MOCK DATA (500 artificial test nodes)
 11 |  * created with factories, not the real production database.
 12 |  *
 13 |  * This is useful for tracking database layer performance in isolation,
 14 |  * but may not reflect real-world performance characteristics.
 15 |  *
 16 |  * For end-to-end MCP tool performance with real data, see mcp-tools.bench.ts
 17 |  */
 18 | describe('Database Query Performance', () => {
 19 |   let repository: NodeRepository;
 20 |   let storage: SQLiteStorageService;
 21 |   const testNodeCount = 500;
 22 | 
 23 |   beforeAll(async () => {
 24 |     storage = new SQLiteStorageService(':memory:');
 25 |     repository = new NodeRepository(storage);
 26 |     
 27 |     // Seed database with test data
 28 |     for (let i = 0; i < testNodeCount; i++) {
 29 |       const node = NodeFactory.build({
 30 |         displayName: `TestNode${i}`,
 31 |         nodeType: `nodes-base.testNode${i}`,
 32 |         category: i % 2 === 0 ? 'transform' : 'trigger',
 33 |         packageName: 'n8n-nodes-base',
 34 |         documentation: `Test documentation for node ${i}`,
 35 |         properties: PropertyDefinitionFactory.buildList(5)
 36 |       });
 37 |       await repository.upsertNode(node);
 38 |     }
 39 |   });
 40 | 
 41 |   afterAll(() => {
 42 |     storage.close();
 43 |   });
 44 | 
 45 |   bench('getNodeByType - existing node', async () => {
 46 |     await repository.getNodeByType('nodes-base.testNode100');
 47 |   }, {
 48 |     iterations: 1000,
 49 |     warmupIterations: 100,
 50 |     warmupTime: 500,
 51 |     time: 3000
 52 |   });
 53 | 
 54 |   bench('getNodeByType - non-existing node', async () => {
 55 |     await repository.getNodeByType('nodes-base.nonExistentNode');
 56 |   }, {
 57 |     iterations: 1000,
 58 |     warmupIterations: 100,
 59 |     warmupTime: 500,
 60 |     time: 3000
 61 |   });
 62 | 
 63 |   bench('getNodesByCategory - transform', async () => {
 64 |     await repository.getNodesByCategory('transform');
 65 |   }, {
 66 |     iterations: 100,
 67 |     warmupIterations: 10,
 68 |     warmupTime: 500,
 69 |     time: 3000
 70 |   });
 71 | 
 72 |   bench('searchNodes - OR mode', async () => {
 73 |     await repository.searchNodes('test node data', 'OR', 20);
 74 |   }, {
 75 |     iterations: 100,
 76 |     warmupIterations: 10,
 77 |     warmupTime: 500,
 78 |     time: 3000
 79 |   });
 80 | 
 81 |   bench('searchNodes - AND mode', async () => {
 82 |     await repository.searchNodes('test node', 'AND', 20);
 83 |   }, {
 84 |     iterations: 100,
 85 |     warmupIterations: 10,
 86 |     warmupTime: 500,
 87 |     time: 3000
 88 |   });
 89 | 
 90 |   bench('searchNodes - FUZZY mode', async () => {
 91 |     await repository.searchNodes('tst nde', 'FUZZY', 20);
 92 |   }, {
 93 |     iterations: 100,
 94 |     warmupIterations: 10,
 95 |     warmupTime: 500,
 96 |     time: 3000
 97 |   });
 98 | 
 99 |   bench('getAllNodes - no limit', async () => {
100 |     await repository.getAllNodes();
101 |   }, {
102 |     iterations: 50,
103 |     warmupIterations: 5,
104 |     warmupTime: 500,
105 |     time: 3000
106 |   });
107 | 
108 |   bench('getAllNodes - with limit', async () => {
109 |     await repository.getAllNodes(50);
110 |   }, {
111 |     iterations: 100,
112 |     warmupIterations: 10,
113 |     warmupTime: 500,
114 |     time: 3000
115 |   });
116 | 
117 |   bench('getNodeCount', async () => {
118 |     await repository.getNodeCount();
119 |   }, {
120 |     iterations: 1000,
121 |     warmupIterations: 100,
122 |     warmupTime: 100,
123 |     time: 2000
124 |   });
125 | 
126 |   bench('getAIToolNodes', async () => {
127 |     await repository.getAIToolNodes();
128 |   }, {
129 |     iterations: 100,
130 |     warmupIterations: 10,
131 |     warmupTime: 500,
132 |     time: 3000
133 |   });
134 | 
135 |   bench('upsertNode - new node', async () => {
136 |     const node = NodeFactory.build({
137 |       displayName: `BenchNode${Date.now()}`,
138 |       nodeType: `nodes-base.benchNode${Date.now()}`
139 |     });
140 |     await repository.upsertNode(node);
141 |   }, {
142 |     iterations: 100,
143 |     warmupIterations: 10,
144 |     warmupTime: 500,
145 |     time: 3000
146 |   });
147 | 
148 |   bench('upsertNode - existing node update', async () => {
149 |     const existingNode = await repository.getNodeByType('nodes-base.testNode0');
150 |     if (existingNode) {
151 |       existingNode.description = `Updated description ${Date.now()}`;
152 |       await repository.upsertNode(existingNode);
153 |     }
154 |   }, {
155 |     iterations: 100,
156 |     warmupIterations: 10,
157 |     warmupTime: 500,
158 |     time: 3000
159 |   });
160 | });
```

--------------------------------------------------------------------------------
/tests/unit/test-infrastructure.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, it, expect, vi, beforeEach } from 'vitest';
  2 | import { nodeFactory, webhookNodeFactory, slackNodeFactory } from '@tests/fixtures/factories/node.factory';
  3 | 
  4 | // Mock better-sqlite3
  5 | vi.mock('better-sqlite3');
  6 | 
  7 | describe('Test Infrastructure', () => {
  8 |   describe('Database Mock', () => {
  9 |     it('should create a mock database instance', async () => {
 10 |       const Database = (await import('better-sqlite3')).default;
 11 |       const db = new Database(':memory:');
 12 |       
 13 |       expect(Database).toHaveBeenCalled();
 14 |       expect(db).toBeDefined();
 15 |       expect(db.prepare).toBeDefined();
 16 |       expect(db.exec).toBeDefined();
 17 |       expect(db.close).toBeDefined();
 18 |     });
 19 |     
 20 |     it('should handle basic CRUD operations', async () => {
 21 |       const { MockDatabase } = await import('@tests/unit/database/__mocks__/better-sqlite3');
 22 |       const db = new MockDatabase();
 23 |       
 24 |       // Test data seeding
 25 |       db._seedData('nodes', [
 26 |         { id: '1', name: 'test-node', type: 'webhook' }
 27 |       ]);
 28 |       
 29 |       // Test SELECT
 30 |       const selectStmt = db.prepare('SELECT * FROM nodes');
 31 |       const allNodes = selectStmt.all();
 32 |       expect(allNodes).toHaveLength(1);
 33 |       expect(allNodes[0]).toEqual({ id: '1', name: 'test-node', type: 'webhook' });
 34 |       
 35 |       // Test INSERT
 36 |       const insertStmt = db.prepare('INSERT INTO nodes (id, name, type) VALUES (?, ?, ?)');
 37 |       const result = insertStmt.run({ id: '2', name: 'new-node', type: 'slack' });
 38 |       expect(result.changes).toBe(1);
 39 |       
 40 |       // Verify insert worked
 41 |       const allNodesAfter = selectStmt.all();
 42 |       expect(allNodesAfter).toHaveLength(2);
 43 |     });
 44 |   });
 45 |   
 46 |   describe('Node Factory', () => {
 47 |     it('should create a basic node definition', () => {
 48 |       const node = nodeFactory.build();
 49 |       
 50 |       expect(node).toMatchObject({
 51 |         name: expect.any(String),
 52 |         displayName: expect.any(String),
 53 |         description: expect.any(String),
 54 |         version: expect.any(Number),
 55 |         defaults: {
 56 |           name: expect.any(String)
 57 |         },
 58 |         inputs: ['main'],
 59 |         outputs: ['main'],
 60 |         properties: expect.any(Array),
 61 |         credentials: []
 62 |       });
 63 |     });
 64 |     
 65 |     it('should create a webhook node', () => {
 66 |       const webhook = webhookNodeFactory.build();
 67 |       
 68 |       expect(webhook).toMatchObject({
 69 |         name: 'webhook',
 70 |         displayName: 'Webhook',
 71 |         description: 'Starts the workflow when a webhook is called',
 72 |         group: ['trigger'],
 73 |         properties: expect.arrayContaining([
 74 |           expect.objectContaining({
 75 |             name: 'path',
 76 |             type: 'string',
 77 |             required: true
 78 |           }),
 79 |           expect.objectContaining({
 80 |             name: 'method',
 81 |             type: 'options'
 82 |           })
 83 |         ])
 84 |       });
 85 |     });
 86 |     
 87 |     it('should create a slack node', () => {
 88 |       const slack = slackNodeFactory.build();
 89 |       
 90 |       expect(slack).toMatchObject({
 91 |         name: 'slack',
 92 |         displayName: 'Slack',
 93 |         description: 'Send messages to Slack',
 94 |         group: ['output'],
 95 |         credentials: [
 96 |           {
 97 |             name: 'slackApi',
 98 |             required: true
 99 |           }
100 |         ],
101 |         properties: expect.arrayContaining([
102 |           expect.objectContaining({
103 |             name: 'resource',
104 |             type: 'options'
105 |           }),
106 |           expect.objectContaining({
107 |             name: 'operation',
108 |             type: 'options',
109 |             displayOptions: {
110 |               show: {
111 |                 resource: ['message']
112 |               }
113 |             }
114 |           })
115 |         ])
116 |       });
117 |     });
118 |     
119 |     it('should allow overriding factory defaults', () => {
120 |       const customNode = nodeFactory.build({
121 |         name: 'custom-node',
122 |         displayName: 'Custom Node',
123 |         version: 2
124 |       });
125 |       
126 |       expect(customNode.name).toBe('custom-node');
127 |       expect(customNode.displayName).toBe('Custom Node');
128 |       expect(customNode.version).toBe(2);
129 |     });
130 |     
131 |     it('should create multiple unique nodes', () => {
132 |       const nodes = nodeFactory.buildList(5);
133 |       
134 |       expect(nodes).toHaveLength(5);
135 |       const names = nodes.map(n => n.name);
136 |       const uniqueNames = new Set(names);
137 |       expect(uniqueNames.size).toBe(5);
138 |     });
139 |   });
140 | });
```

--------------------------------------------------------------------------------
/scripts/test-user-id-persistence.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Test User ID Persistence
  3 |  * Verifies that user IDs are consistent across sessions and modes
  4 |  */
  5 | 
  6 | import { TelemetryConfigManager } from '../src/telemetry/config-manager';
  7 | import { hostname, platform, arch, homedir } from 'os';
  8 | import { createHash } from 'crypto';
  9 | 
 10 | console.log('=== User ID Persistence Test ===\n');
 11 | 
 12 | // Test 1: Verify deterministic ID generation
 13 | console.log('Test 1: Deterministic ID Generation');
 14 | console.log('-----------------------------------');
 15 | 
 16 | const machineId = `${hostname()}-${platform()}-${arch()}-${homedir()}`;
 17 | const expectedUserId = createHash('sha256')
 18 |   .update(machineId)
 19 |   .digest('hex')
 20 |   .substring(0, 16);
 21 | 
 22 | console.log('Machine characteristics:');
 23 | console.log('  hostname:', hostname());
 24 | console.log('  platform:', platform());
 25 | console.log('  arch:', arch());
 26 | console.log('  homedir:', homedir());
 27 | console.log('\nGenerated machine ID:', machineId);
 28 | console.log('Expected user ID:', expectedUserId);
 29 | 
 30 | // Test 2: Load actual config
 31 | console.log('\n\nTest 2: Actual Config Manager');
 32 | console.log('-----------------------------------');
 33 | 
 34 | const configManager = TelemetryConfigManager.getInstance();
 35 | const actualUserId = configManager.getUserId();
 36 | const config = configManager.loadConfig();
 37 | 
 38 | console.log('Actual user ID:', actualUserId);
 39 | console.log('Config first run:', config.firstRun || 'Unknown');
 40 | console.log('Config version:', config.version || 'Unknown');
 41 | console.log('Telemetry enabled:', config.enabled);
 42 | 
 43 | // Test 3: Verify consistency
 44 | console.log('\n\nTest 3: Consistency Check');
 45 | console.log('-----------------------------------');
 46 | 
 47 | const match = actualUserId === expectedUserId;
 48 | console.log('User IDs match:', match ? '✓ YES' : '✗ NO');
 49 | 
 50 | if (!match) {
 51 |   console.log('WARNING: User ID mismatch detected!');
 52 |   console.log('This could indicate an implementation issue.');
 53 | }
 54 | 
 55 | // Test 4: Multiple loads (simulate multiple sessions)
 56 | console.log('\n\nTest 4: Multiple Session Simulation');
 57 | console.log('-----------------------------------');
 58 | 
 59 | const userId1 = configManager.getUserId();
 60 | const userId2 = TelemetryConfigManager.getInstance().getUserId();
 61 | const userId3 = configManager.getUserId();
 62 | 
 63 | console.log('Session 1 user ID:', userId1);
 64 | console.log('Session 2 user ID:', userId2);
 65 | console.log('Session 3 user ID:', userId3);
 66 | 
 67 | const consistent = userId1 === userId2 && userId2 === userId3;
 68 | console.log('All sessions consistent:', consistent ? '✓ YES' : '✗ NO');
 69 | 
 70 | // Test 5: Docker environment simulation
 71 | console.log('\n\nTest 5: Docker Environment Check');
 72 | console.log('-----------------------------------');
 73 | 
 74 | const isDocker = process.env.IS_DOCKER === 'true';
 75 | console.log('Running in Docker:', isDocker);
 76 | 
 77 | if (isDocker) {
 78 |   console.log('\n⚠️  DOCKER MODE DETECTED');
 79 |   console.log('In Docker, user IDs may change across container recreations because:');
 80 |   console.log('  1. Container hostname changes each time');
 81 |   console.log('  2. Config file is not persisted (no volume mount)');
 82 |   console.log('  3. Each container gets a new ephemeral filesystem');
 83 |   console.log('\nRecommendation: Mount ~/.n8n-mcp as a volume for persistent user IDs');
 84 | }
 85 | 
 86 | // Test 6: Environment variable override check
 87 | console.log('\n\nTest 6: Environment Variable Override');
 88 | console.log('-----------------------------------');
 89 | 
 90 | const telemetryDisabledVars = [
 91 |   'N8N_MCP_TELEMETRY_DISABLED',
 92 |   'TELEMETRY_DISABLED',
 93 |   'DISABLE_TELEMETRY'
 94 | ];
 95 | 
 96 | telemetryDisabledVars.forEach(varName => {
 97 |   const value = process.env[varName];
 98 |   if (value !== undefined) {
 99 |     console.log(`${varName}:`, value);
100 |   }
101 | });
102 | 
103 | console.log('\nTelemetry status:', configManager.isEnabled() ? 'ENABLED' : 'DISABLED');
104 | 
105 | // Summary
106 | console.log('\n\n=== SUMMARY ===');
107 | console.log('User ID:', actualUserId);
108 | console.log('Deterministic:', match ? 'YES ✓' : 'NO ✗');
109 | console.log('Persistent across sessions:', consistent ? 'YES ✓' : 'NO ✗');
110 | console.log('Telemetry enabled:', config.enabled ? 'YES' : 'NO');
111 | console.log('Docker mode:', isDocker ? 'YES' : 'NO');
112 | 
113 | if (isDocker && !process.env.N8N_MCP_CONFIG_VOLUME) {
114 |   console.log('\n⚠️  WARNING: Running in Docker without persistent volume!');
115 |   console.log('User IDs will change on container recreation.');
116 |   console.log('Mount /home/nodejs/.n8n-mcp to persist telemetry config.');
117 | }
118 | 
119 | console.log('\n');
120 | 
```

--------------------------------------------------------------------------------
/src/telemetry/startup-checkpoints.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Startup Checkpoint System
  3 |  * Defines checkpoints throughout the server initialization process
  4 |  * to identify where failures occur
  5 |  */
  6 | 
  7 | /**
  8 |  * Startup checkpoint constants
  9 |  * These checkpoints mark key stages in the server initialization process
 10 |  */
 11 | export const STARTUP_CHECKPOINTS = {
 12 |   /** Process has started, very first checkpoint */
 13 |   PROCESS_STARTED: 'process_started',
 14 | 
 15 |   /** About to connect to database */
 16 |   DATABASE_CONNECTING: 'database_connecting',
 17 | 
 18 |   /** Database connection successful */
 19 |   DATABASE_CONNECTED: 'database_connected',
 20 | 
 21 |   /** About to check n8n API configuration (if applicable) */
 22 |   N8N_API_CHECKING: 'n8n_api_checking',
 23 | 
 24 |   /** n8n API is configured and ready (if applicable) */
 25 |   N8N_API_READY: 'n8n_api_ready',
 26 | 
 27 |   /** About to initialize telemetry system */
 28 |   TELEMETRY_INITIALIZING: 'telemetry_initializing',
 29 | 
 30 |   /** Telemetry system is ready */
 31 |   TELEMETRY_READY: 'telemetry_ready',
 32 | 
 33 |   /** About to start MCP handshake */
 34 |   MCP_HANDSHAKE_STARTING: 'mcp_handshake_starting',
 35 | 
 36 |   /** MCP handshake completed successfully */
 37 |   MCP_HANDSHAKE_COMPLETE: 'mcp_handshake_complete',
 38 | 
 39 |   /** Server is fully ready to handle requests */
 40 |   SERVER_READY: 'server_ready',
 41 | } as const;
 42 | 
 43 | /**
 44 |  * Type for checkpoint names
 45 |  */
 46 | export type StartupCheckpoint = typeof STARTUP_CHECKPOINTS[keyof typeof STARTUP_CHECKPOINTS];
 47 | 
 48 | /**
 49 |  * Checkpoint data structure
 50 |  */
 51 | export interface CheckpointData {
 52 |   name: StartupCheckpoint;
 53 |   timestamp: number;
 54 |   success: boolean;
 55 |   error?: string;
 56 | }
 57 | 
 58 | /**
 59 |  * Get all checkpoint names in order
 60 |  */
 61 | export function getAllCheckpoints(): StartupCheckpoint[] {
 62 |   return Object.values(STARTUP_CHECKPOINTS);
 63 | }
 64 | 
 65 | /**
 66 |  * Find which checkpoint failed based on the list of passed checkpoints
 67 |  * Returns the first checkpoint that was not passed
 68 |  */
 69 | export function findFailedCheckpoint(passedCheckpoints: string[]): StartupCheckpoint {
 70 |   const allCheckpoints = getAllCheckpoints();
 71 | 
 72 |   for (const checkpoint of allCheckpoints) {
 73 |     if (!passedCheckpoints.includes(checkpoint)) {
 74 |       return checkpoint;
 75 |     }
 76 |   }
 77 | 
 78 |   // If all checkpoints were passed, the failure must have occurred after SERVER_READY
 79 |   // This would be an unexpected post-initialization failure
 80 |   return STARTUP_CHECKPOINTS.SERVER_READY;
 81 | }
 82 | 
 83 | /**
 84 |  * Validate if a string is a valid checkpoint
 85 |  */
 86 | export function isValidCheckpoint(checkpoint: string): checkpoint is StartupCheckpoint {
 87 |   return getAllCheckpoints().includes(checkpoint as StartupCheckpoint);
 88 | }
 89 | 
 90 | /**
 91 |  * Get human-readable description for a checkpoint
 92 |  */
 93 | export function getCheckpointDescription(checkpoint: StartupCheckpoint): string {
 94 |   const descriptions: Record<StartupCheckpoint, string> = {
 95 |     [STARTUP_CHECKPOINTS.PROCESS_STARTED]: 'Process initialization started',
 96 |     [STARTUP_CHECKPOINTS.DATABASE_CONNECTING]: 'Connecting to database',
 97 |     [STARTUP_CHECKPOINTS.DATABASE_CONNECTED]: 'Database connection established',
 98 |     [STARTUP_CHECKPOINTS.N8N_API_CHECKING]: 'Checking n8n API configuration',
 99 |     [STARTUP_CHECKPOINTS.N8N_API_READY]: 'n8n API ready',
100 |     [STARTUP_CHECKPOINTS.TELEMETRY_INITIALIZING]: 'Initializing telemetry system',
101 |     [STARTUP_CHECKPOINTS.TELEMETRY_READY]: 'Telemetry system ready',
102 |     [STARTUP_CHECKPOINTS.MCP_HANDSHAKE_STARTING]: 'Starting MCP protocol handshake',
103 |     [STARTUP_CHECKPOINTS.MCP_HANDSHAKE_COMPLETE]: 'MCP handshake completed',
104 |     [STARTUP_CHECKPOINTS.SERVER_READY]: 'Server fully initialized and ready',
105 |   };
106 | 
107 |   return descriptions[checkpoint] || 'Unknown checkpoint';
108 | }
109 | 
110 | /**
111 |  * Get the next expected checkpoint after the given one
112 |  * Returns null if this is the last checkpoint
113 |  */
114 | export function getNextCheckpoint(current: StartupCheckpoint): StartupCheckpoint | null {
115 |   const allCheckpoints = getAllCheckpoints();
116 |   const currentIndex = allCheckpoints.indexOf(current);
117 | 
118 |   if (currentIndex === -1 || currentIndex === allCheckpoints.length - 1) {
119 |     return null;
120 |   }
121 | 
122 |   return allCheckpoints[currentIndex + 1];
123 | }
124 | 
125 | /**
126 |  * Calculate completion percentage based on checkpoints passed
127 |  */
128 | export function getCompletionPercentage(passedCheckpoints: string[]): number {
129 |   const totalCheckpoints = getAllCheckpoints().length;
130 |   const passedCount = passedCheckpoints.length;
131 | 
132 |   return Math.round((passedCount / totalCheckpoints) * 100);
133 | }
134 | 
```

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

```typescript
  1 | import { describe, it, expect, vi, beforeEach } from 'vitest';
  2 | import { ExpressionValidator } from '@/services/expression-validator';
  3 | 
  4 | describe('ExpressionValidator', () => {
  5 |   const defaultContext = {
  6 |     availableNodes: [],
  7 |     currentNodeName: 'TestNode',
  8 |     isInLoop: false,
  9 |     hasInputData: true
 10 |   };
 11 | 
 12 |   beforeEach(() => {
 13 |     vi.clearAllMocks();
 14 |   });
 15 | 
 16 |   describe('validateExpression', () => {
 17 |     it('should be a static method that validates expressions', () => {
 18 |       expect(typeof ExpressionValidator.validateExpression).toBe('function');
 19 |     });
 20 | 
 21 |     it('should return a validation result', () => {
 22 |       const result = ExpressionValidator.validateExpression('{{ $json.field }}', defaultContext);
 23 |       
 24 |       expect(result).toHaveProperty('valid');
 25 |       expect(result).toHaveProperty('errors');
 26 |       expect(result).toHaveProperty('warnings');
 27 |       expect(result).toHaveProperty('usedVariables');
 28 |       expect(result).toHaveProperty('usedNodes');
 29 |     });
 30 | 
 31 |     it('should validate expressions with proper syntax', () => {
 32 |       const validExpr = '{{ $json.field }}';
 33 |       const result = ExpressionValidator.validateExpression(validExpr, defaultContext);
 34 |       
 35 |       expect(result).toBeDefined();
 36 |       expect(Array.isArray(result.errors)).toBe(true);
 37 |     });
 38 | 
 39 |     it('should detect malformed expressions', () => {
 40 |       const invalidExpr = '{{ $json.field'; // Missing closing braces
 41 |       const result = ExpressionValidator.validateExpression(invalidExpr, defaultContext);
 42 |       
 43 |       expect(result.errors.length).toBeGreaterThan(0);
 44 |     });
 45 |   });
 46 | 
 47 |   describe('validateNodeExpressions', () => {
 48 |     it('should validate all expressions in node parameters', () => {
 49 |       const parameters = {
 50 |         field1: '{{ $json.data }}',
 51 |         nested: {
 52 |           field2: 'regular text',
 53 |           field3: '{{ $node["Webhook"].json }}'
 54 |         }
 55 |       };
 56 | 
 57 |       const result = ExpressionValidator.validateNodeExpressions(parameters, defaultContext);
 58 | 
 59 |       expect(result).toHaveProperty('valid');
 60 |       expect(result).toHaveProperty('errors');
 61 |       expect(result).toHaveProperty('warnings');
 62 |     });
 63 | 
 64 |     it('should collect errors from invalid expressions', () => {
 65 |       const parameters = {
 66 |         badExpr: '{{ $json.field', // Missing closing
 67 |         goodExpr: '{{ $json.field }}'
 68 |       };
 69 | 
 70 |       const result = ExpressionValidator.validateNodeExpressions(parameters, defaultContext);
 71 | 
 72 |       expect(result.errors.length).toBeGreaterThan(0);
 73 |     });
 74 |   });
 75 | 
 76 |   describe('expression patterns', () => {
 77 |     it('should recognize n8n variable patterns', () => {
 78 |       const expressions = [
 79 |         '{{ $json }}',
 80 |         '{{ $json.field }}',
 81 |         '{{ $node["NodeName"].json }}',
 82 |         '{{ $workflow.id }}',
 83 |         '{{ $now }}',
 84 |         '{{ $itemIndex }}'
 85 |       ];
 86 | 
 87 |       expressions.forEach(expr => {
 88 |         const result = ExpressionValidator.validateExpression(expr, defaultContext);
 89 |         expect(result).toBeDefined();
 90 |       });
 91 |     });
 92 |   });
 93 | 
 94 |   describe('context validation', () => {
 95 |     it('should use available nodes from context', () => {
 96 |       const contextWithNodes = {
 97 |         ...defaultContext,
 98 |         availableNodes: ['Webhook', 'Function', 'Slack']
 99 |       };
100 | 
101 |       const expr = '{{ $node["Webhook"].json }}';
102 |       const result = ExpressionValidator.validateExpression(expr, contextWithNodes);
103 | 
104 |       expect(result.usedNodes.has('Webhook')).toBe(true);
105 |     });
106 |   });
107 | 
108 |   describe('edge cases', () => {
109 |     it('should handle empty expressions', () => {
110 |       const result = ExpressionValidator.validateExpression('{{ }}', defaultContext);
111 |       // The implementation might consider empty expressions as valid
112 |       expect(result).toBeDefined();
113 |       expect(Array.isArray(result.errors)).toBe(true);
114 |     });
115 | 
116 |     it('should handle non-expression text', () => {
117 |       const result = ExpressionValidator.validateExpression('regular text without expressions', defaultContext);
118 |       expect(result.valid).toBe(true);
119 |       expect(result.errors).toHaveLength(0);
120 |     });
121 | 
122 |     it('should handle nested expressions', () => {
123 |       const expr = '{{ $json[{{ $json.index }}] }}'; // Nested expressions not allowed
124 |       const result = ExpressionValidator.validateExpression(expr, defaultContext);
125 |       expect(result).toBeDefined();
126 |     });
127 |   });
128 | });
```

--------------------------------------------------------------------------------
/src/utils/bridge.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { INodeExecutionData, IDataObject } from 'n8n-workflow';
  2 | 
  3 | export class N8NMCPBridge {
  4 |   /**
  5 |    * Convert n8n workflow data to MCP tool arguments
  6 |    */
  7 |   static n8nToMCPToolArgs(data: IDataObject): any {
  8 |     // Handle different data formats from n8n
  9 |     if (data.json) {
 10 |       return data.json;
 11 |     }
 12 |     
 13 |     // Remove n8n-specific metadata
 14 |     const { pairedItem, ...cleanData } = data;
 15 |     return cleanData;
 16 |   }
 17 | 
 18 |   /**
 19 |    * Convert MCP tool response to n8n execution data
 20 |    */
 21 |   static mcpToN8NExecutionData(mcpResponse: any, itemIndex: number = 0): INodeExecutionData {
 22 |     // Handle MCP content array format
 23 |     if (mcpResponse.content && Array.isArray(mcpResponse.content)) {
 24 |       const textContent = mcpResponse.content
 25 |         .filter((c: any) => c.type === 'text')
 26 |         .map((c: any) => c.text)
 27 |         .join('\n');
 28 |       
 29 |       try {
 30 |         // Try to parse as JSON if possible
 31 |         const parsed = JSON.parse(textContent);
 32 |         return {
 33 |           json: parsed,
 34 |           pairedItem: itemIndex,
 35 |         };
 36 |       } catch {
 37 |         // Return as text if not JSON
 38 |         return {
 39 |           json: { result: textContent },
 40 |           pairedItem: itemIndex,
 41 |         };
 42 |       }
 43 |     }
 44 | 
 45 |     // Handle direct object response
 46 |     return {
 47 |       json: mcpResponse,
 48 |       pairedItem: itemIndex,
 49 |     };
 50 |   }
 51 | 
 52 |   /**
 53 |    * Convert n8n workflow definition to MCP-compatible format
 54 |    */
 55 |   static n8nWorkflowToMCP(workflow: any): any {
 56 |     return {
 57 |       id: workflow.id,
 58 |       name: workflow.name,
 59 |       description: workflow.description || '',
 60 |       nodes: workflow.nodes?.map((node: any) => ({
 61 |         id: node.id,
 62 |         type: node.type,
 63 |         name: node.name,
 64 |         parameters: node.parameters,
 65 |         position: node.position,
 66 |       })),
 67 |       connections: workflow.connections,
 68 |       settings: workflow.settings,
 69 |       metadata: {
 70 |         createdAt: workflow.createdAt,
 71 |         updatedAt: workflow.updatedAt,
 72 |         active: workflow.active,
 73 |       },
 74 |     };
 75 |   }
 76 | 
 77 |   /**
 78 |    * Convert MCP workflow format to n8n-compatible format
 79 |    */
 80 |   static mcpToN8NWorkflow(mcpWorkflow: any): any {
 81 |     return {
 82 |       name: mcpWorkflow.name,
 83 |       nodes: mcpWorkflow.nodes || [],
 84 |       connections: mcpWorkflow.connections || {},
 85 |       settings: mcpWorkflow.settings || {
 86 |         executionOrder: 'v1',
 87 |       },
 88 |       staticData: null,
 89 |       pinData: {},
 90 |     };
 91 |   }
 92 | 
 93 |   /**
 94 |    * Convert n8n execution data to MCP resource format
 95 |    */
 96 |   static n8nExecutionToMCPResource(execution: any): any {
 97 |     return {
 98 |       uri: `execution://${execution.id}`,
 99 |       name: `Execution ${execution.id}`,
100 |       description: `Workflow: ${execution.workflowData?.name || 'Unknown'}`,
101 |       mimeType: 'application/json',
102 |       data: {
103 |         id: execution.id,
104 |         workflowId: execution.workflowId,
105 |         status: execution.finished ? 'completed' : execution.stoppedAt ? 'stopped' : 'running',
106 |         mode: execution.mode,
107 |         startedAt: execution.startedAt,
108 |         stoppedAt: execution.stoppedAt,
109 |         error: execution.data?.resultData?.error,
110 |         executionData: execution.data,
111 |       },
112 |     };
113 |   }
114 | 
115 |   /**
116 |    * Convert MCP prompt arguments to n8n-compatible format
117 |    */
118 |   static mcpPromptArgsToN8N(promptArgs: any): IDataObject {
119 |     return {
120 |       prompt: promptArgs.name || '',
121 |       arguments: promptArgs.arguments || {},
122 |       messages: promptArgs.messages || [],
123 |     };
124 |   }
125 | 
126 |   /**
127 |    * Validate and sanitize data before conversion
128 |    */
129 |   static sanitizeData(data: any): any {
130 |     if (data === null || data === undefined) {
131 |       return {};
132 |     }
133 | 
134 |     if (typeof data !== 'object') {
135 |       return { value: data };
136 |     }
137 | 
138 |     // Remove circular references
139 |     const seen = new WeakSet();
140 |     return JSON.parse(JSON.stringify(data, (_key, value) => {
141 |       if (typeof value === 'object' && value !== null) {
142 |         if (seen.has(value)) {
143 |           return '[Circular]';
144 |         }
145 |         seen.add(value);
146 |       }
147 |       return value;
148 |     }));
149 |   }
150 | 
151 |   /**
152 |    * Extract error information for both n8n and MCP formats
153 |    */
154 |   static formatError(error: any): any {
155 |     return {
156 |       message: error.message || 'Unknown error',
157 |       type: error.name || 'Error',
158 |       stack: error.stack,
159 |       details: {
160 |         code: error.code,
161 |         statusCode: error.statusCode,
162 |         data: error.data,
163 |       },
164 |     };
165 |   }
166 | }
```

--------------------------------------------------------------------------------
/src/mcp/tool-docs/templates/search-templates.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ToolDocumentation } from '../types';
 2 | 
 3 | export const searchTemplatesDoc: ToolDocumentation = {
 4 |   name: 'search_templates',
 5 |   category: 'templates',
 6 |   essentials: {
 7 |     description: 'Search templates by name/description keywords. NOT for node types! For nodes use list_node_templates. Example: "chatbot".',
 8 |     keyParameters: ['query', 'limit', 'fields'],
 9 |     example: 'search_templates({query: "chatbot", fields: ["id", "name"]})',
10 |     performance: 'Fast (<100ms) - FTS5 full-text search',
11 |     tips: [
12 |       'Searches template names and descriptions, NOT node types',
13 |       'Use keywords like "automation", "sync", "notification"',
14 |       'For node-specific search, use list_node_templates instead',
15 |       'Use fields parameter to get only specific data (reduces response by 70-90%)'
16 |     ]
17 |   },
18 |   full: {
19 |     description: `Performs full-text search across workflow template names and descriptions. This tool is ideal for finding workflows based on their purpose or functionality rather than specific nodes used. It searches through the community library of 399+ templates using SQLite FTS5 for fast, fuzzy matching.`,
20 |     parameters: {
21 |       query: {
22 |         type: 'string',
23 |         required: true,
24 |         description: 'Search query for template names/descriptions. NOT for node types! Examples: "chatbot", "automation", "social media", "webhook". For node-based search use list_node_templates instead.'
25 |       },
26 |       fields: {
27 |         type: 'array',
28 |         required: false,
29 |         description: 'Fields to include in response. Options: "id", "name", "description", "author", "nodes", "views", "created", "url", "metadata". Default: all fields. Example: ["id", "name"] for minimal response.'
30 |       },
31 |       limit: {
32 |         type: 'number',
33 |         required: false,
34 |         description: 'Maximum number of results. Default 20, max 100'
35 |       }
36 |     },
37 |     returns: `Returns an object containing:
38 | - templates: Array of matching templates sorted by relevance
39 |   - id: Template ID for retrieval
40 |   - name: Template name (with match highlights)
41 |   - description: What the workflow does
42 |   - author: Creator information
43 |   - nodes: Array of all nodes used
44 |   - views: Popularity metric
45 |   - created: Creation date
46 |   - url: Link to template
47 |   - relevanceScore: Search match score
48 | - totalFound: Total matching templates
49 | - searchQuery: The processed search query
50 | - tip: Helpful hints if no results`,
51 |     examples: [
52 |       'search_templates({query: "chatbot"}) - Find chatbot and conversational AI workflows',
53 |       'search_templates({query: "email notification"}) - Find email alert workflows',
54 |       'search_templates({query: "data sync"}) - Find data synchronization workflows',
55 |       'search_templates({query: "webhook automation", limit: 30}) - Find webhook-based automations',
56 |       'search_templates({query: "social media scheduler"}) - Find social posting workflows',
57 |       'search_templates({query: "slack", fields: ["id", "name"]}) - Get only IDs and names of Slack templates',
58 |       'search_templates({query: "automation", fields: ["id", "name", "description"]}) - Get minimal info for automation templates'
59 |     ],
60 |     useCases: [
61 |       'Find workflows by business purpose',
62 |       'Discover automations for specific use cases',
63 |       'Search by workflow functionality',
64 |       'Find templates by problem they solve',
65 |       'Explore workflows by industry or domain'
66 |     ],
67 |     performance: `Excellent performance with FTS5 indexing:
68 | - Full-text search: <50ms for most queries
69 | - Fuzzy matching enabled for typos
70 | - Relevance-based sorting included
71 | - Searches both title and description
72 | - Returns highlighted matches`,
73 |     bestPractices: [
74 |       'Use descriptive keywords about the workflow purpose',
75 |       'Try multiple related terms if first search has few results',
76 |       'Combine terms for more specific results',
77 |       'Check both name and description in results',
78 |       'Use quotes for exact phrase matching'
79 |     ],
80 |     pitfalls: [
81 |       'Does NOT search by node types - use list_node_templates',
82 |       'Search is case-insensitive but not semantic',
83 |       'Very specific terms may return no results',
84 |       'Descriptions may be brief - check full template',
85 |       'Relevance scoring may not match your expectations'
86 |     ],
87 |     relatedTools: ['list_node_templates', 'get_templates_for_task', 'get_template', 'search_nodes']
88 |   }
89 | };
```

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

```typescript
  1 | /**
  2 |  * Integration Tests: handleDeleteWorkflow
  3 |  *
  4 |  * Tests workflow deletion against a real n8n instance.
  5 |  * Covers successful deletion, error handling, and cleanup verification.
  6 |  */
  7 | 
  8 | import { describe, it, expect, beforeEach, afterEach, afterAll } from 'vitest';
  9 | import { createTestContext, TestContext, createTestWorkflowName } from '../utils/test-context';
 10 | import { getTestN8nClient } from '../utils/n8n-client';
 11 | import { N8nApiClient } from '../../../../src/services/n8n-api-client';
 12 | import { SIMPLE_WEBHOOK_WORKFLOW } from '../utils/fixtures';
 13 | import { cleanupOrphanedWorkflows } from '../utils/cleanup-helpers';
 14 | import { createMcpContext } from '../utils/mcp-context';
 15 | import { InstanceContext } from '../../../../src/types/instance-context';
 16 | import { handleDeleteWorkflow } from '../../../../src/mcp/handlers-n8n-manager';
 17 | 
 18 | describe('Integration: handleDeleteWorkflow', () => {
 19 |   let context: TestContext;
 20 |   let client: N8nApiClient;
 21 |   let mcpContext: InstanceContext;
 22 | 
 23 |   beforeEach(() => {
 24 |     context = createTestContext();
 25 |     client = getTestN8nClient();
 26 |     mcpContext = createMcpContext();
 27 |   });
 28 | 
 29 |   afterEach(async () => {
 30 |     await context.cleanup();
 31 |   });
 32 | 
 33 |   afterAll(async () => {
 34 |     if (!process.env.CI) {
 35 |       await cleanupOrphanedWorkflows();
 36 |     }
 37 |   });
 38 | 
 39 |   // ======================================================================
 40 |   // Successful Deletion
 41 |   // ======================================================================
 42 | 
 43 |   describe('Successful Deletion', () => {
 44 |     it('should delete an existing workflow', async () => {
 45 |       // Create workflow
 46 |       const workflow = {
 47 |         ...SIMPLE_WEBHOOK_WORKFLOW,
 48 |         name: createTestWorkflowName('Delete - Success'),
 49 |         tags: ['mcp-integration-test']
 50 |       };
 51 | 
 52 |       const created = await client.createWorkflow(workflow);
 53 |       expect(created.id).toBeTruthy();
 54 |       if (!created.id) throw new Error('Workflow ID is missing');
 55 | 
 56 |       // Do NOT track workflow since we're testing deletion
 57 |       // context.trackWorkflow(created.id);
 58 | 
 59 |       // Delete using MCP handler
 60 |       const response = await handleDeleteWorkflow(
 61 |         { id: created.id },
 62 |         mcpContext
 63 |       );
 64 | 
 65 |       // Verify MCP response
 66 |       expect(response.success).toBe(true);
 67 |       expect(response.data).toBeDefined();
 68 | 
 69 |       // Verify workflow is actually deleted
 70 |       await expect(async () => {
 71 |         await client.getWorkflow(created.id!);
 72 |       }).rejects.toThrow();
 73 |     });
 74 |   });
 75 | 
 76 |   // ======================================================================
 77 |   // Error Handling
 78 |   // ======================================================================
 79 | 
 80 |   describe('Error Handling', () => {
 81 |     it('should return error for non-existent workflow ID', async () => {
 82 |       const response = await handleDeleteWorkflow(
 83 |         { id: '99999999' },
 84 |         mcpContext
 85 |       );
 86 | 
 87 |       expect(response.success).toBe(false);
 88 |       expect(response.error).toBeDefined();
 89 |     });
 90 |   });
 91 | 
 92 |   // ======================================================================
 93 |   // Cleanup Verification
 94 |   // ======================================================================
 95 | 
 96 |   describe('Cleanup Verification', () => {
 97 |     it('should verify workflow is actually deleted from n8n', async () => {
 98 |       // Create workflow
 99 |       const workflow = {
100 |         ...SIMPLE_WEBHOOK_WORKFLOW,
101 |         name: createTestWorkflowName('Delete - Cleanup Check'),
102 |         tags: ['mcp-integration-test']
103 |       };
104 | 
105 |       const created = await client.createWorkflow(workflow);
106 |       expect(created.id).toBeTruthy();
107 |       if (!created.id) throw new Error('Workflow ID is missing');
108 | 
109 |       // Verify workflow exists
110 |       const beforeDelete = await client.getWorkflow(created.id);
111 |       expect(beforeDelete.id).toBe(created.id);
112 | 
113 |       // Delete workflow
114 |       const deleteResponse = await handleDeleteWorkflow(
115 |         { id: created.id },
116 |         mcpContext
117 |       );
118 | 
119 |       expect(deleteResponse.success).toBe(true);
120 | 
121 |       // Verify workflow no longer exists
122 |       try {
123 |         await client.getWorkflow(created.id);
124 |         // If we reach here, workflow wasn't deleted
125 |         throw new Error('Workflow should have been deleted but still exists');
126 |       } catch (error: any) {
127 |         // Expected: workflow should not be found
128 |         expect(error.message).toMatch(/not found|404/i);
129 |       }
130 |     });
131 |   });
132 | });
133 | 
```

--------------------------------------------------------------------------------
/src/scripts/fetch-templates-robust.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | import { createDatabaseAdapter } from '../database/database-adapter';
  3 | import { TemplateRepository } from '../templates/template-repository';
  4 | import { TemplateFetcher } from '../templates/template-fetcher';
  5 | import * as fs from 'fs';
  6 | import * as path from 'path';
  7 | 
  8 | async function fetchTemplatesRobust() {
  9 |   console.log('🌐 Fetching n8n workflow templates (last year)...\n');
 10 |   
 11 |   // Ensure data directory exists
 12 |   const dataDir = './data';
 13 |   if (!fs.existsSync(dataDir)) {
 14 |     fs.mkdirSync(dataDir, { recursive: true });
 15 |   }
 16 |   
 17 |   // Initialize database
 18 |   const db = await createDatabaseAdapter('./data/nodes.db');
 19 |   
 20 |   // Drop existing templates table to ensure clean schema
 21 |   try {
 22 |     db.exec('DROP TABLE IF EXISTS templates');
 23 |     db.exec('DROP TABLE IF EXISTS templates_fts');
 24 |     console.log('🗑️  Dropped existing templates tables\n');
 25 |   } catch (error) {
 26 |     // Ignore errors if tables don't exist
 27 |   }
 28 |   
 29 |   // Apply schema with updated constraint
 30 |   const schema = fs.readFileSync(path.join(__dirname, '../../src/database/schema.sql'), 'utf8');
 31 |   db.exec(schema);
 32 |   
 33 |   // Create repository and fetcher
 34 |   const repository = new TemplateRepository(db);
 35 |   const fetcher = new TemplateFetcher();
 36 |   
 37 |   // Progress tracking
 38 |   let lastMessage = '';
 39 |   const startTime = Date.now();
 40 |   
 41 |   try {
 42 |     // Fetch template list
 43 |     console.log('📋 Phase 1: Fetching template list from n8n.io API\n');
 44 |     const templates = await fetcher.fetchTemplates((current, total) => {
 45 |       // Clear previous line
 46 |       if (lastMessage) {
 47 |         process.stdout.write('\r' + ' '.repeat(lastMessage.length) + '\r');
 48 |       }
 49 |       
 50 |       const progress = Math.round((current / total) * 100);
 51 |       lastMessage = `📊 Fetching template list: ${current}/${total} (${progress}%)`;
 52 |       process.stdout.write(lastMessage);
 53 |     });
 54 |     
 55 |     console.log('\n');
 56 |     console.log(`✅ Found ${templates.length} templates from last year\n`);
 57 |     
 58 |     // Fetch details and save incrementally
 59 |     console.log('📥 Phase 2: Fetching details and saving to database\n');
 60 |     let saved = 0;
 61 |     let errors = 0;
 62 |     
 63 |     for (let i = 0; i < templates.length; i++) {
 64 |       const template = templates[i];
 65 |       
 66 |       try {
 67 |         // Clear previous line
 68 |         if (lastMessage) {
 69 |           process.stdout.write('\r' + ' '.repeat(lastMessage.length) + '\r');
 70 |         }
 71 |         
 72 |         const progress = Math.round(((i + 1) / templates.length) * 100);
 73 |         lastMessage = `📊 Processing: ${i + 1}/${templates.length} (${progress}%) - Saved: ${saved}, Errors: ${errors}`;
 74 |         process.stdout.write(lastMessage);
 75 |         
 76 |         // Fetch detail
 77 |         const detail = await fetcher.fetchTemplateDetail(template.id);
 78 |         
 79 |         // Save immediately
 80 |         repository.saveTemplate(template, detail);
 81 |         saved++;
 82 |         
 83 |         // Rate limiting
 84 |         await new Promise(resolve => setTimeout(resolve, 200));
 85 |       } catch (error: any) {
 86 |         errors++;
 87 |         console.error(`\n❌ Error processing template ${template.id} (${template.name}): ${error.message}`);
 88 |         // Continue with next template
 89 |       }
 90 |     }
 91 |     
 92 |     console.log('\n');
 93 |     
 94 |     // Get stats
 95 |     const elapsed = Math.round((Date.now() - startTime) / 1000);
 96 |     const stats = await repository.getTemplateStats();
 97 |     
 98 |     console.log('✅ Template fetch complete!\n');
 99 |     console.log('📈 Statistics:');
100 |     console.log(`   - Templates found: ${templates.length}`);
101 |     console.log(`   - Templates saved: ${saved}`);
102 |     console.log(`   - Errors: ${errors}`);
103 |     console.log(`   - Success rate: ${Math.round((saved / templates.length) * 100)}%`);
104 |     console.log(`   - Time elapsed: ${elapsed} seconds`);
105 |     console.log(`   - Average time per template: ${(elapsed / saved).toFixed(2)} seconds`);
106 |     
107 |     if (stats.topUsedNodes && stats.topUsedNodes.length > 0) {
108 |       console.log('\n🔝 Top used nodes:');
109 |       stats.topUsedNodes.slice(0, 10).forEach((node: any, index: number) => {
110 |         console.log(`   ${index + 1}. ${node.node} (${node.count} templates)`);
111 |       });
112 |     }
113 |     
114 |   } catch (error) {
115 |     console.error('\n❌ Fatal error:', error);
116 |     process.exit(1);
117 |   }
118 |   
119 |   // Close database
120 |   if ('close' in db && typeof db.close === 'function') {
121 |     db.close();
122 |   }
123 | }
124 | 
125 | // Run if called directly
126 | if (require.main === module) {
127 |   fetchTemplatesRobust().catch(console.error);
128 | }
129 | 
130 | export { fetchTemplatesRobust };
```

--------------------------------------------------------------------------------
/.github/workflows/docker-build.yml:
--------------------------------------------------------------------------------

```yaml
  1 | # .github/workflows/docker-build.yml
  2 | name: Build and Push Docker Images
  3 | 
  4 | on:
  5 |   push:
  6 |     branches:
  7 |       - main
  8 |     tags:
  9 |       - 'v*'
 10 |     paths-ignore:
 11 |       - '**.md'
 12 |       - '**.txt'
 13 |       - 'docs/**'
 14 |       - 'examples/**'
 15 |       - '.github/FUNDING.yml'
 16 |       - '.github/ISSUE_TEMPLATE/**'
 17 |       - '.github/pull_request_template.md'
 18 |       - '.gitignore'
 19 |       - 'LICENSE*'
 20 |       - 'ATTRIBUTION.md'
 21 |       - 'SECURITY.md'
 22 |       - 'CODE_OF_CONDUCT.md'
 23 |   pull_request:
 24 |     branches:
 25 |       - main
 26 |     paths-ignore:
 27 |       - '**.md'
 28 |       - '**.txt'
 29 |       - 'docs/**'
 30 |       - 'examples/**'
 31 |       - '.github/FUNDING.yml'
 32 |       - '.github/ISSUE_TEMPLATE/**'
 33 |       - '.github/pull_request_template.md'
 34 |       - '.gitignore'
 35 |       - 'LICENSE*'
 36 |       - 'ATTRIBUTION.md'
 37 |       - 'SECURITY.md'
 38 |       - 'CODE_OF_CONDUCT.md'
 39 |   workflow_dispatch:
 40 | 
 41 | env:
 42 |   REGISTRY: ghcr.io
 43 |   IMAGE_NAME: ${{ github.repository }}
 44 | 
 45 | jobs:
 46 |   build:
 47 |     name: Build Docker Image
 48 |     runs-on: ubuntu-latest
 49 |     permissions:
 50 |       contents: read
 51 |       packages: write
 52 |       
 53 |     steps:
 54 |       - name: Checkout repository
 55 |         uses: actions/checkout@v4
 56 |         with:
 57 |           lfs: true
 58 |         
 59 |       - name: Set up QEMU
 60 |         uses: docker/setup-qemu-action@v3
 61 |         
 62 |       - name: Set up Docker Buildx
 63 |         id: buildx
 64 |         uses: docker/setup-buildx-action@v3
 65 |         
 66 |       - name: Log in to GitHub Container Registry
 67 |         if: github.event_name != 'pull_request'
 68 |         uses: docker/login-action@v3
 69 |         with:
 70 |           registry: ${{ env.REGISTRY }}
 71 |           username: ${{ github.actor }}
 72 |           password: ${{ secrets.GITHUB_TOKEN }}
 73 |           
 74 |       - name: Extract metadata
 75 |         id: meta
 76 |         uses: docker/metadata-action@v5
 77 |         with:
 78 |           images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
 79 |           tags: |
 80 |             type=ref,event=branch
 81 |             type=ref,event=pr
 82 |             type=semver,pattern={{version}}
 83 |             type=semver,pattern={{major}}.{{minor}}
 84 |             type=semver,pattern={{major}}
 85 |             type=sha,format=short
 86 |             type=raw,value=latest,enable={{is_default_branch}}
 87 |             
 88 |       - name: Build and push Docker image
 89 |         uses: docker/build-push-action@v5
 90 |         with:
 91 |           context: .
 92 |           no-cache: true
 93 |           platforms: linux/amd64,linux/arm64
 94 |           push: ${{ github.event_name != 'pull_request' }}
 95 |           tags: ${{ steps.meta.outputs.tags }}
 96 |           labels: ${{ steps.meta.outputs.labels }}
 97 |           provenance: false
 98 | 
 99 |   build-railway:
100 |     name: Build Railway Docker Image
101 |     runs-on: ubuntu-latest
102 |     permissions:
103 |       contents: read
104 |       packages: write
105 |       
106 |     steps:
107 |       - name: Checkout repository
108 |         uses: actions/checkout@v4
109 |         with:
110 |           lfs: true
111 |         
112 |       - name: Set up QEMU
113 |         uses: docker/setup-qemu-action@v3
114 |         
115 |       - name: Set up Docker Buildx
116 |         id: buildx
117 |         uses: docker/setup-buildx-action@v3
118 |         
119 |       - name: Log in to GitHub Container Registry
120 |         if: github.event_name != 'pull_request'
121 |         uses: docker/login-action@v3
122 |         with:
123 |           registry: ${{ env.REGISTRY }}
124 |           username: ${{ github.actor }}
125 |           password: ${{ secrets.GITHUB_TOKEN }}
126 |           
127 |       - name: Extract metadata for Railway
128 |         id: meta-railway
129 |         uses: docker/metadata-action@v5
130 |         with:
131 |           images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-railway
132 |           tags: |
133 |             type=ref,event=branch
134 |             type=ref,event=pr
135 |             type=semver,pattern={{version}}
136 |             type=semver,pattern={{major}}.{{minor}}
137 |             type=semver,pattern={{major}}
138 |             type=sha,format=short
139 |             type=raw,value=latest,enable={{is_default_branch}}
140 |             
141 |       - name: Build and push Railway Docker image
142 |         uses: docker/build-push-action@v5
143 |         with:
144 |           context: .
145 |           file: ./Dockerfile.railway
146 |           no-cache: true
147 |           platforms: linux/amd64
148 |           push: ${{ github.event_name != 'pull_request' }}
149 |           tags: ${{ steps.meta-railway.outputs.tags }}
150 |           labels: ${{ steps.meta-railway.outputs.labels }}
151 |           provenance: false
152 | 
153 |   # Nginx build commented out until Phase 2
154 |   # build-nginx:
155 |   #   name: Build nginx-enhanced Docker Image
156 |   #   runs-on: ubuntu-latest
157 |   #   permissions:
158 |   #     contents: read
159 |   #     packages: write
```

--------------------------------------------------------------------------------
/tests/fixtures/database/test-nodes.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |   "nodes": [
  3 |     {
  4 |       "style": "programmatic",
  5 |       "nodeType": "nodes-base.httpRequest",
  6 |       "displayName": "HTTP Request",
  7 |       "description": "Makes HTTP requests and returns the response",
  8 |       "category": "Core Nodes",
  9 |       "properties": [
 10 |         {
 11 |           "name": "url",
 12 |           "displayName": "URL",
 13 |           "type": "string",
 14 |           "required": true,
 15 |           "default": ""
 16 |         },
 17 |         {
 18 |           "name": "method",
 19 |           "displayName": "Method",
 20 |           "type": "options",
 21 |           "options": [
 22 |             { "name": "GET", "value": "GET" },
 23 |             { "name": "POST", "value": "POST" },
 24 |             { "name": "PUT", "value": "PUT" },
 25 |             { "name": "DELETE", "value": "DELETE" }
 26 |           ],
 27 |           "default": "GET"
 28 |         }
 29 |       ],
 30 |       "credentials": [],
 31 |       "isAITool": true,
 32 |       "isTrigger": false,
 33 |       "isWebhook": false,
 34 |       "operations": [],
 35 |       "version": "1",
 36 |       "isVersioned": false,
 37 |       "packageName": "n8n-nodes-base",
 38 |       "documentation": "The HTTP Request node makes HTTP requests and returns the response data."
 39 |     },
 40 |     {
 41 |       "style": "programmatic",
 42 |       "nodeType": "nodes-base.webhook",
 43 |       "displayName": "Webhook",
 44 |       "description": "Receives data from external services via webhooks",
 45 |       "category": "Core Nodes",
 46 |       "properties": [
 47 |         {
 48 |           "name": "httpMethod",
 49 |           "displayName": "HTTP Method",
 50 |           "type": "options",
 51 |           "options": [
 52 |             { "name": "GET", "value": "GET" },
 53 |             { "name": "POST", "value": "POST" }
 54 |           ],
 55 |           "default": "POST"
 56 |         },
 57 |         {
 58 |           "name": "path",
 59 |           "displayName": "Path",
 60 |           "type": "string",
 61 |           "default": "webhook"
 62 |         }
 63 |       ],
 64 |       "credentials": [],
 65 |       "isAITool": false,
 66 |       "isTrigger": true,
 67 |       "isWebhook": true,
 68 |       "operations": [],
 69 |       "version": "1",
 70 |       "isVersioned": false,
 71 |       "packageName": "n8n-nodes-base",
 72 |       "documentation": "The Webhook node creates an endpoint to receive data from external services."
 73 |     },
 74 |     {
 75 |       "style": "declarative",
 76 |       "nodeType": "nodes-base.slack",
 77 |       "displayName": "Slack",
 78 |       "description": "Send messages and interact with Slack",
 79 |       "category": "Communication",
 80 |       "properties": [],
 81 |       "credentials": [
 82 |         {
 83 |           "name": "slackApi",
 84 |           "required": true
 85 |         }
 86 |       ],
 87 |       "isAITool": true,
 88 |       "isTrigger": false,
 89 |       "isWebhook": false,
 90 |       "operations": [
 91 |         {
 92 |           "name": "Message",
 93 |           "value": "message",
 94 |           "operations": [
 95 |             {
 96 |               "name": "Send",
 97 |               "value": "send",
 98 |               "description": "Send a message to a channel or user"
 99 |             }
100 |           ]
101 |         }
102 |       ],
103 |       "version": "2.1",
104 |       "isVersioned": true,
105 |       "packageName": "n8n-nodes-base",
106 |       "documentation": "The Slack node allows you to send messages and interact with Slack workspaces."
107 |     }
108 |   ],
109 |   "templates": [
110 |     {
111 |       "id": 1001,
112 |       "name": "HTTP to Webhook",
113 |       "description": "Fetch data from HTTP and send to webhook",
114 |       "workflow": {
115 |         "nodes": [
116 |           {
117 |             "id": "1",
118 |             "name": "HTTP Request",
119 |             "type": "n8n-nodes-base.httpRequest",
120 |             "position": [250, 300],
121 |             "parameters": {
122 |               "url": "https://api.example.com/data",
123 |               "method": "GET"
124 |             }
125 |           },
126 |           {
127 |             "id": "2",
128 |             "name": "Webhook",
129 |             "type": "n8n-nodes-base.webhook",
130 |             "position": [450, 300],
131 |             "parameters": {
132 |               "path": "data-webhook",
133 |               "httpMethod": "POST"
134 |             }
135 |           }
136 |         ],
137 |         "connections": {
138 |           "HTTP Request": {
139 |             "main": [[{ "node": "Webhook", "type": "main", "index": 0 }]]
140 |           }
141 |         }
142 |       },
143 |       "nodes": [
144 |         { "id": 1, "name": "HTTP Request", "icon": "http" },
145 |         { "id": 2, "name": "Webhook", "icon": "webhook" }
146 |       ],
147 |       "categories": ["Data Processing"],
148 |       "user": {
149 |         "id": 1,
150 |         "name": "Test User",
151 |         "username": "testuser",
152 |         "verified": false
153 |       },
154 |       "views": 150,
155 |       "createdAt": "2024-01-15T10:00:00Z",
156 |       "updatedAt": "2024-01-20T15:30:00Z",
157 |       "totalViews": 150
158 |     }
159 |   ]
160 | }
```

--------------------------------------------------------------------------------
/src/utils/node-type-utils.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Utility functions for working with n8n node types
  3 |  * Provides consistent normalization and transformation of node type strings
  4 |  */
  5 | 
  6 | /**
  7 |  * Normalize a node type to the standard short form
  8 |  * Handles both old-style (n8n-nodes-base.) and new-style (nodes-base.) prefixes
  9 |  *
 10 |  * @example
 11 |  * normalizeNodeType('n8n-nodes-base.httpRequest') // 'nodes-base.httpRequest'
 12 |  * normalizeNodeType('@n8n/n8n-nodes-langchain.openAi') // 'nodes-langchain.openAi'
 13 |  * normalizeNodeType('nodes-base.webhook') // 'nodes-base.webhook' (unchanged)
 14 |  */
 15 | export function normalizeNodeType(type: string): string {
 16 |   if (!type) return type;
 17 | 
 18 |   return type
 19 |     .replace(/^n8n-nodes-base\./, 'nodes-base.')
 20 |     .replace(/^@n8n\/n8n-nodes-langchain\./, 'nodes-langchain.');
 21 | }
 22 | 
 23 | /**
 24 |  * Convert a short-form node type to the full package name
 25 |  *
 26 |  * @example
 27 |  * denormalizeNodeType('nodes-base.httpRequest', 'base') // 'n8n-nodes-base.httpRequest'
 28 |  * denormalizeNodeType('nodes-langchain.openAi', 'langchain') // '@n8n/n8n-nodes-langchain.openAi'
 29 |  */
 30 | export function denormalizeNodeType(type: string, packageType: 'base' | 'langchain'): string {
 31 |   if (!type) return type;
 32 | 
 33 |   if (packageType === 'base') {
 34 |     return type.replace(/^nodes-base\./, 'n8n-nodes-base.');
 35 |   }
 36 | 
 37 |   return type.replace(/^nodes-langchain\./, '@n8n/n8n-nodes-langchain.');
 38 | }
 39 | 
 40 | /**
 41 |  * Extract the node name from a full node type
 42 |  *
 43 |  * @example
 44 |  * extractNodeName('nodes-base.httpRequest') // 'httpRequest'
 45 |  * extractNodeName('n8n-nodes-base.webhook') // 'webhook'
 46 |  */
 47 | export function extractNodeName(type: string): string {
 48 |   if (!type) return '';
 49 | 
 50 |   // First normalize the type
 51 |   const normalized = normalizeNodeType(type);
 52 | 
 53 |   // Extract everything after the last dot
 54 |   const parts = normalized.split('.');
 55 |   return parts[parts.length - 1] || '';
 56 | }
 57 | 
 58 | /**
 59 |  * Get the package prefix from a node type
 60 |  *
 61 |  * @example
 62 |  * getNodePackage('nodes-base.httpRequest') // 'nodes-base'
 63 |  * getNodePackage('nodes-langchain.openAi') // 'nodes-langchain'
 64 |  */
 65 | export function getNodePackage(type: string): string | null {
 66 |   if (!type || !type.includes('.')) return null;
 67 | 
 68 |   // First normalize the type
 69 |   const normalized = normalizeNodeType(type);
 70 | 
 71 |   // Extract everything before the first dot
 72 |   const parts = normalized.split('.');
 73 |   return parts[0] || null;
 74 | }
 75 | 
 76 | /**
 77 |  * Check if a node type is from the base package
 78 |  */
 79 | export function isBaseNode(type: string): boolean {
 80 |   const normalized = normalizeNodeType(type);
 81 |   return normalized.startsWith('nodes-base.');
 82 | }
 83 | 
 84 | /**
 85 |  * Check if a node type is from the langchain package
 86 |  */
 87 | export function isLangChainNode(type: string): boolean {
 88 |   const normalized = normalizeNodeType(type);
 89 |   return normalized.startsWith('nodes-langchain.');
 90 | }
 91 | 
 92 | /**
 93 |  * Validate if a string looks like a valid node type
 94 |  * (has package prefix and node name)
 95 |  */
 96 | export function isValidNodeTypeFormat(type: string): boolean {
 97 |   if (!type || typeof type !== 'string') return false;
 98 | 
 99 |   // Must contain at least one dot
100 |   if (!type.includes('.')) return false;
101 | 
102 |   const parts = type.split('.');
103 | 
104 |   // Must have exactly 2 parts (package and node name)
105 |   if (parts.length !== 2) return false;
106 | 
107 |   // Both parts must be non-empty
108 |   return parts[0].length > 0 && parts[1].length > 0;
109 | }
110 | 
111 | /**
112 |  * Try multiple variations of a node type to find a match
113 |  * Returns an array of variations to try in order
114 |  *
115 |  * @example
116 |  * getNodeTypeVariations('httpRequest')
117 |  * // ['nodes-base.httpRequest', 'n8n-nodes-base.httpRequest', 'nodes-langchain.httpRequest', ...]
118 |  */
119 | export function getNodeTypeVariations(type: string): string[] {
120 |   const variations: string[] = [];
121 | 
122 |   // If it already has a package prefix, try normalized version first
123 |   if (type.includes('.')) {
124 |     variations.push(normalizeNodeType(type));
125 | 
126 |     // Also try the denormalized versions
127 |     const normalized = normalizeNodeType(type);
128 |     if (normalized.startsWith('nodes-base.')) {
129 |       variations.push(denormalizeNodeType(normalized, 'base'));
130 |     } else if (normalized.startsWith('nodes-langchain.')) {
131 |       variations.push(denormalizeNodeType(normalized, 'langchain'));
132 |     }
133 |   } else {
134 |     // No package prefix, try common packages
135 |     variations.push(`nodes-base.${type}`);
136 |     variations.push(`n8n-nodes-base.${type}`);
137 |     variations.push(`nodes-langchain.${type}`);
138 |     variations.push(`@n8n/n8n-nodes-langchain.${type}`);
139 |   }
140 | 
141 |   // Remove duplicates while preserving order
142 |   return [...new Set(variations)];
143 | }
```

--------------------------------------------------------------------------------
/tests/integration/n8n-api/system/health-check.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Integration Tests: handleHealthCheck
  3 |  *
  4 |  * Tests API health check against a real n8n instance.
  5 |  * Covers connectivity verification and feature availability.
  6 |  */
  7 | 
  8 | import { describe, it, expect, beforeEach } from 'vitest';
  9 | import { createMcpContext } from '../utils/mcp-context';
 10 | import { InstanceContext } from '../../../../src/types/instance-context';
 11 | import { handleHealthCheck } from '../../../../src/mcp/handlers-n8n-manager';
 12 | import { HealthCheckResponse } from '../utils/response-types';
 13 | 
 14 | describe('Integration: handleHealthCheck', () => {
 15 |   let mcpContext: InstanceContext;
 16 | 
 17 |   beforeEach(() => {
 18 |     mcpContext = createMcpContext();
 19 |   });
 20 | 
 21 |   // ======================================================================
 22 |   // Successful Health Check
 23 |   // ======================================================================
 24 | 
 25 |   describe('API Available', () => {
 26 |     it('should successfully check n8n API health', async () => {
 27 |       const response = await handleHealthCheck(mcpContext);
 28 | 
 29 |       expect(response.success).toBe(true);
 30 |       expect(response.data).toBeDefined();
 31 | 
 32 |       const data = response.data as HealthCheckResponse;
 33 | 
 34 |       // Verify required fields
 35 |       expect(data).toHaveProperty('status');
 36 |       expect(data).toHaveProperty('apiUrl');
 37 |       expect(data).toHaveProperty('mcpVersion');
 38 |       expect(data).toHaveProperty('versionCheck');
 39 |       expect(data).toHaveProperty('performance');
 40 |       expect(data).toHaveProperty('nextSteps');
 41 | 
 42 |       // Status should be a string (e.g., "ok", "healthy")
 43 |       if (data.status) {
 44 |         expect(typeof data.status).toBe('string');
 45 |       }
 46 | 
 47 |       // API URL should match configuration
 48 |       expect(data.apiUrl).toBeDefined();
 49 |       expect(typeof data.apiUrl).toBe('string');
 50 | 
 51 |       // MCP version should be defined
 52 |       expect(data.mcpVersion).toBeDefined();
 53 |       expect(typeof data.mcpVersion).toBe('string');
 54 | 
 55 |       // Version check should be present
 56 |       expect(data.versionCheck).toBeDefined();
 57 |       expect(data.versionCheck).toHaveProperty('current');
 58 |       expect(data.versionCheck).toHaveProperty('upToDate');
 59 |       expect(typeof data.versionCheck.upToDate).toBe('boolean');
 60 | 
 61 |       // Performance metrics should be present
 62 |       expect(data.performance).toBeDefined();
 63 |       expect(data.performance).toHaveProperty('responseTimeMs');
 64 |       expect(typeof data.performance.responseTimeMs).toBe('number');
 65 |       expect(data.performance.responseTimeMs).toBeGreaterThan(0);
 66 | 
 67 |       // Next steps should be present
 68 |       expect(data.nextSteps).toBeDefined();
 69 |       expect(Array.isArray(data.nextSteps)).toBe(true);
 70 |     });
 71 | 
 72 |     it('should include feature availability information', async () => {
 73 |       const response = await handleHealthCheck(mcpContext);
 74 | 
 75 |       expect(response.success).toBe(true);
 76 |       const data = response.data as HealthCheckResponse;
 77 | 
 78 |       // Check for feature information
 79 |       // Note: Features may vary by n8n instance configuration
 80 |       if (data.features) {
 81 |         expect(typeof data.features).toBe('object');
 82 |       }
 83 | 
 84 |       // Check for version information
 85 |       if (data.n8nVersion) {
 86 |         expect(typeof data.n8nVersion).toBe('string');
 87 |       }
 88 | 
 89 |       if (data.supportedN8nVersion) {
 90 |         expect(typeof data.supportedN8nVersion).toBe('string');
 91 |       }
 92 | 
 93 |       // Should include version note for AI agents
 94 |       if (data.versionNote) {
 95 |         expect(typeof data.versionNote).toBe('string');
 96 |         expect(data.versionNote).toContain('version');
 97 |       }
 98 |     });
 99 |   });
100 | 
101 |   // ======================================================================
102 |   // Response Format Verification
103 |   // ======================================================================
104 | 
105 |   describe('Response Format', () => {
106 |     it('should return complete health check response structure', async () => {
107 |       const response = await handleHealthCheck(mcpContext);
108 | 
109 |       expect(response.success).toBe(true);
110 |       expect(response.data).toBeDefined();
111 | 
112 |       const data = response.data as HealthCheckResponse;
113 | 
114 |       // Verify all expected fields are present
115 |       const expectedFields = ['status', 'apiUrl', 'mcpVersion'];
116 |       expectedFields.forEach(field => {
117 |         expect(data).toHaveProperty(field);
118 |       });
119 | 
120 |       // Optional fields that may be present
121 |       const optionalFields = ['instanceId', 'n8nVersion', 'features', 'supportedN8nVersion', 'versionNote'];
122 |       optionalFields.forEach(field => {
123 |         if (data[field] !== undefined) {
124 |           expect(data[field]).not.toBeNull();
125 |         }
126 |       });
127 |     });
128 |   });
129 | });
130 | 
```

--------------------------------------------------------------------------------
/tests/integration/n8n-api/executions/delete-execution.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Integration Tests: handleDeleteExecution
  3 |  *
  4 |  * Tests execution deletion against a real n8n instance.
  5 |  * Covers successful deletion, error handling, and cleanup verification.
  6 |  */
  7 | 
  8 | import { describe, it, expect, beforeEach, beforeAll } from 'vitest';
  9 | import { createMcpContext } from '../utils/mcp-context';
 10 | import { InstanceContext } from '../../../../src/types/instance-context';
 11 | import { handleDeleteExecution, handleTriggerWebhookWorkflow, handleGetExecution } from '../../../../src/mcp/handlers-n8n-manager';
 12 | import { getN8nCredentials } from '../utils/credentials';
 13 | 
 14 | describe('Integration: handleDeleteExecution', () => {
 15 |   let mcpContext: InstanceContext;
 16 |   let webhookUrl: string;
 17 | 
 18 |   beforeEach(() => {
 19 |     mcpContext = createMcpContext();
 20 |   });
 21 | 
 22 |   beforeAll(() => {
 23 |     const creds = getN8nCredentials();
 24 |     webhookUrl = creds.webhookUrls.get;
 25 |   });
 26 | 
 27 |   // ======================================================================
 28 |   // Successful Deletion
 29 |   // ======================================================================
 30 | 
 31 |   describe('Successful Deletion', () => {
 32 |     it('should delete an execution successfully', async () => {
 33 |       // First, create an execution to delete
 34 |       const triggerResponse = await handleTriggerWebhookWorkflow(
 35 |         {
 36 |           webhookUrl,
 37 |           httpMethod: 'GET',
 38 |           waitForResponse: true
 39 |         },
 40 |         mcpContext
 41 |       );
 42 | 
 43 |       // Try to extract execution ID
 44 |       let executionId: string | undefined;
 45 |       if (triggerResponse.success && triggerResponse.data) {
 46 |         const responseData = triggerResponse.data as any;
 47 |         executionId = responseData.executionId ||
 48 |                       responseData.id ||
 49 |                       responseData.execution?.id ||
 50 |                       responseData.workflowData?.executionId;
 51 |       }
 52 | 
 53 |       if (!executionId) {
 54 |         console.warn('Could not extract execution ID for deletion test');
 55 |         return;
 56 |       }
 57 | 
 58 |       // Delete the execution
 59 |       const response = await handleDeleteExecution(
 60 |         { id: executionId },
 61 |         mcpContext
 62 |       );
 63 | 
 64 |       expect(response.success).toBe(true);
 65 |       expect(response.data).toBeDefined();
 66 |     }, 30000);
 67 | 
 68 |     it('should verify execution is actually deleted', async () => {
 69 |       // Create an execution
 70 |       const triggerResponse = await handleTriggerWebhookWorkflow(
 71 |         {
 72 |           webhookUrl,
 73 |           httpMethod: 'GET',
 74 |           waitForResponse: true
 75 |         },
 76 |         mcpContext
 77 |       );
 78 | 
 79 |       let executionId: string | undefined;
 80 |       if (triggerResponse.success && triggerResponse.data) {
 81 |         const responseData = triggerResponse.data as any;
 82 |         executionId = responseData.executionId ||
 83 |                       responseData.id ||
 84 |                       responseData.execution?.id ||
 85 |                       responseData.workflowData?.executionId;
 86 |       }
 87 | 
 88 |       if (!executionId) {
 89 |         console.warn('Could not extract execution ID for deletion verification test');
 90 |         return;
 91 |       }
 92 | 
 93 |       // Delete it
 94 |       const deleteResponse = await handleDeleteExecution(
 95 |         { id: executionId },
 96 |         mcpContext
 97 |       );
 98 | 
 99 |       expect(deleteResponse.success).toBe(true);
100 | 
101 |       // Try to fetch the deleted execution
102 |       const getResponse = await handleGetExecution(
103 |         { id: executionId },
104 |         mcpContext
105 |       );
106 | 
107 |       // Should fail to find the deleted execution
108 |       expect(getResponse.success).toBe(false);
109 |       expect(getResponse.error).toBeDefined();
110 |     }, 30000);
111 |   });
112 | 
113 |   // ======================================================================
114 |   // Error Handling
115 |   // ======================================================================
116 | 
117 |   describe('Error Handling', () => {
118 |     it('should handle non-existent execution ID', async () => {
119 |       const response = await handleDeleteExecution(
120 |         { id: '99999999' },
121 |         mcpContext
122 |       );
123 | 
124 |       expect(response.success).toBe(false);
125 |       expect(response.error).toBeDefined();
126 |     });
127 | 
128 |     it('should handle invalid execution ID format', async () => {
129 |       const response = await handleDeleteExecution(
130 |         { id: 'invalid-id-format' },
131 |         mcpContext
132 |       );
133 | 
134 |       expect(response.success).toBe(false);
135 |       expect(response.error).toBeDefined();
136 |     });
137 | 
138 |     it('should handle missing execution ID', async () => {
139 |       const response = await handleDeleteExecution(
140 |         {} as any,
141 |         mcpContext
142 |       );
143 | 
144 |       expect(response.success).toBe(false);
145 |       expect(response.error).toBeDefined();
146 |     });
147 |   });
148 | });
149 | 
```

--------------------------------------------------------------------------------
/src/scripts/validate.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | /**
  3 |  * Copyright (c) 2024 AiAdvisors Romuald Czlonkowski
  4 |  * Licensed under the Sustainable Use License v1.0
  5 |  */
  6 | import { createDatabaseAdapter } from '../database/database-adapter';
  7 | 
  8 | interface NodeRow {
  9 |   node_type: string;
 10 |   package_name: string;
 11 |   display_name: string;
 12 |   description?: string;
 13 |   category?: string;
 14 |   development_style?: string;
 15 |   is_ai_tool: number;
 16 |   is_trigger: number;
 17 |   is_webhook: number;
 18 |   is_versioned: number;
 19 |   version?: string;
 20 |   documentation?: string;
 21 |   properties_schema?: string;
 22 |   operations?: string;
 23 |   credentials_required?: string;
 24 |   updated_at: string;
 25 | }
 26 | 
 27 | async function validate() {
 28 |   const db = await createDatabaseAdapter('./data/nodes.db');
 29 |   
 30 |   console.log('🔍 Validating critical nodes...\n');
 31 |   
 32 |   const criticalChecks = [
 33 |     { 
 34 |       type: 'nodes-base.httpRequest', 
 35 |       checks: {
 36 |         hasDocumentation: true,
 37 |         documentationContains: 'HTTP Request',
 38 |         style: 'programmatic'
 39 |       }
 40 |     },
 41 |     { 
 42 |       type: 'nodes-base.code', 
 43 |       checks: {
 44 |         hasDocumentation: true,
 45 |         documentationContains: 'Code'
 46 |       }
 47 |     },
 48 |     { 
 49 |       type: 'nodes-base.slack', 
 50 |       checks: {
 51 |         hasOperations: true,
 52 |         style: 'programmatic'
 53 |       }
 54 |     },
 55 |     {
 56 |       type: 'nodes-langchain.agent',
 57 |       checks: {
 58 |         isAITool: false, // According to the database, it's not marked as AI tool
 59 |         packageName: '@n8n/n8n-nodes-langchain'
 60 |       }
 61 |     }
 62 |   ];
 63 |   
 64 |   let passed = 0;
 65 |   let failed = 0;
 66 |   
 67 |   for (const check of criticalChecks) {
 68 |     const node = db.prepare('SELECT * FROM nodes WHERE node_type = ?').get(check.type) as NodeRow | undefined;
 69 |     
 70 |     if (!node) {
 71 |       console.log(`❌ ${check.type}: NOT FOUND`);
 72 |       failed++;
 73 |       continue;
 74 |     }
 75 |     
 76 |     let nodeOk = true;
 77 |     const issues: string[] = [];
 78 |     
 79 |     // Run checks
 80 |     if (check.checks.hasDocumentation && !node.documentation) {
 81 |       nodeOk = false;
 82 |       issues.push('missing documentation');
 83 |     }
 84 |     
 85 |     if (check.checks.documentationContains && 
 86 |         !node.documentation?.includes(check.checks.documentationContains)) {
 87 |       nodeOk = false;
 88 |       issues.push(`documentation doesn't contain "${check.checks.documentationContains}"`);
 89 |     }
 90 |     
 91 |     if (check.checks.style && node.development_style !== check.checks.style) {
 92 |       nodeOk = false;
 93 |       issues.push(`wrong style: ${node.development_style}`);
 94 |     }
 95 |     
 96 |     if (check.checks.hasOperations) {
 97 |       const operations = JSON.parse(node.operations || '[]');
 98 |       if (!operations.length) {
 99 |         nodeOk = false;
100 |         issues.push('no operations found');
101 |       }
102 |     }
103 |     
104 |     if (check.checks.isAITool !== undefined && !!node.is_ai_tool !== check.checks.isAITool) {
105 |       nodeOk = false;
106 |       issues.push(`AI tool flag mismatch: expected ${check.checks.isAITool}, got ${!!node.is_ai_tool}`);
107 |     }
108 |     
109 |     if ('isVersioned' in check.checks && check.checks.isVersioned && !node.is_versioned) {
110 |       nodeOk = false;
111 |       issues.push('not marked as versioned');
112 |     }
113 |     
114 |     if (check.checks.packageName && node.package_name !== check.checks.packageName) {
115 |       nodeOk = false;
116 |       issues.push(`wrong package: ${node.package_name}`);
117 |     }
118 |     
119 |     if (nodeOk) {
120 |       console.log(`✅ ${check.type}`);
121 |       passed++;
122 |     } else {
123 |       console.log(`❌ ${check.type}: ${issues.join(', ')}`);
124 |       failed++;
125 |     }
126 |   }
127 |   
128 |   console.log(`\n📊 Results: ${passed} passed, ${failed} failed`);
129 |   
130 |   // Additional statistics
131 |   const stats = db.prepare(`
132 |     SELECT 
133 |       COUNT(*) as total,
134 |       SUM(is_ai_tool) as ai_tools,
135 |       SUM(is_trigger) as triggers,
136 |       SUM(is_versioned) as versioned,
137 |       COUNT(DISTINCT package_name) as packages
138 |     FROM nodes
139 |   `).get() as any;
140 |   
141 |   console.log('\n📈 Database Statistics:');
142 |   console.log(`   Total nodes: ${stats.total}`);
143 |   console.log(`   AI tools: ${stats.ai_tools}`);
144 |   console.log(`   Triggers: ${stats.triggers}`);
145 |   console.log(`   Versioned: ${stats.versioned}`);
146 |   console.log(`   Packages: ${stats.packages}`);
147 |   
148 |   // Check documentation coverage
149 |   const docStats = db.prepare(`
150 |     SELECT 
151 |       COUNT(*) as total,
152 |       SUM(CASE WHEN documentation IS NOT NULL THEN 1 ELSE 0 END) as with_docs
153 |     FROM nodes
154 |   `).get() as any;
155 |   
156 |   console.log(`\n📚 Documentation Coverage:`);
157 |   console.log(`   Nodes with docs: ${docStats.with_docs}/${docStats.total} (${Math.round(docStats.with_docs / docStats.total * 100)}%)`);
158 |   
159 |   db.close();
160 |   process.exit(failed > 0 ? 1 : 0);
161 | }
162 | 
163 | if (require.main === module) {
164 |   validate().catch(console.error);
165 | }
```
Page 5/59FirstPrevNextLast