Skip to main content
Glama

Google Cloud MCP Server

by andyl25
query-count.js7.47 kB
import { MetricServiceClient } from '@google-cloud/monitoring'; import { z } from 'zod'; import { getProjectId } from '../../utils/auth.js'; import { GcpMcpError } from '../../utils/error.js'; import { parseRelativeTime } from '../../utils/time.js'; /** * Registers the Spanner query count tool with the MCP server * * @param server The MCP server instance */ export function registerSpannerQueryCountTool(server) { server.tool('spanner-query-count', { instanceId: z.string().optional().describe('Spanner instance ID (optional, if not provided will show all instances)'), databaseId: z.string().optional().describe('Spanner database ID (optional, if not provided will show all databases)'), queryType: z.enum(['ALL', 'READ', 'QUERY']).default('ALL').describe('Type of queries to count (ALL, READ, QUERY)'), status: z.enum(['ALL', 'OK', 'ERROR']).default('ALL').describe('Status of queries to count (ALL, OK, ERROR)'), startTime: z.string().default('1h').describe('Start time for the query (e.g., "1h", "2d", "30m")'), endTime: z.string().optional().describe('End time for the query (defaults to now)'), alignmentPeriod: z.string().default('60s').describe('Alignment period for aggregating data points (e.g., "60s", "5m", "1h")') }, async ({ instanceId, databaseId, queryType, status, startTime, endTime, alignmentPeriod }, context) => { try { const projectId = await getProjectId(); const client = new MetricServiceClient({ projectId: process.env.GOOGLE_CLOUD_PROJECT }); // Parse time range const start = parseRelativeTime(startTime); const end = endTime ? parseRelativeTime(endTime) : new Date(); // Build filter for the metric let filter = 'metric.type = "spanner.googleapis.com/query_count"'; // Add resource filters if specified if (instanceId) { filter += ` AND resource.labels.instance_id = "${instanceId}"`; } // Add metric label filters if (databaseId) { filter += ` AND metric.labels.database = "${databaseId}"`; } if (queryType !== 'ALL') { filter += ` AND metric.labels.query_type = "${queryType.toLowerCase()}"`; } if (status !== 'ALL') { filter += ` AND metric.labels.status = "${status.toLowerCase()}"`; } // 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; } // Build the request const request = { name: `projects/${projectId}`, filter, interval: { startTime: { seconds: Math.floor(start.getTime() / 1000), nanos: 0 }, endTime: { seconds: Math.floor(end.getTime() / 1000), nanos: 0 } }, aggregation: { alignmentPeriod: { seconds }, perSeriesAligner: 'ALIGN_SUM', crossSeriesReducer: 'REDUCE_SUM' } }; // Execute the request const timeSeriesData = await client.listTimeSeries(request); const timeSeries = timeSeriesData[0]; if (!timeSeries || timeSeries.length === 0) { return { content: [{ type: 'text', text: `# Spanner Query Count\n\nProject: ${projectId}\n${instanceId ? `\nInstance: ${instanceId}` : ''}\n${databaseId ? `\nDatabase: ${databaseId}` : ''}\n\nQuery Type: ${queryType}\nStatus: ${status}\nTime Range: ${start.toISOString()} to ${end.toISOString()}\nAlignment Period: ${alignmentPeriod}\n\nNo query count data found for the specified parameters.` }] }; } // Format the results let markdown = `# Spanner Query Count\n\nProject: ${projectId}\n${instanceId ? `\nInstance: ${instanceId}` : ''}\n${databaseId ? `\nDatabase: ${databaseId}` : ''}\n\nQuery Type: ${queryType}\nStatus: ${status}\nTime Range: ${start.toISOString()} to ${end.toISOString()}\nAlignment Period: ${alignmentPeriod}\n\n`; // Create a table for each time series for (const series of timeSeries) { const seriesData = series; // Extract labels for the table header const instanceName = seriesData.resource.labels.instance_id || 'unknown'; const databaseName = seriesData.metric.labels?.database || 'all'; const queryTypeValue = seriesData.metric.labels?.query_type || 'all'; const statusValue = seriesData.metric.labels?.status || 'all'; const optimizerVersion = seriesData.metric.labels?.optimizer_version || 'unknown'; markdown += `## Instance: ${instanceName}, Database: ${databaseName}\n`; markdown += `Query Type: ${queryTypeValue}, Status: ${statusValue}, Optimizer Version: ${optimizerVersion}\n\n`; // Table header markdown += '| Timestamp | Query Count |\n'; markdown += '|-----------|------------|\n'; // Table rows if (seriesData.points && seriesData.points.length > 0) { // Sort points by time (oldest first) const sortedPoints = [...seriesData.points].sort((a, b) => { const aTime = Number(a.interval.startTime.seconds); const bTime = Number(b.interval.startTime.seconds); return aTime - bTime; }); for (const point of sortedPoints) { const timestamp = new Date(Number(point.interval.endTime.seconds) * 1000).toISOString(); const count = point.value.int64Value || '0'; markdown += `| ${timestamp} | ${count} |\n`; } } else { markdown += '| No data | 0 |\n'; } markdown += '\n'; } return { content: [{ type: 'text', text: markdown }] }; } catch (error) { console.error('Error in spanner-query-count tool:', error); throw error; } }); } //# sourceMappingURL=query-count.js.map

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/andyl25/googlecloud-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server