feat: Complete overhaul to enhanced documentation-only MCP server

- Removed all workflow execution capabilities per user requirements
- Implemented enhanced documentation extraction with operations and API mappings
- Fixed credential code extraction for all nodes
- Fixed package info extraction (name and version)
- Enhanced operations parser to handle n8n markdown format
- Fixed documentation search to prioritize app nodes over trigger nodes
- Comprehensive test coverage for Slack node extraction
- All node information now includes:
  - Complete operations list (42 for Slack)
  - API method mappings with documentation URLs
  - Source code and credential definitions
  - Package metadata
  - Related resources and templates

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
czlonkowski
2025-06-08 11:07:51 +00:00
parent 887e98ca0b
commit 3d7fdeba02
48 changed files with 9247 additions and 11057 deletions

51
tests/debug-slack-doc.js Normal file
View File

@@ -0,0 +1,51 @@
#!/usr/bin/env node
const { execSync } = require('child_process');
const path = require('path');
const tempDir = path.join(process.cwd(), 'temp', 'n8n-docs');
console.log('🔍 Debugging Slack documentation search...\n');
// Search for all Slack related files
console.log('All Slack-related markdown files:');
try {
const allSlackFiles = execSync(
`find ${tempDir}/docs/integrations/builtin -name "*slack*.md" -type f`,
{ encoding: 'utf-8' }
).trim().split('\n');
allSlackFiles.forEach(file => {
console.log(` - ${file}`);
});
} catch (error) {
console.log(' No files found');
}
console.log('\n📄 Checking file paths:');
const possiblePaths = [
'docs/integrations/builtin/app-nodes/n8n-nodes-base.Slack.md',
'docs/integrations/builtin/app-nodes/n8n-nodes-base.slack.md',
'docs/integrations/builtin/core-nodes/n8n-nodes-base.Slack.md',
'docs/integrations/builtin/core-nodes/n8n-nodes-base.slack.md',
'docs/integrations/builtin/trigger-nodes/n8n-nodes-base.Slack.md',
'docs/integrations/builtin/trigger-nodes/n8n-nodes-base.slack.md',
'docs/integrations/builtin/credentials/slack.md',
];
const fs = require('fs');
possiblePaths.forEach(p => {
const fullPath = path.join(tempDir, p);
const exists = fs.existsSync(fullPath);
console.log(` ${exists ? '✓' : '✗'} ${p}`);
if (exists) {
// Read first few lines
const content = fs.readFileSync(fullPath, 'utf-8');
const lines = content.split('\n').slice(0, 10);
const title = lines.find(l => l.includes('title:'));
if (title) {
console.log(` Title: ${title.trim()}`);
}
}
});

View File

@@ -0,0 +1,112 @@
#!/usr/bin/env node
const { EnhancedDocumentationFetcher } = require('../dist/utils/enhanced-documentation-fetcher');
async function demoEnhancedDocumentation() {
console.log('=== Enhanced Documentation Parser Demo ===\n');
console.log('This demo shows how the enhanced DocumentationFetcher extracts rich content from n8n documentation.\n');
const fetcher = new EnhancedDocumentationFetcher();
try {
// Demo 1: Slack node (complex app node with many operations)
console.log('1. SLACK NODE DOCUMENTATION');
console.log('=' .repeat(50));
const slackDoc = await fetcher.getEnhancedNodeDocumentation('n8n-nodes-base.slack');
if (slackDoc) {
console.log('\n📄 Basic Information:');
console.log(` • Title: ${slackDoc.title}`);
console.log(` • Description: ${slackDoc.description}`);
console.log(` • URL: ${slackDoc.url}`);
console.log('\n📊 Content Statistics:');
console.log(` • Operations: ${slackDoc.operations?.length || 0} operations across multiple resources`);
console.log(` • API Methods: ${slackDoc.apiMethods?.length || 0} mapped to Slack API endpoints`);
console.log(` • Examples: ${slackDoc.examples?.length || 0} code examples`);
console.log(` • Resources: ${slackDoc.relatedResources?.length || 0} related documentation links`);
console.log(` • Scopes: ${slackDoc.requiredScopes?.length || 0} OAuth scopes`);
// Show operations breakdown
if (slackDoc.operations && slackDoc.operations.length > 0) {
console.log('\n🔧 Operations by Resource:');
const resourceMap = new Map();
slackDoc.operations.forEach(op => {
if (!resourceMap.has(op.resource)) {
resourceMap.set(op.resource, []);
}
resourceMap.get(op.resource).push(op);
});
for (const [resource, ops] of resourceMap) {
console.log(`\n ${resource} (${ops.length} operations):`);
ops.slice(0, 5).forEach(op => {
console.log(`${op.operation}: ${op.description}`);
});
if (ops.length > 5) {
console.log(` ... and ${ops.length - 5} more`);
}
}
}
// Show API method mappings
if (slackDoc.apiMethods && slackDoc.apiMethods.length > 0) {
console.log('\n🔗 API Method Mappings (sample):');
slackDoc.apiMethods.slice(0, 5).forEach(api => {
console.log(`${api.resource}.${api.operation}${api.apiMethod}`);
console.log(` URL: ${api.apiUrl}`);
});
if (slackDoc.apiMethods.length > 5) {
console.log(` ... and ${slackDoc.apiMethods.length - 5} more mappings`);
}
}
}
// Demo 2: If node (core node with conditions)
console.log('\n\n2. IF NODE DOCUMENTATION');
console.log('=' .repeat(50));
const ifDoc = await fetcher.getEnhancedNodeDocumentation('n8n-nodes-base.if');
if (ifDoc) {
console.log('\n📄 Basic Information:');
console.log(` • Title: ${ifDoc.title}`);
console.log(` • Description: ${ifDoc.description}`);
console.log(` • URL: ${ifDoc.url}`);
if (ifDoc.relatedResources && ifDoc.relatedResources.length > 0) {
console.log('\n📚 Related Resources:');
ifDoc.relatedResources.forEach(res => {
console.log(`${res.title} (${res.type})`);
console.log(` ${res.url}`);
});
}
}
// Demo 3: Summary of enhanced parsing capabilities
console.log('\n\n3. ENHANCED PARSING CAPABILITIES');
console.log('=' .repeat(50));
console.log('\nThe enhanced DocumentationFetcher can extract:');
console.log(' ✓ Markdown frontmatter (metadata, tags, priority)');
console.log(' ✓ Operations with resource grouping and descriptions');
console.log(' ✓ API method mappings from markdown tables');
console.log(' ✓ Code examples (JSON, JavaScript, YAML)');
console.log(' ✓ Template references');
console.log(' ✓ Related resources and documentation links');
console.log(' ✓ Required OAuth scopes');
console.log('\nThis rich content enables AI agents to:');
console.log(' • Understand node capabilities in detail');
console.log(' • Map operations to actual API endpoints');
console.log(' • Provide accurate examples and usage patterns');
console.log(' • Navigate related documentation');
console.log(' • Understand authentication requirements');
} catch (error) {
console.error('\nError:', error);
} finally {
await fetcher.cleanup();
console.log('\n\n✓ Demo completed');
}
}
// Run the demo
demoEnhancedDocumentation().catch(console.error);

File diff suppressed because one or more lines are too long

94
tests/test-complete-fix.js Executable file
View File

