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
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- src/services/error-reporting/tools.ts:21-177 (registration)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);