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

# Directory Structure

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

# Files

--------------------------------------------------------------------------------
/tests/error-handler.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, it, expect, vi } from 'vitest';
  2 | import {
  3 |   MCPError,
  4 |   N8NConnectionError,
  5 |   AuthenticationError,
  6 |   ValidationError,
  7 |   ToolNotFoundError,
  8 |   ResourceNotFoundError,
  9 |   handleError,
 10 |   withErrorHandling,
 11 | } from '../src/utils/error-handler';
 12 | import { logger } from '../src/utils/logger';
 13 | 
 14 | // Mock the logger
 15 | vi.mock('../src/utils/logger', () => ({
 16 |   logger: {
 17 |     error: vi.fn(),
 18 |   },
 19 | }));
 20 | 
 21 | describe('Error Classes', () => {
 22 |   describe('MCPError', () => {
 23 |     it('should create error with all properties', () => {
 24 |       const error = new MCPError('Test error', 'TEST_CODE', 400, { field: 'value' });
 25 |       
 26 |       expect(error.message).toBe('Test error');
 27 |       expect(error.code).toBe('TEST_CODE');
 28 |       expect(error.statusCode).toBe(400);
 29 |       expect(error.data).toEqual({ field: 'value' });
 30 |       expect(error.name).toBe('MCPError');
 31 |     });
 32 |   });
 33 | 
 34 |   describe('N8NConnectionError', () => {
 35 |     it('should create connection error with correct code', () => {
 36 |       const error = new N8NConnectionError('Connection failed');
 37 |       
 38 |       expect(error.message).toBe('Connection failed');
 39 |       expect(error.code).toBe('N8N_CONNECTION_ERROR');
 40 |       expect(error.statusCode).toBe(503);
 41 |       expect(error.name).toBe('N8NConnectionError');
 42 |     });
 43 |   });
 44 | 
 45 |   describe('AuthenticationError', () => {
 46 |     it('should create auth error with default message', () => {
 47 |       const error = new AuthenticationError();
 48 |       
 49 |       expect(error.message).toBe('Authentication failed');
 50 |       expect(error.code).toBe('AUTH_ERROR');
 51 |       expect(error.statusCode).toBe(401);
 52 |     });
 53 | 
 54 |     it('should accept custom message', () => {
 55 |       const error = new AuthenticationError('Invalid token');
 56 |       expect(error.message).toBe('Invalid token');
 57 |     });
 58 |   });
 59 | 
 60 |   describe('ValidationError', () => {
 61 |     it('should create validation error', () => {
 62 |       const error = new ValidationError('Invalid input', { field: 'email' });
 63 |       
 64 |       expect(error.message).toBe('Invalid input');
 65 |       expect(error.code).toBe('VALIDATION_ERROR');
 66 |       expect(error.statusCode).toBe(400);
 67 |       expect(error.data).toEqual({ field: 'email' });
 68 |     });
 69 |   });
 70 | 
 71 |   describe('ToolNotFoundError', () => {
 72 |     it('should create tool not found error', () => {
 73 |       const error = new ToolNotFoundError('myTool');
 74 |       
 75 |       expect(error.message).toBe("Tool 'myTool' not found");
 76 |       expect(error.code).toBe('TOOL_NOT_FOUND');
 77 |       expect(error.statusCode).toBe(404);
 78 |     });
 79 |   });
 80 | 
 81 |   describe('ResourceNotFoundError', () => {
 82 |     it('should create resource not found error', () => {
 83 |       const error = new ResourceNotFoundError('workflow://123');
 84 |       
 85 |       expect(error.message).toBe("Resource 'workflow://123' not found");
 86 |       expect(error.code).toBe('RESOURCE_NOT_FOUND');
 87 |       expect(error.statusCode).toBe(404);
 88 |     });
 89 |   });
 90 | });
 91 | 
 92 | describe('handleError', () => {
 93 |   it('should return MCPError instances as-is', () => {
 94 |     const mcpError = new ValidationError('Test');
 95 |     const result = handleError(mcpError);
 96 |     
 97 |     expect(result).toBe(mcpError);
 98 |   });
 99 | 
100 |   it('should handle HTTP 401 errors', () => {
101 |     const httpError = {
102 |       response: { status: 401, data: { message: 'Unauthorized' } },
103 |     };
104 |     
105 |     const result = handleError(httpError);
106 |     
107 |     expect(result).toBeInstanceOf(AuthenticationError);
108 |     expect(result.message).toBe('Unauthorized');
109 |   });
110 | 
111 |   it('should handle HTTP 404 errors', () => {
112 |     const httpError = {
113 |       response: { status: 404, data: { message: 'Not found' } },
114 |     };
115 |     
116 |     const result = handleError(httpError);
117 |     
118 |     expect(result.code).toBe('NOT_FOUND');
119 |     expect(result.statusCode).toBe(404);
120 |   });
121 | 
122 |   it('should handle HTTP 5xx errors', () => {
123 |     const httpError = {
124 |       response: { status: 503, data: { message: 'Service unavailable' } },
125 |     };
126 |     
127 |     const result = handleError(httpError);
128 |     
129 |     expect(result).toBeInstanceOf(N8NConnectionError);
130 |   });
131 | 
132 |   it('should handle connection refused errors', () => {
133 |     const connError = { code: 'ECONNREFUSED' };
134 |     
135 |     const result = handleError(connError);
136 |     
137 |     expect(result).toBeInstanceOf(N8NConnectionError);
138 |     expect(result.message).toBe('Cannot connect to n8n API');
139 |   });
140 | 
141 |   it('should handle generic errors', () => {
142 |     const error = new Error('Something went wrong');
143 |     
144 |     const result = handleError(error);
145 |     
146 |     expect(result.message).toBe('Something went wrong');
147 |     expect(result.code).toBe('UNKNOWN_ERROR');
148 |     expect(result.statusCode).toBe(500);
149 |   });
150 | 
151 |   it('should handle errors without message', () => {
152 |     const error = {};
153 |     
154 |     const result = handleError(error);
155 |     
156 |     expect(result.message).toBe('An unexpected error occurred');
157 |   });
158 | });
159 | 
160 | describe('withErrorHandling', () => {
161 |   it('should execute operation successfully', async () => {
162 |     const operation = vi.fn().mockResolvedValue('success');
163 |     
164 |     const result = await withErrorHandling(operation, 'test operation');
165 |     
166 |     expect(result).toBe('success');
167 |     expect(logger.error).not.toHaveBeenCalled();
168 |   });
169 | 
170 |   it('should handle and log errors', async () => {
171 |     const error = new Error('Operation failed');
172 |     const operation = vi.fn().mockRejectedValue(error);
173 |     
174 |     await expect(withErrorHandling(operation, 'test operation')).rejects.toThrow();
175 |     
176 |     expect(logger.error).toHaveBeenCalledWith('Error in test operation:', error);
177 |   });
178 | 
179 |   it('should transform errors using handleError', async () => {
180 |     const error = { code: 'ECONNREFUSED' };
181 |     const operation = vi.fn().mockRejectedValue(error);
182 |     
183 |     try {
184 |       await withErrorHandling(operation, 'test operation');
185 |     } catch (err) {
186 |       expect(err).toBeInstanceOf(N8NConnectionError);
187 |     }
188 |   });
189 | });
```

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

```typescript
  1 | import { ToolDocumentation } from '../types';
  2 | 
  3 | export const searchTemplatesByMetadataDoc: ToolDocumentation = {
  4 |   name: 'search_templates_by_metadata',
  5 |   category: 'templates',
  6 |   essentials: {
  7 |     description: 'Search templates using AI-generated metadata filters. Find templates by complexity, setup time, required services, or target audience. Enables smart template discovery beyond simple text search.',
  8 |     keyParameters: ['category', 'complexity', 'maxSetupMinutes', 'targetAudience'],
  9 |     example: 'search_templates_by_metadata({complexity: "simple", maxSetupMinutes: 30})',
 10 |     performance: 'Fast (<100ms) - JSON extraction queries',
 11 |     tips: [
 12 |       'All filters are optional - combine them for precise results',
 13 |       'Use getAvailableCategories() to see valid category values',
 14 |       'Complexity levels: simple, medium, complex',
 15 |       'Setup time is in minutes (5-480 range)'
 16 |     ]
 17 |   },
 18 |   full: {
 19 |     description: `Advanced template search using AI-generated metadata. Each template has been analyzed by GPT-4 to extract structured information about its purpose, complexity, setup requirements, and target users. This enables intelligent filtering beyond simple keyword matching, helping you find templates that match your specific needs, skill level, and available time.`,
 20 |     parameters: {
 21 |       category: {
 22 |         type: 'string',
 23 |         required: false,
 24 |         description: 'Filter by category like "automation", "integration", "data processing", "communication". Use template service getAvailableCategories() for full list.'
 25 |       },
 26 |       complexity: {
 27 |         type: 'string (enum)',
 28 |         required: false,
 29 |         description: 'Filter by implementation complexity: "simple" (beginner-friendly), "medium" (some experience needed), or "complex" (advanced features)'
 30 |       },
 31 |       maxSetupMinutes: {
 32 |         type: 'number',
 33 |         required: false,
 34 |         description: 'Maximum acceptable setup time in minutes (5-480). Find templates you can implement within your time budget.'
 35 |       },
 36 |       minSetupMinutes: {
 37 |         type: 'number',
 38 |         required: false,
 39 |         description: 'Minimum setup time in minutes (5-480). Find more substantial templates that offer comprehensive solutions.'
 40 |       },
 41 |       requiredService: {
 42 |         type: 'string',
 43 |         required: false,
 44 |         description: 'Filter by required external service like "openai", "slack", "google", "shopify". Ensures you have necessary accounts/APIs.'
 45 |       },
 46 |       targetAudience: {
 47 |         type: 'string',
 48 |         required: false,
 49 |         description: 'Filter by intended users: "developers", "marketers", "analysts", "operations", "sales". Find templates for your role.'
 50 |       },
 51 |       limit: {
 52 |         type: 'number',
 53 |         required: false,
 54 |         description: 'Maximum results to return. Default 20, max 100.'
 55 |       },
 56 |       offset: {
 57 |         type: 'number',
 58 |         required: false,
 59 |         description: 'Pagination offset for results. Default 0.'
 60 |       }
 61 |     },
 62 |     returns: `Returns an object containing:
 63 | - items: Array of matching templates with full metadata
 64 |   - id: Template ID
 65 |   - name: Template name
 66 |   - description: Purpose and functionality
 67 |   - author: Creator details
 68 |   - nodes: Array of nodes used
 69 |   - views: Popularity count
 70 |   - metadata: AI-generated structured data
 71 |     - categories: Primary use categories
 72 |     - complexity: Difficulty level
 73 |     - use_cases: Specific applications
 74 |     - estimated_setup_minutes: Time to implement
 75 |     - required_services: External dependencies
 76 |     - key_features: Main capabilities
 77 |     - target_audience: Intended users
 78 | - total: Total matching templates
 79 | - filters: Applied filter criteria
 80 | - filterSummary: Human-readable filter description
 81 | - availableCategories: Suggested categories if no results
 82 | - availableAudiences: Suggested audiences if no results
 83 | - tip: Contextual guidance`,
 84 |     examples: [
 85 |       'search_templates_by_metadata({complexity: "simple"}) - Find beginner-friendly templates',
 86 |       'search_templates_by_metadata({category: "automation", maxSetupMinutes: 30}) - Quick automation templates',
 87 |       'search_templates_by_metadata({targetAudience: "marketers"}) - Marketing-focused workflows',
 88 |       'search_templates_by_metadata({requiredService: "openai", complexity: "medium"}) - AI templates with moderate complexity',
 89 |       'search_templates_by_metadata({minSetupMinutes: 60, category: "integration"}) - Comprehensive integration solutions'
 90 |     ],
 91 |     useCases: [
 92 |       'Finding beginner-friendly templates by setting complexity:"simple"',
 93 |       'Discovering templates you can implement quickly with maxSetupMinutes:30',
 94 |       'Finding role-specific workflows with targetAudience filter',
 95 |       'Identifying templates that need specific APIs with requiredService filter',
 96 |       'Combining multiple filters for precise template discovery'
 97 |     ],
 98 |     performance: 'Fast (<100ms) - Uses SQLite JSON extraction on pre-generated metadata. 97.5% coverage (2,534/2,598 templates).',
 99 |     bestPractices: [
100 |       'Start with broad filters and narrow down based on results',
101 |       'Use getAvailableCategories() to discover valid category values',
102 |       'Combine complexity and setup time for skill-appropriate templates',
103 |       'Check required services before selecting templates to ensure you have necessary accounts'
104 |     ],
105 |     pitfalls: [
106 |       'Not all templates have metadata (97.5% coverage)',
107 |       'Setup time estimates assume basic n8n familiarity',
108 |       'Categories/audiences use partial matching - be specific',
109 |       'Metadata is AI-generated and may occasionally be imprecise'
110 |     ],
111 |     relatedTools: [
112 |       'list_templates',
113 |       'search_templates',
114 |       'list_node_templates',
115 |       'get_templates_for_task'
116 |     ]
117 |   }
118 | };
```

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

```typescript
  1 | /**
  2 |  * Normalizes node type from n8n export format to database format
  3 |  * 
  4 |  * Examples:
  5 |  * - 'n8n-nodes-base.httpRequest' → 'nodes-base.httpRequest'
  6 |  * - '@n8n/n8n-nodes-langchain.agent' → 'nodes-langchain.agent'
  7 |  * - 'n8n-nodes-langchain.chatTrigger' → 'nodes-langchain.chatTrigger'
  8 |  * - 'nodes-base.slack' → 'nodes-base.slack' (unchanged)
  9 |  * 
 10 |  * @param nodeType The node type to normalize
 11 |  * @returns The normalized node type
 12 |  */
 13 | export function normalizeNodeType(nodeType: string): string {
 14 |   // Handle n8n-nodes-base -> nodes-base
 15 |   if (nodeType.startsWith('n8n-nodes-base.')) {
 16 |     return nodeType.replace('n8n-nodes-base.', 'nodes-base.');
 17 |   }
 18 |   
 19 |   // Handle @n8n/n8n-nodes-langchain -> nodes-langchain
 20 |   if (nodeType.startsWith('@n8n/n8n-nodes-langchain.')) {
 21 |     return nodeType.replace('@n8n/n8n-nodes-langchain.', 'nodes-langchain.');
 22 |   }
 23 |   
 24 |   // Handle n8n-nodes-langchain -> nodes-langchain (without @n8n/ prefix)
 25 |   if (nodeType.startsWith('n8n-nodes-langchain.')) {
 26 |     return nodeType.replace('n8n-nodes-langchain.', 'nodes-langchain.');
 27 |   }
 28 |   
 29 |   // Return unchanged if already normalized or unknown format
 30 |   return nodeType;
 31 | }
 32 | 
 33 | /**
 34 |  * Gets alternative node type formats to try for lookups
 35 |  *
 36 |  * @param nodeType The original node type
 37 |  * @returns Array of alternative formats to try
 38 |  */
 39 | export function getNodeTypeAlternatives(nodeType: string): string[] {
 40 |   // Defensive: validate input to prevent TypeError when nodeType is undefined/null/empty
 41 |   if (!nodeType || typeof nodeType !== 'string' || nodeType.trim() === '') {
 42 |     return [];
 43 |   }
 44 | 
 45 |   const alternatives: string[] = [];
 46 | 
 47 |   // Add lowercase version
 48 |   alternatives.push(nodeType.toLowerCase());
 49 |   
 50 |   // If it has a prefix, try case variations on the node name part
 51 |   if (nodeType.includes('.')) {
 52 |     const [prefix, nodeName] = nodeType.split('.');
 53 |     
 54 |     // Try different case variations for the node name
 55 |     if (nodeName && nodeName.toLowerCase() !== nodeName) {
 56 |       alternatives.push(`${prefix}.${nodeName.toLowerCase()}`);
 57 |     }
 58 |     
 59 |     // For camelCase names like "chatTrigger", also try with capital first letter variations
 60 |     // e.g., "chattrigger" -> "chatTrigger"
 61 |     if (nodeName && nodeName.toLowerCase() === nodeName && nodeName.length > 1) {
 62 |       // Try to detect common patterns and create camelCase version
 63 |       const camelCaseVariants = generateCamelCaseVariants(nodeName);
 64 |       camelCaseVariants.forEach(variant => {
 65 |         alternatives.push(`${prefix}.${variant}`);
 66 |       });
 67 |     }
 68 |   }
 69 |   
 70 |   // If it's just a bare node name, try with common prefixes
 71 |   if (!nodeType.includes('.')) {
 72 |     alternatives.push(`nodes-base.${nodeType}`);
 73 |     alternatives.push(`nodes-langchain.${nodeType}`);
 74 |     
 75 |     // Also try camelCase variants for bare names
 76 |     const camelCaseVariants = generateCamelCaseVariants(nodeType);
 77 |     camelCaseVariants.forEach(variant => {
 78 |       alternatives.push(`nodes-base.${variant}`);
 79 |       alternatives.push(`nodes-langchain.${variant}`);
 80 |     });
 81 |   }
 82 |   
 83 |   // Normalize all alternatives and combine with originals
 84 |   const normalizedAlternatives = alternatives.map(alt => normalizeNodeType(alt));
 85 |   
 86 |   // Combine original alternatives with normalized ones and remove duplicates
 87 |   return [...new Set([...alternatives, ...normalizedAlternatives])];
 88 | }
 89 | 
 90 | /**
 91 |  * Generate camelCase variants for a lowercase string
 92 |  * @param str The lowercase string
 93 |  * @returns Array of possible camelCase variants
 94 |  */
 95 | function generateCamelCaseVariants(str: string): string[] {
 96 |   const variants: string[] = [];
 97 |   
 98 |   // Common patterns for n8n nodes
 99 |   const patterns = [
100 |     // Pattern: wordTrigger (e.g., chatTrigger, webhookTrigger)
101 |     /^(.+)(trigger|node|request|response)$/i,
102 |     // Pattern: httpRequest, mysqlDatabase
103 |     /^(http|mysql|postgres|mongo|redis|mqtt|smtp|imap|ftp|ssh|api)(.+)$/i,
104 |     // Pattern: googleSheets, microsoftTeams
105 |     /^(google|microsoft|amazon|slack|discord|telegram)(.+)$/i,
106 |   ];
107 |   
108 |   for (const pattern of patterns) {
109 |     const match = str.toLowerCase().match(pattern);
110 |     if (match) {
111 |       const [, first, second] = match;
112 |       // Capitalize the second part
113 |       variants.push(first.toLowerCase() + second.charAt(0).toUpperCase() + second.slice(1).toLowerCase());
114 |     }
115 |   }
116 |   
117 |   // Generic camelCase: capitalize after common word boundaries
118 |   if (variants.length === 0) {
119 |     // Try splitting on common boundaries and capitalizing
120 |     const words = str.split(/[-_\s]+/);
121 |     if (words.length > 1) {
122 |       const camelCase = words[0].toLowerCase() + words.slice(1).map(w => 
123 |         w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()
124 |       ).join('');
125 |       variants.push(camelCase);
126 |     }
127 |   }
128 |   
129 |   return variants;
130 | }
131 | 
132 | /**
133 |  * Constructs the workflow node type from package name and normalized node type
134 |  * This creates the format that n8n expects in workflow definitions
135 |  * 
136 |  * Examples:
137 |  * - ('n8n-nodes-base', 'nodes-base.webhook') → 'n8n-nodes-base.webhook'
138 |  * - ('@n8n/n8n-nodes-langchain', 'nodes-langchain.agent') → '@n8n/n8n-nodes-langchain.agent'
139 |  * 
140 |  * @param packageName The package name from the database
141 |  * @param nodeType The normalized node type from the database
142 |  * @returns The workflow node type for use in n8n workflows
143 |  */
144 | export function getWorkflowNodeType(packageName: string, nodeType: string): string {
145 |   // Extract just the node name from the normalized type
146 |   const nodeName = nodeType.split('.').pop() || nodeType;
147 |   
148 |   // Construct the full workflow type based on package
149 |   if (packageName === 'n8n-nodes-base') {
150 |     return `n8n-nodes-base.${nodeName}`;
151 |   } else if (packageName === '@n8n/n8n-nodes-langchain') {
152 |     return `@n8n/n8n-nodes-langchain.${nodeName}`;
153 |   }
154 |   
155 |   // Fallback for unknown packages - return as is
156 |   return nodeType;
157 | }
```

--------------------------------------------------------------------------------
/docker/parse-config.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | /**
  3 |  * Parse JSON config file and output shell-safe export commands
  4 |  * Only outputs variables that aren't already set in environment
  5 |  * 
  6 |  * Security: Uses safe quoting without any shell execution
  7 |  */
  8 | 
  9 | const fs = require('fs');
 10 | 
 11 | // Debug logging support
 12 | const DEBUG = process.env.DEBUG_CONFIG === 'true';
 13 | 
 14 | function debugLog(message) {
 15 |   if (DEBUG) {
 16 |     process.stderr.write(`[parse-config] ${message}\n`);
 17 |   }
 18 | }
 19 | 
 20 | const configPath = process.argv[2] || '/app/config.json';
 21 | debugLog(`Using config path: ${configPath}`);
 22 | 
 23 | // Dangerous environment variables that should never be set
 24 | const DANGEROUS_VARS = new Set([
 25 |   'PATH', 'LD_PRELOAD', 'LD_LIBRARY_PATH', 'LD_AUDIT',
 26 |   'BASH_ENV', 'ENV', 'CDPATH', 'IFS', 'PS1', 'PS2', 'PS3', 'PS4',
 27 |   'SHELL', 'BASH_FUNC', 'SHELLOPTS', 'GLOBIGNORE',
 28 |   'PERL5LIB', 'PYTHONPATH', 'NODE_PATH', 'RUBYLIB'
 29 | ]);
 30 | 
 31 | /**
 32 |  * Sanitize a key name for use as environment variable
 33 |  * Converts to uppercase and replaces invalid chars with underscore
 34 |  */
 35 | function sanitizeKey(key) {
 36 |   // Convert to string and handle edge cases
 37 |   const keyStr = String(key || '').trim();
 38 |   
 39 |   if (!keyStr) {
 40 |     return 'EMPTY_KEY';
 41 |   }
 42 |   
 43 |   // Special handling for NODE_DB_PATH to preserve exact casing
 44 |   if (keyStr === 'NODE_DB_PATH') {
 45 |     return 'NODE_DB_PATH';
 46 |   }
 47 |   
 48 |   const sanitized = keyStr
 49 |     .toUpperCase()
 50 |     .replace(/[^A-Z0-9]+/g, '_')
 51 |     .replace(/^_+|_+$/g, '') // Trim underscores
 52 |     .replace(/^(\d)/, '_$1'); // Prefix with _ if starts with number
 53 |   
 54 |   // If sanitization results in empty string, use a default
 55 |   return sanitized || 'EMPTY_KEY';
 56 | }
 57 | 
 58 | /**
 59 |  * Safely quote a string for shell use
 60 |  * This follows POSIX shell quoting rules
 61 |  */
 62 | function shellQuote(str) {
 63 |   // Remove null bytes which are not allowed in environment variables
 64 |   str = str.replace(/\x00/g, '');
 65 |   
 66 |   // Always use single quotes for consistency and safety
 67 |   // Single quotes protect everything except other single quotes
 68 |   return "'" + str.replace(/'/g, "'\"'\"'") + "'";
 69 | }
 70 | 
 71 | try {
 72 |   if (!fs.existsSync(configPath)) {
 73 |     debugLog(`Config file not found at: ${configPath}`);
 74 |     process.exit(0); // Silent exit if no config file
 75 |   }
 76 | 
 77 |   let configContent;
 78 |   let config;
 79 |   
 80 |   try {
 81 |     configContent = fs.readFileSync(configPath, 'utf8');
 82 |     debugLog(`Read config file, size: ${configContent.length} bytes`);
 83 |   } catch (readError) {
 84 |     // Silent exit on read errors
 85 |     debugLog(`Error reading config: ${readError.message}`);
 86 |     process.exit(0);
 87 |   }
 88 |   
 89 |   try {
 90 |     config = JSON.parse(configContent);
 91 |     debugLog(`Parsed config with ${Object.keys(config).length} top-level keys`);
 92 |   } catch (parseError) {
 93 |     // Silent exit on invalid JSON
 94 |     debugLog(`Error parsing JSON: ${parseError.message}`);
 95 |     process.exit(0);
 96 |   }
 97 |   
 98 |   // Validate config is an object
 99 |   if (typeof config !== 'object' || config === null || Array.isArray(config)) {
100 |     // Silent exit on invalid config structure
101 |     process.exit(0);
102 |   }
103 |   
104 |   // Convert nested objects to flat environment variables
105 |   const flattenConfig = (obj, prefix = '', depth = 0) => {
106 |     const result = {};
107 |     
108 |     // Prevent infinite recursion
109 |     if (depth > 10) {
110 |       return result;
111 |     }
112 |     
113 |     for (const [key, value] of Object.entries(obj)) {
114 |       const sanitizedKey = sanitizeKey(key);
115 |       
116 |       // Skip if sanitization resulted in EMPTY_KEY (indicating invalid key)
117 |       if (sanitizedKey === 'EMPTY_KEY') {
118 |         debugLog(`Skipping key '${key}': invalid key name`);
119 |         continue;
120 |       }
121 |       
122 |       const envKey = prefix ? `${prefix}_${sanitizedKey}` : sanitizedKey;
123 |       
124 |       // Skip if key is too long
125 |       if (envKey.length > 255) {
126 |         debugLog(`Skipping key '${envKey}': too long (${envKey.length} chars)`);
127 |         continue;
128 |       }
129 |       
130 |       if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
131 |         // Recursively flatten nested objects
132 |         Object.assign(result, flattenConfig(value, envKey, depth + 1));
133 |       } else if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
134 |         // Only include if not already set in environment
135 |         if (!process.env[envKey]) {
136 |           let stringValue = String(value);
137 |           
138 |           // Handle special JavaScript number values
139 |           if (typeof value === 'number') {
140 |             if (!isFinite(value)) {
141 |               if (value === Infinity) {
142 |                 stringValue = 'Infinity';
143 |               } else if (value === -Infinity) {
144 |                 stringValue = '-Infinity';
145 |               } else if (isNaN(value)) {
146 |                 stringValue = 'NaN';
147 |               }
148 |             }
149 |           }
150 |           
151 |           // Skip if value is too long
152 |           if (stringValue.length <= 32768) {
153 |             result[envKey] = stringValue;
154 |           }
155 |         }
156 |       }
157 |     }
158 |     
159 |     return result;
160 |   };
161 |   
162 |   // Output shell-safe export commands
163 |   const flattened = flattenConfig(config);
164 |   const exports = [];
165 |   
166 |   for (const [key, value] of Object.entries(flattened)) {
167 |     // Validate key name (alphanumeric and underscore only)
168 |     if (!/^[A-Z_][A-Z0-9_]*$/.test(key)) {
169 |       continue; // Skip invalid variable names
170 |     }
171 |     
172 |     // Skip dangerous variables
173 |     if (DANGEROUS_VARS.has(key) || key.startsWith('BASH_FUNC_')) {
174 |       debugLog(`Warning: Ignoring dangerous variable: ${key}`);
175 |       process.stderr.write(`Warning: Ignoring dangerous variable: ${key}\n`);
176 |       continue;
177 |     }
178 |     
179 |     // Safely quote the value
180 |     const quotedValue = shellQuote(value);
181 |     exports.push(`export ${key}=${quotedValue}`);
182 |   }
183 |   
184 |   // Use process.stdout.write to ensure output goes to stdout
185 |   if (exports.length > 0) {
186 |     process.stdout.write(exports.join('\n') + '\n');
187 |   }
188 |   
189 | } catch (error) {
190 |   // Silent fail - don't break the container startup
191 |   process.exit(0);
192 | }
```