@@ -0,0 +1,94 @@
#!/usr/bin/env node
const { NodeDocumentationService } = require('../dist/services/node-documentation-service');
async function testCompleteFix() {
console.log('=== Testing Complete Documentation Fix ===\n');
const service = new NodeDocumentationService('./data/test-nodes-v2.db');
try {
// First check if we have any nodes
const existingNodes = await service.listNodes();
console.log(`📊 Current database has ${existingNodes.length} nodes`);
if (existingNodes.length === 0) {
console.log('\n🔄 Rebuilding database with fixed documentation fetcher...');
const stats = await service.rebuildDatabase();
console.log(`\n✅ Rebuild complete:`);
console.log(` - Total nodes found: ${stats.total}`);
console.log(` - Successfully processed: ${stats.successful}`);
console.log(` - Failed: ${stats.failed}`);
if (stats.errors.length > 0) {
console.log('\n⚠ Errors encountered:');
stats.errors.slice(0, 5).forEach(err => console.log(` - ${err}`));
}
}
// Test specific nodes
console.log('\n📋 Testing specific nodes:');
const testNodes = ['slack', 'if', 'httpRequest', 'webhook'];
for (const nodeName of testNodes) {
const nodeInfo = await service.getNodeInfo(`n8n-nodes-base.${nodeName}`);
if (nodeInfo) {
console.log(`\n${nodeInfo.displayName || nodeName}:`);
console.log(` - Type: ${nodeInfo.nodeType}`);
console.log(` - Description: ${nodeInfo.description?.substring(0, 80)}...`);
console.log(` - Has source code: ${!!nodeInfo.sourceCode}`);
console.log(` - Has documentation: ${!!nodeInfo.documentation}`);
console.log(` - Documentation URL: ${nodeInfo.documentationUrl || 'N/A'}`);
console.log(` - Has example: ${!!nodeInfo.exampleWorkflow}`);
console.log(` - Category: ${nodeInfo.category || 'N/A'}`);
// Check if it's getting the right documentation
if (nodeInfo.documentation) {
const isCredentialDoc = nodeInfo.documentation.includes('credentials') &&
!nodeInfo.documentation.includes('node documentation');
console.log(` - Is credential doc: ${isCredentialDoc} ${isCredentialDoc ? '❌' : '✅'}`);
}
} else {
console.log(`\n${nodeName}: Not found in database`);
}
}
// Test search functionality
console.log('\n🔍 Testing search functionality:');
const searchTests = [
{ query: 'webhook', label: 'Webhook nodes' },
{ query: 'http', label: 'HTTP nodes' },
{ query: 'slack', label: 'Slack nodes' }
];
for (const test of searchTests) {
const results = await service.searchNodes({ query: test.query });
console.log(`\n ${test.label}: ${results.length} results`);
results.slice(0, 3).forEach(node => {
console.log(` - ${node.displayName} (${node.nodeType})`);
});
}
// Get final statistics
console.log('\n📊 Final database statistics:');
const stats = service.getStatistics();
console.log(` - Total nodes: ${stats.totalNodes}`);
console.log(` - Nodes with documentation: ${stats.nodesWithDocs}`);
console.log(` - Nodes with examples: ${stats.nodesWithExamples}`);
console.log(` - Trigger nodes: ${stats.triggerNodes}`);
console.log(` - Webhook nodes: ${stats.webhookNodes}`);
console.log('\n✅ All tests completed!');
} catch (error) {
console.error('\n❌ Test failed:', error);
process.exit(1);
} finally {
service.close();
}
}
testCompleteFix().catch(console.error);

View File

@@ -0,0 +1,38 @@
#!/usr/bin/env node
const { EnhancedDocumentationFetcher } = require('../dist/utils/enhanced-documentation-fetcher');
async function debugTest() {
console.log('=== Debug Enhanced Documentation ===\n');
const fetcher = new EnhancedDocumentationFetcher();
try {
await fetcher.ensureDocsRepository();
// Test Slack documentation parsing
console.log('Testing Slack documentation...');
const slackDoc = await fetcher.getEnhancedNodeDocumentation('n8n-nodes-base.slack');
if (slackDoc) {
console.log('\nSlack Documentation:');
console.log('- Operations found:', slackDoc.operations?.length || 0);
// Show raw markdown around operations section
const operationsIndex = slackDoc.markdown.indexOf('## Operations');
if (operationsIndex > -1) {
console.log('\nRaw markdown around Operations section:');
console.log('---');
console.log(slackDoc.markdown.substring(operationsIndex, operationsIndex + 1000));
console.log('---');
}
}
} catch (error) {
console.error('Error:', error);
} finally {
await fetcher.cleanup();
}
}
debugTest().catch(console.error);

57
tests/test-docs-fix.js Executable file
View File

@@ -0,0 +1,57 @@
#!/usr/bin/env node
const { DocumentationFetcher } = require('../dist/utils/documentation-fetcher');
const { NodeSourceExtractor } = require('../dist/utils/node-source-extractor');
async function testDocsFix() {
console.log('=== Testing Documentation Fix ===\n');
const docsFetcher = new DocumentationFetcher();
const extractor = new NodeSourceExtractor();
try {
// Test nodes
const testNodes = [
'n8n-nodes-base.slack',
'n8n-nodes-base.if',
'n8n-nodes-base.httpRequest',
'n8n-nodes-base.webhook'
];
for (const nodeType of testNodes) {
console.log(`\n📋 Testing ${nodeType}:`);
// Test documentation fetching
const docs = await docsFetcher.getNodeDocumentation(nodeType);
if (docs) {
console.log(` ✅ Documentation found`);
console.log(` 📄 URL: ${docs.url}`);
const titleMatch = docs.markdown.match(/title:\s*(.+)/);
if (titleMatch) {
console.log(` 📝 Title: ${titleMatch[1]}`);
}
console.log(` 📏 Length: ${docs.markdown.length} characters`);
console.log(` 🔧 Has examples: ${docs.examples && docs.examples.length > 0}`);
} else {
console.log(` ❌ No documentation found`);
}
// Test source extraction
try {
const source = await extractor.extractNodeSource(nodeType);
console.log(` ✅ Source code found at: ${source.location}`);
} catch (error) {
console.log(` ❌ Source extraction failed: ${error.message}`);
}
}
console.log('\n✅ Test completed!');
} catch (error) {
console.error('\n❌ Test failed:', error);
} finally {
await docsFetcher.cleanup();
}
}
testDocsFix().catch(console.error);

View File

@@ -0,0 +1,141 @@
#!/usr/bin/env node
const { EnhancedDocumentationFetcher } = require('../dist/utils/enhanced-documentation-fetcher');
const { EnhancedSQLiteStorageService } = require('../dist/services/enhanced-sqlite-storage-service');
const { NodeSourceExtractor } = require('../dist/utils/node-source-extractor');
async function testEnhancedDocumentation() {
console.log('=== Testing Enhanced Documentation Fetcher ===\n');
const fetcher = new EnhancedDocumentationFetcher();
const storage = new EnhancedSQLiteStorageService('./data/test-enhanced.db');
const extractor = new NodeSourceExtractor();
try {
// Test 1: Fetch and parse Slack node documentation
console.log('1. Testing Slack node documentation parsing...');
const slackDoc = await fetcher.getEnhancedNodeDocumentation('n8n-nodes-base.slack');
if (slackDoc) {
console.log('\n✓ Slack Documentation Found:');
console.log(` - Title: ${slackDoc.title}`);
console.log(` - Description: ${slackDoc.description}`);
console.log(` - URL: ${slackDoc.url}`);
console.log(` - Operations: ${slackDoc.operations?.length || 0} found`);
console.log(` - API Methods: ${slackDoc.apiMethods?.length || 0} found`);
console.log(` - Examples: ${slackDoc.examples?.length || 0} found`);
console.log(` - Required Scopes: ${slackDoc.requiredScopes?.length || 0} found`);
// Show sample operations
if (slackDoc.operations && slackDoc.operations.length > 0) {
console.log('\n Sample Operations:');
slackDoc.operations.slice(0, 5).forEach(op => {
console.log(` - ${op.resource}.${op.operation}: ${op.description}`);
});
}
// Show sample API mappings
if (slackDoc.apiMethods && slackDoc.apiMethods.length > 0) {
console.log('\n Sample API Mappings:');
slackDoc.apiMethods.slice(0, 5).forEach(api => {
console.log(` - ${api.resource}.${api.operation}${api.apiMethod}`);
});
}
} else {
console.log('✗ Slack documentation not found');
}
// Test 2: Test with If node (core node)
console.log('\n\n2. Testing If node documentation parsing...');
const ifDoc = await fetcher.getEnhancedNodeDocumentation('n8n-nodes-base.if');
if (ifDoc) {
console.log('\n✓ If Documentation Found:');
console.log(` - Title: ${ifDoc.title}`);
console.log(` - Description: ${ifDoc.description}`);
console.log(` - Examples: ${ifDoc.examples?.length || 0} found`);
console.log(` - Related Resources: ${ifDoc.relatedResources?.length || 0} found`);
}
// Test 3: Store node with documentation
console.log('\n\n3. Testing node storage with documentation...');
// Extract a node
const nodeInfo = await extractor.extractNodeSource('n8n-nodes-base.slack');
if (nodeInfo) {
const storedNode = await storage.storeNodeWithDocumentation(nodeInfo);
console.log('\n✓ Node stored successfully:');
console.log(` - Node Type: ${storedNode.nodeType}`);
console.log(` - Has Documentation: ${!!storedNode.documentationMarkdown}`);
console.log(` - Operations: ${storedNode.operationCount}`);
console.log(` - API Methods: ${storedNode.apiMethodCount}`);
console.log(` - Examples: ${storedNode.exampleCount}`);
console.log(` - Resources: ${storedNode.resourceCount}`);
console.log(` - Scopes: ${storedNode.scopeCount}`);
// Get detailed operations
const operations = await storage.getNodeOperations(storedNode.id);
if (operations.length > 0) {
console.log('\n Stored Operations (first 5):');
operations.slice(0, 5).forEach(op => {
console.log(` - ${op.resource}.${op.operation}: ${op.description}`);
});
}
// Get examples
const examples = await storage.getNodeExamples(storedNode.id);
if (examples.length > 0) {
console.log('\n Stored Examples:');
examples.forEach(ex => {
console.log(` - ${ex.title || 'Untitled'} (${ex.type}): ${ex.code.length} chars`);
});
}
}
// Test 4: Search with enhanced FTS
console.log('\n\n4. Testing enhanced search...');
const searchResults = await storage.searchNodes({ query: 'slack message' });
console.log(`\n✓ Search Results for "slack message": ${searchResults.length} nodes found`);
if (searchResults.length > 0) {
console.log(' First result:');
const result = searchResults[0];
console.log(` - ${result.displayName || result.name} (${result.nodeType})`);
console.log(` - Documentation: ${result.documentationTitle || 'No title'}`);
}
// Test 5: Get statistics
console.log('\n\n5. Getting enhanced statistics...');
const stats = await storage.getEnhancedStatistics();
console.log('\n✓ Enhanced Statistics:');
console.log(` - Total Nodes: ${stats.totalNodes}`);
console.log(` - Nodes with Documentation: ${stats.nodesWithDocumentation}`);
console.log(` - Documentation Coverage: ${stats.documentationCoverage}%`);
console.log(` - Total Operations: ${stats.totalOperations}`);
console.log(` - Total API Methods: ${stats.totalApiMethods}`);
console.log(` - Total Examples: ${stats.totalExamples}`);
console.log(` - Total Resources: ${stats.totalResources}`);
console.log(` - Total Scopes: ${stats.totalScopes}`);
if (stats.topDocumentedNodes && stats.topDocumentedNodes.length > 0) {
console.log('\n Top Documented Nodes:');
stats.topDocumentedNodes.slice(0, 3).forEach(node => {
console.log(` - ${node.display_name || node.name}: ${node.operation_count} operations, ${node.example_count} examples`);
});
}
} catch (error) {
console.error('Error during testing:', error);
} finally {
// Cleanup
storage.close();
await fetcher.cleanup();
console.log('\n\n✓ Test completed and cleaned up');
}
}
// Run the test
testEnhancedDocumentation().catch(console.error);

