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"),
    },
Behavior1/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Tool has no description.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness1/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Tool has no description.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness1/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Tool has no description.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters1/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Tool has no description.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose1/5

Does the description clearly state what the tool does and how it differs from similar tools?

Tool has no description.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines1/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Tool has no description.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other 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