UNPKG

@codai/romai-mcp

Version:

ROMAI Ultimate MCP Server - All-in-One Enterprise Solution with 26+ Integrated Tools

635 lines (625 loc) 19.3 kB
import { randomUUID } from 'crypto'; // src/logging/enterprise-logger.ts var EnterpriseLogger = class _EnterpriseLogger { static instance; metrics = []; auditTrail = []; constructor() { } static getInstance() { if (!_EnterpriseLogger.instance) { _EnterpriseLogger.instance = new _EnterpriseLogger(); } return _EnterpriseLogger.instance; } /** * Create a new request context with correlation ID */ createRequestContext(method, userId, organizationId) { return { requestId: randomUUID(), userId, organizationId, method, timestamp: (/* @__PURE__ */ new Date()).toISOString(), source: "mcp-server", version: "0.2.0" }; } /** * Log MCP request with structured data */ logRequest(context, params) { const logEntry = { level: "info", ...context, type: "mcp_request", params: this.sanitizeParams(params), message: `MCP Request: ${context.method}` }; console.log(JSON.stringify(logEntry)); this.recordAuditEvent({ eventId: randomUUID(), eventType: "request", severity: "info", details: { params }, context }); } /** * Log MCP response with performance metrics */ logResponse(context, result, duration) { const logEntry = { level: "info", ...context, type: "mcp_response", result: this.sanitizeResult(result), duration_ms: duration, message: `MCP Response: ${context.method} (${duration}ms)` }; console.log(JSON.stringify(logEntry)); this.recordMetric({ name: "mcp_request_duration", value: duration, unit: "ms", labels: { method: context.method, userId: context.userId || "anonymous", organizationId: context.organizationId || "default" }, timestamp: (/* @__PURE__ */ new Date()).toISOString() }); this.recordAuditEvent({ eventId: randomUUID(), eventType: "response", severity: "info", details: { duration, success: true }, context }); } /** * Log error with full context and stack trace */ logError(context, error, details) { const logEntry = { level: "error", ...context, type: "mcp_error", error: { name: error.name, message: error.message, stack: error.stack }, details: details || {}, message: `MCP Error: ${context.method} - ${error.message}` }; console.error(JSON.stringify(logEntry)); this.recordMetric({ name: "mcp_request_errors", value: 1, unit: "count", labels: { method: context.method, errorType: error.name, userId: context.userId || "anonymous", organizationId: context.organizationId || "default" }, timestamp: (/* @__PURE__ */ new Date()).toISOString() }); this.recordAuditEvent({ eventId: randomUUID(), eventType: "error", severity: "error", details: { error: error.message, stack: error.stack, ...details }, context }); } /** * Record performance metric for analytics */ recordMetric(metric) { this.metrics.push(metric); if (this.metrics.length > 1e3) { this.metrics = this.metrics.slice(-1e3); } } /** * Record audit event for compliance */ recordAuditEvent(event) { this.auditTrail.push(event); if (this.auditTrail.length > 500) { this.auditTrail = this.auditTrail.slice(-500); } } /** * Get performance analytics */ getAnalytics(timeWindow = 36e5) { const cutoff = new Date(Date.now() - timeWindow); const recentMetrics = this.metrics.filter((m) => new Date(m.timestamp) > cutoff); const requestMetrics = recentMetrics.filter((m) => m.name === "mcp_request_duration"); const errorMetrics = recentMetrics.filter((m) => m.name === "mcp_request_errors"); const methodCounts = requestMetrics.reduce((acc, metric) => { const method = metric.labels.method; acc[method] = (acc[method] || 0) + 1; return acc; }, {}); const topMethods = Object.entries(methodCounts).map(([method, count]) => ({ method, count })).sort((a, b) => b.count - a.count).slice(0, 5); return { requestCount: requestMetrics.length, averageResponseTime: requestMetrics.length > 0 ? requestMetrics.reduce((sum, m) => sum + m.value, 0) / requestMetrics.length : 0, errorRate: requestMetrics.length > 0 ? errorMetrics.length / requestMetrics.length * 100 : 0, topMethods, performanceMetrics: recentMetrics }; } /** * Get audit trail for compliance */ getAuditTrail(timeWindow = 36e5) { const cutoff = new Date(Date.now() - timeWindow); return this.auditTrail.filter((event) => new Date(event.context.timestamp) > cutoff); } /** * Generate compliance report */ generateComplianceReport() { const last24h = 24 * 60 * 60 * 1e3; const recentEvents = this.getAuditTrail(last24h); const totalRequests = recentEvents.filter((e) => e.eventType === "request").length; const authenticatedRequests = recentEvents.filter( (e) => e.eventType === "request" && e.context.userId ).length; const errorEvents = recentEvents.filter((e) => e.eventType === "error").length; const criticalEvents = recentEvents.filter((e) => e.severity === "critical").length; return { totalRequests, authenticatedRequests, errorEvents, criticalEvents, dataIntegrity: true, // Enhanced validation in production auditCoverage: totalRequests > 0 ? recentEvents.length / totalRequests * 100 : 100 }; } /** * Sanitize sensitive data from parameters */ sanitizeParams(params) { if (!params || typeof params !== "object") return params; const sanitized = { ...params }; const sensitiveFields = ["password", "token", "key", "secret", "apiKey"]; for (const field of sensitiveFields) { if (sanitized[field]) { sanitized[field] = "[REDACTED]"; } } return sanitized; } /** * Sanitize sensitive data from results */ sanitizeResult(result) { if (!result || typeof result !== "object") return result; const resultStr = JSON.stringify(result); if (resultStr.length > 1e3) { return { _truncated: true, _size: resultStr.length, preview: resultStr.substring(0, 200) + "..." }; } return result; } }; var enterpriseLogger = EnterpriseLogger.getInstance(); // src/monitoring/metrics-collector.ts var MetricsCollector = class _MetricsCollector { static instance; startTime; requestCount = 0; errorCount = 0; lastRequestTime = 0; constructor() { this.startTime = Date.now(); } static getInstance() { if (!_MetricsCollector.instance) { _MetricsCollector.instance = new _MetricsCollector(); } return _MetricsCollector.instance; } /** * Collect system performance metrics */ collectSystemMetrics() { const memoryUsage = process.memoryUsage(); const uptime = process.uptime(); return { memoryUsage: { rss: memoryUsage.rss, heapUsed: memoryUsage.heapUsed, heapTotal: memoryUsage.heapTotal, external: memoryUsage.external }, cpuUsage: process.cpuUsage().user / 1e6, // Convert to seconds uptime, timestamp: (/* @__PURE__ */ new Date()).toISOString() }; } /** * Track request metrics */ trackRequest() { this.requestCount++; this.lastRequestTime = Date.now(); } /** * Track error metrics */ trackError() { this.errorCount++; } /** * Get current performance summary */ getPerformanceSummary() { const uptime = Date.now() - this.startTime; const requestsPerSecond = this.requestCount / (uptime / 1e3); const errorRate = this.requestCount > 0 ? this.errorCount / this.requestCount * 100 : 0; const memoryUsageMB = process.memoryUsage().heapUsed / 1024 / 1024; let systemHealth = "excellent"; if (errorRate > 10 || memoryUsageMB > 500) { systemHealth = "critical"; } else if (errorRate > 5 || memoryUsageMB > 200) { systemHealth = "warning"; } else if (errorRate > 1 || memoryUsageMB > 100) { systemHealth = "good"; } return { uptime, totalRequests: this.requestCount, totalErrors: this.errorCount, errorRate, requestsPerSecond, memoryUsageMB, systemHealth }; } /** * Generate Prometheus-compatible metrics */ generatePrometheusMetrics() { const summary = this.getPerformanceSummary(); const systemMetrics = this.collectSystemMetrics(); return ` # HELP romai_requests_total Total number of MCP requests # TYPE romai_requests_total counter romai_requests_total ${summary.totalRequests} # HELP romai_errors_total Total number of MCP errors # TYPE romai_errors_total counter romai_errors_total ${summary.totalErrors} # HELP romai_request_rate_per_second Current request rate # TYPE romai_request_rate_per_second gauge romai_request_rate_per_second ${summary.requestsPerSecond.toFixed(2)} # HELP romai_error_rate_percent Current error rate percentage # TYPE romai_error_rate_percent gauge romai_error_rate_percent ${summary.errorRate.toFixed(2)} # HELP romai_memory_usage_bytes Current memory usage in bytes # TYPE romai_memory_usage_bytes gauge romai_memory_usage_bytes ${systemMetrics.memoryUsage.heapUsed} # HELP romai_uptime_seconds Server uptime in seconds # TYPE romai_uptime_seconds gauge romai_uptime_seconds ${systemMetrics.uptime.toFixed(0)} # HELP romai_system_health System health status (0=critical, 1=warning, 2=good, 3=excellent) # TYPE romai_system_health gauge romai_system_health ${this.getHealthScore(summary.systemHealth)} `.trim(); } /** * Generate OpenTelemetry-compatible metrics */ generateOpenTelemetryMetrics() { const summary = this.getPerformanceSummary(); const systemMetrics = this.collectSystemMetrics(); const timestamp = Date.now(); return [ { name: "romai_requests_total", description: "Total number of MCP requests", unit: "requests", type: "counter", value: summary.totalRequests, labels: { service: "romai-mcp", version: "0.2.0" }, timestamp }, { name: "romai_errors_total", description: "Total number of MCP errors", unit: "errors", type: "counter", value: summary.totalErrors, labels: { service: "romai-mcp", version: "0.2.0" }, timestamp }, { name: "romai_request_duration_seconds", description: "Request duration in seconds", unit: "seconds", type: "histogram", value: 0, // Updated per request labels: { service: "romai-mcp", version: "0.2.0" }, timestamp }, { name: "romai_memory_usage_bytes", description: "Current memory usage in bytes", unit: "bytes", type: "gauge", value: systemMetrics.memoryUsage.heapUsed, labels: { service: "romai-mcp", version: "0.2.0", type: "heap" }, timestamp }, { name: "romai_system_health_score", description: "System health score", unit: "score", type: "gauge", value: this.getHealthScore(summary.systemHealth), labels: { service: "romai-mcp", version: "0.2.0" }, timestamp } ]; } /** * Collect business intelligence metrics */ collectBusinessMetrics(activeUsers = 0, organizationsCount = 1) { const summary = this.getPerformanceSummary(); return { activeUsers, organizationsCount, requestsPerSecond: summary.requestsPerSecond, revenueMetrics: { dailyActiveUsers: activeUsers, monthlyActiveUsers: activeUsers, // Enhanced in production apiCallsUsed: summary.totalRequests, quotaUtilization: 0 // Calculated based on plan limits }, timestamp: (/* @__PURE__ */ new Date()).toISOString() }; } /** * Generate health score from status */ getHealthScore(health) { switch (health) { case "excellent": return 3; case "good": return 2; case "warning": return 1; case "critical": return 0; default: return 0; } } /** * Reset metrics (for testing) */ reset() { this.requestCount = 0; this.errorCount = 0; this.startTime = Date.now(); } }; var metricsCollector = MetricsCollector.getInstance(); // src/monitoring/request-tracer.ts var RequestTracer = class _RequestTracer { static instance; activeTraces = /* @__PURE__ */ new Map(); completedTraces = []; constructor() { } static getInstance() { if (!_RequestTracer.instance) { _RequestTracer.instance = new _RequestTracer(); } return _RequestTracer.instance; } /** * Start tracing a new request */ startTrace(method, params, userId, organizationId) { const context = enterpriseLogger.createRequestContext(method, userId, organizationId); const trace = { requestId: context.requestId, method, startTime: Date.now(), status: "pending", userId, organizationId, metadata: { params: this.sanitizeForTrace(params), userAgent: "Claude Desktop MCP", serverVersion: "0.2.0" } }; this.activeTraces.set(context.requestId, trace); enterpriseLogger.logRequest(context, params); metricsCollector.trackRequest(); return context; } /** * Complete a request trace with success */ completeTrace(requestId, result) { const trace = this.activeTraces.get(requestId); if (!trace) return; const endTime = Date.now(); const duration = endTime - trace.startTime; trace.endTime = endTime; trace.duration = duration; trace.status = "completed"; trace.metadata.result = this.sanitizeForTrace(result); this.activeTraces.delete(requestId); this.completedTraces.push(trace); if (this.completedTraces.length > 1e3) { this.completedTraces = this.completedTraces.slice(-1e3); } const context = this.createContextFromTrace(trace); enterpriseLogger.logResponse(context, result, duration); } /** * Fail a request trace with error */ failTrace(requestId, error, details) { const trace = this.activeTraces.get(requestId); if (!trace) return; const endTime = Date.now(); const duration = endTime - trace.startTime; trace.endTime = endTime; trace.duration = duration; trace.status = "failed"; trace.metadata.error = { name: error.name, message: error.message, stack: error.stack }; trace.metadata.errorDetails = details; this.activeTraces.delete(requestId); this.completedTraces.push(trace); if (this.completedTraces.length > 1e3) { this.completedTraces = this.completedTraces.slice(-1e3); } const context = this.createContextFromTrace(trace); enterpriseLogger.logError(context, error, details); metricsCollector.trackError(); } /** * Get active traces for monitoring */ getActiveTraces() { return Array.from(this.activeTraces.values()); } /** * Get completed traces for analysis */ getCompletedTraces(limit = 100) { return this.completedTraces.slice(-limit); } /** * Get performance statistics */ getPerformanceStats(timeWindow = 36e5) { const cutoff = Date.now() - timeWindow; const recentTraces = this.completedTraces.filter((t) => (t.endTime || 0) > cutoff); const successfulRequests = recentTraces.filter((t) => t.status === "completed").length; const failedRequests = recentTraces.filter((t) => t.status === "failed").length; const totalRequests = recentTraces.length; const durations = recentTraces.filter((t) => t.duration !== void 0).map((t) => t.duration).sort((a, b) => a - b); const averageResponseTime = durations.length > 0 ? durations.reduce((sum, d) => sum + d, 0) / durations.length : 0; const p95Index = Math.floor(durations.length * 0.95); const p99Index = Math.floor(durations.length * 0.99); const p95ResponseTime = durations[p95Index] || 0; const p99ResponseTime = durations[p99Index] || 0; const requestsPerMinute = totalRequests / (timeWindow / 1e3) * 60; const methodStats = {}; recentTraces.forEach((trace) => { if (trace.duration) { if (!methodStats[trace.method]) { methodStats[trace.method] = { totalTime: 0, count: 0 }; } methodStats[trace.method].totalTime += trace.duration; methodStats[trace.method].count += 1; } }); const topSlowMethods = Object.entries(methodStats).map(([method, stats]) => ({ method, averageTime: stats.totalTime / stats.count, count: stats.count })).sort((a, b) => b.averageTime - a.averageTime).slice(0, 5); return { totalRequests, successfulRequests, failedRequests, averageResponseTime, p95ResponseTime, p99ResponseTime, requestsPerMinute, topSlowMethods }; } /** * Get detailed trace by request ID */ getTraceById(requestId) { return this.activeTraces.get(requestId) || this.completedTraces.find((t) => t.requestId === requestId); } /** * Clean up old traces to prevent memory leaks */ cleanup() { const cutoff = Date.now() - 24 * 60 * 60 * 1e3; this.completedTraces = this.completedTraces.filter((t) => (t.endTime || 0) > cutoff); const staleCutoff = Date.now() - 60 * 60 * 1e3; for (const [requestId, trace] of this.activeTraces.entries()) { if (trace.startTime < staleCutoff) { trace.status = "failed"; trace.endTime = Date.now(); trace.duration = trace.endTime - trace.startTime; trace.metadata.error = { message: "Request timeout - cleaned up by system" }; this.completedTraces.push(trace); this.activeTraces.delete(requestId); } } } /** * Create log context from trace */ createContextFromTrace(trace) { return { requestId: trace.requestId, userId: trace.userId, organizationId: trace.organizationId, method: trace.method, timestamp: new Date(trace.startTime).toISOString(), source: "mcp-server", version: "0.2.0" }; } /** * Sanitize data for tracing (remove sensitive info) */ sanitizeForTrace(data) { if (!data || typeof data !== "object") return data; const sanitized = { ...data }; const sensitiveFields = ["password", "token", "key", "secret", "apiKey"]; for (const field of sensitiveFields) { if (sanitized[field]) { sanitized[field] = "[REDACTED]"; } } const dataStr = JSON.stringify(sanitized); if (dataStr.length > 500) { return { _truncated: true, _size: dataStr.length, preview: JSON.parse(dataStr.substring(0, 200) + "}") }; } return sanitized; } }; var requestTracer = RequestTracer.getInstance(); export { RequestTracer, requestTracer }; //# sourceMappingURL=request-tracer.js.map //# sourceMappingURL=request-tracer.js.map