#!/usr/bin/env node
/**
* LearnMCP Demo Script
* Comprehensive testing of LearnMCP functionality in isolation
*/
import { LearnService } from './modules/learn-service.js';
import { createLearnLogger, getLogStats } from './modules/utils/custom-logger.js';
import { ContentExtractorFactory } from './modules/content-extractors/index.js';
import { mcpHandlers } from './modules/mcp-handlers.js';
// Demo configuration
const DEMO_CONFIG = {
projectId: 'demo_project_' + Date.now(),
dataDir: process.env.FOREST_DATA_DIR || './.forest-data',
testUrls: [
'https://www.youtube.com/watch?v=dQw4w9WgXcQ', // Rick Roll for testing
'https://example.com/sample-article', // Will fail but tests error handling
'https://httpbin.org/html', // Simple HTML for article testing
// 'https://example.com/sample.pdf' // PDF disabled for now
]
};
class LearnMCPDemo {
constructor() {
this.logger = createLearnLogger('LearnMCPDemo');
this.learnService = null;
this.extractorFactory = new ContentExtractorFactory();
this.startTime = Date.now();
}
/**
* Run the complete demo
*/
async run() {
try {
this.logger.info('๐ Starting LearnMCP Demo', {
projectId: DEMO_CONFIG.projectId,
dataDir: DEMO_CONFIG.dataDir,
testUrls: DEMO_CONFIG.testUrls.length
});
await this.initializeServices();
await this.testExtractorFactory();
await this.testMCPHandlers();
await this.testFullWorkflow();
await this.showLogStats();
await this.cleanup();
const duration = Date.now() - this.startTime;
this.logger.info('โ
Demo completed successfully', {
duration: `${duration}ms`,
totalTime: `${Math.round(duration / 1000)}s`
});
} catch (error) {
this.logger.error('โ Demo failed', {
error: error.message,
stack: error.stack
});
throw error;
}
}
/**
* Initialize LearnMCP services
*/
async initializeServices() {
this.logger.info('๐ง Initializing LearnMCP services...');
this.learnService = new LearnService(DEMO_CONFIG.dataDir, this.logger);
await this.learnService.initialize();
this.logger.info('โ
Services initialized');
}
/**
* Test the content extractor factory
*/
async testExtractorFactory() {
this.logger.info('๐งช Testing Content Extractor Factory...');
// Test URL validation
const validationResults = this.extractorFactory.validateUrls(DEMO_CONFIG.testUrls);
this.logger.info('URL validation results', {
results: validationResults.map(r => ({ url: r.url, valid: r.valid, type: r.type }))
});
// Test supported types
const supportedTypes = this.extractorFactory.getSupportedTypes();
this.logger.info('Supported content types', {
types: supportedTypes.map(t => t.type)
});
// Test accessibility check
this.logger.info('Testing URL accessibility...');
const accessibilityResults = await this.extractorFactory.checkUrlsAccessibility(
DEMO_CONFIG.testUrls.slice(0, 2) // Test first 2 URLs only
);
this.logger.info('Accessibility results', {
results: accessibilityResults.map(r => ({
url: r.url,
accessible: r.accessible,
status: r.status
}))
});
this.logger.info('โ
Extractor factory tests completed');
}
/**
* Test MCP handlers
*/
async testMCPHandlers() {
this.logger.info('๐งช Testing MCP Handlers...');
// Test tool definitions
const toolDefinitions = mcpHandlers.getToolDefinitions();
this.logger.info('Available MCP tools', {
toolCount: toolDefinitions.length,
tools: toolDefinitions.map(t => t.name)
});
// Test add_learning_sources
this.logger.info('Testing add_learning_sources...');
try {
const addResult = await mcpHandlers.handleToolCall(
'add_learning_sources',
{
project_id: DEMO_CONFIG.projectId,
urls: DEMO_CONFIG.testUrls.slice(0, 2) // Add first 2 URLs
},
this.learnService
);
this.logger.info('add_learning_sources result', {
success: !!addResult.content,
contentLength: addResult.content?.[0]?.text?.length || 0
});
} catch (error) {
this.logger.warn('add_learning_sources failed (expected for some URLs)', {
error: error.message
});
}
// Test list_learning_sources
this.logger.info('Testing list_learning_sources...');
try {
const listResult = await mcpHandlers.handleToolCall(
'list_learning_sources',
{ project_id: DEMO_CONFIG.projectId },
this.learnService
);
this.logger.info('list_learning_sources result', {
success: !!listResult.content,
contentLength: listResult.content?.[0]?.text?.length || 0
});
} catch (error) {
this.logger.warn('list_learning_sources failed', { error: error.message });
}
// Test get_processing_status
this.logger.info('Testing get_processing_status...');
try {
const statusResult = await mcpHandlers.handleToolCall(
'get_processing_status',
{ project_id: DEMO_CONFIG.projectId },
this.learnService
);
this.logger.info('get_processing_status result', {
success: !!statusResult.content,
contentLength: statusResult.content?.[0]?.text?.length || 0
});
} catch (error) {
this.logger.warn('get_processing_status failed', { error: error.message });
}
this.logger.info('โ
MCP handlers tests completed');
}
/**
* Test the full workflow
*/
async testFullWorkflow() {
this.logger.info('๐ Testing Full Workflow...');
try {
// Step 1: Add sources
this.logger.info('Step 1: Adding learning sources...');
const addResult = await this.learnService.addLearningSources(
DEMO_CONFIG.projectId,
[DEMO_CONFIG.testUrls[2]] // Use httpbin.org for reliable testing
);
this.logger.info('Sources added', {
addedCount: addResult.addedSources?.length || 0,
invalidCount: addResult.invalidUrls?.length || 0
});
// Step 2: Start processing
this.logger.info('Step 2: Starting background processing...');
const processResult = await this.learnService.processLearningSources(DEMO_CONFIG.projectId);
this.logger.info('Processing started', {
queuedCount: processResult.queuedCount || 0
});
// Step 3: Wait a bit and check status
this.logger.info('Step 3: Waiting for processing...');
await this.sleep(3000); // Wait 3 seconds
const statusResult = await this.learnService.getProcessingStatus(DEMO_CONFIG.projectId);
this.logger.info('Processing status', {
totalSources: statusResult.sources?.totalSources || 0,
isProcessing: statusResult.sources?.isProcessing || false,
statusCounts: statusResult.sources?.statusCounts || {}
});
// Step 4: Wait for completion and get summary
this.logger.info('Step 4: Waiting for completion...');
let attempts = 0;
const maxAttempts = 10;
while (attempts < maxAttempts) {
await this.sleep(2000);
const status = await this.learnService.getProcessingStatus(DEMO_CONFIG.projectId);
if (!status.sources?.isProcessing) {
this.logger.info('Processing completed!');
break;
}
attempts++;
this.logger.info(`Still processing... (attempt ${attempts}/${maxAttempts})`);
}
// Step 5: Try to get summary
this.logger.info('Step 5: Attempting to get summary...');
try {
const summaryResult = await this.learnService.getLearningSummary(DEMO_CONFIG.projectId);
if (summaryResult) {
this.logger.info('Summary retrieved', {
sourcesIncluded: summaryResult.sourcesIncluded?.length || 0,
totalSources: summaryResult.totalSources || 0,
tokenCount: summaryResult.tokenCount || 0
});
} else {
this.logger.info('No summary available yet (processing may still be in progress)');
}
} catch (error) {
this.logger.warn('Summary retrieval failed (expected if processing failed)', {
error: error.message
});
}
this.logger.info('โ
Full workflow test completed');
} catch (error) {
this.logger.warn('Full workflow test encountered issues (expected for demo)', {
error: error.message
});
}
}
/**
* Show logging statistics
*/
async showLogStats() {
this.logger.info('๐ Logging Statistics...');
const logStats = getLogStats(DEMO_CONFIG.dataDir);
this.logger.info('Log file statistics', logStats);
// Show recent log files
const logFiles = this.logger.getLogFiles();
this.logger.info('Available log files', {
files: logFiles,
logDirectory: this.logger.getLogDirectory()
});
}
/**
* Cleanup demo data
*/
async cleanup() {
this.logger.info('๐งน Cleaning up demo data...');
try {
// Delete demo sources
const sources = await this.learnService.listLearningSources(DEMO_CONFIG.projectId);
if (sources.length > 0) {
const sourceIds = sources.map(s => s.id);
await this.learnService.deleteLearningSources(DEMO_CONFIG.projectId, sourceIds);
this.logger.info('Demo sources cleaned up', { deletedCount: sourceIds.length });
}
// Shutdown services
await this.learnService.shutdown();
this.logger.info('Services shut down');
} catch (error) {
this.logger.warn('Cleanup encountered issues', { error: error.message });
}
}
/**
* Sleep utility
*/
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Display demo instructions
*/
static showInstructions() {
console.log(`
๐ฏ LearnMCP Demo Instructions
This demo will test LearnMCP functionality in isolation:
1. Initialize services and logging
2. Test content extractor factory
3. Test MCP tool handlers
4. Run full workflow (add โ process โ summarize)
5. Show logging statistics
6. Clean up demo data
Environment Variables:
- FOREST_DATA_DIR: Data directory (default: ./.forest-data)
- LOG_LEVEL: Logging level (default: debug)
- NODE_ENV: Environment (development/production)
Usage:
node demo-learn-mcp.js
Log files will be created in:
<FOREST_DATA_DIR>/logs/learn-mcp/
Press Ctrl+C to stop the demo at any time.
`);
}
}
// Run the demo if this file is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
LearnMCPDemo.showInstructions();
const demo = new LearnMCPDemo();
// Handle graceful shutdown
process.on('SIGINT', async () => {
console.log('\n๐ Demo interrupted by user');
try {
await demo.cleanup();
} catch (error) {
console.error('Cleanup error:', error.message);
}
process.exit(0);
});
// Run the demo
demo.run().catch(error => {
console.error('โ Demo failed:', error.message);
process.exit(1);
});
}