mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-03-27 12:43:12 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07bd1d4cc2 | ||
|
|
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 "Zod version: $ZOD_VERSION"
|
||||||
echo ""
|
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
|
if [[ "$SDK_VERSION" == "not found" ]]; then
|
||||||
echo "❌ FAILED: Could not determine MCP SDK version!"
|
echo "❌ FAILED: Could not determine MCP SDK version!"
|
||||||
echo " The dependency may not have been installed correctly."
|
echo " The dependency may not have been installed correctly."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if [[ "$SDK_VERSION" != "1.27.1" ]]; then
|
if [[ "$SDK_VERSION" != "1.28.0" ]]; then
|
||||||
echo "❌ FAILED: MCP SDK version mismatch!"
|
echo "❌ FAILED: MCP SDK version mismatch!"
|
||||||
echo " Expected: 1.27.1"
|
echo " Expected: 1.28.0"
|
||||||
echo " Got: $SDK_VERSION"
|
echo " Got: $SDK_VERSION"
|
||||||
echo ""
|
echo ""
|
||||||
echo "This can cause runtime errors. See issues #440, #444, #446, #447, #450"
|
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]
|
## [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
|
## [2.40.2] - 2026-03-22
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
# n8n Update Process - Quick Reference
|
# 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!
|
**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
|
```bash
|
||||||
# 1. CHECK EXISTING RELEASES FIRST (prevents version conflicts!)
|
# 1. CHECK EXISTING RELEASES FIRST (prevents version conflicts!)
|
||||||
gh release list | head -5
|
gh release list | head -5
|
||||||
@@ -15,14 +22,29 @@ git checkout main && git pull
|
|||||||
# 3. Check for updates (dry run)
|
# 3. Check for updates (dry run)
|
||||||
npm run update:n8n:check
|
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
|
yes y | npm run update:n8n
|
||||||
|
|
||||||
# 5. Refresh community nodes (standard practice!)
|
# 6. Restore community nodes after rebuild
|
||||||
npm run fetch:community
|
sqlite3 data/nodes.db < /tmp/n8n_community_backup.sql
|
||||||
npm run generate:docs
|
|
||||||
|
|
||||||
# 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
|
git checkout -b update/n8n-X.X.X
|
||||||
|
|
||||||
# 7. Update version in package.json (must be HIGHER than latest release!)
|
# 7. Update version in package.json (must be HIGHER than latest release!)
|
||||||
|
|||||||
@@ -5,17 +5,17 @@
|
|||||||
[](https://www.npmjs.com/package/n8n-mcp)
|
[](https://www.npmjs.com/package/n8n-mcp)
|
||||||
[](https://codecov.io/gh/czlonkowski/n8n-mcp)
|
[](https://codecov.io/gh/czlonkowski/n8n-mcp)
|
||||||
[](https://github.com/czlonkowski/n8n-mcp/actions)
|
[](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://github.com/czlonkowski/n8n-mcp/pkgs/container/n8n-mcp)
|
||||||
[](https://railway.com/deploy/n8n-mcp?referralCode=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
|
## 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:
|
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 properties** - 99% coverage with detailed schemas
|
||||||
- ⚡ **Node operations** - 63.6% coverage of available actions
|
- ⚡ **Node operations** - 63.6% coverage of available actions
|
||||||
- 📄 **Documentation** - 87% coverage from official n8n docs (including AI nodes)
|
- 📄 **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":"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"}
|
{"version":3,"file":"node-repository.d.ts","sourceRoot":"","sources":["../../src/database/node-repository.ts"],"names":[],"mappings":"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"}
|
||||||
31
dist/database/node-repository.js
vendored
31
dist/database/node-repository.js
vendored
@@ -13,7 +13,7 @@ class NodeRepository {
|
|||||||
}
|
}
|
||||||
saveNode(node) {
|
saveNode(node) {
|
||||||
const stmt = this.db.prepare(`
|
const stmt = this.db.prepare(`
|
||||||
INSERT OR REPLACE INTO nodes (
|
INSERT INTO nodes (
|
||||||
node_type, package_name, display_name, description,
|
node_type, package_name, display_name, description,
|
||||||
category, development_style, is_ai_tool, is_trigger,
|
category, development_style, is_ai_tool, is_trigger,
|
||||||
is_webhook, is_versioned, is_tool_variant, tool_variant_of,
|
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,
|
is_community, is_verified, author_name, author_github_url,
|
||||||
npm_package_name, npm_version, npm_downloads, community_fetched_at
|
npm_package_name, npm_version, npm_downloads, community_fetched_at
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
) 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);
|
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 getActiveSessionCount;
|
||||||
private canCreateSession;
|
private canCreateSession;
|
||||||
private isValidSessionId;
|
private isValidSessionId;
|
||||||
|
private isJsonRpcNotification;
|
||||||
private sanitizeErrorForClient;
|
private sanitizeErrorForClient;
|
||||||
private updateSessionAccess;
|
private updateSessionAccess;
|
||||||
private switchSessionContext;
|
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) {
|
isValidSessionId(sessionId) {
|
||||||
return Boolean(sessionId && sessionId.length > 0);
|
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) {
|
sanitizeErrorForClient(error) {
|
||||||
const isProduction = process.env.NODE_ENV === 'production';
|
const isProduction = process.env.NODE_ENV === 'production';
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
@@ -381,6 +390,20 @@ class SingleSessionHTTPServer {
|
|||||||
}
|
}
|
||||||
logger_1.logger.info('handleRequest: Reusing existing transport for session', { sessionId });
|
logger_1.logger.info('handleRequest: Reusing existing transport for session', { sessionId });
|
||||||
transport = this.transports[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 isMultiTenantEnabled = process.env.ENABLE_MULTI_TENANT === 'true';
|
||||||
const sessionStrategy = process.env.MULTI_TENANT_SESSION_STRATEGY || 'instance';
|
const sessionStrategy = process.env.MULTI_TENANT_SESSION_STRATEGY || 'instance';
|
||||||
if (isMultiTenantEnabled && sessionStrategy === 'shared' && instanceContext) {
|
if (isMultiTenantEnabled && sessionStrategy === 'shared' && instanceContext) {
|
||||||
@@ -389,6 +412,14 @@ class SingleSessionHTTPServer {
|
|||||||
this.updateSessionAccess(sessionId);
|
this.updateSessionAccess(sessionId);
|
||||||
}
|
}
|
||||||
else {
|
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 = {
|
const errorDetails = {
|
||||||
hasSessionId: !!sessionId,
|
hasSessionId: !!sessionId,
|
||||||
isInitialize: isInitialize,
|
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 client = ensureApiConfigured(context);
|
||||||
const { tableId, name } = updateTableSchema.parse(args);
|
const { tableId, name } = updateTableSchema.parse(args);
|
||||||
const dataTable = await client.updateDataTable(tableId, { name });
|
const dataTable = await client.updateDataTable(tableId, { name });
|
||||||
|
const rawArgs = args;
|
||||||
|
const hasColumns = rawArgs && typeof rawArgs === 'object' && 'columns' in rawArgs;
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
data: dataTable,
|
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) {
|
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',
|
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: {
|
inputSchema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
@@ -600,10 +600,10 @@ exports.n8nManagementTools = [
|
|||||||
description: 'Operation to perform',
|
description: 'Operation to perform',
|
||||||
},
|
},
|
||||||
tableId: { type: 'string', description: 'Data table ID (required for all actions except createTable and listTables)' },
|
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: {
|
columns: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
description: 'For createTable: column definitions',
|
description: 'For createTable only: column definitions (schema is immutable after creation via public API)',
|
||||||
items: {
|
items: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
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";
|
"use strict";
|
||||||
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
|
};
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.defaultWorkflowSettings = exports.workflowSettingsSchema = exports.workflowConnectionSchema = exports.workflowNodeSchema = void 0;
|
exports.defaultWorkflowSettings = exports.workflowSettingsSchema = exports.workflowConnectionSchema = exports.workflowNodeSchema = void 0;
|
||||||
exports.validateWorkflowNode = validateWorkflowNode;
|
exports.validateWorkflowNode = validateWorkflowNode;
|
||||||
@@ -13,6 +16,7 @@ exports.validateOperatorStructure = validateOperatorStructure;
|
|||||||
exports.getWebhookUrl = getWebhookUrl;
|
exports.getWebhookUrl = getWebhookUrl;
|
||||||
exports.getWorkflowStructureExample = getWorkflowStructureExample;
|
exports.getWorkflowStructureExample = getWorkflowStructureExample;
|
||||||
exports.getWorkflowFixSuggestions = getWorkflowFixSuggestions;
|
exports.getWorkflowFixSuggestions = getWorkflowFixSuggestions;
|
||||||
|
const crypto_1 = __importDefault(require("crypto"));
|
||||||
const zod_1 = require("zod");
|
const zod_1 = require("zod");
|
||||||
const node_type_utils_1 = require("../utils/node-type-utils");
|
const node_type_utils_1 = require("../utils/node-type-utils");
|
||||||
const node_classification_1 = require("../utils/node-classification");
|
const node_classification_1 = require("../utils/node-classification");
|
||||||
@@ -76,11 +80,27 @@ function validateWorkflowConnections(connections) {
|
|||||||
function validateWorkflowSettings(settings) {
|
function validateWorkflowSettings(settings) {
|
||||||
return exports.workflowSettingsSchema.parse(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) {
|
function cleanWorkflowForCreate(workflow) {
|
||||||
const { id, createdAt, updatedAt, versionId, meta, active, tags, ...cleanedWorkflow } = workflow;
|
const { id, createdAt, updatedAt, versionId, meta, active, tags, ...cleanedWorkflow } = workflow;
|
||||||
if (!cleanedWorkflow.settings || Object.keys(cleanedWorkflow.settings).length === 0) {
|
if (!cleanedWorkflow.settings || Object.keys(cleanedWorkflow.settings).length === 0) {
|
||||||
cleanedWorkflow.settings = exports.defaultWorkflowSettings;
|
cleanedWorkflow.settings = exports.defaultWorkflowSettings;
|
||||||
}
|
}
|
||||||
|
ensureWebhookIds(cleanedWorkflow.nodes);
|
||||||
return cleanedWorkflow;
|
return cleanedWorkflow;
|
||||||
}
|
}
|
||||||
function cleanWorkflowForUpdate(workflow) {
|
function cleanWorkflowForUpdate(workflow) {
|
||||||
@@ -116,6 +136,7 @@ function cleanWorkflowForUpdate(workflow) {
|
|||||||
else {
|
else {
|
||||||
cleanedWorkflow.settings = { executionOrder: 'v1' };
|
cleanedWorkflow.settings = { executionOrder: 'v1' };
|
||||||
}
|
}
|
||||||
|
ensureWebhookIds(cleanedWorkflow.nodes);
|
||||||
return cleanedWorkflow;
|
return cleanedWorkflow;
|
||||||
}
|
}
|
||||||
function validateWorkflowStructure(workflow) {
|
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",
|
"name": "n8n-mcp",
|
||||||
"version": "2.40.2",
|
"version": "2.41.0",
|
||||||
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
@@ -152,17 +152,17 @@
|
|||||||
"vitest": "^3.2.4"
|
"vitest": "^3.2.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
"@modelcontextprotocol/sdk": "1.28.0",
|
||||||
"@n8n/n8n-nodes-langchain": "^2.12.0",
|
"@n8n/n8n-nodes-langchain": "^2.13.1",
|
||||||
"@supabase/supabase-js": "^2.57.4",
|
"@supabase/supabase-js": "^2.57.4",
|
||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.5.0",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"express-rate-limit": "^7.1.5",
|
"express-rate-limit": "^7.1.5",
|
||||||
"form-data": "^4.0.5",
|
"form-data": "^4.0.5",
|
||||||
"lru-cache": "^11.2.1",
|
"lru-cache": "^11.2.1",
|
||||||
"n8n": "^2.12.3",
|
"n8n": "^2.13.3",
|
||||||
"n8n-core": "^2.12.0",
|
"n8n-core": "^2.13.1",
|
||||||
"n8n-workflow": "^2.12.0",
|
"n8n-workflow": "^2.13.1",
|
||||||
"openai": "^4.77.0",
|
"openai": "^4.77.0",
|
||||||
"sql.js": "^1.13.0",
|
"sql.js": "^1.13.0",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ const DEFAULT_CONFIG: Required<Omit<DocumentationGeneratorConfig, 'baseUrl' | 't
|
|||||||
*/
|
*/
|
||||||
export class DocumentationGenerator {
|
export class DocumentationGenerator {
|
||||||
private client: OpenAI;
|
private client: OpenAI;
|
||||||
|
private baseUrl: string;
|
||||||
|
private apiKey: string;
|
||||||
private model: string;
|
private model: string;
|
||||||
private maxTokens: number;
|
private maxTokens: number;
|
||||||
private timeout: number;
|
private timeout: number;
|
||||||
@@ -85,6 +87,8 @@ export class DocumentationGenerator {
|
|||||||
constructor(config: DocumentationGeneratorConfig) {
|
constructor(config: DocumentationGeneratorConfig) {
|
||||||
const fullConfig = { ...DEFAULT_CONFIG, ...config };
|
const fullConfig = { ...DEFAULT_CONFIG, ...config };
|
||||||
|
|
||||||
|
this.baseUrl = config.baseUrl;
|
||||||
|
this.apiKey = fullConfig.apiKey;
|
||||||
this.client = new OpenAI({
|
this.client = new OpenAI({
|
||||||
baseURL: config.baseUrl,
|
baseURL: config.baseUrl,
|
||||||
apiKey: fullConfig.apiKey,
|
apiKey: fullConfig.apiKey,
|
||||||
@@ -103,21 +107,10 @@ export class DocumentationGenerator {
|
|||||||
try {
|
try {
|
||||||
const prompt = this.buildPrompt(input);
|
const prompt = this.buildPrompt(input);
|
||||||
|
|
||||||
const completion = await this.client.chat.completions.create({
|
const completion = await this.chatCompletion([
|
||||||
model: this.model,
|
{ role: 'system', content: this.getSystemPrompt() },
|
||||||
max_completion_tokens: this.maxTokens,
|
{ role: 'user', content: prompt },
|
||||||
...(this.temperature !== undefined ? { temperature: this.temperature } : {}),
|
], this.maxTokens);
|
||||||
messages: [
|
|
||||||
{
|
|
||||||
role: 'system',
|
|
||||||
content: this.getSystemPrompt(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
role: 'user',
|
|
||||||
content: prompt,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const content = completion.choices[0]?.message?.content;
|
const content = completion.choices[0]?.message?.content;
|
||||||
if (!content) {
|
if (!content) {
|
||||||
@@ -246,20 +239,23 @@ Guidelines:
|
|||||||
* Extract JSON from LLM response (handles markdown code blocks)
|
* Extract JSON from LLM response (handles markdown code blocks)
|
||||||
*/
|
*/
|
||||||
private extractJson(content: string): string {
|
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
|
// 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) {
|
if (jsonBlockMatch) {
|
||||||
return jsonBlockMatch[1].trim();
|
return jsonBlockMatch[1].trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to find JSON object directly
|
// Try to find JSON object directly
|
||||||
const jsonMatch = content.match(/\{[\s\S]*\}/);
|
const jsonMatch = stripped.match(/\{[\s\S]*\}/);
|
||||||
if (jsonMatch) {
|
if (jsonMatch) {
|
||||||
return jsonMatch[0];
|
return jsonMatch[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return as-is if no extraction needed
|
// Return as-is if no extraction needed
|
||||||
return content.trim();
|
return stripped;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -323,16 +319,9 @@ Guidelines:
|
|||||||
*/
|
*/
|
||||||
async testConnection(): Promise<{ success: boolean; message: string }> {
|
async testConnection(): Promise<{ success: boolean; message: string }> {
|
||||||
try {
|
try {
|
||||||
const completion = await this.client.chat.completions.create({
|
const completion = await this.chatCompletion([
|
||||||
model: this.model,
|
{ role: 'user', content: 'Hello' },
|
||||||
max_completion_tokens: 200,
|
], 200);
|
||||||
messages: [
|
|
||||||
{
|
|
||||||
role: 'user',
|
|
||||||
content: 'Hello',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (completion.choices[0]?.message?.content) {
|
if (completion.choices[0]?.message?.content) {
|
||||||
return { success: true, message: `Connected to ${this.model}` };
|
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> {
|
private sleep(ms: number): Promise<void> {
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ export class NodeRepository {
|
|||||||
* Supports both core and community nodes via optional community fields
|
* Supports both core and community nodes via optional community fields
|
||||||
*/
|
*/
|
||||||
saveNode(node: ParsedNode & Partial<CommunityNodeFields>): void {
|
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(`
|
const stmt = this.db.prepare(`
|
||||||
INSERT OR REPLACE INTO nodes (
|
INSERT OR REPLACE INTO nodes (
|
||||||
node_type, package_name, display_name, description,
|
node_type, package_name, display_name, description,
|
||||||
@@ -43,8 +48,9 @@ export class NodeRepository {
|
|||||||
properties_schema, operations, credentials_required,
|
properties_schema, operations, credentials_required,
|
||||||
outputs, output_names,
|
outputs, output_names,
|
||||||
is_community, is_verified, author_name, author_github_url,
|
is_community, is_verified, author_name, author_github_url,
|
||||||
npm_package_name, npm_version, npm_downloads, community_fetched_at
|
npm_package_name, npm_version, npm_downloads, community_fetched_at,
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
npm_readme, ai_documentation_summary, ai_summary_generated_at
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
stmt.run(
|
stmt.run(
|
||||||
@@ -76,7 +82,11 @@ export class NodeRepository {
|
|||||||
node.npmPackageName || null,
|
node.npmPackageName || null,
|
||||||
node.npmVersion || null,
|
node.npmVersion || null,
|
||||||
node.npmDownloads || 0,
|
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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -254,6 +254,22 @@ export class SingleSessionHTTPServer {
|
|||||||
return Boolean(sessionId && sessionId.length > 0);
|
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
|
* Sanitize error information for client responses
|
||||||
*/
|
*/
|
||||||
@@ -614,6 +630,22 @@ export class SingleSessionHTTPServer {
|
|||||||
logger.info('handleRequest: Reusing existing transport for session', { sessionId });
|
logger.info('handleRequest: Reusing existing transport for session', { sessionId });
|
||||||
transport = this.transports[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
|
// In multi-tenant shared mode, update instance context if provided
|
||||||
const isMultiTenantEnabled = process.env.ENABLE_MULTI_TENANT === 'true';
|
const isMultiTenantEnabled = process.env.ENABLE_MULTI_TENANT === 'true';
|
||||||
const sessionStrategy = process.env.MULTI_TENANT_SESSION_STRATEGY || 'instance';
|
const sessionStrategy = process.env.MULTI_TENANT_SESSION_STRATEGY || 'instance';
|
||||||
@@ -627,7 +659,17 @@ export class SingleSessionHTTPServer {
|
|||||||
this.updateSessionAccess(sessionId);
|
this.updateSessionAccess(sessionId);
|
||||||
|
|
||||||
} else {
|
} 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 = {
|
const errorDetails = {
|
||||||
hasSessionId: !!sessionId,
|
hasSessionId: !!sessionId,
|
||||||
isInitialize: isInitialize,
|
isInitialize: isInitialize,
|
||||||
|
|||||||
@@ -2834,10 +2834,13 @@ export async function handleUpdateTable(args: unknown, context?: InstanceContext
|
|||||||
const client = ensureApiConfigured(context);
|
const client = ensureApiConfigured(context);
|
||||||
const { tableId, name } = updateTableSchema.parse(args);
|
const { tableId, name } = updateTableSchema.parse(args);
|
||||||
const dataTable = await client.updateDataTable(tableId, { name });
|
const dataTable = await client.updateDataTable(tableId, { name });
|
||||||
|
const rawArgs = args as Record<string, unknown>;
|
||||||
|
const hasColumns = rawArgs && typeof rawArgs === 'object' && 'columns' in rawArgs;
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
data: dataTable,
|
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) {
|
} catch (error) {
|
||||||
return handleDataTableError(error);
|
return handleDataTableError(error);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const n8nManageDatatableDoc: ToolDocumentation = {
|
|||||||
'Use dryRun: true to preview update/upsert/delete before applying',
|
'Use dryRun: true to preview update/upsert/delete before applying',
|
||||||
'Filter supports: eq, neq, like, ilike, gt, gte, lt, lte conditions',
|
'Filter supports: eq, neq, like, ilike, gt, gte, lt, lte conditions',
|
||||||
'Use returnData: true to get affected rows back from update/upsert/delete',
|
'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: {
|
full: {
|
||||||
@@ -96,8 +96,6 @@ export const n8nManageDatatableDoc: ToolDocumentation = {
|
|||||||
'Use sortBy for deterministic row ordering',
|
'Use sortBy for deterministic row ordering',
|
||||||
],
|
],
|
||||||
pitfalls: [
|
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',
|
'deleteTable permanently deletes all rows — cannot be undone',
|
||||||
'deleteRows requires a filter — cannot delete all rows without one',
|
'deleteRows requires a filter — cannot delete all rows without one',
|
||||||
'Column types cannot be changed after table creation via API',
|
'Column types cannot be changed after table creation via API',
|
||||||
|
|||||||
@@ -609,7 +609,7 @@ export const n8nManagementTools: ToolDefinition[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'n8n_manage_datatable',
|
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: {
|
inputSchema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
@@ -619,10 +619,10 @@ export const n8nManagementTools: ToolDefinition[] = [
|
|||||||
description: 'Operation to perform',
|
description: 'Operation to perform',
|
||||||
},
|
},
|
||||||
tableId: { type: 'string', description: 'Data table ID (required for all actions except createTable and listTables)' },
|
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: {
|
columns: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
description: 'For createTable: column definitions',
|
description: 'For createTable only: column definitions (schema is immutable after creation via public API)',
|
||||||
items: {
|
items: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
|
|||||||
@@ -3,13 +3,14 @@
|
|||||||
* Fetch community nodes from n8n Strapi API and npm registry.
|
* Fetch community nodes from n8n Strapi API and npm registry.
|
||||||
*
|
*
|
||||||
* Usage:
|
* 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:verified # Verified nodes only (fast)
|
||||||
* npm run fetch:community:update # Incremental update (skip existing)
|
* npm run fetch:community:update # Incremental update (skip existing)
|
||||||
*
|
*
|
||||||
* Options:
|
* Options:
|
||||||
* --verified-only Only fetch verified nodes from Strapi API
|
* --verified-only Only fetch verified nodes from Strapi API
|
||||||
* --update Skip nodes that already exist in database
|
* --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)
|
* --npm-limit=N Maximum number of npm packages to fetch (default: 100)
|
||||||
* --staging Use staging Strapi API instead of production
|
* --staging Use staging Strapi API instead of production
|
||||||
*/
|
*/
|
||||||
@@ -22,6 +23,7 @@ import { createDatabaseAdapter } from '../database/database-adapter';
|
|||||||
interface CliOptions {
|
interface CliOptions {
|
||||||
verifiedOnly: boolean;
|
verifiedOnly: boolean;
|
||||||
update: boolean;
|
update: boolean;
|
||||||
|
rebuild: boolean;
|
||||||
npmLimit: number;
|
npmLimit: number;
|
||||||
staging: boolean;
|
staging: boolean;
|
||||||
}
|
}
|
||||||
@@ -32,6 +34,7 @@ function parseArgs(): CliOptions {
|
|||||||
const options: CliOptions = {
|
const options: CliOptions = {
|
||||||
verifiedOnly: false,
|
verifiedOnly: false,
|
||||||
update: false,
|
update: false,
|
||||||
|
rebuild: false,
|
||||||
npmLimit: 100,
|
npmLimit: 100,
|
||||||
staging: false,
|
staging: false,
|
||||||
};
|
};
|
||||||
@@ -41,6 +44,8 @@ function parseArgs(): CliOptions {
|
|||||||
options.verifiedOnly = true;
|
options.verifiedOnly = true;
|
||||||
} else if (arg === '--update') {
|
} else if (arg === '--update') {
|
||||||
options.update = true;
|
options.update = true;
|
||||||
|
} else if (arg === '--rebuild') {
|
||||||
|
options.rebuild = true;
|
||||||
} else if (arg === '--staging') {
|
} else if (arg === '--staging') {
|
||||||
options.staging = true;
|
options.staging = true;
|
||||||
} else if (arg.startsWith('--npm-limit=')) {
|
} else if (arg.startsWith('--npm-limit=')) {
|
||||||
@@ -73,7 +78,7 @@ async function main(): Promise<void> {
|
|||||||
|
|
||||||
// Print options
|
// Print options
|
||||||
console.log('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'}`);
|
console.log(` - Verified only: ${cliOptions.verifiedOnly ? 'Yes' : 'No'}`);
|
||||||
if (!cliOptions.verifiedOnly) {
|
if (!cliOptions.verifiedOnly) {
|
||||||
console.log(` - npm package limit: ${cliOptions.npmLimit}`);
|
console.log(` - npm package limit: ${cliOptions.npmLimit}`);
|
||||||
@@ -92,9 +97,10 @@ async function main(): Promise<void> {
|
|||||||
const environment = cliOptions.staging ? 'staging' : 'production';
|
const environment = cliOptions.staging ? 'staging' : 'production';
|
||||||
const service = new CommunityNodeService(repository, environment);
|
const service = new CommunityNodeService(repository, environment);
|
||||||
|
|
||||||
// If not updating, delete existing community nodes
|
// Only delete existing community nodes when --rebuild is explicitly requested
|
||||||
if (!cliOptions.update) {
|
if (cliOptions.rebuild) {
|
||||||
console.log('\nClearing existing community nodes...');
|
console.log('\nClearing existing community nodes (--rebuild)...');
|
||||||
|
console.log(' WARNING: This wipes READMEs and AI summaries!');
|
||||||
const deleted = service.deleteCommunityNodes();
|
const deleted = service.deleteCommunityNodes();
|
||||||
console.log(` Deleted ${deleted} existing community nodes`);
|
console.log(` Deleted ${deleted} existing community nodes`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import crypto from 'crypto';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { WorkflowNode, WorkflowConnection, Workflow } from '../types/n8n-api';
|
import { WorkflowNode, WorkflowConnection, Workflow } from '../types/n8n-api';
|
||||||
import { isTriggerNode, isActivatableTrigger } from '../utils/node-type-utils';
|
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);
|
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
|
// Clean workflow data for API operations
|
||||||
export function cleanWorkflowForCreate(workflow: Partial<Workflow>): Partial<Workflow> {
|
export function cleanWorkflowForCreate(workflow: Partial<Workflow>): Partial<Workflow> {
|
||||||
const {
|
const {
|
||||||
@@ -109,6 +126,8 @@ export function cleanWorkflowForCreate(workflow: Partial<Workflow>): Partial<Wor
|
|||||||
cleanedWorkflow.settings = defaultWorkflowSettings;
|
cleanedWorkflow.settings = defaultWorkflowSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ensureWebhookIds(cleanedWorkflow.nodes);
|
||||||
|
|
||||||
return cleanedWorkflow;
|
return cleanedWorkflow;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,6 +213,8 @@ export function cleanWorkflowForUpdate(workflow: Workflow): Partial<Workflow> {
|
|||||||
cleanedWorkflow.settings = { executionOrder: 'v1' as const };
|
cleanedWorkflow.settings = { executionOrder: 'v1' as const };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ensureWebhookIds(cleanedWorkflow.nodes);
|
||||||
|
|
||||||
return cleanedWorkflow;
|
return cleanedWorkflow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ class InMemoryDatabaseAdapter implements DatabaseAdapter {
|
|||||||
|
|
||||||
class InMemoryPreparedStatement implements PreparedStatement {
|
class InMemoryPreparedStatement implements PreparedStatement {
|
||||||
run = vi.fn((...params: any[]): RunResult => {
|
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);
|
const node = this.paramsToNode(params);
|
||||||
this.adapter.saveNode(node);
|
this.adapter.saveNode(node);
|
||||||
return { changes: 1, lastInsertRowid: 1 };
|
return { changes: 1, lastInsertRowid: 1 };
|
||||||
@@ -100,6 +100,9 @@ class InMemoryPreparedStatement implements PreparedStatement {
|
|||||||
});
|
});
|
||||||
|
|
||||||
get = vi.fn((...params: any[]) => {
|
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 = ?')) {
|
if (this.sql.includes('SELECT * FROM nodes WHERE node_type = ?')) {
|
||||||
return this.adapter.getNode(params[0]);
|
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
|
// saveNode - INSERT OR REPLACE
|
||||||
if (this.sql.includes('INSERT OR REPLACE INTO nodes')) {
|
if (this.sql.includes('INSERT OR REPLACE INTO nodes')) {
|
||||||
this.run = vi.fn((...params: any[]): RunResult => {
|
this.run = vi.fn((...params: any[]): RunResult => {
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ class MockPreparedStatement implements PreparedStatement {
|
|||||||
this.get = vi.fn((nodeType: string) => this.mockData.get(`node:${nodeType}`));
|
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
|
// Configure all() for getAITools
|
||||||
if (sql.includes('WHERE is_ai_tool = 1')) {
|
if (sql.includes('WHERE is_ai_tool = 1')) {
|
||||||
this.all = vi.fn(() => this.mockData.get('ai_tools') || []);
|
this.all = vi.fn(() => this.mockData.get('ai_tools') || []);
|
||||||
@@ -123,7 +128,10 @@ describe('NodeRepository - Core Functionality', () => {
|
|||||||
null, // npmPackageName
|
null, // npmPackageName
|
||||||
null, // npmVersion
|
null, // npmVersion
|
||||||
0, // npmDownloads
|
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()
|
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 = {
|
mockDb = {
|
||||||
prepare: vi.fn().mockReturnValue(mockStatement),
|
prepare: vi.fn((sql: string) => {
|
||||||
|
if (sql.includes('SELECT npm_readme')) {
|
||||||
|
return selectStatement;
|
||||||
|
}
|
||||||
|
return mockStatement;
|
||||||
|
}),
|
||||||
transaction: vi.fn(),
|
transaction: vi.fn(),
|
||||||
exec: vi.fn(),
|
exec: vi.fn(),
|
||||||
close: vi.fn(),
|
close: vi.fn(),
|
||||||
@@ -55,18 +68,9 @@ describe('NodeRepository - Outputs Handling', () => {
|
|||||||
|
|
||||||
repository.saveNode(node);
|
repository.saveNode(node);
|
||||||
|
|
||||||
expect(mockDb.prepare).toHaveBeenCalledWith(`
|
expect(mockDb.prepare).toHaveBeenCalledWith(
|
||||||
INSERT OR REPLACE INTO nodes (
|
expect.stringContaining('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(mockStatement.run).toHaveBeenCalledWith(
|
expect(mockStatement.run).toHaveBeenCalledWith(
|
||||||
'nodes-base.splitInBatches',
|
'nodes-base.splitInBatches',
|
||||||
@@ -96,7 +100,10 @@ describe('NodeRepository - Outputs Handling', () => {
|
|||||||
null, // npm_package_name
|
null, // npm_package_name
|
||||||
null, // npm_version
|
null, // npm_version
|
||||||
0, // npm_downloads
|
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(),
|
status: vi.fn().mockReturnThis(),
|
||||||
json: vi.fn().mockReturnThis(),
|
json: vi.fn().mockReturnThis(),
|
||||||
send: vi.fn().mockReturnThis(),
|
send: vi.fn().mockReturnThis(),
|
||||||
|
end: vi.fn().mockReturnThis(),
|
||||||
setHeader: vi.fn((key: string, value: string) => {
|
setHeader: vi.fn((key: string, value: string) => {
|
||||||
headers[key.toLowerCase()] = value;
|
headers[key.toLowerCase()] = value;
|
||||||
}),
|
}),
|
||||||
@@ -1186,4 +1187,121 @@ describe('HTTP Server Session Management', () => {
|
|||||||
expect(sessionInfo.age).toBeGreaterThanOrEqual(0);
|
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.success).toBe(false);
|
||||||
expect(result.error).toBe('Update failed');
|
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 { z } from 'zod';
|
||||||
import { WorkflowNode, WorkflowConnection, Workflow } from '../../../src/types/n8n-api';
|
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('n8n-validation', () => {
|
||||||
describe('Zod Schemas', () => {
|
describe('Zod Schemas', () => {
|
||||||
describe('workflowNodeSchema', () => {
|
describe('workflowNodeSchema', () => {
|
||||||
@@ -301,6 +309,44 @@ describe('n8n-validation', () => {
|
|||||||
const cleaned = cleanWorkflowForCreate(workflow as Workflow);
|
const cleaned = cleanWorkflowForCreate(workflow as Workflow);
|
||||||
expect(cleaned.settings).toEqual(customSettings);
|
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', () => {
|
describe('cleanWorkflowForUpdate', () => {
|
||||||
@@ -533,6 +579,44 @@ describe('n8n-validation', () => {
|
|||||||
});
|
});
|
||||||
expect(cleaned.settings).not.toHaveProperty('someOtherProperty');
|
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