Skip to main content
Glama
krzko

Google Cloud MCP Server

by krzko

Detect Cost Anomalies

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;
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. While it mentions detecting anomalies, it doesn't describe what the tool actually returns (e.g., a list of anomalies, a report, or just a flag), whether it performs read-only operations, what permissions are required, or any rate limits. This leaves significant gaps for an AI agent to understand the tool's behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that directly states the tool's purpose without any fluff. It's appropriately sized and front-loaded, making it easy for an AI agent to parse quickly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity of anomaly detection, lack of annotations, and no output schema, the description is incomplete. It doesn't explain what constitutes an 'anomaly', how results are formatted, or any behavioral aspects like error handling. For a tool with 4 parameters and no structured output definition, more context is needed to guide effective usage.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema description coverage is 100%, so the schema already documents all four parameters thoroughly with descriptions, constraints, and defaults. The description doesn't add any additional meaning beyond what's in the schema, such as explaining how parameters interact or providing examples. This meets the baseline for high schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Detect unusual cost patterns and spending anomalies in Google Cloud billing data.' It specifies the verb ('detect') and resource ('cost patterns and spending anomalies'), but doesn't explicitly differentiate it from sibling tools like 'gcp-billing-analyse-costs' or 'gcp-billing-cost-recommendations' which might have overlapping functionality.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. With siblings like 'gcp-billing-analyse-costs' and 'gcp-billing-cost-recommendations' that might handle similar billing analysis, there's no indication of when this anomaly detection tool is preferred or what prerequisites might be needed.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other 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