@creedspace/mcp-server
Version:
Universal MCP server for Creed Space - AI safety guardrails in 10 seconds
217 lines • 7.82 kB
JavaScript
/**
* 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
;