Skip to main content
Glama
krzko

Google Cloud MCP Server

by krzko

gcp-billing-detect-anomalies

Identify unusual spending patterns and cost anomalies in Google Cloud billing data to monitor budget compliance and optimize cloud expenses.

Instructions

Detect unusual cost patterns and spending anomalies in Google Cloud billing data

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
billingAccountNameYesBilling account name (e.g., 'billingAccounts/123456-789ABC-DEF012')
lookbackDaysNoNumber of days to look back for comparison (7-90)
thresholdPercentageNoPercentage threshold for anomaly detection (10-500%)
projectIdNoOptional project ID to filter anomalies

Implementation Reference

  • Main tool handler function that implements the core logic: resolves project ID, logs activity, generates mock current and historical CostData, invokes detectCostAnomalies helper with threshold, formats detailed Markdown response with anomalies, recommendations, and notes about mock data.
    async ({ billingAccountName, lookbackDays, thresholdPercentage, projectId, }) => { try { // Use project hierarchy: provided -> state manager -> auth default const actualProjectId = projectId || stateManager.getCurrentProjectId() || (await getProjectId()); logger.debug( `Detecting cost anomalies for billing account: ${billingAccountName}, project: ${actualProjectId || "all"}`, ); // Mock current and historical cost data for demonstration const currentCosts: CostData[] = [ { billingAccountName, projectId: actualProjectId || "example-project-1", serviceId: "compute.googleapis.com", cost: { amount: 2500, currency: "USD" }, usage: { amount: 200, unit: "hours" }, period: { startTime: new Date( Date.now() - 24 * 60 * 60 * 1000, ).toISOString(), endTime: new Date().toISOString(), }, }, ]; const historicalCosts: CostData[] = [ { billingAccountName, projectId: actualProjectId || "example-project-1", serviceId: "compute.googleapis.com", cost: { amount: 1250, currency: "USD" }, usage: { amount: 100, unit: "hours" }, period: { startTime: new Date( Date.now() - lookbackDays * 24 * 60 * 60 * 1000, ).toISOString(), endTime: new Date( Date.now() - (lookbackDays - 1) * 24 * 60 * 60 * 1000, ).toISOString(), }, }, ]; const anomalies: CostAnomaly[] = detectCostAnomalies( currentCosts, historicalCosts, thresholdPercentage, ); let response = `# Cost Anomaly Detection\n\n`; response += `**Billing Account:** ${billingAccountName}\n`; response += `**Project:** ${actualProjectId || "All projects"}\n`; response += `**Lookback Period:** ${lookbackDays} days\n`; response += `**Threshold:** ${thresholdPercentage}%\n`; response += `**Anomalies Found:** ${anomalies.length}\n\n`; response += `⚠️ **Note:** This is a demonstration with mock data. `; response += `For actual anomaly detection, you would need access to historical billing data.\n\n`; if (anomalies.length > 0) { response += `## Detected Anomalies\n\n`; anomalies.forEach((anomaly: CostAnomaly, index: number) => { response += `### ${index + 1}. ${anomaly.anomalyType.toUpperCase()} - ${anomaly.severity.toUpperCase()}\n\n`; response += `**Project:** ${anomaly.projectId}\n`; response += `**Service:** ${anomaly.serviceId}\n`; response += `**Description:** ${anomaly.description}\n`; response += `**Current Cost:** ${formatCurrency(anomaly.currentCost)}\n`; response += `**Expected Cost:** ${formatCurrency(anomaly.expectedCost)}\n`; response += `**Change:** ${anomaly.percentageChange > 0 ? "+" : ""}${anomaly.percentageChange.toFixed(1)}%\n`; response += `**Detected:** ${new Date(anomaly.detectedAt).toLocaleString("en-AU")}\n`; if (anomaly.recommendations && anomaly.recommendations.length > 0) { response += `**Recommendations:**\n`; anomaly.recommendations.forEach((rec) => { response += `- ${rec}\n`; }); } response += "\n"; }); } else { response += formatCostAnomalies(anomalies); } return { content: [ { type: "text", text: response, }, ], }; } catch (error: any) { logger.error(`Error detecting cost anomalies: ${error.message}`); throw new GcpMcpError( `Failed to detect cost anomalies: ${error.message}`, error.code || "UNKNOWN", error.status || 500, ); } },
  • Tool configuration including title, description, and Zod inputSchema defining required billingAccountName, lookbackDays (7-90 days, default 30), thresholdPercentage (10-500%, default 50), and optional projectId.
    title: "Detect Cost Anomalies", description: "Detect unusual cost patterns and spending anomalies in Google Cloud billing data", inputSchema: { billingAccountName: z .string() .describe( "Billing account name (e.g., 'billingAccounts/123456-789ABC-DEF012')", ), lookbackDays: z .number() .min(7) .max(90) .default(30) .describe("Number of days to look back for comparison (7-90)"), thresholdPercentage: z .number() .min(10) .max(500) .default(50) .describe("Percentage threshold for anomaly detection (10-500%)"), projectId: z .string() .optional() .describe("Optional project ID to filter anomalies"), },
  • server.registerTool("gcp-billing-detect-anomalies", ...) call within registerBillingTools function, which is invoked by registerBillingService.
    server.registerTool( "gcp-billing-detect-anomalies", { title: "Detect Cost Anomalies", description: "Detect unusual cost patterns and spending anomalies in Google Cloud billing data", inputSchema: { billingAccountName: z .string() .describe( "Billing account name (e.g., 'billingAccounts/123456-789ABC-DEF012')", ), lookbackDays: z .number() .min(7) .max(90) .default(30) .describe("Number of days to look back for comparison (7-90)"), thresholdPercentage: z .number() .min(10) .max(500) .default(50) .describe("Percentage threshold for anomaly detection (10-500%)"), projectId: z .string() .optional() .describe("Optional project ID to filter anomalies"), }, }, async ({ billingAccountName, lookbackDays, thresholdPercentage, projectId, }) => { try { // Use project hierarchy: provided -> state manager -> auth default const actualProjectId = projectId || stateManager.getCurrentProjectId() || (await getProjectId()); logger.debug( `Detecting cost anomalies for billing account: ${billingAccountName}, project: ${actualProjectId || "all"}`, ); // Mock current and historical cost data for demonstration const currentCosts: CostData[] = [ { billingAccountName, projectId: actualProjectId || "example-project-1", serviceId: "compute.googleapis.com", cost: { amount: 2500, currency: "USD" }, usage: { amount: 200, unit: "hours" }, period: { startTime: new Date( Date.now() - 24 * 60 * 60 * 1000, ).toISOString(), endTime: new Date().toISOString(), }, }, ]; const historicalCosts: CostData[] = [ { billingAccountName, projectId: actualProjectId || "example-project-1", serviceId: "compute.googleapis.com", cost: { amount: 1250, currency: "USD" }, usage: { amount: 100, unit: "hours" }, period: { startTime: new Date( Date.now() - lookbackDays * 24 * 60 * 60 * 1000, ).toISOString(), endTime: new Date( Date.now() - (lookbackDays - 1) * 24 * 60 * 60 * 1000, ).toISOString(), }, }, ]; const anomalies: CostAnomaly[] = detectCostAnomalies( currentCosts, historicalCosts, thresholdPercentage, ); let response = `# Cost Anomaly Detection\n\n`; response += `**Billing Account:** ${billingAccountName}\n`; response += `**Project:** ${actualProjectId || "All projects"}\n`; response += `**Lookback Period:** ${lookbackDays} days\n`; response += `**Threshold:** ${thresholdPercentage}%\n`; response += `**Anomalies Found:** ${anomalies.length}\n\n`; response += `⚠️ **Note:** This is a demonstration with mock data. `; response += `For actual anomaly detection, you would need access to historical billing data.\n\n`; if (anomalies.length > 0) { response += `## Detected Anomalies\n\n`; anomalies.forEach((anomaly: CostAnomaly, index: number) => { response += `### ${index + 1}. ${anomaly.anomalyType.toUpperCase()} - ${anomaly.severity.toUpperCase()}\n\n`; response += `**Project:** ${anomaly.projectId}\n`; response += `**Service:** ${anomaly.serviceId}\n`; response += `**Description:** ${anomaly.description}\n`; response += `**Current Cost:** ${formatCurrency(anomaly.currentCost)}\n`; response += `**Expected Cost:** ${formatCurrency(anomaly.expectedCost)}\n`; response += `**Change:** ${anomaly.percentageChange > 0 ? "+" : ""}${anomaly.percentageChange.toFixed(1)}%\n`; response += `**Detected:** ${new Date(anomaly.detectedAt).toLocaleString("en-AU")}\n`; if (anomaly.recommendations && anomaly.recommendations.length > 0) { response += `**Recommendations:**\n`; anomaly.recommendations.forEach((rec) => { response += `- ${rec}\n`; }); } response += "\n"; }); } else { response += formatCostAnomalies(anomalies); } return { content: [ { type: "text", text: response, }, ], }; } catch (error: any) { logger.error(`Error detecting cost anomalies: ${error.message}`); throw new GcpMcpError( `Failed to detect cost anomalies: ${error.message}`, error.code || "UNKNOWN", error.status || 500, ); } }, );
  • detectCostAnomalies function: matches current vs historical costs by project/service, computes percentage change, flags anomalies if exceeding threshold, determines severity, generates recommendations using internal helper.
    export function detectCostAnomalies( currentCosts: CostData[], historicalCosts: CostData[], thresholdPercentage: number = 50, ): CostAnomaly[] { const anomalies: CostAnomaly[] = []; for (const current of currentCosts) { if (!current.projectId || !current.serviceId) continue; // Find corresponding historical cost const historical = historicalCosts.find( (h) => h.projectId === current.projectId && h.serviceId === current.serviceId, ); if (!historical) continue; const percentageChange = calculatePercentageChange( current.cost.amount, historical.cost.amount, ); if (Math.abs(percentageChange) >= thresholdPercentage) { const anomalyType = percentageChange > 0 ? "spike" : "drop"; const severity = Math.abs(percentageChange) >= 100 ? "critical" : Math.abs(percentageChange) >= 75 ? "high" : Math.abs(percentageChange) >= 50 ? "medium" : "low"; anomalies.push({ projectId: current.projectId, serviceId: current.serviceId, anomalyType, severity, description: `${percentageChange > 0 ? "Significant increase" : "Significant decrease"} in costs for ${current.serviceId}`, currentCost: current.cost.amount, expectedCost: historical.cost.amount, percentageChange, detectedAt: new Date().toISOString(), period: current.period, recommendations: generateAnomalyRecommendations( anomalyType, current.serviceId, Math.abs(percentageChange), ), }); } } return anomalies; }
  • generateAnomalyRecommendations private helper: provides tailored advice for spike/drop anomalies based on service type (e.g., compute, storage).
    function generateAnomalyRecommendations( anomalyType: "spike" | "drop", serviceId: string, percentageChange: number, ): string[] { const recommendations: string[] = []; if (anomalyType === "spike") { recommendations.push( "Review recent resource provisioning and scaling activities", ); recommendations.push( "Check for unexpected traffic spikes or data processing jobs", ); recommendations.push( "Validate that auto-scaling policies are configured correctly", ); if (serviceId.includes("compute")) { recommendations.push( "Consider using committed use discounts for sustained workloads", ); recommendations.push( "Review instance types and consider right-sizing opportunities", ); } if (serviceId.includes("storage")) { recommendations.push( "Review data retention policies and lifecycle management", ); recommendations.push( "Consider using different storage classes for infrequently accessed data", ); } } else { recommendations.push( "Verify that services are functioning correctly despite reduced costs", ); recommendations.push( "Check if resources were intentionally scaled down or removed", ); recommendations.push( "Ensure monitoring and alerting are still functioning properly", ); } return recommendations; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/krzko/google-cloud-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server