Skip to main content
Glama
krzko

Google Cloud MCP Server

by krzko

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
NameRequiredDescriptionDefault
filterYesThe filter to apply to metrics
startTimeYesStart time in ISO format or relative time (e.g., "1h", "2d")
endTimeNoEnd time in ISO format (defaults to now)
alignmentPeriodNoAlignment period (e.g., "60s", "300s")

Implementation Reference

  • 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, ); } }, );
  • 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, }); }

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