gcp-billing-list-skus
Retrieve pricing details and SKUs for Google Cloud services to analyze costs and plan budgets. Supports pagination and multiple currencies.
Instructions
List all SKUs (Stock Keeping Units) and pricing information for a Google Cloud service
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| serviceId | Yes | Google Cloud service ID (e.g., 'services/6F81-5844-456A') | |
| pageSize | No | Maximum number of SKUs to return (1-100) | |
| pageToken | No | Token for pagination to get next page of results | |
| currencyCode | No | Currency code for pricing information (default: USD) | USD |
Implementation Reference
- src/services/billing/tools.ts:510-644 (handler)Handler function that lists SKUs for a specified Google Cloud service using the Cloud Billing Catalog API. Fetches SKUs, formats them with pricing info, categories, regions, and returns a markdown response with pagination support.async ({ serviceId, pageSize, pageToken, currencyCode }) => { try { const catalogClient = getCatalogClient(); logger.debug(`Listing SKUs for service: ${serviceId}`); const request: any = { parent: serviceId, pageSize, currencyCode, }; if (pageToken) { request.pageToken = pageToken; } const [skus, nextPageToken] = await catalogClient.listSkus(request); if (!skus || skus.length === 0) { return { content: [ { type: "text", text: `No SKUs found for service: ${serviceId}`, }, ], }; } let response = `# SKUs for Service\n\n`; response += `**Service:** ${serviceId}\n`; response += `**Currency:** ${currencyCode}\n`; response += `**SKUs Found:** ${skus.length}\n\n`; for (const skuData of skus) { const sku: SKU = { name: skuData.name || "", skuId: skuData.skuId || "Unknown", description: skuData.description || "Unknown SKU", category: { serviceDisplayName: skuData.category?.serviceDisplayName || "Unknown", resourceFamily: skuData.category?.resourceFamily || "Unknown", resourceGroup: skuData.category?.resourceGroup || "Unknown", usageType: skuData.category?.usageType || "Unknown", }, serviceRegions: skuData.serviceRegions || [], pricingInfo: (skuData.pricingInfo || []).map((pi) => ({ summary: pi.summary || "", pricingExpression: { usageUnit: pi.pricingExpression?.usageUnit || "", usageUnitDescription: pi.pricingExpression?.usageUnitDescription || "", baseUnit: pi.pricingExpression?.baseUnit || "", baseUnitDescription: pi.pricingExpression?.baseUnitDescription || "", baseUnitConversionFactor: pi.pricingExpression?.baseUnitConversionFactor || 0, displayQuantity: pi.pricingExpression?.displayQuantity || 0, tieredRates: (pi.pricingExpression?.tieredRates || []).map( (tr) => ({ startUsageAmount: tr.startUsageAmount || 0, unitPrice: { currencyCode: tr.unitPrice?.currencyCode || "USD", units: String(tr.unitPrice?.units || "0"), nanos: tr.unitPrice?.nanos || 0, }, }), ), }, currencyConversionRate: pi.currencyConversionRate || 1, effectiveTime: typeof pi.effectiveTime === "string" ? pi.effectiveTime : pi.effectiveTime ? new Date().toISOString() : "", })), serviceProviderName: skuData.serviceProviderName || "Unknown", geoTaxonomy: skuData.geoTaxonomy ? { type: String(skuData.geoTaxonomy.type || "Unknown"), regions: skuData.geoTaxonomy.regions || [], } : undefined, }; response += `## ${sku.description}\n\n`; response += `**SKU ID:** ${sku.skuId}\n`; response += `**Service Provider:** ${sku.serviceProviderName}\n`; response += `**Category:**\n`; response += `- Service: ${sku.category.serviceDisplayName}\n`; response += `- Resource Family: ${sku.category.resourceFamily}\n`; response += `- Resource Group: ${sku.category.resourceGroup}\n`; response += `- Usage Type: ${sku.category.usageType}\n`; if (sku.serviceRegions.length > 0) { response += `**Regions:** ${sku.serviceRegions.join(", ")}\n`; } if (sku.geoTaxonomy) { response += `**Geographic Taxonomy:** ${sku.geoTaxonomy.type}\n`; if (sku.geoTaxonomy.regions.length > 0) { response += `**Geo Regions:** ${sku.geoTaxonomy.regions.join(", ")}\n`; } } if (sku.pricingInfo.length > 0) { response += `**Pricing Information:** ${sku.pricingInfo.length} pricing tier(s) available\n`; } response += "\n---\n\n"; } if (nextPageToken) { response += `**Next Page Token:** ${nextPageToken}\n`; response += `Use this token with the same tool to get the next page of results.\n`; } return { content: [ { type: "text", text: response, }, ], }; } catch (error: any) { logger.error(`Error listing SKUs: ${error.message}`); throw new GcpMcpError( `Failed to list SKUs: ${error.message}`, error.code || "UNKNOWN", error.status || 500, ); }
- Input schema definition using Zod for the gcp-billing-list-skus tool, specifying parameters like serviceId, pageSize, pageToken, and currencyCode.{ title: "List Service SKUs", description: "List all SKUs (Stock Keeping Units) and pricing information for a Google Cloud service", inputSchema: { serviceId: z .string() .describe( "Google Cloud service ID (e.g., 'services/6F81-5844-456A')", ), pageSize: z .number() .min(1) .max(100) .default(20) .describe("Maximum number of SKUs to return (1-100)"), pageToken: z .string() .optional() .describe("Token for pagination to get next page of results"), currencyCode: z .string() .default("USD") .describe("Currency code for pricing information (default: USD)"), },
- src/services/billing/tools.ts:482-646 (registration)Registration of the gcp-billing-list-skus tool on the MCP server, including name, schema, and handler reference.server.registerTool( "gcp-billing-list-skus", { title: "List Service SKUs", description: "List all SKUs (Stock Keeping Units) and pricing information for a Google Cloud service", inputSchema: { serviceId: z .string() .describe( "Google Cloud service ID (e.g., 'services/6F81-5844-456A')", ), pageSize: z .number() .min(1) .max(100) .default(20) .describe("Maximum number of SKUs to return (1-100)"), pageToken: z .string() .optional() .describe("Token for pagination to get next page of results"), currencyCode: z .string() .default("USD") .describe("Currency code for pricing information (default: USD)"), }, }, async ({ serviceId, pageSize, pageToken, currencyCode }) => { try { const catalogClient = getCatalogClient(); logger.debug(`Listing SKUs for service: ${serviceId}`); const request: any = { parent: serviceId, pageSize, currencyCode, }; if (pageToken) { request.pageToken = pageToken; } const [skus, nextPageToken] = await catalogClient.listSkus(request); if (!skus || skus.length === 0) { return { content: [ { type: "text", text: `No SKUs found for service: ${serviceId}`, }, ], }; } let response = `# SKUs for Service\n\n`; response += `**Service:** ${serviceId}\n`; response += `**Currency:** ${currencyCode}\n`; response += `**SKUs Found:** ${skus.length}\n\n`; for (const skuData of skus) { const sku: SKU = { name: skuData.name || "", skuId: skuData.skuId || "Unknown", description: skuData.description || "Unknown SKU", category: { serviceDisplayName: skuData.category?.serviceDisplayName || "Unknown", resourceFamily: skuData.category?.resourceFamily || "Unknown", resourceGroup: skuData.category?.resourceGroup || "Unknown", usageType: skuData.category?.usageType || "Unknown", }, serviceRegions: skuData.serviceRegions || [], pricingInfo: (skuData.pricingInfo || []).map((pi) => ({ summary: pi.summary || "", pricingExpression: { usageUnit: pi.pricingExpression?.usageUnit || "", usageUnitDescription: pi.pricingExpression?.usageUnitDescription || "", baseUnit: pi.pricingExpression?.baseUnit || "", baseUnitDescription: pi.pricingExpression?.baseUnitDescription || "", baseUnitConversionFactor: pi.pricingExpression?.baseUnitConversionFactor || 0, displayQuantity: pi.pricingExpression?.displayQuantity || 0, tieredRates: (pi.pricingExpression?.tieredRates || []).map( (tr) => ({ startUsageAmount: tr.startUsageAmount || 0, unitPrice: { currencyCode: tr.unitPrice?.currencyCode || "USD", units: String(tr.unitPrice?.units || "0"), nanos: tr.unitPrice?.nanos || 0, }, }), ), }, currencyConversionRate: pi.currencyConversionRate || 1, effectiveTime: typeof pi.effectiveTime === "string" ? pi.effectiveTime : pi.effectiveTime ? new Date().toISOString() : "", })), serviceProviderName: skuData.serviceProviderName || "Unknown", geoTaxonomy: skuData.geoTaxonomy ? { type: String(skuData.geoTaxonomy.type || "Unknown"), regions: skuData.geoTaxonomy.regions || [], } : undefined, }; response += `## ${sku.description}\n\n`; response += `**SKU ID:** ${sku.skuId}\n`; response += `**Service Provider:** ${sku.serviceProviderName}\n`; response += `**Category:**\n`; response += `- Service: ${sku.category.serviceDisplayName}\n`; response += `- Resource Family: ${sku.category.resourceFamily}\n`; response += `- Resource Group: ${sku.category.resourceGroup}\n`; response += `- Usage Type: ${sku.category.usageType}\n`; if (sku.serviceRegions.length > 0) { response += `**Regions:** ${sku.serviceRegions.join(", ")}\n`; } if (sku.geoTaxonomy) { response += `**Geographic Taxonomy:** ${sku.geoTaxonomy.type}\n`; if (sku.geoTaxonomy.regions.length > 0) { response += `**Geo Regions:** ${sku.geoTaxonomy.regions.join(", ")}\n`; } } if (sku.pricingInfo.length > 0) { response += `**Pricing Information:** ${sku.pricingInfo.length} pricing tier(s) available\n`; } response += "\n---\n\n"; } if (nextPageToken) { response += `**Next Page Token:** ${nextPageToken}\n`; response += `Use this token with the same tool to get the next page of results.\n`; } return { content: [ { type: "text", text: response, }, ], }; } catch (error: any) { logger.error(`Error listing SKUs: ${error.message}`); throw new GcpMcpError( `Failed to list SKUs: ${error.message}`, error.code || "UNKNOWN", error.status || 500, ); } }, );