Skip to main content
Glama
krzko

Google Cloud MCP Server

by krzko

gcp-billing-detect-anomalies

Identify unexpected spending patterns in Google Cloud billing data by analyzing cost anomalies within a specified timeframe and threshold for enhanced cost management.

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)
projectIdNoOptional project ID to filter anomalies
thresholdPercentageNoPercentage threshold for anomaly detection (10-500%)

Implementation Reference

  • Inline async handler function for the 'gcp-billing-detect-anomalies' tool. Generates mock current and historical cost data, calls detectCostAnomalies helper to identify anomalies exceeding the threshold, builds a formatted Markdown response with anomaly details, and handles errors.
    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, ); } },
  • Zod input schema definition for the tool, specifying parameters for billing account, lookback period, anomaly threshold, and optional project filter.
    { 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"), },
  • MCP server registration of the 'gcp-billing-detect-anomalies' tool within the registerBillingTools function.
    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, ); } }, );
  • Key helper function that performs the actual anomaly detection by comparing current costs against historical data using percentage change threshold, classifying anomalies by type and severity, and generating recommendations.
    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; }
  • Helper function used by the handler to format detected anomalies into a user-friendly Markdown response.
    export function formatCostAnomalies(anomalies: CostAnomaly[]): string { if (anomalies.length === 0) { return "✅ No cost anomalies detected for the specified period."; } let result = "## 🚨 Cost Anomalies Detected\n\n"; for (const anomaly of anomalies) { const severityIcon = { low: "🟡", medium: "🟠", high: "🔴", critical: "💥", }[anomaly.severity]; const changeDirection = anomaly.percentageChange > 0 ? "📈" : "📉"; result += `### ${severityIcon} ${anomaly.anomalyType.replace("_", " ").toUpperCase()}\n\n`; result += `**Project:** ${anomaly.projectId}\n`; result += `**Service:** ${anomaly.serviceId}\n`; result += `**Description:** ${anomaly.description}\n`; result += `**Change:** ${changeDirection} ${Math.abs(anomaly.percentageChange).toFixed(1)}%\n`; result += `**Current Cost:** ${formatCurrency(anomaly.currentCost)}\n`; result += `**Expected Cost:** ${formatCurrency(anomaly.expectedCost)}\n`; result += `**Detected:** ${new Date(anomaly.detectedAt).toLocaleString("en-AU")}\n`; if (anomaly.recommendations && anomaly.recommendations.length > 0) { result += "\n**Recommendations:**\n"; for (const rec of anomaly.recommendations) { result += `- ${rec}\n`; } } result += "\n---\n\n"; } return result;

Other Tools

Related Tools

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