@codai/romai-mcp
Version:
ROMAI Ultimate MCP Server - All-in-One Enterprise Solution with 26+ Integrated Tools
635 lines (625 loc) • 19.3 kB
JavaScript
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