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
JavaScript
/**
* 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 };