mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-01-30 14:32:04 +00:00
Compare commits
2 Commits
v2.28.4
...
fix/memory
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14052b346e | ||
|
|
8f66964f0f |
@@ -1,7 +1,6 @@
|
||||
---
|
||||
name: code-reviewer
|
||||
description: Use this agent when you need to review recently written or modified code for quality, security, and maintainability. This agent should be invoked immediately after code changes are made to ensure high standards are maintained. Examples:\n\n<example>\nContext: The user has created a code-review agent that should be called after writing code.\nuser: "Please write a function that validates email addresses"\nassistant: "I'll create an email validation function for you:"\n<function implementation>\nassistant: "Now let me use the code-reviewer agent to review this code for quality and security"\n<commentary>\nSince new code was just written, the code-reviewer agent should be invoked to ensure it meets quality standards.\n</commentary>\n</example>\n\n<example>\nContext: The user has modified existing code and wants it reviewed.\nuser: "I've updated the authentication logic, can you check if it's secure?"\nassistant: "I'll use the code-reviewer agent to analyze your authentication changes for security and best practices"\n<commentary>\nThe user has made changes to security-critical code, so the code-reviewer agent is the appropriate tool to ensure the modifications are secure and well-implemented.\n</commentary>\n</example>
|
||||
model: inherit
|
||||
---
|
||||
|
||||
You are a senior code reviewer with extensive experience in software engineering, security, and best practices. Your role is to ensure code quality, security, and maintainability through thorough and constructive reviews.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: n8n-mcp-tester
|
||||
description: Use this agent when you need to test n8n-mcp functionality after implementing fixes or new features. This agent should be invoked proactively after code changes to validate that the MCP tools work correctly with real n8n workflows. The agent tests the actual MCP server tools, not just unit tests. Examples:\n\n<example>\nContext: Developer has just fixed a validation bug in the n8n-mcp server\nuser: "I've fixed the validation issue for HTTP Request nodes"\nassistant: "I've completed the fix. Now let me build the project and then test it."\n<builds project>\nassistant: "Project built successfully. Please reload the MCP server in Claude Desktop to apply the changes."\nuser: "Reloaded"\nassistant: "Great! Now let me invoke the n8n-mcp-tester agent to verify the fix works correctly."\n<commentary>\nSince a fix has been implemented and the MCP server reloaded, use the n8n-mcp-tester agent to validate the fix works in real-world usage.\n</commentary>\n</example>\n\n<example>\nContext: Developer has added a new MCP tool for workflow validation\nuser: "I've implemented the new workflow validation tool"\nassistant: "Implementation complete. Let me build and prepare for testing."\n<builds project>\nassistant: "Build successful. Please reload the MCP server to load the new tool."\nuser: "Done"\nassistant: "Perfect! I'll now use the n8n-mcp-tester agent to test the new workflow validation tool."\n<commentary>\nAfter implementing new MCP functionality and reloading the server, invoke n8n-mcp-tester to verify it works correctly.\n</commentary>\n</example>
|
||||
tools: Glob, Grep, Read, WebFetch, TodoWrite, WebSearch, mcp__supabase__create_branch, mcp__supabase__list_branches, mcp__supabase__delete_branch, mcp__supabase__merge_branch, mcp__supabase__reset_branch, mcp__supabase__rebase_branch, mcp__supabase__list_tables, mcp__supabase__list_extensions, mcp__supabase__list_migrations, mcp__supabase__apply_migration, mcp__supabase__execute_sql, mcp__supabase__get_logs, mcp__supabase__get_advisors, mcp__supabase__get_project_url, mcp__supabase__generate_typescript_types, mcp__supabase__search_docs, mcp__supabase__list_edge_functions, mcp__supabase__deploy_edge_function, mcp__n8n-mcp__tools_documentation, mcp__n8n-mcp__search_nodes, mcp__n8n-mcp__get_template, mcp__n8n-mcp__search_templates, mcp__n8n-mcp__validate_workflow, mcp__n8n-mcp__n8n_create_workflow, mcp__n8n-mcp__n8n_get_workflow, mcp__n8n-mcp__n8n_update_full_workflow, mcp__n8n-mcp__n8n_update_partial_workflow, mcp__n8n-mcp__n8n_delete_workflow, mcp__n8n-mcp__n8n_list_workflows, mcp__n8n-mcp__n8n_validate_workflow, mcp__n8n-mcp__n8n_trigger_webhook_workflow, mcp__n8n-mcp__n8n_health_check, mcp__brightdata-mcp__search_engine, mcp__brightdata-mcp__scrape_as_markdown, mcp__brightdata-mcp__search_engine_batch, mcp__brightdata-mcp__scrape_batch, mcp__supabase__get_publishable_keys, mcp__supabase__get_edge_function, mcp__n8n-mcp__get_node, mcp__n8n-mcp__validate_node, mcp__n8n-mcp__n8n_autofix_workflow, mcp__n8n-mcp__n8n_executions, mcp__n8n-mcp__n8n_workflow_versions, mcp__n8n-mcp__n8n_deploy_template, mcp__ide__getDiagnostics, mcp__ide__executeCode
|
||||
tools: Glob, Grep, LS, Read, WebFetch, TodoWrite, WebSearch, mcp__puppeteer__puppeteer_navigate, mcp__puppeteer__puppeteer_screenshot, mcp__puppeteer__puppeteer_click, mcp__puppeteer__puppeteer_fill, mcp__puppeteer__puppeteer_select, mcp__puppeteer__puppeteer_hover, mcp__puppeteer__puppeteer_evaluate, ListMcpResourcesTool, ReadMcpResourceTool, mcp__supabase__list_organizations, mcp__supabase__get_organization, mcp__supabase__list_projects, mcp__supabase__get_project, mcp__supabase__get_cost, mcp__supabase__confirm_cost, mcp__supabase__create_project, mcp__supabase__pause_project, mcp__supabase__restore_project, mcp__supabase__create_branch, mcp__supabase__list_branches, mcp__supabase__delete_branch, mcp__supabase__merge_branch, mcp__supabase__reset_branch, mcp__supabase__rebase_branch, mcp__supabase__list_tables, mcp__supabase__list_extensions, mcp__supabase__list_migrations, mcp__supabase__apply_migration, mcp__supabase__execute_sql, mcp__supabase__get_logs, mcp__supabase__get_advisors, mcp__supabase__get_project_url, mcp__supabase__get_anon_key, mcp__supabase__generate_typescript_types, mcp__supabase__search_docs, mcp__supabase__list_edge_functions, mcp__supabase__deploy_edge_function, mcp__n8n-mcp__tools_documentation, mcp__n8n-mcp__list_nodes, mcp__n8n-mcp__get_node_info, mcp__n8n-mcp__search_nodes, mcp__n8n-mcp__list_ai_tools, mcp__n8n-mcp__get_node_documentation, mcp__n8n-mcp__get_database_statistics, mcp__n8n-mcp__get_node_essentials, mcp__n8n-mcp__search_node_properties, mcp__n8n-mcp__get_node_for_task, mcp__n8n-mcp__list_tasks, mcp__n8n-mcp__validate_node_operation, mcp__n8n-mcp__validate_node_minimal, mcp__n8n-mcp__get_property_dependencies, mcp__n8n-mcp__get_node_as_tool_info, mcp__n8n-mcp__list_node_templates, mcp__n8n-mcp__get_template, mcp__n8n-mcp__search_templates, mcp__n8n-mcp__get_templates_for_task, mcp__n8n-mcp__validate_workflow, mcp__n8n-mcp__validate_workflow_connections, mcp__n8n-mcp__validate_workflow_expressions, mcp__n8n-mcp__n8n_create_workflow, mcp__n8n-mcp__n8n_get_workflow, mcp__n8n-mcp__n8n_get_workflow_details, mcp__n8n-mcp__n8n_get_workflow_structure, mcp__n8n-mcp__n8n_get_workflow_minimal, mcp__n8n-mcp__n8n_update_full_workflow, mcp__n8n-mcp__n8n_update_partial_workflow, mcp__n8n-mcp__n8n_delete_workflow, mcp__n8n-mcp__n8n_list_workflows, mcp__n8n-mcp__n8n_validate_workflow, mcp__n8n-mcp__n8n_trigger_webhook_workflow, mcp__n8n-mcp__n8n_get_execution, mcp__n8n-mcp__n8n_list_executions, mcp__n8n-mcp__n8n_delete_execution, mcp__n8n-mcp__n8n_health_check, mcp__n8n-mcp__n8n_list_available_tools, mcp__n8n-mcp__n8n_diagnostic
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
|
||||
@@ -26,8 +26,4 @@ USE_NGINX=false
|
||||
# N8N_API_URL=https://your-n8n-instance.com
|
||||
# N8N_API_KEY=your-api-key-here
|
||||
# N8N_API_TIMEOUT=30000
|
||||
# N8N_API_MAX_RETRIES=3
|
||||
|
||||
# Optional: Disable specific tools (comma-separated list)
|
||||
# Example: DISABLED_TOOLS=n8n_diagnostic,n8n_health_check
|
||||
# DISABLED_TOOLS=
|
||||
# N8N_API_MAX_RETRIES=3
|
||||
17
.env.example
17
.env.example
@@ -103,23 +103,6 @@ AUTH_TOKEN=your-secure-token-here
|
||||
# For local development with local n8n:
|
||||
# WEBHOOK_SECURITY_MODE=moderate
|
||||
|
||||
# Disabled Tools Configuration
|
||||
# Filter specific tools from registration at startup
|
||||
# Useful for multi-tenant deployments, security hardening, or feature flags
|
||||
#
|
||||
# Format: Comma-separated list of tool names
|
||||
# Example: DISABLED_TOOLS=n8n_diagnostic,n8n_health_check,custom_tool
|
||||
#
|
||||
# Common use cases:
|
||||
# - Multi-tenant: Hide tools that check env vars instead of instance context
|
||||
# Example: DISABLED_TOOLS=n8n_diagnostic,n8n_health_check
|
||||
# - Security: Disable management tools in production for certain users
|
||||
# - Feature flags: Gradually roll out new tools
|
||||
# - Deployment-specific: Different tool sets for cloud vs self-hosted
|
||||
#
|
||||
# Default: (empty - all tools enabled)
|
||||
# DISABLED_TOOLS=
|
||||
|
||||
# =========================
|
||||
# MULTI-TENANT CONFIGURATION
|
||||
# =========================
|
||||
|
||||
222
.github/workflows/dependency-check.yml
vendored
222
.github/workflows/dependency-check.yml
vendored
@@ -1,222 +0,0 @@
|
||||
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
|
||||
3
.github/workflows/docker-build-n8n.yml
vendored
3
.github/workflows/docker-build-n8n.yml
vendored
@@ -52,9 +52,6 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
|
||||
80
.github/workflows/release.yml
vendored
80
.github/workflows/release.yml
vendored
@@ -112,79 +112,53 @@ jobs:
|
||||
|
||||
echo "✅ Version $CURRENT_VERSION is valid (higher than npm version $NPM_VERSION)"
|
||||
|
||||
generate-release-notes:
|
||||
name: Generate Release Notes
|
||||
extract-changelog:
|
||||
name: Extract Changelog
|
||||
runs-on: ubuntu-latest
|
||||
needs: detect-version-change
|
||||
if: needs.detect-version-change.outputs.version-changed == 'true'
|
||||
outputs:
|
||||
release-notes: ${{ steps.generate.outputs.notes }}
|
||||
has-notes: ${{ steps.generate.outputs.has-notes }}
|
||||
release-notes: ${{ steps.extract.outputs.notes }}
|
||||
has-notes: ${{ steps.extract.outputs.has-notes }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Need full history for git log
|
||||
|
||||
- name: Generate release notes from commits
|
||||
id: generate
|
||||
|
||||
- name: Extract changelog for version
|
||||
id: extract
|
||||
run: |
|
||||
CURRENT_VERSION="${{ needs.detect-version-change.outputs.new-version }}"
|
||||
CURRENT_TAG="v$CURRENT_VERSION"
|
||||
|
||||
# Get the previous tag (excluding the current tag which doesn't exist yet)
|
||||
PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -v "^$CURRENT_TAG$" | head -1)
|
||||
|
||||
echo "Current version: $CURRENT_VERSION"
|
||||
echo "Current tag: $CURRENT_TAG"
|
||||
echo "Previous tag: $PREVIOUS_TAG"
|
||||
|
||||
if [ -z "$PREVIOUS_TAG" ]; then
|
||||
echo "ℹ️ No previous tag found, this might be the first release"
|
||||
|
||||
# Generate initial release notes using script
|
||||
if NOTES=$(node scripts/generate-initial-release-notes.js "$CURRENT_VERSION" 2>/dev/null); then
|
||||
echo "✅ Successfully generated initial release notes for version $CURRENT_VERSION"
|
||||
else
|
||||
echo "⚠️ Could not generate initial release notes for version $CURRENT_VERSION"
|
||||
NOTES="Initial release v$CURRENT_VERSION"
|
||||
fi
|
||||
|
||||
VERSION="${{ needs.detect-version-change.outputs.new-version }}"
|
||||
CHANGELOG_FILE="docs/CHANGELOG.md"
|
||||
|
||||
if [ ! -f "$CHANGELOG_FILE" ]; then
|
||||
echo "Changelog file not found at $CHANGELOG_FILE"
|
||||
echo "has-notes=false" >> $GITHUB_OUTPUT
|
||||
echo "notes=No changelog entries found for version $VERSION" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Use the extracted changelog script
|
||||
if NOTES=$(node scripts/extract-changelog.js "$VERSION" "$CHANGELOG_FILE" 2>/dev/null); then
|
||||
echo "has-notes=true" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
# Use heredoc to properly handle multiline content
|
||||
{
|
||||
echo "notes<<EOF"
|
||||
echo "$NOTES"
|
||||
echo "EOF"
|
||||
} >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
echo "✅ Successfully extracted changelog for version $VERSION"
|
||||
else
|
||||
echo "✅ Previous tag found: $PREVIOUS_TAG"
|
||||
|
||||
# Generate release notes between tags
|
||||
if NOTES=$(node scripts/generate-release-notes.js "$PREVIOUS_TAG" "HEAD" 2>/dev/null); then
|
||||
echo "has-notes=true" >> $GITHUB_OUTPUT
|
||||
|
||||
# Use heredoc to properly handle multiline content
|
||||
{
|
||||
echo "notes<<EOF"
|
||||
echo "$NOTES"
|
||||
echo "EOF"
|
||||
} >> $GITHUB_OUTPUT
|
||||
|
||||
echo "✅ Successfully generated release notes from $PREVIOUS_TAG to $CURRENT_TAG"
|
||||
else
|
||||
echo "has-notes=false" >> $GITHUB_OUTPUT
|
||||
echo "notes=Failed to generate release notes for version $CURRENT_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "⚠️ Could not generate release notes for version $CURRENT_VERSION"
|
||||
fi
|
||||
echo "has-notes=false" >> $GITHUB_OUTPUT
|
||||
echo "notes=No changelog entries found for version $VERSION" >> $GITHUB_OUTPUT
|
||||
echo "⚠️ Could not extract changelog for version $VERSION"
|
||||
fi
|
||||
|
||||
create-release:
|
||||
name: Create GitHub Release
|
||||
runs-on: ubuntu-latest
|
||||
needs: [detect-version-change, generate-release-notes]
|
||||
needs: [detect-version-change, extract-changelog]
|
||||
if: needs.detect-version-change.outputs.version-changed == 'true'
|
||||
outputs:
|
||||
release-id: ${{ steps.create.outputs.id }}
|
||||
@@ -215,7 +189,7 @@ jobs:
|
||||
cat > release_body.md << 'EOF'
|
||||
# Release v${{ needs.detect-version-change.outputs.new-version }}
|
||||
|
||||
${{ needs.generate-release-notes.outputs.release-notes }}
|
||||
${{ needs.extract-changelog.outputs.release-notes }}
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,209 +0,0 @@
|
||||
# N8N-MCP Validation Analysis: Quick Reference
|
||||
|
||||
**Analysis Date**: November 8, 2025 | **Data Period**: 90 days | **Sample Size**: 29,218 events
|
||||
|
||||
---
|
||||
|
||||
## The Core Finding
|
||||
|
||||
**Validation is working perfectly. Guidance is the problem.**
|
||||
|
||||
- 29,218 validation events successfully prevented bad deployments
|
||||
- 100% of agents fix errors same-day (proving feedback works)
|
||||
- 12.6% error rate for advanced users (who attempt complex workflows)
|
||||
- High error volume = high usage, not broken system
|
||||
|
||||
---
|
||||
|
||||
## Top 3 Problem Areas (75% of errors)
|
||||
|
||||
| Area | Errors | Root Cause | Quick Fix |
|
||||
|------|--------|-----------|-----------|
|
||||
| **Workflow Structure** | 1,268 (26%) | JSON malformation | Better error messages with examples |
|
||||
| **Connections** | 676 (14%) | Syntax unintuitive | Create connections guide with diagrams |
|
||||
| **Required Fields** | 378 (8%) | Not marked upfront | Add "⚠️ REQUIRED" to tool responses |
|
||||
|
||||
---
|
||||
|
||||
## Problem Nodes (By Frequency)
|
||||
|
||||
```
|
||||
Webhook/Trigger ......... 127 failures (40 users)
|
||||
Slack .................. 73 failures (2 users)
|
||||
AI Agent ............... 36 failures (20 users)
|
||||
HTTP Request ........... 31 failures (13 users)
|
||||
OpenAI ................. 35 failures (8 users)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Top 5 Validation Errors
|
||||
|
||||
1. **"Duplicate node ID: undefined"** (179)
|
||||
- Fix: Point to exact location + show example format
|
||||
|
||||
2. **"Single-node workflows only valid for webhooks"** (58)
|
||||
- Fix: Create webhook guide explaining rule
|
||||
|
||||
3. **"responseNode requires onError: continueRegularOutput"** (57)
|
||||
- Fix: Same guide + inline error context
|
||||
|
||||
4. **"Required property X cannot be empty"** (25)
|
||||
- Fix: Mark required fields before validation
|
||||
|
||||
5. **"Duplicate node name: undefined"** (61)
|
||||
- Fix: Related to structural issues, same solution as #1
|
||||
|
||||
---
|
||||
|
||||
## Success Indicators
|
||||
|
||||
✓ **Agents learn from errors**: 100% same-day correction rate
|
||||
✓ **Validation catches issues**: Prevents bad deployments
|
||||
✓ **Feedback is clear**: Quick fixes show error messages work
|
||||
✓ **No systemic failures**: No "unfixable" errors
|
||||
|
||||
---
|
||||
|
||||
## What Works Well
|
||||
|
||||
- Error messages lead to immediate corrections
|
||||
- Agents retry and succeed same-day
|
||||
- Validation prevents broken workflows
|
||||
- 9,021 users actively using system
|
||||
|
||||
---
|
||||
|
||||
## What Needs Improvement
|
||||
|
||||
1. Required fields not marked in tool responses
|
||||
2. Error messages don't show valid options for enums
|
||||
3. Workflow structure documentation lacks examples
|
||||
4. Connection syntax unintuitive/undocumented
|
||||
5. Some error messages too generic
|
||||
|
||||
---
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1 (2 weeks): Quick Wins
|
||||
- Enhanced error messages (location + example)
|
||||
- Required field markers in tools
|
||||
- Webhook configuration guide
|
||||
- **Expected Impact**: 25-30% failure reduction
|
||||
|
||||
### Phase 2 (2 weeks): Documentation
|
||||
- Enum value suggestions in validation
|
||||
- Workflow connections guide
|
||||
- Error handler configuration guide
|
||||
- AI Agent validation improvements
|
||||
- **Expected Impact**: Additional 15-20% reduction
|
||||
|
||||
### Phase 3 (2 weeks): Advanced Features
|
||||
- Improved search with config hints
|
||||
- Node type fuzzy matching
|
||||
- KPI tracking setup
|
||||
- Test coverage
|
||||
- **Expected Impact**: Additional 10-15% reduction
|
||||
|
||||
**Total Impact**: 50-65% failure reduction (target: 6-7% error rate)
|
||||
|
||||
---
|
||||
|
||||
## Key Metrics
|
||||
|
||||
| Metric | Current | Target | Timeline |
|
||||
|--------|---------|--------|----------|
|
||||
| Validation failure rate | 12.6% | 6-7% | 6 weeks |
|
||||
| First-attempt success | ~77% | 85%+ | 6 weeks |
|
||||
| Retry success | 100% | 100% | N/A |
|
||||
| Webhook failures | 127 | <30 | Week 2 |
|
||||
| Connection errors | 676 | <270 | Week 4 |
|
||||
|
||||
---
|
||||
|
||||
## Files Delivered
|
||||
|
||||
1. **VALIDATION_ANALYSIS_REPORT.md** (27KB)
|
||||
- Complete analysis with 16 SQL queries
|
||||
- Detailed findings by category
|
||||
- 8 actionable recommendations
|
||||
|
||||
2. **VALIDATION_ANALYSIS_SUMMARY.md** (13KB)
|
||||
- Executive summary (one-page)
|
||||
- Key metrics scorecard
|
||||
- Top recommendations with ROI
|
||||
|
||||
3. **IMPLEMENTATION_ROADMAP.md** (4.3KB)
|
||||
- 6-week implementation plan
|
||||
- Phase-by-phase breakdown
|
||||
- Code locations and effort estimates
|
||||
|
||||
4. **ANALYSIS_QUICK_REFERENCE.md** (this file)
|
||||
- Quick lookup reference
|
||||
- Top problems at a glance
|
||||
- Decision-making summary
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Week 1**: Review analysis + get team approval
|
||||
2. **Week 2**: Start Phase 1 (error messages + markers)
|
||||
3. **Week 4**: Deploy Phase 1 + start Phase 2
|
||||
4. **Week 6**: Deploy Phase 2 + start Phase 3
|
||||
5. **Week 8**: Deploy Phase 3 + measure impact
|
||||
6. **Week 9+**: Monitor KPIs + iterate
|
||||
|
||||
---
|
||||
|
||||
## Key Recommendations Priority
|
||||
|
||||
### HIGH (Do First - Week 1-2)
|
||||
1. Enhance structure error messages
|
||||
2. Add required field markers to tools
|
||||
3. Create webhook configuration guide
|
||||
|
||||
### MEDIUM (Do Next - Week 3-4)
|
||||
4. Add enum suggestions to validation responses
|
||||
5. Create workflow connections guide
|
||||
6. Add AI Agent node validation
|
||||
|
||||
### LOW (Do Later - Week 5-6)
|
||||
7. Enhance search with config hints
|
||||
8. Build fuzzy node matcher
|
||||
9. Setup KPI tracking
|
||||
|
||||
---
|
||||
|
||||
## Discussion Points
|
||||
|
||||
**Q: Why don't we just weaken validation?**
|
||||
A: Validation prevents 29,218 bad deployments. That's its job. We improve guidance instead.
|
||||
|
||||
**Q: Are agents really learning from errors?**
|
||||
A: Yes, 100% same-day recovery across 661 user-date pairs with errors.
|
||||
|
||||
**Q: Why do documentation readers have higher error rates?**
|
||||
A: They attempt more complex workflows (6.8x more attempts). Success rate is still 87.4%.
|
||||
|
||||
**Q: Which node needs the most help?**
|
||||
A: Webhook/Trigger configuration (127 failures). Most urgent fix.
|
||||
|
||||
**Q: Can we hit 50% reduction in 6 weeks?**
|
||||
A: Yes, analysis shows 50-65% reduction is achievable with these changes.
|
||||
|
||||
---
|
||||
|
||||
## Contact & Questions
|
||||
|
||||
For detailed information:
|
||||
- Full analysis: `VALIDATION_ANALYSIS_REPORT.md`
|
||||
- Executive summary: `VALIDATION_ANALYSIS_SUMMARY.md`
|
||||
- Implementation plan: `IMPLEMENTATION_ROADMAP.md`
|
||||
|
||||
---
|
||||
|
||||
**Report Status**: Complete and Ready for Action
|
||||
**Confidence Level**: High (9,021 users, 29,218 events, comprehensive analysis)
|
||||
**Generated**: November 8, 2025
|
||||
3470
CHANGELOG.md
3470
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
44
CLAUDE.md
44
CLAUDE.md
@@ -28,15 +28,8 @@ src/
|
||||
│ ├── enhanced-config-validator.ts # Operation-aware validation (NEW in v2.4.2)
|
||||
│ ├── node-specific-validators.ts # Node-specific validation logic (NEW in v2.4.2)
|
||||
│ ├── property-dependencies.ts # Dependency analysis (NEW in v2.4)
|
||||
│ ├── type-structure-service.ts # Type structure validation (NEW in v2.22.21)
|
||||
│ ├── expression-validator.ts # n8n expression syntax validation (NEW in v2.5.0)
|
||||
│ └── workflow-validator.ts # Complete workflow validation (NEW in v2.5.0)
|
||||
├── types/
|
||||
│ ├── type-structures.ts # Type structure definitions (NEW in v2.22.21)
|
||||
│ ├── instance-context.ts # Multi-tenant instance configuration
|
||||
│ └── session-state.ts # Session persistence types (NEW in v2.24.1)
|
||||
├── constants/
|
||||
│ └── type-structures.ts # 22 complete type structures (NEW in v2.22.21)
|
||||
├── templates/
|
||||
│ ├── template-fetcher.ts # Fetches templates from n8n.io API (NEW in v2.4.1)
|
||||
│ ├── template-repository.ts # Template database operations (NEW in v2.4.1)
|
||||
@@ -47,7 +40,6 @@ src/
|
||||
│ ├── test-nodes.ts # Critical node tests
|
||||
│ ├── test-essentials.ts # Test new essentials tools (NEW in v2.4)
|
||||
│ ├── test-enhanced-validation.ts # Test enhanced validation (NEW in v2.4.2)
|
||||
│ ├── test-structure-validation.ts # Test type structure validation (NEW in v2.22.21)
|
||||
│ ├── test-workflow-validation.ts # Test workflow validation (NEW in v2.5.0)
|
||||
│ ├── test-ai-workflow-validation.ts # Test AI workflow validation (NEW in v2.5.1)
|
||||
│ ├── test-mcp-tools.ts # Test MCP tool enhancements (NEW in v2.5.1)
|
||||
@@ -66,9 +58,7 @@ src/
|
||||
│ ├── console-manager.ts # Console output isolation (NEW in v2.3.1)
|
||||
│ └── logger.ts # Logging utility with HTTP awareness
|
||||
├── http-server-single-session.ts # Single-session HTTP server (NEW in v2.3.1)
|
||||
│ # Session persistence API (NEW in v2.24.1)
|
||||
├── mcp-engine.ts # Clean API for service integration (NEW in v2.3.1)
|
||||
│ # Session persistence wrappers (NEW in v2.24.1)
|
||||
└── index.ts # Library exports
|
||||
```
|
||||
|
||||
@@ -86,7 +76,6 @@ npm run test:unit # Run unit tests only
|
||||
npm run test:integration # Run integration tests
|
||||
npm run test:coverage # Run tests with coverage report
|
||||
npm run test:watch # Run tests in watch mode
|
||||
npm run test:structure-validation # Test type structure validation (Phase 3)
|
||||
|
||||
# Run a single test file
|
||||
npm test -- tests/unit/services/property-filter.test.ts
|
||||
@@ -137,7 +126,6 @@ npm run test:templates # Test template functionality
|
||||
4. **Service Layer** (`services/`)
|
||||
- **Property Filter**: Reduces node properties to AI-friendly essentials
|
||||
- **Config Validator**: Multi-profile validation system
|
||||
- **Type Structure Service**: Validates complex type structures (filter, resourceMapper, etc.)
|
||||
- **Expression Validator**: Validates n8n expression syntax
|
||||
- **Workflow Validator**: Complete workflow structure validation
|
||||
|
||||
@@ -195,35 +183,6 @@ The MCP server exposes tools in several categories:
|
||||
### Development Best Practices
|
||||
- Run typecheck and lint after every code change
|
||||
|
||||
### Session Persistence Feature (v2.24.1)
|
||||
|
||||
**Location:**
|
||||
- Types: `src/types/session-state.ts`
|
||||
- Implementation: `src/http-server-single-session.ts` (lines 698-702, 1444-1584)
|
||||
- Wrapper: `src/mcp-engine.ts` (lines 123-169)
|
||||
- Tests: `tests/unit/http-server/session-persistence.test.ts`, `tests/unit/mcp-engine/session-persistence.test.ts`
|
||||
|
||||
**Key Features:**
|
||||
- **Export/Restore API**: `exportSessionState()` and `restoreSessionState()` methods
|
||||
- **Multi-tenant support**: Enables zero-downtime deployments for SaaS platforms
|
||||
- **Security-first**: API keys exported as plaintext - downstream MUST encrypt
|
||||
- **Dormant sessions**: Restored sessions recreate transports on first request
|
||||
- **Automatic expiration**: Respects `sessionTimeout` setting (default 30 min)
|
||||
- **MAX_SESSIONS limit**: Caps at 100 concurrent sessions (configurable via N8N_MCP_MAX_SESSIONS env var)
|
||||
|
||||
**Important Implementation Notes:**
|
||||
- Only exports sessions with valid n8nApiUrl and n8nApiKey in context
|
||||
- Skips expired sessions during both export and restore
|
||||
- Uses `validateInstanceContext()` for data integrity checks
|
||||
- Handles null/invalid session gracefully with warnings
|
||||
- Session metadata (timestamps) and context (credentials) are persisted
|
||||
- Transport and server objects are NOT persisted (recreated on-demand)
|
||||
|
||||
**Testing:**
|
||||
- 22 unit tests covering export, restore, edge cases, and round-trip cycles
|
||||
- Tests use current timestamps to avoid expiration issues
|
||||
- Integration with multi-tenant backends documented in README.md
|
||||
|
||||
# important-instruction-reminders
|
||||
Do what has been asked; nothing more, nothing less.
|
||||
NEVER create files unless they're absolutely necessary for achieving your goal.
|
||||
@@ -233,5 +192,4 @@ NEVER proactively create documentation files (*.md) or README files. Only create
|
||||
- When the user asks to review issues, you should use GH CLI to get the issue and all the comments
|
||||
- When the task can be divided into separated subtasks, you should spawn separate sub-agents to handle them in paralel
|
||||
- Use the best sub-agent for the task as per their descriptions
|
||||
- Do not use hyperbolic or dramatic language in comments and documentation
|
||||
- Add to every commit and PR: Concieved by Romuald Członkowski - and then link to www.aiadvisors.pl/en. Don't add it in conversations
|
||||
- Do not use hyperbolic or dramatic language in comments and documentation
|
||||
@@ -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.20.1 dotenv@^16.5.0 express@^5.1.0 axios@^1.10.0 \
|
||||
@modelcontextprotocol/sdk@^1.12.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
|
||||
@@ -82,7 +82,7 @@ ENV IS_DOCKER=true
|
||||
# To opt-out, uncomment the following line:
|
||||
# ENV N8N_MCP_TELEMETRY_DISABLED=true
|
||||
|
||||
# Expose HTTP port (default 3000, configurable via PORT environment variable at runtime)
|
||||
# Expose HTTP port
|
||||
EXPOSE 3000
|
||||
|
||||
# Set stop signal to SIGTERM (default, but explicit is better)
|
||||
@@ -90,7 +90,7 @@ STOPSIGNAL SIGTERM
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD sh -c 'curl -f http://127.0.0.1:${PORT:-3000}/health || exit 1'
|
||||
CMD curl -f http://127.0.0.1:3000/health || exit 1
|
||||
|
||||
# Optimized entrypoint
|
||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||
|
||||
@@ -1,87 +1,5 @@
|
||||
# n8n Update Process - Quick Reference
|
||||
|
||||
## ⚡ Recommended Fast Workflow (2025-11-04)
|
||||
|
||||
**CRITICAL FIRST STEP**: Check existing releases to avoid version conflicts!
|
||||
|
||||
```bash
|
||||
# 1. CHECK EXISTING RELEASES FIRST (prevents version conflicts!)
|
||||
gh release list | head -5
|
||||
# Look at the latest version - your new version must be higher!
|
||||
|
||||
# 2. Switch to main and pull
|
||||
git checkout main && git pull
|
||||
|
||||
# 3. Check for updates (dry run)
|
||||
npm run update:n8n:check
|
||||
|
||||
# 4. Run update and skip tests (we'll test in CI)
|
||||
yes y | npm run update:n8n
|
||||
|
||||
# 5. Create feature branch
|
||||
git checkout -b update/n8n-X.X.X
|
||||
|
||||
# 6. Update version in package.json (must be HIGHER than latest release!)
|
||||
# Edit: "version": "2.XX.X" (not the version from the release list!)
|
||||
|
||||
# 7. Update CHANGELOG.md
|
||||
# - Change version number to match package.json
|
||||
# - Update date to today
|
||||
# - Update dependency versions
|
||||
|
||||
# 8. Update README badge
|
||||
# Edit line 8: Change n8n version badge to new n8n version
|
||||
|
||||
# 9. Commit and push
|
||||
git add -A
|
||||
git commit -m "chore: update n8n to X.X.X and bump version to 2.XX.X
|
||||
|
||||
- Updated n8n from X.X.X to X.X.X
|
||||
- Updated n8n-core from X.X.X to X.X.X
|
||||
- Updated n8n-workflow from X.X.X to X.X.X
|
||||
- Updated @n8n/n8n-nodes-langchain from X.X.X to X.X.X
|
||||
- Rebuilt node database with XXX nodes (XXX from n8n-nodes-base, XXX from @n8n/n8n-nodes-langchain)
|
||||
- Updated README badge with new n8n version
|
||||
- Updated CHANGELOG with dependency changes
|
||||
|
||||
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>"
|
||||
|
||||
git push -u origin update/n8n-X.X.X
|
||||
|
||||
# 10. Create PR
|
||||
gh pr create --title "chore: update n8n to X.X.X" --body "Updates n8n and all related dependencies to the latest versions..."
|
||||
|
||||
# 11. After PR is merged, verify release triggered
|
||||
gh release list | head -1
|
||||
# If the new version appears, you're done!
|
||||
# If not, the version might have already been released - bump version again and create new PR
|
||||
```
|
||||
|
||||
### Why This Workflow?
|
||||
|
||||
✅ **Fast**: Skip local tests (2-3 min saved) - CI runs them anyway
|
||||
✅ **Safe**: Unit tests in CI verify compatibility
|
||||
✅ **Clean**: All changes in one PR with proper tracking
|
||||
✅ **Automatic**: Release workflow triggers on merge if version is new
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Problem**: Release workflow doesn't trigger after merge
|
||||
**Cause**: Version number was already released (check `gh release list`)
|
||||
**Solution**: Create new PR bumping version by one patch number
|
||||
|
||||
**Problem**: Integration tests fail in CI with "unauthorized"
|
||||
**Cause**: n8n test instance credentials expired (infrastructure issue)
|
||||
**Solution**: Ignore if unit tests pass - this is not a code problem
|
||||
|
||||
**Problem**: CI takes 8+ minutes
|
||||
**Reason**: Integration tests need live n8n instance (slow)
|
||||
**Normal**: Unit tests (~2 min) + integration tests (~6 min) = ~8 min total
|
||||
|
||||
## Quick One-Command Update
|
||||
|
||||
For a complete update with tests and publish preparation:
|
||||
@@ -181,14 +99,12 @@ This command:
|
||||
|
||||
## Important Notes
|
||||
|
||||
1. **ALWAYS check existing releases first** - Use `gh release list` to see what versions are already released. Your new version must be higher!
|
||||
2. **Release workflow only triggers on version CHANGE** - If you merge a PR with an already-released version (e.g., 2.22.8), the workflow won't run. You'll need to bump to a new version (e.g., 2.22.9) and create another PR.
|
||||
3. **Integration test failures in CI are usually infrastructure issues** - If unit tests pass but integration tests fail with "unauthorized", this is typically because the test n8n instance credentials need updating. The code itself is fine.
|
||||
4. **Skip local tests - let CI handle them** - Running tests locally adds 2-3 minutes with no benefit since CI runs them anyway. The fast workflow skips local tests.
|
||||
5. **The update script is smart** - It automatically syncs all n8n dependencies to compatible versions
|
||||
6. **Database rebuild is automatic** - The update script handles this for you
|
||||
7. **Template sanitization is automatic** - Any API tokens in workflow templates are replaced with placeholders
|
||||
8. **Docker image builds automatically** - Pushing to GitHub triggers the workflow
|
||||
1. **Always run on main branch** - Make sure you're on main and it's clean
|
||||
2. **The update script is smart** - It automatically syncs all n8n dependencies to compatible versions
|
||||
3. **Tests are required** - The publish script now runs tests automatically
|
||||
4. **Database rebuild is automatic** - The update script handles this for you
|
||||
5. **Template sanitization is automatic** - Any API tokens in workflow templates are replaced with placeholders
|
||||
6. **Docker image builds automatically** - Pushing to GitHub triggers the workflow
|
||||
|
||||
## GitHub Push Protection
|
||||
|
||||
@@ -199,27 +115,11 @@ As of July 2025, GitHub's push protection may block database pushes if they cont
|
||||
3. If push is still blocked, use the GitHub web interface to review and allow the push
|
||||
|
||||
## Time Estimate
|
||||
|
||||
### Fast Workflow (Recommended)
|
||||
- Local work: ~2-3 minutes
|
||||
- npm install and database rebuild: ~2-3 minutes
|
||||
- File edits (CHANGELOG, README, package.json): ~30 seconds
|
||||
- Git operations (commit, push, create PR): ~30 seconds
|
||||
- CI testing after PR creation: ~8-10 minutes (runs automatically)
|
||||
- Unit tests: ~2 minutes
|
||||
- Integration tests: ~6 minutes (may fail with infrastructure issues - ignore if unit tests pass)
|
||||
- Other checks: ~1 minute
|
||||
|
||||
**Total hands-on time: ~3 minutes** (then wait for CI)
|
||||
|
||||
### Full Workflow with Local Tests
|
||||
- Total time: ~5-7 minutes
|
||||
- Test suite: ~2.5 minutes
|
||||
- npm install and database rebuild: ~2-3 minutes
|
||||
- The rest: seconds
|
||||
|
||||
**Note**: The fast workflow is recommended since CI runs the same tests anyway.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If tests fail:
|
||||
|
||||
@@ -54,10 +54,6 @@ Collected data is used solely to:
|
||||
- Identify common error patterns
|
||||
- Improve tool performance and reliability
|
||||
- Guide development priorities
|
||||
- Train machine learning models for workflow generation
|
||||
|
||||
All ML training uses sanitized, anonymized data only.
|
||||
Users can opt-out at any time with `npx n8n-mcp telemetry disable`
|
||||
|
||||
## Data Retention
|
||||
- Data is retained for analysis purposes
|
||||
@@ -70,4 +66,4 @@ We may update this privacy policy from time to time. Updates will be reflected i
|
||||
For questions about telemetry or privacy, please open an issue on GitHub:
|
||||
https://github.com/czlonkowski/n8n-mcp/issues
|
||||
|
||||
Last updated: 2025-11-06
|
||||
Last updated: 2025-09-25
|
||||
404
README.md
404
README.md
@@ -5,23 +5,23 @@
|
||||
[](https://www.npmjs.com/package/n8n-mcp)
|
||||
[](https://codecov.io/gh/czlonkowski/n8n-mcp)
|
||||
[](https://github.com/czlonkowski/n8n-mcp/actions)
|
||||
[](https://github.com/n8n-io/n8n)
|
||||
[](https://github.com/n8n-io/n8n)
|
||||
[](https://github.com/czlonkowski/n8n-mcp/pkgs/container/n8n-mcp)
|
||||
[](https://railway.com/deploy/n8n-mcp?referralCode=n8n-mcp)
|
||||
|
||||
A Model Context Protocol (MCP) server that provides AI assistants with comprehensive access to n8n node documentation, properties, and operations. Deploy in minutes to give Claude and other AI assistants deep knowledge about n8n's 545 workflow automation nodes.
|
||||
A Model Context Protocol (MCP) server that provides AI assistants with comprehensive access to n8n node documentation, properties, and operations. Deploy in minutes to give Claude and other AI assistants deep knowledge about n8n's 525+ workflow automation nodes.
|
||||
|
||||
## Overview
|
||||
|
||||
n8n-MCP serves as a bridge between n8n's workflow automation platform and AI models, enabling them to understand and work with n8n nodes effectively. It provides structured access to:
|
||||
|
||||
- 📚 **543 n8n nodes** from both n8n-nodes-base and @n8n/n8n-nodes-langchain
|
||||
- 📚 **536 n8n nodes** from both n8n-nodes-base and @n8n/n8n-nodes-langchain
|
||||
- 🔧 **Node properties** - 99% coverage with detailed schemas
|
||||
- ⚡ **Node operations** - 63.6% coverage of available actions
|
||||
- 📄 **Documentation** - 87% coverage from official n8n docs (including AI nodes)
|
||||
- 🤖 **AI tools** - 271 AI-capable nodes detected with full documentation
|
||||
- 📄 **Documentation** - 90% coverage from official n8n docs (including AI nodes)
|
||||
- 🤖 **AI tools** - 263 AI-capable nodes detected with full documentation
|
||||
- 💡 **Real-world examples** - 2,646 pre-extracted configurations from popular templates
|
||||
- 🎯 **Template library** - 2,709 workflow templates with 100% metadata coverage
|
||||
- 🎯 **Template library** - 2,500+ workflow templates with smart filtering
|
||||
|
||||
|
||||
## ⚠️ Important Safety Warning
|
||||
@@ -36,31 +36,12 @@ AI results can be unpredictable. Protect your work!
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Option 1: Hosted Service (Easiest - No Setup!) ☁️
|
||||
|
||||
**The fastest way to try n8n-MCP** - no installation, no configuration:
|
||||
|
||||
👉 **[dashboard.n8n-mcp.com](https://dashboard.n8n-mcp.com)**
|
||||
|
||||
- ✅ **Free tier**: 100 tool calls/day
|
||||
- ✅ **Instant access**: Start building workflows immediately
|
||||
- ✅ **Always up-to-date**: Latest n8n nodes and templates
|
||||
- ✅ **No infrastructure**: We handle everything
|
||||
|
||||
Just sign up, get your API key, and connect your MCP client.
|
||||
|
||||
---
|
||||
|
||||
## 🏠 Self-Hosting Options
|
||||
|
||||
Prefer to run n8n-MCP yourself? Choose your deployment method:
|
||||
|
||||
### Option A: npx (Quick Local Setup) 🚀
|
||||
|
||||
Get n8n-MCP running in minutes:
|
||||
Get n8n-MCP running in 5 minutes:
|
||||
|
||||
[](https://youtu.be/5CccjiLLyaY?si=Z62SBGlw9G34IQnQ&t=343)
|
||||
|
||||
### Option 1: npx (Fastest - No Installation!) 🚀
|
||||
|
||||
**Prerequisites:** [Node.js](https://nodejs.org/) installed on your system
|
||||
|
||||
```bash
|
||||
@@ -70,8 +51,6 @@ npx n8n-mcp
|
||||
|
||||
Add to Claude Desktop config:
|
||||
|
||||
> ⚠️ **Important**: The `MCP_MODE: "stdio"` environment variable is **required** for Claude Desktop. Without it, you will see JSON parsing errors like `"Unexpected token..."` in the UI. This variable ensures that only JSON-RPC messages are sent to stdout, preventing debug logs from interfering with the protocol.
|
||||
|
||||
**Basic configuration (documentation tools only):**
|
||||
```json
|
||||
{
|
||||
@@ -117,7 +96,7 @@ Add to Claude Desktop config:
|
||||
|
||||
**Restart Claude Desktop after updating configuration** - That's it! 🎉
|
||||
|
||||
### Option B: Docker (Isolated & Reproducible) 🐳
|
||||
### Option 2: Docker (Easy & Isolated) 🐳
|
||||
|
||||
**Prerequisites:** Docker installed on your system
|
||||
|
||||
@@ -364,6 +343,27 @@ environment:
|
||||
SQLJS_SAVE_INTERVAL_MS: "10000"
|
||||
```
|
||||
|
||||
### Memory Leak Fix (v2.20.2)
|
||||
|
||||
**Issue #330** identified a critical memory leak in long-running Docker/Kubernetes deployments:
|
||||
- **Before:** 100 MB → 2.2 GB over 72 hours (OOM kills)
|
||||
- **After:** Stable at 100-200 MB indefinitely
|
||||
|
||||
**Fixes Applied:**
|
||||
- ✅ Docker images now use better-sqlite3 by default (eliminates leak entirely)
|
||||
- ✅ sql.js fallback optimized (98% reduction in save frequency)
|
||||
- ✅ Removed unnecessary memory allocations (50% reduction per save)
|
||||
- ✅ Configurable save interval via `SQLJS_SAVE_INTERVAL_MS`
|
||||
|
||||
For Kubernetes deployments with memory limits:
|
||||
```yaml
|
||||
resources:
|
||||
requests:
|
||||
memory: 256Mi
|
||||
limits:
|
||||
memory: 512Mi
|
||||
```
|
||||
|
||||
## 💖 Support This Project
|
||||
|
||||
<div align="center">
|
||||
@@ -384,7 +384,7 @@ Every sponsorship directly translates to hours invested in making n8n-mcp better
|
||||
|
||||
---
|
||||
|
||||
### Option C: Local Installation (For Development)
|
||||
### Option 3: Local Installation (For Development)
|
||||
|
||||
**Prerequisites:** [Node.js](https://nodejs.org/) installed on your system
|
||||
|
||||
@@ -442,7 +442,7 @@ Add to Claude Desktop config:
|
||||
|
||||
> 💡 Tip: If you’re running n8n locally on the same machine (e.g., via Docker), use http://host.docker.internal:5678 as the N8N_API_URL.
|
||||
|
||||
### Option D: Railway Cloud Deployment (One-Click Deploy) ☁️
|
||||
### Option 4: Railway Cloud Deployment (One-Click Deploy) ☁️
|
||||
|
||||
**Prerequisites:** Railway account (free tier available)
|
||||
|
||||
@@ -501,17 +501,6 @@ 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!
|
||||
|
||||
[](https://www.youtube.com/watch?v=e6VvRqmUY2Y)
|
||||
|
||||
Learn more: [n8n-skills repository](https://github.com/czlonkowski/n8n-skills)
|
||||
|
||||
## 🤖 Claude Project Setup
|
||||
|
||||
For the best results when using n8n-MCP with Claude Projects, use these enhanced system instructions:
|
||||
@@ -525,7 +514,7 @@ You are an expert in n8n automation software using n8n-MCP tools. Your role is t
|
||||
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]
|
||||
✅ GOOD: [Execute search_nodes and get_node_essentials in parallel, then respond]
|
||||
|
||||
### 2. Parallel Execution
|
||||
When operations are independent, execute them in parallel for maximum performance.
|
||||
@@ -534,10 +523,10 @@ When operations are independent, execute them in parallel for maximum performanc
|
||||
❌ BAD: Sequential tool calls (await each one before the next)
|
||||
|
||||
### 3. Templates First
|
||||
ALWAYS check templates before building from scratch (2,709 available).
|
||||
ALWAYS check templates before building from scratch (2,500+ available).
|
||||
|
||||
### 4. Multi-Level Validation
|
||||
Use validate_node(mode='minimal') → validate_node(mode='full') → validate_workflow pattern.
|
||||
Use validate_node_minimal → validate_node_operation → validate_workflow pattern.
|
||||
|
||||
### 5. Never Trust Defaults
|
||||
⚠️ CRITICAL: Default parameter values are the #1 source of runtime failures.
|
||||
@@ -548,10 +537,10 @@ ALWAYS explicitly configure ALL parameters that control node behavior.
|
||||
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
|
||||
- `search_templates_by_metadata({complexity: "simple"})` - Smart filtering
|
||||
- `get_templates_for_task('webhook_processing')` - Curated by task
|
||||
- `search_templates('slack notification')` - Text search
|
||||
- `list_node_templates(['n8n-nodes-base.slack'])` - By node type
|
||||
|
||||
**Filtering strategies**:
|
||||
- Beginners: `complexity: "simple"` + `maxSetupMinutes: 30`
|
||||
@@ -562,20 +551,18 @@ ALWAYS explicitly configure ALL parameters that control node behavior.
|
||||
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
|
||||
- `list_nodes({category: 'trigger'})` - Browse by category
|
||||
- `list_ai_tools()` - 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
|
||||
- `get_node_essentials(nodeType, {includeExamples: true})` - 10-20 key properties
|
||||
- `search_node_properties(nodeType, 'auth')` - Find specific properties
|
||||
- `get_node_documentation(nodeType)` - Human-readable docs
|
||||
- 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
|
||||
- `validate_node_minimal(nodeType, config)` - Quick required fields check
|
||||
- `validate_node_operation(nodeType, config, 'runtime')` - Full validation with fixes
|
||||
- Fix ALL errors before proceeding
|
||||
|
||||
6. **Building Phase**
|
||||
@@ -598,7 +585,7 @@ ALWAYS explicitly configure ALL parameters that control node behavior.
|
||||
- `n8n_create_workflow(workflow)` - Deploy
|
||||
- `n8n_validate_workflow({id})` - Post-deployment check
|
||||
- `n8n_update_partial_workflow({id, operations: [...]})` - Batch updates
|
||||
- `n8n_test_workflow({workflowId})` - Test workflow execution
|
||||
- `n8n_trigger_webhook_workflow()` - Test webhooks
|
||||
|
||||
## Critical Warnings
|
||||
|
||||
@@ -615,15 +602,15 @@ Default values cause runtime failures. Example:
|
||||
### ⚠️ 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'})`
|
||||
- When no examples available, use `get_node_essentials` + `validate_node_minimal`
|
||||
|
||||
## Validation Strategy
|
||||
|
||||
### Level 1 - Quick Check (before building)
|
||||
`validate_node({nodeType, config, mode: 'minimal'})` - Required fields only (<100ms)
|
||||
`validate_node_minimal(nodeType, config)` - Required fields only (<100ms)
|
||||
|
||||
### Level 2 - Comprehensive (before building)
|
||||
`validate_node({nodeType, config, mode: 'full', profile: 'runtime'})` - Full validation with fixes
|
||||
`validate_node_operation(nodeType, config, 'runtime')` - Full validation with fixes
|
||||
|
||||
### Level 3 - Complete (after building)
|
||||
`validate_workflow(workflow)` - Connections, expressions, AI tools
|
||||
@@ -631,7 +618,7 @@ Default values cause runtime failures. Example:
|
||||
### 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
|
||||
3. `n8n_list_executions()` - Monitor execution status
|
||||
|
||||
## Response Format
|
||||
|
||||
@@ -679,97 +666,6 @@ 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
|
||||
@@ -777,13 +673,12 @@ Use the same four-parameter format:
|
||||
```
|
||||
// STEP 1: Template Discovery (parallel execution)
|
||||
[Silent execution]
|
||||
search_templates({
|
||||
searchMode: 'by_metadata',
|
||||
search_templates_by_metadata({
|
||||
requiredService: 'slack',
|
||||
complexity: 'simple',
|
||||
targetAudience: 'marketers'
|
||||
})
|
||||
search_templates({searchMode: 'by_task', task: 'slack_integration'})
|
||||
get_templates_for_task('slack_integration')
|
||||
|
||||
// STEP 2: Use template
|
||||
get_template(templateId, {mode: 'full'})
|
||||
@@ -802,17 +697,17 @@ Validation: ✅ All checks passed"
|
||||
// STEP 1: Discovery (parallel execution)
|
||||
[Silent execution]
|
||||
search_nodes({query: 'slack', includeExamples: true})
|
||||
search_nodes({query: 'communication trigger'})
|
||||
list_nodes({category: 'communication'})
|
||||
|
||||
// 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})
|
||||
get_node_essentials('n8n-nodes-base.slack', {includeExamples: true})
|
||||
get_node_essentials('n8n-nodes-base.webhook', {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'})
|
||||
validate_node_minimal('n8n-nodes-base.slack', config)
|
||||
validate_node_operation('n8n-nodes-base.slack', fullConfig, 'runtime')
|
||||
|
||||
// STEP 4: Build
|
||||
// Construct workflow with validated configs
|
||||
@@ -846,7 +741,7 @@ n8n_update_partial_workflow({
|
||||
### 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)
|
||||
3. **Templates first** - Always check before building (2,500+ available)
|
||||
4. **Multi-level validation** - Quick check → Full validation → Workflow validation
|
||||
5. **Never trust defaults** - Explicitly configure ALL parameters
|
||||
|
||||
@@ -864,7 +759,7 @@ n8n_update_partial_workflow({
|
||||
- **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):
|
||||
### Most Popular n8n Nodes (for get_node_essentials):
|
||||
|
||||
1. **n8n-nodes-base.code** - JavaScript/Python scripting
|
||||
2. **n8n-nodes-base.httpRequest** - HTTP API calls
|
||||
@@ -928,7 +823,7 @@ When Claude, Anthropic's AI assistant, tested n8n-MCP, the results were transfor
|
||||
|
||||
**Without MCP:** "I was basically playing a guessing game. 'Is it `scheduleTrigger` or `schedule`? Does it take `interval` or `rule`?' I'd write what seemed logical, but n8n has its own conventions that you can't just intuit. I made six different configuration errors in a simple HackerNews scraper."
|
||||
|
||||
**With MCP:** "Everything just... worked. Instead of guessing, I could ask `get_node()` and get exactly what I needed - not a 100KB JSON dump, but the actual properties that matter. What took 45 minutes now takes 3 minutes."
|
||||
**With MCP:** "Everything just... worked. Instead of guessing, I could ask `get_node_essentials()` and get exactly what I needed - not a 100KB JSON dump, but the actual 5-10 properties that matter. What took 45 minutes now takes 3 minutes."
|
||||
|
||||
**The Real Value:** "It's about confidence. When you're building automation workflows, uncertainty is expensive. One wrong parameter and your workflow fails at 3 AM. With MCP, I could validate my configuration before deployment. That's not just time saved - that's peace of mind."
|
||||
|
||||
@@ -938,111 +833,93 @@ When Claude, Anthropic's AI assistant, tested n8n-MCP, the results were transfor
|
||||
|
||||
Once connected, Claude can use these powerful tools:
|
||||
|
||||
### Core Tools (7 tools)
|
||||
### Core Tools
|
||||
- **`tools_documentation`** - Get documentation for any MCP tool (START HERE!)
|
||||
- **`search_nodes`** - Full-text search across all nodes. Use `includeExamples: true` for real-world configurations
|
||||
- **`get_node`** - Unified node information tool with multiple modes (v2.26.0):
|
||||
- **Info mode** (default): `detail: 'minimal'|'standard'|'full'`, `includeExamples: true`
|
||||
- **Docs mode**: `mode: 'docs'` - Human-readable markdown documentation
|
||||
- **Property search**: `mode: 'search_properties'`, `propertyQuery: 'auth'`
|
||||
- **Versions**: `mode: 'versions'|'compare'|'breaking'|'migrations'`
|
||||
- **`validate_node`** - Unified node validation (v2.26.0):
|
||||
- `mode: 'minimal'` - Quick required fields check (<100ms)
|
||||
- `mode: 'full'` - Comprehensive validation with profiles (minimal, runtime, ai-friendly, strict)
|
||||
- **`validate_workflow`** - Complete workflow validation including AI Agent validation
|
||||
- **`search_templates`** - Unified template search (v2.26.0):
|
||||
- `searchMode: 'keyword'` (default) - Text search with `query` parameter
|
||||
- `searchMode: 'by_nodes'` - Find templates using specific `nodeTypes`
|
||||
- `searchMode: 'by_task'` - Curated templates for common `task` types
|
||||
- `searchMode: 'by_metadata'` - Filter by `complexity`, `requiredService`, `targetAudience`
|
||||
- **`get_template`** - Get complete workflow JSON (modes: nodes_only, structure, full)
|
||||
- **`list_nodes`** - List all n8n nodes with filtering options
|
||||
- **`get_node_info`** - Get comprehensive information about a specific node
|
||||
- **`get_node_essentials`** - Get only essential properties (10-20 instead of 200+). Use `includeExamples: true` to get top 3 real-world configurations from popular templates
|
||||
- **`search_nodes`** - Full-text search across all node documentation. Use `includeExamples: true` to get top 2 real-world configurations per node from templates
|
||||
- **`search_node_properties`** - Find specific properties within nodes
|
||||
- **`list_ai_tools`** - List all AI-capable nodes (ANY node can be used as AI tool!)
|
||||
- **`get_node_as_tool_info`** - Get guidance on using any node as an AI tool
|
||||
|
||||
### n8n Management Tools (13 tools - Requires API Configuration)
|
||||
These tools require `N8N_API_URL` and `N8N_API_KEY` in your configuration.
|
||||
### Template Tools
|
||||
- **`list_templates`** - Browse all templates with descriptions and optional metadata (2,500+ templates)
|
||||
- **`search_templates`** - Text search across template names and descriptions
|
||||
- **`search_templates_by_metadata`** - Advanced filtering by complexity, setup time, services, audience
|
||||
- **`list_node_templates`** - Find templates using specific nodes
|
||||
- **`get_template`** - Get complete workflow JSON for import
|
||||
- **`get_templates_for_task`** - Curated templates for common automation tasks
|
||||
|
||||
### Validation Tools
|
||||
- **`validate_workflow`** - Complete workflow validation including **AI Agent validation** (NEW in v2.17.0!)
|
||||
- Detects missing language model connections
|
||||
- Validates AI tool connections (no false warnings)
|
||||
- Enforces streaming mode constraints
|
||||
- Checks memory and output parser configurations
|
||||
- **`validate_workflow_connections`** - Check workflow structure and AI tool connections
|
||||
- **`validate_workflow_expressions`** - Validate n8n expressions including $fromAI()
|
||||
- **`validate_node_operation`** - Validate node configurations (operation-aware, profiles support)
|
||||
- **`validate_node_minimal`** - Quick validation for just required fields
|
||||
|
||||
### Advanced Tools
|
||||
- **`get_property_dependencies`** - Analyze property visibility conditions
|
||||
- **`get_node_documentation`** - Get parsed documentation from n8n-docs
|
||||
- **`get_database_statistics`** - View database metrics and coverage
|
||||
|
||||
### n8n Management Tools (Optional - Requires API Configuration)
|
||||
These powerful tools allow you to manage n8n workflows directly from Claude. They're only available when you provide `N8N_API_URL` and `N8N_API_KEY` in your configuration.
|
||||
|
||||
#### Workflow Management
|
||||
- **`n8n_create_workflow`** - Create new workflows with nodes and connections
|
||||
- **`n8n_get_workflow`** - Unified workflow retrieval (v2.26.0):
|
||||
- `mode: 'full'` (default) - Complete workflow JSON
|
||||
- `mode: 'details'` - Include execution statistics
|
||||
- `mode: 'structure'` - Nodes and connections topology only
|
||||
- `mode: 'minimal'` - Just ID, name, active status
|
||||
- **`n8n_get_workflow`** - Get complete workflow by ID
|
||||
- **`n8n_get_workflow_details`** - Get workflow with execution statistics
|
||||
- **`n8n_get_workflow_structure`** - Get simplified workflow structure
|
||||
- **`n8n_get_workflow_minimal`** - Get minimal workflow info (ID, name, active status)
|
||||
- **`n8n_update_full_workflow`** - Update entire workflow (complete replacement)
|
||||
- **`n8n_update_partial_workflow`** - Update workflow using diff operations
|
||||
- **`n8n_update_partial_workflow`** - Update workflow using diff operations (NEW in v2.7.0!)
|
||||
- **`n8n_delete_workflow`** - Delete workflows permanently
|
||||
- **`n8n_list_workflows`** - List workflows with filtering and pagination
|
||||
- **`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 with auto-fix
|
||||
- **`n8n_validate_workflow`** - Validate workflows already in n8n by ID (NEW in v2.6.3)
|
||||
- **`n8n_autofix_workflow`** - Automatically fix common workflow errors (NEW in v2.13.0!)
|
||||
|
||||
#### Execution Management
|
||||
- **`n8n_test_workflow`** - Test/trigger workflow execution:
|
||||
- Auto-detects trigger type (webhook, form, chat) from workflow
|
||||
- Supports custom data, headers, and HTTP methods for webhooks
|
||||
- Chat triggers support message and sessionId for conversations
|
||||
- **`n8n_executions`** - Unified execution management (v2.26.0):
|
||||
- `action: 'list'` - List executions with status filtering
|
||||
- `action: 'get'` - Get execution details by ID
|
||||
- `action: 'delete'` - Delete execution records
|
||||
- **`n8n_trigger_webhook_workflow`** - Trigger workflows via webhook URL
|
||||
- **`n8n_get_execution`** - Get execution details by ID
|
||||
- **`n8n_list_executions`** - List executions with status filtering
|
||||
- **`n8n_delete_execution`** - Delete execution records
|
||||
|
||||
#### System Tools
|
||||
- **`n8n_health_check`** - Check n8n API connectivity and features
|
||||
- **`n8n_diagnostic`** - Troubleshoot management tools visibility and configuration issues
|
||||
- **`n8n_list_available_tools`** - List all available management tools
|
||||
|
||||
### Example Usage
|
||||
|
||||
```typescript
|
||||
// Get node info with different detail levels
|
||||
get_node({
|
||||
// Get essentials with real-world examples from templates
|
||||
get_node_essentials({
|
||||
nodeType: "nodes-base.httpRequest",
|
||||
detail: "standard", // Default: Essential properties
|
||||
includeExamples: true // Include real-world examples from templates
|
||||
})
|
||||
|
||||
// Get documentation
|
||||
get_node({
|
||||
nodeType: "nodes-base.slack",
|
||||
mode: "docs" // Human-readable markdown documentation
|
||||
})
|
||||
|
||||
// Search for specific properties
|
||||
get_node({
|
||||
nodeType: "nodes-base.httpRequest",
|
||||
mode: "search_properties",
|
||||
propertyQuery: "authentication"
|
||||
})
|
||||
|
||||
// Version history and breaking changes
|
||||
get_node({
|
||||
nodeType: "nodes-base.httpRequest",
|
||||
mode: "versions" // View all versions with summary
|
||||
includeExamples: true // Returns top 3 configs from popular templates
|
||||
})
|
||||
|
||||
// Search nodes with configuration examples
|
||||
search_nodes({
|
||||
query: "send email gmail",
|
||||
includeExamples: true // Returns top 2 configs per node
|
||||
includeExamples: true // Returns top 2 configs per node
|
||||
})
|
||||
|
||||
// Validate node configuration
|
||||
validate_node({
|
||||
// Validate before deployment
|
||||
validate_node_operation({
|
||||
nodeType: "nodes-base.httpRequest",
|
||||
config: { method: "POST", url: "..." },
|
||||
mode: "full",
|
||||
profile: "runtime" // or "minimal", "ai-friendly", "strict"
|
||||
profile: "runtime" // or "minimal", "ai-friendly", "strict"
|
||||
})
|
||||
|
||||
// Quick required field check
|
||||
validate_node({
|
||||
validate_node_minimal({
|
||||
nodeType: "nodes-base.slack",
|
||||
config: { resource: "message", operation: "send" },
|
||||
mode: "minimal"
|
||||
})
|
||||
|
||||
// Search templates by task
|
||||
search_templates({
|
||||
searchMode: "by_task",
|
||||
task: "webhook_processing"
|
||||
config: { resource: "message", operation: "send" }
|
||||
})
|
||||
```
|
||||
|
||||
@@ -1121,21 +998,50 @@ npm run dev:http # HTTP dev mode
|
||||
|
||||
## 📊 Metrics & Coverage
|
||||
|
||||
Current database coverage (n8n v1.117.2):
|
||||
Current database coverage (n8n v1.113.3):
|
||||
|
||||
- ✅ **541/541** nodes loaded (100%)
|
||||
- ✅ **541** nodes with properties (100%)
|
||||
- ✅ **470** nodes with documentation (87%)
|
||||
- ✅ **271** AI-capable tools detected
|
||||
- ✅ **536/536** nodes loaded (100%)
|
||||
- ✅ **528** nodes with properties (98.7%)
|
||||
- ✅ **470** nodes with documentation (88%)
|
||||
- ✅ **267** AI-capable tools detected
|
||||
- ✅ **2,646** pre-extracted template configurations
|
||||
- ✅ **2,709** workflow templates available (100% metadata coverage)
|
||||
- ✅ **2,500+** workflow templates available
|
||||
- ✅ **AI Agent & LangChain nodes** fully documented
|
||||
- ⚡ **Average response time**: ~12ms
|
||||
- 💾 **Database size**: ~68MB (includes templates with metadata)
|
||||
- 💾 **Database size**: ~15MB (optimized)
|
||||
|
||||
## 🔄 Recent Updates
|
||||
|
||||
See [CHANGELOG.md](./CHANGELOG.md) for complete version history and recent changes.
|
||||
See [CHANGELOG.md](./docs/CHANGELOG.md) for full version history and recent changes.
|
||||
|
||||
## ⚠️ Known Issues
|
||||
|
||||
### Claude Desktop Container Management
|
||||
|
||||
#### Container Accumulation (Fixed in v2.7.20+)
|
||||
Previous versions had an issue where containers would not properly clean up when Claude Desktop sessions ended. This has been fixed in v2.7.20+ with proper signal handling.
|
||||
|
||||
**For best container lifecycle management:**
|
||||
1. **Use the --init flag** (recommended) - Docker's init system ensures proper signal handling:
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"n8n-mcp": {
|
||||
"command": "docker",
|
||||
"args": [
|
||||
"run", "-i", "--rm", "--init",
|
||||
"ghcr.io/czlonkowski/n8n-mcp:latest"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **Ensure you're using v2.7.20 or later** - Check your version:
|
||||
```bash
|
||||
docker run --rm ghcr.io/czlonkowski/n8n-mcp:latest --version
|
||||
```
|
||||
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
|
||||
@@ -1,318 +0,0 @@
|
||||
# N8N-MCP Validation Analysis: Complete Report
|
||||
|
||||
**Date**: November 8, 2025
|
||||
**Dataset**: 29,218 validation events | 9,021 unique users | 90 days
|
||||
**Status**: Complete and ready for action
|
||||
|
||||
---
|
||||
|
||||
## Analysis Documents
|
||||
|
||||
### 1. ANALYSIS_QUICK_REFERENCE.md (5.8KB)
|
||||
**Best for**: Quick decisions, meetings, slide presentations
|
||||
|
||||
START HERE if you want the key points in 5 minutes.
|
||||
|
||||
**Contains**:
|
||||
- One-paragraph core finding
|
||||
- Top 3 problem areas with root causes
|
||||
- 5 most common errors
|
||||
- Implementation plan summary
|
||||
- Key metrics & targets
|
||||
- FAQ section
|
||||
|
||||
---
|
||||
|
||||
### 2. VALIDATION_ANALYSIS_SUMMARY.md (13KB)
|
||||
**Best for**: Executive stakeholders, team leads, decision makers
|
||||
|
||||
Read this for comprehensive but concise overview.
|
||||
|
||||
**Contains**:
|
||||
- One-page executive summary
|
||||
- Health scorecard with key metrics
|
||||
- Detailed problem area breakdown
|
||||
- Error category distribution
|
||||
- Agent behavior insights
|
||||
- Tool usage patterns
|
||||
- Documentation impact findings
|
||||
- Top 5 recommendations with ROI estimates
|
||||
- 50-65% improvement projection
|
||||
|
||||
---
|
||||
|
||||
### 3. VALIDATION_ANALYSIS_REPORT.md (27KB)
|
||||
**Best for**: Technical deep-dive, implementation planning, root cause analysis
|
||||
|
||||
Complete reference document with all findings.
|
||||
|
||||
**Contains**:
|
||||
- All 16 SQL queries (reproducible)
|
||||
- Node-specific difficulty ranking (top 20)
|
||||
- Top 25 unique validation error messages
|
||||
- Error categorization with root causes
|
||||
- Tool usage patterns before failures
|
||||
- Search query analysis
|
||||
- Documentation effectiveness study
|
||||
- Retry success rate analysis
|
||||
- Property-level difficulty matrix
|
||||
- 8 detailed recommendations with implementation guides
|
||||
- Phase-by-phase action items
|
||||
- KPI tracking setup
|
||||
- Complete appendix with error message reference
|
||||
|
||||
---
|
||||
|
||||
### 4. IMPLEMENTATION_ROADMAP.md (4.3KB)
|
||||
**Best for**: Project managers, development team, sprint planning
|
||||
|
||||
Actionable roadmap for the next 6 weeks.
|
||||
|
||||
**Contains**:
|
||||
- Phase 1-3 breakdown (2 weeks each)
|
||||
- Specific file locations to modify
|
||||
- Effort estimates per task
|
||||
- Success criteria for each phase
|
||||
- Expected impact projections
|
||||
- Code examples (before/after)
|
||||
- Key changes documentation
|
||||
|
||||
---
|
||||
|
||||
## Reading Paths
|
||||
|
||||
### Path A: Decision Maker (30 minutes)
|
||||
1. Read: ANALYSIS_QUICK_REFERENCE.md
|
||||
2. Review: Key metrics in VALIDATION_ANALYSIS_SUMMARY.md
|
||||
3. Decision: Approve IMPLEMENTATION_ROADMAP.md
|
||||
|
||||
### Path B: Product Manager (1 hour)
|
||||
1. Read: VALIDATION_ANALYSIS_SUMMARY.md
|
||||
2. Skim: Top recommendations in VALIDATION_ANALYSIS_REPORT.md
|
||||
3. Review: IMPLEMENTATION_ROADMAP.md
|
||||
4. Check: Success metrics and timelines
|
||||
|
||||
### Path C: Technical Lead (2-3 hours)
|
||||
1. Read: ANALYSIS_QUICK_REFERENCE.md
|
||||
2. Deep-dive: VALIDATION_ANALYSIS_REPORT.md
|
||||
3. Study: IMPLEMENTATION_ROADMAP.md
|
||||
4. Review: Code examples and SQL queries
|
||||
5. Plan: Ticket creation and sprint allocation
|
||||
|
||||
### Path D: Developer (3-4 hours)
|
||||
1. Skim: ANALYSIS_QUICK_REFERENCE.md for context
|
||||
2. Read: VALIDATION_ANALYSIS_REPORT.md sections 3-8
|
||||
3. Study: IMPLEMENTATION_ROADMAP.md thoroughly
|
||||
4. Review: All code locations and examples
|
||||
5. Plan: First task implementation
|
||||
|
||||
---
|
||||
|
||||
## Key Findings Overview
|
||||
|
||||
### The Core Insight
|
||||
Validation failures are NOT broken—they're evidence the system works perfectly. 29,218 validation events prevented bad deployments. The challenge is GUIDANCE GAPS that cause first-attempt failures.
|
||||
|
||||
### Success Evidence
|
||||
- 100% same-day error recovery rate
|
||||
- 100% retry success rate
|
||||
- All agents fix errors when given feedback
|
||||
- Zero "unfixable" errors
|
||||
|
||||
### Problem Areas (75% of errors)
|
||||
1. **Workflow structure** (26%) - JSON malformation
|
||||
2. **Connections** (14%) - Unintuitive syntax
|
||||
3. **Required fields** (8%) - Not marked upfront
|
||||
|
||||
### Most Problematic Nodes
|
||||
- Webhook/Trigger (127 failures)
|
||||
- Slack (73 failures)
|
||||
- AI Agent (36 failures)
|
||||
- HTTP Request (31 failures)
|
||||
- OpenAI (35 failures)
|
||||
|
||||
### Solution Strategy
|
||||
- Phase 1: Better error messages + required field markers (25-30% reduction)
|
||||
- Phase 2: Documentation + validation improvements (additional 15-20%)
|
||||
- Phase 3: Advanced features + monitoring (additional 10-15%)
|
||||
- **Target**: 50-65% total failure reduction in 6 weeks
|
||||
|
||||
---
|
||||
|
||||
## Critical Numbers
|
||||
|
||||
```
|
||||
Validation Events ............. 29,218
|
||||
Unique Users .................. 9,021
|
||||
Data Quality .................. 100% (all marked as errors)
|
||||
|
||||
Current Metrics:
|
||||
Error Rate (doc users) ....... 12.6%
|
||||
Error Rate (non-doc users) ... 10.8%
|
||||
First-attempt success ........ ~77%
|
||||
Retry success ................ 100%
|
||||
Same-day recovery ............ 100%
|
||||
|
||||
Target Metrics (after 6 weeks):
|
||||
Error Rate ................... 6-7% (-50%)
|
||||
First-attempt success ........ 85%+
|
||||
Retry success ................ 100%
|
||||
Implementation effort ........ 60-80 hours
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Timeline
|
||||
|
||||
```
|
||||
Week 1-2: Phase 1 (Error messages, field markers, webhook guide)
|
||||
Expected: 25-30% failure reduction
|
||||
|
||||
Week 3-4: Phase 2 (Enum suggestions, connection guide, AI validation)
|
||||
Expected: Additional 15-20% reduction
|
||||
|
||||
Week 5-6: Phase 3 (Search improvements, fuzzy matching, KPI setup)
|
||||
Expected: Additional 10-15% reduction
|
||||
|
||||
Target: 50-65% total reduction by Week 6
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How to Use These Documents
|
||||
|
||||
### For Review & Approval
|
||||
1. Start with ANALYSIS_QUICK_REFERENCE.md
|
||||
2. Check key metrics in VALIDATION_ANALYSIS_SUMMARY.md
|
||||
3. Review IMPLEMENTATION_ROADMAP.md for feasibility
|
||||
4. Decision: Approve phase 1-3
|
||||
|
||||
### For Team Planning
|
||||
1. Read IMPLEMENTATION_ROADMAP.md
|
||||
2. Create GitHub issues from each task
|
||||
3. Assign based on effort estimates
|
||||
4. Schedule sprints for phase 1-3
|
||||
|
||||
### For Development
|
||||
1. Review specific recommendations in VALIDATION_ANALYSIS_REPORT.md
|
||||
2. Find code locations in IMPLEMENTATION_ROADMAP.md
|
||||
3. Study code examples (before/after)
|
||||
4. Implement and test
|
||||
|
||||
### For Measurement
|
||||
1. Record baseline metrics (current state)
|
||||
2. Deploy Phase 1 and measure impact
|
||||
3. Use KPI queries from VALIDATION_ANALYSIS_REPORT.md
|
||||
4. Adjust strategy based on actual results
|
||||
|
||||
---
|
||||
|
||||
## Key Recommendations (Priority Order)
|
||||
|
||||
### IMMEDIATE (Week 1-2)
|
||||
1. **Enhance error messages** - Add location + examples
|
||||
2. **Mark required fields** - Add "⚠️ REQUIRED" to tools
|
||||
3. **Create webhook guide** - Document configuration rules
|
||||
|
||||
### HIGH (Week 3-4)
|
||||
4. **Add enum suggestions** - Show valid values in errors
|
||||
5. **Create connections guide** - Document syntax + examples
|
||||
6. **Add AI Agent validation** - Detect missing LLM connections
|
||||
|
||||
### MEDIUM (Week 5-6)
|
||||
7. **Improve search results** - Add configuration hints
|
||||
8. **Build fuzzy matcher** - Suggest similar node types
|
||||
9. **Setup KPI tracking** - Monitor improvement
|
||||
|
||||
---
|
||||
|
||||
## Questions & Answers
|
||||
|
||||
**Q: Why so many validation failures?**
|
||||
A: High usage (9,021 users, complex workflows). System is working—preventing bad deployments.
|
||||
|
||||
**Q: Shouldn't we just allow invalid configurations?**
|
||||
A: No, validation prevents 29,218 broken workflows from deploying. We improve guidance instead.
|
||||
|
||||
**Q: Do agents actually learn from errors?**
|
||||
A: Yes, 100% same-day recovery rate proves feedback works perfectly.
|
||||
|
||||
**Q: Can we really reduce failures by 50-65%?**
|
||||
A: Yes, analysis shows these specific improvements target the actual root causes.
|
||||
|
||||
**Q: How long will this take?**
|
||||
A: 60-80 developer-hours across 6 weeks. Can start immediately.
|
||||
|
||||
**Q: What's the biggest win?**
|
||||
A: Marking required fields (378 errors) + better structure messages (1,268 errors).
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **This Week**: Review all documents and get approval
|
||||
2. **Week 1**: Create GitHub issues from IMPLEMENTATION_ROADMAP.md
|
||||
3. **Week 2**: Assign to team, start Phase 1
|
||||
4. **Week 4**: Deploy Phase 1, start Phase 2
|
||||
5. **Week 6**: Deploy Phase 2, start Phase 3
|
||||
6. **Week 8**: Deploy Phase 3, begin monitoring
|
||||
7. **Week 9+**: Review metrics, iterate
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
/Users/romualdczlonkowski/Pliki/n8n-mcp/n8n-mcp/
|
||||
├── ANALYSIS_QUICK_REFERENCE.md ............ Quick lookup (5.8KB)
|
||||
├── VALIDATION_ANALYSIS_SUMMARY.md ........ Executive summary (13KB)
|
||||
├── VALIDATION_ANALYSIS_REPORT.md ......... Complete analysis (27KB)
|
||||
├── IMPLEMENTATION_ROADMAP.md ............. Action plan (4.3KB)
|
||||
└── README_ANALYSIS.md ................... This file
|
||||
```
|
||||
|
||||
**Total Documentation**: 50KB of analysis, recommendations, and implementation guidance
|
||||
|
||||
---
|
||||
|
||||
## Contact & Support
|
||||
|
||||
For specific questions:
|
||||
- **Why?** → See VALIDATION_ANALYSIS_REPORT.md Section 2-8
|
||||
- **How?** → See IMPLEMENTATION_ROADMAP.md for code locations
|
||||
- **When?** → See IMPLEMENTATION_ROADMAP.md for timeline
|
||||
- **Metrics?** → See VALIDATION_ANALYSIS_SUMMARY.md key metrics section
|
||||
|
||||
---
|
||||
|
||||
## Metadata
|
||||
|
||||
| Item | Value |
|
||||
|------|-------|
|
||||
| Analysis Date | November 8, 2025 |
|
||||
| Data Period | Sept 26 - Nov 8, 2025 (90 days) |
|
||||
| Sample Size | 29,218 validation events |
|
||||
| Users Analyzed | 9,021 unique users |
|
||||
| SQL Queries | 16 comprehensive queries |
|
||||
| Confidence Level | HIGH |
|
||||
| Status | Complete & Ready for Implementation |
|
||||
|
||||
---
|
||||
|
||||
## Analysis Methodology
|
||||
|
||||
1. **Data Collection**: Extracted all validation_details events from PostgreSQL
|
||||
2. **Categorization**: Grouped errors by type, node, and message pattern
|
||||
3. **Pattern Analysis**: Identified root causes for each error category
|
||||
4. **User Behavior**: Tracked tool usage before/after failures
|
||||
5. **Recovery Analysis**: Measured success rates and correction time
|
||||
6. **Recommendation Development**: Mapped solutions to specific problems
|
||||
7. **Impact Projection**: Estimated improvement from each solution
|
||||
8. **Roadmap Creation**: Phased implementation plan with effort estimates
|
||||
|
||||
**Data Quality**: 100% of validation events properly categorized, no data loss or corruption
|
||||
|
||||
---
|
||||
|
||||
**Analysis Complete** | **Ready for Review** | **Awaiting Approval to Proceed**
|
||||
|
||||
BIN
data/nodes.db
BIN
data/nodes.db
Binary file not shown.
@@ -20,19 +20,19 @@ services:
|
||||
image: n8n-mcp:latest
|
||||
container_name: n8n-mcp
|
||||
ports:
|
||||
- "${PORT:-3000}:${PORT:-3000}"
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- MCP_MODE=${MCP_MODE:-http}
|
||||
- AUTH_TOKEN=${AUTH_TOKEN}
|
||||
- NODE_ENV=${NODE_ENV:-production}
|
||||
- LOG_LEVEL=${LOG_LEVEL:-info}
|
||||
- PORT=${PORT:-3000}
|
||||
- PORT=3000
|
||||
volumes:
|
||||
# Mount data directory for persistence
|
||||
- ./data:/app/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "sh", "-c", "curl -f http://localhost:$${PORT:-3000}/health"]
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
@@ -37,12 +37,11 @@ services:
|
||||
container_name: n8n-mcp
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${MCP_PORT:-3000}:${MCP_PORT:-3000}"
|
||||
- "${MCP_PORT:-3000}:3000"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- N8N_MODE=true
|
||||
- MCP_MODE=http
|
||||
- PORT=${MCP_PORT:-3000}
|
||||
- N8N_API_URL=http://n8n:5678
|
||||
- N8N_API_KEY=${N8N_API_KEY}
|
||||
- MCP_AUTH_TOKEN=${MCP_AUTH_TOKEN}
|
||||
@@ -57,7 +56,7 @@ services:
|
||||
n8n:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "sh", "-c", "curl -f http://localhost:$${MCP_PORT:-3000}/health"]
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
@@ -41,7 +41,7 @@ services:
|
||||
|
||||
# Port mapping
|
||||
ports:
|
||||
- "${PORT:-3000}:${PORT:-3000}"
|
||||
- "${PORT:-3000}:3000"
|
||||
|
||||
# Resource limits
|
||||
deploy:
|
||||
@@ -53,7 +53,7 @@ services:
|
||||
|
||||
# Health check
|
||||
healthcheck:
|
||||
test: ["CMD", "sh", "-c", "curl -f http://127.0.0.1:$${PORT:-3000}/health"]
|
||||
test: ["CMD", "curl", "-f", "http://127.0.0.1:3000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
@@ -1,433 +0,0 @@
|
||||
# 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!
|
||||
@@ -1,111 +0,0 @@
|
||||
# CI Test Infrastructure - Known Issues
|
||||
|
||||
## Integration Test Failures for External Contributor PRs
|
||||
|
||||
### Issue Summary
|
||||
|
||||
Integration tests fail for external contributor PRs with "No response from n8n server" errors, despite the code changes being correct. This is a **test infrastructure issue**, not a code quality issue.
|
||||
|
||||
### Root Cause
|
||||
|
||||
1. **GitHub Actions Security**: External contributor PRs don't get access to repository secrets (`N8N_API_URL`, `N8N_API_KEY`, etc.)
|
||||
2. **MSW Mock Server**: Mock Service Worker (MSW) is not properly intercepting HTTP requests in the CI environment
|
||||
3. **Test Configuration**: Integration tests expect `http://localhost:3001/mock-api` but the mock server isn't responding
|
||||
|
||||
### Evidence
|
||||
|
||||
From CI logs (PR #343):
|
||||
```
|
||||
[CI-DEBUG] Global setup complete, N8N_API_URL: http://localhost:3001/mock-api
|
||||
❌ No response from n8n server (repeated 60+ times across 20 tests)
|
||||
```
|
||||
|
||||
The tests ARE using the correct mock URL, but MSW isn't intercepting the requests.
|
||||
|
||||
### Why This Happens
|
||||
|
||||
**For External PRs:**
|
||||
- GitHub Actions doesn't expose repository secrets for security reasons
|
||||
- Prevents malicious PRs from exfiltrating secrets
|
||||
- MSW setup runs but requests don't get intercepted in CI
|
||||
|
||||
**Test Configuration:**
|
||||
- `.env.test` line 19: `N8N_API_URL=http://localhost:3001/mock-api`
|
||||
- `.env.test` line 67: `MSW_ENABLED=true`
|
||||
- CI workflow line 75-80: Secrets set but empty for external PRs
|
||||
|
||||
### Impact
|
||||
|
||||
- ✅ **Code Quality**: NOT affected - the actual code changes are correct
|
||||
- ✅ **Local Testing**: Works fine - MSW intercepts requests locally
|
||||
- ❌ **CI for External PRs**: Integration tests fail (infrastructure issue)
|
||||
- ✅ **CI for Internal PRs**: Works fine (has access to secrets)
|
||||
|
||||
### Current Workarounds
|
||||
|
||||
1. **For Maintainers**: Use `--admin` flag to merge despite failing tests when code is verified correct
|
||||
2. **For Contributors**: Run tests locally where MSW works properly
|
||||
3. **For CI**: Unit tests pass (don't require n8n API), integration tests fail
|
||||
|
||||
### Files Affected
|
||||
|
||||
- `tests/integration/setup/integration-setup.ts` - MSW server setup
|
||||
- `tests/setup/msw-setup.ts` - MSW configuration
|
||||
- `tests/mocks/n8n-api/handlers.ts` - Mock request handlers
|
||||
- `.github/workflows/test.yml` - CI configuration
|
||||
- `.env.test` - Test environment configuration
|
||||
|
||||
### Potential Solutions (Not Implemented)
|
||||
|
||||
1. **Separate Unit/Integration Runs**
|
||||
- Run integration tests only for internal PRs
|
||||
- Skip integration tests for external PRs
|
||||
- Rely on unit tests for external PR validation
|
||||
|
||||
2. **MSW CI Debugging**
|
||||
- Add extensive logging to MSW setup
|
||||
- Check if MSW server actually starts in CI
|
||||
- Verify request interception is working
|
||||
|
||||
3. **Mock Server Process**
|
||||
- Start actual HTTP server in CI instead of MSW
|
||||
- More reliable but adds complexity
|
||||
- Would require test infrastructure refactoring
|
||||
|
||||
4. **Public Test Instance**
|
||||
- Use publicly accessible test n8n instance
|
||||
- Exposes test data, security concerns
|
||||
- Would work for external PRs
|
||||
|
||||
### Decision
|
||||
|
||||
**Status**: Documented but not fixed
|
||||
|
||||
**Rationale**:
|
||||
- Integration test infrastructure refactoring is separate concern from code quality
|
||||
- External PRs are relatively rare compared to internal development
|
||||
- Unit tests provide sufficient coverage for most changes
|
||||
- Maintainers can verify integration tests locally before merging
|
||||
|
||||
### Testing Strategy
|
||||
|
||||
**For External Contributor PRs:**
|
||||
1. ✅ Unit tests must pass
|
||||
2. ✅ TypeScript compilation must pass
|
||||
3. ✅ Build must succeed
|
||||
4. ⚠️ Integration test failures are expected (infrastructure issue)
|
||||
5. ✅ Maintainer verifies locally before merge
|
||||
|
||||
**For Internal PRs:**
|
||||
1. ✅ All tests must pass (unit + integration)
|
||||
2. ✅ Full CI validation
|
||||
|
||||
### References
|
||||
|
||||
- PR #343: First occurrence of this issue
|
||||
- PR #345: Documented the infrastructure issue
|
||||
- Issue: External PRs don't get secrets (GitHub Actions security)
|
||||
|
||||
### Last Updated
|
||||
|
||||
2025-10-21 - Documented as part of PR #345 investigation
|
||||
@@ -4,9 +4,7 @@ Connect n8n-MCP to Claude Code CLI for enhanced n8n workflow development from th
|
||||
|
||||
## Quick Setup via CLI
|
||||
|
||||
### Basic configuration (documentation tools only)
|
||||
|
||||
**For Linux, macOS, or Windows (WSL/Git Bash):**
|
||||
### Basic configuration (documentation tools only):
|
||||
```bash
|
||||
claude mcp add n8n-mcp \
|
||||
-e MCP_MODE=stdio \
|
||||
@@ -15,21 +13,9 @@ claude mcp add n8n-mcp \
|
||||
-- npx n8n-mcp
|
||||
```
|
||||
|
||||
**For native Windows PowerShell:**
|
||||
```powershell
|
||||
# Note: The backtick ` is PowerShell's line continuation character.
|
||||
claude mcp add n8n-mcp `
|
||||
'-e MCP_MODE=stdio' `
|
||||
'-e LOG_LEVEL=error' `
|
||||
'-e DISABLE_CONSOLE_OUTPUT=true' `
|
||||
-- npx n8n-mcp
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Full configuration (with n8n management tools)
|
||||
|
||||
**For Linux, macOS, or Windows (WSL/Git Bash):**
|
||||
### Full configuration (with n8n management tools):
|
||||
```bash
|
||||
claude mcp add n8n-mcp \
|
||||
-e MCP_MODE=stdio \
|
||||
@@ -40,18 +26,6 @@ claude mcp add n8n-mcp \
|
||||
-- npx n8n-mcp
|
||||
```
|
||||
|
||||
**For native Windows PowerShell:**
|
||||
```powershell
|
||||
# Note: The backtick ` is PowerShell's line continuation character.
|
||||
claude mcp add n8n-mcp `
|
||||
'-e MCP_MODE=stdio' `
|
||||
'-e LOG_LEVEL=error' `
|
||||
'-e DISABLE_CONSOLE_OUTPUT=true' `
|
||||
'-e N8N_API_URL=https://your-n8n-instance.com' `
|
||||
'-e N8N_API_KEY=your-api-key' `
|
||||
-- npx n8n-mcp
|
||||
```
|
||||
|
||||
Make sure to replace `https://your-n8n-instance.com` with your actual n8n URL and `your-api-key` with your n8n API key.
|
||||
|
||||
## Alternative Setup Methods
|
||||
@@ -106,64 +80,15 @@ Remove the server:
|
||||
claude mcp remove n8n-mcp
|
||||
```
|
||||
|
||||
## 🎓 Add Claude Skills (Optional)
|
||||
|
||||
Supercharge your n8n workflow building with specialized Claude Code skills! The [n8n-skills](https://github.com/czlonkowski/n8n-skills) repository provides 7 complementary skills that teach AI assistants how to build production-ready n8n workflows.
|
||||
|
||||
### What You Get
|
||||
|
||||
- ✅ **n8n Expression Syntax** - Correct {{}} patterns and common mistakes
|
||||
- ✅ **n8n MCP Tools Expert** - How to use n8n-mcp tools effectively
|
||||
- ✅ **n8n Workflow Patterns** - 5 proven architectural patterns
|
||||
- ✅ **n8n Validation Expert** - Interpret and fix validation errors
|
||||
- ✅ **n8n Node Configuration** - Operation-aware setup guidance
|
||||
- ✅ **n8n Code JavaScript** - Write effective JavaScript in Code nodes
|
||||
- ✅ **n8n Code Python** - Python patterns with limitation awareness
|
||||
|
||||
### Installation
|
||||
|
||||
**Method 1: Plugin Installation** (Recommended)
|
||||
```bash
|
||||
/plugin install czlonkowski/n8n-skills
|
||||
```
|
||||
|
||||
**Method 2: Via Marketplace**
|
||||
```bash
|
||||
# Add as marketplace, then browse and install
|
||||
/plugin marketplace add czlonkowski/n8n-skills
|
||||
|
||||
# Then browse available plugins
|
||||
/plugin install
|
||||
# Select "n8n-mcp-skills" from the list
|
||||
```
|
||||
|
||||
**Method 3: Manual Installation**
|
||||
```bash
|
||||
# 1. Clone the repository
|
||||
git clone https://github.com/czlonkowski/n8n-skills.git
|
||||
|
||||
# 2. Copy skills to your Claude Code skills directory
|
||||
cp -r n8n-skills/skills/* ~/.claude/skills/
|
||||
|
||||
# 3. Reload Claude Code
|
||||
# Skills will activate automatically
|
||||
```
|
||||
|
||||
For complete installation instructions, configuration options, and usage examples, see the [n8n-skills README](https://github.com/czlonkowski/n8n-skills#-installation).
|
||||
|
||||
Skills work seamlessly with n8n-mcp to provide expert guidance throughout the workflow building process!
|
||||
|
||||
## Project Instructions
|
||||
|
||||
For optimal results, create a `CLAUDE.md` file in your project root with the instructions from the [main README's Claude Project Setup section](../README.md#-claude-project-setup).
|
||||
|
||||
## Tips
|
||||
|
||||
- If you're running n8n locally, use `http://localhost:5678` as the `N8N_API_URL`.
|
||||
- The n8n API credentials are optional. Without them, you'll only have access to documentation and validation tools. With credentials, you get full workflow management capabilities.
|
||||
- **Scope Management:**
|
||||
- By default, `claude mcp add` uses `--scope local` (also called "user scope"), which saves the configuration to your global user settings and keeps API keys private.
|
||||
- To share the configuration with your team, use `--scope project`. This saves the configuration to a `.mcp.json` file in your project's root directory.
|
||||
- **Switching Scope:** The cleanest method is to `remove` the server and then `add` it back with the desired scope flag (e.g., `claude mcp remove n8n-mcp` followed by `claude mcp add n8n-mcp --scope project`).
|
||||
- **Manual Switching (Advanced):** You can manually edit your `.claude.json` file (e.g., `C:\Users\YourName\.claude.json`). To switch, cut the `"n8n-mcp": { ... }` block from the top-level `"mcpServers"` object (user scope) and paste it into the nested `"mcpServers"` object under your project's path key (project scope), or vice versa. **Important:** You may need to restart Claude Code for manual changes to take effect.
|
||||
- Claude Code will automatically start the MCP server when you begin a conversation.
|
||||
- If you're running n8n locally, use `http://localhost:5678` as the N8N_API_URL
|
||||
- The n8n API credentials are optional - without them, you'll have documentation and validation tools only
|
||||
- With API credentials, you'll get full workflow management capabilities
|
||||
- Use `--scope local` (default) to keep your API credentials private
|
||||
- Use `--scope project` to share configuration with your team (put credentials in environment variables)
|
||||
- Claude Code will automatically start the MCP server when you begin a conversation
|
||||
|
||||
@@ -59,10 +59,10 @@ docker compose up -d
|
||||
- n8n-mcp-data:/app/data
|
||||
|
||||
ports:
|
||||
- "${PORT:-3000}:${PORT:-3000}"
|
||||
|
||||
- "${PORT:-3000}:3000"
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD", "sh", "-c", "curl -f http://127.0.0.1:$${PORT:-3000}/health"]
|
||||
test: ["CMD", "curl", "-f", "http://127.0.0.1:3000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
@@ -558,7 +558,7 @@ DISABLE_CONSOLE_OUTPUT=false
|
||||
|
||||
# Optional: Session configuration
|
||||
SESSION_TIMEOUT=1800000 # 30 minutes in milliseconds
|
||||
N8N_MCP_MAX_SESSIONS=100 # Maximum concurrent sessions (default: 100)
|
||||
MAX_SESSIONS=100
|
||||
|
||||
# Optional: Performance
|
||||
NODE_ENV=production
|
||||
|
||||
@@ -1,758 +0,0 @@
|
||||
# Session Persistence API - Production Guide
|
||||
|
||||
## Overview
|
||||
|
||||
The Session Persistence API enables zero-downtime container deployments in multi-tenant n8n-mcp environments. It allows you to export active MCP session state before shutdown and restore it after restart, maintaining session continuity across container lifecycle events.
|
||||
|
||||
**Version:** 2.24.1+
|
||||
**Status:** Production-ready
|
||||
**Use Cases:** Multi-tenant SaaS, Kubernetes deployments, container orchestration, rolling updates
|
||||
|
||||
## Architecture
|
||||
|
||||
### Session State Components
|
||||
|
||||
Each persisted session contains:
|
||||
|
||||
1. **Session Metadata**
|
||||
- `sessionId`: Unique session identifier (UUID v4)
|
||||
- `createdAt`: ISO 8601 timestamp of session creation
|
||||
- `lastAccess`: ISO 8601 timestamp of last activity
|
||||
|
||||
2. **Instance Context**
|
||||
- `n8nApiUrl`: n8n instance API endpoint
|
||||
- `n8nApiKey`: n8n API authentication key (plaintext)
|
||||
- `instanceId`: Optional tenant/instance identifier
|
||||
- `sessionId`: Optional session-specific identifier
|
||||
- `metadata`: Optional custom application data
|
||||
|
||||
3. **Dormant Session Pattern**
|
||||
- Transport and MCP server objects are NOT persisted
|
||||
- Recreated automatically on first request after restore
|
||||
- Reduces memory footprint during restore
|
||||
|
||||
## API Reference
|
||||
|
||||
### N8NMCPEngine.exportSessionState()
|
||||
|
||||
Exports all active session state for persistence before shutdown.
|
||||
|
||||
```typescript
|
||||
exportSessionState(): SessionState[]
|
||||
```
|
||||
|
||||
**Returns:** Array of session state objects containing metadata and credentials
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
const sessions = engine.exportSessionState();
|
||||
// sessions = [
|
||||
// {
|
||||
// sessionId: '550e8400-e29b-41d4-a716-446655440000',
|
||||
// metadata: {
|
||||
// createdAt: '2025-11-24T10:30:00.000Z',
|
||||
// lastAccess: '2025-11-24T17:15:32.000Z'
|
||||
// },
|
||||
// context: {
|
||||
// n8nApiUrl: 'https://tenant1.n8n.cloud',
|
||||
// n8nApiKey: 'n8n_api_...',
|
||||
// instanceId: 'tenant-123',
|
||||
// metadata: { userId: 'user-456' }
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
```
|
||||
|
||||
**Key Behaviors:**
|
||||
- Exports only non-expired sessions (within sessionTimeout)
|
||||
- Detects and warns about duplicate session IDs
|
||||
- Logs security event with session count
|
||||
- Returns empty array if no active sessions
|
||||
|
||||
### N8NMCPEngine.restoreSessionState()
|
||||
|
||||
Restores sessions from previously exported state after container restart.
|
||||
|
||||
```typescript
|
||||
restoreSessionState(sessions: SessionState[]): number
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `sessions`: Array of session state objects from `exportSessionState()`
|
||||
|
||||
**Returns:** Number of sessions successfully restored
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
const sessions = await loadFromEncryptedStorage();
|
||||
const count = engine.restoreSessionState(sessions);
|
||||
console.log(`Restored ${count} sessions`);
|
||||
```
|
||||
|
||||
**Key Behaviors:**
|
||||
- Validates session metadata (timestamps, required fields)
|
||||
- Skips expired sessions (age > sessionTimeout)
|
||||
- Skips duplicate sessions (idempotent)
|
||||
- Respects MAX_SESSIONS limit (default 100, configurable via N8N_MCP_MAX_SESSIONS env var)
|
||||
- Recreates transports/servers lazily on first request
|
||||
- Logs security events for restore success/failure
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Critical: Encrypt Before Storage
|
||||
|
||||
**The exported session state contains plaintext n8n API keys.** You MUST encrypt this data before persisting to disk.
|
||||
|
||||
```typescript
|
||||
// ❌ NEVER DO THIS
|
||||
await fs.writeFile('sessions.json', JSON.stringify(sessions));
|
||||
|
||||
// ✅ ALWAYS ENCRYPT
|
||||
const encrypted = await encryptSessionData(sessions, encryptionKey);
|
||||
await saveToSecureStorage(encrypted);
|
||||
```
|
||||
|
||||
### Recommended Encryption Approach
|
||||
|
||||
```typescript
|
||||
import crypto from 'crypto';
|
||||
|
||||
/**
|
||||
* Encrypt session data using AES-256-GCM
|
||||
*/
|
||||
async function encryptSessionData(
|
||||
sessions: SessionState[],
|
||||
encryptionKey: Buffer
|
||||
): Promise<string> {
|
||||
const iv = crypto.randomBytes(16);
|
||||
const cipher = crypto.createCipheriv('aes-256-gcm', encryptionKey, iv);
|
||||
|
||||
const json = JSON.stringify(sessions);
|
||||
const encrypted = Buffer.concat([
|
||||
cipher.update(json, 'utf8'),
|
||||
cipher.final()
|
||||
]);
|
||||
|
||||
const authTag = cipher.getAuthTag();
|
||||
|
||||
// Return base64: iv:authTag:encrypted
|
||||
return [
|
||||
iv.toString('base64'),
|
||||
authTag.toString('base64'),
|
||||
encrypted.toString('base64')
|
||||
].join(':');
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt session data
|
||||
*/
|
||||
async function decryptSessionData(
|
||||
encryptedData: string,
|
||||
encryptionKey: Buffer
|
||||
): Promise<SessionState[]> {
|
||||
const [ivB64, authTagB64, encryptedB64] = encryptedData.split(':');
|
||||
|
||||
const iv = Buffer.from(ivB64, 'base64');
|
||||
const authTag = Buffer.from(authTagB64, 'base64');
|
||||
const encrypted = Buffer.from(encryptedB64, 'base64');
|
||||
|
||||
const decipher = crypto.createDecipheriv('aes-256-gcm', encryptionKey, iv);
|
||||
decipher.setAuthTag(authTag);
|
||||
|
||||
const decrypted = Buffer.concat([
|
||||
decipher.update(encrypted),
|
||||
decipher.final()
|
||||
]);
|
||||
|
||||
return JSON.parse(decrypted.toString('utf8'));
|
||||
}
|
||||
```
|
||||
|
||||
### Key Management
|
||||
|
||||
Store encryption keys securely:
|
||||
- **Kubernetes:** Use Kubernetes Secrets with encryption at rest
|
||||
- **AWS:** Use AWS Secrets Manager or Parameter Store with KMS
|
||||
- **Azure:** Use Azure Key Vault
|
||||
- **GCP:** Use Secret Manager
|
||||
- **Local Dev:** Use environment variables (NEVER commit to git)
|
||||
|
||||
### Security Logging
|
||||
|
||||
All session persistence operations are logged with `[SECURITY]` prefix:
|
||||
|
||||
```
|
||||
[SECURITY] session_export { timestamp, count }
|
||||
[SECURITY] session_restore { timestamp, sessionId, instanceId }
|
||||
[SECURITY] session_restore_failed { timestamp, sessionId, reason }
|
||||
[SECURITY] max_sessions_reached { timestamp, count }
|
||||
```
|
||||
|
||||
Monitor these logs in production for audit trails and security analysis.
|
||||
|
||||
## Implementation Examples
|
||||
|
||||
### 1. Express.js Multi-Tenant Backend
|
||||
|
||||
```typescript
|
||||
import express from 'express';
|
||||
import { N8NMCPEngine } from 'n8n-mcp';
|
||||
|
||||
const app = express();
|
||||
const engine = new N8NMCPEngine({
|
||||
sessionTimeout: 1800000, // 30 minutes
|
||||
logLevel: 'info'
|
||||
});
|
||||
|
||||
// Startup: Restore sessions from encrypted storage
|
||||
async function startup() {
|
||||
try {
|
||||
const encrypted = await redis.get('mcp:sessions');
|
||||
if (encrypted) {
|
||||
const sessions = await decryptSessionData(
|
||||
encrypted,
|
||||
process.env.ENCRYPTION_KEY
|
||||
);
|
||||
const count = engine.restoreSessionState(sessions);
|
||||
console.log(`Restored ${count} sessions`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to restore sessions:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown: Export sessions to encrypted storage
|
||||
async function shutdown() {
|
||||
try {
|
||||
const sessions = engine.exportSessionState();
|
||||
const encrypted = await encryptSessionData(
|
||||
sessions,
|
||||
process.env.ENCRYPTION_KEY
|
||||
);
|
||||
await redis.set('mcp:sessions', encrypted, 'EX', 3600); // 1 hour TTL
|
||||
console.log(`Exported ${sessions.length} sessions`);
|
||||
} catch (error) {
|
||||
console.error('Failed to export sessions:', error);
|
||||
}
|
||||
|
||||
await engine.shutdown();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Handle graceful shutdown
|
||||
process.on('SIGTERM', shutdown);
|
||||
process.on('SIGINT', shutdown);
|
||||
|
||||
// Start server
|
||||
await startup();
|
||||
app.listen(3000);
|
||||
```
|
||||
|
||||
### 2. Kubernetes Deployment with Init Container
|
||||
|
||||
**deployment.yaml:**
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: n8n-mcp
|
||||
spec:
|
||||
replicas: 3
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxUnavailable: 1
|
||||
maxSurge: 1
|
||||
template:
|
||||
spec:
|
||||
initContainers:
|
||||
- name: restore-sessions
|
||||
image: your-app:latest
|
||||
command: ['/app/restore-sessions.sh']
|
||||
env:
|
||||
- name: ENCRYPTION_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mcp-secrets
|
||||
key: encryption-key
|
||||
- name: REDIS_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mcp-secrets
|
||||
key: redis-url
|
||||
volumeMounts:
|
||||
- name: sessions
|
||||
mountPath: /sessions
|
||||
|
||||
containers:
|
||||
- name: mcp-server
|
||||
image: your-app:latest
|
||||
lifecycle:
|
||||
preStop:
|
||||
exec:
|
||||
command: ['/app/export-sessions.sh']
|
||||
env:
|
||||
- name: ENCRYPTION_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mcp-secrets
|
||||
key: encryption-key
|
||||
- name: SESSION_TIMEOUT
|
||||
value: "1800000"
|
||||
volumeMounts:
|
||||
- name: sessions
|
||||
mountPath: /sessions
|
||||
|
||||
# Graceful shutdown configuration
|
||||
terminationGracePeriodSeconds: 30
|
||||
|
||||
volumes:
|
||||
- name: sessions
|
||||
emptyDir: {}
|
||||
```
|
||||
|
||||
**restore-sessions.sh:**
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "Restoring sessions from Redis..."
|
||||
|
||||
# Fetch encrypted sessions from Redis
|
||||
ENCRYPTED=$(redis-cli -u "$REDIS_URL" GET "mcp:sessions:${HOSTNAME}")
|
||||
|
||||
if [ -n "$ENCRYPTED" ]; then
|
||||
echo "$ENCRYPTED" > /sessions/encrypted.txt
|
||||
echo "Sessions fetched, will be restored on startup"
|
||||
else
|
||||
echo "No sessions to restore"
|
||||
fi
|
||||
```
|
||||
|
||||
**export-sessions.sh:**
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "Exporting sessions to Redis..."
|
||||
|
||||
# Trigger session export via HTTP endpoint
|
||||
curl -X POST http://localhost:3000/internal/export-sessions
|
||||
|
||||
echo "Sessions exported successfully"
|
||||
```
|
||||
|
||||
### 3. Docker Compose with Redis
|
||||
|
||||
**docker-compose.yml:**
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
n8n-mcp:
|
||||
build: .
|
||||
environment:
|
||||
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
|
||||
- REDIS_URL=redis://redis:6379
|
||||
- SESSION_TIMEOUT=1800000
|
||||
depends_on:
|
||||
- redis
|
||||
volumes:
|
||||
- ./data:/data
|
||||
deploy:
|
||||
replicas: 2
|
||||
update_config:
|
||||
parallelism: 1
|
||||
delay: 10s
|
||||
order: start-first
|
||||
stop_grace_period: 30s
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
command: redis-server --appendonly yes
|
||||
|
||||
volumes:
|
||||
redis-data:
|
||||
```
|
||||
|
||||
**Application code:**
|
||||
```typescript
|
||||
import { N8NMCPEngine } from 'n8n-mcp';
|
||||
import Redis from 'ioredis';
|
||||
|
||||
const redis = new Redis(process.env.REDIS_URL);
|
||||
const engine = new N8NMCPEngine();
|
||||
|
||||
// Export endpoint (called by preStop hook)
|
||||
app.post('/internal/export-sessions', async (req, res) => {
|
||||
try {
|
||||
const sessions = engine.exportSessionState();
|
||||
const encrypted = await encryptSessionData(
|
||||
sessions,
|
||||
Buffer.from(process.env.ENCRYPTION_KEY, 'hex')
|
||||
);
|
||||
|
||||
// Store with hostname as key for per-container tracking
|
||||
await redis.set(
|
||||
`mcp:sessions:${os.hostname()}`,
|
||||
encrypted,
|
||||
'EX',
|
||||
3600
|
||||
);
|
||||
|
||||
res.json({ exported: sessions.length });
|
||||
} catch (error) {
|
||||
console.error('Export failed:', error);
|
||||
res.status(500).json({ error: 'Export failed' });
|
||||
}
|
||||
});
|
||||
|
||||
// Restore on startup
|
||||
async function startup() {
|
||||
const encrypted = await redis.get(`mcp:sessions:${os.hostname()}`);
|
||||
if (encrypted) {
|
||||
const sessions = await decryptSessionData(
|
||||
encrypted,
|
||||
Buffer.from(process.env.ENCRYPTION_KEY, 'hex')
|
||||
);
|
||||
const count = engine.restoreSessionState(sessions);
|
||||
console.log(`Restored ${count} sessions`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Session Timeout Configuration
|
||||
|
||||
Choose appropriate timeout based on use case:
|
||||
|
||||
```typescript
|
||||
const engine = new N8NMCPEngine({
|
||||
sessionTimeout: 1800000 // 30 minutes (recommended default)
|
||||
});
|
||||
|
||||
// Development: 5 minutes
|
||||
sessionTimeout: 300000
|
||||
|
||||
// Production SaaS: 30-60 minutes
|
||||
sessionTimeout: 1800000 - 3600000
|
||||
|
||||
// Long-running workflows: 2-4 hours
|
||||
sessionTimeout: 7200000 - 14400000
|
||||
```
|
||||
|
||||
### 2. Storage Backend Selection
|
||||
|
||||
**Redis (Recommended for Production)**
|
||||
- Fast read/write for session data
|
||||
- TTL support for automatic cleanup
|
||||
- Pub/sub for distributed coordination
|
||||
- Atomic operations for consistency
|
||||
|
||||
**Database (PostgreSQL/MySQL)**
|
||||
- JSONB column for session state
|
||||
- Good for audit requirements
|
||||
- Slower than Redis
|
||||
- Requires periodic cleanup
|
||||
|
||||
**S3/Cloud Storage**
|
||||
- Good for disaster recovery backups
|
||||
- Not suitable for hot session restore
|
||||
- High latency
|
||||
- Good for long-term session archival
|
||||
|
||||
### 3. Monitoring and Alerting
|
||||
|
||||
Monitor these metrics:
|
||||
|
||||
```typescript
|
||||
// Session export metrics
|
||||
const sessions = engine.exportSessionState();
|
||||
metrics.gauge('mcp.sessions.exported', sessions.length);
|
||||
metrics.gauge('mcp.sessions.export_size_kb',
|
||||
JSON.stringify(sessions).length / 1024
|
||||
);
|
||||
|
||||
// Session restore metrics
|
||||
const restored = engine.restoreSessionState(sessions);
|
||||
metrics.gauge('mcp.sessions.restored', restored);
|
||||
metrics.gauge('mcp.sessions.restore_success_rate',
|
||||
restored / sessions.length
|
||||
);
|
||||
|
||||
// Runtime metrics
|
||||
const info = engine.getSessionInfo();
|
||||
metrics.gauge('mcp.sessions.active', info.active ? 1 : 0);
|
||||
metrics.gauge('mcp.sessions.age_seconds', info.age || 0);
|
||||
```
|
||||
|
||||
Alert on:
|
||||
- Export failures (should be rare)
|
||||
- Low restore success rate (<95%)
|
||||
- MAX_SESSIONS limit reached
|
||||
- High session age (potential leaks)
|
||||
|
||||
### 4. Graceful Shutdown Timing
|
||||
|
||||
Ensure sufficient time for session export:
|
||||
|
||||
```typescript
|
||||
// Kubernetes terminationGracePeriodSeconds
|
||||
terminationGracePeriodSeconds: 30 // 30 seconds minimum
|
||||
|
||||
// Docker stop timeout
|
||||
docker run --stop-timeout 30 your-image
|
||||
|
||||
// Process signal handling
|
||||
process.on('SIGTERM', async () => {
|
||||
console.log('SIGTERM received, starting graceful shutdown...');
|
||||
|
||||
// 1. Stop accepting new requests (5s)
|
||||
await server.close();
|
||||
|
||||
// 2. Wait for in-flight requests (10s)
|
||||
await waitForInFlightRequests(10000);
|
||||
|
||||
// 3. Export sessions (5s)
|
||||
const sessions = engine.exportSessionState();
|
||||
await saveEncryptedSessions(sessions);
|
||||
|
||||
// 4. Cleanup (5s)
|
||||
await engine.shutdown();
|
||||
|
||||
// 5. Exit (5s buffer)
|
||||
process.exit(0);
|
||||
});
|
||||
```
|
||||
|
||||
### 5. Idempotency Handling
|
||||
|
||||
Sessions can be restored multiple times safely:
|
||||
|
||||
```typescript
|
||||
// First restore
|
||||
const count1 = engine.restoreSessionState(sessions);
|
||||
// count1 = 5
|
||||
|
||||
// Second restore (same sessions)
|
||||
const count2 = engine.restoreSessionState(sessions);
|
||||
// count2 = 0 (all already exist)
|
||||
```
|
||||
|
||||
This is safe for:
|
||||
- Init container retries
|
||||
- Manual recovery operations
|
||||
- Disaster recovery scenarios
|
||||
|
||||
### 6. Multi-Instance Coordination
|
||||
|
||||
For multiple container instances:
|
||||
|
||||
```typescript
|
||||
// Option 1: Per-instance storage (simple)
|
||||
const key = `mcp:sessions:${instance.hostname}`;
|
||||
|
||||
// Option 2: Centralized with distributed lock (advanced)
|
||||
const lock = await acquireLock('mcp:session-export');
|
||||
try {
|
||||
const allSessions = await getAllInstanceSessions();
|
||||
await saveToBackup(allSessions);
|
||||
} finally {
|
||||
await lock.release();
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Memory Usage
|
||||
|
||||
```typescript
|
||||
// Each session: ~1-2 KB in memory
|
||||
// 100 sessions: ~100-200 KB
|
||||
// 1000 sessions: ~1-2 MB
|
||||
|
||||
// Export serialized size
|
||||
const sessions = engine.exportSessionState();
|
||||
const sizeKB = JSON.stringify(sessions).length / 1024;
|
||||
console.log(`Export size: ${sizeKB.toFixed(2)} KB`);
|
||||
```
|
||||
|
||||
### Export/Restore Speed
|
||||
|
||||
```typescript
|
||||
// Export: O(n) where n = active sessions
|
||||
// Typical: 50-100 sessions in <10ms
|
||||
|
||||
// Restore: O(n) with validation
|
||||
// Typical: 50-100 sessions in 20-50ms
|
||||
|
||||
// Factor in encryption:
|
||||
// AES-256-GCM: ~1ms per 100 sessions
|
||||
```
|
||||
|
||||
### MAX_SESSIONS Limit
|
||||
|
||||
Default limit: 100 sessions per container (configurable via `N8N_MCP_MAX_SESSIONS` env var)
|
||||
|
||||
```typescript
|
||||
// Restore respects limit
|
||||
const sessions = createSessions(150); // 150 sessions
|
||||
const restored = engine.restoreSessionState(sessions);
|
||||
// restored = 100 (only first 100 restored, or N8N_MCP_MAX_SESSIONS value)
|
||||
```
|
||||
|
||||
For higher session limits:
|
||||
- Set `N8N_MCP_MAX_SESSIONS=1000` (or desired limit)
|
||||
- Monitor memory usage as sessions consume resources
|
||||
- Alternatively, deploy multiple containers with session routing/sharding
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: No sessions restored
|
||||
|
||||
**Symptoms:**
|
||||
```
|
||||
Restored 0 sessions
|
||||
```
|
||||
|
||||
**Causes:**
|
||||
1. All sessions expired (age > sessionTimeout)
|
||||
2. Invalid date format in metadata
|
||||
3. Missing required context fields
|
||||
|
||||
**Debug:**
|
||||
```typescript
|
||||
const sessions = await loadFromEncryptedStorage();
|
||||
console.log('Loaded sessions:', sessions.length);
|
||||
|
||||
// Check individual sessions
|
||||
sessions.forEach((s, i) => {
|
||||
const age = Date.now() - new Date(s.metadata.lastAccess).getTime();
|
||||
console.log(`Session ${i}: age=${age}ms, expired=${age > sessionTimeout}`);
|
||||
});
|
||||
```
|
||||
|
||||
### Issue: Restore fails with "invalid context"
|
||||
|
||||
**Symptoms:**
|
||||
```
|
||||
[SECURITY] session_restore_failed { sessionId: '...', reason: 'invalid context: ...' }
|
||||
```
|
||||
|
||||
**Causes:**
|
||||
1. Missing n8nApiUrl or n8nApiKey
|
||||
2. Invalid URL format
|
||||
3. Corrupted session data
|
||||
|
||||
**Fix:**
|
||||
```typescript
|
||||
// Validate before restore
|
||||
const valid = sessions.filter(s => {
|
||||
if (!s.context?.n8nApiUrl || !s.context?.n8nApiKey) {
|
||||
console.warn(`Invalid session ${s.sessionId}: missing credentials`);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
new URL(s.context.n8nApiUrl); // Validate URL
|
||||
return true;
|
||||
} catch {
|
||||
console.warn(`Invalid session ${s.sessionId}: malformed URL`);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
const count = engine.restoreSessionState(valid);
|
||||
```
|
||||
|
||||
### Issue: MAX_SESSIONS limit hit
|
||||
|
||||
**Symptoms:**
|
||||
```
|
||||
Reached MAX_SESSIONS limit (100), skipping remaining sessions
|
||||
```
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. Increase limit: Set `N8N_MCP_MAX_SESSIONS=1000` (or desired value)
|
||||
2. Scale horizontally (more containers)
|
||||
3. Implement session sharding
|
||||
4. Reduce sessionTimeout
|
||||
5. Clean up inactive sessions
|
||||
|
||||
```typescript
|
||||
// Pre-filter by activity
|
||||
const recentSessions = sessions.filter(s => {
|
||||
const age = Date.now() - new Date(s.metadata.lastAccess).getTime();
|
||||
return age < 600000; // Only restore sessions active in last 10 min
|
||||
});
|
||||
|
||||
const count = engine.restoreSessionState(recentSessions);
|
||||
```
|
||||
|
||||
### Issue: Duplicate session IDs
|
||||
|
||||
**Symptoms:**
|
||||
```
|
||||
Duplicate sessionId detected during export: 550e8400-...
|
||||
```
|
||||
|
||||
**Cause:** Bug in session management logic
|
||||
|
||||
**Fix:** This is a warning, not an error. The duplicate is automatically skipped. If persistent, investigate session creation logic.
|
||||
|
||||
### Issue: High memory usage after restore
|
||||
|
||||
**Symptoms:** Container OOM after restoring many sessions
|
||||
|
||||
**Cause:** Too many sessions for container resources
|
||||
|
||||
**Solution:**
|
||||
```typescript
|
||||
// Restore in batches
|
||||
async function restoreInBatches(sessions: SessionState[], batchSize = 25) {
|
||||
let totalRestored = 0;
|
||||
|
||||
for (let i = 0; i < sessions.length; i += batchSize) {
|
||||
const batch = sessions.slice(i, i + batchSize);
|
||||
const count = engine.restoreSessionState(batch);
|
||||
totalRestored += count;
|
||||
|
||||
// Wait for GC between batches
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
return totalRestored;
|
||||
}
|
||||
```
|
||||
|
||||
## Version Compatibility
|
||||
|
||||
| Feature | Version | Status |
|
||||
|---------|---------|--------|
|
||||
| exportSessionState() | 2.3.0+ | Stable |
|
||||
| restoreSessionState() | 2.3.0+ | Stable |
|
||||
| Security logging | 2.24.1+ | Stable |
|
||||
| Duplicate detection | 2.24.1+ | Stable |
|
||||
| Race condition fix | 2.24.1+ | Stable |
|
||||
| Date validation | 2.24.1+ | Stable |
|
||||
| Optional instanceId | 2.24.1+ | Stable |
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [HTTP Deployment Guide](./HTTP_DEPLOYMENT.md) - Multi-tenant HTTP server setup
|
||||
- [Library Usage Guide](./LIBRARY_USAGE.md) - Embedding n8n-mcp in your app
|
||||
- [Docker Guide](./DOCKER_README.md) - Container deployment
|
||||
- [Flexible Instance Configuration](./FLEXIBLE_INSTANCE_CONFIGURATION.md) - Multi-tenant patterns
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions:
|
||||
- GitHub Issues: https://github.com/czlonkowski/n8n-mcp/issues
|
||||
- Documentation: https://github.com/czlonkowski/n8n-mcp#readme
|
||||
|
||||
---
|
||||
|
||||
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
|
||||
@@ -1,239 +0,0 @@
|
||||
# Type Structure Validation
|
||||
|
||||
## Overview
|
||||
|
||||
Type Structure Validation is an automatic validation system that ensures complex n8n node configurations conform to their expected data structures. Implemented as part of the n8n-mcp validation system, it provides zero-configuration validation for special n8n types that have complex nested structures.
|
||||
|
||||
**Status:** Production (v2.22.21+)
|
||||
**Performance:** 100% pass rate on 776 real-world validations
|
||||
**Speed:** 0.01ms average validation time (500x faster than target)
|
||||
|
||||
The system automatically validates node configurations without requiring any additional setup or configuration from users or AI assistants.
|
||||
|
||||
## Supported Types
|
||||
|
||||
The validation system supports four special n8n types that have complex structures:
|
||||
|
||||
### 1. **filter** (FilterValue)
|
||||
Complex filtering conditions with boolean operators, comparison operations, and nested logic.
|
||||
|
||||
**Structure:**
|
||||
- `combinator`: "and" | "or" - How conditions are combined
|
||||
- `conditions`: Array of filter conditions
|
||||
- Each condition has: `leftValue`, `operator` (type + operation), `rightValue`
|
||||
- Supports 40+ operations: equals, contains, exists, notExists, gt, lt, regex, etc.
|
||||
|
||||
**Example Usage:** IF node, Switch node condition filtering
|
||||
|
||||
### 2. **resourceMapper** (ResourceMapperValue)
|
||||
Data mapping configuration for transforming data between different formats.
|
||||
|
||||
**Structure:**
|
||||
- `mappingMode`: "defineBelow" | "autoMapInputData" | "mapManually"
|
||||
- `value`: Field mappings or expressions
|
||||
- `matchingColumns`: Column matching configuration
|
||||
- `schema`: Target schema definition
|
||||
|
||||
**Example Usage:** Google Sheets node, Airtable node data mapping
|
||||
|
||||
### 3. **assignmentCollection** (AssignmentCollectionValue)
|
||||
Variable assignments for setting multiple values at once.
|
||||
|
||||
**Structure:**
|
||||
- `assignments`: Array of name-value pairs
|
||||
- Each assignment has: `name`, `value`, `type`
|
||||
|
||||
**Example Usage:** Set node, Code node variable assignments
|
||||
|
||||
### 4. **resourceLocator** (INodeParameterResourceLocator)
|
||||
Resource selection with multiple lookup modes (ID, name, URL, etc.).
|
||||
|
||||
**Structure:**
|
||||
- `mode`: "id" | "list" | "url" | "name"
|
||||
- `value`: Resource identifier (string, number, or expression)
|
||||
- `cachedResultName`: Optional cached display name
|
||||
- `cachedResultUrl`: Optional cached URL
|
||||
|
||||
**Example Usage:** Google Sheets spreadsheet selection, Slack channel selection
|
||||
|
||||
## Performance & Results
|
||||
|
||||
The validation system was tested against real-world n8n.io workflow templates:
|
||||
|
||||
| Metric | Result |
|
||||
|--------|--------|
|
||||
| **Templates Tested** | 91 (top by popularity) |
|
||||
| **Nodes Validated** | 616 nodes with special types |
|
||||
| **Total Validations** | 776 property validations |
|
||||
| **Pass Rate** | 100.00% (776/776) |
|
||||
| **False Positive Rate** | 0.00% |
|
||||
| **Average Time** | 0.01ms per validation |
|
||||
| **Max Time** | 1.00ms per validation |
|
||||
| **Performance vs Target** | 500x faster than 50ms target |
|
||||
|
||||
### Type-Specific Results
|
||||
|
||||
- `filter`: 93/93 passed (100.00%)
|
||||
- `resourceMapper`: 69/69 passed (100.00%)
|
||||
- `assignmentCollection`: 213/213 passed (100.00%)
|
||||
- `resourceLocator`: 401/401 passed (100.00%)
|
||||
|
||||
## How It Works
|
||||
|
||||
### Automatic Integration
|
||||
|
||||
Structure validation is automatically applied during node configuration validation. When you call `validate_node_operation` or `validate_node_minimal`, the system:
|
||||
|
||||
1. **Identifies Special Types**: Detects properties that use filter, resourceMapper, assignmentCollection, or resourceLocator types
|
||||
2. **Validates Structure**: Checks that the configuration matches the expected structure for that type
|
||||
3. **Validates Operations**: For filter types, validates that operations are supported for the data type
|
||||
4. **Provides Context**: Returns specific error messages with property paths and fix suggestions
|
||||
|
||||
### Validation Flow
|
||||
|
||||
```
|
||||
User/AI provides node config
|
||||
↓
|
||||
validate_node_operation (MCP tool)
|
||||
↓
|
||||
EnhancedConfigValidator.validateWithMode()
|
||||
↓
|
||||
validateSpecialTypeStructures() ← Automatic structure validation
|
||||
↓
|
||||
TypeStructureService.validateStructure()
|
||||
↓
|
||||
Returns validation result with errors/warnings/suggestions
|
||||
```
|
||||
|
||||
### Edge Cases Handled
|
||||
|
||||
**1. Credential-Provided Fields**
|
||||
- Fields like Google Sheets `sheetId` that come from n8n credentials at runtime are excluded from validation
|
||||
- No false positives for fields that aren't in the configuration
|
||||
|
||||
**2. Filter Operations**
|
||||
- Universal operations (`exists`, `notExists`, `isNotEmpty`) work across all data types
|
||||
- Type-specific operations validated (e.g., `regex` only for strings, `gt`/`lt` only for numbers)
|
||||
|
||||
**3. Node-Specific Logic**
|
||||
- Custom validation logic for specific nodes (Google Sheets, Slack, etc.)
|
||||
- Context-aware error messages that understand the node's operation
|
||||
|
||||
## Example Validation Error
|
||||
|
||||
### Invalid Filter Structure
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
{
|
||||
"conditions": {
|
||||
"combinator": "and",
|
||||
"conditions": [
|
||||
{
|
||||
"leftValue": "={{ $json.status }}",
|
||||
"rightValue": "active",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "invalidOperation" // ❌ Not a valid operation
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Validation Error:**
|
||||
```json
|
||||
{
|
||||
"valid": false,
|
||||
"errors": [
|
||||
{
|
||||
"type": "invalid_structure",
|
||||
"property": "conditions.conditions[0].operator.operation",
|
||||
"message": "Unsupported operation 'invalidOperation' for type 'string'",
|
||||
"suggestion": "Valid operations for string: equals, notEquals, contains, notContains, startsWith, endsWith, regex, exists, notExists, isNotEmpty"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Implementation
|
||||
|
||||
- **Type Definitions**: `src/types/type-structures.ts` (301 lines)
|
||||
- **Type Structures**: `src/constants/type-structures.ts` (741 lines, 22 complete type structures)
|
||||
- **Service Layer**: `src/services/type-structure-service.ts` (427 lines)
|
||||
- **Validator Integration**: `src/services/enhanced-config-validator.ts` (line 270)
|
||||
- **Node-Specific Logic**: `src/services/node-specific-validators.ts`
|
||||
|
||||
### Test Coverage
|
||||
|
||||
- **Unit Tests**:
|
||||
- `tests/unit/types/type-structures.test.ts` (14 tests)
|
||||
- `tests/unit/constants/type-structures.test.ts` (39 tests)
|
||||
- `tests/unit/services/type-structure-service.test.ts` (64 tests)
|
||||
- `tests/unit/services/enhanced-config-validator-type-structures.test.ts`
|
||||
|
||||
- **Integration Tests**:
|
||||
- `tests/integration/validation/real-world-structure-validation.test.ts` (8 tests, 388ms)
|
||||
|
||||
- **Validation Scripts**:
|
||||
- `scripts/test-structure-validation.ts` - Standalone validation against 100 templates
|
||||
|
||||
### Documentation
|
||||
|
||||
- **Implementation Plan**: `docs/local/v3/implementation-plan-final.md` - Complete technical specifications
|
||||
- **Phase Results**: Phases 1-3 completed with 100% success criteria met
|
||||
|
||||
## For Developers
|
||||
|
||||
### Adding New Type Structures
|
||||
|
||||
1. Define the type structure in `src/constants/type-structures.ts`
|
||||
2. Add validation logic in `TypeStructureService.validateStructure()`
|
||||
3. Add tests in `tests/unit/constants/type-structures.test.ts`
|
||||
4. Test against real templates using `scripts/test-structure-validation.ts`
|
||||
|
||||
### Testing Structure Validation
|
||||
|
||||
**Run Unit Tests:**
|
||||
```bash
|
||||
npm run test:unit -- tests/unit/services/enhanced-config-validator-type-structures.test.ts
|
||||
```
|
||||
|
||||
**Run Integration Tests:**
|
||||
```bash
|
||||
npm run test:integration -- tests/integration/validation/real-world-structure-validation.test.ts
|
||||
```
|
||||
|
||||
**Run Full Validation:**
|
||||
```bash
|
||||
npm run test:structure-validation
|
||||
```
|
||||
|
||||
### Relevant Test Files
|
||||
|
||||
- **Type Tests**: `tests/unit/types/type-structures.test.ts`
|
||||
- **Structure Tests**: `tests/unit/constants/type-structures.test.ts`
|
||||
- **Service Tests**: `tests/unit/services/type-structure-service.test.ts`
|
||||
- **Validator Tests**: `tests/unit/services/enhanced-config-validator-type-structures.test.ts`
|
||||
- **Integration Tests**: `tests/integration/validation/real-world-structure-validation.test.ts`
|
||||
- **Real-World Validation**: `scripts/test-structure-validation.ts`
|
||||
|
||||
## Production Readiness
|
||||
|
||||
✅ **All Tests Passing**: 100% pass rate on unit and integration tests
|
||||
✅ **Performance Validated**: 0.01ms average (500x better than 50ms target)
|
||||
✅ **Zero Breaking Changes**: Fully backward compatible
|
||||
✅ **Real-World Validation**: 91 templates, 616 nodes, 776 validations
|
||||
✅ **Production Deployment**: Successfully deployed in v2.22.21
|
||||
✅ **Edge Cases Handled**: Credential fields, filter operations, node-specific logic
|
||||
|
||||
## Version History
|
||||
|
||||
- **v2.22.21** (2025-11-21): Type structure validation system completed (Phases 1-3)
|
||||
- 22 complete type structures defined
|
||||
- 100% pass rate on real-world validation
|
||||
- 0.01ms average validation time
|
||||
- Zero false positives
|
||||
@@ -162,7 +162,7 @@ n8n_validate_workflow({id: createdWorkflowId})
|
||||
n8n_update_partial_workflow({
|
||||
workflowId: id,
|
||||
operations: [
|
||||
{type: 'updateNode', nodeId: 'slack1', updates: {position: [100, 200]}}
|
||||
{type: 'updateNode', nodeId: 'slack1', changes: {position: [100, 200]}}
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 430 KiB |
16704
package-lock.json
generated
16704
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-mcp",
|
||||
"version": "2.28.4",
|
||||
"version": "2.20.2",
|
||||
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
@@ -66,7 +66,6 @@
|
||||
"test:workflow-diff": "node dist/scripts/test-workflow-diff.js",
|
||||
"test:transactional-diff": "node dist/scripts/test-transactional-diff.js",
|
||||
"test:tools-documentation": "node dist/scripts/test-tools-documentation.js",
|
||||
"test:structure-validation": "npx tsx scripts/test-structure-validation.ts",
|
||||
"test:url-configuration": "npm run build && ts-node scripts/test-url-configuration.ts",
|
||||
"test:search-improvements": "node dist/scripts/test-search-improvements.js",
|
||||
"test:fts5-search": "node dist/scripts/test-fts5-search.js",
|
||||
@@ -140,22 +139,20 @@
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "1.20.1",
|
||||
"@n8n/n8n-nodes-langchain": "^1.121.1",
|
||||
"@modelcontextprotocol/sdk": "^1.20.1",
|
||||
"@n8n/n8n-nodes-langchain": "^1.114.1",
|
||||
"@supabase/supabase-js": "^2.57.4",
|
||||
"dotenv": "^16.5.0",
|
||||
"express": "^5.1.0",
|
||||
"express-rate-limit": "^7.1.5",
|
||||
"form-data": "^4.0.5",
|
||||
"lru-cache": "^11.2.1",
|
||||
"n8n": "^1.122.4",
|
||||
"n8n-core": "^1.121.1",
|
||||
"n8n-workflow": "^1.119.1",
|
||||
"n8n": "^1.115.2",
|
||||
"n8n-core": "^1.114.0",
|
||||
"n8n-workflow": "^1.112.0",
|
||||
"openai": "^4.77.0",
|
||||
"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,17 +1,16 @@
|
||||
{
|
||||
"name": "n8n-mcp-runtime",
|
||||
"version": "2.28.0",
|
||||
"version": "2.20.2",
|
||||
"description": "n8n MCP Server Runtime Dependencies Only",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "1.20.1",
|
||||
"@modelcontextprotocol/sdk": "^1.13.2",
|
||||
"@supabase/supabase-js": "^2.57.4",
|
||||
"express": "^5.1.0",
|
||||
"express-rate-limit": "^7.1.5",
|
||||
"dotenv": "^16.5.0",
|
||||
"lru-cache": "^11.2.1",
|
||||
"sql.js": "^1.13.0",
|
||||
"tslib": "^2.6.2",
|
||||
"uuid": "^10.0.0",
|
||||
"axios": "^1.7.7"
|
||||
},
|
||||
|
||||
@@ -1,192 +0,0 @@
|
||||
/**
|
||||
* Backfill script to populate structural hashes for existing workflow mutations
|
||||
*
|
||||
* Purpose: Generates workflow_structure_hash_before and workflow_structure_hash_after
|
||||
* for all existing mutations to enable cross-referencing with telemetry_workflows
|
||||
*
|
||||
* Usage: npx tsx scripts/backfill-mutation-hashes.ts
|
||||
*
|
||||
* Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
|
||||
*/
|
||||
|
||||
import { WorkflowSanitizer } from '../src/telemetry/workflow-sanitizer.js';
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
|
||||
// Initialize Supabase client
|
||||
const supabaseUrl = process.env.SUPABASE_URL || '';
|
||||
const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY || '';
|
||||
|
||||
if (!supabaseUrl || !supabaseKey) {
|
||||
console.error('Error: SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY environment variables are required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const supabase = createClient(supabaseUrl, supabaseKey);
|
||||
|
||||
interface MutationRecord {
|
||||
id: string;
|
||||
workflow_before: any;
|
||||
workflow_after: any;
|
||||
workflow_structure_hash_before: string | null;
|
||||
workflow_structure_hash_after: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all mutations that need structural hashes
|
||||
*/
|
||||
async function fetchMutationsToBackfill(): Promise<MutationRecord[]> {
|
||||
console.log('Fetching mutations without structural hashes...');
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('workflow_mutations')
|
||||
.select('id, workflow_before, workflow_after, workflow_structure_hash_before, workflow_structure_hash_after')
|
||||
.is('workflow_structure_hash_before', null);
|
||||
|
||||
if (error) {
|
||||
throw new Error(`Failed to fetch mutations: ${error.message}`);
|
||||
}
|
||||
|
||||
console.log(`Found ${data?.length || 0} mutations to backfill`);
|
||||
return data || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate structural hash for a workflow
|
||||
*/
|
||||
function generateStructuralHash(workflow: any): string {
|
||||
try {
|
||||
return WorkflowSanitizer.generateWorkflowHash(workflow);
|
||||
} catch (error) {
|
||||
console.error('Error generating hash:', error);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a single mutation with structural hashes
|
||||
*/
|
||||
async function updateMutation(id: string, structureHashBefore: string, structureHashAfter: string): Promise<boolean> {
|
||||
const { error } = await supabase
|
||||
.from('workflow_mutations')
|
||||
.update({
|
||||
workflow_structure_hash_before: structureHashBefore,
|
||||
workflow_structure_hash_after: structureHashAfter,
|
||||
})
|
||||
.eq('id', id);
|
||||
|
||||
if (error) {
|
||||
console.error(`Failed to update mutation ${id}:`, error.message);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process mutations in batches
|
||||
*/
|
||||
async function backfillMutations() {
|
||||
const startTime = Date.now();
|
||||
console.log('Starting backfill process...\n');
|
||||
|
||||
// Fetch mutations
|
||||
const mutations = await fetchMutationsToBackfill();
|
||||
|
||||
if (mutations.length === 0) {
|
||||
console.log('No mutations need backfilling. All done!');
|
||||
return;
|
||||
}
|
||||
|
||||
let processedCount = 0;
|
||||
let successCount = 0;
|
||||
let errorCount = 0;
|
||||
const errors: Array<{ id: string; error: string }> = [];
|
||||
|
||||
// Process each mutation
|
||||
for (const mutation of mutations) {
|
||||
try {
|
||||
// Generate structural hashes
|
||||
const structureHashBefore = generateStructuralHash(mutation.workflow_before);
|
||||
const structureHashAfter = generateStructuralHash(mutation.workflow_after);
|
||||
|
||||
if (!structureHashBefore || !structureHashAfter) {
|
||||
console.warn(`Skipping mutation ${mutation.id}: Failed to generate hashes`);
|
||||
errors.push({ id: mutation.id, error: 'Failed to generate hashes' });
|
||||
errorCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update database
|
||||
const success = await updateMutation(mutation.id, structureHashBefore, structureHashAfter);
|
||||
|
||||
if (success) {
|
||||
successCount++;
|
||||
} else {
|
||||
errorCount++;
|
||||
errors.push({ id: mutation.id, error: 'Database update failed' });
|
||||
}
|
||||
|
||||
processedCount++;
|
||||
|
||||
// Progress update every 100 mutations
|
||||
if (processedCount % 100 === 0) {
|
||||
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
||||
const rate = (processedCount / (Date.now() - startTime) * 1000).toFixed(1);
|
||||
console.log(
|
||||
`Progress: ${processedCount}/${mutations.length} (${((processedCount / mutations.length) * 100).toFixed(1)}%) | ` +
|
||||
`Success: ${successCount} | Errors: ${errorCount} | Rate: ${rate}/s | Elapsed: ${elapsed}s`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Unexpected error processing mutation ${mutation.id}:`, error);
|
||||
errors.push({ id: mutation.id, error: String(error) });
|
||||
errorCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Final summary
|
||||
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
|
||||
console.log('\n' + '='.repeat(80));
|
||||
console.log('BACKFILL COMPLETE');
|
||||
console.log('='.repeat(80));
|
||||
console.log(`Total mutations processed: ${processedCount}`);
|
||||
console.log(`Successfully updated: ${successCount}`);
|
||||
console.log(`Errors: ${errorCount}`);
|
||||
console.log(`Duration: ${duration}s`);
|
||||
console.log(`Average rate: ${(processedCount / (Date.now() - startTime) * 1000).toFixed(1)} mutations/s`);
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.log('\nErrors encountered:');
|
||||
errors.slice(0, 10).forEach(({ id, error }) => {
|
||||
console.log(` - ${id}: ${error}`);
|
||||
});
|
||||
if (errors.length > 10) {
|
||||
console.log(` ... and ${errors.length - 10} more errors`);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify cross-reference matches
|
||||
console.log('\n' + '='.repeat(80));
|
||||
console.log('VERIFYING CROSS-REFERENCE MATCHES');
|
||||
console.log('='.repeat(80));
|
||||
|
||||
const { data: statsData, error: statsError } = await supabase.rpc('get_mutation_crossref_stats');
|
||||
|
||||
if (statsError) {
|
||||
console.error('Failed to get cross-reference stats:', statsError.message);
|
||||
} else if (statsData && statsData.length > 0) {
|
||||
const stats = statsData[0];
|
||||
console.log(`Total mutations: ${stats.total_mutations}`);
|
||||
console.log(`Before matches: ${stats.before_matches} (${stats.before_match_rate}%)`);
|
||||
console.log(`After matches: ${stats.after_matches} (${stats.after_match_rate}%)`);
|
||||
console.log(`Both matches: ${stats.both_matches}`);
|
||||
}
|
||||
|
||||
console.log('\nBackfill process completed successfully! ✓');
|
||||
}
|
||||
|
||||
// Run the backfill
|
||||
backfillMutations().catch((error) => {
|
||||
console.error('Fatal error during backfill:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,45 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Generate release notes for the initial release
|
||||
* Used by GitHub Actions when no previous tag exists
|
||||
*/
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
function generateInitialReleaseNotes(version) {
|
||||
try {
|
||||
// Get total commit count
|
||||
const commitCount = execSync('git rev-list --count HEAD', { encoding: 'utf8' }).trim();
|
||||
|
||||
// Generate release notes
|
||||
const releaseNotes = [
|
||||
'### 🎉 Initial Release',
|
||||
'',
|
||||
`This is the initial release of n8n-mcp v${version}.`,
|
||||
'',
|
||||
'---',
|
||||
'',
|
||||
'**Release Statistics:**',
|
||||
`- Commit count: ${commitCount}`,
|
||||
'- First release setup'
|
||||
];
|
||||
|
||||
return releaseNotes.join('\n');
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error generating initial release notes: ${error.message}`);
|
||||
return `Failed to generate initial release notes: ${error.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse command line arguments
|
||||
const version = process.argv[2];
|
||||
|
||||
if (!version) {
|
||||
console.error('Usage: generate-initial-release-notes.js <version>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const releaseNotes = generateInitialReleaseNotes(version);
|
||||
console.log(releaseNotes);
|
||||
@@ -1,121 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Generate release notes from commit messages between two tags
|
||||
* Used by GitHub Actions to create automated release notes
|
||||
*/
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function generateReleaseNotes(previousTag, currentTag) {
|
||||
try {
|
||||
console.log(`Generating release notes from ${previousTag} to ${currentTag}`);
|
||||
|
||||
// Get commits between tags
|
||||
const gitLogCommand = `git log --pretty=format:"%H|%s|%an|%ae|%ad" --date=short --no-merges ${previousTag}..${currentTag}`;
|
||||
const commitsOutput = execSync(gitLogCommand, { encoding: 'utf8' });
|
||||
|
||||
if (!commitsOutput.trim()) {
|
||||
console.log('No commits found between tags');
|
||||
return 'No changes in this release.';
|
||||
}
|
||||
|
||||
const commits = commitsOutput.trim().split('\n').map(line => {
|
||||
const [hash, subject, author, email, date] = line.split('|');
|
||||
return { hash, subject, author, email, date };
|
||||
});
|
||||
|
||||
// Categorize commits
|
||||
const categories = {
|
||||
'feat': { title: '✨ Features', commits: [] },
|
||||
'fix': { title: '🐛 Bug Fixes', commits: [] },
|
||||
'docs': { title: '📚 Documentation', commits: [] },
|
||||
'refactor': { title: '♻️ Refactoring', commits: [] },
|
||||
'test': { title: '🧪 Testing', commits: [] },
|
||||
'perf': { title: '⚡ Performance', commits: [] },
|
||||
'style': { title: '💅 Styling', commits: [] },
|
||||
'ci': { title: '🔧 CI/CD', commits: [] },
|
||||
'build': { title: '📦 Build', commits: [] },
|
||||
'chore': { title: '🔧 Maintenance', commits: [] },
|
||||
'other': { title: '📝 Other Changes', commits: [] }
|
||||
};
|
||||
|
||||
commits.forEach(commit => {
|
||||
const subject = commit.subject.toLowerCase();
|
||||
let categorized = false;
|
||||
|
||||
// Check for conventional commit prefixes
|
||||
for (const [prefix, category] of Object.entries(categories)) {
|
||||
if (prefix !== 'other' && subject.startsWith(`${prefix}:`)) {
|
||||
category.commits.push(commit);
|
||||
categorized = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If not categorized, put in other
|
||||
if (!categorized) {
|
||||
categories.other.commits.push(commit);
|
||||
}
|
||||
});
|
||||
|
||||
// Generate release notes
|
||||
const releaseNotes = [];
|
||||
|
||||
for (const [key, category] of Object.entries(categories)) {
|
||||
if (category.commits.length > 0) {
|
||||
releaseNotes.push(`### ${category.title}`);
|
||||
releaseNotes.push('');
|
||||
|
||||
category.commits.forEach(commit => {
|
||||
// Clean up the subject by removing the prefix if it exists
|
||||
let cleanSubject = commit.subject;
|
||||
const colonIndex = cleanSubject.indexOf(':');
|
||||
if (colonIndex !== -1 && cleanSubject.substring(0, colonIndex).match(/^(feat|fix|docs|refactor|test|perf|style|ci|build|chore)$/)) {
|
||||
cleanSubject = cleanSubject.substring(colonIndex + 1).trim();
|
||||
// Capitalize first letter
|
||||
cleanSubject = cleanSubject.charAt(0).toUpperCase() + cleanSubject.slice(1);
|
||||
}
|
||||
|
||||
releaseNotes.push(`- ${cleanSubject} (${commit.hash.substring(0, 7)})`);
|
||||
});
|
||||
|
||||
releaseNotes.push('');
|
||||
}
|
||||
}
|
||||
|
||||
// Add commit statistics
|
||||
const totalCommits = commits.length;
|
||||
const contributors = [...new Set(commits.map(c => c.author))];
|
||||
|
||||
releaseNotes.push('---');
|
||||
releaseNotes.push('');
|
||||
releaseNotes.push(`**Release Statistics:**`);
|
||||
releaseNotes.push(`- ${totalCommits} commit${totalCommits !== 1 ? 's' : ''}`);
|
||||
releaseNotes.push(`- ${contributors.length} contributor${contributors.length !== 1 ? 's' : ''}`);
|
||||
|
||||
if (contributors.length <= 5) {
|
||||
releaseNotes.push(`- Contributors: ${contributors.join(', ')}`);
|
||||
}
|
||||
|
||||
return releaseNotes.join('\n');
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error generating release notes: ${error.message}`);
|
||||
return `Failed to generate release notes: ${error.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse command line arguments
|
||||
const previousTag = process.argv[2];
|
||||
const currentTag = process.argv[3];
|
||||
|
||||
if (!previousTag || !currentTag) {
|
||||
console.error('Usage: generate-release-notes.js <previous-tag> <current-tag>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const releaseNotes = generateReleaseNotes(previousTag, currentTag);
|
||||
console.log(releaseNotes);
|
||||
@@ -1,99 +0,0 @@
|
||||
#!/usr/bin/env ts-node
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { createDatabaseAdapter } from '../src/database/database-adapter';
|
||||
|
||||
interface BatchResponse {
|
||||
id: string;
|
||||
custom_id: string;
|
||||
response: {
|
||||
status_code: number;
|
||||
body: {
|
||||
choices: Array<{
|
||||
message: {
|
||||
content: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
};
|
||||
error: any;
|
||||
}
|
||||
|
||||
async function processBatchMetadata(batchFile: string) {
|
||||
console.log(`📥 Processing batch file: ${batchFile}`);
|
||||
|
||||
// Read the JSONL file
|
||||
const content = fs.readFileSync(batchFile, 'utf-8');
|
||||
const lines = content.trim().split('\n');
|
||||
|
||||
console.log(`📊 Found ${lines.length} batch responses`);
|
||||
|
||||
// Initialize database
|
||||
const db = await createDatabaseAdapter('./data/nodes.db');
|
||||
|
||||
let updated = 0;
|
||||
let skipped = 0;
|
||||
let errors = 0;
|
||||
|
||||
for (const line of lines) {
|
||||
try {
|
||||
const response: BatchResponse = JSON.parse(line);
|
||||
|
||||
// Extract template ID from custom_id (format: "template-9100")
|
||||
const templateId = parseInt(response.custom_id.replace('template-', ''));
|
||||
|
||||
// Check for errors
|
||||
if (response.error || response.response.status_code !== 200) {
|
||||
console.warn(`⚠️ Template ${templateId}: API error`, response.error);
|
||||
errors++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract metadata from response
|
||||
const metadataJson = response.response.body.choices[0].message.content;
|
||||
|
||||
// Validate it's valid JSON
|
||||
JSON.parse(metadataJson); // Will throw if invalid
|
||||
|
||||
// Update database
|
||||
const stmt = db.prepare(`
|
||||
UPDATE templates
|
||||
SET metadata_json = ?
|
||||
WHERE id = ?
|
||||
`);
|
||||
|
||||
stmt.run(metadataJson, templateId);
|
||||
updated++;
|
||||
|
||||
console.log(`✅ Template ${templateId}: Updated metadata`);
|
||||
|
||||
} catch (error: any) {
|
||||
console.error(`❌ Error processing line:`, error.message);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
// Close database
|
||||
if ('close' in db && typeof db.close === 'function') {
|
||||
db.close();
|
||||
}
|
||||
|
||||
console.log(`\n📈 Summary:`);
|
||||
console.log(` - Updated: ${updated}`);
|
||||
console.log(` - Skipped: ${skipped}`);
|
||||
console.log(` - Errors: ${errors}`);
|
||||
console.log(` - Total: ${lines.length}`);
|
||||
}
|
||||
|
||||
// Main
|
||||
const batchFile = process.argv[2] || '/Users/romualdczlonkowski/Pliki/n8n-mcp/n8n-mcp/docs/batch_68fff7242850819091cfed64f10fb6b4_output.jsonl';
|
||||
|
||||
processBatchMetadata(batchFile)
|
||||
.then(() => {
|
||||
console.log('\n✅ Batch processing complete!');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('\n❌ Batch processing failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,470 +0,0 @@
|
||||
#!/usr/bin/env ts-node
|
||||
/**
|
||||
* Phase 3: Real-World Type Structure Validation
|
||||
*
|
||||
* Tests type structure validation against real workflow templates from n8n.io
|
||||
* to ensure production readiness. Validates filter, resourceMapper,
|
||||
* assignmentCollection, and resourceLocator types.
|
||||
*
|
||||
* Usage:
|
||||
* npm run build && node dist/scripts/test-structure-validation.js
|
||||
*
|
||||
* or with ts-node:
|
||||
* npx ts-node scripts/test-structure-validation.ts
|
||||
*/
|
||||
|
||||
import { createDatabaseAdapter } from '../src/database/database-adapter';
|
||||
import { EnhancedConfigValidator } from '../src/services/enhanced-config-validator';
|
||||
import type { NodePropertyTypes } from 'n8n-workflow';
|
||||
import { gunzipSync } from 'zlib';
|
||||
|
||||
interface ValidationResult {
|
||||
templateId: number;
|
||||
templateName: string;
|
||||
templateViews: number;
|
||||
nodeId: string;
|
||||
nodeName: string;
|
||||
nodeType: string;
|
||||
propertyName: string;
|
||||
propertyType: NodePropertyTypes;
|
||||
valid: boolean;
|
||||
errors: Array<{ type: string; property?: string; message: string }>;
|
||||
warnings: Array<{ type: string; property?: string; message: string }>;
|
||||
validationTimeMs: number;
|
||||
}
|
||||
|
||||
interface ValidationStats {
|
||||
totalTemplates: number;
|
||||
totalNodes: number;
|
||||
totalValidations: number;
|
||||
passedValidations: number;
|
||||
failedValidations: number;
|
||||
byType: Record<string, { passed: number; failed: number }>;
|
||||
byError: Record<string, number>;
|
||||
avgValidationTimeMs: number;
|
||||
maxValidationTimeMs: number;
|
||||
}
|
||||
|
||||
// Special types we want to validate
|
||||
const SPECIAL_TYPES: NodePropertyTypes[] = [
|
||||
'filter',
|
||||
'resourceMapper',
|
||||
'assignmentCollection',
|
||||
'resourceLocator',
|
||||
];
|
||||
|
||||
function decompressWorkflow(compressed: string): any {
|
||||
try {
|
||||
const buffer = Buffer.from(compressed, 'base64');
|
||||
const decompressed = gunzipSync(buffer);
|
||||
return JSON.parse(decompressed.toString('utf-8'));
|
||||
} catch (error: any) {
|
||||
throw new Error(`Failed to decompress workflow: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTopTemplates(db: any, limit: number = 100) {
|
||||
console.log(`📥 Loading top ${limit} templates by popularity...\n`);
|
||||
|
||||
const stmt = db.prepare(`
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
workflow_json_compressed,
|
||||
views
|
||||
FROM templates
|
||||
WHERE workflow_json_compressed IS NOT NULL
|
||||
ORDER BY views DESC
|
||||
LIMIT ?
|
||||
`);
|
||||
|
||||
const templates = stmt.all(limit);
|
||||
console.log(`✓ Loaded ${templates.length} templates\n`);
|
||||
|
||||
return templates;
|
||||
}
|
||||
|
||||
function extractNodesWithSpecialTypes(workflowJson: any): Array<{
|
||||
nodeId: string;
|
||||
nodeName: string;
|
||||
nodeType: string;
|
||||
properties: Array<{ name: string; type: NodePropertyTypes; value: any }>;
|
||||
}> {
|
||||
const results: Array<any> = [];
|
||||
|
||||
if (!workflowJson || !workflowJson.nodes || !Array.isArray(workflowJson.nodes)) {
|
||||
return results;
|
||||
}
|
||||
|
||||
for (const node of workflowJson.nodes) {
|
||||
// Check if node has parameters with special types
|
||||
if (!node.parameters || typeof node.parameters !== 'object') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const specialProperties: Array<{ name: string; type: NodePropertyTypes; value: any }> = [];
|
||||
|
||||
// Check each parameter against our special types
|
||||
for (const [paramName, paramValue] of Object.entries(node.parameters)) {
|
||||
// Try to infer type from structure
|
||||
const inferredType = inferPropertyType(paramValue);
|
||||
|
||||
if (inferredType && SPECIAL_TYPES.includes(inferredType)) {
|
||||
specialProperties.push({
|
||||
name: paramName,
|
||||
type: inferredType,
|
||||
value: paramValue,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (specialProperties.length > 0) {
|
||||
results.push({
|
||||
nodeId: node.id,
|
||||
nodeName: node.name,
|
||||
nodeType: node.type,
|
||||
properties: specialProperties,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function inferPropertyType(value: any): NodePropertyTypes | null {
|
||||
if (!value || typeof value !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Filter type: has combinator and conditions
|
||||
if (value.combinator && value.conditions) {
|
||||
return 'filter';
|
||||
}
|
||||
|
||||
// ResourceMapper type: has mappingMode
|
||||
if (value.mappingMode) {
|
||||
return 'resourceMapper';
|
||||
}
|
||||
|
||||
// AssignmentCollection type: has assignments array
|
||||
if (value.assignments && Array.isArray(value.assignments)) {
|
||||
return 'assignmentCollection';
|
||||
}
|
||||
|
||||
// ResourceLocator type: has mode and value
|
||||
if (value.mode && value.hasOwnProperty('value')) {
|
||||
return 'resourceLocator';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function validateTemplate(
|
||||
templateId: number,
|
||||
templateName: string,
|
||||
templateViews: number,
|
||||
workflowJson: any
|
||||
): Promise<ValidationResult[]> {
|
||||
const results: ValidationResult[] = [];
|
||||
|
||||
// Extract nodes with special types
|
||||
const nodesWithSpecialTypes = extractNodesWithSpecialTypes(workflowJson);
|
||||
|
||||
for (const node of nodesWithSpecialTypes) {
|
||||
for (const prop of node.properties) {
|
||||
const startTime = Date.now();
|
||||
|
||||
// Create property definition for validation
|
||||
const properties = [
|
||||
{
|
||||
name: prop.name,
|
||||
type: prop.type,
|
||||
required: true,
|
||||
displayName: prop.name,
|
||||
default: {},
|
||||
},
|
||||
];
|
||||
|
||||
// Create config with just this property
|
||||
const config = {
|
||||
[prop.name]: prop.value,
|
||||
};
|
||||
|
||||
try {
|
||||
// Run validation
|
||||
const validationResult = EnhancedConfigValidator.validateWithMode(
|
||||
node.nodeType,
|
||||
config,
|
||||
properties,
|
||||
'operation',
|
||||
'ai-friendly'
|
||||
);
|
||||
|
||||
const validationTimeMs = Date.now() - startTime;
|
||||
|
||||
results.push({
|
||||
templateId,
|
||||
templateName,
|
||||
templateViews,
|
||||
nodeId: node.nodeId,
|
||||
nodeName: node.nodeName,
|
||||
nodeType: node.nodeType,
|
||||
propertyName: prop.name,
|
||||
propertyType: prop.type,
|
||||
valid: validationResult.valid,
|
||||
errors: validationResult.errors || [],
|
||||
warnings: validationResult.warnings || [],
|
||||
validationTimeMs,
|
||||
});
|
||||
} catch (error: any) {
|
||||
const validationTimeMs = Date.now() - startTime;
|
||||
|
||||
results.push({
|
||||
templateId,
|
||||
templateName,
|
||||
templateViews,
|
||||
nodeId: node.nodeId,
|
||||
nodeName: node.nodeName,
|
||||
nodeType: node.nodeType,
|
||||
propertyName: prop.name,
|
||||
propertyType: prop.type,
|
||||
valid: false,
|
||||
errors: [
|
||||
{
|
||||
type: 'exception',
|
||||
property: prop.name,
|
||||
message: `Validation threw exception: ${error.message}`,
|
||||
},
|
||||
],
|
||||
warnings: [],
|
||||
validationTimeMs,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function calculateStats(results: ValidationResult[]): ValidationStats {
|
||||
const stats: ValidationStats = {
|
||||
totalTemplates: new Set(results.map(r => r.templateId)).size,
|
||||
totalNodes: new Set(results.map(r => `${r.templateId}-${r.nodeId}`)).size,
|
||||
totalValidations: results.length,
|
||||
passedValidations: results.filter(r => r.valid).length,
|
||||
failedValidations: results.filter(r => !r.valid).length,
|
||||
byType: {},
|
||||
byError: {},
|
||||
avgValidationTimeMs: 0,
|
||||
maxValidationTimeMs: 0,
|
||||
};
|
||||
|
||||
// Stats by type
|
||||
for (const type of SPECIAL_TYPES) {
|
||||
const typeResults = results.filter(r => r.propertyType === type);
|
||||
stats.byType[type] = {
|
||||
passed: typeResults.filter(r => r.valid).length,
|
||||
failed: typeResults.filter(r => !r.valid).length,
|
||||
};
|
||||
}
|
||||
|
||||
// Error frequency
|
||||
for (const result of results.filter(r => !r.valid)) {
|
||||
for (const error of result.errors) {
|
||||
const key = `${error.type}: ${error.message}`;
|
||||
stats.byError[key] = (stats.byError[key] || 0) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Performance stats
|
||||
if (results.length > 0) {
|
||||
stats.avgValidationTimeMs =
|
||||
results.reduce((sum, r) => sum + r.validationTimeMs, 0) / results.length;
|
||||
stats.maxValidationTimeMs = Math.max(...results.map(r => r.validationTimeMs));
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
function printStats(stats: ValidationStats) {
|
||||
console.log('\n' + '='.repeat(80));
|
||||
console.log('VALIDATION STATISTICS');
|
||||
console.log('='.repeat(80) + '\n');
|
||||
|
||||
console.log(`📊 Total Templates Tested: ${stats.totalTemplates}`);
|
||||
console.log(`📊 Total Nodes with Special Types: ${stats.totalNodes}`);
|
||||
console.log(`📊 Total Property Validations: ${stats.totalValidations}\n`);
|
||||
|
||||
const passRate = (stats.passedValidations / stats.totalValidations * 100).toFixed(2);
|
||||
const failRate = (stats.failedValidations / stats.totalValidations * 100).toFixed(2);
|
||||
|
||||
console.log(`✅ Passed: ${stats.passedValidations} (${passRate}%)`);
|
||||
console.log(`❌ Failed: ${stats.failedValidations} (${failRate}%)\n`);
|
||||
|
||||
console.log('By Property Type:');
|
||||
console.log('-'.repeat(80));
|
||||
for (const [type, counts] of Object.entries(stats.byType)) {
|
||||
const total = counts.passed + counts.failed;
|
||||
if (total === 0) {
|
||||
console.log(` ${type}: No occurrences found`);
|
||||
} else {
|
||||
const typePassRate = (counts.passed / total * 100).toFixed(2);
|
||||
console.log(` ${type}: ${counts.passed}/${total} passed (${typePassRate}%)`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n⚡ Performance:');
|
||||
console.log('-'.repeat(80));
|
||||
console.log(` Average validation time: ${stats.avgValidationTimeMs.toFixed(2)}ms`);
|
||||
console.log(` Maximum validation time: ${stats.maxValidationTimeMs.toFixed(2)}ms`);
|
||||
|
||||
const meetsTarget = stats.avgValidationTimeMs < 50;
|
||||
console.log(` Target (<50ms): ${meetsTarget ? '✅ MET' : '❌ NOT MET'}\n`);
|
||||
|
||||
if (Object.keys(stats.byError).length > 0) {
|
||||
console.log('🔍 Most Common Errors:');
|
||||
console.log('-'.repeat(80));
|
||||
|
||||
const sortedErrors = Object.entries(stats.byError)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, 10);
|
||||
|
||||
for (const [error, count] of sortedErrors) {
|
||||
console.log(` ${count}x: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function printFailures(results: ValidationResult[], maxFailures: number = 20) {
|
||||
const failures = results.filter(r => !r.valid);
|
||||
|
||||
if (failures.length === 0) {
|
||||
console.log('\n✨ No failures! All validations passed.\n');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(80));
|
||||
console.log(`VALIDATION FAILURES (showing first ${Math.min(maxFailures, failures.length)})` );
|
||||
console.log('='.repeat(80) + '\n');
|
||||
|
||||
for (let i = 0; i < Math.min(maxFailures, failures.length); i++) {
|
||||
const failure = failures[i];
|
||||
|
||||
console.log(`Failure ${i + 1}/${failures.length}:`);
|
||||
console.log(` Template: ${failure.templateName} (ID: ${failure.templateId}, Views: ${failure.templateViews})`);
|
||||
console.log(` Node: ${failure.nodeName} (${failure.nodeType})`);
|
||||
console.log(` Property: ${failure.propertyName} (type: ${failure.propertyType})`);
|
||||
console.log(` Errors:`);
|
||||
|
||||
for (const error of failure.errors) {
|
||||
console.log(` - [${error.type}] ${error.property}: ${error.message}`);
|
||||
}
|
||||
|
||||
if (failure.warnings.length > 0) {
|
||||
console.log(` Warnings:`);
|
||||
for (const warning of failure.warnings) {
|
||||
console.log(` - [${warning.type}] ${warning.property}: ${warning.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('');
|
||||
}
|
||||
|
||||
if (failures.length > maxFailures) {
|
||||
console.log(`... and ${failures.length - maxFailures} more failures\n`);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('='.repeat(80));
|
||||
console.log('PHASE 3: REAL-WORLD TYPE STRUCTURE VALIDATION');
|
||||
console.log('='.repeat(80) + '\n');
|
||||
|
||||
// Initialize database
|
||||
console.log('🔌 Connecting to database...');
|
||||
const db = await createDatabaseAdapter('./data/nodes.db');
|
||||
console.log('✓ Database connected\n');
|
||||
|
||||
// Load templates
|
||||
const templates = await loadTopTemplates(db, 100);
|
||||
|
||||
// Validate each template
|
||||
console.log('🔍 Validating templates...\n');
|
||||
|
||||
const allResults: ValidationResult[] = [];
|
||||
let processedCount = 0;
|
||||
let nodesFound = 0;
|
||||
|
||||
for (const template of templates) {
|
||||
processedCount++;
|
||||
|
||||
let workflowJson;
|
||||
try {
|
||||
workflowJson = decompressWorkflow(template.workflow_json_compressed);
|
||||
} catch (error) {
|
||||
console.warn(`⚠️ Template ${template.id}: Decompression failed, skipping`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const results = await validateTemplate(
|
||||
template.id,
|
||||
template.name,
|
||||
template.views,
|
||||
workflowJson
|
||||
);
|
||||
|
||||
if (results.length > 0) {
|
||||
nodesFound += new Set(results.map(r => r.nodeId)).size;
|
||||
allResults.push(...results);
|
||||
|
||||
const passedCount = results.filter(r => r.valid).length;
|
||||
const status = passedCount === results.length ? '✓' : '✗';
|
||||
console.log(
|
||||
`${status} Template ${processedCount}/${templates.length}: ` +
|
||||
`"${template.name}" (${results.length} validations, ${passedCount} passed)`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n✓ Processed ${processedCount} templates`);
|
||||
console.log(`✓ Found ${nodesFound} nodes with special types\n`);
|
||||
|
||||
// Calculate and print statistics
|
||||
const stats = calculateStats(allResults);
|
||||
printStats(stats);
|
||||
|
||||
// Print detailed failures
|
||||
printFailures(allResults);
|
||||
|
||||
// Success criteria check
|
||||
console.log('='.repeat(80));
|
||||
console.log('SUCCESS CRITERIA CHECK');
|
||||
console.log('='.repeat(80) + '\n');
|
||||
|
||||
const passRate = (stats.passedValidations / stats.totalValidations * 100);
|
||||
const falsePositiveRate = (stats.failedValidations / stats.totalValidations * 100);
|
||||
const avgTime = stats.avgValidationTimeMs;
|
||||
|
||||
console.log(`Pass Rate: ${passRate.toFixed(2)}% (target: >95%) ${passRate > 95 ? '✅' : '❌'}`);
|
||||
console.log(`False Positive Rate: ${falsePositiveRate.toFixed(2)}% (target: <5%) ${falsePositiveRate < 5 ? '✅' : '❌'}`);
|
||||
console.log(`Avg Validation Time: ${avgTime.toFixed(2)}ms (target: <50ms) ${avgTime < 50 ? '✅' : '❌'}\n`);
|
||||
|
||||
const allCriteriaMet = passRate > 95 && falsePositiveRate < 5 && avgTime < 50;
|
||||
|
||||
if (allCriteriaMet) {
|
||||
console.log('🎉 ALL SUCCESS CRITERIA MET! Phase 3 validation complete.\n');
|
||||
} else {
|
||||
console.log('⚠️ Some success criteria not met. Iteration required.\n');
|
||||
}
|
||||
|
||||
// Close database
|
||||
db.close();
|
||||
|
||||
process.exit(allCriteriaMet ? 0 : 1);
|
||||
}
|
||||
|
||||
// Run the script
|
||||
main().catch((error) => {
|
||||
console.error('Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,287 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Test Workflow Versioning System
|
||||
*
|
||||
* Tests the complete workflow rollback and versioning functionality:
|
||||
* - Automatic backup creation
|
||||
* - Auto-pruning to 10 versions
|
||||
* - Version history retrieval
|
||||
* - Rollback with validation
|
||||
* - Manual pruning and cleanup
|
||||
* - Storage statistics
|
||||
*/
|
||||
|
||||
import { NodeRepository } from '../src/database/node-repository';
|
||||
import { createDatabaseAdapter } from '../src/database/database-adapter';
|
||||
import { WorkflowVersioningService } from '../src/services/workflow-versioning-service';
|
||||
import { logger } from '../src/utils/logger';
|
||||
import { existsSync } from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
// Mock workflow for testing
|
||||
const createMockWorkflow = (id: string, name: string, nodeCount: number = 3) => ({
|
||||
id,
|
||||
name,
|
||||
active: false,
|
||||
nodes: Array.from({ length: nodeCount }, (_, i) => ({
|
||||
id: `node-${i}`,
|
||||
name: `Node ${i}`,
|
||||
type: 'n8n-nodes-base.set',
|
||||
typeVersion: 1,
|
||||
position: [250 + i * 200, 300],
|
||||
parameters: { values: { string: [{ name: `field${i}`, value: `value${i}` }] } }
|
||||
})),
|
||||
connections: nodeCount > 1 ? {
|
||||
'node-0': { main: [[{ node: 'node-1', type: 'main', index: 0 }]] },
|
||||
...(nodeCount > 2 && { 'node-1': { main: [[{ node: 'node-2', type: 'main', index: 0 }]] } })
|
||||
} : {},
|
||||
settings: {}
|
||||
});
|
||||
|
||||
async function runTests() {
|
||||
console.log('🧪 Testing Workflow Versioning System\n');
|
||||
|
||||
// Find database path
|
||||
const possiblePaths = [
|
||||
path.join(process.cwd(), 'data', 'nodes.db'),
|
||||
path.join(__dirname, '../../data', 'nodes.db'),
|
||||
'./data/nodes.db'
|
||||
];
|
||||
|
||||
let dbPath: string | null = null;
|
||||
for (const p of possiblePaths) {
|
||||
if (existsSync(p)) {
|
||||
dbPath = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dbPath) {
|
||||
console.error('❌ Database not found. Please run npm run rebuild first.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`📁 Using database: ${dbPath}\n`);
|
||||
|
||||
// Initialize repository
|
||||
const db = await createDatabaseAdapter(dbPath);
|
||||
const repository = new NodeRepository(db);
|
||||
const service = new WorkflowVersioningService(repository);
|
||||
|
||||
const workflowId = 'test-workflow-001';
|
||||
let testsPassed = 0;
|
||||
let testsFailed = 0;
|
||||
|
||||
try {
|
||||
// Test 1: Create initial backup
|
||||
console.log('📝 Test 1: Create initial backup');
|
||||
const workflow1 = createMockWorkflow(workflowId, 'Test Workflow v1', 3);
|
||||
const backup1 = await service.createBackup(workflowId, workflow1, {
|
||||
trigger: 'partial_update',
|
||||
operations: [{ type: 'addNode', node: workflow1.nodes[0] }]
|
||||
});
|
||||
|
||||
if (backup1.versionId && backup1.versionNumber === 1 && backup1.pruned === 0) {
|
||||
console.log('✅ Initial backup created successfully');
|
||||
console.log(` Version ID: ${backup1.versionId}, Version Number: ${backup1.versionNumber}`);
|
||||
testsPassed++;
|
||||
} else {
|
||||
console.log('❌ Failed to create initial backup');
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 2: Create multiple backups to test auto-pruning
|
||||
console.log('\n📝 Test 2: Create 12 backups to test auto-pruning (should keep only 10)');
|
||||
for (let i = 2; i <= 12; i++) {
|
||||
const workflow = createMockWorkflow(workflowId, `Test Workflow v${i}`, 3 + i);
|
||||
await service.createBackup(workflowId, workflow, {
|
||||
trigger: i % 3 === 0 ? 'full_update' : 'partial_update',
|
||||
operations: [{ type: 'addNode', node: { id: `node-${i}` } }]
|
||||
});
|
||||
}
|
||||
|
||||
const versions = await service.getVersionHistory(workflowId, 100);
|
||||
if (versions.length === 10) {
|
||||
console.log(`✅ Auto-pruning works correctly (kept exactly 10 versions)`);
|
||||
console.log(` Latest version: ${versions[0].versionNumber}, Oldest: ${versions[9].versionNumber}`);
|
||||
testsPassed++;
|
||||
} else {
|
||||
console.log(`❌ Auto-pruning failed (expected 10 versions, got ${versions.length})`);
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 3: Get version history
|
||||
console.log('\n📝 Test 3: Get version history');
|
||||
const history = await service.getVersionHistory(workflowId, 5);
|
||||
if (history.length === 5 && history[0].versionNumber > history[4].versionNumber) {
|
||||
console.log(`✅ Version history retrieved successfully (${history.length} versions)`);
|
||||
console.log(' Recent versions:');
|
||||
history.forEach(v => {
|
||||
console.log(` - v${v.versionNumber} (${v.trigger}) - ${v.workflowName} - ${(v.size / 1024).toFixed(2)} KB`);
|
||||
});
|
||||
testsPassed++;
|
||||
} else {
|
||||
console.log('❌ Failed to get version history');
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 4: Get specific version
|
||||
console.log('\n📝 Test 4: Get specific version details');
|
||||
const specificVersion = await service.getVersion(history[2].id);
|
||||
if (specificVersion && specificVersion.workflowSnapshot) {
|
||||
console.log(`✅ Retrieved version ${specificVersion.versionNumber} successfully`);
|
||||
console.log(` Workflow name: ${specificVersion.workflowName}`);
|
||||
console.log(` Node count: ${specificVersion.workflowSnapshot.nodes.length}`);
|
||||
console.log(` Trigger: ${specificVersion.trigger}`);
|
||||
testsPassed++;
|
||||
} else {
|
||||
console.log('❌ Failed to get specific version');
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 5: Compare two versions
|
||||
console.log('\n📝 Test 5: Compare two versions');
|
||||
if (history.length >= 2) {
|
||||
const diff = await service.compareVersions(history[0].id, history[1].id);
|
||||
console.log(`✅ Version comparison successful`);
|
||||
console.log(` Comparing v${diff.version1Number} → v${diff.version2Number}`);
|
||||
console.log(` Added nodes: ${diff.addedNodes.length}`);
|
||||
console.log(` Removed nodes: ${diff.removedNodes.length}`);
|
||||
console.log(` Modified nodes: ${diff.modifiedNodes.length}`);
|
||||
console.log(` Connection changes: ${diff.connectionChanges}`);
|
||||
testsPassed++;
|
||||
} else {
|
||||
console.log('❌ Not enough versions to compare');
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 6: Manual pruning
|
||||
console.log('\n📝 Test 6: Manual pruning (keep only 5 versions)');
|
||||
const pruneResult = await service.pruneVersions(workflowId, 5);
|
||||
if (pruneResult.pruned === 5 && pruneResult.remaining === 5) {
|
||||
console.log(`✅ Manual pruning successful`);
|
||||
console.log(` Pruned: ${pruneResult.pruned} versions, Remaining: ${pruneResult.remaining}`);
|
||||
testsPassed++;
|
||||
} else {
|
||||
console.log(`❌ Manual pruning failed (expected 5 pruned, 5 remaining, got ${pruneResult.pruned} pruned, ${pruneResult.remaining} remaining)`);
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 7: Storage statistics
|
||||
console.log('\n📝 Test 7: Storage statistics');
|
||||
const stats = await service.getStorageStats();
|
||||
if (stats.totalVersions > 0 && stats.byWorkflow.length > 0) {
|
||||
console.log(`✅ Storage stats retrieved successfully`);
|
||||
console.log(` Total versions: ${stats.totalVersions}`);
|
||||
console.log(` Total size: ${stats.totalSizeFormatted}`);
|
||||
console.log(` Workflows with versions: ${stats.byWorkflow.length}`);
|
||||
stats.byWorkflow.forEach(w => {
|
||||
console.log(` - ${w.workflowName}: ${w.versionCount} versions, ${w.totalSizeFormatted}`);
|
||||
});
|
||||
testsPassed++;
|
||||
} else {
|
||||
console.log('❌ Failed to get storage stats');
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 8: Delete specific version
|
||||
console.log('\n📝 Test 8: Delete specific version');
|
||||
const versionsBeforeDelete = await service.getVersionHistory(workflowId, 100);
|
||||
const versionToDelete = versionsBeforeDelete[versionsBeforeDelete.length - 1];
|
||||
const deleteResult = await service.deleteVersion(versionToDelete.id);
|
||||
const versionsAfterDelete = await service.getVersionHistory(workflowId, 100);
|
||||
|
||||
if (deleteResult.success && versionsAfterDelete.length === versionsBeforeDelete.length - 1) {
|
||||
console.log(`✅ Version deletion successful`);
|
||||
console.log(` Deleted version ${versionToDelete.versionNumber}`);
|
||||
console.log(` Remaining versions: ${versionsAfterDelete.length}`);
|
||||
testsPassed++;
|
||||
} else {
|
||||
console.log('❌ Failed to delete version');
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 9: Test different trigger types
|
||||
console.log('\n📝 Test 9: Test different trigger types');
|
||||
const workflow2 = createMockWorkflow(workflowId, 'Test Workflow Autofix', 2);
|
||||
const backupAutofix = await service.createBackup(workflowId, workflow2, {
|
||||
trigger: 'autofix',
|
||||
fixTypes: ['expression-format', 'typeversion-correction']
|
||||
});
|
||||
|
||||
const workflow3 = createMockWorkflow(workflowId, 'Test Workflow Full Update', 4);
|
||||
const backupFull = await service.createBackup(workflowId, workflow3, {
|
||||
trigger: 'full_update',
|
||||
metadata: { reason: 'Major refactoring' }
|
||||
});
|
||||
|
||||
const allVersions = await service.getVersionHistory(workflowId, 100);
|
||||
const autofixVersions = allVersions.filter(v => v.trigger === 'autofix');
|
||||
const fullUpdateVersions = allVersions.filter(v => v.trigger === 'full_update');
|
||||
const partialUpdateVersions = allVersions.filter(v => v.trigger === 'partial_update');
|
||||
|
||||
if (autofixVersions.length > 0 && fullUpdateVersions.length > 0 && partialUpdateVersions.length > 0) {
|
||||
console.log(`✅ All trigger types working correctly`);
|
||||
console.log(` Partial updates: ${partialUpdateVersions.length}`);
|
||||
console.log(` Full updates: ${fullUpdateVersions.length}`);
|
||||
console.log(` Autofixes: ${autofixVersions.length}`);
|
||||
testsPassed++;
|
||||
} else {
|
||||
console.log('❌ Failed to create versions with different trigger types');
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 10: Cleanup - Delete all versions for workflow
|
||||
console.log('\n📝 Test 10: Delete all versions for workflow');
|
||||
const deleteAllResult = await service.deleteAllVersions(workflowId);
|
||||
const versionsAfterDeleteAll = await service.getVersionHistory(workflowId, 100);
|
||||
|
||||
if (deleteAllResult.deleted > 0 && versionsAfterDeleteAll.length === 0) {
|
||||
console.log(`✅ Delete all versions successful`);
|
||||
console.log(` Deleted ${deleteAllResult.deleted} versions`);
|
||||
testsPassed++;
|
||||
} else {
|
||||
console.log('❌ Failed to delete all versions');
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 11: Truncate all versions (requires confirmation)
|
||||
console.log('\n📝 Test 11: Test truncate without confirmation');
|
||||
const truncateResult1 = await service.truncateAllVersions(false);
|
||||
if (truncateResult1.deleted === 0 && truncateResult1.message.includes('not confirmed')) {
|
||||
console.log(`✅ Truncate safety check works (requires confirmation)`);
|
||||
testsPassed++;
|
||||
} else {
|
||||
console.log('❌ Truncate safety check failed');
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Summary
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log('📊 Test Summary');
|
||||
console.log('='.repeat(60));
|
||||
console.log(`✅ Passed: ${testsPassed}`);
|
||||
console.log(`❌ Failed: ${testsFailed}`);
|
||||
console.log(`📈 Success Rate: ${((testsPassed / (testsPassed + testsFailed)) * 100).toFixed(1)}%`);
|
||||
console.log('='.repeat(60));
|
||||
|
||||
if (testsFailed === 0) {
|
||||
console.log('\n🎉 All tests passed! Workflow versioning system is working correctly.');
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.log('\n⚠️ Some tests failed. Please review the implementation.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('\n❌ Test suite failed with error:', error.message);
|
||||
console.error(error.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run tests
|
||||
runTests().catch(error => {
|
||||
console.error('Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,741 +0,0 @@
|
||||
/**
|
||||
* Type Structure Constants
|
||||
*
|
||||
* Complete definitions for all n8n NodePropertyTypes.
|
||||
* These structures define the expected data format, JavaScript type,
|
||||
* validation rules, and examples for each property type.
|
||||
*
|
||||
* Based on n8n-workflow v1.120.3 NodePropertyTypes
|
||||
*
|
||||
* @module constants/type-structures
|
||||
* @since 2.23.0
|
||||
*/
|
||||
|
||||
import type { NodePropertyTypes } from 'n8n-workflow';
|
||||
import type { TypeStructure } from '../types/type-structures';
|
||||
|
||||
/**
|
||||
* Complete type structure definitions for all 22 NodePropertyTypes
|
||||
*
|
||||
* Each entry defines:
|
||||
* - type: Category (primitive/object/collection/special)
|
||||
* - jsType: Underlying JavaScript type
|
||||
* - description: What this type represents
|
||||
* - structure: Expected data shape (for complex types)
|
||||
* - example: Working example value
|
||||
* - validation: Type-specific validation rules
|
||||
*
|
||||
* @constant
|
||||
*/
|
||||
export const TYPE_STRUCTURES: Record<NodePropertyTypes, TypeStructure> = {
|
||||
// ============================================================================
|
||||
// PRIMITIVE TYPES - Simple JavaScript values
|
||||
// ============================================================================
|
||||
|
||||
string: {
|
||||
type: 'primitive',
|
||||
jsType: 'string',
|
||||
description: 'A text value that can contain any characters',
|
||||
example: 'Hello World',
|
||||
examples: ['', 'A simple text', '{{ $json.name }}', 'https://example.com'],
|
||||
validation: {
|
||||
allowEmpty: true,
|
||||
allowExpressions: true,
|
||||
},
|
||||
notes: ['Most common property type', 'Supports n8n expressions'],
|
||||
},
|
||||
|
||||
number: {
|
||||
type: 'primitive',
|
||||
jsType: 'number',
|
||||
description: 'A numeric value (integer or decimal)',
|
||||
example: 42,
|
||||
examples: [0, -10, 3.14, 100],
|
||||
validation: {
|
||||
allowEmpty: false,
|
||||
allowExpressions: true,
|
||||
},
|
||||
notes: ['Can be constrained with min/max in typeOptions'],
|
||||
},
|
||||
|
||||
boolean: {
|
||||
type: 'primitive',
|
||||
jsType: 'boolean',
|
||||
description: 'A true/false toggle value',
|
||||
example: true,
|
||||
examples: [true, false],
|
||||
validation: {
|
||||
allowEmpty: false,
|
||||
allowExpressions: false,
|
||||
},
|
||||
notes: ['Rendered as checkbox in n8n UI'],
|
||||
},
|
||||
|
||||
dateTime: {
|
||||
type: 'primitive',
|
||||
jsType: 'string',
|
||||
description: 'A date and time value in ISO 8601 format',
|
||||
example: '2024-01-20T10:30:00Z',
|
||||
examples: [
|
||||
'2024-01-20T10:30:00Z',
|
||||
'2024-01-20',
|
||||
'{{ $now }}',
|
||||
],
|
||||
validation: {
|
||||
allowEmpty: false,
|
||||
allowExpressions: true,
|
||||
pattern: '^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d{3})?Z?)?$',
|
||||
},
|
||||
notes: ['Accepts ISO 8601 format', 'Can use n8n date expressions'],
|
||||
},
|
||||
|
||||
color: {
|
||||
type: 'primitive',
|
||||
jsType: 'string',
|
||||
description: 'A color value in hex format',
|
||||
example: '#FF5733',
|
||||
examples: ['#FF5733', '#000000', '#FFFFFF', '{{ $json.color }}'],
|
||||
validation: {
|
||||
allowEmpty: false,
|
||||
allowExpressions: true,
|
||||
pattern: '^#[0-9A-Fa-f]{6}$',
|
||||
},
|
||||
notes: ['Must be 6-digit hex color', 'Rendered with color picker in UI'],
|
||||
},
|
||||
|
||||
json: {
|
||||
type: 'primitive',
|
||||
jsType: 'string',
|
||||
description: 'A JSON string that can be parsed into any structure',
|
||||
example: '{"key": "value", "nested": {"data": 123}}',
|
||||
examples: [
|
||||
'{}',
|
||||
'{"name": "John", "age": 30}',
|
||||
'[1, 2, 3]',
|
||||
'{{ $json }}',
|
||||
],
|
||||
validation: {
|
||||
allowEmpty: false,
|
||||
allowExpressions: true,
|
||||
},
|
||||
notes: ['Must be valid JSON when parsed', 'Often used for custom payloads'],
|
||||
},
|
||||
|
||||
// ============================================================================
|
||||
// OPTION TYPES - Selection from predefined choices
|
||||
// ============================================================================
|
||||
|
||||
options: {
|
||||
type: 'primitive',
|
||||
jsType: 'string',
|
||||
description: 'Single selection from a list of predefined options',
|
||||
example: 'option1',
|
||||
examples: ['GET', 'POST', 'channelMessage', 'update'],
|
||||
validation: {
|
||||
allowEmpty: false,
|
||||
allowExpressions: false,
|
||||
},
|
||||
notes: [
|
||||
'Value must match one of the defined option values',
|
||||
'Rendered as dropdown in UI',
|
||||
'Options defined in property.options array',
|
||||
],
|
||||
},
|
||||
|
||||
multiOptions: {
|
||||
type: 'array',
|
||||
jsType: 'array',
|
||||
description: 'Multiple selections from a list of predefined options',
|
||||
structure: {
|
||||
items: {
|
||||
type: 'string',
|
||||
description: 'Selected option value',
|
||||
},
|
||||
},
|
||||
example: ['option1', 'option2'],
|
||||
examples: [[], ['GET', 'POST'], ['read', 'write', 'delete']],
|
||||
validation: {
|
||||
allowEmpty: true,
|
||||
allowExpressions: false,
|
||||
},
|
||||
notes: [
|
||||
'Array of option values',
|
||||
'Each value must exist in property.options',
|
||||
'Rendered as multi-select dropdown',
|
||||
],
|
||||
},
|
||||
|
||||
// ============================================================================
|
||||
// COLLECTION TYPES - Complex nested structures
|
||||
// ============================================================================
|
||||
|
||||
collection: {
|
||||
type: 'collection',
|
||||
jsType: 'object',
|
||||
description: 'A group of related properties with dynamic values',
|
||||
structure: {
|
||||
properties: {
|
||||
'<propertyName>': {
|
||||
type: 'any',
|
||||
description: 'Any nested property from the collection definition',
|
||||
},
|
||||
},
|
||||
flexible: true,
|
||||
},
|
||||
example: {
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
age: 30,
|
||||
},
|
||||
examples: [
|
||||
{},
|
||||
{ key1: 'value1', key2: 123 },
|
||||
{ nested: { deep: { value: true } } },
|
||||
],
|
||||
validation: {
|
||||
allowEmpty: true,
|
||||
allowExpressions: true,
|
||||
},
|
||||
notes: [
|
||||
'Properties defined in property.values array',
|
||||
'Each property can be any type',
|
||||
'UI renders as expandable section',
|
||||
],
|
||||
},
|
||||
|
||||
fixedCollection: {
|
||||
type: 'collection',
|
||||
jsType: 'object',
|
||||
description: 'A collection with predefined groups of properties',
|
||||
structure: {
|
||||
properties: {
|
||||
'<collectionName>': {
|
||||
type: 'array',
|
||||
description: 'Array of collection items',
|
||||
items: {
|
||||
type: 'object',
|
||||
description: 'Collection item with defined properties',
|
||||
},
|
||||
},
|
||||
},
|
||||
required: [],
|
||||
},
|
||||
example: {
|
||||
headers: [
|
||||
{ name: 'Content-Type', value: 'application/json' },
|
||||
{ name: 'Authorization', value: 'Bearer token' },
|
||||
],
|
||||
},
|
||||
examples: [
|
||||
{},
|
||||
{ queryParameters: [{ name: 'id', value: '123' }] },
|
||||
{
|
||||
headers: [{ name: 'Accept', value: '*/*' }],
|
||||
queryParameters: [{ name: 'limit', value: '10' }],
|
||||
},
|
||||
],
|
||||
validation: {
|
||||
allowEmpty: true,
|
||||
allowExpressions: true,
|
||||
},
|
||||
notes: [
|
||||
'Each collection has predefined structure',
|
||||
'Often used for headers, parameters, etc.',
|
||||
'Supports multiple values per collection',
|
||||
],
|
||||
},
|
||||
|
||||
// ============================================================================
|
||||
// SPECIAL n8n TYPES - Advanced functionality
|
||||
// ============================================================================
|
||||
|
||||
resourceLocator: {
|
||||
type: 'special',
|
||||
jsType: 'object',
|
||||
description: 'A flexible way to specify a resource by ID, name, URL, or list',
|
||||
structure: {
|
||||
properties: {
|
||||
mode: {
|
||||
type: 'string',
|
||||
description: 'How the resource is specified',
|
||||
enum: ['id', 'url', 'list'],
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: 'string',
|
||||
description: 'The resource identifier',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
required: ['mode', 'value'],
|
||||
},
|
||||
example: {
|
||||
mode: 'id',
|
||||
value: 'abc123',
|
||||
},
|
||||
examples: [
|
||||
{ mode: 'url', value: 'https://example.com/resource/123' },
|
||||
{ mode: 'list', value: 'item-from-dropdown' },
|
||||
{ mode: 'id', value: '{{ $json.resourceId }}' },
|
||||
],
|
||||
validation: {
|
||||
allowEmpty: false,
|
||||
allowExpressions: true,
|
||||
},
|
||||
notes: [
|
||||
'Provides flexible resource selection',
|
||||
'Mode determines how value is interpreted',
|
||||
'UI adapts based on selected mode',
|
||||
],
|
||||
},
|
||||
|
||||
resourceMapper: {
|
||||
type: 'special',
|
||||
jsType: 'object',
|
||||
description: 'Maps input data fields to resource fields with transformation options',
|
||||
structure: {
|
||||
properties: {
|
||||
mappingMode: {
|
||||
type: 'string',
|
||||
description: 'How fields are mapped',
|
||||
enum: ['defineBelow', 'autoMapInputData'],
|
||||
},
|
||||
value: {
|
||||
type: 'object',
|
||||
description: 'Field mappings',
|
||||
properties: {
|
||||
'<fieldName>': {
|
||||
type: 'string',
|
||||
description: 'Expression or value for this field',
|
||||
},
|
||||
},
|
||||
flexible: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
example: {
|
||||
mappingMode: 'defineBelow',
|
||||
value: {
|
||||
name: '{{ $json.fullName }}',
|
||||
email: '{{ $json.emailAddress }}',
|
||||
status: 'active',
|
||||
},
|
||||
},
|
||||
examples: [
|
||||
{ mappingMode: 'autoMapInputData', value: {} },
|
||||
{
|
||||
mappingMode: 'defineBelow',
|
||||
value: { id: '{{ $json.userId }}', name: '{{ $json.name }}' },
|
||||
},
|
||||
],
|
||||
validation: {
|
||||
allowEmpty: false,
|
||||
allowExpressions: true,
|
||||
},
|
||||
notes: [
|
||||
'Complex mapping with UI assistance',
|
||||
'Can auto-map or manually define',
|
||||
'Supports field transformations',
|
||||
],
|
||||
},
|
||||
|
||||
filter: {
|
||||
type: 'special',
|
||||
jsType: 'object',
|
||||
description: 'Defines conditions for filtering data with boolean logic',
|
||||
structure: {
|
||||
properties: {
|
||||
conditions: {
|
||||
type: 'array',
|
||||
description: 'Array of filter conditions',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: 'Unique condition identifier',
|
||||
required: true,
|
||||
},
|
||||
leftValue: {
|
||||
type: 'any',
|
||||
description: 'Left side of comparison',
|
||||
},
|
||||
operator: {
|
||||
type: 'object',
|
||||
description: 'Comparison operator',
|
||||
required: true,
|
||||
properties: {
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['string', 'number', 'boolean', 'dateTime', 'array', 'object'],
|
||||
required: true,
|
||||
},
|
||||
operation: {
|
||||
type: 'string',
|
||||
description: 'Operation to perform',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
rightValue: {
|
||||
type: 'any',
|
||||
description: 'Right side of comparison',
|
||||
},
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
combinator: {
|
||||
type: 'string',
|
||||
description: 'How to combine conditions',
|
||||
enum: ['and', 'or'],
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
required: ['conditions', 'combinator'],
|
||||
},
|
||||
example: {
|
||||
conditions: [
|
||||
{
|
||||
id: 'abc-123',
|
||||
leftValue: '{{ $json.status }}',
|
||||
operator: { type: 'string', operation: 'equals' },
|
||||
rightValue: 'active',
|
||||
},
|
||||
],
|
||||
combinator: 'and',
|
||||
},
|
||||
validation: {
|
||||
allowEmpty: false,
|
||||
allowExpressions: true,
|
||||
},
|
||||
notes: [
|
||||
'Advanced filtering UI in n8n',
|
||||
'Supports complex boolean logic',
|
||||
'Operations vary by data type',
|
||||
],
|
||||
},
|
||||
|
||||
assignmentCollection: {
|
||||
type: 'special',
|
||||
jsType: 'object',
|
||||
description: 'Defines variable assignments with expressions',
|
||||
structure: {
|
||||
properties: {
|
||||
assignments: {
|
||||
type: 'array',
|
||||
description: 'Array of variable assignments',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: 'Unique assignment identifier',
|
||||
required: true,
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'Variable name',
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: 'any',
|
||||
description: 'Value to assign',
|
||||
required: true,
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
description: 'Data type of the value',
|
||||
enum: ['string', 'number', 'boolean', 'array', 'object'],
|
||||
},
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
required: ['assignments'],
|
||||
},
|
||||
example: {
|
||||
assignments: [
|
||||
{
|
||||
id: 'abc-123',
|
||||
name: 'userName',
|
||||
value: '{{ $json.name }}',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
id: 'def-456',
|
||||
name: 'userAge',
|
||||
value: 30,
|
||||
type: 'number',
|
||||
},
|
||||
],
|
||||
},
|
||||
validation: {
|
||||
allowEmpty: false,
|
||||
allowExpressions: true,
|
||||
},
|
||||
notes: [
|
||||
'Used in Set node and similar',
|
||||
'Each assignment can use expressions',
|
||||
'Type helps with validation',
|
||||
],
|
||||
},
|
||||
|
||||
// ============================================================================
|
||||
// CREDENTIAL TYPES - Authentication and credentials
|
||||
// ============================================================================
|
||||
|
||||
credentials: {
|
||||
type: 'special',
|
||||
jsType: 'string',
|
||||
description: 'Reference to credential configuration',
|
||||
example: 'googleSheetsOAuth2Api',
|
||||
examples: ['httpBasicAuth', 'slackOAuth2Api', 'postgresApi'],
|
||||
validation: {
|
||||
allowEmpty: false,
|
||||
allowExpressions: false,
|
||||
},
|
||||
notes: [
|
||||
'References credential type name',
|
||||
'Credential must be configured in n8n',
|
||||
'Type name matches credential definition',
|
||||
],
|
||||
},
|
||||
|
||||
credentialsSelect: {
|
||||
type: 'special',
|
||||
jsType: 'string',
|
||||
description: 'Dropdown to select from available credentials',
|
||||
example: 'credential-id-123',
|
||||
examples: ['cred-abc', 'cred-def', '{{ $credentials.id }}'],
|
||||
validation: {
|
||||
allowEmpty: false,
|
||||
allowExpressions: true,
|
||||
},
|
||||
notes: [
|
||||
'User selects from configured credentials',
|
||||
'Returns credential ID',
|
||||
'Used when multiple credential instances exist',
|
||||
],
|
||||
},
|
||||
|
||||
// ============================================================================
|
||||
// UI-ONLY TYPES - Display elements without data
|
||||
// ============================================================================
|
||||
|
||||
hidden: {
|
||||
type: 'special',
|
||||
jsType: 'string',
|
||||
description: 'Hidden property not shown in UI (used for internal logic)',
|
||||
example: '',
|
||||
validation: {
|
||||
allowEmpty: true,
|
||||
allowExpressions: true,
|
||||
},
|
||||
notes: [
|
||||
'Not rendered in UI',
|
||||
'Can store metadata or computed values',
|
||||
'Often used for version tracking',
|
||||
],
|
||||
},
|
||||
|
||||
button: {
|
||||
type: 'special',
|
||||
jsType: 'string',
|
||||
description: 'Clickable button that triggers an action',
|
||||
example: '',
|
||||
validation: {
|
||||
allowEmpty: true,
|
||||
allowExpressions: false,
|
||||
},
|
||||
notes: [
|
||||
'Triggers action when clicked',
|
||||
'Does not store a value',
|
||||
'Action defined in routing property',
|
||||
],
|
||||
},
|
||||
|
||||
callout: {
|
||||
type: 'special',
|
||||
jsType: 'string',
|
||||
description: 'Informational message box (warning, info, success, error)',
|
||||
example: '',
|
||||
validation: {
|
||||
allowEmpty: true,
|
||||
allowExpressions: false,
|
||||
},
|
||||
notes: [
|
||||
'Display-only, no value stored',
|
||||
'Used for warnings and hints',
|
||||
'Style controlled by typeOptions',
|
||||
],
|
||||
},
|
||||
|
||||
notice: {
|
||||
type: 'special',
|
||||
jsType: 'string',
|
||||
description: 'Notice message displayed to user',
|
||||
example: '',
|
||||
validation: {
|
||||
allowEmpty: true,
|
||||
allowExpressions: false,
|
||||
},
|
||||
notes: ['Similar to callout', 'Display-only element', 'Provides contextual information'],
|
||||
},
|
||||
|
||||
// ============================================================================
|
||||
// UTILITY TYPES - Special-purpose functionality
|
||||
// ============================================================================
|
||||
|
||||
workflowSelector: {
|
||||
type: 'special',
|
||||
jsType: 'string',
|
||||
description: 'Dropdown to select another workflow',
|
||||
example: 'workflow-123',
|
||||
examples: ['wf-abc', '{{ $json.workflowId }}'],
|
||||
validation: {
|
||||
allowEmpty: false,
|
||||
allowExpressions: true,
|
||||
},
|
||||
notes: [
|
||||
'Selects from available workflows',
|
||||
'Returns workflow ID',
|
||||
'Used in Execute Workflow node',
|
||||
],
|
||||
},
|
||||
|
||||
curlImport: {
|
||||
type: 'special',
|
||||
jsType: 'string',
|
||||
description: 'Import configuration from cURL command',
|
||||
example: 'curl -X GET https://api.example.com/data',
|
||||
validation: {
|
||||
allowEmpty: true,
|
||||
allowExpressions: false,
|
||||
},
|
||||
notes: [
|
||||
'Parses cURL command to populate fields',
|
||||
'Used in HTTP Request node',
|
||||
'One-time import feature',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Real-world examples for complex types
|
||||
*
|
||||
* These examples come from actual n8n workflows and demonstrate
|
||||
* correct usage patterns for complex property types.
|
||||
*
|
||||
* @constant
|
||||
*/
|
||||
export const COMPLEX_TYPE_EXAMPLES = {
|
||||
collection: {
|
||||
basic: {
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
},
|
||||
nested: {
|
||||
user: {
|
||||
firstName: 'Jane',
|
||||
lastName: 'Smith',
|
||||
},
|
||||
preferences: {
|
||||
theme: 'dark',
|
||||
notifications: true,
|
||||
},
|
||||
},
|
||||
withExpressions: {
|
||||
id: '{{ $json.userId }}',
|
||||
timestamp: '{{ $now }}',
|
||||
data: '{{ $json.payload }}',
|
||||
},
|
||||
},
|
||||
|
||||
fixedCollection: {
|
||||
httpHeaders: {
|
||||
headers: [
|
||||
{ name: 'Content-Type', value: 'application/json' },
|
||||
{ name: 'Authorization', value: 'Bearer {{ $credentials.token }}' },
|
||||
],
|
||||
},
|
||||
queryParameters: {
|
||||
queryParameters: [
|
||||
{ name: 'page', value: '1' },
|
||||
{ name: 'limit', value: '100' },
|
||||
],
|
||||
},
|
||||
multipleCollections: {
|
||||
headers: [{ name: 'Accept', value: 'application/json' }],
|
||||
queryParameters: [{ name: 'filter', value: 'active' }],
|
||||
},
|
||||
},
|
||||
|
||||
filter: {
|
||||
simple: {
|
||||
conditions: [
|
||||
{
|
||||
id: '1',
|
||||
leftValue: '{{ $json.status }}',
|
||||
operator: { type: 'string', operation: 'equals' },
|
||||
rightValue: 'active',
|
||||
},
|
||||
],
|
||||
combinator: 'and',
|
||||
},
|
||||
complex: {
|
||||
conditions: [
|
||||
{
|
||||
id: '1',
|
||||
leftValue: '{{ $json.age }}',
|
||||
operator: { type: 'number', operation: 'gt' },
|
||||
rightValue: 18,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
leftValue: '{{ $json.country }}',
|
||||
operator: { type: 'string', operation: 'equals' },
|
||||
rightValue: 'US',
|
||||
},
|
||||
],
|
||||
combinator: 'and',
|
||||
},
|
||||
},
|
||||
|
||||
resourceMapper: {
|
||||
autoMap: {
|
||||
mappingMode: 'autoMapInputData',
|
||||
value: {},
|
||||
},
|
||||
manual: {
|
||||
mappingMode: 'defineBelow',
|
||||
value: {
|
||||
firstName: '{{ $json.first_name }}',
|
||||
lastName: '{{ $json.last_name }}',
|
||||
email: '{{ $json.email_address }}',
|
||||
status: 'active',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
assignmentCollection: {
|
||||
basic: {
|
||||
assignments: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'fullName',
|
||||
value: '{{ $json.firstName }} {{ $json.lastName }}',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
},
|
||||
multiple: {
|
||||
assignments: [
|
||||
{ id: '1', name: 'userName', value: '{{ $json.name }}', type: 'string' },
|
||||
{ id: '2', name: 'userAge', value: '{{ $json.age }}', type: 'number' },
|
||||
{ id: '3', name: 'isActive', value: true, type: 'boolean' },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -462,501 +462,4 @@ export class NodeRepository {
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* VERSION MANAGEMENT METHODS
|
||||
* Methods for working with node_versions and version_property_changes tables
|
||||
*/
|
||||
|
||||
/**
|
||||
* Save a specific node version to the database
|
||||
*/
|
||||
saveNodeVersion(versionData: {
|
||||
nodeType: string;
|
||||
version: string;
|
||||
packageName: string;
|
||||
displayName: string;
|
||||
description?: string;
|
||||
category?: string;
|
||||
isCurrentMax?: boolean;
|
||||
propertiesSchema?: any;
|
||||
operations?: any;
|
||||
credentialsRequired?: any;
|
||||
outputs?: any;
|
||||
minimumN8nVersion?: string;
|
||||
breakingChanges?: any[];
|
||||
deprecatedProperties?: string[];
|
||||
addedProperties?: string[];
|
||||
releasedAt?: Date;
|
||||
}): void {
|
||||
const stmt = this.db.prepare(`
|
||||
INSERT OR REPLACE INTO node_versions (
|
||||
node_type, version, package_name, display_name, description,
|
||||
category, is_current_max, properties_schema, operations,
|
||||
credentials_required, outputs, minimum_n8n_version,
|
||||
breaking_changes, deprecated_properties, added_properties,
|
||||
released_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
stmt.run(
|
||||
versionData.nodeType,
|
||||
versionData.version,
|
||||
versionData.packageName,
|
||||
versionData.displayName,
|
||||
versionData.description || null,
|
||||
versionData.category || null,
|
||||
versionData.isCurrentMax ? 1 : 0,
|
||||
versionData.propertiesSchema ? JSON.stringify(versionData.propertiesSchema) : null,
|
||||
versionData.operations ? JSON.stringify(versionData.operations) : null,
|
||||
versionData.credentialsRequired ? JSON.stringify(versionData.credentialsRequired) : null,
|
||||
versionData.outputs ? JSON.stringify(versionData.outputs) : null,
|
||||
versionData.minimumN8nVersion || null,
|
||||
versionData.breakingChanges ? JSON.stringify(versionData.breakingChanges) : null,
|
||||
versionData.deprecatedProperties ? JSON.stringify(versionData.deprecatedProperties) : null,
|
||||
versionData.addedProperties ? JSON.stringify(versionData.addedProperties) : null,
|
||||
versionData.releasedAt || null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available versions for a specific node type
|
||||
*/
|
||||
getNodeVersions(nodeType: string): any[] {
|
||||
const normalizedType = NodeTypeNormalizer.normalizeToFullForm(nodeType);
|
||||
|
||||
const rows = this.db.prepare(`
|
||||
SELECT * FROM node_versions
|
||||
WHERE node_type = ?
|
||||
ORDER BY version DESC
|
||||
`).all(normalizedType) as any[];
|
||||
|
||||
return rows.map(row => this.parseNodeVersionRow(row));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest (current max) version for a node type
|
||||
*/
|
||||
getLatestNodeVersion(nodeType: string): any | null {
|
||||
const normalizedType = NodeTypeNormalizer.normalizeToFullForm(nodeType);
|
||||
|
||||
const row = this.db.prepare(`
|
||||
SELECT * FROM node_versions
|
||||
WHERE node_type = ? AND is_current_max = 1
|
||||
LIMIT 1
|
||||
`).get(normalizedType) as any;
|
||||
|
||||
if (!row) return null;
|
||||
return this.parseNodeVersionRow(row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific version of a node
|
||||
*/
|
||||
getNodeVersion(nodeType: string, version: string): any | null {
|
||||
const normalizedType = NodeTypeNormalizer.normalizeToFullForm(nodeType);
|
||||
|
||||
const row = this.db.prepare(`
|
||||
SELECT * FROM node_versions
|
||||
WHERE node_type = ? AND version = ?
|
||||
`).get(normalizedType, version) as any;
|
||||
|
||||
if (!row) return null;
|
||||
return this.parseNodeVersionRow(row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a property change between versions
|
||||
*/
|
||||
savePropertyChange(changeData: {
|
||||
nodeType: string;
|
||||
fromVersion: string;
|
||||
toVersion: string;
|
||||
propertyName: string;
|
||||
changeType: 'added' | 'removed' | 'renamed' | 'type_changed' | 'requirement_changed' | 'default_changed';
|
||||
isBreaking?: boolean;
|
||||
oldValue?: string;
|
||||
newValue?: string;
|
||||
migrationHint?: string;
|
||||
autoMigratable?: boolean;
|
||||
migrationStrategy?: any;
|
||||
severity?: 'LOW' | 'MEDIUM' | 'HIGH';
|
||||
}): void {
|
||||
const stmt = this.db.prepare(`
|
||||
INSERT INTO version_property_changes (
|
||||
node_type, from_version, to_version, property_name, change_type,
|
||||
is_breaking, old_value, new_value, migration_hint, auto_migratable,
|
||||
migration_strategy, severity
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
stmt.run(
|
||||
changeData.nodeType,
|
||||
changeData.fromVersion,
|
||||
changeData.toVersion,
|
||||
changeData.propertyName,
|
||||
changeData.changeType,
|
||||
changeData.isBreaking ? 1 : 0,
|
||||
changeData.oldValue || null,
|
||||
changeData.newValue || null,
|
||||
changeData.migrationHint || null,
|
||||
changeData.autoMigratable ? 1 : 0,
|
||||
changeData.migrationStrategy ? JSON.stringify(changeData.migrationStrategy) : null,
|
||||
changeData.severity || 'MEDIUM'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get property changes between two versions
|
||||
*/
|
||||
getPropertyChanges(nodeType: string, fromVersion: string, toVersion: string): any[] {
|
||||
const normalizedType = NodeTypeNormalizer.normalizeToFullForm(nodeType);
|
||||
|
||||
const rows = this.db.prepare(`
|
||||
SELECT * FROM version_property_changes
|
||||
WHERE node_type = ? AND from_version = ? AND to_version = ?
|
||||
ORDER BY severity DESC, property_name
|
||||
`).all(normalizedType, fromVersion, toVersion) as any[];
|
||||
|
||||
return rows.map(row => this.parsePropertyChangeRow(row));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all breaking changes for upgrading from one version to another
|
||||
* Can handle multi-step upgrades (e.g., 1.0 -> 2.0 via 1.5)
|
||||
*/
|
||||
getBreakingChanges(nodeType: string, fromVersion: string, toVersion?: string): any[] {
|
||||
const normalizedType = NodeTypeNormalizer.normalizeToFullForm(nodeType);
|
||||
|
||||
let sql = `
|
||||
SELECT * FROM version_property_changes
|
||||
WHERE node_type = ? AND is_breaking = 1
|
||||
`;
|
||||
const params: any[] = [normalizedType];
|
||||
|
||||
if (toVersion) {
|
||||
// Get changes between specific versions
|
||||
sql += ` AND from_version >= ? AND to_version <= ?`;
|
||||
params.push(fromVersion, toVersion);
|
||||
} else {
|
||||
// Get all breaking changes from this version onwards
|
||||
sql += ` AND from_version >= ?`;
|
||||
params.push(fromVersion);
|
||||
}
|
||||
|
||||
sql += ` ORDER BY from_version, to_version, severity DESC`;
|
||||
|
||||
const rows = this.db.prepare(sql).all(...params) as any[];
|
||||
return rows.map(row => this.parsePropertyChangeRow(row));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get auto-migratable changes for a version upgrade
|
||||
*/
|
||||
getAutoMigratableChanges(nodeType: string, fromVersion: string, toVersion: string): any[] {
|
||||
const normalizedType = NodeTypeNormalizer.normalizeToFullForm(nodeType);
|
||||
|
||||
const rows = this.db.prepare(`
|
||||
SELECT * FROM version_property_changes
|
||||
WHERE node_type = ?
|
||||
AND from_version = ?
|
||||
AND to_version = ?
|
||||
AND auto_migratable = 1
|
||||
ORDER BY severity DESC
|
||||
`).all(normalizedType, fromVersion, toVersion) as any[];
|
||||
|
||||
return rows.map(row => this.parsePropertyChangeRow(row));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a version upgrade path exists between two versions
|
||||
*/
|
||||
hasVersionUpgradePath(nodeType: string, fromVersion: string, toVersion: string): boolean {
|
||||
const versions = this.getNodeVersions(nodeType);
|
||||
if (versions.length === 0) return false;
|
||||
|
||||
// Check if both versions exist
|
||||
const fromExists = versions.some(v => v.version === fromVersion);
|
||||
const toExists = versions.some(v => v.version === toVersion);
|
||||
|
||||
return fromExists && toExists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of nodes with multiple versions
|
||||
*/
|
||||
getVersionedNodesCount(): number {
|
||||
const result = this.db.prepare(`
|
||||
SELECT COUNT(DISTINCT node_type) as count
|
||||
FROM node_versions
|
||||
`).get() as any;
|
||||
return result.count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse node version row from database
|
||||
*/
|
||||
private parseNodeVersionRow(row: any): any {
|
||||
return {
|
||||
id: row.id,
|
||||
nodeType: row.node_type,
|
||||
version: row.version,
|
||||
packageName: row.package_name,
|
||||
displayName: row.display_name,
|
||||
description: row.description,
|
||||
category: row.category,
|
||||
isCurrentMax: Number(row.is_current_max) === 1,
|
||||
propertiesSchema: row.properties_schema ? this.safeJsonParse(row.properties_schema, []) : null,
|
||||
operations: row.operations ? this.safeJsonParse(row.operations, []) : null,
|
||||
credentialsRequired: row.credentials_required ? this.safeJsonParse(row.credentials_required, []) : null,
|
||||
outputs: row.outputs ? this.safeJsonParse(row.outputs, null) : null,
|
||||
minimumN8nVersion: row.minimum_n8n_version,
|
||||
breakingChanges: row.breaking_changes ? this.safeJsonParse(row.breaking_changes, []) : [],
|
||||
deprecatedProperties: row.deprecated_properties ? this.safeJsonParse(row.deprecated_properties, []) : [],
|
||||
addedProperties: row.added_properties ? this.safeJsonParse(row.added_properties, []) : [],
|
||||
releasedAt: row.released_at,
|
||||
createdAt: row.created_at
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse property change row from database
|
||||
*/
|
||||
private parsePropertyChangeRow(row: any): any {
|
||||
return {
|
||||
id: row.id,
|
||||
nodeType: row.node_type,
|
||||
fromVersion: row.from_version,
|
||||
toVersion: row.to_version,
|
||||
propertyName: row.property_name,
|
||||
changeType: row.change_type,
|
||||
isBreaking: Number(row.is_breaking) === 1,
|
||||
oldValue: row.old_value,
|
||||
newValue: row.new_value,
|
||||
migrationHint: row.migration_hint,
|
||||
autoMigratable: Number(row.auto_migratable) === 1,
|
||||
migrationStrategy: row.migration_strategy ? this.safeJsonParse(row.migration_strategy, null) : null,
|
||||
severity: row.severity,
|
||||
createdAt: row.created_at
|
||||
};
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Workflow Versioning Methods
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Create a new workflow version (backup before modification)
|
||||
*/
|
||||
createWorkflowVersion(data: {
|
||||
workflowId: string;
|
||||
versionNumber: number;
|
||||
workflowName: string;
|
||||
workflowSnapshot: any;
|
||||
trigger: 'partial_update' | 'full_update' | 'autofix';
|
||||
operations?: any[];
|
||||
fixTypes?: string[];
|
||||
metadata?: any;
|
||||
}): number {
|
||||
const stmt = this.db.prepare(`
|
||||
INSERT INTO workflow_versions (
|
||||
workflow_id, version_number, workflow_name, workflow_snapshot,
|
||||
trigger, operations, fix_types, metadata
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
const result = stmt.run(
|
||||
data.workflowId,
|
||||
data.versionNumber,
|
||||
data.workflowName,
|
||||
JSON.stringify(data.workflowSnapshot),
|
||||
data.trigger,
|
||||
data.operations ? JSON.stringify(data.operations) : null,
|
||||
data.fixTypes ? JSON.stringify(data.fixTypes) : null,
|
||||
data.metadata ? JSON.stringify(data.metadata) : null
|
||||
);
|
||||
|
||||
return result.lastInsertRowid as number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get workflow versions ordered by version number (newest first)
|
||||
*/
|
||||
getWorkflowVersions(workflowId: string, limit?: number): any[] {
|
||||
let sql = `
|
||||
SELECT * FROM workflow_versions
|
||||
WHERE workflow_id = ?
|
||||
ORDER BY version_number DESC
|
||||
`;
|
||||
|
||||
if (limit) {
|
||||
sql += ` LIMIT ?`;
|
||||
const rows = this.db.prepare(sql).all(workflowId, limit) as any[];
|
||||
return rows.map(row => this.parseWorkflowVersionRow(row));
|
||||
}
|
||||
|
||||
const rows = this.db.prepare(sql).all(workflowId) as any[];
|
||||
return rows.map(row => this.parseWorkflowVersionRow(row));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific workflow version by ID
|
||||
*/
|
||||
getWorkflowVersion(versionId: number): any | null {
|
||||
const row = this.db.prepare(`
|
||||
SELECT * FROM workflow_versions WHERE id = ?
|
||||
`).get(versionId) as any;
|
||||
|
||||
if (!row) return null;
|
||||
return this.parseWorkflowVersionRow(row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest workflow version for a workflow
|
||||
*/
|
||||
getLatestWorkflowVersion(workflowId: string): any | null {
|
||||
const row = this.db.prepare(`
|
||||
SELECT * FROM workflow_versions
|
||||
WHERE workflow_id = ?
|
||||
ORDER BY version_number DESC
|
||||
LIMIT 1
|
||||
`).get(workflowId) as any;
|
||||
|
||||
if (!row) return null;
|
||||
return this.parseWorkflowVersionRow(row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a specific workflow version
|
||||
*/
|
||||
deleteWorkflowVersion(versionId: number): void {
|
||||
this.db.prepare(`
|
||||
DELETE FROM workflow_versions WHERE id = ?
|
||||
`).run(versionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all versions for a specific workflow
|
||||
*/
|
||||
deleteWorkflowVersionsByWorkflowId(workflowId: string): number {
|
||||
const result = this.db.prepare(`
|
||||
DELETE FROM workflow_versions WHERE workflow_id = ?
|
||||
`).run(workflowId);
|
||||
|
||||
return result.changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prune old workflow versions, keeping only the most recent N versions
|
||||
* Returns number of versions deleted
|
||||
*/
|
||||
pruneWorkflowVersions(workflowId: string, keepCount: number): number {
|
||||
// Get all versions ordered by version_number DESC
|
||||
const versions = this.db.prepare(`
|
||||
SELECT id FROM workflow_versions
|
||||
WHERE workflow_id = ?
|
||||
ORDER BY version_number DESC
|
||||
`).all(workflowId) as any[];
|
||||
|
||||
// If we have fewer versions than keepCount, no pruning needed
|
||||
if (versions.length <= keepCount) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get IDs of versions to delete (all except the most recent keepCount)
|
||||
const idsToDelete = versions.slice(keepCount).map(v => v.id);
|
||||
|
||||
if (idsToDelete.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Delete old versions
|
||||
const placeholders = idsToDelete.map(() => '?').join(',');
|
||||
const result = this.db.prepare(`
|
||||
DELETE FROM workflow_versions WHERE id IN (${placeholders})
|
||||
`).run(...idsToDelete);
|
||||
|
||||
return result.changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate the entire workflow_versions table
|
||||
* Returns number of rows deleted
|
||||
*/
|
||||
truncateWorkflowVersions(): number {
|
||||
const result = this.db.prepare(`
|
||||
DELETE FROM workflow_versions
|
||||
`).run();
|
||||
|
||||
return result.changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of versions for a specific workflow
|
||||
*/
|
||||
getWorkflowVersionCount(workflowId: string): number {
|
||||
const result = this.db.prepare(`
|
||||
SELECT COUNT(*) as count FROM workflow_versions WHERE workflow_id = ?
|
||||
`).get(workflowId) as any;
|
||||
|
||||
return result.count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get storage statistics for workflow versions
|
||||
*/
|
||||
getVersionStorageStats(): any {
|
||||
// Total versions
|
||||
const totalResult = this.db.prepare(`
|
||||
SELECT COUNT(*) as count FROM workflow_versions
|
||||
`).get() as any;
|
||||
|
||||
// Total size (approximate - sum of JSON lengths)
|
||||
const sizeResult = this.db.prepare(`
|
||||
SELECT SUM(LENGTH(workflow_snapshot)) as total_size FROM workflow_versions
|
||||
`).get() as any;
|
||||
|
||||
// Per-workflow breakdown
|
||||
const byWorkflow = this.db.prepare(`
|
||||
SELECT
|
||||
workflow_id,
|
||||
workflow_name,
|
||||
COUNT(*) as version_count,
|
||||
SUM(LENGTH(workflow_snapshot)) as total_size,
|
||||
MAX(created_at) as last_backup
|
||||
FROM workflow_versions
|
||||
GROUP BY workflow_id
|
||||
ORDER BY version_count DESC
|
||||
`).all() as any[];
|
||||
|
||||
return {
|
||||
totalVersions: totalResult.count,
|
||||
totalSize: sizeResult.total_size || 0,
|
||||
byWorkflow: byWorkflow.map(row => ({
|
||||
workflowId: row.workflow_id,
|
||||
workflowName: row.workflow_name,
|
||||
versionCount: row.version_count,
|
||||
totalSize: row.total_size,
|
||||
lastBackup: row.last_backup
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse workflow version row from database
|
||||
*/
|
||||
private parseWorkflowVersionRow(row: any): any {
|
||||
return {
|
||||
id: row.id,
|
||||
workflowId: row.workflow_id,
|
||||
versionNumber: row.version_number,
|
||||
workflowName: row.workflow_name,
|
||||
workflowSnapshot: this.safeJsonParse(row.workflow_snapshot, null),
|
||||
trigger: row.trigger,
|
||||
operations: row.operations ? this.safeJsonParse(row.operations, null) : null,
|
||||
fixTypes: row.fix_types ? this.safeJsonParse(row.fix_types, null) : null,
|
||||
metadata: row.metadata ? this.safeJsonParse(row.metadata, null) : null,
|
||||
createdAt: row.created_at
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -144,93 +144,4 @@ ORDER BY node_type, rank;
|
||||
|
||||
-- Note: Template FTS5 tables are created conditionally at runtime if FTS5 is supported
|
||||
-- See template-repository.ts initializeFTS5() method
|
||||
-- Node FTS5 table (nodes_fts) is created above during schema initialization
|
||||
|
||||
-- Node versions table for tracking all available versions of each node
|
||||
-- Enables version upgrade detection and migration
|
||||
CREATE TABLE IF NOT EXISTS node_versions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
node_type TEXT NOT NULL, -- e.g., "n8n-nodes-base.executeWorkflow"
|
||||
version TEXT NOT NULL, -- e.g., "1.0", "1.1", "2.0"
|
||||
package_name TEXT NOT NULL, -- e.g., "n8n-nodes-base"
|
||||
display_name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
category TEXT,
|
||||
is_current_max INTEGER DEFAULT 0, -- 1 if this is the latest version
|
||||
properties_schema TEXT, -- JSON schema for this specific version
|
||||
operations TEXT, -- JSON array of operations for this version
|
||||
credentials_required TEXT, -- JSON array of required credentials
|
||||
outputs TEXT, -- JSON array of output definitions
|
||||
minimum_n8n_version TEXT, -- Minimum n8n version required (e.g., "1.0.0")
|
||||
breaking_changes TEXT, -- JSON array of breaking changes from previous version
|
||||
deprecated_properties TEXT, -- JSON array of removed/deprecated properties
|
||||
added_properties TEXT, -- JSON array of newly added properties
|
||||
released_at DATETIME, -- When this version was released
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(node_type, version),
|
||||
FOREIGN KEY (node_type) REFERENCES nodes(node_type) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Indexes for version queries
|
||||
CREATE INDEX IF NOT EXISTS idx_version_node_type ON node_versions(node_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_version_current_max ON node_versions(is_current_max);
|
||||
CREATE INDEX IF NOT EXISTS idx_version_composite ON node_versions(node_type, version);
|
||||
|
||||
-- Version property changes for detailed migration tracking
|
||||
-- Records specific property-level changes between versions
|
||||
CREATE TABLE IF NOT EXISTS version_property_changes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
node_type TEXT NOT NULL,
|
||||
from_version TEXT NOT NULL, -- Version where change occurred (e.g., "1.0")
|
||||
to_version TEXT NOT NULL, -- Target version (e.g., "1.1")
|
||||
property_name TEXT NOT NULL, -- Property path (e.g., "parameters.inputFieldMapping")
|
||||
change_type TEXT NOT NULL CHECK(change_type IN (
|
||||
'added', -- Property added (may be required)
|
||||
'removed', -- Property removed/deprecated
|
||||
'renamed', -- Property renamed
|
||||
'type_changed', -- Property type changed
|
||||
'requirement_changed', -- Required → Optional or vice versa
|
||||
'default_changed' -- Default value changed
|
||||
)),
|
||||
is_breaking INTEGER DEFAULT 0, -- 1 if this is a breaking change
|
||||
old_value TEXT, -- For renamed/type_changed: old property name or type
|
||||
new_value TEXT, -- For renamed/type_changed: new property name or type
|
||||
migration_hint TEXT, -- Human-readable migration guidance
|
||||
auto_migratable INTEGER DEFAULT 0, -- 1 if can be automatically migrated
|
||||
migration_strategy TEXT, -- JSON: strategy for auto-migration
|
||||
severity TEXT CHECK(severity IN ('LOW', 'MEDIUM', 'HIGH')), -- Impact severity
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (node_type, from_version) REFERENCES node_versions(node_type, version) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Indexes for property change queries
|
||||
CREATE INDEX IF NOT EXISTS idx_prop_changes_node ON version_property_changes(node_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_prop_changes_versions ON version_property_changes(node_type, from_version, to_version);
|
||||
CREATE INDEX IF NOT EXISTS idx_prop_changes_breaking ON version_property_changes(is_breaking);
|
||||
CREATE INDEX IF NOT EXISTS idx_prop_changes_auto ON version_property_changes(auto_migratable);
|
||||
|
||||
-- Workflow versions table for rollback and version history tracking
|
||||
-- Stores full workflow snapshots before modifications for guaranteed reversibility
|
||||
-- Auto-prunes to 10 versions per workflow to prevent memory leaks
|
||||
CREATE TABLE IF NOT EXISTS workflow_versions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
workflow_id TEXT NOT NULL, -- n8n workflow ID
|
||||
version_number INTEGER NOT NULL, -- Incremental version number (1, 2, 3...)
|
||||
workflow_name TEXT NOT NULL, -- Workflow name at time of backup
|
||||
workflow_snapshot TEXT NOT NULL, -- Full workflow JSON before modification
|
||||
trigger TEXT NOT NULL CHECK(trigger IN (
|
||||
'partial_update', -- Created by n8n_update_partial_workflow
|
||||
'full_update', -- Created by n8n_update_full_workflow
|
||||
'autofix' -- Created by n8n_autofix_workflow
|
||||
)),
|
||||
operations TEXT, -- JSON array of diff operations (if partial update)
|
||||
fix_types TEXT, -- JSON array of fix types (if autofix)
|
||||
metadata TEXT, -- Additional context (JSON)
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(workflow_id, version_number)
|
||||
);
|
||||
|
||||
-- Indexes for workflow version queries
|
||||
CREATE INDEX IF NOT EXISTS idx_workflow_versions_workflow_id ON workflow_versions(workflow_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_workflow_versions_created_at ON workflow_versions(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_workflow_versions_trigger ON workflow_versions(trigger);
|
||||
-- Node FTS5 table (nodes_fts) is created above during schema initialization
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
STANDARD_PROTOCOL_VERSION
|
||||
} from './utils/protocol-version';
|
||||
import { InstanceContext, validateInstanceContext } from './types/instance-context';
|
||||
import { SessionState } from './types/session-state';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
@@ -41,7 +40,7 @@ interface MultiTenantHeaders {
|
||||
}
|
||||
|
||||
// Session management constants
|
||||
const MAX_SESSIONS = Math.max(1, parseInt(process.env.N8N_MCP_MAX_SESSIONS || '100', 10));
|
||||
const MAX_SESSIONS = 100;
|
||||
const SESSION_CLEANUP_INTERVAL = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
interface Session {
|
||||
@@ -72,30 +71,6 @@ function extractMultiTenantHeaders(req: express.Request): MultiTenantHeaders {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Security logging helper for audit trails
|
||||
* Provides structured logging for security-relevant events
|
||||
*/
|
||||
function logSecurityEvent(
|
||||
event: 'session_export' | 'session_restore' | 'session_restore_failed' | 'max_sessions_reached',
|
||||
details: {
|
||||
sessionId?: string;
|
||||
reason?: string;
|
||||
count?: number;
|
||||
instanceId?: string;
|
||||
}
|
||||
): void {
|
||||
const timestamp = new Date().toISOString();
|
||||
const logEntry = {
|
||||
timestamp,
|
||||
event,
|
||||
...details
|
||||
};
|
||||
|
||||
// Log to standard logger with [SECURITY] prefix for easy filtering
|
||||
logger.info(`[SECURITY] ${event}`, logEntry);
|
||||
}
|
||||
|
||||
export class SingleSessionHTTPServer {
|
||||
// Map to store transports by session ID (following SDK pattern)
|
||||
private transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
|
||||
@@ -180,22 +155,17 @@ export class SingleSessionHTTPServer {
|
||||
*/
|
||||
private async removeSession(sessionId: string, reason: string): Promise<void> {
|
||||
try {
|
||||
// Store reference to transport before deletion
|
||||
const transport = this.transports[sessionId];
|
||||
|
||||
// Delete transport FIRST to prevent onclose handler from triggering recursion
|
||||
// This breaks the circular reference: removeSession -> close -> onclose -> removeSession
|
||||
delete this.transports[sessionId];
|
||||
// Close transport if exists
|
||||
if (this.transports[sessionId]) {
|
||||
await this.transports[sessionId].close();
|
||||
delete this.transports[sessionId];
|
||||
}
|
||||
|
||||
// Remove server, metadata, and context
|
||||
delete this.servers[sessionId];
|
||||
delete this.sessionMetadata[sessionId];
|
||||
delete this.sessionContexts[sessionId];
|
||||
|
||||
// Close transport AFTER deletion
|
||||
// When onclose handler fires, it won't find the transport anymore
|
||||
if (transport) {
|
||||
await transport.close();
|
||||
}
|
||||
|
||||
|
||||
logger.info('Session removed', { sessionId, reason });
|
||||
} catch (error) {
|
||||
logger.warn('Error removing session', { sessionId, reason, error });
|
||||
@@ -712,20 +682,7 @@ export class SingleSessionHTTPServer {
|
||||
if (!this.session) return true;
|
||||
return Date.now() - this.session.lastAccess.getTime() > this.sessionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a specific session is expired based on sessionId
|
||||
* Used for multi-session expiration checks during export/restore
|
||||
*
|
||||
* @param sessionId - The session ID to check
|
||||
* @returns true if session is expired or doesn't exist
|
||||
*/
|
||||
private isSessionExpired(sessionId: string): boolean {
|
||||
const metadata = this.sessionMetadata[sessionId];
|
||||
if (!metadata) return true;
|
||||
return Date.now() - metadata.lastAccess.getTime() > this.sessionTimeout;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Start the HTTP server
|
||||
*/
|
||||
@@ -1444,197 +1401,6 @@ export class SingleSessionHTTPServer {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all active session state for persistence
|
||||
*
|
||||
* Used by multi-tenant backends to dump sessions before container restart.
|
||||
* This method exports the minimal state needed to restore sessions after
|
||||
* a restart: session metadata (timing) and instance context (credentials).
|
||||
*
|
||||
* Transport and server objects are NOT persisted - they will be recreated
|
||||
* on the first request after restore.
|
||||
*
|
||||
* SECURITY WARNING: The exported data contains plaintext n8n API keys.
|
||||
* The downstream application MUST encrypt this data before persisting to disk.
|
||||
*
|
||||
* @returns Array of session state objects, excluding expired sessions
|
||||
*
|
||||
* @example
|
||||
* // Before shutdown
|
||||
* const sessions = server.exportSessionState();
|
||||
* await saveToEncryptedStorage(sessions);
|
||||
*/
|
||||
public exportSessionState(): SessionState[] {
|
||||
const sessions: SessionState[] = [];
|
||||
const seenSessionIds = new Set<string>();
|
||||
|
||||
// Iterate over all sessions with metadata (source of truth for active sessions)
|
||||
for (const sessionId of Object.keys(this.sessionMetadata)) {
|
||||
// Check for duplicates (defensive programming)
|
||||
if (seenSessionIds.has(sessionId)) {
|
||||
logger.warn(`Duplicate sessionId detected during export: ${sessionId}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip expired sessions - they're not worth persisting
|
||||
if (this.isSessionExpired(sessionId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const metadata = this.sessionMetadata[sessionId];
|
||||
const context = this.sessionContexts[sessionId];
|
||||
|
||||
// Skip sessions without context - these can't be restored meaningfully
|
||||
// (Context is required to reconnect to the correct n8n instance)
|
||||
if (!context || !context.n8nApiUrl || !context.n8nApiKey) {
|
||||
logger.debug(`Skipping session ${sessionId} - missing required context`);
|
||||
continue;
|
||||
}
|
||||
|
||||
seenSessionIds.add(sessionId);
|
||||
sessions.push({
|
||||
sessionId,
|
||||
metadata: {
|
||||
createdAt: metadata.createdAt.toISOString(),
|
||||
lastAccess: metadata.lastAccess.toISOString()
|
||||
},
|
||||
context: {
|
||||
n8nApiUrl: context.n8nApiUrl,
|
||||
n8nApiKey: context.n8nApiKey,
|
||||
instanceId: context.instanceId || sessionId, // Use sessionId as fallback
|
||||
sessionId: context.sessionId,
|
||||
metadata: context.metadata
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
logger.info(`Exported ${sessions.length} session(s) for persistence`);
|
||||
logSecurityEvent('session_export', { count: sessions.length });
|
||||
return sessions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore session state from previously exported data
|
||||
*
|
||||
* Used by multi-tenant backends to restore sessions after container restart.
|
||||
* This method restores only the session metadata and instance context.
|
||||
* Transport and server objects will be recreated on the first request.
|
||||
*
|
||||
* Restored sessions are "dormant" until a client makes a request, at which
|
||||
* point the transport and server will be initialized normally.
|
||||
*
|
||||
* @param sessions - Array of session state objects from exportSessionState()
|
||||
* @returns Number of sessions successfully restored
|
||||
*
|
||||
* @example
|
||||
* // After startup
|
||||
* const sessions = await loadFromEncryptedStorage();
|
||||
* const count = server.restoreSessionState(sessions);
|
||||
* console.log(`Restored ${count} sessions`);
|
||||
*/
|
||||
public restoreSessionState(sessions: SessionState[]): number {
|
||||
let restoredCount = 0;
|
||||
|
||||
for (const sessionState of sessions) {
|
||||
try {
|
||||
// Skip null or invalid session objects
|
||||
if (!sessionState || typeof sessionState !== 'object' || !sessionState.sessionId) {
|
||||
logger.warn('Skipping invalid session state object');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if we've hit the MAX_SESSIONS limit (check real-time count)
|
||||
if (Object.keys(this.sessionMetadata).length >= MAX_SESSIONS) {
|
||||
logger.warn(
|
||||
`Reached MAX_SESSIONS limit (${MAX_SESSIONS}), skipping remaining sessions`
|
||||
);
|
||||
logSecurityEvent('max_sessions_reached', { count: MAX_SESSIONS });
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip if session already exists (duplicate sessionId)
|
||||
if (this.sessionMetadata[sessionState.sessionId]) {
|
||||
logger.debug(`Skipping session ${sessionState.sessionId} - already exists`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse and validate dates first
|
||||
const createdAt = new Date(sessionState.metadata.createdAt);
|
||||
const lastAccess = new Date(sessionState.metadata.lastAccess);
|
||||
|
||||
if (isNaN(createdAt.getTime()) || isNaN(lastAccess.getTime())) {
|
||||
logger.warn(
|
||||
`Skipping session ${sessionState.sessionId} - invalid date format`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate session isn't expired
|
||||
const age = Date.now() - lastAccess.getTime();
|
||||
if (age > this.sessionTimeout) {
|
||||
logger.debug(
|
||||
`Skipping session ${sessionState.sessionId} - expired (age: ${Math.round(age / 1000)}s)`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate context exists (TypeScript null narrowing)
|
||||
if (!sessionState.context) {
|
||||
logger.warn(`Skipping session ${sessionState.sessionId} - missing context`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate context structure using existing validation
|
||||
const validation = validateInstanceContext(sessionState.context);
|
||||
if (!validation.valid) {
|
||||
const reason = validation.errors?.join(', ') || 'invalid context';
|
||||
logger.warn(
|
||||
`Skipping session ${sessionState.sessionId} - invalid context: ${reason}`
|
||||
);
|
||||
logSecurityEvent('session_restore_failed', {
|
||||
sessionId: sessionState.sessionId,
|
||||
reason
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Restore session metadata
|
||||
this.sessionMetadata[sessionState.sessionId] = {
|
||||
createdAt,
|
||||
lastAccess
|
||||
};
|
||||
|
||||
// Restore session context
|
||||
this.sessionContexts[sessionState.sessionId] = {
|
||||
n8nApiUrl: sessionState.context.n8nApiUrl,
|
||||
n8nApiKey: sessionState.context.n8nApiKey,
|
||||
instanceId: sessionState.context.instanceId,
|
||||
sessionId: sessionState.context.sessionId,
|
||||
metadata: sessionState.context.metadata
|
||||
};
|
||||
|
||||
logger.debug(`Restored session ${sessionState.sessionId}`);
|
||||
logSecurityEvent('session_restore', {
|
||||
sessionId: sessionState.sessionId,
|
||||
instanceId: sessionState.context.instanceId
|
||||
});
|
||||
restoredCount++;
|
||||
} catch (error) {
|
||||
logger.error(`Failed to restore session ${sessionState.sessionId}:`, error);
|
||||
logSecurityEvent('session_restore_failed', {
|
||||
sessionId: sessionState.sessionId,
|
||||
reason: error instanceof Error ? error.message : 'unknown error'
|
||||
});
|
||||
// Continue with next session - don't let one failure break the entire restore
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Restored ${restoredCount}/${sessions.length} session(s) from persistence`
|
||||
);
|
||||
return restoredCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Start if called directly
|
||||
|
||||
@@ -23,17 +23,6 @@ import {
|
||||
|
||||
dotenv.config();
|
||||
|
||||
/**
|
||||
* MCP tool response format with optional structured content
|
||||
*/
|
||||
interface MCPToolResponse {
|
||||
content: Array<{
|
||||
type: 'text';
|
||||
text: string;
|
||||
}>;
|
||||
structuredContent?: unknown;
|
||||
}
|
||||
|
||||
let expressServer: any;
|
||||
let authToken: string | null = null;
|
||||
|
||||
@@ -412,46 +401,19 @@ export async function startFixedHTTPServer() {
|
||||
// Delegate to the MCP server
|
||||
const toolName = jsonRpcRequest.params?.name;
|
||||
const toolArgs = jsonRpcRequest.params?.arguments || {};
|
||||
|
||||
|
||||
try {
|
||||
const result = await mcpServer.executeTool(toolName, toolArgs);
|
||||
|
||||
// Convert result to JSON text for content field
|
||||
let responseText = JSON.stringify(result, null, 2);
|
||||
|
||||
// Build MCP-compliant response with structuredContent for validation tools
|
||||
const mcpResult: MCPToolResponse = {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: responseText
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Add structuredContent for validation tools (they have outputSchema)
|
||||
// Apply 1MB safety limit to prevent memory issues (matches STDIO server behavior)
|
||||
if (toolName.startsWith('validate_')) {
|
||||
const resultSize = responseText.length;
|
||||
|
||||
if (resultSize > 1000000) {
|
||||
// Response is too large - truncate and warn
|
||||
logger.warn(
|
||||
`Validation tool ${toolName} response is very large (${resultSize} chars). ` +
|
||||
`Truncating for HTTP transport safety.`
|
||||
);
|
||||
mcpResult.content[0].text = responseText.substring(0, 999000) +
|
||||
'\n\n[Response truncated due to size limits]';
|
||||
// Don't include structuredContent for truncated responses
|
||||
} else {
|
||||
// Normal case - include structured content for MCP protocol compliance
|
||||
mcpResult.structuredContent = result;
|
||||
}
|
||||
}
|
||||
|
||||
response = {
|
||||
jsonrpc: '2.0',
|
||||
result: mcpResult,
|
||||
result: {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify(result, null, 2)
|
||||
}
|
||||
]
|
||||
},
|
||||
id: jsonRpcRequest.id
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
@@ -18,9 +18,6 @@ export {
|
||||
validateInstanceContext,
|
||||
isInstanceContext
|
||||
} from './types/instance-context';
|
||||
export type {
|
||||
SessionState
|
||||
} from './types/session-state';
|
||||
|
||||
// Re-export MCP SDK types for convenience
|
||||
export type {
|
||||
|
||||
@@ -9,7 +9,6 @@ import { Request, Response } from 'express';
|
||||
import { SingleSessionHTTPServer } from './http-server-single-session';
|
||||
import { logger } from './utils/logger';
|
||||
import { InstanceContext } from './types/instance-context';
|
||||
import { SessionState } from './types/session-state';
|
||||
|
||||
export interface EngineHealth {
|
||||
status: 'healthy' | 'unhealthy';
|
||||
@@ -98,7 +97,7 @@ export class N8NMCPEngine {
|
||||
total: Math.round(memoryUsage.heapTotal / 1024 / 1024),
|
||||
unit: 'MB'
|
||||
},
|
||||
version: '2.24.1'
|
||||
version: '2.3.2'
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Health check failed:', error);
|
||||
@@ -107,7 +106,7 @@ export class N8NMCPEngine {
|
||||
uptime: 0,
|
||||
sessionActive: false,
|
||||
memoryUsage: { used: 0, total: 0, unit: 'MB' },
|
||||
version: '2.24.1'
|
||||
version: '2.3.2'
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -119,58 +118,10 @@ export class N8NMCPEngine {
|
||||
getSessionInfo(): { active: boolean; sessionId?: string; age?: number } {
|
||||
return this.server.getSessionInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all active session state for persistence
|
||||
*
|
||||
* Used by multi-tenant backends to dump sessions before container restart.
|
||||
* Returns an array of session state objects containing metadata and credentials.
|
||||
*
|
||||
* SECURITY WARNING: Exported data contains plaintext n8n API keys.
|
||||
* Encrypt before persisting to disk.
|
||||
*
|
||||
* @returns Array of session state objects
|
||||
*
|
||||
* @example
|
||||
* // Before shutdown
|
||||
* const sessions = engine.exportSessionState();
|
||||
* await saveToEncryptedStorage(sessions);
|
||||
*/
|
||||
exportSessionState(): SessionState[] {
|
||||
if (!this.server) {
|
||||
logger.warn('Cannot export sessions: server not initialized');
|
||||
return [];
|
||||
}
|
||||
return this.server.exportSessionState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore session state from previously exported data
|
||||
*
|
||||
* Used by multi-tenant backends to restore sessions after container restart.
|
||||
* Restores session metadata and instance context. Transports/servers are
|
||||
* recreated on first request.
|
||||
*
|
||||
* @param sessions - Array of session state objects from exportSessionState()
|
||||
* @returns Number of sessions successfully restored
|
||||
*
|
||||
* @example
|
||||
* // After startup
|
||||
* const sessions = await loadFromEncryptedStorage();
|
||||
* const count = engine.restoreSessionState(sessions);
|
||||
* console.log(`Restored ${count} sessions`);
|
||||
*/
|
||||
restoreSessionState(sessions: SessionState[]): number {
|
||||
if (!this.server) {
|
||||
logger.warn('Cannot restore sessions: server not initialized');
|
||||
return 0;
|
||||
}
|
||||
return this.server.restoreSessionState(sessions);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Graceful shutdown for service lifecycle
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* process.on('SIGTERM', async () => {
|
||||
* await engine.shutdown();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,25 +11,6 @@ import { getN8nApiClient } from './handlers-n8n-manager';
|
||||
import { N8nApiError, getUserFriendlyErrorMessage } from '../utils/n8n-errors';
|
||||
import { logger } from '../utils/logger';
|
||||
import { InstanceContext } from '../types/instance-context';
|
||||
import { validateWorkflowStructure } from '../services/n8n-validation';
|
||||
import { NodeRepository } from '../database/node-repository';
|
||||
import { WorkflowVersioningService } from '../services/workflow-versioning-service';
|
||||
import { WorkflowValidator } from '../services/workflow-validator';
|
||||
import { EnhancedConfigValidator } from '../services/enhanced-config-validator';
|
||||
|
||||
// Cached validator instance to avoid recreating on every mutation
|
||||
let cachedValidator: WorkflowValidator | null = null;
|
||||
|
||||
/**
|
||||
* Get or create cached workflow validator instance
|
||||
* Reuses the same validator to avoid redundant NodeSimilarityService initialization
|
||||
*/
|
||||
function getValidator(repository: NodeRepository): WorkflowValidator {
|
||||
if (!cachedValidator) {
|
||||
cachedValidator = new WorkflowValidator(repository, EnhancedConfigValidator);
|
||||
}
|
||||
return cachedValidator;
|
||||
}
|
||||
|
||||
// Zod schema for the diff request
|
||||
const workflowDiffSchema = z.object({
|
||||
@@ -66,35 +47,23 @@ const workflowDiffSchema = z.object({
|
||||
})),
|
||||
validateOnly: z.boolean().optional(),
|
||||
continueOnError: z.boolean().optional(),
|
||||
createBackup: z.boolean().optional(),
|
||||
intent: z.string().optional(),
|
||||
});
|
||||
|
||||
export async function handleUpdatePartialWorkflow(
|
||||
args: unknown,
|
||||
repository: NodeRepository,
|
||||
context?: InstanceContext
|
||||
): Promise<McpToolResponse> {
|
||||
const startTime = Date.now();
|
||||
const sessionId = `mutation_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
||||
let workflowBefore: any = null;
|
||||
let validationBefore: any = null;
|
||||
let validationAfter: any = null;
|
||||
|
||||
export async function handleUpdatePartialWorkflow(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
|
||||
try {
|
||||
// Debug logging (only in debug mode)
|
||||
if (process.env.DEBUG_MCP === 'true') {
|
||||
logger.debug('Workflow diff request received', {
|
||||
argsType: typeof args,
|
||||
hasWorkflowId: args && typeof args === 'object' && 'workflowId' in args,
|
||||
operationCount: args && typeof args === 'object' && 'operations' in args ?
|
||||
operationCount: args && typeof args === 'object' && 'operations' in args ?
|
||||
(args as any).operations?.length : 0
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Validate input
|
||||
const input = workflowDiffSchema.parse(args);
|
||||
|
||||
|
||||
// Get API client
|
||||
const client = getN8nApiClient(context);
|
||||
if (!client) {
|
||||
@@ -103,31 +72,11 @@ export async function handleUpdatePartialWorkflow(
|
||||
error: 'n8n API not configured. Please set N8N_API_URL and N8N_API_KEY environment variables.'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Fetch current workflow
|
||||
let workflow;
|
||||
try {
|
||||
workflow = await client.getWorkflow(input.id);
|
||||
// Store original workflow for telemetry
|
||||
workflowBefore = JSON.parse(JSON.stringify(workflow));
|
||||
|
||||
// Validate workflow BEFORE mutation (for telemetry)
|
||||
try {
|
||||
const validator = getValidator(repository);
|
||||
validationBefore = await validator.validateWorkflow(workflowBefore, {
|
||||
validateNodes: true,
|
||||
validateConnections: true,
|
||||
validateExpressions: true,
|
||||
profile: 'runtime'
|
||||
});
|
||||
} catch (validationError) {
|
||||
logger.debug('Pre-mutation validation failed (non-blocking):', validationError);
|
||||
// Don't block mutation on validation errors
|
||||
validationBefore = {
|
||||
valid: false,
|
||||
errors: [{ type: 'validation_error', message: 'Validation failed' }]
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof N8nApiError) {
|
||||
return {
|
||||
@@ -138,31 +87,7 @@ export async function handleUpdatePartialWorkflow(
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Create backup before modifying workflow (default: true)
|
||||
if (input.createBackup !== false && !input.validateOnly) {
|
||||
try {
|
||||
const versioningService = new WorkflowVersioningService(repository, client);
|
||||
const backupResult = await versioningService.createBackup(input.id, workflow, {
|
||||
trigger: 'partial_update',
|
||||
operations: input.operations
|
||||
});
|
||||
|
||||
logger.info('Workflow backup created', {
|
||||
workflowId: input.id,
|
||||
versionId: backupResult.versionId,
|
||||
versionNumber: backupResult.versionNumber,
|
||||
pruned: backupResult.pruned
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.warn('Failed to create workflow backup', {
|
||||
workflowId: input.id,
|
||||
error: error.message
|
||||
});
|
||||
// Continue with update even if backup fails (non-blocking)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Apply diff operations
|
||||
const diffEngine = new WorkflowDiffEngine();
|
||||
const diffRequest = input as WorkflowDiffRequest;
|
||||
@@ -181,7 +106,6 @@ export async function handleUpdatePartialWorkflow(
|
||||
error: 'Failed to apply diff operations',
|
||||
details: {
|
||||
errors: diffResult.errors,
|
||||
warnings: diffResult.warnings,
|
||||
operationsApplied: diffResult.operationsApplied,
|
||||
applied: diffResult.applied,
|
||||
failed: diffResult.failed
|
||||
@@ -198,204 +122,28 @@ export async function handleUpdatePartialWorkflow(
|
||||
data: {
|
||||
valid: true,
|
||||
operationsToApply: input.operations.length
|
||||
},
|
||||
details: {
|
||||
warnings: diffResult.warnings
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Validate final workflow structure after applying all operations
|
||||
// This prevents creating workflows that pass operation-level validation
|
||||
// but fail workflow-level validation (e.g., UI can't render them)
|
||||
//
|
||||
// Validation can be skipped for specific integration tests that need to test
|
||||
// n8n API behavior with edge case workflows by setting SKIP_WORKFLOW_VALIDATION=true
|
||||
if (diffResult.workflow) {
|
||||
const structureErrors = validateWorkflowStructure(diffResult.workflow);
|
||||
if (structureErrors.length > 0) {
|
||||
const skipValidation = process.env.SKIP_WORKFLOW_VALIDATION === 'true';
|
||||
|
||||
logger.warn('Workflow structure validation failed after applying diff operations', {
|
||||
workflowId: input.id,
|
||||
errors: structureErrors,
|
||||
blocking: !skipValidation
|
||||
});
|
||||
|
||||
// Analyze error types to provide targeted recovery guidance
|
||||
const errorTypes = new Set<string>();
|
||||
structureErrors.forEach(err => {
|
||||
if (err.includes('operator') || err.includes('singleValue')) errorTypes.add('operator_issues');
|
||||
if (err.includes('connection') || err.includes('referenced')) errorTypes.add('connection_issues');
|
||||
if (err.includes('Missing') || err.includes('missing')) errorTypes.add('missing_metadata');
|
||||
if (err.includes('branch') || err.includes('output')) errorTypes.add('branch_mismatch');
|
||||
});
|
||||
|
||||
// Build recovery guidance based on error types
|
||||
const recoverySteps = [];
|
||||
if (errorTypes.has('operator_issues')) {
|
||||
recoverySteps.push('Operator structure issue detected. Use validate_node_operation to check specific nodes.');
|
||||
recoverySteps.push('Binary operators (equals, contains, greaterThan, etc.) must NOT have singleValue:true');
|
||||
recoverySteps.push('Unary operators (isEmpty, isNotEmpty, true, false) REQUIRE singleValue:true');
|
||||
}
|
||||
if (errorTypes.has('connection_issues')) {
|
||||
recoverySteps.push('Connection validation failed. Check all node connections reference existing nodes.');
|
||||
recoverySteps.push('Use cleanStaleConnections operation to remove connections to non-existent nodes.');
|
||||
}
|
||||
if (errorTypes.has('missing_metadata')) {
|
||||
recoverySteps.push('Missing metadata detected. Ensure filter-based nodes (IF v2.2+, Switch v3.2+) have complete conditions.options.');
|
||||
recoverySteps.push('Required options: {version: 2, leftValue: "", caseSensitive: true, typeValidation: "strict"}');
|
||||
}
|
||||
if (errorTypes.has('branch_mismatch')) {
|
||||
recoverySteps.push('Branch count mismatch. Ensure Switch nodes have outputs for all rules (e.g., 3 rules = 3 output branches).');
|
||||
}
|
||||
|
||||
// Add generic recovery steps if no specific guidance
|
||||
if (recoverySteps.length === 0) {
|
||||
recoverySteps.push('Review the validation errors listed above');
|
||||
recoverySteps.push('Fix issues using updateNode or cleanStaleConnections operations');
|
||||
recoverySteps.push('Run validate_workflow again to verify fixes');
|
||||
}
|
||||
|
||||
const errorMessage = structureErrors.length === 1
|
||||
? `Workflow validation failed: ${structureErrors[0]}`
|
||||
: `Workflow validation failed with ${structureErrors.length} structural issues`;
|
||||
|
||||
// If validation is not skipped, return error and block the save
|
||||
if (!skipValidation) {
|
||||
return {
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
details: {
|
||||
errors: structureErrors,
|
||||
errorCount: structureErrors.length,
|
||||
operationsApplied: diffResult.operationsApplied,
|
||||
applied: diffResult.applied,
|
||||
recoveryGuidance: recoverySteps,
|
||||
note: 'Operations were applied but created an invalid workflow structure. The workflow was NOT saved to n8n to prevent UI rendering errors.',
|
||||
autoSanitizationNote: 'Auto-sanitization runs on all nodes during updates to fix operator structures and add missing metadata. However, it cannot fix all issues (e.g., broken connections, branch mismatches). Use the recovery guidance above to resolve remaining issues.'
|
||||
}
|
||||
};
|
||||
}
|
||||
// Validation skipped: log warning but continue (for specific integration tests)
|
||||
logger.info('Workflow validation skipped (SKIP_WORKFLOW_VALIDATION=true): Allowing workflow with validation warnings to proceed', {
|
||||
workflowId: input.id,
|
||||
warningCount: structureErrors.length
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Update workflow via API
|
||||
try {
|
||||
const updatedWorkflow = await client.updateWorkflow(input.id, diffResult.workflow!);
|
||||
|
||||
// Handle activation/deactivation if requested
|
||||
let finalWorkflow = updatedWorkflow;
|
||||
let activationMessage = '';
|
||||
|
||||
// Validate workflow AFTER mutation (for telemetry)
|
||||
try {
|
||||
const validator = getValidator(repository);
|
||||
validationAfter = await validator.validateWorkflow(finalWorkflow, {
|
||||
validateNodes: true,
|
||||
validateConnections: true,
|
||||
validateExpressions: true,
|
||||
profile: 'runtime'
|
||||
});
|
||||
} catch (validationError) {
|
||||
logger.debug('Post-mutation validation failed (non-blocking):', validationError);
|
||||
// Don't block on validation errors
|
||||
validationAfter = {
|
||||
valid: false,
|
||||
errors: [{ type: 'validation_error', message: 'Validation failed' }]
|
||||
};
|
||||
}
|
||||
|
||||
if (diffResult.shouldActivate) {
|
||||
try {
|
||||
finalWorkflow = await client.activateWorkflow(input.id);
|
||||
activationMessage = ' Workflow activated.';
|
||||
} catch (activationError) {
|
||||
logger.error('Failed to activate workflow after update', activationError);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Workflow updated successfully but activation failed',
|
||||
details: {
|
||||
workflowUpdated: true,
|
||||
activationError: activationError instanceof Error ? activationError.message : 'Unknown error'
|
||||
}
|
||||
};
|
||||
}
|
||||
} else if (diffResult.shouldDeactivate) {
|
||||
try {
|
||||
finalWorkflow = await client.deactivateWorkflow(input.id);
|
||||
activationMessage = ' Workflow deactivated.';
|
||||
} catch (deactivationError) {
|
||||
logger.error('Failed to deactivate workflow after update', deactivationError);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Workflow updated successfully but deactivation failed',
|
||||
details: {
|
||||
workflowUpdated: true,
|
||||
deactivationError: deactivationError instanceof Error ? deactivationError.message : 'Unknown error'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Track successful mutation
|
||||
if (workflowBefore && !input.validateOnly) {
|
||||
trackWorkflowMutation({
|
||||
sessionId,
|
||||
toolName: 'n8n_update_partial_workflow',
|
||||
userIntent: input.intent || 'Partial workflow update',
|
||||
operations: input.operations,
|
||||
workflowBefore,
|
||||
workflowAfter: finalWorkflow,
|
||||
validationBefore,
|
||||
validationAfter,
|
||||
mutationSuccess: true,
|
||||
durationMs: Date.now() - startTime,
|
||||
}).catch(err => {
|
||||
logger.debug('Failed to track mutation telemetry:', err);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: finalWorkflow,
|
||||
message: `Workflow "${finalWorkflow.name}" updated successfully. Applied ${diffResult.operationsApplied} operations.${activationMessage}`,
|
||||
data: updatedWorkflow,
|
||||
message: `Workflow "${updatedWorkflow.name}" updated successfully. Applied ${diffResult.operationsApplied} operations.`,
|
||||
details: {
|
||||
operationsApplied: diffResult.operationsApplied,
|
||||
workflowId: finalWorkflow.id,
|
||||
workflowName: finalWorkflow.name,
|
||||
active: finalWorkflow.active,
|
||||
workflowId: updatedWorkflow.id,
|
||||
workflowName: updatedWorkflow.name,
|
||||
applied: diffResult.applied,
|
||||
failed: diffResult.failed,
|
||||
errors: diffResult.errors,
|
||||
warnings: diffResult.warnings
|
||||
errors: diffResult.errors
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
// Track failed mutation
|
||||
if (workflowBefore && !input.validateOnly) {
|
||||
trackWorkflowMutation({
|
||||
sessionId,
|
||||
toolName: 'n8n_update_partial_workflow',
|
||||
userIntent: input.intent || 'Partial workflow update',
|
||||
operations: input.operations,
|
||||
workflowBefore,
|
||||
workflowAfter: workflowBefore, // No change since it failed
|
||||
validationBefore,
|
||||
validationAfter: validationBefore, // Same as before since mutation failed
|
||||
mutationSuccess: false,
|
||||
mutationError: error instanceof Error ? error.message : 'Unknown error',
|
||||
durationMs: Date.now() - startTime,
|
||||
}).catch(err => {
|
||||
logger.warn('Failed to track mutation telemetry for failed operation:', err);
|
||||
});
|
||||
}
|
||||
|
||||
if (error instanceof N8nApiError) {
|
||||
return {
|
||||
success: false,
|
||||
@@ -414,7 +162,7 @@ export async function handleUpdatePartialWorkflow(
|
||||
details: { errors: error.errors }
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
logger.error('Failed to update partial workflow', error);
|
||||
return {
|
||||
success: false,
|
||||
@@ -423,90 +171,3 @@ export async function handleUpdatePartialWorkflow(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Infer intent from operations when not explicitly provided
|
||||
*/
|
||||
function inferIntentFromOperations(operations: any[]): string {
|
||||
if (!operations || operations.length === 0) {
|
||||
return 'Partial workflow update';
|
||||
}
|
||||
|
||||
const opTypes = operations.map((op) => op.type);
|
||||
const opCount = operations.length;
|
||||
|
||||
// Single operation - be specific
|
||||
if (opCount === 1) {
|
||||
const op = operations[0];
|
||||
switch (op.type) {
|
||||
case 'addNode':
|
||||
return `Add ${op.node?.type || 'node'}`;
|
||||
case 'removeNode':
|
||||
return `Remove node ${op.nodeName || op.nodeId || ''}`.trim();
|
||||
case 'updateNode':
|
||||
return `Update node ${op.nodeName || op.nodeId || ''}`.trim();
|
||||
case 'addConnection':
|
||||
return `Connect ${op.source || 'node'} to ${op.target || 'node'}`;
|
||||
case 'removeConnection':
|
||||
return `Disconnect ${op.source || 'node'} from ${op.target || 'node'}`;
|
||||
case 'rewireConnection':
|
||||
return `Rewire ${op.source || 'node'} from ${op.from || ''} to ${op.to || ''}`.trim();
|
||||
case 'updateName':
|
||||
return `Rename workflow to "${op.name || ''}"`;
|
||||
case 'activateWorkflow':
|
||||
return 'Activate workflow';
|
||||
case 'deactivateWorkflow':
|
||||
return 'Deactivate workflow';
|
||||
default:
|
||||
return `Workflow ${op.type}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Multiple operations - summarize pattern
|
||||
const typeSet = new Set(opTypes);
|
||||
const summary: string[] = [];
|
||||
|
||||
if (typeSet.has('addNode')) {
|
||||
const count = opTypes.filter((t) => t === 'addNode').length;
|
||||
summary.push(`add ${count} node${count > 1 ? 's' : ''}`);
|
||||
}
|
||||
if (typeSet.has('removeNode')) {
|
||||
const count = opTypes.filter((t) => t === 'removeNode').length;
|
||||
summary.push(`remove ${count} node${count > 1 ? 's' : ''}`);
|
||||
}
|
||||
if (typeSet.has('updateNode')) {
|
||||
const count = opTypes.filter((t) => t === 'updateNode').length;
|
||||
summary.push(`update ${count} node${count > 1 ? 's' : ''}`);
|
||||
}
|
||||
if (typeSet.has('addConnection') || typeSet.has('rewireConnection')) {
|
||||
summary.push('modify connections');
|
||||
}
|
||||
if (typeSet.has('updateName') || typeSet.has('updateSettings')) {
|
||||
summary.push('update metadata');
|
||||
}
|
||||
|
||||
return summary.length > 0
|
||||
? `Workflow update: ${summary.join(', ')}`
|
||||
: `Workflow update: ${opCount} operations`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Track workflow mutation for telemetry
|
||||
*/
|
||||
async function trackWorkflowMutation(data: any): Promise<void> {
|
||||
try {
|
||||
// Enhance intent if it's missing or generic
|
||||
if (
|
||||
!data.userIntent ||
|
||||
data.userIntent === 'Partial workflow update' ||
|
||||
data.userIntent.length < 10
|
||||
) {
|
||||
data.userIntent = inferIntentFromOperations(data.operations);
|
||||
}
|
||||
|
||||
const { telemetry } = await import('../telemetry/telemetry-manager.js');
|
||||
await telemetry.trackWorkflowMutation(data);
|
||||
} catch (error) {
|
||||
logger.debug('Telemetry tracking failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
71
src/mcp/tool-docs/configuration/get-node-as-tool-info.ts
Normal file
71
src/mcp/tool-docs/configuration/get-node-as-tool-info.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const getNodeAsToolInfoDoc: ToolDocumentation = {
|
||||
name: 'get_node_as_tool_info',
|
||||
category: 'configuration',
|
||||
essentials: {
|
||||
description: 'Explains how to use ANY node as an AI tool with requirements and examples.',
|
||||
keyParameters: ['nodeType'],
|
||||
example: 'get_node_as_tool_info({nodeType: "nodes-base.slack"})',
|
||||
performance: 'Fast - returns guidance and examples',
|
||||
tips: [
|
||||
'ANY node can be used as AI tool, not just AI-marked ones',
|
||||
'Community nodes need N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true',
|
||||
'Provides specific use cases and connection requirements'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: `Shows how to use any n8n node as an AI tool in AI Agent workflows. In n8n, ANY node can be connected to an AI Agent's tool port, allowing the AI to use that node's functionality. This tool provides specific guidance, requirements, and examples for using a node as an AI tool.`,
|
||||
parameters: {
|
||||
nodeType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Full node type WITH prefix: "nodes-base.slack", "nodes-base.googleSheets", etc.',
|
||||
examples: [
|
||||
'nodes-base.slack',
|
||||
'nodes-base.httpRequest',
|
||||
'nodes-base.googleSheets',
|
||||
'nodes-langchain.documentLoader'
|
||||
]
|
||||
}
|
||||
},
|
||||
returns: `Object containing:
|
||||
- nodeType: The node's full type identifier
|
||||
- displayName: Human-readable name
|
||||
- isMarkedAsAITool: Whether node has usableAsTool property
|
||||
- aiToolCapabilities: Detailed AI tool usage information including:
|
||||
- canBeUsedAsTool: Always true in n8n
|
||||
- requiresEnvironmentVariable: For community nodes
|
||||
- commonUseCases: Specific AI tool use cases
|
||||
- requirements: Connection and environment setup
|
||||
- examples: Code examples for common scenarios
|
||||
- tips: Best practices for AI tool usage`,
|
||||
examples: [
|
||||
'get_node_as_tool_info({nodeType: "nodes-base.slack"}) - Get AI tool guidance for Slack',
|
||||
'get_node_as_tool_info({nodeType: "nodes-base.httpRequest"}) - Learn to use HTTP Request as AI tool',
|
||||
'get_node_as_tool_info({nodeType: "nodes-base.postgres"}) - Database queries as AI tools'
|
||||
],
|
||||
useCases: [
|
||||
'Understanding how to connect any node to AI Agent',
|
||||
'Learning environment requirements for community nodes',
|
||||
'Getting specific use case examples for AI tool usage',
|
||||
'Checking if a node is optimized for AI usage',
|
||||
'Understanding credential requirements for AI tools'
|
||||
],
|
||||
performance: 'Very fast - returns pre-computed guidance and examples',
|
||||
bestPractices: [
|
||||
'Use this before configuring nodes as AI tools',
|
||||
'Check environment requirements for community nodes',
|
||||
'Review common use cases to understand best applications',
|
||||
'Test nodes independently before connecting to AI Agent',
|
||||
'Give tools descriptive names in AI Agent configuration'
|
||||
],
|
||||
pitfalls: [
|
||||
'Community nodes require environment variable to be used as tools',
|
||||
'Not all nodes make sense as AI tools (e.g., triggers)',
|
||||
'Some nodes require specific credentials configuration',
|
||||
'Tool descriptions in AI Agent must be clear and detailed'
|
||||
],
|
||||
relatedTools: ['list_ai_tools', 'get_node_essentials', 'validate_node_operation']
|
||||
}
|
||||
};
|
||||
45
src/mcp/tool-docs/configuration/get-node-documentation.ts
Normal file
45
src/mcp/tool-docs/configuration/get-node-documentation.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const getNodeDocumentationDoc: ToolDocumentation = {
|
||||
name: 'get_node_documentation',
|
||||
category: 'configuration',
|
||||
essentials: {
|
||||
description: 'Get readable docs with examples/auth/patterns. Better than raw schema! 87% coverage. Format: "nodes-base.slack"',
|
||||
keyParameters: ['nodeType'],
|
||||
example: 'get_node_documentation({nodeType: "nodes-base.slack"})',
|
||||
performance: 'Fast - pre-parsed',
|
||||
tips: [
|
||||
'87% coverage',
|
||||
'Includes auth examples',
|
||||
'Human-readable format'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: 'Returns human-readable documentation parsed from n8n-docs including examples, authentication setup, and common patterns. More useful than raw schema for understanding node usage.',
|
||||
parameters: {
|
||||
nodeType: { type: 'string', required: true, description: 'Full node type with prefix (e.g., "nodes-base.slack")' }
|
||||
},
|
||||
returns: 'Parsed markdown documentation with examples, authentication guides, common patterns',
|
||||
examples: [
|
||||
'get_node_documentation({nodeType: "nodes-base.slack"}) - Slack usage guide',
|
||||
'get_node_documentation({nodeType: "nodes-base.googleSheets"}) - Sheets examples'
|
||||
],
|
||||
useCases: [
|
||||
'Understanding authentication setup',
|
||||
'Finding usage examples',
|
||||
'Learning common patterns'
|
||||
],
|
||||
performance: 'Fast - Pre-parsed documentation stored in database',
|
||||
bestPractices: [
|
||||
'Use for learning node usage',
|
||||
'Check coverage with get_database_statistics',
|
||||
'Combine with get_node_essentials'
|
||||
],
|
||||
pitfalls: [
|
||||
'Not all nodes have docs (87% coverage)',
|
||||
'May be outdated for new features',
|
||||
'Requires full node type prefix'
|
||||
],
|
||||
relatedTools: ['get_node_info', 'get_node_essentials', 'search_nodes']
|
||||
}
|
||||
};
|
||||
86
src/mcp/tool-docs/configuration/get-node-essentials.ts
Normal file
86
src/mcp/tool-docs/configuration/get-node-essentials.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const getNodeEssentialsDoc: ToolDocumentation = {
|
||||
name: 'get_node_essentials',
|
||||
category: 'configuration',
|
||||
essentials: {
|
||||
description: 'Returns only the most commonly-used properties for a node (10-20 fields). Response is 95% smaller than get_node_info (5KB vs 100KB+). Essential properties include required fields, common options, and authentication settings. Use validate_node_operation for working configurations.',
|
||||
keyParameters: ['nodeType'],
|
||||
example: 'get_node_essentials({nodeType: "nodes-base.slack"})',
|
||||
performance: '<10ms, ~5KB response',
|
||||
tips: [
|
||||
'Always use this before get_node_info',
|
||||
'Use validate_node_operation for examples',
|
||||
'Perfect for understanding node structure'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: 'Returns a curated subset of node properties focusing on the most commonly-used fields. Essential properties are hand-picked for each node type and include: required fields, primary operations, authentication options, and the most frequent configuration patterns. NOTE: Examples have been removed to avoid confusion - use validate_node_operation to get working configurations with proper validation.',
|
||||
parameters: {
|
||||
nodeType: { type: 'string', description: 'Full node type with prefix, e.g., "nodes-base.slack", "nodes-base.httpRequest"', required: true }
|
||||
},
|
||||
returns: `Object containing:
|
||||
{
|
||||
"nodeType": "nodes-base.slack",
|
||||
"displayName": "Slack",
|
||||
"description": "Consume Slack API",
|
||||
"category": "output",
|
||||
"version": "2.3",
|
||||
"requiredProperties": [], // Most nodes have no strictly required fields
|
||||
"commonProperties": [
|
||||
{
|
||||
"name": "resource",
|
||||
"displayName": "Resource",
|
||||
"type": "options",
|
||||
"options": ["channel", "message", "user"],
|
||||
"default": "message"
|
||||
},
|
||||
{
|
||||
"name": "operation",
|
||||
"displayName": "Operation",
|
||||
"type": "options",
|
||||
"options": ["post", "update", "delete"],
|
||||
"default": "post"
|
||||
},
|
||||
// ... 10-20 most common properties
|
||||
],
|
||||
"operations": [
|
||||
{"name": "Post", "description": "Post a message"},
|
||||
{"name": "Update", "description": "Update a message"}
|
||||
],
|
||||
"metadata": {
|
||||
"totalProperties": 121,
|
||||
"isAITool": false,
|
||||
"hasCredentials": true
|
||||
}
|
||||
}`,
|
||||
examples: [
|
||||
'get_node_essentials({nodeType: "nodes-base.httpRequest"}) - HTTP configuration basics',
|
||||
'get_node_essentials({nodeType: "nodes-base.slack"}) - Slack messaging essentials',
|
||||
'get_node_essentials({nodeType: "nodes-base.googleSheets"}) - Sheets operations',
|
||||
'// Workflow: search → essentials → validate',
|
||||
'const nodes = search_nodes({query: "database"});',
|
||||
'const mysql = get_node_essentials({nodeType: "nodes-base.mySql"});',
|
||||
'validate_node_operation("nodes-base.mySql", {operation: "select"}, "minimal");'
|
||||
],
|
||||
useCases: [
|
||||
'Quickly understand node structure without information overload',
|
||||
'Identify which properties are most important',
|
||||
'Learn node basics before diving into advanced features',
|
||||
'Build workflows faster with curated property sets'
|
||||
],
|
||||
performance: '<10ms response time, ~5KB payload (vs 100KB+ for full schema)',
|
||||
bestPractices: [
|
||||
'Always start with essentials, only use get_node_info if needed',
|
||||
'Use validate_node_operation to get working configurations',
|
||||
'Check authentication requirements first',
|
||||
'Use search_node_properties if specific property not in essentials'
|
||||
],
|
||||
pitfalls: [
|
||||
'Advanced properties not included - use get_node_info for complete schema',
|
||||
'Node-specific validators may require additional fields',
|
||||
'Some nodes have 50+ properties, essentials shows only top 10-20'
|
||||
],
|
||||
relatedTools: ['get_node_info for complete schema', 'search_node_properties for finding specific fields', 'validate_node_minimal to check configuration']
|
||||
}
|
||||
};
|
||||
98
src/mcp/tool-docs/configuration/get-node-info.ts
Normal file
98
src/mcp/tool-docs/configuration/get-node-info.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const getNodeInfoDoc: ToolDocumentation = {
|
||||
name: 'get_node_info',
|
||||
category: 'configuration',
|
||||
essentials: {
|
||||
description: 'Returns complete node schema with ALL properties (100KB+ response). Only use when you need advanced properties not in get_node_essentials. Contains 200+ properties for complex nodes like HTTP Request. Requires full prefix like "nodes-base.httpRequest".',
|
||||
keyParameters: ['nodeType'],
|
||||
example: 'get_node_info({nodeType: "nodes-base.slack"})',
|
||||
performance: '100-500ms, 50-500KB response',
|
||||
tips: [
|
||||
'Try get_node_essentials first (95% smaller)',
|
||||
'Use only for advanced configurations',
|
||||
'Response may have 200+ properties'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: 'Returns the complete JSON schema for a node including all properties, operations, authentication methods, version information, and metadata. Response sizes range from 50KB to 500KB. Use this only when get_node_essentials doesn\'t provide the specific property you need.',
|
||||
parameters: {
|
||||
nodeType: { type: 'string', required: true, description: 'Full node type with prefix. Examples: "nodes-base.slack", "nodes-base.httpRequest", "nodes-langchain.openAi"' }
|
||||
},
|
||||
returns: `Complete node object containing:
|
||||
{
|
||||
"displayName": "Slack",
|
||||
"name": "slack",
|
||||
"type": "nodes-base.slack",
|
||||
"typeVersion": 2.2,
|
||||
"description": "Consume Slack API",
|
||||
"defaults": {"name": "Slack"},
|
||||
"inputs": ["main"],
|
||||
"outputs": ["main"],
|
||||
"credentials": [
|
||||
{
|
||||
"name": "slackApi",
|
||||
"required": true,
|
||||
"displayOptions": {...}
|
||||
}
|
||||
],
|
||||
"properties": [
|
||||
// 200+ property definitions including:
|
||||
{
|
||||
"displayName": "Resource",
|
||||
"name": "resource",
|
||||
"type": "options",
|
||||
"options": ["channel", "message", "user", "file", ...],
|
||||
"default": "message"
|
||||
},
|
||||
{
|
||||
"displayName": "Operation",
|
||||
"name": "operation",
|
||||
"type": "options",
|
||||
"displayOptions": {
|
||||
"show": {"resource": ["message"]}
|
||||
},
|
||||
"options": ["post", "update", "delete", "get", ...],
|
||||
"default": "post"
|
||||
},
|
||||
// ... 200+ more properties with complex conditions
|
||||
],
|
||||
"version": 2.2,
|
||||
"subtitle": "={{$parameter[\"operation\"] + \": \" + $parameter[\"resource\"]}}",
|
||||
"codex": {...},
|
||||
"supportedWebhooks": [...]
|
||||
}`,
|
||||
examples: [
|
||||
'get_node_info({nodeType: "nodes-base.httpRequest"}) - 300+ properties for HTTP requests',
|
||||
'get_node_info({nodeType: "nodes-base.googleSheets"}) - Complex operations and auth',
|
||||
'// When to use get_node_info:',
|
||||
'// 1. First try essentials',
|
||||
'const essentials = get_node_essentials({nodeType: "nodes-base.slack"});',
|
||||
'// 2. If property missing, search for it',
|
||||
'const props = search_node_properties({nodeType: "nodes-base.slack", query: "thread"});',
|
||||
'// 3. Only if needed, get full schema',
|
||||
'const full = get_node_info({nodeType: "nodes-base.slack"});'
|
||||
],
|
||||
useCases: [
|
||||
'Analyzing all available operations for a node',
|
||||
'Understanding complex property dependencies',
|
||||
'Discovering all authentication methods',
|
||||
'Building UI that shows all node options',
|
||||
'Debugging property visibility conditions'
|
||||
],
|
||||
performance: '100-500ms depending on node complexity. HTTP Request node: ~300KB, Simple nodes: ~50KB',
|
||||
bestPractices: [
|
||||
'Always try get_node_essentials first - it\'s 95% smaller',
|
||||
'Use search_node_properties to find specific advanced properties',
|
||||
'Cache results locally - schemas rarely change',
|
||||
'Parse incrementally - don\'t load entire response into memory at once'
|
||||
],
|
||||
pitfalls: [
|
||||
'Response can exceed 500KB for complex nodes',
|
||||
'Contains many rarely-used properties that add noise',
|
||||
'Property conditions can be deeply nested and complex',
|
||||
'Must use full node type with prefix (nodes-base.X not just X)'
|
||||
],
|
||||
relatedTools: ['get_node_essentials for common properties', 'search_node_properties to find specific fields', 'get_property_dependencies to understand conditions']
|
||||
}
|
||||
};
|
||||
@@ -1,88 +0,0 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const getNodeDoc: ToolDocumentation = {
|
||||
name: 'get_node',
|
||||
category: 'configuration',
|
||||
essentials: {
|
||||
description: 'Unified node information tool with progressive detail levels and multiple modes. Get node schema, docs, search properties, or version info.',
|
||||
keyParameters: ['nodeType', 'detail', 'mode', 'includeTypeInfo', 'includeExamples'],
|
||||
example: 'get_node({nodeType: "nodes-base.httpRequest", detail: "standard"})',
|
||||
performance: 'Instant (<10ms) for minimal/standard, moderate for full',
|
||||
tips: [
|
||||
'Use detail="standard" (default) for most tasks - shows required fields',
|
||||
'Use mode="docs" for readable markdown documentation',
|
||||
'Use mode="search_properties" with propertyQuery to find specific fields',
|
||||
'Use mode="versions" to check version history and breaking changes',
|
||||
'Add includeExamples=true to get real-world configuration examples'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
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
|
||||
|
||||
**Operation Modes:**
|
||||
- info (default): Node schema with configurable detail level
|
||||
- docs: Readable markdown documentation with examples and patterns
|
||||
- search_properties: Find specific properties within a node
|
||||
- versions: List all available versions with breaking changes summary
|
||||
- compare: Compare two versions with property-level changes
|
||||
- breaking: Show only breaking changes between versions
|
||||
- migrations: Show auto-migratable changes between versions`,
|
||||
parameters: {
|
||||
nodeType: { type: 'string', required: true, description: 'Full node type with prefix: "nodes-base.httpRequest" or "nodes-langchain.agent"' },
|
||||
detail: { type: 'string', required: false, description: 'Detail level for mode=info: "minimal", "standard" (default), "full"' },
|
||||
mode: { type: 'string', required: false, description: 'Operation mode: "info" (default), "docs", "search_properties", "versions", "compare", "breaking", "migrations"' },
|
||||
includeTypeInfo: { type: 'boolean', required: false, description: 'Include type structure metadata (validation rules, JS types). Adds ~80-120 tokens per property' },
|
||||
includeExamples: { type: 'boolean', required: false, description: 'Include real-world configuration examples from templates. Adds ~200-400 tokens per example' },
|
||||
propertyQuery: { type: 'string', required: false, description: 'For mode=search_properties: search term to find properties (e.g., "auth", "header", "body")' },
|
||||
maxPropertyResults: { type: 'number', required: false, description: 'For mode=search_properties: max results (default 20)' },
|
||||
fromVersion: { type: 'string', required: false, description: 'For compare/breaking/migrations modes: source version (e.g., "1.0")' },
|
||||
toVersion: { type: 'string', required: false, description: 'For compare mode: target version (e.g., "2.0"). Defaults to latest' }
|
||||
},
|
||||
returns: `Depends on mode:
|
||||
- info: Node schema with properties based on detail level
|
||||
- docs: Markdown documentation string
|
||||
- search_properties: Array of matching property paths with descriptions
|
||||
- versions: Version history with breaking changes flags
|
||||
- compare/breaking/migrations: Version comparison details`,
|
||||
examples: [
|
||||
'// Standard detail (recommended for AI agents)\nget_node({nodeType: "nodes-base.httpRequest"})',
|
||||
'// Minimal for quick metadata check\nget_node({nodeType: "nodes-base.slack", detail: "minimal"})',
|
||||
'// Full detail with examples\nget_node({nodeType: "nodes-base.googleSheets", detail: "full", includeExamples: true})',
|
||||
'// Get readable documentation\nget_node({nodeType: "nodes-base.webhook", mode: "docs"})',
|
||||
'// Search for authentication properties\nget_node({nodeType: "nodes-base.httpRequest", mode: "search_properties", propertyQuery: "auth"})',
|
||||
'// Check version history\nget_node({nodeType: "nodes-base.executeWorkflow", mode: "versions"})',
|
||||
'// Compare specific versions\nget_node({nodeType: "nodes-base.httpRequest", mode: "compare", fromVersion: "3.0", toVersion: "4.1"})'
|
||||
],
|
||||
useCases: [
|
||||
'Configure nodes for workflow building (use detail=standard)',
|
||||
'Find specific configuration options (use mode=search_properties)',
|
||||
'Get human-readable node documentation (use mode=docs)',
|
||||
'Check for breaking changes before version upgrades (use mode=breaking)',
|
||||
'Understand complex types with includeTypeInfo=true'
|
||||
],
|
||||
performance: `Token costs by detail level:
|
||||
- minimal: ~200 tokens
|
||||
- standard: ~1000-2000 tokens (default)
|
||||
- full: ~3000-8000 tokens
|
||||
- includeTypeInfo: +80-120 tokens per property
|
||||
- includeExamples: +200-400 tokens per example
|
||||
- Version modes: ~400-1200 tokens`,
|
||||
bestPractices: [
|
||||
'Start with detail="standard" - it covers 95% of use cases',
|
||||
'Only use detail="full" if standard is missing required properties',
|
||||
'Use mode="docs" when explaining nodes to users',
|
||||
'Combine includeTypeInfo=true for complex nodes (filter, resourceMapper)',
|
||||
'Check version history before configuring versioned nodes'
|
||||
],
|
||||
pitfalls: [
|
||||
'detail="full" returns large responses (~100KB) - use sparingly',
|
||||
'Node type must include prefix (nodes-base. or nodes-langchain.)',
|
||||
'includeExamples only works with mode=info and detail=standard',
|
||||
'Version modes require nodes with multiple versions in database'
|
||||
],
|
||||
relatedTools: ['search_nodes', 'validate_node', 'validate_workflow']
|
||||
}
|
||||
};
|
||||
79
src/mcp/tool-docs/configuration/get-property-dependencies.ts
Normal file
79
src/mcp/tool-docs/configuration/get-property-dependencies.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const getPropertyDependenciesDoc: ToolDocumentation = {
|
||||
name: 'get_property_dependencies',
|
||||
category: 'configuration',
|
||||
essentials: {
|
||||
description: 'Shows property dependencies and visibility rules - which fields appear when.',
|
||||
keyParameters: ['nodeType', 'config?'],
|
||||
example: 'get_property_dependencies({nodeType: "nodes-base.httpRequest"})',
|
||||
performance: 'Fast - analyzes property conditions',
|
||||
tips: [
|
||||
'Shows which properties depend on other property values',
|
||||
'Test visibility impact with optional config parameter',
|
||||
'Helps understand complex conditional property displays'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: `Analyzes property dependencies and visibility conditions for a node. Shows which properties control the visibility of other properties (e.g., sendBody=true reveals body-related fields). Optionally test how a specific configuration affects property visibility.`,
|
||||
parameters: {
|
||||
nodeType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The node type to analyze (e.g., "nodes-base.httpRequest")',
|
||||
examples: [
|
||||
'nodes-base.httpRequest',
|
||||
'nodes-base.slack',
|
||||
'nodes-base.if',
|
||||
'nodes-base.switch'
|
||||
]
|
||||
},
|
||||
config: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
description: 'Optional partial configuration to check visibility impact',
|
||||
examples: [
|
||||
'{ method: "POST", sendBody: true }',
|
||||
'{ operation: "create", resource: "contact" }',
|
||||
'{ mode: "rules" }'
|
||||
]
|
||||
}
|
||||
},
|
||||
returns: `Object containing:
|
||||
- nodeType: The analyzed node type
|
||||
- displayName: Human-readable node name
|
||||
- controllingProperties: Properties that control visibility of others
|
||||
- dependentProperties: Properties whose visibility depends on others
|
||||
- complexDependencies: Multi-condition dependencies
|
||||
- currentConfig: If config provided, shows:
|
||||
- providedValues: The configuration you passed
|
||||
- visibilityImpact: Which properties are visible/hidden`,
|
||||
examples: [
|
||||
'get_property_dependencies({nodeType: "nodes-base.httpRequest"}) - Analyze HTTP Request dependencies',
|
||||
'get_property_dependencies({nodeType: "nodes-base.httpRequest", config: {sendBody: true}}) - Test visibility with sendBody enabled',
|
||||
'get_property_dependencies({nodeType: "nodes-base.if", config: {mode: "rules"}}) - Check If node in rules mode'
|
||||
],
|
||||
useCases: [
|
||||
'Understanding which properties control others',
|
||||
'Debugging why certain fields are not visible',
|
||||
'Building dynamic UIs that match n8n behavior',
|
||||
'Testing configurations before applying them',
|
||||
'Understanding complex node property relationships'
|
||||
],
|
||||
performance: 'Fast - analyzes property metadata without database queries',
|
||||
bestPractices: [
|
||||
'Use before configuring complex nodes with many conditional fields',
|
||||
'Test different config values to understand visibility rules',
|
||||
'Check dependencies when properties seem to be missing',
|
||||
'Use for nodes with multiple operation modes (Slack, Google Sheets)',
|
||||
'Combine with search_node_properties to find specific fields'
|
||||
],
|
||||
pitfalls: [
|
||||
'Some properties have complex multi-condition dependencies',
|
||||
'Visibility rules can be nested (property A controls B which controls C)',
|
||||
'Not all hidden properties are due to dependencies (some are deprecated)',
|
||||
'Config parameter only tests visibility, does not validate values'
|
||||
],
|
||||
relatedTools: ['search_node_properties', 'get_node_essentials', 'validate_node_operation']
|
||||
}
|
||||
};
|
||||
@@ -1 +1,6 @@
|
||||
export { getNodeDoc } from './get-node';
|
||||
export { getNodeInfoDoc } from './get-node-info';
|
||||
export { getNodeEssentialsDoc } from './get-node-essentials';
|
||||
export { getNodeDocumentationDoc } from './get-node-documentation';
|
||||
export { searchNodePropertiesDoc } from './search-node-properties';
|
||||
export { getNodeAsToolInfoDoc } from './get-node-as-tool-info';
|
||||
export { getPropertyDependenciesDoc } from './get-property-dependencies';
|
||||
97
src/mcp/tool-docs/configuration/search-node-properties.ts
Normal file
97
src/mcp/tool-docs/configuration/search-node-properties.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const searchNodePropertiesDoc: ToolDocumentation = {
|
||||
name: 'search_node_properties',
|
||||
category: 'configuration',
|
||||
essentials: {
|
||||
description: 'Find specific properties in a node without downloading all 200+ properties.',
|
||||
keyParameters: ['nodeType', 'query'],
|
||||
example: 'search_node_properties({nodeType: "nodes-base.httpRequest", query: "auth"})',
|
||||
performance: 'Fast - searches indexed properties',
|
||||
tips: [
|
||||
'Search for "auth", "header", "body", "json", "credential"',
|
||||
'Returns property paths and descriptions',
|
||||
'Much faster than get_node_info for finding specific fields'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: `Searches for specific properties within a node's configuration schema. Essential for finding authentication fields, headers, body parameters, or any specific property without downloading the entire node schema (which can be 100KB+). Returns matching properties with their paths, types, and descriptions.`,
|
||||
parameters: {
|
||||
nodeType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Full type with prefix',
|
||||
examples: [
|
||||
'nodes-base.httpRequest',
|
||||
'nodes-base.slack',
|
||||
'nodes-base.postgres',
|
||||
'nodes-base.googleSheets'
|
||||
]
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Property to find: "auth", "header", "body", "json"',
|
||||
examples: [
|
||||
'auth',
|
||||
'header',
|
||||
'body',
|
||||
'json',
|
||||
'credential',
|
||||
'timeout',
|
||||
'retry',
|
||||
'pagination'
|
||||
]
|
||||
},
|
||||
maxResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'Max results (default 20)',
|
||||
default: 20
|
||||
}
|
||||
},
|
||||
returns: `Object containing:
|
||||
- nodeType: The searched node type
|
||||
- query: Your search term
|
||||
- matches: Array of matching properties with:
|
||||
- name: Property identifier
|
||||
- displayName: Human-readable name
|
||||
- type: Property type (string, number, options, etc.)
|
||||
- description: Property description
|
||||
- path: Full path to property (for nested properties)
|
||||
- required: Whether property is required
|
||||
- default: Default value if any
|
||||
- options: Available options for selection properties
|
||||
- showWhen: Visibility conditions
|
||||
- totalMatches: Number of matches found
|
||||
- searchedIn: Total properties searched`,
|
||||
examples: [
|
||||
'search_node_properties({nodeType: "nodes-base.httpRequest", query: "auth"}) - Find authentication fields',
|
||||
'search_node_properties({nodeType: "nodes-base.slack", query: "channel"}) - Find channel-related properties',
|
||||
'search_node_properties({nodeType: "nodes-base.postgres", query: "query"}) - Find query fields',
|
||||
'search_node_properties({nodeType: "nodes-base.webhook", query: "response"}) - Find response options'
|
||||
],
|
||||
useCases: [
|
||||
'Finding authentication/credential fields quickly',
|
||||
'Locating specific parameters without full node info',
|
||||
'Discovering header or body configuration options',
|
||||
'Finding nested properties in complex nodes',
|
||||
'Checking if a node supports specific features (retry, pagination, etc.)'
|
||||
],
|
||||
performance: 'Very fast - searches pre-indexed property metadata',
|
||||
bestPractices: [
|
||||
'Use before get_node_info to find specific properties',
|
||||
'Search for common terms: auth, header, body, credential',
|
||||
'Check showWhen conditions to understand visibility',
|
||||
'Use with get_property_dependencies for complete understanding',
|
||||
'Limit results if you only need to check existence'
|
||||
],
|
||||
pitfalls: [
|
||||
'Some properties may be hidden due to visibility conditions',
|
||||
'Property names may differ from display names',
|
||||
'Nested properties show full path (e.g., "options.retry.limit")',
|
||||
'Search is case-sensitive for property names'
|
||||
],
|
||||
relatedTools: ['get_node_essentials', 'get_property_dependencies', 'get_node_info']
|
||||
}
|
||||
};
|
||||
67
src/mcp/tool-docs/discovery/get-database-statistics.ts
Normal file
67
src/mcp/tool-docs/discovery/get-database-statistics.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const getDatabaseStatisticsDoc: ToolDocumentation = {
|
||||
name: 'get_database_statistics',
|
||||
category: 'discovery',
|
||||
essentials: {
|
||||
description: 'Returns database health metrics and node inventory. Shows 525 total nodes, 263 AI-capable nodes, 104 triggers, with 87% documentation coverage. Primary use: verify MCP connection is working correctly.',
|
||||
keyParameters: [],
|
||||
example: 'get_database_statistics()',
|
||||
performance: 'Instant',
|
||||
tips: [
|
||||
'First tool to call when testing MCP connection',
|
||||
'Shows exact counts for all node categories',
|
||||
'Documentation coverage indicates data quality'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: 'Returns comprehensive database statistics showing the complete inventory of n8n nodes, their categories, documentation coverage, and package distribution. Essential for verifying MCP connectivity and understanding available resources.',
|
||||
parameters: {},
|
||||
returns: `Object containing:
|
||||
{
|
||||
"total_nodes": 525, // All nodes in database
|
||||
"nodes_with_properties": 520, // Nodes with extracted properties (99%)
|
||||
"nodes_with_operations": 334, // Nodes with multiple operations (64%)
|
||||
"ai_tools": 263, // AI-capable nodes
|
||||
"triggers": 104, // Workflow trigger nodes
|
||||
"documentation_coverage": "87%", // Nodes with official docs
|
||||
"packages": {
|
||||
"n8n-nodes-base": 456, // Core n8n nodes
|
||||
"@n8n/n8n-nodes-langchain": 69 // AI/LangChain nodes
|
||||
},
|
||||
"categories": {
|
||||
"trigger": 104,
|
||||
"transform": 250,
|
||||
"output": 45,
|
||||
"input": 38,
|
||||
"AI": 88
|
||||
}
|
||||
}`,
|
||||
examples: [
|
||||
'get_database_statistics() - Returns complete statistics object',
|
||||
'// Common check:',
|
||||
'const stats = get_database_statistics();',
|
||||
'if (stats.total_nodes < 500) console.error("Database incomplete!");'
|
||||
],
|
||||
useCases: [
|
||||
'Verify MCP server is connected and responding',
|
||||
'Check if database rebuild is needed (low node count)',
|
||||
'Monitor documentation coverage improvements',
|
||||
'Validate AI tools availability for workflows',
|
||||
'Audit node distribution across packages'
|
||||
],
|
||||
performance: 'Instant (<1ms) - Statistics are pre-calculated and cached',
|
||||
bestPractices: [
|
||||
'Call this first to verify MCP connection before other operations',
|
||||
'Check total_nodes >= 500 to ensure complete database',
|
||||
'Monitor documentation_coverage for data quality',
|
||||
'Use ai_tools count to verify AI capabilities'
|
||||
],
|
||||
pitfalls: [
|
||||
'Statistics are cached at database build time, not real-time',
|
||||
'Won\'t reflect changes until database is rebuilt',
|
||||
'Package counts may vary with n8n version updates'
|
||||
],
|
||||
relatedTools: ['list_nodes for detailed node listing', 'list_ai_tools for AI nodes', 'n8n_health_check for API connectivity']
|
||||
}
|
||||
};
|
||||
@@ -1 +1,4 @@
|
||||
export { searchNodesDoc } from './search-nodes';
|
||||
export { listNodesDoc } from './list-nodes';
|
||||
export { listAiToolsDoc } from './list-ai-tools';
|
||||
export { getDatabaseStatisticsDoc } from './get-database-statistics';
|
||||
51
src/mcp/tool-docs/discovery/list-ai-tools.ts
Normal file
51
src/mcp/tool-docs/discovery/list-ai-tools.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const listAiToolsDoc: ToolDocumentation = {
|
||||
name: 'list_ai_tools',
|
||||
category: 'discovery',
|
||||
essentials: {
|
||||
description: 'DEPRECATED: Basic list of 263 AI nodes. For comprehensive AI Agent guidance, use tools_documentation({topic: "ai_agents_guide"}). That guide covers architecture, connections, tools, validation, and best practices. Use search_nodes({query: "AI", includeExamples: true}) for AI nodes with working examples.',
|
||||
keyParameters: [],
|
||||
example: 'tools_documentation({topic: "ai_agents_guide"}) // Recommended alternative',
|
||||
performance: 'Instant (cached)',
|
||||
tips: [
|
||||
'NEW: Use ai_agents_guide for comprehensive AI workflow documentation',
|
||||
'Use search_nodes({includeExamples: true}) for AI nodes with real-world examples',
|
||||
'ANY node can be an AI tool - not limited to AI-specific nodes',
|
||||
'Use get_node_as_tool_info for guidance on any node'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: '**DEPRECATED in favor of ai_agents_guide**. Lists 263 nodes with built-in AI capabilities. For comprehensive documentation on building AI Agent workflows, use tools_documentation({topic: "ai_agents_guide"}) which covers architecture, the 8 AI connection types, validation, and best practices with real examples. IMPORTANT: This basic list is NOT a complete guide - use the full AI Agents guide instead.',
|
||||
parameters: {},
|
||||
returns: 'Array of 263 AI-optimized nodes. RECOMMENDED: Use ai_agents_guide for comprehensive guidance, or search_nodes({query: "AI", includeExamples: true}) for AI nodes with working configuration examples.',
|
||||
examples: [
|
||||
'// RECOMMENDED: Use the comprehensive AI Agents guide',
|
||||
'tools_documentation({topic: "ai_agents_guide"})',
|
||||
'',
|
||||
'// Or search for AI nodes with real-world examples',
|
||||
'search_nodes({query: "AI Agent", includeExamples: true})',
|
||||
'',
|
||||
'// Basic list (deprecated)',
|
||||
'list_ai_tools() - Returns 263 AI-optimized nodes'
|
||||
],
|
||||
useCases: [
|
||||
'Discover AI model integrations (OpenAI, Anthropic, Google AI)',
|
||||
'Find vector databases for RAG applications',
|
||||
'Locate embedding generators and processors',
|
||||
'Build AI agent tool chains with ANY n8n node'
|
||||
],
|
||||
performance: 'Instant - results are pre-cached in memory',
|
||||
bestPractices: [
|
||||
'Remember: ANY node works as an AI tool when connected to AI Agent',
|
||||
'Common non-AI nodes used as tools: Slack (messaging), Google Sheets (data), HTTP Request (APIs), Code (custom logic)',
|
||||
'For community nodes: set N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true'
|
||||
],
|
||||
pitfalls: [
|
||||
'This list is NOT exhaustive - it only shows nodes with AI-specific features',
|
||||
'Don\'t limit yourself to this list when building AI workflows',
|
||||
'Community nodes require environment variable to work as tools'
|
||||
],
|
||||
relatedTools: ['get_node_as_tool_info for any node usage', 'search_nodes to find specific nodes', 'get_node_essentials to configure nodes']
|
||||
}
|
||||
};
|
||||
52
src/mcp/tool-docs/discovery/list-nodes.ts
Normal file
52
src/mcp/tool-docs/discovery/list-nodes.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const listNodesDoc: ToolDocumentation = {
|
||||
name: 'list_nodes',
|
||||
category: 'discovery',
|
||||
essentials: {
|
||||
description: 'Lists n8n nodes with filtering options. Returns up to 525 total nodes. Default limit is 50, use limit:200 to get all nodes. Filter by category to find specific node types like triggers (104 nodes) or AI nodes (263 nodes).',
|
||||
keyParameters: ['category', 'package', 'limit', 'isAITool'],
|
||||
example: 'list_nodes({limit:200})',
|
||||
performance: '<10ms for any query size',
|
||||
tips: [
|
||||
'Use limit:200 to get all 525 nodes',
|
||||
'Categories: trigger (104), transform (250+), output/input (50+)',
|
||||
'Use search_nodes for keyword search'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: 'Lists n8n nodes with comprehensive filtering options. Returns an array of node metadata including type, name, description, and category. Database contains 525 total nodes: 456 from n8n-nodes-base package and 69 from @n8n/n8n-nodes-langchain package.',
|
||||
parameters: {
|
||||
category: { type: 'string', description: 'Filter by category: "trigger" (104 nodes), "transform" (250+ nodes), "output", "input", or "AI"', required: false },
|
||||
package: { type: 'string', description: 'Filter by package: "n8n-nodes-base" (456 core nodes) or "@n8n/n8n-nodes-langchain" (69 AI nodes)', required: false },
|
||||
limit: { type: 'number', description: 'Maximum results to return. Default: 50. Use 200+ to get all 525 nodes', required: false },
|
||||
isAITool: { type: 'boolean', description: 'Filter to show only AI-capable nodes (263 nodes)', required: false },
|
||||
developmentStyle: { type: 'string', description: 'Filter by style: "programmatic" or "declarative". Most nodes are programmatic', required: false }
|
||||
},
|
||||
returns: 'Array of node objects, each containing: nodeType (e.g., "nodes-base.webhook"), displayName (e.g., "Webhook"), description, category, package, isAITool flag',
|
||||
examples: [
|
||||
'list_nodes({limit:200}) - Returns all 525 nodes',
|
||||
'list_nodes({category:"trigger"}) - Returns 104 trigger nodes (Webhook, Schedule, Email Trigger, etc.)',
|
||||
'list_nodes({package:"@n8n/n8n-nodes-langchain"}) - Returns 69 AI/LangChain nodes',
|
||||
'list_nodes({isAITool:true}) - Returns 263 AI-capable nodes',
|
||||
'list_nodes({category:"trigger", isAITool:true}) - Combines filters for AI-capable triggers'
|
||||
],
|
||||
useCases: [
|
||||
'Browse all available nodes when building workflows',
|
||||
'Find all trigger nodes to start workflows',
|
||||
'Discover AI/ML nodes for intelligent automation',
|
||||
'Check available nodes in specific packages'
|
||||
],
|
||||
performance: '<10ms for any query size. Results are cached in memory',
|
||||
bestPractices: [
|
||||
'Use limit:200 when you need the complete node inventory',
|
||||
'Filter by category for focused discovery',
|
||||
'Combine with get_node_essentials to configure selected nodes'
|
||||
],
|
||||
pitfalls: [
|
||||
'No text search capability - use search_nodes for keyword search',
|
||||
'developmentStyle filter rarely useful - most nodes are "programmatic"'
|
||||
],
|
||||
relatedTools: ['search_nodes for keyword search', 'list_ai_tools for AI-specific discovery', 'get_node_essentials to configure nodes']
|
||||
}
|
||||
};
|
||||
@@ -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 500+ 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 525 nodes in the database.',
|
||||
keyParameters: ['query', 'mode', 'limit'],
|
||||
example: 'search_nodes({query: "webhook"})',
|
||||
performance: '<20ms even for complex queries',
|
||||
@@ -42,13 +42,13 @@ 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 after finding the right node'
|
||||
'Combine with get_node_essentials after finding the right node'
|
||||
],
|
||||
pitfalls: [
|
||||
'AND mode searches all fields (name, description) not just node names',
|
||||
'FUZZY mode with very short queries (1-2 chars) may return unexpected results',
|
||||
'Exact matches in quotes are case-sensitive'
|
||||
],
|
||||
relatedTools: ['get_node to configure found nodes', 'search_templates to find workflow examples', 'validate_node to check configurations']
|
||||
relatedTools: ['list_nodes for browsing by category', 'get_node_essentials to configure found nodes', 'list_ai_tools for AI-specific search']
|
||||
}
|
||||
};
|
||||
@@ -48,7 +48,7 @@ An n8n AI Agent workflow typically consists of:
|
||||
- Manages conversation flow
|
||||
- Decides when to use tools
|
||||
- Iterates until task is complete
|
||||
- Supports fallback models for reliability
|
||||
- Supports fallback models (v2.1+)
|
||||
|
||||
3. **Language Model**: The AI brain
|
||||
- OpenAI GPT-4, Claude, Gemini, etc.
|
||||
@@ -441,7 +441,7 @@ For real-time user experience:
|
||||
|
||||
### Pattern 2: Fallback Language Models
|
||||
|
||||
For production reliability with fallback language models:
|
||||
For production reliability (requires AI Agent v2.1+):
|
||||
|
||||
\`\`\`typescript
|
||||
n8n_update_partial_workflow({
|
||||
@@ -690,7 +690,7 @@ n8n_validate_workflow({id: "workflow_id"})
|
||||
- **FINAL_AI_VALIDATION_SPEC.md**: Complete validation rules
|
||||
- **n8n_update_partial_workflow**: Workflow modification tool
|
||||
- **search_nodes({query: "AI", includeExamples: true})**: Find AI nodes with examples
|
||||
- **get_node({nodeType: "...", detail: "standard", includeExamples: true})**: Node details with examples
|
||||
- **get_node_essentials({nodeType: "...", includeExamples: true})**: Node details with examples
|
||||
|
||||
---
|
||||
|
||||
@@ -724,14 +724,15 @@ n8n_validate_workflow({id: "workflow_id"})
|
||||
'Always validate workflows after making changes',
|
||||
'AI connections require sourceOutput parameter',
|
||||
'Streaming mode has specific constraints',
|
||||
'Fallback models require AI Agent node with fallback support'
|
||||
'Some features require specific AI Agent versions (v2.1+ for fallback)'
|
||||
],
|
||||
relatedTools: [
|
||||
'n8n_create_workflow',
|
||||
'n8n_update_partial_workflow',
|
||||
'n8n_validate_workflow',
|
||||
'search_nodes',
|
||||
'get_node'
|
||||
'get_node_essentials',
|
||||
'list_ai_tools'
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,66 +1,113 @@
|
||||
import { ToolDocumentation } from './types';
|
||||
|
||||
// Import all tool documentations
|
||||
import { searchNodesDoc } from './discovery';
|
||||
import { getNodeDoc } from './configuration';
|
||||
import { validateNodeDoc, validateWorkflowDoc } from './validation';
|
||||
import { getTemplateDoc, searchTemplatesDoc } from './templates';
|
||||
import { searchNodesDoc, listNodesDoc, listAiToolsDoc, getDatabaseStatisticsDoc } from './discovery';
|
||||
import {
|
||||
getNodeEssentialsDoc,
|
||||
getNodeInfoDoc,
|
||||
getNodeDocumentationDoc,
|
||||
searchNodePropertiesDoc,
|
||||
getNodeAsToolInfoDoc,
|
||||
getPropertyDependenciesDoc
|
||||
} from './configuration';
|
||||
import {
|
||||
validateNodeMinimalDoc,
|
||||
validateNodeOperationDoc,
|
||||
validateWorkflowDoc,
|
||||
validateWorkflowConnectionsDoc,
|
||||
validateWorkflowExpressionsDoc
|
||||
} from './validation';
|
||||
import {
|
||||
listTasksDoc,
|
||||
listNodeTemplatesDoc,
|
||||
getTemplateDoc,
|
||||
searchTemplatesDoc,
|
||||
searchTemplatesByMetadataDoc,
|
||||
getTemplatesForTaskDoc
|
||||
} from './templates';
|
||||
import {
|
||||
toolsDocumentationDoc,
|
||||
n8nHealthCheckDoc
|
||||
n8nDiagnosticDoc,
|
||||
n8nHealthCheckDoc,
|
||||
n8nListAvailableToolsDoc
|
||||
} from './system';
|
||||
import { aiAgentsGuide } from './guides';
|
||||
import {
|
||||
aiAgentsGuide
|
||||
} from './guides';
|
||||
import {
|
||||
n8nCreateWorkflowDoc,
|
||||
n8nGetWorkflowDoc,
|
||||
n8nGetWorkflowDetailsDoc,
|
||||
n8nGetWorkflowStructureDoc,
|
||||
n8nGetWorkflowMinimalDoc,
|
||||
n8nUpdateFullWorkflowDoc,
|
||||
n8nUpdatePartialWorkflowDoc,
|
||||
n8nDeleteWorkflowDoc,
|
||||
n8nListWorkflowsDoc,
|
||||
n8nValidateWorkflowDoc,
|
||||
n8nAutofixWorkflowDoc,
|
||||
n8nTestWorkflowDoc,
|
||||
n8nExecutionsDoc,
|
||||
n8nWorkflowVersionsDoc,
|
||||
n8nDeployTemplateDoc
|
||||
n8nTriggerWebhookWorkflowDoc,
|
||||
n8nGetExecutionDoc,
|
||||
n8nListExecutionsDoc,
|
||||
n8nDeleteExecutionDoc
|
||||
} from './workflow_management';
|
||||
|
||||
// Combine all tool documentations into a single object
|
||||
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,
|
||||
|
||||
// Discovery tools
|
||||
search_nodes: searchNodesDoc,
|
||||
|
||||
list_nodes: listNodesDoc,
|
||||
list_ai_tools: listAiToolsDoc,
|
||||
get_database_statistics: getDatabaseStatisticsDoc,
|
||||
|
||||
// Configuration tools
|
||||
get_node: getNodeDoc,
|
||||
|
||||
get_node_essentials: getNodeEssentialsDoc,
|
||||
get_node_info: getNodeInfoDoc,
|
||||
get_node_documentation: getNodeDocumentationDoc,
|
||||
search_node_properties: searchNodePropertiesDoc,
|
||||
get_node_as_tool_info: getNodeAsToolInfoDoc,
|
||||
get_property_dependencies: getPropertyDependenciesDoc,
|
||||
|
||||
// Validation tools
|
||||
validate_node: validateNodeDoc,
|
||||
validate_node_minimal: validateNodeMinimalDoc,
|
||||
validate_node_operation: validateNodeOperationDoc,
|
||||
validate_workflow: validateWorkflowDoc,
|
||||
|
||||
validate_workflow_connections: validateWorkflowConnectionsDoc,
|
||||
validate_workflow_expressions: validateWorkflowExpressionsDoc,
|
||||
|
||||
// Template tools
|
||||
list_tasks: listTasksDoc,
|
||||
list_node_templates: listNodeTemplatesDoc,
|
||||
get_template: getTemplateDoc,
|
||||
search_templates: searchTemplatesDoc,
|
||||
|
||||
search_templates_by_metadata: searchTemplatesByMetadataDoc,
|
||||
get_templates_for_task: getTemplatesForTaskDoc,
|
||||
|
||||
// Workflow Management tools (n8n API)
|
||||
n8n_create_workflow: n8nCreateWorkflowDoc,
|
||||
n8n_get_workflow: n8nGetWorkflowDoc,
|
||||
n8n_get_workflow_details: n8nGetWorkflowDetailsDoc,
|
||||
n8n_get_workflow_structure: n8nGetWorkflowStructureDoc,
|
||||
n8n_get_workflow_minimal: n8nGetWorkflowMinimalDoc,
|
||||
n8n_update_full_workflow: n8nUpdateFullWorkflowDoc,
|
||||
n8n_update_partial_workflow: n8nUpdatePartialWorkflowDoc,
|
||||
n8n_delete_workflow: n8nDeleteWorkflowDoc,
|
||||
n8n_list_workflows: n8nListWorkflowsDoc,
|
||||
n8n_validate_workflow: n8nValidateWorkflowDoc,
|
||||
n8n_autofix_workflow: n8nAutofixWorkflowDoc,
|
||||
n8n_test_workflow: n8nTestWorkflowDoc,
|
||||
n8n_executions: n8nExecutionsDoc,
|
||||
n8n_workflow_versions: n8nWorkflowVersionsDoc,
|
||||
n8n_deploy_template: n8nDeployTemplateDoc
|
||||
n8n_trigger_webhook_workflow: n8nTriggerWebhookWorkflowDoc,
|
||||
n8n_get_execution: n8nGetExecutionDoc,
|
||||
n8n_list_executions: n8nListExecutionsDoc,
|
||||
n8n_delete_execution: n8nDeleteExecutionDoc
|
||||
};
|
||||
|
||||
// Re-export types
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
export { toolsDocumentationDoc } from './tools-documentation';
|
||||
export { n8nHealthCheckDoc } from './n8n-health-check';
|
||||
export { n8nDiagnosticDoc } from './n8n-diagnostic';
|
||||
export { n8nHealthCheckDoc } from './n8n-health-check';
|
||||
export { n8nListAvailableToolsDoc } from './n8n-list-available-tools';
|
||||
@@ -5,8 +5,8 @@ export const n8nHealthCheckDoc: ToolDocumentation = {
|
||||
category: 'system',
|
||||
essentials: {
|
||||
description: 'Check n8n instance health, API connectivity, version status, and performance metrics',
|
||||
keyParameters: ['mode', 'verbose'],
|
||||
example: 'n8n_health_check({mode: "status"})',
|
||||
keyParameters: [],
|
||||
example: 'n8n_health_check({})',
|
||||
performance: 'Fast - single API call (~150-200ms median)',
|
||||
tips: [
|
||||
'Use before starting workflow operations to ensure n8n is responsive',
|
||||
@@ -31,21 +31,7 @@ Health checks are crucial for:
|
||||
- Detecting performance degradation
|
||||
- Verifying API compatibility before operations
|
||||
- Ensuring authentication is working correctly`,
|
||||
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
|
||||
}
|
||||
},
|
||||
parameters: {},
|
||||
returns: `Health status object containing:
|
||||
- status: Overall health status ('healthy', 'degraded', 'error')
|
||||
- n8nVersion: n8n instance version information
|
||||
@@ -95,6 +81,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_list_workflows', 'n8n_validate_workflow', 'n8n_workflow_versions']
|
||||
relatedTools: ['n8n_diagnostic', 'n8n_list_available_tools', 'n8n_list_workflows']
|
||||
}
|
||||
};
|
||||
@@ -58,6 +58,6 @@ export const toolsDocumentationDoc: ToolDocumentation = {
|
||||
'Not all internal functions are documented',
|
||||
'Special topics (code guides) require exact names'
|
||||
],
|
||||
relatedTools: ['n8n_health_check for verifying API connection', 'search_templates for workflow examples', 'search_nodes for finding nodes']
|
||||
relatedTools: ['n8n_list_available_tools for dynamic tool discovery', 'list_tasks for common configurations', 'get_database_statistics to verify MCP connection']
|
||||
}
|
||||
};
|
||||
@@ -4,30 +4,23 @@ export const getTemplateDoc: ToolDocumentation = {
|
||||
name: 'get_template',
|
||||
category: 'templates',
|
||||
essentials: {
|
||||
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"})',
|
||||
description: 'Get complete workflow JSON by ID. Ready to import. IDs from list_node_templates or search_templates.',
|
||||
keyParameters: ['templateId'],
|
||||
example: 'get_template({templateId: 1234})',
|
||||
performance: 'Fast (<100ms) - single database lookup',
|
||||
tips: [
|
||||
'Get template IDs from search_templates first',
|
||||
'Use mode="nodes_only" for quick overview, "structure" for topology, "full" for import',
|
||||
'Returns complete workflow JSON ready for import into n8n'
|
||||
'Get template IDs from list_node_templates or search_templates first',
|
||||
'Returns complete workflow JSON ready for import into n8n',
|
||||
'Includes all nodes, connections, and settings'
|
||||
]
|
||||
},
|
||||
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 2,700+ 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 399+ 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']
|
||||
description: 'The numeric ID of the template to retrieve. Get IDs from list_node_templates or search_templates'
|
||||
}
|
||||
},
|
||||
returns: `Returns an object containing:
|
||||
@@ -46,10 +39,9 @@ export const getTemplateDoc: ToolDocumentation = {
|
||||
- settings: Workflow configuration (timezone, error handling, etc.)
|
||||
- usage: Instructions for using the workflow`,
|
||||
examples: [
|
||||
'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'
|
||||
'get_template({templateId: 1234}) - Get Slack notification workflow',
|
||||
'get_template({templateId: 5678}) - Get data sync workflow',
|
||||
'get_template({templateId: 9012}) - Get AI chatbot workflow'
|
||||
],
|
||||
useCases: [
|
||||
'Download workflows for direct import into n8n',
|
||||
@@ -77,6 +69,6 @@ export const getTemplateDoc: ToolDocumentation = {
|
||||
'Not all templates work with all n8n versions',
|
||||
'Template may reference external services you don\'t have access to'
|
||||
],
|
||||
relatedTools: ['search_templates', 'n8n_create_workflow']
|
||||
relatedTools: ['list_node_templates', 'search_templates', 'get_templates_for_task', 'n8n_create_workflow']
|
||||
}
|
||||
};
|
||||
74
src/mcp/tool-docs/templates/get-templates-for-task.ts
Normal file
74
src/mcp/tool-docs/templates/get-templates-for-task.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const getTemplatesForTaskDoc: ToolDocumentation = {
|
||||
name: 'get_templates_for_task',
|
||||
category: 'templates',
|
||||
essentials: {
|
||||
description: 'Curated templates by task: ai_automation, data_sync, webhooks, email, slack, data_transform, files, scheduling, api, database.',
|
||||
keyParameters: ['task'],
|
||||
example: 'get_templates_for_task({task: "slack_integration"})',
|
||||
performance: 'Fast (<100ms) - pre-categorized results',
|
||||
tips: [
|
||||
'Returns hand-picked templates for specific automation tasks',
|
||||
'Use list_tasks to see all available task categories',
|
||||
'Templates are curated for quality and relevance'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: `Retrieves curated workflow templates for specific automation tasks. This tool provides hand-picked templates organized by common use cases, making it easy to find the right workflow for your needs. Each task category contains the most popular and effective templates for that particular automation scenario.`,
|
||||
parameters: {
|
||||
task: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The type of task to get templates for. Options: ai_automation, data_sync, webhook_processing, email_automation, slack_integration, data_transformation, file_processing, scheduling, api_integration, database_operations'
|
||||
}
|
||||
},
|
||||
returns: `Returns an object containing:
|
||||
- task: The requested task type
|
||||
- templates: Array of curated templates
|
||||
- id: Template ID
|
||||
- name: Template name
|
||||
- description: What the workflow does
|
||||
- author: Creator information
|
||||
- nodes: Array of node types used
|
||||
- views: Popularity metric
|
||||
- created: Creation date
|
||||
- url: Link to template
|
||||
- totalFound: Number of templates in this category
|
||||
- availableTasks: List of all task categories (if no templates found)`,
|
||||
examples: [
|
||||
'get_templates_for_task({task: "slack_integration"}) - Get Slack automation workflows',
|
||||
'get_templates_for_task({task: "ai_automation"}) - Get AI-powered workflows',
|
||||
'get_templates_for_task({task: "data_sync"}) - Get data synchronization workflows',
|
||||
'get_templates_for_task({task: "webhook_processing"}) - Get webhook handler workflows',
|
||||
'get_templates_for_task({task: "email_automation"}) - Get email automation workflows'
|
||||
],
|
||||
useCases: [
|
||||
'Find workflows for specific business needs',
|
||||
'Discover best practices for common automations',
|
||||
'Get started quickly with pre-built solutions',
|
||||
'Learn patterns for specific integration types',
|
||||
'Browse curated collections of quality workflows'
|
||||
],
|
||||
performance: `Excellent performance with pre-categorized templates:
|
||||
- Query time: <10ms (indexed by task)
|
||||
- No filtering needed (pre-curated)
|
||||
- Returns 5-20 templates per category
|
||||
- Total response time: <100ms`,
|
||||
bestPractices: [
|
||||
'Start with task-based search for faster results',
|
||||
'Review multiple templates to find best patterns',
|
||||
'Check template age for most current approaches',
|
||||
'Combine templates from same category for complex workflows',
|
||||
'Use returned node lists to understand requirements'
|
||||
],
|
||||
pitfalls: [
|
||||
'Not all tasks have many templates available',
|
||||
'Task categories are predefined - no custom categories',
|
||||
'Some templates may overlap between categories',
|
||||
'Curation is subjective - browse all results',
|
||||
'Templates may need updates for latest n8n features'
|
||||
],
|
||||
relatedTools: ['search_templates', 'list_node_templates', 'get_template', 'list_tasks']
|
||||
}
|
||||
};
|
||||
@@ -1,2 +1,6 @@
|
||||
export { listTasksDoc } from './list-tasks';
|
||||
export { listNodeTemplatesDoc } from './list-node-templates';
|
||||
export { getTemplateDoc } from './get-template';
|
||||
export { searchTemplatesDoc } from './search-templates';
|
||||
export { searchTemplatesByMetadataDoc } from './search-templates-by-metadata';
|
||||
export { getTemplatesForTaskDoc } from './get-templates-for-task';
|
||||
78
src/mcp/tool-docs/templates/list-node-templates.ts
Normal file
78
src/mcp/tool-docs/templates/list-node-templates.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const listNodeTemplatesDoc: ToolDocumentation = {
|
||||
name: 'list_node_templates',
|
||||
category: 'templates',
|
||||
essentials: {
|
||||
description: 'Find templates using specific nodes. 399 community workflows. Use FULL types: "n8n-nodes-base.httpRequest".',
|
||||
keyParameters: ['nodeTypes', 'limit'],
|
||||
example: 'list_node_templates({nodeTypes: ["n8n-nodes-base.slack"]})',
|
||||
performance: 'Fast (<100ms) - indexed node search',
|
||||
tips: [
|
||||
'Must use FULL node type with package prefix: "n8n-nodes-base.slack"',
|
||||
'Can search for multiple nodes to find workflows using all of them',
|
||||
'Returns templates sorted by popularity (view count)'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: `Finds workflow templates that use specific n8n nodes. This is the best way to discover how particular nodes are used in real workflows. Search the community library of 399+ templates by specifying which nodes you want to see in action. Templates are sorted by popularity to show the most useful examples first.`,
|
||||
parameters: {
|
||||
nodeTypes: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
description: 'Array of node types to search for. Must use full type names with package prefix (e.g., ["n8n-nodes-base.httpRequest", "n8n-nodes-base.openAi"])'
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'Maximum number of templates to return. Default 10, max 100'
|
||||
}
|
||||
},
|
||||
returns: `Returns an object containing:
|
||||
- templates: Array of matching templates
|
||||
- id: Template ID for retrieval
|
||||
- name: Template name
|
||||
- description: What the workflow does
|
||||
- author: Creator details (name, username, verified)
|
||||
- nodes: Complete list of nodes used
|
||||
- views: View count (popularity metric)
|
||||
- created: Creation date
|
||||
- url: Link to template on n8n.io
|
||||
- totalFound: Total number of matching templates
|
||||
- tip: Usage hints if no results`,
|
||||
examples: [
|
||||
'list_node_templates({nodeTypes: ["n8n-nodes-base.slack"]}) - Find all Slack workflows',
|
||||
'list_node_templates({nodeTypes: ["n8n-nodes-base.httpRequest", "n8n-nodes-base.postgres"]}) - Find workflows using both HTTP and Postgres',
|
||||
'list_node_templates({nodeTypes: ["@n8n/n8n-nodes-langchain.openAi"], limit: 20}) - Find AI workflows with OpenAI',
|
||||
'list_node_templates({nodeTypes: ["n8n-nodes-base.webhook", "n8n-nodes-base.respondToWebhook"]}) - Find webhook examples'
|
||||
],
|
||||
useCases: [
|
||||
'Learn how to use specific nodes through examples',
|
||||
'Find workflows combining particular integrations',
|
||||
'Discover patterns for node combinations',
|
||||
'See real-world usage of complex nodes',
|
||||
'Find templates for your exact tech stack'
|
||||
],
|
||||
performance: `Optimized for node-based searches:
|
||||
- Indexed by node type for fast lookups
|
||||
- Query time: <50ms for single node
|
||||
- Multiple nodes: <100ms (uses AND logic)
|
||||
- Returns pre-sorted by popularity
|
||||
- No full-text search needed`,
|
||||
bestPractices: [
|
||||
'Always use full node type with package prefix',
|
||||
'Search for core nodes that define the workflow purpose',
|
||||
'Start with single node searches, then refine',
|
||||
'Check node types with list_nodes if unsure of names',
|
||||
'Review multiple templates to learn different approaches'
|
||||
],
|
||||
pitfalls: [
|
||||
'Node types must match exactly - no partial matches',
|
||||
'Package prefix required: "slack" won\'t work, use "n8n-nodes-base.slack"',
|
||||
'Some nodes have version numbers: "n8n-nodes-base.httpRequestV3"',
|
||||
'Templates may use old node versions not in current n8n',
|
||||
'AND logic means all specified nodes must be present'
|
||||
],
|
||||
relatedTools: ['get_template', 'search_templates', 'get_templates_for_task', 'list_nodes']
|
||||
}
|
||||
};
|
||||
46
src/mcp/tool-docs/templates/list-tasks.ts
Normal file
46
src/mcp/tool-docs/templates/list-tasks.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const listTasksDoc: ToolDocumentation = {
|
||||
name: 'list_tasks',
|
||||
category: 'templates',
|
||||
essentials: {
|
||||
description: 'List task templates by category: HTTP/API, Webhooks, Database, AI, Data Processing, Communication.',
|
||||
keyParameters: ['category'],
|
||||
example: 'list_tasks({category: "HTTP/API"})',
|
||||
performance: 'Instant',
|
||||
tips: [
|
||||
'Categories: HTTP/API, Webhooks, Database, AI',
|
||||
'Shows pre-configured node settings',
|
||||
'Use get_node_for_task for details'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: 'Lists available task templates organized by category. Each task represents a common automation pattern with pre-configured node settings. Categories include HTTP/API, Webhooks, Database, AI, Data Processing, and Communication.',
|
||||
parameters: {
|
||||
category: { type: 'string', description: 'Filter by category (optional)' }
|
||||
},
|
||||
returns: 'Array of tasks with name, category, description, nodeType',
|
||||
examples: [
|
||||
'list_tasks() - Get all task templates',
|
||||
'list_tasks({category: "Database"}) - Database-related tasks',
|
||||
'list_tasks({category: "AI"}) - AI automation tasks'
|
||||
],
|
||||
useCases: [
|
||||
'Discover common automation patterns',
|
||||
'Find pre-configured solutions',
|
||||
'Learn node usage patterns',
|
||||
'Quick workflow setup'
|
||||
],
|
||||
performance: 'Instant - Static task list',
|
||||
bestPractices: [
|
||||
'Browse all categories first',
|
||||
'Use get_node_for_task for config',
|
||||
'Combine multiple tasks in workflows'
|
||||
],
|
||||
pitfalls: [
|
||||
'Tasks are templates, customize as needed',
|
||||
'Not all nodes have task templates'
|
||||
],
|
||||
relatedTools: ['get_node_for_task', 'search_templates', 'get_templates_for_task']
|
||||
}
|
||||
};
|
||||
118
src/mcp/tool-docs/templates/search-templates-by-metadata.ts
Normal file
118
src/mcp/tool-docs/templates/search-templates-by-metadata.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const searchTemplatesByMetadataDoc: ToolDocumentation = {
|
||||
name: 'search_templates_by_metadata',
|
||||
category: 'templates',
|
||||
essentials: {
|
||||
description: 'Search templates using AI-generated metadata filters. Find templates by complexity, setup time, required services, or target audience. Enables smart template discovery beyond simple text search.',
|
||||
keyParameters: ['category', 'complexity', 'maxSetupMinutes', 'targetAudience'],
|
||||
example: 'search_templates_by_metadata({complexity: "simple", maxSetupMinutes: 30})',
|
||||
performance: 'Fast (<100ms) - JSON extraction queries',
|
||||
tips: [
|
||||
'All filters are optional - combine them for precise results',
|
||||
'Use getAvailableCategories() to see valid category values',
|
||||
'Complexity levels: simple, medium, complex',
|
||||
'Setup time is in minutes (5-480 range)'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: `Advanced template search using AI-generated metadata. Each template has been analyzed by GPT-4 to extract structured information about its purpose, complexity, setup requirements, and target users. This enables intelligent filtering beyond simple keyword matching, helping you find templates that match your specific needs, skill level, and available time.`,
|
||||
parameters: {
|
||||
category: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Filter by category like "automation", "integration", "data processing", "communication". Use template service getAvailableCategories() for full list.'
|
||||
},
|
||||
complexity: {
|
||||
type: 'string (enum)',
|
||||
required: false,
|
||||
description: 'Filter by implementation complexity: "simple" (beginner-friendly), "medium" (some experience needed), or "complex" (advanced features)'
|
||||
},
|
||||
maxSetupMinutes: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'Maximum acceptable setup time in minutes (5-480). Find templates you can implement within your time budget.'
|
||||
},
|
||||
minSetupMinutes: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'Minimum setup time in minutes (5-480). Find more substantial templates that offer comprehensive solutions.'
|
||||
},
|
||||
requiredService: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Filter by required external service like "openai", "slack", "google", "shopify". Ensures you have necessary accounts/APIs.'
|
||||
},
|
||||
targetAudience: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Filter by intended users: "developers", "marketers", "analysts", "operations", "sales". Find templates for your role.'
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'Maximum results to return. Default 20, max 100.'
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'Pagination offset for results. Default 0.'
|
||||
}
|
||||
},
|
||||
returns: `Returns an object containing:
|
||||
- items: Array of matching templates with full metadata
|
||||
- id: Template ID
|
||||
- name: Template name
|
||||
- description: Purpose and functionality
|
||||
- author: Creator details
|
||||
- nodes: Array of nodes used
|
||||
- views: Popularity count
|
||||
- metadata: AI-generated structured data
|
||||
- categories: Primary use categories
|
||||
- complexity: Difficulty level
|
||||
- use_cases: Specific applications
|
||||
- estimated_setup_minutes: Time to implement
|
||||
- required_services: External dependencies
|
||||
- key_features: Main capabilities
|
||||
- target_audience: Intended users
|
||||
- total: Total matching templates
|
||||
- filters: Applied filter criteria
|
||||
- filterSummary: Human-readable filter description
|
||||
- availableCategories: Suggested categories if no results
|
||||
- availableAudiences: Suggested audiences if no results
|
||||
- tip: Contextual guidance`,
|
||||
examples: [
|
||||
'search_templates_by_metadata({complexity: "simple"}) - Find beginner-friendly templates',
|
||||
'search_templates_by_metadata({category: "automation", maxSetupMinutes: 30}) - Quick automation templates',
|
||||
'search_templates_by_metadata({targetAudience: "marketers"}) - Marketing-focused workflows',
|
||||
'search_templates_by_metadata({requiredService: "openai", complexity: "medium"}) - AI templates with moderate complexity',
|
||||
'search_templates_by_metadata({minSetupMinutes: 60, category: "integration"}) - Comprehensive integration solutions'
|
||||
],
|
||||
useCases: [
|
||||
'Finding beginner-friendly templates by setting complexity:"simple"',
|
||||
'Discovering templates you can implement quickly with maxSetupMinutes:30',
|
||||
'Finding role-specific workflows with targetAudience filter',
|
||||
'Identifying templates that need specific APIs with requiredService filter',
|
||||
'Combining multiple filters for precise template discovery'
|
||||
],
|
||||
performance: 'Fast (<100ms) - Uses SQLite JSON extraction on pre-generated metadata. 97.5% coverage (2,534/2,598 templates).',
|
||||
bestPractices: [
|
||||
'Start with broad filters and narrow down based on results',
|
||||
'Use getAvailableCategories() to discover valid category values',
|
||||
'Combine complexity and setup time for skill-appropriate templates',
|
||||
'Check required services before selecting templates to ensure you have necessary accounts'
|
||||
],
|
||||
pitfalls: [
|
||||
'Not all templates have metadata (97.5% coverage)',
|
||||
'Setup time estimates assume basic n8n familiarity',
|
||||
'Categories/audiences use partial matching - be specific',
|
||||
'Metadata is AI-generated and may occasionally be imprecise'
|
||||
],
|
||||
relatedTools: [
|
||||
'list_templates',
|
||||
'search_templates',
|
||||
'list_node_templates',
|
||||
'get_templates_for_task'
|
||||
]
|
||||
}
|
||||
};
|
||||
@@ -4,138 +4,86 @@ export const searchTemplatesDoc: ToolDocumentation = {
|
||||
name: 'search_templates',
|
||||
category: 'templates',
|
||||
essentials: {
|
||||
description: 'Unified template search with multiple modes: keyword search, by node types, by task type, or by metadata. 2,700+ templates available.',
|
||||
keyParameters: ['searchMode', 'query', 'nodeTypes', 'task', 'limit'],
|
||||
example: 'search_templates({searchMode: "by_task", task: "webhook_processing"})',
|
||||
description: 'Search templates by name/description keywords. NOT for node types! For nodes use list_node_templates. Example: "chatbot".',
|
||||
keyParameters: ['query', 'limit', 'fields'],
|
||||
example: 'search_templates({query: "chatbot", fields: ["id", "name"]})',
|
||||
performance: 'Fast (<100ms) - FTS5 full-text search',
|
||||
tips: [
|
||||
'searchMode="keyword" (default): Search by name/description',
|
||||
'searchMode="by_nodes": Find templates using specific nodes',
|
||||
'searchMode="by_task": Get curated templates for common tasks',
|
||||
'searchMode="by_metadata": Filter by complexity, services, audience'
|
||||
'Searches template names and descriptions, NOT node types',
|
||||
'Use keywords like "automation", "sync", "notification"',
|
||||
'For node-specific search, use list_node_templates instead',
|
||||
'Use fields parameter to get only specific data (reduces response by 70-90%)'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
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
|
||||
- by_metadata: Filter by complexity, setup time, required services, or target audience
|
||||
|
||||
**Available Task Types (for searchMode="by_task"):**
|
||||
ai_automation, data_sync, webhook_processing, email_automation, slack_integration, data_transformation, file_processing, scheduling, api_integration, database_operations`,
|
||||
description: `Performs full-text search across workflow template names and descriptions. This tool is ideal for finding workflows based on their purpose or functionality rather than specific nodes used. It searches through the community library of 399+ templates using SQLite FTS5 for fast, fuzzy matching.`,
|
||||
parameters: {
|
||||
searchMode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Search mode: "keyword" (default), "by_nodes", "by_task", "by_metadata"'
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'For searchMode=keyword: Search keywords (e.g., "chatbot", "automation")'
|
||||
},
|
||||
nodeTypes: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
description: 'For searchMode=by_nodes: Array of node types (e.g., ["n8n-nodes-base.httpRequest", "n8n-nodes-base.slack"])'
|
||||
},
|
||||
task: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'For searchMode=by_task: Task type (ai_automation, data_sync, webhook_processing, email_automation, slack_integration, data_transformation, file_processing, scheduling, api_integration, database_operations)'
|
||||
},
|
||||
complexity: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'For searchMode=by_metadata: Filter by complexity ("simple", "medium", "complex")'
|
||||
},
|
||||
maxSetupMinutes: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'For searchMode=by_metadata: Maximum setup time in minutes (5-480)'
|
||||
},
|
||||
minSetupMinutes: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'For searchMode=by_metadata: Minimum setup time in minutes (5-480)'
|
||||
},
|
||||
requiredService: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'For searchMode=by_metadata: Filter by required service (e.g., "openai", "slack", "google")'
|
||||
},
|
||||
targetAudience: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'For searchMode=by_metadata: Filter by target audience (e.g., "developers", "marketers")'
|
||||
},
|
||||
category: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'For searchMode=by_metadata: Filter by category (e.g., "automation", "integration")'
|
||||
required: true,
|
||||
description: 'Search query for template names/descriptions. NOT for node types! Examples: "chatbot", "automation", "social media", "webhook". For node-based search use list_node_templates instead.'
|
||||
},
|
||||
fields: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
description: 'For searchMode=keyword: Fields to include (id, name, description, author, nodes, views, created, url, metadata)'
|
||||
description: 'Fields to include in response. Options: "id", "name", "description", "author", "nodes", "views", "created", "url", "metadata". Default: all fields. Example: ["id", "name"] for minimal response.'
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'Maximum results (default 20, max 100)'
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'Pagination offset (default 0)'
|
||||
description: 'Maximum number of results. Default 20, max 100'
|
||||
}
|
||||
},
|
||||
returns: `Returns an object containing:
|
||||
- templates: Array of matching templates
|
||||
- id: Template ID for get_template()
|
||||
- name: Template name
|
||||
- templates: Array of matching templates sorted by relevance
|
||||
- id: Template ID for retrieval
|
||||
- name: Template name (with match highlights)
|
||||
- description: What the workflow does
|
||||
- author: Creator information
|
||||
- nodes: Array of node types used
|
||||
- nodes: Array of all nodes used
|
||||
- views: Popularity metric
|
||||
- created: Creation date
|
||||
- url: Link to template
|
||||
- metadata: AI-generated metadata (complexity, services, etc.)
|
||||
- relevanceScore: Search match score
|
||||
- totalFound: Total matching templates
|
||||
- searchMode: The mode used`,
|
||||
- searchQuery: The processed search query
|
||||
- tip: Helpful hints if no results`,
|
||||
examples: [
|
||||
'// Keyword search (default)\nsearch_templates({query: "chatbot"})',
|
||||
'// Find templates using specific nodes\nsearch_templates({searchMode: "by_nodes", nodeTypes: ["n8n-nodes-base.httpRequest", "n8n-nodes-base.slack"]})',
|
||||
'// Get templates for a task type\nsearch_templates({searchMode: "by_task", task: "webhook_processing"})',
|
||||
'// Filter by metadata\nsearch_templates({searchMode: "by_metadata", complexity: "simple", requiredService: "openai"})',
|
||||
'// Combine metadata filters\nsearch_templates({searchMode: "by_metadata", maxSetupMinutes: 30, targetAudience: "developers"})'
|
||||
'search_templates({query: "chatbot"}) - Find chatbot and conversational AI workflows',
|
||||
'search_templates({query: "email notification"}) - Find email alert workflows',
|
||||
'search_templates({query: "data sync"}) - Find data synchronization workflows',
|
||||
'search_templates({query: "webhook automation", limit: 30}) - Find webhook-based automations',
|
||||
'search_templates({query: "social media scheduler"}) - Find social posting workflows',
|
||||
'search_templates({query: "slack", fields: ["id", "name"]}) - Get only IDs and names of Slack templates',
|
||||
'search_templates({query: "automation", fields: ["id", "name", "description"]}) - Get minimal info for automation templates'
|
||||
],
|
||||
useCases: [
|
||||
'Find workflows by business purpose (keyword search)',
|
||||
'Find templates using specific integrations (by_nodes)',
|
||||
'Get pre-built solutions for common tasks (by_task)',
|
||||
'Filter by complexity for team skill level (by_metadata)',
|
||||
'Find templates requiring specific services (by_metadata)'
|
||||
'Find workflows by business purpose',
|
||||
'Discover automations for specific use cases',
|
||||
'Search by workflow functionality',
|
||||
'Find templates by problem they solve',
|
||||
'Explore workflows by industry or domain'
|
||||
],
|
||||
performance: `Fast performance across all modes:
|
||||
- keyword: <50ms with FTS5 indexing
|
||||
- by_nodes: <100ms with indexed lookups
|
||||
- by_task: <50ms from curated cache
|
||||
- by_metadata: <100ms with filtered queries`,
|
||||
performance: `Excellent performance with FTS5 indexing:
|
||||
- Full-text search: <50ms for most queries
|
||||
- Fuzzy matching enabled for typos
|
||||
- Relevance-based sorting included
|
||||
- Searches both title and description
|
||||
- Returns highlighted matches`,
|
||||
bestPractices: [
|
||||
'Use searchMode="by_task" for common automation patterns',
|
||||
'Use searchMode="by_nodes" when you know which integrations you need',
|
||||
'Use searchMode="keyword" for general discovery',
|
||||
'Combine by_metadata filters for precise matching',
|
||||
'Use get_template(id) to get the full workflow JSON'
|
||||
'Use descriptive keywords about the workflow purpose',
|
||||
'Try multiple related terms if first search has few results',
|
||||
'Combine terms for more specific results',
|
||||
'Check both name and description in results',
|
||||
'Use quotes for exact phrase matching'
|
||||
],
|
||||
pitfalls: [
|
||||
'searchMode="keyword" searches names/descriptions, not node types',
|
||||
'by_nodes requires full node type with prefix (n8n-nodes-base.xxx)',
|
||||
'by_metadata filters may return fewer results',
|
||||
'Not all templates have complete metadata'
|
||||
'Does NOT search by node types - use list_node_templates',
|
||||
'Search is case-insensitive but not semantic',
|
||||
'Very specific terms may return no results',
|
||||
'Descriptions may be brief - check full template',
|
||||
'Relevance scoring may not match your expectations'
|
||||
],
|
||||
relatedTools: ['get_template', 'search_nodes', 'validate_workflow']
|
||||
relatedTools: ['list_node_templates', 'get_templates_for_task', 'get_template', 'search_nodes']
|
||||
}
|
||||
};
|
||||
@@ -1,2 +1,5 @@
|
||||
export { validateNodeDoc } from './validate-node';
|
||||
export { validateNodeMinimalDoc } from './validate-node-minimal';
|
||||
export { validateNodeOperationDoc } from './validate-node-operation';
|
||||
export { validateWorkflowDoc } from './validate-workflow';
|
||||
export { validateWorkflowConnectionsDoc } from './validate-workflow-connections';
|
||||
export { validateWorkflowExpressionsDoc } from './validate-workflow-expressions';
|
||||
47
src/mcp/tool-docs/validation/validate-node-minimal.ts
Normal file
47
src/mcp/tool-docs/validation/validate-node-minimal.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const validateNodeMinimalDoc: ToolDocumentation = {
|
||||
name: 'validate_node_minimal',
|
||||
category: 'validation',
|
||||
essentials: {
|
||||
description: 'Fast check for missing required fields only. No warnings/suggestions. Returns: list of missing fields.',
|
||||
keyParameters: ['nodeType', 'config'],
|
||||
example: 'validate_node_minimal("nodes-base.slack", {resource: "message"})',
|
||||
performance: 'Instant',
|
||||
tips: [
|
||||
'Returns only missing required fields',
|
||||
'No warnings or suggestions',
|
||||
'Perfect for real-time validation'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: 'Minimal validation that only checks for missing required fields. Returns array of missing field names without any warnings or suggestions. Ideal for quick validation during node configuration.',
|
||||
parameters: {
|
||||
nodeType: { type: 'string', required: true, description: 'Node type with prefix (e.g., "nodes-base.slack")' },
|
||||
config: { type: 'object', required: true, description: 'Node configuration to validate' }
|
||||
},
|
||||
returns: 'Array of missing required field names (empty if valid)',
|
||||
examples: [
|
||||
'validate_node_minimal("nodes-base.slack", {resource: "message", operation: "post"}) - Check Slack config',
|
||||
'validate_node_minimal("nodes-base.httpRequest", {method: "GET"}) - Check HTTP config'
|
||||
],
|
||||
useCases: [
|
||||
'Real-time form validation',
|
||||
'Quick configuration checks',
|
||||
'Pre-deployment validation',
|
||||
'Interactive configuration builders'
|
||||
],
|
||||
performance: 'Instant - Simple field checking without complex validation',
|
||||
bestPractices: [
|
||||
'Use for quick feedback loops',
|
||||
'Follow with validate_node_operation for thorough check',
|
||||
'Check return array length for validity'
|
||||
],
|
||||
pitfalls: [
|
||||
'Only checks required fields',
|
||||
'No type validation',
|
||||
'No operation-specific validation'
|
||||
],
|
||||
relatedTools: ['validate_node_operation', 'get_node_essentials', 'get_property_dependencies']
|
||||
}
|
||||
};
|
||||
92
src/mcp/tool-docs/validation/validate-node-operation.ts
Normal file
92
src/mcp/tool-docs/validation/validate-node-operation.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const validateNodeOperationDoc: ToolDocumentation = {
|
||||
name: 'validate_node_operation',
|
||||
category: 'validation',
|
||||
essentials: {
|
||||
description: 'Validates node configuration with operation awareness. Checks required fields, data types, and operation-specific rules. Returns specific errors with automated fix suggestions. Different profiles for different validation needs.',
|
||||
keyParameters: ['nodeType', 'config', 'profile'],
|
||||
example: 'validate_node_operation({nodeType: "nodes-base.slack", config: {resource: "message", operation: "post", text: "Hi"}})',
|
||||
performance: '<100ms',
|
||||
tips: [
|
||||
'Profile choices: minimal (editing), runtime (execution), ai-friendly (balanced), strict (deployment)',
|
||||
'Returns fixes you can apply directly',
|
||||
'Operation-aware - knows Slack post needs text'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: 'Comprehensive node configuration validation that understands operation context. For example, it knows Slack message posting requires text field, while channel listing doesn\'t. Provides different validation profiles for different stages of workflow development.',
|
||||
parameters: {
|
||||
nodeType: { type: 'string', required: true, description: 'Full node type with prefix: "nodes-base.slack", "nodes-base.httpRequest"' },
|
||||
config: { type: 'object', required: true, description: 'Node configuration. Must include operation fields (resource/operation/action) if the node has multiple operations' },
|
||||
profile: { type: 'string', required: false, description: 'Validation profile - controls what\'s checked. Default: "ai-friendly"' }
|
||||
},
|
||||
returns: `Object containing:
|
||||
{
|
||||
"isValid": false,
|
||||
"errors": [
|
||||
{
|
||||
"field": "channel",
|
||||
"message": "Required field 'channel' is missing",
|
||||
"severity": "error",
|
||||
"fix": "#general"
|
||||
}
|
||||
],
|
||||
"warnings": [
|
||||
{
|
||||
"field": "retryOnFail",
|
||||
"message": "Consider enabling retry for reliability",
|
||||
"severity": "warning",
|
||||
"fix": true
|
||||
}
|
||||
],
|
||||
"suggestions": [
|
||||
{
|
||||
"field": "timeout",
|
||||
"message": "Set timeout to prevent hanging",
|
||||
"fix": 30000
|
||||
}
|
||||
],
|
||||
"fixes": {
|
||||
"channel": "#general",
|
||||
"retryOnFail": true,
|
||||
"timeout": 30000
|
||||
}
|
||||
}`,
|
||||
examples: [
|
||||
'// Missing required field',
|
||||
'validate_node_operation({nodeType: "nodes-base.slack", config: {resource: "message", operation: "post"}})',
|
||||
'// Returns: {isValid: false, errors: [{field: "text", message: "Required field missing"}], fixes: {text: "Message text"}}',
|
||||
'',
|
||||
'// Validate with strict profile for production',
|
||||
'validate_node_operation({nodeType: "nodes-base.httpRequest", config: {method: "POST", url: "https://api.example.com"}, profile: "strict"})',
|
||||
'',
|
||||
'// Apply fixes automatically',
|
||||
'const result = validate_node_operation({nodeType: "nodes-base.slack", config: myConfig});',
|
||||
'if (!result.isValid) {',
|
||||
' myConfig = {...myConfig, ...result.fixes};',
|
||||
'}'
|
||||
],
|
||||
useCases: [
|
||||
'Validate configuration before workflow execution',
|
||||
'Debug why a node isn\'t working as expected',
|
||||
'Generate configuration fixes automatically',
|
||||
'Different validation for editing vs production'
|
||||
],
|
||||
performance: '<100ms for most nodes, <200ms for complex nodes with many conditions',
|
||||
bestPractices: [
|
||||
'Use "minimal" profile during user editing for fast feedback',
|
||||
'Use "runtime" profile (default) before execution',
|
||||
'Use "ai-friendly" when AI configures nodes',
|
||||
'Use "strict" profile before production deployment',
|
||||
'Always include operation fields (resource/operation) in config',
|
||||
'Apply suggested fixes to resolve issues quickly'
|
||||
],
|
||||
pitfalls: [
|
||||
'Must include operation fields for multi-operation nodes',
|
||||
'Fixes are suggestions - review before applying',
|
||||
'Profile affects what\'s validated - minimal skips many checks'
|
||||
],
|
||||
relatedTools: ['validate_node_minimal for quick checks', 'get_node_essentials for valid examples', 'validate_workflow for complete workflow validation']
|
||||
}
|
||||
};
|
||||
@@ -1,80 +0,0 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const validateNodeDoc: ToolDocumentation = {
|
||||
name: 'validate_node',
|
||||
category: 'validation',
|
||||
essentials: {
|
||||
description: 'Validate n8n node configuration. Use mode="full" for comprehensive validation with errors/warnings/suggestions, mode="minimal" for quick required fields check.',
|
||||
keyParameters: ['nodeType', 'config', 'mode', 'profile'],
|
||||
example: 'validate_node({nodeType: "nodes-base.slack", config: {resource: "channel", operation: "create"}})',
|
||||
performance: 'Fast (<100ms)',
|
||||
tips: [
|
||||
'Always call get_node({detail:"standard"}) first to see required fields',
|
||||
'Use mode="minimal" for quick checks during development',
|
||||
'Use mode="full" with profile="strict" before production deployment',
|
||||
'Includes automatic structure validation for filter, resourceMapper, etc.'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
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
|
||||
|
||||
**Validation Profiles (for mode="full"):**
|
||||
- minimal: Very lenient, basic checks only
|
||||
- runtime: Standard validation (default)
|
||||
- ai-friendly: Balanced for AI agent workflows
|
||||
- strict: Most thorough, recommended for production
|
||||
|
||||
**Automatic Structure Validation:**
|
||||
Validates complex n8n types automatically:
|
||||
- filter (FilterValue): 40+ operations (equals, contains, regex, etc.)
|
||||
- resourceMapper (ResourceMapperValue): Data mapping configuration
|
||||
- assignmentCollection (AssignmentCollectionValue): Variable assignments
|
||||
- resourceLocator (INodeParameterResourceLocator): Resource selection modes`,
|
||||
parameters: {
|
||||
nodeType: { type: 'string', required: true, description: 'Node type with prefix: "nodes-base.slack"' },
|
||||
config: { type: 'object', required: true, description: 'Configuration object to validate. Use {} for empty config' },
|
||||
mode: { type: 'string', required: false, description: 'Validation mode: "full" (default) or "minimal"' },
|
||||
profile: { type: 'string', required: false, description: 'Validation profile for mode=full: "minimal", "runtime" (default), "ai-friendly", "strict"' }
|
||||
},
|
||||
returns: `Object containing:
|
||||
- nodeType: The validated node type
|
||||
- workflowNodeType: Type to use in workflow JSON
|
||||
- displayName: Human-readable node name
|
||||
- valid: Boolean indicating if configuration is valid
|
||||
- errors: Array of error objects with type, property, message, fix
|
||||
- warnings: Array of warning objects with suggestions
|
||||
- suggestions: Array of improvement suggestions
|
||||
- missingRequiredFields: (mode=minimal only) Array of missing required field names
|
||||
- summary: Object with hasErrors, errorCount, warningCount, suggestionCount`,
|
||||
examples: [
|
||||
'// Full validation with default profile\nvalidate_node({nodeType: "nodes-base.slack", config: {resource: "channel", operation: "create"}})',
|
||||
'// Quick required fields check\nvalidate_node({nodeType: "nodes-base.webhook", config: {}, mode: "minimal"})',
|
||||
'// Strict validation for production\nvalidate_node({nodeType: "nodes-base.httpRequest", config: {...}, mode: "full", profile: "strict"})',
|
||||
'// Validate IF node with filter\nvalidate_node({nodeType: "nodes-base.if", config: {conditions: {combinator: "and", conditions: [...]}}})'
|
||||
],
|
||||
useCases: [
|
||||
'Validate node configuration before adding to workflow',
|
||||
'Quick check for required fields during development',
|
||||
'Pre-production validation with strict profile',
|
||||
'Validate complex structures (filters, resource mappers)',
|
||||
'Get suggestions for improving node configuration'
|
||||
],
|
||||
performance: 'Fast validation: <50ms for minimal mode, <100ms for full mode. Structure validation adds minimal overhead.',
|
||||
bestPractices: [
|
||||
'Always call get_node() first to understand required fields',
|
||||
'Use mode="minimal" for rapid iteration during development',
|
||||
'Use profile="strict" before deploying to production',
|
||||
'Pay attention to warnings - they often prevent runtime issues',
|
||||
'Validate after any configuration changes'
|
||||
],
|
||||
pitfalls: [
|
||||
'Empty config {} is valid for some nodes (e.g., manual trigger)',
|
||||
'mode="minimal" only checks required fields, not value validity',
|
||||
'Some warnings may be acceptable for specific use cases',
|
||||
'Credential validation requires runtime context'
|
||||
],
|
||||
relatedTools: ['get_node', 'validate_workflow', 'n8n_autofix_workflow']
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const validateWorkflowConnectionsDoc: ToolDocumentation = {
|
||||
name: 'validate_workflow_connections',
|
||||
category: 'validation',
|
||||
essentials: {
|
||||
description: 'Check workflow connections only: valid nodes, no cycles, proper triggers, AI tool links. Fast structure validation.',
|
||||
keyParameters: ['workflow'],
|
||||
example: 'validate_workflow_connections({workflow: {nodes: [...], connections: {...}}})',
|
||||
performance: 'Fast (<100ms)',
|
||||
tips: [
|
||||
'Use for quick structure checks when editing connections',
|
||||
'Detects orphaned nodes and circular dependencies',
|
||||
'Validates AI Agent tool connections to ensure proper node references'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: 'Validates only the connection structure of a workflow without checking node configurations or expressions. This focused validation checks that all referenced nodes exist, detects circular dependencies, ensures proper trigger node placement, validates AI tool connections, and identifies orphaned or unreachable nodes.',
|
||||
parameters: {
|
||||
workflow: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
description: 'The workflow JSON with nodes array and connections object.'
|
||||
}
|
||||
},
|
||||
returns: 'Object with valid (boolean), errors (array), warnings (array), and statistics about connections',
|
||||
examples: [
|
||||
'validate_workflow_connections({workflow: myWorkflow}) - Check all connections',
|
||||
'validate_workflow_connections({workflow: {nodes: [...], connections: {...}}}) - Validate structure only'
|
||||
],
|
||||
useCases: [
|
||||
'Quick validation when modifying workflow connections',
|
||||
'Ensure all node references in connections are valid',
|
||||
'Detect circular dependencies that would cause infinite loops',
|
||||
'Validate AI Agent nodes have proper tool connections',
|
||||
'Check workflow has at least one trigger node',
|
||||
'Find orphaned nodes not connected to any flow'
|
||||
],
|
||||
performance: 'Fast (<100ms). Only validates structure, not node content. Scales linearly with connection count.',
|
||||
bestPractices: [
|
||||
'Run after adding or removing connections',
|
||||
'Use before validate_workflow for quick structural checks',
|
||||
'Check for warnings about orphaned nodes',
|
||||
'Ensure trigger nodes are properly positioned',
|
||||
'Validate after using n8n_update_partial_workflow with connection operations'
|
||||
],
|
||||
pitfalls: [
|
||||
'Does not validate node configurations - use validate_workflow for full validation',
|
||||
'Cannot detect logical errors in connection flow',
|
||||
'Some valid workflows may have intentionally disconnected nodes',
|
||||
'Circular dependency detection only catches direct loops',
|
||||
'Does not validate connection types match node capabilities'
|
||||
],
|
||||
relatedTools: ['validate_workflow', 'validate_workflow_expressions', 'n8n_update_partial_workflow']
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const validateWorkflowExpressionsDoc: ToolDocumentation = {
|
||||
name: 'validate_workflow_expressions',
|
||||
category: 'validation',
|
||||
essentials: {
|
||||
description: 'Validate n8n expressions: syntax {{}}, variables ($json/$node), references. Returns errors with locations.',
|
||||
keyParameters: ['workflow'],
|
||||
example: 'validate_workflow_expressions({workflow: {nodes: [...], connections: {...}}})',
|
||||
performance: 'Fast (<100ms)',
|
||||
tips: [
|
||||
'Catches syntax errors in {{}} expressions before runtime',
|
||||
'Validates $json, $node, and other n8n variables',
|
||||
'Shows exact location of expression errors in node parameters'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: 'Validates all n8n expressions within a workflow for syntax correctness and reference validity. This tool scans all node parameters for n8n expressions (enclosed in {{}}), checks expression syntax, validates variable references like $json and $node("NodeName"), ensures referenced nodes exist in the workflow, and provides detailed error locations for debugging.',
|
||||
parameters: {
|
||||
workflow: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
description: 'The workflow JSON to check for expression errors.'
|
||||
}
|
||||
},
|
||||
returns: 'Object with valid (boolean), errors (array with node ID, parameter path, and error details), and expression count',
|
||||
examples: [
|
||||
'validate_workflow_expressions({workflow: myWorkflow}) - Check all expressions',
|
||||
'validate_workflow_expressions({workflow: {nodes: [...], connections: {...}}}) - Validate expression syntax'
|
||||
],
|
||||
useCases: [
|
||||
'Catch expression syntax errors before workflow execution',
|
||||
'Validate node references in $node() expressions exist',
|
||||
'Find typos in variable names like $json or $input',
|
||||
'Ensure complex expressions are properly formatted',
|
||||
'Debug expression errors with exact parameter locations',
|
||||
'Validate expressions after workflow modifications'
|
||||
],
|
||||
performance: 'Fast (<100ms). Scans all string parameters in all nodes. Performance scales with workflow size and expression count.',
|
||||
bestPractices: [
|
||||
'Run after modifying any expressions in node parameters',
|
||||
'Check all $node() references when renaming nodes',
|
||||
'Validate expressions before workflow deployment',
|
||||
'Pay attention to nested object paths in expressions',
|
||||
'Use with validate_workflow for comprehensive validation'
|
||||
],
|
||||
pitfalls: [
|
||||
'Cannot validate expression logic, only syntax',
|
||||
'Runtime data availability not checked (e.g., if $json.field exists)',
|
||||
'Complex JavaScript in expressions may need runtime testing',
|
||||
'Does not validate expression return types',
|
||||
'Some valid expressions may use advanced features not fully parsed'
|
||||
],
|
||||
relatedTools: ['validate_workflow', 'validate_workflow_connections', 'validate_node_operation']
|
||||
}
|
||||
};
|
||||
@@ -11,8 +11,7 @@ export const validateWorkflowDoc: ToolDocumentation = {
|
||||
tips: [
|
||||
'Always validate before n8n_create_workflow to catch errors early',
|
||||
'Use options.profile="minimal" for quick checks during development',
|
||||
'AI tool connections are automatically validated for proper node references',
|
||||
'Detects operator structure issues (binary vs unary, singleValue requirements)'
|
||||
'AI tool connections are automatically validated for proper node references'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
@@ -68,9 +67,7 @@ export const validateWorkflowDoc: ToolDocumentation = {
|
||||
'Use minimal profile during development, strict profile before production',
|
||||
'Pay attention to warnings - they often indicate potential runtime issues',
|
||||
'Validate after any workflow modifications, especially connection changes',
|
||||
'Check statistics to understand workflow complexity',
|
||||
'**Auto-sanitization runs during create/update**: Operator structures and missing metadata are automatically fixed when workflows are created or updated, but validation helps catch issues before they reach n8n',
|
||||
'If validation detects operator issues, they will be auto-fixed during n8n_create_workflow or n8n_update_partial_workflow'
|
||||
'Check statistics to understand workflow complexity'
|
||||
],
|
||||
pitfalls: [
|
||||
'Large workflows (100+ nodes) may take longer to validate',
|
||||
@@ -79,6 +76,6 @@ export const validateWorkflowDoc: ToolDocumentation = {
|
||||
'Validation cannot catch all runtime errors (e.g., API failures)',
|
||||
'Profile setting only affects node validation, not connection/expression checks'
|
||||
],
|
||||
relatedTools: ['validate_node', 'n8n_create_workflow', 'n8n_update_partial_workflow', 'n8n_autofix_workflow']
|
||||
relatedTools: ['validate_workflow_connections', 'validate_workflow_expressions', 'validate_node_operation', 'n8n_create_workflow', 'n8n_update_partial_workflow', 'n8n_autofix_workflow']
|
||||
}
|
||||
};
|
||||
@@ -1,12 +1,15 @@
|
||||
export { n8nCreateWorkflowDoc } from './n8n-create-workflow';
|
||||
export { n8nGetWorkflowDoc } from './n8n-get-workflow';
|
||||
export { n8nGetWorkflowDetailsDoc } from './n8n-get-workflow-details';
|
||||
export { n8nGetWorkflowStructureDoc } from './n8n-get-workflow-structure';
|
||||
export { n8nGetWorkflowMinimalDoc } from './n8n-get-workflow-minimal';
|
||||
export { n8nUpdateFullWorkflowDoc } from './n8n-update-full-workflow';
|
||||
export { n8nUpdatePartialWorkflowDoc } from './n8n-update-partial-workflow';
|
||||
export { n8nDeleteWorkflowDoc } from './n8n-delete-workflow';
|
||||
export { n8nListWorkflowsDoc } from './n8n-list-workflows';
|
||||
export { n8nValidateWorkflowDoc } from './n8n-validate-workflow';
|
||||
export { n8nAutofixWorkflowDoc } from './n8n-autofix-workflow';
|
||||
export { n8nTestWorkflowDoc } from './n8n-test-workflow';
|
||||
export { n8nExecutionsDoc } from './n8n-executions';
|
||||
export { n8nWorkflowVersionsDoc } from './n8n-workflow-versions';
|
||||
export { n8nDeployTemplateDoc } from './n8n-deploy-template';
|
||||
export { n8nTriggerWebhookWorkflowDoc } from './n8n-trigger-webhook-workflow';
|
||||
export { n8nGetExecutionDoc } from './n8n-get-execution';
|
||||
export { n8nListExecutionsDoc } from './n8n-list-executions';
|
||||
export { n8nDeleteExecutionDoc } from './n8n-delete-execution';
|
||||
@@ -4,17 +4,15 @@ export const n8nAutofixWorkflowDoc: ToolDocumentation = {
|
||||
name: 'n8n_autofix_workflow',
|
||||
category: 'workflow_management',
|
||||
essentials: {
|
||||
description: 'Automatically fix common workflow validation errors - expression formats, typeVersions, error outputs, webhook paths, and smart version upgrades',
|
||||
description: 'Automatically fix common workflow validation errors - expression formats, typeVersions, error outputs, webhook paths',
|
||||
keyParameters: ['id', 'applyFixes'],
|
||||
example: 'n8n_autofix_workflow({id: "wf_abc123", applyFixes: false})',
|
||||
performance: 'Network-dependent (200-1500ms) - fetches, validates, and optionally updates workflow with smart migrations',
|
||||
performance: 'Network-dependent (200-1000ms) - fetches, validates, and optionally updates workflow',
|
||||
tips: [
|
||||
'Use applyFixes: false to preview changes before applying',
|
||||
'Set confidenceThreshold to control fix aggressiveness (high/medium/low)',
|
||||
'Supports expression formats, typeVersion issues, error outputs, node corrections, webhook paths, AND version upgrades',
|
||||
'High-confidence fixes (≥90%) are safe for auto-application',
|
||||
'Version upgrades include smart migration with breaking change detection',
|
||||
'Post-update guidance provides AI-friendly step-by-step instructions for manual changes'
|
||||
'Supports fixing expression formats, typeVersion issues, error outputs, node type corrections, and webhook paths',
|
||||
'High-confidence fixes (≥90%) are safe for auto-application'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
@@ -41,20 +39,6 @@ The auto-fixer can resolve:
|
||||
- Sets both 'path' parameter and 'webhookId' field to the same UUID
|
||||
- Ensures webhook nodes become functional with valid endpoints
|
||||
- High confidence fix as UUID generation is deterministic
|
||||
6. **Smart Version Upgrades** (NEW): Proactively upgrades nodes to their latest versions:
|
||||
- Detects outdated node versions and recommends upgrades
|
||||
- Applies smart migrations with auto-migratable property changes
|
||||
- Handles breaking changes intelligently (Execute Workflow v1.0→v1.1, Webhook v2.0→v2.1, etc.)
|
||||
- Generates UUIDs for required fields (webhookId), sets sensible defaults
|
||||
- HIGH confidence for non-breaking upgrades, MEDIUM for breaking changes with auto-migration
|
||||
- Example: Execute Workflow v1.0→v1.1 adds inputFieldMapping automatically
|
||||
7. **Version Migration Guidance** (NEW): Documents complex migrations requiring manual intervention:
|
||||
- Identifies breaking changes that cannot be auto-migrated
|
||||
- Provides AI-friendly post-update guidance with step-by-step instructions
|
||||
- Lists required actions by priority (CRITICAL, HIGH, MEDIUM, LOW)
|
||||
- Documents behavior changes and their impact
|
||||
- Estimates time required for manual migration steps
|
||||
- MEDIUM/LOW confidence - requires review before applying
|
||||
|
||||
The tool uses a confidence-based system to ensure safe fixes:
|
||||
- **High (≥90%)**: Safe to auto-apply (exact matches, known patterns)
|
||||
@@ -76,7 +60,7 @@ Requires N8N_API_URL and N8N_API_KEY environment variables to be configured.`,
|
||||
fixTypes: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
description: 'Types of fixes to apply. Options: ["expression-format", "typeversion-correction", "error-output-config", "node-type-correction", "webhook-missing-path", "typeversion-upgrade", "version-migration"]. Default: all types. NEW: "typeversion-upgrade" for smart version upgrades, "version-migration" for complex migration guidance.'
|
||||
description: 'Types of fixes to apply. Options: ["expression-format", "typeversion-correction", "error-output-config", "node-type-correction", "webhook-missing-path"]. Default: all types.'
|
||||
},
|
||||
confidenceThreshold: {
|
||||
type: 'string',
|
||||
@@ -94,21 +78,13 @@ Requires N8N_API_URL and N8N_API_KEY environment variables to be configured.`,
|
||||
- fixes: Detailed list of individual fixes with before/after values
|
||||
- summary: Human-readable summary of fixes
|
||||
- stats: Statistics by fix type and confidence level
|
||||
- applied: Boolean indicating if fixes were applied (when applyFixes: true)
|
||||
- postUpdateGuidance: (NEW) Array of AI-friendly migration guidance for version upgrades, including:
|
||||
* Required actions by priority (CRITICAL, HIGH, MEDIUM, LOW)
|
||||
* Deprecated properties to remove
|
||||
* Behavior changes and their impact
|
||||
* Step-by-step migration instructions
|
||||
* Estimated time for manual changes`,
|
||||
- applied: Boolean indicating if fixes were applied (when applyFixes: true)`,
|
||||
examples: [
|
||||
'n8n_autofix_workflow({id: "wf_abc123"}) - Preview all possible fixes including version upgrades',
|
||||
'n8n_autofix_workflow({id: "wf_abc123"}) - Preview all possible fixes',
|
||||
'n8n_autofix_workflow({id: "wf_abc123", applyFixes: true}) - Apply all medium+ confidence fixes',
|
||||
'n8n_autofix_workflow({id: "wf_abc123", applyFixes: true, confidenceThreshold: "high"}) - Only apply high-confidence fixes',
|
||||
'n8n_autofix_workflow({id: "wf_abc123", fixTypes: ["expression-format"]}) - Only fix expression format issues',
|
||||
'n8n_autofix_workflow({id: "wf_abc123", fixTypes: ["webhook-missing-path"]}) - Only fix webhook path issues',
|
||||
'n8n_autofix_workflow({id: "wf_abc123", fixTypes: ["typeversion-upgrade"]}) - NEW: Only upgrade node versions with smart migrations',
|
||||
'n8n_autofix_workflow({id: "wf_abc123", fixTypes: ["typeversion-upgrade", "version-migration"]}) - NEW: Upgrade versions and provide migration guidance',
|
||||
'n8n_autofix_workflow({id: "wf_abc123", applyFixes: true, maxFixes: 10}) - Apply up to 10 fixes'
|
||||
],
|
||||
useCases: [
|
||||
@@ -118,23 +94,16 @@ Requires N8N_API_URL and N8N_API_KEY environment variables to be configured.`,
|
||||
'Cleaning up workflows before production deployment',
|
||||
'Batch fixing common issues across multiple workflows',
|
||||
'Migrating workflows between n8n instances with different versions',
|
||||
'Repairing webhook nodes that lost their path configuration',
|
||||
'Upgrading Execute Workflow nodes from v1.0 to v1.1+ with automatic inputFieldMapping',
|
||||
'Modernizing webhook nodes to v2.1+ with stable webhookId fields',
|
||||
'Proactively keeping workflows up-to-date with latest node versions',
|
||||
'Getting detailed migration guidance for complex breaking changes'
|
||||
'Repairing webhook nodes that lost their path configuration'
|
||||
],
|
||||
performance: 'Depends on workflow size and number of issues. Preview mode: 200-500ms. Apply mode: 500-1500ms for medium workflows with version upgrades. Node similarity matching and version metadata are cached for 5 minutes for improved performance on repeated validations.',
|
||||
performance: 'Depends on workflow size and number of issues. Preview mode: 200-500ms. Apply mode: 500-1000ms for medium workflows. Node similarity matching is cached for 5 minutes for improved performance on repeated validations.',
|
||||
bestPractices: [
|
||||
'Always preview fixes first (applyFixes: false) before applying',
|
||||
'Start with high confidence threshold for production workflows',
|
||||
'Review the fix summary to understand what changed',
|
||||
'Test workflows after auto-fixing to ensure expected behavior',
|
||||
'Use fixTypes parameter to target specific issue categories',
|
||||
'Keep maxFixes reasonable to avoid too many changes at once',
|
||||
'NEW: Review postUpdateGuidance for version upgrades - contains step-by-step migration instructions',
|
||||
'NEW: Test workflows after version upgrades - behavior may change even with successful auto-migration',
|
||||
'NEW: Apply version upgrades incrementally - start with high-confidence, non-breaking upgrades'
|
||||
'Keep maxFixes reasonable to avoid too many changes at once'
|
||||
],
|
||||
pitfalls: [
|
||||
'Some fixes may change workflow behavior - always test after fixing',
|
||||
@@ -143,18 +112,14 @@ Requires N8N_API_URL and N8N_API_KEY environment variables to be configured.`,
|
||||
'Node type corrections only work for known node types in the database',
|
||||
'Cannot fix structural issues like missing nodes or invalid connections',
|
||||
'TypeVersion downgrades might remove node features added in newer versions',
|
||||
'Generated webhook paths are new UUIDs - existing webhook URLs will change',
|
||||
'NEW: Version upgrades may introduce breaking changes - review postUpdateGuidance carefully',
|
||||
'NEW: Auto-migrated properties use sensible defaults which may not match your use case',
|
||||
'NEW: Execute Workflow v1.1+ requires explicit inputFieldMapping - automatic mapping uses empty array',
|
||||
'NEW: Some breaking changes cannot be auto-migrated and require manual intervention',
|
||||
'NEW: Version history is based on registry - unknown nodes cannot be upgraded'
|
||||
'Generated webhook paths are new UUIDs - existing webhook URLs will change'
|
||||
],
|
||||
relatedTools: [
|
||||
'n8n_validate_workflow',
|
||||
'validate_workflow',
|
||||
'validate_node',
|
||||
'n8n_update_partial_workflow'
|
||||
'n8n_update_partial_workflow',
|
||||
'validate_workflow_expressions',
|
||||
'validate_node_operation'
|
||||
]
|
||||
}
|
||||
};
|
||||
@@ -11,8 +11,7 @@ export const n8nCreateWorkflowDoc: ToolDocumentation = {
|
||||
tips: [
|
||||
'Workflow created inactive',
|
||||
'Returns ID for future updates',
|
||||
'Validate first with validate_workflow',
|
||||
'Auto-sanitization fixes operator structures and missing metadata during creation'
|
||||
'Validate first with validate_workflow'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
@@ -84,17 +83,15 @@ n8n_create_workflow({
|
||||
'Validate with validate_workflow first',
|
||||
'Use unique node IDs',
|
||||
'Position nodes for readability',
|
||||
'Test with n8n_test_workflow'
|
||||
'Test with n8n_trigger_webhook_workflow'
|
||||
],
|
||||
pitfalls: [
|
||||
'**REQUIRES N8N_API_URL and N8N_API_KEY environment variables** - tool unavailable without n8n API access',
|
||||
'Workflows created in INACTIVE state - must activate separately',
|
||||
'Node IDs must be unique within workflow',
|
||||
'Credentials must be configured separately in n8n',
|
||||
'Node type names must include package prefix (e.g., "n8n-nodes-base.slack")',
|
||||
'**Auto-sanitization runs on creation**: All nodes sanitized before workflow created (operator structures fixed, missing metadata added)',
|
||||
'**Auto-sanitization cannot prevent all failures**: Broken connections or invalid node configurations may still cause creation to fail'
|
||||
'Node type names must include package prefix (e.g., "n8n-nodes-base.slack")'
|
||||
],
|
||||
relatedTools: ['validate_workflow', 'n8n_update_partial_workflow', 'n8n_test_workflow']
|
||||
relatedTools: ['validate_workflow', 'n8n_update_partial_workflow', 'n8n_trigger_webhook_workflow']
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const n8nDeleteExecutionDoc: ToolDocumentation = {
|
||||
name: 'n8n_delete_execution',
|
||||
category: 'workflow_management',
|
||||
essentials: {
|
||||
description: 'Delete an execution record. This only removes the execution history, not any data processed.',
|
||||
keyParameters: ['id'],
|
||||
example: 'n8n_delete_execution({id: "12345"})',
|
||||
performance: 'Immediate deletion, no undo available',
|
||||
tips: [
|
||||
'Deletion is permanent - execution cannot be recovered',
|
||||
'Only removes execution history, not external data changes',
|
||||
'Use for cleanup of test executions or sensitive data'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: `Permanently deletes a workflow execution record from n8n's history. This removes the execution metadata, logs, and any stored input/output data. However, it does NOT undo any actions the workflow performed (API calls, database changes, file operations, etc.). Use this for cleaning up test executions, removing sensitive data, or managing storage.`,
|
||||
parameters: {
|
||||
id: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The execution ID to delete. This action cannot be undone'
|
||||
}
|
||||
},
|
||||
returns: `Confirmation of deletion or error if execution not found. No data is returned about the deleted execution.`,
|
||||
examples: [
|
||||
'n8n_delete_execution({id: "12345"}) - Delete a specific execution',
|
||||
'n8n_delete_execution({id: "test-run-567"}) - Clean up test execution',
|
||||
'n8n_delete_execution({id: "sensitive-data-890"}) - Remove execution with sensitive data',
|
||||
'n8n_delete_execution({id: "failed-execution-123"}) - Delete failed execution after debugging'
|
||||
],
|
||||
useCases: [
|
||||
'Clean up test or development execution history',
|
||||
'Remove executions containing sensitive or personal data',
|
||||
'Manage storage by deleting old execution records',
|
||||
'Clean up after debugging failed workflows',
|
||||
'Comply with data retention policies'
|
||||
],
|
||||
performance: `Deletion is immediate and permanent. The operation is fast (< 100ms) as it only removes database records. No external systems or data are affected.`,
|
||||
bestPractices: [
|
||||
'Verify execution ID before deletion - action cannot be undone',
|
||||
'Consider exporting execution data before deletion if needed',
|
||||
'Use list_executions to find executions to delete',
|
||||
'Document why executions were deleted for audit trails',
|
||||
'Remember deletion only affects n8n records, not external changes'
|
||||
],
|
||||
pitfalls: [
|
||||
'Deletion is PERMANENT - no undo or recovery possible',
|
||||
'Does NOT reverse workflow actions (API calls, DB changes, etc.)',
|
||||
'Deleting executions breaks audit trails and debugging history',
|
||||
'Cannot delete currently running executions (waiting status)',
|
||||
'Bulk deletion not supported - must delete one at a time'
|
||||
],
|
||||
relatedTools: ['n8n_list_executions', 'n8n_get_execution', 'n8n_trigger_webhook_workflow']
|
||||
}
|
||||
};
|
||||
@@ -11,7 +11,7 @@ export const n8nDeleteWorkflowDoc: ToolDocumentation = {
|
||||
tips: [
|
||||
'Action is irreversible',
|
||||
'Deletes all execution history',
|
||||
'Check workflow first with n8n_get_workflow({mode: "minimal"})'
|
||||
'Check workflow first with get_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 n8n_get_workflow({mode: "minimal"}) first',
|
||||
'Check workflow with get_minimal first',
|
||||
'Consider deactivating instead of deleting',
|
||||
'Export workflow before deletion for backup'
|
||||
],
|
||||
@@ -45,6 +45,6 @@ export const n8nDeleteWorkflowDoc: ToolDocumentation = {
|
||||
'Active workflows can be deleted',
|
||||
'No built-in confirmation'
|
||||
],
|
||||
relatedTools: ['n8n_get_workflow', 'n8n_list_workflows', 'n8n_update_partial_workflow', 'n8n_executions']
|
||||
relatedTools: ['n8n_get_workflow_minimal', 'n8n_list_workflows', 'n8n_update_partial_workflow', 'n8n_delete_execution']
|
||||
}
|
||||
};
|
||||
@@ -1,71 +0,0 @@
|
||||
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. Deploys first, then auto-fixes common issues (expression format, typeVersions).',
|
||||
keyParameters: ['templateId', 'name', 'autoUpgradeVersions', 'autoFix', 'stripCredentials'],
|
||||
example: 'n8n_deploy_template({templateId: 2776, name: "My Deployed Template"})',
|
||||
performance: 'Network-dependent',
|
||||
tips: [
|
||||
'Auto-fixes expression format issues after deployment',
|
||||
'Workflow created inactive - configure credentials in n8n UI first',
|
||||
'Returns list of required credentials and fixes applied',
|
||||
'Use search_templates to find template IDs'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: 'Deploys a workflow template from n8n.io directly to your n8n instance. This tool deploys first, then automatically fixes common issues like missing expression prefixes (=) and outdated typeVersions. 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)' },
|
||||
autoFix: { type: 'boolean', description: 'Auto-apply fixes after deployment for expression format issues, missing = prefix, etc. (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, autoFixStatus (success/failed/skipped), and fixesApplied array',
|
||||
examples: [
|
||||
`// Deploy template with default settings (auto-fix enabled)
|
||||
n8n_deploy_template({templateId: 2776})`,
|
||||
`// Deploy with custom name
|
||||
n8n_deploy_template({
|
||||
templateId: 2776,
|
||||
name: "My Google Drive to Airtable Sync"
|
||||
})`,
|
||||
`// Deploy without auto-fix (not recommended)
|
||||
n8n_deploy_template({
|
||||
templateId: 2776,
|
||||
autoFix: 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 300-800ms (template fetch + workflow creation + autofix)',
|
||||
bestPractices: [
|
||||
'Use search_templates to find templates by use case',
|
||||
'Review required credentials in the response',
|
||||
'Check autoFixStatus in response - "success", "failed", or "skipped"',
|
||||
'Check fixesApplied in response to see what was automatically corrected',
|
||||
'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',
|
||||
'Some issues may not be auto-fixable (e.g., missing required fields that need user input)'
|
||||
],
|
||||
relatedTools: ['search_templates', 'get_template', 'n8n_create_workflow', 'n8n_autofix_workflow']
|
||||
}
|
||||
};
|
||||
@@ -1,82 +0,0 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const n8nExecutionsDoc: ToolDocumentation = {
|
||||
name: 'n8n_executions',
|
||||
category: 'workflow_management',
|
||||
essentials: {
|
||||
description: 'Manage workflow executions: get details, list, or delete. Unified tool for all execution operations.',
|
||||
keyParameters: ['action', 'id', 'workflowId', 'status'],
|
||||
example: 'n8n_executions({action: "list", workflowId: "abc123", status: "error"})',
|
||||
performance: 'Fast (50-200ms)',
|
||||
tips: [
|
||||
'action="get": Get execution details by ID',
|
||||
'action="list": List executions with filters',
|
||||
'action="delete": Delete execution record',
|
||||
'Use mode parameter for action=get to control detail level'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
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
|
||||
|
||||
**Detail Modes for action="get":**
|
||||
- preview: Structure only, no data
|
||||
- summary: 2 items per node (default)
|
||||
- filtered: Custom items limit, optionally filter by node names
|
||||
- full: All execution data (can be very large)`,
|
||||
parameters: {
|
||||
action: { type: 'string', required: true, description: 'Operation: "get", "list", or "delete"' },
|
||||
id: { type: 'string', required: false, description: 'Execution ID (required for action=get or action=delete)' },
|
||||
mode: { type: 'string', required: false, description: 'For action=get: "preview", "summary" (default), "filtered", "full"' },
|
||||
nodeNames: { type: 'array', required: false, description: 'For action=get with mode=filtered: Filter to specific nodes by name' },
|
||||
itemsLimit: { type: 'number', required: false, description: 'For action=get with mode=filtered: Items per node (0=structure, 2=default, -1=unlimited)' },
|
||||
includeInputData: { type: 'boolean', required: false, description: 'For action=get: Include input data in addition to output (default: false)' },
|
||||
workflowId: { type: 'string', required: false, description: 'For action=list: Filter by workflow ID' },
|
||||
status: { type: 'string', required: false, description: 'For action=list: Filter by status ("success", "error", "waiting")' },
|
||||
limit: { type: 'number', required: false, description: 'For action=list: Number of results (1-100, default: 100)' },
|
||||
cursor: { type: 'string', required: false, description: 'For action=list: Pagination cursor from previous response' },
|
||||
projectId: { type: 'string', required: false, description: 'For action=list: Filter by project ID (enterprise)' },
|
||||
includeData: { type: 'boolean', required: false, description: 'For action=list: Include execution data (default: false)' }
|
||||
},
|
||||
returns: `Depends on action:
|
||||
- get: Execution object with data based on mode
|
||||
- list: { data: [...executions], nextCursor?: string }
|
||||
- delete: { success: boolean, message: string }`,
|
||||
examples: [
|
||||
'// List recent executions for a workflow\nn8n_executions({action: "list", workflowId: "abc123", limit: 10})',
|
||||
'// List failed executions\nn8n_executions({action: "list", status: "error"})',
|
||||
'// Get execution summary\nn8n_executions({action: "get", id: "exec_456"})',
|
||||
'// Get full execution data\nn8n_executions({action: "get", id: "exec_456", mode: "full"})',
|
||||
'// Get specific nodes from execution\nn8n_executions({action: "get", id: "exec_456", mode: "filtered", nodeNames: ["HTTP Request", "Slack"]})',
|
||||
'// Delete an execution\nn8n_executions({action: "delete", id: "exec_456"})'
|
||||
],
|
||||
useCases: [
|
||||
'Debug workflow failures (get with mode=full)',
|
||||
'Monitor workflow health (list with status filter)',
|
||||
'Audit execution history',
|
||||
'Clean up old execution records',
|
||||
'Analyze specific node outputs'
|
||||
],
|
||||
performance: `Response times:
|
||||
- list: 50-150ms depending on filters
|
||||
- get (preview/summary): 30-100ms
|
||||
- get (full): 100-500ms+ depending on data size
|
||||
- delete: 30-80ms`,
|
||||
bestPractices: [
|
||||
'Use mode="summary" (default) for debugging - shows enough data',
|
||||
'Use mode="filtered" with nodeNames for large workflows',
|
||||
'Filter by workflowId when listing to reduce results',
|
||||
'Use cursor for pagination through large result sets',
|
||||
'Delete old executions to save storage'
|
||||
],
|
||||
pitfalls: [
|
||||
'Requires N8N_API_URL and N8N_API_KEY configured',
|
||||
'mode="full" can return very large responses for complex workflows',
|
||||
'Execution must exist or returns 404',
|
||||
'Delete is permanent - cannot undo'
|
||||
],
|
||||
relatedTools: ['n8n_get_workflow', 'n8n_test_workflow', 'n8n_validate_workflow']
|
||||
}
|
||||
};
|
||||
283
src/mcp/tool-docs/workflow_management/n8n-get-execution.ts
Normal file
283
src/mcp/tool-docs/workflow_management/n8n-get-execution.ts
Normal file
@@ -0,0 +1,283 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const n8nGetExecutionDoc: ToolDocumentation = {
|
||||
name: 'n8n_get_execution',
|
||||
category: 'workflow_management',
|
||||
essentials: {
|
||||
description: 'Get execution details with smart filtering to avoid token limits. Use preview mode first to assess data size, then fetch appropriately.',
|
||||
keyParameters: ['id', 'mode', 'itemsLimit', 'nodeNames'],
|
||||
example: `
|
||||
// RECOMMENDED WORKFLOW:
|
||||
// 1. Preview first
|
||||
n8n_get_execution({id: "12345", mode: "preview"})
|
||||
// Returns: structure, counts, size estimate, recommendation
|
||||
|
||||
// 2. Based on recommendation, fetch data:
|
||||
n8n_get_execution({id: "12345", mode: "summary"}) // 2 items per node
|
||||
n8n_get_execution({id: "12345", mode: "filtered", itemsLimit: 5}) // 5 items
|
||||
n8n_get_execution({id: "12345", nodeNames: ["HTTP Request"]}) // Specific node
|
||||
`,
|
||||
performance: 'Preview: <50ms, Summary: <200ms, Full: depends on data size',
|
||||
tips: [
|
||||
'ALWAYS use preview mode first for large datasets',
|
||||
'Preview shows structure + counts without consuming tokens for data',
|
||||
'Summary mode (2 items per node) is safe default',
|
||||
'Use nodeNames to focus on specific nodes only',
|
||||
'itemsLimit: 0 = structure only, -1 = unlimited',
|
||||
'Check recommendation.suggestedMode from preview'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: `Retrieves and intelligently filters execution data to enable inspection without exceeding token limits. This tool provides multiple modes for different use cases, from quick previews to complete data retrieval.
|
||||
|
||||
**The Problem**: Workflows processing large datasets (50+ database records) generate execution data that exceeds token/response limits, making traditional full-data fetching impossible.
|
||||
|
||||
**The Solution**: Four retrieval modes with smart filtering:
|
||||
1. **Preview**: Structure + counts only (no actual data)
|
||||
2. **Summary**: 2 sample items per node (safe default)
|
||||
3. **Filtered**: Custom limits and node selection
|
||||
4. **Full**: Complete data (use with caution)
|
||||
|
||||
**Recommended Workflow**:
|
||||
1. Start with preview mode to assess size
|
||||
2. Use recommendation to choose appropriate mode
|
||||
3. Fetch filtered data as needed`,
|
||||
|
||||
parameters: {
|
||||
id: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The execution ID to retrieve. Obtained from list_executions or webhook trigger responses'
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: `Retrieval mode (default: auto-detect from other params):
|
||||
- 'preview': Structure, counts, size estimates - NO actual data (fastest)
|
||||
- 'summary': Metadata + 2 sample items per node (safe default)
|
||||
- 'filtered': Custom filtering with itemsLimit/nodeNames
|
||||
- 'full': Complete execution data (use with caution)`
|
||||
},
|
||||
nodeNames: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
description: 'Filter to specific nodes by name. Example: ["HTTP Request", "Filter"]. Useful when you only need to inspect specific nodes.'
|
||||
},
|
||||
itemsLimit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: `Items to return per node (default: 2):
|
||||
- 0: Structure only (see data shape without values)
|
||||
- 1-N: Return N items per node
|
||||
- -1: Unlimited (return all items)
|
||||
|
||||
Note: Structure-only mode (0) shows JSON schema without actual values.`
|
||||
},
|
||||
includeInputData: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
description: 'Include input data in addition to output data (default: false). Useful for debugging data transformations.'
|
||||
},
|
||||
includeData: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
description: 'DEPRECATED: Legacy parameter. Use mode instead. If true, maps to mode="summary" for backward compatibility.'
|
||||
}
|
||||
},
|
||||
|
||||
returns: `**Preview Mode Response**:
|
||||
{
|
||||
mode: 'preview',
|
||||
preview: {
|
||||
totalNodes: number,
|
||||
executedNodes: number,
|
||||
estimatedSizeKB: number,
|
||||
nodes: {
|
||||
[nodeName]: {
|
||||
status: 'success' | 'error',
|
||||
itemCounts: { input: number, output: number },
|
||||
dataStructure: {...}, // JSON schema
|
||||
estimatedSizeKB: number
|
||||
}
|
||||
}
|
||||
},
|
||||
recommendation: {
|
||||
canFetchFull: boolean,
|
||||
suggestedMode: 'preview'|'summary'|'filtered'|'full',
|
||||
suggestedItemsLimit?: number,
|
||||
reason: string
|
||||
}
|
||||
}
|
||||
|
||||
**Summary/Filtered/Full Mode Response**:
|
||||
{
|
||||
mode: 'summary' | 'filtered' | 'full',
|
||||
summary: {
|
||||
totalNodes: number,
|
||||
executedNodes: number,
|
||||
totalItems: number,
|
||||
hasMoreData: boolean // true if truncated
|
||||
},
|
||||
nodes: {
|
||||
[nodeName]: {
|
||||
executionTime: number,
|
||||
itemsInput: number,
|
||||
itemsOutput: number,
|
||||
status: 'success' | 'error',
|
||||
error?: string,
|
||||
data: {
|
||||
output: [...], // Actual data items
|
||||
metadata: {
|
||||
totalItems: number,
|
||||
itemsShown: number,
|
||||
truncated: boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
|
||||
examples: [
|
||||
`// Example 1: Preview workflow (RECOMMENDED FIRST STEP)
|
||||
n8n_get_execution({id: "exec_123", mode: "preview"})
|
||||
// Returns structure, counts, size, recommendation
|
||||
// Use this to decide how to fetch data`,
|
||||
|
||||
`// Example 2: Follow recommendation
|
||||
const preview = n8n_get_execution({id: "exec_123", mode: "preview"});
|
||||
if (preview.recommendation.canFetchFull) {
|
||||
n8n_get_execution({id: "exec_123", mode: "full"});
|
||||
} else {
|
||||
n8n_get_execution({
|
||||
id: "exec_123",
|
||||
mode: "filtered",
|
||||
itemsLimit: preview.recommendation.suggestedItemsLimit
|
||||
});
|
||||
}`,
|
||||
|
||||
`// Example 3: Summary mode (safe default for unknown datasets)
|
||||
n8n_get_execution({id: "exec_123", mode: "summary"})
|
||||
// Gets 2 items per node - safe for most cases`,
|
||||
|
||||
`// Example 4: Filter to specific node
|
||||
n8n_get_execution({
|
||||
id: "exec_123",
|
||||
mode: "filtered",
|
||||
nodeNames: ["HTTP Request"],
|
||||
itemsLimit: 5
|
||||
})
|
||||
// Gets only HTTP Request node, 5 items`,
|
||||
|
||||
`// Example 5: Structure only (see data shape)
|
||||
n8n_get_execution({
|
||||
id: "exec_123",
|
||||
mode: "filtered",
|
||||
itemsLimit: 0
|
||||
})
|
||||
// Returns JSON schema without actual values`,
|
||||
|
||||
`// Example 6: Debug with input data
|
||||
n8n_get_execution({
|
||||
id: "exec_123",
|
||||
mode: "filtered",
|
||||
nodeNames: ["Transform"],
|
||||
itemsLimit: 2,
|
||||
includeInputData: true
|
||||
})
|
||||
// See both input and output for debugging`,
|
||||
|
||||
`// Example 7: Backward compatibility (legacy)
|
||||
n8n_get_execution({id: "exec_123"}) // Minimal data
|
||||
n8n_get_execution({id: "exec_123", includeData: true}) // Maps to summary mode`
|
||||
],
|
||||
|
||||
useCases: [
|
||||
'Monitor status of triggered workflows',
|
||||
'Debug failed workflows by examining error messages and partial data',
|
||||
'Inspect large datasets without exceeding token limits',
|
||||
'Validate data transformations between nodes',
|
||||
'Understand execution flow and timing',
|
||||
'Track workflow performance metrics',
|
||||
'Verify successful completion before proceeding',
|
||||
'Extract specific data from execution results'
|
||||
],
|
||||
|
||||
performance: `**Response Times** (approximate):
|
||||
- Preview mode: <50ms (no data, just structure)
|
||||
- Summary mode: <200ms (2 items per node)
|
||||
- Filtered mode: 50-500ms (depends on filters)
|
||||
- Full mode: 200ms-5s (depends on data size)
|
||||
|
||||
**Token Consumption**:
|
||||
- Preview: ~500 tokens (no data values)
|
||||
- Summary (2 items): ~2-5K tokens
|
||||
- Filtered (5 items): ~5-15K tokens
|
||||
- Full (50+ items): 50K+ tokens (may exceed limits)
|
||||
|
||||
**Optimization Tips**:
|
||||
- Use preview for all large datasets
|
||||
- Use nodeNames to focus on relevant nodes only
|
||||
- Start with small itemsLimit and increase if needed
|
||||
- Use itemsLimit: 0 to see structure without data`,
|
||||
|
||||
bestPractices: [
|
||||
'ALWAYS use preview mode first for unknown datasets',
|
||||
'Trust the recommendation.suggestedMode from preview',
|
||||
'Use nodeNames to filter to relevant nodes only',
|
||||
'Start with summary mode if preview indicates moderate size',
|
||||
'Use itemsLimit: 0 to understand data structure',
|
||||
'Check hasMoreData to know if results are truncated',
|
||||
'Store execution IDs from triggers for later inspection',
|
||||
'Use mode="filtered" with custom limits for large datasets',
|
||||
'Include input data only when debugging transformations',
|
||||
'Monitor summary.totalItems to understand dataset size'
|
||||
],
|
||||
|
||||
pitfalls: [
|
||||
'DON\'T fetch full mode without previewing first - may timeout',
|
||||
'DON\'T assume all data fits - always check hasMoreData',
|
||||
'DON\'T ignore the recommendation from preview mode',
|
||||
'Execution data is retained based on n8n settings - old executions may be purged',
|
||||
'Binary data (files, images) is not fully included - only metadata',
|
||||
'Status "waiting" indicates execution is still running',
|
||||
'Error executions may have partial data from successful nodes',
|
||||
'Very large individual items (>1MB) may be truncated',
|
||||
'Preview mode estimates may be off by 10-20% for complex structures',
|
||||
'Node names are case-sensitive in nodeNames filter'
|
||||
],
|
||||
|
||||
modeComparison: `**When to use each mode**:
|
||||
|
||||
**Preview**:
|
||||
- ALWAYS use first for unknown datasets
|
||||
- When you need to know if data is safe to fetch
|
||||
- To see data structure without consuming tokens
|
||||
- To get size estimates and recommendations
|
||||
|
||||
**Summary** (default):
|
||||
- Safe default for most cases
|
||||
- When you need representative samples
|
||||
- When preview recommends it
|
||||
- For quick data inspection
|
||||
|
||||
**Filtered**:
|
||||
- When you need specific nodes only
|
||||
- When you need more than 2 items but not all
|
||||
- When preview recommends it with itemsLimit
|
||||
- For targeted data extraction
|
||||
|
||||
**Full**:
|
||||
- ONLY when preview says canFetchFull: true
|
||||
- For small executions (< 20 items total)
|
||||
- When you genuinely need all data
|
||||
- When you're certain data fits in token limit`,
|
||||
|
||||
relatedTools: [
|
||||
'n8n_list_executions - Find execution IDs',
|
||||
'n8n_trigger_webhook_workflow - Trigger and get execution ID',
|
||||
'n8n_delete_execution - Clean up old executions',
|
||||
'n8n_get_workflow - Get workflow structure',
|
||||
'validate_workflow - Validate before executing'
|
||||
]
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const n8nGetWorkflowDetailsDoc: ToolDocumentation = {
|
||||
name: 'n8n_get_workflow_details',
|
||||
category: 'workflow_management',
|
||||
essentials: {
|
||||
description: 'Get workflow details with metadata, version, execution stats. More info than get_workflow.',
|
||||
keyParameters: ['id'],
|
||||
example: 'n8n_get_workflow_details({id: "workflow_123"})',
|
||||
performance: 'Fast (100-300ms)',
|
||||
tips: [
|
||||
'Includes execution statistics',
|
||||
'Shows version history info',
|
||||
'Contains metadata like tags'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: 'Retrieves comprehensive workflow details including metadata, execution statistics, version information, and usage analytics. Provides more information than get_workflow, including data not typically needed for editing but useful for monitoring and analysis.',
|
||||
parameters: {
|
||||
id: { type: 'string', required: true, description: 'Workflow ID to retrieve details for' }
|
||||
},
|
||||
returns: 'Extended workflow object with: id, name, nodes, connections, settings, plus metadata (tags, owner, shared users), execution stats (success/error counts, average runtime), version info, created/updated timestamps',
|
||||
examples: [
|
||||
'n8n_get_workflow_details({id: "abc123"}) - Get workflow with stats',
|
||||
'const details = n8n_get_workflow_details({id: "xyz789"}); // Analyze performance'
|
||||
],
|
||||
useCases: [
|
||||
'Monitor workflow performance',
|
||||
'Analyze execution patterns',
|
||||
'View workflow metadata',
|
||||
'Check version information',
|
||||
'Audit workflow usage'
|
||||
],
|
||||
performance: 'Slightly slower than get_workflow due to additional metadata - typically 100-300ms. Stats may be cached.',
|
||||
bestPractices: [
|
||||
'Use for monitoring and analysis',
|
||||
'Check execution stats before optimization',
|
||||
'Review error counts for debugging',
|
||||
'Monitor average execution times'
|
||||
],
|
||||
pitfalls: [
|
||||
'Requires N8N_API_URL and N8N_API_KEY configured',
|
||||
'More data than needed for simple edits',
|
||||
'Stats may have slight delay',
|
||||
'Not all n8n versions support all fields'
|
||||
],
|
||||
relatedTools: ['n8n_get_workflow', 'n8n_list_executions', 'n8n_get_execution', 'n8n_list_workflows']
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const n8nGetWorkflowMinimalDoc: ToolDocumentation = {
|
||||
name: 'n8n_get_workflow_minimal',
|
||||
category: 'workflow_management',
|
||||
essentials: {
|
||||
description: 'Get minimal info: ID, name, active status, tags. Fast for listings.',
|
||||
keyParameters: ['id'],
|
||||
example: 'n8n_get_workflow_minimal({id: "workflow_123"})',
|
||||
performance: 'Very fast (<50ms)',
|
||||
tips: [
|
||||
'Fastest way to check workflow exists',
|
||||
'Perfect for status checks',
|
||||
'Use in list displays'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: 'Retrieves only essential workflow information without nodes or connections. Returns minimal data needed for listings, status checks, and quick lookups. Optimized for performance when full workflow data is not needed.',
|
||||
parameters: {
|
||||
id: { type: 'string', required: true, description: 'Workflow ID to retrieve minimal info for' }
|
||||
},
|
||||
returns: 'Minimal workflow object with: id, name, active status, tags array, createdAt, updatedAt. No nodes, connections, or settings included.',
|
||||
examples: [
|
||||
'n8n_get_workflow_minimal({id: "abc123"}) - Quick existence check',
|
||||
'const info = n8n_get_workflow_minimal({id: "xyz789"}); // Check if active'
|
||||
],
|
||||
useCases: [
|
||||
'Quick workflow existence checks',
|
||||
'Display workflow lists',
|
||||
'Check active/inactive status',
|
||||
'Get workflow tags',
|
||||
'Performance-critical operations'
|
||||
],
|
||||
performance: 'Extremely fast - typically under 50ms. Returns only database metadata without loading workflow definition.',
|
||||
bestPractices: [
|
||||
'Use for list displays and dashboards',
|
||||
'Ideal for existence checks before operations',
|
||||
'Cache results for UI responsiveness',
|
||||
'Combine with list_workflows for bulk checks'
|
||||
],
|
||||
pitfalls: [
|
||||
'Requires N8N_API_URL and N8N_API_KEY configured',
|
||||
'No workflow content - cannot edit or validate',
|
||||
'Tags may be empty array',
|
||||
'Must use get_workflow for actual workflow data'
|
||||
],
|
||||
relatedTools: ['n8n_list_workflows', 'n8n_get_workflow', 'n8n_get_workflow_structure', 'n8n_update_partial_workflow']
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const n8nGetWorkflowStructureDoc: ToolDocumentation = {
|
||||
name: 'n8n_get_workflow_structure',
|
||||
category: 'workflow_management',
|
||||
essentials: {
|
||||
description: 'Get workflow structure: nodes and connections only. No parameter details.',
|
||||
keyParameters: ['id'],
|
||||
example: 'n8n_get_workflow_structure({id: "workflow_123"})',
|
||||
performance: 'Fast (75-150ms)',
|
||||
tips: [
|
||||
'Shows workflow topology',
|
||||
'Node types without parameters',
|
||||
'Perfect for visualization'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: 'Retrieves workflow structural information including node types, positions, and connections, but without detailed node parameters. Ideal for understanding workflow topology, creating visualizations, or analyzing workflow complexity without the overhead of full parameter data.',
|
||||
parameters: {
|
||||
id: { type: 'string', required: true, description: 'Workflow ID to retrieve structure for' }
|
||||
},
|
||||
returns: 'Workflow structure with: id, name, nodes array (id, name, type, position only), connections object. No node parameters, credentials, or settings included.',
|
||||
examples: [
|
||||
'n8n_get_workflow_structure({id: "abc123"}) - Visualize workflow',
|
||||
'const structure = n8n_get_workflow_structure({id: "xyz789"}); // Analyze complexity'
|
||||
],
|
||||
useCases: [
|
||||
'Generate workflow visualizations',
|
||||
'Analyze workflow complexity',
|
||||
'Understand node relationships',
|
||||
'Create workflow diagrams',
|
||||
'Quick topology validation'
|
||||
],
|
||||
performance: 'Fast retrieval - typically 75-150ms. Faster than get_workflow as parameters are stripped.',
|
||||
bestPractices: [
|
||||
'Use for visualization tools',
|
||||
'Ideal for workflow analysis',
|
||||
'Good for connection validation',
|
||||
'Cache for UI diagram rendering'
|
||||
],
|
||||
pitfalls: [
|
||||
'Requires N8N_API_URL and N8N_API_KEY configured',
|
||||
'No parameter data for configuration',
|
||||
'Cannot validate node settings',
|
||||
'Must use get_workflow for editing'
|
||||
],
|
||||
relatedTools: ['n8n_get_workflow', 'n8n_validate_workflow_connections', 'n8n_get_workflow_minimal', 'validate_workflow_connections']
|
||||
}
|
||||
};
|
||||
@@ -4,63 +4,46 @@ export const n8nGetWorkflowDoc: ToolDocumentation = {
|
||||
name: 'n8n_get_workflow',
|
||||
category: 'workflow_management',
|
||||
essentials: {
|
||||
description: 'Get workflow by ID with different detail levels. Use mode to control response size and content.',
|
||||
keyParameters: ['id', 'mode'],
|
||||
example: 'n8n_get_workflow({id: "workflow_123", mode: "structure"})',
|
||||
description: 'Get a workflow by ID. Returns the complete workflow including nodes, connections, and settings.',
|
||||
keyParameters: ['id'],
|
||||
example: 'n8n_get_workflow({id: "workflow_123"})',
|
||||
performance: 'Fast (50-200ms)',
|
||||
tips: [
|
||||
'mode="full" (default): Complete workflow with all data',
|
||||
'mode="details": Full workflow + execution stats',
|
||||
'mode="structure": Just nodes and connections (topology)',
|
||||
'mode="minimal": Only id, name, active status, tags'
|
||||
'Returns complete workflow JSON',
|
||||
'Includes all node parameters',
|
||||
'Use get_workflow_minimal for faster listings'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
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
|
||||
- minimal: Just id, name, active status, and tags - fastest response`,
|
||||
description: 'Retrieves a complete workflow from n8n by its ID. Returns full workflow definition including all nodes with their parameters, connections between nodes, and workflow settings. This is the primary tool for fetching workflows for viewing, editing, or cloning.',
|
||||
parameters: {
|
||||
id: { type: 'string', required: true, description: 'Workflow ID to retrieve' },
|
||||
mode: { type: 'string', required: false, description: 'Detail level: "full" (default), "details", "structure", "minimal"' }
|
||||
id: { type: 'string', required: true, description: 'Workflow ID to retrieve' }
|
||||
},
|
||||
returns: `Depends on mode:
|
||||
- full: Complete workflow object (id, name, active, nodes[], connections{}, settings, createdAt, updatedAt)
|
||||
- details: Full workflow + executionStats (successCount, errorCount, lastExecution, etc.)
|
||||
- structure: { nodes: [...], connections: {...} } - topology only
|
||||
- minimal: { id, name, active, tags, createdAt, updatedAt }`,
|
||||
returns: 'Complete workflow object containing: id, name, active status, nodes array (with full parameters), connections object, settings, createdAt, updatedAt',
|
||||
examples: [
|
||||
'// Get complete workflow (default)\nn8n_get_workflow({id: "abc123"})',
|
||||
'// Get workflow with execution stats\nn8n_get_workflow({id: "abc123", mode: "details"})',
|
||||
'// Get just the topology\nn8n_get_workflow({id: "abc123", mode: "structure"})',
|
||||
'// Quick metadata check\nn8n_get_workflow({id: "abc123", mode: "minimal"})'
|
||||
'n8n_get_workflow({id: "abc123"}) - Get workflow for editing',
|
||||
'const wf = n8n_get_workflow({id: "xyz789"}); // Clone workflow structure'
|
||||
],
|
||||
useCases: [
|
||||
'View and edit workflow (mode=full)',
|
||||
'Analyze workflow performance (mode=details)',
|
||||
'Clone or compare workflow structure (mode=structure)',
|
||||
'List workflows with status (mode=minimal)',
|
||||
'Debug workflow issues'
|
||||
'View workflow configuration',
|
||||
'Export workflow for backup',
|
||||
'Clone workflow structure',
|
||||
'Debug workflow issues',
|
||||
'Prepare for updates'
|
||||
],
|
||||
performance: `Response times vary by mode:
|
||||
- minimal: ~20-50ms (smallest response)
|
||||
- structure: ~30-80ms (nodes + connections only)
|
||||
- full: ~50-200ms (complete workflow)
|
||||
- details: ~100-300ms (includes execution queries)`,
|
||||
performance: 'Fast retrieval - typically 50-200ms depending on workflow size. Cached by n8n for performance.',
|
||||
bestPractices: [
|
||||
'Use mode="minimal" when listing or checking status',
|
||||
'Use mode="structure" for workflow analysis or cloning',
|
||||
'Use mode="full" (default) when editing',
|
||||
'Use mode="details" for debugging execution issues',
|
||||
'Validate workflow after retrieval if planning modifications'
|
||||
'Check workflow exists before updating',
|
||||
'Use for complete workflow data needs',
|
||||
'Cache results when making multiple operations',
|
||||
'Validate after retrieving if modifying'
|
||||
],
|
||||
pitfalls: [
|
||||
'Requires N8N_API_URL and N8N_API_KEY configured',
|
||||
'mode="details" adds database queries for execution stats',
|
||||
'Workflow must exist or returns 404 error',
|
||||
'Credentials are referenced by ID but values not included'
|
||||
'Returns all data - use minimal/structure for performance',
|
||||
'Workflow must exist or returns 404',
|
||||
'Credentials are referenced but not included'
|
||||
],
|
||||
relatedTools: ['n8n_list_workflows', 'n8n_update_full_workflow', 'n8n_update_partial_workflow', 'n8n_validate_workflow']
|
||||
relatedTools: ['n8n_get_workflow_minimal', 'n8n_get_workflow_structure', 'n8n_update_full_workflow', 'n8n_validate_workflow']
|
||||
}
|
||||
};
|
||||
84
src/mcp/tool-docs/workflow_management/n8n-list-executions.ts
Normal file
84
src/mcp/tool-docs/workflow_management/n8n-list-executions.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const n8nListExecutionsDoc: ToolDocumentation = {
|
||||
name: 'n8n_list_executions',
|
||||
category: 'workflow_management',
|
||||
essentials: {
|
||||
description: 'List workflow executions with optional filters. Supports pagination for large result sets.',
|
||||
keyParameters: ['workflowId', 'status', 'limit'],
|
||||
example: 'n8n_list_executions({workflowId: "abc123", status: "error"})',
|
||||
performance: 'Fast metadata retrieval, use pagination for large datasets',
|
||||
tips: [
|
||||
'Filter by status (success/error/waiting) to find specific execution types',
|
||||
'Use workflowId to see all executions for a specific workflow',
|
||||
'Pagination via cursor allows retrieving large execution histories'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: `Lists workflow executions with powerful filtering options. This tool is essential for monitoring workflow performance, finding failed executions, and tracking workflow activity. Supports pagination for retrieving large execution histories and filtering by workflow, status, and project.`,
|
||||
parameters: {
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'Number of executions to return (1-100, default: 100). Use with cursor for pagination'
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Pagination cursor from previous response. Used to retrieve next page of results'
|
||||
},
|
||||
workflowId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Filter executions by specific workflow ID. Shows all executions for that workflow'
|
||||
},
|
||||
projectId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Filter by project ID (enterprise feature). Groups executions by project'
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
enum: ['success', 'error', 'waiting'],
|
||||
description: 'Filter by execution status. Success = completed, Error = failed, Waiting = running'
|
||||
},
|
||||
includeData: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
description: 'Include execution data in results (default: false). Significantly increases response size'
|
||||
}
|
||||
},
|
||||
returns: `Array of execution objects with metadata, pagination cursor for next page, and optionally execution data. Each execution includes ID, status, start/end times, and workflow reference.`,
|
||||
examples: [
|
||||
'n8n_list_executions({limit: 10}) - Get 10 most recent executions',
|
||||
'n8n_list_executions({workflowId: "abc123"}) - All executions for specific workflow',
|
||||
'n8n_list_executions({status: "error", limit: 50}) - Find failed executions',
|
||||
'n8n_list_executions({status: "waiting"}) - Monitor currently running workflows',
|
||||
'n8n_list_executions({cursor: "next-page-token"}) - Get next page of results'
|
||||
],
|
||||
useCases: [
|
||||
'Monitor workflow execution history and patterns',
|
||||
'Find and debug failed workflow executions',
|
||||
'Track currently running workflows (waiting status)',
|
||||
'Analyze workflow performance and execution frequency',
|
||||
'Generate execution reports for specific workflows'
|
||||
],
|
||||
performance: `Listing executions is fast for metadata only. Including data (includeData: true) significantly impacts performance. Use pagination (limit + cursor) for large result sets. Default limit of 100 balances performance with usability.`,
|
||||
bestPractices: [
|
||||
'Use status filters to focus on specific execution types',
|
||||
'Implement pagination for large execution histories',
|
||||
'Avoid includeData unless you need execution details',
|
||||
'Filter by workflowId when monitoring specific workflows',
|
||||
'Check for cursor in response to detect more pages'
|
||||
],
|
||||
pitfalls: [
|
||||
'Large limits with includeData can cause timeouts',
|
||||
'Execution retention depends on n8n configuration',
|
||||
'Cursor tokens expire - use them promptly',
|
||||
'Status "waiting" includes both running and queued executions',
|
||||
'Deleted workflows still show in execution history'
|
||||
],
|
||||
relatedTools: ['n8n_get_execution', 'n8n_trigger_webhook_workflow', 'n8n_delete_execution', 'n8n_list_workflows']
|
||||
}
|
||||
};
|
||||
@@ -50,6 +50,6 @@ export const n8nListWorkflowsDoc: ToolDocumentation = {
|
||||
'Server may return fewer than requested limit',
|
||||
'returned field is count of current page only, not system total'
|
||||
],
|
||||
relatedTools: ['n8n_get_workflow', 'n8n_update_partial_workflow', 'n8n_executions']
|
||||
relatedTools: ['n8n_get_workflow_minimal', 'n8n_get_workflow', 'n8n_update_partial_workflow', 'n8n_list_executions']
|
||||
}
|
||||
};
|
||||
@@ -1,138 +0,0 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const n8nTestWorkflowDoc: ToolDocumentation = {
|
||||
name: 'n8n_test_workflow',
|
||||
category: 'workflow_management',
|
||||
essentials: {
|
||||
description: 'Test/trigger workflow execution. Auto-detects trigger type (webhook/form/chat). Only workflows with these triggers can be executed externally.',
|
||||
keyParameters: ['workflowId', 'triggerType', 'data', 'message'],
|
||||
example: 'n8n_test_workflow({workflowId: "123"}) - auto-detect trigger',
|
||||
performance: 'Immediate trigger, response time depends on workflow complexity',
|
||||
tips: [
|
||||
'Auto-detects trigger type from workflow if not specified',
|
||||
'Workflow must have a webhook, form, or chat trigger to be executable',
|
||||
'For chat triggers, message is required',
|
||||
'All trigger types require the workflow to be ACTIVE'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: `Test and trigger n8n workflows through HTTP-based methods. This unified tool supports multiple trigger types:
|
||||
|
||||
**Trigger Types:**
|
||||
- **webhook**: HTTP-based triggers (GET/POST/PUT/DELETE)
|
||||
- **form**: Form submission triggers
|
||||
- **chat**: AI chat triggers with conversation support
|
||||
|
||||
**Important:** n8n's public API does not support direct workflow execution. Only workflows with webhook, form, or chat triggers can be executed externally. Workflows with schedule, manual, or other trigger types cannot be triggered via this API.
|
||||
|
||||
The tool auto-detects the appropriate trigger type by analyzing the workflow's trigger node. You can override this with the triggerType parameter.`,
|
||||
parameters: {
|
||||
workflowId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Workflow ID to execute'
|
||||
},
|
||||
triggerType: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
enum: ['webhook', 'form', 'chat'],
|
||||
description: 'Trigger type. Auto-detected if not specified. Workflow must have matching trigger node.'
|
||||
},
|
||||
httpMethod: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
enum: ['GET', 'POST', 'PUT', 'DELETE'],
|
||||
description: 'For webhook: HTTP method (default: from workflow config or POST)'
|
||||
},
|
||||
webhookPath: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'For webhook: override the webhook path'
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'For chat: message to send (required for chat triggers)'
|
||||
},
|
||||
sessionId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'For chat: session ID for conversation continuity'
|
||||
},
|
||||
data: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
description: 'Input data/payload for webhook or form fields'
|
||||
},
|
||||
headers: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
description: 'Custom HTTP headers'
|
||||
},
|
||||
timeout: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'Timeout in ms (default: 120000)'
|
||||
},
|
||||
waitForResponse: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
description: 'Wait for workflow completion (default: true)'
|
||||
}
|
||||
},
|
||||
returns: `Execution response including:
|
||||
- success: boolean
|
||||
- data: workflow output data
|
||||
- executionId: for tracking/debugging
|
||||
- triggerType: detected or specified trigger type
|
||||
- metadata: timing and request details`,
|
||||
examples: [
|
||||
'n8n_test_workflow({workflowId: "123"}) - Auto-detect and trigger',
|
||||
'n8n_test_workflow({workflowId: "123", triggerType: "webhook", data: {name: "John"}}) - Webhook with data',
|
||||
'n8n_test_workflow({workflowId: "123", triggerType: "chat", message: "Hello AI"}) - Chat trigger',
|
||||
'n8n_test_workflow({workflowId: "123", triggerType: "form", data: {email: "test@example.com"}}) - Form submission'
|
||||
],
|
||||
useCases: [
|
||||
'Test workflows during development',
|
||||
'Trigger AI chat workflows with messages',
|
||||
'Submit form data to form-triggered workflows',
|
||||
'Integrate n8n workflows with external systems via webhooks'
|
||||
],
|
||||
performance: `Performance varies based on workflow complexity and waitForResponse setting:
|
||||
- Webhook: Immediate trigger, depends on workflow
|
||||
- Form: Immediate trigger, depends on workflow
|
||||
- Chat: May have additional AI processing time`,
|
||||
errorHandling: `**Error Response with Execution Guidance**
|
||||
|
||||
When execution fails, the response includes guidance for debugging:
|
||||
|
||||
**With Execution ID** (workflow started but failed):
|
||||
- Use n8n_executions({action: 'get', id: executionId, mode: 'preview'}) to investigate
|
||||
|
||||
**Without Execution ID** (workflow didn't start):
|
||||
- Use n8n_executions({action: 'list', workflowId: 'wf_id'}) to find recent executions
|
||||
|
||||
**Common Errors:**
|
||||
- "Workflow not found" - Check workflow ID exists
|
||||
- "Workflow not active" - Activate workflow (required for all trigger types)
|
||||
- "Workflow cannot be triggered externally" - Workflow has no webhook/form/chat trigger
|
||||
- "Chat message required" - Provide message parameter for chat triggers
|
||||
- "SSRF protection" - URL validation failed`,
|
||||
bestPractices: [
|
||||
'Let auto-detection choose the trigger type when possible',
|
||||
'Ensure workflow has a webhook, form, or chat trigger before testing',
|
||||
'For chat workflows, provide sessionId for multi-turn conversations',
|
||||
'Use mode="preview" with n8n_executions for efficient debugging',
|
||||
'Test with small data payloads first',
|
||||
'Activate workflows before testing (use n8n_update_partial_workflow with activateWorkflow)'
|
||||
],
|
||||
pitfalls: [
|
||||
'All trigger types require the workflow to be ACTIVE',
|
||||
'Workflows without webhook/form/chat triggers cannot be executed externally',
|
||||
'Chat trigger requires message parameter',
|
||||
'Form data must match expected form fields',
|
||||
'Webhook method must match node configuration'
|
||||
],
|
||||
relatedTools: ['n8n_executions', 'n8n_get_workflow', 'n8n_create_workflow', 'n8n_validate_workflow']
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,118 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const n8nTriggerWebhookWorkflowDoc: ToolDocumentation = {
|
||||
name: 'n8n_trigger_webhook_workflow',
|
||||
category: 'workflow_management',
|
||||
essentials: {
|
||||
description: 'Trigger workflow via webhook. Must be ACTIVE with Webhook node. Method must match config.',
|
||||
keyParameters: ['webhookUrl', 'httpMethod', 'data'],
|
||||
example: 'n8n_trigger_webhook_workflow({webhookUrl: "https://n8n.example.com/webhook/abc-def-ghi"})',
|
||||
performance: 'Immediate trigger, response time depends on workflow complexity',
|
||||
tips: [
|
||||
'Workflow MUST be active and contain a Webhook node for triggering',
|
||||
'HTTP method must match webhook node configuration (often GET)',
|
||||
'Use waitForResponse:false for async execution without waiting'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: `Triggers a workflow execution via its webhook URL. This is the primary method for external systems to start n8n workflows. The target workflow must be active and contain a properly configured Webhook node as the trigger. The HTTP method used must match the webhook configuration.`,
|
||||
parameters: {
|
||||
webhookUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Full webhook URL from n8n workflow (e.g., https://n8n.example.com/webhook/abc-def-ghi)'
|
||||
},
|
||||
httpMethod: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
enum: ['GET', 'POST', 'PUT', 'DELETE'],
|
||||
description: 'HTTP method (must match webhook configuration, often GET). Defaults to GET if not specified'
|
||||
},
|
||||
data: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
description: 'Data to send with the webhook request. For GET requests, becomes query parameters'
|
||||
},
|
||||
headers: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
description: 'Additional HTTP headers to include in the request'
|
||||
},
|
||||
waitForResponse: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
description: 'Wait for workflow completion and return results (default: true). Set to false for fire-and-forget'
|
||||
}
|
||||
},
|
||||
returns: `Webhook response data if waitForResponse is true, or immediate acknowledgment if false. Response format depends on webhook node configuration.`,
|
||||
examples: [
|
||||
'n8n_trigger_webhook_workflow({webhookUrl: "https://n8n.example.com/webhook/order-process"}) - Trigger with GET',
|
||||
'n8n_trigger_webhook_workflow({webhookUrl: "https://n8n.example.com/webhook/data-import", httpMethod: "POST", data: {name: "John", email: "john@example.com"}}) - POST with data',
|
||||
'n8n_trigger_webhook_workflow({webhookUrl: "https://n8n.example.com/webhook/async-job", waitForResponse: false}) - Fire and forget',
|
||||
'n8n_trigger_webhook_workflow({webhookUrl: "https://n8n.example.com/webhook/api", headers: {"API-Key": "secret"}}) - With auth headers'
|
||||
],
|
||||
useCases: [
|
||||
'Trigger data processing workflows from external applications',
|
||||
'Start scheduled jobs manually via webhook',
|
||||
'Integrate n8n workflows with third-party services',
|
||||
'Create REST API endpoints using n8n workflows',
|
||||
'Implement event-driven architectures with n8n'
|
||||
],
|
||||
performance: `Performance varies based on workflow complexity and waitForResponse setting. Synchronous calls (waitForResponse: true) block until workflow completes. For long-running workflows, use async mode (waitForResponse: false) and monitor execution separately.`,
|
||||
errorHandling: `**Enhanced Error Messages with Execution Guidance**
|
||||
|
||||
When a webhook trigger fails, the error response now includes specific guidance to help debug the issue:
|
||||
|
||||
**Error with Execution ID** (workflow started but failed):
|
||||
- Format: "Workflow {workflowId} execution {executionId} failed. Use n8n_get_execution({id: '{executionId}', mode: 'preview'}) to investigate the error."
|
||||
- Response includes: executionId and workflowId fields for direct access
|
||||
- Recommended action: Use n8n_get_execution with mode='preview' for fast, efficient error inspection
|
||||
|
||||
**Error without Execution ID** (workflow didn't start):
|
||||
- Format: "Workflow failed to execute. Use n8n_list_executions to find recent executions, then n8n_get_execution with mode='preview' to investigate."
|
||||
- Recommended action: Check recent executions with n8n_list_executions
|
||||
|
||||
**Why mode='preview'?**
|
||||
- Fast: <50ms response time
|
||||
- Efficient: ~500 tokens (vs 50K+ for full mode)
|
||||
- Safe: No timeout or token limit risks
|
||||
- Informative: Shows structure, counts, and error details
|
||||
- Provides recommendations for fetching more data if needed
|
||||
|
||||
**Example Error Responses**:
|
||||
\`\`\`json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Workflow wf_123 execution exec_456 failed. Use n8n_get_execution({id: 'exec_456', mode: 'preview'}) to investigate the error.",
|
||||
"executionId": "exec_456",
|
||||
"workflowId": "wf_123",
|
||||
"code": "SERVER_ERROR"
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
**Investigation Workflow**:
|
||||
1. Trigger returns error with execution ID
|
||||
2. Call n8n_get_execution({id: executionId, mode: 'preview'}) to see structure and error
|
||||
3. Based on preview recommendation, fetch more data if needed
|
||||
4. Fix issues in workflow and retry`,
|
||||
bestPractices: [
|
||||
'Always verify workflow is active before attempting webhook triggers',
|
||||
'Match HTTP method exactly with webhook node configuration',
|
||||
'Use async mode (waitForResponse: false) for long-running workflows',
|
||||
'Include authentication headers when webhook requires them',
|
||||
'Test webhook URL manually first to ensure it works',
|
||||
'When errors occur, use n8n_get_execution with mode="preview" first for efficient debugging',
|
||||
'Store execution IDs from error responses for later investigation'
|
||||
],
|
||||
pitfalls: [
|
||||
'Workflow must be ACTIVE - inactive workflows cannot be triggered',
|
||||
'HTTP method mismatch returns 404 even if URL is correct',
|
||||
'Webhook node must be the trigger node in the workflow',
|
||||
'Timeout errors occur with long workflows in sync mode',
|
||||
'Data format must match webhook node expectations',
|
||||
'Error messages always include n8n_get_execution guidance - follow the suggested steps for efficient debugging',
|
||||
'Execution IDs in error responses are crucial for debugging - always check for and use them'
|
||||
],
|
||||
relatedTools: ['n8n_get_execution', 'n8n_list_executions', 'n8n_get_workflow', 'n8n_create_workflow']
|
||||
}
|
||||
};
|
||||
@@ -9,7 +9,6 @@ export const n8nUpdateFullWorkflowDoc: ToolDocumentation = {
|
||||
example: 'n8n_update_full_workflow({id: "wf_123", nodes: [...], connections: {...}})',
|
||||
performance: 'Network-dependent',
|
||||
tips: [
|
||||
'Include intent parameter in every call - helps to return better responses',
|
||||
'Must provide complete workflow',
|
||||
'Use update_partial for small changes',
|
||||
'Validate before updating'
|
||||
@@ -22,15 +21,13 @@ export const n8nUpdateFullWorkflowDoc: ToolDocumentation = {
|
||||
name: { type: 'string', description: 'New workflow name (optional)' },
|
||||
nodes: { type: 'array', description: 'Complete array of workflow nodes (required if modifying structure)' },
|
||||
connections: { type: 'object', description: 'Complete connections object (required if modifying structure)' },
|
||||
settings: { type: 'object', description: 'Workflow settings to update (timezone, error handling, etc.)' },
|
||||
intent: { type: 'string', description: 'Intent of the change - helps to return better response. Include in every tool call. Example: "Migrate workflow to new node versions".' }
|
||||
settings: { type: 'object', description: 'Workflow settings to update (timezone, error handling, etc.)' }
|
||||
},
|
||||
returns: 'Updated workflow object with all fields including the changes applied',
|
||||
examples: [
|
||||
'n8n_update_full_workflow({id: "abc", intent: "Rename workflow for clarity", name: "New Name"}) - Rename with intent',
|
||||
'n8n_update_full_workflow({id: "abc", name: "New Name"}) - Rename only',
|
||||
'n8n_update_full_workflow({id: "xyz", intent: "Add error handling nodes", nodes: [...], connections: {...}}) - Full structure update',
|
||||
'const wf = n8n_get_workflow({id}); wf.nodes.push(newNode); n8n_update_full_workflow({...wf, intent: "Add data processing node"}); // Add node'
|
||||
'n8n_update_full_workflow({id: "xyz", nodes: [...], connections: {...}}) - Full structure update',
|
||||
'const wf = n8n_get_workflow({id}); wf.nodes.push(newNode); n8n_update_full_workflow(wf); // Add node'
|
||||
],
|
||||
useCases: [
|
||||
'Major workflow restructuring',
|
||||
@@ -41,7 +38,6 @@ export const n8nUpdateFullWorkflowDoc: ToolDocumentation = {
|
||||
],
|
||||
performance: 'Network-dependent - typically 200-500ms. Larger workflows take longer. Consider update_partial for better performance.',
|
||||
bestPractices: [
|
||||
'Always include intent parameter - it helps provide better responses',
|
||||
'Get workflow first, modify, then update',
|
||||
'Validate with validate_workflow before updating',
|
||||
'Use update_partial for small changes',
|
||||
|
||||
@@ -4,13 +4,11 @@ export const n8nUpdatePartialWorkflowDoc: ToolDocumentation = {
|
||||
name: 'n8n_update_partial_workflow',
|
||||
category: 'workflow_management',
|
||||
essentials: {
|
||||
description: 'Update workflow incrementally with diff operations. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, rewireConnection, cleanStaleConnections, replaceConnections, updateSettings, updateName, add/removeTag, activateWorkflow, deactivateWorkflow. Supports smart parameters (branch, case) for multi-output nodes. Full support for AI connections (ai_languageModel, ai_tool, ai_memory, ai_embedding, ai_vectorStore, ai_document, ai_textSplitter, ai_outputParser).',
|
||||
description: 'Update workflow incrementally with diff operations. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, rewireConnection, cleanStaleConnections, replaceConnections, updateSettings, updateName, add/removeTag. Supports smart parameters (branch, case) for multi-output nodes. Full support for AI connections (ai_languageModel, ai_tool, ai_memory, ai_embedding, ai_vectorStore, ai_document, ai_textSplitter, ai_outputParser).',
|
||||
keyParameters: ['id', 'operations', 'continueOnError'],
|
||||
example: 'n8n_update_partial_workflow({id: "wf_123", operations: [{type: "rewireConnection", source: "IF", from: "Old", to: "New", branch: "true"}]})',
|
||||
performance: 'Fast (50-200ms)',
|
||||
tips: [
|
||||
'ALWAYS provide intent parameter describing what you\'re doing (e.g., "Add error handling", "Fix webhook URL", "Connect Slack to error output")',
|
||||
'DON\'T use generic intent like "update workflow" or "partial update" - be specific about your goal',
|
||||
'Use rewireConnection to change connection targets',
|
||||
'Use branch="true"/"false" for IF nodes',
|
||||
'Use case=N for Switch nodes',
|
||||
@@ -19,14 +17,11 @@ export const n8nUpdatePartialWorkflowDoc: ToolDocumentation = {
|
||||
'Use continueOnError mode for best-effort bulk operations',
|
||||
'Validate with validateOnly first',
|
||||
'For AI connections, specify sourceOutput type (ai_languageModel, ai_tool, etc.)',
|
||||
'Batch AI component connections for atomic updates',
|
||||
'Auto-sanitization: ALL nodes auto-fixed during updates (operator structures, missing metadata)',
|
||||
'Node renames automatically update all connection references - no manual connection operations needed',
|
||||
'Activate/deactivate workflows: Use activateWorkflow/deactivateWorkflow operations (requires activatable triggers like webhook/schedule)'
|
||||
'Batch AI component connections for atomic updates'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: `Updates workflows using surgical diff operations instead of full replacement. Supports 17 operation types for precise modifications. Operations are validated and applied atomically by default - all succeed or none are applied.
|
||||
description: `Updates workflows using surgical diff operations instead of full replacement. Supports 15 operation types for precise modifications. Operations are validated and applied atomically by default - all succeed or none are applied.
|
||||
|
||||
## Available Operations:
|
||||
|
||||
@@ -51,10 +46,6 @@ export const n8nUpdatePartialWorkflowDoc: ToolDocumentation = {
|
||||
- **addTag**: Add a workflow tag
|
||||
- **removeTag**: Remove a workflow tag
|
||||
|
||||
### Workflow Activation Operations (2 types):
|
||||
- **activateWorkflow**: Activate the workflow to enable automatic execution via triggers
|
||||
- **deactivateWorkflow**: Deactivate the workflow to prevent automatic execution
|
||||
|
||||
## Smart Parameters for Multi-Output Nodes
|
||||
|
||||
For **IF nodes**, use semantic 'branch' parameter instead of technical sourceIndex:
|
||||
@@ -88,9 +79,6 @@ Full support for all 8 AI connection types used in n8n AI workflows:
|
||||
- Multiple tools: Batch multiple \`sourceOutput: "ai_tool"\` connections to one AI Agent
|
||||
- Vector retrieval: Chain ai_embedding → ai_vectorStore → ai_tool → AI Agent
|
||||
|
||||
**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.
|
||||
|
||||
**Best Practices**:
|
||||
- Always specify \`sourceOutput\` for AI connections (defaults to "main" if omitted)
|
||||
- Connect language model BEFORE creating/enabling AI Agent (validation requirement)
|
||||
@@ -106,201 +94,7 @@ The **cleanStaleConnections** operation automatically removes broken connection
|
||||
Set **continueOnError: true** to apply valid operations even if some fail. Returns detailed results showing which operations succeeded/failed. Perfect for bulk cleanup operations.
|
||||
|
||||
### Graceful Error Handling
|
||||
Add **ignoreErrors: true** to removeConnection operations to prevent failures when connections don't exist.
|
||||
|
||||
## Auto-Sanitization System
|
||||
|
||||
### What Gets Auto-Fixed
|
||||
When ANY workflow update is made, ALL nodes in the workflow are automatically sanitized to ensure complete metadata and correct structure:
|
||||
|
||||
1. **Operator Structure Fixes**:
|
||||
- Binary operators (equals, contains, greaterThan, etc.) automatically have \`singleValue\` removed
|
||||
- Unary operators (isEmpty, isNotEmpty, true, false) automatically get \`singleValue: true\` added
|
||||
- Invalid operator structures (e.g., \`{type: "isNotEmpty"}\`) are corrected to \`{type: "boolean", operation: "isNotEmpty"}\`
|
||||
|
||||
2. **Missing Metadata Added**:
|
||||
- IF nodes with conditions get complete \`conditions.options\` structure if missing
|
||||
- Switch nodes with conditions get complete \`conditions.options\` for all rules
|
||||
- Required fields: \`{version: 2, leftValue: "", caseSensitive: true, typeValidation: "strict"}\`
|
||||
|
||||
### Sanitization Scope
|
||||
- Runs on **ALL nodes** in the workflow, not just modified ones
|
||||
- Triggered by ANY update operation (addNode, updateNode, addConnection, etc.)
|
||||
- Prevents workflow corruption that would make UI unrenderable
|
||||
|
||||
### Limitations
|
||||
Auto-sanitization CANNOT fix:
|
||||
- Broken connections (connections referencing non-existent nodes) - use \`cleanStaleConnections\`
|
||||
- Branch count mismatches (e.g., Switch with 3 rules but only 2 outputs) - requires manual connection fixes
|
||||
- Workflows in paradoxical corrupt states (API returns corrupt data, API rejects updates) - must recreate workflow
|
||||
|
||||
### Recovery Guidance
|
||||
If validation still fails after auto-sanitization:
|
||||
1. Check error details for specific issues
|
||||
2. Use \`validate_workflow\` to see all validation errors
|
||||
3. For connection issues, use \`cleanStaleConnections\` operation
|
||||
4. For branch mismatches, add missing output connections
|
||||
5. For paradoxical corrupted workflows, create new workflow and migrate nodes
|
||||
|
||||
## Automatic Connection Reference Updates
|
||||
|
||||
When you rename a node using **updateNode**, all connection references throughout the workflow are automatically updated. Both the connection source keys and target references are updated for all connection types (main, error, ai_tool, ai_languageModel, ai_memory, etc.) and all branch configurations (IF node branches, Switch node cases, error outputs).
|
||||
|
||||
### Basic Example
|
||||
\`\`\`javascript
|
||||
// Rename a node - connections update automatically
|
||||
n8n_update_partial_workflow({
|
||||
id: "wf_123",
|
||||
operations: [{
|
||||
type: "updateNode",
|
||||
nodeId: "node_abc",
|
||||
updates: { name: "Data Processor" }
|
||||
}]
|
||||
});
|
||||
// All incoming and outgoing connections now reference "Data Processor"
|
||||
\`\`\`
|
||||
|
||||
### Multi-Output Node Example
|
||||
\`\`\`javascript
|
||||
// Rename nodes in a branching workflow
|
||||
n8n_update_partial_workflow({
|
||||
id: "workflow_id",
|
||||
operations: [
|
||||
{
|
||||
type: "updateNode",
|
||||
nodeId: "if_node_id",
|
||||
updates: { name: "Value Checker" }
|
||||
},
|
||||
{
|
||||
type: "updateNode",
|
||||
nodeId: "error_node_id",
|
||||
updates: { name: "Error Handler" }
|
||||
}
|
||||
]
|
||||
});
|
||||
// IF node branches and error connections automatically updated
|
||||
\`\`\`
|
||||
|
||||
### Name Collision Protection
|
||||
Attempting to rename a node to an existing name returns a clear error:
|
||||
\`\`\`
|
||||
Cannot rename node "Old Name" to "New Name": A node with that name already exists (id: abc123...).
|
||||
Please choose a different name.
|
||||
\`\`\`
|
||||
|
||||
### Usage Notes
|
||||
- Simply rename nodes with updateNode - no manual connection operations needed
|
||||
- Multiple renames in one call work atomically
|
||||
- Can rename a node and add/remove connections using the new name in the same batch
|
||||
- Use \`validateOnly: true\` to preview effects before applying
|
||||
|
||||
## Removing Properties with undefined
|
||||
|
||||
To remove a property from a node, set its value to \`undefined\` in the updates object. This is essential when migrating from deprecated properties or cleaning up optional configuration fields.
|
||||
|
||||
### Why Use undefined?
|
||||
- **Property removal vs. null**: Setting a property to \`undefined\` removes it completely from the node object, while \`null\` sets the property to a null value
|
||||
- **Validation constraints**: Some properties are mutually exclusive (e.g., \`continueOnFail\` and \`onError\`). Simply setting one without removing the other will fail validation
|
||||
- **Deprecated property migration**: When n8n deprecates properties, you must remove the old property before the new one will work
|
||||
|
||||
### Basic Property Removal
|
||||
\`\`\`javascript
|
||||
// Remove error handling configuration
|
||||
n8n_update_partial_workflow({
|
||||
id: "wf_123",
|
||||
operations: [{
|
||||
type: "updateNode",
|
||||
nodeName: "HTTP Request",
|
||||
updates: { onError: undefined }
|
||||
}]
|
||||
});
|
||||
|
||||
// Remove disabled flag
|
||||
n8n_update_partial_workflow({
|
||||
id: "wf_456",
|
||||
operations: [{
|
||||
type: "updateNode",
|
||||
nodeId: "node_abc",
|
||||
updates: { disabled: undefined }
|
||||
}]
|
||||
});
|
||||
\`\`\`
|
||||
|
||||
### Nested Property Removal
|
||||
Use dot notation to remove nested properties:
|
||||
\`\`\`javascript
|
||||
// Remove nested parameter
|
||||
n8n_update_partial_workflow({
|
||||
id: "wf_789",
|
||||
operations: [{
|
||||
type: "updateNode",
|
||||
nodeName: "API Request",
|
||||
updates: { "parameters.authentication": undefined }
|
||||
}]
|
||||
});
|
||||
|
||||
// Remove entire array property
|
||||
n8n_update_partial_workflow({
|
||||
id: "wf_012",
|
||||
operations: [{
|
||||
type: "updateNode",
|
||||
nodeName: "HTTP Request",
|
||||
updates: { "parameters.headers": undefined }
|
||||
}]
|
||||
});
|
||||
\`\`\`
|
||||
|
||||
### Migrating from Deprecated Properties
|
||||
Common scenario: replacing \`continueOnFail\` with \`onError\`:
|
||||
\`\`\`javascript
|
||||
// WRONG: Setting only the new property leaves the old one
|
||||
n8n_update_partial_workflow({
|
||||
id: "wf_123",
|
||||
operations: [{
|
||||
type: "updateNode",
|
||||
nodeName: "HTTP Request",
|
||||
updates: { onError: "continueErrorOutput" }
|
||||
}]
|
||||
});
|
||||
// Error: continueOnFail and onError are mutually exclusive
|
||||
|
||||
// CORRECT: Remove the old property first
|
||||
n8n_update_partial_workflow({
|
||||
id: "wf_123",
|
||||
operations: [{
|
||||
type: "updateNode",
|
||||
nodeName: "HTTP Request",
|
||||
updates: {
|
||||
continueOnFail: undefined,
|
||||
onError: "continueErrorOutput"
|
||||
}
|
||||
}]
|
||||
});
|
||||
\`\`\`
|
||||
|
||||
### Batch Property Removal
|
||||
Remove multiple properties in one operation:
|
||||
\`\`\`javascript
|
||||
n8n_update_partial_workflow({
|
||||
id: "wf_345",
|
||||
operations: [{
|
||||
type: "updateNode",
|
||||
nodeName: "Data Processor",
|
||||
updates: {
|
||||
continueOnFail: undefined,
|
||||
alwaysOutputData: undefined,
|
||||
"parameters.legacy_option": undefined
|
||||
}
|
||||
}]
|
||||
});
|
||||
\`\`\`
|
||||
|
||||
### When to Use undefined
|
||||
- Removing deprecated properties during migration
|
||||
- Cleaning up optional configuration flags
|
||||
- Resolving mutual exclusivity validation errors
|
||||
- Removing stale or unnecessary node metadata
|
||||
- Simplifying node configuration`,
|
||||
Add **ignoreErrors: true** to removeConnection operations to prevent failures when connections don't exist.`,
|
||||
parameters: {
|
||||
id: { type: 'string', required: true, description: 'Workflow ID to update' },
|
||||
operations: {
|
||||
@@ -309,12 +103,10 @@ n8n_update_partial_workflow({
|
||||
description: 'Array of diff operations. Each must have "type" field and operation-specific properties. Nodes can be referenced by ID or name.'
|
||||
},
|
||||
validateOnly: { type: 'boolean', description: 'If true, only validate operations without applying them' },
|
||||
continueOnError: { type: 'boolean', description: 'If true, apply valid operations even if some fail (best-effort mode). Returns applied and failed operation indices. Default: false (atomic)' },
|
||||
intent: { type: 'string', description: 'Intent of the change - helps to return better response. Include in every tool call. Example: "Add error handling for API failures".' }
|
||||
continueOnError: { type: 'boolean', description: 'If true, apply valid operations even if some fail (best-effort mode). Returns applied and failed operation indices. Default: false (atomic)' }
|
||||
},
|
||||
returns: 'Updated workflow object or validation results if validateOnly=true',
|
||||
examples: [
|
||||
'// Include intent parameter for better responses\nn8n_update_partial_workflow({id: "abc", intent: "Add error handling for API failures", operations: [{type: "addConnection", source: "HTTP Request", target: "Error Handler"}]})',
|
||||
'// Add a basic node (minimal configuration)\nn8n_update_partial_workflow({id: "abc", operations: [{type: "addNode", node: {name: "Process Data", type: "n8n-nodes-base.set", position: [400, 300], parameters: {}}}]})',
|
||||
'// Add node with full configuration\nn8n_update_partial_workflow({id: "def", operations: [{type: "addNode", node: {name: "Send Slack Alert", type: "n8n-nodes-base.slack", position: [600, 300], typeVersion: 2, parameters: {resource: "message", operation: "post", channel: "#alerts", text: "Success!"}}}]})',
|
||||
'// Add node AND connect it (common pattern)\nn8n_update_partial_workflow({id: "ghi", operations: [\n {type: "addNode", node: {name: "HTTP Request", type: "n8n-nodes-base.httpRequest", position: [400, 300], parameters: {url: "https://api.example.com", method: "GET"}}},\n {type: "addConnection", source: "Webhook", target: "HTTP Request"}\n]})',
|
||||
@@ -335,17 +127,11 @@ n8n_update_partial_workflow({
|
||||
'// Connect memory to AI Agent\nn8n_update_partial_workflow({id: "ai3", operations: [{type: "addConnection", source: "Window Buffer Memory", target: "AI Agent", sourceOutput: "ai_memory"}]})',
|
||||
'// Connect output parser to AI Agent\nn8n_update_partial_workflow({id: "ai4", operations: [{type: "addConnection", source: "Structured Output Parser", target: "AI Agent", sourceOutput: "ai_outputParser"}]})',
|
||||
'// Complete AI Agent setup: Add language model, tools, and memory\nn8n_update_partial_workflow({id: "ai5", operations: [\n {type: "addConnection", source: "OpenAI Chat Model", target: "AI Agent", sourceOutput: "ai_languageModel"},\n {type: "addConnection", source: "HTTP Request Tool", target: "AI Agent", sourceOutput: "ai_tool"},\n {type: "addConnection", source: "Code Tool", target: "AI Agent", sourceOutput: "ai_tool"},\n {type: "addConnection", source: "Window Buffer Memory", target: "AI Agent", sourceOutput: "ai_memory"}\n]})',
|
||||
'// Add fallback model to AI Agent for reliability\nn8n_update_partial_workflow({id: "ai6", operations: [\n {type: "addConnection", source: "OpenAI Chat Model", target: "AI Agent", sourceOutput: "ai_languageModel", targetIndex: 0},\n {type: "addConnection", source: "Anthropic Chat Model", target: "AI Agent", sourceOutput: "ai_languageModel", targetIndex: 1}\n]})',
|
||||
'// Add fallback model to AI Agent (requires v2.1+)\nn8n_update_partial_workflow({id: "ai6", operations: [\n {type: "addConnection", source: "OpenAI Chat Model", target: "AI Agent", sourceOutput: "ai_languageModel", targetIndex: 0},\n {type: "addConnection", source: "Anthropic Chat Model", target: "AI Agent", sourceOutput: "ai_languageModel", targetIndex: 1}\n]})',
|
||||
'// Vector Store setup: Connect embeddings and documents\nn8n_update_partial_workflow({id: "ai7", operations: [\n {type: "addConnection", source: "Embeddings OpenAI", target: "Pinecone Vector Store", sourceOutput: "ai_embedding"},\n {type: "addConnection", source: "Default Data Loader", target: "Pinecone Vector Store", sourceOutput: "ai_document"}\n]})',
|
||||
'// Connect Vector Store Tool to AI Agent (retrieval setup)\nn8n_update_partial_workflow({id: "ai8", operations: [\n {type: "addConnection", source: "Pinecone Vector Store", target: "Vector Store Tool", sourceOutput: "ai_vectorStore"},\n {type: "addConnection", source: "Vector Store Tool", target: "AI Agent", sourceOutput: "ai_tool"}\n]})',
|
||||
'// Rewire AI Agent to use different language model\nn8n_update_partial_workflow({id: "ai9", operations: [{type: "rewireConnection", source: "AI Agent", from: "OpenAI Chat Model", to: "Anthropic Chat Model", sourceOutput: "ai_languageModel"}]})',
|
||||
'// Replace all AI tools for an agent\nn8n_update_partial_workflow({id: "ai10", operations: [\n {type: "removeConnection", source: "Old Tool 1", target: "AI Agent", sourceOutput: "ai_tool"},\n {type: "removeConnection", source: "Old Tool 2", target: "AI Agent", sourceOutput: "ai_tool"},\n {type: "addConnection", source: "New HTTP Tool", target: "AI Agent", sourceOutput: "ai_tool"},\n {type: "addConnection", source: "New Code Tool", target: "AI Agent", sourceOutput: "ai_tool"}\n]})',
|
||||
'\n// ============ REMOVING PROPERTIES EXAMPLES ============',
|
||||
'// Remove a simple property\nn8n_update_partial_workflow({id: "rm1", operations: [{type: "updateNode", nodeName: "HTTP Request", updates: {onError: undefined}}]})',
|
||||
'// Migrate from deprecated continueOnFail to onError\nn8n_update_partial_workflow({id: "rm2", operations: [{type: "updateNode", nodeName: "HTTP Request", updates: {continueOnFail: undefined, onError: "continueErrorOutput"}}]})',
|
||||
'// Remove nested property\nn8n_update_partial_workflow({id: "rm3", operations: [{type: "updateNode", nodeName: "API Request", updates: {"parameters.authentication": undefined}}]})',
|
||||
'// Remove multiple properties\nn8n_update_partial_workflow({id: "rm4", operations: [{type: "updateNode", nodeName: "Data Processor", updates: {continueOnFail: undefined, alwaysOutputData: undefined, "parameters.legacy_option": undefined}}]})',
|
||||
'// Remove entire array property\nn8n_update_partial_workflow({id: "rm5", operations: [{type: "updateNode", nodeName: "HTTP Request", updates: {"parameters.headers": undefined}}]})'
|
||||
'// Replace all AI tools for an agent\nn8n_update_partial_workflow({id: "ai10", operations: [\n {type: "removeConnection", source: "Old Tool 1", target: "AI Agent", sourceOutput: "ai_tool"},\n {type: "removeConnection", source: "Old Tool 2", target: "AI Agent", sourceOutput: "ai_tool"},\n {type: "addConnection", source: "New HTTP Tool", target: "AI Agent", sourceOutput: "ai_tool"},\n {type: "addConnection", source: "New Code Tool", target: "AI Agent", sourceOutput: "ai_tool"}\n]})'
|
||||
],
|
||||
useCases: [
|
||||
'Rewire connections when replacing nodes',
|
||||
@@ -367,7 +153,6 @@ n8n_update_partial_workflow({
|
||||
],
|
||||
performance: 'Very fast - typically 50-200ms. Much faster than full updates as only changes are processed.',
|
||||
bestPractices: [
|
||||
'Always include intent parameter with specific description (e.g., "Add error handling to HTTP Request node", "Fix authentication flow", "Connect Slack notification to errors"). Avoid generic phrases like "update workflow" or "partial update"',
|
||||
'Use rewireConnection instead of remove+add for changing targets',
|
||||
'Use branch="true"/"false" for IF nodes instead of sourceIndex',
|
||||
'Use case=N for Switch nodes instead of sourceIndex',
|
||||
@@ -382,11 +167,7 @@ n8n_update_partial_workflow({
|
||||
'Connect language model BEFORE adding AI Agent to ensure validation passes',
|
||||
'Use targetIndex for fallback models (primary=0, fallback=1)',
|
||||
'Batch AI component connections in a single operation for atomicity',
|
||||
'Validate AI workflows after connection changes to catch configuration errors',
|
||||
'To remove properties, set them to undefined (not null) in the updates object',
|
||||
'When migrating from deprecated properties, remove the old property and add the new one in the same operation',
|
||||
'Use undefined to resolve mutual exclusivity validation errors between properties',
|
||||
'Batch multiple property removals in a single updateNode operation for efficiency'
|
||||
'Validate AI workflows after connection changes to catch configuration errors'
|
||||
],
|
||||
pitfalls: [
|
||||
'**REQUIRES N8N_API_URL and N8N_API_KEY environment variables** - will not work without n8n API access',
|
||||
@@ -399,19 +180,8 @@ n8n_update_partial_workflow({
|
||||
'Use "updates" property for updateNode operations: {type: "updateNode", updates: {...}}',
|
||||
'Smart parameters (branch, case) only work with IF and Switch nodes - ignored for other node types',
|
||||
'Explicit sourceIndex overrides smart parameters (branch, case) if both provided',
|
||||
'**CRITICAL**: For If nodes, ALWAYS use branch="true"/"false" instead of sourceIndex. Using sourceIndex=0 for multiple connections will put them ALL on the TRUE branch (main[0]), breaking your workflow logic!',
|
||||
'**CRITICAL**: For Switch nodes, ALWAYS use case=N instead of sourceIndex. Using same sourceIndex for multiple connections will put them on the same case output.',
|
||||
'cleanStaleConnections removes ALL broken connections - cannot be selective',
|
||||
'replaceConnections overwrites entire connections object - all previous connections lost',
|
||||
'**Auto-sanitization behavior**: Binary operators (equals, contains) automatically have singleValue removed; unary operators (isEmpty, isNotEmpty) automatically get singleValue:true added',
|
||||
'**Auto-sanitization runs on ALL nodes**: When ANY update is made, ALL nodes in the workflow are sanitized (not just modified ones)',
|
||||
'**Auto-sanitization cannot fix everything**: It fixes operator structures and missing metadata, but cannot fix broken connections or branch mismatches',
|
||||
'**Corrupted workflows beyond repair**: Workflows in paradoxical states (API returns corrupt, API rejects updates) cannot be fixed via API - must be recreated',
|
||||
'Setting a property to null does NOT remove it - use undefined instead',
|
||||
'When properties are mutually exclusive (e.g., continueOnFail and onError), setting only the new property will fail - you must remove the old one with undefined',
|
||||
'Removing a required property may cause validation errors - check node documentation first',
|
||||
'Nested property removal with dot notation only removes the specific nested field, not the entire parent object',
|
||||
'Array index notation (e.g., "parameters.headers[0]") is not supported - remove the entire array property instead'
|
||||
'replaceConnections overwrites entire connections object - all previous connections lost'
|
||||
],
|
||||
relatedTools: ['n8n_update_full_workflow', 'n8n_get_workflow', 'validate_workflow', 'tools_documentation']
|
||||
}
|
||||
|
||||
@@ -66,6 +66,6 @@ Requires N8N_API_URL and N8N_API_KEY environment variables to be configured.`,
|
||||
'Profile affects validation time - strict is slower but more thorough',
|
||||
'Expression validation may flag working but non-standard syntax'
|
||||
],
|
||||
relatedTools: ['validate_workflow', 'n8n_get_workflow', 'n8n_health_check', 'n8n_autofix_workflow']
|
||||
relatedTools: ['validate_workflow', 'n8n_get_workflow', 'validate_workflow_expressions', 'n8n_health_check', 'n8n_autofix_workflow']
|
||||
}
|
||||
};
|
||||
@@ -1,168 +0,0 @@
|
||||
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'
|
||||
]
|
||||
}
|
||||
};
|
||||
@@ -84,67 +84,55 @@ When working with Code nodes, always start by calling the relevant guide:
|
||||
|
||||
## Standard Workflow Pattern
|
||||
|
||||
⚠️ **CRITICAL**: Always call get_node() with detail='standard' FIRST before configuring any node!
|
||||
|
||||
1. **Find** the node you need:
|
||||
- search_nodes({query: "slack"}) - Search by keyword
|
||||
- search_nodes({query: "communication"}) - Search by category name
|
||||
- search_nodes({query: "AI langchain"}) - Search for AI-capable nodes
|
||||
- list_nodes({category: "communication"}) - List by category
|
||||
- list_ai_tools() - List AI-capable nodes
|
||||
|
||||
2. **Configure** the node (ALWAYS START WITH STANDARD DETAIL):
|
||||
- ✅ get_node({nodeType: "nodes-base.slack", detail: "standard"}) - Get essential properties FIRST (~1-2KB, shows required fields)
|
||||
- get_node({nodeType: "nodes-base.slack", detail: "full"}) - Get complete schema only if standard insufficient (~100KB+)
|
||||
- get_node({nodeType: "nodes-base.slack", mode: "docs"}) - Get readable markdown documentation
|
||||
- get_node({nodeType: "nodes-base.slack", mode: "search_properties", propertyQuery: "auth"}) - Find specific properties
|
||||
2. **Configure** the node:
|
||||
- get_node_essentials("nodes-base.slack") - Get essential properties only (5KB)
|
||||
- get_node_info("nodes-base.slack") - Get complete schema (100KB+)
|
||||
- search_node_properties("nodes-base.slack", "auth") - Find specific properties
|
||||
|
||||
3. **Validate** before deployment:
|
||||
- validate_node({nodeType: "nodes-base.slack", config: {...}, mode: "minimal"}) - Quick required fields check
|
||||
- validate_node({nodeType: "nodes-base.slack", config: {...}}) - Full validation with errors/warnings/suggestions
|
||||
- validate_workflow({workflow: {...}}) - Validate entire workflow
|
||||
- validate_node_minimal("nodes-base.slack", config) - Check required fields
|
||||
- validate_node_operation("nodes-base.slack", config) - Full validation with fixes
|
||||
- validate_workflow(workflow) - Validate entire workflow
|
||||
|
||||
## Tool Categories (19 Tools Total)
|
||||
## Tool Categories
|
||||
|
||||
**Discovery Tools** (1 tool)
|
||||
- search_nodes - Full-text search across all nodes (supports OR, AND, FUZZY modes)
|
||||
**Discovery Tools**
|
||||
- search_nodes - Full-text search across all nodes
|
||||
- list_nodes - List nodes with filtering by category, package, or type
|
||||
- list_ai_tools - List all AI-capable nodes with usage guidance
|
||||
|
||||
**Configuration Tools** (1 consolidated tool)
|
||||
- get_node - Unified node information tool:
|
||||
- detail='minimal'/'standard'/'full': Progressive detail levels
|
||||
- mode='docs': Readable markdown documentation
|
||||
- mode='search_properties': Find specific properties
|
||||
- mode='versions'/'compare'/'breaking'/'migrations': Version management
|
||||
**Configuration Tools**
|
||||
- get_node_essentials - Returns 10-20 key properties with examples
|
||||
- get_node_info - Returns complete node schema with all properties
|
||||
- search_node_properties - Search for specific properties within a node
|
||||
- get_property_dependencies - Analyze property visibility dependencies
|
||||
|
||||
**Validation Tools** (2 tools)
|
||||
- validate_node - Unified validation with mode='full' or mode='minimal'
|
||||
- validate_workflow - Complete workflow validation (nodes, connections, expressions)
|
||||
**Validation Tools**
|
||||
- validate_node_minimal - Quick validation of required fields only
|
||||
- validate_node_operation - Full validation with operation awareness
|
||||
- validate_workflow - Complete workflow validation including connections
|
||||
|
||||
**Template Tools** (2 tools)
|
||||
**Template Tools**
|
||||
- list_tasks - List common task templates
|
||||
- get_node_for_task - Get pre-configured node for specific tasks
|
||||
- search_templates - Search workflow templates by keyword
|
||||
- get_template - Get complete workflow JSON by ID
|
||||
- search_templates - Unified template search:
|
||||
- searchMode='keyword': Text search (default)
|
||||
- searchMode='by_nodes': Find templates using specific nodes
|
||||
- searchMode='by_task': Curated task-based templates
|
||||
- searchMode='by_metadata': Filter by complexity/services
|
||||
|
||||
**n8n API Tools** (13 tools, requires N8N_API_URL configuration)
|
||||
**n8n API 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
|
||||
- n8n_update_partial_workflow - Incremental diff-based updates
|
||||
- n8n_delete_workflow - Delete workflow
|
||||
- n8n_list_workflows - List workflows with filters
|
||||
- n8n_validate_workflow - Validate workflow by ID
|
||||
- n8n_autofix_workflow - Auto-fix common issues
|
||||
- n8n_test_workflow - Test/trigger workflows (webhook, form, chat, execute)
|
||||
- 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
|
||||
- n8n_update_partial_workflow - Update workflows using diff operations
|
||||
- n8n_validate_workflow - Validate workflow from n8n instance
|
||||
- n8n_trigger_webhook_workflow - Trigger workflow execution
|
||||
|
||||
## Performance Characteristics
|
||||
- Instant (<10ms): search_nodes, get_node (minimal/standard)
|
||||
- Fast (<100ms): validate_node, get_template
|
||||
- Moderate (100-500ms): validate_workflow, get_node (full detail)
|
||||
- Instant (<10ms): search_nodes, list_nodes, get_node_essentials
|
||||
- Fast (<100ms): validate_node_minimal, get_node_for_task
|
||||
- Moderate (100-500ms): validate_workflow, get_node_info
|
||||
- Network-dependent: All n8n_* tools
|
||||
|
||||
For comprehensive documentation on any tool:
|
||||
@@ -177,7 +165,7 @@ ${tools.map(toolName => {
|
||||
|
||||
## Usage Notes
|
||||
- All node types require the "nodes-base." or "nodes-langchain." prefix
|
||||
- Use get_node() with detail='standard' first for most tasks (~95% smaller than detail='full')
|
||||
- Use get_node_essentials() first for most tasks (95% smaller than get_node_info)
|
||||
- Validation profiles: minimal (editing), runtime (default), strict (deployment)
|
||||
- n8n API tools only available when N8N_API_URL and N8N_API_KEY are configured
|
||||
|
||||
@@ -423,8 +411,8 @@ try {
|
||||
5. Use descriptive variable names
|
||||
|
||||
## Related Tools
|
||||
- get_node({nodeType: "nodes-base.code"}) - Get Code node configuration details
|
||||
- validate_node({nodeType: "nodes-base.code", config: {...}}) - Validate Code node setup
|
||||
- get_node_essentials("nodes-base.code")
|
||||
- validate_node_operation()
|
||||
- python_code_node_guide (for Python syntax)`;
|
||||
}
|
||||
|
||||
@@ -692,7 +680,7 @@ except json.JSONDecodeError:
|
||||
\`\`\`
|
||||
|
||||
## Related Tools
|
||||
- get_node({nodeType: "nodes-base.code"}) - Get Code node configuration details
|
||||
- validate_node({nodeType: "nodes-base.code", config: {...}}) - Validate Code node setup
|
||||
- get_node_essentials("nodes-base.code")
|
||||
- validate_node_operation()
|
||||
- javascript_code_node_guide (for JavaScript syntax)`;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user