View File

@@ -0,0 +1,156 @@
#!/usr/bin/env node
const { EnhancedDocumentationFetcher } = require('../dist/utils/enhanced-documentation-fetcher');
const { EnhancedSQLiteStorageService } = require('../dist/services/enhanced-sqlite-storage-service');
const { NodeSourceExtractor } = require('../dist/utils/node-source-extractor');
async function testEnhancedDocumentation() {
console.log('=== Enhanced Documentation Parser Test ===\n');
const fetcher = new EnhancedDocumentationFetcher();
const extractor = new NodeSourceExtractor();
try {
// Test 1: Parse Slack documentation
console.log('1. Parsing Slack node documentation...');
const slackDoc = await fetcher.getEnhancedNodeDocumentation('n8n-nodes-base.slack');
if (slackDoc) {
console.log('\n✓ Slack Documentation Parsed:');
console.log(` Title: ${slackDoc.title}`);
console.log(` Description: ${slackDoc.description?.substring(0, 100)}...`);
console.log(` URL: ${slackDoc.url}`);
console.log(` Operations: ${slackDoc.operations?.length || 0} found`);
console.log(` API Methods: ${slackDoc.apiMethods?.length || 0} found`);
console.log(` Related Resources: ${slackDoc.relatedResources?.length || 0} found`);
// Show sample operations
if (slackDoc.operations && slackDoc.operations.length > 0) {
console.log('\n Sample Operations (first 10):');
slackDoc.operations.slice(0, 10).forEach((op, i) => {
console.log(` ${i + 1}. ${op.resource}.${op.operation}: ${op.description}`);
});
}
// Show sample API mappings
if (slackDoc.apiMethods && slackDoc.apiMethods.length > 0) {
console.log('\n Sample API Method Mappings (first 5):');
slackDoc.apiMethods.slice(0, 5).forEach((api, i) => {
console.log(` ${i + 1}. ${api.resource}.${api.operation}${api.apiMethod} (${api.apiUrl})`);
});
}
// Show related resources
if (slackDoc.relatedResources && slackDoc.relatedResources.length > 0) {
console.log('\n Related Resources:');
slackDoc.relatedResources.forEach((res, i) => {
console.log(` ${i + 1}. ${res.title} (${res.type}): ${res.url}`);
});
}
}
// Test 2: Parse HTTP Request documentation (if available)
console.log('\n\n2. Parsing HTTP Request node documentation...');
const httpDoc = await fetcher.getEnhancedNodeDocumentation('n8n-nodes-base.httpRequest');
if (httpDoc) {
console.log('\n✓ HTTP Request Documentation Parsed:');
console.log(` Title: ${httpDoc.title}`);
console.log(` Examples: ${httpDoc.examples?.length || 0} found`);
if (httpDoc.examples && httpDoc.examples.length > 0) {
console.log('\n Code Examples:');
httpDoc.examples.forEach((ex, i) => {
console.log(` ${i + 1}. ${ex.title || 'Example'} (${ex.type}): ${ex.code.length} characters`);
});
}
} else {
console.log(' HTTP Request documentation not found');
}
// Test 3: Database storage test with smaller database
console.log('\n\n3. Testing enhanced database storage...');
const storage = new EnhancedSQLiteStorageService('./data/demo-enhanced.db');
try {
// Store Slack node with documentation
const slackNodeInfo = await extractor.extractNodeSource('n8n-nodes-base.slack');
if (slackNodeInfo) {
const storedNode = await storage.storeNodeWithDocumentation(slackNodeInfo);
console.log('\n✓ Slack node stored with documentation:');
console.log(` Node Type: ${storedNode.nodeType}`);
console.log(` Documentation: ${storedNode.documentationTitle || 'No title'}`);
console.log(` Operations stored: ${storedNode.operationCount}`);
console.log(` API methods stored: ${storedNode.apiMethodCount}`);
console.log(` Examples stored: ${storedNode.exampleCount}`);
console.log(` Resources stored: ${storedNode.resourceCount}`);
}
// Store a few more nodes
const nodeTypes = ['n8n-nodes-base.if', 'n8n-nodes-base.webhook'];
for (const nodeType of nodeTypes) {
try {
const nodeInfo = await extractor.extractNodeSource(nodeType);
if (nodeInfo) {
await storage.storeNodeWithDocumentation(nodeInfo);
console.log(` ✓ Stored ${nodeType}`);
}
} catch (e) {
console.log(` ✗ Failed to store ${nodeType}: ${e.message}`);
}
}
// Test search functionality
console.log('\n\n4. Testing enhanced search...');
const searchTests = [
{ query: 'slack', description: 'Search for "slack"' },
{ query: 'message send', description: 'Search for "message send"' },
{ query: 'webhook', description: 'Search for "webhook"' }
];
for (const test of searchTests) {
const results = await storage.searchNodes({ query: test.query });
console.log(`\n ${test.description}: ${results.length} results`);
if (results.length > 0) {
const first = results[0];
console.log(` Top result: ${first.displayName || first.name} (${first.nodeType})`);
if (first.documentationTitle) {
console.log(` Documentation: ${first.documentationTitle}`);
}
}
}
// Get final statistics
console.log('\n\n5. Database Statistics:');
const stats = await storage.getEnhancedStatistics();
console.log(` Total Nodes: ${stats.totalNodes}`);
console.log(` Nodes with Documentation: ${stats.nodesWithDocumentation} (${stats.documentationCoverage}% coverage)`);
console.log(` Total Operations: ${stats.totalOperations}`);
console.log(` Total API Methods: ${stats.totalApiMethods}`);
console.log(` Total Examples: ${stats.totalExamples}`);
console.log(` Total Resources: ${stats.totalResources}`);
if (stats.topDocumentedNodes && stats.topDocumentedNodes.length > 0) {
console.log('\n Best Documented Nodes:');
stats.topDocumentedNodes.forEach((node, i) => {
console.log(` ${i + 1}. ${node.display_name || node.name}: ${node.operation_count} operations, ${node.example_count} examples`);
});
}
} finally {
storage.close();
}
} catch (error) {
console.error('\nError:', error);
} finally {
await fetcher.cleanup();
console.log('\n\n✓ Test completed and cleaned up');
}
}
// Run the test
testEnhancedDocumentation().catch(console.error);

View File

