@access-mcp/system-status 0.5.0 → 0.5.1

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/README.md CHANGED
@@ -1,11 +1,10 @@
1
1
  # System Status MCP Server
2
2
 
3
- MCP server providing real-time system status information for ACCESS-CI resources. Provides critical operational information about ACCESS-CI systems, including current outages, scheduled maintenance, and system-wide announcements.
3
+ MCP server for real-time ACCESS-CI system status including current outages, scheduled maintenance, and operational status.
4
4
 
5
5
  ## Usage Examples
6
6
 
7
- ### **Current Status**
8
-
7
+ ### Current Status
9
8
  ```
10
9
  "Current ACCESS-CI outages"
11
10
  "Delta operational status"
@@ -13,8 +12,7 @@ MCP server providing real-time system status information for ACCESS-CI resources
13
12
  "GPU systems status check"
14
13
  ```
15
14
 
16
- ### **Maintenance & Incidents**
17
-
15
+ ### Maintenance & Incidents
18
16
  ```
19
17
  "Scheduled maintenance this week"
20
18
  "Next Expanse maintenance window"
@@ -24,36 +22,33 @@ MCP server providing real-time system status information for ACCESS-CI resources
24
22
 
25
23
  ## Tools
26
24
 
27
- ### get_infrastructure_news
25
+ ### `get_infrastructure_news`
28
26
 
29
- Comprehensive ACCESS-CI infrastructure status and outage information. Get current outages, scheduled maintenance, past incidents, and operational status for ACCESS-CI resources. Provides real-time monitoring of system health and availability.
27
+ Get infrastructure status, outages, and maintenance information for ACCESS-CI resources.
30
28
 
31
29
  **Parameters:**
32
-
33
- - `resource` (string, optional): Filter by specific resource name or ID (e.g., 'delta', 'bridges2')
34
- - `time` (string, optional): Time period for infrastructure news: 'current' (active outages), 'scheduled' (future maintenance), 'past' (historical incidents), 'all' (comprehensive overview). Default: 'current'
35
- - `resource_ids` (array of strings, optional): Check operational status for specific resource IDs (returns 'operational' or 'affected' status). Use instead of 'resource' parameter for status checking
36
- - `limit` (number, optional): Maximum number of items to return (default: 50 for 'all', 100 for 'past')
37
- - `use_group_api` (boolean, optional): Use resource group API for status checking (only with resource_ids parameter, default: false)
30
+ | Parameter | Type | Description |
31
+ |-----------|------|-------------|
32
+ | `resource` | string | Filter by resource name (e.g., "delta", "bridges2") |
33
+ | `time` | enum | Time period: `current`, `scheduled`, `past`, `all` (default: "current") |
34
+ | `resource_ids` | array | Check status for specific resource IDs |
35
+ | `limit` | number | Max results (default: 50 for "all", 100 for "past") |
36
+ | `use_group_api` | boolean | Use resource group API for status checking (default: false) |
38
37
 
39
38
  **Examples:**
40
-
41
- ```typescript
39
+ ```javascript
42
40
  // Get current outages across all resources
43
41
  get_infrastructure_news({})
44
42
 
45
43
  // Get scheduled maintenance
46
44
  get_infrastructure_news({ time: "scheduled" })
47
45
 
48
- // Get comprehensive overview of all infrastructure news
46
+ // Get comprehensive overview
49
47
  get_infrastructure_news({ time: "all" })
50
48
 
51
49
  // Check current status for specific resource
52
50
  get_infrastructure_news({ resource: "delta", time: "current" })
53
51
 
54
- // Get all news for specific resource
55
- get_infrastructure_news({ resource: "delta", time: "all" })
56
-
57
52
  // Check operational status of specific resources
58
53
  get_infrastructure_news({
59
54
  resource_ids: ["delta.ncsa.access-ci.org", "bridges2.psc.access-ci.org"]
@@ -63,10 +58,6 @@ get_infrastructure_news({
63
58
  get_infrastructure_news({ time: "past", limit: 50 })
64
59
  ```
65
60
 
66
- ## Resources
67
-
68
- - `accessci://system-status`: Current operational status of all ACCESS-CI resources
69
-
70
61
  ## Installation
71
62
 
72
63
  ```bash
@@ -75,8 +66,6 @@ npm install -g @access-mcp/system-status
75
66
 
76
67
  ## Configuration
77
68
 
78
- Add to your Claude Desktop configuration:
79
-
80
69
  ```json