--------------------------------------------------------------------------------
/src/mcp/handlers-workflow-diff.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * MCP Handler for Partial Workflow Updates
  3 |  * Handles diff-based workflow modifications
  4 |  */
  5 | 
  6 | import { z } from 'zod';
  7 | import { McpToolResponse } from '../types/n8n-api';
  8 | import { WorkflowDiffRequest, WorkflowDiffOperation } from '../types/workflow-diff';
  9 | import { WorkflowDiffEngine } from '../services/workflow-diff-engine';
 10 | import { getN8nApiClient } from './handlers-n8n-manager';
 11 | import { N8nApiError, getUserFriendlyErrorMessage } from '../utils/n8n-errors';
 12 | import { logger } from '../utils/logger';
 13 | import { InstanceContext } from '../types/instance-context';
 14 | 
 15 | // Zod schema for the diff request
 16 | const workflowDiffSchema = z.object({
 17 |   id: z.string(),
 18 |   operations: z.array(z.object({
 19 |     type: z.string(),
 20 |     description: z.string().optional(),
 21 |     // Node operations
 22 |     node: z.any().optional(),
 23 |     nodeId: z.string().optional(),
 24 |     nodeName: z.string().optional(),
 25 |     updates: z.any().optional(),
 26 |     position: z.tuple([z.number(), z.number()]).optional(),
 27 |     // Connection operations
 28 |     source: z.string().optional(),
 29 |     target: z.string().optional(),
 30 |     from: z.string().optional(),  // For rewireConnection
 31 |     to: z.string().optional(),    // For rewireConnection
 32 |     sourceOutput: z.string().optional(),
 33 |     targetInput: z.string().optional(),
 34 |     sourceIndex: z.number().optional(),
 35 |     targetIndex: z.number().optional(),
 36 |     // Smart parameters (Phase 1 UX improvement)
 37 |     branch: z.enum(['true', 'false']).optional(),
 38 |     case: z.number().optional(),
 39 |     ignoreErrors: z.boolean().optional(),
 40 |     // Connection cleanup operations
 41 |     dryRun: z.boolean().optional(),
 42 |     connections: z.any().optional(),
 43 |     // Metadata operations
 44 |     settings: z.any().optional(),
 45 |     name: z.string().optional(),
 46 |     tag: z.string().optional(),
 47 |   })),
 48 |   validateOnly: z.boolean().optional(),
 49 |   continueOnError: z.boolean().optional(),
 50 | });
 51 | 
 52 | export async function handleUpdatePartialWorkflow(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
 53 |   try {
 54 |     // Debug logging (only in debug mode)
 55 |     if (process.env.DEBUG_MCP === 'true') {
 56 |       logger.debug('Workflow diff request received', {
 57 |         argsType: typeof args,
 58 |         hasWorkflowId: args && typeof args === 'object' && 'workflowId' in args,
 59 |         operationCount: args && typeof args === 'object' && 'operations' in args ? 
 60 |           (args as any).operations?.length : 0
 61 |       });
 62 |     }
 63 |     
 64 |     // Validate input
 65 |     const input = workflowDiffSchema.parse(args);
 66 |     
 67 |     // Get API client
 68 |     const client = getN8nApiClient(context);
 69 |     if (!client) {
 70 |       return {
 71 |         success: false,
 72 |         error: 'n8n API not configured. Please set N8N_API_URL and N8N_API_KEY environment variables.'
 73 |       };
 74 |     }
 75 |     
 76 |     // Fetch current workflow
 77 |     let workflow;
 78 |     try {
 79 |       workflow = await client.getWorkflow(input.id);
 80 |     } catch (error) {
 81 |       if (error instanceof N8nApiError) {
 82 |         return {
 83 |           success: false,
 84 |           error: getUserFriendlyErrorMessage(error),
 85 |           code: error.code
 86 |         };
 87 |       }
 88 |       throw error;
 89 |     }
 90 |     
 91 |     // Apply diff operations
 92 |     const diffEngine = new WorkflowDiffEngine();
 93 |     const diffRequest = input as WorkflowDiffRequest;
 94 |     const diffResult = await diffEngine.applyDiff(workflow, diffRequest);
 95 | 
 96 |     // Check if this is a complete failure or partial success in continueOnError mode
 97 |     if (!diffResult.success) {
 98 |       // In continueOnError mode, partial success is still valuable
 99 |       if (diffRequest.continueOnError && diffResult.workflow && diffResult.operationsApplied && diffResult.operationsApplied > 0) {
100 |         logger.info(`continueOnError mode: Applying ${diffResult.operationsApplied} successful operations despite ${diffResult.failed?.length || 0} failures`);
101 |         // Continue to update workflow with partial changes
102 |       } else {
103 |         // Complete failure - return error
104 |         return {
105 |           success: false,
106 |           error: 'Failed to apply diff operations',
107 |           details: {
108 |             errors: diffResult.errors,
109 |             operationsApplied: diffResult.operationsApplied,
110 |             applied: diffResult.applied,
111 |             failed: diffResult.failed
112 |           }
113 |         };
114 |       }
115 |     }
116 |     
117 |     // If validateOnly, return validation result
118 |     if (input.validateOnly) {
119 |       return {
120 |         success: true,
121 |         message: diffResult.message,
122 |         data: {
123 |           valid: true,
124 |           operationsToApply: input.operations.length
125 |         }
126 |       };
127 |     }
128 |     
129 |     // Update workflow via API
130 |     try {
131 |       const updatedWorkflow = await client.updateWorkflow(input.id, diffResult.workflow!);
132 |       
133 |       return {
134 |         success: true,
135 |         data: updatedWorkflow,
136 |         message: `Workflow "${updatedWorkflow.name}" updated successfully. Applied ${diffResult.operationsApplied} operations.`,
137 |         details: {
138 |           operationsApplied: diffResult.operationsApplied,
139 |           workflowId: updatedWorkflow.id,
140 |           workflowName: updatedWorkflow.name,
141 |           applied: diffResult.applied,
142 |           failed: diffResult.failed,
143 |           errors: diffResult.errors
144 |         }
145 |       };
146 |     } catch (error) {
147 |       if (error instanceof N8nApiError) {
148 |         return {
149 |           success: false,
150 |           error: getUserFriendlyErrorMessage(error),
151 |           code: error.code,
152 |           details: error.details as Record<string, unknown> | undefined
153 |         };
154 |       }
155 |       throw error;
156 |     }
157 |   } catch (error) {
158 |     if (error instanceof z.ZodError) {
159 |       return {
160 |         success: false,
161 |         error: 'Invalid input',
162 |         details: { errors: error.errors }
163 |       };
164 |     }
165 |     
166 |     logger.error('Failed to update partial workflow', error);
167 |     return {
168 |       success: false,
169 |       error: error instanceof Error ? error.message : 'Unknown error occurred'
170 |     };
171 |   }
172 | }
173 | 
174 | 
```

--------------------------------------------------------------------------------
/tests/test-enhanced-integration.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | const { DocumentationFetcher } = require('../dist/utils/documentation-fetcher');
  4 | const { NodeDocumentationService } = require('../dist/services/node-documentation-service');
  5 | 
  6 | async function testEnhancedIntegration() {
  7 |   console.log('🧪 Testing Enhanced Documentation Integration...\n');
  8 | 
  9 |   // Test 1: DocumentationFetcher backward compatibility
 10 |   console.log('1️⃣ Testing DocumentationFetcher backward compatibility...');
 11 |   const docFetcher = new DocumentationFetcher();
 12 |   
 13 |   try {
 14 |     // Test getNodeDocumentation (backward compatible method)
 15 |     const simpleDoc = await docFetcher.getNodeDocumentation('n8n-nodes-base.slack');
 16 |     if (simpleDoc) {
 17 |       console.log('   ✅ Simple documentation format works');
 18 |       console.log(`   - Has markdown: ${!!simpleDoc.markdown}`);
 19 |       console.log(`   - Has URL: ${!!simpleDoc.url}`);
 20 |       console.log(`   - Has examples: ${simpleDoc.examples?.length || 0}`);
 21 |     }
 22 | 
 23 |     // Test getEnhancedNodeDocumentation (new method)
 24 |     const enhancedDoc = await docFetcher.getEnhancedNodeDocumentation('n8n-nodes-base.slack');
 25 |     if (enhancedDoc) {
 26 |       console.log('   ✅ Enhanced documentation format works');
 27 |       console.log(`   - Title: ${enhancedDoc.title || 'N/A'}`);
 28 |       console.log(`   - Operations: ${enhancedDoc.operations?.length || 0}`);
 29 |       console.log(`   - API Methods: ${enhancedDoc.apiMethods?.length || 0}`);
 30 |       console.log(`   - Examples: ${enhancedDoc.examples?.length || 0}`);
 31 |       console.log(`   - Templates: ${enhancedDoc.templates?.length || 0}`);
 32 |       console.log(`   - Related Resources: ${enhancedDoc.relatedResources?.length || 0}`);
 33 |     }
 34 |   } catch (error) {
 35 |     console.error('   ❌ DocumentationFetcher test failed:', error.message);
 36 |   }
 37 | 
 38 |   // Test 2: NodeDocumentationService with enhanced fields
 39 |   console.log('\n2️⃣ Testing NodeDocumentationService enhanced schema...');
 40 |   const docService = new NodeDocumentationService('data/test-enhanced-docs.db');
 41 |   
 42 |   try {
 43 |     // Store a test node with enhanced documentation
 44 |     const testNode = {
 45 |       nodeType: 'test.enhanced-node',
 46 |       name: 'enhanced-node',
 47 |       displayName: 'Enhanced Test Node',
 48 |       description: 'A test node with enhanced documentation',
 49 |       sourceCode: 'const testCode = "example";',
 50 |       packageName: 'test-package',
 51 |       documentation: '# Test Documentation',
 52 |       documentationUrl: 'https://example.com/docs',
 53 |       documentationTitle: 'Enhanced Test Node Documentation',
 54 |       operations: [
 55 |         {
 56 |           resource: 'Message',
 57 |           operation: 'Send',
 58 |           description: 'Send a message'
 59 |         }
 60 |       ],
 61 |       apiMethods: [
 62 |         {
 63 |           resource: 'Message',
 64 |           operation: 'Send',
 65 |           apiMethod: 'chat.postMessage',
 66 |           apiUrl: 'https://api.slack.com/methods/chat.postMessage'
 67 |         }
 68 |       ],
 69 |       documentationExamples: [
 70 |         {
 71 |           title: 'Send Message Example',
 72 |           type: 'json',
 73 |           code: '{"text": "Hello World"}'
 74 |         }
 75 |       ],
 76 |       templates: [
 77 |         {
 78 |           name: 'Basic Message Template',
 79 |           description: 'Simple message sending template'
 80 |         }
 81 |       ],
 82 |       relatedResources: [
 83 |         {
 84 |           title: 'API Documentation',
 85 |           url: 'https://api.slack.com',
 86 |           type: 'api'
 87 |         }
 88 |       ],
 89 |       requiredScopes: ['chat:write'],
 90 |       hasCredentials: true,
 91 |       isTrigger: false,
 92 |       isWebhook: false
 93 |     };
 94 | 
 95 |     await docService.storeNode(testNode);
 96 |     console.log('   ✅ Stored node with enhanced documentation');
 97 | 
 98 |     // Retrieve and verify
 99 |     const retrieved = await docService.getNodeInfo('test.enhanced-node');
100 |     if (retrieved) {
101 |       console.log('   ✅ Retrieved node with enhanced fields:');
102 |       console.log(`   - Has operations: ${!!retrieved.operations}`);
103 |       console.log(`   - Has API methods: ${!!retrieved.apiMethods}`);
104 |       console.log(`   - Has documentation examples: ${!!retrieved.documentationExamples}`);
105 |       console.log(`   - Has templates: ${!!retrieved.templates}`);
106 |       console.log(`   - Has related resources: ${!!retrieved.relatedResources}`);
107 |       console.log(`   - Has required scopes: ${!!retrieved.requiredScopes}`);
108 |     }
109 | 
110 |     // Test search
111 |     const searchResults = await docService.searchNodes({ query: 'enhanced' });
112 |     console.log(`   ✅ Search found ${searchResults.length} results`);
113 | 
114 |   } catch (error) {
115 |     console.error('   ❌ NodeDocumentationService test failed:', error.message);
116 |   } finally {
117 |     docService.close();
118 |   }
119 | 
120 |   // Test 3: MCP Server integration
121 |   console.log('\n3️⃣ Testing MCP Server integration...');
122 |   try {
123 |     const { N8NMCPServer } = require('../dist/mcp/server');
124 |     console.log('   ✅ MCP Server loads with enhanced documentation support');
125 |     
126 |     // Check if new tools are available
127 |     const { n8nTools } = require('../dist/mcp/tools');
128 |     const enhancedTools = [
129 |       'get_node_documentation',
130 |       'search_node_documentation',
131 |       'get_node_operations',
132 |       'get_node_examples'
133 |     ];
134 |     
135 |     const hasAllTools = enhancedTools.every(toolName => 
136 |       n8nTools.some(tool => tool.name === toolName)
137 |     );
138 |     
139 |     if (hasAllTools) {
140 |       console.log('   ✅ All enhanced documentation tools are available');
141 |       enhancedTools.forEach(toolName => {
142 |         const tool = n8nTools.find(t => t.name === toolName);
143 |         console.log(`   - ${toolName}: ${tool.description}`);
144 |       });
145 |     } else {
146 |       console.log('   ⚠️  Some enhanced tools are missing');
147 |     }
148 |     
149 |   } catch (error) {
150 |     console.error('   ❌ MCP Server integration test failed:', error.message);
151 |   }
152 | 
153 |   console.log('\n✨ Enhanced documentation integration tests completed!');
154 |   
155 |   // Cleanup
156 |   await docFetcher.cleanup();
157 | }
158 | 
159 | // Run tests
160 | testEnhancedIntegration().catch(error => {
161 |   console.error('Fatal error:', error);
162 |   process.exit(1);
163 | });
```

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