@@ -0,0 +1,163 @@
#!/usr/bin/env node
const { DocumentationFetcher } = require('../dist/utils/documentation-fetcher');
const { NodeDocumentationService } = require('../dist/services/node-documentation-service');
async function testEnhancedIntegration() {
console.log('🧪 Testing Enhanced Documentation Integration...\n');
// Test 1: DocumentationFetcher backward compatibility
console.log('1⃣ Testing DocumentationFetcher backward compatibility...');
const docFetcher = new DocumentationFetcher();
try {
// Test getNodeDocumentation (backward compatible method)
const simpleDoc = await docFetcher.getNodeDocumentation('n8n-nodes-base.slack');
if (simpleDoc) {
console.log(' ✅ Simple documentation format works');
console.log(` - Has markdown: ${!!simpleDoc.markdown}`);
console.log(` - Has URL: ${!!simpleDoc.url}`);
console.log(` - Has examples: ${simpleDoc.examples?.length || 0}`);
}
// Test getEnhancedNodeDocumentation (new method)
const enhancedDoc = await docFetcher.getEnhancedNodeDocumentation('n8n-nodes-base.slack');
if (enhancedDoc) {
console.log(' ✅ Enhanced documentation format works');
console.log(` - Title: ${enhancedDoc.title || 'N/A'}`);
console.log(` - Operations: ${enhancedDoc.operations?.length || 0}`);
console.log(` - API Methods: ${enhancedDoc.apiMethods?.length || 0}`);
console.log(` - Examples: ${enhancedDoc.examples?.length || 0}`);
console.log(` - Templates: ${enhancedDoc.templates?.length || 0}`);
console.log(` - Related Resources: ${enhancedDoc.relatedResources?.length || 0}`);
}
} catch (error) {
console.error(' ❌ DocumentationFetcher test failed:', error.message);
}
// Test 2: NodeDocumentationService with enhanced fields
console.log('\n2⃣ Testing NodeDocumentationService enhanced schema...');
const docService = new NodeDocumentationService('data/test-enhanced-docs.db');
try {
// Store a test node with enhanced documentation
const testNode = {
nodeType: 'test.enhanced-node',
name: 'enhanced-node',
displayName: 'Enhanced Test Node',
description: 'A test node with enhanced documentation',
sourceCode: 'const testCode = "example";',
packageName: 'test-package',
documentation: '# Test Documentation',
documentationUrl: 'https://example.com/docs',
documentationTitle: 'Enhanced Test Node Documentation',
operations: [
{
resource: 'Message',
operation: 'Send',
description: 'Send a message'
}
],
apiMethods: [
{
resource: 'Message',
operation: 'Send',
apiMethod: 'chat.postMessage',
apiUrl: 'https://api.slack.com/methods/chat.postMessage'
}
],
documentationExamples: [
{
title: 'Send Message Example',
type: 'json',
code: '{"text": "Hello World"}'
}
],
templates: [
{
name: 'Basic Message Template',
description: 'Simple message sending template'
}
],
relatedResources: [
{
title: 'API Documentation',
url: 'https://api.slack.com',
type: 'api'
}
],
requiredScopes: ['chat:write'],
hasCredentials: true,
isTrigger: false,
isWebhook: false
};
await docService.storeNode(testNode);
console.log(' ✅ Stored node with enhanced documentation');
// Retrieve and verify
const retrieved = await docService.getNodeInfo('test.enhanced-node');
if (retrieved) {
console.log(' ✅ Retrieved node with enhanced fields:');
console.log(` - Has operations: ${!!retrieved.operations}`);
console.log(` - Has API methods: ${!!retrieved.apiMethods}`);
console.log(` - Has documentation examples: ${!!retrieved.documentationExamples}`);
console.log(` - Has templates: ${!!retrieved.templates}`);
console.log(` - Has related resources: ${!!retrieved.relatedResources}`);
console.log(` - Has required scopes: ${!!retrieved.requiredScopes}`);
}
// Test search
const searchResults = await docService.searchNodes({ query: 'enhanced' });
console.log(` ✅ Search found ${searchResults.length} results`);
} catch (error) {
console.error(' ❌ NodeDocumentationService test failed:', error.message);
} finally {
docService.close();
}
// Test 3: MCP Server integration
console.log('\n3⃣ Testing MCP Server integration...');
try {
const { N8NMCPServer } = require('../dist/mcp/server');
console.log(' ✅ MCP Server loads with enhanced documentation support');
// Check if new tools are available
const { n8nTools } = require('../dist/mcp/tools');
const enhancedTools = [
'get_node_documentation',
'search_node_documentation',
'get_node_operations',
'get_node_examples'
];
const hasAllTools = enhancedTools.every(toolName =>
n8nTools.some(tool => tool.name === toolName)
);
if (hasAllTools) {
console.log(' ✅ All enhanced documentation tools are available');
enhancedTools.forEach(toolName => {
const tool = n8nTools.find(t => t.name === toolName);
console.log(` - ${toolName}: ${tool.description}`);
});
} else {
console.log(' ⚠️ Some enhanced tools are missing');
}
} catch (error) {
console.error(' ❌ MCP Server integration test failed:', error.message);
}
console.log('\n✨ Enhanced documentation integration tests completed!');
// Cleanup
await docFetcher.cleanup();
}
// Run tests
testEnhancedIntegration().catch(error => {
console.error('Fatal error:', error);
process.exit(1);
});

View File

@@ -0,0 +1,30 @@
#!/usr/bin/env node
const { NodeSourceExtractor } = require('../dist/utils/node-source-extractor');
async function testPackageInfo() {
console.log('🧪 Testing Package Info Extraction\n');
const extractor = new NodeSourceExtractor();
const testNodes = [
'n8n-nodes-base.Slack',
'n8n-nodes-base.HttpRequest',
'n8n-nodes-base.Function'
];
for (const nodeType of testNodes) {
console.log(`\n📦 Testing ${nodeType}:`);
try {
const result = await extractor.extractNodeSource(nodeType);
console.log(` - Source Code: ${result.sourceCode ? '✅' : '❌'} (${result.sourceCode?.length || 0} bytes)`);
console.log(` - Credential Code: ${result.credentialCode ? '✅' : '❌'} (${result.credentialCode?.length || 0} bytes)`);
console.log(` - Package Name: ${result.packageInfo?.name || '❌ undefined'}`);
console.log(` - Package Version: ${result.packageInfo?.version || '❌ undefined'}`);
} catch (error) {
console.log(` ❌ Error: ${error.message}`);
}
}
}
testPackageInfo().catch(console.error);

View File

