@mseep/doit-mcp-server
Version:
DoiT official MCP Server
155 lines • 5.95 kB
JavaScript
import { z } from "zod";
import { createErrorResponse, createSuccessResponse, formatZodError, handleGeneralError, makeDoitRequest, DOIT_API_BASE, } from "../utils/util.js";
// Schema definitions
export const AnomaliesArgumentsSchema = z.object({
filter: z
.string()
.optional()
.describe("Filter string in format 'key:value|key:value'. Multiple values for same key are treated as OR, different keys as AND."),
pageToken: z
.string()
.optional()
.describe("Token for pagination. Use this to get the next page of results."),
});
export const AnomalyArgumentsSchema = z.object({
id: z.string(),
});
// Tool metadata
export const anomaliesTool = {
name: "get_anomalies",
description: "List anomalies detected in cloud costs",
inputSchema: {
type: "object",
properties: {
filter: {
type: "string",
description: "Filter string in format 'key:value|key:value'. Multiple values for same key are treated as OR, different keys as AND.",
},
pageToken: {
type: "string",
description: "Token for pagination. Use this to get the next page of results.",
},
},
},
};
export const anomalyTool = {
name: "get_anomaly",
description: "Get a specific anomaly by ID",
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: "anomaly ID",
},
},
required: ["id"],
},
};
// Format anomaly data
export function formatAnomaly(anomaly) {
const startDate = new Date(anomaly.startTime).toLocaleString();
const endDate = anomaly.endTime
? new Date(anomaly.endTime).toLocaleString()
: "Ongoing";
// Format the top SKUs
const skusFormatted = anomaly.top3SKUs
.map((sku) => `\n - ${sku.name}: $${sku.cost.toFixed(2)}`)
.join("");
return [
anomaly.id ? `ID: ${anomaly.id}` : null,
anomaly.anomalyChartUrl ? `Chart URL: ${anomaly.anomalyChartUrl}` : null,
`Platform: ${anomaly.platform}`,
`Service: ${anomaly.serviceName}`,
`Scope: ${anomaly.scope}`,
`Cost of Anomaly: $${anomaly.costOfAnomaly.toFixed(2)}`,
`Severity: ${anomaly.severityLevel}`,
`Time Frame: ${anomaly.timeFrame}`,
`Started: ${startDate}`,
`Ended: ${endDate}`,
`Status: ${anomaly.status || "N/A"}`,
`Acknowledged: ${anomaly.acknowledged ? "Yes" : "No"}`,
`Top SKUs: ${skusFormatted}`,
"-----------",
]
.filter(Boolean)
.join("\n");
}
// Handle anomalies request
export async function handleAnomaliesRequest(args, token) {
try {
const { filter, pageToken } = AnomaliesArgumentsSchema.parse(args);
// Create API URL with query parameters
const params = new URLSearchParams();
if (filter) {
params.append("filter", filter);
}
if (pageToken) {
params.append("pageToken", pageToken);
}
let anomaliesUrl = `${DOIT_API_BASE}/anomalies/v1`;
if (params.toString()) {
anomaliesUrl += `?${params.toString()}`;
}
try {
const anomaliesData = await makeDoitRequest(anomaliesUrl, token, { method: "GET" });
if (!anomaliesData) {
return createErrorResponse("Failed to retrieve anomalies data");
}
let anomalies = anomaliesData.anomalies || [];
const rowCount = anomaliesData.rowCount || 0;
if (anomalies.length === 0) {
return createErrorResponse("No anomalies found");
}
const formattedAnomalies = anomalies.map(formatAnomaly);
// Create a descriptive message that includes filter information if provided
let anomaliesText = `Found ${rowCount} anomalies`;
if (filter) {
anomaliesText += ` (filtered by: ${filter})`;
}
anomaliesText += `:\n\n${formattedAnomalies.join("\n")} \n\n${pageToken ? `Page token: ${pageToken}` : ""}`;
return createSuccessResponse(anomaliesText);
}
catch (error) {
return handleGeneralError(error, "making DoiT API request");
}
}
catch (error) {
if (error instanceof z.ZodError) {
return createErrorResponse(formatZodError(error));
}
return handleGeneralError(error, "handling anomalies request");
}
}
// Handle specific anomaly request
export async function handleAnomalyRequest(args, token) {
try {
const { id } = AnomalyArgumentsSchema.parse(args);
let anomalyUrl = `${DOIT_API_BASE}/anomalies/v1/${id}`;
try {
// Explicitly set appendParams to true to ensure URL parameters are added
const anomalyData = await makeDoitRequest(anomalyUrl, token, {
method: "GET",
appendParams: true,
});
if (!anomalyData) {
return createErrorResponse(`Failed to retrieve anomaly with ID: ${id}`);
}
// The API response for a single anomaly doesn't include the id in the response
// Let's add it for consistency in the formatted output
const anomaly = { ...anomalyData, id };
const formattedAnomaly = formatAnomaly(anomaly);
return createSuccessResponse(`Anomaly details:\n\n${formattedAnomaly}`);
}
catch (error) {
return handleGeneralError(error, "making DoiT API request");
}
}
catch (error) {
if (error instanceof z.ZodError) {
return createErrorResponse(formatZodError(error));
}
return handleGeneralError(error, "handling anomaly request");
}
}
//# sourceMappingURL=anomalies.js.map