```typescript
  1 | /**
  2 |  * Integration Test Credentials Management
  3 |  *
  4 |  * Provides environment-aware credential loading for integration tests.
  5 |  * - Local development: Reads from .env file
  6 |  * - CI/GitHub Actions: Uses GitHub secrets from process.env
  7 |  */
  8 | 
  9 | import dotenv from 'dotenv';
 10 | import path from 'path';
 11 | 
 12 | // Load .env file for local development
 13 | dotenv.config({ path: path.resolve(process.cwd(), '.env') });
 14 | 
 15 | export interface N8nTestCredentials {
 16 |   url: string;
 17 |   apiKey: string;
 18 |   webhookUrls: {
 19 |     get: string;
 20 |     post: string;
 21 |     put: string;
 22 |     delete: string;
 23 |   };
 24 |   cleanup: {
 25 |     enabled: boolean;
 26 |     tag: string;
 27 |     namePrefix: string;
 28 |   };
 29 | }
 30 | 
 31 | /**
 32 |  * Get n8n credentials for integration tests
 33 |  *
 34 |  * Automatically detects environment (local vs CI) and loads
 35 |  * credentials from the appropriate source.
 36 |  *
 37 |  * @returns N8nTestCredentials
 38 |  * @throws Error if required credentials are missing
 39 |  */
 40 | export function getN8nCredentials(): N8nTestCredentials {
 41 |   if (process.env.CI) {
 42 |     // CI: Use GitHub secrets - validate required variables first
 43 |     const url = process.env.N8N_API_URL;
 44 |     const apiKey = process.env.N8N_API_KEY;
 45 | 
 46 |     if (!url || !apiKey) {
 47 |       throw new Error(
 48 |         'Missing required CI credentials:\n' +
 49 |         `  N8N_API_URL: ${url ? 'set' : 'MISSING'}\n` +
 50 |         `  N8N_API_KEY: ${apiKey ? 'set' : 'MISSING'}\n` +
 51 |         'Please configure GitHub secrets for integration tests.'
 52 |       );
 53 |     }
 54 | 
 55 |     return {
 56 |       url,
 57 |       apiKey,
 58 |       webhookUrls: {
 59 |         get: process.env.N8N_TEST_WEBHOOK_GET_URL || '',
 60 |         post: process.env.N8N_TEST_WEBHOOK_POST_URL || '',
 61 |         put: process.env.N8N_TEST_WEBHOOK_PUT_URL || '',
 62 |         delete: process.env.N8N_TEST_WEBHOOK_DELETE_URL || ''
 63 |       },
 64 |       cleanup: {
 65 |         enabled: true,
 66 |         tag: 'mcp-integration-test',
 67 |         namePrefix: '[MCP-TEST]'
 68 |       }
 69 |     };
 70 |   } else {
 71 |     // Local: Use .env file - validate required variables first
 72 |     const url = process.env.N8N_API_URL;
 73 |     const apiKey = process.env.N8N_API_KEY;
 74 | 
 75 |     if (!url || !apiKey) {
 76 |       throw new Error(
 77 |         'Missing required credentials in .env:\n' +
 78 |         `  N8N_API_URL: ${url ? 'set' : 'MISSING'}\n` +
 79 |         `  N8N_API_KEY: ${apiKey ? 'set' : 'MISSING'}\n\n` +
 80 |         'Please add these to your .env file.\n' +
 81 |         'See .env.example for configuration details.'
 82 |       );
 83 |     }
 84 | 
 85 |     return {
 86 |       url,
 87 |       apiKey,
 88 |       webhookUrls: {
 89 |         get: process.env.N8N_TEST_WEBHOOK_GET_URL || '',
 90 |         post: process.env.N8N_TEST_WEBHOOK_POST_URL || '',
 91 |         put: process.env.N8N_TEST_WEBHOOK_PUT_URL || '',
 92 |         delete: process.env.N8N_TEST_WEBHOOK_DELETE_URL || ''
 93 |       },
 94 |       cleanup: {
 95 |         enabled: process.env.N8N_TEST_CLEANUP_ENABLED !== 'false',
 96 |         tag: process.env.N8N_TEST_TAG || 'mcp-integration-test',
 97 |         namePrefix: process.env.N8N_TEST_NAME_PREFIX || '[MCP-TEST]'
 98 |       }
 99 |     };
100 |   }
101 | }
102 | 
103 | /**
104 |  * Validate that required credentials are present
105 |  *
106 |  * @param creds - Credentials to validate
107 |  * @throws Error if required credentials are missing
108 |  */
109 | export function validateCredentials(creds: N8nTestCredentials): void {
110 |   const missing: string[] = [];
111 | 
112 |   if (!creds.url) {
113 |     missing.push(process.env.CI ? 'N8N_URL' : 'N8N_API_URL');
114 |   }
115 |   if (!creds.apiKey) {
116 |     missing.push('N8N_API_KEY');
117 |   }
118 | 
119 |   if (missing.length > 0) {
120 |     throw new Error(
121 |       `Missing required n8n credentials: ${missing.join(', ')}\n\n` +
122 |       `Please set the following environment variables:\n` +
123 |       missing.map(v => `  ${v}`).join('\n') + '\n\n' +
124 |       `See .env.example for configuration details.`
125 |     );
126 |   }
127 | }
128 | 
129 | /**
130 |  * Validate that webhook URLs are configured
131 |  *
132 |  * @param creds - Credentials to validate
133 |  * @throws Error with setup instructions if webhook URLs are missing
134 |  */
135 | export function validateWebhookUrls(creds: N8nTestCredentials): void {
136 |   const missing: string[] = [];
137 | 
138 |   if (!creds.webhookUrls.get) missing.push('GET');
139 |   if (!creds.webhookUrls.post) missing.push('POST');
140 |   if (!creds.webhookUrls.put) missing.push('PUT');
141 |   if (!creds.webhookUrls.delete) missing.push('DELETE');
142 | 
143 |   if (missing.length > 0) {
144 |     const envVars = missing.map(m => `N8N_TEST_WEBHOOK_${m}_URL`);
145 | 
146 |     throw new Error(
147 |       `Missing webhook URLs for HTTP methods: ${missing.join(', ')}\n\n` +
148 |       `Webhook testing requires pre-activated workflows in n8n.\n` +
149 |       `n8n API doesn't support workflow activation, so these must be created manually.\n\n` +
150 |       `Setup Instructions:\n` +
151 |       `1. Create ${missing.length} workflow(s) in your n8n instance\n` +
152 |       `2. Each workflow should have a single Webhook node\n` +
153 |       `3. Configure webhook paths:\n` +
154 |       missing.map(m => `   - ${m}: mcp-test-${m.toLowerCase()}`).join('\n') + '\n' +
155 |       `4. ACTIVATE each workflow in n8n UI\n` +
156 |       `5. Set the following environment variables with full webhook URLs:\n` +
157 |       envVars.map(v => `   ${v}=<full-webhook-url>`).join('\n') + '\n\n' +
158 |       `Example: N8N_TEST_WEBHOOK_GET_URL=https://n8n-test.n8n-mcp.com/webhook/mcp-test-get\n\n` +
159 |       `See docs/local/integration-testing-plan.md for detailed instructions.`
160 |     );
161 |   }
162 | }
163 | 
164 | /**
165 |  * Check if credentials are configured (non-throwing version)
166 |  *
167 |  * @returns true if basic credentials are available
168 |  */
169 | export function hasCredentials(): boolean {
170 |   try {
171 |     const creds = getN8nCredentials();
172 |     return !!(creds.url && creds.apiKey);
173 |   } catch {
174 |     return false;
175 |   }
176 | }
177 | 
178 | /**
179 |  * Check if webhook URLs are configured (non-throwing version)
180 |  *
181 |  * @returns true if all webhook URLs are available
182 |  */
183 | export function hasWebhookUrls(): boolean {
184 |   try {
185 |     const creds = getN8nCredentials();
186 |     return !!(
187 |       creds.webhookUrls.get &&
188 |       creds.webhookUrls.post &&
189 |       creds.webhookUrls.put &&
190 |       creds.webhookUrls.delete
191 |     );
192 |   } catch {
193 |     return false;
194 |   }
195 | }
196 | 
```

--------------------------------------------------------------------------------
/src/utils/npm-version-checker.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * NPM Version Checker Utility
  3 |  *
  4 |  * Checks if the current n8n-mcp version is outdated by comparing
  5 |  * against the latest version published on npm.
  6 |  */
  7 | 
  8 | import { logger } from './logger';
  9 | 
 10 | /**
 11 |  * NPM Registry Response structure
 12 |  * Based on npm registry JSON format for package metadata
 13 |  */
 14 | interface NpmRegistryResponse {
 15 |   version: string;
 16 |   [key: string]: unknown;
 17 | }
 18 | 
 19 | export interface VersionCheckResult {
 20 |   currentVersion: string;
 21 |   latestVersion: string | null;
 22 |   isOutdated: boolean;
 23 |   updateAvailable: boolean;
 24 |   error: string | null;
 25 |   checkedAt: Date;
 26 |   updateCommand?: string;
 27 | }
 28 | 
 29 | // Cache for version check to avoid excessive npm requests
 30 | let versionCheckCache: VersionCheckResult | null = null;
 31 | let lastCheckTime: number = 0;
 32 | const CACHE_TTL_MS = 1 * 60 * 60 * 1000; // 1 hour cache
 33 | 
 34 | /**
 35 |  * Check if current version is outdated compared to npm registry
 36 |  * Uses caching to avoid excessive npm API calls
 37 |  *
 38 |  * @param forceRefresh - Force a fresh check, bypassing cache
 39 |  * @returns Version check result
 40 |  */
 41 | export async function checkNpmVersion(forceRefresh: boolean = false): Promise<VersionCheckResult> {
 42 |   const now = Date.now();
 43 | 
 44 |   // Return cached result if available and not expired
 45 |   if (!forceRefresh && versionCheckCache && (now - lastCheckTime) < CACHE_TTL_MS) {
 46 |     logger.debug('Returning cached npm version check result');
 47 |     return versionCheckCache;
 48 |   }
 49 | 
 50 |   // Get current version from package.json
 51 |   const packageJson = require('../../package.json');
 52 |   const currentVersion = packageJson.version;
 53 | 
 54 |   try {
 55 |     // Fetch latest version from npm registry
 56 |     const response = await fetch('https://registry.npmjs.org/n8n-mcp/latest', {
 57 |       headers: {
 58 |         'Accept': 'application/json',
 59 |       },
 60 |       signal: AbortSignal.timeout(5000) // 5 second timeout
 61 |     });
 62 | 
 63 |     if (!response.ok) {
 64 |       logger.warn('Failed to fetch npm version info', {
 65 |         status: response.status,
 66 |         statusText: response.statusText
 67 |       });
 68 | 
 69 |       const result: VersionCheckResult = {
 70 |         currentVersion,
 71 |         latestVersion: null,
 72 |         isOutdated: false,
 73 |         updateAvailable: false,
 74 |         error: `npm registry returned ${response.status}`,
 75 |         checkedAt: new Date()
 76 |       };
 77 | 
 78 |       versionCheckCache = result;
 79 |       lastCheckTime = now;
 80 |       return result;
 81 |     }
 82 | 
 83 |     // Parse and validate JSON response
 84 |     let data: unknown;
 85 |     try {
 86 |       data = await response.json();
 87 |     } catch (error) {
 88 |       throw new Error('Failed to parse npm registry response as JSON');
 89 |     }
 90 | 
 91 |     // Validate response structure
 92 |     if (!data || typeof data !== 'object' || !('version' in data)) {
 93 |       throw new Error('Invalid response format from npm registry');
 94 |     }
 95 | 
 96 |     const registryData = data as NpmRegistryResponse;
 97 |     const latestVersion = registryData.version;
 98 | 
 99 |     // Validate version format (semver: x.y.z or x.y.z-prerelease)
100 |     if (!latestVersion || !/^\d+\.\d+\.\d+/.test(latestVersion)) {
101 |       throw new Error(`Invalid version format from npm registry: ${latestVersion}`);
102 |     }
103 | 
104 |     // Compare versions
105 |     const isOutdated = compareVersions(currentVersion, latestVersion) < 0;
106 | 
107 |     const result: VersionCheckResult = {
108 |       currentVersion,
109 |       latestVersion,
110 |       isOutdated,
111 |       updateAvailable: isOutdated,
112 |       error: null,
113 |       checkedAt: new Date(),
114 |       updateCommand: isOutdated ? `npm install -g n8n-mcp@${latestVersion}` : undefined
115 |     };
116 | 
117 |     // Cache the result
118 |     versionCheckCache = result;
119 |     lastCheckTime = now;
120 | 
121 |     logger.debug('npm version check completed', {
122 |       current: currentVersion,
123 |       latest: latestVersion,
124 |       outdated: isOutdated
125 |     });
126 | 
127 |     return result;
128 | 
129 |   } catch (error) {
130 |     logger.warn('Error checking npm version', {
131 |       error: error instanceof Error ? error.message : String(error)
132 |     });
133 | 
134 |     const result: VersionCheckResult = {
135 |       currentVersion,
136 |       latestVersion: null,
137 |       isOutdated: false,
138 |       updateAvailable: false,
139 |       error: error instanceof Error ? error.message : 'Unknown error',
140 |       checkedAt: new Date()
141 |     };
142 | 
143 |     // Cache error result to avoid rapid retry
144 |     versionCheckCache = result;
145 |     lastCheckTime = now;
146 | 
147 |     return result;
148 |   }
149 | }
150 | 
151 | /**
152 |  * Compare two semantic version strings
153 |  * Returns: -1 if v1 < v2, 0 if v1 === v2, 1 if v1 > v2
154 |  *
155 |  * @param v1 - First version (e.g., "1.2.3")
156 |  * @param v2 - Second version (e.g., "1.3.0")
157 |  * @returns Comparison result
158 |  */
159 | export function compareVersions(v1: string, v2: string): number {
160 |   // Remove 'v' prefix if present
161 |   const clean1 = v1.replace(/^v/, '');
162 |   const clean2 = v2.replace(/^v/, '');
163 | 
164 |   // Split into parts and convert to numbers
165 |   const parts1 = clean1.split('.').map(n => parseInt(n, 10) || 0);
166 |   const parts2 = clean2.split('.').map(n => parseInt(n, 10) || 0);
167 | 
168 |   // Compare each part
169 |   for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
170 |     const p1 = parts1[i] || 0;
171 |     const p2 = parts2[i] || 0;
172 | 
173 |     if (p1 < p2) return -1;
174 |     if (p1 > p2) return 1;
175 |   }
176 | 
177 |   return 0; // Versions are equal
178 | }
179 | 
180 | /**
181 |  * Clear the version check cache (useful for testing)
182 |  */
183 | export function clearVersionCheckCache(): void {
184 |   versionCheckCache = null;
185 |   lastCheckTime = 0;
186 | }
187 | 
188 | /**
189 |  * Format version check result as a user-friendly message
190 |  *
191 |  * @param result - Version check result
192 |  * @returns Formatted message
193 |  */
194 | export function formatVersionMessage(result: VersionCheckResult): string {
195 |   if (result.error) {
196 |     return `Version check failed: ${result.error}. Current version: ${result.currentVersion}`;
197 |   }
198 | 
199 |   if (!result.latestVersion) {
200 |     return `Current version: ${result.currentVersion} (latest version unknown)`;
201 |   }
202 | 
203 |   if (result.isOutdated) {
204 |     return `⚠️ Update available! Current: ${result.currentVersion} → Latest: ${result.latestVersion}`;
205 |   }
206 | 
207 |   return `✓ You're up to date! Current version: ${result.currentVersion}`;
208 | }
209 | 
```

--------------------------------------------------------------------------------
/scripts/test-ai-validation-debug.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | /**
  3 |  * Debug test for AI validation issues
  4 |  * Reproduces the bugs found by n8n-mcp-tester
  5 |  */
  6 | 
  7 | import { validateAISpecificNodes, buildReverseConnectionMap } from '../src/services/ai-node-validator';
  8 | import type { WorkflowJson } from '../src/services/ai-tool-validators';
  9 | import { NodeTypeNormalizer } from '../src/utils/node-type-normalizer';
 10 | 
 11 | console.log('=== AI Validation Debug Tests ===\n');
 12 | 
 13 | // Test 1: AI Agent with NO language model connection
 14 | console.log('Test 1: Missing Language Model Detection');
 15 | const workflow1: WorkflowJson = {
 16 |   name: 'Test Missing LM',
 17 |   nodes: [
 18 |     {
 19 |       id: 'ai-agent-1',
 20 |       name: 'AI Agent',
 21 |       type: '@n8n/n8n-nodes-langchain.agent',
 22 |       position: [500, 300],
 23 |       parameters: {
 24 |         promptType: 'define',
 25 |         text: 'You are a helpful assistant'
 26 |       },
 27 |       typeVersion: 1.7
 28 |     }
 29 |   ],
 30 |   connections: {
 31 |     // NO connections - AI Agent is isolated
 32 |   }
 33 | };
 34 | 
 35 | console.log('Workflow:', JSON.stringify(workflow1, null, 2));
 36 | 
 37 | const reverseMap1 = buildReverseConnectionMap(workflow1);
 38 | console.log('\nReverse connection map for AI Agent:');
 39 | console.log('Entries:', Array.from(reverseMap1.entries()));
 40 | console.log('AI Agent connections:', reverseMap1.get('AI Agent'));
 41 | 
 42 | // Check node normalization
 43 | const normalizedType1 = NodeTypeNormalizer.normalizeToFullForm(workflow1.nodes[0].type);
 44 | console.log(`\nNode type: ${workflow1.nodes[0].type}`);
 45 | console.log(`Normalized type: ${normalizedType1}`);
 46 | console.log(`Match check: ${normalizedType1 === '@n8n/n8n-nodes-langchain.agent'}`);
 47 | 
 48 | const issues1 = validateAISpecificNodes(workflow1);
 49 | console.log('\nValidation issues:');
 50 | console.log(JSON.stringify(issues1, null, 2));
 51 | 
 52 | const hasMissingLMError = issues1.some(
 53 |   i => i.severity === 'error' && i.code === 'MISSING_LANGUAGE_MODEL'
 54 | );
 55 | console.log(`\n✓ Has MISSING_LANGUAGE_MODEL error: ${hasMissingLMError}`);
 56 | console.log(`✗ Expected: true, Got: ${hasMissingLMError}`);
 57 | 
 58 | // Test 2: AI Agent WITH language model connection
 59 | console.log('\n\n' + '='.repeat(60));
 60 | console.log('Test 2: AI Agent WITH Language Model (Should be valid)');
 61 | const workflow2: WorkflowJson = {
 62 |   name: 'Test With LM',
 63 |   nodes: [
 64 |     {
 65 |       id: 'openai-1',
 66 |       name: 'OpenAI Chat Model',
 67 |       type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',
 68 |       position: [200, 300],
 69 |       parameters: {
 70 |         modelName: 'gpt-4'
 71 |       },
 72 |       typeVersion: 1
 73 |     },
 74 |     {
 75 |       id: 'ai-agent-1',
 76 |       name: 'AI Agent',
 77 |       type: '@n8n/n8n-nodes-langchain.agent',
 78 |       position: [500, 300],
 79 |       parameters: {
 80 |         promptType: 'define',
 81 |         text: 'You are a helpful assistant'
 82 |       },
 83 |       typeVersion: 1.7
 84 |     }
 85 |   ],
 86 |   connections: {
 87 |     'OpenAI Chat Model': {
 88 |       ai_languageModel: [
 89 |         [
 90 |           {
 91 |             node: 'AI Agent',
 92 |             type: 'ai_languageModel',
 93 |             index: 0
 94 |           }
 95 |         ]
 96 |       ]
 97 |     }
 98 |   }
 99 | };
100 | 
101 | console.log('\nConnections:', JSON.stringify(workflow2.connections, null, 2));
102 | 
103 | const reverseMap2 = buildReverseConnectionMap(workflow2);
104 | console.log('\nReverse connection map for AI Agent:');
105 | console.log('AI Agent connections:', reverseMap2.get('AI Agent'));
106 | 
107 | const issues2 = validateAISpecificNodes(workflow2);
108 | console.log('\nValidation issues:');
109 | console.log(JSON.stringify(issues2, null, 2));
110 | 
111 | const hasMissingLMError2 = issues2.some(
112 |   i => i.severity === 'error' && i.code === 'MISSING_LANGUAGE_MODEL'
113 | );
114 | console.log(`\n✓ Should NOT have MISSING_LANGUAGE_MODEL error: ${!hasMissingLMError2}`);
115 | console.log(`Expected: false, Got: ${hasMissingLMError2}`);
116 | 
117 | // Test 3: AI Agent with tools but no language model
118 | console.log('\n\n' + '='.repeat(60));
119 | console.log('Test 3: AI Agent with Tools but NO Language Model');
120 | const workflow3: WorkflowJson = {
121 |   name: 'Test Tools No LM',
122 |   nodes: [
123 |     {
124 |       id: 'http-tool-1',
125 |       name: 'HTTP Request Tool',
126 |       type: '@n8n/n8n-nodes-langchain.toolHttpRequest',
127 |       position: [200, 300],
128 |       parameters: {
129 |         toolDescription: 'Calls an API',
130 |         url: 'https://api.example.com'
131 |       },
132 |       typeVersion: 1.1
133 |     },
134 |     {
135 |       id: 'ai-agent-1',
136 |       name: 'AI Agent',
137 |       type: '@n8n/n8n-nodes-langchain.agent',
138 |       position: [500, 300],
139 |       parameters: {
140 |         promptType: 'define',
141 |         text: 'You are a helpful assistant'
142 |       },
143 |       typeVersion: 1.7
144 |     }
145 |   ],
146 |   connections: {
147 |     'HTTP Request Tool': {
148 |       ai_tool: [
149 |         [
150 |           {
151 |             node: 'AI Agent',
152 |             type: 'ai_tool',
153 |             index: 0
154 |           }
155 |         ]
156 |       ]
157 |     }
158 |   }
159 | };
160 | 
161 | console.log('\nConnections:', JSON.stringify(workflow3.connections, null, 2));
162 | 
163 | const reverseMap3 = buildReverseConnectionMap(workflow3);
164 | console.log('\nReverse connection map for AI Agent:');
165 | const aiAgentConns = reverseMap3.get('AI Agent');
166 | console.log('AI Agent connections:', aiAgentConns);
167 | console.log('Connection types:', aiAgentConns?.map(c => c.type));
168 | 
169 | const issues3 = validateAISpecificNodes(workflow3);
170 | console.log('\nValidation issues:');
171 | console.log(JSON.stringify(issues3, null, 2));
172 | 
173 | const hasMissingLMError3 = issues3.some(
174 |   i => i.severity === 'error' && i.code === 'MISSING_LANGUAGE_MODEL'
175 | );
176 | const hasNoToolsInfo3 = issues3.some(
177 |   i => i.severity === 'info' && i.message.includes('no ai_tool connections')
178 | );
179 | 
180 | console.log(`\n✓ Should have MISSING_LANGUAGE_MODEL error: ${hasMissingLMError3}`);
181 | console.log(`Expected: true, Got: ${hasMissingLMError3}`);
182 | console.log(`✗ Should NOT have "no tools" info: ${!hasNoToolsInfo3}`);
183 | console.log(`Expected: false, Got: ${hasNoToolsInfo3}`);
184 | 
185 | console.log('\n' + '='.repeat(60));
186 | console.log('Summary:');
187 | console.log(`Test 1 (No LM): ${hasMissingLMError ? 'PASS ✓' : 'FAIL ✗'}`);
188 | console.log(`Test 2 (With LM): ${!hasMissingLMError2 ? 'PASS ✓' : 'FAIL ✗'}`);
189 | console.log(`Test 3 (Tools, No LM): ${hasMissingLMError3 && !hasNoToolsInfo3 ? 'PASS ✓' : 'FAIL ✗'}`);
190 | 
```

