UNPKG

@access-mcp/system-status

Version:

MCP server for ACCESS-CI System Status and Outages API

351 lines (350 loc) 14.6 kB
import { BaseAccessServer, handleApiError } from "@access-mcp/shared"; export class SystemStatusServer extends BaseAccessServer { constructor() { super("access-mcp-system-status", "0.3.0", "https://operations-api.access-ci.org"); } getTools() { return [ { name: "get_current_outages", description: "Get current system outages and issues affecting ACCESS-CI resources", inputSchema: { type: "object", properties: { resource_filter: { type: "string", description: "Optional: filter by specific resource name or ID", }, }, required: [], }, }, { name: "get_scheduled_maintenance", description: "Get scheduled maintenance and future outages for ACCESS-CI resources", inputSchema: { type: "object", properties: { resource_filter: { type: "string", description: "Optional: filter by specific resource name or ID", }, }, required: [], }, }, { name: "get_system_announcements", description: "Get all system announcements (current and scheduled)", inputSchema: { type: "object", properties: { limit: { type: "number", description: "Maximum number of announcements to return (default: 50)", }, }, required: [], }, }, { name: "check_resource_status", description: "Check the operational status of specific ACCESS-CI resources", inputSchema: { type: "object", properties: { resource_ids: { type: "array", items: { type: "string" }, description: "List of resource IDs to check status for", }, }, required: ["resource_ids"], }, }, ]; } getResources() { return [ { uri: "accessci://system-status", name: "ACCESS-CI System Status", description: "Real-time status of ACCESS-CI infrastructure, outages, and maintenance", mimeType: "application/json", }, { uri: "accessci://outages/current", name: "Current Outages", description: "Currently active outages and system issues", mimeType: "application/json", }, { uri: "accessci://outages/scheduled", name: "Scheduled Maintenance", description: "Upcoming scheduled maintenance and planned outages", mimeType: "application/json", }, ]; } async handleToolCall(request) { const { name, arguments: args = {} } = request.params; try { switch (name) { case "get_current_outages": return await this.getCurrentOutages(args.resource_filter); case "get_scheduled_maintenance": return await this.getScheduledMaintenance(args.resource_filter); case "get_system_announcements": return await this.getSystemAnnouncements(args.limit); case "check_resource_status": return await this.checkResourceStatus(args.resource_ids); default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [ { type: "text", text: `Error: ${handleApiError(error)}`, }, ], }; } } async handleResourceRead(request) { const { uri } = request.params; switch (uri) { case "accessci://system-status": return { contents: [ { uri, mimeType: "text/plain", text: "ACCESS-CI System Status API - Monitor real-time status, outages, and maintenance for ACCESS-CI resources.", }, ], }; case "accessci://outages/current": const currentOutages = await this.getCurrentOutages(); return { contents: [ { uri, mimeType: "application/json", text: currentOutages.content[0].text, }, ], }; case "accessci://outages/scheduled": const scheduledMaintenance = await this.getScheduledMaintenance(); return { contents: [ { uri, mimeType: "application/json", text: scheduledMaintenance.content[0].text, }, ], }; default: throw new Error(`Unknown resource: ${uri}`); } } async getCurrentOutages(resourceFilter) { const response = await this.httpClient.get("/wh2/news/v1/affiliation/access-ci.org/current_outages/"); let outages = response.data.results || []; // Filter by resource if specified if (resourceFilter) { const filter = resourceFilter.toLowerCase(); outages = outages.filter((outage) => outage.Subject?.toLowerCase().includes(filter) || outage.AffectedResources?.some((resource) => resource.ResourceName?.toLowerCase().includes(filter) || resource.ResourceID?.toString().includes(filter))); } // Initialize tracking variables const affectedResources = new Set(); const severityCounts = { high: 0, medium: 0, low: 0, unknown: 0 }; // Enhance outages with status summary const enhancedOutages = outages.map((outage) => { // Track affected resources outage.AffectedResources?.forEach((resource) => { affectedResources.add(resource.ResourceName); }); // Categorize severity (basic heuristic) const subject = outage.Subject?.toLowerCase() || ""; let severity = "unknown"; if (subject.includes("emergency") || subject.includes("critical")) { severity = "high"; } else if (subject.includes("maintenance") || subject.includes("scheduled")) { severity = "low"; } else { severity = "medium"; } severityCounts[severity]++; return { ...outage, severity, posted_time: outage.CreationTime, last_updated: outage.LastModificationTime, }; }); const summary = { total_outages: outages.length, affected_resources: Array.from(affectedResources), severity_counts: severityCounts, outages: enhancedOutages, }; return { content: [ { type: "text", text: JSON.stringify({ ...summary, affected_resources: summary.affected_resources, }, null, 2), }, ], }; } async getScheduledMaintenance(resourceFilter) { const response = await this.httpClient.get("/wh2/news/v1/affiliation/access-ci.org/future_outages/"); let maintenance = response.data.results || []; // Filter by resource if specified if (resourceFilter) { const filter = resourceFilter.toLowerCase(); maintenance = maintenance.filter((item) => item.Subject?.toLowerCase().includes(filter) || item.AffectedResources?.some((resource) => resource.ResourceName?.toLowerCase().includes(filter) || resource.ResourceID?.toString().includes(filter))); } // Sort by scheduled start time maintenance.sort((a, b) => { const dateA = new Date(a.OutageStartDateTime || a.CreationTime); const dateB = new Date(b.OutageStartDateTime || b.CreationTime); return dateA.getTime() - dateB.getTime(); }); const summary = { total_scheduled: maintenance.length, upcoming_24h: 0, upcoming_week: 0, affected_resources: new Set(), maintenance: maintenance.map((item) => { // Track affected resources item.AffectedResources?.forEach((resource) => { summary.affected_resources.add(resource.ResourceName); }); // Check timing const startTime = new Date(item.OutageStartDateTime || item.CreationTime); const now = new Date(); const hoursUntil = (startTime.getTime() - now.getTime()) / (1000 * 60 * 60); if (hoursUntil <= 24) summary.upcoming_24h++; if (hoursUntil <= 168) summary.upcoming_week++; // 7 days * 24 hours return { ...item, scheduled_start: item.OutageStartDateTime, scheduled_end: item.OutageEndDateTime, hours_until_start: Math.max(0, Math.round(hoursUntil)), duration_hours: item.OutageEndDateTime && item.OutageStartDateTime ? Math.round((new Date(item.OutageEndDateTime).getTime() - new Date(item.OutageStartDateTime).getTime()) / (1000 * 60 * 60)) : null, }; }), }; return { content: [ { type: "text", text: JSON.stringify({ ...summary, affected_resources: summary.affected_resources, }, null, 2), }, ], }; } async getSystemAnnouncements(limit = 50) { // Get both current and future announcements const [currentResponse, futureResponse] = await Promise.all([ this.httpClient.get("/wh2/news/v1/affiliation/access-ci.org/current_outages/"), this.httpClient.get("/wh2/news/v1/affiliation/access-ci.org/future_outages/"), ]); const currentOutages = currentResponse.data.results || []; const futureOutages = futureResponse.data.results || []; // Combine and sort by creation time const allAnnouncements = [...currentOutages, ...futureOutages] .sort((a, b) => { const dateA = new Date(a.CreationTime); const dateB = new Date(b.CreationTime); return dateB.getTime() - dateA.getTime(); // Most recent first }) .slice(0, limit); return { content: [ { type: "text", text: JSON.stringify({ total_announcements: allAnnouncements.length, current_outages: currentOutages.length, scheduled_maintenance: futureOutages.length, announcements: allAnnouncements, }, null, 2), }, ], }; } async checkResourceStatus(resourceIds) { // Get current outages to check against resource IDs const currentOutages = await this.getCurrentOutages(); const outageData = JSON.parse(currentOutages.content[0].text); const resourceStatus = resourceIds.map((resourceId) => { const affectedOutages = outageData.outages.filter((outage) => outage.AffectedResources?.some((resource) => resource.ResourceID?.toString() === resourceId || resource.ResourceName?.toLowerCase().includes(resourceId.toLowerCase()))); let status = "operational"; let severity = null; if (affectedOutages.length > 0) { status = "affected"; // Get highest severity const severities = affectedOutages.map((o) => o.severity); if (severities.includes("high")) severity = "high"; else if (severities.includes("medium")) severity = "medium"; else severity = "low"; } return { resource_id: resourceId, status, severity, active_outages: affectedOutages.length, outage_details: affectedOutages.map((outage) => ({ subject: outage.Subject, severity: outage.severity, posted: outage.posted_time, })), }; }); return { content: [ { type: "text", text: JSON.stringify({ checked_at: new Date().toISOString(), resources_checked: resourceIds.length, operational: resourceStatus.filter((r) => r.status === "operational").length, affected: resourceStatus.filter((r) => r.status === "affected") .length, resource_status: resourceStatus, }, null, 2), }, ], }; } }