#
tokens: 49447/50000 68/649 files (page 2/51)
lines: off (toggle) GitHub
raw markdown copy
This is page 2 of 51. Use http://codebase.md/czlonkowski/n8n-mcp?page={x} to view the full context.

# Directory Structure

```
├── _config.yml
├── .claude
│   └── agents
│       ├── code-reviewer.md
│       ├── context-manager.md
│       ├── debugger.md
│       ├── deployment-engineer.md
│       ├── mcp-backend-engineer.md
│       ├── n8n-mcp-tester.md
│       ├── technical-researcher.md
│       └── test-automator.md
├── .dockerignore
├── .env.docker
├── .env.example
├── .env.n8n.example
├── .env.test
├── .env.test.example
├── .github
│   ├── ABOUT.md
│   ├── BENCHMARK_THRESHOLDS.md
│   ├── FUNDING.yml
│   ├── gh-pages.yml
│   ├── secret_scanning.yml
│   └── workflows
│       ├── benchmark-pr.yml
│       ├── benchmark.yml
│       ├── docker-build-fast.yml
│       ├── docker-build-n8n.yml
│       ├── docker-build.yml
│       ├── release.yml
│       ├── test.yml
│       └── update-n8n-deps.yml
├── .gitignore
├── .npmignore
├── ANALYSIS_QUICK_REFERENCE.md
├── 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
│   ├── CI_TEST_INFRASTRUCTURE.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
│   │   ├── skills.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
│   ├── SESSION_PERSISTENCE.md
│   ├── tools-documentation-usage.md
│   ├── TYPE_STRUCTURE_VALIDATION.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_ANALYSIS.md
├── README.md
├── renovate.json
├── scripts
│   ├── analyze-optimization.sh
│   ├── audit-schema-coverage.ts
│   ├── backfill-mutation-hashes.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-initial-release-notes.js
│   ├── generate-release-notes.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
│   ├── process-batch-metadata.ts
│   ├── 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-structure-validation.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
│   ├── test-workflow-versioning.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
│   ├── constants
│   │   └── type-structures.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.ts
│   │   │   │   └── index.ts
│   │   │   ├── discovery
│   │   │   │   ├── index.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
│   │   │   │   ├── index.ts
│   │   │   │   └── search-templates.ts
│   │   │   ├── types.ts
│   │   │   ├── validation
│   │   │   │   ├── index.ts
│   │   │   │   ├── validate-node.ts
│   │   │   │   └── validate-workflow.ts
│   │   │   └── workflow_management
│   │   │       ├── index.ts
│   │   │       ├── n8n-autofix-workflow.ts
│   │   │       ├── n8n-create-workflow.ts
│   │   │       ├── n8n-delete-workflow.ts
│   │   │       ├── n8n-executions.ts
│   │   │       ├── n8n-get-workflow.ts
│   │   │       ├── n8n-list-workflows.ts
│   │   │       ├── n8n-trigger-webhook-workflow.ts
│   │   │       ├── n8n-update-full-workflow.ts
│   │   │       ├── n8n-update-partial-workflow.ts
│   │   │       ├── n8n-validate-workflow.ts
│   │   │       └── n8n-workflow-versions.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-telemetry-mutations-verbose.ts
│   │   ├── test-telemetry-mutations.ts
│   │   ├── test-webhook-autofix.ts
│   │   ├── validate.ts
│   │   └── validation-summary.ts
│   ├── services
│   │   ├── ai-node-validator.ts
│   │   ├── ai-tool-validators.ts
│   │   ├── breaking-change-detector.ts
│   │   ├── breaking-changes-registry.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-migration-service.ts
│   │   ├── node-sanitizer.ts
│   │   ├── node-similarity-service.ts
│   │   ├── node-specific-validators.ts
│   │   ├── node-version-service.ts
│   │   ├── operation-similarity-service.ts
│   │   ├── post-update-validator.ts
│   │   ├── property-dependencies.ts
│   │   ├── property-filter.ts
│   │   ├── resource-similarity-service.ts
│   │   ├── sqlite-storage-service.ts
│   │   ├── task-templates.ts
│   │   ├── type-structure-service.ts
│   │   ├── universal-expression-validator.ts
│   │   ├── workflow-auto-fixer.ts
│   │   ├── workflow-diff-engine.ts
│   │   ├── workflow-validator.ts
│   │   └── workflow-versioning-service.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
│   │   ├── intent-classifier.ts
│   │   ├── intent-sanitizer.ts
│   │   ├── mutation-tracker.ts
│   │   ├── mutation-types.ts
│   │   ├── mutation-validator.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
│   │   ├── session-state.ts
│   │   ├── type-structures.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
│       ├── expression-utils.ts
│       ├── fixed-collection-validator.ts
│       ├── logger.ts
│       ├── mcp-client.ts
│       ├── n8n-errors.ts
│       ├── node-classification.ts
│       ├── node-source-extractor.ts
│       ├── node-type-normalizer.ts
│       ├── node-type-utils.ts
│       ├── node-utils.ts
│       ├── npm-version-checker.ts
│       ├── protocol-version.ts
│       ├── simple-cache.ts
│       ├── ssrf-protection.ts
│       ├── template-node-resolver.ts
│       ├── template-sanitizer.ts
│       ├── url-detector.ts
│       ├── validation-schemas.ts
│       └── version.ts
├── test-output.txt
├── test-reinit-fix.sh
├── tests
│   ├── __snapshots__
│   │   └── .gitkeep
│   ├── auth.test.ts
│   ├── benchmarks
│   │   ├── database-queries.bench.ts
│   │   ├── index.ts
│   │   ├── mcp-tools.bench.ts
│   │   ├── mcp-tools.bench.ts.disabled
│   │   ├── mcp-tools.bench.ts.skip
│   │   ├── node-loading.bench.ts.disabled
│   │   ├── README.md
│   │   ├── search-operations.bench.ts.disabled
│   │   └── validation-performance.bench.ts.disabled
│   ├── bridge.test.ts
│   ├── comprehensive-extraction-test.js
│   ├── data
│   │   └── .gitkeep
│   ├── debug-slack-doc.js
│   ├── demo-enhanced-documentation.js
│   ├── docker-tests-README.md
│   ├── error-handler.test.ts
│   ├── examples
│   │   └── using-database-utils.test.ts
│   ├── extracted-nodes-db
│   │   ├── database-import.json
│   │   ├── extraction-report.json
│   │   ├── insert-nodes.sql
│   │   ├── n8n-nodes-base__Airtable.json
│   │   ├── n8n-nodes-base__Discord.json
│   │   ├── n8n-nodes-base__Function.json
│   │   ├── n8n-nodes-base__HttpRequest.json
│   │   ├── n8n-nodes-base__If.json
│   │   ├── n8n-nodes-base__Slack.json
│   │   ├── n8n-nodes-base__SplitInBatches.json
│   │   └── n8n-nodes-base__Webhook.json
│   ├── factories
│   │   ├── node-factory.ts
│   │   └── property-definition-factory.ts
│   ├── fixtures
│   │   ├── .gitkeep
│   │   ├── database
│   │   │   └── test-nodes.json
│   │   ├── factories
│   │   │   ├── node.factory.ts
│   │   │   └── parser-node.factory.ts
│   │   └── template-configs.ts
│   ├── helpers
│   │   └── env-helpers.ts
│   ├── http-server-auth.test.ts
│   ├── integration
│   │   ├── ai-validation
│   │   │   ├── ai-agent-validation.test.ts
│   │   │   ├── ai-tool-validation.test.ts
│   │   │   ├── chat-trigger-validation.test.ts
│   │   │   ├── e2e-validation.test.ts
│   │   │   ├── helpers.ts
│   │   │   ├── llm-chain-validation.test.ts
│   │   │   ├── README.md
│   │   │   └── TEST_REPORT.md
│   │   ├── ci
│   │   │   └── database-population.test.ts
│   │   ├── database
│   │   │   ├── connection-management.test.ts
│   │   │   ├── empty-database.test.ts
│   │   │   ├── fts5-search.test.ts
│   │   │   ├── node-fts5-search.test.ts
│   │   │   ├── node-repository.test.ts
│   │   │   ├── performance.test.ts
│   │   │   ├── sqljs-memory-leak.test.ts
│   │   │   ├── template-node-configs.test.ts
│   │   │   ├── template-repository.test.ts
│   │   │   ├── test-utils.ts
│   │   │   └── transactions.test.ts
│   │   ├── database-integration.test.ts
│   │   ├── docker
│   │   │   ├── docker-config.test.ts
│   │   │   ├── docker-entrypoint.test.ts
│   │   │   └── test-helpers.ts
│   │   ├── flexible-instance-config.test.ts
│   │   ├── mcp
│   │   │   └── template-examples-e2e.test.ts
│   │   ├── mcp-protocol
│   │   │   ├── basic-connection.test.ts
│   │   │   ├── error-handling.test.ts
│   │   │   ├── performance.test.ts
│   │   │   ├── protocol-compliance.test.ts
│   │   │   ├── README.md
│   │   │   ├── session-management.test.ts
│   │   │   ├── test-helpers.ts
│   │   │   ├── tool-invocation.test.ts
│   │   │   └── workflow-error-validation.test.ts
│   │   ├── msw-setup.test.ts
│   │   ├── n8n-api
│   │   │   ├── executions
│   │   │   │   ├── delete-execution.test.ts
│   │   │   │   ├── get-execution.test.ts
│   │   │   │   ├── list-executions.test.ts
│   │   │   │   └── trigger-webhook.test.ts
│   │   │   ├── scripts
│   │   │   │   └── cleanup-orphans.ts
│   │   │   ├── system
│   │   │   │   ├── diagnostic.test.ts
│   │   │   │   └── health-check.test.ts
│   │   │   ├── 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
│   │   ├── validation
│   │   │   └── real-world-structure-validation.test.ts
│   │   ├── workflow-creation-node-type-format.test.ts
│   │   └── workflow-diff
│   │       ├── ai-node-connection-validation.test.ts
│   │       └── node-rename-integration.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
│   │   ├── constants
│   │   │   └── type-structures.test.ts
│   │   ├── 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
│   │   │   └── session-persistence.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
│   │   │   ├── disabled-tools-additional.test.ts
│   │   │   ├── disabled-tools.test.ts
│   │   │   ├── get-node-essentials-examples.test.ts
│   │   │   ├── get-node-unified.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
│   │   ├── mcp-engine
│   │   │   └── session-persistence.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
│   │   │   ├── breaking-change-detector.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-type-structures.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-sticky-notes.test.ts
│   │   │   ├── n8n-validation.test.ts
│   │   │   ├── node-migration-service.test.ts
│   │   │   ├── node-sanitizer.test.ts
│   │   │   ├── node-similarity-service.test.ts
│   │   │   ├── node-specific-validators.test.ts
│   │   │   ├── node-version-service.test.ts
│   │   │   ├── operation-similarity-service-comprehensive.test.ts
│   │   │   ├── operation-similarity-service.test.ts
│   │   │   ├── post-update-validator.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
│   │   │   ├── type-structure-service.test.ts
│   │   │   ├── universal-expression-validator.test.ts
│   │   │   ├── validation-fixes.test.ts
│   │   │   ├── workflow-auto-fixer.test.ts
│   │   │   ├── workflow-diff-engine.test.ts
│   │   │   ├── workflow-diff-node-rename.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
│   │   │   └── workflow-versioning-service.test.ts
│   │   ├── telemetry
│   │   │   ├── batch-processor.test.ts
│   │   │   ├── config-manager.test.ts
│   │   │   ├── event-tracker.test.ts
│   │   │   ├── event-validator.test.ts
│   │   │   ├── mutation-tracker.test.ts
│   │   │   ├── mutation-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
│   │   │   └── type-structures.test.ts
│   │   ├── utils
│   │   │   ├── auth-timing-safe.test.ts
│   │   │   ├── cache-utils.test.ts
│   │   │   ├── console-manager.test.ts
│   │   │   ├── database-utils.test.ts
│   │   │   ├── expression-utils.test.ts
│   │   │   ├── fixed-collection-validator.test.ts
│   │   │   ├── n8n-errors.test.ts
│   │   │   ├── node-classification.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
├── versioned-nodes.md
├── vitest.config.benchmark.ts
├── vitest.config.integration.ts
└── vitest.config.ts
```

# Files

--------------------------------------------------------------------------------
/scripts/test-telemetry-no-select.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env npx tsx
/**
 * Test telemetry without requesting data back
 */

import { createClient } from '@supabase/supabase-js';
import dotenv from 'dotenv';

dotenv.config();

async function testNoSelect() {
  const supabaseUrl = process.env.SUPABASE_URL!;
  const supabaseAnonKey = process.env.SUPABASE_ANON_KEY!;

  console.log('🧪 Telemetry Test (No Select)\n');

  const supabase = createClient(supabaseUrl, supabaseAnonKey, {
    auth: {
      persistSession: false,
      autoRefreshToken: false,
    }
  });

  // Insert WITHOUT .select() - just fire and forget
  const testData = {
    user_id: 'test-' + Date.now(),
    event: 'test_event',
    properties: { test: true }
  };

  console.log('Inserting:', testData);

  const { error } = await supabase
    .from('telemetry_events')
    .insert([testData]);  // No .select() here!

  if (error) {
    console.error('❌ Failed:', error);
  } else {
    console.log('✅ Success! Data inserted (no response data)');
  }

  // Test workflow insert too
  const testWorkflow = {
    user_id: 'test-' + Date.now(),
    workflow_hash: 'hash-' + Date.now(),
    node_count: 3,
    node_types: ['webhook', 'http', 'slack'],
    has_trigger: true,
    has_webhook: true,
    complexity: 'simple',
    sanitized_workflow: { nodes: [], connections: {} }
  };

  console.log('\nInserting workflow:', testWorkflow);

  const { error: workflowError } = await supabase
    .from('telemetry_workflows')
    .insert([testWorkflow]);  // No .select() here!

  if (workflowError) {
    console.error('❌ Workflow failed:', workflowError);
  } else {
    console.log('✅ Workflow inserted successfully!');
  }
}

testNoSelect().catch(console.error);
```

--------------------------------------------------------------------------------
/scripts/test-single-session.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash
# Test script for single-session HTTP server

set -e

echo "🧪 Testing Single-Session HTTP Server..."
echo

# Generate test auth token if not set
if [ -z "$AUTH_TOKEN" ]; then
  export AUTH_TOKEN="test-token-$(date +%s)"
  echo "Generated test AUTH_TOKEN: $AUTH_TOKEN"
fi

# Start server in background
echo "Starting server..."
MCP_MODE=http npm start > server.log 2>&1 &
SERVER_PID=$!

# Wait for server to start
echo "Waiting for server to start..."
sleep 3

# Check health endpoint
echo
echo "Testing health endpoint..."
curl -s http://localhost:3000/health | jq .

# Test authentication failure
echo
echo "Testing authentication failure..."
curl -s -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer wrong-token" \
  -d '{"jsonrpc":"2.0","method":"tools/list","id":1}' | jq .

# Test successful request
echo
echo "Testing successful request..."
curl -s -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $AUTH_TOKEN" \
  -d '{"jsonrpc":"2.0","method":"tools/list","id":1}' | jq .

# Test session reuse
echo
echo "Testing session reuse (second request)..."
curl -s -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $AUTH_TOKEN" \
  -d '{"jsonrpc":"2.0","method":"get_database_statistics","id":2}' | jq .

# Check health again to see session info
echo
echo "Checking health to see session info..."
curl -s http://localhost:3000/health | jq .

# Clean up
echo
echo "Stopping server..."
kill $SERVER_PID 2>/dev/null || true
wait $SERVER_PID 2>/dev/null || true

echo
echo "✅ Test complete! Check server.log for details."
```

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

```typescript
/**
 * Pre-configured n8n API Client for Integration Tests
 *
 * Provides a singleton API client instance configured with test credentials.
 * Automatically loads credentials from .env (local) or GitHub secrets (CI).
 */

import { N8nApiClient } from '../../../../src/services/n8n-api-client';
import { getN8nCredentials, validateCredentials } from './credentials';

let client: N8nApiClient | null = null;

/**
 * Get or create the test n8n API client
 *
 * Creates a singleton instance configured with credentials from
 * the environment. Validates that required credentials are present.
 *
 * @returns Configured N8nApiClient instance
 * @throws Error if credentials are missing or invalid
 *
 * @example
 * const client = getTestN8nClient();
 * const workflow = await client.createWorkflow({ ... });
 */
export function getTestN8nClient(): N8nApiClient {
  if (!client) {
    const creds = getN8nCredentials();
    validateCredentials(creds);
    client = new N8nApiClient({
      baseUrl: creds.url,
      apiKey: creds.apiKey,
      timeout: 30000,
      maxRetries: 3
    });
  }
  return client;
}

/**
 * Reset the test client instance
 *
 * Forces recreation of the client on next call to getTestN8nClient().
 * Useful for testing or when credentials change.
 */
export function resetTestN8nClient(): void {
  client = null;
}

/**
 * Check if the n8n API is accessible
 *
 * Performs a health check to verify API connectivity.
 *
 * @returns true if API is accessible, false otherwise
 */
export async function isN8nApiAccessible(): Promise<boolean> {
  try {
    const client = getTestN8nClient();
    await client.healthCheck();
    return true;
  } catch {
    return false;
  }
}

```

--------------------------------------------------------------------------------
/tests/factories/node-factory.ts:
--------------------------------------------------------------------------------

```typescript
import { Factory } from 'fishery';
import { faker } from '@faker-js/faker';
import { ParsedNode } from '../../src/parsers/node-parser';

/**
 * Factory for generating ParsedNode test data using Fishery.
 * Creates realistic node configurations with random but valid data.
 * 
 * @example
 * ```typescript
 * // Create a single node with defaults
 * const node = NodeFactory.build();
 * 
 * // Create a node with specific properties
 * const slackNode = NodeFactory.build({
 *   nodeType: 'nodes-base.slack',
 *   displayName: 'Slack',
 *   isAITool: true
 * });
 * 
 * // Create multiple nodes
 * const nodes = NodeFactory.buildList(5);
 * 
 * // Create with custom sequence
 * const sequencedNodes = NodeFactory.buildList(3, {
 *   displayName: (i) => `Node ${i}`
 * });
 * ```
 */
export const NodeFactory = Factory.define<ParsedNode>(() => ({
  nodeType: faker.helpers.arrayElement(['nodes-base.', 'nodes-langchain.']) + faker.word.noun(),
  displayName: faker.helpers.arrayElement(['HTTP', 'Slack', 'Google', 'AWS']) + ' ' + faker.word.noun(),
  description: faker.lorem.sentence(),
  packageName: faker.helpers.arrayElement(['n8n-nodes-base', '@n8n/n8n-nodes-langchain']),
  category: faker.helpers.arrayElement(['transform', 'trigger', 'output', 'input']),
  style: faker.helpers.arrayElement(['declarative', 'programmatic']),
  isAITool: faker.datatype.boolean(),
  isTrigger: faker.datatype.boolean(),
  isWebhook: faker.datatype.boolean(),
  isVersioned: faker.datatype.boolean(),
  version: faker.helpers.arrayElement(['1.0', '2.0', '3.0', '4.2']),
  documentation: faker.datatype.boolean() ? faker.lorem.paragraphs(3) : undefined,
  properties: [],
  operations: [],
  credentials: []
}));
```

--------------------------------------------------------------------------------
/docs/WINDSURF_SETUP.md:
--------------------------------------------------------------------------------

```markdown
# Windsurf Setup

Connect n8n-MCP to Windsurf IDE for enhanced n8n workflow development with AI assistance.

[![n8n-mcp Windsurf Setup Tutorial](./img/windsurf_tut.png)](https://www.youtube.com/watch?v=klxxT1__izg)

## Video Tutorial

Watch the complete setup process: [n8n-MCP Windsurf Setup Tutorial](https://www.youtube.com/watch?v=klxxT1__izg)

## Setup Process

### 1. Access MCP Configuration

1. Go to Settings in Windsurf
2. Navigate to Windsurf Settings
3. Go to MCP Servers > Manage Plugins
4. Click "View Raw Config"

### 2. Add n8n-MCP Configuration

Copy the configuration from this repository and add it to your MCP config:

**Basic configuration (documentation tools only):**
```json
{
  "mcpServers": {
    "n8n-mcp": {
      "command": "npx",
      "args": ["n8n-mcp"],
      "env": {
        "MCP_MODE": "stdio",
        "LOG_LEVEL": "error",
        "DISABLE_CONSOLE_OUTPUT": "true"
      }
    }
  }
}
```

**Full configuration (with n8n management tools):**
```json
{
  "mcpServers": {
    "n8n-mcp": {
      "command": "npx",
      "args": ["n8n-mcp"],
      "env": {
        "MCP_MODE": "stdio",
        "LOG_LEVEL": "error",
        "DISABLE_CONSOLE_OUTPUT": "true",
        "N8N_API_URL": "https://your-n8n-instance.com",
        "N8N_API_KEY": "your-api-key"
      }
    }
  }
}
```

### 3. Configure n8n Connection

1. Replace `https://your-n8n-instance.com` with your actual n8n URL
2. Replace `your-api-key` with your n8n API key
3. Click refresh to apply the changes

### 4. Set Up Project Instructions