--------------------------------------------------------------------------------
/scripts/update-and-publish-prep.sh:
--------------------------------------------------------------------------------

```bash
  1 | #!/bin/bash
  2 | # Comprehensive script to update n8n dependencies, run tests, and prepare for npm publish
  3 | # Based on MEMORY_N8N_UPDATE.md but enhanced with test suite and publish preparation
  4 | 
  5 | set -e
  6 | 
  7 | # Color codes for output
  8 | RED='\033[0;31m'
  9 | GREEN='\033[0;32m'
 10 | YELLOW='\033[1;33m'
 11 | BLUE='\033[0;34m'
 12 | NC='\033[0m' # No Color
 13 | 
 14 | echo -e "${BLUE}🚀 n8n Update and Publish Preparation Script${NC}"
 15 | echo "=============================================="
 16 | echo ""
 17 | 
 18 | # 1. Check current branch
 19 | CURRENT_BRANCH=$(git branch --show-current)
 20 | if [ "$CURRENT_BRANCH" != "main" ]; then
 21 |     echo -e "${YELLOW}⚠️  Warning: Not on main branch (current: $CURRENT_BRANCH)${NC}"
 22 |     echo "It's recommended to run this on the main branch."
 23 |     read -p "Continue anyway? (y/N) " -n 1 -r
 24 |     echo
 25 |     if [[ ! $REPLY =~ ^[Yy]$ ]]; then
 26 |         exit 1
 27 |     fi
 28 | fi
 29 | 
 30 | # 2. Check for uncommitted changes
 31 | if ! git diff-index --quiet HEAD --; then
 32 |     echo -e "${RED}❌ Error: You have uncommitted changes${NC}"
 33 |     echo "Please commit or stash your changes before updating."
 34 |     exit 1
 35 | fi
 36 | 
 37 | # 3. Get current versions for comparison
 38 | echo -e "${BLUE}📊 Current versions:${NC}"
 39 | CURRENT_N8N=$(node -e "console.log(require('./package.json').dependencies['n8n'])" 2>/dev/null || echo "not installed")
 40 | CURRENT_PROJECT=$(node -e "console.log(require('./package.json').version)")
 41 | echo "- n8n: $CURRENT_N8N"
 42 | echo "- n8n-mcp: $CURRENT_PROJECT"
 43 | echo ""
 44 | 
 45 | # 4. Check for updates first
 46 | echo -e "${BLUE}🔍 Checking for n8n updates...${NC}"
 47 | npm run update:n8n:check
 48 | 
 49 | echo ""
 50 | read -p "Do you want to proceed with the update? (y/N) " -n 1 -r
 51 | echo
 52 | if [[ ! $REPLY =~ ^[Yy]$ ]]; then
 53 |     echo "Update cancelled."
 54 |     exit 0
 55 | fi
 56 | 
 57 | # 5. Update n8n dependencies
 58 | echo ""
 59 | echo -e "${BLUE}📦 Updating n8n dependencies...${NC}"
 60 | npm run update:n8n
 61 | 
 62 | # 6. Run the test suite
 63 | echo ""
 64 | echo -e "${BLUE}🧪 Running comprehensive test suite (1,182 tests)...${NC}"
 65 | npm test
 66 | if [ $? -ne 0 ]; then
 67 |     echo -e "${RED}❌ Tests failed! Please fix failing tests before proceeding.${NC}"
 68 |     exit 1
 69 | fi
 70 | echo -e "${GREEN}✅ All tests passed!${NC}"
 71 | 
 72 | # 7. Run validation
 73 | echo ""
 74 | echo -e "${BLUE}✔️  Validating critical nodes...${NC}"
 75 | npm run validate
 76 | 
 77 | # 8. Build the project
 78 | echo ""
 79 | echo -e "${BLUE}🔨 Building project...${NC}"
 80 | npm run build
 81 | 
 82 | # 9. Bump version
 83 | echo ""
 84 | echo -e "${BLUE}📌 Bumping version...${NC}"
 85 | # Get new n8n version
 86 | NEW_N8N=$(node -e "console.log(require('./package.json').dependencies['n8n'])")
 87 | # Bump patch version
 88 | npm version patch --no-git-tag-version
 89 | 
 90 | # Get new project version
 91 | NEW_PROJECT=$(node -e "console.log(require('./package.json').version)")
 92 | 
 93 | # 10. Update n8n version badge in README
 94 | echo ""
 95 | echo -e "${BLUE}📝 Updating n8n version badge...${NC}"
 96 | sed -i.bak "s/n8n-v[0-9.]*/n8n-$NEW_N8N/" README.md && rm README.md.bak
 97 | 
 98 | # 11. Sync runtime version (this also updates the version badge in README)
 99 | echo ""
100 | echo -e "${BLUE}🔄 Syncing runtime version and updating version badge...${NC}"
101 | npm run sync:runtime-version
102 | 
103 | # 12. Get update details for commit message
104 | echo ""
105 | echo -e "${BLUE}📊 Gathering update information...${NC}"
106 | # Get all n8n package versions
107 | N8N_CORE=$(node -e "console.log(require('./package.json').dependencies['n8n-core'])")
108 | N8N_WORKFLOW=$(node -e "console.log(require('./package.json').dependencies['n8n-workflow'])")
109 | N8N_LANGCHAIN=$(node -e "console.log(require('./package.json').dependencies['@n8n/n8n-nodes-langchain'])")
110 | 
111 | # Get node count from database
112 | NODE_COUNT=$(node -e "
113 | const Database = require('better-sqlite3');
114 | const db = new Database('./data/nodes.db', { readonly: true });
115 | const count = db.prepare('SELECT COUNT(*) as count FROM nodes').get().count;
116 | console.log(count);
117 | db.close();
118 | " 2>/dev/null || echo "unknown")
119 | 
120 | # Check if templates were sanitized
121 | TEMPLATES_SANITIZED=false
122 | if [ -f "./data/nodes.db" ]; then
123 |     TEMPLATE_COUNT=$(node -e "
124 |     const Database = require('better-sqlite3');
125 |     const db = new Database('./data/nodes.db', { readonly: true });
126 |     const count = db.prepare('SELECT COUNT(*) as count FROM templates').get().count;
127 |     console.log(count);
128 |     db.close();
129 |     " 2>/dev/null || echo "0")
130 |     if [ "$TEMPLATE_COUNT" != "0" ]; then
131 |         TEMPLATES_SANITIZED=true
132 |     fi
133 | fi
134 | 
135 | # 13. Create commit message
136 | echo ""
137 | echo -e "${BLUE}📝 Creating commit...${NC}"
138 | COMMIT_MSG="chore: update n8n to $NEW_N8N and bump version to $NEW_PROJECT
139 | 
140 | - Updated n8n to $NEW_N8N
141 | - Updated n8n-core to $N8N_CORE
142 | - Updated n8n-workflow to $N8N_WORKFLOW
143 | - Updated @n8n/n8n-nodes-langchain to $N8N_LANGCHAIN
144 | - Rebuilt node database with $NODE_COUNT nodes"
145 | 
146 | if [ "$TEMPLATES_SANITIZED" = true ]; then
147 |     COMMIT_MSG="$COMMIT_MSG
148 | - Sanitized $TEMPLATE_COUNT workflow templates"
149 | fi
150 | 
151 | COMMIT_MSG="$COMMIT_MSG
152 | - All 1,182 tests passing (933 unit, 249 integration)
153 | - All validation tests passing
154 | - Built and prepared for npm publish
155 | 
156 | 🤖 Generated with [Claude Code](https://claude.ai/code)
157 | 
158 | Co-Authored-By: Claude <[email protected]>"
159 | 
160 | # 14. Stage all changes
161 | git add -A
162 | 
163 | # 15. Show what will be committed
164 | echo ""
165 | echo -e "${BLUE}📋 Changes to be committed:${NC}"
166 | git status --short
167 | 
168 | # 16. Commit changes
169 | git commit -m "$COMMIT_MSG"
170 | 
171 | # 17. Summary
172 | echo ""
173 | echo -e "${GREEN}✅ Update completed successfully!${NC}"
174 | echo ""
175 | echo -e "${BLUE}Summary:${NC}"
176 | echo "- Updated n8n from $CURRENT_N8N to $NEW_N8N"
177 | echo "- Bumped version from $CURRENT_PROJECT to $NEW_PROJECT"
178 | echo "- All 1,182 tests passed"
179 | echo "- Project built and ready for npm publish"
180 | echo ""
181 | echo -e "${YELLOW}Next steps:${NC}"
182 | echo "1. Push to GitHub:"
183 | echo -e "   ${GREEN}git push origin $CURRENT_BRANCH${NC}"
184 | echo ""
185 | echo "2. Create a GitHub release (after push):"
186 | echo -e "   ${GREEN}gh release create v$NEW_PROJECT --title \"v$NEW_PROJECT\" --notes \"Updated n8n to $NEW_N8N\"${NC}"
187 | echo ""
188 | echo "3. Publish to npm:"
189 | echo -e "   ${GREEN}npm run prepare:publish${NC}"
190 | echo "   Then follow the instructions to publish with OTP"
191 | echo ""
192 | echo -e "${BLUE}🎉 Done!${NC}"
```

