@access-mcp/system-status 0.5.0 → 0.6.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 +16 -132
- package/dist/__tests__/server.integration.test.js +66 -40
- package/dist/__tests__/server.test.js +183 -86
- package/dist/index.js +1 -1
- package/dist/server.d.ts +11 -56
- package/dist/server.js +135 -67
- package/dist/web-server.js +1 -1
- package/package.json +2 -2
|
@@ -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.purdue.access-ci.org" }],
|
|
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
|
-
|
|
27
|
-
]
|
|
28
|
-
}
|
|
26
|
+
AffectedResources: [{ ResourceName: "Bridges-2", ResourceID: "bridges2.psc.access-ci.org" }],
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
130
|
+
method: "tools/call",
|
|
131
|
+
params: { name: "get_infrastructure_news", arguments: { time: "current" } },
|
|
132
132
|
});
|
|
133
|
-
const
|
|
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
|
-
|
|
148
|
+
method: "tools/call",
|
|
149
|
+
params: { name: "get_infrastructure_news", arguments: { time: "scheduled" } },
|
|
148
150
|
});
|
|
149
|
-
const
|
|
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
|
-
|
|
172
|
+
method: "tools/call",
|
|
173
|
+
params: { name: "get_infrastructure_news", arguments: { time: "scheduled" } },
|
|
168
174
|
});
|
|
169
|
-
const
|
|
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
|
-
|
|
198
|
+
method: "tools/call",
|
|
199
|
+
params: { name: "get_infrastructure_news", arguments: { time: "scheduled" } },
|
|
192
200
|
});
|
|
193
|
-
const
|
|
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
|
-
|
|
214
|
+
method: "tools/call",
|
|
215
|
+
params: { name: "get_infrastructure_news", arguments: { time: "past" } },
|
|
206
216
|
});
|
|
207
|
-
const
|
|
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)
|
|
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
|
|
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
|
-
|
|
256
|
+
method: "tools/call",
|
|
257
|
+
params: { name: "get_infrastructure_news", arguments: { time: "all" } },
|
|
242
258
|
});
|
|
243
259
|
expect(mockHttpClient.get).toHaveBeenCalledTimes(3);
|
|
244
|
-
const
|
|
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
|
-
|
|
275
|
+
method: "tools/call",
|
|
276
|
+
params: { name: "get_infrastructure_news", arguments: { time: "all" } },
|
|
259
277
|
});
|
|
260
|
-
const
|
|
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,104 +285,182 @@ 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
|
});
|
|
290
|
+
// Use full IDs with dots to skip resolution lookup
|
|
271
291
|
const result = await server["handleToolCall"]({
|
|
292
|
+
method: "tools/call",
|
|
272
293
|
params: {
|
|
273
294
|
name: "get_infrastructure_news",
|
|
274
|
-
arguments: { ids: ["anvil-1", "unknown
|
|
275
|
-
}
|
|
295
|
+
arguments: { ids: ["anvil-1.purdue.access-ci.org", "unknown.resource.org"] },
|
|
296
|
+
},
|
|
276
297
|
});
|
|
277
|
-
const
|
|
298
|
+
const content = result.content[0];
|
|
299
|
+
const response = JSON.parse(content.text);
|
|
278
300
|
expect(response.api_method).toBe("direct_outages_check");
|
|
279
301
|
expect(response.resources_checked).toBe(2);
|
|
280
|
-
expect(response.operational).toBe(1); // unknown
|
|
281
|
-
expect(response.affected).toBe(1); // anvil-1
|
|
282
|
-
const anvilStatus = response.resource_status.find((r) => r.resource_id === "anvil-1");
|
|
302
|
+
expect(response.operational).toBe(1); // unknown.resource.org
|
|
303
|
+
expect(response.affected).toBe(1); // anvil-1.purdue.access-ci.org
|
|
304
|
+
const anvilStatus = response.resource_status.find((r) => r.resource_id === "anvil-1.purdue.access-ci.org");
|
|
283
305
|
expect(anvilStatus.status).toBe("affected");
|
|
284
306
|
expect(anvilStatus.severity).toBe("high"); // Emergency maintenance
|
|
285
307
|
});
|
|
286
308
|
it("should use group API when requested", async () => {
|
|
287
309
|
mockHttpClient.get.mockResolvedValue({
|
|
288
310
|
status: 200,
|
|
289
|
-
data: { results: [] } // No outages for this group
|
|
311
|
+
data: { results: [] }, // No outages for this group
|
|
290
312
|
});
|
|
313
|
+
// Use full ID with dots to skip resolution lookup
|
|
291
314
|
const result = await server["handleToolCall"]({
|
|
315
|
+
method: "tools/call",
|
|
292
316
|
params: {
|
|
293
317
|
name: "get_infrastructure_news",
|
|
294
318
|
arguments: {
|
|
295
|
-
ids: ["anvil"],
|
|
296
|
-
use_group_api: true
|
|
297
|
-
}
|
|
298
|
-
}
|
|
319
|
+
ids: ["anvil.purdue.access-ci.org"],
|
|
320
|
+
use_group_api: true,
|
|
321
|
+
},
|
|
322
|
+
},
|
|
299
323
|
});
|
|
300
|
-
expect(mockHttpClient.get).toHaveBeenCalledWith("/wh2/news/v1/info_groupid/anvil/");
|
|
301
|
-
const
|
|
324
|
+
expect(mockHttpClient.get).toHaveBeenCalledWith("/wh2/news/v1/info_groupid/anvil.purdue.access-ci.org/");
|
|
325
|
+
const content = result.content[0];
|
|
326
|
+
const response = JSON.parse(content.text);
|
|
302
327
|
expect(response.api_method).toBe("resource_group_api");
|
|
303
328
|
expect(response.resource_status[0].status).toBe("operational");
|
|
304
329
|
expect(response.resource_status[0].api_method).toBe("group_specific");
|
|
305
330
|
});
|
|
306
331
|
it("should handle group API failures gracefully", async () => {
|
|
307
332
|
mockHttpClient.get.mockRejectedValue(new Error("API Error"));
|
|
333
|
+
// Use full ID with dots to skip resolution lookup
|
|
308
334
|
const result = await server["handleToolCall"]({
|
|
335
|
+
method: "tools/call",
|
|
309
336
|
params: {
|
|
310
337
|
name: "get_infrastructure_news",
|
|
311
338
|
arguments: {
|
|
312
|
-
ids: ["invalid
|
|
313
|
-
use_group_api: true
|
|
314
|
-
}
|
|
315
|
-
}
|
|
339
|
+
ids: ["invalid.resource.org"],
|
|
340
|
+
use_group_api: true,
|
|
341
|
+
},
|
|
342
|
+
},
|
|
316
343
|
});
|
|
317
|
-
const
|
|
344
|
+
const content = result.content[0];
|
|
345
|
+
const response = JSON.parse(content.text);
|
|
318
346
|
expect(response).toHaveProperty("unknown", 1);
|
|
319
347
|
expect(response.resource_status[0].status).toBe("unknown");
|
|
320
348
|
expect(response.resource_status[0].api_method).toBe("group_specific_failed");
|
|
321
349
|
expect(response.resource_status[0]).toHaveProperty("error");
|
|
322
350
|
});
|
|
351
|
+
it("should resolve human-readable name to resource ID", async () => {
|
|
352
|
+
// First call: resource search for name resolution
|
|
353
|
+
// Second call: current outages
|
|
354
|
+
mockHttpClient.get
|
|
355
|
+
.mockResolvedValueOnce({
|
|
356
|
+
status: 200,
|
|
357
|
+
data: {
|
|
358
|
+
results: {
|
|
359
|
+
active_groups: [
|
|
360
|
+
{ info_groupid: "anvil.purdue.access-ci.org", group_descriptive_name: "Anvil" },
|
|
361
|
+
],
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
})
|
|
365
|
+
.mockResolvedValueOnce({
|
|
366
|
+
status: 200,
|
|
367
|
+
data: { results: [] }, // No outages
|
|
368
|
+
});
|
|
369
|
+
const result = await server["handleToolCall"]({
|
|
370
|
+
method: "tools/call",
|
|
371
|
+
params: {
|
|
372
|
+
name: "get_infrastructure_news",
|
|
373
|
+
arguments: { ids: ["Anvil"] },
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
const content = result.content[0];
|
|
377
|
+
const response = JSON.parse(content.text);
|
|
378
|
+
expect(response.resources_checked).toBe(1);
|
|
379
|
+
expect(response.resource_status[0].resource_id).toBe("anvil.purdue.access-ci.org");
|
|
380
|
+
});
|
|
381
|
+
it("should return error when resource name is ambiguous", async () => {
|
|
382
|
+
mockHttpClient.get.mockResolvedValueOnce({
|
|
383
|
+
status: 200,
|
|
384
|
+
data: {
|
|
385
|
+
results: {
|
|
386
|
+
active_groups: [
|
|
387
|
+
{
|
|
388
|
+
info_groupid: "stampede2.tacc.access-ci.org",
|
|
389
|
+
group_descriptive_name: "Stampede 2",
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
info_groupid: "stampede3.tacc.access-ci.org",
|
|
393
|
+
group_descriptive_name: "Stampede 3",
|
|
394
|
+
},
|
|
395
|
+
],
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
const result = await server["handleToolCall"]({
|
|
400
|
+
method: "tools/call",
|
|
401
|
+
params: {
|
|
402
|
+
name: "get_infrastructure_news",
|
|
403
|
+
arguments: { ids: ["Stampede"] },
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
const content = result.content[0];
|
|
407
|
+
const response = JSON.parse(content.text);
|
|
408
|
+
expect(response.error).toContain("Could not resolve");
|
|
409
|
+
expect(response.resolution_errors[0].error).toContain("Multiple resources match");
|
|
410
|
+
});
|
|
323
411
|
});
|
|
324
412
|
describe("Error Handling", () => {
|
|
325
413
|
it("should handle API errors gracefully", async () => {
|
|
326
414
|
mockHttpClient.get.mockResolvedValue({
|
|
327
415
|
status: 500,
|
|
328
|
-
statusText: "Internal Server Error"
|
|
416
|
+
statusText: "Internal Server Error",
|
|
417
|
+
data: null,
|
|
329
418
|
});
|
|
330
419
|
const result = await server["handleToolCall"]({
|
|
331
|
-
|
|
420
|
+
method: "tools/call",
|
|
421
|
+
params: { name: "get_infrastructure_news", arguments: { time: "current" } },
|
|
332
422
|
});
|
|
333
|
-
const
|
|
423
|
+
const content = result.content[0];
|
|
424
|
+
const response = JSON.parse(content.text);
|
|
334
425
|
expect(response.error).toBeDefined();
|
|
335
426
|
});
|
|
336
427
|
it("should handle network errors", async () => {
|
|
337
428
|
mockHttpClient.get.mockRejectedValue(new Error("Network error"));
|
|
338
429
|
const result = await server["handleToolCall"]({
|
|
339
|
-
|
|
430
|
+
method: "tools/call",
|
|
431
|
+
params: { name: "get_infrastructure_news", arguments: { time: "current" } },
|
|
340
432
|
});
|
|
341
|
-
const
|
|
433
|
+
const content = result.content[0];
|
|
434
|
+
const response = JSON.parse(content.text);
|
|
342
435
|
expect(response.error).toBe("Network error");
|
|
343
436
|
});
|
|
344
437
|
it("should handle unknown tools", async () => {
|
|
345
438
|
const result = await server["handleToolCall"]({
|
|
346
|
-
|
|
439
|
+
method: "tools/call",
|
|
440
|
+
params: { name: "unknown_tool", arguments: {} },
|
|
347
441
|
});
|
|
348
|
-
|
|
442
|
+
const content = result.content[0];
|
|
443
|
+
expect(content.text).toContain("Unknown tool");
|
|
349
444
|
});
|
|
350
445
|
});
|
|
351
446
|
describe("Resource Handling", () => {
|
|
352
447
|
it("should handle resource reads correctly", async () => {
|
|
353
448
|
mockHttpClient.get.mockResolvedValue({
|
|
354
449
|
status: 200,
|
|
355
|
-
data: { results: mockCurrentOutagesData }
|
|
450
|
+
data: { results: mockCurrentOutagesData },
|
|
356
451
|
});
|
|
357
452
|
const result = await server["handleResourceRead"]({
|
|
358
|
-
|
|
453
|
+
method: "resources/read",
|
|
454
|
+
params: { uri: "accessci://outages/current" },
|
|
359
455
|
});
|
|
360
456
|
expect(result.contents[0].mimeType).toBe("application/json");
|
|
361
|
-
expect(result.contents[0].text).toBeDefined();
|
|
457
|
+
expect("text" in result.contents[0] && result.contents[0].text).toBeDefined();
|
|
362
458
|
});
|
|
363
459
|
it("should handle unknown resources", async () => {
|
|
364
460
|
await expect(async () => {
|
|
365
461
|
await server["handleResourceRead"]({
|
|
366
|
-
|
|
462
|
+
method: "resources/read",
|
|
463
|
+
params: { uri: "accessci://unknown" },
|
|
367
464
|
});
|
|
368
465
|
}).rejects.toThrow("Unknown resource");
|
|
369
466
|
});
|
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((
|
|
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,21 @@
|
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
+
/**
|
|
6
|
+
* Search for resources by name to resolve human-readable names to full IDs.
|
|
7
|
+
* Used by resolveResourceId callback.
|
|
8
|
+
*/
|
|
9
|
+
private searchResourcesByName;
|
|
10
|
+
protected getTools(): Tool[];
|
|
11
|
+
protected getResources(): Resource[];
|
|
12
|
+
protected handleToolCall(request: CallToolRequest): Promise<CallToolResult>;
|
|
52
13
|
/**
|
|
53
14
|
* Router for consolidated get_infrastructure_news tool
|
|
54
15
|
* Routes to appropriate handler based on parameters
|
|
55
16
|
*/
|
|
56
17
|
private getInfrastructureNewsRouter;
|
|
57
|
-
handleResourceRead(request:
|
|
58
|
-
contents: {
|
|
59
|
-
uri: any;
|
|
60
|
-
mimeType: string;
|
|
61
|
-
text: string;
|
|
62
|
-
}[];
|
|
63
|
-
}>;
|
|
18
|
+
protected handleResourceRead(request: ReadResourceRequest): Promise<ReadResourceResult>;
|
|
64
19
|
private getCurrentOutages;
|
|
65
20
|
private getScheduledMaintenance;
|
|
66
21
|
private getPastOutages;
|