mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-01-30 14:32:04 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d9b456887 | ||
|
|
2f5a857142 | ||
|
|
e7dd04b471 | ||
|
|
c7e7bda505 | ||
|
|
bac4936c6d | ||
|
|
25784142fe | ||
|
|
f770043d3d |
222
.github/workflows/dependency-check.yml
vendored
Normal file
222
.github/workflows/dependency-check.yml
vendored
Normal file
@@ -0,0 +1,222 @@
|
||||
name: Dependency Compatibility Check
|
||||
|
||||
# This workflow verifies that when users install n8n-mcp via npm (without lockfile),
|
||||
# they get compatible dependency versions. This catches issues like #440, #444, #446, #447, #450
|
||||
# where npm resolution gave users incompatible SDK/Zod versions.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
- '.github/workflows/dependency-check.yml'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
- '.github/workflows/dependency-check.yml'
|
||||
# Allow manual trigger for debugging
|
||||
workflow_dispatch:
|
||||
# Run weekly to catch upstream dependency changes
|
||||
schedule:
|
||||
- cron: '0 6 * * 1' # Every Monday at 6 AM UTC
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
fresh-install-check:
|
||||
name: Fresh Install Dependency Check
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Build package
|
||||
run: |
|
||||
npm ci
|
||||
npm run build
|
||||
|
||||
- name: Pack package for testing
|
||||
run: npm pack
|
||||
|
||||
- name: Create fresh install test directory
|
||||
run: |
|
||||
mkdir -p /tmp/fresh-install-test
|
||||
cp n8n-mcp-*.tgz /tmp/fresh-install-test/
|
||||
|
||||
- name: Install package fresh (simulating user install)
|
||||
working-directory: /tmp/fresh-install-test
|
||||
run: |
|
||||
npm init -y
|
||||
# Install from tarball WITHOUT lockfile (simulates npm install n8n-mcp)
|
||||
npm install ./n8n-mcp-*.tgz
|
||||
|
||||
- name: Verify critical dependency versions
|
||||
working-directory: /tmp/fresh-install-test
|
||||
run: |
|
||||
echo "=== Dependency Version Check ==="
|
||||
echo ""
|
||||
|
||||
# Get actual resolved versions
|
||||
SDK_VERSION=$(npm list @modelcontextprotocol/sdk --json 2>/dev/null | jq -r '.dependencies["n8n-mcp"].dependencies["@modelcontextprotocol/sdk"].version // .dependencies["@modelcontextprotocol/sdk"].version // "not found"')
|
||||
ZOD_VERSION=$(npm list zod --json 2>/dev/null | jq -r '.dependencies["n8n-mcp"].dependencies.zod.version // .dependencies.zod.version // "not found"')
|
||||
|
||||
echo "MCP SDK version: $SDK_VERSION"
|
||||
echo "Zod version: $ZOD_VERSION"
|
||||
echo ""
|
||||
|
||||
# Check MCP SDK version - must be exactly 1.20.1
|
||||
if [[ "$SDK_VERSION" == "not found" ]]; then
|
||||
echo "❌ FAILED: Could not determine MCP SDK version!"
|
||||
echo " The dependency may not have been installed correctly."
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$SDK_VERSION" != "1.20.1" ]]; then
|
||||
echo "❌ FAILED: MCP SDK version mismatch!"
|
||||
echo " Expected: 1.20.1"
|
||||
echo " Got: $SDK_VERSION"
|
||||
echo ""
|
||||
echo "This can cause runtime errors. See issues #440, #444, #446, #447, #450"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ MCP SDK version is correct: $SDK_VERSION"
|
||||
|
||||
# Check Zod version - must be 3.x (not 4.x, including pre-releases)
|
||||
if [[ "$ZOD_VERSION" == "not found" ]]; then
|
||||
echo "❌ FAILED: Could not determine Zod version!"
|
||||
echo " The dependency may not have been installed correctly."
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$ZOD_VERSION" =~ ^4\. ]]; then
|
||||
echo "❌ FAILED: Zod v4 detected - incompatible with MCP SDK 1.20.1!"
|
||||
echo " Expected: 3.x"
|
||||
echo " Got: $ZOD_VERSION"
|
||||
echo ""
|
||||
echo "Zod v4 causes '_zod' property errors. See issues #440, #444, #446, #447, #450"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Zod version is compatible: $ZOD_VERSION"
|
||||
|
||||
echo ""
|
||||
echo "=== All dependency checks passed ==="
|
||||
|
||||
- name: Test basic functionality
|
||||
working-directory: /tmp/fresh-install-test
|
||||
run: |
|
||||
echo "=== Basic Functionality Test ==="
|
||||
|
||||
# Create a simple test script
|
||||
cat > test-import.mjs << 'EOF'
|
||||
import { spawn } from 'child_process';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// Test that the package can be required and basic tools work
|
||||
async function test() {
|
||||
console.log('Testing n8n-mcp package import...');
|
||||
|
||||
// Start the MCP server briefly to verify it initializes
|
||||
const serverPath = path.join(__dirname, 'node_modules/n8n-mcp/dist/mcp/index.js');
|
||||
|
||||
const proc = spawn('node', [serverPath], {
|
||||
env: { ...process.env, MCP_MODE: 'stdio' },
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
});
|
||||
|
||||
// Send initialize request
|
||||
const initRequest = JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
id: 1,
|
||||
method: 'initialize',
|
||||
params: {
|
||||
protocolVersion: '2024-11-05',
|
||||
capabilities: {},
|
||||
clientInfo: { name: 'test', version: '1.0.0' }
|
||||
}
|
||||
});
|
||||
|
||||
proc.stdin.write(initRequest + '\n');
|
||||
|
||||
// Wait for response or timeout
|
||||
let output = '';
|
||||
let stderrOutput = '';
|
||||
proc.stdout.on('data', (data) => {
|
||||
output += data.toString();
|
||||
});
|
||||
|
||||
proc.stderr.on('data', (data) => {
|
||||
stderrOutput += data.toString();
|
||||
console.error('stderr:', data.toString());
|
||||
});
|
||||
|
||||
// Give it 5 seconds to respond
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
|
||||
proc.kill();
|
||||
|
||||
// Check for Zod v4 compatibility errors (the bug we're testing for)
|
||||
const allOutput = output + stderrOutput;
|
||||
if (allOutput.includes('_zod') || allOutput.includes('Cannot read properties of undefined')) {
|
||||
console.error('❌ FAILED: Zod compatibility error detected!');
|
||||
console.error('This indicates the SDK/Zod version fix is not working.');
|
||||
console.error('See issues #440, #444, #446, #447, #450');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (output.includes('"result"')) {
|
||||
console.log('✅ MCP server initialized successfully');
|
||||
return true;
|
||||
} else {
|
||||
console.log('Output received:', output.substring(0, 500));
|
||||
// Server might not respond in stdio mode without proper framing
|
||||
// But if we got here without crashing, that's still good
|
||||
console.log('✅ MCP server started without errors');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
test()
|
||||
.then(() => {
|
||||
console.log('=== Basic functionality test passed ===');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('❌ Test failed:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
EOF
|
||||
|
||||
node test-import.mjs
|
||||
|
||||
- name: Generate dependency report
|
||||
if: always()
|
||||
working-directory: /tmp/fresh-install-test
|
||||
run: |
|
||||
echo "=== Full Dependency Tree ===" > dependency-report.txt
|
||||
npm list --all >> dependency-report.txt 2>&1 || true
|
||||
|
||||
echo "" >> dependency-report.txt
|
||||
echo "=== Critical Dependencies ===" >> dependency-report.txt
|
||||
npm list @modelcontextprotocol/sdk zod zod-to-json-schema >> dependency-report.txt 2>&1 || true
|
||||
|
||||
cat dependency-report.txt
|
||||
|
||||
- name: Upload dependency report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dependency-report-${{ github.run_number }}
|
||||
path: /tmp/fresh-install-test/dependency-report.txt
|
||||
retention-days: 30
|
||||
106
CHANGELOG.md
106
CHANGELOG.md
@@ -7,6 +7,112 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2.27.1] - 2025-11-29
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
**Issue #454: Docker Image Missing Zod Fix from #450**
|
||||
|
||||
Fixed Docker image build that was missing the pinned MCP SDK version, causing `n8n_create_workflow` Zod validation errors to persist in the 2.27.0 Docker image.
|
||||
|
||||
#### Root Cause
|
||||
|
||||
Two files were not updated when #450 pinned the SDK version in `package.json`:
|
||||
- `package.runtime.json` had `"@modelcontextprotocol/sdk": "^1.13.2"` instead of `"1.20.1"`
|
||||
- `Dockerfile` builder stage had `@modelcontextprotocol/sdk@^1.12.1` hardcoded
|
||||
|
||||
The Docker runtime stage uses `package.runtime.json` (not `package.json`), and the builder stage has hardcoded dependency versions.
|
||||
|
||||
#### Changes
|
||||
|
||||
- **package.runtime.json**: Updated SDK to pinned version `"1.20.1"` (no caret)
|
||||
- **Dockerfile**: Updated builder stage SDK to `@1.20.1` and pinned `zod@3.24.1`
|
||||
|
||||
#### Impact
|
||||
|
||||
- Docker images now include the correct MCP SDK version with Zod fix
|
||||
- `n8n_create_workflow` and other workflow tools work correctly in Docker deployments
|
||||
- No changes to functionality - this is a build configuration fix
|
||||
|
||||
Fixes #454
|
||||
|
||||
**Conceived by Romuald Członkowski - [AiAdvisors](https://www.aiadvisors.pl/en)**
|
||||
|
||||
## [2.27.0] - 2025-11-28
|
||||
|
||||
### ✨ Features
|
||||
|
||||
**n8n_deploy_template Tool**
|
||||
|
||||
Added new tool for one-click deployment of n8n.io workflow templates directly to your n8n instance.
|
||||
|
||||
#### Key Features
|
||||
|
||||
- Fetches templates from n8n.io by ID
|
||||
- Automatically upgrades node typeVersions to latest supported
|
||||
- Validates workflow before deployment
|
||||
- Lists required credentials for configuration
|
||||
- Strips credential references (user configures in n8n UI)
|
||||
|
||||
#### Usage
|
||||
|
||||
```javascript
|
||||
n8n_deploy_template({
|
||||
templateId: 2639, // Required: template ID from n8n.io
|
||||
name: "My Custom Name", // Optional: custom workflow name
|
||||
autoUpgradeVersions: true, // Default: upgrade node versions
|
||||
validate: true, // Default: validate before deploy
|
||||
stripCredentials: true // Default: remove credential refs
|
||||
})
|
||||
```
|
||||
|
||||
**Conceived by Romuald Członkowski - [AiAdvisors](https://www.aiadvisors.pl/en)**
|
||||
|
||||
## [2.26.5] - 2025-11-27
|
||||
|
||||
### 🔧 Fixed
|
||||
|
||||
- **Tools Documentation: Runtime Token Optimization**
|
||||
- Removed historical migration information from tool descriptions (e.g., "Replaces X, Y, Z...")
|
||||
- Removed version-specific references (v2.21.1, issue #357) that are not needed at runtime
|
||||
- Cleaned up consolidation comments in index.ts
|
||||
- Documentation now starts directly with functional content for better AI agent efficiency
|
||||
- Estimated savings: ~128 tokens per full documentation request
|
||||
- Affected tools: `get_node`, `validate_node`, `search_templates`, `n8n_executions`, `n8n_get_workflow`, `n8n_update_partial_workflow`
|
||||
|
||||
**Conceived by Romuald Członkowski - [AiAdvisors](https://www.aiadvisors.pl/en)**
|
||||
|
||||
## [2.26.4] - 2025-11-26
|
||||
|
||||
### 🔧 Fixed
|
||||
|
||||
- **n8n 1.121 Compatibility**: Added support for new workflow settings introduced in n8n 1.121
|
||||
- Added `availableInMCP` (boolean) to settings whitelist - controls "Available in MCP" toggle
|
||||
- Added `callerPolicy` to settings whitelist - was already in schema but missing from sanitization
|
||||
- Both settings are now preserved during workflow updates instead of being silently stripped
|
||||
- Settings can be toggled via `updateSettings` operation: `{type: "updateSettings", settings: {availableInMCP: true}}`
|
||||
|
||||
**Conceived by Romuald Członkowski - [AiAdvisors](https://www.aiadvisors.pl/en)**
|
||||
|
||||
## [2.26.3] - 2025-11-26
|
||||
|
||||
### 🔧 Fixed
|
||||
|
||||
- **Tools Documentation Gaps**: Addressed remaining documentation issues after v2.26.2 tool consolidation
|
||||
- Added missing `n8n_workflow_versions` documentation with all 6 modes (list, get, rollback, delete, prune, truncate)
|
||||
- Removed non-existent tools (`n8n_diagnostic`, `n8n_list_available_tools`) from documentation exports
|
||||
- Fixed 10+ outdated tool name references:
|
||||
- `get_node_essentials` → `get_node({detail: "standard"})`
|
||||
- `validate_node_operation` → `validate_node()`
|
||||
- `get_minimal` → `n8n_get_workflow({mode: "minimal"})`
|
||||
- Added missing `mode` and `verbose` parameters to `n8n_health_check` documentation
|
||||
- Added missing `mode` parameter to `get_template` documentation (nodes_only, structure, full)
|
||||
- Updated template count from "399+" to "2,700+" in `get_template`
|
||||
- Updated node count from "525" to "500+" in `search_nodes`
|
||||
- Fixed `relatedTools` arrays to remove references to non-existent tools
|
||||
|
||||
**Conceived by Romuald Członkowski - [AiAdvisors](https://www.aiadvisors.pl/en)**
|
||||
|
||||
## [2.26.2] - 2025-11-25
|
||||
|
||||
### 🔧 Fixed
|
||||
|
||||
@@ -13,9 +13,9 @@ COPY tsconfig*.json ./
|
||||
RUN --mount=type=cache,target=/root/.npm \
|
||||
echo '{}' > package.json && \
|
||||
npm install --no-save typescript@^5.8.3 @types/node@^22.15.30 @types/express@^5.0.3 \
|
||||
@modelcontextprotocol/sdk@^1.12.1 dotenv@^16.5.0 express@^5.1.0 axios@^1.10.0 \
|
||||
@modelcontextprotocol/sdk@1.20.1 dotenv@^16.5.0 express@^5.1.0 axios@^1.10.0 \
|
||||
n8n-workflow@^1.96.0 uuid@^11.0.5 @types/uuid@^10.0.0 \
|
||||
openai@^4.77.0 zod@^3.24.1 lru-cache@^11.2.1 @supabase/supabase-js@^2.57.4
|
||||
openai@^4.77.0 zod@3.24.1 lru-cache@^11.2.1 @supabase/supabase-js@^2.57.4
|
||||
|
||||
# Copy source and build
|
||||
COPY src ./src
|
||||
|
||||
30
README.md
30
README.md
@@ -36,10 +36,6 @@ AI results can be unpredictable. Protect your work!
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
Get n8n-MCP running in minutes:
|
||||
|
||||
[](https://youtu.be/5CccjiLLyaY?si=Z62SBGlw9G34IQnQ&t=343)
|
||||
|
||||
### Option 1: Hosted Service (Easiest - No Setup!) ☁️
|
||||
|
||||
**The fastest way to try n8n-MCP** - no installation, no configuration:
|
||||
@@ -51,21 +47,7 @@ Get n8n-MCP running in minutes:
|
||||
- ✅ **Always up-to-date**: Latest n8n nodes and templates
|
||||
- ✅ **No infrastructure**: We handle everything
|
||||
|
||||
Just sign up, get your API key, and add to Claude Desktop:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"n8n-mcp": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@anthropic-ai/mcp-remote@latest", "https://mcp.n8n-mcp.com/sse"],
|
||||
"env": {
|
||||
"API_KEY": "your-api-key-from-dashboard"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
Just sign up, get your API key, and connect your MCP client.
|
||||
|
||||
---
|
||||
|
||||
@@ -75,6 +57,10 @@ Prefer to run n8n-MCP yourself? Choose your deployment method:
|
||||
|
||||
### Option A: npx (Quick Local Setup) 🚀
|
||||
|
||||
Get n8n-MCP running in minutes:
|
||||
|
||||
[](https://youtu.be/5CccjiLLyaY?si=Z62SBGlw9G34IQnQ&t=343)
|
||||
|
||||
**Prerequisites:** [Node.js](https://nodejs.org/) installed on your system
|
||||
|
||||
```bash
|
||||
@@ -515,6 +501,9 @@ Complete guide for integrating n8n-MCP with Windsurf using project rules.
|
||||
### [Codex](./docs/CODEX_SETUP.md)
|
||||
Complete guide for integrating n8n-MCP with Codex.
|
||||
|
||||
### [Antigravity](./docs/ANTIGRAVITY_SETUP.md)
|
||||
Complete guide for integrating n8n-MCP with Antigravity.
|
||||
|
||||
## 🎓 Add Claude Skills (Optional)
|
||||
|
||||
Supercharge your n8n workflow building with specialized skills that teach AI how to build production-ready workflows!
|
||||
@@ -968,7 +957,7 @@ Once connected, Claude can use these powerful tools:
|
||||
- `searchMode: 'by_metadata'` - Filter by `complexity`, `requiredService`, `targetAudience`
|
||||
- **`get_template`** - Get complete workflow JSON (modes: nodes_only, structure, full)
|
||||
|
||||
### n8n Management Tools (12 tools - Requires API Configuration)
|
||||
### n8n Management Tools (13 tools - Requires API Configuration)
|
||||
These tools require `N8N_API_URL` and `N8N_API_KEY` in your configuration.
|
||||
|
||||
#### Workflow Management
|
||||
@@ -985,6 +974,7 @@ These tools require `N8N_API_URL` and `N8N_API_KEY` in your configuration.
|
||||
- **`n8n_validate_workflow`** - Validate workflows in n8n by ID
|
||||
- **`n8n_autofix_workflow`** - Automatically fix common workflow errors
|
||||
- **`n8n_workflow_versions`** - Manage version history and rollback
|
||||
- **`n8n_deploy_template`** - Deploy templates from n8n.io directly to your instance (NEW!)
|
||||
|
||||
#### Execution Management
|
||||
- **`n8n_trigger_webhook_workflow`** - Trigger workflows via webhook URL
|
||||
|
||||
BIN
data/nodes.db
BIN
data/nodes.db
Binary file not shown.
433
docs/ANTIGRAVITY_SETUP.md
Normal file
433
docs/ANTIGRAVITY_SETUP.md
Normal file
@@ -0,0 +1,433 @@
|
||||
# Antigravity Setup
|
||||
|
||||
:white_check_mark: This n8n MCP server is compatible with Antigravity (Chat in IDE).
|
||||
|
||||
## Preconditions
|
||||
|
||||
Assuming you've already deployed the n8n MCP server locally and connected it to the n8n API, and it's available at:
|
||||
`http://localhost:5678`
|
||||
|
||||
Or if you are using `https://n8n.your.production.url/` then just replace the URLs in the below code.
|
||||
|
||||
💡 The deployment process is documented in the [HTTP Deployment Guide](./HTTP_DEPLOYMENT.md).
|
||||
|
||||
## Step 1
|
||||
|
||||
Add n8n-mcp globally: `npm install -g n8n-mcp`
|
||||
|
||||
## Step 2
|
||||
|
||||
Add MCP server by clicking three dots `...` on the top right of chat, and click MCP Servers.
|
||||
Then click on "Manage MCP Servers" and then click on "View raw config" and `C:\Users\<USER_NAME>\.gemini\antigravity\mcp_config.json` will be opened.
|
||||
|
||||
## Step 3
|
||||
|
||||
Add the following code to: `C:\Users\<USER_NAME>\.gemini\antigravity\mcp_config.json` and save the file.
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"n8n-mcp": {
|
||||
"command": "node",
|
||||
"args": [
|
||||
"C:\\Users\\<USER_NAME>\\AppData\\Roaming\\npm\\node_modules\\n8n-mcp\\dist\\mcp\\index.js"
|
||||
],
|
||||
"env": {
|
||||
"MCP_MODE": "stdio",
|
||||
"LOG_LEVEL": "error",
|
||||
"DISABLE_CONSOLE_OUTPUT": "true",
|
||||
"N8N_API_URL": "http://localhost:5678",
|
||||
"N8N_BASE_URL": "http://localhost:5678",
|
||||
"N8N_API_KEY": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Step 4
|
||||
|
||||
Go back to "Manage MCP servers" and click referesh. The n8n-mcp will be enabled with all the tools.
|
||||
|
||||
|
||||
## Step 5
|
||||
|
||||
For the best results when using n8n-MCP with Antigravity, use these enhanced system instructions (create `AGENTS.md` in the root directory of the project, `AGENTS.md` is the file standard for giving special instructions in Antigravity):
|
||||
|
||||
````markdown
|
||||
You are an expert in n8n automation software using n8n-MCP tools. Your role is to design, build, and validate n8n workflows with maximum accuracy and efficiency.
|
||||
|
||||
## Core Principles
|
||||
|
||||
### 1. Silent Execution
|
||||
CRITICAL: Execute tools without commentary. Only respond AFTER all tools complete.
|
||||
|
||||
❌ BAD: "Let me search for Slack nodes... Great! Now let me get details..."
|
||||
✅ GOOD: [Execute search_nodes and get_node in parallel, then respond]
|
||||
|
||||
### 2. Parallel Execution
|
||||
When operations are independent, execute them in parallel for maximum performance.
|
||||
|
||||
✅ GOOD: Call search_nodes, list_nodes, and search_templates simultaneously
|
||||
❌ BAD: Sequential tool calls (await each one before the next)
|
||||
|
||||
### 3. Templates First
|
||||
ALWAYS check templates before building from scratch (2,709 available).
|
||||
|
||||
### 4. Multi-Level Validation
|
||||
Use validate_node(mode='minimal') → validate_node(mode='full') → validate_workflow pattern.
|
||||
|
||||
### 5. Never Trust Defaults
|
||||
⚠️ CRITICAL: Default parameter values are the #1 source of runtime failures.
|
||||
ALWAYS explicitly configure ALL parameters that control node behavior.
|
||||
|
||||
## Workflow Process
|
||||
|
||||
1. **Start**: Call `tools_documentation()` for best practices
|
||||
|
||||
2. **Template Discovery Phase** (FIRST - parallel when searching multiple)
|
||||
- `search_templates({searchMode: 'by_metadata', complexity: 'simple'})` - Smart filtering
|
||||
- `search_templates({searchMode: 'by_task', task: 'webhook_processing'})` - Curated by task
|
||||
- `search_templates({query: 'slack notification'})` - Text search (default searchMode='keyword')
|
||||
- `search_templates({searchMode: 'by_nodes', nodeTypes: ['n8n-nodes-base.slack']})` - By node type
|
||||
|
||||
**Filtering strategies**:
|
||||
- Beginners: `complexity: "simple"` + `maxSetupMinutes: 30`
|
||||
- By role: `targetAudience: "marketers"` | `"developers"` | `"analysts"`
|
||||
- By time: `maxSetupMinutes: 15` for quick wins
|
||||
- By service: `requiredService: "openai"` for compatibility
|
||||
|
||||
3. **Node Discovery** (if no suitable template - parallel execution)
|
||||
- Think deeply about requirements. Ask clarifying questions if unclear.
|
||||
- `search_nodes({query: 'keyword', includeExamples: true})` - Parallel for multiple nodes
|
||||
- `search_nodes({query: 'trigger'})` - Browse triggers
|
||||
- `search_nodes({query: 'AI agent langchain'})` - AI-capable nodes
|
||||
|
||||
4. **Configuration Phase** (parallel for multiple nodes)
|
||||
- `get_node({nodeType, detail: 'standard', includeExamples: true})` - Essential properties (default)
|
||||
- `get_node({nodeType, detail: 'minimal'})` - Basic metadata only (~200 tokens)
|
||||
- `get_node({nodeType, detail: 'full'})` - Complete information (~3000-8000 tokens)
|
||||
- `get_node({nodeType, mode: 'search_properties', propertyQuery: 'auth'})` - Find specific properties
|
||||
- `get_node({nodeType, mode: 'docs'})` - Human-readable markdown documentation
|
||||
- Show workflow architecture to user for approval before proceeding
|
||||
|
||||
5. **Validation Phase** (parallel for multiple nodes)
|
||||
- `validate_node({nodeType, config, mode: 'minimal'})` - Quick required fields check
|
||||
- `validate_node({nodeType, config, mode: 'full', profile: 'runtime'})` - Full validation with fixes
|
||||
- Fix ALL errors before proceeding
|
||||
|
||||
6. **Building Phase**
|
||||
- If using template: `get_template(templateId, {mode: "full"})`
|
||||
- **MANDATORY ATTRIBUTION**: "Based on template by **[author.name]** (@[username]). View at: [url]"
|
||||
- Build from validated configurations
|
||||
- ⚠️ EXPLICITLY set ALL parameters - never rely on defaults
|
||||
- Connect nodes with proper structure
|
||||
- Add error handling
|
||||
- Use n8n expressions: $json, $node["NodeName"].json
|
||||
- Build in artifact (unless deploying to n8n instance)
|
||||
|
||||
7. **Workflow Validation** (before deployment)
|
||||
- `validate_workflow(workflow)` - Complete validation
|
||||
- `validate_workflow_connections(workflow)` - Structure check
|
||||
- `validate_workflow_expressions(workflow)` - Expression validation
|
||||
- Fix ALL issues before deployment
|
||||
|
||||
8. **Deployment** (if n8n API configured)
|
||||
- `n8n_create_workflow(workflow)` - Deploy
|
||||
- `n8n_validate_workflow({id})` - Post-deployment check
|
||||
- `n8n_update_partial_workflow({id, operations: [...]})` - Batch updates
|
||||
- `n8n_trigger_webhook_workflow()` - Test webhooks
|
||||
|
||||
## Critical Warnings
|
||||
|
||||
### ⚠️ Never Trust Defaults
|
||||
Default values cause runtime failures. Example:
|
||||
```json
|
||||
// ❌ FAILS at runtime
|
||||
{resource: "message", operation: "post", text: "Hello"}
|
||||
|
||||
// ✅ WORKS - all parameters explicit
|
||||
{resource: "message", operation: "post", select: "channel", channelId: "C123", text: "Hello"}
|
||||
```
|
||||
|
||||
### ⚠️ Example Availability
|
||||
`includeExamples: true` returns real configurations from workflow templates.
|
||||
- Coverage varies by node popularity
|
||||
- When no examples available, use `get_node` + `validate_node({mode: 'minimal'})`
|
||||
|
||||
## Validation Strategy
|
||||
|
||||
### Level 1 - Quick Check (before building)
|
||||
`validate_node({nodeType, config, mode: 'minimal'})` - Required fields only (<100ms)
|
||||
|
||||
### Level 2 - Comprehensive (before building)
|
||||
`validate_node({nodeType, config, mode: 'full', profile: 'runtime'})` - Full validation with fixes
|
||||
|
||||
### Level 3 - Complete (after building)
|
||||
`validate_workflow(workflow)` - Connections, expressions, AI tools
|
||||
|
||||
### Level 4 - Post-Deployment
|
||||
1. `n8n_validate_workflow({id})` - Validate deployed workflow
|
||||
2. `n8n_autofix_workflow({id})` - Auto-fix common errors
|
||||
3. `n8n_executions({action: 'list'})` - Monitor execution status
|
||||
|
||||
## Response Format
|
||||
|
||||
### Initial Creation
|
||||
```
|
||||
[Silent tool execution in parallel]
|
||||
|
||||
Created workflow:
|
||||
- Webhook trigger → Slack notification
|
||||
- Configured: POST /webhook → #general channel
|
||||
|
||||
Validation: ✅ All checks passed
|
||||
```
|
||||
|
||||
### Modifications
|
||||
```
|
||||
[Silent tool execution]
|
||||
|
||||
Updated workflow:
|
||||
- Added error handling to HTTP node
|
||||
- Fixed required Slack parameters
|
||||
|
||||
Changes validated successfully.
|
||||
```
|
||||
|
||||
## Batch Operations
|
||||
|
||||
Use `n8n_update_partial_workflow` with multiple operations in a single call:
|
||||
|
||||
✅ GOOD - Batch multiple operations:
|
||||
```json
|
||||
n8n_update_partial_workflow({
|
||||
id: "wf-123",
|
||||
operations: [
|
||||
{type: "updateNode", nodeId: "slack-1", changes: {...}},
|
||||
{type: "updateNode", nodeId: "http-1", changes: {...}},
|
||||
{type: "cleanStaleConnections"}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
❌ BAD - Separate calls:
|
||||
```json
|
||||
n8n_update_partial_workflow({id: "wf-123", operations: [{...}]})
|
||||
n8n_update_partial_workflow({id: "wf-123", operations: [{...}]})
|
||||
```
|
||||
|
||||
### CRITICAL: addConnection Syntax
|
||||
|
||||
The `addConnection` operation requires **four separate string parameters**. Common mistakes cause misleading errors.
|
||||
|
||||
❌ WRONG - Object format (fails with "Expected string, received object"):
|
||||
```json
|
||||
{
|
||||
"type": "addConnection",
|
||||
"connection": {
|
||||
"source": {"nodeId": "node-1", "outputIndex": 0},
|
||||
"destination": {"nodeId": "node-2", "inputIndex": 0}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
❌ WRONG - Combined string (fails with "Source node not found"):
|
||||
```json
|
||||
{
|
||||
"type": "addConnection",
|
||||
"source": "node-1:main:0",
|
||||
"target": "node-2:main:0"
|
||||
}
|
||||
```
|
||||
|
||||
✅ CORRECT - Four separate string parameters:
|
||||
```json
|
||||
{
|
||||
"type": "addConnection",
|
||||
"source": "node-id-string",
|
||||
"target": "target-node-id-string",
|
||||
"sourcePort": "main",
|
||||
"targetPort": "main"
|
||||
}
|
||||
```
|
||||
|
||||
**Reference**: [GitHub Issue #327](https://github.com/czlonkowski/n8n-mcp/issues/327)
|
||||
|
||||
### ⚠️ CRITICAL: IF Node Multi-Output Routing
|
||||
|
||||
IF nodes have **two outputs** (TRUE and FALSE). Use the **`branch` parameter** to route to the correct output:
|
||||
|
||||
✅ CORRECT - Route to TRUE branch (when condition is met):
|
||||
```json
|
||||
{
|
||||
"type": "addConnection",
|
||||
"source": "if-node-id",
|
||||
"target": "success-handler-id",
|
||||
"sourcePort": "main",
|
||||
"targetPort": "main",
|
||||
"branch": "true"
|
||||
}
|
||||
```
|
||||
|
||||
✅ CORRECT - Route to FALSE branch (when condition is NOT met):
|
||||
```json
|
||||
{
|
||||
"type": "addConnection",
|
||||
"source": "if-node-id",
|
||||
"target": "failure-handler-id",
|
||||
"sourcePort": "main",
|
||||
"targetPort": "main",
|
||||
"branch": "false"
|
||||
}
|
||||
```
|
||||
|
||||
**Common Pattern** - Complete IF node routing:
|
||||
```json
|
||||
n8n_update_partial_workflow({
|
||||
id: "workflow-id",
|
||||
operations: [
|
||||
{type: "addConnection", source: "If Node", target: "True Handler", sourcePort: "main", targetPort: "main", branch: "true"},
|
||||
{type: "addConnection", source: "If Node", target: "False Handler", sourcePort: "main", targetPort: "main", branch: "false"}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
**Note**: Without the `branch` parameter, both connections may end up on the same output, causing logic errors!
|
||||
|
||||
### removeConnection Syntax
|
||||
|
||||
Use the same four-parameter format:
|
||||
```json
|
||||
{
|
||||
"type": "removeConnection",
|
||||
"source": "source-node-id",
|
||||
"target": "target-node-id",
|
||||
"sourcePort": "main",
|
||||
"targetPort": "main"
|
||||
}
|
||||
```
|
||||
|
||||
## Example Workflow
|
||||
|
||||
### Template-First Approach
|
||||
|
||||
```
|
||||
// STEP 1: Template Discovery (parallel execution)
|
||||
[Silent execution]
|
||||
search_templates({
|
||||
searchMode: 'by_metadata',
|
||||
requiredService: 'slack',
|
||||
complexity: 'simple',
|
||||
targetAudience: 'marketers'
|
||||
})
|
||||
search_templates({searchMode: 'by_task', task: 'slack_integration'})
|
||||
|
||||
// STEP 2: Use template
|
||||
get_template(templateId, {mode: 'full'})
|
||||
validate_workflow(workflow)
|
||||
|
||||
// Response after all tools complete:
|
||||
"Found template by **David Ashby** (@cfomodz).
|
||||
View at: https://n8n.io/workflows/2414
|
||||
|
||||
Validation: ✅ All checks passed"
|
||||
```
|
||||
|
||||
### Building from Scratch (if no template)
|
||||
|
||||
```
|
||||
// STEP 1: Discovery (parallel execution)
|
||||
[Silent execution]
|
||||
search_nodes({query: 'slack', includeExamples: true})
|
||||
search_nodes({query: 'communication trigger'})
|
||||
|
||||
// STEP 2: Configuration (parallel execution)
|
||||
[Silent execution]
|
||||
get_node({nodeType: 'n8n-nodes-base.slack', detail: 'standard', includeExamples: true})
|
||||
get_node({nodeType: 'n8n-nodes-base.webhook', detail: 'standard', includeExamples: true})
|
||||
|
||||
// STEP 3: Validation (parallel execution)
|
||||
[Silent execution]
|
||||
validate_node({nodeType: 'n8n-nodes-base.slack', config, mode: 'minimal'})
|
||||
validate_node({nodeType: 'n8n-nodes-base.slack', config: fullConfig, mode: 'full', profile: 'runtime'})
|
||||
|
||||
// STEP 4: Build
|
||||
// Construct workflow with validated configs
|
||||
// ⚠️ Set ALL parameters explicitly
|
||||
|
||||
// STEP 5: Validate
|
||||
[Silent execution]
|
||||
validate_workflow(workflowJson)
|
||||
|
||||
// Response after all tools complete:
|
||||
"Created workflow: Webhook → Slack
|
||||
Validation: ✅ Passed"
|
||||
```
|
||||
|
||||
### Batch Updates
|
||||
|
||||
```json
|
||||
// ONE call with multiple operations
|
||||
n8n_update_partial_workflow({
|
||||
id: "wf-123",
|
||||
operations: [
|
||||
{type: "updateNode", nodeId: "slack-1", changes: {position: [100, 200]}},
|
||||
{type: "updateNode", nodeId: "http-1", changes: {position: [300, 200]}},
|
||||
{type: "cleanStaleConnections"}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
## Important Rules
|
||||
|
||||
### Core Behavior
|
||||
1. **Silent execution** - No commentary between tools
|
||||
2. **Parallel by default** - Execute independent operations simultaneously
|
||||
3. **Templates first** - Always check before building (2,709 available)
|
||||
4. **Multi-level validation** - Quick check → Full validation → Workflow validation
|
||||
5. **Never trust defaults** - Explicitly configure ALL parameters
|
||||
|
||||
### Attribution & Credits
|
||||
- **MANDATORY TEMPLATE ATTRIBUTION**: Share author name, username, and n8n.io link
|
||||
- **Template validation** - Always validate before deployment (may need updates)
|
||||
|
||||
### Performance
|
||||
- **Batch operations** - Use diff operations with multiple changes in one call
|
||||
- **Parallel execution** - Search, validate, and configure simultaneously
|
||||
- **Template metadata** - Use smart filtering for faster discovery
|
||||
|
||||
### Code Node Usage
|
||||
- **Avoid when possible** - Prefer standard nodes
|
||||
- **Only when necessary** - Use code node as last resort
|
||||
- **AI tool capability** - ANY node can be an AI tool (not just marked ones)
|
||||
|
||||
### Most Popular n8n Nodes (for get_node):
|
||||
|
||||
1. **n8n-nodes-base.code** - JavaScript/Python scripting
|
||||
2. **n8n-nodes-base.httpRequest** - HTTP API calls
|
||||
3. **n8n-nodes-base.webhook** - Event-driven triggers
|
||||
4. **n8n-nodes-base.set** - Data transformation
|
||||
5. **n8n-nodes-base.if** - Conditional routing
|
||||
6. **n8n-nodes-base.manualTrigger** - Manual workflow execution
|
||||
7. **n8n-nodes-base.respondToWebhook** - Webhook responses
|
||||
8. **n8n-nodes-base.scheduleTrigger** - Time-based triggers
|
||||
9. **@n8n/n8n-nodes-langchain.agent** - AI agents
|
||||
10. **n8n-nodes-base.googleSheets** - Spreadsheet integration
|
||||
11. **n8n-nodes-base.merge** - Data merging
|
||||
12. **n8n-nodes-base.switch** - Multi-branch routing
|
||||
13. **n8n-nodes-base.telegram** - Telegram bot integration
|
||||
14. **@n8n/n8n-nodes-langchain.lmChatOpenAi** - OpenAI chat models
|
||||
15. **n8n-nodes-base.splitInBatches** - Batch processing
|
||||
16. **n8n-nodes-base.openAi** - OpenAI legacy node
|
||||
17. **n8n-nodes-base.gmail** - Email automation
|
||||
18. **n8n-nodes-base.function** - Custom functions
|
||||
19. **n8n-nodes-base.stickyNote** - Workflow documentation
|
||||
20. **n8n-nodes-base.executeWorkflowTrigger** - Sub-workflow calls
|
||||
|
||||
**Note:** LangChain nodes use the `@n8n/n8n-nodes-langchain.` prefix, core nodes use `n8n-nodes-base.`
|
||||
|
||||
````
|
||||
|
||||
This helps the agent produce higher-quality, well-structured n8n workflows.
|
||||
|
||||
🧪 This setup is for windows but for Mac and Linux also, it is similar, just provide the absolute path of the global `n8n-mcp` install! 😄 Stay tuned for updates!
|
||||
50
package-lock.json
generated
50
package-lock.json
generated
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "n8n-mcp",
|
||||
"version": "2.26.0",
|
||||
"version": "2.27.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "n8n-mcp",
|
||||
"version": "2.26.0",
|
||||
"version": "2.27.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.20.1",
|
||||
"@modelcontextprotocol/sdk": "1.20.1",
|
||||
"@n8n/n8n-nodes-langchain": "^1.120.1",
|
||||
"@supabase/supabase-js": "^2.57.4",
|
||||
"dotenv": "^16.5.0",
|
||||
@@ -23,7 +23,7 @@
|
||||
"sql.js": "^1.13.0",
|
||||
"tslib": "^2.6.2",
|
||||
"uuid": "^10.0.0",
|
||||
"zod": "^3.24.1"
|
||||
"zod": "3.24.1"
|
||||
},
|
||||
"bin": {
|
||||
"n8n-mcp": "dist/mcp/index.js"
|
||||
@@ -8454,6 +8454,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@langchain/community/node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
},
|
||||
"node_modules/@langchain/core": {
|
||||
"version": "0.3.68",
|
||||
"resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.68.tgz",
|
||||
@@ -8477,6 +8486,15 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@langchain/core/node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
},
|
||||
"node_modules/@langchain/google-common": {
|
||||
"version": "0.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@langchain/google-common/-/google-common-0.2.18.tgz",
|
||||
@@ -8742,6 +8760,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@langchain/openai/node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
},
|
||||
"node_modules/@langchain/pinecone": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@langchain/pinecone/-/pinecone-0.2.0.tgz",
|
||||
@@ -22681,6 +22708,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/langchain/node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
},
|
||||
"node_modules/langsmith": {
|
||||
"version": "0.3.69",
|
||||
"resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.69.tgz",
|
||||
@@ -33907,9 +33943,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"version": "3.24.1",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
|
||||
"integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-mcp",
|
||||
"version": "2.26.2",
|
||||
"version": "2.27.1",
|
||||
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
@@ -140,7 +140,7 @@
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.20.1",
|
||||
"@modelcontextprotocol/sdk": "1.20.1",
|
||||
"@n8n/n8n-nodes-langchain": "^1.120.1",
|
||||
"@supabase/supabase-js": "^2.57.4",
|
||||
"dotenv": "^16.5.0",
|
||||
@@ -154,7 +154,7 @@
|
||||
"sql.js": "^1.13.0",
|
||||
"tslib": "^2.6.2",
|
||||
"uuid": "^10.0.0",
|
||||
"zod": "^3.24.1"
|
||||
"zod": "3.24.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-darwin-arm64": "^4.50.0",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "n8n-mcp-runtime",
|
||||
"version": "2.23.0",
|
||||
"version": "2.27.1",
|
||||
"description": "n8n MCP Server Runtime Dependencies Only",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.13.2",
|
||||
"@modelcontextprotocol/sdk": "1.20.1",
|
||||
"@supabase/supabase-js": "^2.57.4",
|
||||
"express": "^5.1.0",
|
||||
"express-rate-limit": "^7.1.5",
|
||||
|
||||
@@ -34,6 +34,7 @@ import { ExpressionFormatValidator, ExpressionFormatIssue } from '../services/ex
|
||||
import { WorkflowVersioningService } from '../services/workflow-versioning-service';
|
||||
import { handleUpdatePartialWorkflow } from './handlers-workflow-diff';
|
||||
import { telemetry } from '../telemetry';
|
||||
import { TemplateService } from '../templates/template-service';
|
||||
import {
|
||||
createCacheKey,
|
||||
createInstanceCache,
|
||||
@@ -1788,7 +1789,7 @@ export async function handleDiagnostic(request: any, context?: InstanceContext):
|
||||
|
||||
// Check which tools are available
|
||||
const documentationTools = 7; // Base documentation tools (after v2.26.0 consolidation)
|
||||
const managementTools = apiConfigured ? 12 : 0; // Management tools requiring API (after v2.26.0 consolidation)
|
||||
const managementTools = apiConfigured ? 13 : 0; // Management tools requiring API (includes n8n_deploy_template)
|
||||
const totalTools = documentationTools + managementTools;
|
||||
|
||||
// Check npm version
|
||||
@@ -2189,3 +2190,220 @@ export async function handleWorkflowVersions(
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Template Deployment Handler
|
||||
// ========================================================================
|
||||
|
||||
const deployTemplateSchema = z.object({
|
||||
templateId: z.number().positive().int(),
|
||||
name: z.string().optional(),
|
||||
autoUpgradeVersions: z.boolean().default(true),
|
||||
validate: z.boolean().default(true),
|
||||
stripCredentials: z.boolean().default(true)
|
||||
});
|
||||
|
||||
interface RequiredCredential {
|
||||
nodeType: string;
|
||||
nodeName: string;
|
||||
credentialType: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deploy a workflow template from n8n.io directly to the user's n8n instance.
|
||||
*
|
||||
* This handler:
|
||||
* 1. Fetches the template from the local template database
|
||||
* 2. Extracts credential requirements for user guidance
|
||||
* 3. Optionally strips credentials (for user to configure in n8n UI)
|
||||
* 4. Optionally upgrades node typeVersions to latest supported
|
||||
* 5. Optionally validates the workflow structure
|
||||
* 6. Creates the workflow in the n8n instance
|
||||
*/
|
||||
export async function handleDeployTemplate(
|
||||
args: unknown,
|
||||
templateService: TemplateService,
|
||||
repository: NodeRepository,
|
||||
context?: InstanceContext
|
||||
): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured(context);
|
||||
const input = deployTemplateSchema.parse(args);
|
||||
|
||||
// Fetch template
|
||||
const template = await templateService.getTemplate(input.templateId, 'full');
|
||||
if (!template) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Template ${input.templateId} not found`,
|
||||
details: {
|
||||
hint: 'Use search_templates to find available templates',
|
||||
templateUrl: `https://n8n.io/workflows/${input.templateId}`
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Extract workflow from template (deep copy to avoid mutation)
|
||||
const workflow = JSON.parse(JSON.stringify(template.workflow));
|
||||
if (!workflow || !workflow.nodes) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Template has invalid workflow structure',
|
||||
details: { templateId: input.templateId }
|
||||
};
|
||||
}
|
||||
|
||||
// Set workflow name
|
||||
const workflowName = input.name || template.name;
|
||||
|
||||
// Collect required credentials before stripping
|
||||
const requiredCredentials: RequiredCredential[] = [];
|
||||
for (const node of workflow.nodes) {
|
||||
if (node.credentials && typeof node.credentials === 'object') {
|
||||
for (const [credType] of Object.entries(node.credentials)) {
|
||||
requiredCredentials.push({
|
||||
nodeType: node.type,
|
||||
nodeName: node.name,
|
||||
credentialType: credType
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Strip credentials if requested
|
||||
if (input.stripCredentials) {
|
||||
workflow.nodes = workflow.nodes.map((node: any) => {
|
||||
const { credentials, ...rest } = node;
|
||||
return rest;
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-upgrade typeVersions if requested
|
||||
if (input.autoUpgradeVersions) {
|
||||
const autoFixer = new WorkflowAutoFixer(repository);
|
||||
|
||||
// Run validation to get issues to fix
|
||||
const validator = new WorkflowValidator(repository, EnhancedConfigValidator);
|
||||
const validationResult = await validator.validateWorkflow(workflow, {
|
||||
validateNodes: true,
|
||||
validateConnections: false,
|
||||
validateExpressions: false,
|
||||
profile: 'runtime'
|
||||
});
|
||||
|
||||
// Generate fixes focused on typeVersion upgrades
|
||||
const fixResult = await autoFixer.generateFixes(
|
||||
workflow,
|
||||
validationResult,
|
||||
[],
|
||||
{ fixTypes: ['typeversion-upgrade', 'typeversion-correction'] }
|
||||
);
|
||||
|
||||
// Apply fixes to workflow
|
||||
if (fixResult.operations.length > 0) {
|
||||
for (const op of fixResult.operations) {
|
||||
if (op.type === 'updateNode' && op.updates) {
|
||||
const node = workflow.nodes.find((n: any) =>
|
||||
n.id === op.nodeId || n.name === op.nodeName
|
||||
);
|
||||
if (node) {
|
||||
for (const [path, value] of Object.entries(op.updates)) {
|
||||
if (path === 'typeVersion') {
|
||||
node.typeVersion = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate workflow if requested
|
||||
if (input.validate) {
|
||||
const validator = new WorkflowValidator(repository, EnhancedConfigValidator);
|
||||
const validationResult = await validator.validateWorkflow(workflow, {
|
||||
validateNodes: true,
|
||||
validateConnections: true,
|
||||
validateExpressions: true,
|
||||
profile: 'runtime'
|
||||
});
|
||||
|
||||
if (validationResult.errors.length > 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Workflow validation failed',
|
||||
details: {
|
||||
errors: validationResult.errors.map(e => ({
|
||||
node: e.nodeName,
|
||||
message: e.message
|
||||
})),
|
||||
warnings: validationResult.warnings.length,
|
||||
hint: 'Use validate=false to skip validation, or fix the template issues'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Identify trigger type
|
||||
const triggerNode = workflow.nodes.find((n: any) =>
|
||||
n.type?.includes('Trigger') ||
|
||||
n.type?.includes('webhook') ||
|
||||
n.type === 'n8n-nodes-base.webhook'
|
||||
);
|
||||
const triggerType = triggerNode?.type?.split('.').pop() || 'manual';
|
||||
|
||||
// Create workflow via API (always creates inactive)
|
||||
const createdWorkflow = await client.createWorkflow({
|
||||
name: workflowName,
|
||||
nodes: workflow.nodes,
|
||||
connections: workflow.connections,
|
||||
settings: workflow.settings || { executionOrder: 'v1' }
|
||||
});
|
||||
|
||||
// Get base URL for workflow link
|
||||
const apiConfig = context ? getN8nApiConfigFromContext(context) : getN8nApiConfig();
|
||||
const baseUrl = apiConfig?.baseUrl?.replace('/api/v1', '') || '';
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
workflowId: createdWorkflow.id,
|
||||
name: createdWorkflow.name,
|
||||
active: false,
|
||||
nodeCount: workflow.nodes.length,
|
||||
triggerType,
|
||||
requiredCredentials: requiredCredentials.length > 0 ? requiredCredentials : undefined,
|
||||
url: baseUrl ? `${baseUrl}/workflow/${createdWorkflow.id}` : undefined,
|
||||
templateId: input.templateId,
|
||||
templateUrl: template.url || `https://n8n.io/workflows/${input.templateId}`
|
||||
},
|
||||
message: `Workflow "${createdWorkflow.name}" deployed successfully from template ${input.templateId}. ${
|
||||
requiredCredentials.length > 0
|
||||
? `Configure ${requiredCredentials.length} credential(s) in n8n to activate.`
|
||||
: ''
|
||||
}`
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Invalid input',
|
||||
details: { errors: error.errors }
|
||||
};
|
||||
}
|
||||
|
||||
if (error instanceof N8nApiError) {
|
||||
return {
|
||||
success: false,
|
||||
error: getUserFriendlyErrorMessage(error),
|
||||
code: error.code,
|
||||
details: error.details as Record<string, unknown> | undefined
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -856,6 +856,12 @@ export class N8NDocumentationMCPServer {
|
||||
? { valid: true, errors: [] }
|
||||
: { valid: false, errors: [{ field: 'action', message: 'action is required' }] };
|
||||
break;
|
||||
case 'n8n_deploy_template':
|
||||
// Requires templateId parameter
|
||||
validationResult = args.templateId !== undefined
|
||||
? { valid: true, errors: [] }
|
||||
: { valid: false, errors: [{ field: 'templateId', message: 'templateId is required' }] };
|
||||
break;
|
||||
default:
|
||||
// For tools not yet migrated to schema validation, use basic validation
|
||||
return this.validateToolParamsBasic(toolName, args, legacyRequiredParams || []);
|
||||
@@ -1203,6 +1209,13 @@ export class N8NDocumentationMCPServer {
|
||||
this.validateToolParams(name, args, ['mode']);
|
||||
return n8nHandlers.handleWorkflowVersions(args, this.repository!, this.instanceContext);
|
||||
|
||||
case 'n8n_deploy_template':
|
||||
this.validateToolParams(name, args, ['templateId']);
|
||||
await this.ensureInitialized();
|
||||
if (!this.templateService) throw new Error('Template service not initialized');
|
||||
if (!this.repository) throw new Error('Repository not initialized');
|
||||
return n8nHandlers.handleDeployTemplate(args, this.templateService, this.repository, this.instanceContext);
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${name}`);
|
||||
}
|
||||
|
||||
@@ -17,9 +17,7 @@ export const getNodeDoc: ToolDocumentation = {
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: `Unified tool for all node information needs. Replaces get_node_info, get_node_essentials, get_node_documentation, and search_node_properties with a single versatile API.
|
||||
|
||||
**Detail Levels (mode="info", default):**
|
||||
description: `**Detail Levels (mode="info", default):**
|
||||
- minimal (~200 tokens): Basic metadata only - nodeType, displayName, description, category
|
||||
- standard (~1-2K tokens): Essential properties + operations - recommended for most tasks
|
||||
- full (~3-8K tokens): Complete node schema - use only when standard insufficient
|
||||
|
||||
@@ -4,7 +4,7 @@ export const searchNodesDoc: ToolDocumentation = {
|
||||
name: 'search_nodes',
|
||||
category: 'discovery',
|
||||
essentials: {
|
||||
description: 'Text search across node names and descriptions. Returns most relevant nodes first, with frequently-used nodes (HTTP Request, Webhook, Set, Code, Slack) prioritized in results. Searches all 525 nodes in the database.',
|
||||
description: 'Text search across node names and descriptions. Returns most relevant nodes first, with frequently-used nodes (HTTP Request, Webhook, Set, Code, Slack) prioritized in results. Searches all 500+ nodes in the database.',
|
||||
keyParameters: ['query', 'mode', 'limit'],
|
||||
example: 'search_nodes({query: "webhook"})',
|
||||
performance: '<20ms even for complex queries',
|
||||
@@ -42,7 +42,7 @@ export const searchNodesDoc: ToolDocumentation = {
|
||||
'Start with single keywords for broadest results',
|
||||
'Use FUZZY mode when users might misspell node names',
|
||||
'AND mode works best for 2-3 word searches',
|
||||
'Combine with get_node_essentials after finding the right node'
|
||||
'Combine with get_node after finding the right node'
|
||||
],
|
||||
pitfalls: [
|
||||
'AND mode searches all fields (name, description) not just node names',
|
||||
|
||||
@@ -7,9 +7,7 @@ import { validateNodeDoc, validateWorkflowDoc } from './validation';
|
||||
import { getTemplateDoc, searchTemplatesDoc } from './templates';
|
||||
import {
|
||||
toolsDocumentationDoc,
|
||||
n8nDiagnosticDoc,
|
||||
n8nHealthCheckDoc,
|
||||
n8nListAvailableToolsDoc
|
||||
n8nHealthCheckDoc
|
||||
} from './system';
|
||||
import { aiAgentsGuide } from './guides';
|
||||
import {
|
||||
@@ -22,17 +20,16 @@ import {
|
||||
n8nValidateWorkflowDoc,
|
||||
n8nAutofixWorkflowDoc,
|
||||
n8nTriggerWebhookWorkflowDoc,
|
||||
n8nExecutionsDoc
|
||||
n8nExecutionsDoc,
|
||||
n8nWorkflowVersionsDoc,
|
||||
n8nDeployTemplateDoc
|
||||
} from './workflow_management';
|
||||
|
||||
// Combine all tool documentations into a single object
|
||||
// Total: 19 tools after v2.26.0 consolidation
|
||||
export const toolsDocumentation: Record<string, ToolDocumentation> = {
|
||||
// System tools
|
||||
tools_documentation: toolsDocumentationDoc,
|
||||
n8n_diagnostic: n8nDiagnosticDoc,
|
||||
n8n_health_check: n8nHealthCheckDoc,
|
||||
n8n_list_available_tools: n8nListAvailableToolsDoc,
|
||||
|
||||
// Guides
|
||||
ai_agents_guide: aiAgentsGuide,
|
||||
@@ -40,20 +37,20 @@ export const toolsDocumentation: Record<string, ToolDocumentation> = {
|
||||
// Discovery tools
|
||||
search_nodes: searchNodesDoc,
|
||||
|
||||
// Configuration tools (consolidated)
|
||||
get_node: getNodeDoc, // Replaces: get_node_info, get_node_essentials, get_node_documentation, search_node_properties
|
||||
// Configuration tools
|
||||
get_node: getNodeDoc,
|
||||
|
||||
// Validation tools (consolidated)
|
||||
validate_node: validateNodeDoc, // Replaces: validate_node_operation, validate_node_minimal
|
||||
validate_workflow: validateWorkflowDoc, // Options replace: validate_workflow_connections, validate_workflow_expressions
|
||||
// Validation tools
|
||||
validate_node: validateNodeDoc,
|
||||
validate_workflow: validateWorkflowDoc,
|
||||
|
||||
// Template tools (consolidated)
|
||||
// Template tools
|
||||
get_template: getTemplateDoc,
|
||||
search_templates: searchTemplatesDoc, // Modes replace: list_node_templates, search_templates_by_metadata, get_templates_for_task
|
||||
search_templates: searchTemplatesDoc,
|
||||
|
||||
// Workflow Management tools (n8n API)
|
||||
n8n_create_workflow: n8nCreateWorkflowDoc,
|
||||
n8n_get_workflow: n8nGetWorkflowDoc, // Modes replace: n8n_get_workflow_details, n8n_get_workflow_structure, n8n_get_workflow_minimal
|
||||
n8n_get_workflow: n8nGetWorkflowDoc,
|
||||
n8n_update_full_workflow: n8nUpdateFullWorkflowDoc,
|
||||
n8n_update_partial_workflow: n8nUpdatePartialWorkflowDoc,
|
||||
n8n_delete_workflow: n8nDeleteWorkflowDoc,
|
||||
@@ -61,7 +58,9 @@ export const toolsDocumentation: Record<string, ToolDocumentation> = {
|
||||
n8n_validate_workflow: n8nValidateWorkflowDoc,
|
||||
n8n_autofix_workflow: n8nAutofixWorkflowDoc,
|
||||
n8n_trigger_webhook_workflow: n8nTriggerWebhookWorkflowDoc,
|
||||
n8n_executions: n8nExecutionsDoc // Actions replace: n8n_get_execution, n8n_list_executions, n8n_delete_execution
|
||||
n8n_executions: n8nExecutionsDoc,
|
||||
n8n_workflow_versions: n8nWorkflowVersionsDoc,
|
||||
n8n_deploy_template: n8nDeployTemplateDoc
|
||||
};
|
||||
|
||||
// Re-export types
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
export { toolsDocumentationDoc } from './tools-documentation';
|
||||
export { n8nDiagnosticDoc } from './n8n-diagnostic';
|
||||
export { n8nHealthCheckDoc } from './n8n-health-check';
|
||||
export { n8nListAvailableToolsDoc } from './n8n-list-available-tools';
|
||||
export { n8nHealthCheckDoc } from './n8n-health-check';
|
||||
@@ -5,8 +5,8 @@ export const n8nHealthCheckDoc: ToolDocumentation = {
|
||||
category: 'system',
|
||||
essentials: {
|
||||
description: 'Check n8n instance health, API connectivity, version status, and performance metrics',
|
||||
keyParameters: [],
|
||||
example: 'n8n_health_check({})',
|
||||
keyParameters: ['mode', 'verbose'],
|
||||
example: 'n8n_health_check({mode: "status"})',
|
||||
performance: 'Fast - single API call (~150-200ms median)',
|
||||
tips: [
|
||||
'Use before starting workflow operations to ensure n8n is responsive',
|
||||
@@ -31,7 +31,21 @@ Health checks are crucial for:
|
||||
- Detecting performance degradation
|
||||
- Verifying API compatibility before operations
|
||||
- Ensuring authentication is working correctly`,
|
||||
parameters: {},
|
||||
parameters: {
|
||||
mode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Operation mode: "status" (default) for quick health check, "diagnostic" for detailed debug info including env vars and tool status',
|
||||
default: 'status',
|
||||
enum: ['status', 'diagnostic']
|
||||
},
|
||||
verbose: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
description: 'Include extra details in diagnostic mode',
|
||||
default: false
|
||||
}
|
||||
},
|
||||
returns: `Health status object containing:
|
||||
- status: Overall health status ('healthy', 'degraded', 'error')
|
||||
- n8nVersion: n8n instance version information
|
||||
@@ -81,6 +95,6 @@ Health checks are crucial for:
|
||||
'Does not check individual workflow health',
|
||||
'Health endpoint might be cached - not real-time for all metrics'
|
||||
],
|
||||
relatedTools: ['n8n_diagnostic', 'n8n_list_available_tools', 'n8n_list_workflows']
|
||||
relatedTools: ['n8n_list_workflows', 'n8n_validate_workflow', 'n8n_workflow_versions']
|
||||
}
|
||||
};
|
||||
@@ -4,23 +4,30 @@ export const getTemplateDoc: ToolDocumentation = {
|
||||
name: 'get_template',
|
||||
category: 'templates',
|
||||
essentials: {
|
||||
description: 'Get complete workflow JSON by ID. Ready to import. IDs from search_templates.',
|
||||
keyParameters: ['templateId'],
|
||||
example: 'get_template({templateId: 1234})',
|
||||
description: 'Get workflow template by ID with configurable detail level. Ready to import. IDs from search_templates.',
|
||||
keyParameters: ['templateId', 'mode'],
|
||||
example: 'get_template({templateId: 1234, mode: "full"})',
|
||||
performance: 'Fast (<100ms) - single database lookup',
|
||||
tips: [
|
||||
'Get template IDs from search_templates first',
|
||||
'Returns complete workflow JSON ready for import into n8n',
|
||||
'Includes all nodes, connections, and settings'
|
||||
'Use mode="nodes_only" for quick overview, "structure" for topology, "full" for import',
|
||||
'Returns complete workflow JSON ready for import into n8n'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: `Retrieves the complete workflow JSON for a specific template by its ID. The returned workflow can be directly imported into n8n through the UI or API. This tool fetches pre-built workflows from the community template library containing 399+ curated workflows.`,
|
||||
description: `Retrieves the complete workflow JSON for a specific template by its ID. The returned workflow can be directly imported into n8n through the UI or API. This tool fetches pre-built workflows from the community template library containing 2,700+ curated workflows.`,
|
||||
parameters: {
|
||||
templateId: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
description: 'The numeric ID of the template to retrieve. Get IDs from search_templates'
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Response detail level: "nodes_only" (minimal - just node list), "structure" (nodes + connections), "full" (complete workflow JSON, default)',
|
||||
default: 'full',
|
||||
enum: ['nodes_only', 'structure', 'full']
|
||||
}
|
||||
},
|
||||
returns: `Returns an object containing:
|
||||
@@ -39,9 +46,10 @@ export const getTemplateDoc: ToolDocumentation = {
|
||||
- settings: Workflow configuration (timezone, error handling, etc.)
|
||||
- usage: Instructions for using the workflow`,
|
||||
examples: [
|
||||
'get_template({templateId: 1234}) - Get Slack notification workflow',
|
||||
'get_template({templateId: 5678}) - Get data sync workflow',
|
||||
'get_template({templateId: 9012}) - Get AI chatbot workflow'
|
||||
'get_template({templateId: 1234}) - Get complete workflow (default mode="full")',
|
||||
'get_template({templateId: 1234, mode: "nodes_only"}) - Get just the node list',
|
||||
'get_template({templateId: 1234, mode: "structure"}) - Get nodes and connections',
|
||||
'get_template({templateId: 5678, mode: "full"}) - Get complete workflow JSON for import'
|
||||
],
|
||||
useCases: [
|
||||
'Download workflows for direct import into n8n',
|
||||
|
||||
@@ -16,9 +16,7 @@ export const searchTemplatesDoc: ToolDocumentation = {
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: `Unified template search tool with four search modes. Replaces search_templates, list_node_templates, search_templates_by_metadata, and get_templates_for_task.
|
||||
|
||||
**Search Modes:**
|
||||
description: `**Search Modes:**
|
||||
- keyword (default): Full-text search across template names and descriptions
|
||||
- by_nodes: Find templates that use specific node types
|
||||
- by_task: Get curated templates for predefined task categories
|
||||
|
||||
@@ -16,9 +16,7 @@ export const validateNodeDoc: ToolDocumentation = {
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: `Unified node configuration validator. Replaces validate_node_operation and validate_node_minimal with a single tool.
|
||||
|
||||
**Validation Modes:**
|
||||
description: `**Validation Modes:**
|
||||
- full (default): Comprehensive validation with errors, warnings, suggestions, and automatic structure validation
|
||||
- minimal: Quick check for required fields only - fast but less thorough
|
||||
|
||||
|
||||
@@ -8,3 +8,5 @@ export { n8nValidateWorkflowDoc } from './n8n-validate-workflow';
|
||||
export { n8nAutofixWorkflowDoc } from './n8n-autofix-workflow';
|
||||
export { n8nTriggerWebhookWorkflowDoc } from './n8n-trigger-webhook-workflow';
|
||||
export { n8nExecutionsDoc } from './n8n-executions';
|
||||
export { n8nWorkflowVersionsDoc } from './n8n-workflow-versions';
|
||||
export { n8nDeployTemplateDoc } from './n8n-deploy-template';
|
||||
|
||||
@@ -11,7 +11,7 @@ export const n8nDeleteWorkflowDoc: ToolDocumentation = {
|
||||
tips: [
|
||||
'Action is irreversible',
|
||||
'Deletes all execution history',
|
||||
'Check workflow first with get_minimal'
|
||||
'Check workflow first with n8n_get_workflow({mode: "minimal"})'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
@@ -34,7 +34,7 @@ export const n8nDeleteWorkflowDoc: ToolDocumentation = {
|
||||
performance: 'Fast operation - typically 50-150ms. May take longer if workflow has extensive execution history.',
|
||||
bestPractices: [
|
||||
'Always confirm before deletion',
|
||||
'Check workflow with get_minimal first',
|
||||
'Check workflow with n8n_get_workflow({mode: "minimal"}) first',
|
||||
'Consider deactivating instead of deleting',
|
||||
'Export workflow before deletion for backup'
|
||||
],
|
||||
|
||||
69
src/mcp/tool-docs/workflow_management/n8n-deploy-template.ts
Normal file
69
src/mcp/tool-docs/workflow_management/n8n-deploy-template.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const n8nDeployTemplateDoc: ToolDocumentation = {
|
||||
name: 'n8n_deploy_template',
|
||||
category: 'workflow_management',
|
||||
essentials: {
|
||||
description: 'Deploy a workflow template from n8n.io directly to your n8n instance. Fetches template, optionally upgrades node versions and validates, then creates workflow.',
|
||||
keyParameters: ['templateId', 'name', 'autoUpgradeVersions', 'validate', 'stripCredentials'],
|
||||
example: 'n8n_deploy_template({templateId: 2776, name: "My Deployed Template"})',
|
||||
performance: 'Network-dependent',
|
||||
tips: [
|
||||
'Workflow created inactive - configure credentials in n8n UI first',
|
||||
'Returns list of required credentials',
|
||||
'Use search_templates to find template IDs',
|
||||
'Templates are upgraded to latest node versions by default'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: 'Deploys a workflow template from n8n.io directly to your n8n instance. This tool combines fetching a template and creating a workflow in a single operation. Templates are stored locally and fetched from the database. The workflow is always created in an inactive state, allowing you to configure credentials before activation.',
|
||||
parameters: {
|
||||
templateId: { type: 'number', required: true, description: 'Template ID from n8n.io (find via search_templates)' },
|
||||
name: { type: 'string', description: 'Custom workflow name (default: template name)' },
|
||||
autoUpgradeVersions: { type: 'boolean', description: 'Upgrade node typeVersions to latest supported (default: true)' },
|
||||
validate: { type: 'boolean', description: 'Validate workflow before deployment (default: true)' },
|
||||
stripCredentials: { type: 'boolean', description: 'Remove credential references - user configures in n8n UI (default: true)' }
|
||||
},
|
||||
returns: 'Object with workflowId, name, nodeCount, triggerType, requiredCredentials array, url, templateId, templateUrl',
|
||||
examples: [
|
||||
`// Deploy template with default settings
|
||||
n8n_deploy_template({templateId: 2776})`,
|
||||
`// Deploy with custom name
|
||||
n8n_deploy_template({
|
||||
templateId: 2776,
|
||||
name: "My Google Drive to Airtable Sync"
|
||||
})`,
|
||||
`// Deploy without validation (faster, use for trusted templates)
|
||||
n8n_deploy_template({
|
||||
templateId: 2776,
|
||||
validate: false
|
||||
})`,
|
||||
`// Keep original node versions (useful for compatibility)
|
||||
n8n_deploy_template({
|
||||
templateId: 2776,
|
||||
autoUpgradeVersions: false
|
||||
})`
|
||||
],
|
||||
useCases: [
|
||||
'Quickly deploy pre-built workflow templates',
|
||||
'Set up common automation patterns',
|
||||
'Bootstrap new projects with proven workflows',
|
||||
'Deploy templates found via search_templates'
|
||||
],
|
||||
performance: 'Network-dependent - Typically 200-500ms (template fetch + workflow creation)',
|
||||
bestPractices: [
|
||||
'Use search_templates to find templates by use case',
|
||||
'Review required credentials in the response',
|
||||
'Configure credentials in n8n UI before activating',
|
||||
'Test workflow before connecting to production systems'
|
||||
],
|
||||
pitfalls: [
|
||||
'**REQUIRES N8N_API_URL and N8N_API_KEY environment variables** - tool unavailable without n8n API access',
|
||||
'Workflows created in INACTIVE state - must configure credentials and activate in n8n',
|
||||
'Templates may reference services you do not have (Slack, Google, etc.)',
|
||||
'Template database must be populated - run npm run fetch:templates if templates not found',
|
||||
'Validation may fail for templates with outdated node configurations'
|
||||
],
|
||||
relatedTools: ['search_templates', 'get_template', 'n8n_create_workflow', 'n8n_validate_workflow']
|
||||
}
|
||||
};
|
||||
@@ -16,9 +16,7 @@ export const n8nExecutionsDoc: ToolDocumentation = {
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: `Unified execution management tool. Replaces n8n_get_execution, n8n_list_executions, and n8n_delete_execution.
|
||||
|
||||
**Actions:**
|
||||
description: `**Actions:**
|
||||
- get: Retrieve execution details by ID with configurable detail level
|
||||
- list: List executions with filtering and pagination
|
||||
- delete: Remove an execution record from history
|
||||
|
||||
@@ -16,9 +16,7 @@ export const n8nGetWorkflowDoc: ToolDocumentation = {
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: `Unified workflow retrieval with configurable detail levels. Replaces n8n_get_workflow, n8n_get_workflow_details, n8n_get_workflow_structure, and n8n_get_workflow_minimal.
|
||||
|
||||
**Modes:**
|
||||
description: `**Modes:**
|
||||
- full (default): Complete workflow including all nodes with parameters, connections, and settings
|
||||
- details: Full workflow plus execution statistics (success/error counts, last execution time)
|
||||
- structure: Nodes and connections only - useful for topology analysis
|
||||
|
||||
@@ -90,7 +90,6 @@ Full support for all 8 AI connection types used in n8n AI workflows:
|
||||
|
||||
**Important Notes**:
|
||||
- **AI nodes do NOT require main connections**: Nodes like OpenAI Chat Model, Postgres Chat Memory, Embeddings OpenAI, and Supabase Vector Store use AI-specific connection types exclusively. They should ONLY have connections like \`ai_languageModel\`, \`ai_memory\`, \`ai_embedding\`, or \`ai_tool\` - NOT \`main\` connections.
|
||||
- **Fixed in v2.21.1**: Validation now correctly recognizes AI nodes that only have AI-specific connections without requiring \`main\` connections (resolves issue #357).
|
||||
|
||||
**Best Practices**:
|
||||
- Always specify \`sourceOutput\` for AI connections (defaults to "main" if omitted)
|
||||
|
||||
168
src/mcp/tool-docs/workflow_management/n8n-workflow-versions.ts
Normal file
168
src/mcp/tool-docs/workflow_management/n8n-workflow-versions.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const n8nWorkflowVersionsDoc: ToolDocumentation = {
|
||||
name: 'n8n_workflow_versions',
|
||||
category: 'workflow_management',
|
||||
essentials: {
|
||||
description: 'Manage workflow version history, rollback to previous versions, and cleanup old versions',
|
||||
keyParameters: ['mode', 'workflowId', 'versionId'],
|
||||
example: 'n8n_workflow_versions({mode: "list", workflowId: "abc123"})',
|
||||
performance: 'Fast for list/get (~100ms), moderate for rollback (~200-500ms)',
|
||||
tips: [
|
||||
'Use mode="list" to see all saved versions before rollback',
|
||||
'Rollback creates a backup version automatically',
|
||||
'Use prune to clean up old versions and save storage',
|
||||
'truncate requires explicit confirmTruncate: true'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: `Comprehensive workflow version management system. Supports six operations:
|
||||
|
||||
**list** - Show version history for a workflow
|
||||
- Returns all saved versions with timestamps, snapshot sizes, and metadata
|
||||
- Use limit parameter to control how many versions to return
|
||||
|
||||
**get** - Get details of a specific version
|
||||
- Returns the complete workflow snapshot from that version
|
||||
- Use to compare versions or extract old configurations
|
||||
|
||||
**rollback** - Restore workflow to a previous version
|
||||
- Creates a backup of the current workflow before rollback
|
||||
- Optionally validates the workflow structure before applying
|
||||
- Returns the restored workflow and backup version ID
|
||||
|
||||
**delete** - Delete specific version(s)
|
||||
- Delete a single version by versionId
|
||||
- Delete all versions for a workflow with deleteAll: true
|
||||
|
||||
**prune** - Clean up old versions
|
||||
- Keeps only the N most recent versions (default: 10)
|
||||
- Useful for managing storage and keeping history manageable
|
||||
|
||||
**truncate** - Delete ALL versions for ALL workflows
|
||||
- Dangerous operation requiring explicit confirmation
|
||||
- Use for complete version history cleanup`,
|
||||
parameters: {
|
||||
mode: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Operation mode: "list", "get", "rollback", "delete", "prune", or "truncate"',
|
||||
enum: ['list', 'get', 'rollback', 'delete', 'prune', 'truncate']
|
||||
},
|
||||
workflowId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Workflow ID (required for list, rollback, delete, prune modes)'
|
||||
},
|
||||
versionId: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'Version ID (required for get mode, optional for rollback to specific version, required for single delete)'
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 10,
|
||||
description: 'Maximum versions to return in list mode'
|
||||
},
|
||||
validateBefore: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true,
|
||||
description: 'Validate workflow structure before rollback (rollback mode only)'
|
||||
},
|
||||
deleteAll: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: false,
|
||||
description: 'Delete all versions for workflow (delete mode only)'
|
||||
},
|
||||
maxVersions: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 10,
|
||||
description: 'Keep N most recent versions (prune mode only)'
|
||||
},
|
||||
confirmTruncate: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: false,
|
||||
description: 'REQUIRED: Must be true to truncate all versions (truncate mode only)'
|
||||
}
|
||||
},
|
||||
returns: `Response varies by mode:
|
||||
|
||||
**list mode:**
|
||||
- versions: Array of version objects with id, workflowId, snapshotSize, createdAt
|
||||
- totalCount: Total number of versions
|
||||
|
||||
**get mode:**
|
||||
- version: Complete version object including workflow snapshot
|
||||
|
||||
**rollback mode:**
|
||||
- success: Boolean indicating success
|
||||
- restoredVersion: The version that was restored
|
||||
- backupVersionId: ID of the backup created before rollback
|
||||
|
||||
**delete mode:**
|
||||
- deletedCount: Number of versions deleted
|
||||
|
||||
**prune mode:**
|
||||
- prunedCount: Number of old versions removed
|
||||
- remainingCount: Number of versions kept
|
||||
|
||||
**truncate mode:**
|
||||
- deletedCount: Total versions deleted across all workflows`,
|
||||
examples: [
|
||||
'// List version history\nn8n_workflow_versions({mode: "list", workflowId: "abc123", limit: 5})',
|
||||
'// Get specific version details\nn8n_workflow_versions({mode: "get", versionId: 42})',
|
||||
'// Rollback to latest saved version\nn8n_workflow_versions({mode: "rollback", workflowId: "abc123"})',
|
||||
'// Rollback to specific version\nn8n_workflow_versions({mode: "rollback", workflowId: "abc123", versionId: 42})',
|
||||
'// Delete specific version\nn8n_workflow_versions({mode: "delete", workflowId: "abc123", versionId: 42})',
|
||||
'// Delete all versions for workflow\nn8n_workflow_versions({mode: "delete", workflowId: "abc123", deleteAll: true})',
|
||||
'// Prune to keep only 5 most recent\nn8n_workflow_versions({mode: "prune", workflowId: "abc123", maxVersions: 5})',
|
||||
'// Truncate all versions (dangerous!)\nn8n_workflow_versions({mode: "truncate", confirmTruncate: true})'
|
||||
],
|
||||
useCases: [
|
||||
'Recover from accidental workflow changes',
|
||||
'Compare workflow versions to understand changes',
|
||||
'Maintain audit trail of workflow modifications',
|
||||
'Clean up old versions to save database storage',
|
||||
'Roll back failed workflow deployments'
|
||||
],
|
||||
performance: `Performance varies by operation:
|
||||
- list: Fast (~100ms) - simple database query
|
||||
- get: Fast (~100ms) - single row retrieval
|
||||
- rollback: Moderate (~200-500ms) - includes backup creation and workflow update
|
||||
- delete: Fast (~50-100ms) - database delete operation
|
||||
- prune: Moderate (~100-300ms) - depends on number of versions to delete
|
||||
- truncate: Slow (1-5s) - deletes all records across all workflows`,
|
||||
modeComparison: `| Mode | Required Params | Optional Params | Risk Level |
|
||||
|------|-----------------|-----------------|------------|
|
||||
| list | workflowId | limit | Low |
|
||||
| get | versionId | - | Low |
|
||||
| rollback | workflowId | versionId, validateBefore | Medium |
|
||||
| delete | workflowId | versionId, deleteAll | High |
|
||||
| prune | workflowId | maxVersions | Medium |
|
||||
| truncate | confirmTruncate=true | - | Critical |`,
|
||||
bestPractices: [
|
||||
'Always list versions before rollback to pick the right one',
|
||||
'Enable validateBefore for rollback to catch structural issues',
|
||||
'Use prune regularly to keep version history manageable',
|
||||
'Never use truncate in production without explicit need',
|
||||
'Document why you are rolling back for audit purposes'
|
||||
],
|
||||
pitfalls: [
|
||||
'Rollback overwrites current workflow - backup is created automatically',
|
||||
'Deleted versions cannot be recovered',
|
||||
'Truncate affects ALL workflows - use with extreme caution',
|
||||
'Version IDs are sequential but may have gaps after deletes',
|
||||
'Large workflows may have significant version storage overhead'
|
||||
],
|
||||
relatedTools: [
|
||||
'n8n_get_workflow - View current workflow state',
|
||||
'n8n_update_partial_workflow - Make incremental changes',
|
||||
'n8n_validate_workflow - Validate before deployment'
|
||||
]
|
||||
}
|
||||
};
|
||||
@@ -126,7 +126,7 @@ When working with Code nodes, always start by calling the relevant guide:
|
||||
- searchMode='by_task': Curated task-based templates
|
||||
- searchMode='by_metadata': Filter by complexity/services
|
||||
|
||||
**n8n API Tools** (12 tools, requires N8N_API_URL configuration)
|
||||
**n8n API Tools** (13 tools, requires N8N_API_URL configuration)
|
||||
- n8n_create_workflow - Create new workflows
|
||||
- n8n_get_workflow - Get workflow with mode='full'/'details'/'structure'/'minimal'
|
||||
- n8n_update_full_workflow - Full workflow replacement
|
||||
@@ -139,6 +139,7 @@ When working with Code nodes, always start by calling the relevant guide:
|
||||
- n8n_executions - Unified execution management (action='get'/'list'/'delete')
|
||||
- n8n_health_check - Check n8n API connectivity
|
||||
- n8n_workflow_versions - Version history and rollback
|
||||
- n8n_deploy_template - Deploy templates directly to n8n instance
|
||||
|
||||
## Performance Characteristics
|
||||
- Instant (<10ms): search_nodes, get_node (minimal/standard)
|
||||
@@ -422,8 +423,8 @@ try {
|
||||
5. Use descriptive variable names
|
||||
|
||||
## Related Tools
|
||||
- get_node_essentials("nodes-base.code")
|
||||
- validate_node_operation()
|
||||
- get_node({nodeType: "nodes-base.code"}) - Get Code node configuration details
|
||||
- validate_node({nodeType: "nodes-base.code", config: {...}}) - Validate Code node setup
|
||||
- python_code_node_guide (for Python syntax)`;
|
||||
}
|
||||
|
||||
@@ -691,7 +692,7 @@ except json.JSONDecodeError:
|
||||
\`\`\`
|
||||
|
||||
## Related Tools
|
||||
- get_node_essentials("nodes-base.code")
|
||||
- validate_node_operation()
|
||||
- get_node({nodeType: "nodes-base.code"}) - Get Code node configuration details
|
||||
- validate_node({nodeType: "nodes-base.code", config: {...}}) - Validate Code node setup
|
||||
- javascript_code_node_guide (for JavaScript syntax)`;
|
||||
}
|
||||
@@ -445,5 +445,40 @@ export const n8nManagementTools: ToolDefinition[] = [
|
||||
},
|
||||
required: ['mode']
|
||||
}
|
||||
},
|
||||
|
||||
// Template Deployment Tool
|
||||
{
|
||||
name: 'n8n_deploy_template',
|
||||
description: `Deploy a workflow template from n8n.io directly to your n8n instance. Fetches template, optionally upgrades node versions and validates, then creates workflow. Returns workflow ID and required credentials list.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
templateId: {
|
||||
type: 'number',
|
||||
description: 'Template ID from n8n.io (required)'
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'Custom workflow name (default: template name)'
|
||||
},
|
||||
autoUpgradeVersions: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Automatically upgrade node typeVersions to latest supported (default: true)'
|
||||
},
|
||||
validate: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Validate workflow before deployment (default: true)'
|
||||
},
|
||||
stripCredentials: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Remove credential references from nodes - user configures in n8n UI (default: true)'
|
||||
}
|
||||
},
|
||||
required: ['templateId']
|
||||
}
|
||||
}
|
||||
];
|
||||
@@ -62,6 +62,7 @@ export const workflowSettingsSchema = z.object({
|
||||
executionTimeout: z.number().optional(),
|
||||
errorWorkflow: z.string().optional(),
|
||||
callerPolicy: z.enum(['any', 'workflowsFromSameOwner', 'workflowsFromAList']).optional(),
|
||||
availableInMCP: z.boolean().optional(),
|
||||
});
|
||||
|
||||
// Default settings for workflow creation
|
||||
@@ -181,7 +182,9 @@ export function cleanWorkflowForUpdate(workflow: Workflow): Partial<Workflow> {
|
||||
'executionTimeout',
|
||||
'errorWorkflow',
|
||||
'timezone',
|
||||
'executionOrder'
|
||||
'executionOrder',
|
||||
'callerPolicy',
|
||||
'availableInMCP',
|
||||
];
|
||||
|
||||
if (cleanedWorkflow.settings && typeof cleanedWorkflow.settings === 'object') {
|
||||
|
||||
@@ -383,14 +383,11 @@ export class WorkflowValidator {
|
||||
});
|
||||
}
|
||||
}
|
||||
// Normalize node type FIRST to ensure consistent lookup
|
||||
// Normalize node type for database lookup (DO NOT mutate the original workflow)
|
||||
// The normalizer converts to short form (nodes-base.*) for database queries,
|
||||
// but n8n API requires full form (n8n-nodes-base.*). Never modify the input workflow.
|
||||
const normalizedType = NodeTypeNormalizer.normalizeToFullForm(node.type);
|
||||
|
||||
// Update node type in place if it was normalized
|
||||
if (normalizedType !== node.type) {
|
||||
node.type = normalizedType;
|
||||
}
|
||||
|
||||
// Get node definition using normalized type (needed for typeVersion validation)
|
||||
const nodeInfo = this.nodeRepository.getNode(normalizedType);
|
||||
|
||||
@@ -684,7 +681,12 @@ export class WorkflowValidator {
|
||||
}
|
||||
|
||||
// Special validation for SplitInBatches node
|
||||
if (sourceNode && sourceNode.type === 'nodes-base.splitInBatches') {
|
||||
// Check both full form (n8n-nodes-base.*) and short form (nodes-base.*)
|
||||
const isSplitInBatches = sourceNode && (
|
||||
sourceNode.type === 'n8n-nodes-base.splitInBatches' ||
|
||||
sourceNode.type === 'nodes-base.splitInBatches'
|
||||
);
|
||||
if (isSplitInBatches) {
|
||||
this.validateSplitInBatchesConnection(
|
||||
sourceNode,
|
||||
outputIndex,
|
||||
@@ -696,8 +698,8 @@ export class WorkflowValidator {
|
||||
|
||||
// Check for self-referencing connections
|
||||
if (connection.node === sourceName) {
|
||||
// This is only a warning for non-loop nodes
|
||||
if (sourceNode && sourceNode.type !== 'nodes-base.splitInBatches') {
|
||||
// This is only a warning for non-loop nodes (not SplitInBatches)
|
||||
if (sourceNode && !isSplitInBatches) {
|
||||
result.warnings.push({
|
||||
type: 'warning',
|
||||
message: `Node "${sourceName}" has a self-referencing connection. This can cause infinite loops.`
|
||||
|
||||
265
tests/unit/mcp/handlers-deploy-template.test.ts
Normal file
265
tests/unit/mcp/handlers-deploy-template.test.ts
Normal file
@@ -0,0 +1,265 @@
|
||||
/**
|
||||
* Unit tests for handleDeployTemplate handler - input validation and schema tests
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { z } from 'zod';
|
||||
|
||||
// Test the schema directly without needing full API mocking
|
||||
const deployTemplateSchema = z.object({
|
||||
templateId: z.number().positive().int(),
|
||||
name: z.string().optional(),
|
||||
autoUpgradeVersions: z.boolean().default(true),
|
||||
validate: z.boolean().default(true),
|
||||
stripCredentials: z.boolean().default(true)
|
||||
});
|
||||
|
||||
describe('handleDeployTemplate Schema Validation', () => {
|
||||
describe('Input Schema', () => {
|
||||
it('should require templateId as a positive integer', () => {
|
||||
// Valid input
|
||||
const validResult = deployTemplateSchema.safeParse({ templateId: 123 });
|
||||
expect(validResult.success).toBe(true);
|
||||
|
||||
// Invalid: missing templateId
|
||||
const missingResult = deployTemplateSchema.safeParse({});
|
||||
expect(missingResult.success).toBe(false);
|
||||
|
||||
// Invalid: templateId as string
|
||||
const stringResult = deployTemplateSchema.safeParse({ templateId: '123' });
|
||||
expect(stringResult.success).toBe(false);
|
||||
|
||||
// Invalid: negative templateId
|
||||
const negativeResult = deployTemplateSchema.safeParse({ templateId: -1 });
|
||||
expect(negativeResult.success).toBe(false);
|
||||
|
||||
// Invalid: zero templateId
|
||||
const zeroResult = deployTemplateSchema.safeParse({ templateId: 0 });
|
||||
expect(zeroResult.success).toBe(false);
|
||||
|
||||
// Invalid: decimal templateId
|
||||
const decimalResult = deployTemplateSchema.safeParse({ templateId: 123.5 });
|
||||
expect(decimalResult.success).toBe(false);
|
||||
});
|
||||
|
||||
it('should accept optional name parameter', () => {
|
||||
const withName = deployTemplateSchema.safeParse({
|
||||
templateId: 123,
|
||||
name: 'Custom Name'
|
||||
});
|
||||
expect(withName.success).toBe(true);
|
||||
if (withName.success) {
|
||||
expect(withName.data.name).toBe('Custom Name');
|
||||
}
|
||||
|
||||
const withoutName = deployTemplateSchema.safeParse({ templateId: 123 });
|
||||
expect(withoutName.success).toBe(true);
|
||||
if (withoutName.success) {
|
||||
expect(withoutName.data.name).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('should default autoUpgradeVersions to true', () => {
|
||||
const result = deployTemplateSchema.safeParse({ templateId: 123 });
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.autoUpgradeVersions).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('should allow setting autoUpgradeVersions to false', () => {
|
||||
const result = deployTemplateSchema.safeParse({
|
||||
templateId: 123,
|
||||
autoUpgradeVersions: false
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.autoUpgradeVersions).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it('should default validate to true', () => {
|
||||
const result = deployTemplateSchema.safeParse({ templateId: 123 });
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.validate).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('should default stripCredentials to true', () => {
|
||||
const result = deployTemplateSchema.safeParse({ templateId: 123 });
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.stripCredentials).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('should accept all parameters together', () => {
|
||||
const result = deployTemplateSchema.safeParse({
|
||||
templateId: 2776,
|
||||
name: 'My Deployed Workflow',
|
||||
autoUpgradeVersions: false,
|
||||
validate: false,
|
||||
stripCredentials: false
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.templateId).toBe(2776);
|
||||
expect(result.data.name).toBe('My Deployed Workflow');
|
||||
expect(result.data.autoUpgradeVersions).toBe(false);
|
||||
expect(result.data.validate).toBe(false);
|
||||
expect(result.data.stripCredentials).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleDeployTemplate Helper Functions', () => {
|
||||
describe('Credential Extraction Logic', () => {
|
||||
it('should extract credential types from node credentials object', () => {
|
||||
const nodes = [
|
||||
{
|
||||
id: 'node-1',
|
||||
name: 'Slack',
|
||||
type: 'n8n-nodes-base.slack',
|
||||
credentials: {
|
||||
slackApi: { id: 'cred-1', name: 'My Slack' }
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'node-2',
|
||||
name: 'Google Sheets',
|
||||
type: 'n8n-nodes-base.googleSheets',
|
||||
credentials: {
|
||||
googleSheetsOAuth2Api: { id: 'cred-2', name: 'My Google' }
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'node-3',
|
||||
name: 'Set',
|
||||
type: 'n8n-nodes-base.set'
|
||||
// No credentials
|
||||
}
|
||||
];
|
||||
|
||||
// Simulate the credential extraction logic from the handler
|
||||
const requiredCredentials: Array<{
|
||||
nodeType: string;
|
||||
nodeName: string;
|
||||
credentialType: string;
|
||||
}> = [];
|
||||
|
||||
for (const node of nodes) {
|
||||
if (node.credentials && typeof node.credentials === 'object') {
|
||||
for (const [credType] of Object.entries(node.credentials)) {
|
||||
requiredCredentials.push({
|
||||
nodeType: node.type,
|
||||
nodeName: node.name,
|
||||
credentialType: credType
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect(requiredCredentials).toHaveLength(2);
|
||||
expect(requiredCredentials[0]).toEqual({
|
||||
nodeType: 'n8n-nodes-base.slack',
|
||||
nodeName: 'Slack',
|
||||
credentialType: 'slackApi'
|
||||
});
|
||||
expect(requiredCredentials[1]).toEqual({
|
||||
nodeType: 'n8n-nodes-base.googleSheets',
|
||||
nodeName: 'Google Sheets',
|
||||
credentialType: 'googleSheetsOAuth2Api'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Credential Stripping Logic', () => {
|
||||
it('should remove credentials property from nodes', () => {
|
||||
const nodes = [
|
||||
{
|
||||
id: 'node-1',
|
||||
name: 'Slack',
|
||||
type: 'n8n-nodes-base.slack',
|
||||
typeVersion: 2,
|
||||
position: [250, 300],
|
||||
parameters: { channel: '#general' },
|
||||
credentials: {
|
||||
slackApi: { id: 'cred-1', name: 'My Slack' }
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
// Simulate the credential stripping logic from the handler
|
||||
const strippedNodes = nodes.map((node: any) => {
|
||||
const { credentials, ...rest } = node;
|
||||
return rest;
|
||||
});
|
||||
|
||||
expect(strippedNodes[0].credentials).toBeUndefined();
|
||||
expect(strippedNodes[0].id).toBe('node-1');
|
||||
expect(strippedNodes[0].name).toBe('Slack');
|
||||
expect(strippedNodes[0].parameters).toEqual({ channel: '#general' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Trigger Type Detection Logic', () => {
|
||||
it('should identify trigger nodes', () => {
|
||||
const testCases = [
|
||||
{ type: 'n8n-nodes-base.scheduleTrigger', expected: 'scheduleTrigger' },
|
||||
{ type: 'n8n-nodes-base.webhook', expected: 'webhook' },
|
||||
{ type: 'n8n-nodes-base.emailReadImapTrigger', expected: 'emailReadImapTrigger' },
|
||||
{ type: 'n8n-nodes-base.googleDriveTrigger', expected: 'googleDriveTrigger' }
|
||||
];
|
||||
|
||||
for (const { type, expected } of testCases) {
|
||||
const nodes = [{ type, name: 'Trigger' }];
|
||||
|
||||
// Simulate the trigger detection logic from the handler
|
||||
const triggerNode = nodes.find((n: any) =>
|
||||
n.type?.includes('Trigger') ||
|
||||
n.type?.includes('webhook') ||
|
||||
n.type === 'n8n-nodes-base.webhook'
|
||||
);
|
||||
const triggerType = triggerNode?.type?.split('.').pop() || 'manual';
|
||||
|
||||
expect(triggerType).toBe(expected);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return manual for workflows without trigger', () => {
|
||||
const nodes = [
|
||||
{ type: 'n8n-nodes-base.set', name: 'Set' },
|
||||
{ type: 'n8n-nodes-base.httpRequest', name: 'HTTP Request' }
|
||||
];
|
||||
|
||||
const triggerNode = nodes.find((n: any) =>
|
||||
n.type?.includes('Trigger') ||
|
||||
n.type?.includes('webhook') ||
|
||||
n.type === 'n8n-nodes-base.webhook'
|
||||
);
|
||||
const triggerType = triggerNode?.type?.split('.').pop() || 'manual';
|
||||
|
||||
expect(triggerType).toBe('manual');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tool Definition Validation', () => {
|
||||
it('should have correct tool name', () => {
|
||||
// This tests that the tool is properly exported
|
||||
const toolName = 'n8n_deploy_template';
|
||||
expect(toolName).toBe('n8n_deploy_template');
|
||||
});
|
||||
|
||||
it('should have required parameter templateId in schema', () => {
|
||||
// Validate that the schema correctly requires templateId
|
||||
const validResult = deployTemplateSchema.safeParse({ templateId: 123 });
|
||||
const invalidResult = deployTemplateSchema.safeParse({});
|
||||
|
||||
expect(validResult.success).toBe(true);
|
||||
expect(invalidResult.success).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1072,10 +1072,10 @@ describe('handlers-n8n-manager', () => {
|
||||
enabled: true,
|
||||
},
|
||||
managementTools: {
|
||||
count: 12,
|
||||
count: 13,
|
||||
enabled: true,
|
||||
},
|
||||
totalAvailable: 19,
|
||||
totalAvailable: 20,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -403,45 +403,47 @@ describe('n8n-validation', () => {
|
||||
settings: {
|
||||
executionOrder: 'v1' as const,
|
||||
saveDataSuccessExecution: 'none' as const,
|
||||
callerPolicy: 'workflowsFromSameOwner' as const, // Filtered out (not in OpenAPI spec)
|
||||
callerPolicy: 'workflowsFromSameOwner' as const, // Now whitelisted (n8n 1.121+)
|
||||
timeSavedPerExecution: 5, // Filtered out (UI-only property)
|
||||
},
|
||||
} as any;
|
||||
|
||||
const cleaned = cleanWorkflowForUpdate(workflow);
|
||||
|
||||
// Unsafe properties filtered out, safe properties kept
|
||||
// Unsafe properties filtered out, safe properties kept (callerPolicy now whitelisted)
|
||||
expect(cleaned.settings).toEqual({
|
||||
executionOrder: 'v1',
|
||||
saveDataSuccessExecution: 'none'
|
||||
saveDataSuccessExecution: 'none',
|
||||
callerPolicy: 'workflowsFromSameOwner'
|
||||
});
|
||||
expect(cleaned.settings).not.toHaveProperty('callerPolicy');
|
||||
expect(cleaned.settings).not.toHaveProperty('timeSavedPerExecution');
|
||||
});
|
||||
|
||||
it('should filter out callerPolicy (Issue #248 - API limitation)', () => {
|
||||
it('should preserve callerPolicy and availableInMCP (n8n 1.121+ settings)', () => {
|
||||
const workflow = {
|
||||
name: 'Test Workflow',
|
||||
nodes: [],
|
||||
connections: {},
|
||||
settings: {
|
||||
executionOrder: 'v1' as const,
|
||||
callerPolicy: 'workflowsFromSameOwner' as const, // Filtered out
|
||||
callerPolicy: 'workflowsFromSameOwner' as const, // Now whitelisted
|
||||
availableInMCP: true, // New in n8n 1.121
|
||||
errorWorkflow: 'N2O2nZy3aUiBRGFN',
|
||||
},
|
||||
} as any;
|
||||
|
||||
const cleaned = cleanWorkflowForUpdate(workflow);
|
||||
|
||||
// callerPolicy filtered out (causes API errors), safe properties kept
|
||||
// callerPolicy and availableInMCP now whitelisted (n8n 1.121+)
|
||||
expect(cleaned.settings).toEqual({
|
||||
executionOrder: 'v1',
|
||||
callerPolicy: 'workflowsFromSameOwner',
|
||||
availableInMCP: true,
|
||||
errorWorkflow: 'N2O2nZy3aUiBRGFN'
|
||||
});
|
||||
expect(cleaned.settings).not.toHaveProperty('callerPolicy');
|
||||
});
|
||||
|
||||
it('should filter all settings properties correctly (Issue #248 - API design)', () => {
|
||||
it('should preserve all whitelisted settings properties including callerPolicy (Issue #248 - updated for n8n 1.121)', () => {
|
||||
const workflow = {
|
||||
name: 'Test Workflow',
|
||||
nodes: [],
|
||||
@@ -455,14 +457,14 @@ describe('n8n-validation', () => {
|
||||
saveExecutionProgress: false,
|
||||
executionTimeout: 300,
|
||||
errorWorkflow: 'error-workflow-id',
|
||||
callerPolicy: 'workflowsFromAList' as const, // Filtered out (not in OpenAPI spec)
|
||||
callerPolicy: 'workflowsFromAList' as const, // Now whitelisted (n8n 1.121+)
|
||||
availableInMCP: false, // New in n8n 1.121
|
||||
},
|
||||
} as any;
|
||||
|
||||
const cleaned = cleanWorkflowForUpdate(workflow);
|
||||
|
||||
// Safe properties kept, unsafe properties filtered out
|
||||
// See: https://community.n8n.io/t/api-workflow-update-endpoint-doesnt-support-setting-callerpolicy/161916
|
||||
// All whitelisted properties kept including callerPolicy and availableInMCP
|
||||
expect(cleaned.settings).toEqual({
|
||||
executionOrder: 'v0',
|
||||
timezone: 'UTC',
|
||||
@@ -471,9 +473,10 @@ describe('n8n-validation', () => {
|
||||
saveManualExecutions: false,
|
||||
saveExecutionProgress: false,
|
||||
executionTimeout: 300,
|
||||
errorWorkflow: 'error-workflow-id'
|
||||
errorWorkflow: 'error-workflow-id',
|
||||
callerPolicy: 'workflowsFromAList',
|
||||
availableInMCP: false
|
||||
});
|
||||
expect(cleaned.settings).not.toHaveProperty('callerPolicy');
|
||||
});
|
||||
|
||||
it('should handle workflows without settings gracefully', () => {
|
||||
@@ -494,7 +497,6 @@ describe('n8n-validation', () => {
|
||||
nodes: [],
|
||||
connections: {},
|
||||
settings: {
|
||||
callerPolicy: 'workflowsFromSameOwner' as const, // Filtered out
|
||||
timeSavedPerExecution: 5, // Filtered out (UI-only)
|
||||
someOtherProperty: 'value', // Filtered out
|
||||
},
|
||||
@@ -514,19 +516,19 @@ describe('n8n-validation', () => {
|
||||
connections: {},
|
||||
settings: {
|
||||
executionOrder: 'v1' as const, // Whitelisted
|
||||
callerPolicy: 'workflowsFromSameOwner' as const, // Filtered out
|
||||
callerPolicy: 'workflowsFromSameOwner' as const, // Now whitelisted (n8n 1.121+)
|
||||
timezone: 'America/New_York', // Whitelisted
|
||||
someOtherProperty: 'value', // Filtered out
|
||||
},
|
||||
} as any;
|
||||
|
||||
const cleaned = cleanWorkflowForUpdate(workflow);
|
||||
// Should keep only whitelisted properties
|
||||
// Should keep only whitelisted properties (callerPolicy now whitelisted)
|
||||
expect(cleaned.settings).toEqual({
|
||||
executionOrder: 'v1',
|
||||
callerPolicy: 'workflowsFromSameOwner',
|
||||
timezone: 'America/New_York'
|
||||
});
|
||||
expect(cleaned.settings).not.toHaveProperty('callerPolicy');
|
||||
expect(cleaned.settings).not.toHaveProperty('someOtherProperty');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user