feat: enhance AI tool support and clarify documentation (v2.5.1)

- Update tool descriptions to clarify ANY node can be used as AI tool
- Add get_node_as_tool_info to available tools in README
- Enhance workflow validation tool descriptions for AI connections
- Update README with v2.5.1 release notes
- Remove redundant _General__Scrape_with_HTTP_tool.json file
- Bump version to 2.5.1 in package.json

The key insight: ANY n8n node can be connected to an AI Agent's tool
port, not just those marked with usableAsTool=true. This update makes
that clear throughout the documentation and tool descriptions.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
czlonkowski
2025-06-24 15:52:01 +02:00
parent 533b1acc20
commit aad1b69fb1
10 changed files with 737 additions and 146 deletions

View File

@@ -6,7 +6,18 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
n8n-mcp is a comprehensive documentation and knowledge server that provides AI assistants with complete access to n8n node information through the Model Context Protocol (MCP). It serves as a bridge between n8n's workflow automation platform and AI models, enabling them to understand and work with n8n nodes effectively.
## ✅ Latest Updates (v2.5.0)
## ✅ Latest Updates (v2.5.1)
### Update (v2.5.1) - AI Tool Support Enhancements:
-**NEW: get_node_as_tool_info tool** - Get specific information about using ANY node as an AI tool
-**Enhanced: get_node_info** - Now includes `aiToolCapabilities` section for all nodes
-**Enhanced: list_ai_tools** - Added usage guidance explaining ANY node can be used as a tool
-**Enhanced: WorkflowValidator** - Now validates `ai_tool` connections in workflows
- ✅ AI workflow pattern detection - Warns when AI Agents have no tools connected
- ✅ Community node detection - Reminds about N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE environment variable
-**NEW: AI Tool TaskTemplates** - Added use_google_sheets_as_tool, use_slack_as_tool, multi_tool_ai_agent
- ✅ Comprehensive examples showing how to connect regular nodes as AI tools
- ✅ Tool usage documentation with $fromAI() expression examples
### Update (v2.5.0) - Complete Workflow Validation:
-**NEW: validate_workflow tool** - Validate entire workflows before deployment
@@ -131,6 +142,8 @@ src/
│ ├── test-essentials.ts # Test new essentials tools (NEW in v2.4)
│ ├── test-enhanced-validation.ts # Test enhanced validation (NEW in v2.4.2)
│ ├── 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)
│ ├── fetch-templates.ts # Fetch workflow templates from n8n.io (NEW in v2.4.1)
│ └── test-templates.ts # Test template functionality (NEW in v2.4.1)
├── mcp/
@@ -276,19 +289,20 @@ The project implements MCP (Model Context Protocol) to expose n8n node documenta
### MCP Tools Available
- `list_nodes` - List all available n8n nodes with filtering
- `get_node_info` - Get comprehensive information about a specific node (properties, operations, credentials)
- `get_node_info` - Get comprehensive information about a specific node (now includes aiToolCapabilities)
- `get_node_essentials` - **NEW** Get only essential properties (10-20) with examples (95% smaller)
- `get_node_as_tool_info` - **NEW v2.5.1** Get specific information about using ANY node as an AI tool
- `search_nodes` - Full-text search across all node documentation
- `search_node_properties` - **NEW** Search for specific properties within a node
- `get_node_for_task` - **NEW** Get pre-configured node settings for common tasks
- `list_tasks` - **NEW** List all available task templates
- `validate_node_operation` - **NEW v2.4.2** Verify node configuration with operation awareness and profiles
- `validate_node_minimal` - **NEW v2.4.2** Quick validation for just required fields
- `validate_workflow` - **NEW v2.5.0** Validate entire workflows before deployment
- `validate_workflow` - **NEW v2.5.0** Validate entire workflows before deployment (now validates ai_tool connections)
- `validate_workflow_connections` - **NEW v2.5.0** Check workflow structure and connections
- `validate_workflow_expressions` - **NEW v2.5.0** Validate all n8n expressions in a workflow
- `get_property_dependencies` - **NEW** Analyze property dependencies and visibility conditions
- `list_ai_tools` - List all AI-capable nodes (usableAsTool: true)
- `list_ai_tools` - List all AI-capable nodes (now includes usage guidance)
- `get_node_documentation` - Get parsed documentation from n8n-docs
- `get_database_statistics` - Get database usage statistics and metrics
- `list_node_templates` - **NEW** Find workflow templates using specific nodes

View File