1. Create a `.windsurfrules` file in your project root
2. Copy the Claude Project instructions from the [main README's Claude Project Setup section](../README.md#-claude-project-setup)

```

--------------------------------------------------------------------------------
/scripts/test-workflow-tracking-debug.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env npx tsx
/**
 * Debug workflow tracking in telemetry manager
 */

import { TelemetryManager } from '../src/telemetry/telemetry-manager';

// Get the singleton instance
const telemetry = TelemetryManager.getInstance();

const testWorkflow = {
  nodes: [
    {
      id: 'webhook1',
      type: 'n8n-nodes-base.webhook',
      name: 'Webhook',
      position: [0, 0],
      parameters: { 
        path: '/test-' + Date.now(),
        httpMethod: 'POST'
      }
    },
    {
      id: 'http1',
      type: 'n8n-nodes-base.httpRequest',
      name: 'HTTP Request',
      position: [250, 0],
      parameters: { 
        url: 'https://api.example.com/data',
        method: 'GET'
      }
    },
    {
      id: 'slack1',
      type: 'n8n-nodes-base.slack',
      name: 'Slack',
      position: [500, 0],
      parameters: {
        channel: '#general',
        text: 'Workflow complete!'
      }
    }
  ],
  connections: {
    'webhook1': {
      main: [[{ node: 'http1', type: 'main', index: 0 }]]
    },
    'http1': {
      main: [[{ node: 'slack1', type: 'main', index: 0 }]]
    }
  }
};

console.log('🧪 Testing Workflow Tracking\n');
console.log('Workflow has', testWorkflow.nodes.length, 'nodes');

// Track the workflow
console.log('Calling trackWorkflowCreation...');
telemetry.trackWorkflowCreation(testWorkflow, true);

console.log('Waiting for async processing...');

// Wait for setImmediate to process
setTimeout(async () => {
  console.log('\nForcing flush...');
  await telemetry.flush();
  console.log('✅ Flush complete!');
  
  console.log('\nWorkflow should now be in the telemetry_workflows table.');
  console.log('Check with: SELECT * FROM telemetry_workflows ORDER BY created_at DESC LIMIT 1;');
}, 2000);

```

--------------------------------------------------------------------------------
/scripts/test-workflow-sanitizer.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env npx tsx
/**
 * Test workflow sanitizer
 */

import { WorkflowSanitizer } from '../src/telemetry/workflow-sanitizer';

const testWorkflow = {
  nodes: [
    {
      id: 'webhook1',
      type: 'n8n-nodes-base.webhook',
      name: 'Webhook',
      position: [0, 0],
      parameters: { 
        path: '/test-webhook',
        httpMethod: 'POST'
      }
    },
    {
      id: 'http1',
      type: 'n8n-nodes-base.httpRequest',
      name: 'HTTP Request',
      position: [250, 0],
      parameters: { 
        url: 'https://api.example.com/endpoint',
        method: 'GET',
        authentication: 'genericCredentialType',
        sendHeaders: true,
        headerParameters: {
          parameters: [
            {
              name: 'Authorization',
              value: 'Bearer sk-1234567890abcdef'
            }
          ]
        }
      }
    }
  ],
  connections: {
    'webhook1': {
      main: [[{ node: 'http1', type: 'main', index: 0 }]]
    }
  }
};

console.log('🧪 Testing Workflow Sanitizer\n');
console.log('Original workflow has', testWorkflow.nodes.length, 'nodes');

try {
  const sanitized = WorkflowSanitizer.sanitizeWorkflow(testWorkflow);
  
  console.log('\n✅ Sanitization successful!');
  console.log('\nSanitized output:');
  console.log(JSON.stringify(sanitized, null, 2));
  
  console.log('\n📊 Metrics:');
  console.log('- Workflow Hash:', sanitized.workflowHash);
  console.log('- Node Count:', sanitized.nodeCount);
  console.log('- Node Types:', sanitized.nodeTypes);
  console.log('- Has Trigger:', sanitized.hasTrigger);
  console.log('- Has Webhook:', sanitized.hasWebhook);
  console.log('- Complexity:', sanitized.complexity);
} catch (error) {
  console.error('❌ Sanitization failed:', error);
}

```

--------------------------------------------------------------------------------
/scripts/test-node-info.js:
--------------------------------------------------------------------------------

```javascript
#!/usr/bin/env node
/**
 * Test get_node_info to diagnose timeout issues
 */

const { N8NDocumentationMCPServer } = require('../dist/mcp/server');

async function testNodeInfo() {
  console.log('🔍 Testing get_node_info...\n');
  
  try {
    const server = new N8NDocumentationMCPServer();
    await new Promise(resolve => setTimeout(resolve, 500));
    
    const nodes = [
      'nodes-base.httpRequest',
      'nodes-base.webhook',
      'nodes-langchain.agent'
    ];
    
    for (const nodeType of nodes) {
      console.log(`Testing ${nodeType}...`);
      const start = Date.now();
      
      try {
        const result = await server.executeTool('get_node_info', { nodeType });
        const elapsed = Date.now() - start;
        const size = JSON.stringify(result).length;
        
        console.log(`✅ Success in ${elapsed}ms`);
        console.log(`   Size: ${(size / 1024).toFixed(1)}KB`);
        console.log(`   Properties: ${result.properties?.length || 0}`);
        console.log(`   Operations: ${result.operations?.length || 0}`);
        
        // Check for issues
        if (size > 50000) {
          console.log(`   ⚠️  WARNING: Response over 50KB!`);
        }
        
        // Check property quality
        const propsWithoutDesc = result.properties?.filter(p => !p.description && !p.displayName).length || 0;
        if (propsWithoutDesc > 0) {
          console.log(`   ⚠️  ${propsWithoutDesc} properties without descriptions`);
        }
        
      } catch (error) {
        const elapsed = Date.now() - start;
        console.log(`❌ Failed after ${elapsed}ms: ${error.message}`);
      }
      
      console.log('');
    }
    
  } catch (error) {
    console.error('Fatal error:', error);
  }
}

testNodeInfo().catch(console.error);
```

--------------------------------------------------------------------------------
/docs/CURSOR_SETUP.md:
--------------------------------------------------------------------------------

```markdown
# Cursor Setup

Connect n8n-MCP to Cursor IDE for enhanced n8n workflow development with AI assistance.

[![n8n-mcp Cursor Setup Tutorial](./img/cursor_tut.png)](https://www.youtube.com/watch?v=hRmVxzLGJWI)

## Video Tutorial

Watch the complete setup process: [n8n-MCP Cursor Setup Tutorial](https://www.youtube.com/watch?v=hRmVxzLGJWI)

## Setup Process

### 1. Create MCP Configuration

1. Create a `.cursor` folder in your project root
2. Create `mcp.json` file inside the `.cursor` folder
3. Copy the configuration from this repository

**Basic configuration (documentation tools only):**
```json
{
  "mcpServers": {
    "n8n-mcp": {
      "command": "npx",
      "args": ["n8n-mcp"],
      "env": {
        "MCP_MODE": "stdio",
        "LOG_LEVEL": "error",
        "DISABLE_CONSOLE_OUTPUT": "true"
      }
    }
  }
}
```

**Full configuration (with n8n management tools):**
```json
{
  "mcpServers": {
    "n8n-mcp": {
      "command": "npx",
      "args": ["n8n-mcp"],
      "env": {
        "MCP_MODE": "stdio",
        "LOG_LEVEL": "error",
        "DISABLE_CONSOLE_OUTPUT": "true",
        "N8N_API_URL": "https://your-n8n-instance.com",
        "N8N_API_KEY": "your-api-key"
      }
    }
  }
}
```

### 2. Configure n8n Connection

1. Replace `https://your-n8n-instance.com` with your actual n8n URL
2. Replace `your-api-key` with your n8n API key

### 3. Enable MCP Server

1. Click "Enable MCP Server" button in Cursor
2. Go to Cursor Settings
3. Search for "mcp"
4. Confirm MCP is working

### 4. Set Up Project Instructions

1. In your Cursor chat, invoke "create rule" and hit Tab
2. Name the rule (e.g., "n8n-mcp")
3. Set rule type to "always"
4. Copy the Claude Project instructions from the [main README's Claude Project Setup section](../README.md#-claude-project-setup)


```

--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------

```json
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:base"
  ],
  "schedule": ["after 9am on monday"],
  "timezone": "UTC",
  "packageRules": [
    {
      "description": "Group all n8n-related updates",
      "groupName": "n8n dependencies",
      "matchPackagePatterns": ["^n8n", "^@n8n/"],
      "matchUpdateTypes": ["minor", "patch"],
      "schedule": ["after 9am on monday"]
    },
    {
      "description": "Require approval for major n8n updates",
      "matchPackagePatterns": ["^n8n", "^@n8n/"],
      "matchUpdateTypes": ["major"],
      "dependencyDashboardApproval": true
    },
    {
      "description": "Disable updates for other dependencies",
      "excludePackagePatterns": ["^n8n", "^@n8n/"],
      "enabled": false
    }
  ],
  "postUpdateOptions": [
    "npmDedupe"
  ],
  "prConcurrentLimit": 1,
  "prCreation": "immediate",
  "labels": ["dependencies", "n8n-update"],
  "assignees": ["@czlonkowski"],
  "reviewers": ["@czlonkowski"],
  "commitMessagePrefix": "chore: ",
  "commitMessageTopic": "{{depName}}",
  "commitMessageExtra": "from {{currentVersion}} to {{newVersion}}",
  "prBodyDefinitions": {
    "Package": "{{depName}}",
    "Type": "{{depType}}",
    "Update": "{{updateType}}",
    "Current": "{{currentVersion}}",
    "New": "{{newVersion}}",
    "Change": "[Compare]({{compareUrl}})"
  },
  "prBodyColumns": ["Package", "Type", "Update", "Current", "New", "Change"],
  "prBodyNotes": [
    "**Important**: Please review the [n8n release notes](https://docs.n8n.io/release-notes/) for breaking changes.",
    "",
    "After merging, please:",
    "1. Run `npm run rebuild` to update the node database",
    "2. Run `npm run validate` to ensure all nodes are properly loaded",
    "3. Test critical functionality"
  ]
}
```

--------------------------------------------------------------------------------
/scripts/test-error-message-tracking.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Test script to verify error message tracking is working
 */

import { telemetry } from '../src/telemetry';

async function testErrorTracking() {
  console.log('=== Testing Error Message Tracking ===\n');

  // Track session first
  console.log('1. Starting session...');
  telemetry.trackSessionStart();

  // Track an error WITH a message
  console.log('\n2. Tracking error WITH message:');
  const testErrorMessage = 'This is a test error message with sensitive data: password=secret123 and [email protected]';
  telemetry.trackError(
    'TypeError',
    'tool_execution',
    'test_tool',
    testErrorMessage
  );
  console.log(`   Original message: "${testErrorMessage}"`);

  // Track an error WITHOUT a message
  console.log('\n3. Tracking error WITHOUT message:');
  telemetry.trackError(
    'Error',
    'tool_execution',
    'test_tool2'
  );

  // Check the event queue
  const metrics = telemetry.getMetrics();
  console.log('\n4. Telemetry metrics:');
  console.log('   Status:', metrics.status);
  console.log('   Events queued:', metrics.tracking.eventsQueued);

  // Get raw event queue to inspect
  const eventTracker = (telemetry as any).eventTracker;
  const queue = eventTracker.getEventQueue();

  console.log('\n5. Event queue contents:');
  queue.forEach((event, i) => {
    console.log(`\n   Event ${i + 1}:`);
    console.log(`   - Type: ${event.event}`);
    console.log(`   - Properties:`, JSON.stringify(event.properties, null, 6));
  });

  // Flush to database
  console.log('\n6. Flushing to database...');
  await telemetry.flush();

  console.log('\n7. Done! Check Supabase for error events with "error" field.');
  console.log('   Query: SELECT * FROM telemetry_events WHERE event = \'error_occurred\' ORDER BY created_at DESC LIMIT 5;');
}

testErrorTracking().catch(console.error);

```

--------------------------------------------------------------------------------
/scripts/sync-runtime-version.js:
--------------------------------------------------------------------------------

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

/**
 * Sync version from package.json to package.runtime.json and README.md
 * This ensures all files always have the same version
 */

const fs = require('fs');
const path = require('path');

const packageJsonPath = path.join(__dirname, '..', 'package.json');
const packageRuntimePath = path.join(__dirname, '..', 'package.runtime.json');
const readmePath = path.join(__dirname, '..', 'README.md');

try {
  // Read package.json
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
  const version = packageJson.version;
  
  // Read package.runtime.json
  const packageRuntime = JSON.parse(fs.readFileSync(packageRuntimePath, 'utf-8'));
  
  // Update version if different
  if (packageRuntime.version !== version) {
    packageRuntime.version = version;
    
    // Write back with proper formatting
    fs.writeFileSync(
      packageRuntimePath, 
      JSON.stringify(packageRuntime, null, 2) + '\n',
      'utf-8'
    );
    
    console.log(`✅ Updated package.runtime.json version to ${version}`);
  } else {
    console.log(`✓ package.runtime.json already at version ${version}`);
  }
  
  // Update README.md version badge
  let readmeContent = fs.readFileSync(readmePath, 'utf-8');
  const versionBadgeRegex = /(\[!\[Version\]\(https:\/\/img\.shields\.io\/badge\/version-)[^-]+(-.+?\)\])/;
  const newVersionBadge = `$1${version}$2`;
  const updatedReadmeContent = readmeContent.replace(versionBadgeRegex, newVersionBadge);
  
  if (updatedReadmeContent !== readmeContent) {
    fs.writeFileSync(readmePath, updatedReadmeContent);
    console.log(`✅ Updated README.md version badge to ${version}`);
  } else {
    console.log(`✓ README.md already has version badge ${version}`);
  }
} catch (error) {
  console.error('❌ Error syncing version:', error.message);
  process.exit(1);
}
```

--------------------------------------------------------------------------------
/src/config/n8n-api.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';
import dotenv from 'dotenv';
import { logger } from '../utils/logger';

// n8n API configuration schema
const n8nApiConfigSchema = z.object({
  N8N_API_URL: z.string().url().optional(),
  N8N_API_KEY: z.string().min(1).optional(),
  N8N_API_TIMEOUT: z.coerce.number().positive().default(30000),
  N8N_API_MAX_RETRIES: z.coerce.number().positive().default(3),
});

// Track if we've loaded env vars
let envLoaded = false;

// Parse and validate n8n API configuration
export function getN8nApiConfig() {
  // Load environment variables on first access
  if (!envLoaded) {
    dotenv.config();
    envLoaded = true;
  }
  
  const result = n8nApiConfigSchema.safeParse(process.env);
  
  if (!result.success) {
    return null;
  }
  
  const config = result.data;
  
  // Check if both URL and API key are provided
  if (!config.N8N_API_URL || !config.N8N_API_KEY) {
    return null;
  }
  
  return {
    baseUrl: config.N8N_API_URL,
    apiKey: config.N8N_API_KEY,
    timeout: config.N8N_API_TIMEOUT,
    maxRetries: config.N8N_API_MAX_RETRIES,
  };
}

// Helper to check if n8n API is configured (lazy check)
export function isN8nApiConfigured(): boolean {
  const config = getN8nApiConfig();
  return config !== null;
}

/**
 * Create n8n API configuration from instance context
 * Used for flexible instance configuration support
 */
export function getN8nApiConfigFromContext(context: {
  n8nApiUrl?: string;
  n8nApiKey?: string;
  n8nApiTimeout?: number;
  n8nApiMaxRetries?: number;
}): N8nApiConfig | null {
  if (!context.n8nApiUrl || !context.n8nApiKey) {
    return null;
  }

  return {
    baseUrl: context.n8nApiUrl,
    apiKey: context.n8nApiKey,
    timeout: context.n8nApiTimeout ?? 30000,
    maxRetries: context.n8nApiMaxRetries ?? 3,
  };
}

// Type export
export type N8nApiConfig = NonNullable<ReturnType<typeof getN8nApiConfig>>;
```

--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------

```yaml
# docker-compose.yml
# For optimized builds with BuildKit, use: docker-compose -f docker-compose.buildkit.yml up
version: '3.8'

services:
  n8n-mcp:
    image: ghcr.io/czlonkowski/n8n-mcp:latest
    container_name: n8n-mcp
    restart: unless-stopped
    
    # Environment configuration
    environment:
      # Mode configuration
      MCP_MODE: ${MCP_MODE:-http}
      USE_FIXED_HTTP: ${USE_FIXED_HTTP:-true}  # Use fixed implementation for stability
      AUTH_TOKEN: ${AUTH_TOKEN:?AUTH_TOKEN is required for HTTP mode}
      
      # Application settings
      NODE_ENV: ${NODE_ENV:-production}
      LOG_LEVEL: ${LOG_LEVEL:-info}
      PORT: ${PORT:-3000}
      
      # Database
      NODE_DB_PATH: ${NODE_DB_PATH:-/app/data/nodes.db}
      REBUILD_ON_START: ${REBUILD_ON_START:-false}

      # Telemetry: Anonymous usage statistics are ENABLED by default
      # To opt-out, uncomment and set to 'true':
      # N8N_MCP_TELEMETRY_DISABLED: ${N8N_MCP_TELEMETRY_DISABLED:-true}

      # Optional: n8n API configuration (enables 16 additional management tools)
      # Uncomment and configure to enable n8n workflow management
      # N8N_API_URL: ${N8N_API_URL}
      # N8N_API_KEY: ${N8N_API_KEY}
      # N8N_API_TIMEOUT: ${N8N_API_TIMEOUT:-30000}
      # N8N_API_MAX_RETRIES: ${N8N_API_MAX_RETRIES:-3}
    
    # Volumes for persistence
    volumes:
      - n8n-mcp-data:/app/data
    
    # Port mapping
    ports:
      - "${PORT:-3000}:${PORT:-3000}"
    
    # Resource limits
    deploy:
      resources:
        limits:
          memory: 512M
        reservations:
          memory: 256M
    
    # Health check
    healthcheck:
      test: ["CMD", "sh", "-c", "curl -f http://127.0.0.1:$${PORT:-3000}/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

# Named volume for data persistence
volumes:
  n8n-mcp-data:
    driver: local
```

--------------------------------------------------------------------------------
/tests/integration/n8n-api/utils/node-repository.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Node Repository Utility for Integration Tests
 *
 * Provides a singleton NodeRepository instance for integration tests
 * that require validation or autofix functionality.
 */

import path from 'path';
import { createDatabaseAdapter, DatabaseAdapter } from '../../../../src/database/database-adapter';
import { NodeRepository } from '../../../../src/database/node-repository';

let repositoryInstance: NodeRepository | null = null;
let dbInstance: DatabaseAdapter | null = null;

/**
 * Get or create NodeRepository instance
 *
 * Uses the production nodes.db database (data/nodes.db).
 *
 * @returns Singleton NodeRepository instance
 * @throws {Error} If database file cannot be found or opened
 *
 * @example
 * const repository = await getNodeRepository();
 * const nodeInfo = await repository.getNodeByType('n8n-nodes-base.webhook');
 */
export async function getNodeRepository(): Promise<NodeRepository> {
  if (repositoryInstance) {
    return repositoryInstance;
  }

  const dbPath = path.join(process.cwd(), 'data/nodes.db');
  dbInstance = await createDatabaseAdapter(dbPath);
  repositoryInstance = new NodeRepository(dbInstance);

  return repositoryInstance;
}

/**
 * Close database and reset repository instance
 *
 * Should be called in test cleanup (afterAll) to prevent resource leaks.
 * Properly closes the database connection and resets the singleton.
 *
 * @example
 * afterAll(async () => {
 *   await closeNodeRepository();
 * });
 */
export async function closeNodeRepository(): Promise<void> {
  if (dbInstance && typeof dbInstance.close === 'function') {
    await dbInstance.close();
  }
  dbInstance = null;
  repositoryInstance = null;
}

/**
 * Reset repository instance (useful for test cleanup)
 *
 * @deprecated Use closeNodeRepository() instead to properly close database connections
 */
export function resetNodeRepository(): void {
  repositoryInstance = null;
}

```

--------------------------------------------------------------------------------
/tests/factories/property-definition-factory.ts:
--------------------------------------------------------------------------------

```typescript
import { Factory } from 'fishery';
import { faker } from '@faker-js/faker';

/**
 * Interface for n8n node property definitions.
 * Represents the structure of properties that configure node behavior.
 */
interface PropertyDefinition {
  name: string;
  displayName: string;
  type: string;
  default?: any;
  required?: boolean;
  description?: string;
  options?: any[];
}

/**
 * Factory for generating PropertyDefinition test data.
 * Creates realistic property configurations for testing node validation and processing.
 * 
 * @example
 * ```typescript
 * // Create a single property
 * const prop = PropertyDefinitionFactory.build();
 * 
 * // Create a required string property
 * const urlProp = PropertyDefinitionFactory.build({
 *   name: 'url',
 *   displayName: 'URL',
 *   type: 'string',
 *   required: true
 * });
 * 
 * // Create an options property with choices
 * const methodProp = PropertyDefinitionFactory.build({
 *   name: 'method',
 *   type: 'options',
 *   options: [
 *     { name: 'GET', value: 'GET' },
 *     { name: 'POST', value: 'POST' }
 *   ]
 * });
 * 
 * // Create multiple properties for a node
 * const nodeProperties = PropertyDefinitionFactory.buildList(5);
 * ```
 */
export const PropertyDefinitionFactory = Factory.define<PropertyDefinition>(() => ({
  name: faker.word.noun() + faker.word.adjective().charAt(0).toUpperCase() + faker.word.adjective().slice(1),
  displayName: faker.helpers.arrayElement(['URL', 'Method', 'Headers', 'Body', 'Authentication']),
  type: faker.helpers.arrayElement(['string', 'number', 'boolean', 'options', 'json']),
  default: faker.datatype.boolean() ? faker.word.sample() : undefined,
  required: faker.datatype.boolean(),
  description: faker.lorem.sentence(),
  options: faker.datatype.boolean() ? [
    {
      name: faker.word.noun(),
      value: faker.word.noun(),
      description: faker.lorem.sentence()
    }
  ] : undefined
}));
```

--------------------------------------------------------------------------------
/src/mcp/tool-docs/index.ts:
--------------------------------------------------------------------------------

```typescript
import { ToolDocumentation } from './types';

// Import all tool documentations
import { searchNodesDoc } from './discovery';
import { getNodeDoc } from './configuration';
import { validateNodeDoc, validateWorkflowDoc } from './validation';
import { getTemplateDoc, searchTemplatesDoc } from './templates';
import {
  toolsDocumentationDoc,
  n8nHealthCheckDoc
} from './system';
import { aiAgentsGuide } from './guides';
import {
  n8nCreateWorkflowDoc,
  n8nGetWorkflowDoc,
  n8nUpdateFullWorkflowDoc,
  n8nUpdatePartialWorkflowDoc,
  n8nDeleteWorkflowDoc,
  n8nListWorkflowsDoc,
  n8nValidateWorkflowDoc,
  n8nAutofixWorkflowDoc,
  n8nTriggerWebhookWorkflowDoc,
  n8nExecutionsDoc,
  n8nWorkflowVersionsDoc
} from './workflow_management';

// Combine all tool documentations into a single object
export const toolsDocumentation: Record<string, ToolDocumentation> = {
  // System tools
  tools_documentation: toolsDocumentationDoc,
  n8n_health_check: n8nHealthCheckDoc,

  // Guides
  ai_agents_guide: aiAgentsGuide,

  // Discovery tools
  search_nodes: searchNodesDoc,

  // Configuration tools
  get_node: getNodeDoc,

  // Validation tools
  validate_node: validateNodeDoc,
  validate_workflow: validateWorkflowDoc,

  // Template tools
  get_template: getTemplateDoc,
  search_templates: searchTemplatesDoc,

  // Workflow Management tools (n8n API)
  n8n_create_workflow: n8nCreateWorkflowDoc,
  n8n_get_workflow: n8nGetWorkflowDoc,
  n8n_update_full_workflow: n8nUpdateFullWorkflowDoc,
  n8n_update_partial_workflow: n8nUpdatePartialWorkflowDoc,
  n8n_delete_workflow: n8nDeleteWorkflowDoc,
  n8n_list_workflows: n8nListWorkflowsDoc,
  n8n_validate_workflow: n8nValidateWorkflowDoc,
  n8n_autofix_workflow: n8nAutofixWorkflowDoc,
  n8n_trigger_webhook_workflow: n8nTriggerWebhookWorkflowDoc,
  n8n_executions: n8nExecutionsDoc,
  n8n_workflow_versions: n8nWorkflowVersionsDoc
};

// Re-export types
export type { ToolDocumentation } from './types';
```

--------------------------------------------------------------------------------
/tests/extracted-nodes-db/extraction-report.json:
--------------------------------------------------------------------------------

```json
[
  {
    "nodeType": "n8n-nodes-base.Slack",
    "success": true,
    "hasPackageInfo": true,
    "hasCredentials": true,
    "sourceSize": 1007,
    "credentialSize": 7553,
    "packageName": "n8n-nodes-base",
    "packageVersion": "1.14.1"
  },
  {
    "nodeType": "n8n-nodes-base.Discord",
    "success": true,
    "hasPackageInfo": true,
    "hasCredentials": false,
    "sourceSize": 10049,
    "credentialSize": 0,
    "packageName": "n8n-nodes-base",
    "packageVersion": "1.14.1"
  },
  {
    "nodeType": "n8n-nodes-base.HttpRequest",
    "success": true,
    "hasPackageInfo": true,
    "hasCredentials": false,
    "sourceSize": 1343,
    "credentialSize": 0,
    "packageName": "n8n-nodes-base",
    "packageVersion": "1.14.1"
  },
  {
    "nodeType": "n8n-nodes-base.Webhook",
    "success": true,
    "hasPackageInfo": true,
    "hasCredentials": false,
    "sourceSize": 10667,
    "credentialSize": 0,
    "packageName": "n8n-nodes-base",
    "packageVersion": "1.14.1"
  },
  {
    "nodeType": "n8n-nodes-base.If",
    "success": true,
    "hasPackageInfo": true,
    "hasCredentials": false,
    "sourceSize": 20533,
    "credentialSize": 0,
    "packageName": "n8n-nodes-base",
    "packageVersion": "1.14.1"
  },
  {
    "nodeType": "n8n-nodes-base.SplitInBatches",
    "success": true,
    "hasPackageInfo": true,
    "hasCredentials": false,
    "sourceSize": 1135,
    "credentialSize": 0,
    "packageName": "n8n-nodes-base",
    "packageVersion": "1.14.1"
  },
  {
    "nodeType": "n8n-nodes-base.Airtable",
    "success": true,
    "hasPackageInfo": true,
    "hasCredentials": true,
    "sourceSize": 936,
    "credentialSize": 5985,
    "packageName": "n8n-nodes-base",
    "packageVersion": "1.14.1"
  },
  {
    "nodeType": "n8n-nodes-base.Function",
    "success": true,
    "hasPackageInfo": true,
    "hasCredentials": false,
    "sourceSize": 7449,
    "credentialSize": 0,
    "packageName": "n8n-nodes-base",
    "packageVersion": "1.14.1"
  }
]
```

--------------------------------------------------------------------------------
/tests/setup/global-setup.ts:
--------------------------------------------------------------------------------

```typescript
import { beforeEach, afterEach, vi } from 'vitest';
import { loadTestEnvironment, getTestConfig, getTestTimeout } from './test-env';

// CI Debug: Log environment loading in CI only
if (process.env.CI === 'true') {
  console.log('[CI-DEBUG] Global setup starting, NODE_ENV:', process.env.NODE_ENV);
}

// Load test environment configuration
loadTestEnvironment();

if (process.env.CI === 'true') {
  console.log('[CI-DEBUG] Global setup complete, N8N_API_URL:', process.env.N8N_API_URL);
}

// Get test configuration
const testConfig = getTestConfig();

// Reset mocks between tests
beforeEach(() => {
  vi.clearAllMocks();
});

// Clean up after each test
afterEach(() => {
  vi.restoreAllMocks();
  
  // Perform cleanup if enabled
  if (testConfig.cleanup.enabled) {
    // Add cleanup logic here if needed
  }
});

// Global test timeout from configuration
vi.setConfig({ testTimeout: getTestTimeout('global') });

// Configure console output based on test configuration
if (!testConfig.logging.debug) {
  global.console = {
    ...console,
    log: vi.fn(),
    debug: vi.fn(),
    info: vi.fn(),
    warn: testConfig.logging.level === 'error' ? vi.fn() : console.warn,
    error: console.error, // Always show errors
  };
}

// Set up performance monitoring if enabled
if (testConfig.performance) {
  // Use a high-resolution timer that maintains timing precision
  let startTime = process.hrtime.bigint();
  
  global.performance = global.performance || {
    now: () => {
      // Convert nanoseconds to milliseconds with high precision
      const currentTime = process.hrtime.bigint();
      return Number(currentTime - startTime) / 1000000; // Convert nanoseconds to milliseconds
    },
    mark: vi.fn(),
    measure: vi.fn(),
    getEntriesByName: vi.fn(() => []),
    getEntriesByType: vi.fn(() => []),
    clearMarks: vi.fn(),
    clearMeasures: vi.fn(),
  } as any;
}

// Export test configuration for use in tests
export { testConfig, getTestTimeout, getTestConfig };
```

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

```typescript
/**
 * Error Sanitizer for Startup Errors (v2.18.3)
 * Extracts and sanitizes error messages with security-focused patterns
 * Now uses shared sanitization utilities to avoid code duplication
 */

import { logger } from '../utils/logger';
import { sanitizeErrorMessageCore } from './error-sanitization-utils';

/**
 * Extract error message from unknown error type
 * Safely handles Error objects, strings, and other types
 */
export function extractErrorMessage(error: unknown): string {
  try {
    if (error instanceof Error) {
      // Include stack trace if available (will be truncated later)
      return error.stack || error.message || 'Unknown error';
    }

    if (typeof error === 'string') {
      return error;
    }

    if (error && typeof error === 'object') {
      // Try to extract message from object
      const errorObj = error as any;
      if (errorObj.message) {
        return String(errorObj.message);
      }
      if (errorObj.error) {
        return String(errorObj.error);
      }
      // Fall back to JSON stringify with truncation
      try {
        return JSON.stringify(error).substring(0, 500);
      } catch {
        return 'Error object (unstringifiable)';
      }
    }

    return String(error);
  } catch (extractError) {
    logger.debug('Error during message extraction:', extractError);
    return 'Error message extraction failed';
  }
}

/**
 * Sanitize startup error message to remove sensitive data
 * Now uses shared sanitization core from error-sanitization-utils.ts (v2.18.3)
 * This eliminates code duplication and the ReDoS vulnerability
 */
export function sanitizeStartupError(errorMessage: string): string {
  return sanitizeErrorMessageCore(errorMessage);
}

/**
 * Combined operation: Extract and sanitize error message
 * This is the main entry point for startup error processing
 */
export function processStartupError(error: unknown): string {
  const message = extractErrorMessage(error);
  return sanitizeStartupError(message);
}

```

--------------------------------------------------------------------------------
/src/database/migrations/add-template-node-configs.sql:
--------------------------------------------------------------------------------

```sql
-- Migration: Add template_node_configs table
-- Run during `npm run rebuild` or `npm run fetch:templates`
-- This migration is idempotent - safe to run multiple times

-- Create table if it doesn't exist
CREATE TABLE IF NOT EXISTS template_node_configs (
  id INTEGER PRIMARY KEY,
  node_type TEXT NOT NULL,
  template_id INTEGER NOT NULL,
  template_name TEXT NOT NULL,
  template_views INTEGER DEFAULT 0,

  -- Node configuration (extracted from workflow)
  node_name TEXT,                  -- Node name in workflow (e.g., "HTTP Request")
  parameters_json TEXT NOT NULL,   -- JSON: node.parameters
  credentials_json TEXT,            -- JSON: node.credentials (if present)

  -- Pre-calculated metadata for filtering
  has_credentials INTEGER DEFAULT 0,
  has_expressions INTEGER DEFAULT 0,  -- Contains {{...}} or $json/$node
  complexity TEXT CHECK(complexity IN ('simple', 'medium', 'complex')),
  use_cases TEXT,                   -- JSON array from template.metadata.use_cases

  -- Pre-calculated ranking (1 = best, 2 = second best, etc.)
  rank INTEGER DEFAULT 0,

  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (template_id) REFERENCES templates(id) ON DELETE CASCADE
);

-- Create indexes if they don't exist
CREATE INDEX IF NOT EXISTS idx_config_node_type_rank
  ON template_node_configs(node_type, rank);

CREATE INDEX IF NOT EXISTS idx_config_complexity
  ON template_node_configs(node_type, complexity, rank);

CREATE INDEX IF NOT EXISTS idx_config_auth
  ON template_node_configs(node_type, has_credentials, rank);

-- Create view if it doesn't exist
CREATE VIEW IF NOT EXISTS ranked_node_configs AS
SELECT
  node_type,
  template_name,
  template_views,
  parameters_json,
  credentials_json,
  has_credentials,
  has_expressions,
  complexity,
  use_cases,
  rank
FROM template_node_configs
WHERE rank <= 5  -- Top 5 per node type
ORDER BY node_type, rank;

-- Note: Actual data population is handled by the fetch-templates script
-- This migration only creates the schema

```

--------------------------------------------------------------------------------
/scripts/demo-optimization.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash
# Demonstrate the optimization concept

echo "🎯 Demonstrating Docker Optimization"
echo "===================================="

# Create a demo directory structure
DEMO_DIR="optimization-demo"
rm -rf $DEMO_DIR
mkdir -p $DEMO_DIR

# Copy only runtime files
echo -e "\n📦 Creating minimal runtime package..."
cat > $DEMO_DIR/package.json << 'EOF'
{
  "name": "n8n-mcp-optimized",
  "version": "1.0.0",
  "private": true,
  "main": "dist/mcp/index.js",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.12.1",
    "better-sqlite3": "^11.10.0",
    "sql.js": "^1.13.0",
    "express": "^5.1.0",
    "dotenv": "^16.5.0"
  }
}
EOF

# Copy built files
echo "📁 Copying built application..."
cp -r dist $DEMO_DIR/
cp -r data $DEMO_DIR/
mkdir -p $DEMO_DIR/src/database
cp src/database/schema*.sql $DEMO_DIR/src/database/

# Calculate sizes
echo -e "\n📊 Size comparison:"
echo "Original project: $(du -sh . | cut -f1)"
echo "Optimized runtime: $(du -sh $DEMO_DIR | cut -f1)"

# Show what's included
echo -e "\n✅ Optimized package includes:"
echo "- Pre-built SQLite database with all node info"
echo "- Compiled JavaScript (dist/)"
echo "- Minimal runtime dependencies"
echo "- No n8n packages needed!"

# Create a simple test
echo -e "\n🧪 Testing database content..."
if command -v sqlite3 &> /dev/null; then
    NODE_COUNT=$(sqlite3 data/nodes.db "SELECT COUNT(*) FROM nodes;" 2>/dev/null || echo "0")
    AI_COUNT=$(sqlite3 data/nodes.db "SELECT COUNT(*) FROM nodes WHERE is_ai_tool = 1;" 2>/dev/null || echo "0")
    echo "- Total nodes in database: $NODE_COUNT"
    echo "- AI-capable nodes: $AI_COUNT"
else
    echo "- SQLite CLI not installed, skipping count"
fi

echo -e "\n💡 This demonstrates that we can run n8n-MCP with:"
echo "- ~50MB of runtime dependencies (vs 1.6GB)"
echo "- Pre-built database (11MB)"
echo "- No n8n packages at runtime"
echo "- Total optimized size: ~200MB (vs 2.6GB)"

# Cleanup
echo -e "\n🧹 Cleaning up demo..."
rm -rf $DEMO_DIR

echo -e "\n✨ Optimization concept demonstrated!"
```

--------------------------------------------------------------------------------
/docker-compose.n8n.yml:
--------------------------------------------------------------------------------

```yaml
version: '3.8'

services:
  # n8n workflow automation
  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: unless-stopped
    ports:
      - "${N8N_PORT:-5678}:5678"
    environment:
      - N8N_BASIC_AUTH_ACTIVE=${N8N_BASIC_AUTH_ACTIVE:-true}
      - N8N_BASIC_AUTH_USER=${N8N_BASIC_AUTH_USER:-admin}
      - N8N_BASIC_AUTH_PASSWORD=${N8N_BASIC_AUTH_PASSWORD:-password}
      - N8N_HOST=${N8N_HOST:-localhost}
      - N8N_PORT=5678
      - N8N_PROTOCOL=${N8N_PROTOCOL:-http}
      - WEBHOOK_URL=${N8N_WEBHOOK_URL:-http://localhost:5678/}
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
    volumes:
      - n8n_data:/home/node/.n8n
    networks:
      - n8n-network
    healthcheck:
      test: ["CMD", "sh", "-c", "wget --quiet --spider --tries=1 --timeout=10 http://localhost:5678/healthz || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s

  # n8n-mcp server for AI assistance
  n8n-mcp:
    build:
      context: .
      dockerfile: Dockerfile  # Uses standard Dockerfile with N8N_MODE=true env var
    image: ghcr.io/${GITHUB_REPOSITORY:-czlonkowski/n8n-mcp}/n8n-mcp:${VERSION:-latest}
    container_name: n8n-mcp
    restart: unless-stopped
    ports:
      - "${MCP_PORT:-3000}:${MCP_PORT:-3000}"
    environment:
      - NODE_ENV=production
      - N8N_MODE=true
      - MCP_MODE=http
      - PORT=${MCP_PORT:-3000}
      - N8N_API_URL=http://n8n:5678
      - N8N_API_KEY=${N8N_API_KEY}
      - MCP_AUTH_TOKEN=${MCP_AUTH_TOKEN}
      - AUTH_TOKEN=${MCP_AUTH_TOKEN}
      - LOG_LEVEL=${LOG_LEVEL:-info}
    volumes:
      - ./data:/app/data:ro
      - mcp_logs:/app/logs
    networks:
      - n8n-network
    depends_on:
      n8n:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "sh", "-c", "curl -f http://localhost:$${MCP_PORT:-3000}/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

volumes:
  n8n_data:
    driver: local
  mcp_logs:
    driver: local

networks:
  n8n-network:
    driver: bridge
```

--------------------------------------------------------------------------------
/scripts/publish-npm-quick.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash
# Quick publish script that skips tests
set -e

# Color codes
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

echo "🚀 Preparing n8n-mcp for npm publish (quick mode)..."

# Sync version
echo "🔄 Syncing version to package.runtime.json..."
npm run sync:runtime-version

VERSION=$(node -e "console.log(require('./package.json').version)")
echo -e "${GREEN}📌 Version: $VERSION${NC}"

# Prepare publish directory
PUBLISH_DIR="npm-publish-temp"
rm -rf $PUBLISH_DIR
mkdir -p $PUBLISH_DIR

echo "📦 Copying files..."
cp -r dist $PUBLISH_DIR/
cp -r data $PUBLISH_DIR/
cp README.md LICENSE .env.example $PUBLISH_DIR/
cp .npmignore $PUBLISH_DIR/ 2>/dev/null || true
cp package.runtime.json $PUBLISH_DIR/package.json

cd $PUBLISH_DIR

# Configure package.json
node -e "
const pkg = require('./package.json');
pkg.name = 'n8n-mcp';
pkg.description = 'Integration between n8n workflow automation and Model Context Protocol (MCP)';
pkg.bin = { 'n8n-mcp': './dist/mcp/index.js' };
pkg.repository = { type: 'git', url: 'git+https://github.com/czlonkowski/n8n-mcp.git' };
pkg.keywords = ['n8n', 'mcp', 'model-context-protocol', 'ai', 'workflow', 'automation'];
pkg.author = 'Romuald Czlonkowski @ www.aiadvisors.pl/en';
pkg.license = 'MIT';
pkg.bugs = { url: 'https://github.com/czlonkowski/n8n-mcp/issues' };
pkg.homepage = 'https://github.com/czlonkowski/n8n-mcp#readme';
pkg.files = ['dist/**/*', 'data/nodes.db', '.env.example', 'README.md', 'LICENSE'];
delete pkg.private;
require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2));
"

