UNPKG

csvlod-ai-mcp-server

Version:

CSVLOD-AI MCP Server v3.0 with Quantum Context Intelligence - Revolutionary Context Intelligence Engine and Multimodal Processor for sovereign AI development

353 lines (298 loc) • 11 kB
#!/usr/bin/env node /** * CSVLOD-AI Privacy-Respecting Telemetry Framework * * A sovereignty-first approach to telemetry that: * - Requires explicit opt-in (never opt-out) * - Stores data locally first, transmits only with permission * - Provides complete transparency about what's collected * - Enables instant opt-out and data deletion * - Anonymizes all data before transmission * - Gives users full control over their data * * Philosophy: "Telemetry should serve the user's interests, not compromise their sovereignty" */ import fs from 'fs'; import path from 'path'; import crypto from 'crypto'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); class SovereigntyTelemetry { constructor(projectPath = process.cwd()) { this.projectPath = projectPath; this.csvlodDir = path.join(projectPath, '.csvlod'); this.telemetryDir = path.join(this.csvlodDir, 'telemetry'); this.configPath = path.join(this.csvlodDir, 'telemetry-config.json'); this.localDataPath = path.join(this.telemetryDir, 'local-data.json'); this.consentPath = path.join(this.telemetryDir, 'consent.json'); this.config = this.loadConfig(); this.ensureDirectories(); } loadConfig() { const defaultConfig = { enabled: false, explicitOptIn: false, optInTimestamp: null, dataMinimization: true, localStorage: true, anonymization: true, userControlled: true, easyOptOut: true, transparentLogging: true, retentionPeriodDays: 30, transmissionFrequency: 'weekly', // never, daily, weekly, monthly allowedDataTypes: [], sovereigntyLevel: 'high' }; if (fs.existsSync(this.configPath)) { const existingConfig = JSON.parse(fs.readFileSync(this.configPath, 'utf-8')); return { ...defaultConfig, ...existingConfig }; } return defaultConfig; } saveConfig() { fs.writeFileSync(this.configPath, JSON.stringify(this.config, null, 2)); } ensureDirectories() { if (!fs.existsSync(this.csvlodDir)) { fs.mkdirSync(this.csvlodDir, { recursive: true }); } if (!fs.existsSync(this.telemetryDir)) { fs.mkdirSync(this.telemetryDir, { recursive: true }); } } // Explicit opt-in with full transparency async optIn(dataTypes = [], retentionDays = 30) { console.log(` šŸ›”ļø CSVLOD-AI Telemetry Opt-In You're about to enable anonymous usage data collection. This helps improve CSVLOD-AI while preserving your sovereignty. šŸ“Š Data Types Requested: ${dataTypes.map(type => ` • ${type}: ${this.getDataTypeDescription(type)}`).join('\n')} šŸ”’ Your Data Sovereignty: • All data stored locally first • Anonymized before any transmission • Easy opt-out anytime with full deletion • Retention period: ${retentionDays} days • You control transmission frequency • Open source collection code 🚫 What we NEVER collect: • Your code or project content • Personal information or identifiers • File names, paths, or structures • Specific AI interactions or prompts `); // Record explicit consent const consent = { timestamp: new Date().toISOString(), dataTypes, retentionDays, userConfirmed: true, sovereigntyLevel: this.config.sovereigntyLevel, consentVersion: '1.0.0' }; fs.writeFileSync(this.consentPath, JSON.stringify(consent, null, 2)); this.config.enabled = true; this.config.explicitOptIn = true; this.config.optInTimestamp = consent.timestamp; this.config.allowedDataTypes = dataTypes; this.config.retentionPeriodDays = retentionDays; this.saveConfig(); console.log('āœ… Telemetry opt-in confirmed with complete sovereignty preservation'); return true; } // Instant opt-out with data deletion async optOut(deleteExistingData = true) { console.log('🚪 Opting out of telemetry...'); this.config.enabled = false; this.config.explicitOptIn = false; this.config.optInTimestamp = null; if (deleteExistingData) { await this.deleteAllTelemetryData(); console.log('šŸ—‘ļø All telemetry data deleted'); } this.saveConfig(); console.log('āœ… Telemetry opt-out complete - your sovereignty is fully restored'); } // Collect data with sovereignty preservation async collectEvent(eventType, data = {}, sovereigntyImpact = 'none') { if (!this.config.enabled || !this.config.allowedDataTypes.includes(eventType)) { return; // No collection if not opted in or type not allowed } const event = { id: this.generateAnonymousId(), timestamp: new Date().toISOString(), type: eventType, sovereigntyImpact, data: this.anonymizeData(data), sessionId: this.getAnonymousSessionId(), version: '2.0.4' }; // Store locally first await this.storeLocally(event); // Log transparently if (this.config.transparentLogging) { console.log(`šŸ“Š Telemetry: ${eventType} (${sovereigntyImpact} sovereignty impact)`); } } async storeLocally(event) { let localData = []; if (fs.existsSync(this.localDataPath)) { localData = JSON.parse(fs.readFileSync(this.localDataPath, 'utf-8')); } localData.push(event); // Apply retention policy const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - this.config.retentionPeriodDays); localData = localData.filter(event => new Date(event.timestamp) > cutoffDate ); fs.writeFileSync(this.localDataPath, JSON.stringify(localData, null, 2)); } anonymizeData(data) { if (!this.config.anonymization) return data; const anonymized = {}; for (const [key, value] of Object.entries(data)) { if (this.isSensitiveField(key)) { anonymized[key] = this.hashValue(value); } else if (typeof value === 'number') { anonymized[key] = value; // Numbers are generally safe } else if (typeof value === 'boolean') { anonymized[key] = value; // Booleans are safe } else if (typeof value === 'string') { anonymized[key] = this.anonymizeString(value); } else { anonymized[key] = '[anonymized]'; } } return anonymized; } isSensitiveField(fieldName) { const sensitiveFields = [ 'path', 'filename', 'username', 'email', 'ip', 'id', 'name', 'code', 'content', 'message', 'query' ]; return sensitiveFields.some(field => fieldName.toLowerCase().includes(field) ); } hashValue(value) { return crypto.createHash('sha256') .update(String(value)) .digest('hex') .substring(0, 8); // Only first 8 chars for anonymity } anonymizeString(str) { if (str.length <= 3) return '[short]'; return `[${str.length}chars]`; } generateAnonymousId() { return crypto.randomBytes(8).toString('hex'); } getAnonymousSessionId() { const sessionFile = path.join(this.telemetryDir, 'session.json'); if (fs.existsSync(sessionFile)) { const session = JSON.parse(fs.readFileSync(sessionFile, 'utf-8')); if (Date.now() - session.created < 24 * 60 * 60 * 1000) { // 24 hours return session.id; } } const sessionId = this.generateAnonymousId(); fs.writeFileSync(sessionFile, JSON.stringify({ id: sessionId, created: Date.now() })); return sessionId; } getDataTypeDescription(dataType) { const descriptions = { 'tool_usage': 'Which MCP tools are used (no parameters or content)', 'performance': 'Response times and success rates (no sensitive data)', 'errors': 'Error types and frequency (no code or personal data)', 'sovereignty_scores': 'Sovereignty audit results (anonymized)', 'feature_usage': 'Which features are used (no usage details)', 'agent_coordination': 'Agent swarm patterns (no task content)' }; return descriptions[dataType] || 'Unknown data type'; } // Status and control methods getStatus() { return { enabled: this.config.enabled, explicitOptIn: this.config.explicitOptIn, optInTimestamp: this.config.optInTimestamp, allowedDataTypes: this.config.allowedDataTypes, retentionPeriodDays: this.config.retentionPeriodDays, transmissionFrequency: this.config.transmissionFrequency, localDataCount: this.getLocalDataCount(), sovereigntyLevel: this.config.sovereigntyLevel }; } getLocalDataCount() { if (!fs.existsSync(this.localDataPath)) return 0; const localData = JSON.parse(fs.readFileSync(this.localDataPath, 'utf-8')); return localData.length; } async exportData() { const exportPath = path.join(this.telemetryDir, `export-${Date.now()}.json`); const exportData = { config: this.config, consent: fs.existsSync(this.consentPath) ? JSON.parse(fs.readFileSync(this.consentPath, 'utf-8')) : null, localData: fs.existsSync(this.localDataPath) ? JSON.parse(fs.readFileSync(this.localDataPath, 'utf-8')) : [] }; fs.writeFileSync(exportPath, JSON.stringify(exportData, null, 2)); console.log(`šŸ“„ Telemetry data exported to: ${exportPath}`); return exportPath; } async deleteAllTelemetryData() { const filesToDelete = [ this.localDataPath, this.consentPath, path.join(this.telemetryDir, 'session.json') ]; for (const file of filesToDelete) { if (fs.existsSync(file)) { fs.unlinkSync(file); } } // Remove export files if (fs.existsSync(this.telemetryDir)) { const files = fs.readdirSync(this.telemetryDir); for (const file of files) { if (file.startsWith('export-')) { fs.unlinkSync(path.join(this.telemetryDir, file)); } } } } // Common telemetry events with sovereignty preservation static async trackToolUsage(toolName, duration, success, sovereigntyImpact = 'none') { const telemetry = new SovereigntyTelemetry(); await telemetry.collectEvent('tool_usage', { tool: toolName, duration_ms: duration, success, timestamp_hour: new Date().getHours() // Only hour, not exact time }, sovereigntyImpact); } static async trackPerformance(operation, metrics) { const telemetry = new SovereigntyTelemetry(); await telemetry.collectEvent('performance', { operation, duration: metrics.duration, success: metrics.success, // No sensitive performance data }, 'none'); } static async trackSovereigntyScore(score, category) { const telemetry = new SovereigntyTelemetry(); await telemetry.collectEvent('sovereignty_scores', { score: Math.round(score / 10) * 10, // Round to nearest 10 for anonymity category }, 'positive'); // Sovereignty tracking improves sovereignty } } export { SovereigntyTelemetry };