@@ -2,7 +2,7 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![GitHub stars](https://img.shields.io/github/stars/czlonkowski/n8n-mcp?style=social)](https://github.com/czlonkowski/n8n-mcp)
[![Version](https://img.shields.io/badge/version-2.4.2-blue.svg)](https://github.com/czlonkowski/n8n-mcp)
[![Version](https://img.shields.io/badge/version-2.5.1-blue.svg)](https://github.com/czlonkowski/n8n-mcp)
[![Docker](https://img.shields.io/badge/docker-ghcr.io%2Fczlonkowski%2Fn8n--mcp-green.svg)](https://github.com/czlonkowski/n8n-mcp/pkgs/container/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 525+ workflow automation nodes.
@@ -126,13 +126,17 @@ Once connected, Claude can use these powerful tools:
- **`get_node_essentials`** - Get only essential properties with examples (10-20 properties instead of 200+)
- **`search_nodes`** - Full-text search across all node documentation
- **`search_node_properties`** - Find specific properties within nodes
- **`list_ai_tools`** - List all AI-capable 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
### Advanced Tools
- **`get_node_for_task`** - Pre-configured node settings for common tasks
- **`list_tasks`** - Discover available task templates
- **`validate_node_operation`** - Validate node configurations (operation-aware, profiles support)
- **`validate_node_minimal`** - Quick validation for just required fields
- **`validate_workflow`** - Complete workflow validation including AI tool connections
- **`validate_workflow_connections`** - Check workflow structure and AI tool connections
- **`validate_workflow_expressions`** - Validate n8n expressions including $fromAI()
- **`get_property_dependencies`** - Analyze property visibility conditions
- **`get_node_documentation`** - Get parsed documentation from n8n-docs
- **`get_database_statistics`** - View database metrics and coverage
@@ -351,6 +355,21 @@ Current database coverage (n8n v1.97.1):
## 🔄 Recent Updates
### v2.5.1 - AI Tool Support Enhancement
-**NEW**: AI tool connection validation in workflows
-**NEW**: `get_node_as_tool_info` - Guidance for using ANY node as AI tool
-**ENHANCED**: `get_node_info` now includes aiToolCapabilities section
-**IMPROVED**: Workflow validation understands ai_tool connections
-**ADDED**: $fromAI() expression validation for dynamic AI parameters
-**CLARIFIED**: ANY node can be used as an AI tool, not just usableAsTool nodes
### v2.5.0 - Complete Workflow Validation
-**NEW**: `validate_workflow` - Validate entire workflows before deployment
-**NEW**: `validate_workflow_connections` - Check workflow structure
-**NEW**: `validate_workflow_expressions` - Validate n8n expressions
-**NEW**: Cycle detection for workflows
-**NEW**: Expression syntax validation
### v2.4.2 - Professional Validation System
-**NEW**: `validate_node_operation` - Operation-aware validation with 80% fewer false positives
-**NEW**: `validate_node_minimal` - Lightning-fast required field checking

View File

@@ -1,131 +0,0 @@
{
"name": "[General] Scrape with HTTP tool",
"nodes": [
{
"parameters": {
"workflowInputs": {
"values": [
{
"name": "url"
}
]
}
},
"type": "n8n-nodes-base.executeWorkflowTrigger",
"typeVersion": 1.1,
"position": [
0,
0
],
"id": "5346cc19-24b6-4a35-aa97-8415f4bea3d6",
"name": "When Executed by Another Workflow"
},
{
"parameters": {
"url": "={{ $json.url }}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
240,
0
],
"id": "b0883f0f-a325-4d8f-9323-46ec5ac259f2",
"name": "HTTP Request3"
},
{
"parameters": {
"operation": "extractHtmlContent",
"extractionValues": {
"values": [
{
"cssSelector": "body",
"skipSelectors": "img"
}
]
},
"options": {
"cleanUpText": true
}
},
"type": "n8n-nodes-base.html",
"typeVersion": 1.2,
"position": [
460,
0
],
"id": "413d2e39-a958-44ad-b8bf-5b2ff473977f",
"name": "HTML"
}
],
"pinData": {
"When Executed by Another Workflow": [
{
"json": {
"url": "https://www.onet.pl"
}
}
]
},
"connections": {
"When Executed by Another Workflow": {
"main": [
[
{
"node": "HTTP Request3",
"type": "main",
"index": 0
}
]
]
},
"HTTP Request3": {
"main": [
[
{
"node": "HTML",
"type": "main",
"index": 0
}
]
]
},
"HTML": {
"main": [
[]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"saveDataSuccessExecution": "none",
"callerPolicy": "workflowsFromSameOwner"
},
"versionId": "e0f02ae3-2abc-4fa4-a201-dc9519d3b77f",
"meta": {
"instanceId": "f90d4c09665c36771f63f8fb50686f33a5a32ed6de3eadcceb02fed4e27faf46"
},
"id": "mu4NBMrRsGq4xAGs",
"tags": [
{
"createdAt": "2025-02-17T03:16:20.972Z",
"updatedAt": "2025-02-17T03:16:20.972Z",
"id": "kEJjx5SVonwfM7TL",
"name": "AI tool"
},
{
"createdAt": "2025-02-20T20:00:37.258Z",
"updatedAt": "2025-02-20T20:00:37.258Z",
"id": "iWK0KA4yRf7NGdav",
"name": "general"
},
{
"createdAt": "2025-02-20T21:14:11.867Z",
"updatedAt": "2025-02-20T21:14:11.867Z",
"id": "MmgG3evIY7x5HI86",
"name": "stage: production"
}
]
}

View File

@@ -1,6 +1,6 @@
{
"name": "n8n-mcp",
"version": "2.4.2",
"version": "2.5.1",
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
"main": "dist/index.js",
"scripts": {

View File

@@ -195,6 +195,8 @@ export class N8NDocumentationMCPServer {
return this.validateNodeMinimal(args.nodeType, args.config);
case 'get_property_dependencies':
return this.getPropertyDependencies(args.nodeType, args.config);
case 'get_node_as_tool_info':
return this.getNodeAsToolInfo(args.nodeType);
case 'list_node_templates':
return this.listNodeTemplates(args.nodeTypes, args.limit);
case 'get_template':
@@ -300,7 +302,22 @@ export class N8NDocumentationMCPServer {
}
}
return node;
// Add AI tool capabilities information
const aiToolCapabilities = {
canBeUsedAsTool: true, // Any node can be used as a tool in n8n
hasUsableAsToolProperty: node.isAITool,
requiresEnvironmentVariable: !node.isAITool && node.package !== 'n8n-nodes-base',
toolConnectionType: 'ai_tool',
commonToolUseCases: this.getCommonAIToolUseCases(node.nodeType),
environmentRequirement: node.package !== 'n8n-nodes-base' ?
'N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true' :
null
};
return {
...node,
aiToolCapabilities
};
}
private async searchNodes(query: string, limit: number = 20): Promise<any> {
@@ -393,6 +410,15 @@ export class N8NDocumentationMCPServer {
environmentVariable: 'N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true',
nodeProperty: 'usableAsTool: true',
},
usage: {
description: 'These nodes have the usableAsTool property set to true, making them optimized for AI agent usage.',
note: 'ANY node in n8n can be used as an AI tool by connecting it to the ai_tool port of an AI Agent node.',
examples: [
'Regular nodes like Slack, Google Sheets, or HTTP Request can be used as tools',
'Connect any node to an AI Agent\'s tool port to make it available for AI-driven automation',
'Community nodes require the environment variable to be set'
]
}
};
}
@@ -823,6 +849,180 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
};
}
private async getNodeAsToolInfo(nodeType: string): Promise<any> {
await this.ensureInitialized();
if (!this.repository) throw new Error('Repository not initialized');
// Get node info
let node = this.repository.getNode(nodeType);
if (!node) {
// Try alternative formats
const alternatives = [
nodeType,
nodeType.replace('n8n-nodes-base.', ''),
`n8n-nodes-base.${nodeType}`,
nodeType.toLowerCase()
];
for (const alt of alternatives) {
const found = this.repository!.getNode(alt);
if (found) {
node = found;
break;
}
}
if (!node) {
throw new Error(`Node ${nodeType} not found`);
}
}
// Determine common AI tool use cases based on node type
const commonUseCases = this.getCommonAIToolUseCases(node.nodeType);
// Build AI tool capabilities info
const aiToolCapabilities = {
canBeUsedAsTool: true, // In n8n, ANY node can be used as a tool when connected to AI Agent
hasUsableAsToolProperty: node.isAITool,
requiresEnvironmentVariable: !node.isAITool && node.package !== 'n8n-nodes-base',
connectionType: 'ai_tool',
commonUseCases,
requirements: {
connection: 'Connect to the "ai_tool" port of an AI Agent node',
environment: node.package !== 'n8n-nodes-base' ?
'Set N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true for community nodes' :
'No special environment variables needed for built-in nodes'
},
examples: this.getAIToolExamples(node.nodeType),
tips: [
'Give the tool a clear, descriptive name in the AI Agent settings',
'Write a detailed tool description to help the AI understand when to use it',
'Test the node independently before connecting it as a tool',
node.isAITool ?
'This node is optimized for AI tool usage' :
'This is a regular node that can be used as an AI tool'
]
};
return {
nodeType: node.nodeType,
displayName: node.displayName,
description: node.description,
package: node.package,
isMarkedAsAITool: node.isAITool,
aiToolCapabilities
};
}
private getCommonAIToolUseCases(nodeType: string): string[] {
const useCaseMap: Record<string, string[]> = {
'nodes-base.slack': [
'Send notifications about task completion',
'Post updates to channels',
'Send direct messages',
'Create alerts and reminders'
],
'nodes-base.googleSheets': [
'Read data for analysis',
'Log results and outputs',
'Update spreadsheet records',
'Create reports'
],
'nodes-base.gmail': [
'Send email notifications',
'Read and process emails',
'Send reports and summaries',
'Handle email-based workflows'
],
'nodes-base.httpRequest': [
'Call external APIs',
'Fetch data from web services',
'Send webhooks',
'Integrate with any REST API'
],
'nodes-base.postgres': [
'Query database for information',
'Store analysis results',
'Update records based on AI decisions',
'Generate reports from data'
],
'nodes-base.webhook': [
'Receive external triggers',
'Create callback endpoints',
'Handle incoming data',
'Integrate with external systems'
]
};
// Check for partial matches
for (const [key, useCases] of Object.entries(useCaseMap)) {
if (nodeType.includes(key)) {
return useCases;
}
}
// Generic use cases for unknown nodes
return [
'Perform automated actions',
'Integrate with external services',
'Process and transform data',
'Extend AI agent capabilities'
];
}
private getAIToolExamples(nodeType: string): any {
const exampleMap: Record<string, any> = {
'nodes-base.slack': {
toolName: 'Send Slack Message',
toolDescription: 'Sends a message to a specified Slack channel or user. Use this to notify team members about important events or results.',
nodeConfig: {
resource: 'message',
operation: 'post',
channel: '={{ $fromAI("channel", "The Slack channel to send to, e.g. #general") }}',
text: '={{ $fromAI("message", "The message content to send") }}'
}
},
'nodes-base.googleSheets': {
toolName: 'Update Google Sheet',
toolDescription: 'Reads or updates data in a Google Sheets spreadsheet. Use this to log information, retrieve data, or update records.',
nodeConfig: {
operation: 'append',
sheetId: 'your-sheet-id',
range: 'A:Z',
dataMode: 'autoMap'
}
},
'nodes-base.httpRequest': {
toolName: 'Call API',
toolDescription: 'Makes HTTP requests to external APIs. Use this to fetch data, trigger webhooks, or integrate with any web service.',
nodeConfig: {
method: '={{ $fromAI("method", "HTTP method: GET, POST, PUT, DELETE") }}',
url: '={{ $fromAI("url", "The complete API endpoint URL") }}',
sendBody: true,
bodyContentType: 'json',
jsonBody: '={{ $fromAI("body", "Request body as JSON object") }}'
}
}
};
// Check for exact match or partial match
for (const [key, example] of Object.entries(exampleMap)) {
if (nodeType.includes(key)) {
return example;
}
}
// Generic example
return {
toolName: 'Custom Tool',
toolDescription: 'Performs specific operations. Describe what this tool does and when to use it.',
nodeConfig: {
note: 'Configure the node based on its specific requirements'
}
};
}
private async validateNodeMinimal(nodeType: string, config: Record<string, any>): Promise<any> {
await this.ensureInitialized();
if (!this.repository) throw new Error('Repository not initialized');

View File

@@ -54,7 +54,7 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
},
{
name: 'get_node_info',
description: `Get COMPLETE technical schema for a node. WARNING: Returns massive JSON (often 100KB+) with all properties, operations, credentials. Contains duplicates and complex conditional logic. TIPS: 1) Use get_node_essentials first for common use cases, 2) Try get_node_documentation for human-readable info, 3) Look for "required":true properties, 4) Find properties without "displayOptions" for simpler versions. Node type MUST include prefix: "nodes-base.httpRequest" NOT "httpRequest".`,
description: `Get COMPLETE technical schema for a node. WARNING: Returns massive JSON (often 100KB+) with all properties, operations, credentials. Contains duplicates and complex conditional logic. TIPS: 1) Use get_node_essentials first for common use cases, 2) Try get_node_documentation for human-readable info, 3) Look for "required":true properties, 4) Find properties without "displayOptions" for simpler versions. Node type MUST include prefix: "nodes-base.httpRequest" NOT "httpRequest". NOW INCLUDES: aiToolCapabilities section showing how to use any node as an AI tool.`,
inputSchema: {
type: 'object',
properties: {
@@ -87,7 +87,7 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
},
{
name: 'list_ai_tools',
description: `List all 263 nodes that AI agents can use as function-calling tools. These have usableAsTool=true and work with OpenAI Assistants, LangChain agents, etc. Simpler than list_nodes - just returns names and descriptions. Great for finding nodes to give AI agents real-world capabilities (send emails, query databases, call APIs). Note: n8n needs N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true.`,
description: `List all 263 nodes marked with usableAsTool=true property. IMPORTANT: ANY node in n8n can be used as an AI tool - not just these! These nodes are optimized for AI usage but you can connect any node (Slack, Google Sheets, HTTP Request, etc.) to an AI Agent's tool port. Returns names and descriptions. For community nodes as tools, set N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true. Use get_node_as_tool_info for guidance on using any node as a tool.`,
inputSchema: {
type: 'object',
properties: {},
@@ -239,6 +239,20 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
required: ['nodeType'],
},
},
{
name: 'get_node_as_tool_info',
description: `Get specific information about using a node as an AI tool. Returns whether the node can be used as a tool, common use cases, requirements, and examples. Essential for understanding how to connect regular nodes to AI Agents. Works for ANY node - not just those marked as AI tools.`,
inputSchema: {
type: 'object',
properties: {
nodeType: {
type: 'string',
description: 'Full node type WITH prefix: "nodes-base.slack", "nodes-base.googleSheets", etc.',
},
},
required: ['nodeType'],
},
},
{
name: 'list_node_templates',
description: `List workflow templates that use specific node type(s). Returns ready-to-use workflows from n8n.io community. Templates are from the last year (399 total). Use FULL node types like "n8n-nodes-base.httpRequest" or "@n8n/n8n-nodes-langchain.openAi". Great for finding proven workflow patterns.`,
@@ -320,7 +334,7 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
},
{
name: 'validate_workflow',
description: `Validate an entire n8n workflow before deployment. Checks: workflow structure, node connections, expressions, best practices, and more. Returns comprehensive validation report with errors, warnings, and suggestions. Essential for AI agents building complete workflows. Prevents common workflow errors before they happen.`,
description: `Validate an entire n8n workflow before deployment. Checks: workflow structure, node connections (including ai_tool connections), expressions, best practices, AI Agent configurations, and more. Returns comprehensive validation report with errors, warnings, and suggestions. Essential for AI agents building complete workflows. Validates AI tool connections and $fromAI() expressions. Prevents common workflow errors before they happen.`,
inputSchema: {
type: 'object',
properties: {
@@ -361,7 +375,7 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
},
{
name: 'validate_workflow_connections',
description: `Validate only the connections in a workflow. Checks: all connections point to existing nodes, no cycles (infinite loops), no orphaned nodes, proper trigger node setup. Faster than full validation when you only need to check workflow structure.`,
description: `Validate only the connections in a workflow. Checks: all connections point to existing nodes, no cycles (infinite loops), no orphaned nodes, proper trigger node setup, AI tool connections are valid. Validates ai_tool connection types between AI Agents and tool nodes. Faster than full validation when you only need to check workflow structure.`,
inputSchema: {
type: 'object',
properties: {

View File

@@ -0,0 +1,212 @@
#!/usr/bin/env node
/**
* Test AI workflow validation enhancements
*/
import { createDatabaseAdapter } from '../database/database-adapter';
import { NodeRepository } from '../database/node-repository';
import { WorkflowValidator } from '../services/workflow-validator';
import { Logger } from '../utils/logger';
import { EnhancedConfigValidator } from '../services/enhanced-config-validator';
const logger = new Logger({ prefix: '[TestAIWorkflow]' });
// Test workflow with AI Agent and tools
const aiWorkflow = {
name: 'AI Agent with Tools',
nodes: [
{
id: '1',
name: 'Webhook',
type: 'n8n-nodes-base.webhook',
position: [100, 100],
parameters: {
path: 'ai-webhook',
httpMethod: 'POST'
}
},
{
id: '2',
name: 'AI Agent',
type: '@n8n/n8n-nodes-langchain.agent',
position: [300, 100],
parameters: {
text: '={{ $json.query }}',
systemMessage: 'You are a helpful assistant with access to tools'
}
},
{
id: '3',
name: 'Google Sheets Tool',
type: 'n8n-nodes-base.googleSheets',
position: [300, 250],
parameters: {
operation: 'append',
sheetId: '={{ $fromAI("sheetId", "Sheet ID") }}',
range: 'A:Z'
}
},
{
id: '4',
name: 'Slack Tool',
type: 'n8n-nodes-base.slack',
position: [300, 350],
parameters: {
resource: 'message',
operation: 'post',
channel: '={{ $fromAI("channel", "Channel name") }}',
text: '={{ $fromAI("message", "Message text") }}'
}
},
{
id: '5',
name: 'Response',
type: 'n8n-nodes-base.respondToWebhook',
position: [500, 100],
parameters: {
responseCode: 200
}
}
],
connections: {
'Webhook': {
main: [[{ node: 'AI Agent', type: 'main', index: 0 }]]
},
'AI Agent': {
main: [[{ node: 'Response', type: 'main', index: 0 }]],
ai_tool: [
[
{ node: 'Google Sheets Tool', type: 'ai_tool', index: 0 },
{ node: 'Slack Tool', type: 'ai_tool', index: 0 }
]
]
}
}
};
// Test workflow without tools (should trigger warning)
const aiWorkflowNoTools = {
name: 'AI Agent without Tools',
nodes: [
{
id: '1',
name: 'Manual',
type: 'n8n-nodes-base.manualTrigger',
position: [100, 100],
parameters: {}
},
{
id: '2',
name: 'AI Agent',
type: '@n8n/n8n-nodes-langchain.agent',
position: [300, 100],
parameters: {
text: 'Hello AI'
}
}
],
connections: {
'Manual': {
main: [[{ node: 'AI Agent', type: 'main', index: 0 }]]
}
}
};
// Test workflow with googleSheetsTool (unknown node type)
const unknownToolWorkflow = {
name: 'Unknown Tool Test',
nodes: [
{
id: '1',
name: 'Agent',
type: 'nodes-langchain.agent',
position: [100, 100],
parameters: {}
},
{
id: '2',
name: 'Sheets Tool',
type: 'googleSheetsTool',
position: [300, 100],
parameters: {}
}
],
connections: {
'Agent': {
ai_tool: [[{ node: 'Sheets Tool', type: 'ai_tool', index: 0 }]]
}
}
};
async function testWorkflow(name: string, workflow: any) {
console.log(`\n🧪 Testing: ${name}`);
console.log('='.repeat(50));
const db = await createDatabaseAdapter('./data/nodes.db');
const repository = new NodeRepository(db);
const validator = new WorkflowValidator(repository, EnhancedConfigValidator);
try {
const result = await validator.validateWorkflow(workflow);
console.log(`\n📊 Validation Results:`);
console.log(`Valid: ${result.valid ? '✅' : '❌'}`);
if (result.errors.length > 0) {
console.log('\n❌ Errors:');
result.errors.forEach((err: any) => {
if (typeof err === 'string') {
console.log(` - ${err}`);
} else if (err.message) {
const nodeInfo = err.nodeName ? ` [${err.nodeName}]` : '';
console.log(` - ${err.message}${nodeInfo}`);
} else {
console.log(` - ${JSON.stringify(err, null, 2)}`);
}
});
}
if (result.warnings.length > 0) {
console.log('\n⚠ Warnings:');
result.warnings.forEach((warn: any) => {
const msg = warn.message || warn;
const nodeInfo = warn.nodeName ? ` [${warn.nodeName}]` : '';
console.log(` - ${msg}${nodeInfo}`);
});
}
if (result.suggestions.length > 0) {
console.log('\n💡 Suggestions:');
result.suggestions.forEach((sug: any) => console.log(` - ${sug}`));
}
console.log('\n📈 Statistics:');
console.log(` - Total nodes: ${result.statistics.totalNodes}`);
console.log(` - Valid connections: ${result.statistics.validConnections}`);
console.log(` - Invalid connections: ${result.statistics.invalidConnections}`);
console.log(` - Expressions validated: ${result.statistics.expressionsValidated}`);
} catch (error) {
console.error('Validation error:', error);
} finally {
db.close();
}
}
async function main() {
console.log('🤖 Testing AI Workflow Validation Enhancements');
// Test 1: Complete AI workflow with tools
await testWorkflow('AI Agent with Multiple Tools', aiWorkflow);
// Test 2: AI Agent without tools (should warn)
await testWorkflow('AI Agent without Tools', aiWorkflowNoTools);
// Test 3: Unknown tool type (like googleSheetsTool)
await testWorkflow('Unknown Tool Type', unknownToolWorkflow);
console.log('\n✅ All tests completed!');
}
if (require.main === module) {
main().catch(console.error);
}

View File

@@ -0,0 +1,54 @@
#!/usr/bin/env node
/**
* Test MCP tools directly
*/
import { createDatabaseAdapter } from '../database/database-adapter';
import { NodeRepository } from '../database/node-repository';
import { N8NDocumentationMCPServer } from '../mcp/server-update';
import { Logger } from '../utils/logger';
const logger = new Logger({ prefix: '[TestMCPTools]' });
async function testTool(server: any, toolName: string, args: any) {
try {
console.log(`\n🔧 Testing: ${toolName}`);
console.log('Args:', JSON.stringify(args, null, 2));
console.log('-'.repeat(60));
const result = await server[toolName].call(server, args);
console.log('Result:', JSON.stringify(result, null, 2));
} catch (error) {
console.error(`❌ Error: ${error}`);
}
}
async function main() {
console.log('🤖 Testing MCP Tools\n');
// Create server instance and wait for initialization
const server = new N8NDocumentationMCPServer();
// Give it time to initialize
await new Promise(resolve => setTimeout(resolve, 100));
// Test get_node_as_tool_info
console.log('\n=== Testing get_node_as_tool_info ===');
await testTool(server, 'getNodeAsToolInfo', 'nodes-base.slack');
await testTool(server, 'getNodeAsToolInfo', 'nodes-base.googleSheets');
// Test enhanced get_node_info with aiToolCapabilities
console.log('\n\n=== Testing get_node_info (with aiToolCapabilities) ===');
await testTool(server, 'getNodeInfo', 'nodes-base.httpRequest');
// Test list_ai_tools with enhanced response
console.log('\n\n=== Testing list_ai_tools (enhanced) ===');
await testTool(server, 'listAITools', {});
console.log('\n✅ All tests completed!');
process.exit(0);
}
if (require.main === module) {
main().catch(console.error);
}

View File

@@ -461,6 +461,107 @@ return results;`
description: 'Attach files to the email'
}
]
},
// AI Tool Usage Tasks
'use_google_sheets_as_tool': {
task: 'use_google_sheets_as_tool',
description: 'Use Google Sheets as an AI tool for reading/writing data',
nodeType: 'nodes-base.googleSheets',
configuration: {
operation: 'append',
sheetId: '={{ $fromAI("sheetId", "The Google Sheets ID") }}',
range: '={{ $fromAI("range", "The range to append to, e.g. A:Z") }}',
dataMode: 'autoMap'
},
userMustProvide: [
{
property: 'Google Sheets credentials',
description: 'Configure Google Sheets API credentials in n8n'
},
{
property: 'Tool name in AI Agent',
description: 'Give it a descriptive name like "Log Results to Sheet"'
},
{
property: 'Tool description',
description: 'Describe when and how the AI should use this tool'
}
],
notes: [
'Connect this node to the ai_tool port of an AI Agent node',
'The AI can dynamically determine sheetId and range using $fromAI',
'Works great for logging AI analysis results or reading data for processing'
]
},
'use_slack_as_tool': {
task: 'use_slack_as_tool',
description: 'Use Slack as an AI tool for sending notifications',
nodeType: 'nodes-base.slack',
configuration: {
resource: 'message',
operation: 'post',
channel: '={{ $fromAI("channel", "The Slack channel, e.g. #general") }}',
text: '={{ $fromAI("message", "The message to send") }}',
attachments: []
},
userMustProvide: [
{
property: 'Slack credentials',
description: 'Configure Slack OAuth2 credentials in n8n'
},
{
property: 'Tool configuration in AI Agent',
description: 'Name it something like "Send Slack Notification"'
}
],
notes: [
'Perfect for AI agents that need to notify teams',
'The AI determines channel and message content dynamically',
'Can be enhanced with blocks for rich formatting'
]
},
'multi_tool_ai_agent': {
task: 'multi_tool_ai_agent',
description: 'AI agent with multiple tools for complex automation',
nodeType: 'nodes-langchain.agent',
configuration: {
text: '={{ $json.query }}',
outputType: 'output',
systemMessage: 'You are an intelligent assistant with access to multiple tools. Use them wisely to complete tasks.'
},
userMustProvide: [
{
property: 'AI model credentials',
description: 'OpenAI, Anthropic, or other LLM credentials'
},
{
property: 'Multiple tool nodes',
description: 'Connect various nodes to the ai_tool port'
},
{
property: 'Tool descriptions',
description: 'Clear descriptions for each connected tool'
}
],
optionalEnhancements: [
{
property: 'Memory',
description: 'Add memory nodes for conversation context'
},
{
property: 'Custom tools',
description: 'Create Code nodes as custom tools'
}
],
notes: [
'Connect multiple nodes: HTTP Request, Slack, Google Sheets, etc.',
'Each tool should have a clear, specific purpose',
'Test each tool individually before combining',
'Set N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true for community nodes'
]
}
};
@@ -509,9 +610,10 @@ return results;`
'HTTP/API': ['get_api_data', 'post_json_request', 'call_api_with_auth'],
'Webhooks': ['receive_webhook', 'webhook_with_response'],
'Database': ['query_postgres', 'insert_postgres_data'],
'AI/LangChain': ['chat_with_ai', 'ai_agent_workflow'],
'AI/LangChain': ['chat_with_ai', 'ai_agent_workflow', 'multi_tool_ai_agent'],
'Data Processing': ['transform_data', 'filter_data'],
'Communication': ['send_slack_message', 'send_email']
'Communication': ['send_slack_message', 'send_email'],
'AI Tool Usage': ['use_google_sheets_as_tool', 'use_slack_as_tool', 'multi_tool_ai_agent']
};
}
}

View File

@@ -26,6 +26,7 @@ interface WorkflowConnection {
[sourceNode: string]: {
main?: Array<Array<{ node: string; type: string; index: number }>>;
error?: Array<Array<{ node: string; type: string; index: number }>>;
ai_tool?: Array<Array<{ node: string; type: string; index: number }>>;
};
}
@@ -340,6 +341,17 @@ export class WorkflowValidator {
'error'
);
}
// Check AI tool outputs
if (outputs.ai_tool) {
this.validateConnectionOutputs(
sourceName,
outputs.ai_tool,
nodeMap,
result,
'ai_tool'
);
}
}
// Check for orphaned nodes (not connected and not triggers)
@@ -360,6 +372,11 @@ export class WorkflowValidator {
if (conn) connectedNodes.add(conn.node);
});
}
if (outputs.ai_tool) {
outputs.ai_tool.flat().forEach(conn => {
if (conn) connectedNodes.add(conn.node);
});
}
});
// Check for orphaned nodes
@@ -400,7 +417,7 @@ export class WorkflowValidator {
outputs: Array<Array<{ node: string; type: string; index: number }>>,
nodeMap: Map<string, WorkflowNode>,
result: WorkflowValidationResult,
outputType: 'main' | 'error'
outputType: 'main' | 'error' | 'ai_tool'
): void {
outputs.forEach((outputConnections, outputIndex) => {
if (!outputConnections) return;
@@ -421,11 +438,57 @@ export class WorkflowValidator {
});
} else {
result.statistics.validConnections++;
// Additional validation for AI tool connections
if (outputType === 'ai_tool') {
this.validateAIToolConnection(sourceName, targetNode, result);
}
}
});
});
}
/**
* Validate AI tool connections
*/
private validateAIToolConnection(
sourceName: string,
targetNode: WorkflowNode,
result: WorkflowValidationResult
): void {
// For AI tool connections, we just need to check if this is being used as a tool
// The source should be an AI Agent connecting to this target node as a tool
// Get target node info to check if it can be used as a tool
let targetNodeInfo = this.nodeRepository.getNode(targetNode.type);
// Try normalized type if not found
if (!targetNodeInfo) {
let normalizedType = targetNode.type;
// Handle n8n-nodes-base -> nodes-base
if (targetNode.type.startsWith('n8n-nodes-base.')) {
normalizedType = targetNode.type.replace('n8n-nodes-base.', 'nodes-base.');
targetNodeInfo = this.nodeRepository.getNode(normalizedType);
}
// Handle @n8n/n8n-nodes-langchain -> nodes-langchain
else if (targetNode.type.startsWith('@n8n/n8n-nodes-langchain.')) {
normalizedType = targetNode.type.replace('@n8n/n8n-nodes-langchain.', 'nodes-langchain.');
targetNodeInfo = this.nodeRepository.getNode(normalizedType);
}
}
if (targetNodeInfo && !targetNodeInfo.isAITool && targetNodeInfo.package !== 'n8n-nodes-base') {
// It's a community node being used as a tool
result.warnings.push({
type: 'warning',
nodeId: targetNode.id,
nodeName: targetNode.name,
message: `Community node "${targetNode.name}" is being used as an AI tool. Ensure N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true is set.`
});
}
}
/**
* Check if workflow has cycles
*/
@@ -446,6 +509,18 @@ export class WorkflowValidator {
if (conn) allTargets.push(conn.node);
});
}
if (connections.error) {
connections.error.flat().forEach(conn => {
if (conn) allTargets.push(conn.node);
});
}
if (connections.ai_tool) {
connections.ai_tool.flat().forEach(conn => {
if (conn) allTargets.push(conn.node);
});
}
for (const target of allTargets) {
if (!visited.has(target)) {
@@ -578,6 +653,38 @@ export class WorkflowValidator {
}
}
}
// Check for AI Agent workflows
const aiAgentNodes = workflow.nodes.filter(n =>
n.type.toLowerCase().includes('agent') ||
n.type.includes('langchain.agent')
);
if (aiAgentNodes.length > 0) {
// Check if AI agents have tools connected
for (const agentNode of aiAgentNodes) {
const connections = workflow.connections[agentNode.name];
if (!connections?.ai_tool || connections.ai_tool.flat().filter(c => c).length === 0) {
result.warnings.push({
type: 'warning',
nodeId: agentNode.id,
nodeName: agentNode.name,
message: 'AI Agent has no tools connected. Consider adding tools to enhance agent capabilities.'
});
}
}
// Check for community nodes used as tools
const hasAIToolConnections = Object.values(workflow.connections).some(
outputs => outputs.ai_tool && outputs.ai_tool.length > 0
);
if (hasAIToolConnections) {
result.suggestions.push(
'For community nodes used as AI tools, ensure N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true is set'
);
}
}
}
/**