gcp-monitoring-query-metrics
Query Google Cloud Monitoring metrics using filters and time ranges to analyze performance data and monitor resource usage.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| filter | Yes | The filter to apply to metrics | |
| startTime | Yes | Start time in ISO format or relative time (e.g., "1h", "2d") | |
| endTime | No | End time in ISO format (defaults to now) | |
| alignmentPeriod | No | Alignment period (e.g., "60s", "300s") |
Implementation Reference
- src/services/monitoring/tools.ts:29-137 (registration)Registers the gcp-monitoring-query-metrics tool on the MCP server, including input schema validation and the handler function."gcp-monitoring-query-metrics", { filter: z.string().describe("The filter to apply to metrics"), startTime: z .string() .describe( 'Start time in ISO format or relative time (e.g., "1h", "2d")', ), endTime: z .string() .optional() .describe("End time in ISO format (defaults to now)"), alignmentPeriod: z .string() .optional() .describe('Alignment period (e.g., "60s", "300s")'), }, async ({ filter, startTime, endTime, alignmentPeriod }) => { try { const projectId = await getProjectId(); const client = getMonitoringClient(); const start = parseRelativeTime(startTime); const end = endTime ? parseRelativeTime(endTime) : new Date(); // Build request const request: any = { name: `projects/${projectId}`, filter, interval: { startTime: { seconds: Math.floor(start.getTime() / 1000), nanos: 0, }, endTime: { seconds: Math.floor(end.getTime() / 1000), nanos: 0, }, }, }; // Add alignment if specified if (alignmentPeriod) { // Parse alignment period (e.g., "60s" -> 60 seconds) const match = alignmentPeriod.match(/^(\d+)([smhd])$/); if (!match) { throw new GcpMcpError( 'Invalid alignment period format. Use format like "60s", "5m", "1h".', "INVALID_ARGUMENT", 400, ); } const value = parseInt(match[1]); const unit = match[2]; let seconds = value; switch (unit) { case "m": // minutes seconds = value * 60; break; case "h": // hours seconds = value * 60 * 60; break; case "d": // days seconds = value * 60 * 60 * 24; break; } request.aggregation = { alignmentPeriod: { seconds: seconds, }, perSeriesAligner: "ALIGN_MEAN", }; } const [timeSeries] = await client.listTimeSeries(request); if (!timeSeries || timeSeries.length === 0) { return { content: [ { type: "text", text: `# Metric Query Results\n\nProject: ${projectId}\nFilter: ${filter}\nTime Range: ${start.toISOString()} to ${end.toISOString()}\n\nNo metrics found matching the filter.`, }, ], }; } const formattedData = formatTimeSeriesData(timeSeries); return { content: [ { type: "text", text: `# Metric Query Results\n\nProject: ${projectId}\nFilter: ${filter}\nTime Range: ${start.toISOString()} to ${end.toISOString()}\n${alignmentPeriod ? `\nAlignment: ${alignmentPeriod}` : ""}\n\n${formattedData}`, }, ], }; } catch (error: any) { throw new GcpMcpError( `Failed to query metrics: ${error.message}`, error.code || "UNKNOWN", error.statusCode || 500, ); } }, );
- src/services/monitoring/tools.ts:46-136 (handler)The core handler function that queries GCP Monitoring API using listTimeSeries with custom filter, time range, and optional aggregation. Formats results into Markdown tables using formatTimeSeriesData.async ({ filter, startTime, endTime, alignmentPeriod }) => { try { const projectId = await getProjectId(); const client = getMonitoringClient(); const start = parseRelativeTime(startTime); const end = endTime ? parseRelativeTime(endTime) : new Date(); // Build request const request: any = { name: `projects/${projectId}`, filter, interval: { startTime: { seconds: Math.floor(start.getTime() / 1000), nanos: 0, }, endTime: { seconds: Math.floor(end.getTime() / 1000), nanos: 0, }, }, }; // Add alignment if specified if (alignmentPeriod) { // Parse alignment period (e.g., "60s" -> 60 seconds) const match = alignmentPeriod.match(/^(\d+)([smhd])$/); if (!match) { throw new GcpMcpError( 'Invalid alignment period format. Use format like "60s", "5m", "1h".', "INVALID_ARGUMENT", 400, ); } const value = parseInt(match[1]); const unit = match[2]; let seconds = value; switch (unit) { case "m": // minutes seconds = value * 60; break; case "h": // hours seconds = value * 60 * 60; break; case "d": // days seconds = value * 60 * 60 * 24; break; } request.aggregation = { alignmentPeriod: { seconds: seconds, }, perSeriesAligner: "ALIGN_MEAN", }; } const [timeSeries] = await client.listTimeSeries(request); if (!timeSeries || timeSeries.length === 0) { return { content: [ { type: "text", text: `# Metric Query Results\n\nProject: ${projectId}\nFilter: ${filter}\nTime Range: ${start.toISOString()} to ${end.toISOString()}\n\nNo metrics found matching the filter.`, }, ], }; } const formattedData = formatTimeSeriesData(timeSeries); return { content: [ { type: "text", text: `# Metric Query Results\n\nProject: ${projectId}\nFilter: ${filter}\nTime Range: ${start.toISOString()} to ${end.toISOString()}\n${alignmentPeriod ? `\nAlignment: ${alignmentPeriod}` : ""}\n\n${formattedData}`, }, ], }; } catch (error: any) { throw new GcpMcpError( `Failed to query metrics: ${error.message}`, error.code || "UNKNOWN", error.statusCode || 500, ); } },
- Zod input schema defining parameters for the tool: filter (required string), startTime (required string), endTime (optional string), alignmentPeriod (optional string).filter: z.string().describe("The filter to apply to metrics"), startTime: z .string() .describe( 'Start time in ISO format or relative time (e.g., "1h", "2d")', ), endTime: z .string() .optional() .describe("End time in ISO format (defaults to now)"), alignmentPeriod: z .string() .optional() .describe('Alignment period (e.g., "60s", "300s")'), },
- Helper function to format raw GCP time series data into human-readable Markdown tables, including metric details, resource info, and timestamped values.export function formatTimeSeriesData( timeSeries: google.monitoring.v3.ITimeSeries[], ): string { if (!timeSeries || timeSeries.length === 0) { return "No time series data found."; } let result = ""; for (const series of timeSeries) { // Format metric information const metricType = series.metric?.type; const metricLabels = series.metric?.labels ? Object.entries(series.metric?.labels) .map(([k, v]) => `${k}=${v}`) .join(", ") : ""; const resourceType = series.resource?.type; const resourceLabels = Object.entries(series.resource?.labels ?? {}) .map(([k, v]) => `${k}=${v}`) .join(", "); result += `## Metric: ${metricType}\n`; result += `- Resource: ${resourceType}(${resourceLabels})\n`; if (metricLabels) { result += `- Labels: ${metricLabels}\n`; } result += `- Kind: ${series.metricKind}, Type: ${series.valueType}\n\n`; // Format data points result += "| Timestamp | Value |\n"; result += "|-----------|-------|\n"; for (const point of series.points ?? []) { const timestamp = new Date( Number(point.interval?.endTime?.seconds) * 1000, ).toISOString(); // Extract the value based on valueType let value: string; if (point.value?.boolValue !== undefined) { value = String(point.value?.boolValue) ?? "N/A"; } else if (point.value?.int64Value !== undefined) { value = point.value?.int64Value?.toString() ?? "N/A"; } else if (point.value?.doubleValue !== undefined) { value = point.value?.doubleValue?.toFixed(6) ?? "N/A"; } else if (point.value?.stringValue !== undefined) { value = point.value?.stringValue ?? "N/A"; } else if (point.value?.distributionValue) { value = "Distribution"; } else { value = "N/A"; } result += `| ${timestamp} | ${value} |\n`; } result += "\n---\n\n"; } return result; }
- Helper function that initializes and returns the Google Cloud MetricServiceClient using the project ID from environment.export function getMonitoringClient() { return new MetricServiceClient({ projectId: process.env.GOOGLE_CLOUD_PROJECT, }); }