@access-mcp/system-status 0.4.0 → 0.5.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/README.md CHANGED
@@ -1,79 +1,67 @@
1
1
  # System Status MCP Server
2
2
 
3
- MCP server providing real-time system status information for ACCESS-CI resources.
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.
4
4
 
5
- ## Overview
6
-
7
- This server provides critical operational information about ACCESS-CI systems, including current outages, scheduled maintenance, and system-wide announcements.
8
-
9
- ## Tools
10
-
11
- ### get_current_outages
12
-
13
- Get current system outages and issues affecting ACCESS-CI resources.
14
-
15
- **Parameters:**
16
-
17
- - `resource_filter` (string, optional): Filter by specific resource name or ID
18
-
19
- **Returns:**
20
- - Total outages count and severity breakdown
21
- - Affected resources list
22
- - Enhanced outage details with severity levels
5
+ ## Usage Examples
23
6
 
24
- ### get_scheduled_maintenance
7
+ ### **Current Status**
25
8
 
26
- Get scheduled maintenance and future outages for ACCESS-CI resources.
9
+ ```
10
+ "Current ACCESS-CI outages"
11
+ "Delta operational status"
12
+ "Systems experiencing issues"
13
+ "GPU systems status check"
14
+ ```
27
15
 
28
- **Parameters:**
16
+ ### **Maintenance & Incidents**
29
17
 
30
- - `resource_filter` (string, optional): Filter by specific resource name or ID
18
+ ```
19
+ "Scheduled maintenance this week"
20
+ "Next Expanse maintenance window"
21
+ "Past outages for Bridges-2"
22
+ "All infrastructure news for Delta"
23
+ ```
31
24
 
32
- **Returns:**
33
- - Scheduled maintenance sorted by start time
34
- - Time until maintenance starts
35
- - Duration calculations for planned windows
25
+ ## Tools
36
26
 
37
- ### get_past_outages
27
+ ### get_infrastructure_news
38
28
 
39
- Get historical outages and past incidents affecting ACCESS-CI resources.
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.
40
30
 
41
31
  **Parameters:**
42
32
 
43
- - `resource_filter` (string, optional): Filter by specific resource name or ID
44
- - `limit` (number, optional): Maximum number of past outages to return (default: 100)
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)
45
38
 
46
- **Returns:**
47
- - Historical outage data with duration analysis
48
- - Recent outages (last 30 days) summary
49
- - Outage type categorization
39
+ **Examples:**
50
40
 
51
- ### get_system_announcements
52
-
53
- Get comprehensive system announcements combining current, scheduled, and recent past outages.
54
-
55
- **Parameters:**
56
-
57
- - `limit` (number, optional): Maximum number of announcements to return (default: 50)
41
+ ```typescript
42
+ // Get current outages across all resources
43
+ get_infrastructure_news({})
58
44
 
59
- **Returns:**
60
- - Unified view of current outages, scheduled maintenance, and recent past incidents
61
- - Categorized announcements for better organization
62
- - Timeline-based sorting
45
+ // Get scheduled maintenance
46
+ get_infrastructure_news({ time: "scheduled" })
63
47
 
64
- ### check_resource_status
48
+ // Get comprehensive overview of all infrastructure news
49
+ get_infrastructure_news({ time: "all" })
65
50
 
66
- Check the operational status of specific ACCESS-CI resources.
51
+ // Check current status for specific resource
52
+ get_infrastructure_news({ resource: "delta", time: "current" })
67
53
 
68
- **Parameters:**
54
+ // Get all news for specific resource
55
+ get_infrastructure_news({ resource: "delta", time: "all" })
69
56
 
70
- - `resource_ids` (array): List of resource IDs or names to check status for
71
- - `use_group_api` (boolean, optional): Use resource group API for more efficient querying (default: false)
57
+ // Check operational status of specific resources
58
+ get_infrastructure_news({
59
+ resource_ids: ["delta.ncsa.access-ci.org", "bridges2.psc.access-ci.org"]
60
+ })
72
61
 
73
- **Returns:**
74
- - Operational status for each requested resource
75
- - Active outage details with severity levels
76
- - API method used (direct vs group-specific)
62
+ // Get past outages with limit
63
+ get_infrastructure_news({ time: "past", limit: 50 })
64
+ ```
77
65
 
78
66
  ## Resources
79
67
 
@@ -133,74 +121,73 @@ Add to your Claude Desktop configuration:
133
121
  **Natural Language**: "Are there any systems down right now?"
134
122
 
135
123
  **Tool Call**:
136
-
137
124
  ```typescript
138
- const outages = await get_current_outages();
125
+ const outages = await get_infrastructure_news({});
126
+ // or explicitly: get_infrastructure_news({ time: "current" })
139
127
  ```
140
128
 
141
129
  **Returns**: List of active outages with:
142
-
143
130
  - Affected resources
131
+ - Severity categorization (high/medium/low)
144
132
  - Start time and expected resolution
145
133
  - Impact description
146
- - Workaround information if available
134
+ - Summary statistics (total outages, affected resources)
147
135
 
148
136
  ### Finding Scheduled Maintenance
149
137
 
150
138
  **Natural Language**: "When is Delta scheduled for maintenance?"
151
139
 
152
140
  **Tool Call**:
153
-
154
141
  ```typescript
155
- const maintenance = await get_scheduled_maintenance({
156
- resource_filter: "delta",
142
+ const maintenance = await get_infrastructure_news({
143
+ resource: "delta",
144
+ time: "scheduled"
157
145
  });
158
146
  ```
