mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-03-26 20:23:08 +00:00
Compare commits
11 Commits
v2.40.2
...
update/n8n
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e49013d72 | ||
|
|
af2cfec668 | ||
|
|
ab2dc9824e | ||
|
|
657d5ab60c | ||
|
|
7e314f8ff2 | ||
|
|
3cc09b4f07 | ||
|
|
537e701610 | ||
|
|
a571ad5ef5 | ||
|
|
1f0738e637 | ||
|
|
93816fce30 | ||
|
|
ec19c9dade |
6
.github/workflows/dependency-check.yml
vendored
6
.github/workflows/dependency-check.yml
vendored
@@ -77,15 +77,15 @@ jobs:
|
||||
echo "Zod version: $ZOD_VERSION"
|
||||
echo ""
|
||||
|
||||
# Check MCP SDK version - must be exactly 1.27.1
|
||||
# Check MCP SDK version - must be exactly 1.28.0
|
||||
if [[ "$SDK_VERSION" == "not found" ]]; then
|
||||
echo "❌ FAILED: Could not determine MCP SDK version!"
|
||||
echo " The dependency may not have been installed correctly."
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$SDK_VERSION" != "1.27.1" ]]; then
|
||||
if [[ "$SDK_VERSION" != "1.28.0" ]]; then
|
||||
echo "❌ FAILED: MCP SDK version mismatch!"
|
||||
echo " Expected: 1.27.1"
|
||||
echo " Expected: 1.28.0"
|
||||
echo " Got: $SDK_VERSION"
|
||||
echo ""
|
||||
echo "This can cause runtime errors. See issues #440, #444, #446, #447, #450"
|
||||
|
||||
40
CHANGELOG.md
40
CHANGELOG.md
@@ -7,6 +7,46 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2.41.0] - 2026-03-25
|
||||
|
||||
### Changed
|
||||
|
||||
- **Updated n8n dependencies**: n8n 2.12.3 → 2.13.3, n8n-core 2.12.0 → 2.13.1, n8n-workflow 2.12.0 → 2.13.1, @n8n/n8n-nodes-langchain 2.12.0 → 2.13.1
|
||||
- **Rebuilt node database**: 1,396 nodes (812 from n8n-nodes-base/langchain + 584 community: 516 verified + 68 npm)
|
||||
- **Refreshed community nodes**: 584 total (up from 430), with 581 AI-generated documentation summaries
|
||||
- **Improved documentation generator**: Strip `<think>` tags from thinking-model responses; use raw fetch for vLLM `chat_template_kwargs` support
|
||||
- **Incremental community node updates**: `fetch:community` now upserts by default, preserving existing READMEs and AI summaries. Use `--rebuild` for clean slate
|
||||
|
||||
Conceived by Romuald Czlonkowski - https://www.aiadvisors.pl/en
|
||||
|
||||
## [2.40.5] - 2026-03-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Webhook workflows created via MCP get 404 errors** (Issue #643): Auto-inject `webhookId` (UUID) on webhook-type nodes (`webhook`, `webhookTrigger`, `formTrigger`, `chatTrigger`) during `cleanWorkflowForCreate()` and `cleanWorkflowForUpdate()`. n8n 2.10+ requires this field for proper webhook URL registration; without it, webhooks silently fail with 404. Existing `webhookId` values are preserved.
|
||||
|
||||
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
|
||||
|
||||
## [2.40.4] - 2026-03-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Incorrect data tables availability info**: Removed "enterprise/cloud only" restriction from tool description and documentation — data tables are available on all n8n plans including self-hosted
|
||||
- **Redundant pitfalls removed**: Removed "Requires N8N_API_URL and N8N_API_KEY" and "enterprise or cloud plans" pitfalls — the first is implicit for all n8n management tools, the second was incorrect
|
||||
|
||||
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
|
||||
|
||||
## [2.40.3] - 2026-03-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Notification 400 disconnect storms (#654)**: `handleRequest()` now returns 202 Accepted for JSON-RPC notifications with stale/expired session IDs instead of 400. Per JSON-RPC 2.0 spec, notifications don't expect responses — returning 400 caused Claude's proxy to trigger reconnection storms (930 errors/day, 216 users affected)
|
||||
- **TOCTOU race in session lookup**: Added null guard after transport assignment to handle sessions removed between the existence check and use
|
||||
- **`updateTable` silently ignoring `columns` parameter**: Now returns a warning message when `columns` is passed to `updateTable`, clarifying that table schema is immutable after creation via the public API
|
||||
- **Tool schema descriptions clarified**: `name` and `columns` parameter descriptions now explicitly document that `updateTable` is rename-only and columns are for `createTable` only
|
||||
|
||||
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
|
||||
|
||||
## [2.40.2] - 2026-03-22
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
# n8n Update Process - Quick Reference
|
||||
|
||||
## ⚡ Recommended Fast Workflow (2025-11-04)
|
||||
## ⚡ Recommended Fast Workflow (2026-03-25)
|
||||
|
||||
**CRITICAL FIRST STEP**: Check existing releases to avoid version conflicts!
|
||||
|
||||
**IMPORTANT: Community nodes are preserved incrementally!**
|
||||
- `npm run update:n8n` rebuilds the base node DB (wipes community nodes temporarily)
|
||||
- Community nodes must be backed up BEFORE and restored AFTER the base rebuild
|
||||
- `npm run fetch:community` now upserts by default (preserves READMEs + AI summaries)
|
||||
- `npm run generate:docs:incremental` only processes nodes missing docs
|
||||
- Use `generate:docs:readme-only` first, then `generate:docs:summary-only` with a local LLM
|
||||
|
||||
```bash
|
||||
# 1. CHECK EXISTING RELEASES FIRST (prevents version conflicts!)
|
||||
gh release list | head -5
|
||||
@@ -15,14 +22,29 @@ git checkout main && git pull
|
||||
# 3. Check for updates (dry run)
|
||||
npm run update:n8n:check
|
||||
|
||||
# 4. Run update and skip tests (we'll test in CI)
|
||||
# 4. Back up community nodes BEFORE update (update:n8n rebuilds base DB!)
|
||||
sqlite3 data/nodes.db ".mode insert nodes" "SELECT * FROM nodes WHERE is_community = 1;" > /tmp/n8n_community_backup.sql
|
||||
|
||||
# 5. Run update and skip tests (we'll test in CI)
|
||||
yes y | npm run update:n8n
|
||||
|
||||
# 5. Refresh community nodes (standard practice!)
|
||||
npm run fetch:community
|
||||
npm run generate:docs
|
||||
# 6. Restore community nodes after rebuild
|
||||
sqlite3 data/nodes.db < /tmp/n8n_community_backup.sql
|
||||
|
||||
# 6. Create feature branch
|
||||
# 7. Refresh community nodes (upserts - preserves existing READMEs + AI summaries!)
|
||||
npm run fetch:community
|
||||
# NOTE: Default mode is now "upsert" - no deletion. Use --rebuild for clean slate.
|
||||
|
||||
# 8. Generate docs incrementally (only for new/missing nodes)
|
||||
npm run generate:docs:readme-only # Fetch READMEs from npm (no LLM needed)
|
||||
# Then with a local LLM server running (LM Studio, vLLM, Ollama):
|
||||
N8N_MCP_LLM_BASE_URL="http://YOUR_SERVER:PORT/v1" \
|
||||
N8N_MCP_LLM_MODEL="your-model-name" \
|
||||
node dist/scripts/generate-community-docs.js --summary-only --skip-existing-summary --llm-concurrency=11
|
||||
# For vLLM with thinking models, the code auto-sends chat_template_kwargs: {enable_thinking: false}
|
||||
# Context length needed: 8K minimum (README truncated to 6000 chars, output max 2000 tokens)
|
||||
|
||||
# 9. Create feature branch
|
||||
git checkout -b update/n8n-X.X.X
|
||||
|
||||
# 7. Update version in package.json (must be HIGHER than latest release!)
|
||||
|
||||
@@ -5,17 +5,17 @@
|
||||
[](https://www.npmjs.com/package/n8n-mcp)
|
||||
[](https://codecov.io/gh/czlonkowski/n8n-mcp)
|
||||
[](https://github.com/czlonkowski/n8n-mcp/actions)
|
||||
[](https://github.com/n8n-io/n8n)
|
||||
[](https://github.com/n8n-io/n8n)
|
||||
[](https://github.com/czlonkowski/n8n-mcp/pkgs/container/n8n-mcp)
|
||||
[](https://railway.com/deploy/n8n-mcp?referralCode=n8n-mcp)
|
||||
|
||||
A Model Context Protocol (MCP) server that provides AI assistants with comprehensive access to n8n node documentation, properties, and operations. Deploy in minutes to give Claude and other AI assistants deep knowledge about n8n's 1,239 workflow automation nodes (809 core + 430 community).
|
||||
A Model Context Protocol (MCP) server that provides AI assistants with comprehensive access to n8n node documentation, properties, and operations. Deploy in minutes to give Claude and other AI assistants deep knowledge about n8n's 1,396 workflow automation nodes (812 core + 584 community).
|
||||
|
||||
## Overview
|
||||
|
||||
n8n-MCP serves as a bridge between n8n's workflow automation platform and AI models, enabling them to understand and work with n8n nodes effectively. It provides structured access to:
|
||||
|
||||
- 📚 **1,084 n8n nodes** - 537 core nodes + 547 community nodes (301 verified)
|
||||
- 📚 **1,396 n8n nodes** - 812 core nodes + 584 community nodes (516 verified)
|
||||
- 🔧 **Node properties** - 99% coverage with detailed schemas
|
||||
- ⚡ **Node operations** - 63.6% coverage of available actions
|
||||
- 📄 **Documentation** - 87% coverage from official n8n docs (including AI nodes)
|
||||
|
||||
BIN
data/nodes.db
BIN
data/nodes.db
Binary file not shown.
2
dist/database/node-repository.d.ts.map
vendored
2
dist/database/node-repository.d.ts.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"node-repository.d.ts","sourceRoot":"","sources":["../../src/database/node-repository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAM1E,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,EAAE,CAAkB;gBAEhB,WAAW,EAAE,eAAe,GAAG,oBAAoB;IAa/D,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,mBAAmB,CAAC,GAAG,IAAI;IAmD/D,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG;IAuC9B,UAAU,IAAI,GAAG,EAAE;IAgBnB,OAAO,CAAC,aAAa;IASrB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;IAIlC,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG;IAIpC,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,EAAE;IAqB3C,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,IAAI,GAAG,KAAK,GAAG,OAAc,EAAE,KAAK,GAAE,MAAW,GAAG,GAAG,EAAE;IAwC1F,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,GAAG,EAAE;IAUlC,YAAY,IAAI,MAAM;IAKtB,cAAc,IAAI,GAAG,EAAE;IAOvB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAYhD,yBAAyB,CAAC,YAAY,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAY3D,eAAe,IAAI,GAAG,EAAE;IAoBxB,mBAAmB,IAAI,MAAM;IAK7B,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,GAAG,EAAE;IAS7C,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,GAAE,MAAW,GAAG,GAAG,EAAE;IAmCrF,OAAO,CAAC,YAAY;IA2CpB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,GAAG,EAAE;IAmD7D,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,EAAE;IAmBzC,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,GAAG,EAAE;IAyBnE,gBAAgB,IAAI,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC;IAiBtC,eAAe,IAAI,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC;IAiBrC,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAwB9D,8BAA8B,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAsDvF,iBAAiB,CAAC,OAAO,CAAC,EAAE;QAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAAC;KAC5C,GAAG,GAAG,EAAE;IAkCT,iBAAiB,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;IAmB5E,mBAAmB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO;IAUpD,mBAAmB,CAAC,cAAc,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAYvD,oBAAoB,IAAI,MAAM;IAc9B,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAUxD,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAY5D,8BAA8B,IAAI,GAAG,EAAE;IAYvC,iCAAiC,IAAI,GAAG,EAAE;IAc1C,qBAAqB,IAAI;QACvB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,aAAa,EAAE,MAAM,CAAC;QACtB,aAAa,EAAE,MAAM,CAAC;QACtB,gBAAgB,EAAE,MAAM,CAAC;KAC1B;IA8BD,eAAe,CAAC,WAAW,EAAE;QAC3B,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,gBAAgB,CAAC,EAAE,GAAG,CAAC;QACvB,UAAU,CAAC,EAAE,GAAG,CAAC;QACjB,mBAAmB,CAAC,EAAE,GAAG,CAAC;QAC1B,OAAO,CAAC,EAAE,GAAG,CAAC;QACd,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,eAAe,CAAC,EAAE,GAAG,EAAE,CAAC;QACxB,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;QAChC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;QAC3B,UAAU,CAAC,EAAE,IAAI,CAAC;KACnB,GAAG,IAAI;IAkCR,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,EAAE;IAexC,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAgBlD,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAe7D,kBAAkB,CAAC,UAAU,EAAE;QAC7B,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,cAAc,GAAG,qBAAqB,GAAG,iBAAiB,CAAC;QACzG,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,iBAAiB,CAAC,EAAE,GAAG,CAAC;QACxB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;KACtC,GAAG,IAAI;IA4BR,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,GAAG,EAAE;IAgBnF,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,GAAG,EAAE;IA4BpF,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,GAAG,EAAE;IAkBzF,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;IAcxF,sBAAsB,IAAI,MAAM;IAWhC,OAAO,CAAC,mBAAmB;IA0B3B,OAAO,CAAC,sBAAsB;IA0B9B,qBAAqB,CAAC,IAAI,EAAE;QAC1B,UAAU,EAAE,MAAM,CAAC;QACnB,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;QACrB,gBAAgB,EAAE,GAAG,CAAC;QACtB,OAAO,EAAE,gBAAgB,GAAG,aAAa,GAAG,SAAS,CAAC;QACtD,UAAU,CAAC,EAAE,GAAG,EAAE,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,QAAQ,CAAC,EAAE,GAAG,CAAC;KAChB,GAAG,MAAM;IAyBV,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,GAAG,EAAE;IAoB9D,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAYjD,wBAAwB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAexD,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAS9C,kCAAkC,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAY9D,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;IAiCpE,wBAAwB,IAAI,MAAM;IAWlC,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAWnD,sBAAsB,IAAI,GAAG;IAwC7B,OAAO,CAAC,uBAAuB;CAchC"}
|
||||
{"version":3,"file":"node-repository.d.ts","sourceRoot":"","sources":["../../src/database/node-repository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAM1E,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,EAAE,CAAkB;gBAEhB,WAAW,EAAE,eAAe,GAAG,oBAAoB;IAa/D,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,mBAAmB,CAAC,GAAG,IAAI;IAgF/D,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG;IAuC9B,UAAU,IAAI,GAAG,EAAE;IAgBnB,OAAO,CAAC,aAAa;IASrB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;IAIlC,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG;IAIpC,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,EAAE;IAqB3C,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,IAAI,GAAG,KAAK,GAAG,OAAc,EAAE,KAAK,GAAE,MAAW,GAAG,GAAG,EAAE;IAwC1F,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,GAAG,EAAE;IAUlC,YAAY,IAAI,MAAM;IAKtB,cAAc,IAAI,GAAG,EAAE;IAOvB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAYhD,yBAAyB,CAAC,YAAY,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAY3D,eAAe,IAAI,GAAG,EAAE;IAoBxB,mBAAmB,IAAI,MAAM;IAK7B,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,GAAG,EAAE;IAS7C,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,GAAE,MAAW,GAAG,GAAG,EAAE;IAmCrF,OAAO,CAAC,YAAY;IA2CpB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,GAAG,EAAE;IAmD7D,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,EAAE;IAmBzC,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,GAAG,EAAE;IAyBnE,gBAAgB,IAAI,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC;IAiBtC,eAAe,IAAI,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC;IAiBrC,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAwB9D,8BAA8B,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAsDvF,iBAAiB,CAAC,OAAO,CAAC,EAAE;QAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAAC;KAC5C,GAAG,GAAG,EAAE;IAkCT,iBAAiB,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;IAmB5E,mBAAmB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO;IAUpD,mBAAmB,CAAC,cAAc,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAYvD,oBAAoB,IAAI,MAAM;IAc9B,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAUxD,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAY5D,8BAA8B,IAAI,GAAG,EAAE;IAYvC,iCAAiC,IAAI,GAAG,EAAE;IAc1C,qBAAqB,IAAI;QACvB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,aAAa,EAAE,MAAM,CAAC;QACtB,aAAa,EAAE,MAAM,CAAC;QACtB,gBAAgB,EAAE,MAAM,CAAC;KAC1B;IA8BD,eAAe,CAAC,WAAW,EAAE;QAC3B,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,gBAAgB,CAAC,EAAE,GAAG,CAAC;QACvB,UAAU,CAAC,EAAE,GAAG,CAAC;QACjB,mBAAmB,CAAC,EAAE,GAAG,CAAC;QAC1B,OAAO,CAAC,EAAE,GAAG,CAAC;QACd,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,eAAe,CAAC,EAAE,GAAG,EAAE,CAAC;QACxB,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;QAChC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;QAC3B,UAAU,CAAC,EAAE,IAAI,CAAC;KACnB,GAAG,IAAI;IAkCR,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,EAAE;IAexC,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAgBlD,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAe7D,kBAAkB,CAAC,UAAU,EAAE;QAC7B,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,cAAc,GAAG,qBAAqB,GAAG,iBAAiB,CAAC;QACzG,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,iBAAiB,CAAC,EAAE,GAAG,CAAC;QACxB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;KACtC,GAAG,IAAI;IA4BR,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,GAAG,EAAE;IAgBnF,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,GAAG,EAAE;IA4BpF,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,GAAG,EAAE;IAkBzF,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;IAcxF,sBAAsB,IAAI,MAAM;IAWhC,OAAO,CAAC,mBAAmB;IA0B3B,OAAO,CAAC,sBAAsB;IA0B9B,qBAAqB,CAAC,IAAI,EAAE;QAC1B,UAAU,EAAE,MAAM,CAAC;QACnB,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;QACrB,gBAAgB,EAAE,GAAG,CAAC;QACtB,OAAO,EAAE,gBAAgB,GAAG,aAAa,GAAG,SAAS,CAAC;QACtD,UAAU,CAAC,EAAE,GAAG,EAAE,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,QAAQ,CAAC,EAAE,GAAG,CAAC;KAChB,GAAG,MAAM;IAyBV,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,GAAG,EAAE;IAoB9D,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAYjD,wBAAwB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAexD,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAS9C,kCAAkC,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAY9D,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;IAiCpE,wBAAwB,IAAI,MAAM;IAWlC,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAWnD,sBAAsB,IAAI,GAAG;IAwC7B,OAAO,CAAC,uBAAuB;CAchC"}
|
||||
31
dist/database/node-repository.js
vendored
31
dist/database/node-repository.js
vendored
@@ -13,7 +13,7 @@ class NodeRepository {
|
||||
}
|
||||
saveNode(node) {
|
||||
const stmt = this.db.prepare(`
|
||||
INSERT OR REPLACE INTO nodes (
|
||||
INSERT INTO nodes (
|
||||
node_type, package_name, display_name, description,
|
||||
category, development_style, is_ai_tool, is_trigger,
|
||||
is_webhook, is_versioned, is_tool_variant, tool_variant_of,
|
||||
@@ -23,6 +23,35 @@ class NodeRepository {
|
||||
is_community, is_verified, author_name, author_github_url,
|
||||
npm_package_name, npm_version, npm_downloads, community_fetched_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(node_type) DO UPDATE SET
|
||||
package_name = excluded.package_name,
|
||||
display_name = excluded.display_name,
|
||||
description = excluded.description,
|
||||
category = excluded.category,
|
||||
development_style = excluded.development_style,
|
||||
is_ai_tool = excluded.is_ai_tool,
|
||||
is_trigger = excluded.is_trigger,
|
||||
is_webhook = excluded.is_webhook,
|
||||
is_versioned = excluded.is_versioned,
|
||||
is_tool_variant = excluded.is_tool_variant,
|
||||
tool_variant_of = excluded.tool_variant_of,
|
||||
has_tool_variant = excluded.has_tool_variant,
|
||||
version = excluded.version,
|
||||
documentation = excluded.documentation,
|
||||
properties_schema = excluded.properties_schema,
|
||||
operations = excluded.operations,
|
||||
credentials_required = excluded.credentials_required,
|
||||
outputs = excluded.outputs,
|
||||
output_names = excluded.output_names,
|
||||
is_community = excluded.is_community,
|
||||
is_verified = excluded.is_verified,
|
||||
author_name = excluded.author_name,
|
||||
author_github_url = excluded.author_github_url,
|
||||
npm_package_name = excluded.npm_package_name,
|
||||
npm_version = excluded.npm_version,
|
||||
npm_downloads = excluded.npm_downloads,
|
||||
community_fetched_at = excluded.community_fetched_at,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
`);
|
||||
stmt.run(node.nodeType, node.packageName, node.displayName, node.description, node.category, node.style, node.isAITool ? 1 : 0, node.isTrigger ? 1 : 0, node.isWebhook ? 1 : 0, node.isVersioned ? 1 : 0, node.isToolVariant ? 1 : 0, node.toolVariantOf || null, node.hasToolVariant ? 1 : 0, node.version, node.documentation || null, JSON.stringify(node.properties, null, 2), JSON.stringify(node.operations, null, 2), JSON.stringify(node.credentials, null, 2), node.outputs ? JSON.stringify(node.outputs, null, 2) : null, node.outputNames ? JSON.stringify(node.outputNames, null, 2) : null, node.isCommunity ? 1 : 0, node.isVerified ? 1 : 0, node.authorName || null, node.authorGithubUrl || null, node.npmPackageName || null, node.npmVersion || null, node.npmDownloads || 0, node.communityFetchedAt || null);
|
||||
}
|
||||
|
||||
2
dist/database/node-repository.js.map
vendored
2
dist/database/node-repository.js.map
vendored
File diff suppressed because one or more lines are too long
1
dist/http-server-single-session.d.ts
vendored
1
dist/http-server-single-session.d.ts
vendored
@@ -21,6 +21,7 @@ export declare class SingleSessionHTTPServer {
|
||||
private getActiveSessionCount;
|
||||
private canCreateSession;
|
||||
private isValidSessionId;
|
||||
private isJsonRpcNotification;
|
||||
private sanitizeErrorForClient;
|
||||
private updateSessionAccess;
|
||||
private switchSessionContext;
|
||||
|
||||
2
dist/http-server-single-session.d.ts.map
vendored
2
dist/http-server-single-session.d.ts.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"http-server-single-session.d.ts","sourceRoot":"","sources":["../src/http-server-single-session.ts"],"names":[],"mappings":";AAMA,OAAO,OAAO,MAAM,SAAS,CAAC;AAoB9B,OAAO,EAAE,eAAe,EAA2B,MAAM,0BAA0B,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAwErD,qBAAa,uBAAuB;IAElC,OAAO,CAAC,UAAU,CAA8D;IAChF,OAAO,CAAC,OAAO,CAA0D;IACzE,OAAO,CAAC,eAAe,CAAsE;IAC7F,OAAO,CAAC,eAAe,CAA4D;IACnF,OAAO,CAAC,kBAAkB,CAAyC;IACnE,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,cAAc,CAAwB;IAC9C,OAAO,CAAC,aAAa,CAAM;IAI3B,OAAO,CAAC,cAAc,CAER;IACd,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,YAAY,CAA+B;;IAcnD,OAAO,CAAC,mBAAmB;IAmB3B,OAAO,CAAC,sBAAsB;YAqChB,aAAa;IAuC3B,OAAO,CAAC,qBAAqB;IAO7B,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,sBAAsB;IAkC9B,OAAO,CAAC,mBAAmB;YASb,oBAAoB;YAwBpB,oBAAoB;IAwBlC,OAAO,CAAC,iBAAiB;IAsBzB,OAAO,CAAC,aAAa;IA2BrB,OAAO,CAAC,mBAAmB;IAoDrB,aAAa,CACjB,GAAG,EAAE,OAAO,CAAC,OAAO,EACpB,GAAG,EAAE,OAAO,CAAC,QAAQ,EACrB,eAAe,CAAC,EAAE,eAAe,GAChC,OAAO,CAAC,IAAI,CAAC;YA0PF,eAAe;IA4D7B,OAAO,CAAC,SAAS;IAYjB,OAAO,CAAC,gBAAgB;IASlB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgnBtB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IA2D/B,cAAc,IAAI;QAChB,MAAM,EAAE,OAAO,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE;YACT,KAAK,EAAE,MAAM,CAAC;YACd,MAAM,EAAE,MAAM,CAAC;YACf,OAAO,EAAE,MAAM,CAAC;YAChB,GAAG,EAAE,MAAM,CAAC;YACZ,UAAU,EAAE,MAAM,EAAE,CAAC;SACtB,CAAC;KACH;IAmDM,kBAAkB,IAAI,YAAY,EAAE;IAoEpC,mBAAmB,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,MAAM;CAsG7D"}
|
||||
{"version":3,"file":"http-server-single-session.d.ts","sourceRoot":"","sources":["../src/http-server-single-session.ts"],"names":[],"mappings":";AAMA,OAAO,OAAO,MAAM,SAAS,CAAC;AAoB9B,OAAO,EAAE,eAAe,EAA2B,MAAM,0BAA0B,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAwErD,qBAAa,uBAAuB;IAElC,OAAO,CAAC,UAAU,CAA8D;IAChF,OAAO,CAAC,OAAO,CAA0D;IACzE,OAAO,CAAC,eAAe,CAAsE;IAC7F,OAAO,CAAC,eAAe,CAA4D;IACnF,OAAO,CAAC,kBAAkB,CAAyC;IACnE,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,cAAc,CAAwB;IAC9C,OAAO,CAAC,aAAa,CAAM;IAI3B,OAAO,CAAC,cAAc,CAER;IACd,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,YAAY,CAA+B;;IAcnD,OAAO,CAAC,mBAAmB;IAmB3B,OAAO,CAAC,sBAAsB;YAqChB,aAAa;IAuC3B,OAAO,CAAC,qBAAqB;IAO7B,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,gBAAgB;IAYxB,OAAO,CAAC,qBAAqB;IAa7B,OAAO,CAAC,sBAAsB;IAkC9B,OAAO,CAAC,mBAAmB;YASb,oBAAoB;YAwBpB,oBAAoB;IAwBlC,OAAO,CAAC,iBAAiB;IAsBzB,OAAO,CAAC,aAAa;IA2BrB,OAAO,CAAC,mBAAmB;IAoDrB,aAAa,CACjB,GAAG,EAAE,OAAO,CAAC,OAAO,EACpB,GAAG,EAAE,OAAO,CAAC,QAAQ,EACrB,eAAe,CAAC,EAAE,eAAe,GAChC,OAAO,CAAC,IAAI,CAAC;YAoRF,eAAe;IA4D7B,OAAO,CAAC,SAAS;IAYjB,OAAO,CAAC,gBAAgB;IASlB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgnBtB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IA2D/B,cAAc,IAAI;QAChB,MAAM,EAAE,OAAO,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE;YACT,KAAK,EAAE,MAAM,CAAC;YACd,MAAM,EAAE,MAAM,CAAC;YACf,OAAO,EAAE,MAAM,CAAC;YAChB,GAAG,EAAE,MAAM,CAAC;YACZ,UAAU,EAAE,MAAM,EAAE,CAAC;SACtB,CAAC;KACH;IAmDM,kBAAkB,IAAI,YAAY,EAAE;IAoEpC,mBAAmB,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,MAAM;CAsG7D"}
|
||||
31
dist/http-server-single-session.js
vendored
31
dist/http-server-single-session.js
vendored
@@ -133,6 +133,15 @@ class SingleSessionHTTPServer {
|
||||
isValidSessionId(sessionId) {
|
||||
return Boolean(sessionId && sessionId.length > 0);
|
||||
}
|
||||
isJsonRpcNotification(body) {
|
||||
if (!body || typeof body !== 'object')
|
||||
return false;
|
||||
const isSingleNotification = (msg) => msg && typeof msg.method === 'string' && !('id' in msg);
|
||||
if (Array.isArray(body)) {
|
||||
return body.length > 0 && body.every(isSingleNotification);
|
||||
}
|
||||
return isSingleNotification(body);
|
||||
}
|
||||
sanitizeErrorForClient(error) {
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
if (error instanceof Error) {
|
||||
@@ -381,6 +390,20 @@ class SingleSessionHTTPServer {
|
||||
}
|
||||
logger_1.logger.info('handleRequest: Reusing existing transport for session', { sessionId });
|
||||
transport = this.transports[sessionId];
|
||||
if (!transport) {
|
||||
if (this.isJsonRpcNotification(req.body)) {
|
||||
logger_1.logger.info('handleRequest: Session removed during lookup, accepting notification', { sessionId });
|
||||
res.status(202).end();
|
||||
return;
|
||||
}
|
||||
logger_1.logger.warn('handleRequest: Session removed between check and use (TOCTOU)', { sessionId });
|
||||
res.status(400).json({
|
||||
jsonrpc: '2.0',
|
||||
error: { code: -32000, message: 'Bad Request: Session not found or expired' },
|
||||
id: req.body?.id || null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const isMultiTenantEnabled = process.env.ENABLE_MULTI_TENANT === 'true';
|
||||
const sessionStrategy = process.env.MULTI_TENANT_SESSION_STRATEGY || 'instance';
|
||||
if (isMultiTenantEnabled && sessionStrategy === 'shared' && instanceContext) {
|
||||
@@ -389,6 +412,14 @@ class SingleSessionHTTPServer {
|
||||
this.updateSessionAccess(sessionId);
|
||||
}
|
||||
else {
|
||||
if (this.isJsonRpcNotification(req.body)) {
|
||||
logger_1.logger.info('handleRequest: Accepting notification for stale/missing session', {
|
||||
method: req.body?.method,
|
||||
sessionId: sessionId || 'none',
|
||||
});
|
||||
res.status(202).end();
|
||||
return;
|
||||
}
|
||||
const errorDetails = {
|
||||
hasSessionId: !!sessionId,
|
||||
isInitialize: isInitialize,
|
||||
|
||||
2
dist/http-server-single-session.js.map
vendored
2
dist/http-server-single-session.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/mcp/handlers-n8n-manager.d.ts.map
vendored
2
dist/mcp/handlers-n8n-manager.d.ts.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"handlers-n8n-manager.d.ts","sourceRoot":"","sources":["../../src/mcp/handlers-n8n-manager.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"handlers-n8n-manager.d.ts","sourceRoot":"","sources":["../../src/mcp/handlers-n8n-manager.ts"],"names":[],"mappings":"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"}
|
||||
5
dist/mcp/handlers-n8n-manager.js
vendored
5
dist/mcp/handlers-n8n-manager.js
vendored
@@ -2176,10 +2176,13 @@ async function handleUpdateTable(args, context) {
|
||||
const client = ensureApiConfigured(context);
|
||||
const { tableId, name } = updateTableSchema.parse(args);
|
||||
const dataTable = await client.updateDataTable(tableId, { name });
|
||||
const rawArgs = args;
|
||||
const hasColumns = rawArgs && typeof rawArgs === 'object' && 'columns' in rawArgs;
|
||||
return {
|
||||
success: true,
|
||||
data: dataTable,
|
||||
message: `Data table renamed to "${dataTable.name}"`,
|
||||
message: `Data table renamed to "${dataTable.name}"` +
|
||||
(hasColumns ? '. Note: columns parameter was ignored — table schema is immutable after creation via the public API' : ''),
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
|
||||
2
dist/mcp/handlers-n8n-manager.js.map
vendored
2
dist/mcp/handlers-n8n-manager.js.map
vendored
File diff suppressed because one or more lines are too long
6
dist/mcp/tools-n8n-manager.js
vendored
6
dist/mcp/tools-n8n-manager.js
vendored
@@ -590,7 +590,7 @@ exports.n8nManagementTools = [
|
||||
},
|
||||
{
|
||||
name: 'n8n_manage_datatable',
|
||||
description: `Manage n8n data tables and rows. Actions: createTable, listTables, getTable, updateTable, deleteTable, getRows, insertRows, updateRows, upsertRows, deleteRows. Requires n8n enterprise/cloud with data tables feature.`,
|
||||
description: `Manage n8n data tables and rows. Actions: createTable, listTables, getTable, updateTable, deleteTable, getRows, insertRows, updateRows, upsertRows, deleteRows.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@@ -600,10 +600,10 @@ exports.n8nManagementTools = [
|
||||
description: 'Operation to perform',
|
||||
},
|
||||
tableId: { type: 'string', description: 'Data table ID (required for all actions except createTable and listTables)' },
|
||||
name: { type: 'string', description: 'For createTable/updateTable: table name' },
|
||||
name: { type: 'string', description: 'For createTable: table name. For updateTable: new name (rename only — schema is immutable after creation)' },
|
||||
columns: {
|
||||
type: 'array',
|
||||
description: 'For createTable: column definitions',
|
||||
description: 'For createTable only: column definitions (schema is immutable after creation via public API)',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
|
||||
2
dist/mcp/tools-n8n-manager.js.map
vendored
2
dist/mcp/tools-n8n-manager.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/services/n8n-validation.d.ts.map
vendored
2
dist/services/n8n-validation.d.ts.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"n8n-validation.d.ts","sourceRoot":"","sources":["../../src/services/n8n-validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAM9E,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiB7B,CAAC;AAkBH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAUpC,CAAC;AAEF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWjC,CAAC;AAGH,eAAO,MAAM,uBAAuB;;;;;;CAMnC,CAAC;AAGF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,YAAY,CAEhE;AAED,wBAAgB,2BAA2B,CAAC,WAAW,EAAE,OAAO,GAAG,kBAAkB,CAEpF;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAElG;AAGD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAsBrF;AAiBD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAoE5E;AAGD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE,CAkQ/E;AAGD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAK7D;AAMD,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,EAAE,CA+F5E;AAMD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CA0D/E;AAGD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAmB/D;AAGD,wBAAgB,2BAA2B,IAAI,MAAM,CA6CpD;AAGD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAmBpE"}
|
||||
{"version":3,"file":"n8n-validation.d.ts","sourceRoot":"","sources":["../../src/services/n8n-validation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAM9E,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiB7B,CAAC;AAkBH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAUpC,CAAC;AAEF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWjC,CAAC;AAGH,eAAO,MAAM,uBAAuB;;;;;;CAMnC,CAAC;AAGF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,YAAY,CAEhE;AAED,wBAAgB,2BAA2B,CAAC,WAAW,EAAE,OAAO,GAAG,kBAAkB,CAEpF;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAElG;AAmBD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAwBrF;AAiBD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAsE5E;AAGD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE,CAkQ/E;AAGD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAK7D;AAMD,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,EAAE,CA+F5E;AAMD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CA0D/E;AAGD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAmB/D;AAGD,wBAAgB,2BAA2B,IAAI,MAAM,CA6CpD;AAGD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAmBpE"}
|
||||
21
dist/services/n8n-validation.js
vendored
21
dist/services/n8n-validation.js
vendored
@@ -1,4 +1,7 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.defaultWorkflowSettings = exports.workflowSettingsSchema = exports.workflowConnectionSchema = exports.workflowNodeSchema = void 0;
|
||||
exports.validateWorkflowNode = validateWorkflowNode;
|
||||
@@ -13,6 +16,7 @@ exports.validateOperatorStructure = validateOperatorStructure;
|
||||
exports.getWebhookUrl = getWebhookUrl;
|
||||
exports.getWorkflowStructureExample = getWorkflowStructureExample;
|
||||
exports.getWorkflowFixSuggestions = getWorkflowFixSuggestions;
|
||||
const crypto_1 = __importDefault(require("crypto"));
|
||||
const zod_1 = require("zod");
|
||||
const node_type_utils_1 = require("../utils/node-type-utils");
|
||||
const node_classification_1 = require("../utils/node-classification");
|
||||
@@ -76,11 +80,27 @@ function validateWorkflowConnections(connections) {
|
||||
function validateWorkflowSettings(settings) {
|
||||
return exports.workflowSettingsSchema.parse(settings);
|
||||
}
|
||||
const WEBHOOK_NODE_TYPES = new Set([
|
||||
'n8n-nodes-base.webhook',
|
||||
'n8n-nodes-base.webhookTrigger',
|
||||
'n8n-nodes-base.formTrigger',
|
||||
'@n8n/n8n-nodes-langchain.chatTrigger',
|
||||
]);
|
||||
function ensureWebhookIds(nodes) {
|
||||
if (!nodes)
|
||||
return;
|
||||
for (const node of nodes) {
|
||||
if (WEBHOOK_NODE_TYPES.has(node.type) && !node.webhookId) {
|
||||
node.webhookId = crypto_1.default.randomUUID();
|
||||
}
|
||||
}
|
||||
}
|
||||
function cleanWorkflowForCreate(workflow) {
|
||||
const { id, createdAt, updatedAt, versionId, meta, active, tags, ...cleanedWorkflow } = workflow;
|
||||
if (!cleanedWorkflow.settings || Object.keys(cleanedWorkflow.settings).length === 0) {
|
||||
cleanedWorkflow.settings = exports.defaultWorkflowSettings;
|
||||
}
|
||||
ensureWebhookIds(cleanedWorkflow.nodes);
|
||||
return cleanedWorkflow;
|
||||
}
|
||||
function cleanWorkflowForUpdate(workflow) {
|
||||
@@ -116,6 +136,7 @@ function cleanWorkflowForUpdate(workflow) {
|
||||
else {
|
||||
cleanedWorkflow.settings = { executionOrder: 'v1' };
|
||||
}
|
||||
ensureWebhookIds(cleanedWorkflow.nodes);
|
||||
return cleanedWorkflow;
|
||||
}
|
||||
function validateWorkflowStructure(workflow) {
|
||||
|
||||
2
dist/services/n8n-validation.js.map
vendored
2
dist/services/n8n-validation.js.map
vendored
File diff suppressed because one or more lines are too long
9017
package-lock.json
generated
9017
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-mcp",
|
||||
"version": "2.40.2",
|
||||
"version": "2.41.0",
|
||||
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
@@ -152,17 +152,17 @@
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||
"@n8n/n8n-nodes-langchain": "^2.12.0",
|
||||
"@modelcontextprotocol/sdk": "1.28.0",
|
||||
"@n8n/n8n-nodes-langchain": "^2.13.1",
|
||||
"@supabase/supabase-js": "^2.57.4",
|
||||
"dotenv": "^16.5.0",
|
||||
"express": "^5.1.0",
|
||||
"express-rate-limit": "^7.1.5",
|
||||
"form-data": "^4.0.5",
|
||||
"lru-cache": "^11.2.1",
|
||||
"n8n": "^2.12.3",
|
||||
"n8n-core": "^2.12.0",
|
||||
"n8n-workflow": "^2.12.0",
|
||||
"n8n": "^2.13.3",
|
||||
"n8n-core": "^2.13.1",
|
||||
"n8n-workflow": "^2.13.1",
|
||||
"openai": "^4.77.0",
|
||||
"sql.js": "^1.13.0",
|
||||
"tslib": "^2.6.2",
|
||||
|
||||
@@ -77,6 +77,8 @@ const DEFAULT_CONFIG: Required<Omit<DocumentationGeneratorConfig, 'baseUrl' | 't
|
||||
*/
|
||||
export class DocumentationGenerator {
|
||||
private client: OpenAI;
|
||||
private baseUrl: string;
|
||||
private apiKey: string;
|
||||
private model: string;
|
||||
private maxTokens: number;
|
||||
private timeout: number;
|
||||
@@ -85,6 +87,8 @@ export class DocumentationGenerator {
|
||||
constructor(config: DocumentationGeneratorConfig) {
|
||||
const fullConfig = { ...DEFAULT_CONFIG, ...config };
|
||||
|
||||
this.baseUrl = config.baseUrl;
|
||||
this.apiKey = fullConfig.apiKey;
|
||||
this.client = new OpenAI({
|
||||
baseURL: config.baseUrl,
|
||||
apiKey: fullConfig.apiKey,
|
||||
@@ -103,21 +107,10 @@ export class DocumentationGenerator {
|
||||
try {
|
||||
const prompt = this.buildPrompt(input);
|
||||
|
||||
const completion = await this.client.chat.completions.create({
|
||||
model: this.model,
|
||||
max_completion_tokens: this.maxTokens,
|
||||
...(this.temperature !== undefined ? { temperature: this.temperature } : {}),
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: this.getSystemPrompt(),
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: prompt,
|
||||
},
|
||||
],
|
||||
});
|
||||
const completion = await this.chatCompletion([
|
||||
{ role: 'system', content: this.getSystemPrompt() },
|
||||
{ role: 'user', content: prompt },
|
||||
], this.maxTokens);
|
||||
|
||||
const content = completion.choices[0]?.message?.content;
|
||||
if (!content) {
|
||||
@@ -246,20 +239,23 @@ Guidelines:
|
||||
* Extract JSON from LLM response (handles markdown code blocks)
|
||||
*/
|
||||
private extractJson(content: string): string {
|
||||
// Strip <think>...</think> blocks from thinking models (e.g., Qwen3-Thinking)
|
||||
const stripped = content.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
|
||||
|
||||
// Try to extract from markdown code block
|
||||
const jsonBlockMatch = content.match(/```(?:json)?\s*([\s\S]*?)```/);
|
||||
const jsonBlockMatch = stripped.match(/```(?:json)?\s*([\s\S]*?)```/);
|
||||
if (jsonBlockMatch) {
|
||||
return jsonBlockMatch[1].trim();
|
||||
}
|
||||
|
||||
// Try to find JSON object directly
|
||||
const jsonMatch = content.match(/\{[\s\S]*\}/);
|
||||
const jsonMatch = stripped.match(/\{[\s\S]*\}/);
|
||||
if (jsonMatch) {
|
||||
return jsonMatch[0];
|
||||
}
|
||||
|
||||
// Return as-is if no extraction needed
|
||||
return content.trim();
|
||||
return stripped;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -323,16 +319,9 @@ Guidelines:
|
||||
*/
|
||||
async testConnection(): Promise<{ success: boolean; message: string }> {
|
||||
try {
|
||||
const completion = await this.client.chat.completions.create({
|
||||
model: this.model,
|
||||
max_completion_tokens: 200,
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Hello',
|
||||
},
|
||||
],
|
||||
});
|
||||
const completion = await this.chatCompletion([
|
||||
{ role: 'user', content: 'Hello' },
|
||||
], 200);
|
||||
|
||||
if (completion.choices[0]?.message?.content) {
|
||||
return { success: true, message: `Connected to ${this.model}` };
|
||||
@@ -345,6 +334,44 @@ Guidelines:
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a chat completion request with chat_template_kwargs support for vLLM thinking models
|
||||
*/
|
||||
private async chatCompletion(
|
||||
messages: Array<{ role: string; content: string }>,
|
||||
maxTokens: number
|
||||
): Promise<{ choices: Array<{ message: { content: string | null } }> }> {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(this.apiKey !== 'not-needed' ? { Authorization: `Bearer ${this.apiKey}` } : {}),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: this.model,
|
||||
messages,
|
||||
max_completion_tokens: maxTokens,
|
||||
...(this.temperature !== undefined ? { temperature: this.temperature } : {}),
|
||||
chat_template_kwargs: { enable_thinking: false },
|
||||
}),
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
throw new Error(`${response.status} ${text}`);
|
||||
}
|
||||
|
||||
return (await response.json()) as { choices: Array<{ message: { content: string | null } }> };
|
||||
} finally {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
private sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
@@ -34,6 +34,11 @@ export class NodeRepository {
|
||||
* Supports both core and community nodes via optional community fields
|
||||
*/
|
||||
saveNode(node: ParsedNode & Partial<CommunityNodeFields>): void {
|
||||
// Preserve existing npm_readme and ai_documentation_summary on upsert
|
||||
const existing = this.db.prepare(
|
||||
'SELECT npm_readme, ai_documentation_summary, ai_summary_generated_at FROM nodes WHERE node_type = ?'
|
||||
).get(node.nodeType) as { npm_readme?: string; ai_documentation_summary?: string; ai_summary_generated_at?: string } | undefined;
|
||||
|
||||
const stmt = this.db.prepare(`
|
||||
INSERT OR REPLACE INTO nodes (
|
||||
node_type, package_name, display_name, description,
|
||||
@@ -43,8 +48,9 @@ export class NodeRepository {
|
||||
properties_schema, operations, credentials_required,
|
||||
outputs, output_names,
|
||||
is_community, is_verified, author_name, author_github_url,
|
||||
npm_package_name, npm_version, npm_downloads, community_fetched_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
npm_package_name, npm_version, npm_downloads, community_fetched_at,
|
||||
npm_readme, ai_documentation_summary, ai_summary_generated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
stmt.run(
|
||||
@@ -76,7 +82,11 @@ export class NodeRepository {
|
||||
node.npmPackageName || null,
|
||||
node.npmVersion || null,
|
||||
node.npmDownloads || 0,
|
||||
node.communityFetchedAt || null
|
||||
node.communityFetchedAt || null,
|
||||
// Preserve existing docs data on upsert
|
||||
existing?.npm_readme || null,
|
||||
existing?.ai_documentation_summary || null,
|
||||
existing?.ai_summary_generated_at || null
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -253,6 +253,22 @@ export class SingleSessionHTTPServer {
|
||||
// This ensures compatibility with all MCP clients and proxies
|
||||
return Boolean(sessionId && sessionId.length > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a request body is a JSON-RPC notification (or batch of only notifications).
|
||||
* Per JSON-RPC 2.0 §4.1, a notification is a request without an "id" member.
|
||||
* Note: `!('id' in msg)` is strict — messages with `id: null` are treated as
|
||||
* requests, not notifications. This is spec-compliant.
|
||||
*/
|
||||
private isJsonRpcNotification(body: unknown): boolean {
|
||||
if (!body || typeof body !== 'object') return false;
|
||||
const isSingleNotification = (msg: any): boolean =>
|
||||
msg && typeof msg.method === 'string' && !('id' in msg);
|
||||
if (Array.isArray(body)) {
|
||||
return body.length > 0 && body.every(isSingleNotification);
|
||||
}
|
||||
return isSingleNotification(body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize error information for client responses
|
||||
@@ -614,6 +630,22 @@ export class SingleSessionHTTPServer {
|
||||
logger.info('handleRequest: Reusing existing transport for session', { sessionId });
|
||||
transport = this.transports[sessionId];
|
||||
|
||||
// TOCTOU guard: session may have been removed between the check above and here
|
||||
if (!transport) {
|
||||
if (this.isJsonRpcNotification(req.body)) {
|
||||
logger.info('handleRequest: Session removed during lookup, accepting notification', { sessionId });
|
||||
res.status(202).end();
|
||||
return;
|
||||
}
|
||||
logger.warn('handleRequest: Session removed between check and use (TOCTOU)', { sessionId });
|
||||
res.status(400).json({
|
||||
jsonrpc: '2.0',
|
||||
error: { code: -32000, message: 'Bad Request: Session not found or expired' },
|
||||
id: req.body?.id || null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// In multi-tenant shared mode, update instance context if provided
|
||||
const isMultiTenantEnabled = process.env.ENABLE_MULTI_TENANT === 'true';
|
||||
const sessionStrategy = process.env.MULTI_TENANT_SESSION_STRATEGY || 'instance';
|
||||
@@ -627,23 +659,33 @@ export class SingleSessionHTTPServer {
|
||||
this.updateSessionAccess(sessionId);
|
||||
|
||||
} else {
|
||||
// Invalid request - no session ID and not an initialize request
|
||||
// Notifications are fire-and-forget; returning 400 triggers reconnection storms (#654)
|
||||
if (this.isJsonRpcNotification(req.body)) {
|
||||
logger.info('handleRequest: Accepting notification for stale/missing session', {
|
||||
method: req.body?.method,
|
||||
sessionId: sessionId || 'none',
|
||||
});
|
||||
res.status(202).end();
|
||||
return;
|
||||
}
|
||||
|
||||
// Only return 400 for actual requests that need a valid session
|
||||
const errorDetails = {
|
||||
hasSessionId: !!sessionId,
|
||||
isInitialize: isInitialize,
|
||||
sessionIdValid: sessionId ? this.isValidSessionId(sessionId) : false,
|
||||
sessionExists: sessionId ? !!this.transports[sessionId] : false
|
||||
};
|
||||
|
||||
|
||||
logger.warn('handleRequest: Invalid request - no session ID and not initialize', errorDetails);
|
||||
|
||||
|
||||
let errorMessage = 'Bad Request: No valid session ID provided and not an initialize request';
|
||||
if (sessionId && !this.isValidSessionId(sessionId)) {
|
||||
errorMessage = 'Bad Request: Invalid session ID format';
|
||||
} else if (sessionId && !this.transports[sessionId]) {
|
||||
errorMessage = 'Bad Request: Session not found or expired';
|
||||
}
|
||||
|
||||
|
||||
res.status(400).json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
|
||||
@@ -2834,10 +2834,13 @@ export async function handleUpdateTable(args: unknown, context?: InstanceContext
|
||||
const client = ensureApiConfigured(context);
|
||||
const { tableId, name } = updateTableSchema.parse(args);
|
||||
const dataTable = await client.updateDataTable(tableId, { name });
|
||||
const rawArgs = args as Record<string, unknown>;
|
||||
const hasColumns = rawArgs && typeof rawArgs === 'object' && 'columns' in rawArgs;
|
||||
return {
|
||||
success: true,
|
||||
data: dataTable,
|
||||
message: `Data table renamed to "${dataTable.name}"`,
|
||||
message: `Data table renamed to "${dataTable.name}"` +
|
||||
(hasColumns ? '. Note: columns parameter was ignored — table schema is immutable after creation via the public API' : ''),
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDataTableError(error);
|
||||
|
||||
@@ -14,7 +14,7 @@ export const n8nManageDatatableDoc: ToolDocumentation = {
|
||||
'Use dryRun: true to preview update/upsert/delete before applying',
|
||||
'Filter supports: eq, neq, like, ilike, gt, gte, lt, lte conditions',
|
||||
'Use returnData: true to get affected rows back from update/upsert/delete',
|
||||
'Requires n8n enterprise or cloud with data tables feature'
|
||||
'Requires N8N_API_URL and N8N_API_KEY configured'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
@@ -96,8 +96,6 @@ export const n8nManageDatatableDoc: ToolDocumentation = {
|
||||
'Use sortBy for deterministic row ordering',
|
||||
],
|
||||
pitfalls: [
|
||||
'Requires N8N_API_URL and N8N_API_KEY configured',
|
||||
'Feature only available on n8n enterprise or cloud plans',
|
||||
'deleteTable permanently deletes all rows — cannot be undone',
|
||||
'deleteRows requires a filter — cannot delete all rows without one',
|
||||
'Column types cannot be changed after table creation via API',
|
||||
|
||||
@@ -609,7 +609,7 @@ export const n8nManagementTools: ToolDefinition[] = [
|
||||
},
|
||||
{
|
||||
name: 'n8n_manage_datatable',
|
||||
description: `Manage n8n data tables and rows. Actions: createTable, listTables, getTable, updateTable, deleteTable, getRows, insertRows, updateRows, upsertRows, deleteRows. Requires n8n enterprise/cloud with data tables feature.`,
|
||||
description: `Manage n8n data tables and rows. Actions: createTable, listTables, getTable, updateTable, deleteTable, getRows, insertRows, updateRows, upsertRows, deleteRows.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@@ -619,10 +619,10 @@ export const n8nManagementTools: ToolDefinition[] = [
|
||||
description: 'Operation to perform',
|
||||
},
|
||||
tableId: { type: 'string', description: 'Data table ID (required for all actions except createTable and listTables)' },
|
||||
name: { type: 'string', description: 'For createTable/updateTable: table name' },
|
||||
name: { type: 'string', description: 'For createTable: table name. For updateTable: new name (rename only — schema is immutable after creation)' },
|
||||
columns: {
|
||||
type: 'array',
|
||||
description: 'For createTable: column definitions',
|
||||
description: 'For createTable only: column definitions (schema is immutable after creation via public API)',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
* Fetch community nodes from n8n Strapi API and npm registry.
|
||||
*
|
||||
* Usage:
|
||||
* npm run fetch:community # Full rebuild (verified + top 100 npm)
|
||||
* npm run fetch:community # Upsert all (preserves READMEs and AI summaries)
|
||||
* npm run fetch:community:verified # Verified nodes only (fast)
|
||||
* npm run fetch:community:update # Incremental update (skip existing)
|
||||
*
|
||||
* Options:
|
||||
* --verified-only Only fetch verified nodes from Strapi API
|
||||
* --update Skip nodes that already exist in database
|
||||
* --rebuild Delete all community nodes first (wipes READMEs/AI summaries!)
|
||||
* --npm-limit=N Maximum number of npm packages to fetch (default: 100)
|
||||
* --staging Use staging Strapi API instead of production
|
||||
*/
|
||||
@@ -22,6 +23,7 @@ import { createDatabaseAdapter } from '../database/database-adapter';
|
||||
interface CliOptions {
|
||||
verifiedOnly: boolean;
|
||||
update: boolean;
|
||||
rebuild: boolean;
|
||||
npmLimit: number;
|
||||
staging: boolean;
|
||||
}
|
||||
@@ -32,6 +34,7 @@ function parseArgs(): CliOptions {
|
||||
const options: CliOptions = {
|
||||
verifiedOnly: false,
|
||||
update: false,
|
||||
rebuild: false,
|
||||
npmLimit: 100,
|
||||
staging: false,
|
||||
};
|
||||
@@ -41,6 +44,8 @@ function parseArgs(): CliOptions {
|
||||
options.verifiedOnly = true;
|
||||
} else if (arg === '--update') {
|
||||
options.update = true;
|
||||
} else if (arg === '--rebuild') {
|
||||
options.rebuild = true;
|
||||
} else if (arg === '--staging') {
|
||||
options.staging = true;
|
||||
} else if (arg.startsWith('--npm-limit=')) {
|
||||
@@ -73,7 +78,7 @@ async function main(): Promise<void> {
|
||||
|
||||
// Print options
|
||||
console.log('Options:');
|
||||
console.log(` - Mode: ${cliOptions.update ? 'Update (incremental)' : 'Rebuild'}`);
|
||||
console.log(` - Mode: ${cliOptions.rebuild ? 'Rebuild (clean slate)' : cliOptions.update ? 'Update (skip existing)' : 'Upsert (preserves docs)'}`);
|
||||
console.log(` - Verified only: ${cliOptions.verifiedOnly ? 'Yes' : 'No'}`);
|
||||
if (!cliOptions.verifiedOnly) {
|
||||
console.log(` - npm package limit: ${cliOptions.npmLimit}`);
|
||||
@@ -92,9 +97,10 @@ async function main(): Promise<void> {
|
||||
const environment = cliOptions.staging ? 'staging' : 'production';
|
||||
const service = new CommunityNodeService(repository, environment);
|
||||
|
||||
// If not updating, delete existing community nodes
|
||||
if (!cliOptions.update) {
|
||||
console.log('\nClearing existing community nodes...');
|
||||
// Only delete existing community nodes when --rebuild is explicitly requested
|
||||
if (cliOptions.rebuild) {
|
||||
console.log('\nClearing existing community nodes (--rebuild)...');
|
||||
console.log(' WARNING: This wipes READMEs and AI summaries!');
|
||||
const deleted = service.deleteCommunityNodes();
|
||||
console.log(` Deleted ${deleted} existing community nodes`);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import crypto from 'crypto';
|
||||
import { z } from 'zod';
|
||||
import { WorkflowNode, WorkflowConnection, Workflow } from '../types/n8n-api';
|
||||
import { isTriggerNode, isActivatableTrigger } from '../utils/node-type-utils';
|
||||
@@ -87,6 +88,22 @@ export function validateWorkflowSettings(settings: unknown): z.infer<typeof work
|
||||
return workflowSettingsSchema.parse(settings);
|
||||
}
|
||||
|
||||
const WEBHOOK_NODE_TYPES = new Set([
|
||||
'n8n-nodes-base.webhook',
|
||||
'n8n-nodes-base.webhookTrigger',
|
||||
'n8n-nodes-base.formTrigger',
|
||||
'@n8n/n8n-nodes-langchain.chatTrigger',
|
||||
]);
|
||||
|
||||
function ensureWebhookIds(nodes?: WorkflowNode[]): void {
|
||||
if (!nodes) return;
|
||||
for (const node of nodes) {
|
||||
if (WEBHOOK_NODE_TYPES.has(node.type) && !node.webhookId) {
|
||||
node.webhookId = crypto.randomUUID();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean workflow data for API operations
|
||||
export function cleanWorkflowForCreate(workflow: Partial<Workflow>): Partial<Workflow> {
|
||||
const {
|
||||
@@ -109,6 +126,8 @@ export function cleanWorkflowForCreate(workflow: Partial<Workflow>): Partial<Wor
|
||||
cleanedWorkflow.settings = defaultWorkflowSettings;
|
||||
}
|
||||
|
||||
ensureWebhookIds(cleanedWorkflow.nodes);
|
||||
|
||||
return cleanedWorkflow;
|
||||
}
|
||||
|
||||
@@ -194,6 +213,8 @@ export function cleanWorkflowForUpdate(workflow: Workflow): Partial<Workflow> {
|
||||
cleanedWorkflow.settings = { executionOrder: 'v1' as const };
|
||||
}
|
||||
|
||||
ensureWebhookIds(cleanedWorkflow.nodes);
|
||||
|
||||
return cleanedWorkflow;
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ class InMemoryDatabaseAdapter implements DatabaseAdapter {
|
||||
|
||||
class InMemoryPreparedStatement implements PreparedStatement {
|
||||
run = vi.fn((...params: any[]): RunResult => {
|
||||
if (this.sql.includes('INSERT OR REPLACE INTO nodes')) {
|
||||
if (this.sql.includes('INSERT') && this.sql.includes('INTO nodes')) {
|
||||
const node = this.paramsToNode(params);
|
||||
this.adapter.saveNode(node);
|
||||
return { changes: 1, lastInsertRowid: 1 };
|
||||
@@ -100,6 +100,9 @@ class InMemoryPreparedStatement implements PreparedStatement {
|
||||
});
|
||||
|
||||
get = vi.fn((...params: any[]) => {
|
||||
if (this.sql.includes('SELECT npm_readme')) {
|
||||
return undefined; // No existing docs to preserve
|
||||
}
|
||||
if (this.sql.includes('SELECT * FROM nodes WHERE node_type = ?')) {
|
||||
return this.adapter.getNode(params[0]);
|
||||
}
|
||||
|
||||
115
tests/integration/n8n-api/scripts/cleanup-non-test-workflows.ts
Normal file
115
tests/integration/n8n-api/scripts/cleanup-non-test-workflows.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env tsx
|
||||
/**
|
||||
* Cleanup Non-Test Workflows
|
||||
*
|
||||
* Deletes all workflows from the n8n test instance EXCEPT those
|
||||
* with "[TEST]" in the name. This helps keep the test instance
|
||||
* clean and prevents list endpoint pagination issues.
|
||||
*
|
||||
* Usage:
|
||||
* npx tsx tests/integration/n8n-api/scripts/cleanup-non-test-workflows.ts
|
||||
* npx tsx tests/integration/n8n-api/scripts/cleanup-non-test-workflows.ts --dry-run
|
||||
*/
|
||||
|
||||
import { getN8nCredentials, validateCredentials } from '../utils/credentials';
|
||||
|
||||
const DRY_RUN = process.argv.includes('--dry-run');
|
||||
|
||||
interface Workflow {
|
||||
id: string;
|
||||
name: string;
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
async function fetchAllWorkflows(baseUrl: string, apiKey: string): Promise<Workflow[]> {
|
||||
const all: Workflow[] = [];
|
||||
let cursor: string | undefined;
|
||||
|
||||
while (true) {
|
||||
const url = new URL('/api/v1/workflows', baseUrl);
|
||||
url.searchParams.set('limit', '100');
|
||||
if (cursor) url.searchParams.set('cursor', cursor);
|
||||
|
||||
const res = await fetch(url.toString(), {
|
||||
headers: { 'X-N8N-API-KEY': apiKey }
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to list workflows: ${res.status} ${res.statusText}`);
|
||||
}
|
||||
|
||||
const body = await res.json() as { data: Workflow[]; nextCursor?: string };
|
||||
all.push(...body.data);
|
||||
|
||||
if (!body.nextCursor) break;
|
||||
cursor = body.nextCursor;
|
||||
}
|
||||
|
||||
return all;
|
||||
}
|
||||
|
||||
async function deleteWorkflow(baseUrl: string, apiKey: string, id: string): Promise<void> {
|
||||
const res = await fetch(`${baseUrl}/api/v1/workflows/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'X-N8N-API-KEY': apiKey }
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to delete workflow ${id}: ${res.status} ${res.statusText}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const creds = getN8nCredentials();
|
||||
validateCredentials(creds);
|
||||
|
||||
console.log(`n8n Instance: ${creds.url}`);
|
||||
console.log(`Mode: ${DRY_RUN ? 'DRY RUN' : 'LIVE DELETE'}\n`);
|
||||
|
||||
const workflows = await fetchAllWorkflows(creds.url, creds.apiKey);
|
||||
console.log(`Total workflows found: ${workflows.length}\n`);
|
||||
|
||||
const toKeep = workflows.filter(w => w.name.includes('[TEST]'));
|
||||
const toDelete = workflows.filter(w => !w.name.includes('[TEST]'));
|
||||
|
||||
console.log(`Keeping (${toKeep.length}):`);
|
||||
for (const w of toKeep) {
|
||||
console.log(` ✅ ${w.id} - ${w.name}`);
|
||||
}
|
||||
|
||||
console.log(`\nDeleting (${toDelete.length}):`);
|
||||
for (const w of toDelete) {
|
||||
console.log(` 🗑️ ${w.id} - ${w.name}${w.active ? ' (ACTIVE)' : ''}`);
|
||||
}
|
||||
|
||||
if (DRY_RUN) {
|
||||
console.log('\nDry run complete. No workflows were deleted.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (toDelete.length === 0) {
|
||||
console.log('\nNothing to delete.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`\nDeleting ${toDelete.length} workflows...`);
|
||||
let deleted = 0;
|
||||
let failed = 0;
|
||||
|
||||
for (const w of toDelete) {
|
||||
try {
|
||||
await deleteWorkflow(creds.url, creds.apiKey, w.id);
|
||||
deleted++;
|
||||
} catch (err) {
|
||||
console.error(` Failed to delete ${w.id} (${w.name}): ${err}`);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nDone! Deleted: ${deleted}, Failed: ${failed}, Kept: ${toKeep.length}`);
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('Fatal error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -119,6 +119,11 @@ class MockPreparedStatement implements PreparedStatement {
|
||||
});
|
||||
}
|
||||
|
||||
// saveNode - SELECT existing doc fields before upsert
|
||||
if (this.sql.includes('SELECT npm_readme, ai_documentation_summary, ai_summary_generated_at FROM nodes')) {
|
||||
this.get = vi.fn(() => undefined); // No existing row by default
|
||||
}
|
||||
|
||||
// saveNode - INSERT OR REPLACE
|
||||
if (this.sql.includes('INSERT OR REPLACE INTO nodes')) {
|
||||
this.run = vi.fn((...params: any[]): RunResult => {
|
||||
|
||||
@@ -49,7 +49,12 @@ class MockPreparedStatement implements PreparedStatement {
|
||||
if (sql.includes('SELECT * FROM nodes WHERE node_type = ?')) {
|
||||
this.get = vi.fn((nodeType: string) => this.mockData.get(`node:${nodeType}`));
|
||||
}
|
||||
|
||||
|
||||
// Configure get() for saveNode's SELECT to preserve existing doc fields
|
||||
if (sql.includes('SELECT npm_readme, ai_documentation_summary, ai_summary_generated_at FROM nodes')) {
|
||||
this.get = vi.fn(() => undefined); // No existing row by default
|
||||
}
|
||||
|
||||
// Configure all() for getAITools
|
||||
if (sql.includes('WHERE is_ai_tool = 1')) {
|
||||
this.all = vi.fn(() => this.mockData.get('ai_tools') || []);
|
||||
@@ -123,7 +128,10 @@ describe('NodeRepository - Core Functionality', () => {
|
||||
null, // npmPackageName
|
||||
null, // npmVersion
|
||||
0, // npmDownloads
|
||||
null // communityFetchedAt
|
||||
null, // communityFetchedAt
|
||||
null, // npm_readme (preserved from existing)
|
||||
null, // ai_documentation_summary (preserved from existing)
|
||||
null // ai_summary_generated_at (preserved from existing)
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -15,8 +15,21 @@ describe('NodeRepository - Outputs Handling', () => {
|
||||
all: vi.fn()
|
||||
};
|
||||
|
||||
// saveNode now calls prepare twice: first a SELECT (returns get), then INSERT (returns run).
|
||||
// We create a separate mock for the SELECT statement that returns undefined (no existing row).
|
||||
const selectStatement = {
|
||||
run: vi.fn(),
|
||||
get: vi.fn().mockReturnValue(undefined),
|
||||
all: vi.fn()
|
||||
};
|
||||
|
||||
mockDb = {
|
||||
prepare: vi.fn().mockReturnValue(mockStatement),
|
||||
prepare: vi.fn((sql: string) => {
|
||||
if (sql.includes('SELECT npm_readme')) {
|
||||
return selectStatement;
|
||||
}
|
||||
return mockStatement;
|
||||
}),
|
||||
transaction: vi.fn(),
|
||||
exec: vi.fn(),
|
||||
close: vi.fn(),
|
||||
@@ -55,18 +68,9 @@ describe('NodeRepository - Outputs Handling', () => {
|
||||
|
||||
repository.saveNode(node);
|
||||
|
||||
expect(mockDb.prepare).toHaveBeenCalledWith(`
|
||||
INSERT OR REPLACE INTO nodes (
|
||||
node_type, package_name, display_name, description,
|
||||
category, development_style, is_ai_tool, is_trigger,
|
||||
is_webhook, is_versioned, is_tool_variant, tool_variant_of,
|
||||
has_tool_variant, version, documentation,
|
||||
properties_schema, operations, credentials_required,
|
||||
outputs, output_names,
|
||||
is_community, is_verified, author_name, author_github_url,
|
||||
npm_package_name, npm_version, npm_downloads, community_fetched_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
expect(mockDb.prepare).toHaveBeenCalledWith(
|
||||
expect.stringContaining('INSERT OR REPLACE INTO nodes')
|
||||
);
|
||||
|
||||
expect(mockStatement.run).toHaveBeenCalledWith(
|
||||
'nodes-base.splitInBatches',
|
||||
@@ -96,7 +100,10 @@ describe('NodeRepository - Outputs Handling', () => {
|
||||
null, // npm_package_name
|
||||
null, // npm_version
|
||||
0, // npm_downloads
|
||||
null // community_fetched_at
|
||||
null, // community_fetched_at
|
||||
null, // npm_readme (preserved from existing)
|
||||
null, // ai_documentation_summary (preserved from existing)
|
||||
null // ai_summary_generated_at (preserved from existing)
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -219,6 +219,7 @@ describe('HTTP Server Session Management', () => {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
json: vi.fn().mockReturnThis(),
|
||||
send: vi.fn().mockReturnThis(),
|
||||
end: vi.fn().mockReturnThis(),
|
||||
setHeader: vi.fn((key: string, value: string) => {
|
||||
headers[key.toLowerCase()] = value;
|
||||
}),
|
||||
@@ -1170,7 +1171,7 @@ describe('HTTP Server Session Management', () => {
|
||||
|
||||
it('should show legacy SSE session when present', async () => {
|
||||
server = new SingleSessionHTTPServer();
|
||||
|
||||
|
||||
// Mock legacy session
|
||||
const mockSession = {
|
||||
sessionId: 'sse-session-123',
|
||||
@@ -1180,10 +1181,127 @@ describe('HTTP Server Session Management', () => {
|
||||
(server as any).session = mockSession;
|
||||
|
||||
const sessionInfo = server.getSessionInfo();
|
||||
|
||||
|
||||
expect(sessionInfo.active).toBe(true);
|
||||
expect(sessionInfo.sessionId).toBe('sse-session-123');
|
||||
expect(sessionInfo.age).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Notification handling for stale sessions (#654)', () => {
|
||||
beforeEach(() => {
|
||||
// Re-apply mockImplementation after vi.clearAllMocks() resets it
|
||||
mockConsoleManager.wrapOperation.mockImplementation(async (fn: () => Promise<any>) => {
|
||||
return await fn();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 202 for notification with stale session ID', async () => {
|
||||
server = new SingleSessionHTTPServer();
|
||||
|
||||
const { req, res } = createMockReqRes();
|
||||
|
||||
req.headers = { 'mcp-session-id': 'stale-session-that-does-not-exist' };
|
||||
req.method = 'POST';
|
||||
req.body = {
|
||||
jsonrpc: '2.0',
|
||||
method: 'notifications/initialized',
|
||||
};
|
||||
|
||||
await server.handleRequest(req as any, res as any);
|
||||
|
||||
expect(res.status).toHaveBeenCalledWith(202);
|
||||
expect(res.end).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return 202 for notification batch with stale session ID', async () => {
|
||||
server = new SingleSessionHTTPServer();
|
||||
|
||||
const { req, res } = createMockReqRes();
|
||||
|
||||
req.headers = { 'mcp-session-id': 'stale-session-that-does-not-exist' };
|
||||
req.method = 'POST';
|
||||
req.body = [
|
||||
{ jsonrpc: '2.0', method: 'notifications/initialized' },
|
||||
{ jsonrpc: '2.0', method: 'notifications/cancelled' },
|
||||
];
|
||||
|
||||
await server.handleRequest(req as any, res as any);
|
||||
|
||||
expect(res.status).toHaveBeenCalledWith(202);
|
||||
expect(res.end).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return 400 for request (with id) with stale session ID', async () => {
|
||||
server = new SingleSessionHTTPServer();
|
||||
|
||||
const { req, res } = createMockReqRes();
|
||||
req.headers = { 'mcp-session-id': 'stale-session-that-does-not-exist' };
|
||||
req.method = 'POST';
|
||||
req.body = {
|
||||
jsonrpc: '2.0',
|
||||
method: 'tools/call',
|
||||
params: { name: 'search_nodes', arguments: { query: 'http' } },
|
||||
id: 42,
|
||||
};
|
||||
|
||||
await server.handleRequest(req as any, res as any);
|
||||
|
||||
expect(res.status).toHaveBeenCalledWith(400);
|
||||
expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
|
||||
error: expect.objectContaining({
|
||||
message: 'Bad Request: Session not found or expired',
|
||||
}),
|
||||
}));
|
||||
});
|
||||
|
||||
it('should return 202 for notification with no session ID', async () => {
|
||||
server = new SingleSessionHTTPServer();
|
||||
|
||||
const { req, res } = createMockReqRes();
|
||||
|
||||
req.method = 'POST';
|
||||
req.body = {
|
||||
jsonrpc: '2.0',
|
||||
method: 'notifications/cancelled',
|
||||
};
|
||||
|
||||
await server.handleRequest(req as any, res as any);
|
||||
|
||||
expect(res.status).toHaveBeenCalledWith(202);
|
||||
expect(res.end).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return 400 for request with no session ID and not initialize', async () => {
|
||||
server = new SingleSessionHTTPServer();
|
||||
|
||||
const { req, res } = createMockReqRes();
|
||||
req.method = 'POST';
|
||||
req.body = {
|
||||
jsonrpc: '2.0',
|
||||
method: 'tools/list',
|
||||
id: 1,
|
||||
};
|
||||
|
||||
await server.handleRequest(req as any, res as any);
|
||||
|
||||
expect(res.status).toHaveBeenCalledWith(400);
|
||||
});
|
||||
|
||||
it('should return 400 for mixed batch (notification + request) with stale session', async () => {
|
||||
server = new SingleSessionHTTPServer();
|
||||
|
||||
const { req, res } = createMockReqRes();
|
||||
req.headers = { 'mcp-session-id': 'stale-session-that-does-not-exist' };
|
||||
req.method = 'POST';
|
||||
req.body = [
|
||||
{ jsonrpc: '2.0', method: 'notifications/initialized' },
|
||||
{ jsonrpc: '2.0', method: 'tools/list', id: 1 },
|
||||
];
|
||||
|
||||
await server.handleRequest(req as any, res as any);
|
||||
|
||||
expect(res.status).toHaveBeenCalledWith(400);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -363,6 +363,22 @@ describe('Data Table Handlers (n8n_manage_datatable)', () => {
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Update failed');
|
||||
});
|
||||
|
||||
it('should warn when columns parameter is passed', async () => {
|
||||
const updatedTable = { id: 'dt-1', name: 'Renamed' };
|
||||
mockApiClient.updateDataTable.mockResolvedValue(updatedTable);
|
||||
|
||||
const result = await handlers.handleUpdateTable({
|
||||
tableId: 'dt-1',
|
||||
name: 'Renamed',
|
||||
columns: [{ name: 'phone', type: 'string' }],
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.message).toContain('columns parameter was ignored');
|
||||
expect(result.message).toContain('immutable after creation');
|
||||
expect(mockApiClient.updateDataTable).toHaveBeenCalledWith('dt-1', { name: 'Renamed' });
|
||||
});
|
||||
});
|
||||
|
||||
// ========================================================================
|
||||
|
||||
@@ -19,6 +19,14 @@ import { WorkflowBuilder } from '../../utils/builders/workflow.builder';
|
||||
import { z } from 'zod';
|
||||
import { WorkflowNode, WorkflowConnection, Workflow } from '../../../src/types/n8n-api';
|
||||
|
||||
function webhookNode(id: string, name: string, type: string, typeVersion = 2): WorkflowNode {
|
||||
return { id, name, type, typeVersion, position: [250, 300] as [number, number], parameters: {} };
|
||||
}
|
||||
|
||||
function workflowWithNodes(nodes: WorkflowNode[]): Partial<Workflow> {
|
||||
return { name: 'Test', nodes, connections: {} };
|
||||
}
|
||||
|
||||
describe('n8n-validation', () => {
|
||||
describe('Zod Schemas', () => {
|
||||
describe('workflowNodeSchema', () => {
|
||||
@@ -301,6 +309,44 @@ describe('n8n-validation', () => {
|
||||
const cleaned = cleanWorkflowForCreate(workflow as Workflow);
|
||||
expect(cleaned.settings).toEqual(customSettings);
|
||||
});
|
||||
|
||||
it('should inject webhookId on webhook nodes missing it', () => {
|
||||
const workflow = workflowWithNodes([
|
||||
webhookNode('1', 'Webhook', 'n8n-nodes-base.webhook'),
|
||||
]);
|
||||
|
||||
const cleaned = cleanWorkflowForCreate(workflow as Workflow);
|
||||
expect(cleaned.nodes![0].webhookId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-/);
|
||||
});
|
||||
|
||||
it('should preserve existing webhookId on webhook nodes', () => {
|
||||
const workflow = workflowWithNodes([
|
||||
{ ...webhookNode('1', 'Webhook', 'n8n-nodes-base.webhook'), webhookId: 'existing-id' },
|
||||
]);
|
||||
|
||||
const cleaned = cleanWorkflowForCreate(workflow as Workflow);
|
||||
expect(cleaned.nodes![0].webhookId).toBe('existing-id');
|
||||
});
|
||||
|
||||
it('should inject webhookId on formTrigger and chatTrigger nodes', () => {
|
||||
const workflow = workflowWithNodes([
|
||||
webhookNode('1', 'Form', 'n8n-nodes-base.formTrigger'),
|
||||
webhookNode('2', 'Chat', '@n8n/n8n-nodes-langchain.chatTrigger'),
|
||||
]);
|
||||
|
||||
const cleaned = cleanWorkflowForCreate(workflow as Workflow);
|
||||
expect(cleaned.nodes![0].webhookId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-/);
|
||||
expect(cleaned.nodes![1].webhookId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-/);
|
||||
});
|
||||
|
||||
it('should not inject webhookId on non-webhook nodes', () => {
|
||||
const workflow = workflowWithNodes([
|
||||
webhookNode('1', 'Set', 'n8n-nodes-base.set', 3.4),
|
||||
]);
|
||||
|
||||
const cleaned = cleanWorkflowForCreate(workflow as Workflow);
|
||||
expect(cleaned.nodes![0].webhookId).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('cleanWorkflowForUpdate', () => {
|
||||
@@ -533,6 +579,44 @@ describe('n8n-validation', () => {
|
||||
});
|
||||
expect(cleaned.settings).not.toHaveProperty('someOtherProperty');
|
||||
});
|
||||
|
||||
it('should inject webhookId on webhook nodes missing it', () => {
|
||||
const workflow = workflowWithNodes([
|
||||
webhookNode('1', 'Webhook', 'n8n-nodes-base.webhook'),
|
||||
]) as any;
|
||||
|
||||
const cleaned = cleanWorkflowForUpdate(workflow);
|
||||
expect(cleaned.nodes![0].webhookId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-/);
|
||||
});
|
||||
|
||||
it('should preserve existing webhookId on webhook nodes', () => {
|
||||
const workflow = workflowWithNodes([
|
||||
{ ...webhookNode('1', 'Webhook', 'n8n-nodes-base.webhook'), webhookId: 'existing-id' },
|
||||
]) as any;
|
||||
|
||||
const cleaned = cleanWorkflowForUpdate(workflow);
|
||||
expect(cleaned.nodes![0].webhookId).toBe('existing-id');
|
||||
});
|
||||
|
||||
it('should inject webhookId on formTrigger and chatTrigger nodes', () => {
|
||||
const workflow = workflowWithNodes([
|
||||
webhookNode('1', 'Form', 'n8n-nodes-base.formTrigger'),
|
||||
webhookNode('2', 'Chat', '@n8n/n8n-nodes-langchain.chatTrigger'),
|
||||
]) as any;
|
||||
|
||||
const cleaned = cleanWorkflowForUpdate(workflow);
|
||||
expect(cleaned.nodes![0].webhookId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-/);
|
||||
expect(cleaned.nodes![1].webhookId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-/);
|
||||
});
|
||||
|
||||
it('should not inject webhookId on non-webhook nodes', () => {
|
||||
const workflow = workflowWithNodes([
|
||||
webhookNode('1', 'Set', 'n8n-nodes-base.set', 3.4),
|
||||
]) as any;
|
||||
|
||||
const cleaned = cleanWorkflowForUpdate(workflow);
|
||||
expect(cleaned.nodes![0].webhookId).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user