81
70
  {
82
71
  "mcpServers": {
@@ -88,111 +77,6 @@ Add to your Claude Desktop configuration:
88
77
  }
89
78
  ```
90
79
 
91
- ## Usage Examples
92
-
93
- ### 🚨 **Monitor Current Issues**
94
-
95
- - "Are there any current outages on ACCESS-CI?"
96
- - "Is Delta currently operational?"
97
- - "What systems are experiencing issues right now?"
98
-
99
- ### 🔧 **Track Maintenance Windows**
100
-
101
- - "When is the next maintenance for Expanse?"
102
- - "Show me all scheduled maintenance for this week"
103
- - "Is there upcoming maintenance on Bridges-2?"
104
-
105
- ### 📢 **System Announcements**
106
-
107
- - "What are the latest system announcements?"
108
- - "Are there any important notices for ACCESS users?"
109
- - "Show me recent updates about system changes"
110
-
111
- ### ✅ **Check Resource Status**
112
-
113
- - "What's the current status of Anvil?"
114
- - "Is Frontera available for job submission?"
115
- - "Check if all GPU systems are operational"
116
-
117
- ## Detailed Usage Examples
118
-
119
- ### Checking Current Outages
120
-
121
- **Natural Language**: "Are there any systems down right now?"
122
-
123
- **Tool Call**:
124
- ```typescript
125
- const outages = await get_infrastructure_news({});
126
- // or explicitly: get_infrastructure_news({ time: "current" })
127
- ```
128
-
129
- **Returns**: List of active outages with:
130
- - Affected resources
131
- - Severity categorization (high/medium/low)
132
- - Start time and expected resolution
133
- - Impact description
134
- - Summary statistics (total outages, affected resources)
135
-
136
- ### Finding Scheduled Maintenance
137
-
138
- **Natural Language**: "When is Delta scheduled for maintenance?"
139
-
140
- **Tool Call**:
141
- ```typescript
142
- const maintenance = await get_infrastructure_news({
143
- resource: "delta",
144
- time: "scheduled"
145
- });
146
- ```
147
-
148
- **Returns**: Upcoming maintenance windows including:
149
- - Scheduled start and end times
150
- - Systems affected
151
- - Duration in hours
152
- - Hours until maintenance starts
153
- - Summary (upcoming in 24h, upcoming this week)
154
-
155
- ### Getting Comprehensive System Overview
156
-
157
- **Natural Language**: "What are the latest announcements?"
158
-
159
- **Tool Call**:
160
- ```typescript
161
- const announcements = await get_infrastructure_news({
162
- time: "all",
163
- limit: 10
164
- });
165
- ```
166
-
167
- **Returns**: Comprehensive overview including:
168
- - Current outages
169
- - Scheduled maintenance
170
- - Recent past outages (last 30 days)
171
- - Category breakdown
172
- - Sorted by relevance (current issues first)
173
-
174
- ### Checking Specific Resource Status
175
-
176
- **Natural Language**: "Is Expanse available?"
177
-
178
- **Tool Call**:
179
- ```typescript
180
- const status = await get_infrastructure_news({
181
- resource_ids: ["expanse.sdsc.access-ci.org"]
182
- });
183
- ```
184
-
185
- **Returns**: Current operational status:
186
- - Overall status ("operational" or "affected")
187
- - Severity level if affected
188
- - Number of active outages
189
- - Outage details with subjects
190
- - Timestamp of status check
191
-
192
- ## API Endpoints
193
-
194
- This server connects to the ACCESS-CI Operations API at `https://operations-api.access-ci.org`
195
-
196
- ## License
80
+ ## Resources
197
81
 
198
- MIT
82
+ - `accessci://system-status` - Current operational status of all ACCESS-CI resources
@@ -8,12 +8,14 @@ describe("SystemStatusServer Integration Tests", () => {
8
8
  describe("Real API Integration", () => {
9
9
  it("should fetch current outages from real API", async () => {
10
10
  const result = await server["handleToolCall"]({
11
+ method: "tools/call",
11
12
  params: {
12
13
  name: "get_infrastructure_news",
13
- arguments: { time: "current", limit: 5 }
14
- }
14
+ arguments: { time: "current", limit: 5 },
15
+ },
15
16
  });
16
- const responseData = JSON.parse(result.content[0].text);
17
+ const content = result.content[0];
18
+ const responseData = JSON.parse(content.text);
17
19
  expect(responseData).toHaveProperty("total_outages");
18
20
  expect(responseData).toHaveProperty("affected_resources");
19
21
  expect(responseData).toHaveProperty("severity_counts");
@@ -32,11 +34,14 @@ describe("SystemStatusServer Integration Tests", () => {
32
34
  }, 10000);
33
35
  it("should fetch scheduled maintenance from real API", async () => {
34
36
  const result = await server["handleToolCall"]({
37
+ method: "tools/call",
35
38
  params: {
36
- name: "get_infrastructure_news", arguments: { time: "scheduled", limit: 5 }
37
- }
39
+ name: "get_infrastructure_news",
40
+ arguments: { time: "scheduled", limit: 5 },
41
+ },
38
42
  });
39
- const responseData = JSON.parse(result.content[0].text);
43
+ const content = result.content[0];
44
+ const responseData = JSON.parse(content.text);
40
45
  expect(responseData).toHaveProperty("total_scheduled");
41
46
  expect(responseData).toHaveProperty("upcoming_24h");
42
47
  expect(responseData).toHaveProperty("upcoming_week");
@@ -52,11 +57,14 @@ describe("SystemStatusServer Integration Tests", () => {
52
57
  }, 10000);
53
58
  it("should fetch past outages from real API", async () => {
54
59
  const result = await server["handleToolCall"]({
60
+ method: "tools/call",
55
61
  params: {
56
- name: "get_infrastructure_news", arguments: { time: "past", limit: 5 }
57
- }
62
+ name: "get_infrastructure_news",
63
+ arguments: { time: "past", limit: 5 },
64
+ },
58
65
  });
59
- const responseData = JSON.parse(result.content[0].text);
66
+ const content = result.content[0];
67
+ const responseData = JSON.parse(content.text);
60
68
  expect(responseData).toHaveProperty("total_past_outages");
61
69
  expect(responseData).toHaveProperty("recent_outages_30_days");
62
70
  expect(responseData).toHaveProperty("affected_resources");
@@ -74,12 +82,14 @@ describe("SystemStatusServer Integration Tests", () => {
74
82
  }, 10000);
75
83
  it("should get comprehensive system announcements", async () => {
76
84
  const result = await server["handleToolCall"]({
85
+ method: "tools/call",
77
86
  params: {
78
87
  name: "get_infrastructure_news",
79
- arguments: { time: "all", limit: 20 }
80
- }
88
+ arguments: { time: "all", limit: 20 },
89
+ },
81
90
  });
82
- const responseData = JSON.parse(result.content[0].text);
91
+ const content = result.content[0];
92
+ const responseData = JSON.parse(content.text);
83
93
  expect(responseData).toHaveProperty("total_announcements");
84
94
  expect(responseData).toHaveProperty("current_outages");
85
95
  expect(responseData).toHaveProperty("scheduled_maintenance");
@@ -99,15 +109,17 @@ describe("SystemStatusServer Integration Tests", () => {
99
109
  it("should check resource status with direct method", async () => {
100
110
  // Test with common resource names that might exist
101
111
  const result = await server["handleToolCall"]({
112
+ method: "tools/call",
102
113
  params: {
103
114
  name: "get_infrastructure_news",
104
115
  arguments: {
105
116
  ids: ["anvil", "bridges", "jetstream"],
106
- use_group_api: false
107
- }
108
- }
117
+ use_group_api: false,
118
+ },
119
+ },
109
120
  });
110
- const responseData = JSON.parse(result.content[0].text);
121
+ const content = result.content[0];
122
+ const responseData = JSON.parse(content.text);
111
123
  expect(responseData).toHaveProperty("checked_at");
112
124
  expect(responseData).toHaveProperty("resources_checked", 3);
113
125
  expect(responseData).toHaveProperty("operational");
@@ -127,15 +139,17 @@ describe("SystemStatusServer Integration Tests", () => {
127
139
  it("should test group API functionality", async () => {
128
140
  // Test group API with a resource that might have a group ID
129
141
  const result = await server["handleToolCall"]({
142
+ method: "tools/call",
130
143
  params: {
131
144
  name: "get_infrastructure_news",
132
145
  arguments: {
133
146
  ids: ["anvil"],
134
- use_group_api: true
135
- }
136
- }
147
+ use_group_api: true,
148
+ },
149
+ },
137
150
  });
138
- const responseData = JSON.parse(result.content[0].text);
151
+ const content = result.content[0];
152
+ const responseData = JSON.parse(content.text);
139
153
  expect(responseData).toHaveProperty("api_method", "resource_group_api");
140
154
  expect(responseData).toHaveProperty("resources_checked", 1);
141
155
  expect(responseData.resource_status).toHaveLength(1);
@@ -156,12 +170,14 @@ describe("SystemStatusServer Integration Tests", () => {
156
170
  }, 10000);
157
171
  it("should filter outages by resource correctly", async () => {
158
172
  const result = await server["handleToolCall"]({
173
+ method: "tools/call",
159
174
  params: {
160
175
  name: "get_infrastructure_news",
161
- arguments: { time: "current", query: "anvil" }
162
- }
176
+ arguments: { time: "current", query: "anvil" },
177
+ },
163
178
  });
164
- const responseData = JSON.parse(result.content[0].text);
179
+ const content = result.content[0];
180
+ const responseData = JSON.parse(content.text);
165
181
  expect(responseData).toHaveProperty("total_outages");
166
182
  // If there are any results, they should match the filter
167
183
  if (responseData.outages.length > 0) {
@@ -178,11 +194,12 @@ describe("SystemStatusServer Integration Tests", () => {
178
194
  "accessci://system-status",
179
195
  "accessci://outages/current",
180
196
  "accessci://outages/scheduled",
181
- "accessci://outages/past"
197
+ "accessci://outages/past",
182
198
  ];
183
199
  for (const uri of resources) {
184
200
  const result = await server["handleResourceRead"]({
185
- params: { uri }
201
+ method: "resources/read",
202
+ params: { uri },
186
203
  });
187
204
  expect(result.contents).toHaveLength(1);
188
205
  expect(result.contents[0]).toHaveProperty("uri", uri);
@@ -199,24 +216,28 @@ describe("SystemStatusServer Integration Tests", () => {
199
216
  it("should handle empty API responses", async () => {
200
217
  // This tests the robustness of our logic with potentially empty responses
201
218
  const result = await server["handleToolCall"]({
219
+ method: "tools/call",
202
220
  params: {
203
221
  name: "get_infrastructure_news",
204
- arguments: { time: "current", query: "nonexistent-resource-xyz-12345" }
205
- }
222
+ arguments: { time: "current", query: "nonexistent-resource-xyz-12345" },
223
+ },
206
224
  });
207
- const responseData = JSON.parse(result.content[0].text);
225
+ const content = result.content[0];
226
+ const responseData = JSON.parse(content.text);
208
227
  expect(responseData).toHaveProperty("total_outages", 0);
209
228
  expect(responseData.outages).toHaveLength(0);
210
229
  expect(responseData.affected_resources).toHaveLength(0);
211
230
  }, 10000);
212
231
  it("should handle large limit values gracefully", async () => {
213
232
  const result = await server["handleToolCall"]({
233
+ method: "tools/call",
214
234
  params: {
215
235
  name: "get_infrastructure_news",
216
- arguments: { time: "past", limit: 1000 }
217
- }
236
+ arguments: { time: "past", limit: 1000 },
237
+ },
218
238
  });
219
- const responseData = JSON.parse(result.content[0].text);
239
+ const content = result.content[0];
240
+ const responseData = JSON.parse(content.text);
220
241
  expect(responseData).toHaveProperty("total_past_outages");
221
242
  // Should not crash or timeout
222
243
  expect(responseData.outages.length).toBeLessThanOrEqual(1000);
@@ -1,5 +1,8 @@
1
1
  import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
2
2
  import { SystemStatusServer } from "../server.js";
3
+ import { createRequire } from "module";
4
+ const require = createRequire(import.meta.url);
5
+ const { version } = require("../../package.json");
3
6
  // Mock axios
4
7
  vi.mock("axios");
5
8
  describe("SystemStatusServer", () => {
@@ -12,9 +15,7 @@ describe("SystemStatusServer", () => {
12
15
  Content: "Critical issue requiring immediate attention",
13
16
  OutageStart: "2024-08-27T10:00:00Z",
14
17
  OutageEnd: "2024-08-27T11:00:00Z",
15
- AffectedResources: [
16
- { ResourceName: "Anvil", ResourceID: "anvil-1" }
17
- ]
18
+ AffectedResources: [{ ResourceName: "Anvil", ResourceID: "anvil-1" }],
18
19
  },
19
20
  {
20
21
  id: "2",
@@ -22,10 +23,8 @@ describe("SystemStatusServer", () => {
22
23
  Content: "Regular maintenance window",
23
24
  OutageStart: "2024-08-27T08:00:00Z",
24
25
  OutageEnd: "2024-08-27T08:30:00Z",
25
- AffectedResources: [
26
- { ResourceName: "Bridges-2", ResourceID: "bridges2-1" }
27
- ]
28
- }
26
+ AffectedResources: [{ ResourceName: "Bridges-2", ResourceID: "bridges2-1" }],
27
+ },
29
28
  ];
30
29
  const mockFutureOutagesData = [
31
30
  {
@@ -34,10 +33,8 @@ describe("SystemStatusServer", () => {
34
33
  Content: "Planned maintenance",
35
34
  OutageStart: "2024-08-30T10:00:00Z",
36
35
  OutageEnd: "2024-08-30T14:00:00Z",
37
- AffectedResources: [
38
- { ResourceName: "Jetstream", ResourceID: "jetstream-1" }
39
- ]
40
- }
36
+ AffectedResources: [{ ResourceName: "Jetstream", ResourceID: "jetstream-1" }],
37
+ },
41
38
  ];
42
39
  const mockPastOutagesData = [
43
40
  {
@@ -47,10 +44,8 @@ describe("SystemStatusServer", () => {
47
44
  OutageStart: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(), // 3 days ago
48
45
  OutageEnd: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000 + 6 * 60 * 60 * 1000).toISOString(), // 3 days ago + 6 hours
49
46
  OutageType: "Full",
50
- AffectedResources: [
51
- { ResourceName: "Stampede3", ResourceID: "stampede3-1" }
52
- ]
53
- }
47
+ AffectedResources: [{ ResourceName: "Stampede3", ResourceID: "stampede3-1" }],
48
+ },
54
49
  ];
55
50
  beforeEach(() => {
56
51
  server = new SystemStatusServer();
@@ -71,7 +66,7 @@ describe("SystemStatusServer", () => {
71
66
  it("should initialize with correct server info", () => {
72
67
  expect(server).toBeDefined();
73
68
  expect(server["serverName"]).toBe("access-mcp-system-status");
74
- expect(server["version"]).toBe("0.4.0");
69
+ expect(server["version"]).toBe(version);
75
70
  expect(server["baseURL"]).toBe("https://operations-api.access-ci.org");
76
71
  });
77
72
  it("should provide correct tools", () => {
@@ -82,11 +77,11 @@ describe("SystemStatusServer", () => {
82
77
  it("should provide correct resources", () => {
83
78
  const resources = server["getResources"]();
84
79
  expect(resources).toHaveLength(4);
85
- expect(resources.map(r => r.uri)).toEqual([
80
+ expect(resources.map((r) => r.uri)).toEqual([
86
81
  "accessci://system-status",
87
82
  "accessci://outages/current",
88
83
  "accessci://outages/scheduled",
89
- "accessci://outages/past"
84
+ "accessci://outages/past",
90
85
  ]);
91
86
  });
92
87
  });
@@ -94,13 +89,15 @@ describe("SystemStatusServer", () => {
94
89
  it("should fetch and enhance current outages", async () => {
95
90
  mockHttpClient.get.mockResolvedValue({
96
91
  status: 200,
97
- data: { results: mockCurrentOutagesData }
92
+ data: { results: mockCurrentOutagesData },
98
93
  });
99
94
  const result = await server["handleToolCall"]({
100
- params: { name: "get_infrastructure_news", arguments: { time: "current" } }
95
+ method: "tools/call",
96
+ params: { name: "get_infrastructure_news", arguments: { time: "current" } },
101
97
  });
102
98
  expect(mockHttpClient.get).toHaveBeenCalledWith("/wh2/news/v1/affiliation/access-ci.org/current_outages/");
103
- const response = JSON.parse(result.content[0].text);
99
+ const content = result.content[0];
100
+ const response = JSON.parse(content.text);
104
101
  expect(response.total_outages).toBe(2);
105
102
  expect(response.affected_resources).toEqual(["Anvil", "Bridges-2"]);
106
103
  expect(response.severity_counts).toHaveProperty("high", 1); // Emergency
@@ -110,27 +107,31 @@ describe("SystemStatusServer", () => {
110
107
  it("should filter outages by resource", async () => {
111
108
  mockHttpClient.get.mockResolvedValue({
112
109
  status: 200,
113
- data: { results: mockCurrentOutagesData }
110
+ data: { results: mockCurrentOutagesData },
114
111
  });
115
112
  const result = await server["handleToolCall"]({
113
+ method: "tools/call",
116
114
  params: {
117
115
  name: "get_infrastructure_news",
118
- arguments: { query: "Anvil", time: "current" }
119
- }
116
+ arguments: { query: "Anvil", time: "current" },
117
+ },
120
118
  });
121
- const response = JSON.parse(result.content[0].text);
119
+ const content = result.content[0];
120
+ const response = JSON.parse(content.text);
122
121
  expect(response.total_outages).toBe(1);
123
122
  expect(response.outages[0].Subject).toContain("Anvil");
124
123
  });
125
124
  it("should categorize severity correctly", async () => {
126
125
  mockHttpClient.get.mockResolvedValue({
127
126
  status: 200,
128
- data: { results: mockCurrentOutagesData }
127
+ data: { results: mockCurrentOutagesData },
129
128
  });
130
129
  const result = await server["handleToolCall"]({
131
- params: { name: "get_infrastructure_news", arguments: { time: "current" } }
130
+ method: "tools/call",
131
+ params: { name: "get_infrastructure_news", arguments: { time: "current" } },
132
132
  });
133
- const response = JSON.parse(result.content[0].text);
133
+ const content = result.content[0];
134
+ const response = JSON.parse(content.text);
134
135
  const emergencyOutage = response.outages.find((o) => o.Subject.includes("Emergency"));
135
136
  const maintenanceOutage = response.outages.find((o) => o.Subject.includes("Scheduled"));
136
137
  expect(emergencyOutage.severity).toBe("high");
@@ -141,12 +142,14 @@ describe("SystemStatusServer", () => {
141
142
  it("should fetch and enhance scheduled maintenance", async () => {
142
143
  mockHttpClient.get.mockResolvedValue({
143
144
  status: 200,
144
- data: { results: mockFutureOutagesData }
145
+ data: { results: mockFutureOutagesData },
145
146
  });
146
147
  const result = await server["handleToolCall"]({
147
- params: { name: "get_infrastructure_news", arguments: { time: "scheduled" } }
148
+ method: "tools/call",
149
+ params: { name: "get_infrastructure_news", arguments: { time: "scheduled" } },
148
150
  });
149
- const response = JSON.parse(result.content[0].text);
151
+ const content = result.content[0];
152
+ const response = JSON.parse(content.text);
150
153
  expect(response.total_scheduled).toBe(1);
151
154
  expect(response.affected_resources).toEqual(["Jetstream"]);
152
155
  expect(response.maintenance[0]).toHaveProperty("hours_until_start");
@@ -154,19 +157,23 @@ describe("SystemStatusServer", () => {
154
157
  expect(response.maintenance[0]).toHaveProperty("has_scheduled_time", true);
155
158
  });
156
159
  it("should handle missing scheduled times", async () => {
157
- const dataWithoutSchedule = [{
160
+ const dataWithoutSchedule = [
161
+ {
158
162
  ...mockFutureOutagesData[0],
159
163
  OutageStart: null,
160
- OutageEnd: null
161
- }];
164
+ OutageEnd: null,
165
+ },
166
+ ];
162
167
  mockHttpClient.get.mockResolvedValue({
163
168
  status: 200,
164
- data: { results: dataWithoutSchedule }
169
+ data: { results: dataWithoutSchedule },
165
170
  });
166
171
  const result = await server["handleToolCall"]({
167
- params: { name: "get_infrastructure_news", arguments: { time: "scheduled" } }
172
+ method: "tools/call",
173
+ params: { name: "get_infrastructure_news", arguments: { time: "scheduled" } },
168
174
  });
169
- const response = JSON.parse(result.content[0].text);
175
+ const content = result.content[0];
176
+ const response = JSON.parse(content.text);
170
177
  expect(response.maintenance[0].has_scheduled_time).toBe(false);
171
178
  expect(response.maintenance[0].duration_hours).toBe(null);
172
179
  });
@@ -175,22 +182,24 @@ describe("SystemStatusServer", () => {
175
182
  {
176
183
  ...mockFutureOutagesData[0],
177
184
  OutageStart: "2024-08-31T10:00:00Z", // Later
178
- Subject: "Later maintenance"
185
+ Subject: "Later maintenance",
179
186
  },
180
187
  {
181
188
  ...mockFutureOutagesData[0],
182
189
  OutageStart: "2024-08-30T10:00:00Z", // Earlier
183
- Subject: "Earlier maintenance"
184
- }
190
+ Subject: "Earlier maintenance",
191
+ },
185
192
  ];
186
193
  mockHttpClient.get.mockResolvedValue({
187
194
  status: 200,
188
- data: { results: multipleMaintenanceData }
195
+ data: { results: multipleMaintenanceData },
189
196
  });
190
197
  const result = await server["handleToolCall"]({
191
- params: { name: "get_infrastructure_news", arguments: { time: "scheduled" } }
198
+ method: "tools/call",
199
+ params: { name: "get_infrastructure_news", arguments: { time: "scheduled" } },
192
200
  });
193
- const response = JSON.parse(result.content[0].text);
201
+ const content = result.content[0];
202
+ const response = JSON.parse(content.text);
194
203
  expect(response.maintenance[0].Subject).toBe("Earlier maintenance");
195
204
  expect(response.maintenance[1].Subject).toBe("Later maintenance");
196
205
  });
@@ -199,12 +208,14 @@ describe("SystemStatusServer", () => {
199
208
  it("should fetch and enhance past outages", async () => {
200
209
  mockHttpClient.get.mockResolvedValue({
201
210
  status: 200,
202
- data: { results: mockPastOutagesData }
211
+ data: { results: mockPastOutagesData },
203
212
  });
204
213
  const result = await server["handleToolCall"]({
205
- params: { name: "get_infrastructure_news", arguments: { time: "past" } }
214
+ method: "tools/call",
215
+ params: { name: "get_infrastructure_news", arguments: { time: "past" } },
206
216
  });
207
- const response = JSON.parse(result.content[0].text);
217
+ const content = result.content[0];
218
+ const response = JSON.parse(content.text);
208
219
  expect(response.total_past_outages).toBe(1);
209
220
  expect(response.outage_types).toEqual(["Full"]);
210
221
  expect(response.average_duration_hours).toBe(6); // 6 hour duration
@@ -212,22 +223,26 @@ describe("SystemStatusServer", () => {
212
223
  expect(response.outages[0]).toHaveProperty("days_ago");
213
224
  });
214
225
  it("should apply limit correctly", async () => {
215
- const manyOutages = Array(50).fill(0).map((_, i) => ({
226
+ const manyOutages = Array(50)
227
+ .fill(0)
228
+ .map((_, i) => ({
216
229
  ...mockPastOutagesData[0],
217
230
  id: `past-${i}`,
218
- Subject: `Past outage ${i}`
231
+ Subject: `Past outage ${i}`,
219
232
  }));
220
233
  mockHttpClient.get.mockResolvedValue({
221
234
  status: 200,
222
- data: { results: manyOutages }
235
+ data: { results: manyOutages },
223
236
  });
224
237
  const result = await server["handleToolCall"]({
238
+ method: "tools/call",
225
239
  params: {
226
240
  name: "get_infrastructure_news",
227
- arguments: { time: "past", limit: 10 }
228
- }
241
+ arguments: { time: "past", limit: 10 },
242
+ },
229
243
  });
230
- const response = JSON.parse(result.content[0].text);
244
+ const content = result.content[0];
245
+ const response = JSON.parse(content.text);
231
246
  expect(response.outages).toHaveLength(10);
232
247
  });
233
248
  });
@@ -238,10 +253,12 @@ describe("SystemStatusServer", () => {
238
253
  .mockResolvedValueOnce({ status: 200, data: { results: mockFutureOutagesData } })
239
254
  .mockResolvedValueOnce({ status: 200, data: { results: mockPastOutagesData } });
240
255
  const result = await server["handleToolCall"]({
241
- params: { name: "get_infrastructure_news", arguments: { time: "all" } }
256
+ method: "tools/call",
257
+ params: { name: "get_infrastructure_news", arguments: { time: "all" } },
242
258
  });
243
259
  expect(mockHttpClient.get).toHaveBeenCalledTimes(3);
244
- const response = JSON.parse(result.content[0].text);
260
+ const content = result.content[0];
261
+ const response = JSON.parse(content.text);
245
262
  expect(response.current_outages).toBe(2);
246
263
  expect(response.scheduled_maintenance).toBe(1);
247
264
  expect(response.recent_past_outages).toBe(1); // Within 30 days
@@ -255,9 +272,11 @@ describe("SystemStatusServer", () => {
255
272
  .mockResolvedValueOnce({ status: 200, data: { results: mockFutureOutagesData } })
256
273
  .mockResolvedValueOnce({ status: 200, data: { results: mockPastOutagesData } });
257
274
  const result = await server["handleToolCall"]({
258
- params: { name: "get_infrastructure_news", arguments: { time: "all" } }
275
+ method: "tools/call",
276
+ params: { name: "get_infrastructure_news", arguments: { time: "all" } },
259
277
  });
260
- const response = JSON.parse(result.content[0].text);
278
+ const content = result.content[0];
279
+ const response = JSON.parse(content.text);
261
280
  const firstAnnouncement = response.announcements[0];
262
281
  expect(firstAnnouncement.category).toBe("current");
263
282
  });
@@ -266,15 +285,17 @@ describe("SystemStatusServer", () => {
266
285
  it("should check resource status efficiently (direct method)", async () => {
267
286
  mockHttpClient.get.mockResolvedValue({
268
287
  status: 200,
269
- data: { results: mockCurrentOutagesData }
288
+ data: { results: mockCurrentOutagesData },
270
289
  });
271
290
  const result = await server["handleToolCall"]({
291
+ method: "tools/call",
272
292
  params: {
273
293
  name: "get_infrastructure_news",
274
- arguments: { ids: ["anvil-1", "unknown-resource"] }
275
- }
294
+ arguments: { ids: ["anvil-1", "unknown-resource"] },
295
+ },
276
296
  });
277
- const response = JSON.parse(result.content[0].text);
297
+ const content = result.content[0];
298
+ const response = JSON.parse(content.text);
278
299
  expect(response.api_method).toBe("direct_outages_check");
279
300
  expect(response.resources_checked).toBe(2);
280
301
  expect(response.operational).toBe(1); // unknown-resource
@@ -286,19 +307,21 @@ describe("SystemStatusServer", () => {
286
307
  it("should use group API when requested", async () => {
287
308
  mockHttpClient.get.mockResolvedValue({
288
309
  status: 200,
289
- data: { results: [] } // No outages for this group
310
+ data: { results: [] }, // No outages for this group
290
311
  });
291
312
  const result = await server["handleToolCall"]({
313
+ method: "tools/call",
292
314
  params: {
293
315
  name: "get_infrastructure_news",
294
316
  arguments: {
295
317
  ids: ["anvil"],
296
- use_group_api: true
297
- }
298
- }
318
+ use_group_api: true,
319
+ },
320
+ },
299
321
  });
300
322
  expect(mockHttpClient.get).toHaveBeenCalledWith("/wh2/news/v1/info_groupid/anvil/");
301
- const response = JSON.parse(result.content[0].text);
323
+ const content = result.content[0];
324
+ const response = JSON.parse(content.text);
302
325
  expect(response.api_method).toBe("resource_group_api");
303
326
  expect(response.resource_status[0].status).toBe("operational");
304
327
  expect(response.resource_status[0].api_method).toBe("group_specific");
@@ -306,15 +329,17 @@ describe("SystemStatusServer", () => {
306
329
  it("should handle group API failures gracefully", async () => {
307
330
  mockHttpClient.get.mockRejectedValue(new Error("API Error"));
308
331
  const result = await server["handleToolCall"]({
332
+ method: "tools/call",
309
333
  params: {
310
334
  name: "get_infrastructure_news",
311
335
  arguments: {
312
336
  ids: ["invalid-resource"],
313
- use_group_api: true
314
- }
315
- }
337
+ use_group_api: true,
338
+ },
339
+ },
316
340
  });
317
- const response = JSON.parse(result.content[0].text);
341
+ const content = result.content[0];
342
+ const response = JSON.parse(content.text);
318
343
  expect(response).toHaveProperty("unknown", 1);
319
344
  expect(response.resource_status[0].status).toBe("unknown");
320
345
  expect(response.resource_status[0].api_method).toBe("group_specific_failed");
@@ -325,37 +350,45 @@ describe("SystemStatusServer", () => {
325
350
  it("should handle API errors gracefully", async () => {
326
351
  mockHttpClient.get.mockResolvedValue({
327
352
  status: 500,
328
- statusText: "Internal Server Error"
353
+ statusText: "Internal Server Error",
354
+ data: null,
329
355
  });
330
356
  const result = await server["handleToolCall"]({
331
- params: { name: "get_infrastructure_news", arguments: { time: "current" } }
357
+ method: "tools/call",
358
+ params: { name: "get_infrastructure_news", arguments: { time: "current" } },
332
359
  });
333
- const response = JSON.parse(result.content[0].text);
360
+ const content = result.content[0];
361
+ const response = JSON.parse(content.text);
334
362
  expect(response.error).toBeDefined();
335
363
  });
336
364
  it("should handle network errors", async () => {
337
365
  mockHttpClient.get.mockRejectedValue(new Error("Network error"));
338
366
  const result = await server["handleToolCall"]({
339
- params: { name: "get_infrastructure_news", arguments: { time: "current" } }
367
+ method: "tools/call",
368
+ params: { name: "get_infrastructure_news", arguments: { time: "current" } },
340
369
  });
341
- const response = JSON.parse(result.content[0].text);
370
+ const content = result.content[0];
371
+ const response = JSON.parse(content.text);
342
372
  expect(response.error).toBe("Network error");
343
373
  });
344
374
  it("should handle unknown tools", async () => {
345
375
  const result = await server["handleToolCall"]({
346
- params: { name: "unknown_tool", arguments: {} }
376
+ method: "tools/call",
377
+ params: { name: "unknown_tool", arguments: {} },
347
378
  });
348
- expect(result.content[0].text).toContain("Unknown tool");
379
+ const content = result.content[0];
380
+ expect(content.text).toContain("Unknown tool");
349
381
  });
350
382
  });
351
383
  describe("Resource Handling", () => {
352
384
  it("should handle resource reads correctly", async () => {
353
385
  mockHttpClient.get.mockResolvedValue({
354
386
  status: 200,
355
- data: { results: mockCurrentOutagesData }
387
+ data: { results: mockCurrentOutagesData },
356
388
  });
357
389
  const result = await server["handleResourceRead"]({
358
- params: { uri: "accessci://outages/current" }
390
+ method: "resources/read",
391
+ params: { uri: "accessci://outages/current" },
359
392
  });
360
393
  expect(result.contents[0].mimeType).toBe("application/json");
361
394
  expect(result.contents[0].text).toBeDefined();
@@ -363,7 +396,8 @@ describe("SystemStatusServer", () => {
363
396
  it("should handle unknown resources", async () => {
364
397
  await expect(async () => {
365
398
  await server["handleResourceRead"]({
366
- params: { uri: "accessci://unknown" }
399
+ method: "resources/read",
400
+ params: { uri: "accessci://unknown" },
367
401
  });
368
402
  }).rejects.toThrow("Unknown resource");
369
403
  });
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ async function main() {
5
5
  const port = process.env.PORT ? parseInt(process.env.PORT, 10) : undefined;
6
6
  await server.start(port ? { httpPort: port } : undefined);
7
7
  }
8
- main().catch((error) => {
8
+ main().catch(() => {
9
9
  // Log errors to a file instead of stderr to avoid interfering with JSON-RPC
10
10
  process.exit(1);
11
11
  });
package/dist/server.d.ts CHANGED
@@ -1,66 +1,16 @@
1
- import { BaseAccessServer } from "@access-mcp/shared";
1
+ import { BaseAccessServer, Tool, Resource, CallToolResult } from "@access-mcp/shared";
2
+ import { CallToolRequest, ReadResourceRequest, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
2
3
  export declare class SystemStatusServer extends BaseAccessServer {
3
4
  constructor();
4
- protected getTools(): {
5
- name: string;
6
- description: string;
7
- inputSchema: {
8
- type: string;
9
- properties: {
10
- query: {
11
- type: string;
12
- description: string;
13
- };
14
- time: {
15
- type: string;
16
- enum: string[];
17
- description: string;
18
- default: string;
19
- };
20
- ids: {
21
- type: string;
22
- items: {
23
- type: string;
24
- };
25
- description: string;
26
- };
27
- limit: {
28
- type: string;
29
- description: string;
30
- default: number;
31
- };
32
- use_group_api: {
33
- type: string;
34
- description: string;
35
- default: boolean;
36
- };
37
- };
38
- };
39
- }[];
40
- protected getResources(): {
41
- uri: string;
42
- name: string;
43
- description: string;
44
- mimeType: string;
45
- }[];
46
- handleToolCall(request: any): Promise<{
47
- content: {
48
- type: string;
49
- text: string;
50
- }[];
51
- }>;
5
+ protected getTools(): Tool[];
6
+ protected getResources(): Resource[];
7
+ protected handleToolCall(request: CallToolRequest): Promise<CallToolResult>;
52
8
  /**
53
9
  * Router for consolidated get_infrastructure_news tool
54
10
  * Routes to appropriate handler based on parameters
55
11
  */
56
12
  private getInfrastructureNewsRouter;
57
- handleResourceRead(request: any): Promise<{
58
- contents: {
59
- uri: any;
60
- mimeType: string;
61
- text: string;
62
- }[];
63
- }>;
13
+ protected handleResourceRead(request: ReadResourceRequest): Promise<ReadResourceResult>;
64
14
  private getCurrentOutages;
65
15
  private getScheduledMaintenance;
66
16
  private getPastOutages;
package/dist/server.js CHANGED
@@ -1,7 +1,10 @@
1
- import { BaseAccessServer, handleApiError } from "@access-mcp/shared";
1
+ import { BaseAccessServer, handleApiError, } from "@access-mcp/shared";
2
+ import { createRequire } from "module";
3
+ const require = createRequire(import.meta.url);
4
+ const { version } = require("../package.json");
2
5
  export class SystemStatusServer extends BaseAccessServer {
3
6
  constructor() {
4
- super("access-mcp-system-status", "0.4.0", "https://operations-api.access-ci.org");
7
+ super("access-mcp-system-status", version, "https://operations-api.access-ci.org");
5
8
  }
6
9
  getTools() {
7
10
  return [
@@ -13,30 +16,30 @@ export class SystemStatusServer extends BaseAccessServer {
13
16
  properties: {
14
17
  query: {
15
18
  type: "string",
16
- description: "Filter by resource name (e.g., 'delta', 'bridges2')"
19
+ description: "Filter by resource name (e.g., 'delta', 'bridges2')",
17
20
  },
18
21
  time: {
19
22
  type: "string",
20
23
  enum: ["current", "scheduled", "past", "all"],
21
24
  description: "Period: current (active), scheduled (future), past, all",
22
- default: "current"
25
+ default: "current",
23
26
  },
24
27
  ids: {
25
28
  type: "array",
26
29
  items: { type: "string" },
27
- description: "Check status for specific resource IDs"
30
+ description: "Check status for specific resource IDs",
28
31
  },
29
32
  limit: {
30
33
  type: "number",
31
34
  description: "Max results (default: 50)",
32
- default: 50
35
+ default: 50,
33
36
  },
34
37
  use_group_api: {
35
38
  type: "boolean",
36
39
  description: "Use group API for status (with ids only)",
37
- default: false
38
- }
39
- }
40
+ default: false,
41
+ },
42
+ },
40
43
  },
41
44
  },
42
45
  ];
@@ -71,15 +74,16 @@ export class SystemStatusServer extends BaseAccessServer {
71
74
  }
72
75
  async handleToolCall(request) {
73
76
  const { name, arguments: args = {} } = request.params;
77
+ const typedArgs = args;
74
78
  try {
75
79
  switch (name) {
76
80
  case "get_infrastructure_news":
77
81
  return await this.getInfrastructureNewsRouter({
78
- resource: args.query,
79
- time: args.time,
80
- resource_ids: args.ids,
81
- limit: args.limit,
82
- use_group_api: args.use_group_api
82
+ resource: typedArgs.query,
83
+ time: typedArgs.time,
84
+ resource_ids: typedArgs.ids,
85
+ limit: typedArgs.limit,
86
+ use_group_api: typedArgs.use_group_api,
83
87
  });
84
88
  default:
85
89
  return this.errorResponse(`Unknown tool: ${name}`);
@@ -116,7 +120,7 @@ export class SystemStatusServer extends BaseAccessServer {
116
120
  async handleResourceRead(request) {
117
121
  const { uri } = request.params;
118
122
  switch (uri) {
119
- case "accessci://system-status":
123
+ case "accessci://system-status": {
120
124
  return {
121
125
  contents: [
122
126
  {
@@ -126,39 +130,49 @@ export class SystemStatusServer extends BaseAccessServer {
126
130
  },
127
131
  ],
128
132
  };
129
- case "accessci://outages/current":
133
+ }
134
+ case "accessci://outages/current": {
130
135
  const currentOutages = await this.getCurrentOutages();
136
+ const content = currentOutages.content[0];
137
+ const text = content.type === "text" ? content.text : "";
131
138
  return {
132
139
  contents: [
133
140
  {
134
141
  uri,
135
142
  mimeType: "application/json",
136
- text: currentOutages.content[0].text,
143
+ text,
137
144
  },
138
145
  ],
139
146
  };
140
- case "accessci://outages/scheduled":
147
+ }
148
+ case "accessci://outages/scheduled": {
141
149
  const scheduledMaintenance = await this.getScheduledMaintenance();
150
+ const content = scheduledMaintenance.content[0];
151
+ const text = content.type === "text" ? content.text : "";
142
152
  return {
143
153
  contents: [
144
154
  {
145
155
  uri,
146
156
  mimeType: "application/json",
147
- text: scheduledMaintenance.content[0].text,
157
+ text,
148
158
  },
149
159
  ],
150
160
  };
151
- case "accessci://outages/past":
161
+ }
162
+ case "accessci://outages/past": {
152
163
  const pastOutages = await this.getPastOutages();
164
+ const content = pastOutages.content[0];
165
+ const text = content.type === "text" ? content.text : "";
153
166
  return {
154
167
  contents: [
155
168
  {
156
169
  uri,
157
170
  mimeType: "application/json",
158
- text: pastOutages.content[0].text,
171
+ text,
159
172
  },
160
173
  ],
161
174
  };
175
+ }
162
176
  default:
163
177
  throw new Error(`Unknown resource: ${uri}`);
164
178
  }
@@ -178,9 +192,11 @@ export class SystemStatusServer extends BaseAccessServer {
178
192
  const severityCounts = { high: 0, medium: 0, low: 0, unknown: 0 };
179
193
  // Enhance outages with status summary
180
194
  const enhancedOutages = outages.map((outage) => {
181
- // Track affected resources
195
+ // Track affected resources (use ResourceID as fallback if ResourceName is missing)
182
196
  outage.AffectedResources?.forEach((resource) => {
183
- affectedResources.add(resource.ResourceName);
197
+ const resourceIdentifier = resource.ResourceName || resource.ResourceID;
198
+ if (resourceIdentifier)
199
+ affectedResources.add(String(resourceIdentifier));
184
200
  });
185
201
  // Categorize severity (basic heuristic)
186
202
  const subject = outage.Subject?.toLowerCase() || "";
@@ -188,8 +204,7 @@ export class SystemStatusServer extends BaseAccessServer {
188
204
  if (subject.includes("emergency") || subject.includes("critical")) {
189
205
  severity = "high";
190
206
  }
191
- else if (subject.includes("maintenance") ||
192
- subject.includes("scheduled")) {
207
+ else if (subject.includes("maintenance") || subject.includes("scheduled")) {
193
208
  severity = "low";
194
209
  }
195
210
  else {
@@ -228,8 +243,8 @@ export class SystemStatusServer extends BaseAccessServer {
228
243
  }
229
244
  // Sort by scheduled start time
230
245
  maintenance.sort((a, b) => {
231
- const dateA = new Date(a.OutageStart);
232
- const dateB = new Date(b.OutageStart);
246
+ const dateA = new Date(a.OutageStart || "");
247
+ const dateB = new Date(b.OutageStart || "");
233
248
  return dateA.getTime() - dateB.getTime();
234
249
  });
235
250
  // Initialize tracking variables
@@ -239,11 +254,13 @@ export class SystemStatusServer extends BaseAccessServer {
239
254
  const enhancedMaintenance = maintenance.map((item) => {
240
255
  // Track affected resources
241
256
  item.AffectedResources?.forEach((resource) => {
242
- affectedResources.add(resource.ResourceName);
257
+ const resourceIdentifier = resource.ResourceName || resource.ResourceID;
258
+ if (resourceIdentifier)
259
+ affectedResources.add(String(resourceIdentifier));
243
260
  });
244
261
  // Check timing - use OutageStart for scheduling
245
262
  const hasScheduledTime = !!item.OutageStart;
246
- const startTime = new Date(item.OutageStart);
263
+ const startTime = new Date(item.OutageStart || "");
247
264
  const now = new Date();
248
265
  const hoursUntil = (startTime.getTime() - now.getTime()) / (1000 * 60 * 60);
249
266
  if (hoursUntil <= 24)
@@ -256,8 +273,7 @@ export class SystemStatusServer extends BaseAccessServer {
256
273
  scheduled_end: item.OutageEnd,
257
274
  hours_until_start: Math.max(0, Math.round(hoursUntil)),
258
275
  duration_hours: item.OutageEnd && item.OutageStart
259
- ? Math.round((new Date(item.OutageEnd).getTime() -
260
- new Date(item.OutageStart).getTime()) /
276
+ ? Math.round((new Date(item.OutageEnd).getTime() - new Date(item.OutageStart).getTime()) /
261
277
  (1000 * 60 * 60))
262
278
  : null,
263
279
  has_scheduled_time: hasScheduledTime,
@@ -291,8 +307,8 @@ export class SystemStatusServer extends BaseAccessServer {
291
307
  }
292
308
  // Sort by outage end time (most recent first)
293
309
  pastOutages.sort((a, b) => {
294
- const dateA = new Date(a.OutageEnd);
295
- const dateB = new Date(b.OutageEnd);
310
+ const dateA = new Date(a.OutageEnd || "");
311
+ const dateB = new Date(b.OutageEnd || "");
296
312
  return dateB.getTime() - dateA.getTime();
297
313
  });
298
314
  // Apply limit
@@ -303,23 +319,25 @@ export class SystemStatusServer extends BaseAccessServer {
303
319
  const affectedResources = new Set();
304
320
  const outageTypes = new Set();
305
321
  const recentOutages = pastOutages.filter((outage) => {
306
- const endTime = new Date(outage.OutageEnd);
322
+ const endTime = new Date(outage.OutageEnd || "");
307
323
  const daysAgo = (Date.now() - endTime.getTime()) / (1000 * 60 * 60 * 24);
308
324
  return daysAgo <= 30; // Last 30 days
309
325
  });
310
326
  // Enhance outages with calculated fields
311
327
  const enhancedOutages = pastOutages.map((outage) => {
312
- // Track affected resources
328
+ // Track affected resources (use ResourceID as fallback if ResourceName is missing)
313
329
  outage.AffectedResources?.forEach((resource) => {
314
- affectedResources.add(resource.ResourceName);
330
+ const resourceIdentifier = resource.ResourceName || resource.ResourceID;
331
+ if (resourceIdentifier)
332
+ affectedResources.add(String(resourceIdentifier));
315
333
  });
316
334
  // Track outage types
317
335
  if (outage.OutageType) {
318
336
  outageTypes.add(outage.OutageType);
319
337
  }
320
338
  // Calculate duration
321
- const startTime = new Date(outage.OutageStart);
322
- const endTime = new Date(outage.OutageEnd);
339
+ const startTime = new Date(outage.OutageStart || "");
340
+ const endTime = new Date(outage.OutageEnd || "");
323
341
  const durationHours = Math.round((endTime.getTime() - startTime.getTime()) / (1000 * 60 * 60));
324
342
  // Calculate how long ago it ended
325
343
  const daysAgo = Math.round((Date.now() - endTime.getTime()) / (1000 * 60 * 60 * 24));
@@ -339,9 +357,9 @@ export class SystemStatusServer extends BaseAccessServer {
339
357
  outage_types: Array.from(outageTypes),
340
358
  average_duration_hours: enhancedOutages.length > 0
341
359
  ? Math.round(enhancedOutages
342
- .filter((o) => o.duration_hours > 0)
343
- .reduce((sum, o) => sum + o.duration_hours, 0) /
344
- enhancedOutages.filter((o) => o.duration_hours > 0).length)
360
+ .filter((o) => o.duration_hours && o.duration_hours > 0)
361
+ .reduce((sum, o) => sum + (o.duration_hours || 0), 0) /
362
+ enhancedOutages.filter((o) => o.duration_hours && o.duration_hours > 0).length)
345
363
  : 0,
346
364
  outages: enhancedOutages,
347
365
  };
@@ -363,27 +381,30 @@ export class SystemStatusServer extends BaseAccessServer {
363
381
  ]);
364
382
  const currentOutages = currentResponse.data.results || [];
365
383
  const futureOutages = futureResponse.data.results || [];
366
- const pastOutages = pastResponse.data.results || [];
384
+ const pastOutagesData = pastResponse.data.results || [];
367
385
  // Filter recent past outages (last 30 days) for announcements
368
- const recentPastOutages = pastOutages.filter((outage) => {
369
- const endTime = new Date(outage.OutageEnd);
386
+ const recentPastOutages = pastOutagesData.filter((outage) => {
387
+ const endTime = new Date(outage.OutageEnd || "");
370
388
  const daysAgo = (Date.now() - endTime.getTime()) / (1000 * 60 * 60 * 24);
371
389
  return daysAgo <= 30;
372
390
  });
373
391
  // Combine all announcements and sort by most relevant date
374
392
  const allAnnouncements = [
375
- ...currentOutages.map((item) => ({ ...item, category: 'current' })),
376
- ...futureOutages.map((item) => ({ ...item, category: 'scheduled' })),
377
- ...recentPastOutages.map((item) => ({ ...item, category: 'recent_past' })),
393
+ ...currentOutages.map((item) => ({ ...item, category: "current" })),
394
+ ...futureOutages.map((item) => ({ ...item, category: "scheduled" })),
395
+ ...recentPastOutages.map((item) => ({
396
+ ...item,
397
+ category: "recent_past",
398
+ })),
378
399
  ]
379
400
  .sort((a, b) => {
380
401
  // Sort by most relevant date: current first, then future by start time, then past by end time
381
- if (a.category === 'current' && b.category !== 'current')
402
+ if (a.category === "current" && b.category !== "current")
382
403
  return -1;
383
- if (b.category === 'current' && a.category !== 'current')
404
+ if (b.category === "current" && a.category !== "current")
384
405
  return 1;
385
- const dateA = new Date(a.OutageStart);
386
- const dateB = new Date(b.OutageStart);
406
+ const dateA = new Date(a.OutageStart || "");
407
+ const dateB = new Date(b.OutageStart || "");
387
408
  return dateB.getTime() - dateA.getTime(); // Most recent first
388
409
  })
389
410
  .slice(0, limit);
@@ -393,9 +414,9 @@ export class SystemStatusServer extends BaseAccessServer {
393
414
  scheduled_maintenance: futureOutages.length,
394
415
  recent_past_outages: recentPastOutages.length,
395
416
  categories: {
396
- current: allAnnouncements.filter(a => a.category === 'current').length,
397
- scheduled: allAnnouncements.filter(a => a.category === 'scheduled').length,
398
- recent_past: allAnnouncements.filter(a => a.category === 'recent_past').length,
417
+ current: allAnnouncements.filter((a) => a.category === "current").length,
418
+ scheduled: allAnnouncements.filter((a) => a.category === "scheduled").length,
419
+ recent_past: allAnnouncements.filter((a) => a.category === "recent_past").length,
399
420
  },
400
421
  announcements: allAnnouncements,
401
422
  };
@@ -464,8 +485,7 @@ export class SystemStatusServer extends BaseAccessServer {
464
485
  checked_at: new Date().toISOString(),
465
486
  resources_checked: resourceIds.length,
466
487
  operational: resourceStatus.filter((r) => r.status === "operational").length,
467
- affected: resourceStatus.filter((r) => r.status === "affected")
468
- .length,
488
+ affected: resourceStatus.filter((r) => r.status === "affected").length,
469
489
  api_method: "direct_outages_check",
470
490
  resource_status: resourceStatus,
471
491
  }, null, 2),
@@ -494,7 +514,7 @@ export class SystemStatusServer extends BaseAccessServer {
494
514
  api_method: "group_specific",
495
515
  };
496
516
  }
497
- catch (error) {
517
+ catch {
498
518
  // Fallback to general check if group API fails
499
519
  return {
500
520
  resource_id: resourceId,
@@ -516,10 +536,8 @@ export class SystemStatusServer extends BaseAccessServer {
516
536
  checked_at: new Date().toISOString(),
517
537
  resources_checked: resourceIds.length,
518
538
  operational: resourceStatus.filter((r) => r.status === "operational").length,
519
- affected: resourceStatus.filter((r) => r.status === "affected")
520
- .length,
521
- unknown: resourceStatus.filter((r) => r.status === "unknown")
522
- .length,
539
+ affected: resourceStatus.filter((r) => r.status === "affected").length,
540
+ unknown: resourceStatus.filter((r) => r.status === "unknown").length,
523
541
  api_method: "resource_group_api",
524
542
  resource_status: resourceStatus,
525
543
  }, null, 2),
@@ -3,7 +3,7 @@ import path from "path";
3
3
  import { SystemStatusServer } from "./server.js";
4
4
  export function startWebServer(port = 3000) {
5
5
  const app = express();
6
- const server = new SystemStatusServer();
6
+ new SystemStatusServer();
7
7
  // Serve static files from public directory
8
8
  const publicDir = path.join(__dirname, "../../../public");
9
9
  app.use(express.static(publicDir));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@access-mcp/system-status",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "MCP server for ACCESS-CI System Status and Outages API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -44,7 +44,7 @@
44
44
  "node": ">=18.0.0"
45
45
  },
46
46
  "dependencies": {
47
- "@access-mcp/shared": "^0.3.3",
47
+ "@access-mcp/shared": "^0.6.0",
48
48
  "express": "^4.18.0"
49
49
  },
50
50
  "devDependencies": {