Skip to main content
Glama
krzko

Google Cloud MCP Server

by krzko

gcp-billing-service-breakdown

Analyze Google Cloud costs by service, usage, and SKU for a specified billing account. Filter by project ID and time range to gain detailed expense insights.

Instructions

Get detailed cost breakdown by Google Cloud service with usage and SKU information

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
billingAccountNameYesBilling account name (e.g., 'billingAccounts/123456-789ABC-DEF012')
projectIdNoOptional project ID to filter costs
timeRangeNoTime range for analysis (7d, 30d, 90d, 1y)30d

Implementation Reference

  • Complete registration of the 'gcp-billing-service-breakdown' MCP tool, including input schema validation with Zod, title, description, and the full async handler function that generates mock detailed service cost breakdowns grouped by service with SKU, usage, labels, and summary statistics in markdown format.
    "gcp-billing-service-breakdown", { title: "Get Service Cost Breakdown", description: "Get detailed cost breakdown by Google Cloud service with usage and SKU information", inputSchema: { billingAccountName: z .string() .describe( "Billing account name (e.g., 'billingAccounts/123456-789ABC-DEF012')", ), projectId: z .string() .optional() .describe("Optional project ID to filter costs"), timeRange: z .enum(["7d", "30d", "90d", "1y"]) .default("30d") .describe("Time range for analysis (7d, 30d, 90d, 1y)"), }, }, async ({ billingAccountName, projectId, timeRange }) => { try { // Use project hierarchy: provided -> state manager -> auth default const actualProjectId = projectId || stateManager.getCurrentProjectId() || (await getProjectId()); logger.debug( `Getting service breakdown for billing account: ${billingAccountName}, project: ${actualProjectId || "all"}, range: ${timeRange}`, ); // Mock detailed service cost data const serviceBreakdown: CostData[] = [ { billingAccountName, projectId: actualProjectId || "production-project", serviceId: "compute.googleapis.com", skuId: "services/6F81-5844-456A/skus/CP-COMPUTEENGINE-VMIMAGE-N1-STANDARD-1", cost: { amount: 1847.25, currency: "USD" }, usage: { amount: 744, unit: "hours" }, period: { startTime: new Date( Date.now() - parseInt(timeRange.slice(0, -1)) * 24 * 60 * 60 * 1000, ).toISOString(), endTime: new Date().toISOString(), }, labels: { environment: "production", team: "backend", instance_type: "n1-standard-1", zone: "us-central1-a", }, }, { billingAccountName, projectId: actualProjectId || "production-project", serviceId: "storage.googleapis.com", skuId: "services/95FF-2EF5-5EA1/skus/9E26-D0CA-7C08", cost: { amount: 426.8, currency: "USD" }, usage: { amount: 2500, unit: "GB" }, period: { startTime: new Date( Date.now() - parseInt(timeRange.slice(0, -1)) * 24 * 60 * 60 * 1000, ).toISOString(), endTime: new Date().toISOString(), }, labels: { environment: "production", team: "data", storage_class: "standard", location: "us-central1", }, }, { billingAccountName, projectId: actualProjectId || "development-project", serviceId: "bigquery.googleapis.com", skuId: "services/24E6-581D-38E5/skus/1145-49C5-8000", cost: { amount: 189.45, currency: "USD" }, usage: { amount: 1250, unit: "TB" }, period: { startTime: new Date( Date.now() - parseInt(timeRange.slice(0, -1)) * 24 * 60 * 60 * 1000, ).toISOString(), endTime: new Date().toISOString(), }, labels: { environment: "development", team: "analytics", query_type: "on_demand", }, }, ]; let response = `# Service Cost Breakdown\n\n`; response += `**Billing Account:** ${billingAccountName}\n`; response += `**Project:** ${actualProjectId || "All projects"}\n`; response += `**Time Range:** ${timeRange}\n`; response += `**Services Analysed:** ${new Set(serviceBreakdown.map((c) => c.serviceId)).size}\n\n`; response += `⚠️ **Note:** This is demonstration data with detailed service and SKU breakdown.\n\n`; // Group by service for detailed analysis const serviceMap = new Map<string, CostData[]>(); serviceBreakdown.forEach((costData: CostData) => { if (!serviceMap.has(costData.serviceId!)) { serviceMap.set(costData.serviceId!, []); } serviceMap.get(costData.serviceId!)!.push(costData); }); // Detailed breakdown by service for (const [serviceId, costs] of serviceMap) { const serviceName = serviceId .replace(".googleapis.com", "") .toUpperCase(); const serviceTotal = costs.reduce( (sum, cost) => sum + cost.cost.amount, 0, ); response += `## ${serviceName}\n\n`; response += `**Service ID:** ${serviceId}\n`; response += `**Total Cost:** ${formatCurrency(serviceTotal)}\n`; response += `**SKUs:** ${costs.length}\n\n`; response += "| Project | SKU ID | Cost | Usage | Labels |\n"; response += "|---------|--------|------|-------|--------|\n"; costs.forEach((costData: CostData) => { const project = costData.projectId || "Unknown"; const skuId = costData.skuId ? costData.skuId.split("/").pop() : "Unknown"; const cost = formatCurrency(costData.cost.amount); const usage = `${costData.usage.amount} ${costData.usage.unit}`; const labels = costData.labels ? Object.entries(costData.labels) .map(([k, v]) => `${k}:${v}`) .join(", ") : "None"; response += `| ${project} | ${skuId} | ${cost} | ${usage} | ${labels} |\n`; }); response += "\n"; } // Summary const totalCost = serviceBreakdown.reduce( (sum, cost) => sum + cost.cost.amount, 0, ); response += `## Summary\n\n`; response += `**Total Cost:** ${formatCurrency(totalCost)}\n`; response += `**Average Cost per Service:** ${formatCurrency(totalCost / serviceMap.size)}\n`; // Top cost drivers const sortedServices = Array.from(serviceMap.entries()) .map(([serviceId, costs]) => ({ serviceId, total: costs.reduce((sum, cost) => sum + cost.cost.amount, 0), })) .sort((a, b) => b.total - a.total); response += `**Top Cost Driver:** ${sortedServices[0].serviceId} (${formatCurrency(sortedServices[0].total)})\n`; return { content: [ { type: "text", text: response, }, ], }; } catch (error: any) { logger.error(`Error getting service breakdown: ${error.message}`); throw new GcpMcpError( `Failed to get service breakdown: ${error.message}`, error.code || "UNKNOWN", error.status || 500, ); } }, );
  • The core handler logic for executing the tool. Uses mock data to simulate GCP billing service breakdown, processes into per-service tables showing projects, SKUs, costs, usage, and labels. Generates comprehensive markdown report with summaries and top cost drivers.
    async ({ billingAccountName, projectId, timeRange }) => { try { // Use project hierarchy: provided -> state manager -> auth default const actualProjectId = projectId || stateManager.getCurrentProjectId() || (await getProjectId()); logger.debug( `Getting service breakdown for billing account: ${billingAccountName}, project: ${actualProjectId || "all"}, range: ${timeRange}`, ); // Mock detailed service cost data const serviceBreakdown: CostData[] = [ { billingAccountName, projectId: actualProjectId || "production-project", serviceId: "compute.googleapis.com", skuId: "services/6F81-5844-456A/skus/CP-COMPUTEENGINE-VMIMAGE-N1-STANDARD-1", cost: { amount: 1847.25, currency: "USD" }, usage: { amount: 744, unit: "hours" }, period: { startTime: new Date( Date.now() - parseInt(timeRange.slice(0, -1)) * 24 * 60 * 60 * 1000, ).toISOString(), endTime: new Date().toISOString(), }, labels: { environment: "production", team: "backend", instance_type: "n1-standard-1", zone: "us-central1-a", }, }, { billingAccountName, projectId: actualProjectId || "production-project", serviceId: "storage.googleapis.com", skuId: "services/95FF-2EF5-5EA1/skus/9E26-D0CA-7C08", cost: { amount: 426.8, currency: "USD" }, usage: { amount: 2500, unit: "GB" }, period: { startTime: new Date( Date.now() - parseInt(timeRange.slice(0, -1)) * 24 * 60 * 60 * 1000, ).toISOString(), endTime: new Date().toISOString(), }, labels: { environment: "production", team: "data", storage_class: "standard", location: "us-central1", }, }, { billingAccountName, projectId: actualProjectId || "development-project", serviceId: "bigquery.googleapis.com", skuId: "services/24E6-581D-38E5/skus/1145-49C5-8000", cost: { amount: 189.45, currency: "USD" }, usage: { amount: 1250, unit: "TB" }, period: { startTime: new Date( Date.now() - parseInt(timeRange.slice(0, -1)) * 24 * 60 * 60 * 1000, ).toISOString(), endTime: new Date().toISOString(), }, labels: { environment: "development", team: "analytics", query_type: "on_demand", }, }, ]; let response = `# Service Cost Breakdown\n\n`; response += `**Billing Account:** ${billingAccountName}\n`; response += `**Project:** ${actualProjectId || "All projects"}\n`; response += `**Time Range:** ${timeRange}\n`; response += `**Services Analysed:** ${new Set(serviceBreakdown.map((c) => c.serviceId)).size}\n\n`; response += `⚠️ **Note:** This is demonstration data with detailed service and SKU breakdown.\n\n`; // Group by service for detailed analysis const serviceMap = new Map<string, CostData[]>(); serviceBreakdown.forEach((costData: CostData) => { if (!serviceMap.has(costData.serviceId!)) { serviceMap.set(costData.serviceId!, []); } serviceMap.get(costData.serviceId!)!.push(costData); }); // Detailed breakdown by service for (const [serviceId, costs] of serviceMap) { const serviceName = serviceId .replace(".googleapis.com", "") .toUpperCase(); const serviceTotal = costs.reduce( (sum, cost) => sum + cost.cost.amount, 0, ); response += `## ${serviceName}\n\n`; response += `**Service ID:** ${serviceId}\n`; response += `**Total Cost:** ${formatCurrency(serviceTotal)}\n`; response += `**SKUs:** ${costs.length}\n\n`; response += "| Project | SKU ID | Cost | Usage | Labels |\n"; response += "|---------|--------|------|-------|--------|\n"; costs.forEach((costData: CostData) => { const project = costData.projectId || "Unknown"; const skuId = costData.skuId ? costData.skuId.split("/").pop() : "Unknown"; const cost = formatCurrency(costData.cost.amount); const usage = `${costData.usage.amount} ${costData.usage.unit}`; const labels = costData.labels ? Object.entries(costData.labels) .map(([k, v]) => `${k}:${v}`) .join(", ") : "None"; response += `| ${project} | ${skuId} | ${cost} | ${usage} | ${labels} |\n`; }); response += "\n"; } // Summary const totalCost = serviceBreakdown.reduce( (sum, cost) => sum + cost.cost.amount, 0, ); response += `## Summary\n\n`; response += `**Total Cost:** ${formatCurrency(totalCost)}\n`; response += `**Average Cost per Service:** ${formatCurrency(totalCost / serviceMap.size)}\n`; // Top cost drivers const sortedServices = Array.from(serviceMap.entries()) .map(([serviceId, costs]) => ({ serviceId, total: costs.reduce((sum, cost) => sum + cost.cost.amount, 0), })) .sort((a, b) => b.total - a.total); response += `**Top Cost Driver:** ${sortedServices[0].serviceId} (${formatCurrency(sortedServices[0].total)})\n`; return { content: [ { type: "text", text: response, }, ], }; } catch (error: any) { logger.error(`Error getting service breakdown: ${error.message}`); throw new GcpMcpError( `Failed to get service breakdown: ${error.message}`, error.code || "UNKNOWN", error.status || 500, ); } },
  • Zod input schema defining parameters: required billingAccountName (string), optional projectId (string), timeRange (enum ["7d","30d","90d","1y"], default "30d").
    billingAccountName: z .string() .describe( "Billing account name (e.g., 'billingAccounts/123456-789ABC-DEF012')", ), projectId: z .string() .optional() .describe("Optional project ID to filter costs"), timeRange: z .enum(["7d", "30d", "90d", "1y"]) .default("30d") .describe("Time range for analysis (7d, 30d, 90d, 1y)"), },
  • formatCurrency utility function imported and used multiple times in the handler to format cost amounts (e.g., serviceTotal, totalCost) for display in the markdown response tables and summaries.
    export function formatCurrency( amount: number, currency: string = "USD", ): string { return new Intl.NumberFormat("en-AU", { style: "currency", currency: currency, minimumFractionDigits: 2, maximumFractionDigits: 4, }).format(amount); }

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