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, track frequency changes, and prioritize debugging efforts.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • Registration of the 'gcp-error-reporting-analyse-trends' MCP tool, including inline schema and handler function.
    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,
          );
        }
      },
    );
  • Handler function that implements the tool logic: authenticates with GCP, queries error group stats with timed counts for trend analysis, computes aggregates, detects spikes (>2x average), lists top error groups, and returns formatted 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,
        );
      }
    },
  • Zod input schema defining parameters: timeRange (default 24h), serviceFilter (optional), resolution (1m|5m|1h|1d, default 1h).
      "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"),
    },

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