Files
n8n-mcp/src/mcp/tools-documentation.ts
czlonkowski d8e84307e4 feat: optimize MCP tool descriptions for 65-70% token reduction
- Reduced average description length from 250-450 to 93-129 chars
- Documentation tools now average 129 chars per description
- Management tools average just 93 chars per description
- Moved detailed documentation to tools_documentation() system
- Only 2 tools exceed 200 chars (necessarily verbose)

Also includes search_nodes improvements:
- Fixed primary node ranking (webhook, HTTP Request now appear first)
- Fixed FUZZY mode threshold for better typo tolerance
- Removed unnecessary searchInfo messages
- Fixed HTTP node type case sensitivity issue

This significantly improves AI agent performance by reducing context usage
while preserving all essential information.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-10 11:42:23 +02:00

1426 lines
48 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

interface ToolDocumentation {
name: string;
category: string;
essentials: {
description: string;
keyParameters: string[];
example: string;
performance: string;
tips: string[];
};
full: {
description: string;
parameters: Record<string, { type: string; description: string; required?: boolean }>;
returns: string;
examples: string[];
useCases: string[];
performance: string;
bestPractices: string[];
pitfalls: string[];
relatedTools: string[];
};
}
export const toolsDocumentation: Record<string, ToolDocumentation> = {
search_nodes: {
name: 'search_nodes',
category: 'discovery',
essentials: {
description: 'Search nodes. Primary nodes ranked first.',
keyParameters: ['query', 'limit', 'mode'],
example: 'search_nodes({query: "webhook"})',
performance: 'Fast - FTS5 when available',
tips: [
'Primary nodes first: webhook→Webhook, http→HTTP Request',
'Modes: OR (any word), AND (all words), FUZZY (typos OK)'
]
},
full: {
description: 'Search n8n nodes using FTS5 full-text search (when available) with relevance ranking. Supports OR (default), AND, and FUZZY search modes. Results are sorted by relevance, ensuring primary nodes like Webhook and HTTP Request appear first.',
parameters: {
query: { type: 'string', description: 'Search terms. Wrap in quotes for exact phrase matching', required: true },
limit: { type: 'number', description: 'Maximum results to return (default: 20)', required: false },
mode: { type: 'string', description: 'Search mode: OR (any word), AND (all words in ANY field), FUZZY (typo-tolerant using edit distance)', required: false }
},
returns: 'Array of nodes sorted by relevance with nodeType, displayName, description, category. AND mode includes searchInfo explaining the search scope.',
examples: [
'search_nodes({query: "webhook"}) - Webhook node appears first',
'search_nodes({query: "http call"}) - HTTP Request node appears first',
'search_nodes({query: "send message", mode: "AND"}) - Nodes with both words anywhere in their data',
'search_nodes({query: "slak", mode: "FUZZY"}) - Finds Slack using typo tolerance'
],
useCases: [
'Finding primary nodes quickly (webhook, http, email)',
'Discovering nodes with typo tolerance',
'Precise searches with AND mode',
'Exploratory searches with OR mode'
],
performance: 'FTS5: <20ms for most queries. Falls back to optimized LIKE queries if FTS5 unavailable.',
bestPractices: [
'Default OR mode is best for exploration',
'Use AND mode when you need all terms present',
'Use FUZZY mode if unsure of spelling',
'Quotes force exact phrase matching',
'Primary nodes are boosted in relevance'
],
pitfalls: [
'AND mode searches ALL fields (description, documentation, operations) not just names',
'FUZZY mode uses edit distance - may return unexpected matches for very short queries',
'Special characters are ignored in search',
'FTS5 syntax errors fallback to basic LIKE search'
],
relatedTools: ['list_nodes', 'get_node_essentials', 'get_node_info']
}
},
get_node_essentials: {
name: 'get_node_essentials',
category: 'configuration',
essentials: {
description: 'Get 10-20 key properties with examples',
keyParameters: ['nodeType'],
example: 'get_node_essentials("nodes-base.slack")',
performance: '<5KB vs 100KB+',
tips: [
'Use this first! Has examples.'
]
},
full: {
description: 'Returns a curated set of essential properties for a node, typically 10-20 most commonly used properties. Includes working examples and is 95% smaller than get_node_info. Designed for efficient node configuration.',
parameters: {
nodeType: { type: 'string', description: 'Full node type (e.g., "n8n-nodes-base.slack")', required: true }
},
returns: 'Object with node info, essential properties, examples, and common patterns',
examples: [
'get_node_essentials("n8n-nodes-base.httpRequest") - Get HTTP request essentials',
'get_node_essentials("n8n-nodes-base.webhook") - Get webhook configuration',
'get_node_essentials("n8n-nodes-base.slack") - Get Slack essentials'
],
useCases: [
'Quickly configuring nodes without information overload',
'Getting working examples for immediate use',
'Understanding the most important node options',
'Building workflows efficiently'
],
performance: 'Extremely fast - returns pre-filtered data. Response size <5KB vs 100KB+ for full node info.',
bestPractices: [
'Always try this before get_node_info',
'Use included examples as starting points',
'Check commonPatterns for typical configurations',
'Combine with validate_node_minimal for quick validation'
],
pitfalls: [
'May not include rarely-used properties',
'Some advanced options might be missing',
'Use search_node_properties if specific property not found'
],
relatedTools: ['get_node_info', 'search_node_properties', 'validate_node_minimal']
}
},
list_nodes: {
name: 'list_nodes',
category: 'discovery',
essentials: {
description: 'List all available n8n nodes with optional filtering',
keyParameters: ['category', 'limit', 'onlyTriggers'],
example: 'list_nodes({category: "communication", limit: 20})',
performance: 'Fast - direct database query',
tips: [
'Great for browsing nodes by category',
'Use onlyTriggers:true to find workflow starters'
]
},
full: {
description: 'Lists all available n8n nodes with comprehensive filtering options. Can filter by category, package, trigger status, and more. Returns complete node metadata.',
parameters: {
category: { type: 'string', description: 'Filter by category (e.g., "communication", "data")', required: false },
limit: { type: 'number', description: 'Maximum results (default: 50)', required: false },
offset: { type: 'number', description: 'Pagination offset', required: false },
onlyTriggers: { type: 'boolean', description: 'Only show trigger nodes', required: false },
onlyAITools: { type: 'boolean', description: 'Only show AI-capable nodes', required: false },
package: { type: 'string', description: 'Filter by package name', required: false }
},
returns: 'Array of nodes with complete metadata including type, name, description, category',
examples: [
'list_nodes() - Get first 50 nodes',
'list_nodes({category: "trigger"}) - All trigger nodes',
'list_nodes({onlyAITools: true}) - Nodes marked as AI tools',
'list_nodes({package: "n8n-nodes-base", limit: 100}) - Core nodes'
],
useCases: [
'Browsing available nodes by category',
'Finding all triggers or webhooks',
'Discovering AI-capable nodes',
'Getting overview of available integrations'
],
performance: 'Fast - uses indexed queries. Returns in <100ms even for large result sets.',
bestPractices: [
'Use categories for focused browsing',
'Combine with search_nodes for keyword search',
'Use pagination for large result sets',
'Check onlyTriggers for workflow starting points'
],
pitfalls: [
'No text search - use search_nodes for that',
'Categories are predefined, not all nodes have them',
'Large result sets without limit can be overwhelming'
],
relatedTools: ['search_nodes', 'list_ai_tools', 'get_node_essentials']
}
},
validate_node_minimal: {
name: 'validate_node_minimal',
category: 'validation',
essentials: {
description: 'Quick validation checking only required fields',
keyParameters: ['nodeType', 'config'],
example: 'validate_node_minimal("n8n-nodes-base.slack", {resource: "message", operation: "post"})',
performance: 'Very fast - minimal checks only',
tips: [
'Use for quick validation during configuration',
'Follow up with validate_node_operation for full validation'
]
},
full: {
description: 'Performs minimal validation checking only required fields. Fastest validation option, perfect for iterative configuration. Checks if all required fields are present without complex dependency validation.',
parameters: {
nodeType: { type: 'string', description: 'Full node type', required: true },
config: { type: 'object', description: 'Node configuration to validate', required: true }
},
returns: 'Object with isValid boolean, missing required fields, and basic feedback',
examples: [
'validate_node_minimal("n8n-nodes-base.httpRequest", {url: "https://api.example.com"})',
'validate_node_minimal("n8n-nodes-base.slack", {resource: "message", operation: "post", channel: "general"})'
],
useCases: [
'Quick validation during iterative configuration',
'Checking if minimum requirements are met',
'Fast feedback loop while building',
'Pre-validation before full check'
],
performance: 'Extremely fast - only checks required fields. Typically <10ms.',
bestPractices: [
'Use during configuration for quick feedback',
'Follow with validate_node_operation for complete validation',
'Great for iterative development',
'Combine with get_node_essentials for requirements'
],
pitfalls: [
'Doesn\'t check field dependencies',
'Won\'t catch configuration conflicts',
'Missing optional but recommended fields'
],
relatedTools: ['validate_node_operation', 'get_node_essentials', 'validate_workflow']
}
},
validate_node_operation: {
name: 'validate_node_operation',
category: 'validation',
essentials: {
description: 'Full validation with operation-aware checking and helpful suggestions',
keyParameters: ['nodeType', 'config', 'profile'],
example: 'validate_node_operation("n8n-nodes-base.slack", {resource: "message", operation: "post", channel: "general"})',
performance: 'Moderate - comprehensive validation',
tips: [
'Provides specific error messages and fixes',
'Use "strict" profile for production workflows'
]
},
full: {
description: 'Comprehensive validation that understands operation-specific requirements. Checks dependencies, validates field values, and provides helpful suggestions for fixing issues.',
parameters: {
nodeType: { type: 'string', description: 'Full node type', required: true },
config: { type: 'object', description: 'Complete node configuration', required: true },
profile: { type: 'string', description: 'Validation profile: "development" or "strict"', required: false }
},
returns: 'Detailed validation results with errors, warnings, suggestions, and fixes',
examples: [
'validate_node_operation("n8n-nodes-base.httpRequest", {method: "POST", url: "{{$json.url}}", bodyParametersUi: {...}})',
'validate_node_operation("n8n-nodes-base.postgres", {operation: "executeQuery", query: "SELECT * FROM users"}, "strict")'
],
useCases: [
'Final validation before deployment',
'Understanding complex field dependencies',
'Getting suggestions for configuration improvements',
'Validating operation-specific requirements'
],
performance: 'Moderate speed - performs comprehensive checks. 50-200ms depending on complexity.',
bestPractices: [
'Use after validate_node_minimal passes',
'Apply suggested fixes from response',
'Use strict profile for production',
'Check warnings even if validation passes'
],
pitfalls: [
'Slower than minimal validation',
'May be overkill for simple configurations',
'Strict profile might be too restrictive for development'
],
relatedTools: ['validate_node_minimal', 'validate_workflow', 'get_property_dependencies']
}
},
get_node_for_task: {
name: 'get_node_for_task',
category: 'templates',
essentials: {
description: 'Get pre-configured node settings for common tasks',
keyParameters: ['task'],
example: 'get_node_for_task("send_slack_message")',
performance: 'Instant - returns pre-built configurations',
tips: [
'Use list_tasks() to see all available tasks',
'Look for userMustProvide fields to complete'
]
},
full: {
description: 'Returns pre-configured node settings for common automation tasks. Each template includes the correct node type, operation settings, and clear markers for what needs user input.',
parameters: {
task: { type: 'string', description: 'Task identifier (use list_tasks to see all)', required: true }
},
returns: 'Complete node configuration with parameters, position, and user guidance',
examples: [
'get_node_for_task("send_slack_message") - Slack message template',
'get_node_for_task("receive_webhook") - Webhook trigger setup',
'get_node_for_task("query_database") - Database query template'
],
useCases: [
'Quickly setting up common automation patterns',
'Learning correct node configurations',
'Avoiding configuration mistakes',
'Rapid workflow prototyping'
],
performance: 'Instant - returns static templates. No computation required.',
bestPractices: [
'Check userMustProvide fields for required inputs',
'Use list_tasks() to discover available templates',
'Validate with validate_node_minimal after filling in',
'Use as starting point, then customize'
],
pitfalls: [
'Templates are generic - customize for specific needs',
'Not all tasks have templates',
'Some fields marked userMustProvide are critical'
],
relatedTools: ['list_tasks', 'get_node_essentials', 'validate_node_minimal']
}
},
n8n_create_workflow: {
name: 'n8n_create_workflow',
category: 'workflow_management',
essentials: {
description: 'Create a new workflow in n8n via API',
keyParameters: ['name', 'nodes', 'connections'],
example: 'n8n_create_workflow({name: "My Workflow", nodes: [...], connections: {...}})',
performance: 'API call - depends on n8n instance',
tips: [
'ALWAYS use node names in connections, never IDs',
'Error handling properties go at NODE level, not inside parameters!',
'Requires N8N_API_URL and N8N_API_KEY configuration'
]
},
full: {
description: 'Creates a new workflow in your n8n instance via API. Requires proper API configuration. Returns the created workflow with assigned ID.\n\n⚠ CRITICAL: Error handling properties (onError, retryOnFail, etc.) are NODE-LEVEL properties, not inside parameters!',
parameters: {
name: { type: 'string', description: 'Workflow name', required: true },
nodes: { type: 'array', description: 'Array of node configurations', required: true },
connections: { type: 'object', description: 'Node connections (use names!)', required: true },
settings: { type: 'object', description: 'Workflow settings', required: false },
tags: { type: 'array', description: 'Tag IDs (not names)', required: false }
},
returns: 'Created workflow object with id, name, nodes, connections, and metadata',
examples: [
`// Basic workflow with proper error handling
n8n_create_workflow({
name: "Slack Notification with Error Handling",
nodes: [
{
id: "1",
name: "Webhook",
type: "n8n-nodes-base.webhook",
typeVersion: 2,
position: [250, 300],
parameters: {
path: "/webhook",
method: "POST"
},
// ✅ CORRECT - Error handling at node level
onError: "continueRegularOutput"
},
{
id: "2",
name: "Database Query",
type: "n8n-nodes-base.postgres",
typeVersion: 2.4,
position: [450, 300],
parameters: {
operation: "executeQuery",
query: "SELECT * FROM users"
},
// ✅ CORRECT - Error handling at node level
onError: "continueErrorOutput",
retryOnFail: true,
maxTries: 3,
waitBetweenTries: 2000
},
{
id: "3",
name: "Error Handler",
type: "n8n-nodes-base.slack",
typeVersion: 2.2,
position: [650, 450],
parameters: {
resource: "message",
operation: "post",
channel: "#errors",
text: "Database query failed!"
}
}
],
connections: {
"Webhook": {
main: [[{node: "Database Query", type: "main", index: 0}]]
},
"Database Query": {
main: [[{node: "Success Handler", type: "main", index: 0}]],
error: [[{node: "Error Handler", type: "main", index: 0}]] // Error output
}
}
})`
],
useCases: [
'Deploying workflows programmatically',
'Automating workflow creation',
'Migrating workflows between instances',
'Creating workflows from templates',
'Building error-resilient workflows'
],
performance: 'Depends on n8n instance and network. Typically 100-500ms.',
bestPractices: [
'CRITICAL: Use node NAMES in connections, not IDs',
'CRITICAL: Place error handling at NODE level, not in parameters',
'Validate workflow before creating',
'Use meaningful workflow names',
'Add error handling to external service nodes',
'Check n8n_health_check before creating'
],
pitfalls: [
'Placing error handling properties inside parameters object',
'Using node IDs in connections breaks UI display',
'Workflow not automatically activated',
'Tags must exist (use tag IDs not names)',
'API must be configured correctly'
],
relatedTools: ['validate_workflow', 'n8n_update_partial_workflow', 'n8n_list_workflows']
}
},
n8n_update_partial_workflow: {
name: 'n8n_update_partial_workflow',
category: 'workflow_management',
essentials: {
description: 'Update workflows using diff operations - only send changes, not entire workflow',
keyParameters: ['id', 'operations'],
example: 'n8n_update_partial_workflow({id: "123", operations: [{type: "updateNode", nodeName: "Slack", changes: {onError: "continueRegularOutput"}}]})',
performance: '80-90% more efficient than full updates',
tips: [
'Maximum 5 operations per request',
'Can reference nodes by name or ID',
'Error handling properties go at NODE level, not inside parameters!'
]
},
full: {
description: 'Update existing workflows using diff operations. Much more efficient than full updates as it only sends the changes. Supports 13 different operation types.\n\n⚠ CRITICAL: Error handling properties (onError, retryOnFail, maxTries, etc.) are NODE-LEVEL properties, not parameters!',
parameters: {
id: { type: 'string', description: 'Workflow ID to update', required: true },
operations: { type: 'array', description: 'Array of diff operations (max 5)', required: true },
validateOnly: { type: 'boolean', description: 'Only validate without applying', required: false }
},
returns: 'Updated workflow with applied changes and operation results',
examples: [
`// Update node parameters (properties inside parameters object)
n8n_update_partial_workflow({
id: "123",
operations: [{
type: "updateNode",
nodeName: "Slack",
changes: {
"parameters.channel": "#general", // Nested property
"parameters.text": "Hello world" // Nested property
}
}]
})`,
`// Update error handling (NODE-LEVEL properties, NOT inside parameters!)
n8n_update_partial_workflow({
id: "123",
operations: [{
type: "updateNode",
nodeName: "HTTP Request",
changes: {
onError: "continueErrorOutput", // ✅ Correct - node level
retryOnFail: true, // ✅ Correct - node level
maxTries: 3, // ✅ Correct - node level
waitBetweenTries: 2000 // ✅ Correct - node level
}
}]
})`,
`// WRONG - Don't put error handling inside parameters!
// ❌ BAD: changes: {"parameters.onError": "continueErrorOutput"}
// ✅ GOOD: changes: {onError: "continueErrorOutput"}`,
`// Add error connection between nodes
n8n_update_partial_workflow({
id: "123",
operations: [{
type: "addConnection",
source: "Database Query",
target: "Error Handler",
sourceOutput: "error", // Error output
targetInput: "main"
}]
})`
],
useCases: [
'Updating node configurations',
'Adding error handling to nodes',
'Adding/removing connections',
'Enabling/disabling nodes',
'Moving nodes in canvas',
'Updating workflow metadata'
],
performance: 'Very efficient - only sends changes. 80-90% less data than full updates.',
bestPractices: [
'Error handling properties (onError, retryOnFail, etc.) go at NODE level, not in parameters',
'Use dot notation for nested properties: "parameters.url"',
'Batch related operations together',
'Use validateOnly:true to test first',
'Reference nodes by name for clarity'
],
pitfalls: [
'Placing error handling properties inside parameters (common mistake!)',
'Maximum 5 operations per request',
'Some operations have dependencies',
'Node must exist for update operations',
'Connection nodes must both exist'
],
relatedTools: ['n8n_get_workflow', 'n8n_update_full_workflow', 'validate_workflow']
}
},
// Code Node specific documentation
code_node_guide: {
name: 'code_node_guide',
category: 'code_node',
essentials: {
description: 'Comprehensive guide for writing Code node JavaScript and Python',
keyParameters: ['topic'],
example: 'tools_documentation({topic: "code_node_guide"})',
performance: 'Instant - returns documentation',
tips: [
'Essential reading before writing Code node scripts',
'Covers all built-in variables and helpers',
'Includes common patterns and error handling'
]
},
full: {
description: `Complete reference for the n8n Code node, covering JavaScript and Python execution environments, built-in variables, helper functions, and best practices.
## Code Node Basics
The Code node allows custom JavaScript or Python code execution within workflows. It runs in a sandboxed environment with access to n8n-specific variables and helpers.
### JavaScript Environment
- **ES2022 support** with async/await
- **Built-in libraries**:
- **luxon** (DateTime) - Date/time manipulation
- **jmespath** - JSON queries via $jmespath()
- **crypto** - Available via require('crypto') despite editor warnings!
- **Node.js globals**: Buffer, process.env (limited)
- **require() IS available** for built-in modules only (crypto, util, etc.)
- **No npm packages** - only Node.js built-ins and n8n-provided libraries
### Python Environment
- **Python 3.10+** with standard library (Pyodide runtime)
- **No pip install** - standard library only
- **Variables use underscore prefix**: \`_input\`, \`_json\`, \`_jmespath\` (not \`$\`)
- **item.json is JsProxy**: Use \`.to_py()\` to convert to Python dict
- **Shared state** between Code nodes in same execution
## Essential Variables
### $input
Access to all incoming data:
\`\`\`javascript
// Get all items from all inputs
const allItems = $input.all(); // Returns: Item[][]
// Get items from specific input (0-indexed)
const firstInput = $input.all(0); // Returns: Item[]
// Get first item from first input
const firstItem = $input.first(); // Returns: Item
// Get last item from first input
const lastItem = $input.last(); // Returns: Item
// Get specific item by index
const item = $input.item(2); // Returns: Item at index 2
\`\`\`
### items
Direct access to incoming items (legacy, prefer $input):
\`\`\`javascript
// items is equivalent to $input.all()[0]
for (const item of items) {
console.log(item.json); // Access JSON data
console.log(item.binary); // Access binary data
}
\`\`\`
### $json
Shortcut to current item's JSON data (only in "Run Once for Each Item" mode):
\`\`\`javascript
// These are equivalent in single-item mode:
const value1 = $json.fieldName;
const value2 = items[0].json.fieldName;
\`\`\`
### Accessing Other Nodes
Access data from other nodes using $('Node Name') syntax:
\`\`\`javascript
// Access another node's output - use $('Node Name') NOT $node
const prevData = $('Previous Node').all();
const firstItem = $('Previous Node').first();
const specificItem = $('Previous Node').item(0);
// Get node parameter
const webhookUrl = $('Webhook').params.path;
// Python uses underscore prefix
const pythonData = _('Previous Node').all();
\`\`\`
⚠️ **Expression vs Code Node Syntax**:
- **Expressions**: \`{{$node['Previous Node'].json.field}}\`
- **Code Node**: \`$('Previous Node').first().json.field\`
- These are NOT interchangeable!
### $workflow
Workflow metadata:
\`\`\`javascript
const workflowId = $workflow.id;
const workflowName = $workflow.name;
const isActive = $workflow.active;
\`\`\`
### $execution
Execution context:
\`\`\`javascript
const executionId = $execution.id;
const executionMode = $execution.mode; // 'manual', 'trigger', etc.
const resumeUrl = $execution.resumeUrl; // For wait nodes
\`\`\`
### $prevNode
Access to the immediate previous node:
\`\`\`javascript
const prevOutput = $prevNode.outputIndex; // Which output triggered this
const prevData = $prevNode.data; // Previous node's data
const prevName = $prevNode.name; // Previous node's name
\`\`\`
## Helper Functions
### Date/Time (Luxon)
\`\`\`javascript
// Current time
const now = DateTime.now();
const iso = now.toISO();
// Parse dates
const date = DateTime.fromISO('2024-01-01');
const formatted = date.toFormat('yyyy-MM-dd');
// Time math
const tomorrow = now.plus({ days: 1 });
const hourAgo = now.minus({ hours: 1 });
\`\`\`
### JSON Queries (JMESPath)
\`\`\`javascript
// n8n uses $jmespath() - NOTE: parameter order is reversed from standard JMESPath!
const data = { users: [{ name: 'John', age: 30 }, { name: 'Jane', age: 25 }] };
const names = $jmespath(data, 'users[*].name'); // ['John', 'Jane']
// ⚠️ IMPORTANT: Numeric literals in filters need BACKTICKS in n8n!
const adults = $jmespath(data, 'users[?age >= \`18\`]'); // ✅ CORRECT - backticks around 18
const seniors = $jmespath(data, 'users[?age >= \`65\`]'); // ✅ CORRECT
// ❌ WRONG - This will cause a syntax error!
// const adults = $jmespath(data, 'users[?age >= 18]'); // Missing backticks
// More filter examples with proper backticks:
const expensive = $jmespath(items, '[?price > \`100\`]');
const inStock = $jmespath(products, '[?quantity >= \`1\`]');
const highPriority = $jmespath(tasks, '[?priority == \`1\`]');
// String comparisons don't need backticks
const activeUsers = $jmespath(data, 'users[?status == "active"]');
// Python uses underscore prefix
const pythonAdults = _jmespath(data, 'users[?age >= \`18\`]');
\`\`\`
⚠️ **CRITICAL DIFFERENCES** from standard JMESPath:
1. **Parameter order is REVERSED**:
- **Expression**: \`{{$jmespath("query", data)}}\`
- **Code Node**: \`$jmespath(data, "query")\`
2. **Numeric literals in filters MUST use backticks**: \`[?age >= \`18\`]\`
- This is n8n-specific and differs from standard JMESPath documentation!
### Available Functions and Libraries
#### Built-in Node.js Modules (via require)
\`\`\`javascript
// ✅ These modules ARE available via require():
const crypto = require('crypto'); // Cryptographic functions
const util = require('util'); // Utility functions
const querystring = require('querystring'); // URL query string utilities
// Example: Generate secure random token
const crypto = require('crypto');
const token = crypto.randomBytes(32).toString('hex');
const uuid = crypto.randomUUID();
\`\`\`
**Note**: The editor may show errors for require() but it WORKS at runtime!
#### Standalone Functions (Global Scope)
\`\`\`javascript
// ✅ Workflow static data - persists between executions
// IMPORTANT: These are standalone functions, NOT methods on $helpers!
const staticData = $getWorkflowStaticData('global'); // Global static data
const nodeData = $getWorkflowStaticData('node'); // Node-specific data
// Example: Counter that persists
const staticData = $getWorkflowStaticData('global');
staticData.counter = (staticData.counter || 0) + 1;
// ❌ WRONG - This will cause "$helpers is not defined" error:
// const data = $helpers.getWorkflowStaticData('global');
// JMESPath queries - note the parameter order!
const result = $jmespath(data, 'users[*].name');
\`\`\`
#### $helpers Object (When Available)
\`\`\`javascript
// Some n8n versions provide $helpers with these methods:
// (Always test availability in your n8n instance)
// HTTP requests
const response = await $helpers.httpRequest({
method: 'GET',
url: 'https://api.example.com/data',
headers: { 'Authorization': 'Bearer token' }
});
// Binary data preparation
const binaryData = await $helpers.prepareBinaryData(
Buffer.from('content'),
'file.txt',
'text/plain'
);
// Check if $helpers exists before using:
if (typeof $helpers !== 'undefined' && $helpers.httpRequest) {
// Use $helpers.httpRequest
} else {
throw new Error('HTTP requests not available in this n8n version');
}
\`\`\`
#### Important Notes:
- **$getWorkflowStaticData()** is ALWAYS a standalone function
- **require()** works for built-in Node.js modules despite editor warnings
- **$helpers** availability varies by n8n version - always check first
- Python uses underscore prefix: \`_getWorkflowStaticData()\`, \`_jmespath()\`
- Editor red underlines are often false positives - test at runtime!
## Return Format
Code nodes MUST return an array of objects with 'json' property:
\`\`\`javascript
// ✅ CORRECT - Array of objects with json property
return [
{ json: { id: 1, name: 'Item 1' } },
{ json: { id: 2, name: 'Item 2' } }
];
// ✅ CORRECT - Single item (still wrapped in array)
return [{ json: { result: 'success' } }];
// ✅ CORRECT - With binary data
return [{
json: { filename: 'report.pdf' },
binary: {
data: {
data: base64String,
mimeType: 'application/pdf',
fileName: 'report.pdf'
}
}
}];
// ❌ WRONG - Not an array
return { json: { result: 'success' } };
// ❌ WRONG - No json property
return [{ result: 'success' }];
// ❌ WRONG - Not wrapped in object
return ['item1', 'item2'];
\`\`\`
## Common Patterns
### Data Transformation
\`\`\`javascript
// Transform all items
const transformedItems = [];
for (const item of items) {
transformedItems.push({
json: {
...item.json,
processed: true,
timestamp: DateTime.now().toISO(),
uppercaseName: item.json.name?.toUpperCase()
}
});
}
return transformedItems;
\`\`\`
### Filtering Items
\`\`\`javascript
// Filter items based on condition
return items
.filter(item => item.json.status === 'active')
.map(item => ({ json: item.json }));
\`\`\`
### Aggregation
\`\`\`javascript
// Aggregate data from all items
const total = items.reduce((sum, item) => sum + (item.json.amount || 0), 0);
const average = total / items.length;
return [{
json: {
total,
average,
count: items.length,
items: items.map(i => i.json)
}
}];
\`\`\`
### Error Handling
\`\`\`javascript
// Safe data access with defaults
const results = [];
for (const item of items) {
try {
const value = item.json?.nested?.field || 'default';
results.push({
json: {
processed: value,
status: 'success'
}
});
} catch (error) {
results.push({
json: {
error: error.message,
status: 'failed',
originalItem: item.json
}
});
}
}
return results;
\`\`\`
### Working with APIs
\`\`\`javascript
// Make HTTP request and process response
try {
const response = await $helpers.httpRequest({
method: 'POST',
url: 'https://api.example.com/process',
body: {
data: items.map(item => item.json)
},
headers: {
'Content-Type': 'application/json'
}
});
return [{ json: response }];
} catch (error) {
throw new Error(\`API request failed: \${error.message}\`);
}
\`\`\`
### Async Operations
\`\`\`javascript
// Process items with async operations
const results = [];
for (const item of items) {
// Simulate async operation
await new Promise(resolve => setTimeout(resolve, 100));
results.push({
json: {
...item.json,
processedAt: new Date().toISOString()
}
});
}
return results;
\`\`\`
### Webhook Data Access (CRITICAL!)
\`\`\`javascript
// ⚠️ WEBHOOK DATA IS NESTED UNDER 'body' PROPERTY!
// This is a common source of errors in webhook-triggered workflows
// ❌ WRONG - This will be undefined for webhook data:
const command = items[0].json.testCommand;
// ✅ CORRECT - Webhook data is wrapped in 'body':
const command = items[0].json.body.testCommand;
// Complete webhook data processing example:
const webhookData = items[0].json.body; // Get the actual webhook payload
const headers = items[0].json.headers; // HTTP headers are separate
const query = items[0].json.query; // Query parameters are separate
// Process webhook payload
return [{
json: {
command: webhookData.testCommand,
user: webhookData.user,
timestamp: DateTime.now().toISO(),
requestId: headers['x-request-id'],
source: query.source || 'unknown'
}
}];
// For other trigger nodes (non-webhook), data is directly under json:
// - Schedule Trigger: items[0].json contains timestamp
// - Database Trigger: items[0].json contains row data
// - File Trigger: items[0].json contains file info
\`\`\`
## Python Code Examples
### Basic Python Structure
\`\`\`python
import json
from datetime import datetime
# Access items - Python uses underscore prefix for built-in variables
results = []
for item in _input.all():
# IMPORTANT: item.json is NOT a standard Python dict!
# Use to_py() to convert to a proper Python dict
processed_item = item.json.to_py() # Converts JsProxy to Python dict
processed_item['timestamp'] = datetime.now().isoformat()
results.append({'json': processed_item})
return results
\`\`\`
### Python Data Processing
\`\`\`python
# Aggregate data - use _input.all() to get items
items = _input.all()
total = sum(item.json.get('amount', 0) for item in items)
average = total / len(items) if items else 0
# For safe dict operations, convert JsProxy to Python dict
safe_items = []
for item in items:
# Convert JsProxy to dict to avoid KeyError with null values
safe_dict = item.json.to_py()
safe_items.append(safe_dict)
# Return aggregated result
return [{
'json': {
'total': total,
'average': average,
'count': len(items),
'processed_at': datetime.now().isoformat(),
'items': safe_items # Now these are proper Python dicts
}
}]
\`\`\`
## Code Node as AI Tool
Code nodes can be used as custom tools for AI agents:
\`\`\`javascript
// Code node configured as AI tool
// Name: "Calculate Discount"
// Description: "Calculates discount based on quantity"
const quantity = $json.quantity || 1;
const basePrice = $json.price || 0;
let discount = 0;
if (quantity >= 100) discount = 0.20;
else if (quantity >= 50) discount = 0.15;
else if (quantity >= 20) discount = 0.10;
else if (quantity >= 10) discount = 0.05;
const discountAmount = basePrice * quantity * discount;
const finalPrice = (basePrice * quantity) - discountAmount;
return [{
json: {
quantity,
basePrice,
discountPercentage: discount * 100,
discountAmount,
finalPrice,
savings: discountAmount
}
}];
\`\`\`
## Security Considerations
### Available Security Features
\`\`\`javascript
// ✅ Crypto IS available despite editor warnings!
const crypto = require('crypto');
// Generate secure random values
const randomBytes = crypto.randomBytes(32);
const randomUUID = crypto.randomUUID();
// Create hashes
const hash = crypto.createHash('sha256')
.update('data to hash')
.digest('hex');
// HMAC for signatures
const hmac = crypto.createHmac('sha256', 'secret-key')
.update('data to sign')
.digest('hex');
\`\`\`
### Banned Operations
- No file system access (fs module) - except read-only for some paths
- No network requests except via $helpers.httpRequest
- No child process execution
- No external npm packages (only built-in Node.js modules)
- No eval() or Function() constructor
### Safe Practices
\`\`\`javascript
// ✅ SAFE - Use crypto for secure operations
const crypto = require('crypto');
const token = crypto.randomBytes(32).toString('hex');
// ✅ SAFE - Use built-in JSON parsing
const parsed = JSON.parse(jsonString);
// ❌ UNSAFE - Never use eval
const parsed = eval('(' + jsonString + ')');
// ✅ SAFE - Validate input
if (typeof item.json.userId !== 'string') {
throw new Error('userId must be a string');
}
// ✅ SAFE - Sanitize for logs
const safeLog = String(userInput).substring(0, 100);
// ✅ SAFE - Time-safe comparison for secrets
const expectedToken = 'abc123';
const providedToken = item.json.token;
const tokensMatch = crypto.timingSafeEqual(
Buffer.from(expectedToken),
Buffer.from(providedToken || '')
);
\`\`\`
## Debugging Tips
### Console Output
\`\`\`javascript
// Console.log appears in n8n execution logs
console.log('Processing item:', item.json.id);
console.error('Error details:', error);
// Return debug info in development
return [{
json: {
result: processedData,
debug: {
itemCount: items.length,
executionId: $execution.id,
timestamp: new Date().toISOString()
}
}
}];
\`\`\`
### Error Messages
\`\`\`javascript
// Provide helpful error context
if (!item.json.requiredField) {
throw new Error(\`Missing required field 'requiredField' in item \${items.indexOf(item)}\`);
}
// Include original data in errors
try {
// processing...
} catch (error) {
throw new Error(\`Failed to process item \${item.json.id}: \${error.message}\`);
}
\`\`\`
## Performance Best Practices
1. **Avoid nested loops** when possible
2. **Use array methods** (map, filter, reduce) for clarity
3. **Limit HTTP requests** - batch when possible
4. **Return early** for error conditions
5. **Keep state minimal** - Code nodes are stateless between executions
## Common Mistakes to Avoid
1. **Forgetting to return an array**
2. **Not wrapping in json property**
3. **Modifying items array directly**
4. **Using undefined variables**
5. **Infinite loops with while statements**
6. **Not handling missing data gracefully**
7. **Forgetting await for async operations**`,
parameters: {
topic: { type: 'string', description: 'Specific Code node topic (optional)', required: false }
},
returns: 'Comprehensive Code node documentation and examples',
examples: [
'tools_documentation({topic: "code_node_guide"}) - Full guide',
'tools_documentation({topic: "code_node_guide", depth: "full"}) - Complete reference'
],
useCases: [
'Learning Code node capabilities',
'Understanding built-in variables',
'Finding the right helper function',
'Debugging Code node issues',
'Building custom AI tools'
],
performance: 'Instant - returns static documentation',
bestPractices: [
'Read before writing Code nodes',
'Reference for variable names',
'Copy examples as starting points',
'Check security considerations'
],
pitfalls: [
'Not all Node.js features available',
'Python has limited libraries',
'State not preserved between executions'
],
relatedTools: ['get_node_essentials', 'validate_node_operation', 'get_node_for_task']
}
}
};
export function getToolDocumentation(toolName: string, depth: 'essentials' | 'full' = 'essentials'): string {
const tool = toolsDocumentation[toolName];
if (!tool) {
return `Tool '${toolName}' not found. Use tools_documentation() to see available tools.`;
}
if (depth === 'essentials') {
const { essentials } = tool;
return `# ${tool.name}
${essentials.description}
**Example**: ${essentials.example}
**Key parameters**: ${essentials.keyParameters.join(', ')}
**Performance**: ${essentials.performance}
**Tips**:
${essentials.tips.map(tip => `- ${tip}`).join('\n')}
For full documentation, use: tools_documentation({topic: "${toolName}", depth: "full"})`;
}
// Full documentation
const { full } = tool;
return `# ${tool.name}
${full.description}
## Parameters
${Object.entries(full.parameters).map(([param, info]) =>
`- **${param}** (${info.type}${info.required ? ', required' : ''}): ${info.description}`
).join('\n')}
## Returns
${full.returns}
## Examples
${full.examples.map(ex => `\`\`\`javascript\n${ex}\n\`\`\``).join('\n\n')}
## Common Use Cases
${full.useCases.map(uc => `- ${uc}`).join('\n')}
## Performance
${full.performance}
## Best Practices
${full.bestPractices.map(bp => `- ${bp}`).join('\n')}
## Common Pitfalls
${full.pitfalls.map(p => `- ${p}`).join('\n')}
## Related Tools
${full.relatedTools.map(t => `- ${t}`).join('\n')}`;
}
export function getToolsOverview(depth: 'essentials' | 'full' = 'essentials'): string {
if (depth === 'essentials') {
return `# n8n MCP Tools Quick Reference
Welcome! Here's how to efficiently work with n8n nodes:
## Essential Workflow
1. **Find**: search_nodes({query: "slack"})
2. **Configure**: get_node_essentials("n8n-nodes-base.slack")
3. **Validate**: validate_node_minimal() → validate_node_operation()
4. **Deploy**: n8n_create_workflow() (if API configured)
## Key Tips
- Always use get_node_essentials instead of get_node_info (95% smaller!)
- Use node NAMES in connections, never IDs
- Try get_node_for_task() for common patterns
- Call validate_node_minimal() for quick checks
## Get Help
- tools_documentation({topic: "search_nodes"}) - Get help for specific tool
- tools_documentation({topic: "code_node_guide"}) - Essential Code node reference
- tools_documentation({topic: "overview", depth: "full"}) - See complete guide
- list_tasks() - See available task templates
Available tools: ${Object.keys(toolsDocumentation).join(', ')}`;
}
// Full overview
return `# n8n MCP Tools Complete Guide
## Overview
The n8n MCP provides 39 tools to help you discover, configure, validate, and deploy n8n workflows. Tools are organized into categories for easy discovery.
## Tool Categories
### Discovery Tools
- **search_nodes**: Find nodes by keyword (uses OR logic)
- **list_nodes**: Browse nodes by category, package, or type
- **list_ai_tools**: See all AI-capable nodes (263 available)
### Configuration Tools
- **get_node_essentials**: Get key properties only (<5KB vs 100KB+)
- **get_node_info**: Get complete node details (use sparingly)
- **search_node_properties**: Find specific properties in large nodes
- **get_property_dependencies**: Understand field relationships
### Validation Tools
- **validate_node_minimal**: Quick required field check
- **validate_node_operation**: Full operation-aware validation
- **validate_workflow**: Complete workflow validation
- **validate_workflow_connections**: Check node connections
- **validate_workflow_expressions**: Validate n8n expressions
### Task & Template Tools
- **list_tasks**: See available task templates
- **get_node_for_task**: Get pre-configured nodes
- **list_node_templates**: Find workflow templates
- **search_templates**: Search template library
### Workflow Management (requires API config)
- **n8n_create_workflow**: Create new workflows
- **n8n_update_partial_workflow**: Efficient diff-based updates
- **n8n_update_full_workflow**: Replace entire workflow
- **n8n_list_workflows**: List workflows with filtering
## Recommended Patterns
### Building a Simple Workflow
\`\`\`javascript
// 1. Find what you need
search_nodes({query: "webhook"})
search_nodes({query: "slack"})
// 2. Get configurations
get_node_essentials("n8n-nodes-base.webhook")
get_node_essentials("n8n-nodes-base.slack")
// 3. Build and validate
const workflow = {
name: "My Webhook to Slack",
nodes: [...],
connections: {"Webhook": {main: [[{node: "Slack", type: "main", index: 0}]]}}
};
validate_workflow(workflow)
// 4. Deploy (if API configured)
n8n_create_workflow(workflow)
\`\`\`
### Working with Code Nodes
The Code node is essential for custom logic. Always reference the guide:
\`\`\`javascript
// Get comprehensive Code node documentation
tools_documentation({topic: "code_node_guide"})
// Common Code node pattern
get_node_essentials("n8n-nodes-base.code")
// Returns minimal config with JavaScript/Python examples
// Validate Code node configuration
validate_node_operation("n8n-nodes-base.code", {
language: "javaScript",
jsCode: "return items.map(item => ({json: {...item.json, processed: true}}))"
})
\`\`\`
### Node-Level Properties Reference
⚠️ **CRITICAL**: These properties go at the NODE level, not inside parameters!
\`\`\`javascript
{
// Required properties
"id": "unique_id",
"name": "Node Name",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [450, 300],
"parameters": { /* operation-specific params */ },
// Optional properties (all at node level!)
"credentials": {
"postgres": {
"id": "cred-id",
"name": "My Postgres"
}
},
"disabled": false, // Disable node execution
"notes": "Internal note", // Node documentation
"notesInFlow": true, // Show notes on canvas
"executeOnce": true, // Execute only once per run
// Error handling (at node level!)
"onError": "continueErrorOutput", // or "continueRegularOutput", "stopWorkflow"
"retryOnFail": true,
"maxTries": 3,
"waitBetweenTries": 2000,
"alwaysOutputData": true,
// Deprecated (use onError instead)
"continueOnFail": false
}
\`\`\`
**Common properties explained:**
- **credentials**: Links to credential sets (use credential ID and name)
- **disabled**: Node won't execute when true
- **notes**: Internal documentation for the node
- **notesInFlow**: Display notes on workflow canvas
- **executeOnce**: Execute node only once even with multiple input items
- **onError**: Modern error handling - what to do on failure
- **retryOnFail**: Automatically retry failed executions
- **maxTries**: Number of retry attempts (with retryOnFail)
- **waitBetweenTries**: Milliseconds between retries
- **alwaysOutputData**: Output data even on error (for debugging)
### Using AI Tools
Any node can be an AI tool! Connect it to an AI Agent's ai_tool port:
\`\`\`javascript
get_node_as_tool_info("n8n-nodes-base.slack")
// Returns how to configure Slack as an AI tool
\`\`\`
### Efficient Updates
Use partial updates to save 80-90% bandwidth:
\`\`\`javascript
n8n_update_partial_workflow({
id: "workflow-id",
operations: [
{type: "updateNode", nodeId: "Slack", updates: {parameters: {channel: "general"}}}
]
})
\`\`\`
## Performance Guide
- **Fastest**: get_node_essentials, validate_node_minimal, list_tasks
- **Fast**: search_nodes, list_nodes, get_node_for_task
- **Moderate**: validate_node_operation, n8n_update_partial_workflow
- **Slow**: get_node_info (100KB+), validate_workflow (full analysis)
## Common Pitfalls to Avoid
1. Using get_node_info when get_node_essentials would work
2. Using node IDs instead of names in connections
3. Not validating before creating workflows
4. Searching with long phrases instead of keywords
5. Forgetting to configure N8N_API_URL for management tools
## Getting More Help
- Use tools_documentation({topic: "toolname"}) for any tool
- Check CLAUDE.md for latest updates and examples
- Run n8n_health_check() to verify API connectivity`;
}
export function searchToolDocumentation(query: string): string[] {
const results: string[] = [];
const searchTerms = query.toLowerCase().split(' ');
for (const [toolName, tool] of Object.entries(toolsDocumentation)) {
const searchText = `${toolName} ${tool.essentials.description} ${tool.category}`.toLowerCase();
if (searchTerms.some(term => searchText.includes(term))) {
results.push(toolName);
}
}
return results;
}
export function getToolsByCategory(category: string): string[] {
return Object.entries(toolsDocumentation)
.filter(([_, tool]) => tool.category === category)
.map(([name, _]) => name);
}
export function getAllCategories(): string[] {
const categories = new Set<string>();
Object.values(toolsDocumentation).forEach(tool => {
categories.add(tool.category);
});
return Array.from(categories);
}