mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-02-05 21:13:07 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25cb8bb455 | ||
|
|
2713db6d10 | ||
|
|
f10772a9d2 | ||
|
|
808088f25e |
107
CHANGELOG.md
107
CHANGELOG.md
@@ -7,6 +7,113 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [2.31.6] - 2026-01-03
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
**Dependencies Update**
|
||||||
|
|
||||||
|
- Updated n8n from 2.1.4 to 2.1.5
|
||||||
|
- Updated n8n-core from 2.1.3 to 2.1.4
|
||||||
|
- Updated @n8n/n8n-nodes-langchain from 2.1.3 to 2.1.4
|
||||||
|
- Rebuilt node database with 540 nodes (434 from n8n-nodes-base, 106 from @n8n/n8n-nodes-langchain)
|
||||||
|
|
||||||
|
## [2.31.5] - 2026-01-02
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
**MCP Tool Annotations (PR #512)**
|
||||||
|
|
||||||
|
Added MCP tool annotations to all 20 tools following the [MCP specification](https://spec.modelcontextprotocol.io/specification/2025-03-26/server/tools/#annotations). These annotations help AI assistants understand tool behavior and capabilities.
|
||||||
|
|
||||||
|
**Annotations added:**
|
||||||
|
- `title`: Human-readable name for each tool
|
||||||
|
- `readOnlyHint`: True for tools that don't modify state (11 tools)
|
||||||
|
- `destructiveHint`: True for delete operations (3 tools)
|
||||||
|
- `idempotentHint`: True for operations that produce same result when called repeatedly (14 tools)
|
||||||
|
- `openWorldHint`: True for tools accessing external n8n API (13 tools)
|
||||||
|
|
||||||
|
**Documentation tools** (7): All marked `readOnlyHint=true`, `idempotentHint=true`
|
||||||
|
- `tools_documentation`, `search_nodes`, `get_node`, `validate_node`, `get_template`, `search_templates`, `validate_workflow`
|
||||||
|
|
||||||
|
**Management tools** (13): All marked `openWorldHint=true`
|
||||||
|
- Read-only: `n8n_get_workflow`, `n8n_list_workflows`, `n8n_validate_workflow`, `n8n_health_check`
|
||||||
|
- Idempotent updates: `n8n_update_full_workflow`, `n8n_update_partial_workflow`, `n8n_autofix_workflow`
|
||||||
|
- Destructive: `n8n_delete_workflow`, `n8n_executions` (delete action), `n8n_workflow_versions` (delete/truncate)
|
||||||
|
|
||||||
|
## [2.31.4] - 2026-01-02
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
**Workflow Data Mangled During Serialization: snake_case Conversion (Issue #517)**
|
||||||
|
|
||||||
|
Fixed a critical bug where workflow mutation data was corrupted during serialization to Supabase, making 98.9% of collected workflow data invalid for n8n API operations.
|
||||||
|
|
||||||
|
**Problem:**
|
||||||
|
The `toSnakeCase()` function in `batch-processor.ts` was applied **recursively** to the entire mutation object, including nested workflow data that should be preserved exactly as-is:
|
||||||
|
|
||||||
|
- **Connection keys mangled**: Node names like `"Webhook"` became `"_webhook"`, `"AI Agent"` became `"_a_i _agent"`
|
||||||
|
- **Node field names mangled**: n8n camelCase fields like `typeVersion`, `webhookId`, `onError` became `type_version`, `webhook_id`, `on_error`
|
||||||
|
|
||||||
|
**Root Cause:**
|
||||||
|
```javascript
|
||||||
|
// Old code - recursive conversion corrupted nested data
|
||||||
|
result[snakeKey] = toSnakeCase(obj[key]); // WRONG
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
Replaced recursive `toSnakeCase()` with selective `mutationToSupabaseFormat()` that:
|
||||||
|
- Converts **only** top-level field names to snake_case (for Supabase columns)
|
||||||
|
- Preserves all nested data (workflow JSON, operations, validations) **exactly as-is**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// New code - preserves nested workflow structure
|
||||||
|
for (const [key, value] of Object.entries(mutation)) {
|
||||||
|
result[keyToSnakeCase(key)] = value; // Value preserved as-is
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
- Workflow mutation data now maintains n8n API compatibility
|
||||||
|
- Deployability rate improved from ~21% to ~68%
|
||||||
|
- Added 3 regression tests to prevent future occurrences
|
||||||
|
|
||||||
|
## [2.31.3] - 2025-12-26
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
**Documentation Bug: Connection Keys Say "Node IDs" but Require "Node Names" (Issue #510)**
|
||||||
|
|
||||||
|
Fixed documentation that incorrectly stated connection keys should be "node IDs" when n8n actually requires "node names".
|
||||||
|
|
||||||
|
**Problem:**
|
||||||
|
The `n8n_create_workflow` documentation and examples showed using node IDs (e.g., `"webhook_1"`) as connection keys, but the validator requires node names (e.g., `"Webhook"`). This caused workflow creation failures and contributed to low success rates for AI-generated workflows.
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- Updated `tools-n8n-manager.ts` parameter description: "Keys are source node names (the name field, not id)"
|
||||||
|
- Updated `n8n-create-workflow.ts` documentation: "Keys are source node names (not IDs)"
|
||||||
|
- Fixed example to use `"Webhook"` and `"Slack"` instead of `"webhook_1"` and `"slack_1"`
|
||||||
|
- Clarified `get-template.ts` return description
|
||||||
|
|
||||||
|
**Before (incorrect):**
|
||||||
|
```javascript
|
||||||
|
connections: {
|
||||||
|
"webhook_1": { "main": [[{node: "slack_1", ...}]] } // WRONG
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**After (correct):**
|
||||||
|
```javascript
|
||||||
|
connections: {
|
||||||
|
"Webhook": { "main": [[{node: "Slack", ...}]] } // CORRECT
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
- AI models following documentation will now generate valid workflows
|
||||||
|
- Clear distinction between node `id` (internal identifier) and `name` (connection key)
|
||||||
|
- No breaking changes - validator behavior unchanged
|
||||||
|
|
||||||
## [2.31.2] - 2025-12-24
|
## [2.31.2] - 2025-12-24
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
[](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)
|
||||||
|
|
||||||
|
|||||||
BIN
data/nodes.db
BIN
data/nodes.db
Binary file not shown.
92
package-lock.json
generated
92
package-lock.json
generated
@@ -1,24 +1,24 @@
|
|||||||
{
|
{
|
||||||
"name": "n8n-mcp",
|
"name": "n8n-mcp",
|
||||||
"version": "2.31.1",
|
"version": "2.31.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "n8n-mcp",
|
"name": "n8n-mcp",
|
||||||
"version": "2.31.1",
|
"version": "2.31.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "1.20.1",
|
"@modelcontextprotocol/sdk": "1.20.1",
|
||||||
"@n8n/n8n-nodes-langchain": "^2.1.3",
|
"@n8n/n8n-nodes-langchain": "^2.1.4",
|
||||||
"@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.1.4",
|
"n8n": "^2.1.5",
|
||||||
"n8n-core": "^2.1.3",
|
"n8n-core": "^2.1.4",
|
||||||
"n8n-workflow": "^2.1.1",
|
"n8n-workflow": "^2.1.1",
|
||||||
"openai": "^4.77.0",
|
"openai": "^4.77.0",
|
||||||
"sql.js": "^1.13.0",
|
"sql.js": "^1.13.0",
|
||||||
@@ -7624,9 +7624,9 @@
|
|||||||
"license": "SEE LICENSE IN LICENSE.md"
|
"license": "SEE LICENSE IN LICENSE.md"
|
||||||
},
|
},
|
||||||
"node_modules/@n8n/db": {
|
"node_modules/@n8n/db": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@n8n/db/-/db-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@n8n/db/-/db-1.1.4.tgz",
|
||||||
"integrity": "sha512-KQyHIHdKD6B1cJ7L0uk81i5vkiFQ6vVNIGRrnhCwkWi2gB9ySyu2TuvCQj7MOVVlb/EWLSDj96QfaWVUCXKaFw==",
|
"integrity": "sha512-48wXccnSmyR1LcNU++7fiIvk7Th22bddDenLsehmPbnlwncnXW79UW7JarE5g1Hcu2IehbhIPgile7KYOBi4RQ==",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@n8n/api-types": "1.1.1",
|
"@n8n/api-types": "1.1.1",
|
||||||
@@ -7640,7 +7640,7 @@
|
|||||||
"class-validator": "0.14.0",
|
"class-validator": "0.14.0",
|
||||||
"flatted": "3.2.7",
|
"flatted": "3.2.7",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"n8n-core": "2.1.3",
|
"n8n-core": "2.1.4",
|
||||||
"n8n-workflow": "2.1.1",
|
"n8n-workflow": "2.1.1",
|
||||||
"nanoid": "3.3.8",
|
"nanoid": "3.3.8",
|
||||||
"p-lazy": "3.1.0",
|
"p-lazy": "3.1.0",
|
||||||
@@ -8158,9 +8158,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@n8n/db/node_modules/n8n-core": {
|
"node_modules/@n8n/db/node_modules/n8n-core": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/n8n-core/-/n8n-core-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/n8n-core/-/n8n-core-2.1.4.tgz",
|
||||||
"integrity": "sha512-22WjdE0Yj2HUGI0pSWBB8oWeI8ceVz/ApD84AOE5aZU8ElDyTfmfk7pTZnYe3C4/4LHmuyiS+Ain6JpYlApEgQ==",
|
"integrity": "sha512-n7PQIojUBDNU3o6IKz49ONAJpP2a6IqEr3S91D8/N9C89k7MGg3XXp8E52+vIc+zCKf91POhHvofZTxblmDC3g==",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "3.808.0",
|
"@aws-sdk/client-s3": "3.808.0",
|
||||||
@@ -9522,9 +9522,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@n8n/task-runner": {
|
"node_modules/@n8n/task-runner": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@n8n/task-runner/-/task-runner-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@n8n/task-runner/-/task-runner-2.1.4.tgz",
|
||||||
"integrity": "sha512-DQANE5b0sT0XCuStfjidKV+CEbO+FQdFeZYqN1/TV49TDbcy08Pa/FUnRWpfHgNdBVco+pmq5xLdoY7hRm2tfw==",
|
"integrity": "sha512-kWhPCDE5+H+DTHkEEWNSKu77wOcQvmF+mU06uEYIFWNYvUAycnUFXiW8K3pK89ePHNDXAaHGoFhuzihI6i77og==",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@n8n/config": "2.0.2",
|
"@n8n/config": "2.0.2",
|
||||||
@@ -9535,7 +9535,7 @@
|
|||||||
"acorn-walk": "8.3.4",
|
"acorn-walk": "8.3.4",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"luxon": "3.4.4",
|
"luxon": "3.4.4",
|
||||||
"n8n-core": "2.1.3",
|
"n8n-core": "2.1.4",
|
||||||
"n8n-workflow": "2.1.1",
|
"n8n-workflow": "2.1.1",
|
||||||
"nanoid": "3.3.8",
|
"nanoid": "3.3.8",
|
||||||
"ws": "^8.18.0"
|
"ws": "^8.18.0"
|
||||||
@@ -10055,9 +10055,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@n8n/task-runner/node_modules/n8n-core": {
|
"node_modules/@n8n/task-runner/node_modules/n8n-core": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/n8n-core/-/n8n-core-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/n8n-core/-/n8n-core-2.1.4.tgz",
|
||||||
"integrity": "sha512-22WjdE0Yj2HUGI0pSWBB8oWeI8ceVz/ApD84AOE5aZU8ElDyTfmfk7pTZnYe3C4/4LHmuyiS+Ain6JpYlApEgQ==",
|
"integrity": "sha512-n7PQIojUBDNU3o6IKz49ONAJpP2a6IqEr3S91D8/N9C89k7MGg3XXp8E52+vIc+zCKf91POhHvofZTxblmDC3g==",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "3.808.0",
|
"@aws-sdk/client-s3": "3.808.0",
|
||||||
@@ -23283,9 +23283,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/n8n": {
|
"node_modules/n8n": {
|
||||||
"version": "2.1.4",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/n8n/-/n8n-2.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/n8n/-/n8n-2.1.5.tgz",
|
||||||
"integrity": "sha512-mnGhIRIoaDnBIQd1ddDZBmfm9mVKNdhNPrgiYdzsSkG1QE6P4WI0ZUv3Z7Yus1tPK3QHtF4R0pGhzCOuifyfKA==",
|
"integrity": "sha512-6yRks7uzRjPx3ZQXLQkfArwF78TzvkfTUvfwTTKRnGLk4Yuvmx+GXwByCyB3YIU7Szzi+80gnasmramKyRYldw==",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-secrets-manager": "3.808.0",
|
"@aws-sdk/client-secrets-manager": "3.808.0",
|
||||||
@@ -23300,14 +23300,14 @@
|
|||||||
"@n8n/client-oauth2": "1.0.0-rc.0",
|
"@n8n/client-oauth2": "1.0.0-rc.0",
|
||||||
"@n8n/config": "2.0.2",
|
"@n8n/config": "2.0.2",
|
||||||
"@n8n/constants": "0.15.0",
|
"@n8n/constants": "0.15.0",
|
||||||
"@n8n/db": "1.1.3",
|
"@n8n/db": "1.1.4",
|
||||||
"@n8n/decorators": "1.1.1",
|
"@n8n/decorators": "1.1.1",
|
||||||
"@n8n/di": "0.10.0",
|
"@n8n/di": "0.10.0",
|
||||||
"@n8n/errors": "0.5.0",
|
"@n8n/errors": "0.5.0",
|
||||||
"@n8n/localtunnel": "3.0.0",
|
"@n8n/localtunnel": "3.0.0",
|
||||||
"@n8n/n8n-nodes-langchain": "2.1.3",
|
"@n8n/n8n-nodes-langchain": "2.1.4",
|
||||||
"@n8n/permissions": "0.44.0",
|
"@n8n/permissions": "0.44.0",
|
||||||
"@n8n/task-runner": "2.1.3",
|
"@n8n/task-runner": "2.1.4",
|
||||||
"@n8n/typeorm": "0.3.20-15",
|
"@n8n/typeorm": "0.3.20-15",
|
||||||
"@n8n/utils": "1.21.0",
|
"@n8n/utils": "1.21.0",
|
||||||
"@parcel/watcher": "^2.5.1",
|
"@parcel/watcher": "^2.5.1",
|
||||||
@@ -23348,9 +23348,9 @@
|
|||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"luxon": "3.4.4",
|
"luxon": "3.4.4",
|
||||||
"mysql2": "3.15.0",
|
"mysql2": "3.15.0",
|
||||||
"n8n-core": "2.1.3",
|
"n8n-core": "2.1.4",
|
||||||
"n8n-editor-ui": "2.1.2",
|
"n8n-editor-ui": "2.1.2",
|
||||||
"n8n-nodes-base": "2.1.3",
|
"n8n-nodes-base": "2.1.4",
|
||||||
"n8n-workflow": "2.1.1",
|
"n8n-workflow": "2.1.1",
|
||||||
"nanoid": "3.3.8",
|
"nanoid": "3.3.8",
|
||||||
"nodemailer": "7.0.11",
|
"nodemailer": "7.0.11",
|
||||||
@@ -24614,9 +24614,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/n8n/node_modules/@modelcontextprotocol/sdk/node_modules/zod-to-json-schema": {
|
"node_modules/n8n/node_modules/@modelcontextprotocol/sdk/node_modules/zod-to-json-schema": {
|
||||||
"version": "3.25.0",
|
"version": "3.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.0.tgz",
|
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz",
|
||||||
"integrity": "sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==",
|
"integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"zod": "^3.25 || ^4"
|
"zod": "^3.25 || ^4"
|
||||||
@@ -24634,9 +24634,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/n8n/node_modules/@n8n/n8n-nodes-langchain": {
|
"node_modules/n8n/node_modules/@n8n/n8n-nodes-langchain": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@n8n/n8n-nodes-langchain/-/n8n-nodes-langchain-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@n8n/n8n-nodes-langchain/-/n8n-nodes-langchain-2.1.4.tgz",
|
||||||
"integrity": "sha512-XT4bAs0OBZiBqr9TQSzcIXDf4kXQkF5avHSgo2HStoPlK/en/eQmWmBbTimm4B2mA6HwggOM8LsYEJDADG8TFw==",
|
"integrity": "sha512-6cFn7Zw3hUqvIF2lL/d+FFPFQHccIev5SE5d+P4n+d+l4Fal8EymnjvNy9+UKV9fsxJ4VK3RYtPr19bGu1CEng==",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sso-oidc": "3.808.0",
|
"@aws-sdk/client-sso-oidc": "3.808.0",
|
||||||
@@ -24698,7 +24698,7 @@
|
|||||||
"mammoth": "1.11.0",
|
"mammoth": "1.11.0",
|
||||||
"mime-types": "3.0.1",
|
"mime-types": "3.0.1",
|
||||||
"mongodb": "^6.17.0",
|
"mongodb": "^6.17.0",
|
||||||
"n8n-nodes-base": "2.1.3",
|
"n8n-nodes-base": "2.1.4",
|
||||||
"n8n-workflow": "2.1.1",
|
"n8n-workflow": "2.1.1",
|
||||||
"openai": "^6.9.0",
|
"openai": "^6.9.0",
|
||||||
"pdf-parse": "1.1.1",
|
"pdf-parse": "1.1.1",
|
||||||
@@ -25204,9 +25204,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/n8n/node_modules/@n8n/n8n-nodes-langchain/node_modules/@langchain/community/node_modules/zod": {
|
"node_modules/n8n/node_modules/@n8n/n8n-nodes-langchain/node_modules/@langchain/community/node_modules/zod": {
|
||||||
"version": "4.2.1",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.4.tgz",
|
||||||
"integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==",
|
"integrity": "sha512-Zw/uYiiyF6pUT1qmKbZziChgNPRu+ZRneAsMUDU6IwmXdWt5JwcUfy2bvLOCUtz5UniaN/Zx5aFttZYbYc7O/A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
@@ -26173,9 +26173,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/n8n/node_modules/langchain/node_modules/zod": {
|
"node_modules/n8n/node_modules/langchain/node_modules/zod": {
|
||||||
"version": "4.2.1",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.4.tgz",
|
||||||
"integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==",
|
"integrity": "sha512-Zw/uYiiyF6pUT1qmKbZziChgNPRu+ZRneAsMUDU6IwmXdWt5JwcUfy2bvLOCUtz5UniaN/Zx5aFttZYbYc7O/A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
@@ -26215,9 +26215,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/n8n/node_modules/n8n-core": {
|
"node_modules/n8n/node_modules/n8n-core": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/n8n-core/-/n8n-core-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/n8n-core/-/n8n-core-2.1.4.tgz",
|
||||||
"integrity": "sha512-22WjdE0Yj2HUGI0pSWBB8oWeI8ceVz/ApD84AOE5aZU8ElDyTfmfk7pTZnYe3C4/4LHmuyiS+Ain6JpYlApEgQ==",
|
"integrity": "sha512-n7PQIojUBDNU3o6IKz49ONAJpP2a6IqEr3S91D8/N9C89k7MGg3XXp8E52+vIc+zCKf91POhHvofZTxblmDC3g==",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "3.808.0",
|
"@aws-sdk/client-s3": "3.808.0",
|
||||||
@@ -26301,9 +26301,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/n8n/node_modules/n8n-nodes-base": {
|
"node_modules/n8n/node_modules/n8n-nodes-base": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/n8n-nodes-base/-/n8n-nodes-base-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/n8n-nodes-base/-/n8n-nodes-base-2.1.4.tgz",
|
||||||
"integrity": "sha512-Ll34OuiZ5fl4MUCxNVb2X6dPxcH24Hx3Cfv/teagIjjI88XcVdPtNCbJfZjqrunOyzA3O5MECrDvm5EQD+qrbA==",
|
"integrity": "sha512-6CW++gy0q7LgTM9UmxsNohlsBe6Q+QPEToiRYhqqLIvR8hn9wy2sr4cGsPWsflNAA28b8GKNj5+ueKZtq3Kd1w==",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sso-oidc": "3.808.0",
|
"@aws-sdk/client-sso-oidc": "3.808.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "n8n-mcp",
|
"name": "n8n-mcp",
|
||||||
"version": "2.31.2",
|
"version": "2.31.6",
|
||||||
"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",
|
||||||
@@ -141,15 +141,15 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "1.20.1",
|
"@modelcontextprotocol/sdk": "1.20.1",
|
||||||
"@n8n/n8n-nodes-langchain": "^2.1.3",
|
"@n8n/n8n-nodes-langchain": "^2.1.4",
|
||||||
"@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.1.4",
|
"n8n": "^2.1.5",
|
||||||
"n8n-core": "^2.1.3",
|
"n8n-core": "^2.1.4",
|
||||||
"n8n-workflow": "^2.1.1",
|
"n8n-workflow": "^2.1.1",
|
||||||
"openai": "^4.77.0",
|
"openai": "^4.77.0",
|
||||||
"sql.js": "^1.13.0",
|
"sql.js": "^1.13.0",
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export const getTemplateDoc: ToolDocumentation = {
|
|||||||
- url: Link to template on n8n.io
|
- url: Link to template on n8n.io
|
||||||
- workflow: Complete workflow JSON with structure:
|
- workflow: Complete workflow JSON with structure:
|
||||||
- nodes: Array of node objects (id, name, type, typeVersion, position, parameters)
|
- nodes: Array of node objects (id, name, type, typeVersion, position, parameters)
|
||||||
- connections: Object mapping source nodes to targets
|
- connections: Object mapping source node names to targets
|
||||||
- settings: Workflow configuration (timezone, error handling, etc.)
|
- settings: Workflow configuration (timezone, error handling, etc.)
|
||||||
- usage: Instructions for using the workflow`,
|
- usage: Instructions for using the workflow`,
|
||||||
examples: [
|
examples: [
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const n8nCreateWorkflowDoc: ToolDocumentation = {
|
|||||||
parameters: {
|
parameters: {
|
||||||
name: { type: 'string', required: true, description: 'Workflow name' },
|
name: { type: 'string', required: true, description: 'Workflow name' },
|
||||||
nodes: { type: 'array', required: true, description: 'Array of nodes with id, name, type, typeVersion, position, parameters' },
|
nodes: { type: 'array', required: true, description: 'Array of nodes with id, name, type, typeVersion, position, parameters' },
|
||||||
connections: { type: 'object', required: true, description: 'Node connections. Keys are source node IDs' },
|
connections: { type: 'object', required: true, description: 'Node connections. Keys are source node names (not IDs)' },
|
||||||
settings: { type: 'object', description: 'Optional workflow settings (timezone, error handling, etc.)' }
|
settings: { type: 'object', description: 'Optional workflow settings (timezone, error handling, etc.)' }
|
||||||
},
|
},
|
||||||
returns: 'Minimal summary (id, name, active, nodeCount) for token efficiency. Use n8n_get_workflow with mode "structure" to verify current state if needed.',
|
returns: 'Minimal summary (id, name, active, nodeCount) for token efficiency. Use n8n_get_workflow with mode "structure" to verify current state if needed.',
|
||||||
@@ -55,8 +55,8 @@ n8n_create_workflow({
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
connections: {
|
connections: {
|
||||||
"webhook_1": {
|
"Webhook": {
|
||||||
"main": [[{node: "slack_1", type: "main", index: 0}]]
|
"main": [[{node: "Slack", type: "main", index: 0}]]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})`,
|
})`,
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export const n8nManagementTools: ToolDefinition[] = [
|
|||||||
},
|
},
|
||||||
connections: {
|
connections: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
description: 'Workflow connections object. Keys are source node IDs, values define output connections'
|
description: 'Workflow connections object. Keys are source node names (the name field, not id), values define output connections'
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -66,7 +66,13 @@ export const n8nManagementTools: ToolDefinition[] = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
required: ['name', 'nodes', 'connections']
|
required: ['name', 'nodes', 'connections']
|
||||||
}
|
},
|
||||||
|
annotations: {
|
||||||
|
title: 'Create Workflow',
|
||||||
|
readOnlyHint: false,
|
||||||
|
destructiveHint: false,
|
||||||
|
openWorldHint: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'n8n_get_workflow',
|
name: 'n8n_get_workflow',
|
||||||
@@ -86,7 +92,13 @@ export const n8nManagementTools: ToolDefinition[] = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
required: ['id']
|
required: ['id']
|
||||||
}
|
},
|
||||||
|
annotations: {
|
||||||
|
title: 'Get Workflow',
|
||||||
|
readOnlyHint: true,
|
||||||
|
idempotentHint: true,
|
||||||
|
openWorldHint: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'n8n_update_full_workflow',
|
name: 'n8n_update_full_workflow',
|
||||||
@@ -120,7 +132,14 @@ export const n8nManagementTools: ToolDefinition[] = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
required: ['id']
|
required: ['id']
|
||||||
}
|
},
|
||||||
|
annotations: {
|
||||||
|
title: 'Update Full Workflow',
|
||||||
|
readOnlyHint: false,
|
||||||
|
destructiveHint: false,
|
||||||
|
idempotentHint: true,
|
||||||
|
openWorldHint: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'n8n_update_partial_workflow',
|
name: 'n8n_update_partial_workflow',
|
||||||
@@ -151,7 +170,14 @@ export const n8nManagementTools: ToolDefinition[] = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
required: ['id', 'operations']
|
required: ['id', 'operations']
|
||||||
}
|
},
|
||||||
|
annotations: {
|
||||||
|
title: 'Update Partial Workflow',
|
||||||
|
readOnlyHint: false,
|
||||||
|
destructiveHint: false,
|
||||||
|
idempotentHint: true,
|
||||||
|
openWorldHint: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'n8n_delete_workflow',
|
name: 'n8n_delete_workflow',
|
||||||
@@ -165,7 +191,13 @@ export const n8nManagementTools: ToolDefinition[] = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
required: ['id']
|
required: ['id']
|
||||||
}
|
},
|
||||||
|
annotations: {
|
||||||
|
title: 'Delete Workflow',
|
||||||
|
readOnlyHint: false,
|
||||||
|
destructiveHint: true,
|
||||||
|
openWorldHint: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'n8n_list_workflows',
|
name: 'n8n_list_workflows',
|
||||||
@@ -199,7 +231,13 @@ export const n8nManagementTools: ToolDefinition[] = [
|
|||||||
description: 'Exclude pinned data from response (default: true)'
|
description: 'Exclude pinned data from response (default: true)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
annotations: {
|
||||||
|
title: 'List Workflows',
|
||||||
|
readOnlyHint: true,
|
||||||
|
idempotentHint: true,
|
||||||
|
openWorldHint: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'n8n_validate_workflow',
|
name: 'n8n_validate_workflow',
|
||||||
@@ -236,7 +274,13 @@ export const n8nManagementTools: ToolDefinition[] = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
required: ['id']
|
required: ['id']
|
||||||
}
|
},
|
||||||
|
annotations: {
|
||||||
|
title: 'Validate Workflow',
|
||||||
|
readOnlyHint: true,
|
||||||
|
idempotentHint: true,
|
||||||
|
openWorldHint: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'n8n_autofix_workflow',
|
name: 'n8n_autofix_workflow',
|
||||||
@@ -271,7 +315,14 @@ export const n8nManagementTools: ToolDefinition[] = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
required: ['id']
|
required: ['id']
|
||||||
}
|
},
|
||||||
|
annotations: {
|
||||||
|
title: 'Autofix Workflow',
|
||||||
|
readOnlyHint: false,
|
||||||
|
destructiveHint: false,
|
||||||
|
idempotentHint: true,
|
||||||
|
openWorldHint: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Execution Management Tools
|
// Execution Management Tools
|
||||||
@@ -328,7 +379,13 @@ export const n8nManagementTools: ToolDefinition[] = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
required: ['workflowId']
|
required: ['workflowId']
|
||||||
}
|
},
|
||||||
|
annotations: {
|
||||||
|
title: 'Test Workflow',
|
||||||
|
readOnlyHint: false,
|
||||||
|
destructiveHint: false,
|
||||||
|
openWorldHint: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'n8n_executions',
|
name: 'n8n_executions',
|
||||||
@@ -410,7 +467,13 @@ export const n8nManagementTools: ToolDefinition[] = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
required: ['action']
|
required: ['action']
|
||||||
}
|
},
|
||||||
|
annotations: {
|
||||||
|
title: 'Manage Executions',
|
||||||
|
readOnlyHint: false,
|
||||||
|
destructiveHint: true,
|
||||||
|
openWorldHint: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// System Tools
|
// System Tools
|
||||||
@@ -431,7 +494,13 @@ export const n8nManagementTools: ToolDefinition[] = [
|
|||||||
description: 'Include extra details in diagnostic mode (default: false)'
|
description: 'Include extra details in diagnostic mode (default: false)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
annotations: {
|
||||||
|
title: 'Health Check',
|
||||||
|
readOnlyHint: true,
|
||||||
|
idempotentHint: true,
|
||||||
|
openWorldHint: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'n8n_workflow_versions',
|
name: 'n8n_workflow_versions',
|
||||||
@@ -485,7 +554,13 @@ export const n8nManagementTools: ToolDefinition[] = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
required: ['mode']
|
required: ['mode']
|
||||||
}
|
},
|
||||||
|
annotations: {
|
||||||
|
title: 'Workflow Versions',
|
||||||
|
readOnlyHint: false,
|
||||||
|
destructiveHint: true,
|
||||||
|
openWorldHint: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Template Deployment Tool
|
// Template Deployment Tool
|
||||||
@@ -520,6 +595,12 @@ export const n8nManagementTools: ToolDefinition[] = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
required: ['templateId']
|
required: ['templateId']
|
||||||
}
|
},
|
||||||
|
annotations: {
|
||||||
|
title: 'Deploy Template',
|
||||||
|
readOnlyHint: false,
|
||||||
|
destructiveHint: false,
|
||||||
|
openWorldHint: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@@ -25,6 +25,11 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
annotations: {
|
||||||
|
title: 'Tools Documentation',
|
||||||
|
readOnlyHint: true,
|
||||||
|
idempotentHint: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'search_nodes',
|
name: 'search_nodes',
|
||||||
@@ -55,6 +60,11 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
|
|||||||
},
|
},
|
||||||
required: ['query'],
|
required: ['query'],
|
||||||
},
|
},
|
||||||
|
annotations: {
|
||||||
|
title: 'Search Nodes',
|
||||||
|
readOnlyHint: true,
|
||||||
|
idempotentHint: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'get_node',
|
name: 'get_node',
|
||||||
@@ -108,6 +118,11 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
|
|||||||
},
|
},
|
||||||
required: ['nodeType'],
|
required: ['nodeType'],
|
||||||
},
|
},
|
||||||
|
annotations: {
|
||||||
|
title: 'Get Node Info',
|
||||||
|
readOnlyHint: true,
|
||||||
|
idempotentHint: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'validate_node',
|
name: 'validate_node',
|
||||||
@@ -188,6 +203,11 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
|
|||||||
},
|
},
|
||||||
required: ['nodeType', 'displayName', 'valid']
|
required: ['nodeType', 'displayName', 'valid']
|
||||||
},
|
},
|
||||||
|
annotations: {
|
||||||
|
title: 'Validate Node Config',
|
||||||
|
readOnlyHint: true,
|
||||||
|
idempotentHint: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'get_template',
|
name: 'get_template',
|
||||||
@@ -208,6 +228,11 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
|
|||||||
},
|
},
|
||||||
required: ['templateId'],
|
required: ['templateId'],
|
||||||
},
|
},
|
||||||
|
annotations: {
|
||||||
|
title: 'Get Template',
|
||||||
|
readOnlyHint: true,
|
||||||
|
idempotentHint: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'search_templates',
|
name: 'search_templates',
|
||||||
@@ -303,6 +328,11 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
annotations: {
|
||||||
|
title: 'Search Templates',
|
||||||
|
readOnlyHint: true,
|
||||||
|
idempotentHint: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'validate_workflow',
|
name: 'validate_workflow',
|
||||||
@@ -388,6 +418,11 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
|
|||||||
},
|
},
|
||||||
required: ['valid', 'summary']
|
required: ['valid', 'summary']
|
||||||
},
|
},
|
||||||
|
annotations: {
|
||||||
|
title: 'Validate Workflow',
|
||||||
|
readOnlyHint: true,
|
||||||
|
idempotentHint: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -9,23 +9,34 @@ import { TelemetryError, TelemetryErrorType, TelemetryCircuitBreaker } from './t
|
|||||||
import { logger } from '../utils/logger';
|
import { logger } from '../utils/logger';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert camelCase object keys to snake_case
|
* Convert camelCase key to snake_case
|
||||||
* Needed because Supabase PostgREST doesn't auto-convert
|
|
||||||
*/
|
*/
|
||||||
function toSnakeCase(obj: any): any {
|
function keyToSnakeCase(key: string): string {
|
||||||
if (obj === null || obj === undefined) return obj;
|
return key.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
|
||||||
if (Array.isArray(obj)) return obj.map(toSnakeCase);
|
}
|
||||||
if (typeof obj !== 'object') return obj;
|
|
||||||
|
|
||||||
const result: any = {};
|
/**
|
||||||
for (const key in obj) {
|
* Convert WorkflowMutationRecord to Supabase-compatible format.
|
||||||
if (obj.hasOwnProperty(key)) {
|
*
|
||||||
// Convert camelCase to snake_case
|
* IMPORTANT: Only converts top-level field names to snake_case.
|
||||||
const snakeKey = key.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
|
* Nested workflow data (workflowBefore, workflowAfter, operations, etc.)
|
||||||
// Recursively convert nested objects
|
* is preserved EXACTLY as-is to maintain n8n API compatibility.
|
||||||
result[snakeKey] = toSnakeCase(obj[key]);
|
*
|
||||||
}
|
* The Supabase workflow_mutations table stores workflow_before and
|
||||||
|
* workflow_after as JSONB columns, which preserve the original structure.
|
||||||
|
* Only the top-level columns (user_id, session_id, etc.) require snake_case.
|
||||||
|
*
|
||||||
|
* Issue #517: Previously this used recursive conversion which mangled:
|
||||||
|
* - Connection keys (node names like "Webhook" → "_webhook")
|
||||||
|
* - Node field names (typeVersion → type_version)
|
||||||
|
*/
|
||||||
|
function mutationToSupabaseFormat(mutation: WorkflowMutationRecord): Record<string, any> {
|
||||||
|
const result: Record<string, any> = {};
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(mutation)) {
|
||||||
|
result[keyToSnakeCase(key)] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +277,7 @@ export class TelemetryBatchProcessor {
|
|||||||
for (const batch of batches) {
|
for (const batch of batches) {
|
||||||
const result = await this.executeWithRetry(async () => {
|
const result = await this.executeWithRetry(async () => {
|
||||||
// Convert camelCase to snake_case for Supabase
|
// Convert camelCase to snake_case for Supabase
|
||||||
const snakeCaseBatch = batch.map(mutation => toSnakeCase(mutation));
|
const snakeCaseBatch = batch.map(mutation => mutationToSupabaseFormat(mutation));
|
||||||
|
|
||||||
const { error } = await this.supabase!
|
const { error } = await this.supabase!
|
||||||
.from('workflow_mutations')
|
.from('workflow_mutations')
|
||||||
|
|||||||
@@ -10,6 +10,23 @@ export interface MCPServerConfig {
|
|||||||
authToken?: string;
|
authToken?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MCP Tool annotations to help AI assistants understand tool behavior.
|
||||||
|
* Per MCP spec: https://spec.modelcontextprotocol.io/specification/2025-03-26/server/tools/#annotations
|
||||||
|
*/
|
||||||
|
export interface ToolAnnotations {
|
||||||
|
/** Human-readable title for the tool */
|
||||||
|
title?: string;
|
||||||
|
/** If true, the tool does not modify its environment */
|
||||||
|
readOnlyHint?: boolean;
|
||||||
|
/** If true, the tool may perform destructive updates to its environment */
|
||||||
|
destructiveHint?: boolean;
|
||||||
|
/** If true, calling the tool repeatedly with the same arguments has no additional effect */
|
||||||
|
idempotentHint?: boolean;
|
||||||
|
/** If true, the tool may interact with external entities (APIs, services) */
|
||||||
|
openWorldHint?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ToolDefinition {
|
export interface ToolDefinition {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
@@ -25,6 +42,8 @@ export interface ToolDefinition {
|
|||||||
required?: string[];
|
required?: string[];
|
||||||
additionalProperties?: boolean | Record<string, any>;
|
additionalProperties?: boolean | Record<string, any>;
|
||||||
};
|
};
|
||||||
|
/** Tool behavior hints for AI assistants */
|
||||||
|
annotations?: ToolAnnotations;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResourceDefinition {
|
export interface ResourceDefinition {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { describe, it, expect, beforeEach, vi, afterEach, beforeAll, afterAll, type MockInstance } from 'vitest';
|
import { describe, it, expect, beforeEach, vi, afterEach, beforeAll, afterAll, type MockInstance } from 'vitest';
|
||||||
import { TelemetryBatchProcessor } from '../../../src/telemetry/batch-processor';
|
import { TelemetryBatchProcessor } from '../../../src/telemetry/batch-processor';
|
||||||
import { TelemetryEvent, WorkflowTelemetry, TELEMETRY_CONFIG } from '../../../src/telemetry/telemetry-types';
|
import { TelemetryEvent, WorkflowTelemetry, WorkflowMutationRecord, TELEMETRY_CONFIG } from '../../../src/telemetry/telemetry-types';
|
||||||
import { TelemetryError, TelemetryErrorType } from '../../../src/telemetry/telemetry-error';
|
import { TelemetryError, TelemetryErrorType } from '../../../src/telemetry/telemetry-error';
|
||||||
|
import { IntentClassification, MutationToolName } from '../../../src/telemetry/mutation-types';
|
||||||
|
import { AddNodeOperation } from '../../../src/types/workflow-diff';
|
||||||
import type { SupabaseClient } from '@supabase/supabase-js';
|
import type { SupabaseClient } from '@supabase/supabase-js';
|
||||||
|
|
||||||
// Mock logger to avoid console output in tests
|
// Mock logger to avoid console output in tests
|
||||||
@@ -679,4 +681,258 @@ describe('TelemetryBatchProcessor', () => {
|
|||||||
expect(mockProcessExit).toHaveBeenCalledWith(0);
|
expect(mockProcessExit).toHaveBeenCalledWith(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Issue #517: workflow data preservation', () => {
|
||||||
|
// This test verifies that workflow mutation data is NOT recursively converted to snake_case
|
||||||
|
// Previously, the toSnakeCase function was applied recursively which caused:
|
||||||
|
// - Connection keys like "Webhook" to become "_webhook"
|
||||||
|
// - Node fields like "typeVersion" to become "type_version"
|
||||||
|
|
||||||
|
it('should preserve connection keys exactly as-is (node names)', async () => {
|
||||||
|
const mutation: WorkflowMutationRecord = {
|
||||||
|
userId: 'user1',
|
||||||
|
sessionId: 'session1',
|
||||||
|
workflowBefore: {
|
||||||
|
nodes: [],
|
||||||
|
connections: {}
|
||||||
|
},
|
||||||
|
workflowAfter: {
|
||||||
|
nodes: [
|
||||||
|
{ id: '1', name: 'Webhook', type: 'n8n-nodes-base.webhook', typeVersion: 1, position: [0, 0], parameters: {} }
|
||||||
|
],
|
||||||
|
// Connection keys are NODE NAMES - must be preserved exactly
|
||||||
|
connections: {
|
||||||
|
'Webhook': { main: [[{ node: 'AI Agent', type: 'main', index: 0 }]] },
|
||||||
|
'AI Agent': { main: [[{ node: 'HTTP Request', type: 'main', index: 0 }]] },
|
||||||
|
'HTTP Request': { main: [[{ node: 'Send Email', type: 'main', index: 0 }]] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
workflowHashBefore: 'hash1',
|
||||||
|
workflowHashAfter: 'hash2',
|
||||||
|
userIntent: 'Test',
|
||||||
|
intentClassification: IntentClassification.ADD_FUNCTIONALITY,
|
||||||
|
toolName: MutationToolName.UPDATE_PARTIAL,
|
||||||
|
operations: [],
|
||||||
|
operationCount: 0,
|
||||||
|
operationTypes: [],
|
||||||
|
validationImproved: null,
|
||||||
|
errorsResolved: 0,
|
||||||
|
errorsIntroduced: 0,
|
||||||
|
nodesAdded: 1,
|
||||||
|
nodesRemoved: 0,
|
||||||
|
nodesModified: 0,
|
||||||
|
connectionsAdded: 3,
|
||||||
|
connectionsRemoved: 0,
|
||||||
|
propertiesChanged: 0,
|
||||||
|
mutationSuccess: true,
|
||||||
|
durationMs: 100
|
||||||
|
};
|
||||||
|
|
||||||
|
let capturedData: any = null;
|
||||||
|
vi.mocked(mockSupabase.from).mockImplementation((table) => ({
|
||||||
|
insert: vi.fn().mockImplementation((data) => {
|
||||||
|
if (table === 'workflow_mutations') {
|
||||||
|
capturedData = data;
|
||||||
|
}
|
||||||
|
return Promise.resolve(createMockSupabaseResponse());
|
||||||
|
}),
|
||||||
|
url: { href: '' },
|
||||||
|
headers: {},
|
||||||
|
select: vi.fn(),
|
||||||
|
upsert: vi.fn(),
|
||||||
|
update: vi.fn(),
|
||||||
|
delete: vi.fn()
|
||||||
|
} as any));
|
||||||
|
|
||||||
|
await batchProcessor.flush(undefined, undefined, [mutation]);
|
||||||
|
|
||||||
|
expect(capturedData).toBeDefined();
|
||||||
|
expect(capturedData).toHaveLength(1);
|
||||||
|
|
||||||
|
const savedMutation = capturedData[0];
|
||||||
|
|
||||||
|
// Top-level keys should be snake_case for Supabase
|
||||||
|
expect(savedMutation).toHaveProperty('user_id');
|
||||||
|
expect(savedMutation).toHaveProperty('session_id');
|
||||||
|
expect(savedMutation).toHaveProperty('workflow_after');
|
||||||
|
|
||||||
|
// Connection keys should be preserved EXACTLY (not "_webhook", "_a_i _agent", etc.)
|
||||||
|
const connections = savedMutation.workflow_after.connections;
|
||||||
|
expect(connections).toHaveProperty('Webhook'); // NOT "_webhook"
|
||||||
|
expect(connections).toHaveProperty('AI Agent'); // NOT "_a_i _agent"
|
||||||
|
expect(connections).toHaveProperty('HTTP Request'); // NOT "_h_t_t_p _request"
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should preserve node field names in camelCase', async () => {
|
||||||
|
const mutation: WorkflowMutationRecord = {
|
||||||
|
userId: 'user1',
|
||||||
|
sessionId: 'session1',
|
||||||
|
workflowBefore: { nodes: [], connections: {} },
|
||||||
|
workflowAfter: {
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'Webhook',
|
||||||
|
type: 'n8n-nodes-base.webhook',
|
||||||
|
// These fields MUST remain in camelCase for n8n API compatibility
|
||||||
|
typeVersion: 2,
|
||||||
|
webhookId: 'abc123',
|
||||||
|
onError: 'continueOnFail',
|
||||||
|
alwaysOutputData: true,
|
||||||
|
continueOnFail: false,
|
||||||
|
retryOnFail: true,
|
||||||
|
maxTries: 3,
|
||||||
|
notesInFlow: true,
|
||||||
|
waitBetweenTries: 1000,
|
||||||
|
executeOnce: false,
|
||||||
|
position: [100, 200],
|
||||||
|
parameters: {}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
connections: {}
|
||||||
|
},
|
||||||
|
workflowHashBefore: 'hash1',
|
||||||
|
workflowHashAfter: 'hash2',
|
||||||
|
userIntent: 'Test',
|
||||||
|
intentClassification: IntentClassification.ADD_FUNCTIONALITY,
|
||||||
|
toolName: MutationToolName.UPDATE_PARTIAL,
|
||||||
|
operations: [],
|
||||||
|
operationCount: 0,
|
||||||
|
operationTypes: [],
|
||||||
|
validationImproved: null,
|
||||||
|
errorsResolved: 0,
|
||||||
|
errorsIntroduced: 0,
|
||||||
|
nodesAdded: 1,
|
||||||
|
nodesRemoved: 0,
|
||||||
|
nodesModified: 0,
|
||||||
|
connectionsAdded: 0,
|
||||||
|
connectionsRemoved: 0,
|
||||||
|
propertiesChanged: 0,
|
||||||
|
mutationSuccess: true,
|
||||||
|
durationMs: 100
|
||||||
|
};
|
||||||
|
|
||||||
|
let capturedData: any = null;
|
||||||
|
vi.mocked(mockSupabase.from).mockImplementation((table) => ({
|
||||||
|
insert: vi.fn().mockImplementation((data) => {
|
||||||
|
if (table === 'workflow_mutations') {
|
||||||
|
capturedData = data;
|
||||||
|
}
|
||||||
|
return Promise.resolve(createMockSupabaseResponse());
|
||||||
|
}),
|
||||||
|
url: { href: '' },
|
||||||
|
headers: {},
|
||||||
|
select: vi.fn(),
|
||||||
|
upsert: vi.fn(),
|
||||||
|
update: vi.fn(),
|
||||||
|
delete: vi.fn()
|
||||||
|
} as any));
|
||||||
|
|
||||||
|
await batchProcessor.flush(undefined, undefined, [mutation]);
|
||||||
|
|
||||||
|
expect(capturedData).toBeDefined();
|
||||||
|
const savedNode = capturedData[0].workflow_after.nodes[0];
|
||||||
|
|
||||||
|
// Node fields should be preserved in camelCase (NOT snake_case)
|
||||||
|
expect(savedNode).toHaveProperty('typeVersion'); // NOT type_version
|
||||||
|
expect(savedNode).toHaveProperty('webhookId'); // NOT webhook_id
|
||||||
|
expect(savedNode).toHaveProperty('onError'); // NOT on_error
|
||||||
|
expect(savedNode).toHaveProperty('alwaysOutputData'); // NOT always_output_data
|
||||||
|
expect(savedNode).toHaveProperty('continueOnFail'); // NOT continue_on_fail
|
||||||
|
expect(savedNode).toHaveProperty('retryOnFail'); // NOT retry_on_fail
|
||||||
|
expect(savedNode).toHaveProperty('maxTries'); // NOT max_tries
|
||||||
|
expect(savedNode).toHaveProperty('notesInFlow'); // NOT notes_in_flow
|
||||||
|
expect(savedNode).toHaveProperty('waitBetweenTries'); // NOT wait_between_tries
|
||||||
|
expect(savedNode).toHaveProperty('executeOnce'); // NOT execute_once
|
||||||
|
|
||||||
|
// Verify values are preserved
|
||||||
|
expect(savedNode.typeVersion).toBe(2);
|
||||||
|
expect(savedNode.webhookId).toBe('abc123');
|
||||||
|
expect(savedNode.maxTries).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert only top-level mutation record fields to snake_case', async () => {
|
||||||
|
const mutation: WorkflowMutationRecord = {
|
||||||
|
userId: 'user1',
|
||||||
|
sessionId: 'session1',
|
||||||
|
workflowBefore: { nodes: [], connections: {} },
|
||||||
|
workflowAfter: { nodes: [], connections: {} },
|
||||||
|
workflowHashBefore: 'hash1',
|
||||||
|
workflowHashAfter: 'hash2',
|
||||||
|
workflowStructureHashBefore: 'struct1',
|
||||||
|
workflowStructureHashAfter: 'struct2',
|
||||||
|
isTrulySuccessful: true,
|
||||||
|
userIntent: 'Test intent',
|
||||||
|
intentClassification: IntentClassification.ADD_FUNCTIONALITY,
|
||||||
|
toolName: MutationToolName.UPDATE_PARTIAL,
|
||||||
|
operations: [{ type: 'addNode', node: { name: 'Test', type: 'n8n-nodes-base.set', position: [0, 0] } } as AddNodeOperation],
|
||||||
|
operationCount: 1,
|
||||||
|
operationTypes: ['addNode'],
|
||||||
|
validationBefore: { valid: false, errors: [] },
|
||||||
|
validationAfter: { valid: true, errors: [] },
|
||||||
|
validationImproved: true,
|
||||||
|
errorsResolved: 1,
|
||||||
|
errorsIntroduced: 0,
|
||||||
|
nodesAdded: 1,
|
||||||
|
nodesRemoved: 0,
|
||||||
|
nodesModified: 0,
|
||||||
|
connectionsAdded: 0,
|
||||||
|
connectionsRemoved: 0,
|
||||||
|
propertiesChanged: 0,
|
||||||
|
mutationSuccess: true,
|
||||||
|
mutationError: undefined,
|
||||||
|
durationMs: 150
|
||||||
|
};
|
||||||
|
|
||||||
|
let capturedData: any = null;
|
||||||
|
vi.mocked(mockSupabase.from).mockImplementation((table) => ({
|
||||||
|
insert: vi.fn().mockImplementation((data) => {
|
||||||
|
if (table === 'workflow_mutations') {
|
||||||
|
capturedData = data;
|
||||||
|
}
|
||||||
|
return Promise.resolve(createMockSupabaseResponse());
|
||||||
|
}),
|
||||||
|
url: { href: '' },
|
||||||
|
headers: {},
|
||||||
|
select: vi.fn(),
|
||||||
|
upsert: vi.fn(),
|
||||||
|
update: vi.fn(),
|
||||||
|
delete: vi.fn()
|
||||||
|
} as any));
|
||||||
|
|
||||||
|
await batchProcessor.flush(undefined, undefined, [mutation]);
|
||||||
|
|
||||||
|
expect(capturedData).toBeDefined();
|
||||||
|
const saved = capturedData[0];
|
||||||
|
|
||||||
|
// Top-level fields should be converted to snake_case
|
||||||
|
expect(saved).toHaveProperty('user_id', 'user1');
|
||||||
|
expect(saved).toHaveProperty('session_id', 'session1');
|
||||||
|
expect(saved).toHaveProperty('workflow_before');
|
||||||
|
expect(saved).toHaveProperty('workflow_after');
|
||||||
|
expect(saved).toHaveProperty('workflow_hash_before', 'hash1');
|
||||||
|
expect(saved).toHaveProperty('workflow_hash_after', 'hash2');
|
||||||
|
expect(saved).toHaveProperty('workflow_structure_hash_before', 'struct1');
|
||||||
|
expect(saved).toHaveProperty('workflow_structure_hash_after', 'struct2');
|
||||||
|
expect(saved).toHaveProperty('is_truly_successful', true);
|
||||||
|
expect(saved).toHaveProperty('user_intent', 'Test intent');
|
||||||
|
expect(saved).toHaveProperty('intent_classification');
|
||||||
|
expect(saved).toHaveProperty('tool_name');
|
||||||
|
expect(saved).toHaveProperty('operation_count', 1);
|
||||||
|
expect(saved).toHaveProperty('operation_types');
|
||||||
|
expect(saved).toHaveProperty('validation_before');
|
||||||
|
expect(saved).toHaveProperty('validation_after');
|
||||||
|
expect(saved).toHaveProperty('validation_improved', true);
|
||||||
|
expect(saved).toHaveProperty('errors_resolved', 1);
|
||||||
|
expect(saved).toHaveProperty('errors_introduced', 0);
|
||||||
|
expect(saved).toHaveProperty('nodes_added', 1);
|
||||||
|
expect(saved).toHaveProperty('nodes_removed', 0);
|
||||||
|
expect(saved).toHaveProperty('nodes_modified', 0);
|
||||||
|
expect(saved).toHaveProperty('connections_added', 0);
|
||||||
|
expect(saved).toHaveProperty('connections_removed', 0);
|
||||||
|
expect(saved).toHaveProperty('properties_changed', 0);
|
||||||
|
expect(saved).toHaveProperty('mutation_success', true);
|
||||||
|
expect(saved).toHaveProperty('duration_ms', 150);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user