@@ -0,0 +1,82 @@
#!/usr/bin/env node
const markdown = `
## Operations
* **Channel**
* **Archive** a channel.
* **Close** a direct message or multi-person direct message.
* **Create** a public or private channel-based conversation.
* **Get** information about a channel.
* **Get Many**: Get a list of channels in Slack.
* **File**
* **Get** a file.
* **Get Many**: Get and filter team files.
* **Upload**: Create or upload an existing file.
## Templates and examples
`;
function extractOperations(markdown) {
const operations = [];
// Find operations section
const operationsMatch = markdown.match(/##\s+Operations\s*\n([\s\S]*?)(?=\n##|\n#|$)/i);
if (!operationsMatch) {
console.log('No operations section found');
return operations;
}
const operationsText = operationsMatch[1];
console.log('Operations text:', operationsText.substring(0, 200));
// Parse operation structure
let currentResource = null;
const lines = operationsText.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const trimmedLine = line.trim();
// Resource level (e.g., "* **Channel**")
if (trimmedLine.match(/^\*\s+\*\*([^*]+)\*\*/)) {
currentResource = trimmedLine.match(/^\*\s+\*\*([^*]+)\*\*/)[1].trim();
console.log(`Found resource: ${currentResource}`);
continue;
}
// Skip if we don't have a current resource
if (!currentResource) continue;
// Operation level - look for indented bullets (4 spaces + *)
if (line.match(/^\s{4}\*\s+/)) {
console.log(`Found operation line: "${line}"`);
// Extract operation name and description
const operationMatch = trimmedLine.match(/^\*\s+\*\*([^*]+)\*\*(.*)$/);
if (operationMatch) {
const operation = operationMatch[1].trim();
let description = operationMatch[2].trim();
// Clean up description
description = description.replace(/^:\s*/, '').replace(/\.$/, '').trim();
operations.push({
resource: currentResource,
operation,
description: description || operation,
});
console.log(` Parsed: ${operation} - ${description}`);
}
}
}
return operations;
}
const operations = extractOperations(markdown);
console.log('\nTotal operations found:', operations.length);
console.log('\nOperations:');
operations.forEach(op => {
console.log(`- ${op.resource}.${op.operation}: ${op.description}`);
});

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,760 @@
{
"totalTests": 6,
"passed": 6,
"failed": 0,
"startTime": "2025-06-08T10:57:55.233Z",
"endTime": "2025-06-08T10:57:59.249Z",
"tests": [
{
"name": "Basic Node Extraction",
"status": "passed",
"startTime": "2025-06-08T10:57:55.236Z",
"endTime": "2025-06-08T10:57:55.342Z",
"error": null,
"details": {
"results": [
{
"nodeType": "@n8n/n8n-nodes-langchain.Agent",
"extracted": false,
"error": "Node source code not found for: @n8n/n8n-nodes-langchain.Agent"
},
{
"nodeType": "n8n-nodes-base.Function",
"extracted": true,
"codeLength": 7449,
"hasCredentials": false,
"hasPackageInfo": true,
"location": "node_modules/n8n-nodes-base/dist/nodes/Function/Function.node.js"
},
{
"nodeType": "n8n-nodes-base.Webhook",
"extracted": true,
"codeLength": 10667,
"hasCredentials": false,
"hasPackageInfo": true,
"location": "node_modules/n8n-nodes-base/dist/nodes/Webhook/Webhook.node.js"
}
],
"successCount": 2,
"totalTested": 3
}
},
{
"name": "List Available Nodes",
"status": "passed",
"startTime": "2025-06-08T10:57:55.342Z",
"endTime": "2025-06-08T10:57:55.689Z",
"error": null,
"details": {
"totalNodes": 439,
"packages": [
"unknown"
],
"nodesByPackage": {
"unknown": [
"ActionNetwork",
"ActiveCampaign",
"ActiveCampaignTrigger",
"AcuitySchedulingTrigger",
"Adalo",
"Affinity",
"AffinityTrigger",
"AgileCrm",
"Airtable",
"AirtableTrigger",
"AirtableV1",
"Amqp",
"AmqpTrigger",
"ApiTemplateIo",
"Asana",
"AsanaTrigger",
"Automizy",
"Autopilot",
"AutopilotTrigger",
"AwsLambda",
"AwsSns",
"AwsSnsTrigger",
"AwsCertificateManager",
"AwsComprehend",
"AwsDynamoDB",
"AwsElb",
"AwsRekognition",
"AwsS3",
"AwsS3V1",
"AwsS3V2",
"AwsSes",
"AwsSqs",
"AwsTextract",
"AwsTranscribe",
"Bannerbear",
"Baserow",
"Beeminder",
"BitbucketTrigger",
"Bitly",
"Bitwarden",
"Box",
"BoxTrigger",
"Brandfetch",
"Brevo",
"BrevoTrigger",
"Bubble",
"CalTrigger",
"CalendlyTrigger",
"Chargebee",
"ChargebeeTrigger",
"CircleCi",
"CiscoWebex",
"CiscoWebexTrigger",
"CitrixAdc",
"Clearbit",
"ClickUp",
"ClickUpTrigger",
"Clockify",
"ClockifyTrigger",
"Cloudflare",
"Cockpit",
"Coda",
"Code",
"CoinGecko",
"CompareDatasets",
"Compression",
"Contentful",
"ConvertKit",
"ConvertKitTrigger",
"Copper",
"CopperTrigger",
"Cortex",
"CrateDb",
"Cron",
"CrowdDev",
"CrowdDevTrigger",
"Crypto",
"CustomerIo",
"CustomerIoTrigger",
"DateTime",
"DateTimeV1",
"DateTimeV2",
"DebugHelper",
"DeepL",
"Demio",
"Dhl",
"Discord",
"Discourse",
"Disqus",
"Drift",
"Dropbox",
"Dropcontact",
"E2eTest",
"ERPNext",
"EditImage",
"Egoi",
"ElasticSecurity",
"Elasticsearch",
"EmailReadImap",
"EmailReadImapV1",
"EmailReadImapV2",
"EmailSend",
"EmailSendV1",
"EmailSendV2",
"Emelia",
"EmeliaTrigger",
"ErrorTrigger",
"EventbriteTrigger",
"ExecuteCommand",
"ExecuteWorkflow",
"ExecuteWorkflowTrigger",
"ExecutionData",
"FacebookGraphApi",
"FacebookTrigger",
"FacebookLeadAdsTrigger",
"FigmaTrigger",
"FileMaker",
"Filter",
"Flow",
"FlowTrigger",
"FormTrigger",
"FormIoTrigger",
"FormstackTrigger",
"Freshdesk",
"Freshservice",
"FreshworksCrm",
"Ftp",
"Function",
"FunctionItem",
"GetResponse",
"GetResponseTrigger",
"Ghost",
"Git",
"Github",
"GithubTrigger",
"Gitlab",
"GitlabTrigger",
"GoToWebinar",
"GoogleAds",
"GoogleAnalytics",
"GoogleAnalyticsV1",
"GoogleBigQuery",
"GoogleBigQueryV1",
"GoogleBooks",
"GoogleCalendar",
"GoogleCalendarTrigger",
"GoogleChat",
"GoogleCloudNaturalLanguage",
"GoogleCloudStorage",
"GoogleContacts",
"GoogleDocs",
"GoogleDrive",
"GoogleDriveTrigger",
"GoogleDriveV1",
"GoogleFirebaseCloudFirestore",
"GoogleFirebaseRealtimeDatabase",
"GSuiteAdmin",
"Gmail",
"GmailTrigger",
"GmailV1",
"GmailV2",
"GooglePerspective",
"GoogleSheets",
"GoogleSheetsTrigger",
"GoogleSlides",
"GoogleTasks",
"GoogleTranslate",
"YouTube",
"Gotify",
"Grafana",
"GraphQL",
"Grist",
"GumroadTrigger",
"HackerNews",
"HaloPSA",
"Harvest",
"HelpScout",
"HelpScoutTrigger",
"HighLevel",
"HomeAssistant",
"Html",
"HtmlExtract",
"HttpRequest",
"HttpRequestV1",
"HttpRequestV2",
"HttpRequestV3",
"Hubspot",
"HubspotTrigger",
"HubspotV1",
"HubspotV2",
"HumanticAi",
"Hunter",
"ICalendar",
"If",
"Intercom",
"Interval",
"InvoiceNinja",
"InvoiceNinjaTrigger",
"ItemLists",
"ItemListsV1",
"ItemListsV2",
"Iterable",
"Jenkins",
"Jira",
"JiraTrigger",
"JotFormTrigger",
"Kafka",
"KafkaTrigger",
"Keap",
"KeapTrigger",
"Kitemaker",
"KoBoToolbox",
"KoBoToolboxTrigger",
"Ldap",
"Lemlist",
"LemlistTrigger",
"Line",
"Linear",
"LinearTrigger",
"LingvaNex",
"LinkedIn",
"LocalFileTrigger",
"LoneScale",
"LoneScaleTrigger",
"Mqtt",
"MqttTrigger",
"Magento2",
"Mailcheck",
"Mailchimp",
"MailchimpTrigger",
"MailerLite",
"MailerLiteTrigger",
"Mailgun",
"Mailjet",
"MailjetTrigger",
"Mandrill",
"ManualTrigger",
"Markdown",
"Marketstack",
"Matrix",
"Mattermost",
"Mautic",
"MauticTrigger",
"Medium",
"Merge",
"MergeV1",
"MergeV2",
"MessageBird",
"Metabase",
"MicrosoftDynamicsCrm",
"MicrosoftExcel",
"MicrosoftExcelV1",
"MicrosoftGraphSecurity",
"MicrosoftOneDrive",
"MicrosoftOutlook",
"MicrosoftOutlookV1",
"MicrosoftSql",
"MicrosoftTeams",
"MicrosoftToDo",
"Mindee",
"Misp",
"Mocean",
"MondayCom",
"MongoDb",
"MonicaCrm",
"MoveBinaryData",
"Msg91",
"MySql",
"MySqlV1",
"N8n",
"N8nTrainingCustomerDatastore",
"N8nTrainingCustomerMessenger",
"N8nTrigger",
"Nasa",
"Netlify",
"NetlifyTrigger",
"NextCloud",
"NoOp",
"NocoDB",
"Notion",
"NotionTrigger",
"Npm",
"Odoo",
"OneSimpleApi",
"Onfleet",
"OnfleetTrigger",
"OpenAi",
"OpenThesaurus",
"OpenWeatherMap",
"Orbit",
"Oura",
"Paddle",
"PagerDuty",
"PayPal",
"PayPalTrigger",
"Peekalink",
"Phantombuster",
"PhilipsHue",
"Pipedrive",
"PipedriveTrigger",
"Plivo",
"PostBin",
"PostHog",
"Postgres",
"PostgresTrigger",
"PostgresV1",
"PostmarkTrigger",
"ProfitWell",
"Pushbullet",
"Pushcut",
"PushcutTrigger",
"Pushover",
"QuestDb",
"QuickBase",
"QuickBooks",
"QuickChart",
"RabbitMQ",
"RabbitMQTrigger",
"Raindrop",
"ReadBinaryFile",
"ReadBinaryFiles",
"ReadPDF",
"Reddit",
"Redis",
"RedisTrigger",
"RenameKeys",
"RespondToWebhook",
"Rocketchat",
"RssFeedRead",
"RssFeedReadTrigger",
"Rundeck",
"S3",
"Salesforce",
"Salesmate",
"ScheduleTrigger",
"SeaTable",
"SeaTableTrigger",
"SecurityScorecard",
"Segment",
"SendGrid",
"Sendy",
"SentryIo",
"ServiceNow",
"Set",
"SetV1",
"SetV2",
"Shopify",
"ShopifyTrigger",
"Signl4",
"Slack",
"SlackV1",
"SlackV2",
"Sms77",
"Snowflake",
"SplitInBatches",
"SplitInBatchesV1",
"SplitInBatchesV2",
"SplitInBatchesV3",
"Splunk",
"Spontit",
"Spotify",
"SpreadsheetFile",
"SseTrigger",
"Ssh",
"Stackby",
"Start",
"StickyNote",
"StopAndError",
"Storyblok",
"Strapi",
"Strava",
"StravaTrigger",
"Stripe",
"StripeTrigger",
"Supabase",
"SurveyMonkeyTrigger",
"Switch",
"SwitchV1",
"SwitchV2",
"SyncroMsp",
"Taiga",
"TaigaTrigger",
"Tapfiliate",
"Telegram",
"TelegramTrigger",
"TheHive",
"TheHiveTrigger",
"TheHiveProjectTrigger",
"TimescaleDb",
"Todoist",
"TodoistV1",
"TodoistV2",
"TogglTrigger",
"Totp",
"TravisCi",
"Trello",
"TrelloTrigger",
"Twake",
"Twilio",
"Twist",
"Twitter",
"TwitterV1",
"TwitterV2",
"TypeformTrigger",
"UProc",
"UnleashedSoftware",
"Uplead",
"UptimeRobot",
"UrlScanIo",
"VenafiTlsProtectDatacenter",
"VenafiTlsProtectDatacenterTrigger",
"VenafiTlsProtectCloud",
"VenafiTlsProtectCloudTrigger",
"Vero",
"Vonage",
"Wait",
"Webflow",
"WebflowTrigger",
"Webhook",
"Wekan",
"WhatsApp",
"Wise",
"WiseTrigger",
"WooCommerce",
"WooCommerceTrigger",
"Wordpress",
"WorkableTrigger",
"WorkflowTrigger",
"WriteBinaryFile",
"WufooTrigger",
"Xero",
"Xml",
"Yourls",
"Zammad",
"Zendesk",
"ZendeskTrigger",
"ZohoCrm",
"Zoom",
"Zulip"
]
},
"sampleNodes": [
{
"name": "ActionNetwork",
"displayName": "Action Network",
"description": "Consume the Action Network API",
"location": "node_modules/n8n-nodes-base/dist/nodes/ActionNetwork/ActionNetwork.node.js"
},
{
"name": "ActiveCampaign",
"displayName": "ActiveCampaign",
"description": "Create and edit data in ActiveCampaign",
"location": "node_modules/n8n-nodes-base/dist/nodes/ActiveCampaign/ActiveCampaign.node.js"
},
{
"name": "ActiveCampaignTrigger",
"displayName": "ActiveCampaign Trigger",
"description": "Handle ActiveCampaign events via webhooks",
"location": "node_modules/n8n-nodes-base/dist/nodes/ActiveCampaign/ActiveCampaignTrigger.node.js"
},
{
"name": "AcuitySchedulingTrigger",
"displayName": "Acuity Scheduling Trigger",
"description": "Handle Acuity Scheduling events via webhooks",
"location": "node_modules/n8n-nodes-base/dist/nodes/AcuityScheduling/AcuitySchedulingTrigger.node.js"
},
{
"name": "Adalo",
"displayName": "Adalo",
"description": "Consume Adalo API",
"location": "node_modules/n8n-nodes-base/dist/nodes/Adalo/Adalo.node.js"
}
]
}
},
{
"name": "Bulk Node Extraction",
"status": "passed",
"startTime": "2025-06-08T10:57:55.689Z",
"endTime": "2025-06-08T10:57:58.574Z",
"error": null,
"details": {
"totalAttempted": 10,
"successCount": 6,
"failureCount": 4,
"timeElapsed": 2581,
"results": [
{
"success": true,
"data": {
"nodeType": "ActionNetwork",
"name": "ActionNetwork",
"codeLength": 15810,
"codeHash": "c0a880f5754b6b532ff787bdb253dc49ffd7f470f28aeddda5be0c73f9f9935f",
"hasCredentials": true,
"hasPackageInfo": true,
"location": "node_modules/n8n-nodes-base/dist/nodes/ActionNetwork/ActionNetwork.node.js",
"extractedAt": "2025-06-08T10:57:56.009Z"
}
},
{
"success": true,
"data": {
"nodeType": "ActiveCampaign",
"name": "ActiveCampaign",
"codeLength": 38399,
"codeHash": "5ea90671718d20eecb6cddae2e21c91470fdb778e8be97106ee2539303422ad2",
"hasCredentials": true,
"hasPackageInfo": true,
"location": "node_modules/n8n-nodes-base/dist/nodes/ActiveCampaign/ActiveCampaign.node.js",
"extractedAt": "2025-06-08T10:57:56.032Z"
}
},
{
"success": false,
"nodeType": "ActiveCampaignTrigger",
"error": "Node source code not found for: ActiveCampaignTrigger"
},
{
"success": false,
"nodeType": "AcuitySchedulingTrigger",
"error": "Node source code not found for: AcuitySchedulingTrigger"
},
{
"success": true,
"data": {
"nodeType": "Adalo",
"name": "Adalo",
"codeLength": 8234,
"codeHash": "0fbcb0b60141307fdc3394154af1b2c3133fa6181aac336249c6c211fd24846f",
"hasCredentials": true,
"hasPackageInfo": true,
"location": "node_modules/n8n-nodes-base/dist/nodes/Adalo/Adalo.node.js",
"extractedAt": "2025-06-08T10:57:57.330Z"
}
},
{
"success": true,
"data": {
"nodeType": "Affinity",
"name": "Affinity",
"codeLength": 16217,
"codeHash": "e605ea187767403dfa55cd374690f7df563a0baa7ca6991d86d522dc101a2846",
"hasCredentials": true,
"hasPackageInfo": true,
"location": "node_modules/n8n-nodes-base/dist/nodes/Affinity/Affinity.node.js",
"extractedAt": "2025-06-08T10:57:57.343Z"
}
},
{
"success": false,
"nodeType": "AffinityTrigger",
"error": "Node source code not found for: AffinityTrigger"
},
{
"success": true,
"data": {
"nodeType": "AgileCrm",
"name": "AgileCrm",
"codeLength": 28115,
"codeHash": "ce71c3b5dec23a48d24c5775e9bb79006ce395bed62b306c56340b5c772379c2",
"hasCredentials": true,
"hasPackageInfo": true,
"location": "node_modules/n8n-nodes-base/dist/nodes/AgileCrm/AgileCrm.node.js",
"extractedAt": "2025-06-08T10:57:57.925Z"
}
},
{
"success": true,
"data": {
"nodeType": "Airtable",
"name": "Airtable",
"codeLength": 936,
"codeHash": "2d67e72931697178946f5127b43e954649c4c5e7ad9e29764796404ae96e7db5",
"hasCredentials": true,
"hasPackageInfo": true,
"location": "node_modules/n8n-nodes-base/dist/nodes/Airtable/Airtable.node.js",
"extractedAt": "2025-06-08T10:57:57.941Z"
}
},
{
"success": false,
"nodeType": "AirtableTrigger",
"error": "Node source code not found for: AirtableTrigger"
}
]
}
},
{
"name": "Database Schema Validation",
"status": "passed",
"startTime": "2025-06-08T10:57:58.574Z",
"endTime": "2025-06-08T10:57:58.575Z",
"error": null,
"details": {
"schemaValid": true,
"tablesCount": 4,
"estimatedStoragePerNode": 16834
}
},
{
"name": "Error Handling",
"status": "passed",
"startTime": "2025-06-08T10:57:58.575Z",
"endTime": "2025-06-08T10:57:59.244Z",
"error": null,
"details": {
"totalTests": 3,
"passed": 2,
"results": [
{
"name": "Non-existent node",
"nodeType": "non-existent-package.FakeNode",
"expectedError": "not found",
"passed": true,
"actualError": "Node source code not found for: non-existent-package.FakeNode"
},
{
"name": "Invalid node type format",
"nodeType": "",
"expectedError": "invalid",
"passed": false,
"actualError": "Node source code not found for: "
},
{
"name": "Malformed package name",
"nodeType": "@invalid@package.Node",
"expectedError": "not found",
"passed": true,
"actualError": "Node source code not found for: @invalid@package.Node"
}
]
}
},
{
"name": "MCP Server Integration",
"status": "passed",
"startTime": "2025-06-08T10:57:59.244Z",
"endTime": "2025-06-08T10:57:59.249Z",
"error": null,
"details": {
"serverCreated": true,
"config": {
"port": 3000,
"host": "0.0.0.0",
"authToken": "test-token"
}
}
}
],
"extractedNodes": 6,
"databaseSchema": {
"tables": {
"nodes": {
"columns": {
"id": "UUID PRIMARY KEY",
"node_type": "VARCHAR(255) UNIQUE NOT NULL",
"name": "VARCHAR(255) NOT NULL",
"package_name": "VARCHAR(255)",
"display_name": "VARCHAR(255)",
"description": "TEXT",
"version": "VARCHAR(50)",
"code_hash": "VARCHAR(64) NOT NULL",
"code_length": "INTEGER NOT NULL",
"source_location": "TEXT",
"extracted_at": "TIMESTAMP NOT NULL",
"updated_at": "TIMESTAMP"
},
"indexes": [
"node_type",
"package_name",
"code_hash"
]
},
"node_source_code": {
"columns": {
"id": "UUID PRIMARY KEY",
"node_id": "UUID REFERENCES nodes(id)",
"source_code": "TEXT NOT NULL",
"compiled_code": "TEXT",
"source_map": "TEXT"
}
},
"node_credentials": {
"columns": {
"id": "UUID PRIMARY KEY",
"node_id": "UUID REFERENCES nodes(id)",
"credential_type": "VARCHAR(255) NOT NULL",
"credential_code": "TEXT NOT NULL",
"required_fields": "JSONB"
}
},
"node_metadata": {
"columns": {
"id": "UUID PRIMARY KEY",
"node_id": "UUID REFERENCES nodes(id)",
"package_info": "JSONB",
"dependencies": "JSONB",
"icon": "TEXT",
"categories": "TEXT[]",
"documentation_url": "TEXT"
}
}
}
}
}

