UNPKG

@crazyrabbitltc/railway-mcp

Version:

Railway MCP Server - 146+ tools with 100% Railway API coverage, comprehensive MCP testing framework, and real infrastructure management through AI assistants. Enhanced version with enterprise features, based on original work by Jason Tan.

304 lines (303 loc) 14.1 kB
import { BaseService } from "./base.service.js"; import { createSuccessResponse, createErrorResponse, formatError } from "../utils/responses.js"; export class MonitoringService extends BaseService { constructor() { super(); } async queryMetrics(projectId, serviceId, metricName, startTime, endTime, step, labels) { try { const end = endTime || new Date().toISOString(); const start = startTime || new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); const metrics = await this.client.monitoring.queryMetrics({ projectId, serviceId, metricName, startTime: start, endTime: end, step: step || '5m', labels }); const uniqueMetrics = [...new Set(metrics.map(m => m.name))]; const latestValues = uniqueMetrics.map(name => { const metricData = metrics.filter(m => m.name === name); const latest = metricData.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())[0]; return { name, value: latest?.value, unit: latest?.unit, timestamp: latest?.timestamp }; }); return createSuccessResponse({ text: `Found ${metrics.length} metric data points across ${uniqueMetrics.length} metrics`, data: { projectId, serviceId: serviceId || 'All services', timeRange: { start, end }, summary: { totalDataPoints: metrics.length, uniqueMetrics: uniqueMetrics.length, step }, latestValues, metrics: metrics.map(metric => ({ id: metric.id, name: metric.name, type: metric.type, value: metric.value, unit: metric.unit, labels: metric.labels, timestamp: metric.timestamp })) } }); } catch (error) { return createErrorResponse(`Failed to query metrics: ${formatError(error)}`); } } async createCustomMetric(projectId, serviceId, name, type, value, labels, unit) { try { const metric = await this.client.monitoring.createCustomMetric(projectId, serviceId, name, type, value, labels, unit || 'count'); return createSuccessResponse({ text: `Custom metric "${name}" created with value ${value}`, data: { id: metric.id, name: metric.name, type: metric.type, value: metric.value, unit: metric.unit, labels: metric.labels, projectId: metric.projectId, serviceId: metric.serviceId, timestamp: metric.timestamp } }); } catch (error) { return createErrorResponse(`Failed to create custom metric: ${formatError(error)}`); } } async getAPMData(projectId, serviceId, startTime, endTime) { try { const end = endTime || new Date().toISOString(); const start = startTime || new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); const apmData = await this.client.monitoring.getAPMData(projectId, serviceId, start, end); const avgResponseTime = apmData.reduce((sum, d) => sum + d.responseTime, 0) / apmData.length || 0; const avgThroughput = apmData.reduce((sum, d) => sum + d.throughput, 0) / apmData.length || 0; const avgErrorRate = apmData.reduce((sum, d) => sum + d.errorRate, 0) / apmData.length || 0; const maxCpuUsage = Math.max(...apmData.map(d => d.cpuUsage), 0); const maxMemoryUsage = Math.max(...apmData.map(d => d.memoryUsage), 0); return createSuccessResponse({ text: `APM data shows ${avgResponseTime.toFixed(2)}ms avg response time, ${avgErrorRate.toFixed(2)}% error rate`, data: { projectId, serviceId: serviceId || 'All services', timeRange: { start, end }, summary: { dataPoints: apmData.length, averageResponseTime: `${avgResponseTime.toFixed(2)}ms`, averageThroughput: `${avgThroughput.toFixed(2)} req/sec`, averageErrorRate: `${avgErrorRate.toFixed(2)}%`, peakCpuUsage: `${maxCpuUsage.toFixed(1)}%`, peakMemoryUsage: `${maxMemoryUsage.toFixed(1)}%` }, apmData: apmData.map(data => ({ id: data.id, serviceId: data.serviceId, timestamp: data.timestamp, responseTime: `${data.responseTime}ms`, throughput: `${data.throughput} req/sec`, errorRate: `${data.errorRate}%`, cpuUsage: `${data.cpuUsage}%`, memoryUsage: `${data.memoryUsage}%`, customMetrics: data.customMetrics })) } }); } catch (error) { return createErrorResponse(`Failed to get APM data: ${formatError(error)}`); } } async listAlerts(projectId, serviceId) { try { const alerts = await this.client.monitoring.listAlerts(projectId, serviceId); const activeCount = alerts.filter(a => a.isActive).length; const criticalCount = alerts.filter(a => a.severity === 'CRITICAL').length; const bySeverity = alerts.reduce((acc, alert) => { acc[alert.severity] = (acc[alert.severity] || 0) + 1; return acc; }, {}); return createSuccessResponse({ text: `Found ${alerts.length} alerts (${activeCount} active, ${criticalCount} critical)`, data: { projectId, serviceId: serviceId || 'All services', summary: { totalAlerts: alerts.length, activeCount, criticalCount, bySeverity }, alerts: alerts.map(alert => ({ id: alert.id, name: alert.name, description: alert.description, condition: alert.condition, threshold: alert.threshold, severity: alert.severity, isActive: alert.isActive, notificationCount: alert.notifications.length, notifications: alert.notifications, createdAt: alert.createdAt, updatedAt: alert.updatedAt })) } }); } catch (error) { return createErrorResponse(`Failed to list alerts: ${formatError(error)}`); } } async createAlert(projectId, name, description, condition, threshold, severity, notifications, serviceId) { try { const alert = await this.client.monitoring.createAlert(projectId, name, description, condition, threshold, severity, notifications, serviceId); return createSuccessResponse({ text: `Alert "${name}" created with ${severity} severity`, data: { id: alert.id, name: alert.name, description: alert.description, condition: alert.condition, threshold: alert.threshold, severity: alert.severity, isActive: alert.isActive, notifications: alert.notifications, projectId: alert.projectId, serviceId: alert.serviceId, createdAt: alert.createdAt } }); } catch (error) { return createErrorResponse(`Failed to create alert: ${formatError(error)}`); } } async updateAlert(alertId, isActive, threshold, notifications) { try { const alert = await this.client.monitoring.updateAlert(alertId, isActive, threshold, notifications); return createSuccessResponse({ text: `Alert "${alert.name}" updated successfully`, data: { id: alert.id, name: alert.name, isActive: alert.isActive, threshold: alert.threshold, notifications: alert.notifications, updatedAt: alert.updatedAt } }); } catch (error) { return createErrorResponse(`Failed to update alert: ${formatError(error)}`); } } async deleteAlert(alertId) { try { const success = await this.client.monitoring.deleteAlert(alertId); if (success) { return createSuccessResponse({ text: "Alert deleted successfully" }); } else { return createErrorResponse("Failed to delete alert"); } } catch (error) { return createErrorResponse(`Failed to delete alert: ${formatError(error)}`); } } async getTraces(projectId, serviceId, startTime, endTime, operationName) { try { const end = endTime || new Date().toISOString(); const start = startTime || new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); const traces = await this.client.monitoring.getTraces(projectId, serviceId, start, end, operationName); const uniqueTraces = [...new Set(traces.map(t => t.traceId))]; const avgDuration = traces.reduce((sum, t) => sum + t.duration, 0) / traces.length || 0; const errorCount = traces.filter(t => t.status === 'ERROR').length; const services = [...new Set(traces.map(t => t.serviceName))]; return createSuccessResponse({ text: `Found ${traces.length} spans across ${uniqueTraces.length} traces (${errorCount} errors)`, data: { projectId, serviceId: serviceId || 'All services', timeRange: { start, end }, summary: { totalSpans: traces.length, uniqueTraces: uniqueTraces.length, averageDuration: `${avgDuration.toFixed(2)}ms`, errorCount, errorRate: `${((errorCount / traces.length) * 100).toFixed(2)}%`, services }, traces: traces.map(trace => ({ id: trace.id, traceId: trace.traceId, parentId: trace.parentId, operationName: trace.operationName, serviceName: trace.serviceName, duration: `${trace.duration}ms`, status: trace.status, startTime: trace.startTime, endTime: trace.endTime, tags: trace.tags, logCount: trace.logs.length })) } }); } catch (error) { return createErrorResponse(`Failed to get traces: ${formatError(error)}`); } } async getTraceDetails(traceId) { try { const spans = await this.client.monitoring.getTraceById(traceId); const totalDuration = Math.max(...spans.map(s => s.duration), 0); const errorSpans = spans.filter(s => s.status === 'ERROR'); const services = [...new Set(spans.map(s => s.serviceName))]; const operations = [...new Set(spans.map(s => s.operationName))]; return createSuccessResponse({ text: `Trace contains ${spans.length} spans across ${services.length} services`, data: { traceId, summary: { totalSpans: spans.length, totalDuration: `${totalDuration.toFixed(2)}ms`, services, operations, errorCount: errorSpans.length, hasErrors: errorSpans.length > 0 }, spans: spans.map(span => ({ id: span.id, parentId: span.parentId, operationName: span.operationName, serviceName: span.serviceName, duration: `${span.duration}ms`, status: span.status, startTime: span.startTime, endTime: span.endTime, tags: span.tags, logs: span.logs.map(log => ({ timestamp: log.timestamp, level: log.level, message: log.message.substring(0, 200) + (log.message.length > 200 ? '...' : '') })) })) } }); } catch (error) { return createErrorResponse(`Failed to get trace details: ${formatError(error)}`); } } } export const monitoringService = new MonitoringService();