--------------------------------------------------------------------------------
/scripts/test-node-type-validation.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env tsx
  2 | 
  3 | /**
  4 |  * Test script for node type validation
  5 |  * Tests the improvements to catch invalid node types like "nodes-base.webhook"
  6 |  */
  7 | 
  8 | import { WorkflowValidator } from '../src/services/workflow-validator';
  9 | import { EnhancedConfigValidator } from '../src/services/enhanced-config-validator';
 10 | import { NodeRepository } from '../src/database/node-repository';
 11 | import { createDatabaseAdapter } from '../src/database/database-adapter';
 12 | import { validateWorkflowStructure } from '../src/services/n8n-validation';
 13 | import { Logger } from '../src/utils/logger';
 14 | 
 15 | const logger = new Logger({ prefix: '[TestNodeTypeValidation]' });
 16 | 
 17 | async function testValidation() {
 18 |   const adapter = await createDatabaseAdapter('./data/nodes.db');
 19 |   const repository = new NodeRepository(adapter);
 20 |   const validator = new WorkflowValidator(repository, EnhancedConfigValidator);
 21 | 
 22 |   logger.info('Testing node type validation...\n');
 23 | 
 24 |   // Test 1: The exact broken workflow from Claude Desktop
 25 |   const brokenWorkflowFromLogs = {
 26 |     "nodes": [
 27 |       {
 28 |         "parameters": {},
 29 |         "id": "webhook_node",
 30 |         "name": "Webhook",
 31 |         "type": "nodes-base.webhook", // WRONG! Missing n8n- prefix
 32 |         "typeVersion": 2,
 33 |         "position": [260, 300] as [number, number]
 34 |       }
 35 |     ],
 36 |     "connections": {},
 37 |     "pinData": {},
 38 |     "meta": {
 39 |       "instanceId": "74e11c77e266f2c77f6408eb6c88e3fec63c9a5d8c4a3a2ea4c135c542012d6b"
 40 |     }
 41 |   };
 42 | 
 43 |   logger.info('Test 1: Invalid node type "nodes-base.webhook" (missing n8n- prefix)');
 44 |   const result1 = await validator.validateWorkflow(brokenWorkflowFromLogs as any);
 45 |   
 46 |   logger.info('Validation result:');
 47 |   logger.info(`Valid: ${result1.valid}`);
 48 |   logger.info(`Errors: ${result1.errors.length}`);
 49 |   result1.errors.forEach(err => {
 50 |     if (typeof err === 'string') {
 51 |       logger.error(`  - ${err}`);
 52 |     } else if (err && typeof err === 'object' && 'message' in err) {
 53 |       logger.error(`  - ${err.message}`);
 54 |     }
 55 |   });
 56 |   
 57 |   // Check if the specific error about nodes-base.webhook was caught
 58 |   const hasNodeBaseError = result1.errors.some(err => 
 59 |     err && typeof err === 'object' && 'message' in err && 
 60 |     err.message.includes('nodes-base.webhook') && 
 61 |     err.message.includes('n8n-nodes-base.webhook')
 62 |   );
 63 |   logger.info(`Caught nodes-base.webhook error: ${hasNodeBaseError ? 'YES ✅' : 'NO ❌'}`);
 64 | 
 65 |   // Test 2: Node type without any prefix
 66 |   const noPrefixWorkflow = {
 67 |     "name": "Test Workflow",
 68 |     "nodes": [
 69 |       {
 70 |         "id": "webhook-1",
 71 |         "name": "My Webhook",
 72 |         "type": "webhook", // WRONG! No package prefix
 73 |         "typeVersion": 2,
 74 |         "position": [250, 300] as [number, number],
 75 |         "parameters": {}
 76 |       },
 77 |       {
 78 |         "id": "set-1",
 79 |         "name": "Set Data",
 80 |         "type": "set", // WRONG! No package prefix
 81 |         "typeVersion": 3.4,
 82 |         "position": [450, 300] as [number, number],
 83 |         "parameters": {}
 84 |       }
 85 |     ],
 86 |     "connections": {
 87 |       "My Webhook": {
 88 |         "main": [[{
 89 |           "node": "Set Data",
 90 |           "type": "main",
 91 |           "index": 0
 92 |         }]]
 93 |       }
 94 |     }
 95 |   };
 96 | 
 97 |   logger.info('\nTest 2: Node types without package prefix ("webhook", "set")');
 98 |   const result2 = await validator.validateWorkflow(noPrefixWorkflow as any);
 99 |   
100 |   logger.info('Validation result:');
101 |   logger.info(`Valid: ${result2.valid}`);
102 |   logger.info(`Errors: ${result2.errors.length}`);
103 |   result2.errors.forEach(err => {
104 |     if (typeof err === 'string') {
105 |       logger.error(`  - ${err}`);
106 |     } else if (err && typeof err === 'object' && 'message' in err) {
107 |       logger.error(`  - ${err.message}`);
108 |     }
109 |   });
110 | 
111 |   // Test 3: Completely invalid node type
112 |   const invalidNodeWorkflow = {
113 |     "name": "Test Workflow",
114 |     "nodes": [
115 |       {
116 |         "id": "fake-1",
117 |         "name": "Fake Node",
118 |         "type": "n8n-nodes-base.fakeNodeThatDoesNotExist",
119 |         "typeVersion": 1,
120 |         "position": [250, 300] as [number, number],
121 |         "parameters": {}
122 |       }
123 |     ],
124 |     "connections": {}
125 |   };
126 | 
127 |   logger.info('\nTest 3: Completely invalid node type');
128 |   const result3 = await validator.validateWorkflow(invalidNodeWorkflow as any);
129 |   
130 |   logger.info('Validation result:');
131 |   logger.info(`Valid: ${result3.valid}`);
132 |   logger.info(`Errors: ${result3.errors.length}`);
133 |   result3.errors.forEach(err => {
134 |     if (typeof err === 'string') {
135 |       logger.error(`  - ${err}`);
136 |     } else if (err && typeof err === 'object' && 'message' in err) {
137 |       logger.error(`  - ${err.message}`);
138 |     }
139 |   });
140 | 
141 |   // Test 4: Using n8n-validation.ts function
142 |   logger.info('\nTest 4: Testing n8n-validation.ts with invalid node types');
143 |   
144 |   const errors = validateWorkflowStructure(brokenWorkflowFromLogs as any);
145 |   logger.info('Validation errors:');
146 |   errors.forEach(err => logger.error(`  - ${err}`));
147 | 
148 |   // Test 5: Valid workflow (should pass)
149 |   const validWorkflow = {
150 |     "name": "Valid Webhook Workflow",
151 |     "nodes": [
152 |       {
153 |         "id": "webhook-1",
154 |         "name": "Webhook",
155 |         "type": "n8n-nodes-base.webhook", // CORRECT!
156 |         "typeVersion": 2,
157 |         "position": [250, 300] as [number, number],
158 |         "parameters": {
159 |           "path": "my-webhook",
160 |           "responseMode": "onReceived",
161 |           "responseData": "allEntries"
162 |         }
163 |       }
164 |     ],
165 |     "connections": {}
166 |   };
167 | 
168 |   logger.info('\nTest 5: Valid workflow with correct node type');
169 |   const result5 = await validator.validateWorkflow(validWorkflow as any);
170 |   
171 |   logger.info('Validation result:');
172 |   logger.info(`Valid: ${result5.valid}`);
173 |   logger.info(`Errors: ${result5.errors.length}`);
174 |   logger.info(`Warnings: ${result5.warnings.length}`);
175 |   result5.warnings.forEach(warn => {
176 |     if (warn && typeof warn === 'object' && 'message' in warn) {
177 |       logger.warn(`  - ${warn.message}`);
178 |     }
179 |   });
180 |   
181 |   adapter.close();
182 | }
183 | 
184 | testValidation().catch(err => {
185 |   logger.error('Test failed:', err);
186 |   process.exit(1);
187 | });
```

--------------------------------------------------------------------------------
/scripts/test-code-node-enhancements.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env npx tsx
  2 | 
  3 | /**
  4 |  * Test script for Code node enhancements
  5 |  * Tests:
  6 |  * 1. Code node documentation in tools_documentation
  7 |  * 2. Enhanced validation for Code nodes
  8 |  * 3. Code node examples
  9 |  * 4. Code node task templates
 10 |  */
 11 | 
 12 | import { EnhancedConfigValidator } from '../src/services/enhanced-config-validator.js';
 13 | import { ExampleGenerator } from '../src/services/example-generator.js';
 14 | import { TaskTemplates } from '../src/services/task-templates.js';
 15 | import { getToolDocumentation } from '../src/mcp/tools-documentation.js';
 16 | 
 17 | console.log('🧪 Testing Code Node Enhancements\n');
 18 | 
 19 | // Test 1: Code node documentation
 20 | console.log('1️⃣ Testing Code Node Documentation');
 21 | console.log('=====================================');
 22 | const codeNodeDocs = getToolDocumentation('code_node_guide', 'essentials');
 23 | console.log('✅ Code node documentation available');
 24 | console.log('First 500 chars:', codeNodeDocs.substring(0, 500) + '...\n');
 25 | 
 26 | // Test 2: Code node validation
 27 | console.log('2️⃣ Testing Code Node Validation');
 28 | console.log('=====================================');
 29 | 
 30 | // Test cases
 31 | const validationTests = [
 32 |   {
 33 |     name: 'Empty code',
 34 |     config: {
 35 |       language: 'javaScript',
 36 |       jsCode: ''
 37 |     }
 38 |   },
 39 |   {
 40 |     name: 'No return statement',
 41 |     config: {
 42 |       language: 'javaScript',
 43 |       jsCode: 'const data = items;'
 44 |     }
 45 |   },
 46 |   {
 47 |     name: 'Invalid return format',
 48 |     config: {
 49 |       language: 'javaScript',
 50 |       jsCode: 'return "hello";'
 51 |     }
 52 |   },
 53 |   {
 54 |     name: 'Valid code',
 55 |     config: {
 56 |       language: 'javaScript',
 57 |       jsCode: 'return [{json: {result: "success"}}];'
 58 |     }
 59 |   },
 60 |   {
 61 |     name: 'Python with external library',
 62 |     config: {
 63 |       language: 'python',
 64 |       pythonCode: 'import pandas as pd\nreturn [{"json": {"result": "fail"}}]'
 65 |     }
 66 |   },
 67 |   {
 68 |     name: 'Code with $json in wrong mode',
 69 |     config: {
 70 |       language: 'javaScript',
 71 |       jsCode: 'const value = $json.field;\nreturn [{json: {value}}];'
 72 |     }
 73 |   },
 74 |   {
 75 |     name: 'Code with security issue',
 76 |     config: {
 77 |       language: 'javaScript',
 78 |       jsCode: 'const result = eval(item.json.code);\nreturn [{json: {result}}];'
 79 |     }
 80 |   }
 81 | ];
 82 | 
 83 | for (const test of validationTests) {
 84 |   console.log(`\nTest: ${test.name}`);
 85 |   const result = EnhancedConfigValidator.validateWithMode(
 86 |     'nodes-base.code',
 87 |     test.config,
 88 |     [
 89 |       { name: 'language', type: 'options', options: ['javaScript', 'python'] },
 90 |       { name: 'jsCode', type: 'string' },
 91 |       { name: 'pythonCode', type: 'string' },
 92 |       { name: 'mode', type: 'options', options: ['runOnceForAllItems', 'runOnceForEachItem'] }
 93 |     ],
 94 |     'operation',
 95 |     'ai-friendly'
 96 |   );
 97 |   
 98 |   console.log(`  Valid: ${result.valid}`);
 99 |   if (result.errors.length > 0) {
100 |     console.log(`  Errors: ${result.errors.map(e => e.message).join(', ')}`);
101 |   }
102 |   if (result.warnings.length > 0) {
103 |     console.log(`  Warnings: ${result.warnings.map(w => w.message).join(', ')}`);
104 |   }
105 |   if (result.suggestions.length > 0) {
106 |     console.log(`  Suggestions: ${result.suggestions.join(', ')}`);
107 |   }
108 | }
109 | 
110 | // Test 3: Code node examples
111 | console.log('\n\n3️⃣ Testing Code Node Examples');
112 | console.log('=====================================');
113 | 
114 | const codeExamples = ExampleGenerator.getExamples('nodes-base.code');
115 | console.log('Available examples:', Object.keys(codeExamples));
116 | console.log('\nMinimal example:');
117 | console.log(JSON.stringify(codeExamples.minimal, null, 2));
118 | console.log('\nCommon example preview:');
119 | console.log(codeExamples.common?.jsCode?.substring(0, 200) + '...');
120 | 
121 | // Test 4: Code node task templates
122 | console.log('\n\n4️⃣ Testing Code Node Task Templates');
123 | console.log('=====================================');
124 | 
125 | const codeNodeTasks = [
126 |   'transform_data',
127 |   'custom_ai_tool',
128 |   'aggregate_data',
129 |   'batch_process_with_api',
130 |   'error_safe_transform',
131 |   'async_data_processing',
132 |   'python_data_analysis'
133 | ];
134 | 
135 | for (const taskName of codeNodeTasks) {
136 |   const template = TaskTemplates.getTemplate(taskName);
137 |   if (template) {
138 |     console.log(`\n✅ ${taskName}:`);
139 |     console.log(`   Description: ${template.description}`);
140 |     console.log(`   Language: ${template.configuration.language || 'javaScript'}`);
141 |     console.log(`   Code preview: ${template.configuration.jsCode?.substring(0, 100) || template.configuration.pythonCode?.substring(0, 100)}...`);
142 |   } else {
143 |     console.log(`\n❌ ${taskName}: Template not found`);
144 |   }
145 | }
146 | 
147 | // Test 5: Validate a complex Code node configuration
148 | console.log('\n\n5️⃣ Testing Complex Code Node Validation');
149 | console.log('==========================================');
150 | 
151 | const complexCode = {
152 |   language: 'javaScript',
153 |   mode: 'runOnceForEachItem',
154 |   jsCode: `// Complex validation test
155 | try {
156 |   const email = $json.email;
157 |   const response = await $helpers.httpRequest({
158 |     method: 'POST',
159 |     url: 'https://api.example.com/validate',
160 |     body: { email }
161 |   });
162 |   
163 |   return [{
164 |     json: {
165 |       ...response,
166 |       validated: true
167 |     }
168 |   }];
169 | } catch (error) {
170 |   return [{
171 |     json: {
172 |       error: error.message,
173 |       validated: false
174 |     }
175 |   }];
176 | }`,
177 |   onError: 'continueRegularOutput',
178 |   retryOnFail: true,
179 |   maxTries: 3
180 | };
181 | 
182 | const complexResult = EnhancedConfigValidator.validateWithMode(
183 |   'nodes-base.code',
184 |   complexCode,
185 |   [
186 |     { name: 'language', type: 'options', options: ['javaScript', 'python'] },
187 |     { name: 'jsCode', type: 'string' },
188 |     { name: 'mode', type: 'options', options: ['runOnceForAllItems', 'runOnceForEachItem'] },
189 |     { name: 'onError', type: 'options' },
190 |     { name: 'retryOnFail', type: 'boolean' },
191 |     { name: 'maxTries', type: 'number' }
192 |   ],
193 |   'operation',
194 |   'strict'
195 | );
196 | 
197 | console.log('Complex code validation:');
198 | console.log(`  Valid: ${complexResult.valid}`);
199 | console.log(`  Errors: ${complexResult.errors.length}`);
200 | console.log(`  Warnings: ${complexResult.warnings.length}`);
201 | console.log(`  Suggestions: ${complexResult.suggestions.length}`);
202 | 
203 | console.log('\n✅ All Code node enhancement tests completed!');
```

