Skip to main content
Glama

Google Cloud MCP Server

by krzko
tools.ts22 kB
/** * Google Cloud Profiler tools for MCP */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { getProjectId, initGoogleAuth } from "../../utils/auth.js"; import { GcpMcpError } from "../../utils/error.js"; import { formatProfileSummary, analyseProfilePatterns, getProfileTypeDescription, Profile, ProfileType, ListProfilesResponse, } from "./types.js"; /** * Registers Google Cloud Profiler tools with the MCP server * * @param server The MCP server instance */ export function registerProfilerTools(server: McpServer): void { // Tool to list profiles with filtering and pagination support server.tool( "gcp-profiler-list-profiles", { title: "List Profiles", description: "List profiles from Google Cloud Profiler with optional filtering and pagination", inputSchema: { pageSize: z .number() .min(1) .max(1000) .default(50) .describe("Maximum number of profiles to return (1-1000)"), pageToken: z .string() .optional() .describe("Token for pagination to get next page of results"), profileType: z .enum([ ProfileType.CPU, ProfileType.WALL, ProfileType.HEAP, ProfileType.THREADS, ProfileType.CONTENTION, ProfileType.PEAK_HEAP, ProfileType.HEAP_ALLOC, ]) .optional() .describe("Filter by specific profile type"), target: z .string() .optional() .describe("Filter by deployment target (service name)"), }, }, async ({ pageSize, pageToken, profileType, target }) => { try { const projectId = await getProjectId(); // Initialize Google Auth client (same pattern as error reporting) const auth = await initGoogleAuth(true); if (!auth) { throw new GcpMcpError( "Google Cloud authentication not available. Please configure authentication to access profiler data.", "UNAUTHENTICATED", 401, ); } const client = await auth.getClient(); const token = await client.getAccessToken(); // Parse parameters const actualPageSize = pageSize || 50; // Build query parameters const params = new URLSearchParams({ pageSize: actualPageSize.toString(), }); // Add page token if provided if (pageToken) { params.set("pageToken", pageToken); } // Make REST API call to list profiles const apiUrl = `https://cloudprofiler.googleapis.com/v2/projects/${projectId}/profiles?${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 profiles: ${errorText}`, "FAILED_PRECONDITION", response.status, ); } const data: ListProfilesResponse = await response.json(); let profiles = data.profiles || []; // Apply client-side filtering if specified if (profileType) { profiles = profiles.filter((p) => p.profileType === profileType); } if (target) { profiles = profiles.filter((p) => p.deployment?.target?.toLowerCase().includes(target.toLowerCase()), ); } if (!profiles || profiles.length === 0) { let filterText = ""; if (profileType) filterText += `Profile Type: ${profileType}\n`; if (target) filterText += `Target: ${target}\n`; return { content: [ { type: "text", text: `# Profiles\n\nProject: ${projectId}\n${filterText}${data.nextPageToken ? `Page Token: ${pageToken || "first"}\n` : ""}No profiles found.`, }, ], }; } // Generate analysis and insights const analysis = analyseProfilePatterns(profiles); let content = `# Profiler Analysis\n\nProject: ${projectId}\n`; if (profileType) content += `Profile Type Filter: ${getProfileTypeDescription(profileType)}\n`; if (target) content += `Target Filter: ${target}\n`; if (data.nextPageToken) content += `Next Page Available: Use token "${data.nextPageToken}"\n`; if (data.skippedProfiles) content += `Skipped Profiles: ${data.skippedProfiles}\n`; content += `\n${analysis}\n\n`; content += `## Detailed Profile List\n\n`; profiles.forEach((profile, index) => { content += `### ${index + 1}. ${formatProfileSummary(profile)}\n`; }); // Add pagination info if available if (data.nextPageToken) { content += `\n---\n\n**Pagination:** Use page token "${data.nextPageToken}" to get the next ${actualPageSize} results.\n`; } return { content: [ { type: "text", text: content, }, ], }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; throw new GcpMcpError( `Failed to list profiles: ${errorMessage}`, "INTERNAL_ERROR", 500, ); } }, ); // Tool to get detailed analysis of specific profile types and patterns server.tool( "gcp-profiler-analyse-performance", { title: "Analyse Profile Performance", description: "Analyse profiles to identify performance patterns, bottlenecks, and optimisation opportunities", inputSchema: { profileType: z .enum([ ProfileType.CPU, ProfileType.WALL, ProfileType.HEAP, ProfileType.THREADS, ProfileType.CONTENTION, ProfileType.PEAK_HEAP, ProfileType.HEAP_ALLOC, ]) .optional() .describe("Focus analysis on specific profile type"), target: z .string() .optional() .describe("Focus analysis on specific deployment target"), pageSize: z .number() .min(1) .max(1000) .default(100) .describe( "Number of profiles to analyse (more profiles = better insights)", ), }, }, async ({ profileType, target, pageSize }) => { try { const projectId = await getProjectId(); // Initialize Google Auth client (same pattern as error reporting) const auth = await initGoogleAuth(true); if (!auth) { throw new GcpMcpError( "Google Cloud authentication not available. Please configure authentication to access profiler data.", "UNAUTHENTICATED", 401, ); } const client = await auth.getClient(); const token = await client.getAccessToken(); // Parse parameters const actualPageSize = pageSize || 100; // Build query parameters for maximum data collection const params = new URLSearchParams({ pageSize: actualPageSize.toString(), }); // Make REST API call to list profiles const apiUrl = `https://cloudprofiler.googleapis.com/v2/projects/${projectId}/profiles?${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 profiles for analysis: ${errorText}`, "FAILED_PRECONDITION", response.status, ); } const data: ListProfilesResponse = await response.json(); let profiles = data.profiles || []; // Apply filtering if specified if (profileType) { profiles = profiles.filter((p) => p.profileType === profileType); } if (target) { profiles = profiles.filter((p) => p.deployment?.target?.toLowerCase().includes(target.toLowerCase()), ); } if (!profiles || profiles.length === 0) { let filterText = "No profiles found for analysis"; if (profileType) filterText += ` with profile type: ${profileType}`; if (target) filterText += ` and target: ${target}`; return { content: [ { type: "text", text: `# Profile Performance Analysis\n\nProject: ${projectId}\n\n${filterText}.`, }, ], }; } // Generate comprehensive analysis let content = `# Profile Performance Analysis\n\nProject: ${projectId}\n`; if (profileType) content += `Focus: ${getProfileTypeDescription(profileType)}\n`; if (target) content += `Target: ${target}\n`; content += `Analysed: ${profiles.length} profiles\n\n`; // Get detailed analysis const analysis = analyseProfilePatterns(profiles); content += analysis; // Add performance insights specific to the analysis content += `\n## Performance Insights\n\n`; // Analyse profile collection patterns const timeDistribution = analyseProfileTimeDistribution(profiles); content += timeDistribution; // Analyse deployment patterns const deploymentAnalysis = analyseDeploymentPatterns(profiles); content += deploymentAnalysis; // Add actionable recommendations content += `\n## Actionable Recommendations\n\n`; content += getActionableRecommendations(profiles, profileType); return { content: [ { type: "text", text: content, }, ], }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; throw new GcpMcpError( `Failed to analyse profile performance: ${errorMessage}`, "INTERNAL_ERROR", 500, ); } }, ); // Tool to compare profiles and identify performance trends server.tool( "gcp-profiler-compare-trends", { title: "Compare Profile Trends", description: "Compare profiles over time to identify performance trends, regressions, and improvements", inputSchema: { target: z .string() .optional() .describe("Focus comparison on specific deployment target"), profileType: z .enum([ ProfileType.CPU, ProfileType.WALL, ProfileType.HEAP, ProfileType.THREADS, ProfileType.CONTENTION, ProfileType.PEAK_HEAP, ProfileType.HEAP_ALLOC, ]) .optional() .describe("Focus comparison on specific profile type"), pageSize: z .number() .min(1) .max(1000) .default(200) .describe("Number of profiles to analyse for trends"), }, }, async ({ target, profileType, pageSize }) => { try { const projectId = await getProjectId(); // Initialize Google Auth client const auth = await initGoogleAuth(true); if (!auth) { throw new GcpMcpError( "Google Cloud authentication not available. Please configure authentication to access profiler data.", "UNAUTHENTICATED", 401, ); } const client = await auth.getClient(); const token = await client.getAccessToken(); const actualPageSize = pageSize || 200; // Build query parameters const params = new URLSearchParams({ pageSize: actualPageSize.toString(), }); // Make REST API call to list profiles const apiUrl = `https://cloudprofiler.googleapis.com/v2/projects/${projectId}/profiles?${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 profiles for trend analysis: ${errorText}`, "FAILED_PRECONDITION", response.status, ); } const data: ListProfilesResponse = await response.json(); let profiles = data.profiles || []; // Apply filtering if (profileType) { profiles = profiles.filter((p) => p.profileType === profileType); } if (target) { profiles = profiles.filter((p) => p.deployment?.target?.toLowerCase().includes(target.toLowerCase()), ); } if (!profiles || profiles.length === 0) { return { content: [ { type: "text", text: `# Profile Trend Analysis\n\nProject: ${projectId}\n\nNo profiles found for trend analysis.`, }, ], }; } // Generate trend analysis let content = `# Profile Trend Analysis\n\nProject: ${projectId}\n`; if (profileType) content += `Profile Type: ${getProfileTypeDescription(profileType)}\n`; if (target) content += `Target: ${target}\n`; content += `Analysed: ${profiles.length} profiles\n\n`; // Analyse trends over time const trendAnalysis = analyseProfileTrends(profiles); content += trendAnalysis; return { content: [ { type: "text", text: content, }, ], }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; throw new GcpMcpError( `Failed to analyse profile trends: ${errorMessage}`, "INTERNAL_ERROR", 500, ); } }, ); } /** * Analyses profile collection time distribution */ function analyseProfileTimeDistribution(profiles: Profile[]): string { const profilesByTime = profiles .filter((p) => p.startTime) .sort( (a, b) => new Date(a.startTime).getTime() - new Date(b.startTime).getTime(), ); if (profilesByTime.length === 0) { return "No time-stamped profiles available for temporal analysis.\n\n"; } let analysis = "### Profile Collection Timeline\n\n"; // Group by time buckets (last 24 hours, last week, older) const now = new Date().getTime(); const oneDayAgo = now - 24 * 60 * 60 * 1000; const oneWeekAgo = now - 7 * 24 * 60 * 60 * 1000; const recent = profilesByTime.filter( (p) => new Date(p.startTime).getTime() > oneDayAgo, ); const thisWeek = profilesByTime.filter((p) => { const time = new Date(p.startTime).getTime(); return time <= oneDayAgo && time > oneWeekAgo; }); const older = profilesByTime.filter( (p) => new Date(p.startTime).getTime() <= oneWeekAgo, ); analysis += `- **Last 24 hours:** ${recent.length} profiles\n`; analysis += `- **Last week:** ${thisWeek.length} profiles\n`; analysis += `- **Older:** ${older.length} profiles\n\n`; if (recent.length > 0) { const oldestRecent = new Date(recent[0].startTime).toLocaleString(); const newestRecent = new Date( recent[recent.length - 1].startTime, ).toLocaleString(); analysis += `Recent activity spans from ${oldestRecent} to ${newestRecent}\n\n`; } return analysis; } /** * Analyses deployment patterns across profiles */ function analyseDeploymentPatterns(profiles: Profile[]): string { let analysis = "### Deployment Analysis\n\n"; // Analyse targets const targetCounts = profiles.reduce( (acc, profile) => { const target = profile.deployment?.target || "Unknown"; acc[target] = (acc[target] || 0) + 1; return acc; }, {} as Record<string, number>, ); analysis += `**Target Distribution:**\n`; Object.entries(targetCounts) .sort(([, a], [, b]) => b - a) .forEach(([target, count]) => { const percentage = Math.round((count / profiles.length) * 100); analysis += `- ${target}: ${count} profiles (${percentage}%)\n`; }); analysis += `\n`; // Analyse labels if available const allLabels = new Set<string>(); profiles.forEach((profile) => { Object.keys(profile.labels || {}).forEach((label) => allLabels.add(label)); Object.keys(profile.deployment?.labels || {}).forEach((label) => allLabels.add(label), ); }); if (allLabels.size > 0) { analysis += `**Common Labels Found:** ${Array.from(allLabels).join(", ")}\n\n`; } return analysis; } /** * Generates actionable recommendations based on profile analysis */ function getActionableRecommendations( profiles: Profile[], profileType?: ProfileType, ): string { let recommendations = ""; const targets = [ ...new Set(profiles.map((p) => p.deployment?.target).filter(Boolean)), ]; const hasMultipleTargets = targets.length > 1; recommendations += `**Immediate Actions:**\n`; recommendations += `- Review the ${profiles.length} profiles for performance patterns\n`; if (hasMultipleTargets) { recommendations += `- Compare performance across ${targets.length} different targets\n`; } if (profileType) { switch (profileType) { case ProfileType.CPU: recommendations += `- Identify CPU hotspots and optimise high-usage functions\n`; recommendations += `- Look for algorithms that can be optimised or parallelised\n`; break; case ProfileType.HEAP: recommendations += `- Review memory allocation patterns for optimisation\n`; recommendations += `- Check for memory leaks or excessive garbage collection\n`; break; case ProfileType.WALL: recommendations += `- Identify blocking I/O operations and network calls\n`; recommendations += `- Consider asynchronous processing for slow operations\n`; break; } } recommendations += `\n**Long-term Optimisation:**\n`; recommendations += `- Set up continuous profiling alerts for performance regressions\n`; recommendations += `- Establish performance baselines for each service\n`; recommendations += `- Integrate profiling data with monitoring and alerting systems\n`; recommendations += `- Use profile data to guide load testing and capacity planning\n`; return recommendations; } /** * Analyses performance trends over time */ function analyseProfileTrends(profiles: Profile[]): string { const profilesByTime = profiles .filter((p) => p.startTime) .sort( (a, b) => new Date(a.startTime).getTime() - new Date(b.startTime).getTime(), ); if (profilesByTime.length < 2) { return "Insufficient time-series data for trend analysis. Need at least 2 time-stamped profiles.\n"; } let analysis = "## Trend Analysis\n\n"; // Analyse profile frequency over time analysis += "### Profile Collection Frequency\n\n"; const earliest = new Date(profilesByTime[0].startTime); const latest = new Date(profilesByTime[profilesByTime.length - 1].startTime); const timeSpan = latest.getTime() - earliest.getTime(); const timeSpanDays = timeSpan / (1000 * 60 * 60 * 24); analysis += `- **Time Span:** ${Math.round(timeSpanDays)} days (from ${earliest.toLocaleDateString()} to ${latest.toLocaleDateString()})\n`; analysis += `- **Collection Frequency:** ${Math.round(profiles.length / timeSpanDays)} profiles per day\n\n`; // Analyse profile type trends analysis += "### Profile Type Trends\n\n"; const typesByTime = profilesByTime.reduce( (acc, profile) => { const day = new Date(profile.startTime).toDateString(); if (!acc[day]) acc[day] = {}; acc[day][profile.profileType] = (acc[day][profile.profileType] || 0) + 1; return acc; }, {} as Record<string, Record<string, number>>, ); const days = Object.keys(typesByTime).sort(); if (days.length > 1) { analysis += `Collected profiles across ${days.length} different days:\n\n`; days.slice(-5).forEach((day) => { // Show last 5 days const typeCounts = typesByTime[day]; const totalForDay = Object.values(typeCounts).reduce( (sum, count) => sum + count, 0, ); analysis += `**${day}:** ${totalForDay} profiles (`; analysis += Object.entries(typeCounts) .map(([type, count]) => `${type}: ${count}`) .join(", "); analysis += `)\n`; }); analysis += `\n`; } // Recommendations based on trends analysis += "### Trend-Based Recommendations\n\n"; if (timeSpanDays < 1) { analysis += `- **Short timeframe:** Consider collecting profiles over a longer period for better trend analysis\n`; } else if (profiles.length / timeSpanDays < 1) { analysis += `- **Low frequency:** Consider increasing profile collection frequency for better insights\n`; } else { analysis += `- **Good coverage:** Profile collection frequency appears adequate for trend analysis\n`; } analysis += `- **Pattern monitoring:** Set up alerts for unusual changes in profile patterns\n`; analysis += `- **Performance baseline:** Use this trend data to establish performance baselines\n`; return analysis; }

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