echo ""
echo "📋 Package details:"
echo -e "${GREEN}Name:${NC} $(node -e "console.log(require('./package.json').name)")"
echo -e "${GREEN}Version:${NC} $(node -e "console.log(require('./package.json').version)")"
echo -e "${GREEN}Size:${NC} ~50MB"
echo ""
echo "✅ Ready to publish!"
echo ""
echo -e "${YELLOW}⚠️  Note: Tests were skipped in quick mode${NC}"
echo ""
echo "To publish, run:"
echo -e "  ${GREEN}cd $PUBLISH_DIR${NC}"
echo -e "  ${GREEN}npm publish --otp=YOUR_OTP_CODE${NC}"
```

--------------------------------------------------------------------------------
/src/utils/console-manager.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Console Manager for MCP HTTP Server
 * 
 * Prevents console output from interfering with StreamableHTTPServerTransport
 * by silencing console methods during MCP request handling.
 */
export class ConsoleManager {
  private originalConsole = {
    log: console.log,
    error: console.error,
    warn: console.warn,
    info: console.info,
    debug: console.debug,
    trace: console.trace
  };
  
  private isSilenced = false;
  
  /**
   * Silence all console output
   */
  public silence(): void {
    if (this.isSilenced || process.env.MCP_MODE !== 'http') {
      return;
    }
    
    this.isSilenced = true;
    process.env.MCP_REQUEST_ACTIVE = 'true';
    console.log = () => {};
    console.error = () => {};
    console.warn = () => {};
    console.info = () => {};
    console.debug = () => {};
    console.trace = () => {};
  }
  
  /**
   * Restore original console methods
   */
  public restore(): void {
    if (!this.isSilenced) {
      return;
    }
    
    this.isSilenced = false;
    process.env.MCP_REQUEST_ACTIVE = 'false';
    console.log = this.originalConsole.log;
    console.error = this.originalConsole.error;
    console.warn = this.originalConsole.warn;
    console.info = this.originalConsole.info;
    console.debug = this.originalConsole.debug;
    console.trace = this.originalConsole.trace;
  }
  
  /**
   * Wrap an operation with console silencing
   * Automatically restores console on completion or error
   */
  public async wrapOperation<T>(operation: () => T | Promise<T>): Promise<T> {
    this.silence();
    try {
      const result = operation();
      if (result instanceof Promise) {
        return await result.finally(() => this.restore());
      }
      this.restore();
      return result;
    } catch (error) {
      this.restore();
      throw error;
    }
  }
  
  /**
   * Check if console is currently silenced
   */
  public get isActive(): boolean {
    return this.isSilenced;
  }
}

// Export singleton instance for easy use
export const consoleManager = new ConsoleManager();
```

--------------------------------------------------------------------------------
/tests/integration/n8n-api/types/mcp-responses.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * TypeScript interfaces for MCP handler responses
 *
 * These interfaces provide type safety for integration tests,
 * replacing unsafe `as any` casts with proper type definitions.
 */

/**
 * Workflow validation response from handleValidateWorkflow
 */
export interface ValidationResponse {
  valid: boolean;
  workflowId: string;
  workflowName: string;
  summary: {
    totalNodes: number;
    enabledNodes: number;
    triggerNodes: number;
    validConnections?: number;
    invalidConnections?: number;
    expressionsValidated?: number;
    errorCount: number;
    warningCount: number;
  };
  errors?: Array<{
    node: string;
    nodeName?: string;
    message: string;
    details?: {
      code?: string;
      [key: string]: unknown;
    };
    code?: string;
  }>;
  warnings?: Array<{
    node: string;
    nodeName?: string;
    message: string;
    details?: {
      code?: string;
      [key: string]: unknown;
    };
    code?: string;
  }>;
  info?: Array<{
    node: string;
    nodeName?: string;
    message: string;
    severity?: string;
    details?: unknown;
  }>;
  suggestions?: string[];
}

/**
 * Workflow autofix response from handleAutofixWorkflow
 */
export interface AutofixResponse {
  workflowId: string;
  workflowName: string;
  preview?: boolean;
  fixesAvailable?: number;
  fixesApplied?: number;
  fixes?: Array<{
    type: 'expression-format' | 'typeversion-correction' | 'error-output-config' | 'node-type-correction' | 'webhook-missing-path';
    confidence: 'high' | 'medium' | 'low';
    description: string;
    nodeName?: string;
    nodeId?: string;
    before?: unknown;
    after?: unknown;
  }>;
  summary?: {
    totalFixes: number;
    byType: Record<string, number>;
    byConfidence: Record<string, number>;
  };
  stats?: {
    expressionFormat?: number;
    typeVersionCorrection?: number;
    errorOutputConfig?: number;
    nodeTypeCorrection?: number;
    webhookMissingPath?: number;
  };
  message?: string;
  validationSummary?: {
    errors: number;
    warnings: number;
  };
}

