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:
czlonkowski
2025-06-16 12:37:45 +02:00
parent 4cfc3cc5c8
commit 1884d5babf
28 changed files with 8122 additions and 4 deletions

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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);