133
tests/test-slack-docs-issue.js Executable file
View File

@@ -0,0 +1,133 @@
#!/usr/bin/env node
const { DocumentationFetcher } = require('../dist/utils/documentation-fetcher');
const { NodeSourceExtractor } = require('../dist/utils/node-source-extractor');
const { execSync } = require('child_process');
const path = require('path');
const fs = require('fs');
async function investigateSlackDocs() {
console.log('=== Investigating Slack Node Documentation Issue ===\n');
const docsFetcher = new DocumentationFetcher();
const extractor = new NodeSourceExtractor();
try {
// 1. Ensure docs repo is available
console.log('1⃣ Ensuring documentation repository...');
await docsFetcher.ensureDocsRepository();
// 2. Check what files exist for Slack
console.log('\n2⃣ Searching for Slack documentation files...');
const docsPath = path.join(process.cwd(), 'temp', 'n8n-docs');
try {
const slackFiles = execSync(
`find ${docsPath} -name "*slack*" -type f | grep -v ".git"`,
{ encoding: 'utf-8' }
).trim().split('\n').filter(Boolean);
console.log(`Found ${slackFiles.length} files with "slack" in the name:`);
slackFiles.forEach(file => {
const relPath = path.relative(docsPath, file);
console.log(` - ${relPath}`);
});
// Check content of each file
console.log('\n3⃣ Checking content of Slack-related files...');
for (const file of slackFiles.slice(0, 5)) { // Check first 5 files
if (file.endsWith('.md')) {
const content = fs.readFileSync(file, 'utf-8');
const firstLine = content.split('\n')[0];
const isCredential = content.includes('credential') || content.includes('authentication');
console.log(`\n 📄 ${path.basename(file)}`);
console.log(` First line: ${firstLine}`);
console.log(` Is credential doc: ${isCredential}`);
// Check if it mentions being a node or credential
if (content.includes('# Slack node')) {
console.log(' ✅ This is the Slack NODE documentation!');
console.log(` Path: ${file}`);
} else if (content.includes('# Slack credentials')) {
console.log(' ⚠️ This is the Slack CREDENTIALS documentation');
}
}
}
} catch (error) {
console.log('Error searching for Slack files:', error.message);
}
// 4. Test the getNodeDocumentation method
console.log('\n4⃣ Testing getNodeDocumentation for Slack...');
const slackDocs = await docsFetcher.getNodeDocumentation('n8n-nodes-base.slack');
if (slackDocs) {
console.log(' ✅ Found documentation for Slack node');
console.log(` URL: ${slackDocs.url}`);
console.log(` Content preview: ${slackDocs.markdown.substring(0, 200)}...`);
// Check if it's credential or node docs
const isCredentialDoc = slackDocs.markdown.includes('credential') ||
slackDocs.markdown.includes('authentication') ||
slackDocs.markdown.includes('# Slack credentials');
const isNodeDoc = slackDocs.markdown.includes('# Slack node') ||
slackDocs.markdown.includes('## Properties');
console.log(` Is credential doc: ${isCredentialDoc}`);
console.log(` Is node doc: ${isNodeDoc}`);
} else {
console.log(' ❌ No documentation found for Slack node');
}
// 5. Extract the Slack node source to understand its structure
console.log('\n5⃣ Extracting Slack node source code...');
try {
const slackNode = await extractor.extractNodeSource('n8n-nodes-base.slack');
console.log(' ✅ Successfully extracted Slack node');
console.log(` Location: ${slackNode.location}`);
console.log(` Has credential code: ${!!slackNode.credentialCode}`);
// Parse the node definition
const descMatch = slackNode.sourceCode.match(/description\s*[:=]\s*({[\s\S]*?})\s*[,;]/);
if (descMatch) {
console.log(' Found node description in source');
}
} catch (error) {
console.log(' ❌ Failed to extract Slack node:', error.message);
}
// 6. Check documentation structure
console.log('\n6⃣ Checking n8n-docs repository structure...');
const docStructure = [
'docs/integrations/builtin/app-nodes',
'docs/integrations/builtin/core-nodes',
'docs/integrations/builtin/trigger-nodes',
'docs/integrations/builtin/credentials'
];
for (const dir of docStructure) {
const fullPath = path.join(docsPath, dir);
try {
const files = fs.readdirSync(fullPath);
const slackFile = files.find(f => f.toLowerCase().includes('slack'));
console.log(`\n 📁 ${dir}:`);
if (slackFile) {
console.log(` Found: ${slackFile}`);
} else {
console.log(` No Slack files found`);
}
} catch (error) {
console.log(` Directory doesn't exist`);
}
}
} catch (error) {
console.error('\n❌ Investigation failed:', error);
} finally {
// Cleanup
await docsFetcher.cleanup();
}
}
// Run investigation
investigateSlackDocs().catch(console.error);