```

--------------------------------------------------------------------------------
/src/database/schema-optimized.sql:
--------------------------------------------------------------------------------

```sql
-- Optimized schema with source code storage for Docker optimization
CREATE TABLE IF NOT EXISTS nodes (
  node_type TEXT PRIMARY KEY,
  package_name TEXT NOT NULL,
  display_name TEXT NOT NULL,
  description TEXT,
  category TEXT,
  development_style TEXT CHECK(development_style IN ('declarative', 'programmatic')),
  is_ai_tool INTEGER DEFAULT 0,
  is_trigger INTEGER DEFAULT 0,
  is_webhook INTEGER DEFAULT 0,
  is_versioned INTEGER DEFAULT 0,
  version TEXT,
  documentation TEXT,
  properties_schema TEXT,
  operations TEXT,
  credentials_required TEXT,
  -- New columns for source code storage
  node_source_code TEXT,
  credential_source_code TEXT,
  source_location TEXT,
  source_extracted_at DATETIME,
  -- Metadata
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- Indexes for performance
CREATE INDEX IF NOT EXISTS idx_package ON nodes(package_name);
CREATE INDEX IF NOT EXISTS idx_ai_tool ON nodes(is_ai_tool);
CREATE INDEX IF NOT EXISTS idx_category ON nodes(category);

-- FTS5 table for full-text search including source code
CREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(
  node_type,
  display_name,
  description,
  documentation,
  operations,
  node_source_code,
  content=nodes,
  content_rowid=rowid
);

-- Trigger to keep FTS in sync
CREATE TRIGGER IF NOT EXISTS nodes_fts_insert AFTER INSERT ON nodes
BEGIN
  INSERT INTO nodes_fts(rowid, node_type, display_name, description, documentation, operations, node_source_code)
  VALUES (new.rowid, new.node_type, new.display_name, new.description, new.documentation, new.operations, new.node_source_code);
END;

CREATE TRIGGER IF NOT EXISTS nodes_fts_update AFTER UPDATE ON nodes
BEGIN
  UPDATE nodes_fts 
  SET node_type = new.node_type,
      display_name = new.display_name,
      description = new.description,
      documentation = new.documentation,
      operations = new.operations,
      node_source_code = new.node_source_code
  WHERE rowid = new.rowid;
END;

CREATE TRIGGER IF NOT EXISTS nodes_fts_delete AFTER DELETE ON nodes
BEGIN
  DELETE FROM nodes_fts WHERE rowid = old.rowid;
END;
```

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

```typescript
import { ToolDocumentation } from '../types';

export const n8nDeleteWorkflowDoc: ToolDocumentation = {
  name: 'n8n_delete_workflow',
  category: 'workflow_management',
  essentials: {
    description: 'Permanently delete a workflow. This action cannot be undone.',
    keyParameters: ['id'],
    example: 'n8n_delete_workflow({id: "workflow_123"})',
    performance: 'Fast (50-150ms)',
    tips: [
      'Action is irreversible',
      'Deletes all execution history',
      'Check workflow first with n8n_get_workflow({mode: "minimal"})'
    ]
  },
  full: {
    description: 'Permanently deletes a workflow from n8n including all associated data, execution history, and settings. This is an irreversible operation that should be used with caution. The workflow must exist and the user must have appropriate permissions.',
    parameters: {
      id: { type: 'string', required: true, description: 'Workflow ID to delete permanently' }
    },
    returns: 'Success confirmation or error if workflow not found/cannot be deleted',
    examples: [
      'n8n_delete_workflow({id: "abc123"}) - Delete specific workflow',
      'if (confirm) { n8n_delete_workflow({id: wf.id}); } // With confirmation'
    ],
    useCases: [
      'Remove obsolete workflows',
      'Clean up test workflows',
      'Delete failed experiments',
      'Manage workflow limits',
      'Remove duplicates'
    ],
    performance: 'Fast operation - typically 50-150ms. May take longer if workflow has extensive execution history.',
    bestPractices: [
      'Always confirm before deletion',
      'Check workflow with n8n_get_workflow({mode: "minimal"}) first',
      'Consider deactivating instead of deleting',
      'Export workflow before deletion for backup'
    ],
    pitfalls: [
      'Requires N8N_API_URL and N8N_API_KEY configured',
      'Cannot be undone - permanent deletion',
      'Deletes all execution history',
      'Active workflows can be deleted',
      'No built-in confirmation'
    ],
    relatedTools: ['n8n_get_workflow', 'n8n_list_workflows', 'n8n_update_partial_workflow', 'n8n_executions']
  }
};
```

--------------------------------------------------------------------------------
/tests/test-small-rebuild.js:
--------------------------------------------------------------------------------

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

const { NodeDocumentationService } = require('../dist/services/node-documentation-service');

async function testSmallRebuild() {
  console.log('Testing small rebuild...\n');
  
  const service = new NodeDocumentationService('./data/nodes-v2-test.db');
  
  try {
    // First, let's just try the IF node specifically
    const extractor = service.extractor;
    console.log('1️⃣ Testing extraction of IF node...');
    
    try {
      const ifNodeData = await extractor.extractNodeSource('n8n-nodes-base.If');
      console.log('   ✅ Successfully extracted IF node');
      console.log('   Source code length:', ifNodeData.sourceCode.length);
      console.log('   Has credentials:', !!ifNodeData.credentialCode);
    } catch (error) {
      console.log('   ❌ Failed to extract IF node:', error.message);
    }
    
    // Try the Webhook node
    console.log('\n2️⃣ Testing extraction of Webhook node...');
    try {
      const webhookNodeData = await extractor.extractNodeSource('n8n-nodes-base.Webhook');
      console.log('   ✅ Successfully extracted Webhook node');
      console.log('   Source code length:', webhookNodeData.sourceCode.length);
    } catch (error) {
      console.log('   ❌ Failed to extract Webhook node:', error.message);
    }
    
    // Now try storing just these nodes
    console.log('\n3️⃣ Testing storage of a single node...');
    const nodeInfo = {
      nodeType: 'n8n-nodes-base.If',
      name: 'If',
      displayName: 'If',
      description: 'Route items based on comparison operations',
      sourceCode: 'test source code',
      packageName: 'n8n-nodes-base',
      hasCredentials: false,
      isTrigger: false,
      isWebhook: false
    };
    
    await service.storeNode(nodeInfo);
    console.log('   ✅ Successfully stored test node');
    
    // Check if it was stored
    const retrievedNode = await service.getNodeInfo('n8n-nodes-base.If');
    console.log('   Retrieved node:', retrievedNode ? 'Found' : 'Not found');
    
  } catch (error) {
    console.error('❌ Test failed:', error);
  } finally {
    service.close();
  }
}

testSmallRebuild();
```

--------------------------------------------------------------------------------
/src/mcp/workflow-examples.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Example workflows for n8n AI agents to understand the structure
 */

export const MINIMAL_WORKFLOW_EXAMPLE = {
  nodes: [
    {
      name: "Webhook",
      type: "n8n-nodes-base.webhook",
      typeVersion: 2,
      position: [250, 300],
      parameters: {
        httpMethod: "POST",
        path: "webhook"
      }
    }
  ],
  connections: {}
};

export const SIMPLE_WORKFLOW_EXAMPLE = {
  nodes: [
    {
      name: "Webhook",
      type: "n8n-nodes-base.webhook",
      typeVersion: 2,
      position: [250, 300],
      parameters: {
        httpMethod: "POST",
        path: "webhook"
      }
    },
    {
      name: "Set",
      type: "n8n-nodes-base.set",
      typeVersion: 2,
      position: [450, 300],
      parameters: {
        mode: "manual",
        assignments: {
          assignments: [
            {
              name: "message",
              type: "string",
              value: "Hello"
            }
          ]
        }
      }
    },
    {
      name: "Respond to Webhook",
      type: "n8n-nodes-base.respondToWebhook",
      typeVersion: 1,
      position: [650, 300],
      parameters: {
        respondWith: "firstIncomingItem"
      }
    }
  ],
  connections: {
    "Webhook": {
      "main": [
        [
          {
            "node": "Set",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
};

export function getWorkflowExampleString(): string {
  return `Example workflow structure:
${JSON.stringify(MINIMAL_WORKFLOW_EXAMPLE, null, 2)}

Each node MUST have:
- name: unique string identifier
- type: full node type with prefix (e.g., "n8n-nodes-base.webhook")
- typeVersion: number (usually 1 or 2)
- position: [x, y] coordinates array
- parameters: object with node-specific settings

Connections format:
{
  "SourceNodeName": {
    "main": [
      [
        {
          "node": "TargetNodeName",
          "type": "main",
          "index": 0
        }
      ]
    ]
  }
}`;
}
```

--------------------------------------------------------------------------------
/scripts/test-docker-optimization.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash
# Test script to verify Docker optimization (no n8n deps)

set -e

echo "🧪 Testing Docker optimization..."
echo ""

# Check if nodes.db exists
if [ ! -f "data/nodes.db" ]; then
    echo "❌ ERROR: data/nodes.db not found!"
    echo "   Run 'npm run rebuild' first to create the database"
    exit 1
fi

# Build the image
echo "📦 Building Docker image..."
DOCKER_BUILDKIT=1 docker build -t n8n-mcp:test . > /dev/null 2>&1

# Check image size
echo "📊 Checking image size..."
SIZE=$(docker images n8n-mcp:test --format "{{.Size}}")
echo "   Image size: $SIZE"

# Test that n8n is NOT in the image
echo ""
echo "🔍 Verifying no n8n dependencies..."
if docker run --rm n8n-mcp:test sh -c "ls node_modules | grep -E '^n8n$|^n8n-|^@n8n'" 2>/dev/null; then
    echo "❌ ERROR: Found n8n dependencies in runtime image!"
    exit 1
else
    echo "✅ No n8n dependencies found (as expected)"
fi

# Test that runtime dependencies ARE present
echo ""
echo "🔍 Verifying runtime dependencies..."
EXPECTED_DEPS=("@modelcontextprotocol" "better-sqlite3" "express" "dotenv")
for dep in "${EXPECTED_DEPS[@]}"; do
    if docker run --rm n8n-mcp:test sh -c "ls node_modules | grep -q '$dep'" 2>/dev/null; then
        echo "✅ Found: $dep"
    else
        echo "❌ Missing: $dep"
        exit 1
    fi
done

# Test that the server starts
echo ""
echo "🚀 Testing server startup..."
docker run --rm -d \
    --name n8n-mcp-test \
    -e MCP_MODE=http \
    -e AUTH_TOKEN=test-token \
    -e LOG_LEVEL=error \
    n8n-mcp:test > /dev/null 2>&1

# Wait for startup
sleep 3

# Check if running
if docker ps | grep -q n8n-mcp-test; then
    echo "✅ Server started successfully"
    docker stop n8n-mcp-test > /dev/null 2>&1
else
    echo "❌ Server failed to start"
    docker logs n8n-mcp-test 2>&1
    exit 1
fi

# Clean up
docker rmi n8n-mcp:test > /dev/null 2>&1

echo ""
echo "🎉 All tests passed! Docker optimization is working correctly."
echo ""
echo "📈 Benefits:"
echo "   - No n8n dependencies in runtime image"
echo "   - Image size: ~200MB (vs ~1.5GB with n8n)"
echo "   - Build time: ~1-2 minutes (vs ~12 minutes)"
echo "   - No version conflicts at runtime"
```

--------------------------------------------------------------------------------
/.github/BENCHMARK_THRESHOLDS.md:
--------------------------------------------------------------------------------

```markdown
# Performance Benchmark Thresholds

This file defines the expected performance thresholds for n8n-mcp operations.

## Critical Operations

| Operation | Expected Time | Warning Threshold | Error Threshold |
|-----------|---------------|-------------------|-----------------|
| Node Loading (per package) | <100ms | 150ms | 200ms |
| Database Query (simple) | <5ms | 10ms | 20ms |
| Search (simple word) | <10ms | 20ms | 50ms |
| Search (complex query) | <50ms | 100ms | 200ms |
| Validation (simple config) | <1ms | 2ms | 5ms |
| Validation (complex config) | <10ms | 20ms | 50ms |
| MCP Tool Execution | <50ms | 100ms | 200ms |

## Benchmark Categories

### Node Loading Performance
- **loadPackage**: Should handle large packages efficiently
- **loadNodesFromPath**: Individual file loading should be fast
- **parsePackageJson**: JSON parsing overhead should be minimal

### Database Query Performance
- **getNodeByType**: Direct lookups should be instant
- **searchNodes**: Full-text search should scale well
- **getAllNodes**: Pagination should prevent performance issues

### Search Operations
- **OR mode**: Should handle multiple terms efficiently
- **AND mode**: More restrictive but still performant
- **FUZZY mode**: Slower but acceptable for typo tolerance

### Validation Performance
- **minimal profile**: Fastest, only required fields
- **ai-friendly profile**: Balanced performance
- **strict profile**: Comprehensive but slower

### MCP Tool Execution
- Tools should respond quickly for interactive use
- Complex operations may take longer but should remain responsive

## Regression Detection

Performance regressions are detected when:
1. Any operation exceeds its warning threshold by 10%
2. Multiple operations show degradation in the same category
3. Average performance across all benchmarks degrades by 5%

## Optimization Targets

Future optimization efforts should focus on:
1. **Search performance**: Implement FTS5 for better full-text search
2. **Caching**: Add intelligent caching for frequently accessed nodes
3. **Lazy loading**: Defer loading of large property schemas
4. **Batch operations**: Optimize bulk inserts and updates
```

--------------------------------------------------------------------------------
/scripts/test-fuzzy-fix.ts:
--------------------------------------------------------------------------------

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

import { N8NDocumentationMCPServer } from '../src/mcp/server';

async function testFuzzyFix() {
  console.log('Testing FUZZY mode fix...\n');
  
  const server = new N8NDocumentationMCPServer();
  
  // Wait for initialization
  await new Promise(resolve => setTimeout(resolve, 1000));
  
  // Test 1: FUZZY mode with typo
  console.log('Test 1: FUZZY mode with "slak" (typo for "slack")');
  const fuzzyResult = await server.executeTool('search_nodes', {
    query: 'slak',
    mode: 'FUZZY',
    limit: 5
  });
  
  console.log(`Results: ${fuzzyResult.results.length} found`);
  if (fuzzyResult.results.length > 0) {
    console.log('✅ FUZZY mode now finds results!');
    fuzzyResult.results.forEach((node: any, i: number) => {
      console.log(`  ${i + 1}. ${node.nodeType} - ${node.displayName}`);
    });
  } else {
    console.log('❌ FUZZY mode still not working');
  }
  
  // Test 2: AND mode with explanation
  console.log('\n\nTest 2: AND mode with "send message"');
  const andResult = await server.executeTool('search_nodes', {
    query: 'send message',
    mode: 'AND',
    limit: 5
  });
  
  console.log(`Results: ${andResult.results.length} found`);
  if (andResult.searchInfo) {
    console.log('✅ AND mode now includes search info:');
    console.log(`   ${andResult.searchInfo.message}`);
    console.log(`   Tip: ${andResult.searchInfo.tip}`);
  }
  
  console.log('\nFirst 5 results:');
  andResult.results.slice(0, 5).forEach((node: any, i: number) => {
    console.log(`  ${i + 1}. ${node.nodeType} - ${node.displayName}`);
  });
  
  // Test 3: More typos
  console.log('\n\nTest 3: More FUZZY tests');
  const typos = ['htpp', 'webook', 'slck', 'emial'];
  
  for (const typo of typos) {
    const result = await server.executeTool('search_nodes', {
      query: typo,
      mode: 'FUZZY',
      limit: 1
    });
    
    if (result.results.length > 0) {
      console.log(`✅ "${typo}" → ${result.results[0].displayName}`);
    } else {
      console.log(`❌ "${typo}" → No results`);
    }
  }
  
  process.exit(0);
}

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

--------------------------------------------------------------------------------
/tests/unit/database/__mocks__/better-sqlite3.ts:
--------------------------------------------------------------------------------

```typescript
import { vi } from 'vitest';

export class MockDatabase {
  private data = new Map<string, any[]>();
  private prepared = new Map<string, any>();
  public inTransaction = false;
  
  constructor() {
    this.data.set('nodes', []);
    this.data.set('templates', []);
    this.data.set('tools_documentation', []);
  }
  
  prepare(sql: string) {
    const key = this.extractTableName(sql);
    const self = this;
    
    return {
      all: vi.fn(() => self.data.get(key) || []),
      get: vi.fn((id: string) => {
        const items = self.data.get(key) || [];
        return items.find(item => item.id === id);
      }),
      run: vi.fn((params: any) => {
        const items = self.data.get(key) || [];
        items.push(params);
        self.data.set(key, items);
        return { changes: 1, lastInsertRowid: items.length };
      }),
      iterate: vi.fn(function* () {
        const items = self.data.get(key) || [];
        for (const item of items) {
          yield item;
        }
      }),
      pluck: vi.fn(function(this: any) { return this; }),
      expand: vi.fn(function(this: any) { return this; }),
      raw: vi.fn(function(this: any) { return this; }),
      columns: vi.fn(() => []),
      bind: vi.fn(function(this: any) { return this; })
    };
  }
  
  exec(sql: string) {
    // Mock schema creation
    return true;
  }
  
  close() {
    // Mock close
    return true;
  }
  
  pragma(key: string, value?: any) {
    // Mock pragma
    if (key === 'journal_mode' && value === 'WAL') {
      return 'wal';
    }
    return null;
  }
  
  transaction<T>(fn: () => T): T {
    this.inTransaction = true;
    try {
      const result = fn();
      this.inTransaction = false;
      return result;
    } catch (error) {
      this.inTransaction = false;
      throw error;
    }
  }
  
  // Helper to extract table name from SQL
  private extractTableName(sql: string): string {
    const match = sql.match(/FROM\s+(\w+)|INTO\s+(\w+)|UPDATE\s+(\w+)/i);
    return match ? (match[1] || match[2] || match[3]) : 'nodes';
  }
  
  // Test helper to seed data
  _seedData(table: string, data: any[]) {
    this.data.set(table, data);
  }
}

export default vi.fn(() => new MockDatabase());
```

--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------

```typescript
import { defineConfig } from 'vitest/config';
import path from 'path';

export default defineConfig({
  test: {
    globals: true,
    environment: 'node',
    // Only include global-setup.ts, remove msw-setup.ts from global setup
    setupFiles: ['./tests/setup/global-setup.ts'],
    // Load environment variables from .env.test
    env: {
      NODE_ENV: 'test'
    },
    // Test execution settings
    pool: 'threads',
    poolOptions: {
      threads: {
        singleThread: process.env.TEST_PARALLEL !== 'true',
        maxThreads: parseInt(process.env.TEST_MAX_WORKERS || '4', 10),
        minThreads: 1
      }
    },
    // Retry configuration
    retry: parseInt(process.env.TEST_RETRY_ATTEMPTS || '2', 10),
    // Test reporter - reduce reporters in CI to prevent hanging
    reporters: process.env.CI ? ['default', 'junit'] : ['default'],
    outputFile: {
      junit: './test-results/junit.xml'
    },
    coverage: {
      provider: 'v8',
      enabled: process.env.FEATURE_TEST_COVERAGE !== 'false',
      reporter: process.env.CI ? ['lcov', 'text-summary'] : (process.env.COVERAGE_REPORTER || 'lcov,html,text-summary').split(','),
      reportsDirectory: process.env.COVERAGE_DIR || './coverage',
      exclude: [
        'node_modules/',
        'tests/',
        '**/*.d.ts',
        '**/*.test.ts',
        '**/*.spec.ts',
        'scripts/',
        'dist/',
        '**/test-*.ts',
        '**/mock-*.ts',
        '**/__mocks__/**'
      ],
      thresholds: {
        lines: 80,
        functions: 80,
        branches: 75,
        statements: 80
      },
      // Add coverage-specific settings to prevent hanging
      all: false, // Don't collect coverage for untested files
      skipFull: true // Skip files with 100% coverage
    },
    // Test isolation
    isolate: true,
    // Force exit after tests complete in CI to prevent hanging
    forceRerunTriggers: ['**/tests/**/*.ts'],
    teardownTimeout: 1000
  },
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      '@tests': path.resolve(__dirname, './tests')
    }
  },
  // TypeScript configuration
  esbuild: {
    target: 'node18'
  },
  // Define global constants
  define: {
    'process.env.TEST_ENVIRONMENT': JSON.stringify('true')
  }
});
```

--------------------------------------------------------------------------------
/scripts/extract-changelog.js:
--------------------------------------------------------------------------------

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

/**
 * Extract changelog content for a specific version
 * Used by GitHub Actions to extract release notes
 */

const fs = require('fs');
const path = require('path');

function extractChangelog(version, changelogPath) {
  try {
    if (!fs.existsSync(changelogPath)) {
      console.error(`Changelog file not found at ${changelogPath}`);
      process.exit(1);
    }

    const content = fs.readFileSync(changelogPath, 'utf8');
    const lines = content.split('\n');
    
    // Find the start of this version's section
    const versionHeaderRegex = new RegExp(`^## \\[${version.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\]`);
    let startIndex = -1;
    let endIndex = -1;
    
    for (let i = 0; i < lines.length; i++) {
      if (versionHeaderRegex.test(lines[i])) {
        startIndex = i;
        break;
      }
    }
    
    if (startIndex === -1) {
      console.error(`No changelog entries found for version ${version}`);
      process.exit(1);
    }
    
    // Find the end of this version's section (next version or end of file)
    for (let i = startIndex + 1; i < lines.length; i++) {
      if (lines[i].startsWith('## [') && !lines[i].includes('Unreleased')) {
        endIndex = i;
        break;
      }
    }
    
    if (endIndex === -1) {
      endIndex = lines.length;
    }
    
    // Extract the section content
    const sectionLines = lines.slice(startIndex, endIndex);
    
    // Remove the version header and any trailing empty lines
    let contentLines = sectionLines.slice(1);
    while (contentLines.length > 0 && contentLines[contentLines.length - 1].trim() === '') {
      contentLines.pop();
    }
    
    if (contentLines.length === 0) {
      console.error(`No content found for version ${version}`);
      process.exit(1);
    }
    
    const releaseNotes = contentLines.join('\n').trim();
    
    // Write to stdout for GitHub Actions
    console.log(releaseNotes);
    
  } catch (error) {
    console.error(`Error extracting changelog: ${error.message}`);
    process.exit(1);
  }
}

// Parse command line arguments
const version = process.argv[2];
const changelogPath = process.argv[3];

if (!version || !changelogPath) {
  console.error('Usage: extract-changelog.js <version> <changelog-path>');
  process.exit(1);
}

extractChangelog(version, changelogPath);
```

--------------------------------------------------------------------------------
/scripts/test-helpers-validation.ts:
--------------------------------------------------------------------------------

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

import { EnhancedConfigValidator } from '../src/services/enhanced-config-validator.js';

console.log('🧪 Testing $helpers Validation\n');

const testCases = [
  {
    name: 'Incorrect $helpers.getWorkflowStaticData',
    config: {
      language: 'javaScript',
      jsCode: `const data = $helpers.getWorkflowStaticData('global');
data.counter = 1;
return [{json: {counter: data.counter}}];`
    }
  },
  {
    name: 'Correct $getWorkflowStaticData',
    config: {
      language: 'javaScript',
      jsCode: `const data = $getWorkflowStaticData('global');
data.counter = 1;
return [{json: {counter: data.counter}}];`
    }
  },
  {
    name: '$helpers without check',
    config: {
      language: 'javaScript',
      jsCode: `const response = await $helpers.httpRequest({
  method: 'GET',
  url: 'https://api.example.com'
});
return [{json: response}];`
    }
  },
  {
    name: '$helpers with proper check',
    config: {
      language: 'javaScript',
      jsCode: `if (typeof $helpers !== 'undefined' && $helpers.httpRequest) {
  const response = await $helpers.httpRequest({
    method: 'GET',
    url: 'https://api.example.com'
  });
  return [{json: response}];
}
return [{json: {error: 'HTTP not available'}}];`
    }
  },
  {
    name: 'Crypto without require',
    config: {
      language: 'javaScript',
      jsCode: `const token = crypto.randomBytes(32).toString('hex');
return [{json: {token}}];`
    }
  },
  {
    name: 'Crypto with require',
    config: {
      language: 'javaScript',
      jsCode: `const crypto = require('crypto');
const token = crypto.randomBytes(32).toString('hex');
return [{json: {token}}];`
    }
  }
];

for (const test of testCases) {
  console.log(`Test: ${test.name}`);
  const result = EnhancedConfigValidator.validateWithMode(
    'nodes-base.code',
    test.config,
    [
      { name: 'language', type: 'options', options: ['javaScript', 'python'] },
      { name: 'jsCode', type: 'string' }
    ],
    'operation',
    'ai-friendly'
  );
  
  console.log(`  Valid: ${result.valid}`);
  if (result.errors.length > 0) {
    console.log(`  Errors: ${result.errors.map(e => e.message).join(', ')}`);
  }
  if (result.warnings.length > 0) {
    console.log(`  Warnings: ${result.warnings.map(w => w.message).join(', ')}`);
  }
  console.log();
}

console.log('✅ $helpers validation tests completed!');
```

--------------------------------------------------------------------------------
/scripts/nginx-n8n-mcp.conf:
--------------------------------------------------------------------------------

```
server {
    listen 80;
    server_name n8ndocumentation.aiservices.pl;

    # Redirect HTTP to HTTPS
    location / {
        return 301 https://$server_name$request_uri;
    }
}

server {
    listen 443 ssl http2;
    server_name n8ndocumentation.aiservices.pl;

    # SSL configuration (managed by Certbot)
    ssl_certificate /etc/letsencrypt/live/n8ndocumentation.aiservices.pl/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/n8ndocumentation.aiservices.pl/privkey.pem;
    
    # SSL security settings
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    
    # Security headers
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options DENY;
    add_header X-XSS-Protection "1; mode=block";
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    
    # Logging
    access_log /var/log/nginx/n8n-mcp-access.log;
    error_log /var/log/nginx/n8n-mcp-error.log;
    
    # Proxy settings
    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        
        # Timeouts for MCP operations
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        
        # Increase buffer sizes for large responses
        proxy_buffer_size 16k;
        proxy_buffers 8 16k;
        proxy_busy_buffers_size 32k;
    }
    
    # Rate limiting for API endpoints
    location /mcp {
        limit_req zone=mcp_limit burst=10 nodelay;
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # Larger timeouts for MCP
        proxy_connect_timeout 120s;
        proxy_send_timeout 120s;
        proxy_read_timeout 120s;
    }
}

# Rate limiting zone
limit_req_zone $binary_remote_addr zone=mcp_limit:10m rate=10r/s;
```

--------------------------------------------------------------------------------
/src/utils/error-handler.ts:
--------------------------------------------------------------------------------

```typescript
import { logger } from './logger';

export class MCPError extends Error {
  public code: string;
  public statusCode?: number;
  public data?: any;

  constructor(message: string, code: string, statusCode?: number, data?: any) {
    super(message);
    this.name = 'MCPError';
    this.code = code;
    this.statusCode = statusCode;
    this.data = data;
  }
}

export class N8NConnectionError extends MCPError {
  constructor(message: string, data?: any) {
    super(message, 'N8N_CONNECTION_ERROR', 503, data);
    this.name = 'N8NConnectionError';
  }
}

export class AuthenticationError extends MCPError {
  constructor(message: string = 'Authentication failed') {
    super(message, 'AUTH_ERROR', 401);
    this.name = 'AuthenticationError';
  }
}

export class ValidationError extends MCPError {
  constructor(message: string, data?: any) {
    super(message, 'VALIDATION_ERROR', 400, data);
    this.name = 'ValidationError';
  }
}

export class ToolNotFoundError extends MCPError {
  constructor(toolName: string) {
    super(`Tool '${toolName}' not found`, 'TOOL_NOT_FOUND', 404);
    this.name = 'ToolNotFoundError';
  }
}

export class ResourceNotFoundError extends MCPError {
  constructor(resourceUri: string) {
    super(`Resource '${resourceUri}' not found`, 'RESOURCE_NOT_FOUND', 404);
    this.name = 'ResourceNotFoundError';
  }
}

export function handleError(error: any): MCPError {
  if (error instanceof MCPError) {
    return error;
  }

  if (error.response) {
    // HTTP error from n8n API
    const status = error.response.status;
    const message = error.response.data?.message || error.message;
    
    if (status === 401) {
      return new AuthenticationError(message);
    } else if (status === 404) {
      return new MCPError(message, 'NOT_FOUND', 404);
    } else if (status >= 500) {
      return new N8NConnectionError(message);
    }
    
    return new MCPError(message, 'API_ERROR', status);
  }

  if (error.code === 'ECONNREFUSED') {
    return new N8NConnectionError('Cannot connect to n8n API');
  }

  // Generic error
  return new MCPError(
    error.message || 'An unexpected error occurred',
    'UNKNOWN_ERROR',
    500
  );
}

export async function withErrorHandling<T>(
  operation: () => Promise<T>,
  context: string
): Promise<T> {
  try {
    return await operation();
  } catch (error) {
    logger.error(`Error in ${context}:`, error);
    throw handleError(error);
  }
}
```

--------------------------------------------------------------------------------
/scripts/test-telemetry-integration.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env npx tsx
/**
 * Integration test for the telemetry manager
 */

import { telemetry } from '../src/telemetry/telemetry-manager';

async function testIntegration() {
  console.log('🧪 Testing Telemetry Manager Integration\n');

  // Check status
  console.log('Status:', telemetry.getStatus());

  // Track session start
  console.log('\nTracking session start...');
  telemetry.trackSessionStart();

  // Track tool usage
  console.log('Tracking tool usage...');
  telemetry.trackToolUsage('search_nodes', true, 150);
  telemetry.trackToolUsage('get_node_info', true, 75);
  telemetry.trackToolUsage('validate_workflow', false, 200);

  // Track errors
  console.log('Tracking errors...');
  telemetry.trackError('ValidationError', 'workflow_validation', 'validate_workflow', 'Required field missing: nodes array is empty');

  // Track a test workflow
  console.log('Tracking workflow creation...');
  const testWorkflow = {
    nodes: [
      {
        id: '1',
        type: 'n8n-nodes-base.webhook',
        name: 'Webhook',
        position: [0, 0],
        parameters: {
          path: '/test-webhook',
          httpMethod: 'POST'
        }
      },
      {
        id: '2',
        type: 'n8n-nodes-base.httpRequest',
        name: 'HTTP Request',
        position: [250, 0],
        parameters: {
          url: 'https://api.example.com/endpoint',
          method: 'POST',
          authentication: 'genericCredentialType',
          genericAuthType: 'httpHeaderAuth',
          sendHeaders: true,
          headerParameters: {
            parameters: [
              {
                name: 'Authorization',
                value: 'Bearer sk-1234567890abcdef'
              }
            ]
          }
        }
      },
      {
        id: '3',
        type: 'n8n-nodes-base.slack',
        name: 'Slack',
        position: [500, 0],
        parameters: {
          channel: '#notifications',
          text: 'Workflow completed!'
        }
      }
    ],
    connections: {
      '1': {
        main: [[{ node: '2', type: 'main', index: 0 }]]
      },
      '2': {
        main: [[{ node: '3', type: 'main', index: 0 }]]
      }
    }
  };

  telemetry.trackWorkflowCreation(testWorkflow, true);

  // Force flush
  console.log('\nFlushing telemetry data...');
  await telemetry.flush();

  console.log('\n✅ Telemetry integration test completed!');
  console.log('Check your Supabase dashboard for the telemetry data.');
}

testIntegration().catch(console.error);

```

--------------------------------------------------------------------------------
/PRIVACY.md:
--------------------------------------------------------------------------------

```markdown
# Privacy Policy for n8n-mcp Telemetry

## Overview
n8n-mcp collects anonymous usage statistics to help improve the tool. This data collection is designed to respect user privacy while providing valuable insights into how the tool is used.

## What We Collect
- **Anonymous User ID**: A hashed identifier derived from your machine characteristics (no personal information)
- **Tool Usage**: Which MCP tools are used and their performance metrics
- **Workflow Patterns**: Sanitized workflow structures (all sensitive data removed)
- **Error Types**: Categories of errors encountered (no error messages with user data)
- **System Information**: Platform, architecture, Node.js version, and n8n-mcp version

## What We DON'T Collect
- Personal information or usernames
- API keys, tokens, or credentials
- URLs, endpoints, or hostnames
- Email addresses or contact information
- File paths or directory structures
- Actual workflow data or parameters
- Database connection strings
- Any authentication information

## Data Sanitization
All collected data undergoes automatic sanitization:
- URLs are replaced with `[URL]` or `[REDACTED]`
- Long alphanumeric strings (potential keys) are replaced with `[KEY]`
- Email addresses are replaced with `[EMAIL]`
- Authentication-related fields are completely removed

## Data Storage
- Data is stored securely using Supabase
- Anonymous users have write-only access (cannot read data back)
- Row Level Security (RLS) policies prevent data access by anonymous users

## Opt-Out
You can disable telemetry at any time:
```bash
npx n8n-mcp telemetry disable
```

To re-enable:
```bash
npx n8n-mcp telemetry enable
```

To check status:
```bash
npx n8n-mcp telemetry status
```

## Data Usage
Collected data is used solely to:
- Understand which features are most used
- Identify common error patterns
- Improve tool performance and reliability
- Guide development priorities
- Train machine learning models for workflow generation

All ML training uses sanitized, anonymized data only.
Users can opt-out at any time with `npx n8n-mcp telemetry disable`

## Data Retention
- Data is retained for analysis purposes
- No personal identification is possible from the collected data

## Changes to This Policy
We may update this privacy policy from time to time. Updates will be reflected in this document.

## Contact
For questions about telemetry or privacy, please open an issue on GitHub:
https://github.com/czlonkowski/n8n-mcp/issues

Last updated: 2025-11-06
```

--------------------------------------------------------------------------------
/scripts/test-security.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node
import axios from 'axios';
import { spawn } from 'child_process';

async function testMaliciousHeaders() {
  console.log('🔒 Testing Security Fixes...\n');
  
  // Start server with TRUST_PROXY enabled
  const serverProcess = spawn('node', ['dist/mcp/index.js'], {
    env: {
      ...process.env,
      MCP_MODE: 'http',
      AUTH_TOKEN: 'test-security-token-32-characters-long',
      PORT: '3999',
      TRUST_PROXY: '1'
    }
  });

  // Wait for server to start
  await new Promise(resolve => {
    serverProcess.stdout.on('data', (data) => {
      if (data.toString().includes('Press Ctrl+C to stop')) {
        resolve(undefined);
      }
    });
  });

  const testCases = [
    {
      name: 'Valid proxy headers',
      headers: {
        'X-Forwarded-Host': 'example.com',
        'X-Forwarded-Proto': 'https'
      }
    },
    {
      name: 'Malicious host header (with path)',
      headers: {
        'X-Forwarded-Host': 'evil.com/path/to/evil',
        'X-Forwarded-Proto': 'https'
      }
    },
    {
      name: 'Malicious host header (with @)',
      headers: {
        'X-Forwarded-Host': '[email protected]',
        'X-Forwarded-Proto': 'https'
      }
    },
    {
      name: 'Invalid hostname (multiple dots)',
      headers: {
        'X-Forwarded-Host': '.....',
        'X-Forwarded-Proto': 'https'
      }
    },
    {
      name: 'IPv6 address',
      headers: {
        'X-Forwarded-Host': '[::1]:3000',
        'X-Forwarded-Proto': 'https'
      }
    }
  ];

  for (const testCase of testCases) {
    try {
      const response = await axios.get('http://localhost:3999/', {
        headers: testCase.headers,
        timeout: 2000
      });
      
      const endpoints = response.data.endpoints;
      const healthUrl = endpoints?.health?.url || 'N/A';
      
      console.log(`✅ ${testCase.name}`);
      console.log(`   Response: ${healthUrl}`);
      
      // Check if malicious headers were blocked
      if (testCase.name.includes('Malicious') || testCase.name.includes('Invalid')) {
        if (healthUrl.includes('evil.com') || healthUrl.includes('@') || healthUrl.includes('.....')) {
          console.log('   ❌ SECURITY ISSUE: Malicious header was not blocked!');
        } else {
          console.log('   ✅ Malicious header was blocked');
        }
      }
    } catch (error) {
      console.log(`❌ ${testCase.name} - Request failed`);
    }
    console.log('');
  }

  serverProcess.kill();
}

testMaliciousHeaders().catch(console.error);
```

--------------------------------------------------------------------------------
/src/services/sqlite-storage-service.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * SQLiteStorageService - A simple wrapper around DatabaseAdapter for benchmarks
 */
import { DatabaseAdapter, createDatabaseAdapter } from '../database/database-adapter';

export class SQLiteStorageService {
  private adapter: DatabaseAdapter | null = null;
  private dbPath: string;

  constructor(dbPath: string = ':memory:') {
    this.dbPath = dbPath;
    this.initSync();
  }

  private initSync() {
    // For benchmarks, we'll use synchronous initialization
    // In real usage, this should be async
    const Database = require('better-sqlite3');
    const db = new Database(this.dbPath);
    
    // Create a simple adapter
    this.adapter = {
      prepare: (sql: string) => db.prepare(sql),
      exec: (sql: string) => db.exec(sql),
      close: () => db.close(),
      pragma: (key: string, value?: any) => db.pragma(`${key}${value !== undefined ? ` = ${value}` : ''}`),
      inTransaction: db.inTransaction,
      transaction: (fn: () => any) => db.transaction(fn)(),
      checkFTS5Support: () => {
        try {
          db.exec("CREATE VIRTUAL TABLE test_fts USING fts5(content)");
          db.exec("DROP TABLE test_fts");
          return true;
        } catch {
          return false;
        }
      }
    };
    
    // Initialize schema
    this.initializeSchema();
  }
  
  private initializeSchema() {
    const schema = `
      CREATE TABLE IF NOT EXISTS nodes (
        node_type TEXT PRIMARY KEY,
        package_name TEXT NOT NULL,
        display_name TEXT NOT NULL,
        description TEXT,
        category TEXT,
        development_style TEXT CHECK(development_style IN ('declarative', 'programmatic')),
        is_ai_tool INTEGER DEFAULT 0,
        is_trigger INTEGER DEFAULT 0,
        is_webhook INTEGER DEFAULT 0,
        is_versioned INTEGER DEFAULT 0,
        version TEXT,
        documentation TEXT,
        properties_schema TEXT,
        operations TEXT,
        credentials_required TEXT,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
      );
      
      CREATE INDEX IF NOT EXISTS idx_package ON nodes(package_name);
      CREATE INDEX IF NOT EXISTS idx_ai_tool ON nodes(is_ai_tool);
      CREATE INDEX IF NOT EXISTS idx_category ON nodes(category);
    `;
    
    this.adapter!.exec(schema);
  }

  get db(): DatabaseAdapter {
    if (!this.adapter) {
      throw new Error('Database not initialized');
    }
    return this.adapter;
  }

  close() {
    if (this.adapter) {
      this.adapter.close();
      this.adapter = null;
    }
  }
}
```

--------------------------------------------------------------------------------
/tests/test-parsing-operations.js:
--------------------------------------------------------------------------------

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

const markdown = `
## Operations

* **Channel**
    * **Archive** a channel.
    * **Close** a direct message or multi-person direct message.
    * **Create** a public or private channel-based conversation.
    * **Get** information about a channel.
    * **Get Many**: Get a list of channels in Slack.
* **File**
    * **Get** a file.
    * **Get Many**: Get and filter team files.
    * **Upload**: Create or upload an existing file.

## Templates and examples
`;

function extractOperations(markdown) {
  const operations = [];
  
  // Find operations section
  const operationsMatch = markdown.match(/##\s+Operations\s*\n([\s\S]*?)(?=\n##|\n#|$)/i);
  if (!operationsMatch) {
    console.log('No operations section found');
    return operations;
  }
  
  const operationsText = operationsMatch[1];
  console.log('Operations text:', operationsText.substring(0, 200));
  
  // Parse operation structure
  let currentResource = null;
  const lines = operationsText.split('\n');
  
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    const trimmedLine = line.trim();
    
    // Resource level (e.g., "* **Channel**")
    if (trimmedLine.match(/^\*\s+\*\*([^*]+)\*\*/)) {
      currentResource = trimmedLine.match(/^\*\s+\*\*([^*]+)\*\*/)[1].trim();
      console.log(`Found resource: ${currentResource}`);
      continue;
    }
    
    // Skip if we don't have a current resource
    if (!currentResource) continue;
    
    // Operation level - look for indented bullets (4 spaces + *)
    if (line.match(/^\s{4}\*\s+/)) {
      console.log(`Found operation line: "${line}"`);
      
      // Extract operation name and description
      const operationMatch = trimmedLine.match(/^\*\s+\*\*([^*]+)\*\*(.*)$/);
      if (operationMatch) {
        const operation = operationMatch[1].trim();
        let description = operationMatch[2].trim();
        
        // Clean up description
        description = description.replace(/^:\s*/, '').replace(/\.$/, '').trim();
        
        operations.push({
          resource: currentResource,
          operation,
          description: description || operation,
        });
        console.log(`  Parsed: ${operation} - ${description}`);
      }
    }
  }
  
  return operations;
}

const operations = extractOperations(markdown);
console.log('\nTotal operations found:', operations.length);
console.log('\nOperations:');
operations.forEach(op => {
  console.log(`- ${op.resource}.${op.operation}: ${op.description}`);
});
```

--------------------------------------------------------------------------------
/scripts/test-telemetry-security.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env npx tsx
/**
 * Test that RLS properly protects data
 */

import { createClient } from '@supabase/supabase-js';
import dotenv from 'dotenv';

dotenv.config();

async function testSecurity() {
  const supabaseUrl = process.env.SUPABASE_URL!;
  const supabaseAnonKey = process.env.SUPABASE_ANON_KEY!;

  console.log('🔒 Testing Telemetry Security (RLS)\n');

  const supabase = createClient(supabaseUrl, supabaseAnonKey, {
    auth: {
      persistSession: false,
      autoRefreshToken: false,
    }
  });

  // Test 1: Verify anon can INSERT
  console.log('Test 1: Anonymous INSERT (should succeed)...');
  const testData = {
    user_id: 'security-test-' + Date.now(),
    event: 'security_test',
    properties: { test: true }
  };

  const { error: insertError } = await supabase
    .from('telemetry_events')
    .insert([testData]);

  if (insertError) {
    console.error('❌ Insert failed:', insertError.message);
  } else {
    console.log('✅ Insert succeeded (as expected)');
  }

  // Test 2: Verify anon CANNOT SELECT
  console.log('\nTest 2: Anonymous SELECT (should fail)...');
  const { data, error: selectError } = await supabase
    .from('telemetry_events')
    .select('*')
    .limit(1);

  if (selectError) {
    console.log('✅ Select blocked by RLS (as expected):', selectError.message);
  } else if (data && data.length > 0) {
    console.error('❌ SECURITY ISSUE: Anon can read data!', data);
  } else if (data && data.length === 0) {
    console.log('⚠️  Select returned empty array (might be RLS working)');
  }

  // Test 3: Verify anon CANNOT UPDATE
  console.log('\nTest 3: Anonymous UPDATE (should fail)...');
  const { error: updateError } = await supabase
    .from('telemetry_events')
    .update({ event: 'hacked' })
    .eq('user_id', 'test');

  if (updateError) {
    console.log('✅ Update blocked (as expected):', updateError.message);
  } else {
    console.error('❌ SECURITY ISSUE: Anon can update data!');
  }

  // Test 4: Verify anon CANNOT DELETE
  console.log('\nTest 4: Anonymous DELETE (should fail)...');
  const { error: deleteError } = await supabase
    .from('telemetry_events')
    .delete()
    .eq('user_id', 'test');

  if (deleteError) {
    console.log('✅ Delete blocked (as expected):', deleteError.message);
  } else {
    console.error('❌ SECURITY ISSUE: Anon can delete data!');
  }

  console.log('\n✨ Security test completed!');
  console.log('Summary: Anonymous users can INSERT (for telemetry) but cannot READ/UPDATE/DELETE');
}

testSecurity().catch(console.error);
```

--------------------------------------------------------------------------------
/src/types/session-state.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Session persistence types for multi-tenant deployments
 *
 * These types support exporting and restoring MCP session state across
 * container restarts, enabling seamless session persistence in production.
 */

import { InstanceContext } from './instance-context.js';

/**
 * Serializable session state for persistence across restarts
 *
 * This interface represents the minimal state needed to restore an MCP session
 * after a container restart. Only the session metadata and instance context are
 * persisted - transport and server objects are recreated on the first request.
 *
 * @example
 * // Export sessions before shutdown
 * const sessions = server.exportSessionState();
 * await saveToEncryptedStorage(sessions);
 *
 * @example
 * // Restore sessions on startup
 * const sessions = await loadFromEncryptedStorage();
 * const count = server.restoreSessionState(sessions);
 * console.log(`Restored ${count} sessions`);
 */
export interface SessionState {
  /**
   * Unique session identifier
   * Format: UUID v4 or custom format from MCP proxy
   */
  sessionId: string;

  /**
   * Session timing metadata for expiration tracking
   */
  metadata: {
    /**
     * When the session was created (ISO 8601 timestamp)
     * Used to track total session age
     */
    createdAt: string;

    /**
     * When the session was last accessed (ISO 8601 timestamp)
     * Used to determine if session has expired based on timeout
     */
    lastAccess: string;
  };

  /**
   * n8n instance context (credentials and configuration)
   *
   * Contains the n8n API credentials and instance-specific settings.
   * This is the critical data needed to reconnect to the correct n8n instance.
   *
   * Note: API keys are stored in plaintext. The downstream application
   * MUST encrypt this data before persisting to disk.
   */
  context: {
    /**
     * n8n instance API URL
     * Example: "https://n8n.example.com"
     */
    n8nApiUrl: string;

    /**
     * n8n instance API key (plaintext - encrypt before storage!)
     * Example: "n8n_api_1234567890abcdef"
     */
    n8nApiKey: string;

    /**
     * Instance identifier (optional)
     * Custom identifier for tracking which n8n instance this session belongs to
     */
    instanceId?: string;

    /**
     * Session-specific ID (optional)
     * May differ from top-level sessionId in some proxy configurations
     */
    sessionId?: string;

    /**
     * Additional metadata (optional)
     * Extensible field for custom application data
     */
    metadata?: Record<string, any>;
  };
}

```

--------------------------------------------------------------------------------
/scripts/process-batch-metadata.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env ts-node
import * as fs from 'fs';
import * as path from 'path';
import { createDatabaseAdapter } from '../src/database/database-adapter';

interface BatchResponse {
  id: string;
  custom_id: string;
  response: {
    status_code: number;
    body: {
      choices: Array<{
        message: {
          content: string;
        };
      }>;
    };
  };
  error: any;
}

async function processBatchMetadata(batchFile: string) {
  console.log(`📥 Processing batch file: ${batchFile}`);

  // Read the JSONL file
  const content = fs.readFileSync(batchFile, 'utf-8');
  const lines = content.trim().split('\n');

  console.log(`📊 Found ${lines.length} batch responses`);

  // Initialize database
  const db = await createDatabaseAdapter('./data/nodes.db');

  let updated = 0;
  let skipped = 0;
  let errors = 0;

  for (const line of lines) {
    try {
      const response: BatchResponse = JSON.parse(line);

      // Extract template ID from custom_id (format: "template-9100")
      const templateId = parseInt(response.custom_id.replace('template-', ''));

      // Check for errors
      if (response.error || response.response.status_code !== 200) {
        console.warn(`⚠️  Template ${templateId}: API error`, response.error);
        errors++;
        continue;
      }

      // Extract metadata from response
      const metadataJson = response.response.body.choices[0].message.content;

      // Validate it's valid JSON
      JSON.parse(metadataJson); // Will throw if invalid

      // Update database
      const stmt = db.prepare(`
        UPDATE templates
        SET metadata_json = ?
        WHERE id = ?
      `);

      stmt.run(metadataJson, templateId);
      updated++;

      console.log(`✅ Template ${templateId}: Updated metadata`);

    } catch (error: any) {
      console.error(`❌ Error processing line:`, error.message);
      errors++;
    }
  }

  // Close database
  if ('close' in db && typeof db.close === 'function') {
    db.close();
  }

  console.log(`\n📈 Summary:`);
  console.log(`   - Updated: ${updated}`);
  console.log(`   - Skipped: ${skipped}`);
  console.log(`   - Errors: ${errors}`);
  console.log(`   - Total: ${lines.length}`);
}

// Main
const batchFile = process.argv[2] || '/Users/romualdczlonkowski/Pliki/n8n-mcp/n8n-mcp/docs/batch_68fff7242850819091cfed64f10fb6b4_output.jsonl';

processBatchMetadata(batchFile)
  .then(() => {
    console.log('\n✅ Batch processing complete!');
    process.exit(0);
  })
  .catch((error) => {
    console.error('\n❌ Batch processing failed:', error);
    process.exit(1);
  });

```

--------------------------------------------------------------------------------
/scripts/format-benchmark-results.js:
--------------------------------------------------------------------------------

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

const fs = require('fs');
const path = require('path');

/**
 * Formats Vitest benchmark results for github-action-benchmark
 * Converts from Vitest format to the expected format
 */
function formatBenchmarkResults() {
  const resultsPath = path.join(process.cwd(), 'benchmark-results.json');
  
  if (!fs.existsSync(resultsPath)) {
    console.error('benchmark-results.json not found');
    process.exit(1);
  }

  const vitestResults = JSON.parse(fs.readFileSync(resultsPath, 'utf8'));
  
  // Convert to github-action-benchmark format
  const formattedResults = [];

  // Vitest benchmark JSON reporter format
  if (vitestResults.files) {
    for (const file of vitestResults.files) {
      const suiteName = path.basename(file.filepath, '.bench.ts');
      
      // Process each suite in the file
      if (file.groups) {
        for (const group of file.groups) {
          for (const benchmark of group.benchmarks || []) {
            if (benchmark.result) {
              formattedResults.push({
                name: `${suiteName} - ${benchmark.name}`,
                unit: 'ms',
                value: benchmark.result.mean || 0,
                range: (benchmark.result.max - benchmark.result.min) || 0,
                extra: `${benchmark.result.hz?.toFixed(0) || 0} ops/sec`
              });
            }
          }
        }
      }
    }
  } else if (Array.isArray(vitestResults)) {
    // Alternative format handling
    for (const result of vitestResults) {
      if (result.name && result.result) {
        formattedResults.push({
          name: result.name,
          unit: 'ms',
          value: result.result.mean || 0,
          range: (result.result.max - result.result.min) || 0,
          extra: `${result.result.hz?.toFixed(0) || 0} ops/sec`
        });
      }
    }
  }

  // Write formatted results
  const outputPath = path.join(process.cwd(), 'benchmark-results-formatted.json');
  fs.writeFileSync(outputPath, JSON.stringify(formattedResults, null, 2));
  
  // Also create a summary for PR comments
  const summary = {
    timestamp: new Date().toISOString(),
    benchmarks: formattedResults.map(b => ({
      name: b.name,
      time: `${b.value.toFixed(3)}ms`,
      opsPerSec: b.extra,
      range: `±${(b.range / 2).toFixed(3)}ms`
    }))
  };
  
  fs.writeFileSync(
    path.join(process.cwd(), 'benchmark-summary.json'),
    JSON.stringify(summary, null, 2)
  );

  console.log(`Formatted ${formattedResults.length} benchmark results`);
}

// Run if called directly
if (require.main === module) {
  formatBenchmarkResults();
}
```

--------------------------------------------------------------------------------
/scripts/vitest-benchmark-reporter.ts:
--------------------------------------------------------------------------------

```typescript
import type { Task, TaskResult, BenchmarkResult } from 'vitest';
import { writeFileSync } from 'fs';
import { resolve } from 'path';

interface BenchmarkJsonResult {
  timestamp: string;
  files: Array<{
    filepath: string;
    groups: Array<{
      name: string;
      benchmarks: Array<{
        name: string;
        result: {
          mean: number;
          min: number;
          max: number;
          hz: number;
          p75: number;
          p99: number;
          p995: number;
          p999: number;
          rme: number;
          samples: number;
        };
      }>;
    }>;
  }>;
}

export class BenchmarkJsonReporter {
  private results: BenchmarkJsonResult = {
    timestamp: new Date().toISOString(),
    files: []
  };

  onInit() {
    console.log('[BenchmarkJsonReporter] Initialized');
  }

  onFinished(files?: Task[]) {
    console.log('[BenchmarkJsonReporter] onFinished called');
    
    if (!files) {
      console.log('[BenchmarkJsonReporter] No files provided');
      return;
    }

    for (const file of files) {
      const fileResult = {
        filepath: file.filepath || 'unknown',
        groups: [] as any[]
      };

      this.processTask(file, fileResult);

      if (fileResult.groups.length > 0) {
        this.results.files.push(fileResult);
      }
    }

    // Write results
    const outputPath = resolve(process.cwd(), 'benchmark-results.json');
    writeFileSync(outputPath, JSON.stringify(this.results, null, 2));
    console.log(`[BenchmarkJsonReporter] Results written to ${outputPath}`);
  }

  private processTask(task: Task, fileResult: any) {
    if (task.type === 'suite' && task.tasks) {
      const group = {
        name: task.name,
        benchmarks: [] as any[]
      };

      for (const benchmark of task.tasks) {
        const result = benchmark.result as TaskResult & { benchmark?: BenchmarkResult };
        if (result?.benchmark) {
          group.benchmarks.push({
            name: benchmark.name,
            result: {
              mean: result.benchmark.mean || 0,
              min: result.benchmark.min || 0,
              max: result.benchmark.max || 0,
              hz: result.benchmark.hz || 0,
              p75: result.benchmark.p75 || 0,
              p99: result.benchmark.p99 || 0,
              p995: result.benchmark.p995 || 0,
              p999: result.benchmark.p999 || 0,
              rme: result.benchmark.rme || 0,
              samples: result.benchmark.samples?.length || 0
            }
          });
        }
      }

      if (group.benchmarks.length > 0) {
        fileResult.groups.push(group);
      }
    }
  }
}
```

--------------------------------------------------------------------------------
/tests/fixtures/factories/node.factory.ts:
--------------------------------------------------------------------------------

```typescript
import { Factory } from 'fishery';
import { faker } from '@faker-js/faker';

interface NodeDefinition {
  name: string;
  displayName: string;
  description: string;
  version: number;
  defaults: { name: string };
  inputs: string[];
  outputs: string[];
  properties: any[];
  credentials?: any[];
  group?: string[];
}

export const nodeFactory = Factory.define<NodeDefinition>(() => ({
  name: faker.helpers.slugify(faker.word.noun()),
  displayName: faker.company.name(),
  description: faker.lorem.sentence(),
  version: faker.number.int({ min: 1, max: 5 }),
  defaults: {
    name: faker.word.noun()
  },
  inputs: ['main'],
  outputs: ['main'],
  group: [faker.helpers.arrayElement(['transform', 'trigger', 'output'])],
  properties: [
    {
      displayName: 'Resource',
      name: 'resource',
      type: 'options',
      default: 'user',
      options: [
        { name: 'User', value: 'user' },
        { name: 'Post', value: 'post' }
      ]
    }
  ],
  credentials: []
}));

// Specific node factories
export const webhookNodeFactory = nodeFactory.params({
  name: 'webhook',
  displayName: 'Webhook',
  description: 'Starts the workflow when a webhook is called',
  group: ['trigger'],
  properties: [
    {
      displayName: 'Path',
      name: 'path',
      type: 'string',
      default: 'webhook',
      required: true
    },
    {
      displayName: 'Method',
      name: 'method',
      type: 'options',
      default: 'GET',
      options: [
        { name: 'GET', value: 'GET' },
        { name: 'POST', value: 'POST' }
      ]
    }
  ]
});

export const slackNodeFactory = nodeFactory.params({
  name: 'slack',
  displayName: 'Slack',
  description: 'Send messages to Slack',
  group: ['output'],
  credentials: [
    {
      name: 'slackApi',
      required: true
    }
  ],
  properties: [
    {
      displayName: 'Resource',
      name: 'resource',
      type: 'options',
      default: 'message',
      options: [
        { name: 'Message', value: 'message' },
        { name: 'Channel', value: 'channel' }
      ]
    },
    {
      displayName: 'Operation',
      name: 'operation',
      type: 'options',
      displayOptions: {
        show: {
          resource: ['message']
        }
      },
      default: 'post',
      options: [
        { name: 'Post', value: 'post' },
        { name: 'Update', value: 'update' }
      ]
    },
    {
      displayName: 'Channel',
      name: 'channel',
      type: 'string',
      required: true,
      displayOptions: {
        show: {
          resource: ['message'],
          operation: ['post']
        }
      },
      default: ''
    }
  ]
});
```

--------------------------------------------------------------------------------
/types/test-env.d.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Type definitions for test environment variables
 */

declare global {
  namespace NodeJS {
    interface ProcessEnv {
      // Core Environment
      NODE_ENV: 'test' | 'development' | 'production';
      MCP_MODE?: 'test' | 'http' | 'stdio';
      TEST_ENVIRONMENT?: string;

      // Database Configuration
      NODE_DB_PATH?: string;
      REBUILD_ON_START?: string;
      TEST_SEED_DATABASE?: string;
      TEST_SEED_TEMPLATES?: string;

      // API Configuration
      N8N_API_URL?: string;
      N8N_API_KEY?: string;
      N8N_WEBHOOK_BASE_URL?: string;
      N8N_WEBHOOK_TEST_URL?: string;

      // Server Configuration
      PORT?: string;
      HOST?: string;
      CORS_ORIGIN?: string;

      // Authentication
      AUTH_TOKEN?: string;
      MCP_AUTH_TOKEN?: string;

      // Logging
      LOG_LEVEL?: 'debug' | 'info' | 'warn' | 'error';
      DEBUG?: string;
      TEST_LOG_VERBOSE?: string;
      ERROR_SHOW_STACK?: string;
      ERROR_SHOW_DETAILS?: string;

      // Test Timeouts
      TEST_TIMEOUT_UNIT?: string;
      TEST_TIMEOUT_INTEGRATION?: string;
      TEST_TIMEOUT_E2E?: string;
      TEST_TIMEOUT_GLOBAL?: string;

      // Test Execution
      TEST_RETRY_ATTEMPTS?: string;
      TEST_RETRY_DELAY?: string;
      TEST_PARALLEL?: string;
      TEST_MAX_WORKERS?: string;

      // Feature Flags
      FEATURE_TEST_COVERAGE?: string;
      FEATURE_TEST_SCREENSHOTS?: string;
      FEATURE_TEST_VIDEOS?: string;
      FEATURE_TEST_TRACE?: string;
      FEATURE_MOCK_EXTERNAL_APIS?: string;
      FEATURE_USE_TEST_CONTAINERS?: string;

      // Mock Services
      MSW_ENABLED?: string;
      MSW_API_DELAY?: string;
      REDIS_MOCK_ENABLED?: string;
      REDIS_MOCK_PORT?: string;
      ELASTICSEARCH_MOCK_ENABLED?: string;
      ELASTICSEARCH_MOCK_PORT?: string;

      // Test Paths
      TEST_FIXTURES_PATH?: string;
      TEST_DATA_PATH?: string;
      TEST_SNAPSHOTS_PATH?: string;

      // Performance Thresholds
      PERF_THRESHOLD_API_RESPONSE?: string;
      PERF_THRESHOLD_DB_QUERY?: string;
      PERF_THRESHOLD_NODE_PARSE?: string;

      // Rate Limiting
      RATE_LIMIT_MAX?: string;
      RATE_LIMIT_WINDOW?: string;

      // Caching
      CACHE_TTL?: string;
      CACHE_ENABLED?: string;

      // Cleanup
      TEST_CLEANUP_ENABLED?: string;
      TEST_CLEANUP_ON_FAILURE?: string;

      // Network
      NETWORK_TIMEOUT?: string;
      NETWORK_RETRY_COUNT?: string;

      // Memory
      TEST_MEMORY_LIMIT?: string;

      // Coverage
      COVERAGE_DIR?: string;
      COVERAGE_REPORTER?: string;
    }
  }
}

// Export empty object to make this a module
export {};
```

--------------------------------------------------------------------------------
/scripts/http-bridge.js:
--------------------------------------------------------------------------------

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

/**
 * HTTP-to-stdio bridge for n8n-MCP
 * Connects to n8n-MCP HTTP server and bridges stdio communication
 */

const http = require('http');
const readline = require('readline');

// Use MCP_URL from environment or construct from HOST/PORT if available
const defaultHost = process.env.HOST || 'localhost';
const defaultPort = process.env.PORT || '3000';
const MCP_URL = process.env.MCP_URL || `http://${defaultHost}:${defaultPort}/mcp`;
const AUTH_TOKEN = process.env.AUTH_TOKEN || process.argv[2];

if (!AUTH_TOKEN) {
  console.error('Error: AUTH_TOKEN environment variable or first argument required');
  process.exit(1);
}

// Parse URL
const url = new URL(MCP_URL);

// Create readline interface for stdio
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  terminal: false
});

// Buffer for incomplete JSON messages
let buffer = '';

// Handle incoming stdio messages
rl.on('line', async (line) => {
  try {
    const message = JSON.parse(line);
    
    // Forward to HTTP server
    const options = {
      hostname: url.hostname,
      port: url.port || 80,
      path: url.pathname,
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${AUTH_TOKEN}`,
        'Accept': 'application/json, text/event-stream'
      }
    };

    const req = http.request(options, (res) => {
      let responseData = '';

      res.on('data', (chunk) => {
        responseData += chunk;
      });

      res.on('end', () => {
        try {
          // Try to parse as JSON
          const response = JSON.parse(responseData);
          console.log(JSON.stringify(response));
        } catch (e) {
          // Handle SSE format
          const lines = responseData.split('\n');
          for (const line of lines) {
            if (line.startsWith('data: ')) {
              try {
                const data = JSON.parse(line.substring(6));
                console.log(JSON.stringify(data));
              } catch (e) {
                // Ignore parse errors
              }
            }
          }
        }
      });
    });

    req.on('error', (error) => {
      console.error(JSON.stringify({
        jsonrpc: '2.0',
        error: {
          code: -32000,
          message: `HTTP request failed: ${error.message}`
        },
        id: message.id || null
      }));
    });

    req.write(JSON.stringify(message));
    req.end();
  } catch (error) {
    // Not valid JSON, ignore
  }
});

// Handle process termination
process.on('SIGTERM', () => {
  process.exit(0);
});

process.on('SIGINT', () => {
  process.exit(0);
});
```

--------------------------------------------------------------------------------
/tests/integration/setup/integration-setup.ts:
--------------------------------------------------------------------------------

```typescript
import { beforeAll, afterAll, afterEach } from 'vitest';
import { setupServer } from 'msw/node';
import { handlers as defaultHandlers } from '../../mocks/n8n-api/handlers';

// Create the MSW server instance with default handlers
export const server = setupServer(...defaultHandlers);

// Enable request logging in development/debugging
if (process.env.MSW_DEBUG === 'true' || process.env.TEST_DEBUG === 'true') {
  server.events.on('request:start', ({ request }) => {
    console.log('[MSW] %s %s', request.method, request.url);
  });

  server.events.on('request:match', ({ request }) => {
    console.log('[MSW] Request matched:', request.method, request.url);
  });

  server.events.on('request:unhandled', ({ request }) => {
    console.warn('[MSW] Unhandled request:', request.method, request.url);
  });

  server.events.on('response:mocked', ({ request, response }) => {
    console.log('[MSW] Mocked response for %s %s: %d', 
      request.method, 
      request.url, 
      response.status
    );
  });
}

// Start server before all tests
beforeAll(() => {
  server.listen({
    onUnhandledRequest: process.env.CI === 'true' ? 'error' : 'warn',
  });
});

// Reset handlers after each test (important for test isolation)
afterEach(() => {
  server.resetHandlers();
});

// Clean up after all tests
afterAll(() => {
  // Remove all event listeners to prevent memory leaks
  server.events.removeAllListeners();
  
  // Close the server
  server.close();
});

// Export the server and utility functions for use in integration tests
export { server as integrationServer };
export { http, HttpResponse } from 'msw';

/**
 * Utility function to add temporary handlers for specific tests
 * @param handlers Array of MSW request handlers
 */
export function useHandlers(...handlers: any[]) {
  server.use(...handlers);
}

/**
 * Utility to wait for a specific request to be made
 * Useful for testing async operations
 */
export function waitForRequest(method: string, url: string | RegExp, timeout = 5000): Promise<Request> {
  return new Promise((resolve, reject) => {
    let timeoutId: NodeJS.Timeout;
    
    const handler = ({ request }: { request: Request }) => {
      if (request.method === method && 
          (typeof url === 'string' ? request.url === url : url.test(request.url))) {
        clearTimeout(timeoutId);
        server.events.removeListener('request:match', handler);
        resolve(request);
      }
    };
    
    // Set timeout
    timeoutId = setTimeout(() => {
      server.events.removeListener('request:match', handler);
      reject(new Error(`Timeout waiting for ${method} request to ${url}`));
    }, timeout);
    
    server.events.on('request:match', handler);
  });
}
```

--------------------------------------------------------------------------------
/tests/test-direct-extraction.js:
--------------------------------------------------------------------------------

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

const fs = require('fs');
const path = require('path');

// Import the NodeSourceExtractor
const { NodeSourceExtractor } = require('../dist/utils/node-source-extractor');

async function testExtraction() {
  console.log('=== Direct Node Extraction Test ===\n');
  
  const extractor = new NodeSourceExtractor();
  
  // Test extraction of AI Agent node
  const nodeType = '@n8n/n8n-nodes-langchain.Agent';
  
  console.log(`Testing extraction of: ${nodeType}`);
  
  // First, let's debug what paths are being searched
  console.log('\nSearching in paths:');
  const searchPaths = [
    '/usr/local/lib/node_modules/n8n/node_modules',
    '/app/node_modules',
    '/home/node/.n8n/custom/nodes',
    './node_modules'
  ];
  
  for (const basePath of searchPaths) {
    console.log(`- ${basePath}`);
    try {
      const exists = fs.existsSync(basePath);
      console.log(`  Exists: ${exists}`);
      if (exists) {
        const items = fs.readdirSync(basePath).slice(0, 5);
        console.log(`  Sample items: ${items.join(', ')}...`);
      }
    } catch (e) {
      console.log(`  Error: ${e.message}`);
    }
  }
  
  try {
    const result = await extractor.extractNodeSource(nodeType, true);
    
    console.log('\n✅ Extraction successful!');
    console.log(`Source file: ${result.location}`);
    console.log(`Code length: ${result.sourceCode.length} characters`);
    console.log(`Credential code found: ${result.credentialCode ? 'Yes' : 'No'}`);
    console.log(`Package.json found: ${result.packageInfo ? 'Yes' : 'No'}`);
    
    // Show first 500 characters of the code
    console.log('\nFirst 500 characters of code:');
    console.log('=' .repeat(60));
    console.log(result.sourceCode.substring(0, 500) + '...');
    console.log('=' .repeat(60));
    
    // Show credential code if found
    if (result.credentialCode) {
      console.log('\nCredential code found!');
      console.log('First 200 characters of credential code:');
      console.log(result.credentialCode.substring(0, 200) + '...');
    }
    
    // Check if we can find it in Docker volume
    const dockerPath = '/usr/local/lib/node_modules/n8n/node_modules/.pnpm/@n8n+n8n-nodes-langchain@file+packages+@n8n+nodes-langchain_f35e7d377a7fe4d08dc2766706b5dbff/node_modules/@n8n/n8n-nodes-langchain/dist/nodes/agents/Agent/Agent.node.js';
    
    if (fs.existsSync(dockerPath)) {
      console.log('\n✅ File also found in expected Docker path');
      const dockerContent = fs.readFileSync(dockerPath, 'utf8');
      console.log(`Docker file size: ${dockerContent.length} bytes`);
    }
    
  } catch (error) {
    console.error('\n❌ Extraction failed:', error.message);
    console.error('Stack trace:', error.stack);
  }
}

testExtraction().catch(console.error);
```

--------------------------------------------------------------------------------
/tests/integration/mcp-protocol/basic-connection.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect } from 'vitest';
import { N8NDocumentationMCPServer } from '../../../src/mcp/server';

describe('Basic MCP Connection', () => {
  it('should initialize MCP server', async () => {
    const server = new N8NDocumentationMCPServer();

    // Test executeTool directly - tools_documentation returns a string
    const result = await server.executeTool('tools_documentation', {});
    expect(result).toBeDefined();
    expect(typeof result).toBe('string');
    expect(result).toContain('n8n MCP');

    await server.shutdown();
  });

  it('should execute search_nodes tool', async () => {
    const server = new N8NDocumentationMCPServer();

    try {
      // Search for a common node to verify database has content
      const result = await server.executeTool('search_nodes', { query: 'http', limit: 5 });
      expect(result).toBeDefined();
      expect(typeof result).toBe('object');
      expect(result.results).toBeDefined();
      expect(Array.isArray(result.results)).toBe(true);

      if (result.totalCount > 0) {
        // If database has nodes, we should get results
        expect(result.results.length).toBeLessThanOrEqual(5);
        expect(result.results.length).toBeGreaterThan(0);
        expect(result.results[0]).toHaveProperty('nodeType');
        expect(result.results[0]).toHaveProperty('displayName');
      }
    } catch (error: any) {
      // In test environment with empty database, expect appropriate error
      expect(error.message).toContain('Database is empty');
    }

    await server.shutdown();
  });

  it('should search nodes by keyword', async () => {
    const server = new N8NDocumentationMCPServer();

    try {
      // Search to check if database has nodes
      const searchResult = await server.executeTool('search_nodes', { query: 'set', limit: 1 });
      const hasNodes = searchResult.totalCount > 0;

      const result = await server.executeTool('search_nodes', { query: 'webhook' });
      expect(result).toBeDefined();
      expect(typeof result).toBe('object');
      expect(result.results).toBeDefined();
      expect(Array.isArray(result.results)).toBe(true);

      // Only expect results if the database has nodes
      if (hasNodes) {
        expect(result.results.length).toBeGreaterThan(0);
        expect(result.totalCount).toBeGreaterThan(0);

        // Should find webhook node
        const webhookNode = result.results.find((n: any) => n.nodeType === 'nodes-base.webhook');
        expect(webhookNode).toBeDefined();
        expect(webhookNode.displayName).toContain('Webhook');
      }
    } catch (error: any) {
      // In test environment with empty database, expect appropriate error
      expect(error.message).toContain('Database is empty');
    }

    await server.shutdown();
  });
});
```

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

```typescript
/**
 * Shared Error Sanitization Utilities
 * Used by both error-sanitizer.ts and event-tracker.ts to avoid code duplication
 *
 * Security patterns from v2.15.3 with ReDoS fix from v2.18.3
 */

import { logger } from '../utils/logger';

/**
 * Core error message sanitization with security-focused patterns
 *
 * Sanitization order (critical for preventing leakage):
 * 1. Early truncation (ReDoS prevention)
 * 2. Stack trace limitation
 * 3. URLs (most encompassing) - fully redact
 * 4. Specific credentials (AWS, GitHub, JWT, Bearer)
 * 5. Emails (after URLs)
 * 6. Long keys and tokens
 * 7. Generic credential patterns
 * 8. Final truncation
 *
 * @param errorMessage - Raw error message to sanitize
 * @returns Sanitized error message safe for telemetry
 */
export function sanitizeErrorMessageCore(errorMessage: string): string {
  try {
    // Early truncate to prevent ReDoS and performance issues
    const maxLength = 1500;
    const trimmed = errorMessage.length > maxLength
      ? errorMessage.substring(0, maxLength)
      : errorMessage;

    // Handle stack traces - keep only first 3 lines (message + top stack frames)
    const lines = trimmed.split('\n');
    let sanitized = lines.slice(0, 3).join('\n');

    // Sanitize sensitive data in correct order to prevent leakage

    // 1. URLs first (most encompassing) - fully redact to prevent path leakage
    sanitized = sanitized.replace(/https?:\/\/\S+/gi, '[URL]');

    // 2. Specific credential patterns (before generic patterns)
    sanitized = sanitized
      .replace(/AKIA[A-Z0-9]{16}/g, '[AWS_KEY]')
      .replace(/ghp_[a-zA-Z0-9]{36,}/g, '[GITHUB_TOKEN]')
      .replace(/eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g, '[JWT]')
      .replace(/Bearer\s+[^\s]+/gi, 'Bearer [TOKEN]');

    // 3. Emails (after URLs to avoid partial matches)
    sanitized = sanitized.replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, '[EMAIL]');

    // 4. Long keys and quoted tokens
    sanitized = sanitized
      .replace(/\b[a-zA-Z0-9_-]{32,}\b/g, '[KEY]')
      .replace(/(['"])[a-zA-Z0-9_-]{16,}\1/g, '$1[TOKEN]$1');

    // 5. Generic credential patterns (after specific ones to avoid conflicts)
    // FIX (v2.18.3): Replaced negative lookbehind with simpler regex to prevent ReDoS
    sanitized = sanitized
      .replace(/password\s*[=:]\s*\S+/gi, 'password=[REDACTED]')
      .replace(/api[_-]?key\s*[=:]\s*\S+/gi, 'api_key=[REDACTED]')
      .replace(/\btoken\s*[=:]\s*[^\s;,)]+/gi, 'token=[REDACTED]'); // Simplified regex (no negative lookbehind)

    // Final truncate to 500 chars
    if (sanitized.length > 500) {
      sanitized = sanitized.substring(0, 500) + '...';
    }

    return sanitized;
  } catch (error) {
    logger.debug('Error message sanitization failed:', error);
    return '[SANITIZATION_FAILED]';
  }
}

```

--------------------------------------------------------------------------------
/src/scripts/rebuild-database.ts:
--------------------------------------------------------------------------------

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

import * as dotenv from 'dotenv';
import { NodeDocumentationService } from '../services/node-documentation-service';
import { logger } from '../utils/logger';

// Load environment variables
dotenv.config();

/**
 * Rebuild the enhanced documentation database
 */
async function rebuildDocumentationDatabase() {
  console.log('🔄 Starting enhanced documentation database rebuild...\n');
  
  const startTime = Date.now();
  const service = new NodeDocumentationService();
  
  try {
    // Run the rebuild
    const results = await service.rebuildDatabase();
    
    const duration = ((Date.now() - startTime) / 1000).toFixed(2);
    
    console.log('\n✅ Enhanced documentation database rebuild completed!\n');
    console.log('📊 Results:');
    console.log(`   Total nodes found: ${results.total}`);
    console.log(`   Successfully processed: ${results.successful}`);
    console.log(`   Failed: ${results.failed}`);
    console.log(`   Duration: ${duration}s`);
    
    if (results.errors.length > 0) {
      console.log(`\n⚠️  First ${Math.min(5, results.errors.length)} errors:`);
      results.errors.slice(0, 5).forEach(err => {
        console.log(`   - ${err}`);
      });
      
      if (results.errors.length > 5) {
        console.log(`   ... and ${results.errors.length - 5} more errors`);
      }
    }
    
    // Get and display statistics
    const stats = await service.getStatistics();
    console.log('\n📈 Database Statistics:');
    console.log(`   Total nodes: ${stats.totalNodes}`);
    console.log(`   Nodes with documentation: ${stats.nodesWithDocs}`);
    console.log(`   Nodes with examples: ${stats.nodesWithExamples}`);
    console.log(`   Nodes with credentials: ${stats.nodesWithCredentials}`);
    console.log(`   Trigger nodes: ${stats.triggerNodes}`);
    console.log(`   Webhook nodes: ${stats.webhookNodes}`);
    
    console.log('\n📦 Package distribution:');
    stats.packageDistribution.slice(0, 10).forEach((pkg: any) => {
      console.log(`   ${pkg.package}: ${pkg.count} nodes`);
    });
    
    // Close database connection
    await service.close();
    
    console.log('\n✨ Enhanced documentation database is ready!');
    console.log('💡 The database now includes:');
    console.log('   - Complete node source code');
    console.log('   - Enhanced documentation with operations and API methods');
    console.log('   - Code examples and templates');
    console.log('   - Related resources and required scopes');
    
  } catch (error) {
    console.error('\n❌ Documentation database rebuild failed:', error);
    service.close();
    process.exit(1);
  }
}

// Run if called directly
if (require.main === module) {
  rebuildDocumentationDatabase().catch(error => {
    console.error('Fatal error:', error);
    process.exit(1);
  });
}

export { rebuildDocumentationDatabase };
```

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

```typescript
import { ToolDocumentation } from '../types';

export const n8nListWorkflowsDoc: ToolDocumentation = {
  name: 'n8n_list_workflows',
  category: 'workflow_management',
  essentials: {
    description: 'List workflows (minimal metadata only - no nodes/connections). Supports pagination via cursor.',
    keyParameters: ['limit', 'active', 'tags'],
    example: 'n8n_list_workflows({limit: 20, active: true})',
    performance: 'Fast (100-300ms)',
    tips: [
      'Use cursor for pagination',
      'Filter by active status',
      'Tag filtering for organization'
    ]
  },
  full: {
    description: 'Lists workflows from n8n with powerful filtering options. Returns ONLY minimal metadata (id, name, active, dates, tags, nodeCount) - no workflow structure, nodes, or connections. Use n8n_get_workflow to fetch full workflow details.',
    parameters: {
      limit: { type: 'number', description: 'Number of workflows to return (1-100, default: 100)' },
      cursor: { type: 'string', description: 'Pagination cursor from previous response for next page' },
      active: { type: 'boolean', description: 'Filter by active/inactive status' },
      tags: { type: 'array', description: 'Filter by exact tag matches (AND logic)' },
      projectId: { type: 'string', description: 'Filter by project ID (enterprise feature)' },
      excludePinnedData: { type: 'boolean', description: 'Exclude pinned data from response (default: true)' }
    },
    returns: 'Object with: workflows array (minimal fields: id, name, active, createdAt, updatedAt, tags, nodeCount), returned (count in this response), hasMore (boolean), nextCursor (for pagination), and _note (guidance when more data exists)',
    examples: [
      'n8n_list_workflows({limit: 20}) - First 20 workflows',
      'n8n_list_workflows({active: true, tags: ["production"]}) - Active production workflows',
      'n8n_list_workflows({cursor: "abc123", limit: 50}) - Next page of results'
    ],
    useCases: [
      'Build workflow dashboards',
      'Find workflows by status',
      'Organize by tags',
      'Bulk workflow operations',
      'Generate workflow reports'
    ],
    performance: 'Very fast - typically 50-200ms. Returns only minimal metadata without workflow structure.',
    bestPractices: [
      'Always check hasMore flag to determine if pagination is needed',
      'Use cursor from previous response to get next page',
      'The returned count is NOT the total in the system',
      'Iterate with cursor until hasMore is false for complete list'
    ],
    pitfalls: [
      'Requires N8N_API_URL and N8N_API_KEY configured',
      'Maximum 100 workflows per request',
      'Server may return fewer than requested limit',
      'returned field is count of current page only, not system total'
    ],
    relatedTools: ['n8n_get_workflow', 'n8n_update_partial_workflow', 'n8n_executions']
  }
};
```

--------------------------------------------------------------------------------
/src/scripts/test-summary.ts:
--------------------------------------------------------------------------------

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

import { createDatabaseAdapter } from '../database/database-adapter';
import { NodeRepository } from '../database/node-repository';
import { NodeSimilarityService } from '../services/node-similarity-service';
import path from 'path';

async function testSummary() {
  const dbPath = path.join(process.cwd(), 'data/nodes.db');
  const db = await createDatabaseAdapter(dbPath);
  const repository = new NodeRepository(db);
  const similarityService = new NodeSimilarityService(repository);

  const testCases = [
    { invalid: 'HttpRequest', expected: 'nodes-base.httpRequest' },
    { invalid: 'HTTPRequest', expected: 'nodes-base.httpRequest' },
    { invalid: 'Webhook', expected: 'nodes-base.webhook' },
    { invalid: 'WebHook', expected: 'nodes-base.webhook' },
    { invalid: 'slack', expected: 'nodes-base.slack' },
    { invalid: 'googleSheets', expected: 'nodes-base.googleSheets' },
    { invalid: 'telegram', expected: 'nodes-base.telegram' },
    { invalid: 'htpRequest', expected: 'nodes-base.httpRequest' },
    { invalid: 'webook', expected: 'nodes-base.webhook' },
    { invalid: 'slak', expected: 'nodes-base.slack' },
    { invalid: 'http', expected: 'nodes-base.httpRequest' },
    { invalid: 'sheet', expected: 'nodes-base.googleSheets' },
    { invalid: 'nodes-base.openai', expected: 'nodes-langchain.openAi' },
    { invalid: 'n8n-nodes-base.httpRequest', expected: 'nodes-base.httpRequest' },
    { invalid: 'foobar', expected: null },
    { invalid: 'xyz123', expected: null },
  ];

  let passed = 0;
  let failed = 0;

  console.log('Test Results Summary:');
  console.log('='.repeat(60));

  for (const testCase of testCases) {
    const suggestions = await similarityService.findSimilarNodes(testCase.invalid, 3);

    let result = '❌';
    let status = 'FAILED';

    if (testCase.expected === null) {
      // Should have no suggestions
      if (suggestions.length === 0) {
        result = '✅';
        status = 'PASSED';
        passed++;
      } else {
        failed++;
      }
    } else {
      // Should have the expected suggestion
      const found = suggestions.some(s => s.nodeType === testCase.expected);
      if (found) {
        const suggestion = suggestions.find(s => s.nodeType === testCase.expected);
        const isAutoFixable = suggestion && suggestion.confidence >= 0.9;
        result = '✅';
        status = isAutoFixable ? 'PASSED (auto-fixable)' : 'PASSED';
        passed++;
      } else {
        failed++;
      }
    }

    console.log(`${result} "${testCase.invalid}" → ${testCase.expected || 'no suggestions'}: ${status}`);
  }

  console.log('='.repeat(60));
  console.log(`\nTotal: ${passed}/${testCases.length} tests passed`);

  if (failed === 0) {
    console.log('🎉 All tests passed!');
  } else {
    console.log(`⚠️  ${failed} tests failed`);
  }
}

testSummary().catch(console.error);
```

--------------------------------------------------------------------------------
/tests/extracted-nodes-db/insert-nodes.sql:
--------------------------------------------------------------------------------

```sql
-- Auto-generated SQL for n8n nodes

-- Node: n8n-nodes-base.Function
INSERT INTO nodes (node_type, name, package_name, code_hash, code_length, source_location, has_credentials)
VALUES ('n8n-nodes-base.Function', 'Function', 'n8n-nodes-base', 'd68f1ab94b190161e2ec2c56ec6631f6c3992826557c100ec578efff5de96a70', 7449, 'node_modules/n8n-nodes-base/dist/nodes/Function/Function.node.js', false);

-- Node: n8n-nodes-base.Webhook
INSERT INTO nodes (node_type, name, package_name, code_hash, code_length, source_location, has_credentials)
VALUES ('n8n-nodes-base.Webhook', 'Webhook', 'n8n-nodes-base', '143d6bbdce335c5a9204112b2c1e8b92e4061d75ba3cb23301845f6fed9e6c71', 10667, 'node_modules/n8n-nodes-base/dist/nodes/Webhook/Webhook.node.js', false);

-- Node: n8n-nodes-base.HttpRequest
INSERT INTO nodes (node_type, name, package_name, code_hash, code_length, source_location, has_credentials)
VALUES ('n8n-nodes-base.HttpRequest', 'HttpRequest', 'n8n-nodes-base', '5b5e2328474b7e85361c940dfe942e167b3f0057f38062f56d6b693f0a7ffe7e', 1343, 'node_modules/n8n-nodes-base/dist/nodes/HttpRequest/HttpRequest.node.js', false);

-- Node: n8n-nodes-base.If
INSERT INTO nodes (node_type, name, package_name, code_hash, code_length, source_location, has_credentials)
VALUES ('n8n-nodes-base.If', 'If', 'n8n-nodes-base', '7910ed9177a946b76f04ca847defb81226c37c698e4cdb63913f038c6c257ee1', 20533, 'node_modules/n8n-nodes-base/dist/nodes/If/If.node.js', false);

-- Node: n8n-nodes-base.SplitInBatches
INSERT INTO nodes (node_type, name, package_name, code_hash, code_length, source_location, has_credentials)
VALUES ('n8n-nodes-base.SplitInBatches', 'SplitInBatches', 'n8n-nodes-base', 'c751422a11e30bf361a6c4803376289740a40434aeb77f90e18cd4dd7ba5c019', 1135, 'node_modules/n8n-nodes-base/dist/nodes/SplitInBatches/SplitInBatches.node.js', false);

-- Node: n8n-nodes-base.Airtable
INSERT INTO nodes (node_type, name, package_name, code_hash, code_length, source_location, has_credentials)
VALUES ('n8n-nodes-base.Airtable', 'Airtable', 'n8n-nodes-base', '2d67e72931697178946f5127b43e954649c4c5e7ad9e29764796404ae96e7db5', 936, 'node_modules/n8n-nodes-base/dist/nodes/Airtable/Airtable.node.js', false);

-- Node: n8n-nodes-base.Slack
INSERT INTO nodes (node_type, name, package_name, code_hash, code_length, source_location, has_credentials)
VALUES ('n8n-nodes-base.Slack', 'Slack', 'n8n-nodes-base', '0ed10d0646f3c595406359edfa2c293dac41991cee59ad4fb3ccf2bb70eca6fc', 1007, 'node_modules/n8n-nodes-base/dist/nodes/Slack/Slack.node.js', false);

-- Node: n8n-nodes-base.Discord
INSERT INTO nodes (node_type, name, package_name, code_hash, code_length, source_location, has_credentials)
VALUES ('n8n-nodes-base.Discord', 'Discord', 'n8n-nodes-base', '4995f9ca5c5b57d2486c2e320cc7505238e7f2260861f7e321b44b45ccabeb00', 10049, 'node_modules/n8n-nodes-base/dist/nodes/Discord/Discord.node.js', false);


```

--------------------------------------------------------------------------------
/test-reinit-fix.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash

# Test script to verify re-initialization fix works

echo "Starting n8n MCP server..."
AUTH_TOKEN=test123456789012345678901234567890 npm run start:http &
SERVER_PID=$!

# Wait for server to start
sleep 3

echo "Testing multiple initialize requests..."

# First initialize request
echo "1. First initialize request:"
RESPONSE1=$(curl -s -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer test123456789012345678901234567890" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2024-11-05",
      "capabilities": {
        "roots": {
          "listChanged": false
        }
      },
      "clientInfo": {
        "name": "test-client-1",
        "version": "1.0.0"
      }
    }
  }')

if echo "$RESPONSE1" | grep -q '"result"'; then
    echo "✅ First initialize request succeeded"
else
    echo "❌ First initialize request failed: $RESPONSE1"
fi

# Second initialize request (this was failing before)
echo "2. Second initialize request (this was failing before the fix):"
RESPONSE2=$(curl -s -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer test123456789012345678901234567890" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "initialize",
    "params": {
      "protocolVersion": "2024-11-05",
      "capabilities": {
        "roots": {
          "listChanged": false
        }
      },
      "clientInfo": {
        "name": "test-client-2",
        "version": "1.0.0"
      }
    }
  }')

if echo "$RESPONSE2" | grep -q '"result"'; then
    echo "✅ Second initialize request succeeded - FIX WORKING!"
else
    echo "❌ Second initialize request failed: $RESPONSE2"
fi

# Third initialize request to be sure
echo "3. Third initialize request:"
RESPONSE3=$(curl -s -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer test123456789012345678901234567890" \
  -d '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "initialize",
    "params": {
      "protocolVersion": "2024-11-05",
      "capabilities": {
        "roots": {
          "listChanged": false
        }
      },
      "clientInfo": {
        "name": "test-client-3",
        "version": "1.0.0"
      }
    }
  }')

if echo "$RESPONSE3" | grep -q '"result"'; then
    echo "✅ Third initialize request succeeded"
else
    echo "❌ Third initialize request failed: $RESPONSE3"
fi

# Check health to see active transports
echo "4. Checking server health for active transports:"
HEALTH=$(curl -s -X GET http://localhost:3000/health)
echo "$HEALTH" | python3 -m json.tool

# Cleanup
echo "Stopping server..."
kill $SERVER_PID
wait $SERVER_PID 2>/dev/null

echo "Test completed!"
```

--------------------------------------------------------------------------------
/scripts/test-optimized-docker.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash
# Test script for optimized Docker build

set -e

echo "🧪 Testing Optimized Docker Build"
echo "================================="

# Colors for output
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m' # No Color

# Build the optimized image
echo -e "\n📦 Building optimized Docker image..."
docker build -f Dockerfile.optimized -t n8n-mcp:optimized .

# Check image size
echo -e "\n📊 Checking image size..."
SIZE=$(docker images n8n-mcp:optimized --format "{{.Size}}")
echo "Image size: $SIZE"

# Extract size in MB for comparison
SIZE_MB=$(docker images n8n-mcp:optimized --format "{{.Size}}" | sed 's/MB//' | sed 's/GB/*1024/' | bc 2>/dev/null || echo "0")
if [ "$SIZE_MB" != "0" ] && [ "$SIZE_MB" -lt "300" ]; then
    echo -e "${GREEN}✅ Image size is optimized (<300MB)${NC}"
else
    echo -e "${RED}⚠️  Image might be larger than expected${NC}"
fi

# Test stdio mode
echo -e "\n🔍 Testing stdio mode..."
TEST_RESULT=$(echo '{"jsonrpc":"2.0","method":"tools/list","id":1}' | \
    docker run --rm -i -e MCP_MODE=stdio n8n-mcp:optimized 2>/dev/null | \
    grep -o '"name":"[^"]*"' | head -1)

if [ -n "$TEST_RESULT" ]; then
    echo -e "${GREEN}✅ Stdio mode working${NC}"
else
    echo -e "${RED}❌ Stdio mode failed${NC}"
fi

# Test HTTP mode
echo -e "\n🌐 Testing HTTP mode..."
docker run -d --name test-optimized \
    -e MCP_MODE=http \
    -e AUTH_TOKEN=test-token \
    -p 3002:3000 \
    n8n-mcp:optimized

# Wait for startup
sleep 5

# Test health endpoint
HEALTH=$(curl -s http://localhost:3002/health | grep -o '"status":"healthy"' || echo "")
if [ -n "$HEALTH" ]; then
    echo -e "${GREEN}✅ Health endpoint working${NC}"
else
    echo -e "${RED}❌ Health endpoint failed${NC}"
fi

# Test MCP endpoint
MCP_TEST=$(curl -s -H "Authorization: Bearer test-token" \
    -H "Content-Type: application/json" \
    -d '{"jsonrpc":"2.0","method":"tools/list","id":1}' \
    http://localhost:3002/mcp | grep -o '"tools":\[' || echo "")

if [ -n "$MCP_TEST" ]; then
    echo -e "${GREEN}✅ MCP endpoint working${NC}"
else
    echo -e "${RED}❌ MCP endpoint failed${NC}"
fi

# Test database statistics tool
STATS_TEST=$(curl -s -H "Authorization: Bearer test-token" \
    -H "Content-Type: application/json" \
    -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"get_database_statistics","arguments":{}},"id":2}' \
    http://localhost:3002/mcp | grep -o '"totalNodes":[0-9]*' || echo "")

if [ -n "$STATS_TEST" ]; then
    echo -e "${GREEN}✅ Database statistics tool working${NC}"
    echo "Database stats: $STATS_TEST"
else
    echo -e "${RED}❌ Database statistics tool failed${NC}"
fi

# Cleanup
docker stop test-optimized >/dev/null 2>&1
docker rm test-optimized >/dev/null 2>&1

# Compare with original image
echo -e "\n📊 Size Comparison:"
echo "Original image: $(docker images n8n-mcp:latest --format "{{.Size}}" 2>/dev/null || echo "Not built")"
echo "Optimized image: $SIZE"

echo -e "\n✨ Testing complete!"
```

--------------------------------------------------------------------------------
/tests/test-mcp-server-extraction.js:
--------------------------------------------------------------------------------

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

/**
 * Test MCP Server extraction functionality
 * Simulates an MCP client calling the get_node_source_code tool
 */

const { spawn } = require('child_process');
const path = require('path');

// MCP request to get AI Agent node source code
const mcpRequest = {
  jsonrpc: '2.0',
  id: 1,
  method: 'tools/call',
  params: {
    name: 'get_node_source_code',
    arguments: {
      nodeType: '@n8n/n8n-nodes-langchain.Agent',
      includeCredentials: true
    }
  }
};

async function testMCPExtraction() {
  console.log('=== MCP Server Node Extraction Test ===\n');
  console.log('Starting MCP server...');
  
  // Start the MCP server
  const serverPath = path.join(__dirname, '../dist/index.js');
  const server = spawn('node', [serverPath], {
    env: {
      ...process.env,
      MCP_SERVER_PORT: '3000',
      MCP_SERVER_HOST: '0.0.0.0',
      N8N_API_URL: 'http://n8n:5678',
      N8N_API_KEY: 'test-api-key',
      MCP_AUTH_TOKEN: 'test-token',
      LOG_LEVEL: 'info'
    },
    stdio: ['pipe', 'pipe', 'pipe']
  });

  let responseBuffer = '';
  let errorBuffer = '';

  server.stdout.on('data', (data) => {
    responseBuffer += data.toString();
  });

  server.stderr.on('data', (data) => {
    errorBuffer += data.toString();
  });

  // Give server time to start
  await new Promise(resolve => setTimeout(resolve, 2000));

  console.log('Sending MCP request...');
  console.log(JSON.stringify(mcpRequest, null, 2));
  
  // Send the request via stdin (MCP uses stdio transport)
  server.stdin.write(JSON.stringify(mcpRequest) + '\n');
  
  // Wait for response
  await new Promise(resolve => setTimeout(resolve, 3000));
  
  // Kill the server
  server.kill();
  
  console.log('\n=== Server Output ===');
  console.log(responseBuffer);
  
  if (errorBuffer) {
    console.log('\n=== Server Errors ===');
    console.log(errorBuffer);
  }
  
  // Try to parse any JSON responses
  const lines = responseBuffer.split('\n').filter(line => line.trim());
  for (const line of lines) {
    try {
      const data = JSON.parse(line);
      if (data.id === 1 && data.result) {
        console.log('\n✅ MCP Response received!');
        console.log(`Node type: ${data.result.nodeType}`);
        console.log(`Source code length: ${data.result.sourceCode ? data.result.sourceCode.length : 0} characters`);
        console.log(`Location: ${data.result.location}`);
        console.log(`Has credentials: ${data.result.credentialCode ? 'Yes' : 'No'}`);
        console.log(`Has package info: ${data.result.packageInfo ? 'Yes' : 'No'}`);
        
        if (data.result.sourceCode) {
          console.log('\nFirst 300 characters of extracted code:');
          console.log('='.repeat(60));
          console.log(data.result.sourceCode.substring(0, 300) + '...');
          console.log('='.repeat(60));
        }
      }
    } catch (e) {
      // Not JSON, skip
    }
  }
}

testMCPExtraction().catch(console.error);
```

--------------------------------------------------------------------------------
/scripts/audit-schema-coverage.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Database Schema Coverage Audit Script
 *
 * Audits the database to determine how many nodes have complete schema information
 * for resourceLocator mode validation. This helps assess the coverage of our
 * schema-driven validation approach.
 */

import Database from 'better-sqlite3';
import path from 'path';

const dbPath = path.join(__dirname, '../data/nodes.db');
const db = new Database(dbPath, { readonly: true });

console.log('=== Schema Coverage Audit ===\n');

// Query 1: How many nodes have resourceLocator properties?
const totalResourceLocator = db.prepare(`
  SELECT COUNT(*) as count FROM nodes
  WHERE properties_schema LIKE '%resourceLocator%'
`).get() as { count: number };

console.log(`Nodes with resourceLocator properties: ${totalResourceLocator.count}`);

// Query 2: Of those, how many have modes defined?
const withModes = db.prepare(`
  SELECT COUNT(*) as count FROM nodes
  WHERE properties_schema LIKE '%resourceLocator%'
    AND properties_schema LIKE '%modes%'
`).get() as { count: number };

console.log(`Nodes with modes defined: ${withModes.count}`);

// Query 3: Which nodes have resourceLocator but NO modes?
const withoutModes = db.prepare(`
  SELECT node_type, display_name
  FROM nodes
  WHERE properties_schema LIKE '%resourceLocator%'
    AND properties_schema NOT LIKE '%modes%'
  LIMIT 10
`).all() as Array<{ node_type: string; display_name: string }>;

console.log(`\nSample nodes WITHOUT modes (showing 10):`);
withoutModes.forEach(node => {
  console.log(`  - ${node.display_name} (${node.node_type})`);
});

// Calculate coverage percentage
const coverage = totalResourceLocator.count > 0
  ? (withModes.count / totalResourceLocator.count) * 100
  : 0;

console.log(`\nSchema coverage: ${coverage.toFixed(1)}% of resourceLocator nodes have modes defined`);

// Query 4: Get some examples of nodes WITH modes for verification
console.log('\nSample nodes WITH modes (showing 5):');
const withModesExamples = db.prepare(`
  SELECT node_type, display_name
  FROM nodes
  WHERE properties_schema LIKE '%resourceLocator%'
    AND properties_schema LIKE '%modes%'
  LIMIT 5
`).all() as Array<{ node_type: string; display_name: string }>;

withModesExamples.forEach(node => {
  console.log(`  - ${node.display_name} (${node.node_type})`);
});

// Summary
console.log('\n=== Summary ===');
console.log(`Total nodes in database: ${db.prepare('SELECT COUNT(*) as count FROM nodes').get() as any as { count: number }.count}`);
console.log(`Nodes with resourceLocator: ${totalResourceLocator.count}`);
console.log(`Nodes with complete mode schemas: ${withModes.count}`);
console.log(`Nodes without mode schemas: ${totalResourceLocator.count - withModes.count}`);
console.log(`\nImplication: Schema-driven validation will apply to ${withModes.count} nodes.`);
console.log(`For the remaining ${totalResourceLocator.count - withModes.count} nodes, validation will be skipped (graceful degradation).`);

db.close();

```

--------------------------------------------------------------------------------
/src/scripts/debug-http-search.ts:
--------------------------------------------------------------------------------

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

import { createDatabaseAdapter } from '../database/database-adapter';
import { NodeRepository } from '../database/node-repository';
import { NodeSimilarityService } from '../services/node-similarity-service';
import path from 'path';

async function debugHttpSearch() {
  const dbPath = path.join(process.cwd(), 'data/nodes.db');
  const db = await createDatabaseAdapter(dbPath);
  const repository = new NodeRepository(db);
  const service = new NodeSimilarityService(repository);

  console.log('Testing "http" search...\n');

  // Check if httpRequest exists
  const httpNode = repository.getNode('nodes-base.httpRequest');
  console.log('HTTP Request node exists:', httpNode ? 'Yes' : 'No');
  if (httpNode) {
    console.log('  Display name:', httpNode.displayName);
  }

  // Test the search with internal details
  const suggestions = await service.findSimilarNodes('http', 5);
  console.log('\nSuggestions for "http":', suggestions.length);
  suggestions.forEach(s => {
    console.log(`  - ${s.nodeType} (${Math.round(s.confidence * 100)}%)`);
  });

  // Manually calculate score for httpRequest
  console.log('\nManual score calculation for httpRequest:');
  const testNode = {
    nodeType: 'nodes-base.httpRequest',
    displayName: 'HTTP Request',
    category: 'Core Nodes'
  };

  const cleanInvalid = 'http';
  const cleanValid = 'nodesbasehttprequest';
  const displayNameClean = 'httprequest';

  // Check substring
  const hasSubstring = cleanValid.includes(cleanInvalid) || displayNameClean.includes(cleanInvalid);
  console.log(`  Substring match: ${hasSubstring}`);

  // This should give us pattern match score
  const patternScore = hasSubstring ? 35 : 0; // Using 35 for short searches
  console.log(`  Pattern score: ${patternScore}`);

  // Name similarity would be low
  console.log(`  Total score would need to be >= 50 to appear`);

  // Get all nodes and check which ones contain 'http'
  const allNodes = repository.getAllNodes();
  const httpNodes = allNodes.filter(n =>
    n.nodeType.toLowerCase().includes('http') ||
    (n.displayName && n.displayName.toLowerCase().includes('http'))
  );

  console.log('\n\nNodes containing "http" in name:');
  httpNodes.slice(0, 5).forEach(n => {
    console.log(`  - ${n.nodeType} (${n.displayName})`);

    // Calculate score for this node
    const normalizedSearch = 'http';
    const normalizedType = n.nodeType.toLowerCase().replace(/[^a-z0-9]/g, '');
    const normalizedDisplay = (n.displayName || '').toLowerCase().replace(/[^a-z0-9]/g, '');

    const containsInType = normalizedType.includes(normalizedSearch);
    const containsInDisplay = normalizedDisplay.includes(normalizedSearch);

    console.log(`    Type check: "${normalizedType}" includes "${normalizedSearch}" = ${containsInType}`);
    console.log(`    Display check: "${normalizedDisplay}" includes "${normalizedSearch}" = ${containsInDisplay}`);
  });
}

debugHttpSearch().catch(console.error);
```

--------------------------------------------------------------------------------
/src/utils/expression-utils.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Utility functions for detecting and handling n8n expressions
 */

/**
 * Detects if a value is an n8n expression
 *
 * n8n expressions can be:
 * - Pure expression: `={{ $json.value }}`
 * - Mixed content: `=https://api.com/{{ $json.id }}/data`
 * - Prefix-only: `=$json.value`
 *
 * @param value - The value to check
 * @returns true if the value is an expression (starts with =)
 */
export function isExpression(value: unknown): value is string {
  return typeof value === 'string' && value.startsWith('=');
}

/**
 * Detects if a string contains n8n expression syntax {{ }}
 *
 * This checks for expression markers within the string,
 * regardless of whether it has the = prefix.
 *
 * @param value - The value to check
 * @returns true if the value contains {{ }} markers
 */
export function containsExpression(value: unknown): boolean {
  if (typeof value !== 'string') {
    return false;
  }
  // Use single regex for better performance than two includes()
  return /\{\{.*\}\}/s.test(value);
}

/**
 * Detects if a value should skip literal validation
 *
 * This is the main utility to use before validating values like URLs, JSON, etc.
 * It returns true if:
 * - The value is an expression (starts with =)
 * - OR the value contains expression markers {{ }}
 *
 * @param value - The value to check
 * @returns true if validation should be skipped
 */
export function shouldSkipLiteralValidation(value: unknown): boolean {
  return isExpression(value) || containsExpression(value);
}

/**
 * Extracts the expression content from a value
 *
 * If value is `={{ $json.value }}`, returns `$json.value`
 * If value is `=$json.value`, returns `$json.value`
 * If value is not an expression, returns the original value
 *
 * @param value - The value to extract from
 * @returns The expression content or original value
 */
export function extractExpressionContent(value: string): string {
  if (!isExpression(value)) {
    return value;
  }

  const withoutPrefix = value.substring(1); // Remove =

  // Check if it's wrapped in {{ }}
  const match = withoutPrefix.match(/^\{\{(.+)\}\}$/s);
  if (match) {
    return match[1].trim();
  }

  return withoutPrefix;
}

/**
 * Checks if a value is a mixed content expression
 *
 * Mixed content has both literal text and expressions:
 * - `Hello {{ $json.name }}!`
 * - `https://api.com/{{ $json.id }}/data`
 *
 * @param value - The value to check
 * @returns true if the value has mixed content
 */
export function hasMixedContent(value: unknown): boolean {
  // Type guard first to avoid calling containsExpression on non-strings
  if (typeof value !== 'string') {
    return false;
  }

  if (!containsExpression(value)) {
    return false;
  }

  // If it's wrapped entirely in {{ }}, it's not mixed
  const trimmed = value.trim();
  if (trimmed.startsWith('={{') && trimmed.endsWith('}}')) {
    // Check if there's only one pair of {{ }}
    const count = (trimmed.match(/\{\{/g) || []).length;
    if (count === 1) {
      return false;
    }
  }

  return true;
}

```

--------------------------------------------------------------------------------
/scripts/test-sqljs-triggers.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node
/**
 * Test script to verify trigger detection works with sql.js adapter
 */

import { createDatabaseAdapter } from '../src/database/database-adapter';
import { NodeRepository } from '../src/database/node-repository';
import { logger } from '../src/utils/logger';
import path from 'path';

async function testSqlJsTriggers() {
  logger.info('🧪 Testing trigger detection with sql.js adapter...\n');
  
  try {
    // Force sql.js by temporarily renaming better-sqlite3
    const originalRequire = require.cache[require.resolve('better-sqlite3')];
    if (originalRequire) {
      delete require.cache[require.resolve('better-sqlite3')];
    }
    
    // Mock better-sqlite3 to force sql.js usage
    const Module = require('module');
    const originalResolveFilename = Module._resolveFilename;
    Module._resolveFilename = function(request: string, parent: any, isMain: boolean) {
      if (request === 'better-sqlite3') {
        throw new Error('Forcing sql.js adapter for testing');
      }
      return originalResolveFilename.apply(this, arguments);
    };
    
    // Now create adapter - should use sql.js
    const dbPath = path.join(process.cwd(), 'data', 'nodes.db');
    logger.info(`📁 Database path: ${dbPath}`);
    
    const adapter = await createDatabaseAdapter(dbPath);
    logger.info('✅ Adapter created (should be sql.js)\n');
    
    // Test direct query
    logger.info('📊 Testing direct database query:');
    const triggerNodes = ['nodes-base.webhook', 'nodes-base.cron', 'nodes-base.interval', 'nodes-base.emailReadImap'];
    
    for (const nodeType of triggerNodes) {
      const row = adapter.prepare('SELECT * FROM nodes WHERE node_type = ?').get(nodeType);
      if (row) {
        logger.info(`${nodeType}:`);
        logger.info(`  is_trigger raw value: ${row.is_trigger} (type: ${typeof row.is_trigger})`);
        logger.info(`  !!is_trigger: ${!!row.is_trigger}`);
        logger.info(`  Number(is_trigger) === 1: ${Number(row.is_trigger) === 1}`);
      }
    }
    
    // Test through repository
    logger.info('\n📦 Testing through NodeRepository:');
    const repository = new NodeRepository(adapter);
    
    for (const nodeType of triggerNodes) {
      const node = repository.getNode(nodeType);
      if (node) {
        logger.info(`${nodeType}: isTrigger = ${node.isTrigger}`);
      }
    }
    
    // Test list query
    logger.info('\n📋 Testing list query:');
    const allTriggers = adapter.prepare(
      'SELECT node_type, is_trigger FROM nodes WHERE node_type IN (?, ?, ?, ?)'
    ).all(...triggerNodes);
    
    for (const node of allTriggers) {
      logger.info(`${node.node_type}: is_trigger = ${node.is_trigger} (type: ${typeof node.is_trigger})`);
    }
    
    adapter.close();
    logger.info('\n✅ Test complete!');
    
    // Restore original require
    Module._resolveFilename = originalResolveFilename;
    
  } catch (error) {
    logger.error('Test failed:', error);
    process.exit(1);
  }
}

// Run test
testSqlJsTriggers().catch(console.error);
```

--------------------------------------------------------------------------------
/scripts/extract-nodes-simple.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash
set -e

echo "🐳 Simple n8n Node Extraction via Docker"
echo "======================================="

# Colors for output
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color

# Function to print colored output
print_status() {
    echo -e "${GREEN}[$(date +'%H:%M:%S')]${NC} $1"
}

print_warning() {
    echo -e "${YELLOW}[$(date +'%H:%M:%S')]${NC} ⚠️  $1"
}

print_error() {
    echo -e "${RED}[$(date +'%H:%M:%S')]${NC} ❌ $1"
}

# Check if Docker is running
if ! docker info > /dev/null 2>&1; then
    print_error "Docker is not running. Please start Docker and try again."
    exit 1
fi

print_status "Docker is running ✅"

# Build the project first
print_status "Building the project..."
npm run build

# Create a temporary directory for extraction
TEMP_DIR=$(mktemp -d)
print_status "Created temporary directory: $TEMP_DIR"

# Run Docker container to copy node files
print_status "Running n8n container to extract nodes..."
docker run --rm -d --name n8n-temp n8nio/n8n:latest sleep 300

# Wait a bit for container to start
sleep 5

# Copy n8n modules from container
print_status "Copying n8n modules from container..."
docker cp n8n-temp:/usr/local/lib/node_modules/n8n/node_modules "$TEMP_DIR/node_modules" || {
    print_error "Failed to copy node_modules"
    docker stop n8n-temp
    rm -rf "$TEMP_DIR"
    exit 1
}

# Stop the container
docker stop n8n-temp

# Run our extraction script locally
print_status "Running extraction script..."
NODE_ENV=development \
NODE_DB_PATH=./data/nodes-fresh.db \
N8N_MODULES_PATH="$TEMP_DIR/node_modules" \
node scripts/extract-from-docker.js

# Clean up
print_status "Cleaning up temporary files..."
rm -rf "$TEMP_DIR"

# Check the results
print_status "Checking extraction results..."
if [ -f "./data/nodes-fresh.db" ]; then
    NODE_COUNT=$(sqlite3 ./data/nodes-fresh.db "SELECT COUNT(*) FROM nodes;" 2>/dev/null || echo "0")
    print_status "Extracted $NODE_COUNT nodes"
    
    # Check if we got the If node source code and look for version
    IF_SOURCE=$(sqlite3 ./data/nodes-fresh.db "SELECT source_code FROM nodes WHERE node_type='n8n-nodes-base.If' LIMIT 1;" 2>/dev/null || echo "")
    if [[ $IF_SOURCE =~ version:[[:space:]]*([0-9]+) ]]; then
        IF_CODE_VERSION="${BASH_REMATCH[1]}"
        print_status "If node version from source code: v$IF_CODE_VERSION"
        
        if [ "$IF_CODE_VERSION" -ge "2" ]; then
            print_status "✅ Successfully extracted latest If node (v$IF_CODE_VERSION)!"
        else
            print_warning "If node is still v$IF_CODE_VERSION, expected v2 or higher"
        fi
    fi
else
    print_error "Database file not found after extraction"
fi

print_status "✨ Extraction complete!"

# Offer to restart the MCP server
echo ""
read -p "Would you like to restart the MCP server with the new nodes? (y/n) " -n 1 -r
echo ""
if [[ $REPLY =~ ^[Yy]$ ]]; then
    print_status "Restarting MCP server..."
    # Kill any existing server process
    pkill -f "node.*dist/index.js" || true
    
    # Start the server
    npm start &
    print_status "MCP server restarted with fresh node database"
fi
```
Page 2/51FirstPrevNextLast