/**
* Node.js Client Library for MCP Self-Learning Server
* Provides programmatic access to all learning tools and features
*/
import { EventEmitter } from 'events';
class SelfLearningClient extends EventEmitter {
constructor(options = {}) {
super();
this.baseUrl = options.baseUrl || `http://localhost:${options.port || 8765}`;
this.apiKey = options.apiKey || null;
this.timeout = options.timeout || 30000;
this.retryAttempts = options.retryAttempts || 3;
this.retryDelay = options.retryDelay || 1000;
// Connection tracking
this.isConnected = false;
this.lastHealthCheck = null;
this.websocket = null;
// Request tracking
this.requestId = 0;
this.pendingRequests = new Map();
}
/**
* Initialize the client and check server connection
*/
async connect() {
try {
const health = await this.getHealth();
this.isConnected = health.status === 'healthy';
this.lastHealthCheck = Date.now();
if (this.isConnected) {
this.emit('connected');
}
return this.isConnected;
} catch (error) {
this.isConnected = false;
this.emit('error', error);
throw new Error(`Failed to connect to MCP Self-Learning Server: ${error.message}`);
}
}
/**
* Connect to WebSocket for real-time updates
*/
async connectWebSocket() {
if (this.websocket) {
return; // Already connected
}
const wsUrl = this.baseUrl.replace(/^http/, 'ws') + '/ws';
try {
const WebSocket = (await import('ws')).default;
this.websocket = new WebSocket(wsUrl);
this.websocket.on('open', () => {
this.emit('websocket-connected');
});
this.websocket.on('message', (data) => {
try {
const message = JSON.parse(data.toString());
this.emit('realtime-update', message);
// Emit specific events based on message type
if (message.type) {
this.emit(message.type, message.data);
}
} catch (error) {
this.emit('error', new Error(`Invalid WebSocket message: ${error.message}`));
}
});
this.websocket.on('close', () => {
this.websocket = null;
this.emit('websocket-disconnected');
});
this.websocket.on('error', (error) => {
this.emit('error', error);
});
} catch (error) {
throw new Error(`Failed to connect WebSocket: ${error.message}`);
}
}
/**
* Disconnect WebSocket
*/
disconnectWebSocket() {
if (this.websocket) {
this.websocket.close();
this.websocket = null;
}
}
/**
* Make HTTP request with retry logic
*/
async makeRequest(method, endpoint, data = null) {
const requestId = ++this.requestId;
let lastError;
for (let attempt = 1; attempt <= this.retryAttempts; attempt++) {
try {
const url = `${this.baseUrl}${endpoint}`;
const options = {
method,
headers: {
'Content-Type': 'application/json',
...(this.apiKey && { 'Authorization': `Bearer ${this.apiKey}` })
},
...(data && { body: JSON.stringify(data) })
};
const response = await fetch(url, options);
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: response.statusText }));
throw new Error(`HTTP ${response.status}: ${errorData.error || errorData.message}`);
}
const result = await response.json();
this.emit('request-completed', { requestId, method, endpoint, attempt });
return result;
} catch (error) {
lastError = error;
if (attempt < this.retryAttempts) {
await new Promise(resolve => setTimeout(resolve, this.retryDelay * attempt));
this.emit('request-retry', { requestId, method, endpoint, attempt, error: error.message });
}
}
}
this.emit('request-failed', { requestId, method, endpoint, error: lastError.message });
throw lastError;
}
/**
* Health check
*/
async getHealth() {
return await this.makeRequest('GET', '/health');
}
/**
* Get server status
*/
async getStatus() {
return await this.makeRequest('GET', '/status');
}
/**
* Analyze an interaction pattern for learning
*/
async analyzePattern(interaction) {
if (!interaction || typeof interaction !== 'object') {
throw new Error('Interaction object is required');
}
const requiredFields = ['type', 'input', 'output', 'success'];
for (const field of requiredFields) {
if (!(field in interaction)) {
throw new Error(`Missing required field: ${field}`);
}
}
return await this.makeRequest('POST', '/analyze', { interaction });
}
/**
* Get learning insights and analytics
*/
async getInsights() {
return await this.makeRequest('GET', '/insights');
}
/**
* Trigger a learning cycle
*/
async triggerLearning() {
return await this.makeRequest('POST', '/learn');
}
/**
* Export learned knowledge
*/
async exportKnowledge(options = {}) {
const { format = 'json' } = options;
const queryParams = new URLSearchParams({ format });
return await this.makeRequest('GET', `/export?${queryParams}`);
}
/**
* Import knowledge from external source
*/
async importKnowledge(data) {
if (!data || typeof data !== 'object') {
throw new Error('Import data object is required');
}
return await this.makeRequest('POST', '/import', data);
}
/**
* Get optimization suggestions
*/
async getOptimizations(toolName = null) {
const queryParams = new URLSearchParams();
if (toolName) {
queryParams.set('tool_name', toolName);
}
const query = queryParams.toString();
return await this.makeRequest('GET', `/optimize${query ? '?' + query : ''}`);
}
/**
* Predict next actions based on context
*/
async predictNextAction(context) {
if (!context || typeof context !== 'object') {
throw new Error('Context object is required');
}
return await this.makeRequest('POST', '/predict', { context });
}
/**
* Get performance metrics
*/
async getPerformanceMetrics(toolName = null) {
const queryParams = new URLSearchParams();
if (toolName) {
queryParams.set('tool_name', toolName);
}
const query = queryParams.toString();
return await this.makeRequest('GET', `/metrics${query ? '?' + query : ''}`);
}
/**
* Bulk operations for multiple patterns
*/
async analyzeMultiplePatterns(interactions) {
if (!Array.isArray(interactions)) {
throw new Error('Interactions must be an array');
}
const results = [];
for (const interaction of interactions) {
try {
const result = await this.analyzePattern(interaction);
results.push({ success: true, data: result, interaction });
} catch (error) {
results.push({ success: false, error: error.message, interaction });
}
}
return results;
}
/**
* Monitor learning activity with callback
*/
async monitorLearning(callback, interval = 5000) {
if (typeof callback !== 'function') {
throw new Error('Callback function is required');
}
const monitor = async () => {
try {
const status = await this.getStatus();
const insights = await this.getInsights();
callback(null, { status, insights });
} catch (error) {
callback(error, null);
}
};
// Initial call
await monitor();
// Set up interval
const intervalId = setInterval(monitor, interval);
// Return cleanup function
return () => clearInterval(intervalId);
}
/**
* Wait for specific learning milestones
*/
async waitForLearningMilestone(condition, timeout = 60000) {
const startTime = Date.now();
return new Promise((resolve, reject) => {
const check = async () => {
try {
const insights = await this.getInsights();
if (condition(insights)) {
resolve(insights);
return;
}
if (Date.now() - startTime > timeout) {
reject(new Error('Timeout waiting for learning milestone'));
return;
}
setTimeout(check, 1000);
} catch (error) {
reject(error);
}
};
check();
});
}
/**
* Create a learning session for related interactions
*/
createLearningSession(sessionId) {
return new LearningSession(this, sessionId);
}
/**
* Get API information
*/
async getApiInfo() {
return await this.makeRequest('GET', '/api');
}
/**
* Download exported knowledge file
*/
async downloadKnowledgeFile(filename, outputPath) {
const fs = await import('fs/promises');
const response = await fetch(`${this.baseUrl}/download/${filename}`);
if (!response.ok) {
throw new Error(`Failed to download file: ${response.statusText}`);
}
const buffer = await response.arrayBuffer();
await fs.writeFile(outputPath, Buffer.from(buffer));
return outputPath;
}
}
/**
* Learning Session for related interactions
*/
class LearningSession {
constructor(client, sessionId) {
this.client = client;
this.sessionId = sessionId;
this.interactions = [];
this.startTime = Date.now();
}
/**
* Add interaction to session
*/
addInteraction(interaction) {
const sessionInteraction = {
...interaction,
sessionId: this.sessionId,
sessionTime: Date.now() - this.startTime
};
this.interactions.push(sessionInteraction);
return sessionInteraction;
}
/**
* Analyze all interactions in session
*/
async analyzeSession() {
const results = [];
for (const interaction of this.interactions) {
try {
const result = await this.client.analyzePattern(interaction);
results.push({ success: true, data: result, interaction });
} catch (error) {
results.push({ success: false, error: error.message, interaction });
}
}
return {
sessionId: this.sessionId,
totalInteractions: this.interactions.length,
duration: Date.now() - this.startTime,
results
};
}
/**
* Get session summary
*/
getSummary() {
return {
sessionId: this.sessionId,
interactions: this.interactions.length,
duration: Date.now() - this.startTime,
types: [...new Set(this.interactions.map(i => i.type))],
successRate: this.interactions.filter(i => i.success).length / this.interactions.length
};
}
}
export default SelfLearningClient;
export { LearningSession };