Skip to main content
Glama
krzko

Google Cloud MCP Server

by krzko

gcp-error-reporting-analyse-trends

Analyze error trends in Google Cloud Error Reporting to identify patterns, prioritize issues, and improve system reliability using actionable insights derived from error logs.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • Executes GCP Error Reporting API call for group stats with timed counts to analyze trends, aggregates data over time buckets, identifies spikes, summarizes top error groups, and returns a comprehensive Markdown analysis report.
    async ({ timeRange, serviceFilter, resolution }) => { 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 || "24h"; const actualResolution = resolution || "1h"; // 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 day for trend analysis period = "PERIOD_1_DAY"; break; } // Calculate timed count duration based on resolution let timedCountDuration: string; switch (actualResolution) { case "1m": timedCountDuration = "60s"; break; case "5m": timedCountDuration = "300s"; break; case "1h": timedCountDuration = "3600s"; break; case "1d": timedCountDuration = "86400s"; break; default: timedCountDuration = "3600s"; // Default to 1 hour break; } // Build query parameters for trends analysis const params = new URLSearchParams({ "timeRange.period": period, timedCountDuration: timedCountDuration, order: "COUNT_DESC", pageSize: "50", }); // Add service filter if provided if (serviceFilter) { params.set("serviceFilter.service", serviceFilter); } // Make REST API call for trends 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 trends: ${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 Trends Analysis\n\nProject: ${projectId}\nTime Range: ${actualTimeRange}\n${serviceFilter ? `Service Filter: ${serviceFilter}\n` : ""}\nResolution: ${actualResolution}\n\nNo error data found for trend analysis.`, }, ], }; } let content = `# Error Trends Analysis\n\nProject: ${projectId}\nTime Range: ${actualTimeRange}\n${serviceFilter ? `Service Filter: ${serviceFilter}\n` : ""}\nResolution: ${actualResolution}\n\n`; // Aggregate trends across all error groups const timeSlots = new Map<string, number>(); let totalErrors = 0; const totalGroups = errorGroupStats.length; errorGroupStats.forEach((stat: any) => { const count = parseInt(stat.count || "0"); totalErrors += count; if (stat.timedCounts) { stat.timedCounts.forEach((timedCount: any) => { const timeKey = timedCount.startTime; const currentCount = timeSlots.get(timeKey) || 0; timeSlots.set( timeKey, currentCount + parseInt(timedCount.count || "0"), ); }); } }); content += `## Summary\n\n`; content += `- **Total Error Groups:** ${totalGroups}\n`; content += `- **Total Errors:** ${totalErrors.toLocaleString()}\n`; content += `- **Average per Group:** ${Math.round(totalErrors / totalGroups).toLocaleString()}\n\n`; // Sort time slots chronologically const sortedTimeSlots = Array.from(timeSlots.entries()).sort( ([a], [b]) => new Date(a).getTime() - new Date(b).getTime(), ); // Initialize variables for recommendations let averageErrors = 0; let spikes: Array<[string, number]> = []; if (sortedTimeSlots.length > 0) { content += `## Error Count Over Time\n\n`; content += `| Time Period | Error Count |\n`; content += `|-------------|-------------|\n`; sortedTimeSlots.forEach(([time, count]) => { const timeStr = new Date(time).toLocaleString(); content += `| ${timeStr} | ${count.toLocaleString()} |\n`; }); content += `\n`; // Identify spikes (errors significantly above average) averageErrors = totalErrors / sortedTimeSlots.length; spikes = sortedTimeSlots.filter( ([, count]) => count > averageErrors * 2, ); if (spikes.length > 0) { content += `## Error Spikes Detected\n\n`; content += `*Time periods with error counts > 2x average (${Math.round(averageErrors)})*\n\n`; spikes.forEach(([time, count]) => { const timeStr = new Date(time).toLocaleString(); const multiplier = Math.round((count / averageErrors) * 10) / 10; content += `- **${timeStr}:** ${count.toLocaleString()} errors (${multiplier}x average)\n`; }); content += `\n`; } } // Top error groups contributing to trends content += `## Top Contributing Error Groups\n\n`; const topErrors = errorGroupStats.slice(0, 5).map((stat: any) => ({ service: stat.representative?.serviceContext?.service || "Unknown", message: stat.representative?.message || "No message", count: parseInt(stat.count || "0"), groupId: stat.group?.groupId || "unknown", })); topErrors.forEach( ( error: { service: string; message: string; count: number; groupId: string; }, index: number, ) => { const percentage = Math.round((error.count / totalErrors) * 100); content += `${index + 1}. **${error.service}** (${percentage}% of total)\n`; content += ` - ${error.message}\n`; content += ` - ${error.count.toLocaleString()} occurrences\n`; content += ` - Group ID: ${error.groupId}\n\n`; }, ); // Recommendations based on trends content += `## Recommendations\n\n`; if (spikes.length > 0) { content += `- **Investigate Error Spikes:** Focus on the ${spikes.length} time periods with significantly elevated error rates\n`; content += `- **Correlate with Deployments:** Check if error spikes align with recent deployments or configuration changes\n`; } content += `- **Monitor Top Contributors:** The top ${Math.min(3, topErrors.length)} error groups account for the majority of errors\n`; content += `- **Set Up Alerting:** Configure alerts for error rates exceeding ${Math.round(averageErrors * 1.5)} errors per ${resolution}\n`; content += `- **Review Patterns:** Look for recurring patterns in error timing to identify systemic issues\n`; return { content: [ { type: "text", text: content, }, ], }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; throw new GcpMcpError( `Failed to analyse error trends: ${errorMessage}`, "INTERNAL_ERROR", 500, ); } },
  • Zod schema validating input parameters: timeRange (optional, default '24h'), serviceFilter (optional string), resolution (enum '1m'|'5m'|'1h'|'1d', default '1h').
    inputSchema: { timeRange: z .string() .optional() .default("24h") .describe('Time range to analyse (e.g., "1h", "24h", "7d")'), serviceFilter: z.string().optional().describe("Filter by service name"), resolution: z .enum(["1m", "5m", "1h", "1d"]) .optional() .default("1h") .describe("Time resolution for trend analysis"), },
  • Registers the 'gcp-error-reporting-analyse-trends' tool on the MCP server with title, description, input schema, and handler function.
    // Tool to analyse error trends over time server.tool( "gcp-error-reporting-analyse-trends", { title: "Analyse Error Trends", description: "Analyse error trends over time to identify patterns and spikes", inputSchema: { timeRange: z .string() .optional() .default("24h") .describe('Time range to analyse (e.g., "1h", "24h", "7d")'), serviceFilter: z.string().optional().describe("Filter by service name"), resolution: z .enum(["1m", "5m", "1h", "1d"]) .optional() .default("1h") .describe("Time resolution for trend analysis"), }, }, async ({ timeRange, serviceFilter, resolution }) => { 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 || "24h"; const actualResolution = resolution || "1h"; // 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 day for trend analysis period = "PERIOD_1_DAY"; break; } // Calculate timed count duration based on resolution let timedCountDuration: string; switch (actualResolution) { case "1m": timedCountDuration = "60s"; break; case "5m": timedCountDuration = "300s"; break; case "1h": timedCountDuration = "3600s"; break; case "1d": timedCountDuration = "86400s"; break; default: timedCountDuration = "3600s"; // Default to 1 hour break; } // Build query parameters for trends analysis const params = new URLSearchParams({ "timeRange.period": period, timedCountDuration: timedCountDuration, order: "COUNT_DESC", pageSize: "50", }); // Add service filter if provided if (serviceFilter) { params.set("serviceFilter.service", serviceFilter); } // Make REST API call for trends 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 trends: ${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 Trends Analysis\n\nProject: ${projectId}\nTime Range: ${actualTimeRange}\n${serviceFilter ? `Service Filter: ${serviceFilter}\n` : ""}\nResolution: ${actualResolution}\n\nNo error data found for trend analysis.`, }, ], }; } let content = `# Error Trends Analysis\n\nProject: ${projectId}\nTime Range: ${actualTimeRange}\n${serviceFilter ? `Service Filter: ${serviceFilter}\n` : ""}\nResolution: ${actualResolution}\n\n`; // Aggregate trends across all error groups const timeSlots = new Map<string, number>(); let totalErrors = 0; const totalGroups = errorGroupStats.length; errorGroupStats.forEach((stat: any) => { const count = parseInt(stat.count || "0"); totalErrors += count; if (stat.timedCounts) { stat.timedCounts.forEach((timedCount: any) => { const timeKey = timedCount.startTime; const currentCount = timeSlots.get(timeKey) || 0; timeSlots.set( timeKey, currentCount + parseInt(timedCount.count || "0"), ); }); } }); content += `## Summary\n\n`; content += `- **Total Error Groups:** ${totalGroups}\n`; content += `- **Total Errors:** ${totalErrors.toLocaleString()}\n`; content += `- **Average per Group:** ${Math.round(totalErrors / totalGroups).toLocaleString()}\n\n`; // Sort time slots chronologically const sortedTimeSlots = Array.from(timeSlots.entries()).sort( ([a], [b]) => new Date(a).getTime() - new Date(b).getTime(), ); // Initialize variables for recommendations let averageErrors = 0; let spikes: Array<[string, number]> = []; if (sortedTimeSlots.length > 0) { content += `## Error Count Over Time\n\n`; content += `| Time Period | Error Count |\n`; content += `|-------------|-------------|\n`; sortedTimeSlots.forEach(([time, count]) => { const timeStr = new Date(time).toLocaleString(); content += `| ${timeStr} | ${count.toLocaleString()} |\n`; }); content += `\n`; // Identify spikes (errors significantly above average) averageErrors = totalErrors / sortedTimeSlots.length; spikes = sortedTimeSlots.filter( ([, count]) => count > averageErrors * 2, ); if (spikes.length > 0) { content += `## Error Spikes Detected\n\n`; content += `*Time periods with error counts > 2x average (${Math.round(averageErrors)})*\n\n`; spikes.forEach(([time, count]) => { const timeStr = new Date(time).toLocaleString(); const multiplier = Math.round((count / averageErrors) * 10) / 10; content += `- **${timeStr}:** ${count.toLocaleString()} errors (${multiplier}x average)\n`; }); content += `\n`; } } // Top error groups contributing to trends content += `## Top Contributing Error Groups\n\n`; const topErrors = errorGroupStats.slice(0, 5).map((stat: any) => ({ service: stat.representative?.serviceContext?.service || "Unknown", message: stat.representative?.message || "No message", count: parseInt(stat.count || "0"), groupId: stat.group?.groupId || "unknown", })); topErrors.forEach( ( error: { service: string; message: string; count: number; groupId: string; }, index: number, ) => { const percentage = Math.round((error.count / totalErrors) * 100); content += `${index + 1}. **${error.service}** (${percentage}% of total)\n`; content += ` - ${error.message}\n`; content += ` - ${error.count.toLocaleString()} occurrences\n`; content += ` - Group ID: ${error.groupId}\n\n`; }, ); // Recommendations based on trends content += `## Recommendations\n\n`; if (spikes.length > 0) { content += `- **Investigate Error Spikes:** Focus on the ${spikes.length} time periods with significantly elevated error rates\n`; content += `- **Correlate with Deployments:** Check if error spikes align with recent deployments or configuration changes\n`; } content += `- **Monitor Top Contributors:** The top ${Math.min(3, topErrors.length)} error groups account for the majority of errors\n`; content += `- **Set Up Alerting:** Configure alerts for error rates exceeding ${Math.round(averageErrors * 1.5)} errors per ${resolution}\n`; content += `- **Review Patterns:** Look for recurring patterns in error timing to identify systemic issues\n`; return { content: [ { type: "text", text: content, }, ], }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; throw new GcpMcpError( `Failed to analyse error trends: ${errorMessage}`, "INTERNAL_ERROR", 500, ); } }, );

Other Tools

Related 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