--------------------------------------------------------------------------------
/tests/unit/database/database-adapter-unit.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, it, expect, vi } from 'vitest';
  2 | 
  3 | // Mock logger
  4 | vi.mock('../../../src/utils/logger', () => ({
  5 |   logger: {
  6 |     info: vi.fn(),
  7 |     warn: vi.fn(),
  8 |     error: vi.fn(),
  9 |     debug: vi.fn()
 10 |   }
 11 | }));
 12 | 
 13 | describe('Database Adapter - Unit Tests', () => {
 14 |   describe('DatabaseAdapter Interface', () => {
 15 |     it('should define interface when adapter is created', () => {
 16 |       // This is a type test - ensuring the interface is correctly defined
 17 |       type DatabaseAdapter = {
 18 |         prepare: (sql: string) => any;
 19 |         exec: (sql: string) => void;
 20 |         close: () => void;
 21 |         pragma: (key: string, value?: any) => any;
 22 |         readonly inTransaction: boolean;
 23 |         transaction: <T>(fn: () => T) => T;
 24 |         checkFTS5Support: () => boolean;
 25 |       };
 26 |       
 27 |       // Type assertion to ensure interface matches
 28 |       const mockAdapter: DatabaseAdapter = {
 29 |         prepare: vi.fn(),
 30 |         exec: vi.fn(),
 31 |         close: vi.fn(),
 32 |         pragma: vi.fn(),
 33 |         inTransaction: false,
 34 |         transaction: vi.fn((fn) => fn()),
 35 |         checkFTS5Support: vi.fn(() => true)
 36 |       };
 37 |       
 38 |       expect(mockAdapter).toBeDefined();
 39 |       expect(mockAdapter.prepare).toBeDefined();
 40 |       expect(mockAdapter.exec).toBeDefined();
 41 |       expect(mockAdapter.close).toBeDefined();
 42 |       expect(mockAdapter.pragma).toBeDefined();
 43 |       expect(mockAdapter.transaction).toBeDefined();
 44 |       expect(mockAdapter.checkFTS5Support).toBeDefined();
 45 |     });
 46 |   });
 47 |   
 48 |   describe('PreparedStatement Interface', () => {
 49 |     it('should define interface when statement is prepared', () => {
 50 |       // Type test for PreparedStatement
 51 |       type PreparedStatement = {
 52 |         run: (...params: any[]) => { changes: number; lastInsertRowid: number | bigint };
 53 |         get: (...params: any[]) => any;
 54 |         all: (...params: any[]) => any[];
 55 |         iterate: (...params: any[]) => IterableIterator<any>;
 56 |         pluck: (toggle?: boolean) => PreparedStatement;
 57 |         expand: (toggle?: boolean) => PreparedStatement;
 58 |         raw: (toggle?: boolean) => PreparedStatement;
 59 |         columns: () => any[];
 60 |         bind: (...params: any[]) => PreparedStatement;
 61 |       };
 62 |       
 63 |       const mockStmt: PreparedStatement = {
 64 |         run: vi.fn(() => ({ changes: 1, lastInsertRowid: 1 })),
 65 |         get: vi.fn(),
 66 |         all: vi.fn(() => []),
 67 |         iterate: vi.fn(function* () {}),
 68 |         pluck: vi.fn(function(this: any) { return this; }),
 69 |         expand: vi.fn(function(this: any) { return this; }),
 70 |         raw: vi.fn(function(this: any) { return this; }),
 71 |         columns: vi.fn(() => []),
 72 |         bind: vi.fn(function(this: any) { return this; })
 73 |       };
 74 |       
 75 |       expect(mockStmt).toBeDefined();
 76 |       expect(mockStmt.run).toBeDefined();
 77 |       expect(mockStmt.get).toBeDefined();
 78 |       expect(mockStmt.all).toBeDefined();
 79 |       expect(mockStmt.iterate).toBeDefined();
 80 |       expect(mockStmt.pluck).toBeDefined();
 81 |       expect(mockStmt.expand).toBeDefined();
 82 |       expect(mockStmt.raw).toBeDefined();
 83 |       expect(mockStmt.columns).toBeDefined();
 84 |       expect(mockStmt.bind).toBeDefined();
 85 |     });
 86 |   });
 87 |   
 88 |   describe('FTS5 Support Detection', () => {
 89 |     it('should detect support when FTS5 module is available', () => {
 90 |       const mockDb = {
 91 |         exec: vi.fn()
 92 |       };
 93 |       
 94 |       // Function to test FTS5 support detection logic
 95 |       const checkFTS5Support = (db: any): boolean => {
 96 |         try {
 97 |           db.exec("CREATE VIRTUAL TABLE IF NOT EXISTS test_fts5 USING fts5(content);");
 98 |           db.exec("DROP TABLE IF EXISTS test_fts5;");
 99 |           return true;
100 |         } catch (error) {
101 |           return false;
102 |         }
103 |       };
104 |       
105 |       // Test when FTS5 is supported
106 |       expect(checkFTS5Support(mockDb)).toBe(true);
107 |       expect(mockDb.exec).toHaveBeenCalledWith(
108 |         "CREATE VIRTUAL TABLE IF NOT EXISTS test_fts5 USING fts5(content);"
109 |       );
110 |       
111 |       // Test when FTS5 is not supported
112 |       mockDb.exec.mockImplementation(() => {
113 |         throw new Error('no such module: fts5');
114 |       });
115 |       
116 |       expect(checkFTS5Support(mockDb)).toBe(false);
117 |     });
118 |   });
119 |   
120 |   describe('Transaction Handling', () => {
121 |     it('should handle commit and rollback when transaction is executed', () => {
122 |       // Test transaction wrapper logic
123 |       const mockDb = {
124 |         exec: vi.fn(),
125 |         inTransaction: false
126 |       };
127 |       
128 |       const transaction = <T>(db: any, fn: () => T): T => {
129 |         try {
130 |           db.exec('BEGIN');
131 |           db.inTransaction = true;
132 |           const result = fn();
133 |           db.exec('COMMIT');
134 |           db.inTransaction = false;
135 |           return result;
136 |         } catch (error) {
137 |           db.exec('ROLLBACK');
138 |           db.inTransaction = false;
139 |           throw error;
140 |         }
141 |       };
142 |       
143 |       // Test successful transaction
144 |       const result = transaction(mockDb, () => 'success');
145 |       expect(result).toBe('success');
146 |       expect(mockDb.exec).toHaveBeenCalledWith('BEGIN');
147 |       expect(mockDb.exec).toHaveBeenCalledWith('COMMIT');
148 |       expect(mockDb.inTransaction).toBe(false);
149 |       
150 |       // Reset mocks
151 |       mockDb.exec.mockClear();
152 |       
153 |       // Test failed transaction
154 |       expect(() => {
155 |         transaction(mockDb, () => {
156 |           throw new Error('transaction error');
157 |         });
158 |       }).toThrow('transaction error');
159 |       
160 |       expect(mockDb.exec).toHaveBeenCalledWith('BEGIN');
161 |       expect(mockDb.exec).toHaveBeenCalledWith('ROLLBACK');
162 |       expect(mockDb.inTransaction).toBe(false);
163 |     });
164 |   });
165 |   
166 |   describe('Pragma Handling', () => {
167 |     it('should return values when pragma commands are executed', () => {
168 |       const mockDb = {
169 |         pragma: vi.fn((key: string, value?: any) => {
170 |           if (key === 'journal_mode' && value === 'WAL') {
171 |             return 'wal';
172 |           }
173 |           return null;
174 |         })
175 |       };
176 |       
177 |       expect(mockDb.pragma('journal_mode', 'WAL')).toBe('wal');
178 |       expect(mockDb.pragma('other_key')).toBe(null);
179 |     });
180 |   });
181 | });
```

--------------------------------------------------------------------------------
/.github/workflows/benchmark-pr.yml:
--------------------------------------------------------------------------------

```yaml
  1 | name: Benchmark PR Comparison
  2 | on:
  3 |   pull_request:
  4 |     branches: [main]
  5 |     paths-ignore:
  6 |       - '**.md'
  7 |       - '**.txt'
  8 |       - 'docs/**'
  9 |       - 'examples/**'
 10 |       - '.github/FUNDING.yml'
 11 |       - '.github/ISSUE_TEMPLATE/**'
 12 |       - '.github/pull_request_template.md'
 13 |       - '.gitignore'
 14 |       - 'LICENSE*'
 15 |       - 'ATTRIBUTION.md'
 16 |       - 'SECURITY.md'
 17 |       - 'CODE_OF_CONDUCT.md'
 18 | 
 19 | permissions:
 20 |   pull-requests: write
 21 |   contents: read
 22 |   statuses: write
 23 | 
 24 | jobs:
 25 |   benchmark-comparison:
 26 |     runs-on: ubuntu-latest
 27 |     steps:
 28 |       - name: Checkout PR branch
 29 |         uses: actions/checkout@v4
 30 |         with:
 31 |           fetch-depth: 0
 32 |       
 33 |       - name: Setup Node.js
 34 |         uses: actions/setup-node@v4
 35 |         with:
 36 |           node-version: 20
 37 |           cache: 'npm'
 38 |       
 39 |       - name: Install dependencies
 40 |         run: npm ci
 41 |       
 42 |       # Run benchmarks on current branch
 43 |       - name: Run current benchmarks
 44 |         run: npm run benchmark:ci
 45 |       
 46 |       - name: Save current results
 47 |         run: cp benchmark-results.json benchmark-current.json
 48 |       
 49 |       # Checkout and run benchmarks on base branch
 50 |       - name: Checkout base branch
 51 |         run: |
 52 |           git checkout ${{ github.event.pull_request.base.sha }}
 53 |           git status
 54 |       
 55 |       - name: Install base dependencies
 56 |         run: npm ci
 57 |       
 58 |       - name: Run baseline benchmarks
 59 |         run: npm run benchmark:ci
 60 |         continue-on-error: true
 61 |       
 62 |       - name: Save baseline results
 63 |         run: |
 64 |           if [ -f benchmark-results.json ]; then
 65 |             cp benchmark-results.json benchmark-baseline.json
 66 |           else
 67 |             echo '{"files":[]}' > benchmark-baseline.json
 68 |           fi
 69 |       
 70 |       # Compare results
 71 |       - name: Checkout PR branch again
 72 |         run: git checkout ${{ github.event.pull_request.head.sha }}
 73 |       
 74 |       - name: Compare benchmarks
 75 |         id: compare
 76 |         run: |
 77 |           node scripts/compare-benchmarks.js benchmark-current.json benchmark-baseline.json || echo "REGRESSION=true" >> $GITHUB_OUTPUT
 78 |       
 79 |       # Upload comparison artifacts
 80 |       - name: Upload benchmark comparison
 81 |         if: always()
 82 |         uses: actions/upload-artifact@v4
 83 |         with:
 84 |           name: benchmark-comparison-${{ github.run_number }}
 85 |           path: |
 86 |             benchmark-current.json
 87 |             benchmark-baseline.json
 88 |             benchmark-comparison.json
 89 |             benchmark-comparison.md
 90 |           retention-days: 30
 91 |       
 92 |       # Post comparison to PR
 93 |       - name: Post benchmark comparison to PR
 94 |         if: always()
 95 |         uses: actions/github-script@v7
 96 |         continue-on-error: true
 97 |         with:
 98 |           script: |
 99 |             try {
100 |               const fs = require('fs');
101 |               let comment = '## ⚡ Benchmark Comparison\n\n';
102 |               
103 |               try {
104 |                 if (fs.existsSync('benchmark-comparison.md')) {
105 |                   const comparison = fs.readFileSync('benchmark-comparison.md', 'utf8');
106 |                   comment += comparison;
107 |                 } else {
108 |                   comment += 'Benchmark comparison could not be generated.';
109 |                 }
110 |               } catch (error) {
111 |                 comment += `Error reading benchmark comparison: ${error.message}`;
112 |               }
113 |               
114 |               comment += '\n\n---\n';
115 |               comment += `*[View full benchmark results](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})*`;
116 |               
117 |               // Find existing comment
118 |               const { data: comments } = await github.rest.issues.listComments({
119 |                 owner: context.repo.owner,
120 |                 repo: context.repo.repo,
121 |                 issue_number: context.issue.number,
122 |               });
123 |               
124 |               const botComment = comments.find(comment => 
125 |                 comment.user.type === 'Bot' && 
126 |                 comment.body.includes('## ⚡ Benchmark Comparison')
127 |               );
128 |               
129 |               if (botComment) {
130 |                 await github.rest.issues.updateComment({
131 |                   owner: context.repo.owner,
132 |                   repo: context.repo.repo,
133 |                   comment_id: botComment.id,
134 |                   body: comment
135 |                 });
136 |               } else {
137 |                 await github.rest.issues.createComment({
138 |                   owner: context.repo.owner,
139 |                   repo: context.repo.repo,
140 |                   issue_number: context.issue.number,
141 |                   body: comment
142 |                 });
143 |               }
144 |             } catch (error) {
145 |               console.error('Failed to create/update PR comment:', error.message);
146 |               console.log('This is likely due to insufficient permissions for external PRs.');
147 |               console.log('Benchmark comparison has been saved to artifacts instead.');
148 |             }
149 |       
150 |       # Add status check
151 |       - name: Set benchmark status
152 |         if: always()
153 |         uses: actions/github-script@v7
154 |         continue-on-error: true
155 |         with:
156 |           script: |
157 |             try {
158 |               const hasRegression = '${{ steps.compare.outputs.REGRESSION }}' === 'true';
159 |               const state = hasRegression ? 'failure' : 'success';
160 |               const description = hasRegression 
161 |                 ? 'Performance regressions detected' 
162 |                 : 'No performance regressions';
163 |               
164 |               await github.rest.repos.createCommitStatus({
165 |                 owner: context.repo.owner,
166 |                 repo: context.repo.repo,
167 |                 sha: context.sha,
168 |                 state: state,
169 |                 target_url: `https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}`,
170 |                 description: description,
171 |                 context: 'benchmarks/regression-check'
172 |               });
173 |             } catch (error) {
174 |               console.error('Failed to create commit status:', error.message);
175 |               console.log('This is likely due to insufficient permissions for external PRs.');
176 |             }
```

--------------------------------------------------------------------------------
/tests/test-slack-node-complete.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | const { NodeDocumentationService } = require('../dist/services/node-documentation-service');
  4 | const { EnhancedDocumentationFetcher } = require('../dist/utils/documentation-fetcher');
  5 | const { NodeSourceExtractor } = require('../dist/utils/node-source-extractor');
  6 | const path = require('path');
  7 | 
  8 | async function testSlackNode() {
  9 |   console.log('🧪 Testing Slack Node Complete Information Extraction\n');
 10 |   
 11 |   const dbPath = path.join(__dirname, '../data/test-slack.db');
 12 |   const service = new NodeDocumentationService(dbPath);
 13 |   const fetcher = new EnhancedDocumentationFetcher();
 14 |   const extractor = new NodeSourceExtractor();
 15 |   
 16 |   try {
 17 |     console.log('📚 Fetching Slack node documentation...');
 18 |     const docs = await fetcher.getEnhancedNodeDocumentation('n8n-nodes-base.Slack');
 19 |     
 20 |     console.log('\n✅ Documentation Structure:');
 21 |     console.log(`- Title: ${docs.title}`);
 22 |     console.log(`- Has markdown: ${docs.markdown?.length > 0 ? 'Yes' : 'No'} (${docs.markdown?.length || 0} chars)`);
 23 |     console.log(`- Operations: ${docs.operations?.length || 0}`);
 24 |     console.log(`- API Methods: ${docs.apiMethods?.length || 0}`);
 25 |     console.log(`- Examples: ${docs.examples?.length || 0}`);
 26 |     console.log(`- Templates: ${docs.templates?.length || 0}`);
 27 |     console.log(`- Related Resources: ${docs.relatedResources?.length || 0}`);
 28 |     console.log(`- Required Scopes: ${docs.requiredScopes?.length || 0}`);
 29 |     
 30 |     console.log('\n📋 Operations by Resource:');
 31 |     const resourceMap = new Map();
 32 |     if (docs.operations) {
 33 |       docs.operations.forEach(op => {
 34 |         if (!resourceMap.has(op.resource)) {
 35 |           resourceMap.set(op.resource, []);
 36 |         }
 37 |         resourceMap.get(op.resource).push(op);
 38 |       });
 39 |     }
 40 |     
 41 |     for (const [resource, ops] of resourceMap) {
 42 |       console.log(`\n  ${resource}:`);
 43 |       ops.forEach(op => {
 44 |         console.log(`    - ${op.operation}: ${op.description}`);
 45 |       });
 46 |     }
 47 |     
 48 |     console.log('\n🔌 Sample API Methods:');
 49 |     if (docs.apiMethods) {
 50 |       docs.apiMethods.slice(0, 5).forEach(method => {
 51 |         console.log(`  - ${method.operation} → ${method.apiMethod}`);
 52 |       });
 53 |     }
 54 |     
 55 |     console.log('\n💻 Extracting Slack node source code...');
 56 |     const sourceInfo = await extractor.extractNodeSource('n8n-nodes-base.Slack');
 57 |     
 58 |     console.log('\n✅ Source Code Extraction:');
 59 |     console.log(`- Has source code: ${sourceInfo.sourceCode ? 'Yes' : 'No'} (${sourceInfo.sourceCode?.length || 0} chars)`);
 60 |     console.log(`- Has credential code: ${sourceInfo.credentialCode ? 'Yes' : 'No'} (${sourceInfo.credentialCode?.length || 0} chars)`);
 61 |     console.log(`- Package name: ${sourceInfo.packageInfo?.name}`);
 62 |     console.log(`- Package version: ${sourceInfo.packageInfo?.version}`);
 63 |     
 64 |     // Store in database
 65 |     console.log('\n💾 Storing in database...');
 66 |     await service.storeNode({
 67 |       nodeType: 'n8n-nodes-base.Slack',
 68 |       name: 'Slack',
 69 |       displayName: 'Slack',
 70 |       description: 'Send and receive messages, manage channels, and more',
 71 |       category: 'Communication',
 72 |       documentationUrl: docs?.url || 'https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.slack/',
 73 |       documentationMarkdown: docs?.markdown,
 74 |       documentationTitle: docs?.title,
 75 |       operations: docs?.operations,
 76 |       apiMethods: docs?.apiMethods,
 77 |       documentationExamples: docs?.examples,
 78 |       templates: docs?.templates,
 79 |       relatedResources: docs?.relatedResources,
 80 |       requiredScopes: docs?.requiredScopes,
 81 |       sourceCode: sourceInfo.sourceCode || '',
 82 |       credentialCode: sourceInfo.credentialCode,
 83 |       packageName: sourceInfo.packageInfo?.name || 'n8n-nodes-base',
 84 |       version: sourceInfo.packageInfo?.version,
 85 |       hasCredentials: true,
 86 |       isTrigger: false,
 87 |       isWebhook: false
 88 |     });
 89 |     
 90 |     // Retrieve and verify
 91 |     console.log('\n🔍 Retrieving from database...');
 92 |     const storedNode = await service.getNodeInfo('n8n-nodes-base.Slack');
 93 |     
 94 |     console.log('\n✅ Verification Results:');
 95 |     console.log(`- Node found: ${storedNode ? 'Yes' : 'No'}`);
 96 |     if (storedNode) {
 97 |       console.log(`- Has operations: ${storedNode.operations?.length > 0 ? 'Yes' : 'No'} (${storedNode.operations?.length || 0})`);
 98 |       console.log(`- Has API methods: ${storedNode.apiMethods?.length > 0 ? 'Yes' : 'No'} (${storedNode.apiMethods?.length || 0})`);
 99 |       console.log(`- Has examples: ${storedNode.documentationExamples?.length > 0 ? 'Yes' : 'No'} (${storedNode.documentationExamples?.length || 0})`);
100 |       console.log(`- Has source code: ${storedNode.sourceCode ? 'Yes' : 'No'}`);
101 |       console.log(`- Has credential code: ${storedNode.credentialCode ? 'Yes' : 'No'}`);
102 |     }
103 |     
104 |     // Test search
105 |     console.log('\n🔍 Testing search...');
106 |     const searchResults = await service.searchNodes('message send');
107 |     const slackInResults = searchResults.some(r => r.nodeType === 'n8n-nodes-base.Slack');
108 |     console.log(`- Slack found in search results: ${slackInResults ? 'Yes' : 'No'}`);
109 |     
110 |     console.log('\n✅ Complete Information Test Summary:');
111 |     const hasCompleteInfo = 
112 |       storedNode &&
113 |       storedNode.operations?.length > 0 &&
114 |       storedNode.apiMethods?.length > 0 &&
115 |       storedNode.sourceCode &&
116 |       storedNode.documentationMarkdown;
117 |     
118 |     console.log(`- Has complete information: ${hasCompleteInfo ? '✅ YES' : '❌ NO'}`);
119 |     
120 |     if (!hasCompleteInfo) {
121 |       console.log('\n❌ Missing Information:');
122 |       if (!storedNode) console.log('  - Node not stored properly');
123 |       if (!storedNode?.operations?.length) console.log('  - No operations extracted');
124 |       if (!storedNode?.apiMethods?.length) console.log('  - No API methods extracted');
125 |       if (!storedNode?.sourceCode) console.log('  - No source code extracted');
126 |       if (!storedNode?.documentationMarkdown) console.log('  - No documentation extracted');
127 |     }
128 |     
129 |   } catch (error) {
130 |     console.error('❌ Test failed:', error);
131 |   } finally {
132 |     await service.close();
133 |   }
134 | }
135 | 
136 | // Run the test
137 | testSlackNode().catch(console.error);
```

--------------------------------------------------------------------------------
/src/scripts/test-protocol-negotiation.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | /**
  3 |  * Test Protocol Version Negotiation
  4 |  * 
  5 |  * This script tests the protocol version negotiation logic with different client scenarios.
  6 |  */
  7 | 
  8 | import { 
  9 |   negotiateProtocolVersion, 
 10 |   isN8nClient,
 11 |   STANDARD_PROTOCOL_VERSION,
 12 |   N8N_PROTOCOL_VERSION 
 13 | } from '../utils/protocol-version';
 14 | 
 15 | interface TestCase {
 16 |   name: string;
 17 |   clientVersion?: string;  
 18 |   clientInfo?: any;
 19 |   userAgent?: string;
 20 |   headers?: Record<string, string>;
 21 |   expectedVersion: string;
 22 |   expectedIsN8nClient: boolean;
 23 | }
 24 | 
 25 | const testCases: TestCase[] = [
 26 |   {
 27 |     name: 'Standard MCP client (Claude Desktop)',
 28 |     clientVersion: '2025-03-26',
 29 |     clientInfo: { name: 'Claude Desktop', version: '1.0.0' },
 30 |     expectedVersion: '2025-03-26',
 31 |     expectedIsN8nClient: false
 32 |   },
 33 |   {
 34 |     name: 'n8n client with specific client info',
 35 |     clientVersion: '2025-03-26',
 36 |     clientInfo: { name: 'n8n', version: '1.0.0' },
 37 |     expectedVersion: N8N_PROTOCOL_VERSION,
 38 |     expectedIsN8nClient: true
 39 |   },
 40 |   {
 41 |     name: 'LangChain client',
 42 |     clientVersion: '2025-03-26',
 43 |     clientInfo: { name: 'langchain-js', version: '0.1.0' },
 44 |     expectedVersion: N8N_PROTOCOL_VERSION,
 45 |     expectedIsN8nClient: true
 46 |   },
 47 |   {
 48 |     name: 'n8n client via user agent',
 49 |     clientVersion: '2025-03-26',
 50 |     userAgent: 'n8n/1.0.0',
 51 |     expectedVersion: N8N_PROTOCOL_VERSION,
 52 |     expectedIsN8nClient: true
 53 |   },
 54 |   {
 55 |     name: 'n8n mode environment variable',
 56 |     clientVersion: '2025-03-26',
 57 |     expectedVersion: N8N_PROTOCOL_VERSION,
 58 |     expectedIsN8nClient: true
 59 |   },
 60 |   {
 61 |     name: 'Client requesting older version',
 62 |     clientVersion: '2024-06-25',
 63 |     clientInfo: { name: 'Some Client', version: '1.0.0' },
 64 |     expectedVersion: '2024-06-25',
 65 |     expectedIsN8nClient: false
 66 |   },
 67 |   {
 68 |     name: 'Client requesting unsupported version',
 69 |     clientVersion: '2020-01-01',
 70 |     clientInfo: { name: 'Old Client', version: '1.0.0' },
 71 |     expectedVersion: STANDARD_PROTOCOL_VERSION,
 72 |     expectedIsN8nClient: false
 73 |   },
 74 |   {
 75 |     name: 'No client info provided',
 76 |     expectedVersion: STANDARD_PROTOCOL_VERSION,
 77 |     expectedIsN8nClient: false
 78 |   },
 79 |   {
 80 |     name: 'n8n headers detection',
 81 |     clientVersion: '2025-03-26',
 82 |     headers: { 'x-n8n-version': '1.0.0' },
 83 |     expectedVersion: N8N_PROTOCOL_VERSION,
 84 |     expectedIsN8nClient: true
 85 |   }
 86 | ];
 87 | 
 88 | async function runTests(): Promise<void> {
 89 |   console.log('🧪 Testing Protocol Version Negotiation\n');
 90 |   
 91 |   let passed = 0;
 92 |   let failed = 0;
 93 |   
 94 |   // Set N8N_MODE for the environment variable test
 95 |   const originalN8nMode = process.env.N8N_MODE;
 96 |   
 97 |   for (const testCase of testCases) {
 98 |     try {
 99 |       // Set N8N_MODE for specific test
100 |       if (testCase.name.includes('environment variable')) {
101 |         process.env.N8N_MODE = 'true';
102 |       } else {
103 |         delete process.env.N8N_MODE;
104 |       }
105 |       
106 |       // Test isN8nClient function
107 |       const detectedAsN8n = isN8nClient(testCase.clientInfo, testCase.userAgent, testCase.headers);
108 |       
109 |       // Test negotiateProtocolVersion function
110 |       const result = negotiateProtocolVersion(
111 |         testCase.clientVersion,
112 |         testCase.clientInfo,
113 |         testCase.userAgent,
114 |         testCase.headers
115 |       );
116 |       
117 |       // Check results
118 |       const versionCorrect = result.version === testCase.expectedVersion;
119 |       const n8nDetectionCorrect = result.isN8nClient === testCase.expectedIsN8nClient;
120 |       const isN8nFunctionCorrect = detectedAsN8n === testCase.expectedIsN8nClient;
121 |       
122 |       if (versionCorrect && n8nDetectionCorrect && isN8nFunctionCorrect) {
123 |         console.log(`✅ ${testCase.name}`);
124 |         console.log(`   Version: ${result.version}, n8n client: ${result.isN8nClient}`);
125 |         console.log(`   Reasoning: ${result.reasoning}\n`);
126 |         passed++;
127 |       } else {
128 |         console.log(`❌ ${testCase.name}`);
129 |         console.log(`   Expected: version=${testCase.expectedVersion}, isN8n=${testCase.expectedIsN8nClient}`);
130 |         console.log(`   Got: version=${result.version}, isN8n=${result.isN8nClient}`);
131 |         console.log(`   isN8nClient function: ${detectedAsN8n} (expected: ${testCase.expectedIsN8nClient})`);
132 |         console.log(`   Reasoning: ${result.reasoning}\n`);
133 |         failed++;
134 |       }
135 |       
136 |     } catch (error) {
137 |       console.log(`💥 ${testCase.name} - ERROR`);
138 |       console.log(`   ${error instanceof Error ? error.message : String(error)}\n`);
139 |       failed++;
140 |     }
141 |   }
142 |   
143 |   // Restore original N8N_MODE
144 |   if (originalN8nMode) {
145 |     process.env.N8N_MODE = originalN8nMode;
146 |   } else {
147 |     delete process.env.N8N_MODE;
148 |   }
149 |   
150 |   // Summary
151 |   console.log(`\n📊 Test Results:`);
152 |   console.log(`   ✅ Passed: ${passed}`);
153 |   console.log(`   ❌ Failed: ${failed}`);
154 |   console.log(`   Total: ${passed + failed}`);
155 |   
156 |   if (failed > 0) {
157 |     console.log(`\n❌ Some tests failed!`);
158 |     process.exit(1);
159 |   } else {
160 |     console.log(`\n🎉 All tests passed!`);
161 |   }
162 | }
163 | 
164 | // Additional integration test
165 | async function testIntegration(): Promise<void> {
166 |   console.log('\n🔧 Integration Test - MCP Server Protocol Negotiation\n');
167 |   
168 |   // This would normally test the actual MCP server, but we'll just verify
169 |   // the negotiation logic works in typical scenarios
170 |   
171 |   const scenarios = [
172 |     {
173 |       name: 'Claude Desktop connecting',
174 |       clientInfo: { name: 'Claude Desktop', version: '1.0.0' },
175 |       clientVersion: '2025-03-26'
176 |     },
177 |     {
178 |       name: 'n8n connecting via HTTP',
179 |       headers: { 'user-agent': 'n8n/1.52.0' },
180 |       clientVersion: '2025-03-26'
181 |     }
182 |   ];
183 |   
184 |   for (const scenario of scenarios) {
185 |     const result = negotiateProtocolVersion(
186 |       scenario.clientVersion,
187 |       scenario.clientInfo,
188 |       scenario.headers?.['user-agent'],
189 |       scenario.headers
190 |     );
191 |     
192 |     console.log(`🔍 ${scenario.name}:`);
193 |     console.log(`   Negotiated version: ${result.version}`);
194 |     console.log(`   Is n8n client: ${result.isN8nClient}`);
195 |     console.log(`   Reasoning: ${result.reasoning}\n`);
196 |   }
197 | }
198 | 
199 | if (require.main === module) {
200 |   runTests()
201 |     .then(() => testIntegration())
202 |     .catch(error => {
203 |       console.error('Test execution failed:', error);
204 |       process.exit(1);
205 |     });
206 | }
```

--------------------------------------------------------------------------------
/tests/test-enhanced-documentation.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | const { EnhancedDocumentationFetcher } = require('../dist/utils/enhanced-documentation-fetcher');
  4 | const { EnhancedSQLiteStorageService } = require('../dist/services/enhanced-sqlite-storage-service');
  5 | const { NodeSourceExtractor } = require('../dist/utils/node-source-extractor');
  6 | 
  7 | async function testEnhancedDocumentation() {
  8 |   console.log('=== Testing Enhanced Documentation Fetcher ===\n');
  9 | 
 10 |   const fetcher = new EnhancedDocumentationFetcher();
 11 |   const storage = new EnhancedSQLiteStorageService('./data/test-enhanced.db');
 12 |   const extractor = new NodeSourceExtractor();
 13 | 
 14 |   try {
 15 |     // Test 1: Fetch and parse Slack node documentation
 16 |     console.log('1. Testing Slack node documentation parsing...');
 17 |     const slackDoc = await fetcher.getEnhancedNodeDocumentation('n8n-nodes-base.slack');
 18 |     
 19 |     if (slackDoc) {
 20 |       console.log('\n✓ Slack Documentation Found:');
 21 |       console.log(`  - Title: ${slackDoc.title}`);
 22 |       console.log(`  - Description: ${slackDoc.description}`);
 23 |       console.log(`  - URL: ${slackDoc.url}`);
 24 |       console.log(`  - Operations: ${slackDoc.operations?.length || 0} found`);
 25 |       console.log(`  - API Methods: ${slackDoc.apiMethods?.length || 0} found`);
 26 |       console.log(`  - Examples: ${slackDoc.examples?.length || 0} found`);
 27 |       console.log(`  - Required Scopes: ${slackDoc.requiredScopes?.length || 0} found`);
 28 |       
 29 |       // Show sample operations
 30 |       if (slackDoc.operations && slackDoc.operations.length > 0) {
 31 |         console.log('\n  Sample Operations:');
 32 |         slackDoc.operations.slice(0, 5).forEach(op => {
 33 |           console.log(`    - ${op.resource}.${op.operation}: ${op.description}`);
 34 |         });
 35 |       }
 36 |       
 37 |       // Show sample API mappings
 38 |       if (slackDoc.apiMethods && slackDoc.apiMethods.length > 0) {
 39 |         console.log('\n  Sample API Mappings:');
 40 |         slackDoc.apiMethods.slice(0, 5).forEach(api => {
 41 |           console.log(`    - ${api.resource}.${api.operation} → ${api.apiMethod}`);
 42 |         });
 43 |       }
 44 |     } else {
 45 |       console.log('✗ Slack documentation not found');
 46 |     }
 47 | 
 48 |     // Test 2: Test with If node (core node)
 49 |     console.log('\n\n2. Testing If node documentation parsing...');
 50 |     const ifDoc = await fetcher.getEnhancedNodeDocumentation('n8n-nodes-base.if');
 51 |     
 52 |     if (ifDoc) {
 53 |       console.log('\n✓ If Documentation Found:');
 54 |       console.log(`  - Title: ${ifDoc.title}`);
 55 |       console.log(`  - Description: ${ifDoc.description}`);
 56 |       console.log(`  - Examples: ${ifDoc.examples?.length || 0} found`);
 57 |       console.log(`  - Related Resources: ${ifDoc.relatedResources?.length || 0} found`);
 58 |     }
 59 | 
 60 |     // Test 3: Store node with documentation
 61 |     console.log('\n\n3. Testing node storage with documentation...');
 62 |     
 63 |     // Extract a node
 64 |     const nodeInfo = await extractor.extractNodeSource('n8n-nodes-base.slack');
 65 |     if (nodeInfo) {
 66 |       const storedNode = await storage.storeNodeWithDocumentation(nodeInfo);
 67 |       
 68 |       console.log('\n✓ Node stored successfully:');
 69 |       console.log(`  - Node Type: ${storedNode.nodeType}`);
 70 |       console.log(`  - Has Documentation: ${!!storedNode.documentationMarkdown}`);
 71 |       console.log(`  - Operations: ${storedNode.operationCount}`);
 72 |       console.log(`  - API Methods: ${storedNode.apiMethodCount}`);
 73 |       console.log(`  - Examples: ${storedNode.exampleCount}`);
 74 |       console.log(`  - Resources: ${storedNode.resourceCount}`);
 75 |       console.log(`  - Scopes: ${storedNode.scopeCount}`);
 76 |       
 77 |       // Get detailed operations
 78 |       const operations = await storage.getNodeOperations(storedNode.id);
 79 |       if (operations.length > 0) {
 80 |         console.log('\n  Stored Operations (first 5):');
 81 |         operations.slice(0, 5).forEach(op => {
 82 |           console.log(`    - ${op.resource}.${op.operation}: ${op.description}`);
 83 |         });
 84 |       }
 85 |       
 86 |       // Get examples
 87 |       const examples = await storage.getNodeExamples(storedNode.id);
 88 |       if (examples.length > 0) {
 89 |         console.log('\n  Stored Examples:');
 90 |         examples.forEach(ex => {
 91 |           console.log(`    - ${ex.title || 'Untitled'} (${ex.type}): ${ex.code.length} chars`);
 92 |         });
 93 |       }
 94 |     }
 95 | 
 96 |     // Test 4: Search with enhanced FTS
 97 |     console.log('\n\n4. Testing enhanced search...');
 98 |     
 99 |     const searchResults = await storage.searchNodes({ query: 'slack message' });
100 |     console.log(`\n✓ Search Results for "slack message": ${searchResults.length} nodes found`);
101 |     
102 |     if (searchResults.length > 0) {
103 |       console.log('  First result:');
104 |       const result = searchResults[0];
105 |       console.log(`    - ${result.displayName || result.name} (${result.nodeType})`);
106 |       console.log(`    - Documentation: ${result.documentationTitle || 'No title'}`);
107 |     }
108 | 
109 |     // Test 5: Get statistics
110 |     console.log('\n\n5. Getting enhanced statistics...');
111 |     const stats = await storage.getEnhancedStatistics();
112 |     
113 |     console.log('\n✓ Enhanced Statistics:');
114 |     console.log(`  - Total Nodes: ${stats.totalNodes}`);
115 |     console.log(`  - Nodes with Documentation: ${stats.nodesWithDocumentation}`);
116 |     console.log(`  - Documentation Coverage: ${stats.documentationCoverage}%`);
117 |     console.log(`  - Total Operations: ${stats.totalOperations}`);
118 |     console.log(`  - Total API Methods: ${stats.totalApiMethods}`);
119 |     console.log(`  - Total Examples: ${stats.totalExamples}`);
120 |     console.log(`  - Total Resources: ${stats.totalResources}`);
121 |     console.log(`  - Total Scopes: ${stats.totalScopes}`);
122 |     
123 |     if (stats.topDocumentedNodes && stats.topDocumentedNodes.length > 0) {
124 |       console.log('\n  Top Documented Nodes:');
125 |       stats.topDocumentedNodes.slice(0, 3).forEach(node => {
126 |         console.log(`    - ${node.display_name || node.name}: ${node.operation_count} operations, ${node.example_count} examples`);
127 |       });
128 |     }
129 | 
130 |   } catch (error) {
131 |     console.error('Error during testing:', error);
132 |   } finally {
133 |     // Cleanup
134 |     storage.close();
135 |     await fetcher.cleanup();
136 |     console.log('\n\n✓ Test completed and cleaned up');
137 |   }
138 | }
139 | 
140 | // Run the test
141 | testEnhancedDocumentation().catch(console.error);
```