119
tests/test-slack-fix.js Executable file
View File

@@ -0,0 +1,119 @@
#!/usr/bin/env node
const { NodeDocumentationService } = require('../dist/services/node-documentation-service');
const { NodeSourceExtractor } = require('../dist/utils/node-source-extractor');
const { DocumentationFetcher } = require('../dist/utils/documentation-fetcher');
async function testSlackFix() {
console.log('=== Testing Slack Node Fix ===\n');
const extractor = new NodeSourceExtractor();
const docsFetcher = new DocumentationFetcher();
try {
// Test 1: Node source extraction
console.log('1⃣ Testing Slack node source extraction...');
const slackSource = await extractor.extractNodeSource('n8n-nodes-base.slack');
console.log(` ✅ Source code found at: ${slackSource.location}`);
console.log(` 📏 Source length: ${slackSource.sourceCode.length} bytes`);
// Extract display name from source
const displayNameMatch = slackSource.sourceCode.match(/displayName\s*[:=]\s*['"`]([^'"`]+)['"`]/);
console.log(` 📛 Display name: ${displayNameMatch ? displayNameMatch[1] : 'Not found'}`);
// Test 2: Documentation fetching
console.log('\n2⃣ Testing Slack documentation fetching...');
const slackDocs = await docsFetcher.getNodeDocumentation('n8n-nodes-base.slack');
if (slackDocs) {
console.log(` ✅ Documentation found`);
console.log(` 📄 URL: ${slackDocs.url}`);
// Extract title from markdown
const titleMatch = slackDocs.markdown.match(/title:\s*(.+)/);
console.log(` 📝 Title: ${titleMatch ? titleMatch[1] : 'Not found'}`);
// Check if it's the correct documentation
const isNodeDoc = slackDocs.markdown.includes('Slack node') ||
slackDocs.markdown.includes('node documentation');
const isCredentialDoc = slackDocs.markdown.includes('Slack credentials') &&
!slackDocs.markdown.includes('node documentation');
console.log(` ✅ Is node documentation: ${isNodeDoc}`);
console.log(` ❌ Is credential documentation: ${isCredentialDoc}`);
if (isNodeDoc && !isCredentialDoc) {
console.log('\n🎉 SUCCESS: Slack node documentation is correctly fetched!');
} else {
console.log('\n⚠ WARNING: Documentation may not be correct');
}
// Show first few lines of content
console.log('\n📋 Documentation preview:');
const lines = slackDocs.markdown.split('\n').slice(0, 15);
lines.forEach(line => console.log(` ${line}`));
} else {
console.log(' ❌ No documentation found');
}
// Test 3: Complete node info using NodeDocumentationService
console.log('\n3⃣ Testing complete node info storage...');
const service = new NodeDocumentationService('./data/test-slack-fix.db');
try {
// Parse node definition
const nodeDefinition = {
displayName: displayNameMatch ? displayNameMatch[1] : 'Slack',
description: 'Send messages to Slack channels, users and conversations',
category: 'Communication',
icon: 'file:slack.svg',
version: 2
};
// Store node info
await service.storeNode({
nodeType: 'n8n-nodes-base.slack',
name: 'slack',
displayName: nodeDefinition.displayName,
description: nodeDefinition.description,
category: nodeDefinition.category,
icon: nodeDefinition.icon,
sourceCode: slackSource.sourceCode,
credentialCode: slackSource.credentialCode,
documentation: slackDocs?.markdown,
documentationUrl: slackDocs?.url,
packageName: 'n8n-nodes-base',
version: nodeDefinition.version,
hasCredentials: !!slackSource.credentialCode,
isTrigger: false,
isWebhook: false
});
console.log(' ✅ Node info stored successfully');
// Retrieve and verify
const retrievedNode = await service.getNodeInfo('n8n-nodes-base.slack');
if (retrievedNode) {
console.log(' ✅ Node retrieved successfully');
console.log(` 📛 Display name: ${retrievedNode.displayName}`);
console.log(` 📝 Has documentation: ${!!retrievedNode.documentation}`);
console.log(` 📄 Documentation URL: ${retrievedNode.documentationUrl || 'N/A'}`);
}
service.close();
} catch (error) {
console.error(' ❌ Error with node service:', error.message);
service.close();
}
console.log('\n✅ All tests completed!');
} catch (error) {
console.error('\n❌ Test failed:', error);
} finally {
await docsFetcher.cleanup();
}
}
testSlackFix().catch(console.error);

View File

@@ -0,0 +1,137 @@
#!/usr/bin/env node
const { NodeDocumentationService } = require('../dist/services/node-documentation-service');
const { EnhancedDocumentationFetcher } = require('../dist/utils/documentation-fetcher');
const { NodeSourceExtractor } = require('../dist/utils/node-source-extractor');
const path = require('path');
async function testSlackNode() {
console.log('🧪 Testing Slack Node Complete Information Extraction\n');
const dbPath = path.join(__dirname, '../data/test-slack.db');
const service = new NodeDocumentationService(dbPath);
const fetcher = new EnhancedDocumentationFetcher();
const extractor = new NodeSourceExtractor();
try {
console.log('📚 Fetching Slack node documentation...');
const docs = await fetcher.getEnhancedNodeDocumentation('n8n-nodes-base.Slack');
console.log('\n✅ Documentation Structure:');
console.log(`- Title: ${docs.title}`);
console.log(`- Has markdown: ${docs.markdown?.length > 0 ? 'Yes' : 'No'} (${docs.markdown?.length || 0} chars)`);
console.log(`- Operations: ${docs.operations?.length || 0}`);
console.log(`- API Methods: ${docs.apiMethods?.length || 0}`);
console.log(`- Examples: ${docs.examples?.length || 0}`);
console.log(`- Templates: ${docs.templates?.length || 0}`);
console.log(`- Related Resources: ${docs.relatedResources?.length || 0}`);
console.log(`- Required Scopes: ${docs.requiredScopes?.length || 0}`);
console.log('\n📋 Operations by Resource:');
const resourceMap = new Map();
if (docs.operations) {
docs.operations.forEach(op => {
if (!resourceMap.has(op.resource)) {
resourceMap.set(op.resource, []);
}
resourceMap.get(op.resource).push(op);
});
}
for (const [resource, ops] of resourceMap) {
console.log(`\n ${resource}:`);
ops.forEach(op => {
console.log(` - ${op.operation}: ${op.description}`);
});
}
console.log('\n🔌 Sample API Methods:');
if (docs.apiMethods) {
docs.apiMethods.slice(0, 5).forEach(method => {
console.log(` - ${method.operation}${method.apiMethod}`);
});
}
console.log('\n💻 Extracting Slack node source code...');
const sourceInfo = await extractor.extractNodeSource('n8n-nodes-base.Slack');
console.log('\n✅ Source Code Extraction:');
console.log(`- Has source code: ${sourceInfo.sourceCode ? 'Yes' : 'No'} (${sourceInfo.sourceCode?.length || 0} chars)`);
console.log(`- Has credential code: ${sourceInfo.credentialCode ? 'Yes' : 'No'} (${sourceInfo.credentialCode?.length || 0} chars)`);
console.log(`- Package name: ${sourceInfo.packageInfo?.name}`);
console.log(`- Package version: ${sourceInfo.packageInfo?.version}`);
// Store in database
console.log('\n💾 Storing in database...');
await service.storeNode({
nodeType: 'n8n-nodes-base.Slack',
name: 'Slack',
displayName: 'Slack',
description: 'Send and receive messages, manage channels, and more',
category: 'Communication',
documentationUrl: docs?.url || 'https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.slack/',
documentationMarkdown: docs?.markdown,
documentationTitle: docs?.title,
operations: docs?.operations,
apiMethods: docs?.apiMethods,
documentationExamples: docs?.examples,
templates: docs?.templates,
relatedResources: docs?.relatedResources,
requiredScopes: docs?.requiredScopes,
sourceCode: sourceInfo.sourceCode || '',
credentialCode: sourceInfo.credentialCode,
packageName: sourceInfo.packageInfo?.name || 'n8n-nodes-base',
version: sourceInfo.packageInfo?.version,
hasCredentials: true,
isTrigger: false,
isWebhook: false
});
// Retrieve and verify
console.log('\n🔍 Retrieving from database...');
const storedNode = await service.getNodeInfo('n8n-nodes-base.Slack');
console.log('\n✅ Verification Results:');
console.log(`- Node found: ${storedNode ? 'Yes' : 'No'}`);
if (storedNode) {
console.log(`- Has operations: ${storedNode.operations?.length > 0 ? 'Yes' : 'No'} (${storedNode.operations?.length || 0})`);
console.log(`- Has API methods: ${storedNode.apiMethods?.length > 0 ? 'Yes' : 'No'} (${storedNode.apiMethods?.length || 0})`);
console.log(`- Has examples: ${storedNode.documentationExamples?.length > 0 ? 'Yes' : 'No'} (${storedNode.documentationExamples?.length || 0})`);
console.log(`- Has source code: ${storedNode.sourceCode ? 'Yes' : 'No'}`);
console.log(`- Has credential code: ${storedNode.credentialCode ? 'Yes' : 'No'}`);
}
// Test search
console.log('\n🔍 Testing search...');
const searchResults = await service.searchNodes('message send');
const slackInResults = searchResults.some(r => r.nodeType === 'n8n-nodes-base.Slack');
console.log(`- Slack found in search results: ${slackInResults ? 'Yes' : 'No'}`);
console.log('\n✅ Complete Information Test Summary:');
const hasCompleteInfo =
storedNode &&
storedNode.operations?.length > 0 &&
storedNode.apiMethods?.length > 0 &&
storedNode.sourceCode &&
storedNode.documentationMarkdown;
console.log(`- Has complete information: ${hasCompleteInfo ? '✅ YES' : '❌ NO'}`);
if (!hasCompleteInfo) {
console.log('\n❌ Missing Information:');
if (!storedNode) console.log(' - Node not stored properly');
if (!storedNode?.operations?.length) console.log(' - No operations extracted');
if (!storedNode?.apiMethods?.length) console.log(' - No API methods extracted');
if (!storedNode?.sourceCode) console.log(' - No source code extracted');
if (!storedNode?.documentationMarkdown) console.log(' - No documentation extracted');
}
} catch (error) {
console.error('❌ Test failed:', error);
} finally {
await service.close();
}
}
// Run the test
testSlackNode().catch(console.error);