Skip to content

Commit 600dce4

Browse files
committed
feat: Alerts Listing
1 parent 7b033ad commit 600dce4

File tree

4 files changed

+129
-0
lines changed

4 files changed

+129
-0
lines changed

src/common/atlas/apiClient.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,5 +365,13 @@ export class ApiClient {
365365
return data;
366366
}
367367

368+
async listAlerts(options: FetchOptions<operations["listAlerts"]>) {
369+
const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}/alerts", options);
370+
if (error) {
371+
throw ApiClientError.fromError(response, error);
372+
}
373+
return data;
374+
}
375+
368376
// DO NOT EDIT. This is auto-generated code.
369377
}

src/tools/atlas/read/listAlerts.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { z } from "zod";
2+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3+
import { AtlasToolBase } from "../atlasTool.js";
4+
import { ToolArgs, OperationType } from "../../tool.js";
5+
6+
interface Alert {
7+
id: string;
8+
status: string;
9+
created: string;
10+
updated: string;
11+
eventTypeName: string;
12+
summary: string;
13+
}
14+
15+
interface AlertResponse {
16+
results: Alert[];
17+
totalCount: number;
18+
}
19+
20+
export class ListAlertsTool extends AtlasToolBase {
21+
protected name = "atlas-list-alerts";
22+
protected description = "List MongoDB Atlas alerts";
23+
protected operationType: OperationType = "read";
24+
protected argsShape = {
25+
projectId: z.string().describe("Atlas project ID to list alerts for"),
26+
};
27+
28+
protected async execute({ projectId }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
29+
const data = (await this.session.apiClient.listAlerts({
30+
params: {
31+
path: {
32+
groupId: projectId,
33+
},
34+
},
35+
})) as AlertResponse;
36+
37+
if (!data?.results?.length) {
38+
throw new Error("No alerts found in your MongoDB Atlas project.");
39+
}
40+
41+
// Format alerts as a table
42+
const output =
43+
`Alert ID | Status | Created | Updated | Type | Summary
44+
----------|---------|----------|----------|------|--------
45+
` +
46+
data.results
47+
.map((alert) => {
48+
const created = alert.created ? new Date(alert.created).toLocaleString() : "N/A";
49+
const updated = alert.updated ? new Date(alert.updated).toLocaleString() : "N/A";
50+
return `${alert.id} | ${alert.status} | ${created} | ${updated} | ${alert.eventTypeName} | ${alert.summary}`;
51+
})
52+
.join("\n");
53+
54+
return {
55+
content: [{ type: "text", text: output }],
56+
};
57+
}
58+
}

src/tools/atlas/tools.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { CreateDBUserTool } from "./create/createDBUser.js";
99
import { CreateProjectTool } from "./create/createProject.js";
1010
import { ListOrganizationsTool } from "./read/listOrgs.js";
1111
import { ConnectClusterTool } from "./metadata/connectCluster.js";
12+
import { ListAlertsTool } from "./read/listAlerts.js";
1213

1314
export const AtlasTools = [
1415
ListClustersTool,
@@ -22,4 +23,5 @@ export const AtlasTools = [
2223
CreateProjectTool,
2324
ListOrganizationsTool,
2425
ConnectClusterTool,
26+
ListAlertsTool,
2527
];
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2+
import { expectDefined } from "../../helpers.js";
3+
import { parseTable, describeWithAtlas, withProject } from "./atlasHelpers.js";
4+
5+
describeWithAtlas("alerts", (integration) => {
6+
describe("atlas-list-alerts", () => {
7+
it("should have correct metadata", async () => {
8+
const { tools } = await integration.mcpClient().listTools();
9+
const listAlerts = tools.find((tool) => tool.name === "atlas-list-alerts");
10+
expectDefined(listAlerts);
11+
expect(listAlerts.inputSchema.type).toBe("object");
12+
expectDefined(listAlerts.inputSchema.properties);
13+
expect(listAlerts.inputSchema.properties).toHaveProperty("projectId");
14+
});
15+
16+
withProject(integration, ({ getProjectId }) => {
17+
it("returns alerts in table format", async () => {
18+
const response = (await integration.mcpClient().callTool({
19+
name: "atlas-list-alerts",
20+
arguments: { projectId: getProjectId() },
21+
})) as CallToolResult;
22+
23+
expect(response.content).toBeArray();
24+
expect(response.content).toHaveLength(1);
25+
26+
const data = parseTable(response.content[0].text as string);
27+
expect(data).toBeArray();
28+
29+
// Since we can't guarantee alerts will exist, we just verify the table structure
30+
if (data.length > 0) {
31+
const alert = data[0];
32+
expect(alert).toHaveProperty("Alert ID");
33+
expect(alert).toHaveProperty("Status");
34+
expect(alert).toHaveProperty("Created");
35+
expect(alert).toHaveProperty("Updated");
36+
expect(alert).toHaveProperty("Type");
37+
expect(alert).toHaveProperty("Summary");
38+
}
39+
});
40+
41+
it("handles no alerts gracefully", async () => {
42+
// Mock the API client to return no alerts
43+
const apiClient = integration.mcpServer().session.apiClient;
44+
const originalListAlerts = apiClient.listAlerts.bind(apiClient);
45+
apiClient.listAlerts = () => Promise.resolve({ results: [], totalCount: 0 });
46+
47+
try {
48+
await expect(
49+
integration.mcpClient().callTool({
50+
name: "atlas-list-alerts",
51+
arguments: { projectId: getProjectId() },
52+
})
53+
).rejects.toThrow("No alerts found in your MongoDB Atlas project.");
54+
} finally {
55+
// Restore the original method
56+
apiClient.listAlerts = originalListAlerts;
57+
}
58+
});
59+
});
60+
});
61+
});

0 commit comments

Comments
 (0)