Skip to main content
Glama
RadiumGu

GCP Billing and Monitoring MCP Server

by RadiumGu

gcp-error-reporting-list-groups

List and analyze error groups from Google Cloud Error Reporting to identify and troubleshoot application issues in your GCP environment.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • Registration of the 'gcp-error-reporting-list-groups' tool using server.tool(), including title, description, input schema, and inline handler function.
    server.tool( "gcp-error-reporting-list-groups", { title: "List Error Groups", description: "List error groups from Google Cloud Error Reporting with optional filtering and time range", inputSchema: { timeRange: z .string() .optional() .default("1h") .describe('Time range to query: "1h", "6h", "24h"/"1d", "7d", "30d"'), serviceFilter: z.string().optional().describe("Filter by service name"), order: z .enum([ "COUNT_DESC", "LAST_SEEN_DESC", "CREATED_DESC", "AFFECTED_USERS_DESC", ]) .optional() .default("COUNT_DESC") .describe("Sort order for error groups"), pageSize: z .number() .min(1) .max(100) .default(20) .describe("Maximum number of error groups to return"), }, }, async ({ timeRange, serviceFilter, order, pageSize }) => { try { const projectId = await getProjectId(); // Initialize Google Auth client (same pattern as trace service) const auth = await initGoogleAuth(true); if (!auth) { throw new GcpMcpError( "Google Cloud authentication not available. Please configure authentication to access error reporting data.", "UNAUTHENTICATED", 401, ); } const client = await auth.getClient(); const token = await client.getAccessToken(); // Parse time range - ensure we have a valid timeRange value const actualTimeRange = timeRange || "1h"; const actualOrder = order || "COUNT_DESC"; const actualPageSize = pageSize || 20; // Map time range to Google Cloud Error Reporting periods let period: string; switch (actualTimeRange) { case "1h": period = "PERIOD_1_HOUR"; break; case "6h": period = "PERIOD_6_HOURS"; break; case "24h": case "1d": period = "PERIOD_1_DAY"; break; case "7d": period = "PERIOD_1_WEEK"; break; case "30d": period = "PERIOD_30_DAYS"; break; default: // Default to 1 hour for any other time range period = "PERIOD_1_HOUR"; break; } // Build query parameters const params = new URLSearchParams({ "timeRange.period": period, order: actualOrder, pageSize: actualPageSize.toString(), }); // Add service filter if provided if (serviceFilter) { params.set("serviceFilter.service", serviceFilter); } // Make REST API call using same fetch approach as trace service const apiUrl = `https://clouderrorreporting.googleapis.com/v1beta1/projects/${projectId}/groupStats?${params}`; const response = await fetch(apiUrl, { method: "GET", headers: { Authorization: `Bearer ${token.token}`, Accept: "application/json", }, }); if (!response.ok) { const errorText = await response.text(); throw new GcpMcpError( `Failed to fetch error group stats: ${errorText}`, "FAILED_PRECONDITION", response.status, ); } const data = await response.json(); const errorGroupStats = data.errorGroupStats || []; if (!errorGroupStats || errorGroupStats.length === 0) { return { content: [ { type: "text", text: `# Error Groups\n\nProject: ${projectId}\nTime Range: ${actualTimeRange}\n${serviceFilter ? `Service Filter: ${serviceFilter}\n` : ""}\nNo error groups found.`, }, ], }; } // errorGroupStats should already match our ErrorGroupStats interface const errorSummaries: ErrorGroupStats[] = errorGroupStats; // Generate analysis and recommendations const analysis = analyseErrorPatternsAndSuggestRemediation(errorSummaries); let content = `# Error Groups Analysis\n\nProject: ${projectId}\nTime Range: ${actualTimeRange}\n${serviceFilter ? `Service Filter: ${serviceFilter}\n` : ""}\n\n${analysis}\n\n`; content += `## Detailed Error Groups\n\n`; errorSummaries.forEach((errorSummary, index) => { content += `### ${index + 1}. ${formatErrorGroupSummary(errorSummary)}\n`; }); return { content: [ { type: "text", text: content, }, ], }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; throw new GcpMcpError( `Failed to list error groups: ${errorMessage}`, "INTERNAL_ERROR", 500, ); } }, );
  • Inline async handler that authenticates, queries GCP Error Reporting API for group stats, analyzes patterns, formats summaries, and returns structured markdown response.
    async ({ timeRange, serviceFilter, order, pageSize }) => { try { const projectId = await getProjectId(); // Initialize Google Auth client (same pattern as trace service) const auth = await initGoogleAuth(true); if (!auth) { throw new GcpMcpError( "Google Cloud authentication not available. Please configure authentication to access error reporting data.", "UNAUTHENTICATED", 401, ); } const client = await auth.getClient(); const token = await client.getAccessToken(); // Parse time range - ensure we have a valid timeRange value const actualTimeRange = timeRange || "1h"; const actualOrder = order || "COUNT_DESC"; const actualPageSize = pageSize || 20; // Map time range to Google Cloud Error Reporting periods let period: string; switch (actualTimeRange) { case "1h": period = "PERIOD_1_HOUR"; break; case "6h": period = "PERIOD_6_HOURS"; break; case "24h": case "1d": period = "PERIOD_1_DAY"; break; case "7d": period = "PERIOD_1_WEEK"; break; case "30d": period = "PERIOD_30_DAYS"; break; default: // Default to 1 hour for any other time range period = "PERIOD_1_HOUR"; break; } // Build query parameters const params = new URLSearchParams({ "timeRange.period": period, order: actualOrder, pageSize: actualPageSize.toString(), }); // Add service filter if provided if (serviceFilter) { params.set("serviceFilter.service", serviceFilter); } // Make REST API call using same fetch approach as trace service const apiUrl = `https://clouderrorreporting.googleapis.com/v1beta1/projects/${projectId}/groupStats?${params}`; const response = await fetch(apiUrl, { method: "GET", headers: { Authorization: `Bearer ${token.token}`, Accept: "application/json", }, }); if (!response.ok) { const errorText = await response.text(); throw new GcpMcpError( `Failed to fetch error group stats: ${errorText}`, "FAILED_PRECONDITION", response.status, ); } const data = await response.json(); const errorGroupStats = data.errorGroupStats || []; if (!errorGroupStats || errorGroupStats.length === 0) { return { content: [ { type: "text", text: `# Error Groups\n\nProject: ${projectId}\nTime Range: ${actualTimeRange}\n${serviceFilter ? `Service Filter: ${serviceFilter}\n` : ""}\nNo error groups found.`, }, ], }; } // errorGroupStats should already match our ErrorGroupStats interface const errorSummaries: ErrorGroupStats[] = errorGroupStats; // Generate analysis and recommendations const analysis = analyseErrorPatternsAndSuggestRemediation(errorSummaries); let content = `# Error Groups Analysis\n\nProject: ${projectId}\nTime Range: ${actualTimeRange}\n${serviceFilter ? `Service Filter: ${serviceFilter}\n` : ""}\n\n${analysis}\n\n`; content += `## Detailed Error Groups\n\n`; errorSummaries.forEach((errorSummary, index) => { content += `### ${index + 1}. ${formatErrorGroupSummary(errorSummary)}\n`; }); return { content: [ { type: "text", text: content, }, ], }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; throw new GcpMcpError( `Failed to list error groups: ${errorMessage}`, "INTERNAL_ERROR", 500, ); } },
  • Zod-based input schema validating tool parameters: timeRange, serviceFilter, order, and pageSize.
    inputSchema: { timeRange: z .string() .optional() .default("1h") .describe('Time range to query: "1h", "6h", "24h"/"1d", "7d", "30d"'), serviceFilter: z.string().optional().describe("Filter by service name"), order: z .enum([ "COUNT_DESC", "LAST_SEEN_DESC", "CREATED_DESC", "AFFECTED_USERS_DESC", ]) .optional() .default("COUNT_DESC") .describe("Sort order for error groups"), pageSize: z .number() .min(1) .max(100) .default(20) .describe("Maximum number of error groups to return"), },
  • Helper function to format individual error group stats into detailed markdown summary, used in handler output generation.
    export function formatErrorGroupSummary(errorGroup: ErrorGroupStats): string { const { group, count, affectedUsersCount, firstSeenTime, lastSeenTime, representative, } = errorGroup; let markdown = `## Error Group: ${group?.groupId || "Unknown"}\n\n`; // Error summary markdown += `**Message:** ${representative?.message || "No message"}\n\n`; markdown += `**Service:** ${representative?.serviceContext?.service || "Unknown"}`; if (representative?.serviceContext?.version) { markdown += ` (v${representative.serviceContext.version})`; } markdown += `\n\n`; // Statistics markdown += `**Statistics:**\n`; markdown += `- Total occurrences: ${count}\n`; markdown += `- Affected users: ${affectedUsersCount}\n`; markdown += `- First seen: ${new Date(firstSeenTime).toLocaleString()}\n`; markdown += `- Last seen: ${new Date(lastSeenTime).toLocaleString()}\n\n`; // Error context if (representative?.context) { if (representative.context?.httpRequest) { const req = representative.context.httpRequest; markdown += `**HTTP Request Context:**\n`; if (req.method && req.url) { markdown += `- ${req.method} ${req.url}\n`; } if (req.responseStatusCode) { markdown += `- Response status: ${req.responseStatusCode}\n`; } if (req.userAgent) { markdown += `- User agent: ${req.userAgent}\n`; } markdown += `\n`; } if (representative.context?.reportLocation) { const loc = representative.context.reportLocation; markdown += `**Source Location:**\n`; if (loc.filePath) { markdown += `- File: ${loc.filePath}`; if (loc.lineNumber) { markdown += `:${loc.lineNumber}`; } markdown += `\n`; } if (loc.functionName) { markdown += `- Function: ${loc.functionName}\n`; } markdown += `\n`; } } // Resolution status if (group?.resolutionStatus) { markdown += `**Resolution Status:** ${group.resolutionStatus}\n\n`; } // Tracking issues if (group?.trackingIssues && group.trackingIssues.length > 0) { markdown += `**Tracking Issues:**\n`; group.trackingIssues.forEach((issue) => { markdown += `- ${issue.url}\n`; }); markdown += `\n`; } return markdown; }
  • Helper function that performs pattern analysis on error groups, generates summary statistics, top errors list with remediation, service breakdown, and returns markdown analysis used by the handler.
    export function analyseErrorPatternsAndSuggestRemediation( errorGroups: ErrorGroupStats[], ): string { if (errorGroups.length === 0) { return "No errors found in the specified time range."; } let analysis = `# Error Analysis and Remediation Suggestions\n\n`; analysis += `**Total Error Groups:** ${errorGroups.length}\n\n`; // Calculate total errors and affected users const totalErrors = errorGroups.reduce( (sum, group) => sum + parseInt(group.count), 0, ); const totalAffectedUsers = errorGroups.reduce( (sum, group) => sum + parseInt(group.affectedUsersCount), 0, ); analysis += `**Summary:**\n`; analysis += `- Total errors: ${totalErrors.toLocaleString()}\n`; analysis += `- Total affected users: ${totalAffectedUsers.toLocaleString()}\n`; analysis += `- Error groups: ${errorGroups.length}\n\n`; // Sort by error count (most frequent first) const sortedErrors = [...errorGroups].sort( (a, b) => parseInt(b.count) - parseInt(a.count), ); analysis += `## Top Error Groups by Frequency\n\n`; // Analyse top 5 errors sortedErrors.slice(0, 5).forEach((errorGroup, index) => { analysis += `### ${index + 1}. ${errorGroup.representative?.serviceContext?.service || "Unknown Service"}\n\n`; analysis += `**Error:** ${errorGroup.representative?.message || "No message"}\n\n`; analysis += `**Impact:** ${errorGroup.count} occurrences, ${errorGroup.affectedUsersCount} affected users\n\n`; // Suggest remediation based on error patterns const remediation = suggestRemediation(errorGroup); if (remediation) { analysis += `**Suggested Remediation:**\n${remediation}\n\n`; } analysis += `---\n\n`; }); // Pattern analysis analysis += `## Pattern Analysis\n\n`; // Group by service const serviceGroups = new Map<string, ErrorGroupStats[]>(); errorGroups.forEach((group) => { const service = group.representative?.serviceContext?.service || "Unknown"; if (!serviceGroups.has(service)) { serviceGroups.set(service, []); } serviceGroups.get(service)!.push(group); }); if (serviceGroups.size > 1) { analysis += `**Services Affected:** ${serviceGroups.size}\n`; serviceGroups.forEach((groups, service) => { const serviceErrors = groups.reduce( (sum, group) => sum + parseInt(group.count), 0, ); analysis += `- ${service}: ${groups.length} error groups, ${serviceErrors} total errors\n`; }); analysis += `\n`; } // Time-based analysis const recentErrors = errorGroups.filter((group) => { const lastSeen = new Date(group.lastSeenTime); const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000); return lastSeen > oneHourAgo; }); if (recentErrors.length > 0) { analysis += `**Recent Activity:** ${recentErrors.length} error groups with activity in the last hour\n\n`; } return analysis; }
  • src/index.ts:212-213 (registration)
    Top-level registration call in main server setup that invokes registerErrorReportingTools(server), which in turn registers this tool.
    registerErrorReportingResources(server); registerErrorReportingTools(server);

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/RadiumGu/gcp-billing-and-monitoring-mcp'

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