--------------------------------------------------------------------------------
/src/telemetry/telemetry-error.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Telemetry Error Classes
  3 |  * Custom error types for telemetry system with enhanced tracking
  4 |  */
  5 | 
  6 | import { TelemetryErrorType, TelemetryErrorContext } from './telemetry-types';
  7 | import { logger } from '../utils/logger';
  8 | 
  9 | // Re-export types for convenience
 10 | export { TelemetryErrorType, TelemetryErrorContext } from './telemetry-types';
 11 | 
 12 | export class TelemetryError extends Error {
 13 |   public readonly type: TelemetryErrorType;
 14 |   public readonly context?: Record<string, any>;
 15 |   public readonly timestamp: number;
 16 |   public readonly retryable: boolean;
 17 | 
 18 |   constructor(
 19 |     type: TelemetryErrorType,
 20 |     message: string,
 21 |     context?: Record<string, any>,
 22 |     retryable: boolean = false
 23 |   ) {
 24 |     super(message);
 25 |     this.name = 'TelemetryError';
 26 |     this.type = type;
 27 |     this.context = context;
 28 |     this.timestamp = Date.now();
 29 |     this.retryable = retryable;
 30 | 
 31 |     // Ensure proper prototype chain
 32 |     Object.setPrototypeOf(this, TelemetryError.prototype);
 33 |   }
 34 | 
 35 |   /**
 36 |    * Convert error to context object
 37 |    */
 38 |   toContext(): TelemetryErrorContext {
 39 |     return {
 40 |       type: this.type,
 41 |       message: this.message,
 42 |       context: this.context,
 43 |       timestamp: this.timestamp,
 44 |       retryable: this.retryable
 45 |     };
 46 |   }
 47 | 
 48 |   /**
 49 |    * Log the error with appropriate level
 50 |    */
 51 |   log(): void {
 52 |     const logContext = {
 53 |       type: this.type,
 54 |       message: this.message,
 55 |       ...this.context
 56 |     };
 57 | 
 58 |     if (this.retryable) {
 59 |       logger.debug('Retryable telemetry error:', logContext);
 60 |     } else {
 61 |       logger.debug('Non-retryable telemetry error:', logContext);
 62 |     }
 63 |   }
 64 | }
 65 | 
 66 | /**
 67 |  * Circuit Breaker for handling repeated failures
 68 |  */
 69 | export class TelemetryCircuitBreaker {
 70 |   private failureCount: number = 0;
 71 |   private lastFailureTime: number = 0;
 72 |   private state: 'closed' | 'open' | 'half-open' = 'closed';
 73 | 
 74 |   private readonly failureThreshold: number;
 75 |   private readonly resetTimeout: number;
 76 |   private readonly halfOpenRequests: number;
 77 |   private halfOpenCount: number = 0;
 78 | 
 79 |   constructor(
 80 |     failureThreshold: number = 5,
 81 |     resetTimeout: number = 60000, // 1 minute
 82 |     halfOpenRequests: number = 3
 83 |   ) {
 84 |     this.failureThreshold = failureThreshold;
 85 |     this.resetTimeout = resetTimeout;
 86 |     this.halfOpenRequests = halfOpenRequests;
 87 |   }
 88 | 
 89 |   /**
 90 |    * Check if requests should be allowed
 91 |    */
 92 |   shouldAllow(): boolean {
 93 |     const now = Date.now();
 94 | 
 95 |     switch (this.state) {
 96 |       case 'closed':
 97 |         return true;
 98 | 
 99 |       case 'open':
100 |         // Check if enough time has passed to try half-open
101 |         if (now - this.lastFailureTime > this.resetTimeout) {
102 |           this.state = 'half-open';
103 |           this.halfOpenCount = 0;
104 |           logger.debug('Circuit breaker transitioning to half-open');
105 |           return true;
106 |         }
107 |         return false;
108 | 
109 |       case 'half-open':
110 |         // Allow limited requests in half-open state
111 |         if (this.halfOpenCount < this.halfOpenRequests) {
112 |           this.halfOpenCount++;
113 |           return true;
114 |         }
115 |         return false;
116 | 
117 |       default:
118 |         return false;
119 |     }
120 |   }
121 | 
122 |   /**
123 |    * Record a success
124 |    */
125 |   recordSuccess(): void {
126 |     if (this.state === 'half-open') {
127 |       // If we've had enough successful requests, close the circuit
128 |       if (this.halfOpenCount >= this.halfOpenRequests) {
129 |         this.state = 'closed';
130 |         this.failureCount = 0;
131 |         logger.debug('Circuit breaker closed after successful recovery');
132 |       }
133 |     } else if (this.state === 'closed') {
134 |       // Reset failure count on success
135 |       this.failureCount = 0;
136 |     }
137 |   }
138 | 
139 |   /**
140 |    * Record a failure
141 |    */
142 |   recordFailure(error?: Error): void {
143 |     this.failureCount++;
144 |     this.lastFailureTime = Date.now();
145 | 
146 |     if (this.state === 'half-open') {
147 |       // Immediately open on failure in half-open state
148 |       this.state = 'open';
149 |       logger.debug('Circuit breaker opened from half-open state', { error: error?.message });
150 |     } else if (this.state === 'closed' && this.failureCount >= this.failureThreshold) {
151 |       // Open circuit after threshold reached
152 |       this.state = 'open';
153 |       logger.debug(
154 |         `Circuit breaker opened after ${this.failureCount} failures`,
155 |         { error: error?.message }
156 |       );
157 |     }
158 |   }
159 | 
160 |   /**
161 |    * Get current state
162 |    */
163 |   getState(): { state: string; failureCount: number; canRetry: boolean } {
164 |     return {
165 |       state: this.state,
166 |       failureCount: this.failureCount,
167 |       canRetry: this.shouldAllow()
168 |     };
169 |   }
170 | 
171 |   /**
172 |    * Force reset the circuit breaker
173 |    */
174 |   reset(): void {
175 |     this.state = 'closed';
176 |     this.failureCount = 0;
177 |     this.lastFailureTime = 0;
178 |     this.halfOpenCount = 0;
179 |   }
180 | }
181 | 
182 | /**
183 |  * Error aggregator for tracking error patterns
184 |  */
185 | export class TelemetryErrorAggregator {
186 |   private errors: Map<TelemetryErrorType, number> = new Map();
187 |   private errorDetails: TelemetryErrorContext[] = [];
188 |   private readonly maxDetails: number = 100;
189 | 
190 |   /**
191 |    * Record an error
192 |    */
193 |   record(error: TelemetryError): void {
194 |     // Increment counter for this error type
195 |     const count = this.errors.get(error.type) || 0;
196 |     this.errors.set(error.type, count + 1);
197 | 
198 |     // Store error details (limited)
199 |     this.errorDetails.push(error.toContext());
200 |     if (this.errorDetails.length > this.maxDetails) {
201 |       this.errorDetails.shift();
202 |     }
203 |   }
204 | 
205 |   /**
206 |    * Get error statistics
207 |    */
208 |   getStats(): {
209 |     totalErrors: number;
210 |     errorsByType: Record<string, number>;
211 |     mostCommonError?: string;
212 |     recentErrors: TelemetryErrorContext[];
213 |   } {
214 |     const errorsByType: Record<string, number> = {};
215 |     let totalErrors = 0;
216 |     let mostCommonError: string | undefined;
217 |     let maxCount = 0;
218 | 
219 |     for (const [type, count] of this.errors.entries()) {
220 |       errorsByType[type] = count;
221 |       totalErrors += count;
222 | 
223 |       if (count > maxCount) {
224 |         maxCount = count;
225 |         mostCommonError = type;
226 |       }
227 |     }
228 | 
229 |     return {
230 |       totalErrors,
231 |       errorsByType,
232 |       mostCommonError,
233 |       recentErrors: this.errorDetails.slice(-10) // Last 10 errors
234 |     };
235 |   }
236 | 
237 |   /**
238 |    * Clear error history
239 |    */
240 |   reset(): void {
241 |     this.errors.clear();
242 |     this.errorDetails = [];
243 |   }
244 | }
```

--------------------------------------------------------------------------------
/.claude/agents/test-automator.md:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | name: test-automator
  3 | description: Use this agent when you need to create comprehensive test suites, improve test coverage, or set up test automation infrastructure. This includes writing unit tests with mocks, integration tests with test containers, E2E tests with Playwright/Cypress, configuring CI/CD pipelines for testing, or establishing test data management strategies. The agent should be used proactively when new features are implemented without tests or when test coverage needs improvement.\n\nExamples:\n- <example>\n  Context: The user has just implemented a new API endpoint without tests.\n  user: "I've added a new user registration endpoint to our API"\n  assistant: "I see you've added a new endpoint. Let me use the test-automator agent to create a comprehensive test suite for it."\n  <commentary>\n  Since new functionality was added without tests, proactively use the test-automator agent to ensure proper test coverage.\n  </commentary>\n  </example>\n- <example>\n  Context: The user explicitly asks for test creation.\n  user: "Can you write tests for the payment processing module?"\n  assistant: "I'll use the test-automator agent to create a comprehensive test suite for the payment processing module."\n  <commentary>\n  The user directly requested tests, so use the test-automator agent to handle this task.\n  </commentary>\n  </example>\n- <example>\n  Context: The user mentions issues with test reliability.\n  user: "Our tests keep failing randomly in CI"\n  assistant: "I'll use the test-automator agent to analyze and fix the flaky tests, ensuring they run deterministically."\n  <commentary>\n  Test reliability issues require the test-automator agent's expertise in creating deterministic tests.\n  </commentary>\n  </example>
  4 | ---
  5 | 
  6 | You are a test automation specialist with deep expertise in comprehensive testing strategies across multiple frameworks and languages. Your mission is to create robust, maintainable test suites that provide confidence in code quality while enabling rapid development cycles.
  7 | 
  8 | ## Core Responsibilities
  9 | 
 10 | You will design and implement test suites following the test pyramid principle:
 11 | - **Unit Tests (70%)**: Fast, isolated tests with extensive mocking and stubbing
 12 | - **Integration Tests (20%)**: Tests verifying component interactions, using test containers when needed
 13 | - **E2E Tests (10%)**: Critical user journey tests using Playwright, Cypress, or similar tools
 14 | 
 15 | ## Testing Philosophy
 16 | 
 17 | 1. **Test Behavior, Not Implementation**: Focus on what the code does, not how it does it. Tests should survive refactoring.
 18 | 2. **Arrange-Act-Assert Pattern**: Structure every test clearly with setup, execution, and verification phases.
 19 | 3. **Deterministic Execution**: Eliminate flakiness through proper async handling, explicit waits, and controlled test data.
 20 | 4. **Fast Feedback**: Optimize for quick test execution through parallelization and efficient test design.
 21 | 5. **Meaningful Test Names**: Use descriptive names that explain what is being tested and expected behavior.
 22 | 
 23 | ## Implementation Guidelines
 24 | 
 25 | ### Unit Testing
 26 | - Create focused tests for individual functions/methods
 27 | - Mock all external dependencies (databases, APIs, file systems)
 28 | - Use factories or builders for test data creation
 29 | - Include edge cases: null values, empty collections, boundary conditions
 30 | - Aim for high code coverage but prioritize critical paths
 31 | 
 32 | ### Integration Testing
 33 | - Test real interactions between components
 34 | - Use test containers for databases and external services
 35 | - Verify data persistence and retrieval
 36 | - Test transaction boundaries and rollback scenarios
 37 | - Include error handling and recovery tests
 38 | 
 39 | ### E2E Testing
 40 | - Focus on critical user journeys only
 41 | - Use page object pattern for maintainability
 42 | - Implement proper wait strategies (no arbitrary sleeps)
 43 | - Create reusable test utilities and helpers
 44 | - Include accessibility checks where applicable
 45 | 
 46 | ### Test Data Management
 47 | - Create factories or fixtures for consistent test data
 48 | - Use builders for complex object creation
 49 | - Implement data cleanup strategies
 50 | - Separate test data from production data
 51 | - Version control test data schemas
 52 | 
 53 | ### CI/CD Integration
 54 | - Configure parallel test execution
 55 | - Set up test result reporting and artifacts
 56 | - Implement test retry strategies for network-dependent tests
 57 | - Create test environment provisioning
 58 | - Configure coverage thresholds and reporting
 59 | 
 60 | ## Output Requirements
 61 | 
 62 | You will provide:
 63 | 1. **Complete test files** with all necessary imports and setup
 64 | 2. **Mock implementations** for external dependencies
 65 | 3. **Test data factories** or fixtures as separate modules
 66 | 4. **CI pipeline configuration** (GitHub Actions, GitLab CI, Jenkins, etc.)
 67 | 5. **Coverage configuration** files and scripts
 68 | 6. **E2E test scenarios** with page objects and utilities
 69 | 7. **Documentation** explaining test structure and running instructions
 70 | 
 71 | ## Framework Selection
 72 | 
 73 | Choose appropriate frameworks based on the technology stack:
 74 | - **JavaScript/TypeScript**: Jest, Vitest, Mocha + Chai, Playwright, Cypress
 75 | - **Python**: pytest, unittest, pytest-mock, factory_boy
 76 | - **Java**: JUnit 5, Mockito, TestContainers, REST Assured
 77 | - **Go**: testing package, testify, gomock
 78 | - **Ruby**: RSpec, Minitest, FactoryBot
 79 | 
 80 | ## Quality Checks
 81 | 
 82 | Before finalizing any test suite, verify:
 83 | - All tests pass consistently (run multiple times)
 84 | - No hardcoded values or environment dependencies
 85 | - Proper teardown and cleanup
 86 | - Clear assertion messages for failures
 87 | - Appropriate use of beforeEach/afterEach hooks
 88 | - No test interdependencies
 89 | - Reasonable execution time
 90 | 
 91 | ## Special Considerations
 92 | 
 93 | - For async code, ensure proper promise handling and async/await usage
 94 | - For UI tests, implement proper element waiting strategies
 95 | - For API tests, validate both response structure and data
 96 | - For performance-critical code, include benchmark tests
 97 | - For security-sensitive code, include security-focused test cases
 98 | 
 99 | When encountering existing tests, analyze them first to understand patterns and conventions before adding new ones. Always strive for consistency with the existing test architecture while improving where possible.
100 | 
```

