@access-mcp/system-status 0.2.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -1,65 +1,88 @@
1
- import { BaseAccessServer, handleApiError } from '@access-mcp/shared';
1
+ import { BaseAccessServer, handleApiError } from "@access-mcp/shared";
2
2
  export class SystemStatusServer extends BaseAccessServer {
3
3
  constructor() {
4
- super('access-mcp-system-status', '0.1.0', 'https://operations-api.access-ci.org');
4
+ super("access-mcp-system-status", "0.4.0", "https://operations-api.access-ci.org");
5
5
  }
6
6
  getTools() {
7
7
  return [
8
8
  {
9
- name: 'get_current_outages',
10
- description: 'Get current system outages and issues affecting ACCESS-CI resources',
9
+ name: "get_current_outages",
10
+ description: "Get current system outages and issues affecting ACCESS-CI resources",
11
11
  inputSchema: {
12
- type: 'object',
12
+ type: "object",
13
13
  properties: {
14
14
  resource_filter: {
15
- type: 'string',
16
- description: 'Optional: filter by specific resource name or ID',
15
+ type: "string",
16
+ description: "Optional: filter by specific resource name or ID",
17
17
  },
18
18
  },
19
19
  required: [],
20
20
  },
21
21
  },
22
22
  {
23
- name: 'get_scheduled_maintenance',
24
- description: 'Get scheduled maintenance and future outages for ACCESS-CI resources',
23
+ name: "get_scheduled_maintenance",
24
+ description: "Get scheduled maintenance and future outages for ACCESS-CI resources",
25
25
  inputSchema: {
26
- type: 'object',
26
+ type: "object",
27
27
  properties: {
28
28
  resource_filter: {
29
- type: 'string',
30
- description: 'Optional: filter by specific resource name or ID',
29
+ type: "string",
30
+ description: "Optional: filter by specific resource name or ID",
31
31
  },
32
32
  },
33
33
  required: [],
34
34
  },
35
35
  },
36
36
  {
37
- name: 'get_system_announcements',
38
- description: 'Get all system announcements (current and scheduled)',
37
+ name: "get_past_outages",
38
+ description: "Get historical outages and past incidents affecting ACCESS-CI resources",
39
39
  inputSchema: {
40
- type: 'object',
40
+ type: "object",
41
41
  properties: {
42
+ resource_filter: {
43
+ type: "string",
44
+ description: "Optional: filter by specific resource name or ID",
45
+ },
42
46
  limit: {
43
- type: 'number',
44
- description: 'Maximum number of announcements to return (default: 50)',
47
+ type: "number",
48
+ description: "Maximum number of past outages to return (default: 100)",
45
49
  },
46
50
  },
47
51
  required: [],
48
52
  },
49
53
  },
50
54
  {
51
- name: 'check_resource_status',
52
- description: 'Check the operational status of specific ACCESS-CI resources',
55
+ name: "get_system_announcements",
56
+ description: "Get all system announcements (current and scheduled)",
53
57
  inputSchema: {
54
- type: 'object',
58
+ type: "object",
59
+ properties: {
60
+ limit: {
61
+ type: "number",
62
+ description: "Maximum number of announcements to return (default: 50)",
63
+ },
64
+ },
65
+ required: [],
66
+ },
67
+ },
68
+ {
69
+ name: "check_resource_status",
70
+ description: "Check the operational status of specific ACCESS-CI resources",
71
+ inputSchema: {
72
+ type: "object",
55
73
  properties: {
56
74
  resource_ids: {
57
- type: 'array',
58
- items: { type: 'string' },
59
- description: 'List of resource IDs to check status for',
75
+ type: "array",
76
+ items: { type: "string" },
77
+ description: "List of resource IDs or names to check status for",
78
+ },
79
+ use_group_api: {
80
+ type: "boolean",
81
+ description: "Use resource group API for more efficient querying (default: false)",
82
+ default: false,
60
83
  },
61
84
  },
62
- required: ['resource_ids'],
85
+ required: ["resource_ids"],
63
86
  },
64
87
  },
65
88
  ];
@@ -67,22 +90,28 @@ export class SystemStatusServer extends BaseAccessServer {
67
90
  getResources() {
68
91
  return [
69
92
  {
70
- uri: 'accessci://system-status',
71
- name: 'ACCESS-CI System Status',
72
- description: 'Real-time status of ACCESS-CI infrastructure, outages, and maintenance',
73
- mimeType: 'application/json',
93
+ uri: "accessci://system-status",
94
+ name: "ACCESS-CI System Status",
95
+ description: "Real-time status of ACCESS-CI infrastructure, outages, and maintenance",
96
+ mimeType: "application/json",
74
97
  },
75
98
  {
76
- uri: 'accessci://outages/current',
77
- name: 'Current Outages',
78
- description: 'Currently active outages and system issues',
79
- mimeType: 'application/json',
99
+ uri: "accessci://outages/current",
100
+ name: "Current Outages",
101
+ description: "Currently active outages and system issues",
102
+ mimeType: "application/json",
80
103
  },
81
104
  {
82
- uri: 'accessci://outages/scheduled',
83
- name: 'Scheduled Maintenance',
84
- description: 'Upcoming scheduled maintenance and planned outages',
85
- mimeType: 'application/json',
105
+ uri: "accessci://outages/scheduled",
106
+ name: "Scheduled Maintenance",
107
+ description: "Upcoming scheduled maintenance and planned outages",
108
+ mimeType: "application/json",
109
+ },
110
+ {
111
+ uri: "accessci://outages/past",
112
+ name: "Past Outages",
113
+ description: "Historical outages and past incidents",
114
+ mimeType: "application/json",
86
115
  },
87
116
  ];
88
117
  }
@@ -90,14 +119,16 @@ export class SystemStatusServer extends BaseAccessServer {
90
119
  const { name, arguments: args = {} } = request.params;
91
120
  try {
92
121
  switch (name) {
93
- case 'get_current_outages':
122
+ case "get_current_outages":
94
123
  return await this.getCurrentOutages(args.resource_filter);
95
- case 'get_scheduled_maintenance':
124
+ case "get_scheduled_maintenance":
96
125
  return await this.getScheduledMaintenance(args.resource_filter);
97
- case 'get_system_announcements':
126
+ case "get_past_outages":
127
+ return await this.getPastOutages(args.resource_filter, args.limit);
128
+ case "get_system_announcements":
98
129
  return await this.getSystemAnnouncements(args.limit);
99
- case 'check_resource_status':
100
- return await this.checkResourceStatus(args.resource_ids);
130
+ case "check_resource_status":
131
+ return await this.checkResourceStatus(args.resource_ids, args.use_group_api);
101
132
  default:
102
133
  throw new Error(`Unknown tool: ${name}`);
103
134
  }
@@ -106,7 +137,7 @@ export class SystemStatusServer extends BaseAccessServer {
106
137
  return {
107
138
  content: [
108
139
  {
109
- type: 'text',
140
+ type: "text",
110
141
  text: `Error: ${handleApiError(error)}`,
111
142
  },
112
143
  ],
@@ -116,44 +147,55 @@ export class SystemStatusServer extends BaseAccessServer {
116
147
  async handleResourceRead(request) {
117
148
  const { uri } = request.params;
118
149
  switch (uri) {
119
- case 'accessci://system-status':
150
+ case "accessci://system-status":
120
151
  return {
121
152
  contents: [
122
153
  {
123
154
  uri,
124
- mimeType: 'text/plain',
125
- text: 'ACCESS-CI System Status API - Monitor real-time status, outages, and maintenance for ACCESS-CI resources.',
155
+ mimeType: "text/plain",
156
+ text: "ACCESS-CI System Status API - Monitor real-time status, outages, and maintenance for ACCESS-CI resources.",
126
157
  },
127
158
  ],
128
159
  };
129
- case 'accessci://outages/current':
160
+ case "accessci://outages/current":
130
161
  const currentOutages = await this.getCurrentOutages();
131
162
  return {
132
163
  contents: [
133
164
  {
134
165
  uri,
135
- mimeType: 'application/json',
166
+ mimeType: "application/json",
136
167
  text: currentOutages.content[0].text,
137
168
  },
138
169
  ],
139
170
  };
140
- case 'accessci://outages/scheduled':
171
+ case "accessci://outages/scheduled":
141
172
  const scheduledMaintenance = await this.getScheduledMaintenance();
142
173
  return {
143
174
  contents: [
144
175
  {
145
176
  uri,
146
- mimeType: 'application/json',
177
+ mimeType: "application/json",
147
178
  text: scheduledMaintenance.content[0].text,
148
179
  },
149
180
  ],
150
181
  };
182
+ case "accessci://outages/past":
183
+ const pastOutages = await this.getPastOutages();
184
+ return {
185
+ contents: [
186
+ {
187
+ uri,
188
+ mimeType: "application/json",
189
+ text: pastOutages.content[0].text,
190
+ },
191
+ ],
192
+ };
151
193
  default:
152
194
  throw new Error(`Unknown resource: ${uri}`);
153
195
  }
154
196
  }
155
197
  async getCurrentOutages(resourceFilter) {
156
- const response = await this.httpClient.get('/wh2/news/v1/affiliation/access-ci.org/current_outages/');
198
+ const response = await this.httpClient.get("/wh2/news/v1/affiliation/access-ci.org/current_outages/");
157
199
  let outages = response.data.results || [];
158
200
  // Filter by resource if specified
159
201
  if (resourceFilter) {
@@ -172,16 +214,17 @@ export class SystemStatusServer extends BaseAccessServer {
172
214
  affectedResources.add(resource.ResourceName);
173
215
  });
174
216
  // Categorize severity (basic heuristic)
175
- const subject = outage.Subject?.toLowerCase() || '';
176
- let severity = 'unknown';
177
- if (subject.includes('emergency') || subject.includes('critical')) {
178
- severity = 'high';
217
+ const subject = outage.Subject?.toLowerCase() || "";
218
+ let severity = "unknown";
219
+ if (subject.includes("emergency") || subject.includes("critical")) {
220
+ severity = "high";
179
221
  }
180
- else if (subject.includes('maintenance') || subject.includes('scheduled')) {
181
- severity = 'low';
222
+ else if (subject.includes("maintenance") ||
223
+ subject.includes("scheduled")) {
224
+ severity = "low";
182
225
  }
183
226
  else {
184
- severity = 'medium';
227
+ severity = "medium";
185
228
  }
186
229
  severityCounts[severity]++;
187
230
  return {
@@ -195,22 +238,19 @@ export class SystemStatusServer extends BaseAccessServer {
195
238
  total_outages: outages.length,
196
239
  affected_resources: Array.from(affectedResources),
197
240
  severity_counts: severityCounts,
198
- outages: enhancedOutages
241
+ outages: enhancedOutages,
199
242
  };
200
243
  return {
201
244
  content: [
202
245
  {
203
- type: 'text',
204
- text: JSON.stringify({
205
- ...summary,
206
- affected_resources: summary.affected_resources
207
- }, null, 2),
246
+ type: "text",
247
+ text: JSON.stringify(summary, null, 2),
208
248
  },
209
249
  ],
210
250
  };
211
251
  }
212
252
  async getScheduledMaintenance(resourceFilter) {
213
- const response = await this.httpClient.get('/wh2/news/v1/affiliation/access-ci.org/future_outages/');
253
+ const response = await this.httpClient.get("/wh2/news/v1/affiliation/access-ci.org/future_outages/");
214
254
  let maintenance = response.data.results || [];
215
255
  // Filter by resource if specified
216
256
  if (resourceFilter) {
@@ -225,97 +265,217 @@ export class SystemStatusServer extends BaseAccessServer {
225
265
  const dateB = new Date(b.OutageStartDateTime || b.CreationTime);
226
266
  return dateA.getTime() - dateB.getTime();
227
267
  });
268
+ // Initialize tracking variables
269
+ const affectedResources = new Set();
270
+ let upcoming24h = 0;
271
+ let upcomingWeek = 0;
272
+ const enhancedMaintenance = maintenance.map((item) => {
273
+ // Track affected resources
274
+ item.AffectedResources?.forEach((resource) => {
275
+ affectedResources.add(resource.ResourceName);
276
+ });
277
+ // Check timing - only use OutageStartDateTime for scheduling, fallback shows warning
278
+ const hasScheduledTime = !!item.OutageStartDateTime;
279
+ const startTime = new Date(item.OutageStartDateTime || item.CreationTime);
280
+ const now = new Date();
281
+ const hoursUntil = (startTime.getTime() - now.getTime()) / (1000 * 60 * 60);
282
+ if (hoursUntil <= 24)
283
+ upcoming24h++;
284
+ if (hoursUntil <= 168)
285
+ upcomingWeek++; // 7 days * 24 hours
286
+ return {
287
+ ...item,
288
+ scheduled_start: item.OutageStartDateTime,
289
+ scheduled_end: item.OutageEndDateTime,
290
+ hours_until_start: Math.max(0, Math.round(hoursUntil)),
291
+ duration_hours: item.OutageEndDateTime && item.OutageStartDateTime
292
+ ? Math.round((new Date(item.OutageEndDateTime).getTime() -
293
+ new Date(item.OutageStartDateTime).getTime()) /
294
+ (1000 * 60 * 60))
295
+ : null,
296
+ has_scheduled_time: hasScheduledTime,
297
+ };
298
+ });
228
299
  const summary = {
229
300
  total_scheduled: maintenance.length,
230
- upcoming_24h: 0,
231
- upcoming_week: 0,
232
- affected_resources: new Set(),
233
- maintenance: maintenance.map((item) => {
234
- // Track affected resources
235
- item.AffectedResources?.forEach((resource) => {
236
- summary.affected_resources.add(resource.ResourceName);
237
- });
238
- // Check timing
239
- const startTime = new Date(item.OutageStartDateTime || item.CreationTime);
240
- const now = new Date();
241
- const hoursUntil = (startTime.getTime() - now.getTime()) / (1000 * 60 * 60);
242
- if (hoursUntil <= 24)
243
- summary.upcoming_24h++;
244
- if (hoursUntil <= 168)
245
- summary.upcoming_week++; // 7 days * 24 hours
246
- return {
247
- ...item,
248
- scheduled_start: item.OutageStartDateTime,
249
- scheduled_end: item.OutageEndDateTime,
250
- hours_until_start: Math.max(0, Math.round(hoursUntil)),
251
- duration_hours: item.OutageEndDateTime && item.OutageStartDateTime
252
- ? Math.round((new Date(item.OutageEndDateTime).getTime() -
253
- new Date(item.OutageStartDateTime).getTime()) / (1000 * 60 * 60))
254
- : null,
255
- };
256
- })
301
+ upcoming_24h: upcoming24h,
302
+ upcoming_week: upcomingWeek,
303
+ affected_resources: Array.from(affectedResources),
304
+ maintenance: enhancedMaintenance,
257
305
  };
258
306
  return {
259
307
  content: [
260
308
  {
261
- type: 'text',
262
- text: JSON.stringify({
263
- ...summary,
264
- affected_resources: summary.affected_resources
265
- }, null, 2),
309
+ type: "text",
310
+ text: JSON.stringify(summary, null, 2),
311
+ },
312
+ ],
313
+ };
314
+ }
315
+ async getPastOutages(resourceFilter, limit = 100) {
316
+ const response = await this.httpClient.get("/wh2/news/v1/affiliation/access-ci.org/past_outages/");
317
+ let pastOutages = response.data.results || [];
318
+ // Filter by resource if specified
319
+ if (resourceFilter) {
320
+ const filter = resourceFilter.toLowerCase();
321
+ pastOutages = pastOutages.filter((outage) => outage.Subject?.toLowerCase().includes(filter) ||
322
+ outage.AffectedResources?.some((resource) => resource.ResourceName?.toLowerCase().includes(filter) ||
323
+ resource.ResourceID?.toString().includes(filter)));
324
+ }
325
+ // Sort by outage end time (most recent first)
326
+ pastOutages.sort((a, b) => {
327
+ const dateA = new Date(a.OutageEndDateTime || a.LastModificationTime);
328
+ const dateB = new Date(b.OutageEndDateTime || b.LastModificationTime);
329
+ return dateB.getTime() - dateA.getTime();
330
+ });
331
+ // Apply limit
332
+ if (limit && pastOutages.length > limit) {
333
+ pastOutages = pastOutages.slice(0, limit);
334
+ }
335
+ // Initialize tracking variables
336
+ const affectedResources = new Set();
337
+ const outageTypes = new Set();
338
+ const recentOutages = pastOutages.filter((outage) => {
339
+ const endTime = new Date(outage.OutageEndDateTime || outage.LastModificationTime);
340
+ const daysAgo = (Date.now() - endTime.getTime()) / (1000 * 60 * 60 * 24);
341
+ return daysAgo <= 30; // Last 30 days
342
+ });
343
+ // Enhance outages with calculated fields
344
+ const enhancedOutages = pastOutages.map((outage) => {
345
+ // Track affected resources
346
+ outage.AffectedResources?.forEach((resource) => {
347
+ affectedResources.add(resource.ResourceName);
348
+ });
349
+ // Track outage types
350
+ if (outage.OutageType) {
351
+ outageTypes.add(outage.OutageType);
352
+ }
353
+ // Calculate duration
354
+ const startTime = new Date(outage.OutageStartDateTime);
355
+ const endTime = new Date(outage.OutageEndDateTime);
356
+ const durationHours = Math.round((endTime.getTime() - startTime.getTime()) / (1000 * 60 * 60));
357
+ // Calculate how long ago it ended
358
+ const daysAgo = Math.round((Date.now() - endTime.getTime()) / (1000 * 60 * 60 * 24));
359
+ return {
360
+ ...outage,
361
+ outage_start: outage.OutageStartDateTime,
362
+ outage_end: outage.OutageEndDateTime,
363
+ duration_hours: durationHours,
364
+ days_ago: daysAgo,
365
+ outage_type: outage.OutageType,
366
+ posted_time: outage.CreationTime,
367
+ last_updated: outage.LastModificationTime,
368
+ };
369
+ });
370
+ const summary = {
371
+ total_past_outages: enhancedOutages.length,
372
+ recent_outages_30_days: recentOutages.length,
373
+ affected_resources: Array.from(affectedResources),
374
+ outage_types: Array.from(outageTypes),
375
+ average_duration_hours: enhancedOutages.length > 0
376
+ ? Math.round(enhancedOutages
377
+ .filter((o) => o.duration_hours > 0)
378
+ .reduce((sum, o) => sum + o.duration_hours, 0) /
379
+ enhancedOutages.filter((o) => o.duration_hours > 0).length)
380
+ : 0,
381
+ outages: enhancedOutages,
382
+ };
383
+ return {
384
+ content: [
385
+ {
386
+ type: "text",
387
+ text: JSON.stringify(summary, null, 2),
266
388
  },
267
389
  ],
268
390
  };
269
391
  }
270
392
  async getSystemAnnouncements(limit = 50) {
271
- // Get both current and future announcements
272
- const [currentResponse, futureResponse] = await Promise.all([
273
- this.httpClient.get('/wh2/news/v1/affiliation/access-ci.org/current_outages/'),
274
- this.httpClient.get('/wh2/news/v1/affiliation/access-ci.org/future_outages/')
393
+ // Get current, future, and recent past announcements for comprehensive view
394
+ const [currentResponse, futureResponse, pastResponse] = await Promise.all([
395
+ this.httpClient.get("/wh2/news/v1/affiliation/access-ci.org/current_outages/"),
396
+ this.httpClient.get("/wh2/news/v1/affiliation/access-ci.org/future_outages/"),
397
+ this.httpClient.get("/wh2/news/v1/affiliation/access-ci.org/past_outages/"),
275
398
  ]);
276
399
  const currentOutages = currentResponse.data.results || [];
277
400
  const futureOutages = futureResponse.data.results || [];
278
- // Combine and sort by creation time
279
- const allAnnouncements = [...currentOutages, ...futureOutages]
401
+ const pastOutages = pastResponse.data.results || [];
402
+ // Filter recent past outages (last 30 days) for announcements
403
+ const recentPastOutages = pastOutages.filter((outage) => {
404
+ const endTime = new Date(outage.OutageEndDateTime || outage.LastModificationTime);
405
+ const daysAgo = (Date.now() - endTime.getTime()) / (1000 * 60 * 60 * 24);
406
+ return daysAgo <= 30;
407
+ });
408
+ // Combine all announcements and sort by most relevant date
409
+ const allAnnouncements = [
410
+ ...currentOutages.map((item) => ({ ...item, category: 'current' })),
411
+ ...futureOutages.map((item) => ({ ...item, category: 'scheduled' })),
412
+ ...recentPastOutages.map((item) => ({ ...item, category: 'recent_past' })),
413
+ ]
280
414
  .sort((a, b) => {
281
- const dateA = new Date(a.CreationTime);
282
- const dateB = new Date(b.CreationTime);
415
+ // Sort by most relevant date: current first, then future by start time, then past by end time
416
+ if (a.category === 'current' && b.category !== 'current')
417
+ return -1;
418
+ if (b.category === 'current' && a.category !== 'current')
419
+ return 1;
420
+ const dateA = new Date(a.OutageStartDateTime || a.CreationTime);
421
+ const dateB = new Date(b.OutageStartDateTime || b.CreationTime);
283
422
  return dateB.getTime() - dateA.getTime(); // Most recent first
284
423
  })
285
424
  .slice(0, limit);
425
+ const summary = {
426
+ total_announcements: allAnnouncements.length,
427
+ current_outages: currentOutages.length,
428
+ scheduled_maintenance: futureOutages.length,
429
+ recent_past_outages: recentPastOutages.length,
430
+ categories: {
431
+ current: allAnnouncements.filter(a => a.category === 'current').length,
432
+ scheduled: allAnnouncements.filter(a => a.category === 'scheduled').length,
433
+ recent_past: allAnnouncements.filter(a => a.category === 'recent_past').length,
434
+ },
435
+ announcements: allAnnouncements,
436
+ };
286
437
  return {
287
438
  content: [
288
439
  {
289
- type: 'text',
290
- text: JSON.stringify({
291
- total_announcements: allAnnouncements.length,
292
- current_outages: currentOutages.length,
293
- scheduled_maintenance: futureOutages.length,
294
- announcements: allAnnouncements
295
- }, null, 2),
440
+ type: "text",
441
+ text: JSON.stringify(summary, null, 2),
296
442
  },
297
443
  ],
298
444
  };
299
445
  }
300
- async checkResourceStatus(resourceIds) {
301
- // Get current outages to check against resource IDs
302
- const currentOutages = await this.getCurrentOutages();
303
- const outageData = JSON.parse(currentOutages.content[0].text);
304
- const resourceStatus = resourceIds.map(resourceId => {
305
- const affectedOutages = outageData.outages.filter((outage) => outage.AffectedResources?.some((resource) => resource.ResourceID?.toString() === resourceId ||
446
+ async checkResourceStatus(resourceIds, useGroupApi = false) {
447
+ if (useGroupApi) {
448
+ return await this.checkResourceStatusViaGroups(resourceIds);
449
+ }
450
+ // Efficient approach: fetch raw current outages data once
451
+ const response = await this.httpClient.get("/wh2/news/v1/affiliation/access-ci.org/current_outages/");
452
+ const rawOutages = response.data.results || [];
453
+ const resourceStatus = resourceIds.map((resourceId) => {
454
+ const affectedOutages = rawOutages.filter((outage) => outage.AffectedResources?.some((resource) => resource.ResourceID?.toString() === resourceId ||
306
455
  resource.ResourceName?.toLowerCase().includes(resourceId.toLowerCase())));
307
- let status = 'operational';
456
+ let status = "operational";
308
457
  let severity = null;
309
458
  if (affectedOutages.length > 0) {
310
- status = 'affected';
311
- // Get highest severity
312
- const severities = affectedOutages.map((o) => o.severity);
313
- if (severities.includes('high'))
314
- severity = 'high';
315
- else if (severities.includes('medium'))
316
- severity = 'medium';
459
+ status = "affected";
460
+ // Get highest severity using same logic as getCurrentOutages
461
+ const severities = affectedOutages.map((outage) => {
462
+ const subject = outage.Subject?.toLowerCase() || "";
463
+ if (subject.includes("emergency") || subject.includes("critical")) {
464
+ return "high";
465
+ }
466
+ else if (subject.includes("maintenance") || subject.includes("scheduled")) {
467
+ return "low";
468
+ }
469
+ else {
470
+ return "medium";
471
+ }
472
+ });
473
+ if (severities.includes("high"))
474
+ severity = "high";
475
+ else if (severities.includes("medium"))
476
+ severity = "medium";
317
477
  else
318
- severity = 'low';
478
+ severity = "low";
319
479
  }
320
480
  return {
321
481
  resource_id: resourceId,
@@ -324,21 +484,77 @@ export class SystemStatusServer extends BaseAccessServer {
324
484
  active_outages: affectedOutages.length,
325
485
  outage_details: affectedOutages.map((outage) => ({
326
486
  subject: outage.Subject,
327
- severity: outage.severity,
328
- posted: outage.posted_time
329
- }))
487
+ severity,
488
+ posted: outage.CreationTime,
489
+ last_updated: outage.LastModificationTime,
490
+ })),
330
491
  };
331
492
  });
332
493
  return {
333
494
  content: [
334
495
  {
335
- type: 'text',
496
+ type: "text",
497
+ text: JSON.stringify({
498
+ checked_at: new Date().toISOString(),
499
+ resources_checked: resourceIds.length,
500
+ operational: resourceStatus.filter((r) => r.status === "operational").length,
501
+ affected: resourceStatus.filter((r) => r.status === "affected")
502
+ .length,
503
+ api_method: "direct_outages_check",
504
+ resource_status: resourceStatus,
505
+ }, null, 2),
506
+ },
507
+ ],
508
+ };
509
+ }
510
+ async checkResourceStatusViaGroups(resourceIds) {
511
+ // Try to use the more efficient group-based API
512
+ const statusPromises = resourceIds.map(async (resourceId) => {
513
+ try {
514
+ const response = await this.httpClient.get(`/wh2/news/v1/info_groupid/${resourceId}/`);
515
+ const groupData = response.data.results || [];
516
+ const hasOutages = groupData.length > 0;
517
+ return {
518
+ resource_id: resourceId,
519
+ status: hasOutages ? "affected" : "operational",
520
+ severity: hasOutages ? "medium" : null,
521
+ active_outages: groupData.length,
522
+ outage_details: groupData.map((outage) => ({
523
+ subject: outage.Subject,
524
+ posted: outage.CreationTime,
525
+ last_updated: outage.LastModificationTime,
526
+ })),
527
+ api_method: "group_specific",
528
+ };
529
+ }
530
+ catch (error) {
531
+ // Fallback to general check if group API fails
532
+ return {
533
+ resource_id: resourceId,
534
+ status: "unknown",
535
+ severity: null,
536
+ active_outages: 0,
537
+ outage_details: [],
538
+ error: `Group API failed for ${resourceId}`,
539
+ api_method: "group_specific_failed",
540
+ };
541
+ }
542
+ });
543
+ const resourceStatus = await Promise.all(statusPromises);
544
+ return {
545
+ content: [
546
+ {
547
+ type: "text",
336
548
  text: JSON.stringify({
337
549
  checked_at: new Date().toISOString(),
338
550
  resources_checked: resourceIds.length,
339
- operational: resourceStatus.filter(r => r.status === 'operational').length,
340
- affected: resourceStatus.filter(r => r.status === 'affected').length,
341
- resource_status: resourceStatus
551
+ operational: resourceStatus.filter((r) => r.status === "operational").length,
552
+ affected: resourceStatus.filter((r) => r.status === "affected")
553
+ .length,
554
+ unknown: resourceStatus.filter((r) => r.status === "unknown")
555
+ .length,
556
+ api_method: "resource_group_api",
557
+ resource_status: resourceStatus,
342
558
  }, null, 2),
343
559
  },
344
560
  ],