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.

207 lines (206 loc) 10.3 kB
import { BaseService } from "./base.service.js"; import { createSuccessResponse, createErrorResponse, formatError } from "../utils/responses.js"; export class UsageService extends BaseService { constructor() { super(); } async getTeamUsage(teamId, startDate, endDate) { try { const usage = await this.client.usage.getTeamUsage(teamId, startDate, endDate); const totalCost = usage.costs.total; const highestCostMetric = Object.entries(usage.metrics) .sort(([, a], [, b]) => b.cost - a.cost)[0]; const utilizationPercentages = Object.entries(usage.metrics) .filter(([, metric]) => metric.limit) .map(([name, metric]) => ({ name, percentage: Math.round((metric.used / metric.limit) * 100) })); return createSuccessResponse({ text: `Team usage: $${totalCost.toFixed(2)} for period ${usage.period.start} to ${usage.period.end}`, data: { teamId: usage.teamId, period: usage.period, summary: { totalCost: `$${totalCost.toFixed(2)} ${usage.costs.currency}`, highestCostMetric: { name: highestCostMetric[0], cost: `$${highestCostMetric[1].cost.toFixed(2)}`, usage: `${highestCostMetric[1].used} ${highestCostMetric[1].unit}` } }, utilization: utilizationPercentages, breakdown: { compute: `$${usage.costs.breakdown.compute.toFixed(2)}`, memory: `$${usage.costs.breakdown.memory.toFixed(2)}`, network: `$${usage.costs.breakdown.network.toFixed(2)}`, storage: `$${usage.costs.breakdown.storage.toFixed(2)}`, builds: `$${usage.costs.breakdown.builds.toFixed(2)}`, addOns: `$${usage.costs.breakdown.addOns.toFixed(2)}` }, metrics: Object.entries(usage.metrics).map(([name, metric]) => ({ name, used: metric.used, limit: metric.limit, unit: metric.unit, cost: `$${metric.cost.toFixed(2)}`, utilization: metric.limit ? `${Math.round((metric.used / metric.limit) * 100)}%` : 'Unlimited' })) } }); } catch (error) { return createErrorResponse(`Failed to get team usage: ${formatError(error)}`); } } async getProjectUsage(projectId, startDate, endDate) { try { const usage = await this.client.usage.getProjectUsage(projectId, startDate, endDate); const totalCost = usage.costs.total; const topCostComponents = Object.entries(usage.costs.breakdown) .sort(([, a], [, b]) => b - a) .slice(0, 3) .map(([name, cost]) => ({ name, cost: `$${cost.toFixed(2)}` })); return createSuccessResponse({ text: `Project usage: $${totalCost.toFixed(2)} for period ${usage.period.start} to ${usage.period.end}`, data: { projectId: usage.projectId, teamId: usage.teamId, period: usage.period, summary: { totalCost: `$${totalCost.toFixed(2)} ${usage.costs.currency}`, topCostComponents }, metrics: Object.entries(usage.metrics).map(([name, metric]) => ({ name, used: metric.used, unit: metric.unit, cost: `$${metric.cost.toFixed(2)}` })), breakdown: usage.costs.breakdown } }); } catch (error) { return createErrorResponse(`Failed to get project usage: ${formatError(error)}`); } } async getBillingInfo(teamId) { try { const billing = await this.client.usage.getBillingInfo(teamId); const planUtilization = Object.entries(billing.plan.limits).map(([resource, limit]) => ({ resource, limit, // Note: Current usage would need to be calculated from actual usage metrics available: limit })); const daysUntilBilling = billing.nextBillingDate ? Math.ceil((new Date(billing.nextBillingDate).getTime() - Date.now()) / (1000 * 60 * 60 * 24)) : null; return createSuccessResponse({ text: `${billing.plan.name} plan - $${billing.currentUsage.amount.toFixed(2)} current usage`, data: { teamId: billing.teamId, plan: { name: billing.plan.name, type: billing.plan.type, price: `$${billing.plan.price} ${billing.plan.currency}/${billing.plan.billingCycle.toLowerCase()}`, limits: billing.plan.limits }, currentPeriod: { usage: `$${billing.currentUsage.amount.toFixed(2)} ${billing.currentUsage.currency}`, period: billing.currentUsage.period, daysRemaining: daysUntilBilling }, payment: billing.paymentMethod ? { type: billing.paymentMethod.type, last4: billing.paymentMethod.last4, expires: billing.paymentMethod.expiryMonth && billing.paymentMethod.expiryYear ? `${billing.paymentMethod.expiryMonth}/${billing.paymentMethod.expiryYear}` : undefined } : null, nextBillingDate: billing.nextBillingDate, planLimits: planUtilization } }); } catch (error) { return createErrorResponse(`Failed to get billing info: ${formatError(error)}`); } } async getUsageAlerts(teamId) { try { const alerts = await this.client.usage.getUsageAlerts(teamId); const activeAlerts = alerts.filter(alert => alert.isActive); const alertsByType = alerts.reduce((acc, alert) => { if (!acc[alert.type]) acc[alert.type] = []; acc[alert.type].push(alert); return acc; }, {}); const criticalAlerts = activeAlerts.filter(alert => alert.currentValue >= alert.threshold * 0.9); return createSuccessResponse({ text: `${alerts.length} usage alerts configured (${activeAlerts.length} active, ${criticalAlerts.length} critical)`, data: { totalCount: alerts.length, activeCount: activeAlerts.length, criticalCount: criticalAlerts.length, byType: Object.entries(alertsByType).map(([type, typeAlerts]) => ({ type, count: typeAlerts.length, active: typeAlerts.filter(a => a.isActive).length })), alerts: alerts.map(alert => ({ id: alert.id, type: alert.type, threshold: alert.threshold, currentValue: alert.currentValue, isActive: alert.isActive, severity: alert.currentValue >= alert.threshold * 0.9 ? 'CRITICAL' : alert.currentValue >= alert.threshold * 0.7 ? 'WARNING' : 'OK', utilization: `${Math.round((alert.currentValue / alert.threshold) * 100)}%`, notificationEmail: alert.notificationEmail, createdAt: alert.createdAt })) } }); } catch (error) { return createErrorResponse(`Failed to get usage alerts: ${formatError(error)}`); } } async compareUsage(teamId, projectIds) { try { const teamUsage = await this.client.usage.getTeamUsage(teamId); let projectComparisons = []; if (projectIds?.length) { const projectUsagePromises = projectIds.map(id => this.client.usage.getProjectUsage(id)); const projectUsages = await Promise.all(projectUsagePromises); projectComparisons = projectUsages.map(usage => ({ projectId: usage.projectId, totalCost: usage.costs.total, percentage: (usage.costs.total / teamUsage.costs.total) * 100, topMetric: Object.entries(usage.metrics) .sort(([, a], [, b]) => b.cost - a.cost)[0] })); } return createSuccessResponse({ text: `Usage comparison for team with ${projectComparisons.length} projects`, data: { teamTotal: `$${teamUsage.costs.total.toFixed(2)}`, period: teamUsage.period, projectBreakdown: projectComparisons, teamMetrics: Object.entries(teamUsage.metrics).map(([name, metric]) => ({ name, cost: `$${metric.cost.toFixed(2)}`, percentage: `${Math.round((metric.cost / teamUsage.costs.total) * 100)}%` })).sort((a, b) => parseFloat(b.cost.slice(1)) - parseFloat(a.cost.slice(1))) } }); } catch (error) { return createErrorResponse(`Failed to compare usage: ${formatError(error)}`); } } } export const usageService = new UsageService();