@access-mcp/system-status 0.4.1 → 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
@@ -4,83 +4,64 @@ MCP server providing real-time system status information for ACCESS-CI resources
4
4
 
5
5
  ## Usage Examples
6
6
 
7
- ### **Monitor Current Issues**
7
+ ### **Current Status**
8
8
 
9
9
  ```
10
- "Are there any current outages on ACCESS-CI?"
11
- "Is Delta currently operational?"
12
- "What systems are experiencing issues right now?"
13
- "Show me all systems that are down"
10
+ "Current ACCESS-CI outages"
11
+ "Delta operational status"
12
+ "Systems experiencing issues"
13
+ "GPU systems status check"
14
14
  ```
15
15
 
16
- ### **Track Maintenance Windows**
16
+ ### **Maintenance & Incidents**
17
17
 
18
18
  ```
19
- "When is the next maintenance for Expanse?"
20
- "Show me all scheduled maintenance for this week"
21
- "Is there upcoming maintenance on Bridges-2?"
22
- "What maintenance is planned for GPU systems?"
23
- ```
24
-
25
- ### **System Announcements**
26
-
27
- ```
28
- "What are the latest system announcements?"
29
- "Are there any important notices for ACCESS users?"
30
- "Show me recent updates about system changes"
31
- "Any policy updates I should know about?"
32
- ```
33
-
34
- ### **Check Resource Status**
35
-
36
- ```
37
- "What's the current status of Anvil?"
38
- "Is Frontera available for job submission?"
39
- "Check if all GPU systems are operational"
40
- "Get status for all TACC resources"
19
+ "Scheduled maintenance this week"
20
+ "Next Expanse maintenance window"
21
+ "Past outages for Bridges-2"
22
+ "All infrastructure news for Delta"
41
23
  ```
42
24
 
43
25
  ## Tools
44
26
 
45
- ### get_current_outages
27
+ ### get_infrastructure_news
46
28
 
47
- Get current system outages and issues 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.
48
30
 
49
31
  **Parameters:**
50
32
 
51
- - `resource_filter` (string, optional): Filter by specific resource name or ID
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)
52
38
 
53
- **Example:**
54
- ```typescript
55
- // User: "Are there any current outages on Delta?"
56
- const outages = await get_current_outages({
57
- resource_filter: "delta"
58
- });
59
- ```
39
+ **Examples:**
60
40
 
61
- ### get_scheduled_maintenance
62
-
63
- Get scheduled maintenance and future outages for ACCESS-CI resources.
64
-
65
- **Parameters:**
66
-
67
- - `resource_filter` (string, optional): Filter by specific resource name or ID
68
-
69
- ### get_system_announcements
41
+ ```typescript
42
+ // Get current outages across all resources
43
+ get_infrastructure_news({})
70
44
 
71
- Get all system announcements (current and scheduled).
45
+ // Get scheduled maintenance
46
+ get_infrastructure_news({ time: "scheduled" })
72
47
 
73
- **Parameters:**
48
+ // Get comprehensive overview of all infrastructure news
49
+ get_infrastructure_news({ time: "all" })
74
50
 
75
- - `limit` (number, optional): Maximum number of announcements to return (default: 50)
51
+ // Check current status for specific resource
52
+ get_infrastructure_news({ resource: "delta", time: "current" })
76
53
 
77
- ### get_resource_status
54
+ // Get all news for specific resource
55
+ get_infrastructure_news({ resource: "delta", time: "all" })
78
56
 
79
- Get the current operational status of a specific resource.
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
+ })
80
61
 
81
- **Parameters:**
82
-
83
- - `resource_id` (string): The resource ID to check status for
62
+ // Get past outages with limit
63
+ get_infrastructure_news({ time: "past", limit: 50 })
64
+ ```
84
65
 
85
66
  ## Resources
86
67
 
@@ -141,14 +122,16 @@ Add to your Claude Desktop configuration:
141
122
 
142
123
  **Tool Call**:
143
124
  ```typescript
144
- const outages = await get_current_outages();
125
+ const outages = await get_infrastructure_news({});
126
+ // or explicitly: get_infrastructure_news({ time: "current" })
145
127
  ```
146
128
 
147
129
  **Returns**: List of active outages with:
148
130
  - Affected resources
131
+ - Severity categorization (high/medium/low)
149
132
  - Start time and expected resolution
150
133
  - Impact description
151
- - Workaround information if available
134
+ - Summary statistics (total outages, affected resources)
152
135
 
153
136
  ### Finding Scheduled Maintenance
154
137
 
@@ -156,33 +139,37 @@ const outages = await get_current_outages();
156
139
 
157
140
  **Tool Call**:
158
141
  ```typescript
159
- const maintenance = await get_scheduled_maintenance({
160
- resource_filter: "delta"
142
+ const maintenance = await get_infrastructure_news({
143
+ resource: "delta",
144
+ time: "scheduled"
161
145
  });
162
146
  ```
163
147
 
164
148
  **Returns**: Upcoming maintenance windows including:
165
149
  - Scheduled start and end times
166
150
  - Systems affected
167
- - Type of maintenance
168
- - Expected impact on users
151
+ - Duration in hours
152
+ - Hours until maintenance starts
153
+ - Summary (upcoming in 24h, upcoming this week)
169
154
 
170
- ### Getting System Announcements
155
+ ### Getting Comprehensive System Overview
171
156
 
172
157
  **Natural Language**: "What are the latest announcements?"
173
158
 
174
159
  **Tool Call**:
175
160
  ```typescript
176
- const announcements = await get_system_announcements({
161
+ const announcements = await get_infrastructure_news({
162
+ time: "all",
177
163
  limit: 10
178
164
  });
179
165
  ```
180
166
 
181
- **Returns**: Recent announcements about:
182
- - Policy changes
183
- - New features or services
184
- - Important deadlines
185
- - 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)
186
173
 
187
174
  ### Checking Specific Resource Status
188
175
 
@@ -190,16 +177,17 @@ const announcements = await get_system_announcements({
190
177
 
191
178
  **Tool Call**:
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
- - Overall system health
200
- - Service availability
201
- - Performance metrics
202
- - 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
203
191
 
204
192
  ## API Endpoints
205
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,17 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { SystemStatusServer } from "./server.js";
3
3
  async function main() {
4
- // Check if we should run as HTTP server (for deployment)
5
- const port = process.env.PORT;
6
4
  const server = new SystemStatusServer();
7
- if (port) {
8
- // Running in HTTP mode (deployment)
9
- await server.start({ httpPort: parseInt(port) });
10
- }
11
- else {
12
- // Running in MCP mode (stdio)
13
- await server.start();
14
- }
5
+ const port = process.env.PORT ? parseInt(process.env.PORT, 10) : undefined;
6
+ await server.start(port ? { httpPort: port } : undefined);
15
7
  }
16
8
  main().catch((error) => {
17
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 || outage.DateTime || outage.created_at || outage.date_posted,
234
- last_updated: outage.LastModificationTime || outage.DateTime || outage.updated_at || outage.date_modified,
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.1",
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": {