--------------------------------------------------------------------------------
/docs/INSTALLATION.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Installation Guide
  2 | 
  3 | This guide covers all installation methods for n8n-MCP.
  4 | 
  5 | ## Table of Contents
  6 | 
  7 | - [Quick Start](#quick-start)
  8 | - [Docker Installation](#docker-installation)
  9 | - [Manual Installation](#manual-installation)
 10 | - [Development Setup](#development-setup)
 11 | - [Troubleshooting](#troubleshooting)
 12 | 
 13 | ## Quick Start
 14 | 
 15 | The fastest way to get n8n-MCP running:
 16 | 
 17 | ```bash
 18 | # Using Docker (recommended)
 19 | cat > .env << EOF
 20 | AUTH_TOKEN=$(openssl rand -base64 32)
 21 | USE_FIXED_HTTP=true
 22 | EOF
 23 | docker compose up -d
 24 | ```
 25 | 
 26 | ## Docker Installation
 27 | 
 28 | ### Prerequisites
 29 | 
 30 | - Docker Engine (install via package manager or Docker Desktop)
 31 | - Docker Compose V2 (included with modern Docker installations)
 32 | 
 33 | ### Method 1: Using Pre-built Images
 34 | 
 35 | 1. **Create a project directory:**
 36 |    ```bash
 37 |    mkdir n8n-mcp && cd n8n-mcp
 38 |    ```
 39 | 
 40 | 2. **Create docker-compose.yml:**
 41 |    ```yaml
 42 |    version: '3.8'
 43 |    
 44 |    services:
 45 |      n8n-mcp:
 46 |        image: ghcr.io/czlonkowski/n8n-mcp:latest
 47 |        container_name: n8n-mcp
 48 |        restart: unless-stopped
 49 |        
 50 |        environment:
 51 |          MCP_MODE: ${MCP_MODE:-http}
 52 |          USE_FIXED_HTTP: ${USE_FIXED_HTTP:-true}
 53 |          AUTH_TOKEN: ${AUTH_TOKEN:?AUTH_TOKEN is required}
 54 |          NODE_ENV: ${NODE_ENV:-production}
 55 |          LOG_LEVEL: ${LOG_LEVEL:-info}
 56 |          PORT: ${PORT:-3000}
 57 |        
 58 |        volumes:
 59 |          - n8n-mcp-data:/app/data
 60 |        
 61 |        ports:
 62 |          - "${PORT:-3000}:3000"
 63 |        
 64 |        healthcheck:
 65 |          test: ["CMD", "curl", "-f", "http://127.0.0.1:3000/health"]
 66 |          interval: 30s
 67 |          timeout: 10s
 68 |          retries: 3
 69 |    
 70 |    volumes:
 71 |      n8n-mcp-data:
 72 |        driver: local
 73 |    ```
 74 | 
 75 | 3. **Create .env file:**
 76 |    ```bash
 77 |    echo "AUTH_TOKEN=$(openssl rand -base64 32)" > .env
 78 |    ```
 79 | 
 80 | 4. **Start the container:**
 81 |    ```bash
 82 |    docker compose up -d
 83 |    ```
 84 | 
 85 | 5. **Verify installation:**
 86 |    ```bash
 87 |    curl http://localhost:3000/health
 88 |    ```
 89 | 
 90 | ### Method 2: Building from Source
 91 | 
 92 | 1. **Clone the repository:**
 93 |    ```bash
 94 |    git clone https://github.com/czlonkowski/n8n-mcp.git
 95 |    cd n8n-mcp
 96 |    ```
 97 | 
 98 | 2. **Build the image:**
 99 |    ```bash
100 |    docker build -t n8n-mcp:local .
101 |    ```
102 | 
103 | 3. **Run with docker-compose:**
104 |    ```bash
105 |    docker compose up -d
106 |    ```
107 | 
108 | ### Docker Management Commands
109 | 
110 | ```bash
111 | # View logs
112 | docker compose logs -f
113 | 
114 | # Stop the container
115 | docker compose stop
116 | 
117 | # Remove container and volumes
118 | docker compose down -v
119 | 
120 | # Update to latest image
121 | docker compose pull
122 | docker compose up -d
123 | 
124 | # Execute commands inside container
125 | docker compose exec n8n-mcp npm run validate
126 | 
127 | # Backup database
128 | docker cp n8n-mcp:/app/data/nodes.db ./nodes-backup.db
129 | ```
130 | 
131 | ## Manual Installation
132 | 
133 | ### Prerequisites
134 | 
135 | - Node.js v16+ (v20+ recommended)
136 | - npm or yarn
137 | - Git
138 | 
139 | ### Step-by-Step Installation
140 | 
141 | 1. **Clone the repository:**
142 |    ```bash
143 |    git clone https://github.com/czlonkowski/n8n-mcp.git
144 |    cd n8n-mcp
145 |    ```
146 | 
147 | 2. **Clone n8n documentation (optional but recommended):**
148 |    ```bash
149 |    git clone https://github.com/n8n-io/n8n-docs.git ../n8n-docs
150 |    ```
151 | 
152 | 3. **Install dependencies:**
153 |    ```bash
154 |    npm install
155 |    ```
156 | 
157 | 4. **Build the project:**
158 |    ```bash
159 |    npm run build
160 |    ```
161 | 
162 | 5. **Initialize the database:**
163 |    ```bash
164 |    npm run rebuild
165 |    ```
166 | 
167 | 6. **Validate installation:**
168 |    ```bash
169 |    npm run test-nodes
170 |    ```
171 | 
172 | ### Running the Server
173 | 
174 | #### stdio Mode (for Claude Desktop)
175 | ```bash
176 | npm start
177 | ```
178 | 
179 | #### HTTP Mode (for remote access)
180 | ```bash
181 | npm run start:http
182 | ```
183 | 
184 | ### Environment Configuration
185 | 
186 | Create a `.env` file in the project root:
187 | 
188 | ```env
189 | # Server configuration
190 | MCP_MODE=http          # or stdio
191 | PORT=3000
192 | HOST=0.0.0.0
193 | NODE_ENV=production
194 | LOG_LEVEL=info
195 | 
196 | # Authentication (required for HTTP mode)
197 | AUTH_TOKEN=your-secure-token-here
198 | 
199 | # Database
200 | NODE_DB_PATH=./data/nodes.db
201 | REBUILD_ON_START=false
202 | ```
203 | 
204 | ## Development Setup
205 | 
206 | ### Prerequisites
207 | 
208 | - All manual installation prerequisites
209 | - TypeScript knowledge
210 | - Familiarity with MCP protocol
211 | 
212 | ### Setup Steps
213 | 
214 | 1. **Clone and install:**
215 |    ```bash
216 |    git clone https://github.com/czlonkowski/n8n-mcp.git
217 |    cd n8n-mcp
218 |    npm install
219 |    ```
220 | 
221 | 2. **Set up development environment:**
222 |    ```bash
223 |    cp .env.example .env
224 |    # Edit .env with your settings
225 |    ```
226 | 
227 | 3. **Development commands:**
228 |    ```bash
229 |    # Run in development mode with auto-reload
230 |    npm run dev
231 |    
232 |    # Run tests
233 |    npm test
234 |    
235 |    # Type checking
236 |    npm run typecheck
237 |    
238 |    # Linting
239 |    npm run lint
240 |    ```
241 | 
242 | ### Docker Development
243 | 
244 | 1. **Use docker-compose override:**
245 |    ```bash
246 |    cp docker-compose.override.yml.example docker-compose.override.yml
247 |    ```
248 | 
249 | 2. **Edit override for development:**
250 |    ```yaml
251 |    version: '3.8'
252 |    
253 |    services:
254 |      n8n-mcp:
255 |        build: .
256 |        environment:
257 |          NODE_ENV: development
258 |          LOG_LEVEL: debug
259 |        volumes:
260 |          - ./src:/app/src:ro
261 |          - ./dist:/app/dist
262 |    ```
263 | 
264 | 3. **Run with live reload:**
265 |    ```bash
266 |    docker compose up --build
267 |    ```
268 | 
269 | ## Troubleshooting
270 | 
271 | ### Common Issues
272 | 
273 | #### Port Already in Use
274 | ```bash
275 | # Find process using port 3000
276 | lsof -i :3000
277 | 
278 | # Use a different port
279 | PORT=3001 docker compose up -d
280 | ```
281 | 
282 | #### Database Initialization Failed
283 | ```bash
284 | # For Docker
285 | docker compose exec n8n-mcp npm run rebuild
286 | 
287 | # For manual installation
288 | npm run rebuild
289 | ```
290 | 
291 | #### Permission Denied Errors
292 | ```bash
293 | # Fix permissions (Linux/macOS)
294 | sudo chown -R $(whoami) ./data
295 | 
296 | # For Docker volumes
297 | docker compose exec n8n-mcp chown -R nodejs:nodejs /app/data
298 | ```
299 | 
300 | #### Node Version Mismatch
301 | The project includes automatic fallback to sql.js for compatibility. If you still have issues:
302 | ```bash
303 | # Check Node version
304 | node --version
305 | 
306 | # Use nvm to switch versions
307 | nvm use 20
308 | ```
309 | 
310 | ### Getting Help
311 | 
312 | 1. Check the logs:
313 |    - Docker: `docker compose logs`
314 |    - Manual: Check console output or `LOG_LEVEL=debug npm start`
315 | 
316 | 2. Validate the database:
317 |    ```bash
318 |    npm run validate
319 |    ```
320 | 
321 | 3. Run tests:
322 |    ```bash
323 |    npm test
324 |    ```
325 | 
326 | 4. Report issues:
327 |    - GitHub Issues: https://github.com/czlonkowski/n8n-mcp/issues
328 |    - Include logs and environment details
329 | 
330 | ## Next Steps
331 | 
332 | After installation, configure Claude Desktop to use n8n-MCP:
333 | - See [Claude Desktop Setup Guide](./README_CLAUDE_SETUP.md)
334 | - For remote deployments, see [HTTP Deployment Guide](./HTTP_DEPLOYMENT.md)
335 | - For Docker details, see [Docker README](../DOCKER_README.md)
```

--------------------------------------------------------------------------------
/deploy/quick-deploy-n8n.sh:
--------------------------------------------------------------------------------

```bash
  1 | #!/bin/bash
  2 | # Quick deployment script for n8n + n8n-mcp stack
  3 | 
  4 | set -e
  5 | 
  6 | # Colors for output
  7 | RED='\033[0;31m'
  8 | GREEN='\033[0;32m'
  9 | YELLOW='\033[1;33m'
 10 | NC='\033[0m' # No Color
 11 | 
 12 | # Default values
 13 | COMPOSE_FILE="docker-compose.n8n.yml"
 14 | ENV_FILE=".env"
 15 | ENV_EXAMPLE=".env.n8n.example"
 16 | 
 17 | # Function to print colored output
 18 | print_info() {
 19 |     echo -e "${GREEN}[INFO]${NC} $1"
 20 | }
 21 | 
 22 | print_warn() {
 23 |     echo -e "${YELLOW}[WARN]${NC} $1"
 24 | }
 25 | 
 26 | print_error() {
 27 |     echo -e "${RED}[ERROR]${NC} $1"
 28 | }
 29 | 
 30 | # Function to generate random token
 31 | generate_token() {
 32 |     openssl rand -hex 32
 33 | }
 34 | 
 35 | # Function to check prerequisites
 36 | check_prerequisites() {
 37 |     print_info "Checking prerequisites..."
 38 |     
 39 |     # Check Docker
 40 |     if ! command -v docker &> /dev/null; then
 41 |         print_error "Docker is not installed. Please install Docker first."
 42 |         exit 1
 43 |     fi
 44 |     
 45 |     # Check Docker Compose
 46 |     if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
 47 |         print_error "Docker Compose is not installed. Please install Docker Compose first."
 48 |         exit 1
 49 |     fi
 50 |     
 51 |     # Check openssl for token generation
 52 |     if ! command -v openssl &> /dev/null; then
 53 |         print_error "OpenSSL is not installed. Please install OpenSSL first."
 54 |         exit 1
 55 |     fi
 56 |     
 57 |     print_info "All prerequisites are installed."
 58 | }
 59 | 
 60 | # Function to setup environment
 61 | setup_environment() {
 62 |     print_info "Setting up environment..."
 63 |     
 64 |     # Check if .env exists
 65 |     if [ -f "$ENV_FILE" ]; then
 66 |         print_warn ".env file already exists. Backing up to .env.backup"
 67 |         cp "$ENV_FILE" ".env.backup"
 68 |     fi
 69 |     
 70 |     # Copy example env file
 71 |     if [ -f "$ENV_EXAMPLE" ]; then
 72 |         cp "$ENV_EXAMPLE" "$ENV_FILE"
 73 |         print_info "Created .env file from example"
 74 |     else
 75 |         print_error ".env.n8n.example file not found!"
 76 |         exit 1
 77 |     fi
 78 |     
 79 |     # Generate encryption key
 80 |     ENCRYPTION_KEY=$(generate_token)
 81 |     if [[ "$OSTYPE" == "darwin"* ]]; then
 82 |         sed -i '' "s/N8N_ENCRYPTION_KEY=/N8N_ENCRYPTION_KEY=$ENCRYPTION_KEY/" "$ENV_FILE"
 83 |     else
 84 |         sed -i "s/N8N_ENCRYPTION_KEY=/N8N_ENCRYPTION_KEY=$ENCRYPTION_KEY/" "$ENV_FILE"
 85 |     fi
 86 |     print_info "Generated n8n encryption key"
 87 |     
 88 |     # Generate MCP auth token
 89 |     MCP_TOKEN=$(generate_token)
 90 |     if [[ "$OSTYPE" == "darwin"* ]]; then
 91 |         sed -i '' "s/MCP_AUTH_TOKEN=/MCP_AUTH_TOKEN=$MCP_TOKEN/" "$ENV_FILE"
 92 |     else
 93 |         sed -i "s/MCP_AUTH_TOKEN=/MCP_AUTH_TOKEN=$MCP_TOKEN/" "$ENV_FILE"
 94 |     fi
 95 |     print_info "Generated MCP authentication token"
 96 |     
 97 |     print_warn "Please update the following in .env file:"
 98 |     print_warn "  - N8N_BASIC_AUTH_PASSWORD (current: changeme)"
 99 |     print_warn "  - N8N_API_KEY (get from n8n UI after first start)"
100 | }
101 | 
102 | # Function to build images
103 | build_images() {
104 |     print_info "Building n8n-mcp image..."
105 |     
106 |     if docker compose version &> /dev/null; then
107 |         docker compose -f "$COMPOSE_FILE" build
108 |     else
109 |         docker-compose -f "$COMPOSE_FILE" build
110 |     fi
111 |     
112 |     print_info "Image built successfully"
113 | }
114 | 
115 | # Function to start services
116 | start_services() {
117 |     print_info "Starting services..."
118 |     
119 |     if docker compose version &> /dev/null; then
120 |         docker compose -f "$COMPOSE_FILE" up -d
121 |     else
122 |         docker-compose -f "$COMPOSE_FILE" up -d
123 |     fi
124 |     
125 |     print_info "Services started"
126 | }
127 | 
128 | # Function to show status
129 | show_status() {
130 |     print_info "Checking service status..."
131 |     
132 |     if docker compose version &> /dev/null; then
133 |         docker compose -f "$COMPOSE_FILE" ps
134 |     else
135 |         docker-compose -f "$COMPOSE_FILE" ps
136 |     fi
137 |     
138 |     echo ""
139 |     print_info "Services are starting up. This may take a minute..."
140 |     print_info "n8n will be available at: http://localhost:5678"
141 |     print_info "n8n-mcp will be available at: http://localhost:3000"
142 |     echo ""
143 |     print_warn "Next steps:"
144 |     print_warn "1. Access n8n at http://localhost:5678"
145 |     print_warn "2. Log in with admin/changeme (or your custom password)"
146 |     print_warn "3. Go to Settings > n8n API > Create API Key"
147 |     print_warn "4. Update N8N_API_KEY in .env file"
148 |     print_warn "5. Restart n8n-mcp: docker-compose -f $COMPOSE_FILE restart n8n-mcp"
149 | }
150 | 
151 | # Function to stop services
152 | stop_services() {
153 |     print_info "Stopping services..."
154 |     
155 |     if docker compose version &> /dev/null; then
156 |         docker compose -f "$COMPOSE_FILE" down
157 |     else
158 |         docker-compose -f "$COMPOSE_FILE" down
159 |     fi
160 |     
161 |     print_info "Services stopped"
162 | }
163 | 
164 | # Function to view logs
165 | view_logs() {
166 |     SERVICE=$1
167 |     
168 |     if [ -z "$SERVICE" ]; then
169 |         if docker compose version &> /dev/null; then
170 |             docker compose -f "$COMPOSE_FILE" logs -f
171 |         else
172 |             docker-compose -f "$COMPOSE_FILE" logs -f
173 |         fi
174 |     else
175 |         if docker compose version &> /dev/null; then
176 |             docker compose -f "$COMPOSE_FILE" logs -f "$SERVICE"
177 |         else
178 |             docker-compose -f "$COMPOSE_FILE" logs -f "$SERVICE"
179 |         fi
180 |     fi
181 | }
182 | 
183 | # Main script
184 | case "${1:-help}" in
185 |     setup)
186 |         check_prerequisites
187 |         setup_environment
188 |         build_images
189 |         start_services
190 |         show_status
191 |         ;;
192 |     start)
193 |         start_services
194 |         show_status
195 |         ;;
196 |     stop)
197 |         stop_services
198 |         ;;
199 |     restart)
200 |         stop_services
201 |         start_services
202 |         show_status
203 |         ;;
204 |     status)
205 |         show_status
206 |         ;;
207 |     logs)
208 |         view_logs "${2}"
209 |         ;;
210 |     build)
211 |         build_images
212 |         ;;
213 |     *)
214 |         echo "n8n-mcp Quick Deploy Script"
215 |         echo ""
216 |         echo "Usage: $0 {setup|start|stop|restart|status|logs|build}"
217 |         echo ""
218 |         echo "Commands:"
219 |         echo "  setup    - Initial setup: create .env, build images, and start services"
220 |         echo "  start    - Start all services"
221 |         echo "  stop     - Stop all services"
222 |         echo "  restart  - Restart all services"
223 |         echo "  status   - Show service status"
224 |         echo "  logs     - View logs (optionally specify service: logs n8n-mcp)"
225 |         echo "  build    - Build/rebuild images"
226 |         echo ""
227 |         echo "Examples:"
228 |         echo "  $0 setup          # First time setup"
229 |         echo "  $0 logs n8n-mcp   # View n8n-mcp logs"
230 |         echo "  $0 restart        # Restart all services"
231 |         ;;
232 | esac
```
Page 8/59FirstPrevNextLast