Compare commits

..

1 Commits

Author SHA1 Message Date
Romuald Członkowski
32f63c64a5 fix: memory leak in session removal - close MCP server properly (#471)
- Add close() method to N8NDocumentationMCPServer that:
  - Calls server.close() (MCP SDK cleanup)
  - Clears internal cache
  - Nullifies service references to help GC
- Update removeSession() to call server.close() before releasing references

Root cause: removeSession() deleted server from map but didn't call cleanup
Evidence: Production server memory grew ~1GB in 43 minutes (10% to 35%)

Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 18:18:56 +01:00
75 changed files with 6062 additions and 2855 deletions

View File

@@ -7,134 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [2.29.2] - 2025-12-12
### Added
**Tool Variant Validation and Auto-Fix**
Added validation to detect when base nodes are incorrectly used as AI tools when Tool variants should be used instead, with automatic fix capability.
**Key Features:**
- **New Validation**: `validateAIToolSource()` detects base nodes (e.g., `n8n-nodes-base.supabase`) connected via `ai_tool` output when a Tool variant exists
- **Clear Error Messages**: Returns actionable error with `WRONG_NODE_TYPE_FOR_AI_TOOL` code explaining which Tool variant to use
- **Auto-Fix Support**: New `tool-variant-correction` fix type in `n8n_autofix_workflow` automatically replaces base nodes with their Tool variants
- **High Confidence Fixes**: Tool variant corrections are marked as high confidence since the correct type is known
**New Utility Method:**
- `NodeTypeNormalizer.toWorkflowFormat()` - Converts database format (short) back to n8n API format (full)
**Test Coverage:**
- 83 new unit tests covering Tool variant validation, auto-fix, and type normalization
- Tests for edge cases: langchain tools, unknown nodes, multiple errors, community nodes
**Files Changed:**
- `src/services/workflow-validator.ts` - Added `validateAIToolSource()` method
- `src/services/workflow-auto-fixer.ts` - Added `tool-variant-correction` fix type
- `src/utils/node-type-normalizer.ts` - Added `toWorkflowFormat()` method
- `tests/unit/services/workflow-validator-tool-variants.test.ts` - **NEW** 12 tests
- `tests/unit/services/workflow-auto-fixer-tool-variants.test.ts` - **NEW** 13 tests
- `tests/unit/utils/node-type-normalizer.test.ts` - Added 19 tests for `toWorkflowFormat()`
**Conceived by Romuald Członkowski - [AiAdvisors](https://www.aiadvisors.pl/en)**
## [2.29.1] - 2025-12-12
### Added
**Tool Variant Support for AI Agent Integration**
Added comprehensive support for n8n Tool variants - specialized node versions created for AI Agent tool connections (e.g., `nodes-base.supabaseTool` from `nodes-base.supabase`).
**Key Features:**
- **266 Tool Variants Generated**: During database rebuild, Tool variants are automatically created for all nodes with `usableAsTool: true`
- **Bidirectional Cross-References**:
- Base nodes show `toolVariantInfo.hasToolVariant: true` with guidance to use Tool variant
- Tool variants show `toolVariantInfo.isToolVariant: true` with reference to base node
- **Clear AI Guidance**: Contextual messages help AI assistants choose the correct node type:
- Base node guidance: "To use this node with AI Agents, use the Tool variant: nodes-base.supabaseTool"
- Tool variant guidance: "This is the Tool variant for AI Agent integration"
- **Tool Description Property**: Tool variants include `toolDescription` property for AI context
- **ai_tool Output Type**: Tool variants output `ai_tool` instead of `main` for proper Agent connections
**Database Schema Changes:**
- Added `is_tool_variant` column (1 if Tool variant)
- Added `tool_variant_of` column (base node type reference)
- Added `has_tool_variant` column (1 if base node has Tool variant)
- Added indexes for efficient Tool variant queries
**Files Changed:**
- `src/database/schema.sql` - New columns and indexes
- `src/parsers/node-parser.ts` - Extended ParsedNode interface
- `src/services/tool-variant-generator.ts` - **NEW** Tool variant generation service
- `src/database/node-repository.ts` - Store/retrieve Tool variant fields
- `src/scripts/rebuild.ts` - Generate Tool variants during rebuild
- `src/mcp/server.ts` - Add `toolVariantInfo` to get_node responses
**Conceived by Romuald Członkowski - [AiAdvisors](https://www.aiadvisors.pl/en)**
## [2.29.0] - 2025-12-09
### Performance
**Token-Efficient Workflow Tool Responses (#479)**
Optimized 4 workflow management tools to return minimal responses instead of full workflow objects, reducing token usage by 75-90%:
- **n8n_update_partial_workflow**: Returns `{id, name, active, nodeCount, operationsApplied}` instead of full workflow
- **n8n_create_workflow**: Returns `{id, name, active, nodeCount}` instead of full workflow
- **n8n_update_full_workflow**: Returns `{id, name, active, nodeCount}` instead of full workflow
- **n8n_delete_workflow**: Returns `{id, name, deleted: true}` instead of full deleted workflow
**Impact**:
- ~75-90% reduction in response token usage per operation
- Messages now guide AI agents to use `n8n_get_workflow` with mode 'structure' if verification needed
- No functional changes - full workflow data still available via `n8n_get_workflow`
**Files Modified**:
- `src/mcp/handlers-workflow-diff.ts` - Optimized partial update response
- `src/mcp/handlers-n8n-manager.ts` - Optimized create, full update, and delete responses
- `src/mcp/tool-docs/workflow_management/*.ts` - Updated documentation
**Conceived by Romuald Członkowski - [AiAdvisors](https://www.aiadvisors.pl/en)**
## [2.28.9] - 2025-12-08
### Dependencies
**Updated n8n to 1.123.4**
- Updated n8n from 1.122.4 to 1.123.4
- Updated n8n-core from 1.121.1 to 1.122.1
- Updated n8n-workflow from 1.119.1 to 1.120.0
- Updated @n8n/n8n-nodes-langchain from 1.121.1 to 1.122.1
- Rebuilt node database with 545 nodes (439 from n8n-nodes-base, 106 from @n8n/n8n-nodes-langchain)
- Updated README badge with new n8n version
**Conceived by Romuald Członkowski - [AiAdvisors](https://www.aiadvisors.pl/en)**
## [2.28.8] - 2025-12-07
### Bug Fixes
**Multi-tenant: handleValidateWorkflow missing context parameter (#474)**
Fixed `n8n_validate_workflow` tool failing in multi-tenant mode with error:
`"n8n API not configured. Please set N8N_API_URL and N8N_API_KEY environment variables."`
- **Root Cause**: `handleValidateWorkflow` called `handleGetWorkflow` without passing the `context` parameter
- **Impact**: Multi-tenant deployments could not use the `n8n_validate_workflow` tool
- **Solution**: Pass `context` parameter to `handleGetWorkflow` call (line 987)
**Conceived by Romuald Członkowski - [AiAdvisors](https://www.aiadvisors.pl/en)**
## [2.28.7] - 2025-12-05
### Bug Fixes

View File

@@ -5,7 +5,7 @@
[![npm version](https://img.shields.io/npm/v/n8n-mcp.svg)](https://www.npmjs.com/package/n8n-mcp)
[![codecov](https://codecov.io/gh/czlonkowski/n8n-mcp/graph/badge.svg?token=YOUR_TOKEN)](https://codecov.io/gh/czlonkowski/n8n-mcp)
[![Tests](https://img.shields.io/badge/tests-3336%20passing-brightgreen.svg)](https://github.com/czlonkowski/n8n-mcp/actions)
[![n8n version](https://img.shields.io/badge/n8n-1.123.4-orange.svg)](https://github.com/n8n-io/n8n)
[![n8n version](https://img.shields.io/badge/n8n-1.122.4-orange.svg)](https://github.com/n8n-io/n8n)
[![Docker](https://img.shields.io/badge/docker-ghcr.io%2Fczlonkowski%2Fn8n--mcp-green.svg)](https://github.com/czlonkowski/n8n-mcp/pkgs/container/n8n-mcp)
[![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/n8n-mcp?referralCode=n8n-mcp)

Binary file not shown.

View File

@@ -15,10 +15,6 @@ export declare class NodeRepository {
getAllNodes(limit?: number): any[];
getNodeCount(): number;
getAIToolNodes(): any[];
getToolVariant(baseNodeType: string): any | null;
getBaseNodeForToolVariant(toolNodeType: string): any | null;
getToolVariants(): any[];
getToolVariantCount(): number;
getNodesByPackage(packageName: string): any[];
searchNodeProperties(nodeType: string, query: string, maxResults?: number): any[];
private parseNodeRow;

View File

@@ -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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"}

View File

@@ -16,13 +16,12 @@ class NodeRepository {
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,
is_webhook, is_versioned, version, documentation,
properties_schema, operations, credentials_required,
outputs, output_names
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
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);
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.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);
}
getNode(nodeType) {
const normalizedType = node_type_normalizer_1.NodeTypeNormalizer.normalizeToFullForm(nodeType);
@@ -123,40 +122,6 @@ class NodeRepository {
getAIToolNodes() {
return this.getAITools();
}
getToolVariant(baseNodeType) {
if (!baseNodeType || typeof baseNodeType !== 'string' || !baseNodeType.includes('.')) {
return null;
}
const toolNodeType = `${baseNodeType}Tool`;
return this.getNode(toolNodeType);
}
getBaseNodeForToolVariant(toolNodeType) {
const row = this.db.prepare(`
SELECT tool_variant_of FROM nodes WHERE node_type = ?
`).get(toolNodeType);
if (!row?.tool_variant_of)
return null;
return this.getNode(row.tool_variant_of);
}
getToolVariants() {
const rows = this.db.prepare(`
SELECT node_type, display_name, description, package_name, tool_variant_of
FROM nodes
WHERE is_tool_variant = 1
ORDER BY display_name
`).all();
return rows.map(row => ({
nodeType: row.node_type,
displayName: row.display_name,
description: row.description,
package: row.package_name,
toolVariantOf: row.tool_variant_of
}));
}
getToolVariantCount() {
const result = this.db.prepare('SELECT COUNT(*) as count FROM nodes WHERE is_tool_variant = 1').get();
return result.count;
}
getNodesByPackage(packageName) {
const rows = this.db.prepare(`
SELECT * FROM nodes WHERE package_name = ?
@@ -205,9 +170,6 @@ class NodeRepository {
isTrigger: Number(row.is_trigger) === 1,
isWebhook: Number(row.is_webhook) === 1,
isVersioned: Number(row.is_versioned) === 1,
isToolVariant: Number(row.is_tool_variant) === 1,
toolVariantOf: row.tool_variant_of || null,
hasToolVariant: Number(row.has_tool_variant) === 1,
version: row.version,
properties: this.safeJsonParse(row.properties_schema, []),
operations: this.safeJsonParse(row.operations, []),

File diff suppressed because one or more lines are too long

View File

@@ -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;AAuErD,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;IAC3B,OAAO,CAAC,cAAc,CAAkB;IACxC,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;YAmOF,eAAe;IA8C7B,OAAO,CAAC,SAAS;IAYjB,OAAO,CAAC,gBAAgB;IASlB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgnBtB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAkD/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;AAuErD,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;IAC3B,OAAO,CAAC,cAAc,CAAkB;IACxC,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,YAAY,CAA+B;;IAcnD,OAAO,CAAC,mBAAmB;IAmB3B,OAAO,CAAC,sBAAsB;YAqChB,aAAa;IAkC3B,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;YAmOF,eAAe;IA8C7B,OAAO,CAAC,SAAS;IAYjB,OAAO,CAAC,gBAAgB;IASlB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgnBtB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAkD/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"}

View File

@@ -106,13 +106,8 @@ class SingleSessionHTTPServer {
delete this.servers[sessionId];
delete this.sessionMetadata[sessionId];
delete this.sessionContexts[sessionId];
if (server && typeof server.close === 'function') {
try {
await server.close();
}
catch (serverError) {
logger_1.logger.warn('Error closing server', { sessionId, error: serverError });
}
if (server) {
await server.close();
}
if (transport) {
await transport.close();

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"handlers-n8n-manager.d.ts","sourceRoot":"","sources":["../../src/mcp/handlers-n8n-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE1D,OAAO,EAML,eAAe,EAGhB,MAAM,kBAAkB,CAAC;AAkB1B,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,eAAe,EAA2B,MAAM,2BAA2B,CAAC;AAOrF,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAqNhE,wBAAgB,0BAA0B,IAAI,MAAM,CAEnD;AAMD,wBAAgB,uBAAuB,gDAEtC;AAKD,wBAAgB,kBAAkB,IAAI,IAAI,CAIzC;AAED,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,YAAY,GAAG,IAAI,CAgF9E;AAqHD,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAmF7G;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAiC1G;AAED,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAoDjH;AAED,wBAAsB,0BAA0B,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAmDnH;AAED,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAyCjH;AAED,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,cAAc,EAC1B,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,eAAe,CAAC,CA8H1B;AAeD,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAsC7G;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAiE5G;AAED,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,cAAc,EAC1B,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,eAAe,CAAC,CA0F1B;AAED,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,cAAc,EAC1B,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,eAAe,CAAC,CAoK1B;AAQD,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAwJ3G;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CA4F3G;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAgD7G;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAiC9G;AAID,wBAAsB,iBAAiB,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAwG3F;AAkLD,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAkQxG;AAED,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,cAAc,EAC1B,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,eAAe,CAAC,CAsL1B;AA+BD,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,OAAO,EACb,eAAe,EAAE,eAAe,EAChC,UAAU,EAAE,cAAc,EAC1B,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,eAAe,CAAC,CAoM1B;AAQD,wBAAsB,4BAA4B,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAyErH"}
{"version":3,"file":"handlers-n8n-manager.d.ts","sourceRoot":"","sources":["../../src/mcp/handlers-n8n-manager.ts"],"names":[],"mappings":"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"}

View File

@@ -285,13 +285,8 @@ async function handleCreateWorkflow(args, context) {
telemetry_1.telemetry.trackWorkflowCreation(workflow, true);
return {
success: true,
data: {
id: workflow.id,
name: workflow.name,
active: workflow.active,
nodeCount: workflow.nodes?.length || 0
},
message: `Workflow "${workflow.name}" created successfully with ID: ${workflow.id}. Use n8n_get_workflow with mode 'structure' to verify current state.`
data: workflow,
message: `Workflow "${workflow.name}" created successfully with ID: ${workflow.id}`
};
}
catch (error) {
@@ -542,13 +537,8 @@ async function handleUpdateWorkflow(args, repository, context) {
}
return {
success: true,
data: {
id: workflow.id,
name: workflow.name,
active: workflow.active,
nodeCount: workflow.nodes?.length || 0
},
message: `Workflow "${workflow.name}" updated successfully. Use n8n_get_workflow with mode 'structure' to verify current state.`
data: workflow,
message: `Workflow "${workflow.name}" updated successfully`
};
}
catch (error) {
@@ -604,12 +594,8 @@ async function handleDeleteWorkflow(args, context) {
const deleted = await client.deleteWorkflow(id);
return {
success: true,
data: {
id: deleted?.id || id,
name: deleted?.name,
deleted: true
},
message: `Workflow "${deleted?.name || id}" deleted successfully.`
data: deleted,
message: `Workflow ${id} deleted successfully`
};
}
catch (error) {
@@ -696,7 +682,7 @@ async function handleValidateWorkflow(args, repository, context) {
try {
const client = ensureApiConfigured(context);
const input = validateWorkflowSchema.parse(args);
const workflowResponse = await handleGetWorkflow({ id: input.id }, context);
const workflowResponse = await handleGetWorkflow({ id: input.id });
if (!workflowResponse.success) {
return workflowResponse;
}

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"handlers-workflow-diff.d.ts","sourceRoot":"","sources":["../../src/mcp/handlers-workflow-diff.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAMnD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AA0D7D,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,cAAc,EAC1B,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,eAAe,CAAC,CA6V1B"}
{"version":3,"file":"handlers-workflow-diff.d.ts","sourceRoot":"","sources":["../../src/mcp/handlers-workflow-diff.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAMnD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AA0D7D,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,cAAc,EAC1B,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,eAAe,CAAC,CA2V1B"}

View File

@@ -329,15 +329,13 @@ async function handleUpdatePartialWorkflow(args, repository, context) {
}
return {
success: true,
data: {
id: finalWorkflow.id,
name: finalWorkflow.name,
active: finalWorkflow.active,
nodeCount: finalWorkflow.nodes?.length || 0,
operationsApplied: diffResult.operationsApplied
},
message: `Workflow "${finalWorkflow.name}" updated successfully. Applied ${diffResult.operationsApplied} operations.${activationMessage} Use n8n_get_workflow with mode 'structure' to verify current state.`,
data: finalWorkflow,
message: `Workflow "${finalWorkflow.name}" updated successfully. Applied ${diffResult.operationsApplied} operations.${activationMessage}`,
details: {
operationsApplied: diffResult.operationsApplied,
workflowId: finalWorkflow.id,
workflowName: finalWorkflow.name,
active: finalWorkflow.active,
applied: diffResult.applied,
failed: diffResult.failed,
errors: diffResult.errors,

File diff suppressed because one or more lines are too long

View File

@@ -60,7 +60,6 @@ export declare class N8NDocumentationMCPServer {
private getNodeAsToolInfo;
private getOutputDescriptions;
private getCommonAIToolUseCases;
private buildToolVariantGuidance;
private getAIToolExamples;
private validateNodeMinimal;
private getToolsDocumentation;

View File

@@ -1 +1 @@
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAsCA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAgGnE,qBAAa,yBAAyB;IACpC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,EAAE,CAAgC;IAC1C,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,eAAe,CAAgC;IACvD,OAAO,CAAC,WAAW,CAAgB;IACnC,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,eAAe,CAAC,CAAkB;IAC1C,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,qBAAqB,CAAsB;IACnD,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,kBAAkB,CAA4B;gBAE1C,eAAe,CAAC,EAAE,eAAe,EAAE,WAAW,CAAC,EAAE,gBAAgB;IAiGvE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YA6Bd,kBAAkB;YAwClB,wBAAwB;IA0BtC,OAAO,CAAC,kBAAkB;YA6CZ,iBAAiB;IAa/B,OAAO,CAAC,eAAe,CAAkB;YAE3B,sBAAsB;IAgDpC,OAAO,CAAC,gBAAgB;IAqCxB,OAAO,CAAC,aAAa;IAoTrB,OAAO,CAAC,wBAAwB;IAoFhC,OAAO,CAAC,kBAAkB;IAqE1B,OAAO,CAAC,uBAAuB;IAwB/B,OAAO,CAAC,qBAAqB;YAgTf,SAAS;YA2DT,WAAW;YAkFX,WAAW;YAyCX,cAAc;YAyKd,gBAAgB;IAqD9B,OAAO,CAAC,mBAAmB;IAwE3B,OAAO,CAAC,eAAe;YAsBT,eAAe;IAqI7B,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,uBAAuB;IA0D/B,OAAO,CAAC,iBAAiB;YAqFX,WAAW;YAgCX,oBAAoB;YA2EpB,qBAAqB;YAwDrB,iBAAiB;YAiKjB,OAAO;YAgDP,cAAc;YAwFd,iBAAiB;IAqC/B,OAAO,CAAC,iBAAiB;IA0BzB,OAAO,CAAC,iBAAiB;IA0BzB,OAAO,CAAC,eAAe;IAwCvB,OAAO,CAAC,kBAAkB;IAiC1B,OAAO,CAAC,aAAa;IAoCrB,OAAO,CAAC,0BAA0B;IAgClC,OAAO,CAAC,4BAA4B;YAKtB,oBAAoB;IAsDlC,OAAO,CAAC,gBAAgB;YAiBV,SAAS;YA6CT,kBAAkB;YA+DlB,uBAAuB;YAsDvB,iBAAiB;IAqE/B,OAAO,CAAC,qBAAqB;IA8C7B,OAAO,CAAC,uBAAuB;IA4D/B,OAAO,CAAC,wBAAwB;IAkChC,OAAO,CAAC,iBAAiB;YAoDX,mBAAmB;YAkGnB,qBAAqB;IAS7B,OAAO,CAAC,SAAS,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;YAS9B,aAAa;YAcb,iBAAiB;YAoBjB,WAAW;YAwBX,eAAe;YAqBf,mBAAmB;YAwBnB,yBAAyB;IA4CvC,OAAO,CAAC,kBAAkB;YAiBZ,gBAAgB;YA6HhB,2BAA2B;YAiE3B,2BAA2B;IAyEnC,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BpB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAuBhC"}
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAsCA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAkFnE,qBAAa,yBAAyB;IACpC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,EAAE,CAAgC;IAC1C,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,eAAe,CAAgC;IACvD,OAAO,CAAC,WAAW,CAAgB;IACnC,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,eAAe,CAAC,CAAkB;IAC1C,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,qBAAqB,CAAsB;IACnD,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,kBAAkB,CAA4B;gBAE1C,eAAe,CAAC,EAAE,eAAe,EAAE,WAAW,CAAC,EAAE,gBAAgB;IA2FvE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAed,kBAAkB;YAwClB,wBAAwB;IA0BtC,OAAO,CAAC,kBAAkB;YA6CZ,iBAAiB;IAa/B,OAAO,CAAC,eAAe,CAAkB;YAE3B,sBAAsB;IAgDpC,OAAO,CAAC,gBAAgB;IAqCxB,OAAO,CAAC,aAAa;IAoTrB,OAAO,CAAC,wBAAwB;IAoFhC,OAAO,CAAC,kBAAkB;IAqE1B,OAAO,CAAC,uBAAuB;IAwB/B,OAAO,CAAC,qBAAqB;YAgTf,SAAS;YA2DT,WAAW;YA0EX,WAAW;YAyCX,cAAc;YAyKd,gBAAgB;IAqD9B,OAAO,CAAC,mBAAmB;IAwE3B,OAAO,CAAC,eAAe;YAsBT,eAAe;IAqI7B,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,uBAAuB;IA0D/B,OAAO,CAAC,iBAAiB;YAqFX,WAAW;YAgCX,oBAAoB;YA2EpB,qBAAqB;YAwDrB,iBAAiB;YA2JjB,OAAO;YAgDP,cAAc;YAgFd,iBAAiB;IAqC/B,OAAO,CAAC,iBAAiB;IA0BzB,OAAO,CAAC,iBAAiB;IA0BzB,OAAO,CAAC,eAAe;IAwCvB,OAAO,CAAC,kBAAkB;IAiC1B,OAAO,CAAC,aAAa;IAoCrB,OAAO,CAAC,0BAA0B;IAgClC,OAAO,CAAC,4BAA4B;YAKtB,oBAAoB;IAsDlC,OAAO,CAAC,gBAAgB;YAiBV,SAAS;YA6CT,kBAAkB;YA+DlB,uBAAuB;YAsDvB,iBAAiB;IAqE/B,OAAO,CAAC,qBAAqB;IA8C7B,OAAO,CAAC,uBAAuB;IAwD/B,OAAO,CAAC,iBAAiB;YAoDX,mBAAmB;YAkGnB,qBAAqB;IAS7B,OAAO,CAAC,SAAS,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;YAS9B,aAAa;YAcb,iBAAiB;YAoBjB,WAAW;YAwBX,eAAe;YAqBf,mBAAmB;YAwBnB,yBAAyB;IA4CvC,OAAO,CAAC,kBAAkB;YAiBZ,gBAAgB;YA6HhB,2BAA2B;YAiE3B,2BAA2B;IAyEnC,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BpB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAuBhC"}

56
dist/mcp/server.js vendored
View File

@@ -150,17 +150,7 @@ class N8NDocumentationMCPServer {
async close() {
try {
await this.server.close();
this.cache.destroy();
if (this.db) {
try {
this.db.close();
}
catch (dbError) {
logger_1.logger.warn('Error closing database', {
error: dbError instanceof Error ? dbError.message : String(dbError)
});
}
}
this.cache.clear();
this.db = null;
this.repository = null;
this.templateService = null;
@@ -1025,17 +1015,12 @@ class N8NDocumentationMCPServer {
};
});
}
const result = {
return {
...node,
workflowNodeType: (0, node_utils_1.getWorkflowNodeType)(node.package ?? 'n8n-nodes-base', node.nodeType),
aiToolCapabilities,
outputs
};
const toolVariantInfo = this.buildToolVariantGuidance(node);
if (toolVariantInfo) {
result.toolVariantInfo = toolVariantInfo;
}
return result;
}
async searchNodes(query, limit = 20, options) {
await this.ensureInitialized();
@@ -1708,10 +1693,6 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
developmentStyle: node.developmentStyle ?? 'programmatic'
}
};
const toolVariantInfo = this.buildToolVariantGuidance(node);
if (toolVariantInfo) {
result.toolVariantInfo = toolVariantInfo;
}
if (includeExamples) {
try {
const examples = this.db.prepare(`
@@ -1793,7 +1774,7 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
if (!node) {
throw new Error(`Node ${nodeType} not found`);
}
const result = {
return {
nodeType: node.nodeType,
workflowNodeType: (0, node_utils_1.getWorkflowNodeType)(node.package ?? 'n8n-nodes-base', node.nodeType),
displayName: node.displayName,
@@ -1804,11 +1785,6 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
isTrigger: node.isTrigger,
isWebhook: node.isWebhook
};
const toolVariantInfo = this.buildToolVariantGuidance(node);
if (toolVariantInfo) {
result.toolVariantInfo = toolVariantInfo;
}
return result;
}
case 'standard': {
const essentials = await this.getNodeEssentials(nodeType, includeExamples);
@@ -2306,32 +2282,6 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
'Extend AI agent capabilities'
];
}
buildToolVariantGuidance(node) {
const isToolVariant = !!node.isToolVariant;
const hasToolVariant = !!node.hasToolVariant;
const toolVariantOf = node.toolVariantOf;
if (!isToolVariant && !hasToolVariant) {
return undefined;
}
if (isToolVariant) {
return {
isToolVariant: true,
toolVariantOf,
hasToolVariant: false,
guidance: `This is the Tool variant for AI Agent integration. Use this node type when connecting to AI Agents. The base node is: ${toolVariantOf}`
};
}
if (hasToolVariant && node.nodeType) {
const toolVariantNodeType = `${node.nodeType}Tool`;
return {
isToolVariant: false,
hasToolVariant: true,
toolVariantNodeType,
guidance: `To use this node with AI Agents, use the Tool variant: ${toolVariantNodeType}. The Tool variant has an additional 'toolDescription' property and outputs 'ai_tool' instead of 'main'.`
};
}
return undefined;
}
getAIToolExamples(nodeType) {
const exampleMap = {
'nodes-base.slack': {

File diff suppressed because one or more lines are too long

View File

@@ -24,7 +24,7 @@ exports.n8nCreateWorkflowDoc = {
connections: { type: 'object', required: true, description: 'Node connections. Keys are source node IDs' },
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: 'Created workflow object with id, name, nodes, connections, active status',
examples: [
`// Basic webhook to Slack workflow
n8n_create_workflow({

View File

@@ -1 +1 @@
{"version":3,"file":"n8n-create-workflow.js","sourceRoot":"","sources":["../../../../src/mcp/tool-docs/workflow_management/n8n-create-workflow.ts"],"names":[],"mappings":";;;AAEa,QAAA,oBAAoB,GAAsB;IACrD,IAAI,EAAE,qBAAqB;IAC3B,QAAQ,EAAE,qBAAqB;IAC/B,UAAU,EAAE;QACV,WAAW,EAAE,sGAAsG;QACnH,aAAa,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC;QAC/C,OAAO,EAAE,0EAA0E;QACnF,WAAW,EAAE,mBAAmB;QAChC,IAAI,EAAE;YACJ,2BAA2B;YAC3B,+BAA+B;YAC/B,uCAAuC;YACvC,kFAAkF;SACnF;KACF;IACD,IAAI,EAAE;QACJ,WAAW,EAAE,uLAAuL;QACpM,UAAU,EAAE;YACV,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,eAAe,EAAE;YACtE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uEAAuE,EAAE;YAC9H,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,4CAA4C,EAAE;YAC1G,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,6DAA6D,EAAE;SACzG;QACD,OAAO,EAAE,mJAAmJ;QAC5J,QAAQ,EAAE;YACR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCH;YACG;;;;;;;;;;;GAWH;SACE;QACD,QAAQ,EAAE;YACR,4BAA4B;YAC5B,4BAA4B;YAC5B,2BAA2B;YAC3B,qBAAqB;SACtB;QACD,WAAW,EAAE,oEAAoE;QACjF,aAAa,EAAE;YACb,uCAAuC;YACvC,qBAAqB;YACrB,gCAAgC;YAChC,6BAA6B;SAC9B;QACD,QAAQ,EAAE;YACR,0GAA0G;YAC1G,gEAAgE;YAChE,yCAAyC;YACzC,kDAAkD;YAClD,4EAA4E;YAC5E,yIAAyI;YACzI,uIAAuI;SACxI;QACD,YAAY,EAAE,CAAC,mBAAmB,EAAE,6BAA6B,EAAE,mBAAmB,CAAC;KACxF;CACF,CAAC"}
{"version":3,"file":"n8n-create-workflow.js","sourceRoot":"","sources":["../../../../src/mcp/tool-docs/workflow_management/n8n-create-workflow.ts"],"names":[],"mappings":";;;AAEa,QAAA,oBAAoB,GAAsB;IACrD,IAAI,EAAE,qBAAqB;IAC3B,QAAQ,EAAE,qBAAqB;IAC/B,UAAU,EAAE;QACV,WAAW,EAAE,sGAAsG;QACnH,aAAa,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC;QAC/C,OAAO,EAAE,0EAA0E;QACnF,WAAW,EAAE,mBAAmB;QAChC,IAAI,EAAE;YACJ,2BAA2B;YAC3B,+BAA+B;YAC/B,uCAAuC;YACvC,kFAAkF;SACnF;KACF;IACD,IAAI,EAAE;QACJ,WAAW,EAAE,uLAAuL;QACpM,UAAU,EAAE;YACV,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,eAAe,EAAE;YACtE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uEAAuE,EAAE;YAC9H,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,4CAA4C,EAAE;YAC1G,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,6DAA6D,EAAE;SACzG;QACD,OAAO,EAAE,0EAA0E;QACnF,QAAQ,EAAE;YACR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCH;YACG;;;;;;;;;;;GAWH;SACE;QACD,QAAQ,EAAE;YACR,4BAA4B;YAC5B,4BAA4B;YAC5B,2BAA2B;YAC3B,qBAAqB;SACtB;QACD,WAAW,EAAE,oEAAoE;QACjF,aAAa,EAAE;YACb,uCAAuC;YACvC,qBAAqB;YACrB,gCAAgC;YAChC,6BAA6B;SAC9B;QACD,QAAQ,EAAE;YACR,0GAA0G;YAC1G,gEAAgE;YAChE,yCAAyC;YACzC,kDAAkD;YAClD,4EAA4E;YAC5E,yIAAyI;YACzI,uIAAuI;SACxI;QACD,YAAY,EAAE,CAAC,mBAAmB,EAAE,6BAA6B,EAAE,mBAAmB,CAAC;KACxF;CACF,CAAC"}

View File

@@ -20,7 +20,7 @@ exports.n8nDeleteWorkflowDoc = {
parameters: {
id: { type: 'string', required: true, description: 'Workflow ID to delete permanently' }
},
returns: 'Minimal confirmation (id, name, deleted: true) for token efficiency.',
returns: 'Success confirmation or error if workflow not found/cannot be deleted',
examples: [
'n8n_delete_workflow({id: "abc123"}) - Delete specific workflow',
'if (confirm) { n8n_delete_workflow({id: wf.id}); } // With confirmation'

View File

@@ -1 +1 @@
{"version":3,"file":"n8n-delete-workflow.js","sourceRoot":"","sources":["../../../../src/mcp/tool-docs/workflow_management/n8n-delete-workflow.ts"],"names":[],"mappings":";;;AAEa,QAAA,oBAAoB,GAAsB;IACrD,IAAI,EAAE,qBAAqB;IAC3B,QAAQ,EAAE,qBAAqB;IAC/B,UAAU,EAAE;QACV,WAAW,EAAE,8DAA8D;QAC3E,aAAa,EAAE,CAAC,IAAI,CAAC;QACrB,OAAO,EAAE,2CAA2C;QACpD,WAAW,EAAE,iBAAiB;QAC9B,IAAI,EAAE;YACJ,wBAAwB;YACxB,+BAA+B;YAC/B,+DAA+D;SAChE;KACF;IACD,IAAI,EAAE;QACJ,WAAW,EAAE,qPAAqP;QAClQ,UAAU,EAAE;YACV,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,mCAAmC,EAAE;SACzF;QACD,OAAO,EAAE,sEAAsE;QAC/E,QAAQ,EAAE;YACR,gEAAgE;YAChE,yEAAyE;SAC1E;QACD,QAAQ,EAAE;YACR,2BAA2B;YAC3B,yBAAyB;YACzB,2BAA2B;YAC3B,wBAAwB;YACxB,mBAAmB;SACpB;QACD,WAAW,EAAE,mGAAmG;QAChH,aAAa,EAAE;YACb,gCAAgC;YAChC,+DAA+D;YAC/D,2CAA2C;YAC3C,4CAA4C;SAC7C;QACD,QAAQ,EAAE;YACR,iDAAiD;YACjD,uCAAuC;YACvC,+BAA+B;YAC/B,iCAAiC;YACjC,0BAA0B;SAC3B;QACD,YAAY,EAAE,CAAC,kBAAkB,EAAE,oBAAoB,EAAE,6BAA6B,EAAE,gBAAgB,CAAC;KAC1G;CACF,CAAC"}
{"version":3,"file":"n8n-delete-workflow.js","sourceRoot":"","sources":["../../../../src/mcp/tool-docs/workflow_management/n8n-delete-workflow.ts"],"names":[],"mappings":";;;AAEa,QAAA,oBAAoB,GAAsB;IACrD,IAAI,EAAE,qBAAqB;IAC3B,QAAQ,EAAE,qBAAqB;IAC/B,UAAU,EAAE;QACV,WAAW,EAAE,8DAA8D;QAC3E,aAAa,EAAE,CAAC,IAAI,CAAC;QACrB,OAAO,EAAE,2CAA2C;QACpD,WAAW,EAAE,iBAAiB;QAC9B,IAAI,EAAE;YACJ,wBAAwB;YACxB,+BAA+B;YAC/B,+DAA+D;SAChE;KACF;IACD,IAAI,EAAE;QACJ,WAAW,EAAE,qPAAqP;QAClQ,UAAU,EAAE;YACV,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,mCAAmC,EAAE;SACzF;QACD,OAAO,EAAE,uEAAuE;QAChF,QAAQ,EAAE;YACR,gEAAgE;YAChE,yEAAyE;SAC1E;QACD,QAAQ,EAAE;YACR,2BAA2B;YAC3B,yBAAyB;YACzB,2BAA2B;YAC3B,wBAAwB;YACxB,mBAAmB;SACpB;QACD,WAAW,EAAE,mGAAmG;QAChH,aAAa,EAAE;YACb,gCAAgC;YAChC,+DAA+D;YAC/D,2CAA2C;YAC3C,4CAA4C;SAC7C;QACD,QAAQ,EAAE;YACR,iDAAiD;YACjD,uCAAuC;YACvC,+BAA+B;YAC/B,iCAAiC;YACjC,0BAA0B;SAC3B;QACD,YAAY,EAAE,CAAC,kBAAkB,EAAE,oBAAoB,EAAE,6BAA6B,EAAE,gBAAgB,CAAC;KAC1G;CACF,CAAC"}

View File

@@ -26,7 +26,7 @@ exports.n8nUpdateFullWorkflowDoc = {
settings: { type: 'object', description: 'Workflow settings to update (timezone, error handling, etc.)' },
intent: { type: 'string', description: 'Intent of the change - helps to return better response. Include in every tool call. Example: "Migrate workflow to new node versions".' }
},
returns: 'Minimal summary (id, name, active, nodeCount) for token efficiency. Use n8n_get_workflow with mode "structure" to verify current state if needed.',
returns: 'Updated workflow object with all fields including the changes applied',
examples: [
'n8n_update_full_workflow({id: "abc", intent: "Rename workflow for clarity", name: "New Name"}) - Rename with intent',
'n8n_update_full_workflow({id: "abc", name: "New Name"}) - Rename only',

View File

@@ -1 +1 @@
{"version":3,"file":"n8n-update-full-workflow.js","sourceRoot":"","sources":["../../../../src/mcp/tool-docs/workflow_management/n8n-update-full-workflow.ts"],"names":[],"mappings":";;;AAEa,QAAA,wBAAwB,GAAsB;IACzD,IAAI,EAAE,0BAA0B;IAChC,QAAQ,EAAE,qBAAqB;IAC/B,UAAU,EAAE;QACV,WAAW,EAAE,qHAAqH;QAClI,aAAa,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,aAAa,CAAC;QAC7C,OAAO,EAAE,4EAA4E;QACrF,WAAW,EAAE,mBAAmB;QAChC,IAAI,EAAE;YACJ,2EAA2E;YAC3E,gCAAgC;YAChC,sCAAsC;YACtC,0BAA0B;SAC3B;KACF;IACD,IAAI,EAAE;QACJ,WAAW,EAAE,4QAA4Q;QACzR,UAAU,EAAE;YACV,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uBAAuB,EAAE;YAC5E,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,8BAA8B,EAAE;YACrE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,oEAAoE,EAAE;YAC3G,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,+DAA+D,EAAE;YAC7G,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,8DAA8D,EAAE;YACzG,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,uIAAuI,EAAE;SACjL;QACD,OAAO,EAAE,mJAAmJ;QAC5J,QAAQ,EAAE;YACR,qHAAqH;YACrH,uEAAuE;YACvE,qIAAqI;YACrI,+IAA+I;SAChJ;QACD,QAAQ,EAAE;YACR,8BAA8B;YAC9B,mBAAmB;YACnB,0BAA0B;YAC1B,+BAA+B;YAC/B,kBAAkB;SACnB;QACD,WAAW,EAAE,wHAAwH;QACrI,aAAa,EAAE;YACb,qEAAqE;YACrE,yCAAyC;YACzC,iDAAiD;YACjD,sCAAsC;YACtC,sCAAsC;SACvC;QACD,QAAQ,EAAE;YACR,iDAAiD;YACjD,oCAAoC;YACpC,+BAA+B;YAC/B,4BAA4B;YAC5B,iDAAiD;SAClD;QACD,YAAY,EAAE,CAAC,kBAAkB,EAAE,6BAA6B,EAAE,mBAAmB,EAAE,qBAAqB,CAAC;KAC9G;CACF,CAAC"}
{"version":3,"file":"n8n-update-full-workflow.js","sourceRoot":"","sources":["../../../../src/mcp/tool-docs/workflow_management/n8n-update-full-workflow.ts"],"names":[],"mappings":";;;AAEa,QAAA,wBAAwB,GAAsB;IACzD,IAAI,EAAE,0BAA0B;IAChC,QAAQ,EAAE,qBAAqB;IAC/B,UAAU,EAAE;QACV,WAAW,EAAE,qHAAqH;QAClI,aAAa,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,aAAa,CAAC;QAC7C,OAAO,EAAE,4EAA4E;QACrF,WAAW,EAAE,mBAAmB;QAChC,IAAI,EAAE;YACJ,2EAA2E;YAC3E,gCAAgC;YAChC,sCAAsC;YACtC,0BAA0B;SAC3B;KACF;IACD,IAAI,EAAE;QACJ,WAAW,EAAE,4QAA4Q;QACzR,UAAU,EAAE;YACV,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uBAAuB,EAAE;YAC5E,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,8BAA8B,EAAE;YACrE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,oEAAoE,EAAE;YAC3G,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,+DAA+D,EAAE;YAC7G,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,8DAA8D,EAAE;YACzG,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,uIAAuI,EAAE;SACjL;QACD,OAAO,EAAE,uEAAuE;QAChF,QAAQ,EAAE;YACR,qHAAqH;YACrH,uEAAuE;YACvE,qIAAqI;YACrI,+IAA+I;SAChJ;QACD,QAAQ,EAAE;YACR,8BAA8B;YAC9B,mBAAmB;YACnB,0BAA0B;YAC1B,+BAA+B;YAC/B,kBAAkB;SACnB;QACD,WAAW,EAAE,wHAAwH;QACrI,aAAa,EAAE;YACb,qEAAqE;YACrE,yCAAyC;YACzC,iDAAiD;YACjD,sCAAsC;YACtC,sCAAsC;SACvC;QACD,QAAQ,EAAE;YACR,iDAAiD;YACjD,oCAAoC;YACpC,+BAA+B;YAC/B,4BAA4B;YAC5B,iDAAiD;SAClD;QACD,YAAY,EAAE,CAAC,kBAAkB,EAAE,6BAA6B,EAAE,mBAAmB,EAAE,qBAAqB,CAAC;KAC9G;CACF,CAAC"}

View File

@@ -313,7 +313,7 @@ n8n_update_partial_workflow({
continueOnError: { type: 'boolean', description: 'If true, apply valid operations even if some fail (best-effort mode). Returns applied and failed operation indices. Default: false (atomic)' },
intent: { type: 'string', description: 'Intent of the change - helps to return better response. Include in every tool call. Example: "Add error handling for API failures".' }
},
returns: 'Minimal summary (id, name, active, nodeCount, operationsApplied) for token efficiency. Use n8n_get_workflow with mode "structure" to verify current state if needed. Returns validation results if validateOnly=true.',
returns: 'Updated workflow object or validation results if validateOnly=true',
examples: [
'// Include intent parameter for better responses\nn8n_update_partial_workflow({id: "abc", intent: "Add error handling for API failures", operations: [{type: "addConnection", source: "HTTP Request", target: "Error Handler"}]})',
'// Add a basic node (minimal configuration)\nn8n_update_partial_workflow({id: "abc", operations: [{type: "addNode", node: {name: "Process Data", type: "n8n-nodes-base.set", position: [400, 300], parameters: {}}}]})',

View File

@@ -1 +1 @@
{"version":3,"file":"n8n-update-partial-workflow.js","sourceRoot":"","sources":["../../../../src/mcp/tool-docs/workflow_management/n8n-update-partial-workflow.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"n8n-update-partial-workflow.js","sourceRoot":"","sources":["../../../../src/mcp/tool-docs/workflow_management/n8n-update-partial-workflow.ts"],"names":[],"mappings":"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"}

View File

@@ -17,9 +17,6 @@ export interface ParsedNode {
documentation?: string;
outputs?: any[];
outputNames?: string[];
isToolVariant?: boolean;
toolVariantOf?: string;
hasToolVariant?: boolean;
}
export declare class NodeParser {
private propertyExtractor;

View File

@@ -1 +1 @@
{"version":3,"file":"node-parser.d.ts","sourceRoot":"","sources":["../../src/parsers/node-parser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,SAAS,EAEV,MAAM,qBAAqB,CAAC;AAQ7B,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,aAAa,GAAG,cAAc,CAAC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,GAAG,EAAE,CAAC;IAClB,WAAW,EAAE,GAAG,EAAE,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,GAAG,EAAE,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAEvB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,iBAAiB,CAA2B;IACpD,OAAO,CAAC,gBAAgB,CAA0B;IAElD,KAAK,CAAC,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,GAAG,UAAU;IA0B5D,OAAO,CAAC,kBAAkB;IAoD1B,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,aAAa;IAyBrB,OAAO,CAAC,cAAc;IA0FtB,OAAO,CAAC,eAAe;IA2CvB,OAAO,CAAC,cAAc;CAsDvB"}
{"version":3,"file":"node-parser.d.ts","sourceRoot":"","sources":["../../src/parsers/node-parser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,SAAS,EAEV,MAAM,qBAAqB,CAAC;AAQ7B,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,aAAa,GAAG,cAAc,CAAC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,GAAG,EAAE,CAAC;IAClB,WAAW,EAAE,GAAG,EAAE,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,GAAG,EAAE,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,iBAAiB,CAA2B;IACpD,OAAO,CAAC,gBAAgB,CAA0B;IAElD,KAAK,CAAC,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,GAAG,UAAU;IA0B5D,OAAO,CAAC,kBAAkB;IAoD1B,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,aAAa;IAyBrB,OAAO,CAAC,cAAc;IA0FtB,OAAO,CAAC,eAAe;IA2CvB,OAAO,CAAC,cAAc;CAsDvB"}

File diff suppressed because one or more lines are too long

View File

@@ -39,7 +39,6 @@ const node_loader_1 = require("../loaders/node-loader");
const node_parser_1 = require("../parsers/node-parser");
const docs_mapper_1 = require("../mappers/docs-mapper");
const node_repository_1 = require("../database/node-repository");
const tool_variant_generator_1 = require("../services/tool-variant-generator");
const template_sanitizer_1 = require("../utils/template-sanitizer");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
@@ -51,7 +50,6 @@ async function rebuild() {
const parser = new node_parser_1.NodeParser();
const mapper = new docs_mapper_1.DocsMapper();
const repository = new node_repository_1.NodeRepository(db);
const toolVariantGenerator = new tool_variant_generator_1.ToolVariantGenerator();
const schema = fs.readFileSync(path.join(__dirname, '../../src/database/schema.sql'), 'utf8');
db.exec(schema);
db.exec('DELETE FROM nodes');
@@ -66,8 +64,7 @@ async function rebuild() {
webhooks: 0,
withProperties: 0,
withOperations: 0,
withDocs: 0,
toolVariants: 0
withDocs: 0
};
console.log('🔄 Processing nodes...');
const processedNodes = [];
@@ -82,18 +79,6 @@ async function rebuild() {
}
const docs = await mapper.fetchDocumentation(parsed.nodeType);
parsed.documentation = docs || undefined;
if (parsed.isAITool && !parsed.isTrigger) {
const toolVariant = toolVariantGenerator.generateToolVariant(parsed);
if (toolVariant) {
parsed.hasToolVariant = true;
processedNodes.push({
parsed: toolVariant,
docs: undefined,
nodeName: `${nodeName}Tool`
});
stats.toolVariants++;
}
}
processedNodes.push({ parsed, docs: docs || undefined, nodeName });
}
catch (error) {
@@ -150,7 +135,6 @@ async function rebuild() {
console.log(` Successful: ${stats.successful}`);
console.log(` Failed: ${stats.failed}`);
console.log(` AI Tools: ${stats.aiTools}`);
console.log(` Tool Variants: ${stats.toolVariants}`);
console.log(` Triggers: ${stats.triggers}`);
console.log(` Webhooks: ${stats.webhooks}`);
console.log(` With Properties: ${stats.withProperties}`);
@@ -181,7 +165,6 @@ async function rebuild() {
console.log('\n✨ Rebuild complete!');
db.close();
}
const MIN_EXPECTED_TOOL_VARIANTS = 200;
function validateDatabase(repository) {
const issues = [];
try {
@@ -209,13 +192,6 @@ function validateDatabase(repository) {
if (aiTools.length === 0) {
issues.push('No AI tools found - check detection logic');
}
const toolVariantCount = repository.getToolVariantCount();
if (toolVariantCount === 0) {
issues.push('No Tool variants found - check ToolVariantGenerator');
}
else if (toolVariantCount < MIN_EXPECTED_TOOL_VARIANTS) {
issues.push(`Only ${toolVariantCount} Tool variants found - expected at least ${MIN_EXPECTED_TOOL_VARIANTS}`);
}
const ftsTableCheck = db.prepare(`
SELECT name FROM sqlite_master
WHERE type='table' AND name='nodes_fts'

File diff suppressed because one or more lines are too long

View File

@@ -5,7 +5,7 @@ import { WorkflowDiffOperation } from '../types/workflow-diff';
import { Workflow } from '../types/n8n-api';
import { PostUpdateGuidance } from './post-update-validator';
export type FixConfidenceLevel = 'high' | 'medium' | 'low';
export type FixType = 'expression-format' | 'typeversion-correction' | 'error-output-config' | 'node-type-correction' | 'webhook-missing-path' | 'typeversion-upgrade' | 'version-migration' | 'tool-variant-correction';
export type FixType = 'expression-format' | 'typeversion-correction' | 'error-output-config' | 'node-type-correction' | 'webhook-missing-path' | 'typeversion-upgrade' | 'version-migration';
export interface AutoFixConfig {
applyFixes: boolean;
fixTypes?: FixType[];
@@ -62,7 +62,6 @@ export declare class WorkflowAutoFixer {
private processErrorOutputFixes;
private processNodeTypeFixes;
private processWebhookPathFixes;
private processToolVariantFixes;
private setNestedValue;
private filterByConfidence;
private filterOperationsByFixes;

View File

@@ -1 +1 @@
{"version":3,"file":"workflow-auto-fixer.d.ts","sourceRoot":"","sources":["../../src/services/workflow-auto-fixer.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"workflow-auto-fixer.d.ts","sourceRoot":"","sources":["../../src/services/workflow-auto-fixer.ts"],"names":[],"mappings":"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"}

View File

@@ -63,9 +63,6 @@ class WorkflowAutoFixer {
if (!fullConfig.fixTypes || fullConfig.fixTypes.includes('webhook-missing-path')) {
this.processWebhookPathFixes(validationResult, nodeMap, operations, fixes);
}
if (!fullConfig.fixTypes || fullConfig.fixTypes.includes('tool-variant-correction')) {
this.processToolVariantFixes(validationResult, nodeMap, workflow, operations, fixes);
}
if (!fullConfig.fixTypes || fullConfig.fixTypes.includes('typeversion-upgrade')) {
await this.processVersionUpgradeFixes(workflow, nodeMap, operations, fixes, postUpdateGuidance);
}
@@ -270,41 +267,6 @@ class WorkflowAutoFixer {
}
}
}
processToolVariantFixes(validationResult, nodeMap, _workflow, operations, fixes) {
for (const error of validationResult.errors) {
if (error.code !== 'WRONG_NODE_TYPE_FOR_AI_TOOL' || !error.fix) {
continue;
}
const fix = error.fix;
if (fix.type !== 'tool-variant-correction') {
continue;
}
const nodeName = error.nodeName || error.nodeId;
if (!nodeName)
continue;
const node = nodeMap.get(nodeName);
if (!node)
continue;
fixes.push({
node: nodeName,
field: 'type',
type: 'tool-variant-correction',
before: fix.currentType,
after: fix.suggestedType,
confidence: 'high',
description: fix.description || `Replace "${fix.currentType}" with Tool variant "${fix.suggestedType}"`
});
const operation = {
type: 'updateNode',
nodeId: nodeName,
updates: {
type: fix.suggestedType
}
};
operations.push(operation);
logger.info(`Generated tool variant correction for ${nodeName}: ${fix.currentType}${fix.suggestedType}`);
}
}
setNestedValue(obj, path, value) {
if (!obj || typeof obj !== 'object') {
throw new Error('Cannot set value on non-object');
@@ -410,8 +372,7 @@ class WorkflowAutoFixer {
'node-type-correction': 0,
'webhook-missing-path': 0,
'typeversion-upgrade': 0,
'version-migration': 0,
'tool-variant-correction': 0
'version-migration': 0
},
byConfidence: {
'high': 0,
@@ -451,9 +412,6 @@ class WorkflowAutoFixer {
if (stats.byType['version-migration'] > 0) {
parts.push(`${stats.byType['version-migration']} version ${stats.byType['version-migration'] === 1 ? 'migration' : 'migrations'}`);
}
if (stats.byType['tool-variant-correction'] > 0) {
parts.push(`${stats.byType['tool-variant-correction']} tool variant ${stats.byType['tool-variant-correction'] === 1 ? 'correction' : 'corrections'}`);
}
if (parts.length === 0) {
return `Fixed ${stats.total} ${stats.total === 1 ? 'issue' : 'issues'}`;
}

File diff suppressed because one or more lines are too long

View File

@@ -47,19 +47,12 @@ interface WorkflowJson {
pinData?: any;
meta?: any;
}
export interface ValidationIssue {
interface ValidationIssue {
type: 'error' | 'warning';
nodeId?: string;
nodeName?: string;
message: string;
details?: any;
code?: string;
fix?: {
type: string;
currentType?: string;
suggestedType?: string;
description?: string;
};
}
export interface WorkflowValidationResult {
valid: boolean;
@@ -93,7 +86,6 @@ export declare class WorkflowValidator {
private validateConnectionOutputs;
private validateErrorOutputConfiguration;
private validateAIToolConnection;
private validateAIToolSource;
private hasCycle;
private validateExpressions;
private countExpressionsInObject;

View File

@@ -1 +1 @@
{"version":3,"file":"workflow-validator.d.ts","sourceRoot":"","sources":["../../src/services/workflow-validator.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"workflow-validator.d.ts","sourceRoot":"","sources":["../../src/services/workflow-validator.ts"],"names":[],"mappings":"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"}

View File

@@ -11,10 +11,8 @@ const node_similarity_service_1 = require("./node-similarity-service");
const node_type_normalizer_1 = require("../utils/node-type-normalizer");
const logger_1 = require("../utils/logger");
const ai_node_validator_1 = require("./ai-node-validator");
const ai_tool_validators_1 = require("./ai-tool-validators");
const node_type_utils_1 = require("../utils/node-type-utils");
const node_classification_1 = require("../utils/node-classification");
const tool_variant_generator_1 = require("./tool-variant-generator");
const logger = new logger_1.Logger({ prefix: '[WorkflowValidator]' });
class WorkflowValidator {
constructor(nodeRepository, nodeValidator) {
@@ -369,7 +367,6 @@ class WorkflowValidator {
this.validateConnectionOutputs(sourceName, outputs.error, nodeMap, nodeIdMap, result, 'error');
}
if (outputs.ai_tool) {
this.validateAIToolSource(sourceNode, result);
this.validateConnectionOutputs(sourceName, outputs.ai_tool, nodeMap, nodeIdMap, result, 'ai_tool');
}
}
@@ -558,51 +555,6 @@ class WorkflowValidator {
});
}
}
validateAIToolSource(sourceNode, result) {
const normalizedType = node_type_normalizer_1.NodeTypeNormalizer.normalizeToFullForm(sourceNode.type);
if ((0, ai_tool_validators_1.isAIToolSubNode)(normalizedType)) {
return;
}
const nodeInfo = this.nodeRepository.getNode(normalizedType);
if (tool_variant_generator_1.ToolVariantGenerator.isToolVariantNodeType(normalizedType)) {
if (nodeInfo?.isToolVariant) {
return;
}
}
if (!nodeInfo) {
return;
}
if (nodeInfo.hasToolVariant) {
const toolVariantType = tool_variant_generator_1.ToolVariantGenerator.getToolVariantNodeType(normalizedType);
const workflowToolVariantType = node_type_normalizer_1.NodeTypeNormalizer.toWorkflowFormat(toolVariantType);
result.errors.push({
type: 'error',
nodeId: sourceNode.id,
nodeName: sourceNode.name,
message: `Node "${sourceNode.name}" uses "${sourceNode.type}" which cannot output ai_tool connections. ` +
`Use the Tool variant "${workflowToolVariantType}" instead for AI Agent integration.`,
code: 'WRONG_NODE_TYPE_FOR_AI_TOOL',
fix: {
type: 'tool-variant-correction',
currentType: sourceNode.type,
suggestedType: workflowToolVariantType,
description: `Change node type from "${sourceNode.type}" to "${workflowToolVariantType}"`
}
});
return;
}
if (nodeInfo.isAITool) {
return;
}
result.errors.push({
type: 'error',
nodeId: sourceNode.id,
nodeName: sourceNode.name,
message: `Node "${sourceNode.name}" of type "${sourceNode.type}" cannot output ai_tool connections. ` +
`Only AI tool nodes (e.g., Calculator, HTTP Request Tool) or Tool variants (e.g., *Tool suffix nodes) can be connected to AI Agents as tools.`,
code: 'INVALID_AI_TOOL_SOURCE'
});
}
hasCycle(workflow) {
const visited = new Set();
const recursionStack = new Set();

File diff suppressed because one or more lines are too long

View File

@@ -7,13 +7,13 @@ export declare const telemetryEventSchema: z.ZodObject<{
created_at: z.ZodOptional<z.ZodString>;
}, "strip", z.ZodTypeAny, {
properties: Record<string, any>;
event: string;
user_id: string;
event: string;
created_at?: string | undefined;
}, {
properties: Record<string, unknown>;
event: string;
user_id: string;
event: string;
created_at?: string | undefined;
}>;
export declare const workflowTelemetrySchema: z.ZodObject<{

View File

@@ -12,6 +12,5 @@ export declare class NodeTypeNormalizer {
static normalizeWorkflowNodeTypes(workflow: any): any;
static isFullForm(type: string): boolean;
static isShortForm(type: string): boolean;
static toWorkflowFormat(type: string): string;
}
//# sourceMappingURL=node-type-normalizer.d.ts.map

View File

@@ -1 +1 @@
{"version":3,"file":"node-type-normalizer.d.ts","sourceRoot":"","sources":["../../src/utils/node-type-normalizer.ts"],"names":[],"mappings":"AA2CA,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,WAAW,GAAG,SAAS,CAAC;CACzD;AAED,qBAAa,kBAAkB;IAyB7B,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAuChD,MAAM,CAAC,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,2BAA2B;IAkBtE,OAAO,CAAC,MAAM,CAAC,aAAa;IAuB5B,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IA8B3D,MAAM,CAAC,0BAA0B,CAAC,QAAQ,EAAE,GAAG,GAAG,GAAG;IAoBrD,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAkBxC,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAgCzC,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAgB9C"}
{"version":3,"file":"node-type-normalizer.d.ts","sourceRoot":"","sources":["../../src/utils/node-type-normalizer.ts"],"names":[],"mappings":"AA2CA,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,WAAW,GAAG,SAAS,CAAC;CACzD;AAED,qBAAa,kBAAkB;IAyB7B,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAuChD,MAAM,CAAC,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,2BAA2B;IAkBtE,OAAO,CAAC,MAAM,CAAC,aAAa;IAuB5B,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IA8B3D,MAAM,CAAC,0BAA0B,CAAC,QAAQ,EAAE,GAAG,GAAG,GAAG;IAoBrD,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAkBxC,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;CAU1C"}

View File

@@ -70,18 +70,6 @@ class NodeTypeNormalizer {
return (type.startsWith('nodes-base.') ||
type.startsWith('nodes-langchain.'));
}
static toWorkflowFormat(type) {
if (!type || typeof type !== 'string') {
return type;
}
if (type.startsWith('nodes-base.')) {
return type.replace(/^nodes-base\./, 'n8n-nodes-base.');
}
if (type.startsWith('nodes-langchain.')) {
return type.replace(/^nodes-langchain\./, '@n8n/n8n-nodes-langchain.');
}
return type;
}
}
exports.NodeTypeNormalizer = NodeTypeNormalizer;
//# sourceMappingURL=node-type-normalizer.js.map

View File

@@ -1 +1 @@
{"version":3,"file":"node-type-normalizer.js","sourceRoot":"","sources":["../../src/utils/node-type-normalizer.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"node-type-normalizer.js","sourceRoot":"","sources":["../../src/utils/node-type-normalizer.ts"],"names":[],"mappings":"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"}

6004
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "n8n-mcp",
"version": "2.29.2",
"version": "2.28.7",
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@@ -141,16 +141,16 @@
},
"dependencies": {
"@modelcontextprotocol/sdk": "1.20.1",
"@n8n/n8n-nodes-langchain": "^1.122.1",
"@n8n/n8n-nodes-langchain": "^1.121.1",
"@supabase/supabase-js": "^2.57.4",
"dotenv": "^16.5.0",
"express": "^5.1.0",
"express-rate-limit": "^7.1.5",
"form-data": "^4.0.5",
"lru-cache": "^11.2.1",
"n8n": "^1.123.4",
"n8n-core": "^1.122.1",
"n8n-workflow": "^1.120.0",
"n8n": "^1.122.4",
"n8n-core": "^1.121.1",
"n8n-workflow": "^1.119.1",
"openai": "^4.77.0",
"sql.js": "^1.13.0",
"tslib": "^2.6.2",

View File

@@ -23,13 +23,12 @@ export class NodeRepository {
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,
is_webhook, is_versioned, version, documentation,
properties_schema, operations, credentials_required,
outputs, output_names
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
stmt.run(
node.nodeType,
node.packageName,
@@ -41,9 +40,6 @@ export class NodeRepository {
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),
@@ -198,58 +194,6 @@ export class NodeRepository {
return this.getAITools();
}
/**
* Get the Tool variant for a base node
*/
getToolVariant(baseNodeType: string): any | null {
// Validate node type format (must be package.nodeName pattern)
if (!baseNodeType || typeof baseNodeType !== 'string' || !baseNodeType.includes('.')) {
return null;
}
const toolNodeType = `${baseNodeType}Tool`;
return this.getNode(toolNodeType);
}
/**
* Get the base node for a Tool variant
*/
getBaseNodeForToolVariant(toolNodeType: string): any | null {
const row = this.db.prepare(`
SELECT tool_variant_of FROM nodes WHERE node_type = ?
`).get(toolNodeType) as any;
if (!row?.tool_variant_of) return null;
return this.getNode(row.tool_variant_of);
}
/**
* Get all Tool variants
*/
getToolVariants(): any[] {
const rows = this.db.prepare(`
SELECT node_type, display_name, description, package_name, tool_variant_of
FROM nodes
WHERE is_tool_variant = 1
ORDER BY display_name
`).all() as any[];
return rows.map(row => ({
nodeType: row.node_type,
displayName: row.display_name,
description: row.description,
package: row.package_name,
toolVariantOf: row.tool_variant_of
}));
}
/**
* Get count of Tool variants
*/
getToolVariantCount(): number {
const result = this.db.prepare('SELECT COUNT(*) as count FROM nodes WHERE is_tool_variant = 1').get() as any;
return result.count;
}
getNodesByPackage(packageName: string): any[] {
const rows = this.db.prepare(`
SELECT * FROM nodes WHERE package_name = ?
@@ -306,9 +250,6 @@ export class NodeRepository {
isTrigger: Number(row.is_trigger) === 1,
isWebhook: Number(row.is_webhook) === 1,
isVersioned: Number(row.is_versioned) === 1,
isToolVariant: Number(row.is_tool_variant) === 1,
toolVariantOf: row.tool_variant_of || null,
hasToolVariant: Number(row.has_tool_variant) === 1,
version: row.version,
properties: this.safeJsonParse(row.properties_schema, []),
operations: this.safeJsonParse(row.operations, []),

View File

@@ -10,9 +10,6 @@ CREATE TABLE IF NOT EXISTS nodes (
is_trigger INTEGER DEFAULT 0,
is_webhook INTEGER DEFAULT 0,
is_versioned INTEGER DEFAULT 0,
is_tool_variant INTEGER DEFAULT 0, -- 1 if this is a *Tool variant for AI Agents
tool_variant_of TEXT, -- For Tool variants: base node type (e.g., nodes-base.supabase)
has_tool_variant INTEGER DEFAULT 0, -- For base nodes: 1 if Tool variant exists
version TEXT,
documentation TEXT,
properties_schema TEXT,
@@ -27,8 +24,6 @@ CREATE TABLE IF NOT EXISTS nodes (
CREATE INDEX IF NOT EXISTS idx_package ON nodes(package_name);
CREATE INDEX IF NOT EXISTS idx_ai_tool ON nodes(is_ai_tool);
CREATE INDEX IF NOT EXISTS idx_category ON nodes(category);
CREATE INDEX IF NOT EXISTS idx_tool_variant ON nodes(is_tool_variant);
CREATE INDEX IF NOT EXISTS idx_tool_variant_of ON nodes(tool_variant_of);
-- FTS5 full-text search index for nodes
CREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(

View File

@@ -518,13 +518,8 @@ export async function handleCreateWorkflow(args: unknown, context?: InstanceCont
return {
success: true,
data: {
id: workflow.id,
name: workflow.name,
active: workflow.active,
nodeCount: workflow.nodes?.length || 0
},
message: `Workflow "${workflow.name}" created successfully with ID: ${workflow.id}. Use n8n_get_workflow with mode 'structure' to verify current state.`
data: workflow,
message: `Workflow "${workflow.name}" created successfully with ID: ${workflow.id}`
};
} catch (error) {
if (error instanceof z.ZodError) {
@@ -818,13 +813,8 @@ export async function handleUpdateWorkflow(
return {
success: true,
data: {
id: workflow.id,
name: workflow.name,
active: workflow.active,
nodeCount: workflow.nodes?.length || 0
},
message: `Workflow "${workflow.name}" updated successfully. Use n8n_get_workflow with mode 'structure' to verify current state.`
data: workflow,
message: `Workflow "${workflow.name}" updated successfully`
};
} catch (error) {
// Track failed mutation
@@ -890,12 +880,8 @@ export async function handleDeleteWorkflow(args: unknown, context?: InstanceCont
return {
success: true,
data: {
id: deleted?.id || id,
name: deleted?.name,
deleted: true
},
message: `Workflow "${deleted?.name || id}" deleted successfully.`
data: deleted,
message: `Workflow ${id} deleted successfully`
};
} catch (error) {
if (error instanceof z.ZodError) {
@@ -998,7 +984,7 @@ export async function handleValidateWorkflow(
const input = validateWorkflowSchema.parse(args);
// First, fetch the workflow from n8n
const workflowResponse = await handleGetWorkflow({ id: input.id }, context);
const workflowResponse = await handleGetWorkflow({ id: input.id });
if (!workflowResponse.success) {
return workflowResponse; // Return the error from fetching

View File

@@ -363,15 +363,13 @@ export async function handleUpdatePartialWorkflow(
return {
success: true,
data: {
id: finalWorkflow.id,
name: finalWorkflow.name,
active: finalWorkflow.active,
nodeCount: finalWorkflow.nodes?.length || 0,
operationsApplied: diffResult.operationsApplied
},
message: `Workflow "${finalWorkflow.name}" updated successfully. Applied ${diffResult.operationsApplied} operations.${activationMessage} Use n8n_get_workflow with mode 'structure' to verify current state.`,
data: finalWorkflow,
message: `Workflow "${finalWorkflow.name}" updated successfully. Applied ${diffResult.operationsApplied} operations.${activationMessage}`,
details: {
operationsApplied: diffResult.operationsApplied,
workflowId: finalWorkflow.id,
workflowName: finalWorkflow.name,
active: finalWorkflow.active,
applied: diffResult.applied,
failed: diffResult.failed,
errors: diffResult.errors,

View File

@@ -52,9 +52,6 @@ interface NodeRow {
is_trigger: number;
is_webhook: number;
is_versioned: number;
is_tool_variant: number;
tool_variant_of?: string;
has_tool_variant: number;
version?: string;
documentation?: string;
properties_schema?: string;
@@ -68,14 +65,6 @@ interface VersionSummary {
hasVersionHistory: boolean;
}
interface ToolVariantGuidance {
isToolVariant: boolean;
toolVariantOf?: string;
hasToolVariant: boolean;
toolVariantNodeType?: string;
guidance?: string;
}
interface NodeMinimalInfo {
nodeType: string;
workflowNodeType: string;
@@ -86,7 +75,6 @@ interface NodeMinimalInfo {
isAITool: boolean;
isTrigger: boolean;
isWebhook: boolean;
toolVariantInfo?: ToolVariantGuidance;
}
interface NodeStandardInfo {
@@ -100,7 +88,6 @@ interface NodeStandardInfo {
credentials?: any;
examples?: any[];
versionInfo: VersionSummary;
toolVariantInfo?: ToolVariantGuidance;
}
interface NodeFullInfo {
@@ -113,7 +100,6 @@ interface NodeFullInfo {
credentials?: any;
documentation?: string;
versionInfo: VersionSummary;
toolVariantInfo?: ToolVariantGuidance;
}
interface VersionHistoryInfo {
@@ -1390,20 +1376,12 @@ export class N8NDocumentationMCPServer {
});
}
const result: any = {
return {
...node,
workflowNodeType: getWorkflowNodeType(node.package ?? 'n8n-nodes-base', node.nodeType),
aiToolCapabilities,
outputs
};
// Add tool variant guidance if applicable
const toolVariantInfo = this.buildToolVariantGuidance(node);
if (toolVariantInfo) {
result.toolVariantInfo = toolVariantInfo;
}
return result;
}
/**
@@ -2267,7 +2245,7 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
// Get the latest version - this is important for AI to use correct typeVersion
const latestVersion = node.version ?? '1';
const result: any = {
const result = {
nodeType: node.nodeType,
workflowNodeType: getWorkflowNodeType(node.package ?? 'n8n-nodes-base', node.nodeType),
displayName: node.displayName,
@@ -2297,12 +2275,6 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
}
};
// Add tool variant guidance if applicable
const toolVariantInfo = this.buildToolVariantGuidance(node);
if (toolVariantInfo) {
result.toolVariantInfo = toolVariantInfo;
}
// Add examples from templates if requested
if (includeExamples) {
try {
@@ -2454,7 +2426,7 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
throw new Error(`Node ${nodeType} not found`);
}
const result: NodeMinimalInfo = {
return {
nodeType: node.nodeType,
workflowNodeType: getWorkflowNodeType(node.package ?? 'n8n-nodes-base', node.nodeType),
displayName: node.displayName,
@@ -2465,14 +2437,6 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
isTrigger: node.isTrigger,
isWebhook: node.isWebhook
};
// Add tool variant guidance if applicable
const toolVariantInfo = this.buildToolVariantGuidance(node);
if (toolVariantInfo) {
result.toolVariantInfo = toolVariantInfo;
}
return result;
}
case 'standard': {
@@ -3154,45 +3118,7 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
'Extend AI agent capabilities'
];
}
/**
* Build tool variant guidance for node responses.
* Provides cross-reference information between base nodes and their Tool variants.
*/
private buildToolVariantGuidance(node: any): ToolVariantGuidance | undefined {
const isToolVariant = !!node.isToolVariant;
const hasToolVariant = !!node.hasToolVariant;
const toolVariantOf = node.toolVariantOf;
// If this is neither a Tool variant nor has one, no guidance needed
if (!isToolVariant && !hasToolVariant) {
return undefined;
}
if (isToolVariant) {
// This IS a Tool variant (e.g., nodes-base.supabaseTool)
return {
isToolVariant: true,
toolVariantOf,
hasToolVariant: false,
guidance: `This is the Tool variant for AI Agent integration. Use this node type when connecting to AI Agents. The base node is: ${toolVariantOf}`
};
}
if (hasToolVariant && node.nodeType) {
// This base node HAS a Tool variant (e.g., nodes-base.supabase)
const toolVariantNodeType = `${node.nodeType}Tool`;
return {
isToolVariant: false,
hasToolVariant: true,
toolVariantNodeType,
guidance: `To use this node with AI Agents, use the Tool variant: ${toolVariantNodeType}. The Tool variant has an additional 'toolDescription' property and outputs 'ai_tool' instead of 'main'.`
};
}
return undefined;
}
private getAIToolExamples(nodeType: string): any {
const exampleMap: Record<string, any> = {
'nodes-base.slack': {

View File

@@ -23,7 +23,7 @@ export const n8nCreateWorkflowDoc: ToolDocumentation = {
connections: { type: 'object', required: true, description: 'Node connections. Keys are source node IDs' },
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: 'Created workflow object with id, name, nodes, connections, active status',
examples: [
`// Basic webhook to Slack workflow
n8n_create_workflow({

View File

@@ -19,7 +19,7 @@ export const n8nDeleteWorkflowDoc: ToolDocumentation = {
parameters: {
id: { type: 'string', required: true, description: 'Workflow ID to delete permanently' }
},
returns: 'Minimal confirmation (id, name, deleted: true) for token efficiency.',
returns: 'Success confirmation or error if workflow not found/cannot be deleted',
examples: [
'n8n_delete_workflow({id: "abc123"}) - Delete specific workflow',
'if (confirm) { n8n_delete_workflow({id: wf.id}); } // With confirmation'

View File

@@ -25,7 +25,7 @@ export const n8nUpdateFullWorkflowDoc: ToolDocumentation = {
settings: { type: 'object', description: 'Workflow settings to update (timezone, error handling, etc.)' },
intent: { type: 'string', description: 'Intent of the change - helps to return better response. Include in every tool call. Example: "Migrate workflow to new node versions".' }
},
returns: 'Minimal summary (id, name, active, nodeCount) for token efficiency. Use n8n_get_workflow with mode "structure" to verify current state if needed.',
returns: 'Updated workflow object with all fields including the changes applied',
examples: [
'n8n_update_full_workflow({id: "abc", intent: "Rename workflow for clarity", name: "New Name"}) - Rename with intent',
'n8n_update_full_workflow({id: "abc", name: "New Name"}) - Rename only',

View File

@@ -312,7 +312,7 @@ n8n_update_partial_workflow({
continueOnError: { type: 'boolean', description: 'If true, apply valid operations even if some fail (best-effort mode). Returns applied and failed operation indices. Default: false (atomic)' },
intent: { type: 'string', description: 'Intent of the change - helps to return better response. Include in every tool call. Example: "Add error handling for API failures".' }
},
returns: 'Minimal summary (id, name, active, nodeCount, operationsApplied) for token efficiency. Use n8n_get_workflow with mode "structure" to verify current state if needed. Returns validation results if validateOnly=true.',
returns: 'Updated workflow object or validation results if validateOnly=true',
examples: [
'// Include intent parameter for better responses\nn8n_update_partial_workflow({id: "abc", intent: "Add error handling for API failures", operations: [{type: "addConnection", source: "HTTP Request", target: "Error Handler"}]})',
'// Add a basic node (minimal configuration)\nn8n_update_partial_workflow({id: "abc", operations: [{type: "addNode", node: {name: "Process Data", type: "n8n-nodes-base.set", position: [400, 300], parameters: {}}}]})',

View File

@@ -28,10 +28,6 @@ export interface ParsedNode {
documentation?: string;
outputs?: any[];
outputNames?: string[];
// Tool variant fields (for nodes with usableAsTool: true)
isToolVariant?: boolean; // True for *Tool variants (e.g., supabaseTool)
toolVariantOf?: string; // For Tool variants: base node type (e.g., nodes-base.supabase)
hasToolVariant?: boolean; // For base nodes: true if Tool variant exists
}
export class NodeParser {

View File

@@ -8,7 +8,6 @@ import { N8nNodeLoader } from '../loaders/node-loader';
import { NodeParser, ParsedNode } from '../parsers/node-parser';
import { DocsMapper } from '../mappers/docs-mapper';
import { NodeRepository } from '../database/node-repository';
import { ToolVariantGenerator } from '../services/tool-variant-generator';
import { TemplateSanitizer } from '../utils/template-sanitizer';
import * as fs from 'fs';
import * as path from 'path';
@@ -22,7 +21,6 @@ async function rebuild() {
const parser = new NodeParser();
const mapper = new DocsMapper();
const repository = new NodeRepository(db);
const toolVariantGenerator = new ToolVariantGenerator();
// Initialize database
const schema = fs.readFileSync(path.join(__dirname, '../../src/database/schema.sql'), 'utf8');
@@ -45,8 +43,7 @@ async function rebuild() {
webhooks: 0,
withProperties: 0,
withOperations: 0,
withDocs: 0,
toolVariants: 0
withDocs: 0
};
// Process each node (documentation fetching must be outside transaction due to async)
@@ -57,38 +54,21 @@ async function rebuild() {
try {
// Parse node
const parsed = parser.parse(NodeClass, packageName);
// Validate parsed data
if (!parsed.nodeType || !parsed.displayName) {
throw new Error(`Missing required fields - nodeType: ${parsed.nodeType}, displayName: ${parsed.displayName}, packageName: ${parsed.packageName}`);
}
// Additional validation for required fields
if (!parsed.packageName) {
throw new Error(`Missing packageName for node ${nodeName}`);
}
// Get documentation
const docs = await mapper.fetchDocumentation(parsed.nodeType);
parsed.documentation = docs || undefined;
// Generate Tool variant for nodes with usableAsTool: true
if (parsed.isAITool && !parsed.isTrigger) {
const toolVariant = toolVariantGenerator.generateToolVariant(parsed);
if (toolVariant) {
// Mark base node as having a Tool variant
parsed.hasToolVariant = true;
// Add Tool variant to processed nodes
processedNodes.push({
parsed: toolVariant,
docs: undefined, // Tool variants don't have separate docs
nodeName: `${nodeName}Tool`
});
stats.toolVariants++;
}
}
processedNodes.push({ parsed, docs: docs || undefined, nodeName });
} catch (error) {
stats.failed++;
@@ -147,7 +127,6 @@ async function rebuild() {
console.log(` Successful: ${stats.successful}`);
console.log(` Failed: ${stats.failed}`);
console.log(` AI Tools: ${stats.aiTools}`);
console.log(` Tool Variants: ${stats.toolVariants}`);
console.log(` Triggers: ${stats.triggers}`);
console.log(` Webhooks: ${stats.webhooks}`);
console.log(` With Properties: ${stats.withProperties}`);
@@ -186,9 +165,6 @@ async function rebuild() {
db.close();
}
// Expected minimum based on n8n v1.123.4 AI-capable nodes
const MIN_EXPECTED_TOOL_VARIANTS = 200;
function validateDatabase(repository: NodeRepository): { passed: boolean; issues: string[] } {
const issues = [];
@@ -229,14 +205,6 @@ function validateDatabase(repository: NodeRepository): { passed: boolean; issues
issues.push('No AI tools found - check detection logic');
}
// Check Tool variants
const toolVariantCount = repository.getToolVariantCount();
if (toolVariantCount === 0) {
issues.push('No Tool variants found - check ToolVariantGenerator');
} else if (toolVariantCount < MIN_EXPECTED_TOOL_VARIANTS) {
issues.push(`Only ${toolVariantCount} Tool variants found - expected at least ${MIN_EXPECTED_TOOL_VARIANTS}`);
}
// Check FTS5 table existence and population
const ftsTableCheck = db.prepare(`
SELECT name FROM sqlite_master

View File

@@ -1,158 +0,0 @@
/**
* Tool Variant Generator
*
* Generates Tool variant nodes for nodes with usableAsTool: true.
*
* n8n dynamically creates Tool variants (e.g., supabaseTool from supabase)
* that can be connected to AI Agents. These variants have:
* - A 'Tool' suffix on the node type
* - An additional 'toolDescription' property
* - Output type 'ai_tool' instead of 'main'
*/
import type { ParsedNode } from '../parsers/node-parser';
export class ToolVariantGenerator {
/**
* Generate a Tool variant from a base node with usableAsTool: true
*
* @param baseNode - The base ParsedNode that has isAITool: true
* @returns A new ParsedNode representing the Tool variant, or null if not applicable
*/
generateToolVariant(baseNode: ParsedNode): ParsedNode | null {
// Only generate for nodes with usableAsTool: true (isAITool)
if (!baseNode.isAITool) {
return null;
}
// Don't generate Tool variant for nodes that are already Tool variants
if (baseNode.isToolVariant) {
return null;
}
// Don't generate for trigger nodes (they can't be used as tools)
if (baseNode.isTrigger) {
return null;
}
// Validate nodeType exists
if (!baseNode.nodeType) {
return null;
}
// Generate the Tool variant node type
// e.g., nodes-base.supabase -> nodes-base.supabaseTool
const toolNodeType = `${baseNode.nodeType}Tool`;
// Ensure properties is an array to prevent spread operator errors
const baseProperties = Array.isArray(baseNode.properties) ? baseNode.properties : [];
return {
...baseNode,
nodeType: toolNodeType,
displayName: `${baseNode.displayName} Tool`,
description: baseNode.description
? `${baseNode.description} (AI Tool variant for use with AI Agents)`
: 'AI Tool variant for use with AI Agents',
// Mark as Tool variant
isToolVariant: true,
toolVariantOf: baseNode.nodeType,
hasToolVariant: false, // Tool variants don't have further variants
// Override outputs for Tool variant
outputs: [{ type: 'ai_tool', displayName: 'Tool' }],
outputNames: ['Tool'],
// Add toolDescription property at the beginning
properties: this.addToolDescriptionProperty(baseProperties, baseNode.displayName),
};
}
/**
* Add the toolDescription property to the beginning of the properties array
*/
private addToolDescriptionProperty(properties: any[], displayName: string): any[] {
const toolDescriptionProperty = {
displayName: 'Tool Description',
name: 'toolDescription',
type: 'string',
default: '',
required: false,
description: 'Description for the AI to understand what this tool does and when to use it',
typeOptions: {
rows: 3
},
placeholder: `e.g., Use this tool to ${this.generateDescriptionPlaceholder(displayName)}`
};
return [toolDescriptionProperty, ...properties];
}
/**
* Generate a placeholder description based on the node display name
*/
private generateDescriptionPlaceholder(displayName: string): string {
const lowerName = displayName.toLowerCase();
// Common patterns
if (lowerName.includes('database') || lowerName.includes('sql')) {
return 'query and manage data in the database';
}
if (lowerName.includes('email') || lowerName.includes('mail')) {
return 'send and manage emails';
}
if (lowerName.includes('sheet') || lowerName.includes('spreadsheet')) {
return 'read and write spreadsheet data';
}
if (lowerName.includes('file') || lowerName.includes('drive') || lowerName.includes('storage')) {
return 'manage files and storage';
}
if (lowerName.includes('message') || lowerName.includes('chat') || lowerName.includes('slack')) {
return 'send messages and communicate';
}
if (lowerName.includes('http') || lowerName.includes('api') || lowerName.includes('request')) {
return 'make API requests and fetch data';
}
if (lowerName.includes('calendar') || lowerName.includes('event')) {
return 'manage calendar events and schedules';
}
// Default placeholder
return `interact with ${displayName}`;
}
/**
* Check if a node type looks like a Tool variant.
* Valid Tool variants must:
* - End with 'Tool' but not 'ToolTool'
* - Have a valid package.nodeName pattern (contain a dot)
* - Have content after the dot before 'Tool' suffix
*/
static isToolVariantNodeType(nodeType: string): boolean {
if (!nodeType || !nodeType.endsWith('Tool') || nodeType.endsWith('ToolTool')) {
return false;
}
// The base part (without 'Tool' suffix) should be a valid node pattern
const basePart = nodeType.slice(0, -4);
// Valid pattern: package.nodeName (must contain a dot and have content after it)
return basePart.includes('.') && basePart.split('.').pop()!.length > 0;
}
/**
* Get the base node type from a Tool variant node type
*/
static getBaseNodeType(toolNodeType: string): string | null {
if (!ToolVariantGenerator.isToolVariantNodeType(toolNodeType)) {
return null;
}
return toolNodeType.slice(0, -4); // Remove 'Tool' suffix
}
/**
* Get the Tool variant node type from a base node type
*/
static getToolVariantNodeType(baseNodeType: string): string {
return `${baseNodeType}Tool`;
}
}

View File

@@ -30,9 +30,8 @@ export type FixType =
| 'error-output-config'
| 'node-type-correction'
| 'webhook-missing-path'
| 'typeversion-upgrade' // Proactive version upgrades
| 'version-migration' // Smart version migrations with breaking changes
| 'tool-variant-correction'; // Fix base nodes used as AI tools when Tool variant exists
| 'typeversion-upgrade' // NEW: Proactive version upgrades
| 'version-migration'; // NEW: Smart version migrations with breaking changes
export interface AutoFixConfig {
applyFixes: boolean;
@@ -160,12 +159,7 @@ export class WorkflowAutoFixer {
this.processWebhookPathFixes(validationResult, nodeMap, operations, fixes);
}
// Process tool variant corrections (HIGH confidence)
if (!fullConfig.fixTypes || fullConfig.fixTypes.includes('tool-variant-correction')) {
this.processToolVariantFixes(validationResult, nodeMap, workflow, operations, fixes);
}
// Process version upgrades (HIGH/MEDIUM confidence)
// NEW: Process version upgrades (HIGH/MEDIUM confidence)
if (!fullConfig.fixTypes || fullConfig.fixTypes.includes('typeversion-upgrade')) {
await this.processVersionUpgradeFixes(workflow, nodeMap, operations, fixes, postUpdateGuidance);
}
@@ -465,69 +459,6 @@ export class WorkflowAutoFixer {
}
}
/**
* Process tool variant corrections for base nodes incorrectly used as AI tools.
*
* When a base node (e.g., n8n-nodes-base.supabase) is connected via ai_tool output
* but has a Tool variant available (e.g., n8n-nodes-base.supabaseTool), this fix
* replaces the node type with the correct Tool variant.
*
* @param validationResult - Validation result containing errors to process
* @param nodeMap - Map of node names/IDs to node objects
* @param _workflow - Workflow object (unused, kept for API consistency with other fix methods)
* @param operations - Array to push generated diff operations to
* @param fixes - Array to push generated fix records to
*/
private processToolVariantFixes(
validationResult: WorkflowValidationResult,
nodeMap: Map<string, WorkflowNode>,
_workflow: Workflow,
operations: WorkflowDiffOperation[],
fixes: FixOperation[]
): void {
for (const error of validationResult.errors) {
// Check for errors with the WRONG_NODE_TYPE_FOR_AI_TOOL code
// ValidationIssue interface includes optional code and fix properties
if (error.code !== 'WRONG_NODE_TYPE_FOR_AI_TOOL' || !error.fix) {
continue;
}
const fix = error.fix;
if (fix.type !== 'tool-variant-correction') {
continue;
}
const nodeName = error.nodeName || error.nodeId;
if (!nodeName) continue;
const node = nodeMap.get(nodeName);
if (!node) continue;
// Create the fix record
fixes.push({
node: nodeName,
field: 'type',
type: 'tool-variant-correction',
before: fix.currentType,
after: fix.suggestedType,
confidence: 'high', // This is a direct match - we know exactly which type to use
description: fix.description || `Replace "${fix.currentType}" with Tool variant "${fix.suggestedType}"`
});
// Create the update operation
const operation: UpdateNodeOperation = {
type: 'updateNode',
nodeId: nodeName,
updates: {
type: fix.suggestedType
}
};
operations.push(operation);
logger.info(`Generated tool variant correction for ${nodeName}: ${fix.currentType}${fix.suggestedType}`);
}
}
/**
* Set a nested value in an object using a path array
* Includes validation to prevent silent failures
@@ -676,8 +607,7 @@ export class WorkflowAutoFixer {
'node-type-correction': 0,
'webhook-missing-path': 0,
'typeversion-upgrade': 0,
'version-migration': 0,
'tool-variant-correction': 0
'version-migration': 0
},
byConfidence: {
'high': 0,
@@ -726,9 +656,6 @@ export class WorkflowAutoFixer {
if (stats.byType['version-migration'] > 0) {
parts.push(`${stats.byType['version-migration']} version ${stats.byType['version-migration'] === 1 ? 'migration' : 'migrations'}`);
}
if (stats.byType['tool-variant-correction'] > 0) {
parts.push(`${stats.byType['tool-variant-correction']} tool variant ${stats.byType['tool-variant-correction'] === 1 ? 'correction' : 'corrections'}`);
}
if (parts.length === 0) {
return `Fixed ${stats.total} ${stats.total === 1 ? 'issue' : 'issues'}`;

View File

@@ -12,10 +12,8 @@ import { NodeSimilarityService, NodeSuggestion } from './node-similarity-service
import { NodeTypeNormalizer } from '../utils/node-type-normalizer';
import { Logger } from '../utils/logger';
import { validateAISpecificNodes, hasAINodes } from './ai-node-validator';
import { isAIToolSubNode } from './ai-tool-validators';
import { isTriggerNode } from '../utils/node-type-utils';
import { isNonExecutableNode } from '../utils/node-classification';
import { ToolVariantGenerator } from './tool-variant-generator';
const logger = new Logger({ prefix: '[WorkflowValidator]' });
interface WorkflowNode {
@@ -56,19 +54,12 @@ interface WorkflowJson {
meta?: any;
}
export interface ValidationIssue {
interface ValidationIssue {
type: 'error' | 'warning';
nodeId?: string;
nodeName?: string;
message: string;
details?: any;
code?: string;
fix?: {
type: string;
currentType?: string;
suggestedType?: string;
description?: string;
};
}
export interface WorkflowValidationResult {
@@ -594,9 +585,6 @@ export class WorkflowValidator {
// Check AI tool outputs
if (outputs.ai_tool) {
// Validate that the source node can actually output ai_tool
this.validateAIToolSource(sourceNode, result);
this.validateConnectionOutputs(
sourceName,
outputs.ai_tool,
@@ -870,83 +858,6 @@ export class WorkflowValidator {
}
}
/**
* Validate that a node can actually output ai_tool connections.
*
* Valid ai_tool sources are:
* 1. Langchain tool nodes (in AI_TOOL_VALIDATORS)
* 2. Tool variant nodes (e.g., nodes-base.supabaseTool)
*
* If a base node (e.g., nodes-base.supabase) is used with ai_tool connection
* but it has a Tool variant available, this is an error.
*/
private validateAIToolSource(
sourceNode: WorkflowNode,
result: WorkflowValidationResult
): void {
const normalizedType = NodeTypeNormalizer.normalizeToFullForm(sourceNode.type);
// Check if it's a known langchain tool node
if (isAIToolSubNode(normalizedType)) {
return; // Valid - it's a langchain tool
}
// Get node info from repository (single lookup, reused below)
const nodeInfo = this.nodeRepository.getNode(normalizedType);
// Check if it's a Tool variant (ends with Tool and is in database as isToolVariant)
if (ToolVariantGenerator.isToolVariantNodeType(normalizedType)) {
// It looks like a Tool variant, verify it exists in database
if (nodeInfo?.isToolVariant) {
return; // Valid - it's a Tool variant
}
}
if (!nodeInfo) {
// Node not found in database - might be a community node or unknown
// Don't error here, let other validation handle unknown nodes
return;
}
// Check if this is a base node that has a Tool variant available
if (nodeInfo.hasToolVariant) {
const toolVariantType = ToolVariantGenerator.getToolVariantNodeType(normalizedType);
const workflowToolVariantType = NodeTypeNormalizer.toWorkflowFormat(toolVariantType);
result.errors.push({
type: 'error',
nodeId: sourceNode.id,
nodeName: sourceNode.name,
message: `Node "${sourceNode.name}" uses "${sourceNode.type}" which cannot output ai_tool connections. ` +
`Use the Tool variant "${workflowToolVariantType}" instead for AI Agent integration.`,
code: 'WRONG_NODE_TYPE_FOR_AI_TOOL',
fix: {
type: 'tool-variant-correction',
currentType: sourceNode.type,
suggestedType: workflowToolVariantType,
description: `Change node type from "${sourceNode.type}" to "${workflowToolVariantType}"`
}
});
return;
}
// Check if it's an AI-capable node (isAITool flag) but not a Tool variant
if (nodeInfo.isAITool) {
// This node is AI-capable, which is fine for ai_tool connections
return;
}
// Node is not valid for ai_tool connections
result.errors.push({
type: 'error',
nodeId: sourceNode.id,
nodeName: sourceNode.name,
message: `Node "${sourceNode.name}" of type "${sourceNode.type}" cannot output ai_tool connections. ` +
`Only AI tool nodes (e.g., Calculator, HTTP Request Tool) or Tool variants (e.g., *Tool suffix nodes) can be connected to AI Agents as tools.`,
code: 'INVALID_AI_TOOL_SOURCE'
});
}
/**
* Check if workflow has cycles
* Allow legitimate loops for SplitInBatches and similar loop nodes

View File

@@ -231,42 +231,4 @@ export class NodeTypeNormalizer {
type.startsWith('nodes-langchain.')
);
}
/**
* Convert short database format to full n8n workflow format.
*
* This method converts node types from the SHORT form used in the database
* to the FULL form required by the n8n API.
*
* @param type - Node type in short database format (e.g., 'nodes-base.webhook')
* @returns Node type in full workflow format (e.g., 'n8n-nodes-base.webhook')
*
* @example
* toWorkflowFormat('nodes-base.webhook')
* // → 'n8n-nodes-base.webhook'
*
* @example
* toWorkflowFormat('nodes-langchain.agent')
* // → '@n8n/n8n-nodes-langchain.agent'
*
* @example
* toWorkflowFormat('n8n-nodes-base.webhook')
* // → 'n8n-nodes-base.webhook' (already in full format)
*/
static toWorkflowFormat(type: string): string {
if (!type || typeof type !== 'string') {
return type;
}
// Convert short form to full form (API/workflow format)
if (type.startsWith('nodes-base.')) {
return type.replace(/^nodes-base\./, 'n8n-nodes-base.');
}
if (type.startsWith('nodes-langchain.')) {
return type.replace(/^nodes-langchain\./, '@n8n/n8n-nodes-langchain.');
}
// Already in full form or community node - return unchanged
return type;
}
}

View File

@@ -348,8 +348,8 @@ describe('MCP Performance Tests', () => {
console.log(`Memory increase after large operations: ${(memoryIncrease / 1024 / 1024).toFixed(2)}MB`);
// Should not retain excessive memory (30MB threshold accounts for CI variability)
expect(memoryIncrease).toBeLessThan(30 * 1024 * 1024);
// Should not retain excessive memory
expect(memoryIncrease).toBeLessThan(20 * 1024 * 1024);
});
});

View File

@@ -74,21 +74,17 @@ describe('Integration: handleCreateWorkflow', () => {
const result = response.data as Workflow;
// Verify workflow created successfully
// Response now returns minimal data
expect(result).toBeDefined();
expect(result.id).toBeTruthy();
if (!result.id) throw new Error('Workflow ID is missing');
context.trackWorkflow(result.id);
expect(result.name).toBe(workflowName);
expect((result as any).nodeCount).toBe(1);
expect(result.nodes).toHaveLength(1);
// Fetch actual workflow to verify details
const workflow2 = await client.getWorkflow(result.id);
expect(workflow2.nodes).toHaveLength(1);
// Critical: Verify FULL node type format is preserved
expect(workflow2.nodes[0].type).toBe('n8n-nodes-base.webhook');
expect(workflow2.nodes[0].name).toBe('Webhook');
expect(workflow2.nodes[0].parameters).toBeDefined();
expect(result.nodes[0].type).toBe('n8n-nodes-base.webhook');
expect(result.nodes[0].name).toBe('Webhook');
expect(result.nodes[0].parameters).toBeDefined();
});
});
@@ -108,21 +104,16 @@ describe('Integration: handleCreateWorkflow', () => {
expect(response.success).toBe(true);
const result = response.data as Workflow;
// Response now returns minimal data
expect(result).toBeDefined();
expect(result.id).toBeTruthy();
if (!result.id) throw new Error('Workflow ID is missing');
context.trackWorkflow(result.id);
expect(result.name).toBe(workflowName);
expect((result as any).nodeCount).toBe(2);
// Fetch actual workflow to verify details
const actual = await client.getWorkflow(result.id);
expect(actual.nodes).toHaveLength(2);
expect(result.nodes).toHaveLength(2);
// Verify both nodes created with FULL type format
const webhookNode = actual.nodes.find((n: any) => n.name === 'Webhook');
const httpNode = actual.nodes.find((n: any) => n.name === 'HTTP Request');
const webhookNode = result.nodes.find((n: any) => n.name === 'Webhook');
const httpNode = result.nodes.find((n: any) => n.name === 'HTTP Request');
expect(webhookNode).toBeDefined();
expect(webhookNode!.type).toBe('n8n-nodes-base.webhook');
@@ -131,8 +122,8 @@ describe('Integration: handleCreateWorkflow', () => {
expect(httpNode!.type).toBe('n8n-nodes-base.httpRequest');
// Verify connections
expect(actual.connections).toBeDefined();
expect(actual.connections.Webhook).toBeDefined();
expect(result.connections).toBeDefined();
expect(result.connections.Webhook).toBeDefined();
});
it('should create workflow with langchain agent node', async () => {
@@ -146,20 +137,15 @@ describe('Integration: handleCreateWorkflow', () => {
expect(response.success).toBe(true);
const result = response.data as Workflow;
// Response now returns minimal data
expect(result).toBeDefined();
expect(result.id).toBeTruthy();
if (!result.id) throw new Error('Workflow ID is missing');
context.trackWorkflow(result.id);
expect(result.name).toBe(workflowName);
expect((result as any).nodeCount).toBe(2);
// Fetch actual workflow to verify details
const actual = await client.getWorkflow(result.id);
expect(actual.nodes).toHaveLength(2);
expect(result.nodes).toHaveLength(2);
// Verify langchain node type format
const agentNode = actual.nodes.find((n: any) => n.name === 'AI Agent');
const agentNode = result.nodes.find((n: any) => n.name === 'AI Agent');
expect(agentNode).toBeDefined();
expect(agentNode!.type).toBe('@n8n/n8n-nodes-langchain.agent');
});
@@ -175,26 +161,21 @@ describe('Integration: handleCreateWorkflow', () => {
expect(response.success).toBe(true);
const result = response.data as Workflow;
// Response now returns minimal data
expect(result).toBeDefined();
expect(result.id).toBeTruthy();
if (!result.id) throw new Error('Workflow ID is missing');
context.trackWorkflow(result.id);
expect(result.name).toBe(workflowName);
expect((result as any).nodeCount).toBe(4);
// Fetch actual workflow to verify details
const actual = await client.getWorkflow(result.id);
expect(actual.nodes).toHaveLength(4);
expect(result.nodes).toHaveLength(4);
// Verify all node types preserved
const nodeTypes = actual.nodes.map((n: any) => n.type);
const nodeTypes = result.nodes.map((n: any) => n.type);
expect(nodeTypes).toContain('n8n-nodes-base.webhook');
expect(nodeTypes).toContain('n8n-nodes-base.set');
expect(nodeTypes).toContain('n8n-nodes-base.merge');
// Verify complex connections
expect(actual.connections.Webhook.main[0]).toHaveLength(2); // Branches to 2 nodes
expect(result.connections.Webhook.main[0]).toHaveLength(2); // Branches to 2 nodes
});
});
@@ -214,23 +195,19 @@ describe('Integration: handleCreateWorkflow', () => {
expect(response.success).toBe(true);
const result = response.data as Workflow;
// Response now returns minimal data
expect(result).toBeDefined();
expect(result.id).toBeTruthy();
if (!result.id) throw new Error('Workflow ID is missing');
context.trackWorkflow(result.id);
// Fetch actual workflow to verify connections
const actual = await client.getWorkflow(result.id);
expect(actual.connections).toBeDefined();
expect(result.connections).toBeDefined();
// Verify branching: Webhook -> Set 1 and Set 2
const webhookConnections = actual.connections.Webhook.main[0];
const webhookConnections = result.connections.Webhook.main[0];
expect(webhookConnections).toHaveLength(2);
// Verify merging: Set 1 -> Merge (port 0), Set 2 -> Merge (port 1)
const set1Connections = actual.connections['Set 1'].main[0];
const set2Connections = actual.connections['Set 2'].main[0];
const set1Connections = result.connections['Set 1'].main[0];
const set2Connections = result.connections['Set 2'].main[0];
expect(set1Connections[0].node).toBe('Merge');
expect(set1Connections[0].index).toBe(0);
@@ -257,16 +234,12 @@ describe('Integration: handleCreateWorkflow', () => {
expect(response.success).toBe(true);
const result = response.data as Workflow;
// Response now returns minimal data
expect(result).toBeDefined();
expect(result.id).toBeTruthy();
if (!result.id) throw new Error('Workflow ID is missing');
context.trackWorkflow(result.id);
// Fetch actual workflow to verify settings
const actual = await client.getWorkflow(result.id);
expect(actual.settings).toBeDefined();
expect(actual.settings!.executionOrder).toBe('v1');
expect(result.settings).toBeDefined();
expect(result.settings!.executionOrder).toBe('v1');
});
it('should create workflow with n8n expressions', async () => {
@@ -280,19 +253,14 @@ describe('Integration: handleCreateWorkflow', () => {
expect(response.success).toBe(true);
const result = response.data as Workflow;
// Response now returns minimal data
expect(result).toBeDefined();
expect(result.id).toBeTruthy();
if (!result.id) throw new Error('Workflow ID is missing');
context.trackWorkflow(result.id);
expect((result as any).nodeCount).toBe(2);
// Fetch actual workflow to verify expressions
const actual = await client.getWorkflow(result.id);
expect(actual.nodes).toHaveLength(2);
expect(result.nodes).toHaveLength(2);
// Verify Set node with expressions
const setNode = actual.nodes.find((n: any) => n.name === 'Set Variables');
const setNode = result.nodes.find((n: any) => n.name === 'Set Variables');
expect(setNode).toBeDefined();
expect(setNode!.parameters.assignments).toBeDefined();
@@ -315,26 +283,21 @@ describe('Integration: handleCreateWorkflow', () => {
expect(response.success).toBe(true);
const result = response.data as Workflow;
// Response now returns minimal data
expect(result).toBeDefined();
expect(result.id).toBeTruthy();
if (!result.id) throw new Error('Workflow ID is missing');
context.trackWorkflow(result.id);
expect((result as any).nodeCount).toBe(3);
// Fetch actual workflow to verify error handling
const actual = await client.getWorkflow(result.id);
expect(actual.nodes).toHaveLength(3);
expect(result.nodes).toHaveLength(3);
// Verify HTTP node with error handling
const httpNode = actual.nodes.find((n: any) => n.name === 'HTTP Request');
const httpNode = result.nodes.find((n: any) => n.name === 'HTTP Request');
expect(httpNode).toBeDefined();
expect(httpNode!.continueOnFail).toBe(true);
expect(httpNode!.onError).toBe('continueErrorOutput');
// Verify error connection
expect(actual.connections['HTTP Request'].error).toBeDefined();
expect(actual.connections['HTTP Request'].error[0][0].node).toBe('Handle Error');
expect(result.connections['HTTP Request'].error).toBeDefined();
expect(result.connections['HTTP Request'].error[0][0].node).toBe('Handle Error');
});
});

View File

@@ -99,13 +99,7 @@ describe('Integration: handleUpdatePartialWorkflow', () => {
);
expect(response.success).toBe(true);
// Response now returns minimal data - verify with a follow-up get
const responseData = response.data as any;
expect(responseData.id).toBe(created.id);
expect(responseData.nodeCount).toBe(2);
// Fetch actual workflow to verify changes
const updated = await client.getWorkflow(created.id);
const updated = response.data as any;
expect(updated.nodes).toHaveLength(2);
expect(updated.nodes.find((n: any) => n.name === 'Set')).toBeDefined();
});
@@ -177,13 +171,7 @@ describe('Integration: handleUpdatePartialWorkflow', () => {
);
expect(response.success).toBe(true);
// Response now returns minimal data - verify with a follow-up get
const responseData = response.data as any;
expect(responseData.id).toBe(created.id);
expect(responseData.nodeCount).toBe(1);
// Fetch actual workflow to verify changes
const updated = await client.getWorkflow(created.id);
const updated = response.data as any;
expect(updated.nodes).toHaveLength(1);
expect(updated.nodes.find((n: any) => n.name === 'HTTP Request')).toBeUndefined();
});
@@ -250,14 +238,9 @@ describe('Integration: handleUpdatePartialWorkflow', () => {
);
expect(response.success).toBe(true);
// Response now returns minimal data - verify with a follow-up get
expect((response.data as any).id).toBe(created.id);
// Fetch actual workflow to verify changes
const updated = await client.getWorkflow(created.id);
const updated = response.data as any;
const webhookNode = updated.nodes.find((n: any) => n.name === 'Webhook');
expect(webhookNode).toBeDefined();
expect(webhookNode!.parameters.path).toBe('updated-path');
expect(webhookNode.parameters.path).toBe('updated-path');
});
it('should update nested parameters', async () => {
@@ -291,15 +274,10 @@ describe('Integration: handleUpdatePartialWorkflow', () => {
);
expect(response.success).toBe(true);
// Response now returns minimal data - verify with a follow-up get
expect((response.data as any).id).toBe(created.id);
// Fetch actual workflow to verify changes
const updated = await client.getWorkflow(created.id);
const updated = response.data as any;
const webhookNode = updated.nodes.find((n: any) => n.name === 'Webhook');
expect(webhookNode).toBeDefined();
expect(webhookNode!.parameters.httpMethod).toBe('POST');
expect(webhookNode!.parameters.path).toBe('new-path');
expect(webhookNode.parameters.httpMethod).toBe('POST');
expect(webhookNode.parameters.path).toBe('new-path');
});
});
@@ -334,14 +312,9 @@ describe('Integration: handleUpdatePartialWorkflow', () => {
);
expect(response.success).toBe(true);
// Response now returns minimal data - verify with a follow-up get
expect((response.data as any).id).toBe(created.id);
// Fetch actual workflow to verify changes
const updated = await client.getWorkflow(created.id);
const updated = response.data as any;
const webhookNode = updated.nodes.find((n: any) => n.name === 'Webhook');
expect(webhookNode).toBeDefined();
expect(webhookNode!.position).toEqual(newPosition);
expect(webhookNode.position).toEqual(newPosition);
});
});
@@ -373,14 +346,9 @@ describe('Integration: handleUpdatePartialWorkflow', () => {
);
expect(response.success).toBe(true);
// Response now returns minimal data - verify with a follow-up get
expect((response.data as any).id).toBe(created.id);
// Fetch actual workflow to verify changes
const updated = await client.getWorkflow(created.id);
const updated = response.data as any;
const webhookNode = updated.nodes.find((n: any) => n.name === 'Webhook');
expect(webhookNode).toBeDefined();
expect(webhookNode!.disabled).toBe(true);
expect(webhookNode.disabled).toBe(true);
});
it('should enable a disabled node', async () => {
@@ -421,15 +389,10 @@ describe('Integration: handleUpdatePartialWorkflow', () => {
);
expect(response.success).toBe(true);
// Response now returns minimal data - verify with a follow-up get
expect((response.data as any).id).toBe(created.id);
// Fetch actual workflow to verify changes
const updated = await client.getWorkflow(created.id);
const updated = response.data as any;
const webhookNode = updated.nodes.find((n: any) => n.name === 'Webhook');
expect(webhookNode).toBeDefined();
// After enabling, disabled should be false or undefined (both mean enabled)
expect(webhookNode!.disabled).toBeFalsy();
expect(webhookNode.disabled).toBeFalsy();
});
});
});
@@ -471,11 +434,7 @@ describe('Integration: handleUpdatePartialWorkflow', () => {
);
expect(response.success).toBe(true);
// Response now returns minimal data - verify with a follow-up get
expect((response.data as any).id).toBe(created.id);
// Fetch actual workflow to verify changes
const updated = await client.getWorkflow(created.id);
const updated = response.data as any;
expect(updated.connections).toBeDefined();
expect(updated.connections.Webhook).toBeDefined();
});
@@ -699,11 +658,8 @@ describe('Integration: handleUpdatePartialWorkflow', () => {
);
expect(response.success).toBe(true);
// Response now returns minimal data - verify with a follow-up get
expect((response.data as any).id).toBe(created.id);
const updated = response.data as any;
// Fetch actual workflow to verify changes
const updated = await client.getWorkflow(created.id);
// Note: n8n API may not return all settings in response
// The operation should succeed even if settings aren't reflected in the response
expect(updated.settings).toBeDefined();
@@ -866,13 +822,7 @@ describe('Integration: handleUpdatePartialWorkflow', () => {
);
expect(response.success).toBe(true);
// Response now returns minimal data - verify with a follow-up get
const responseData = response.data as any;
expect(responseData.id).toBe(created.id);
expect(responseData.nodeCount).toBe(2);
// Fetch actual workflow to verify changes
const updated = await client.getWorkflow(created.id);
const updated = response.data as any;
expect(updated.nodes).toHaveLength(2);
expect(updated.connections.Webhook).toBeDefined();
});
@@ -1090,13 +1040,7 @@ describe('Integration: handleUpdatePartialWorkflow', () => {
// Should succeed
expect(response.success).toBe(true);
// Response now returns minimal data - verify with a follow-up get
const responseData = response.data as any;
expect(responseData.id).toBe(created.id);
expect(responseData.nodeCount).toBe(5); // Original 4 + 1 new
// Fetch actual workflow to verify changes
const updated = await client.getWorkflow(created.id);
const updated = response.data as any;
expect(updated.nodes).toHaveLength(5); // Original 4 + 1 new
expect(updated.nodes.find((n: any) => n.name === 'Process Data')).toBeDefined();
});

View File

@@ -75,18 +75,14 @@ describe('Integration: handleUpdateWorkflow', () => {
mcpContext
);
// Verify MCP response - now returns minimal data
// Verify MCP response
expect(response.success).toBe(true);
expect(response.data).toBeDefined();
const updated = response.data as any;
expect(updated.id).toBe(created.id);
expect(updated.name).toBe(replacement.name);
expect(updated.nodeCount).toBe(2); // HTTP workflow has 2 nodes
// Fetch actual workflow to verify
const actual = await client.getWorkflow(created.id);
expect(actual.nodes).toHaveLength(2);
expect(updated.nodes).toHaveLength(2); // HTTP workflow has 2 nodes
});
});
@@ -151,14 +147,9 @@ describe('Integration: handleUpdateWorkflow', () => {
);
expect(response.success).toBe(true);
// Response now returns minimal data
const updated = response.data as any;
expect(updated.nodeCount).toBe(2);
// Fetch actual workflow to verify
const actual = await client.getWorkflow(created.id);
expect(actual.nodes).toHaveLength(2);
expect(actual.nodes.find((n: any) => n.name === 'Set')).toBeDefined();
expect(updated.nodes).toHaveLength(2);
expect(updated.nodes.find((n: any) => n.name === 'Set')).toBeDefined();
});
});
@@ -202,13 +193,9 @@ describe('Integration: handleUpdateWorkflow', () => {
);
expect(response.success).toBe(true);
// Response now returns minimal data
const updated = response.data as any;
expect(updated.nodeCount).toBe(1); // Nodes unchanged
// Fetch actual workflow to verify
const actual = await client.getWorkflow(created.id);
expect(actual.nodes).toHaveLength(1);
// Note: n8n API may not return settings in response
expect(updated.nodes).toHaveLength(1); // Nodes unchanged
});
});
@@ -307,14 +294,9 @@ describe('Integration: handleUpdateWorkflow', () => {
);
expect(response.success).toBe(true);
// Response now returns minimal data
const updated = response.data as any;
expect(updated.name).toBe(newName);
expect(updated.nodeCount).toBe(1); // Structure unchanged
// Fetch actual workflow to verify
const actual = await client.getWorkflow(created.id);
expect(actual.nodes).toHaveLength(1);
expect(updated.nodes).toHaveLength(1); // Structure unchanged
});
});
@@ -358,13 +340,9 @@ describe('Integration: handleUpdateWorkflow', () => {
);
expect(response.success).toBe(true);
// Response now returns minimal data
const updated = response.data as any;
expect(updated.name).toBe(newName);
// Fetch actual workflow to verify settings
const actual = await client.getWorkflow(created.id);
expect(actual.settings?.timezone).toBe('America/New_York');
expect(updated.settings?.timezone).toBe('America/New_York');
});
});
});

View File

@@ -18,7 +18,6 @@ import { gunzipSync } from 'zlib';
describe('Integration: Real-World Type Structure Validation', () => {
let db: DatabaseAdapter;
let templatesAvailable = false;
const SAMPLE_SIZE = 20; // Use smaller sample for fast tests
const SPECIAL_TYPES: NodePropertyTypes[] = [
'filter',
@@ -30,14 +29,6 @@ describe('Integration: Real-World Type Structure Validation', () => {
beforeAll(async () => {
// Connect to production database
db = await createDatabaseAdapter('./data/nodes.db');
// Check if templates are available (may not be populated in CI)
try {
const result = db.prepare('SELECT COUNT(*) as count FROM templates').get() as any;
templatesAvailable = result.count > 0;
} catch {
templatesAvailable = false;
}
});
afterAll(() => {
@@ -101,10 +92,6 @@ describe('Integration: Real-World Type Structure Validation', () => {
}
it('should have templates database available', () => {
// Skip this test if templates are not populated (common in CI environments)
if (!templatesAvailable) {
return; // Test passes but doesn't validate - templates not available
}
const result = db.prepare('SELECT COUNT(*) as count FROM templates').get() as any;
expect(result.count).toBeGreaterThan(0);
});

View File

@@ -89,10 +89,10 @@ describe('NodeRepository - Core Functionality', () => {
};
repository.saveNode(parsedNode);
// Verify prepare was called with correct SQL
expect(mockAdapter.prepare).toHaveBeenCalledWith(expect.stringContaining('INSERT OR REPLACE INTO nodes'));
// Get the prepared statement and verify run was called
const stmt = mockAdapter._getStatement(mockAdapter.prepare.mock.lastCall?.[0] || '');
expect(stmt?.run).toHaveBeenCalledWith(
@@ -106,9 +106,6 @@ describe('NodeRepository - Core Functionality', () => {
0, // isTrigger
0, // isWebhook
1, // isVersioned
0, // isToolVariant
null, // toolVariantOf
0, // hasToolVariant
'1.0',
'HTTP Request documentation',
JSON.stringify([{ name: 'url', type: 'string' }], null, 2),
@@ -138,14 +135,14 @@ describe('NodeRepository - Core Functionality', () => {
};
repository.saveNode(minimalNode);
const stmt = mockAdapter._getStatement(mockAdapter.prepare.mock.lastCall?.[0] || '');
const runCall = stmt?.run.mock.lastCall;
expect(runCall?.[2]).toBe('Simple Node'); // displayName
expect(runCall?.[3]).toBeUndefined(); // description
expect(runCall?.[13]).toBeUndefined(); // version (was 10, now 13 after 3 new columns)
expect(runCall?.[14]).toBeNull(); // documentation (was 11, now 14 after 3 new columns)
expect(runCall?.[10]).toBeUndefined(); // version
expect(runCall?.[11]).toBeNull(); // documentation
});
});
@@ -162,9 +159,6 @@ describe('NodeRepository - Core Functionality', () => {
is_trigger: 0,
is_webhook: 0,
is_versioned: 1,
is_tool_variant: 0,
tool_variant_of: null,
has_tool_variant: 0,
version: '1.0',
properties_schema: JSON.stringify([{ name: 'url', type: 'string' }]),
operations: JSON.stringify([{ name: 'execute' }]),
@@ -173,11 +167,11 @@ describe('NodeRepository - Core Functionality', () => {
outputs: null,
output_names: null
};
mockAdapter._setMockData('node:nodes-base.httpRequest', mockRow);
const result = repository.getNode('nodes-base.httpRequest');
expect(result).toEqual({
nodeType: 'nodes-base.httpRequest',
displayName: 'HTTP Request',
@@ -189,9 +183,6 @@ describe('NodeRepository - Core Functionality', () => {
isTrigger: false,
isWebhook: false,
isVersioned: true,
isToolVariant: false,
toolVariantOf: null,
hasToolVariant: false,
version: '1.0',
properties: [{ name: 'url', type: 'string' }],
operations: [{ name: 'execute' }],
@@ -219,9 +210,6 @@ describe('NodeRepository - Core Functionality', () => {
is_trigger: 0,
is_webhook: 0,
is_versioned: 0,
is_tool_variant: 0,
tool_variant_of: null,
has_tool_variant: 0,
version: null,
properties_schema: '{invalid json',
operations: 'not json at all',
@@ -230,11 +218,11 @@ describe('NodeRepository - Core Functionality', () => {
outputs: null,
output_names: null
};
mockAdapter._setMockData('node:nodes-base.broken', mockRow);
const result = repository.getNode('nodes-base.broken');
expect(result?.properties).toEqual([]); // defaultValue from safeJsonParse
expect(result?.operations).toEqual([]); // defaultValue from safeJsonParse
expect(result?.credentials).toEqual({ valid: 'json' }); // successfully parsed
@@ -350,11 +338,11 @@ describe('NodeRepository - Core Functionality', () => {
};
repository.saveNode(node);
const stmt = mockAdapter._getStatement(mockAdapter.prepare.mock.lastCall?.[0] || '');
const runCall = stmt?.run.mock.lastCall;
const savedProperties = runCall?.[15]; // was 12, now 15 after 3 new columns
const savedProperties = runCall?.[12];
expect(savedProperties).toBe(JSON.stringify(largeProperties, null, 2));
});
@@ -370,9 +358,6 @@ describe('NodeRepository - Core Functionality', () => {
is_trigger: 0,
is_webhook: '1', // String that should be converted
is_versioned: '0', // String that should be converted
is_tool_variant: 1,
tool_variant_of: 'nodes-base.bool-base',
has_tool_variant: 0,
version: null,
properties_schema: '[]',
operations: '[]',
@@ -381,18 +366,15 @@ describe('NodeRepository - Core Functionality', () => {
outputs: null,
output_names: null
};
mockAdapter._setMockData('node:nodes-base.bool-test', mockRow);
const result = repository.getNode('nodes-base.bool-test');
expect(result?.isAITool).toBe(true);
expect(result?.isTrigger).toBe(false);
expect(result?.isWebhook).toBe(true);
expect(result?.isVersioned).toBe(false);
expect(result?.isToolVariant).toBe(true);
expect(result?.toolVariantOf).toBe('nodes-base.bool-base');
expect(result?.hasToolVariant).toBe(false);
});
});
});

View File

@@ -59,11 +59,10 @@ describe('NodeRepository - Outputs Handling', () => {
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,
is_webhook, is_versioned, version, documentation,
properties_schema, operations, credentials_required,
outputs, output_names
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
expect(mockStatement.run).toHaveBeenCalledWith(
@@ -73,14 +72,11 @@ describe('NodeRepository - Outputs Handling', () => {
'Split data into batches',
'transform',
'programmatic',
0, // isAITool
0, // isTrigger
0, // isWebhook
0, // isVersioned
0, // isToolVariant
null, // toolVariantOf
0, // hasToolVariant
'3', // version
0, // false
0, // false
0, // false
0, // false
'3',
null, // documentation
JSON.stringify([], null, 2), // properties
JSON.stringify([], null, 2), // operations
@@ -118,8 +114,8 @@ describe('NodeRepository - Outputs Handling', () => {
repository.saveNode(node);
const callArgs = mockStatement.run.mock.calls[0];
expect(callArgs[18]).toBe(JSON.stringify(outputs, null, 2)); // outputs
expect(callArgs[19]).toBe(null); // output_names should be null
expect(callArgs[15]).toBe(JSON.stringify(outputs, null, 2)); // outputs
expect(callArgs[16]).toBe(null); // output_names should be null
});
it('should save node with only outputNames (no outputs)', () => {
@@ -147,8 +143,8 @@ describe('NodeRepository - Outputs Handling', () => {
repository.saveNode(node);
const callArgs = mockStatement.run.mock.calls[0];
expect(callArgs[18]).toBe(null); // outputs should be null
expect(callArgs[19]).toBe(JSON.stringify(outputNames, null, 2)); // output_names
expect(callArgs[15]).toBe(null); // outputs should be null
expect(callArgs[16]).toBe(JSON.stringify(outputNames, null, 2)); // output_names
});
it('should save node without outputs or outputNames', () => {
@@ -173,8 +169,8 @@ describe('NodeRepository - Outputs Handling', () => {
repository.saveNode(node);
const callArgs = mockStatement.run.mock.calls[0];
expect(callArgs[18]).toBe(null); // outputs should be null
expect(callArgs[19]).toBe(null); // output_names should be null
expect(callArgs[15]).toBe(null); // outputs should be null
expect(callArgs[16]).toBe(null); // output_names should be null
});
it('should handle empty outputs and outputNames arrays', () => {
@@ -200,8 +196,8 @@ describe('NodeRepository - Outputs Handling', () => {
repository.saveNode(node);
const callArgs = mockStatement.run.mock.calls[0];
expect(callArgs[18]).toBe(JSON.stringify([], null, 2)); // outputs
expect(callArgs[19]).toBe(JSON.stringify([], null, 2)); // output_names
expect(callArgs[15]).toBe(JSON.stringify([], null, 2)); // outputs
expect(callArgs[16]).toBe(JSON.stringify([], null, 2)); // output_names
});
});
@@ -224,9 +220,6 @@ describe('NodeRepository - Outputs Handling', () => {
is_trigger: 0,
is_webhook: 0,
is_versioned: 0,
is_tool_variant: 0,
tool_variant_of: null,
has_tool_variant: 0,
version: '3',
properties_schema: JSON.stringify([]),
operations: JSON.stringify([]),
@@ -251,9 +244,6 @@ describe('NodeRepository - Outputs Handling', () => {
isTrigger: false,
isWebhook: false,
isVersioned: false,
isToolVariant: false,
toolVariantOf: null,
hasToolVariant: false,
version: '3',
properties: [],
operations: [],
@@ -280,9 +270,6 @@ describe('NodeRepository - Outputs Handling', () => {
is_trigger: 0,
is_webhook: 0,
is_versioned: 0,
is_tool_variant: 0,
tool_variant_of: null,
has_tool_variant: 0,
version: '2',
properties_schema: JSON.stringify([]),
operations: JSON.stringify([]),
@@ -314,9 +301,6 @@ describe('NodeRepository - Outputs Handling', () => {
is_trigger: 0,
is_webhook: 0,
is_versioned: 0,
is_tool_variant: 0,
tool_variant_of: null,
has_tool_variant: 0,
version: '1',
properties_schema: JSON.stringify([]),
operations: JSON.stringify([]),
@@ -346,9 +330,6 @@ describe('NodeRepository - Outputs Handling', () => {
is_trigger: 0,
is_webhook: 0,
is_versioned: 0,
is_tool_variant: 0,
tool_variant_of: null,
has_tool_variant: 0,
version: '4',
properties_schema: JSON.stringify([]),
operations: JSON.stringify([]),
@@ -378,9 +359,6 @@ describe('NodeRepository - Outputs Handling', () => {
is_trigger: 0,
is_webhook: 0,
is_versioned: 0,
is_tool_variant: 0,
tool_variant_of: null,
has_tool_variant: 0,
version: '1',
properties_schema: JSON.stringify([]),
operations: JSON.stringify([]),
@@ -426,9 +404,6 @@ describe('NodeRepository - Outputs Handling', () => {
is_trigger: 0,
is_webhook: 0,
is_versioned: 0,
is_tool_variant: 0,
tool_variant_of: null,
has_tool_variant: 0,
version: '3',
properties_schema: JSON.stringify([]),
operations: JSON.stringify([]),
@@ -466,9 +441,6 @@ describe('NodeRepository - Outputs Handling', () => {
is_trigger: 0,
is_webhook: 0,
is_versioned: 0,
is_tool_variant: 0,
tool_variant_of: null,
has_tool_variant: 0,
version: '1',
properties_schema: JSON.stringify([]),
operations: JSON.stringify([]),
@@ -498,9 +470,6 @@ describe('NodeRepository - Outputs Handling', () => {
is_trigger: 0,
is_webhook: 0,
is_versioned: 0,
is_tool_variant: 0,
tool_variant_of: null,
has_tool_variant: 0,
version: '1',
properties_schema: JSON.stringify([]),
operations: JSON.stringify([]),
@@ -574,9 +543,6 @@ describe('NodeRepository - Outputs Handling', () => {
is_trigger: 0,
is_webhook: 0,
is_versioned: 0,
is_tool_variant: 0,
tool_variant_of: null,
has_tool_variant: 0,
version: '3',
properties_schema: JSON.stringify([]),
operations: JSON.stringify([]),

View File

@@ -227,13 +227,8 @@ describe('handlers-n8n-manager', () => {
expect(result).toEqual({
success: true,
data: {
id: 'test-workflow-id',
name: 'Test Workflow',
active: true,
nodeCount: 1,
},
message: 'Workflow "Test Workflow" created successfully with ID: test-workflow-id. Use n8n_get_workflow with mode \'structure\' to verify current state.',
data: testWorkflow,
message: 'Workflow "Test Workflow" created successfully with ID: test-workflow-id',
});
// Should send input as-is to API (n8n expects FULL form: n8n-nodes-base.*)
@@ -737,12 +732,8 @@ describe('handlers-n8n-manager', () => {
expect(result).toEqual({
success: true,
data: {
id: 'test-workflow-id',
name: 'Test Workflow',
deleted: true,
},
message: 'Workflow "Test Workflow" deleted successfully.',
data: testWorkflow,
message: 'Workflow test-workflow-id deleted successfully',
});
expect(mockApiClient.deleteWorkflow).toHaveBeenCalledWith('test-workflow-id');
});

View File

@@ -150,15 +150,13 @@ describe('handlers-workflow-diff', () => {
expect(result).toEqual({
success: true,
data: {
id: 'test-workflow-id',
name: 'Test Workflow',
active: true,
nodeCount: 3,
operationsApplied: 1,
},
message: 'Workflow "Test Workflow" updated successfully. Applied 1 operations. Use n8n_get_workflow with mode \'structure\' to verify current state.',
data: updatedWorkflow,
message: 'Workflow "Test Workflow" updated successfully. Applied 1 operations.',
details: {
operationsApplied: 1,
workflowId: 'test-workflow-id',
workflowName: 'Test Workflow',
active: true,
applied: [0],
failed: [],
errors: [],
@@ -662,15 +660,9 @@ describe('handlers-workflow-diff', () => {
}, mockRepository);
expect(result.success).toBe(true);
expect(result.data).toEqual({
id: 'test-workflow-id',
name: 'Test Workflow',
active: true,
nodeCount: 2,
operationsApplied: 1,
});
expect(result.data).toEqual(activatedWorkflow);
expect(result.message).toContain('Workflow activated');
expect((result.data as any).active).toBe(true);
expect(result.details?.active).toBe(true);
expect(mockApiClient.activateWorkflow).toHaveBeenCalledWith('test-workflow-id');
});
@@ -697,15 +689,9 @@ describe('handlers-workflow-diff', () => {
}, mockRepository);
expect(result.success).toBe(true);
expect(result.data).toEqual({
id: 'test-workflow-id',
name: 'Test Workflow',
active: false,
nodeCount: 2,
operationsApplied: 1,
});
expect(result.data).toEqual(deactivatedWorkflow);
expect(result.message).toContain('Workflow deactivated');
expect((result.data as any).active).toBe(false);
expect(result.details?.active).toBe(false);
expect(mockApiClient.deactivateWorkflow).toHaveBeenCalledWith('test-workflow-id');
});

View File

@@ -1,647 +0,0 @@
/**
* Tests for WorkflowAutoFixer - Tool Variant Fixes
*
* Tests the processToolVariantFixes() method which generates fix operations
* to replace base node types with their Tool variant equivalents when
* incorrectly used with ai_tool connections.
*
* Coverage:
* - tool-variant-correction fixes are generated from validation errors
* - Fix changes node type from base to Tool variant
* - Fixes have high confidence
* - Multiple tool variant fixes in same workflow
* - Fix operations are correctly structured
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { WorkflowAutoFixer } from '@/services/workflow-auto-fixer';
import { NodeRepository } from '@/database/node-repository';
import type { WorkflowValidationResult } from '@/services/workflow-validator';
import type { Workflow, WorkflowNode } from '@/types/n8n-api';
vi.mock('@/database/node-repository');
vi.mock('@/utils/logger');
describe('WorkflowAutoFixer - Tool Variant Fixes', () => {
let autoFixer: WorkflowAutoFixer;
let mockRepository: NodeRepository;
const createMockWorkflow = (nodes: WorkflowNode[]): Workflow => ({
id: 'test-workflow',
name: 'Test Workflow',
active: false,
nodes,
connections: {},
settings: {},
createdAt: '',
updatedAt: ''
});
const createMockNode = (
id: string,
name: string,
type: string,
parameters: any = {}
): WorkflowNode => ({
id,
name,
type,
typeVersion: 1,
position: [0, 0],
parameters
});
beforeEach(() => {
vi.clearAllMocks();
mockRepository = new NodeRepository({} as any);
// Mock getNodeVersions to return empty array (prevent version upgrade processing)
vi.spyOn(mockRepository, 'getNodeVersions').mockReturnValue([]);
autoFixer = new WorkflowAutoFixer(mockRepository);
});
describe('processToolVariantFixes - Basic functionality', () => {
it('should generate fix for base node incorrectly used as AI tool', async () => {
const workflow = createMockWorkflow([
createMockNode('supabase-1', 'Supabase', 'n8n-nodes-base.supabase', {})
]);
const validationResult: WorkflowValidationResult = {
valid: false,
errors: [
{
type: 'error',
nodeId: 'supabase-1',
nodeName: 'Supabase',
message: 'Node "Supabase" uses "n8n-nodes-base.supabase" which cannot output ai_tool connections. Use the Tool variant "n8n-nodes-base.supabaseTool" instead for AI Agent integration.',
code: 'WRONG_NODE_TYPE_FOR_AI_TOOL',
fix: {
type: 'tool-variant-correction',
currentType: 'n8n-nodes-base.supabase',
suggestedType: 'n8n-nodes-base.supabaseTool',
description: 'Change node type from "n8n-nodes-base.supabase" to "n8n-nodes-base.supabaseTool"'
}
}
],
warnings: [],
statistics: {
totalNodes: 1,
enabledNodes: 1,
triggerNodes: 0,
validConnections: 0,
invalidConnections: 0,
expressionsValidated: 0
},
suggestions: []
};
const result = await autoFixer.generateFixes(workflow, validationResult, []);
expect(result.fixes).toHaveLength(1);
expect(result.fixes[0].type).toBe('tool-variant-correction');
expect(result.fixes[0].node).toBe('Supabase');
expect(result.fixes[0].field).toBe('type');
expect(result.fixes[0].before).toBe('n8n-nodes-base.supabase');
expect(result.fixes[0].after).toBe('n8n-nodes-base.supabaseTool');
});
it('should generate fix with high confidence', async () => {
const workflow = createMockWorkflow([
createMockNode('supabase-1', 'Supabase', 'n8n-nodes-base.supabase', {})
]);
const validationResult: WorkflowValidationResult = {
valid: false,
errors: [
{
type: 'error',
nodeId: 'supabase-1',
nodeName: 'Supabase',
message: 'Node uses wrong type for AI tool',
code: 'WRONG_NODE_TYPE_FOR_AI_TOOL',
fix: {
type: 'tool-variant-correction',
currentType: 'n8n-nodes-base.supabase',
suggestedType: 'n8n-nodes-base.supabaseTool',
description: 'Fix tool variant'
}
}
],
warnings: [],
statistics: {
totalNodes: 1,
enabledNodes: 1,
triggerNodes: 0,
validConnections: 0,
invalidConnections: 0,
expressionsValidated: 0
},
suggestions: []
};
const result = await autoFixer.generateFixes(workflow, validationResult, []);
expect(result.fixes).toHaveLength(1);
expect(result.fixes[0].confidence).toBe('high');
});
it('should generate correct update operation', async () => {
const workflow = createMockWorkflow([
createMockNode('supabase-1', 'Supabase', 'n8n-nodes-base.supabase', {
resource: 'database',
operation: 'query'
})
]);
const validationResult: WorkflowValidationResult = {
valid: false,
errors: [
{
type: 'error',
nodeId: 'supabase-1',
nodeName: 'Supabase',
message: 'Wrong node type for AI tool',
code: 'WRONG_NODE_TYPE_FOR_AI_TOOL',
fix: {
type: 'tool-variant-correction',
currentType: 'n8n-nodes-base.supabase',
suggestedType: 'n8n-nodes-base.supabaseTool',
description: 'Fix tool variant'
}
}
],
warnings: [],
statistics: {
totalNodes: 1,
enabledNodes: 1,
triggerNodes: 0,
validConnections: 0,
invalidConnections: 0,
expressionsValidated: 0
},
suggestions: []
};
const result = await autoFixer.generateFixes(workflow, validationResult, []);
expect(result.operations).toHaveLength(1);
expect(result.operations[0].type).toBe('updateNode');
expect((result.operations[0] as any).nodeId).toBe('Supabase');
expect((result.operations[0] as any).updates.type).toBe('n8n-nodes-base.supabaseTool');
});
});
describe('processToolVariantFixes - Multiple fixes', () => {
it('should generate fixes for multiple nodes', async () => {
const workflow = createMockWorkflow([
createMockNode('supabase-1', 'Supabase', 'n8n-nodes-base.supabase', {}),
createMockNode('postgres-1', 'Postgres', 'n8n-nodes-base.postgres', {})
]);
const validationResult: WorkflowValidationResult = {
valid: false,
errors: [
{
type: 'error',
nodeId: 'supabase-1',
nodeName: 'Supabase',
message: 'Wrong node type',
code: 'WRONG_NODE_TYPE_FOR_AI_TOOL',
fix: {
type: 'tool-variant-correction',
currentType: 'n8n-nodes-base.supabase',
suggestedType: 'n8n-nodes-base.supabaseTool',
description: 'Fix supabase tool variant'
}
},
{
type: 'error',
nodeId: 'postgres-1',
nodeName: 'Postgres',
message: 'Wrong node type',
code: 'WRONG_NODE_TYPE_FOR_AI_TOOL',
fix: {
type: 'tool-variant-correction',
currentType: 'n8n-nodes-base.postgres',
suggestedType: 'n8n-nodes-base.postgresTool',
description: 'Fix postgres tool variant'
}
}
],
warnings: [],
statistics: {
totalNodes: 2,
enabledNodes: 2,
triggerNodes: 0,
validConnections: 0,
invalidConnections: 0,
expressionsValidated: 0
},
suggestions: []
};
const result = await autoFixer.generateFixes(workflow, validationResult, []);
expect(result.fixes).toHaveLength(2);
expect(result.operations).toHaveLength(2);
const supabaseFix = result.fixes.find(f => f.node === 'Supabase');
expect(supabaseFix).toBeDefined();
expect(supabaseFix!.after).toBe('n8n-nodes-base.supabaseTool');
const postgresFix = result.fixes.find(f => f.node === 'Postgres');
expect(postgresFix).toBeDefined();
expect(postgresFix!.after).toBe('n8n-nodes-base.postgresTool');
});
});
describe('processToolVariantFixes - Error handling', () => {
it('should skip errors without WRONG_NODE_TYPE_FOR_AI_TOOL code', async () => {
const workflow = createMockWorkflow([
createMockNode('supabase-1', 'Supabase', 'n8n-nodes-base.supabase', {})
]);
const validationResult: WorkflowValidationResult = {
valid: false,
errors: [
{
type: 'error',
nodeId: 'supabase-1',
nodeName: 'Supabase',
message: 'Different error',
code: 'DIFFERENT_ERROR'
}
],
warnings: [],
statistics: {
totalNodes: 1,
enabledNodes: 1,
triggerNodes: 0,
validConnections: 0,
invalidConnections: 0,
expressionsValidated: 0
},
suggestions: []
};
const result = await autoFixer.generateFixes(workflow, validationResult, []);
const toolVariantFixes = result.fixes.filter(f => f.type === 'tool-variant-correction');
expect(toolVariantFixes).toHaveLength(0);
});
it('should skip errors without fix metadata', async () => {
const workflow = createMockWorkflow([
createMockNode('supabase-1', 'Supabase', 'n8n-nodes-base.supabase', {})
]);
const validationResult: WorkflowValidationResult = {
valid: false,
errors: [
{
type: 'error',
nodeId: 'supabase-1',
nodeName: 'Supabase',
message: 'Wrong node type',
code: 'WRONG_NODE_TYPE_FOR_AI_TOOL'
// No fix metadata
}
],
warnings: [],
statistics: {
totalNodes: 1,
enabledNodes: 1,
triggerNodes: 0,
validConnections: 0,
invalidConnections: 0,
expressionsValidated: 0
},
suggestions: []
};
const result = await autoFixer.generateFixes(workflow, validationResult, []);
const toolVariantFixes = result.fixes.filter(f => f.type === 'tool-variant-correction');
expect(toolVariantFixes).toHaveLength(0);
});
it('should skip errors with wrong fix type', async () => {
const workflow = createMockWorkflow([
createMockNode('supabase-1', 'Supabase', 'n8n-nodes-base.supabase', {})
]);
const validationResult: WorkflowValidationResult = {
valid: false,
errors: [
{
type: 'error',
nodeId: 'supabase-1',
nodeName: 'Supabase',
message: 'Wrong node type',
code: 'WRONG_NODE_TYPE_FOR_AI_TOOL',
fix: {
type: 'different-fix-type',
currentType: 'n8n-nodes-base.supabase',
suggestedType: 'n8n-nodes-base.supabaseTool',
description: 'Fix'
}
}
],
warnings: [],
statistics: {
totalNodes: 1,
enabledNodes: 1,
triggerNodes: 0,
validConnections: 0,
invalidConnections: 0,
expressionsValidated: 0
},
suggestions: []
};
const result = await autoFixer.generateFixes(workflow, validationResult, []);
const toolVariantFixes = result.fixes.filter(f => f.type === 'tool-variant-correction');
expect(toolVariantFixes).toHaveLength(0);
});
it('should skip errors without node name or ID', async () => {
const workflow = createMockWorkflow([
createMockNode('supabase-1', 'Supabase', 'n8n-nodes-base.supabase', {})
]);
const validationResult: WorkflowValidationResult = {
valid: false,
errors: [
{
type: 'error',
message: 'Wrong node type',
code: 'WRONG_NODE_TYPE_FOR_AI_TOOL',
fix: {
type: 'tool-variant-correction',
currentType: 'n8n-nodes-base.supabase',
suggestedType: 'n8n-nodes-base.supabaseTool',
description: 'Fix'
}
}
],
warnings: [],
statistics: {
totalNodes: 1,
enabledNodes: 1,
triggerNodes: 0,
validConnections: 0,
invalidConnections: 0,
expressionsValidated: 0
},
suggestions: []
};
const result = await autoFixer.generateFixes(workflow, validationResult, []);
const toolVariantFixes = result.fixes.filter(f => f.type === 'tool-variant-correction');
expect(toolVariantFixes).toHaveLength(0);
});
it('should skip errors when node not found in workflow', async () => {
const workflow = createMockWorkflow([
createMockNode('other-1', 'Other', 'n8n-nodes-base.set', {})
]);
const validationResult: WorkflowValidationResult = {
valid: false,
errors: [
{
type: 'error',
nodeId: 'supabase-1',
nodeName: 'Supabase',
message: 'Wrong node type',
code: 'WRONG_NODE_TYPE_FOR_AI_TOOL',
fix: {
type: 'tool-variant-correction',
currentType: 'n8n-nodes-base.supabase',
suggestedType: 'n8n-nodes-base.supabaseTool',
description: 'Fix'
}
}
],
warnings: [],
statistics: {
totalNodes: 1,
enabledNodes: 1,
triggerNodes: 0,
validConnections: 0,
invalidConnections: 0,
expressionsValidated: 0
},
suggestions: []
};
const result = await autoFixer.generateFixes(workflow, validationResult, []);
const toolVariantFixes = result.fixes.filter(f => f.type === 'tool-variant-correction');
expect(toolVariantFixes).toHaveLength(0);
});
});
describe('processToolVariantFixes - Integration with other fixes', () => {
it('should work alongside expression format fixes', async () => {
const workflow = createMockWorkflow([
createMockNode('supabase-1', 'Supabase', 'n8n-nodes-base.supabase', {
url: '{{ $json.url }}' // Missing = prefix
})
]);
const validationResult: WorkflowValidationResult = {
valid: false,
errors: [
{
type: 'error',
nodeId: 'supabase-1',
nodeName: 'Supabase',
message: 'Wrong node type',
code: 'WRONG_NODE_TYPE_FOR_AI_TOOL',
fix: {
type: 'tool-variant-correction',
currentType: 'n8n-nodes-base.supabase',
suggestedType: 'n8n-nodes-base.supabaseTool',
description: 'Fix tool variant'
}
}
],
warnings: [],
statistics: {
totalNodes: 1,
enabledNodes: 1,
triggerNodes: 0,
validConnections: 0,
invalidConnections: 0,
expressionsValidated: 1
},
suggestions: []
};
const formatIssues = [
{
fieldPath: 'url',
currentValue: '{{ $json.url }}',
correctedValue: '={{ $json.url }}',
issueType: 'missing-prefix' as const,
severity: 'error' as const,
explanation: 'Missing = prefix',
nodeName: 'Supabase',
nodeId: 'supabase-1'
}
];
const result = await autoFixer.generateFixes(workflow, validationResult, formatIssues);
// Should have both tool variant and expression fixes
expect(result.fixes.length).toBeGreaterThanOrEqual(2);
const toolVariantFix = result.fixes.find(f => f.type === 'tool-variant-correction');
const expressionFix = result.fixes.find(f => f.type === 'expression-format');
expect(toolVariantFix).toBeDefined();
expect(expressionFix).toBeDefined();
});
});
describe('processToolVariantFixes - Description and summary', () => {
it('should include fix description from error', async () => {
const workflow = createMockWorkflow([
createMockNode('supabase-1', 'Supabase', 'n8n-nodes-base.supabase', {})
]);
const validationResult: WorkflowValidationResult = {
valid: false,
errors: [
{
type: 'error',
nodeId: 'supabase-1',
nodeName: 'Supabase',
message: 'Wrong node type',
code: 'WRONG_NODE_TYPE_FOR_AI_TOOL',
fix: {
type: 'tool-variant-correction',
currentType: 'n8n-nodes-base.supabase',
suggestedType: 'n8n-nodes-base.supabaseTool',
description: 'Change node type from "n8n-nodes-base.supabase" to "n8n-nodes-base.supabaseTool"'
}
}
],
warnings: [],
statistics: {
totalNodes: 1,
enabledNodes: 1,
triggerNodes: 0,
validConnections: 0,
invalidConnections: 0,
expressionsValidated: 0
},
suggestions: []
};
const result = await autoFixer.generateFixes(workflow, validationResult, []);
expect(result.fixes[0].description).toBe(
'Change node type from "n8n-nodes-base.supabase" to "n8n-nodes-base.supabaseTool"'
);
});
it('should include tool variant corrections in summary', async () => {
const workflow = createMockWorkflow([
createMockNode('supabase-1', 'Supabase', 'n8n-nodes-base.supabase', {})
]);
const validationResult: WorkflowValidationResult = {
valid: false,
errors: [
{
type: 'error',
nodeId: 'supabase-1',
nodeName: 'Supabase',
message: 'Wrong node type',
code: 'WRONG_NODE_TYPE_FOR_AI_TOOL',
fix: {
type: 'tool-variant-correction',
currentType: 'n8n-nodes-base.supabase',
suggestedType: 'n8n-nodes-base.supabaseTool',
description: 'Fix tool variant'
}
}
],
warnings: [],
statistics: {
totalNodes: 1,
enabledNodes: 1,
triggerNodes: 0,
validConnections: 0,
invalidConnections: 0,
expressionsValidated: 0
},
suggestions: []
};
const result = await autoFixer.generateFixes(workflow, validationResult, []);
expect(result.summary).toContain('tool variant');
expect(result.stats.byType['tool-variant-correction']).toBe(1);
});
it('should pluralize summary correctly', async () => {
const workflow = createMockWorkflow([
createMockNode('supabase-1', 'Supabase', 'n8n-nodes-base.supabase', {}),
createMockNode('postgres-1', 'Postgres', 'n8n-nodes-base.postgres', {})
]);
const validationResult: WorkflowValidationResult = {
valid: false,
errors: [
{
type: 'error',
nodeId: 'supabase-1',
nodeName: 'Supabase',
message: 'Wrong node type',
code: 'WRONG_NODE_TYPE_FOR_AI_TOOL',
fix: {
type: 'tool-variant-correction',
currentType: 'n8n-nodes-base.supabase',
suggestedType: 'n8n-nodes-base.supabaseTool',
description: 'Fix supabase'
}
},
{
type: 'error',
nodeId: 'postgres-1',
nodeName: 'Postgres',
message: 'Wrong node type',
code: 'WRONG_NODE_TYPE_FOR_AI_TOOL',
fix: {
type: 'tool-variant-correction',
currentType: 'n8n-nodes-base.postgres',
suggestedType: 'n8n-nodes-base.postgresTool',
description: 'Fix postgres'
}
}
],
warnings: [],
statistics: {
totalNodes: 2,
enabledNodes: 2,
triggerNodes: 0,
validConnections: 0,
invalidConnections: 0,
expressionsValidated: 0
},
suggestions: []
};
const result = await autoFixer.generateFixes(workflow, validationResult, []);
expect(result.summary).toContain('tool variant corrections');
expect(result.stats.byType['tool-variant-correction']).toBe(2);
});
});
});

View File

@@ -1,602 +0,0 @@
/**
* Tests for WorkflowValidator - Tool Variant Validation
*
* Tests the validateAIToolSource() method which ensures that base nodes
* with ai_tool connections use the correct Tool variant node type.
*
* Coverage:
* - Langchain tool nodes pass validation
* - Tool variant nodes pass validation
* - Base nodes with Tool variants fail with WRONG_NODE_TYPE_FOR_AI_TOOL
* - Error includes fix suggestion with tool-variant-correction type
* - Unknown nodes don't cause errors
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { WorkflowValidator } from '@/services/workflow-validator';
import { NodeRepository } from '@/database/node-repository';
import { EnhancedConfigValidator } from '@/services/enhanced-config-validator';
// Mock dependencies
vi.mock('@/database/node-repository');
vi.mock('@/services/enhanced-config-validator');
vi.mock('@/utils/logger');
describe('WorkflowValidator - Tool Variant Validation', () => {
let validator: WorkflowValidator;
let mockRepository: NodeRepository;
let mockValidator: typeof EnhancedConfigValidator;
beforeEach(() => {
vi.clearAllMocks();
// Create mock repository
mockRepository = {
getNode: vi.fn((nodeType: string) => {
// Mock base node with Tool variant available
if (nodeType === 'nodes-base.supabase') {
return {
nodeType: 'nodes-base.supabase',
displayName: 'Supabase',
isAITool: true,
hasToolVariant: true,
isToolVariant: false,
isTrigger: false,
properties: []
};
}
// Mock Tool variant node
if (nodeType === 'nodes-base.supabaseTool') {
return {
nodeType: 'nodes-base.supabaseTool',
displayName: 'Supabase Tool',
isAITool: true,
hasToolVariant: false,
isToolVariant: true,
toolVariantOf: 'nodes-base.supabase',
isTrigger: false,
properties: []
};
}
// Mock langchain node (Calculator tool)
if (nodeType === 'nodes-langchain.toolCalculator') {
return {
nodeType: 'nodes-langchain.toolCalculator',
displayName: 'Calculator',
isAITool: true,
hasToolVariant: false,
isToolVariant: false,
isTrigger: false,
properties: []
};
}
// Mock HTTP Request Tool node
if (nodeType === 'nodes-langchain.toolHttpRequest') {
return {
nodeType: 'nodes-langchain.toolHttpRequest',
displayName: 'HTTP Request Tool',
isAITool: true,
hasToolVariant: false,
isToolVariant: false,
isTrigger: false,
properties: []
};
}
// Mock base node without Tool variant
if (nodeType === 'nodes-base.httpRequest') {
return {
nodeType: 'nodes-base.httpRequest',
displayName: 'HTTP Request',
isAITool: false,
hasToolVariant: false,
isToolVariant: false,
isTrigger: false,
properties: []
};
}
return null; // Unknown node
})
} as any;
mockValidator = EnhancedConfigValidator;
validator = new WorkflowValidator(mockRepository, mockValidator);
});
describe('validateAIToolSource - Langchain tool nodes', () => {
it('should pass validation for Calculator tool node', async () => {
const workflow = {
nodes: [
{
id: 'calculator-1',
name: 'Calculator',
type: 'n8n-nodes-langchain.toolCalculator',
typeVersion: 1.2,
position: [250, 300] as [number, number],
parameters: {}
},
{
id: 'agent-1',
name: 'AI Agent',
type: '@n8n/n8n-nodes-langchain.agent',
typeVersion: 1.7,
position: [450, 300] as [number, number],
parameters: {}
}
],
connections: {
Calculator: {
ai_tool: [[{ node: 'AI Agent', type: 'ai_tool', index: 0 }]]
}
}
};
const result = await validator.validateWorkflow(workflow);
// Should not have errors about wrong node type for AI tool
const toolVariantErrors = result.errors.filter(e =>
e.code === 'WRONG_NODE_TYPE_FOR_AI_TOOL'
);
expect(toolVariantErrors).toHaveLength(0);
});
it('should pass validation for HTTP Request Tool node', async () => {
const workflow = {
nodes: [
{
id: 'http-tool-1',
name: 'HTTP Request Tool',
type: '@n8n/n8n-nodes-langchain.toolHttpRequest',
typeVersion: 1.2,
position: [250, 300] as [number, number],
parameters: {
url: 'https://api.example.com',
toolDescription: 'Fetch data from API'
}
},
{
id: 'agent-1',
name: 'AI Agent',
type: '@n8n/n8n-nodes-langchain.agent',
typeVersion: 1.7,
position: [450, 300] as [number, number],
parameters: {}
}
],
connections: {
'HTTP Request Tool': {
ai_tool: [[{ node: 'AI Agent', type: 'ai_tool', index: 0 }]]
}
}
};
const result = await validator.validateWorkflow(workflow);
const toolVariantErrors = result.errors.filter(e =>
e.code === 'WRONG_NODE_TYPE_FOR_AI_TOOL'
);
expect(toolVariantErrors).toHaveLength(0);
});
});
describe('validateAIToolSource - Tool variant nodes', () => {
it('should pass validation for Tool variant node (supabaseTool)', async () => {
const workflow = {
nodes: [
{
id: 'supabase-tool-1',
name: 'Supabase Tool',
type: 'n8n-nodes-base.supabaseTool',
typeVersion: 1,
position: [250, 300] as [number, number],
parameters: {
toolDescription: 'Query Supabase database'
}
},
{
id: 'agent-1',
name: 'AI Agent',
type: '@n8n/n8n-nodes-langchain.agent',
typeVersion: 1.7,
position: [450, 300] as [number, number],
parameters: {}
}
],
connections: {
'Supabase Tool': {
ai_tool: [[{ node: 'AI Agent', type: 'ai_tool', index: 0 }]]
}
}
};
const result = await validator.validateWorkflow(workflow);
const toolVariantErrors = result.errors.filter(e =>
e.code === 'WRONG_NODE_TYPE_FOR_AI_TOOL'
);
expect(toolVariantErrors).toHaveLength(0);
});
it('should verify Tool variant is marked correctly in database', async () => {
const workflow = {
nodes: [
{
id: 'supabase-tool-1',
name: 'Supabase Tool',
type: 'n8n-nodes-base.supabaseTool',
typeVersion: 1,
position: [250, 300] as [number, number],
parameters: {}
}
],
connections: {
'Supabase Tool': {
ai_tool: [[{ node: 'AI Agent', type: 'ai_tool', index: 0 }]]
}
}
};
await validator.validateWorkflow(workflow);
// Verify repository was called to check if it's a Tool variant
expect(mockRepository.getNode).toHaveBeenCalledWith('nodes-base.supabaseTool');
});
});
describe('validateAIToolSource - Base nodes with Tool variants', () => {
it('should fail when base node is used instead of Tool variant', async () => {
const workflow = {
nodes: [
{
id: 'supabase-1',
name: 'Supabase',
type: 'n8n-nodes-base.supabase',
typeVersion: 1,
position: [250, 300] as [number, number],
parameters: {}
},
{
id: 'agent-1',
name: 'AI Agent',
type: '@n8n/n8n-nodes-langchain.agent',
typeVersion: 1.7,
position: [450, 300] as [number, number],
parameters: {}
}
],
connections: {
Supabase: {
ai_tool: [[{ node: 'AI Agent', type: 'ai_tool', index: 0 }]]
}
}
};
const result = await validator.validateWorkflow(workflow);
// Should have error with WRONG_NODE_TYPE_FOR_AI_TOOL code
const toolVariantErrors = result.errors.filter(e =>
e.code === 'WRONG_NODE_TYPE_FOR_AI_TOOL'
);
expect(toolVariantErrors).toHaveLength(1);
});
it('should include fix suggestion in error', async () => {
const workflow = {
nodes: [
{
id: 'supabase-1',
name: 'Supabase',
type: 'n8n-nodes-base.supabase',
typeVersion: 1,
position: [250, 300] as [number, number],
parameters: {}
},
{
id: 'agent-1',
name: 'AI Agent',
type: '@n8n/n8n-nodes-langchain.agent',
typeVersion: 1.7,
position: [450, 300] as [number, number],
parameters: {}
}
],
connections: {
Supabase: {
ai_tool: [[{ node: 'AI Agent', type: 'ai_tool', index: 0 }]]
}
}
};
const result = await validator.validateWorkflow(workflow);
const toolVariantError = result.errors.find(e =>
e.code === 'WRONG_NODE_TYPE_FOR_AI_TOOL'
) as any;
expect(toolVariantError).toBeDefined();
expect(toolVariantError.fix).toBeDefined();
expect(toolVariantError.fix.type).toBe('tool-variant-correction');
expect(toolVariantError.fix.currentType).toBe('n8n-nodes-base.supabase');
expect(toolVariantError.fix.suggestedType).toBe('n8n-nodes-base.supabaseTool');
expect(toolVariantError.fix.description).toContain('n8n-nodes-base.supabase');
expect(toolVariantError.fix.description).toContain('n8n-nodes-base.supabaseTool');
});
it('should provide clear error message', async () => {
const workflow = {
nodes: [
{
id: 'supabase-1',
name: 'Supabase',
type: 'n8n-nodes-base.supabase',
typeVersion: 1,
position: [250, 300] as [number, number],
parameters: {}
},
{
id: 'agent-1',
name: 'AI Agent',
type: '@n8n/n8n-nodes-langchain.agent',
typeVersion: 1.7,
position: [450, 300] as [number, number],
parameters: {}
}
],
connections: {
Supabase: {
ai_tool: [[{ node: 'AI Agent', type: 'ai_tool', index: 0 }]]
}
}
};
const result = await validator.validateWorkflow(workflow);
const toolVariantError = result.errors.find(e =>
e.code === 'WRONG_NODE_TYPE_FOR_AI_TOOL'
);
expect(toolVariantError).toBeDefined();
expect(toolVariantError!.message).toContain('cannot output ai_tool connections');
expect(toolVariantError!.message).toContain('Tool variant');
expect(toolVariantError!.message).toContain('n8n-nodes-base.supabaseTool');
});
it('should handle multiple base nodes incorrectly used as tools', async () => {
mockRepository.getNode = vi.fn((nodeType: string) => {
if (nodeType === 'nodes-base.postgres') {
return {
nodeType: 'nodes-base.postgres',
displayName: 'Postgres',
isAITool: true,
hasToolVariant: true,
isToolVariant: false,
isTrigger: false,
properties: []
};
}
if (nodeType === 'nodes-base.supabase') {
return {
nodeType: 'nodes-base.supabase',
displayName: 'Supabase',
isAITool: true,
hasToolVariant: true,
isToolVariant: false,
isTrigger: false,
properties: []
};
}
return null;
}) as any;
const workflow = {
nodes: [
{
id: 'postgres-1',
name: 'Postgres',
type: 'n8n-nodes-base.postgres',
typeVersion: 1,
position: [250, 300] as [number, number],
parameters: {}
},
{
id: 'supabase-1',
name: 'Supabase',
type: 'n8n-nodes-base.supabase',
typeVersion: 1,
position: [250, 400] as [number, number],
parameters: {}
},
{
id: 'agent-1',
name: 'AI Agent',
type: '@n8n/n8n-nodes-langchain.agent',
typeVersion: 1.7,
position: [450, 300] as [number, number],
parameters: {}
}
],
connections: {
Postgres: {
ai_tool: [[{ node: 'AI Agent', type: 'ai_tool', index: 0 }]]
},
Supabase: {
ai_tool: [[{ node: 'AI Agent', type: 'ai_tool', index: 0 }]]
}
}
};
const result = await validator.validateWorkflow(workflow);
const toolVariantErrors = result.errors.filter(e =>
e.code === 'WRONG_NODE_TYPE_FOR_AI_TOOL'
);
expect(toolVariantErrors).toHaveLength(2);
});
});
describe('validateAIToolSource - Unknown nodes', () => {
it('should not error for unknown node types', async () => {
const workflow = {
nodes: [
{
id: 'unknown-1',
name: 'Unknown Tool',
type: 'custom-package.unknownTool',
typeVersion: 1,
position: [250, 300] as [number, number],
parameters: {}
},
{
id: 'agent-1',
name: 'AI Agent',
type: '@n8n/n8n-nodes-langchain.agent',
typeVersion: 1.7,
position: [450, 300] as [number, number],
parameters: {}
}
],
connections: {
'Unknown Tool': {
ai_tool: [[{ node: 'AI Agent', type: 'ai_tool', index: 0 }]]
}
}
};
const result = await validator.validateWorkflow(workflow);
// Unknown nodes should not cause tool variant errors
// Let other validation handle unknown node types
const toolVariantErrors = result.errors.filter(e =>
e.code === 'WRONG_NODE_TYPE_FOR_AI_TOOL'
);
expect(toolVariantErrors).toHaveLength(0);
// But there might be an "Unknown node type" error from different validation
const unknownNodeErrors = result.errors.filter(e =>
e.message && e.message.includes('Unknown node type')
);
expect(unknownNodeErrors.length).toBeGreaterThan(0);
});
it('should not error for community nodes', async () => {
const workflow = {
nodes: [
{
id: 'community-1',
name: 'Community Tool',
type: 'community-package.customTool',
typeVersion: 1,
position: [250, 300] as [number, number],
parameters: {}
},
{
id: 'agent-1',
name: 'AI Agent',
type: '@n8n/n8n-nodes-langchain.agent',
typeVersion: 1.7,
position: [450, 300] as [number, number],
parameters: {}
}
],
connections: {
'Community Tool': {
ai_tool: [[{ node: 'AI Agent', type: 'ai_tool', index: 0 }]]
}
}
};
const result = await validator.validateWorkflow(workflow);
// Community nodes should not cause tool variant errors
const toolVariantErrors = result.errors.filter(e =>
e.code === 'WRONG_NODE_TYPE_FOR_AI_TOOL'
);
expect(toolVariantErrors).toHaveLength(0);
});
});
describe('validateAIToolSource - Edge cases', () => {
it('should not error for base nodes without ai_tool connections', async () => {
const workflow = {
nodes: [
{
id: 'supabase-1',
name: 'Supabase',
type: 'n8n-nodes-base.supabase',
typeVersion: 1,
position: [250, 300] as [number, number],
parameters: {}
},
{
id: 'set-1',
name: 'Set',
type: 'n8n-nodes-base.set',
typeVersion: 1,
position: [450, 300] as [number, number],
parameters: {}
}
],
connections: {
Supabase: {
main: [[{ node: 'Set', type: 'main', index: 0 }]]
}
}
};
const result = await validator.validateWorkflow(workflow);
// No ai_tool connections, so no tool variant validation errors
const toolVariantErrors = result.errors.filter(e =>
e.code === 'WRONG_NODE_TYPE_FOR_AI_TOOL'
);
expect(toolVariantErrors).toHaveLength(0);
});
it('should not error when base node without Tool variant uses ai_tool', async () => {
const workflow = {
nodes: [
{
id: 'http-1',
name: 'HTTP Request',
type: 'n8n-nodes-base.httpRequest',
typeVersion: 1,
position: [250, 300] as [number, number],
parameters: {}
},
{
id: 'agent-1',
name: 'AI Agent',
type: '@n8n/n8n-nodes-langchain.agent',
typeVersion: 1.7,
position: [450, 300] as [number, number],
parameters: {}
}
],
connections: {
'HTTP Request': {
ai_tool: [[{ node: 'AI Agent', type: 'ai_tool', index: 0 }]]
}
}
};
const result = await validator.validateWorkflow(workflow);
// httpRequest has no Tool variant, so this should produce a different error
const toolVariantErrors = result.errors.filter(e =>
e.code === 'WRONG_NODE_TYPE_FOR_AI_TOOL'
);
expect(toolVariantErrors).toHaveLength(0);
// Should have INVALID_AI_TOOL_SOURCE error instead
const invalidToolErrors = result.errors.filter(e =>
e.code === 'INVALID_AI_TOOL_SOURCE'
);
expect(invalidToolErrors.length).toBeGreaterThan(0);
});
});
});

View File

@@ -312,122 +312,6 @@ describe('NodeTypeNormalizer', () => {
});
});
describe('toWorkflowFormat', () => {
describe('Base nodes', () => {
it('should convert short base form to full workflow format', () => {
expect(NodeTypeNormalizer.toWorkflowFormat('nodes-base.webhook'))
.toBe('n8n-nodes-base.webhook');
});
it('should convert multiple base nodes', () => {
expect(NodeTypeNormalizer.toWorkflowFormat('nodes-base.httpRequest'))
.toBe('n8n-nodes-base.httpRequest');
expect(NodeTypeNormalizer.toWorkflowFormat('nodes-base.set'))
.toBe('n8n-nodes-base.set');
expect(NodeTypeNormalizer.toWorkflowFormat('nodes-base.slack'))
.toBe('n8n-nodes-base.slack');
});
it('should leave full base form unchanged', () => {
expect(NodeTypeNormalizer.toWorkflowFormat('n8n-nodes-base.webhook'))
.toBe('n8n-nodes-base.webhook');
expect(NodeTypeNormalizer.toWorkflowFormat('n8n-nodes-base.httpRequest'))
.toBe('n8n-nodes-base.httpRequest');
});
});
describe('LangChain nodes', () => {
it('should convert short langchain form to full workflow format', () => {
expect(NodeTypeNormalizer.toWorkflowFormat('nodes-langchain.agent'))
.toBe('@n8n/n8n-nodes-langchain.agent');
expect(NodeTypeNormalizer.toWorkflowFormat('nodes-langchain.openAi'))
.toBe('@n8n/n8n-nodes-langchain.openAi');
});
it('should leave full langchain form unchanged', () => {
expect(NodeTypeNormalizer.toWorkflowFormat('@n8n/n8n-nodes-langchain.agent'))
.toBe('@n8n/n8n-nodes-langchain.agent');
expect(NodeTypeNormalizer.toWorkflowFormat('@n8n/n8n-nodes-langchain.openAi'))
.toBe('@n8n/n8n-nodes-langchain.openAi');
});
it('should leave n8n-nodes-langchain form unchanged', () => {
// Alternative full form without @n8n/ prefix
expect(NodeTypeNormalizer.toWorkflowFormat('n8n-nodes-langchain.agent'))
.toBe('n8n-nodes-langchain.agent');
});
});
describe('Edge cases', () => {
it('should handle empty string', () => {
expect(NodeTypeNormalizer.toWorkflowFormat('')).toBe('');
});
it('should handle null', () => {
expect(NodeTypeNormalizer.toWorkflowFormat(null as any)).toBe(null);
});
it('should handle undefined', () => {
expect(NodeTypeNormalizer.toWorkflowFormat(undefined as any)).toBe(undefined);
});
it('should handle non-string input', () => {
expect(NodeTypeNormalizer.toWorkflowFormat(123 as any)).toBe(123);
expect(NodeTypeNormalizer.toWorkflowFormat({} as any)).toEqual({});
});
it('should leave community nodes unchanged', () => {
expect(NodeTypeNormalizer.toWorkflowFormat('custom-package.myNode'))
.toBe('custom-package.myNode');
});
it('should leave nodes without prefixes unchanged', () => {
expect(NodeTypeNormalizer.toWorkflowFormat('someRandomNode'))
.toBe('someRandomNode');
});
});
describe('Tool variants', () => {
it('should convert short Tool variant to full form', () => {
expect(NodeTypeNormalizer.toWorkflowFormat('nodes-base.supabaseTool'))
.toBe('n8n-nodes-base.supabaseTool');
expect(NodeTypeNormalizer.toWorkflowFormat('nodes-base.postgresTool'))
.toBe('n8n-nodes-base.postgresTool');
});
it('should leave full Tool variant unchanged', () => {
expect(NodeTypeNormalizer.toWorkflowFormat('n8n-nodes-base.supabaseTool'))
.toBe('n8n-nodes-base.supabaseTool');
});
});
describe('Round-trip conversion', () => {
it('should maintain workflow format through round trip', () => {
const workflowType = 'n8n-nodes-base.webhook';
// Convert to short (database) form
const shortForm = NodeTypeNormalizer.normalizeToFullForm(workflowType);
expect(shortForm).toBe('nodes-base.webhook');
// Convert back to workflow form
const backToWorkflow = NodeTypeNormalizer.toWorkflowFormat(shortForm);
expect(backToWorkflow).toBe(workflowType);
});
it('should handle langchain round trip', () => {
const workflowType = '@n8n/n8n-nodes-langchain.agent';
// Convert to short form
const shortForm = NodeTypeNormalizer.normalizeToFullForm(workflowType);
expect(shortForm).toBe('nodes-langchain.agent');
// Convert back to workflow form
const backToWorkflow = NodeTypeNormalizer.toWorkflowFormat(shortForm);
expect(backToWorkflow).toBe(workflowType);
});
});
});
describe('Integration scenarios', () => {
it('should handle the critical use case from P0-R1', () => {
// This is the exact scenario - normalize full form to match database
@@ -452,33 +336,5 @@ describe('NodeTypeNormalizer', () => {
// All node types should now be in short form for database lookup
expect(normalized.nodes.every((n: any) => n.type.startsWith('nodes-base.'))).toBe(true);
});
it('should convert database format to workflow format for API calls', () => {
// Scenario: Reading from database and sending to n8n API
const dbTypes = [
'nodes-base.webhook',
'nodes-base.httpRequest',
'nodes-langchain.agent'
];
const workflowTypes = dbTypes.map(t => NodeTypeNormalizer.toWorkflowFormat(t));
expect(workflowTypes).toEqual([
'n8n-nodes-base.webhook',
'n8n-nodes-base.httpRequest',
'@n8n/n8n-nodes-langchain.agent'
]);
});
it('should handle Tool variant correction scenario', () => {
// Scenario: Auto-fixer changes base node to Tool variant
const baseType = 'n8n-nodes-base.supabase'; // Current type in workflow
const toolVariantShort = 'nodes-base.supabaseTool'; // From database/generator
// Convert to workflow format for the fix
const fixedType = NodeTypeNormalizer.toWorkflowFormat(toolVariantShort);
expect(fixedType).toBe('n8n-nodes-base.supabaseTool');
});
});
});