UNPKG

@creedspace/mcp-server

Version:

Universal MCP server for Creed Space - AI safety guardrails in 10 seconds

217 lines 7.82 kB
"use strict"; /** * Analytics module for tracking NPM package usage * Respects user privacy - only collects aggregate metrics */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.Analytics = void 0; exports.getAnalytics = getAnalytics; exports.trackToolUsage = trackToolUsage; exports.trackError = trackError; exports.trackPersonaSwitch = trackPersonaSwitch; exports.trackSessionStart = trackSessionStart; exports.trackSessionEnd = trackSessionEnd; const os = __importStar(require("os")); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const fetch_polyfill_js_1 = require("./fetch-polyfill.js"); class Analytics { enabled; sessionId; startTime; events = []; toolsUsed = new Set(); errorCount = 0; analyticsEndpoint; constructor(enabled = true) { // Respect DO_NOT_TRACK and CI environments this.enabled = enabled && process.env.DO_NOT_TRACK !== '1' && process.env.CI !== 'true'; this.sessionId = this.generateSessionId(); this.startTime = Date.now(); this.analyticsEndpoint = process.env.CREEDSPACE_ANALYTICS_URL || 'https://api.creed.space/analytics'; if (this.enabled) { this.setupExitHandler(); } } generateSessionId() { return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } setupExitHandler() { const sendMetrics = () => { this.sendMetrics().catch((error) => { // SECURITY: Log analytics failures for monitoring console.error('[ANALYTICS_ERROR] Failed to send metrics on exit', { error: error instanceof Error ? error.message : String(error), sessionId: this.sessionId, timestamp: new Date().toISOString() }); // Don't interrupt user flow, but ensure we log the failure }); }; // Send metrics on exit process.on('exit', sendMetrics); process.on('SIGINT', () => { sendMetrics(); process.exit(0); }); process.on('SIGTERM', () => { sendMetrics(); process.exit(0); }); } track(event, properties) { if (!this.enabled) return; this.events.push({ event, timestamp: new Date().toISOString(), properties, }); // Track tool usage if (event === 'tool_called' && properties?.tool) { this.toolsUsed.add(properties.tool); } // Track errors if (event === 'error') { this.errorCount++; } } async sendMetrics() { if (!this.enabled || this.events.length === 0) return; const metrics = { sessionId: this.sessionId, packageVersion: this.getPackageVersion(), nodeVersion: process.version, platform: os.platform(), toolsUsed: Array.from(this.toolsUsed), errorCount: this.errorCount, sessionDuration: Math.floor((Date.now() - this.startTime) / 1000), }; try { // Only send aggregate metrics, no personal data await (0, fetch_polyfill_js_1.fetch)(this.analyticsEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'User-Agent': `creedspace-mcp-server/${metrics.packageVersion}`, }, body: JSON.stringify({ metrics, events: this.events.slice(0, 100), // Limit events }), }); } catch (error) { // SECURITY: Log analytics send failures for debugging console.error('[ANALYTICS_SEND_ERROR]', { error: error instanceof Error ? error.message : String(error), endpoint: this.analyticsEndpoint, sessionId: this.sessionId, eventCount: this.events.length, timestamp: new Date().toISOString() }); // Analytics should never block user, but we need to know about failures // Re-throw critical errors that indicate security issues if (error instanceof Error && error.message.includes('ENOTFOUND')) { throw new Error(`Analytics endpoint unreachable: ${this.analyticsEndpoint}`); } } } getPackageVersion() { try { const packagePath = path.join(__dirname, '..', 'package.json'); const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8')); return packageJson.version; } catch (error) { // SECURITY: Log package version read failures console.warn('[PACKAGE_VERSION_ERROR]', { error: error instanceof Error ? error.message : String(error), path: path.join(__dirname, '..', 'package.json'), timestamp: new Date().toISOString() }); return 'unknown'; } } // Public methods for opting out disable() { this.enabled = false; this.events = []; this.toolsUsed.clear(); } isEnabled() { return this.enabled; } } exports.Analytics = Analytics; // Singleton instance let analytics = null; function getAnalytics() { if (!analytics) { // Check user preference const optOut = process.env.CREEDSPACE_ANALYTICS_OPT_OUT === '1'; analytics = new Analytics(!optOut); } return analytics; } // Usage tracking helpers function trackToolUsage(toolName) { getAnalytics().track('tool_called', { tool: toolName }); } function trackError(error, context) { getAnalytics().track('error', { message: error.message, context, stack: error.stack?.split('\n').slice(0, 3).join('\n'), // Limited stack }); } function trackPersonaSwitch(fromPersona, toPersona) { getAnalytics().track('persona_switched', { from: fromPersona, to: toPersona, }); } function trackSessionStart(persona) { getAnalytics().track('session_started', { persona }); } function trackSessionEnd() { getAnalytics().track('session_ended', { duration: Date.now() - getAnalytics()['startTime'], }); } //# sourceMappingURL=analytics.js.map