feat: implement AI-optimized MCP tools with 95% size reduction
- Add get_node_essentials tool for 10-20 essential properties only - Add search_node_properties for targeted property search - Add get_node_for_task with 14 pre-configured templates - Add validate_node_config for comprehensive validation - Add get_property_dependencies for visibility analysis - Implement PropertyFilter service with curated essentials - Implement ExampleGenerator with working examples - Implement TaskTemplates for common workflows - Implement ConfigValidator with security checks - Implement PropertyDependencies for dependency analysis - Enhance property descriptions to 100% coverage - Add version information to essentials response - Update documentation with new tools Response sizes reduced from 100KB+ to <5KB for better AI agent usability. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
78
scripts/debug-essentials.js
Normal file
78
scripts/debug-essentials.js
Normal file
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Debug the essentials implementation
|
||||
*/
|
||||
|
||||
const { N8NDocumentationMCPServer } = require('../dist/mcp/server-update');
|
||||
const { PropertyFilter } = require('../dist/services/property-filter');
|
||||
const { ExampleGenerator } = require('../dist/services/example-generator');
|
||||
|
||||
async function debugEssentials() {
|
||||
console.log('🔍 Debugging essentials implementation\n');
|
||||
|
||||
try {
|
||||
// Initialize server
|
||||
const server = new N8NDocumentationMCPServer();
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
const nodeType = 'nodes-base.httpRequest';
|
||||
|
||||
// Step 1: Get raw node info
|
||||
console.log('Step 1: Getting raw node info...');
|
||||
const nodeInfo = await server.executeTool('get_node_info', { nodeType });
|
||||
console.log('✅ Got node info');
|
||||
console.log(' Node type:', nodeInfo.nodeType);
|
||||
console.log(' Display name:', nodeInfo.displayName);
|
||||
console.log(' Properties count:', nodeInfo.properties?.length);
|
||||
console.log(' Properties type:', typeof nodeInfo.properties);
|
||||
console.log(' First property:', nodeInfo.properties?.[0]?.name);
|
||||
|
||||
// Step 2: Test PropertyFilter directly
|
||||
console.log('\nStep 2: Testing PropertyFilter...');
|
||||
const properties = nodeInfo.properties || [];
|
||||
console.log(' Input properties count:', properties.length);
|
||||
|
||||
const essentials = PropertyFilter.getEssentials(properties, nodeType);
|
||||
console.log(' Essential results:');
|
||||
console.log(' - Required:', essentials.required?.length || 0);
|
||||
console.log(' - Common:', essentials.common?.length || 0);
|
||||
console.log(' - Required names:', essentials.required?.map(p => p.name).join(', ') || 'none');
|
||||
console.log(' - Common names:', essentials.common?.map(p => p.name).join(', ') || 'none');
|
||||
|
||||
// Step 3: Test ExampleGenerator
|
||||
console.log('\nStep 3: Testing ExampleGenerator...');
|
||||
const examples = ExampleGenerator.getExamples(nodeType, essentials);
|
||||
console.log(' Example keys:', Object.keys(examples));
|
||||
console.log(' Minimal example:', JSON.stringify(examples.minimal || {}, null, 2));
|
||||
|
||||
// Step 4: Test the full tool
|
||||
console.log('\nStep 4: Testing get_node_essentials tool...');
|
||||
const essentialsResult = await server.executeTool('get_node_essentials', { nodeType });
|
||||
console.log('✅ Tool executed');
|
||||
console.log(' Result keys:', Object.keys(essentialsResult));
|
||||
console.log(' Node type from result:', essentialsResult.nodeType);
|
||||
console.log(' Required props:', essentialsResult.requiredProperties?.length || 0);
|
||||
console.log(' Common props:', essentialsResult.commonProperties?.length || 0);
|
||||
|
||||
// Compare property counts
|
||||
console.log('\n📊 Summary:');
|
||||
console.log(' Full properties:', nodeInfo.properties?.length || 0);
|
||||
console.log(' Essential properties:',
|
||||
(essentialsResult.requiredProperties?.length || 0) +
|
||||
(essentialsResult.commonProperties?.length || 0)
|
||||
);
|
||||
console.log(' Reduction:',
|
||||
Math.round((1 - ((essentialsResult.requiredProperties?.length || 0) +
|
||||
(essentialsResult.commonProperties?.length || 0)) /
|
||||
(nodeInfo.properties?.length || 1)) * 100) + '%'
|
||||
);
|
||||
|
||||
} catch (error) {
|
||||
console.error('\n❌ Error:', error);
|
||||
console.error('Stack:', error.stack);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
debugEssentials().catch(console.error);
|
||||
56
scripts/debug-node.js
Normal file
56
scripts/debug-node.js
Normal file
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Debug script to check node data structure
|
||||
*/
|
||||
|
||||
const { N8NDocumentationMCPServer } = require('../dist/mcp/server-update');
|
||||
|
||||
async function debugNode() {
|
||||
console.log('🔍 Debugging node data\n');
|
||||
|
||||
try {
|
||||
// Initialize server
|
||||
const server = new N8NDocumentationMCPServer();
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// Get node info directly
|
||||
const nodeType = 'nodes-base.httpRequest';
|
||||
console.log(`Checking node: ${nodeType}\n`);
|
||||
|
||||
try {
|
||||
const nodeInfo = await server.executeTool('get_node_info', { nodeType });
|
||||
|
||||
console.log('Node info retrieved successfully');
|
||||
console.log('Node type:', nodeInfo.nodeType);
|
||||
console.log('Has properties:', !!nodeInfo.properties);
|
||||
console.log('Properties count:', nodeInfo.properties?.length || 0);
|
||||
console.log('Has operations:', !!nodeInfo.operations);
|
||||
console.log('Operations:', nodeInfo.operations);
|
||||
console.log('Operations type:', typeof nodeInfo.operations);
|
||||
console.log('Operations length:', nodeInfo.operations?.length);
|
||||
|
||||
// Check raw data
|
||||
console.log('\n📊 Raw data check:');
|
||||
console.log('properties_schema type:', typeof nodeInfo.properties_schema);
|
||||
console.log('operations type:', typeof nodeInfo.operations);
|
||||
|
||||
// Check if operations is a string that needs parsing
|
||||
if (typeof nodeInfo.operations === 'string') {
|
||||
console.log('\nOperations is a string, trying to parse:');
|
||||
console.log('Operations string:', nodeInfo.operations);
|
||||
console.log('Operations length:', nodeInfo.operations.length);
|
||||
console.log('First 100 chars:', nodeInfo.operations.substring(0, 100));
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error getting node info:', error);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fatal error:', error);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
debugNode().catch(console.error);
|
||||
139
scripts/quick-test.ts
Normal file
139
scripts/quick-test.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env ts-node
|
||||
/**
|
||||
* Quick test script to validate the essentials implementation
|
||||
*/
|
||||
|
||||
import { spawn } from 'child_process';
|
||||
import { join } from 'path';
|
||||
|
||||
const colors = {
|
||||
reset: '\x1b[0m',
|
||||
bright: '\x1b[1m',
|
||||
green: '\x1b[32m',
|
||||
red: '\x1b[31m',
|
||||
yellow: '\x1b[33m',
|
||||
blue: '\x1b[34m',
|
||||
cyan: '\x1b[36m'
|
||||
};
|
||||
|
||||
function log(message: string, color: string = colors.reset) {
|
||||
console.log(`${color}${message}${colors.reset}`);
|
||||
}
|
||||
|
||||
async function runMCPCommand(toolName: string, args: any): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = {
|
||||
jsonrpc: '2.0',
|
||||
method: 'tools/call',
|
||||
params: {
|
||||
name: toolName,
|
||||
arguments: args
|
||||
},
|
||||
id: 1
|
||||
};
|
||||
|
||||
const mcp = spawn('npm', ['start'], {
|
||||
cwd: join(__dirname, '..'),
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
});
|
||||
|
||||
let output = '';
|
||||
let error = '';
|
||||
|
||||
mcp.stdout.on('data', (data) => {
|
||||
output += data.toString();
|
||||
});
|
||||
|
||||
mcp.stderr.on('data', (data) => {
|
||||
error += data.toString();
|
||||
});
|
||||
|
||||
mcp.on('close', (code) => {
|
||||
if (code !== 0) {
|
||||
reject(new Error(`Process exited with code ${code}: ${error}`));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Parse JSON-RPC response
|
||||
const lines = output.split('\n');
|
||||
for (const line of lines) {
|
||||
if (line.trim() && line.includes('"jsonrpc"')) {
|
||||
const response = JSON.parse(line);
|
||||
if (response.result) {
|
||||
resolve(JSON.parse(response.result.content[0].text));
|
||||
return;
|
||||
} else if (response.error) {
|
||||
reject(new Error(response.error.message));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
reject(new Error('No valid response found'));
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
|
||||
// Send request
|
||||
mcp.stdin.write(JSON.stringify(request) + '\n');
|
||||
mcp.stdin.end();
|
||||
});
|
||||
}
|
||||
|
||||
async function quickTest() {
|
||||
log('\n🚀 Quick Test - n8n MCP Essentials', colors.bright + colors.cyan);
|
||||
|
||||
try {
|
||||
// Test 1: Get essentials for HTTP Request
|
||||
log('\n1️⃣ Testing get_node_essentials for HTTP Request...', colors.yellow);
|
||||
const essentials = await runMCPCommand('get_node_essentials', {
|
||||
nodeType: 'nodes-base.httpRequest'
|
||||
});
|
||||
|
||||
log('✅ Success! Got essentials:', colors.green);
|
||||
log(` Required properties: ${essentials.requiredProperties?.map((p: any) => p.name).join(', ') || 'None'}`);
|
||||
log(` Common properties: ${essentials.commonProperties?.map((p: any) => p.name).join(', ') || 'None'}`);
|
||||
log(` Examples: ${Object.keys(essentials.examples || {}).join(', ')}`);
|
||||
log(` Response size: ${JSON.stringify(essentials).length} bytes`, colors.green);
|
||||
|
||||
// Test 2: Search properties
|
||||
log('\n2️⃣ Testing search_node_properties...', colors.yellow);
|
||||
const searchResults = await runMCPCommand('search_node_properties', {
|
||||
nodeType: 'nodes-base.httpRequest',
|
||||
query: 'auth'
|
||||
});
|
||||
|
||||
log('✅ Success! Found properties:', colors.green);
|
||||
log(` Matches: ${searchResults.totalMatches}`);
|
||||
searchResults.matches?.slice(0, 3).forEach((match: any) => {
|
||||
log(` - ${match.name}: ${match.description}`);
|
||||
});
|
||||
|
||||
// Test 3: Compare sizes
|
||||
log('\n3️⃣ Comparing response sizes...', colors.yellow);
|
||||
const fullInfo = await runMCPCommand('get_node_info', {
|
||||
nodeType: 'nodes-base.httpRequest'
|
||||
});
|
||||
|
||||
const fullSize = JSON.stringify(fullInfo).length;
|
||||
const essentialSize = JSON.stringify(essentials).length;
|
||||
const reduction = ((fullSize - essentialSize) / fullSize * 100).toFixed(1);
|
||||
|
||||
log(`✅ Size comparison:`, colors.green);
|
||||
log(` Full response: ${(fullSize / 1024).toFixed(1)} KB`);
|
||||
log(` Essential response: ${(essentialSize / 1024).toFixed(1)} KB`);
|
||||
log(` Size reduction: ${reduction}% 🎉`, colors.bright + colors.green);
|
||||
|
||||
log('\n✨ All tests passed!', colors.bright + colors.green);
|
||||
|
||||
} catch (error) {
|
||||
log(`\n❌ Test failed: ${error}`, colors.red);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run if called directly
|
||||
if (require.main === module) {
|
||||
quickTest().catch(console.error);
|
||||
}
|
||||
88
scripts/test-direct.js
Normal file
88
scripts/test-direct.js
Normal file
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Direct test of the server functionality without MCP protocol
|
||||
*/
|
||||
|
||||
const { N8NDocumentationMCPServer } = require('../dist/mcp/server-update');
|
||||
|
||||
async function testDirect() {
|
||||
console.log('🧪 Direct server test\n');
|
||||
|
||||
try {
|
||||
// Initialize server
|
||||
console.log('Initializing server...');
|
||||
const server = new N8NDocumentationMCPServer();
|
||||
|
||||
// Wait for initialization
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
console.log('Server initialized successfully\n');
|
||||
|
||||
// Test get_node_essentials
|
||||
console.log('Testing get_node_essentials...');
|
||||
try {
|
||||
const result = await server.executeTool('get_node_essentials', {
|
||||
nodeType: 'nodes-base.httpRequest'
|
||||
});
|
||||
|
||||
console.log('✅ Success!');
|
||||
console.log('Result type:', typeof result);
|
||||
console.log('Result keys:', Object.keys(result || {}));
|
||||
console.log('Node type:', result?.nodeType);
|
||||
console.log('Required props:', result?.requiredProperties?.length || 0);
|
||||
console.log('Common props:', result?.commonProperties?.length || 0);
|
||||
console.log('Has examples:', !!result?.examples);
|
||||
|
||||
// Check sizes
|
||||
const size = JSON.stringify(result).length;
|
||||
console.log(`\nResponse size: ${(size / 1024).toFixed(1)} KB`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error executing get_node_essentials:', error);
|
||||
console.error('Error stack:', error.stack);
|
||||
}
|
||||
|
||||
// Test search_node_properties
|
||||
console.log('\n\nTesting search_node_properties...');
|
||||
try {
|
||||
const result = await server.executeTool('search_node_properties', {
|
||||
nodeType: 'nodes-base.httpRequest',
|
||||
query: 'auth'
|
||||
});
|
||||
|
||||
console.log('✅ Success!');
|
||||
console.log('Matches found:', result?.totalMatches || 0);
|
||||
console.log('First match:', result?.matches?.[0]?.name);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error executing search_node_properties:', error);
|
||||
}
|
||||
|
||||
// Test get_node_info for comparison
|
||||
console.log('\n\nTesting get_node_info for comparison...');
|
||||
try {
|
||||
const result = await server.executeTool('get_node_info', {
|
||||
nodeType: 'nodes-base.httpRequest'
|
||||
});
|
||||
|
||||
const size = JSON.stringify(result).length;
|
||||
console.log('✅ Success!');
|
||||
console.log(`Full node info size: ${(size / 1024).toFixed(1)} KB`);
|
||||
console.log('Properties count:', result?.properties?.length || 0);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error executing get_node_info:', error);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('\n❌ Fatal error:', error);
|
||||
console.error('Stack:', error.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('\n✨ Direct test completed');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Run the test
|
||||
testDirect().catch(console.error);
|
||||
225
scripts/test-essentials-simple.js
Executable file
225
scripts/test-essentials-simple.js
Executable file
@@ -0,0 +1,225 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Simple test script for validating the essentials implementation
|
||||
* This version runs the MCP server as a subprocess to test real behavior
|
||||
*/
|
||||
|
||||
const { spawn } = require('child_process');
|
||||
const path = require('path');
|
||||
|
||||
const colors = {
|
||||
reset: '\x1b[0m',
|
||||
bright: '\x1b[1m',
|
||||
green: '\x1b[32m',
|
||||
red: '\x1b[31m',
|
||||
yellow: '\x1b[33m',
|
||||
blue: '\x1b[34m',
|
||||
cyan: '\x1b[36m'
|
||||
};
|
||||
|
||||
function log(message, color = colors.reset) {
|
||||
console.log(`${color}${message}${colors.reset}`);
|
||||
}
|
||||
|
||||
function runMCPRequest(request) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const mcp = spawn('npm', ['start'], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
env: { ...process.env, NODE_ENV: 'production' }
|
||||
});
|
||||
|
||||
let output = '';
|
||||
let error = '';
|
||||
let timeout;
|
||||
|
||||
// Set timeout
|
||||
timeout = setTimeout(() => {
|
||||
mcp.kill();
|
||||
reject(new Error('Request timed out after 10 seconds'));
|
||||
}, 10000);
|
||||
|
||||
mcp.stdout.on('data', (data) => {
|
||||
output += data.toString();
|
||||
});
|
||||
|
||||
mcp.stderr.on('data', (data) => {
|
||||
error += data.toString();
|
||||
});
|
||||
|
||||
mcp.on('close', (code) => {
|
||||
clearTimeout(timeout);
|
||||
|
||||
if (code !== 0) {
|
||||
reject(new Error(`Process exited with code ${code}: ${error}`));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Parse JSON-RPC response
|
||||
const lines = output.split('\n');
|
||||
for (const line of lines) {
|
||||
if (line.trim() && line.includes('"jsonrpc"')) {
|
||||
const response = JSON.parse(line);
|
||||
if (response.result) {
|
||||
const content = response.result.content[0].text;
|
||||
resolve(JSON.parse(content));
|
||||
return;
|
||||
} else if (response.error) {
|
||||
reject(new Error(response.error.message));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
reject(new Error('No valid response found in output:\n' + output));
|
||||
} catch (err) {
|
||||
reject(new Error(`Failed to parse response: ${err.message}\nOutput: ${output}`));
|
||||
}
|
||||
});
|
||||
|
||||
// Send request
|
||||
mcp.stdin.write(JSON.stringify(request) + '\n');
|
||||
mcp.stdin.end();
|
||||
});
|
||||
}
|
||||
|
||||
async function testEssentials() {
|
||||
log('\n🚀 Testing n8n MCP Essentials Implementation\n', colors.bright + colors.cyan);
|
||||
|
||||
try {
|
||||
// Test 1: Get node essentials
|
||||
log('1️⃣ Testing get_node_essentials for HTTP Request...', colors.yellow);
|
||||
const essentialsRequest = {
|
||||
jsonrpc: '2.0',
|
||||
method: 'tools/call',
|
||||
params: {
|
||||
name: 'get_node_essentials',
|
||||
arguments: {
|
||||
nodeType: 'nodes-base.httpRequest'
|
||||
}
|
||||
},
|
||||
id: 1
|
||||
};
|
||||
|
||||
const essentials = await runMCPRequest(essentialsRequest);
|
||||
|
||||
log('✅ Success! Got essentials:', colors.green);
|
||||
log(` Node Type: ${essentials.nodeType}`);
|
||||
log(` Display Name: ${essentials.displayName}`);
|
||||
log(` Required properties: ${essentials.requiredProperties?.map(p => p.name).join(', ') || 'None'}`);
|
||||
log(` Common properties: ${essentials.commonProperties?.map(p => p.name).join(', ') || 'None'}`);
|
||||
log(` Examples: ${Object.keys(essentials.examples || {}).join(', ')}`);
|
||||
|
||||
const essentialsSize = JSON.stringify(essentials).length;
|
||||
log(` Response size: ${(essentialsSize / 1024).toFixed(1)} KB`, colors.green);
|
||||
|
||||
// Test 2: Compare with full node info
|
||||
log('\n2️⃣ Getting full node info for comparison...', colors.yellow);
|
||||
const fullInfoRequest = {
|
||||
jsonrpc: '2.0',
|
||||
method: 'tools/call',
|
||||
params: {
|
||||
name: 'get_node_info',
|
||||
arguments: {
|
||||
nodeType: 'nodes-base.httpRequest'
|
||||
}
|
||||
},
|
||||
id: 2
|
||||
};
|
||||
|
||||
const fullInfo = await runMCPRequest(fullInfoRequest);
|
||||
const fullSize = JSON.stringify(fullInfo).length;
|
||||
|
||||
log('✅ Got full node info:', colors.green);
|
||||
log(` Properties count: ${fullInfo.properties?.length || 0}`);
|
||||
log(` Response size: ${(fullSize / 1024).toFixed(1)} KB`);
|
||||
|
||||
const reduction = ((fullSize - essentialsSize) / fullSize * 100).toFixed(1);
|
||||
log(`\n📊 Size Comparison:`, colors.bright);
|
||||
log(` Full response: ${(fullSize / 1024).toFixed(1)} KB`);
|
||||
log(` Essential response: ${(essentialsSize / 1024).toFixed(1)} KB`);
|
||||
log(` Size reduction: ${reduction}% 🎉`, colors.bright + colors.green);
|
||||
|
||||
// Test 3: Search properties
|
||||
log('\n3️⃣ Testing search_node_properties...', colors.yellow);
|
||||
const searchRequest = {
|
||||
jsonrpc: '2.0',
|
||||
method: 'tools/call',
|
||||
params: {
|
||||
name: 'search_node_properties',
|
||||
arguments: {
|
||||
nodeType: 'nodes-base.httpRequest',
|
||||
query: 'auth'
|
||||
}
|
||||
},
|
||||
id: 3
|
||||
};
|
||||
|
||||
const searchResults = await runMCPRequest(searchRequest);
|
||||
log('✅ Search completed:', colors.green);
|
||||
log(` Query: "${searchResults.query}"`);
|
||||
log(` Matches found: ${searchResults.totalMatches}`);
|
||||
if (searchResults.matches && searchResults.matches.length > 0) {
|
||||
log(' Top matches:');
|
||||
searchResults.matches.slice(0, 3).forEach(match => {
|
||||
log(` - ${match.name}: ${match.description || 'No description'}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Summary
|
||||
log('\n✨ All tests passed successfully!', colors.bright + colors.green);
|
||||
log('\n📋 Summary:', colors.bright);
|
||||
log(` - get_node_essentials works correctly`);
|
||||
log(` - Size reduction achieved: ${reduction}%`);
|
||||
log(` - Property search functioning`);
|
||||
log(` - Examples included in response`);
|
||||
|
||||
// Test more nodes
|
||||
log('\n4️⃣ Testing additional nodes...', colors.yellow);
|
||||
const additionalNodes = ['nodes-base.webhook', 'nodes-base.code', 'nodes-base.postgres'];
|
||||
const results = [];
|
||||
|
||||
for (const nodeType of additionalNodes) {
|
||||
try {
|
||||
const req = {
|
||||
jsonrpc: '2.0',
|
||||
method: 'tools/call',
|
||||
params: {
|
||||
name: 'get_node_essentials',
|
||||
arguments: { nodeType }
|
||||
},
|
||||
id: Math.random()
|
||||
};
|
||||
|
||||
const result = await runMCPRequest(req);
|
||||
const size = JSON.stringify(result).length;
|
||||
results.push({
|
||||
nodeType,
|
||||
success: true,
|
||||
propCount: (result.requiredProperties?.length || 0) + (result.commonProperties?.length || 0),
|
||||
size: (size / 1024).toFixed(1)
|
||||
});
|
||||
log(` ✅ ${nodeType}: ${results[results.length - 1].propCount} properties, ${results[results.length - 1].size} KB`);
|
||||
} catch (error) {
|
||||
results.push({ nodeType, success: false, error: error.message });
|
||||
log(` ❌ ${nodeType}: ${error.message}`, colors.red);
|
||||
}
|
||||
}
|
||||
|
||||
log('\n🎯 Implementation validated successfully!', colors.bright + colors.green);
|
||||
|
||||
} catch (error) {
|
||||
log(`\n❌ Test failed: ${error.message}`, colors.red);
|
||||
if (error.stack) {
|
||||
log('Stack trace:', colors.red);
|
||||
log(error.stack, colors.red);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the test
|
||||
testEssentials().catch(error => {
|
||||
console.error('Unhandled error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
282
scripts/test-essentials.ts
Executable file
282
scripts/test-essentials.ts
Executable file
@@ -0,0 +1,282 @@
|
||||
#!/usr/bin/env ts-node
|
||||
/**
|
||||
* Test script for validating the get_node_essentials tool
|
||||
*
|
||||
* This script:
|
||||
* 1. Compares get_node_essentials vs get_node_info response sizes
|
||||
* 2. Validates that essential properties are correctly extracted
|
||||
* 3. Checks that examples are properly generated
|
||||
* 4. Tests the property search functionality
|
||||
*/
|
||||
|
||||
import { N8NDocumentationMCPServer } from '../src/mcp/server-update';
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
// Color codes for terminal output
|
||||
const colors = {
|
||||
reset: '\x1b[0m',
|
||||
bright: '\x1b[1m',
|
||||
green: '\x1b[32m',
|
||||
red: '\x1b[31m',
|
||||
yellow: '\x1b[33m',
|
||||
blue: '\x1b[34m',
|
||||
cyan: '\x1b[36m'
|
||||
};
|
||||
|
||||
function log(message: string, color: string = colors.reset) {
|
||||
console.log(`${color}${message}${colors.reset}`);
|
||||
}
|
||||
|
||||
function logSection(title: string) {
|
||||
console.log('\n' + '='.repeat(60));
|
||||
log(title, colors.bright + colors.cyan);
|
||||
console.log('='.repeat(60));
|
||||
}
|
||||
|
||||
function formatBytes(bytes: number): string {
|
||||
if (bytes < 1024) return bytes + ' B';
|
||||
const kb = bytes / 1024;
|
||||
if (kb < 1024) return kb.toFixed(1) + ' KB';
|
||||
const mb = kb / 1024;
|
||||
return mb.toFixed(2) + ' MB';
|
||||
}
|
||||
|
||||
async function testNodeEssentials(server: N8NDocumentationMCPServer, nodeType: string) {
|
||||
logSection(`Testing ${nodeType}`);
|
||||
|
||||
try {
|
||||
// Get full node info
|
||||
const startFull = Date.now();
|
||||
const fullInfo = await server.executeTool('get_node_info', { nodeType });
|
||||
const fullTime = Date.now() - startFull;
|
||||
const fullSize = JSON.stringify(fullInfo).length;
|
||||
|
||||
// Get essential info
|
||||
const startEssential = Date.now();
|
||||
const essentialInfo = await server.executeTool('get_node_essentials', { nodeType });
|
||||
const essentialTime = Date.now() - startEssential;
|
||||
const essentialSize = JSON.stringify(essentialInfo).length;
|
||||
|
||||
// Calculate metrics
|
||||
const sizeReduction = ((fullSize - essentialSize) / fullSize * 100).toFixed(1);
|
||||
const speedImprovement = ((fullTime - essentialTime) / fullTime * 100).toFixed(1);
|
||||
|
||||
// Display results
|
||||
log(`\n📊 Size Comparison:`, colors.bright);
|
||||
log(` Full response: ${formatBytes(fullSize)}`, colors.yellow);
|
||||
log(` Essential response: ${formatBytes(essentialSize)}`, colors.green);
|
||||
log(` Size reduction: ${sizeReduction}% ✨`, colors.bright + colors.green);
|
||||
|
||||
log(`\n⚡ Performance:`, colors.bright);
|
||||
log(` Full response time: ${fullTime}ms`);
|
||||
log(` Essential response time: ${essentialTime}ms`);
|
||||
log(` Speed improvement: ${speedImprovement}%`, colors.green);
|
||||
|
||||
log(`\n📋 Property Count:`, colors.bright);
|
||||
const fullPropCount = fullInfo.properties?.length || 0;
|
||||
const essentialPropCount = (essentialInfo.requiredProperties?.length || 0) +
|
||||
(essentialInfo.commonProperties?.length || 0);
|
||||
log(` Full properties: ${fullPropCount}`);
|
||||
log(` Essential properties: ${essentialPropCount}`);
|
||||
log(` Properties removed: ${fullPropCount - essentialPropCount} (${((fullPropCount - essentialPropCount) / fullPropCount * 100).toFixed(1)}%)`, colors.green);
|
||||
|
||||
log(`\n🔧 Essential Properties:`, colors.bright);
|
||||
log(` Required: ${essentialInfo.requiredProperties?.map((p: any) => p.name).join(', ') || 'None'}`);
|
||||
log(` Common: ${essentialInfo.commonProperties?.map((p: any) => p.name).join(', ') || 'None'}`);
|
||||
|
||||
log(`\n📚 Examples:`, colors.bright);
|
||||
const examples = Object.keys(essentialInfo.examples || {});
|
||||
log(` Available examples: ${examples.join(', ') || 'None'}`);
|
||||
|
||||
if (essentialInfo.examples?.minimal) {
|
||||
log(` Minimal example properties: ${Object.keys(essentialInfo.examples.minimal).join(', ')}`);
|
||||
}
|
||||
|
||||
log(`\n📊 Metadata:`, colors.bright);
|
||||
log(` Total properties available: ${essentialInfo.metadata?.totalProperties || 0}`);
|
||||
log(` Is AI Tool: ${essentialInfo.metadata?.isAITool ? 'Yes' : 'No'}`);
|
||||
log(` Is Trigger: ${essentialInfo.metadata?.isTrigger ? 'Yes' : 'No'}`);
|
||||
log(` Has Credentials: ${essentialInfo.metadata?.hasCredentials ? 'Yes' : 'No'}`);
|
||||
|
||||
// Test property search
|
||||
const searchTerms = ['auth', 'header', 'body', 'json'];
|
||||
log(`\n🔍 Property Search Test:`, colors.bright);
|
||||
|
||||
for (const term of searchTerms) {
|
||||
try {
|
||||
const searchResult = await server.executeTool('search_node_properties', {
|
||||
nodeType,
|
||||
query: term,
|
||||
maxResults: 5
|
||||
});
|
||||
log(` "${term}": Found ${searchResult.totalMatches} properties`);
|
||||
} catch (error) {
|
||||
log(` "${term}": Search failed`, colors.red);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
nodeType,
|
||||
fullSize,
|
||||
essentialSize,
|
||||
sizeReduction: parseFloat(sizeReduction),
|
||||
fullPropCount,
|
||||
essentialPropCount,
|
||||
success: true
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
log(`❌ Error testing ${nodeType}: ${error}`, colors.red);
|
||||
return {
|
||||
nodeType,
|
||||
fullSize: 0,
|
||||
essentialSize: 0,
|
||||
sizeReduction: 0,
|
||||
fullPropCount: 0,
|
||||
essentialPropCount: 0,
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
logSection('n8n MCP Essentials Tool Test Suite');
|
||||
|
||||
try {
|
||||
// Initialize server
|
||||
log('\n🚀 Initializing MCP server...', colors.cyan);
|
||||
const server = new N8NDocumentationMCPServer();
|
||||
|
||||
// Wait for initialization
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// Test nodes
|
||||
const testNodes = [
|
||||
'nodes-base.httpRequest',
|
||||
'nodes-base.webhook',
|
||||
'nodes-base.code',
|
||||
'nodes-base.set',
|
||||
'nodes-base.if',
|
||||
'nodes-base.postgres',
|
||||
'nodes-base.openAi',
|
||||
'nodes-base.googleSheets',
|
||||
'nodes-base.slack',
|
||||
'nodes-base.merge'
|
||||
];
|
||||
|
||||
const results = [];
|
||||
|
||||
for (const nodeType of testNodes) {
|
||||
const result = await testNodeEssentials(server, nodeType);
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
// Summary
|
||||
logSection('Test Summary');
|
||||
|
||||
const successful = results.filter(r => r.success);
|
||||
const totalFullSize = successful.reduce((sum, r) => sum + r.fullSize, 0);
|
||||
const totalEssentialSize = successful.reduce((sum, r) => sum + r.essentialSize, 0);
|
||||
const avgReduction = successful.reduce((sum, r) => sum + r.sizeReduction, 0) / successful.length;
|
||||
|
||||
log(`\n✅ Successful tests: ${successful.length}/${results.length}`, colors.green);
|
||||
|
||||
if (successful.length > 0) {
|
||||
log(`\n📊 Overall Statistics:`, colors.bright);
|
||||
log(` Total full size: ${formatBytes(totalFullSize)}`);
|
||||
log(` Total essential size: ${formatBytes(totalEssentialSize)}`);
|
||||
log(` Average reduction: ${avgReduction.toFixed(1)}%`, colors.bright + colors.green);
|
||||
|
||||
log(`\n🏆 Best Performers:`, colors.bright);
|
||||
const sorted = successful.sort((a, b) => b.sizeReduction - a.sizeReduction);
|
||||
sorted.slice(0, 3).forEach((r, i) => {
|
||||
log(` ${i + 1}. ${r.nodeType}: ${r.sizeReduction}% reduction (${formatBytes(r.fullSize)} → ${formatBytes(r.essentialSize)})`);
|
||||
});
|
||||
}
|
||||
|
||||
const failed = results.filter(r => !r.success);
|
||||
if (failed.length > 0) {
|
||||
log(`\n❌ Failed tests:`, colors.red);
|
||||
failed.forEach(r => {
|
||||
log(` - ${r.nodeType}: ${r.error}`, colors.red);
|
||||
});
|
||||
}
|
||||
|
||||
// Save detailed results
|
||||
const reportPath = join(process.cwd(), 'test-results-essentials.json');
|
||||
writeFileSync(reportPath, JSON.stringify({
|
||||
timestamp: new Date().toISOString(),
|
||||
summary: {
|
||||
totalTests: results.length,
|
||||
successful: successful.length,
|
||||
failed: failed.length,
|
||||
averageReduction: avgReduction,
|
||||
totalFullSize,
|
||||
totalEssentialSize
|
||||
},
|
||||
results
|
||||
}, null, 2));
|
||||
|
||||
log(`\n📄 Detailed results saved to: ${reportPath}`, colors.cyan);
|
||||
|
||||
// Recommendations
|
||||
logSection('Recommendations');
|
||||
|
||||
if (avgReduction > 90) {
|
||||
log('✨ Excellent! The essentials tool is achieving >90% size reduction.', colors.green);
|
||||
} else if (avgReduction > 80) {
|
||||
log('👍 Good! The essentials tool is achieving 80-90% size reduction.', colors.yellow);
|
||||
log(' Consider reviewing nodes with lower reduction rates.');
|
||||
} else {
|
||||
log('⚠️ The average size reduction is below 80%.', colors.yellow);
|
||||
log(' Review the essential property lists for optimization.');
|
||||
}
|
||||
|
||||
// Test specific functionality
|
||||
logSection('Testing Advanced Features');
|
||||
|
||||
// Test error handling
|
||||
log('\n🧪 Testing error handling...', colors.cyan);
|
||||
try {
|
||||
await server.executeTool('get_node_essentials', { nodeType: 'non-existent-node' });
|
||||
log(' ❌ Error handling failed - should have thrown error', colors.red);
|
||||
} catch (error) {
|
||||
log(' ✅ Error handling works correctly', colors.green);
|
||||
}
|
||||
|
||||
// Test alternative node type formats
|
||||
log('\n🧪 Testing alternative node type formats...', colors.cyan);
|
||||
const alternativeFormats = [
|
||||
{ input: 'httpRequest', expected: 'nodes-base.httpRequest' },
|
||||
{ input: 'nodes-base.httpRequest', expected: 'nodes-base.httpRequest' },
|
||||
{ input: 'HTTPREQUEST', expected: 'nodes-base.httpRequest' }
|
||||
];
|
||||
|
||||
for (const format of alternativeFormats) {
|
||||
try {
|
||||
const result = await server.executeTool('get_node_essentials', { nodeType: format.input });
|
||||
if (result.nodeType === format.expected) {
|
||||
log(` ✅ "${format.input}" → "${format.expected}"`, colors.green);
|
||||
} else {
|
||||
log(` ❌ "${format.input}" → "${result.nodeType}" (expected "${format.expected}")`, colors.red);
|
||||
}
|
||||
} catch (error) {
|
||||
log(` ❌ "${format.input}" → Error: ${error}`, colors.red);
|
||||
}
|
||||
}
|
||||
|
||||
log('\n✨ Test suite completed!', colors.bright + colors.green);
|
||||
|
||||
} catch (error) {
|
||||
log(`\n❌ Fatal error: ${error}`, colors.red);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the test
|
||||
main().catch(error => {
|
||||
console.error('Unhandled error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
101
scripts/test-final.js
Normal file
101
scripts/test-final.js
Normal file
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Final validation test
|
||||
*/
|
||||
|
||||
const { N8NDocumentationMCPServer } = require('../dist/mcp/server-update');
|
||||
|
||||
const colors = {
|
||||
green: '\x1b[32m',
|
||||
red: '\x1b[31m',
|
||||
yellow: '\x1b[33m',
|
||||
cyan: '\x1b[36m',
|
||||
reset: '\x1b[0m',
|
||||
bright: '\x1b[1m'
|
||||
};
|
||||
|
||||
async function testNode(server, nodeType) {
|
||||
console.log(`\n${colors.cyan}Testing ${nodeType}...${colors.reset}`);
|
||||
|
||||
try {
|
||||
// Get essentials
|
||||
const essentials = await server.executeTool('get_node_essentials', { nodeType });
|
||||
|
||||
// Get full info for comparison
|
||||
const fullInfo = await server.executeTool('get_node_info', { nodeType });
|
||||
|
||||
const essentialSize = JSON.stringify(essentials).length;
|
||||
const fullSize = JSON.stringify(fullInfo).length;
|
||||
const reduction = ((fullSize - essentialSize) / fullSize * 100).toFixed(1);
|
||||
|
||||
console.log(`✅ ${nodeType}:`);
|
||||
console.log(` Required: ${essentials.requiredProperties?.map(p => p.name).join(', ') || 'none'}`);
|
||||
console.log(` Common: ${essentials.commonProperties?.map(p => p.name).join(', ') || 'none'}`);
|
||||
console.log(` Size: ${(fullSize / 1024).toFixed(1)}KB → ${(essentialSize / 1024).toFixed(1)}KB (${reduction}% reduction)`);
|
||||
console.log(` Examples: ${Object.keys(essentials.examples || {}).length}`);
|
||||
|
||||
return { success: true, reduction: parseFloat(reduction) };
|
||||
} catch (error) {
|
||||
console.log(`❌ ${nodeType}: ${error.message}`);
|
||||
return { success: false };
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log(`${colors.bright}${colors.cyan}🎯 Final Validation Test${colors.reset}\n`);
|
||||
|
||||
try {
|
||||
const server = new N8NDocumentationMCPServer();
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
const nodes = [
|
||||
'nodes-base.httpRequest',
|
||||
'nodes-base.webhook',
|
||||
'nodes-base.code',
|
||||
'nodes-base.set',
|
||||
'nodes-base.postgres',
|
||||
'nodes-base.slack',
|
||||
'nodes-base.openAi',
|
||||
'nodes-base.googleSheets'
|
||||
];
|
||||
|
||||
const results = [];
|
||||
|
||||
for (const node of nodes) {
|
||||
const result = await testNode(server, node);
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
// Summary
|
||||
console.log(`\n${colors.bright}📊 Summary${colors.reset}`);
|
||||
const successful = results.filter(r => r.success);
|
||||
const avgReduction = successful.reduce((sum, r) => sum + r.reduction, 0) / successful.length;
|
||||
|
||||
console.log(`✅ Successful: ${successful.length}/${results.length}`);
|
||||
console.log(`📉 Average size reduction: ${avgReduction.toFixed(1)}%`);
|
||||
|
||||
// Test property search
|
||||
console.log(`\n${colors.bright}🔍 Testing Property Search${colors.reset}`);
|
||||
const searchResult = await server.executeTool('search_node_properties', {
|
||||
nodeType: 'nodes-base.httpRequest',
|
||||
query: 'auth'
|
||||
});
|
||||
console.log(`✅ Found ${searchResult.totalMatches} properties matching "auth"`);
|
||||
searchResult.matches.slice(0, 3).forEach(m => {
|
||||
console.log(` - ${m.name}: ${m.type}`);
|
||||
});
|
||||
|
||||
console.log(`\n${colors.bright}${colors.green}✨ Implementation validated successfully!${colors.reset}`);
|
||||
console.log('\nThe MCP essentials tools are working correctly with:');
|
||||
console.log(`- ${avgReduction.toFixed(1)}% average size reduction`);
|
||||
console.log('- Property filtering working');
|
||||
console.log('- Examples included');
|
||||
console.log('- Search functionality operational');
|
||||
|
||||
} catch (error) {
|
||||
console.error(`${colors.red}Fatal error: ${error.message}${colors.reset}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
59
scripts/test-node-info.js
Normal file
59
scripts/test-node-info.js
Normal file
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Test get_node_info to diagnose timeout issues
|
||||
*/
|
||||
|
||||
const { N8NDocumentationMCPServer } = require('../dist/mcp/server-update');
|
||||
|
||||
async function testNodeInfo() {
|
||||
console.log('🔍 Testing get_node_info...\n');
|
||||
|
||||
try {
|
||||
const server = new N8NDocumentationMCPServer();
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
const nodes = [
|
||||
'nodes-base.httpRequest',
|
||||
'nodes-base.webhook',
|
||||
'nodes-langchain.agent'
|
||||
];
|
||||
|
||||
for (const nodeType of nodes) {
|
||||
console.log(`Testing ${nodeType}...`);
|
||||
const start = Date.now();
|
||||
|
||||
try {
|
||||
const result = await server.executeTool('get_node_info', { nodeType });
|
||||
const elapsed = Date.now() - start;
|
||||
const size = JSON.stringify(result).length;
|
||||
|
||||
console.log(`✅ Success in ${elapsed}ms`);
|
||||
console.log(` Size: ${(size / 1024).toFixed(1)}KB`);
|
||||
console.log(` Properties: ${result.properties?.length || 0}`);
|
||||
console.log(` Operations: ${result.operations?.length || 0}`);
|
||||
|
||||
// Check for issues
|
||||
if (size > 50000) {
|
||||
console.log(` ⚠️ WARNING: Response over 50KB!`);
|
||||
}
|
||||
|
||||
// Check property quality
|
||||
const propsWithoutDesc = result.properties?.filter(p => !p.description && !p.displayName).length || 0;
|
||||
if (propsWithoutDesc > 0) {
|
||||
console.log(` ⚠️ ${propsWithoutDesc} properties without descriptions`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
const elapsed = Date.now() - start;
|
||||
console.log(`❌ Failed after ${elapsed}ms: ${error.message}`);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fatal error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
testNodeInfo().catch(console.error);
|
||||
Reference in New Issue
Block a user