159
147
 
160
148
  **Returns**: Upcoming maintenance windows including:
161
-
162
149
  - Scheduled start and end times
163
150
  - Systems affected
164
- - Type of maintenance
165
- - Expected impact on users
151
+ - Duration in hours
152
+ - Hours until maintenance starts
153
+ - Summary (upcoming in 24h, upcoming this week)
166
154
 
167
- ### Getting System Announcements
155
+ ### Getting Comprehensive System Overview
168
156
 
169
157
  **Natural Language**: "What are the latest announcements?"
170
158
 
171
159
  **Tool Call**:
172
-
173
160
  ```typescript
174
- const announcements = await get_system_announcements({
175
- limit: 10,
161
+ const announcements = await get_infrastructure_news({
162
+ time: "all",
163
+ limit: 10
176
164
  });
177
165
  ```
178
166
 
179
- **Returns**: Recent announcements about:
180
-
181
- - Policy changes
182
- - New features or services
183
- - Important deadlines
184
- - System-wide updates
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)
185
173
 
186
174
  ### Checking Specific Resource Status
187
175
 
188
176
  **Natural Language**: "Is Expanse available?"
189
177
 
190
178
  **Tool Call**:
191
-
192
179
  ```typescript
193
- const status = await get_resource_status({
194
- resource_id: "expanse.sdsc.xsede.org",
180
+ const status = await get_infrastructure_news({
181
+ resource_ids: ["expanse.sdsc.access-ci.org"]
195
182
  });
196
183
  ```
197
184
 
198
185
  **Returns**: Current operational status:
199
-
200
- - Overall system health
201
- - Service availability
202
- - Performance metrics
203
- - Any active issues or limitations
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
204
191
 
205
192
  ## API Endpoints
206
193
 
@@ -9,8 +9,8 @@ describe("SystemStatusServer Integration Tests", () => {
9
9
  it("should fetch current outages from real API", async () => {
10
10
  const result = await server["handleToolCall"]({
11
11
  params: {
12
- name: "get_current_outages",
13
- arguments: { limit: 5 }
12
+ name: "get_infrastructure_news",
13
+ arguments: { time: "current", limit: 5 }
14
14
  }
15
15
  });
16
16
  const responseData = JSON.parse(result.content[0].text);
@@ -26,15 +26,14 @@ describe("SystemStatusServer Integration Tests", () => {
26
26
  if (responseData.outages.length > 0) {
27
27
  const outage = responseData.outages[0];
28
28
  expect(outage).toHaveProperty("severity");
29
- expect(outage).toHaveProperty("posted_time");
30
- expect(outage).toHaveProperty("last_updated");
29
+ // The API response includes original fields like Subject, OutageStart, OutageEnd, etc.
30
+ expect(outage).toHaveProperty("Subject");
31
31
  }
32
32
  }, 10000);
33
33
  it("should fetch scheduled maintenance from real API", async () => {
34
34
  const result = await server["handleToolCall"]({
35
35
  params: {
36
- name: "get_scheduled_maintenance",
37
- arguments: { limit: 5 }
36
+ name: "get_infrastructure_news", arguments: { time: "scheduled", limit: 5 }
38
37
  }
39
38
  });
40
39
  const responseData = JSON.parse(result.content[0].text);
@@ -54,8 +53,7 @@ describe("SystemStatusServer Integration Tests", () => {
54
53
  it("should fetch past outages from real API", async () => {
55
54
  const result = await server["handleToolCall"]({
56
55
  params: {
57
- name: "get_past_outages",
58
- arguments: { limit: 10 }
56
+ name: "get_infrastructure_news", arguments: { time: "past", limit: 5 }
59
57
  }
60
58
  });
61
59
  const responseData = JSON.parse(result.content[0].text);
@@ -77,8 +75,8 @@ describe("SystemStatusServer Integration Tests", () => {
77
75
  it("should get comprehensive system announcements", async () => {
78
76
  const result = await server["handleToolCall"]({
79
77
  params: {
80
- name: "get_system_announcements",
81
- arguments: { limit: 20 }
78
+ name: "get_infrastructure_news",
79
+ arguments: { time: "all", limit: 20 }
82
80
  }
83
81
  });
84
82
  const responseData = JSON.parse(result.content[0].text);
@@ -102,9 +100,9 @@ describe("SystemStatusServer Integration Tests", () => {
102
100
  // Test with common resource names that might exist
103
101
  const result = await server["handleToolCall"]({
104
102
  params: {
105
- name: "check_resource_status",
103
+ name: "get_infrastructure_news",
106
104
  arguments: {
107
- resource_ids: ["anvil", "bridges", "jetstream"],
105
+ ids: ["anvil", "bridges", "jetstream"],
108
106
  use_group_api: false
109
107
  }
110
108
  }
@@ -130,9 +128,9 @@ describe("SystemStatusServer Integration Tests", () => {
130
128
  // Test group API with a resource that might have a group ID
131
129
  const result = await server["handleToolCall"]({
132
130
  params: {
133
- name: "check_resource_status",
131
+ name: "get_infrastructure_news",
134
132
  arguments: {
135
- resource_ids: ["anvil"],
133
+ ids: ["anvil"],
136
134
  use_group_api: true
137
135
  }
138
136
  }
@@ -159,19 +157,21 @@ describe("SystemStatusServer Integration Tests", () => {
159
157
  it("should filter outages by resource correctly", async () => {
160
158
  const result = await server["handleToolCall"]({
161
159
  params: {
162
- name: "get_current_outages",
163
- arguments: { resource_filter: "anvil" }
160
+ name: "get_infrastructure_news",
161
+ arguments: { time: "current", query: "anvil" }
164
162
  }
165
163
  });
166
164
  const responseData = JSON.parse(result.content[0].text);
167
165
  expect(responseData).toHaveProperty("total_outages");
168
166
  // If there are any results, they should match the filter
169
- responseData.outages.forEach((outage) => {
170
- const matchesFilter = outage.Subject?.toLowerCase().includes("anvil") ||
171
- outage.AffectedResources?.some((resource) => resource.ResourceName?.toLowerCase().includes("anvil") ||
172
- resource.ResourceID?.toString().includes("anvil"));
173
- expect(matchesFilter).toBe(true);
174
- });
167
+ if (responseData.outages.length > 0) {
168
+ responseData.outages.forEach((outage) => {
169
+ const matchesFilter = outage.Subject?.toLowerCase().includes("anvil") ||
170
+ outage.AffectedResources?.some((resource) => resource.ResourceName?.toLowerCase().includes("anvil") ||
171
+ resource.ResourceID?.toString().includes("anvil"));
172
+ expect(matchesFilter).toBe(true);
173
+ });
174
+ }
175
175
  }, 10000);
176
176
  it("should handle resource reads for all endpoints", async () => {
177
177
  const resources = [
@@ -200,8 +200,8 @@ describe("SystemStatusServer Integration Tests", () => {
200
200
  // This tests the robustness of our logic with potentially empty responses
201
201
  const result = await server["handleToolCall"]({
202
202
  params: {
203
- name: "get_current_outages",
204
- arguments: { resource_filter: "nonexistent-resource-xyz" }
203
+ name: "get_infrastructure_news",
204
+ arguments: { time: "current", query: "nonexistent-resource-xyz-12345" }
205
205
  }
206
206
  });
207
207
  const responseData = JSON.parse(result.content[0].text);
@@ -212,8 +212,8 @@ describe("SystemStatusServer Integration Tests", () => {
212
212
  it("should handle large limit values gracefully", async () => {
213
213
  const result = await server["handleToolCall"]({
214
214
  params: {
215
- name: "get_past_outages",
216
- arguments: { limit: 1000 }
215
+ name: "get_infrastructure_news",
216
+ arguments: { time: "past", limit: 1000 }
217
217
  }
218
218
  });
219
219
  const responseData = JSON.parse(result.content[0].text);
@@ -10,8 +10,8 @@ describe("SystemStatusServer", () => {
10
10
  id: "1",
11
11
  Subject: "Emergency maintenance on Anvil",
12
12
  Content: "Critical issue requiring immediate attention",
13
- CreationTime: "2024-08-27T10:00:00Z",
14
- LastModificationTime: "2024-08-27T11:00:00Z",
13
+ OutageStart: "2024-08-27T10:00:00Z",
14
+ OutageEnd: "2024-08-27T11:00:00Z",
15
15
  AffectedResources: [
16
16
  { ResourceName: "Anvil", ResourceID: "anvil-1" }
17
17
  ]
@@ -20,8 +20,8 @@ describe("SystemStatusServer", () => {
20
20
  id: "2",
21
21
  Subject: "Scheduled maintenance on Bridges-2",
22
22
  Content: "Regular maintenance window",
23
- CreationTime: "2024-08-27T08:00:00Z",
24
- LastModificationTime: "2024-08-27T08:30:00Z",
23
+ OutageStart: "2024-08-27T08:00:00Z",
24
+ OutageEnd: "2024-08-27T08:30:00Z",
25
25
  AffectedResources: [
26
26
  { ResourceName: "Bridges-2", ResourceID: "bridges2-1" }
27
27
  ]
@@ -32,10 +32,8 @@ describe("SystemStatusServer", () => {
32
32
  id: "3",
33
33
  Subject: "Scheduled Jetstream maintenance",
34
34
  Content: "Planned maintenance",
35
- CreationTime: "2024-08-27T09:00:00Z",
36
- LastModificationTime: "2024-08-27T09:00:00Z",
37
- OutageStartDateTime: "2024-08-30T10:00:00Z",
38
- OutageEndDateTime: "2024-08-30T14:00:00Z",
35
+ OutageStart: "2024-08-30T10:00:00Z",
36
+ OutageEnd: "2024-08-30T14:00:00Z",
39
37
  AffectedResources: [
40
38
  { ResourceName: "Jetstream", ResourceID: "jetstream-1" }
41
39
  ]
@@ -46,10 +44,8 @@ describe("SystemStatusServer", () => {
46
44
  id: "4",
47
45
  Subject: "Past maintenance on Stampede3",
48
46
  Content: "Completed maintenance",
49
- CreationTime: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(), // 5 days ago
50
- LastModificationTime: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), // 2 days ago
51
- OutageStartDateTime: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(), // 3 days ago
52
- OutageEndDateTime: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000 + 6 * 60 * 60 * 1000).toISOString(), // 3 days ago + 6 hours
47
+ OutageStart: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(), // 3 days ago
48
+ OutageEnd: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000 + 6 * 60 * 60 * 1000).toISOString(), // 3 days ago + 6 hours
53
49
  OutageType: "Full",
54
50
  AffectedResources: [
55
51
  { ResourceName: "Stampede3", ResourceID: "stampede3-1" }
@@ -80,14 +76,8 @@ describe("SystemStatusServer", () => {
80
76
  });
81
77
  it("should provide correct tools", () => {
82
78
  const tools = server["getTools"]();
83
- expect(tools).toHaveLength(5);
84
- expect(tools.map(t => t.name)).toEqual([
85
- "get_current_outages",
86
- "get_scheduled_maintenance",
87
- "get_past_outages",
88
- "get_system_announcements",
89
- "check_resource_status"
90
- ]);
79
+ expect(tools).toHaveLength(1);
80
+ expect(tools[0].name).toBe("get_infrastructure_news");
91
81
  });
92
82
  it("should provide correct resources", () => {
93
83
  const resources = server["getResources"]();
@@ -107,7 +97,7 @@ describe("SystemStatusServer", () => {
107
97
  data: { results: mockCurrentOutagesData }
108
98
  });
109
99
  const result = await server["handleToolCall"]({
110
- params: { name: "get_current_outages", arguments: {} }
100
+ params: { name: "get_infrastructure_news", arguments: { time: "current" } }
111
101
  });
112
102
  expect(mockHttpClient.get).toHaveBeenCalledWith("/wh2/news/v1/affiliation/access-ci.org/current_outages/");
113
103
  const response = JSON.parse(result.content[0].text);
@@ -116,7 +106,6 @@ describe("SystemStatusServer", () => {
116
106
  expect(response.severity_counts).toHaveProperty("high", 1); // Emergency
117
107
  expect(response.severity_counts).toHaveProperty("low", 1); // Scheduled maintenance
118
108
  expect(response.outages[0]).toHaveProperty("severity");
119
- expect(response.outages[0]).toHaveProperty("posted_time");
120
109
  });
121
110
  it("should filter outages by resource", async () => {
122
111
  mockHttpClient.get.mockResolvedValue({
@@ -125,8 +114,8 @@ describe("SystemStatusServer", () => {
125
114
  });
126
115
  const result = await server["handleToolCall"]({
127
116
  params: {
128
- name: "get_current_outages",
129
- arguments: { resource_filter: "Anvil" }
117
+ name: "get_infrastructure_news",
118
+ arguments: { query: "Anvil", time: "current" }
130
119
  }
131
120
  });
132
121
  const response = JSON.parse(result.content[0].text);
@@ -139,7 +128,7 @@ describe("SystemStatusServer", () => {
139
128
  data: { results: mockCurrentOutagesData }
140
129
  });
141
130
  const result = await server["handleToolCall"]({
142
- params: { name: "get_current_outages", arguments: {} }
131
+ params: { name: "get_infrastructure_news", arguments: { time: "current" } }
143
132
  });
144
133
  const response = JSON.parse(result.content[0].text);
145
134
  const emergencyOutage = response.outages.find((o) => o.Subject.includes("Emergency"));
@@ -155,7 +144,7 @@ describe("SystemStatusServer", () => {
155
144
  data: { results: mockFutureOutagesData }
156
145
  });
157
146
  const result = await server["handleToolCall"]({
158
- params: { name: "get_scheduled_maintenance", arguments: {} }
147
+ params: { name: "get_infrastructure_news", arguments: { time: "scheduled" } }
159
148
  });
160
149
  const response = JSON.parse(result.content[0].text);
161
150
  expect(response.total_scheduled).toBe(1);
@@ -167,15 +156,15 @@ describe("SystemStatusServer", () => {
167
156
  it("should handle missing scheduled times", async () => {
168
157
  const dataWithoutSchedule = [{
169
158
  ...mockFutureOutagesData[0],
170
- OutageStartDateTime: null,
171
- OutageEndDateTime: null
159
+ OutageStart: null,
160
+ OutageEnd: null
172
161
  }];
173
162
  mockHttpClient.get.mockResolvedValue({
174
163
  status: 200,
175
164
  data: { results: dataWithoutSchedule }
176
165
  });
177
166
  const result = await server["handleToolCall"]({
178
- params: { name: "get_scheduled_maintenance", arguments: {} }
167
+ params: { name: "get_infrastructure_news", arguments: { time: "scheduled" } }
179
168
  });
180
169
  const response = JSON.parse(result.content[0].text);
181
170
  expect(response.maintenance[0].has_scheduled_time).toBe(false);
@@ -185,12 +174,12 @@ describe("SystemStatusServer", () => {
185
174
  const multipleMaintenanceData = [
186
175
  {
187
176
  ...mockFutureOutagesData[0],
188
- OutageStartDateTime: "2024-08-31T10:00:00Z", // Later
177
+ OutageStart: "2024-08-31T10:00:00Z", // Later
189
178
  Subject: "Later maintenance"
190
179
  },
191
180
  {
192
181
  ...mockFutureOutagesData[0],
193
- OutageStartDateTime: "2024-08-30T10:00:00Z", // Earlier
182
+ OutageStart: "2024-08-30T10:00:00Z", // Earlier
194
183
  Subject: "Earlier maintenance"
195
184
  }
196
185
  ];
@@ -199,7 +188,7 @@ describe("SystemStatusServer", () => {
199
188
  data: { results: multipleMaintenanceData }
200
189
  });
201
190
  const result = await server["handleToolCall"]({
202
- params: { name: "get_scheduled_maintenance", arguments: {} }
191
+ params: { name: "get_infrastructure_news", arguments: { time: "scheduled" } }
203
192
  });
204
193
  const response = JSON.parse(result.content[0].text);
205
194
  expect(response.maintenance[0].Subject).toBe("Earlier maintenance");
@@ -213,7 +202,7 @@ describe("SystemStatusServer", () => {
213
202
  data: { results: mockPastOutagesData }
214
203
  });
215
204
  const result = await server["handleToolCall"]({
216
- params: { name: "get_past_outages", arguments: {} }
205
+ params: { name: "get_infrastructure_news", arguments: { time: "past" } }
217
206
  });
218
207
  const response = JSON.parse(result.content[0].text);
219
208
  expect(response.total_past_outages).toBe(1);
@@ -234,8 +223,8 @@ describe("SystemStatusServer", () => {
234
223
  });
235
224
  const result = await server["handleToolCall"]({
236
225
  params: {
237
- name: "get_past_outages",
238
- arguments: { limit: 10 }
226
+ name: "get_infrastructure_news",
227
+ arguments: { time: "past", limit: 10 }
239
228
  }
240
229
  });
241
230
  const response = JSON.parse(result.content[0].text);
@@ -249,7 +238,7 @@ describe("SystemStatusServer", () => {
249
238
  .mockResolvedValueOnce({ status: 200, data: { results: mockFutureOutagesData } })
250
239
  .mockResolvedValueOnce({ status: 200, data: { results: mockPastOutagesData } });
251
240
  const result = await server["handleToolCall"]({
252
- params: { name: "get_system_announcements", arguments: {} }
241
+ params: { name: "get_infrastructure_news", arguments: { time: "all" } }
253
242
  });
254
243
  expect(mockHttpClient.get).toHaveBeenCalledTimes(3);
255
244
  const response = JSON.parse(result.content[0].text);
@@ -266,7 +255,7 @@ describe("SystemStatusServer", () => {
266
255
  .mockResolvedValueOnce({ status: 200, data: { results: mockFutureOutagesData } })
267
256
  .mockResolvedValueOnce({ status: 200, data: { results: mockPastOutagesData } });
268
257
  const result = await server["handleToolCall"]({
269
- params: { name: "get_system_announcements", arguments: {} }
258
+ params: { name: "get_infrastructure_news", arguments: { time: "all" } }
270
259
  });
271
260
  const response = JSON.parse(result.content[0].text);
272
261
  const firstAnnouncement = response.announcements[0];
@@ -281,8 +270,8 @@ describe("SystemStatusServer", () => {
281
270
  });
282
271
  const result = await server["handleToolCall"]({
283
272
  params: {
284
- name: "check_resource_status",
285
- arguments: { resource_ids: ["anvil-1", "unknown-resource"] }
273
+ name: "get_infrastructure_news",
274
+ arguments: { ids: ["anvil-1", "unknown-resource"] }
286
275
  }
287
276
  });
288
277
  const response = JSON.parse(result.content[0].text);
@@ -301,9 +290,9 @@ describe("SystemStatusServer", () => {
301
290
  });
302
291
  const result = await server["handleToolCall"]({
303
292
  params: {
304
- name: "check_resource_status",
293
+ name: "get_infrastructure_news",
305
294
  arguments: {
306
- resource_ids: ["anvil"],
295
+ ids: ["anvil"],
307
296
  use_group_api: true
308
297
  }
309
298
  }
@@ -318,15 +307,15 @@ describe("SystemStatusServer", () => {
318
307
  mockHttpClient.get.mockRejectedValue(new Error("API Error"));
319
308
  const result = await server["handleToolCall"]({
320
309
  params: {
321
- name: "check_resource_status",
310
+ name: "get_infrastructure_news",
322
311
  arguments: {
323
- resource_ids: ["invalid-resource"],
312
+ ids: ["invalid-resource"],
324
313
  use_group_api: true
325
314
  }
326
315
  }
327
316
  });
328
317
  const response = JSON.parse(result.content[0].text);
329
- expect(response.unknown).toBe(1);
318
+ expect(response).toHaveProperty("unknown", 1);
330
319
  expect(response.resource_status[0].status).toBe("unknown");
331
320
  expect(response.resource_status[0].api_method).toBe("group_specific_failed");
332
321
  expect(response.resource_status[0]).toHaveProperty("error");
@@ -339,16 +328,18 @@ describe("SystemStatusServer", () => {
339
328
  statusText: "Internal Server Error"
340
329
  });
341
330
  const result = await server["handleToolCall"]({
342
- params: { name: "get_current_outages", arguments: {} }
331
+ params: { name: "get_infrastructure_news", arguments: { time: "current" } }
343
332
  });
344
- expect(result.content[0].text).toContain("Error");
333
+ const response = JSON.parse(result.content[0].text);
334
+ expect(response.error).toBeDefined();
345
335
  });
346
336
  it("should handle network errors", async () => {
347
337
  mockHttpClient.get.mockRejectedValue(new Error("Network error"));
348
338
  const result = await server["handleToolCall"]({
349
- params: { name: "get_current_outages", arguments: {} }
339
+ params: { name: "get_infrastructure_news", arguments: { time: "current" } }
350
340
  });
351
- expect(result.content[0].text).toContain("Error");
341
+ const response = JSON.parse(result.content[0].text);
342
+ expect(response.error).toBe("Network error");
352
343
  });
353
344
  it("should handle unknown tools", async () => {
354
345
  const result = await server["handleToolCall"]({
package/dist/index.js CHANGED
@@ -1,18 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { SystemStatusServer } from "./server.js";
3
- import { startWebServer } from "./web-server.js";
4
3
  async function main() {
5
- // Check if we should run as web server (for deployment)
6
- const port = process.env.PORT;
7
- if (port) {
8
- // Running in web mode (deployment)
9
- startWebServer(parseInt(port));
10
- }
11
- else {
12
- // Running in MCP mode (stdio)
13
- const server = new SystemStatusServer();
14
- await server.start();
15
- }
4
+ const server = new SystemStatusServer();
5
+ const port = process.env.PORT ? parseInt(process.env.PORT, 10) : undefined;
6
+ await server.start(port ? { httpPort: port } : undefined);
16
7
  }
17
8
  main().catch((error) => {
18
9
  // Log errors to a file instead of stderr to avoid interfering with JSON-RPC
package/dist/server.d.ts CHANGED
@@ -1,81 +1,42 @@
1
1
  import { BaseAccessServer } from "@access-mcp/shared";
2
2
  export declare class SystemStatusServer extends BaseAccessServer {
3
3
  constructor();
4
- protected getTools(): ({
4
+ protected getTools(): {
5
5
  name: string;
6
6
  description: string;
7
7
  inputSchema: {
8
8
  type: string;
9
9
  properties: {
10
- resource_filter: {
10
+ query: {
11
11
  type: string;
12
12
  description: string;
13
13
  };
14
- limit?: undefined;
15
- resource_ids?: undefined;
16
- use_group_api?: undefined;
17
- };
18
- required: never[];
19
- };
20
- } | {
21
- name: string;
22
- description: string;
23
- inputSchema: {
24
- type: string;
25
- properties: {
26
- resource_filter: {
14
+ time: {
27
15
  type: string;
16
+ enum: string[];
28
17
  description: string;
18
+ default: string;
29
19
  };
30
- limit: {
20
+ ids: {
31
21
  type: string;
22
+ items: {
23
+ type: string;
24
+ };
32
25
  description: string;
33
26
  };
34
- resource_ids?: undefined;
35
- use_group_api?: undefined;
36
- };
37
- required: never[];
38
- };
39
- } | {
40
- name: string;
41
- description: string;
42
- inputSchema: {
43
- type: string;
44
- properties: {
45
27
  limit: {
46
28
  type: string;
47
29
  description: string;
48
- };
49
- resource_filter?: undefined;
50
- resource_ids?: undefined;
51
- use_group_api?: undefined;
52
- };
53
- required: never[];
54
- };
55
- } | {
56
- name: string;
57
- description: string;
58
- inputSchema: {
59
- type: string;
60
- properties: {
61
- resource_ids: {
62
- type: string;
63
- items: {
64
- type: string;
65
- };
66
- description: string;
30
+ default: number;
67
31
  };
68
32
  use_group_api: {
69
33
  type: string;
70
34
  description: string;
71
35
  default: boolean;
72
36
  };
73
- resource_filter?: undefined;
74
- limit?: undefined;
75
37
  };
76
- required: string[];
77
38
  };
78
- })[];
39
+ }[];
79
40
  protected getResources(): {
80
41
  uri: string;
81
42
  name: string;
@@ -88,6 +49,11 @@ export declare class SystemStatusServer extends BaseAccessServer {
88
49
  text: string;
89
50
  }[];
90
51
  }>;
52
+ /**
53
+ * Router for consolidated get_infrastructure_news tool
54
+ * Routes to appropriate handler based on parameters
55
+ */
56
+ private getInfrastructureNewsRouter;
91
57
  handleResourceRead(request: any): Promise<{
92
58
  contents: {
93
59
  uri: any;
package/dist/server.js CHANGED
@@ -6,83 +6,37 @@ export class SystemStatusServer extends BaseAccessServer {
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_infrastructure_news",
10
+ description: "Get ACCESS-CI infrastructure status (outages, maintenance, incidents). Returns {total, items}.",
11
11
  inputSchema: {
12
12
  type: "object",
13
13
  properties: {
14
- resource_filter: {
14
+ query: {
15
15
  type: "string",
16
- description: "Optional: filter by specific resource name or ID",
16
+ description: "Filter by resource name (e.g., 'delta', 'bridges2')"
17
17
  },
18
- },
19
- required: [],
20
- },
21
- },
22
- {
23
- name: "get_scheduled_maintenance",
24
- description: "Get scheduled maintenance and future outages for ACCESS-CI resources",
25
- inputSchema: {
26
- type: "object",
27
- properties: {
28
- resource_filter: {
18
+ time: {
29
19
  type: "string",
30
- description: "Optional: filter by specific resource name or ID",
20
+ enum: ["current", "scheduled", "past", "all"],
21
+ description: "Period: current (active), scheduled (future), past, all",
22
+ default: "current"
31
23
  },
32
- },
33
- required: [],
34
- },
35
- },
36
- {
37
- name: "get_past_outages",
38
- description: "Get historical outages and past incidents affecting ACCESS-CI resources",
39
- inputSchema: {
40
- type: "object",
41
- properties: {
42
- resource_filter: {
43
- type: "string",
44
- description: "Optional: filter by specific resource name or ID",
24
+ ids: {
25
+ type: "array",
26
+ items: { type: "string" },
27
+ description: "Check status for specific resource IDs"
45
28
  },
46
29
  limit: {
47
30
  type: "number",
48
- description: "Maximum number of past outages to return (default: 100)",
49
- },
50
- },
51
- required: [],
52
- },
53
- },
54
- {
55
- name: "get_system_announcements",
56
- description: "Get all system announcements (current and scheduled)",
57
- inputSchema: {
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",
73
- properties: {
74
- resource_ids: {
75
- type: "array",
76
- items: { type: "string" },
77
- description: "List of resource IDs or names to check status for",
31
+ description: "Max results (default: 50)",
32
+ default: 50
78
33
  },
79
34
  use_group_api: {
80
35
  type: "boolean",
81
- description: "Use resource group API for more efficient querying (default: false)",
82
- default: false,
83
- },
84
- },
85
- required: ["resource_ids"],
36
+ description: "Use group API for status (with ids only)",
37
+ default: false
38
+ }
39
+ }
86
40
  },
87
41
  },
88
42
  ];
@@ -119,29 +73,44 @@ export class SystemStatusServer extends BaseAccessServer {
119
73
  const { name, arguments: args = {} } = request.params;
120
74
  try {
121
75
  switch (name) {
122
- case "get_current_outages":
123
- return await this.getCurrentOutages(args.resource_filter);
124
- case "get_scheduled_maintenance":
125
- return await this.getScheduledMaintenance(args.resource_filter);
126
- case "get_past_outages":
127
- return await this.getPastOutages(args.resource_filter, args.limit);
128
- case "get_system_announcements":
129
- return await this.getSystemAnnouncements(args.limit);
130
- case "check_resource_status":
131
- return await this.checkResourceStatus(args.resource_ids, args.use_group_api);
76
+ case "get_infrastructure_news":
77
+ 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
83
+ });
132
84
  default:
133
- throw new Error(`Unknown tool: ${name}`);
85
+ return this.errorResponse(`Unknown tool: ${name}`);
134
86
  }
135
87
  }
136
88
  catch (error) {
137
- return {
138
- content: [
139
- {
140
- type: "text",
141
- text: `Error: ${handleApiError(error)}`,
142
- },
143
- ],
144
- };
89
+ return this.errorResponse(handleApiError(error));
90
+ }
91
+ }
92
+ /**
93
+ * Router for consolidated get_infrastructure_news tool
94
+ * Routes to appropriate handler based on parameters
95
+ */
96
+ async getInfrastructureNewsRouter(args) {
97
+ const { resource, time = "current", resource_ids, limit, use_group_api = false } = args;
98
+ // Check resource status (returns operational/affected)
99
+ if (resource_ids && Array.isArray(resource_ids)) {
100
+ return await this.checkResourceStatus(resource_ids, use_group_api);
101
+ }
102
+ // Time-based routing
103
+ switch (time) {
104
+ case "current":
105
+ return await this.getCurrentOutages(resource);
106
+ case "scheduled":
107
+ return await this.getScheduledMaintenance(resource);
108
+ case "past":
109
+ return await this.getPastOutages(resource, limit || 100);
110
+ case "all":
111
+ return await this.getSystemAnnouncements(limit || 50);
112
+ default:
113
+ throw new Error(`Invalid time parameter: ${time}. Must be one of: current, scheduled, past, all`);
145
114
  }
146
115
  }
147
116
  async handleResourceRead(request) {
@@ -230,8 +199,6 @@ export class SystemStatusServer extends BaseAccessServer {
230
199
  return {
231
200
  ...outage,
232
201
  severity,
233
- posted_time: outage.CreationTime,
234
- last_updated: outage.LastModificationTime,
235
202
  };
236
203
  });
237
204
  const summary = {
@@ -261,8 +228,8 @@ export class SystemStatusServer extends BaseAccessServer {
261
228
  }
262
229
  // Sort by scheduled start time
263
230
  maintenance.sort((a, b) => {
264
- const dateA = new Date(a.OutageStartDateTime || a.CreationTime);
265
- const dateB = new Date(b.OutageStartDateTime || b.CreationTime);
231
+ const dateA = new Date(a.OutageStart);
232
+ const dateB = new Date(b.OutageStart);
266
233
  return dateA.getTime() - dateB.getTime();
267
234
  });
268
235
  // Initialize tracking variables
@@ -274,9 +241,9 @@ export class SystemStatusServer extends BaseAccessServer {
274
241
  item.AffectedResources?.forEach((resource) => {
275
242
  affectedResources.add(resource.ResourceName);
276
243
  });
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);
244
+ // Check timing - use OutageStart for scheduling
245
+ const hasScheduledTime = !!item.OutageStart;
246
+ const startTime = new Date(item.OutageStart);
280
247
  const now = new Date();
281
248
  const hoursUntil = (startTime.getTime() - now.getTime()) / (1000 * 60 * 60);
282
249
  if (hoursUntil <= 24)
@@ -285,12 +252,12 @@ export class SystemStatusServer extends BaseAccessServer {
285
252
  upcomingWeek++; // 7 days * 24 hours
286
253
  return {
287
254
  ...item,
288
- scheduled_start: item.OutageStartDateTime,
289
- scheduled_end: item.OutageEndDateTime,
255
+ scheduled_start: item.OutageStart,
256
+ scheduled_end: item.OutageEnd,
290
257
  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()) /
258
+ duration_hours: item.OutageEnd && item.OutageStart
259
+ ? Math.round((new Date(item.OutageEnd).getTime() -
260
+ new Date(item.OutageStart).getTime()) /
294
261
  (1000 * 60 * 60))
295
262
  : null,
296
263
  has_scheduled_time: hasScheduledTime,
@@ -324,8 +291,8 @@ export class SystemStatusServer extends BaseAccessServer {
324
291
  }
325
292
  // Sort by outage end time (most recent first)
326
293
  pastOutages.sort((a, b) => {
327
- const dateA = new Date(a.OutageEndDateTime || a.LastModificationTime);
328
- const dateB = new Date(b.OutageEndDateTime || b.LastModificationTime);
294
+ const dateA = new Date(a.OutageEnd);
295
+ const dateB = new Date(b.OutageEnd);
329
296
  return dateB.getTime() - dateA.getTime();
330
297
  });
331
298
  // Apply limit
@@ -336,7 +303,7 @@ export class SystemStatusServer extends BaseAccessServer {
336
303
  const affectedResources = new Set();
337
304
  const outageTypes = new Set();
338
305
  const recentOutages = pastOutages.filter((outage) => {
339
- const endTime = new Date(outage.OutageEndDateTime || outage.LastModificationTime);
306
+ const endTime = new Date(outage.OutageEnd);
340
307
  const daysAgo = (Date.now() - endTime.getTime()) / (1000 * 60 * 60 * 24);
341
308
  return daysAgo <= 30; // Last 30 days
342
309
  });
@@ -351,20 +318,18 @@ export class SystemStatusServer extends BaseAccessServer {
351
318
  outageTypes.add(outage.OutageType);
352
319
  }
353
320
  // Calculate duration
354
- const startTime = new Date(outage.OutageStartDateTime);
355
- const endTime = new Date(outage.OutageEndDateTime);
321
+ const startTime = new Date(outage.OutageStart);
322
+ const endTime = new Date(outage.OutageEnd);
356
323
  const durationHours = Math.round((endTime.getTime() - startTime.getTime()) / (1000 * 60 * 60));
357
324
  // Calculate how long ago it ended
358
325
  const daysAgo = Math.round((Date.now() - endTime.getTime()) / (1000 * 60 * 60 * 24));
359
326
  return {
360
327
  ...outage,
361
- outage_start: outage.OutageStartDateTime,
362
- outage_end: outage.OutageEndDateTime,
328
+ outage_start: outage.OutageStart,
329
+ outage_end: outage.OutageEnd,
363
330
  duration_hours: durationHours,
364
331
  days_ago: daysAgo,
365
332
  outage_type: outage.OutageType,
366
- posted_time: outage.CreationTime,
367
- last_updated: outage.LastModificationTime,
368
333
  };
369
334
  });
370
335
  const summary = {
@@ -401,7 +366,7 @@ export class SystemStatusServer extends BaseAccessServer {
401
366
  const pastOutages = pastResponse.data.results || [];
402
367
  // Filter recent past outages (last 30 days) for announcements
403
368
  const recentPastOutages = pastOutages.filter((outage) => {
404
- const endTime = new Date(outage.OutageEndDateTime || outage.LastModificationTime);
369
+ const endTime = new Date(outage.OutageEnd);
405
370
  const daysAgo = (Date.now() - endTime.getTime()) / (1000 * 60 * 60 * 24);
406
371
  return daysAgo <= 30;
407
372
  });
@@ -417,8 +382,8 @@ export class SystemStatusServer extends BaseAccessServer {
417
382
  return -1;
418
383
  if (b.category === 'current' && a.category !== 'current')
419
384
  return 1;
420
- const dateA = new Date(a.OutageStartDateTime || a.CreationTime);
421
- const dateB = new Date(b.OutageStartDateTime || b.CreationTime);
385
+ const dateA = new Date(a.OutageStart);
386
+ const dateB = new Date(b.OutageStart);
422
387
  return dateB.getTime() - dateA.getTime(); // Most recent first
423
388
  })
424
389
  .slice(0, limit);
@@ -444,6 +409,9 @@ export class SystemStatusServer extends BaseAccessServer {
444
409
  };
445
410
  }
446
411
  async checkResourceStatus(resourceIds, useGroupApi = false) {
412
+ if (!resourceIds || !Array.isArray(resourceIds) || resourceIds.length === 0) {
413
+ throw new Error("resource_ids parameter is required and must be a non-empty array of resource IDs");
414
+ }
447
415
  if (useGroupApi) {
448
416
  return await this.checkResourceStatusViaGroups(resourceIds);
449
417
  }
@@ -485,8 +453,6 @@ export class SystemStatusServer extends BaseAccessServer {
485
453
  outage_details: affectedOutages.map((outage) => ({
486
454
  subject: outage.Subject,
487
455
  severity,
488
- posted: outage.CreationTime,
489
- last_updated: outage.LastModificationTime,
490
456
  })),
491
457
  };
492
458
  });
@@ -508,6 +474,9 @@ export class SystemStatusServer extends BaseAccessServer {
508
474
  };
509
475
  }
510
476
  async checkResourceStatusViaGroups(resourceIds) {
477
+ if (!resourceIds || !Array.isArray(resourceIds) || resourceIds.length === 0) {
478
+ throw new Error("resource_ids parameter is required and must be a non-empty array of resource IDs");
479
+ }
511
480
  // Try to use the more efficient group-based API
512
481
  const statusPromises = resourceIds.map(async (resourceId) => {
513
482
  try {
@@ -521,8 +490,6 @@ export class SystemStatusServer extends BaseAccessServer {
521
490
  active_outages: groupData.length,
522
491
  outage_details: groupData.map((outage) => ({
523
492
  subject: outage.Subject,
524
- posted: outage.CreationTime,
525
- last_updated: outage.LastModificationTime,
526
493
  })),
527
494
  api_method: "group_specific",
528
495
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@access-mcp/system-status",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
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.0",
47
+ "@access-mcp/shared": "^0.3.3",
48
48
  "express": "^4.18.0"
49
49
  },
50
50
  "devDependencies": {