Skip to main content
Glama
krzko

Google Cloud MCP Server

by krzko

gcp-trace-query-natural-language

Query Google Cloud Trace data using natural language to analyze performance issues and debug distributed systems.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesNatural language query about traces (e.g., "Show me failed traces from the last hour")
projectIdNoOptional Google Cloud project ID

Implementation Reference

  • Registration of the 'gcp-trace-query-natural-language' MCP tool, including inline input schema (query string and optional projectId) and the complete handler function that parses natural language queries to query GCP Cloud Trace API for traces, handle specific trace IDs, logs, time ranges, filters, and formats results in markdown tables.
    "gcp-trace-query-natural-language", { query: z .string() .describe( 'Natural language query about traces (e.g., "Show me failed traces from the last hour")', ), projectId: z .string() .optional() .describe("Optional Google Cloud project ID"), }, async ({ query, projectId }, context) => { try { // Use provided project ID or get the default one from state manager first const actualProjectId = projectId || stateManager.getCurrentProjectId() || (await getProjectId()); // Process the natural language query const normalizedQuery = query.toLowerCase(); // Default parameters let filter = ""; let limit = 10; let startTime = "1h"; // Default to 1 hour let traceId = ""; // Extract trace ID if present const traceIdMatch = normalizedQuery.match( /trace(?:\s+id)?[:\s]+([a-f0-9]+)/i, ); if (traceIdMatch && traceIdMatch[1]) { traceId = traceIdMatch[1]; // If we have a trace ID, implement the get-trace functionality directly // Validate trace ID format (hex string) if (typeof traceId === "string" && !traceId.match(/^[a-f0-9]+$/i)) { throw new GcpMcpError( "Invalid trace ID format. Trace ID should be a hexadecimal string.", "INVALID_ARGUMENT", 400, ); } // Initialize Google Auth client const auth = await initGoogleAuth(true); if (!auth) { throw new GcpMcpError( "Google Cloud authentication not available. Please configure authentication to access trace data.", "UNAUTHENTICATED", 401, ); } const client = await auth.getClient(); const token = await client.getAccessToken(); // Fetch the trace from the Cloud Trace API const response = await fetch( `https://cloudtrace.googleapis.com/v1/projects/${actualProjectId}/traces/${traceId}`, { headers: { Authorization: `Bearer ${token.token}`, "Content-Type": "application/json", }, }, ); if (!response.ok) { const errorText = await response.text(); throw new GcpMcpError( `Failed to fetch trace: ${errorText}`, "FAILED_PRECONDITION", response.status, ); } const traceData = await response.json(); if (!traceData || !traceData.spans || traceData.spans.length === 0) { return { content: [ { type: "text", text: `No trace found with ID: ${traceId} in project: ${actualProjectId}`, }, ], }; } // Build the trace hierarchy const hierarchicalTrace = buildTraceHierarchy( actualProjectId.toString(), traceId.toString(), traceData.spans, ); // Format the trace data for display const formattedTrace = formatTraceData(hierarchicalTrace); return { content: [ { type: "text", text: formattedTrace, }, ], }; } // Extract time range if ( normalizedQuery.includes("last hour") || normalizedQuery.includes("past hour") ) { startTime = "1h"; } else if ( normalizedQuery.includes("last day") || normalizedQuery.includes("past day") || normalizedQuery.includes("24 hours") || normalizedQuery.includes("today") ) { startTime = "24h"; } else if ( normalizedQuery.includes("last week") || normalizedQuery.includes("past week") ) { startTime = "7d"; } else if ( normalizedQuery.includes("last month") || normalizedQuery.includes("past month") ) { startTime = "30d"; } // Extract status filter if ( normalizedQuery.includes("fail") || normalizedQuery.includes("error") || normalizedQuery.includes("exception") || normalizedQuery.includes("problem") ) { filter = "status.code != 0"; // Non-zero status codes indicate errors } // Extract limit const limitMatch = normalizedQuery.match( /\b(show|display|list|get|find)?\s+(\d+)\s+(trace|span|result)/i, ); if (limitMatch && limitMatch[2]) { limit = parseInt(limitMatch[2]); limit = Math.min(Math.max(limit, 1), 100); // Clamp between 1 and 100 } // If query mentions logs, use the find-traces-from-logs tool if ( normalizedQuery.includes("log") || normalizedQuery.includes("logging") ) { let logFilter = "severity>=ERROR"; // Default to error logs if ( normalizedQuery.includes("info") || normalizedQuery.includes("information") ) { logFilter = "severity>=INFO"; } else if ( normalizedQuery.includes("warn") || normalizedQuery.includes("warning") ) { logFilter = "severity>=WARNING"; } else if (normalizedQuery.includes("debug")) { logFilter = "severity>=DEBUG"; } // Implement find-traces-from-logs functionality directly // Initialize the logging client const logging = new Logging({ projectId: actualProjectId, }); // Fetch logs with the given filter const [entries] = await logging.getEntries({ filter: logFilter, pageSize: limit, }); if (!entries || entries.length === 0) { return { content: [ { type: "text", text: `No logs found matching the filter: "${logFilter}" in project: ${actualProjectId}`, }, ], }; } // Extract trace IDs from logs const traceMap = new Map< string, { traceId: string; timestamp: string; severity: string; logName: string; message: string; } >(); for (const entry of entries) { const metadata = entry.metadata; const traceId = extractTraceIdFromLog(metadata); if (traceId) { // Convert timestamp to string let timestampStr = "Unknown"; if (metadata.timestamp) { if (typeof metadata.timestamp === "string") { timestampStr = metadata.timestamp; } else { try { // Handle different timestamp formats if ( typeof metadata.timestamp === "object" && metadata.timestamp !== null ) { if ( "seconds" in metadata.timestamp && "nanos" in metadata.timestamp ) { // Handle Timestamp proto format const seconds = Number(metadata.timestamp.seconds); const nanos = Number(metadata.timestamp.nanos || 0); const milliseconds = seconds * 1000 + nanos / 1000000; timestampStr = new Date(milliseconds).toISOString(); } else { // Try to convert using JSON timestampStr = JSON.stringify(metadata.timestamp); } } else { timestampStr = String(metadata.timestamp); } } catch (e) { timestampStr = "Invalid timestamp"; } } } // Convert severity to string let severityStr = "DEFAULT"; if (metadata.severity) { severityStr = String(metadata.severity); } // Convert logName to string let logNameStr = "Unknown"; if (metadata.logName) { logNameStr = String(metadata.logName); } // Extract message let messageStr = "No message"; if (metadata.textPayload) { messageStr = String(metadata.textPayload); } else if (metadata.jsonPayload) { try { messageStr = JSON.stringify(metadata.jsonPayload); } catch (e) { messageStr = "Invalid JSON payload"; } } traceMap.set(traceId, { traceId, timestamp: timestampStr, severity: severityStr, logName: logNameStr, message: messageStr, }); } } if (traceMap.size === 0) { return { content: [ { type: "text", text: `No traces found in the logs matching the filter: "${logFilter}" in project: ${actualProjectId}`, }, ], }; } // Format the traces for display let markdown = `# Traces Found in Logs\n\n`; markdown += `Project: ${actualProjectId}\n`; markdown += `Log Filter: ${logFilter}\n`; markdown += `Found ${traceMap.size} unique traces in ${entries.length} log entries:\n\n`; // Table header markdown += "| Trace ID | Timestamp | Severity | Log Name | Message |\n"; markdown += "|----------|-----------|----------|----------|--------|\n"; // Table rows for (const trace of traceMap.values()) { const traceId = trace.traceId; // Handle timestamp formatting safely let timestamp = trace.timestamp; try { if ( timestamp !== "Unknown" && timestamp !== "Invalid timestamp" ) { timestamp = new Date(trace.timestamp).toISOString(); } } catch (e) { // Keep the original timestamp if conversion fails } const severity = trace.severity; const logName = trace.logName.split("/").pop() || trace.logName; const message = trace.message.length > 100 ? `${trace.message.substring(0, 100)}...` : trace.message; markdown += `| ${traceId} | ${timestamp} | ${severity} | ${logName} | ${message} |\n`; } return { content: [ { type: "text", text: markdown, }, ], }; } // Otherwise, use the list-traces tool // Implement list-traces functionality directly // Calculate time range const endTime = new Date(); let startTimeDate: Date; if (startTime) { logger.debug(`Raw startTime parameter: ${JSON.stringify(startTime)}`); // Handle the case where startTime might be passed as an object from JSON const startTimeStr = typeof startTime === "string" ? startTime : String(startTime); logger.debug(`Processing startTime: ${startTimeStr}`); // Check if the input is a relative time format (e.g., "1h", "2d", "30m") if (startTimeStr.match(/^\d+[hmd]$/)) { // Parse relative time (e.g., "1h", "2d") const value = parseInt(startTimeStr.slice(0, -1)); const unit = startTimeStr.slice(-1); startTimeDate = new Date(endTime); if (unit === "h") { startTimeDate.setHours(startTimeDate.getHours() - value); } else if (unit === "d") { startTimeDate.setDate(startTimeDate.getDate() - value); } else if (unit === "m") { startTimeDate.setMinutes(startTimeDate.getMinutes() - value); } logger.debug( `Parsed relative time: ${startTimeStr} to ${startTimeDate.toISOString()}`, ); } else { // Parse ISO format try { startTimeDate = new Date(startTimeStr); if (isNaN(startTimeDate.getTime())) { throw new Error("Invalid date format"); } logger.debug( `Parsed ISO time: ${startTimeStr} to ${startTimeDate.toISOString()}`, ); } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; logger.error(`Error parsing time: ${errorMessage}`); throw new GcpMcpError( `Invalid start time format: "${startTimeStr}". Use ISO format or relative time (e.g., "1h", "2d").`, "INVALID_ARGUMENT", 400, ); } } } else { // Default to 1 hour ago startTimeDate = new Date(endTime); startTimeDate.setHours(startTimeDate.getHours() - 1); } // Build the request body const requestBody: any = { projectId: actualProjectId, startTime: startTimeDate.toISOString(), endTime: endTime.toISOString(), pageSize: limit, }; if (filter) { requestBody.filter = filter; } // Initialize Google Auth client const auth = await initGoogleAuth(true); if (!auth) { throw new GcpMcpError( "Google Cloud authentication not available. Please configure authentication to access trace data.", "UNAUTHENTICATED", 401, ); } const client = await auth.getClient(); const token = await client.getAccessToken(); // Build the query parameters for the request const queryParams = new URLSearchParams(); // Format timestamps in RFC3339 UTC "Zulu" format const startTimeUTC = new Date( startTimeDate.toISOString(), ).toISOString(); const endTimeUTC = new Date(endTime.toISOString()).toISOString(); // Add required query parameters according to the API documentation queryParams.append("startTime", startTimeUTC); queryParams.append("endTime", endTimeUTC); queryParams.append("pageSize", limit.toString()); // Add view parameter to specify the type of data returned queryParams.append("view", "MINIMAL"); if (filter) { queryParams.append("filter", filter); } // Construct the URL for the Cloud Trace API v1 endpoint const apiUrl = `https://cloudtrace.googleapis.com/v1/projects/${actualProjectId}/traces`; const requestUrl = `${apiUrl}?${queryParams.toString()}`; logger.debug(`Fetching traces from: ${requestUrl}`); // Fetch traces from the Cloud Trace API const response = await fetch(requestUrl, { method: "GET", headers: { Authorization: `Bearer ${token.token}`, Accept: "application/json", }, }); if (!response.ok) { const errorText = await response.text(); throw new GcpMcpError( `Failed to list traces: ${errorText}`, "FAILED_PRECONDITION", response.status, ); } const tracesData = await response.json(); // Check if we have valid traces data // In v1 API, the response contains a traces array if (!tracesData.traces || tracesData.traces.length === 0) { return { content: [ { type: "text", text: `No traces found matching the criteria in project: ${actualProjectId}`, }, ], }; } // Format the traces for display let markdown = `# Traces for ${actualProjectId}\n\n`; markdown += `Time Range: ${startTimeDate.toISOString()} to ${endTime.toISOString()}\n`; markdown += `Filter: ${filter || "None"}\n\n`; markdown += `Found ${tracesData.traces.length} traces:\n\n`; // Table header markdown += "| Trace ID | Display Name | Start Time | Duration | Status |\n"; markdown += "|----------|--------------|------------|----------|--------|\n"; // Table rows for (const trace of tracesData.traces) { const traceId = trace.traceId; const displayName = trace.displayName || "Unknown"; const startTimeStr = new Date(trace.startTime).toISOString(); const duration = calculateDuration(trace.startTime, trace.endTime); const status = trace.status?.code === 0 ? "OK" : `Error: ${trace.status?.message || "Unknown error"}`; markdown += `| ${traceId} | ${displayName} | ${startTimeStr} | ${duration} | ${status} |\n`; } return { content: [ { type: "text", text: markdown, }, ], }; } catch (error: any) { // Error handling for natural-language-trace-query tool throw new GcpMcpError( `Failed to process natural language query: ${error.message}`, error.code || "UNKNOWN", error.statusCode || 500, ); } }, );
  • The handler function executes natural language queries by parsing intent (e.g., time ranges like 'last hour', errors, logs), fetching traces via Cloud Trace API (/v1/projects/{project}/traces), handling auth, formatting hierarchical traces or lists in markdown tables, and error handling.
    async ({ query, projectId }, context) => { try { // Use provided project ID or get the default one from state manager first const actualProjectId = projectId || stateManager.getCurrentProjectId() || (await getProjectId()); // Process the natural language query const normalizedQuery = query.toLowerCase(); // Default parameters let filter = ""; let limit = 10; let startTime = "1h"; // Default to 1 hour let traceId = ""; // Extract trace ID if present const traceIdMatch = normalizedQuery.match( /trace(?:\s+id)?[:\s]+([a-f0-9]+)/i, ); if (traceIdMatch && traceIdMatch[1]) { traceId = traceIdMatch[1]; // If we have a trace ID, implement the get-trace functionality directly // Validate trace ID format (hex string) if (typeof traceId === "string" && !traceId.match(/^[a-f0-9]+$/i)) { throw new GcpMcpError( "Invalid trace ID format. Trace ID should be a hexadecimal string.", "INVALID_ARGUMENT", 400, ); } // Initialize Google Auth client const auth = await initGoogleAuth(true); if (!auth) { throw new GcpMcpError( "Google Cloud authentication not available. Please configure authentication to access trace data.", "UNAUTHENTICATED", 401, ); } const client = await auth.getClient(); const token = await client.getAccessToken(); // Fetch the trace from the Cloud Trace API const response = await fetch( `https://cloudtrace.googleapis.com/v1/projects/${actualProjectId}/traces/${traceId}`, { headers: { Authorization: `Bearer ${token.token}`, "Content-Type": "application/json", }, }, ); if (!response.ok) { const errorText = await response.text(); throw new GcpMcpError( `Failed to fetch trace: ${errorText}`, "FAILED_PRECONDITION", response.status, ); } const traceData = await response.json(); if (!traceData || !traceData.spans || traceData.spans.length === 0) { return { content: [ { type: "text", text: `No trace found with ID: ${traceId} in project: ${actualProjectId}`, }, ], }; } // Build the trace hierarchy const hierarchicalTrace = buildTraceHierarchy( actualProjectId.toString(), traceId.toString(), traceData.spans, ); // Format the trace data for display const formattedTrace = formatTraceData(hierarchicalTrace); return { content: [ { type: "text", text: formattedTrace, }, ], }; } // Extract time range if ( normalizedQuery.includes("last hour") || normalizedQuery.includes("past hour") ) { startTime = "1h"; } else if ( normalizedQuery.includes("last day") || normalizedQuery.includes("past day") || normalizedQuery.includes("24 hours") || normalizedQuery.includes("today") ) { startTime = "24h"; } else if ( normalizedQuery.includes("last week") || normalizedQuery.includes("past week") ) { startTime = "7d"; } else if ( normalizedQuery.includes("last month") || normalizedQuery.includes("past month") ) { startTime = "30d"; } // Extract status filter if ( normalizedQuery.includes("fail") || normalizedQuery.includes("error") || normalizedQuery.includes("exception") || normalizedQuery.includes("problem") ) { filter = "status.code != 0"; // Non-zero status codes indicate errors } // Extract limit const limitMatch = normalizedQuery.match( /\b(show|display|list|get|find)?\s+(\d+)\s+(trace|span|result)/i, ); if (limitMatch && limitMatch[2]) { limit = parseInt(limitMatch[2]); limit = Math.min(Math.max(limit, 1), 100); // Clamp between 1 and 100 } // If query mentions logs, use the find-traces-from-logs tool if ( normalizedQuery.includes("log") || normalizedQuery.includes("logging") ) { let logFilter = "severity>=ERROR"; // Default to error logs if ( normalizedQuery.includes("info") || normalizedQuery.includes("information") ) { logFilter = "severity>=INFO"; } else if ( normalizedQuery.includes("warn") || normalizedQuery.includes("warning") ) { logFilter = "severity>=WARNING"; } else if (normalizedQuery.includes("debug")) { logFilter = "severity>=DEBUG"; } // Implement find-traces-from-logs functionality directly // Initialize the logging client const logging = new Logging({ projectId: actualProjectId, }); // Fetch logs with the given filter const [entries] = await logging.getEntries({ filter: logFilter, pageSize: limit, }); if (!entries || entries.length === 0) { return { content: [ { type: "text", text: `No logs found matching the filter: "${logFilter}" in project: ${actualProjectId}`, }, ], }; } // Extract trace IDs from logs const traceMap = new Map< string, { traceId: string; timestamp: string; severity: string; logName: string; message: string; } >(); for (const entry of entries) { const metadata = entry.metadata; const traceId = extractTraceIdFromLog(metadata); if (traceId) { // Convert timestamp to string let timestampStr = "Unknown"; if (metadata.timestamp) { if (typeof metadata.timestamp === "string") { timestampStr = metadata.timestamp; } else { try { // Handle different timestamp formats if ( typeof metadata.timestamp === "object" && metadata.timestamp !== null ) { if ( "seconds" in metadata.timestamp && "nanos" in metadata.timestamp ) { // Handle Timestamp proto format const seconds = Number(metadata.timestamp.seconds); const nanos = Number(metadata.timestamp.nanos || 0); const milliseconds = seconds * 1000 + nanos / 1000000; timestampStr = new Date(milliseconds).toISOString(); } else { // Try to convert using JSON timestampStr = JSON.stringify(metadata.timestamp); } } else { timestampStr = String(metadata.timestamp); } } catch (e) { timestampStr = "Invalid timestamp"; } } } // Convert severity to string let severityStr = "DEFAULT"; if (metadata.severity) { severityStr = String(metadata.severity); } // Convert logName to string let logNameStr = "Unknown"; if (metadata.logName) { logNameStr = String(metadata.logName); } // Extract message let messageStr = "No message"; if (metadata.textPayload) { messageStr = String(metadata.textPayload); } else if (metadata.jsonPayload) { try { messageStr = JSON.stringify(metadata.jsonPayload); } catch (e) { messageStr = "Invalid JSON payload"; } } traceMap.set(traceId, { traceId, timestamp: timestampStr, severity: severityStr, logName: logNameStr, message: messageStr, }); } } if (traceMap.size === 0) { return { content: [ { type: "text", text: `No traces found in the logs matching the filter: "${logFilter}" in project: ${actualProjectId}`, }, ], }; } // Format the traces for display let markdown = `# Traces Found in Logs\n\n`; markdown += `Project: ${actualProjectId}\n`; markdown += `Log Filter: ${logFilter}\n`; markdown += `Found ${traceMap.size} unique traces in ${entries.length} log entries:\n\n`; // Table header markdown += "| Trace ID | Timestamp | Severity | Log Name | Message |\n"; markdown += "|----------|-----------|----------|----------|--------|\n"; // Table rows for (const trace of traceMap.values()) { const traceId = trace.traceId; // Handle timestamp formatting safely let timestamp = trace.timestamp; try { if ( timestamp !== "Unknown" && timestamp !== "Invalid timestamp" ) { timestamp = new Date(trace.timestamp).toISOString(); } } catch (e) { // Keep the original timestamp if conversion fails } const severity = trace.severity; const logName = trace.logName.split("/").pop() || trace.logName; const message = trace.message.length > 100 ? `${trace.message.substring(0, 100)}...` : trace.message; markdown += `| ${traceId} | ${timestamp} | ${severity} | ${logName} | ${message} |\n`; } return { content: [ { type: "text", text: markdown, }, ], }; } // Otherwise, use the list-traces tool // Implement list-traces functionality directly // Calculate time range const endTime = new Date(); let startTimeDate: Date; if (startTime) { logger.debug(`Raw startTime parameter: ${JSON.stringify(startTime)}`); // Handle the case where startTime might be passed as an object from JSON const startTimeStr = typeof startTime === "string" ? startTime : String(startTime); logger.debug(`Processing startTime: ${startTimeStr}`); // Check if the input is a relative time format (e.g., "1h", "2d", "30m") if (startTimeStr.match(/^\d+[hmd]$/)) { // Parse relative time (e.g., "1h", "2d") const value = parseInt(startTimeStr.slice(0, -1)); const unit = startTimeStr.slice(-1); startTimeDate = new Date(endTime); if (unit === "h") { startTimeDate.setHours(startTimeDate.getHours() - value); } else if (unit === "d") { startTimeDate.setDate(startTimeDate.getDate() - value); } else if (unit === "m") { startTimeDate.setMinutes(startTimeDate.getMinutes() - value); } logger.debug( `Parsed relative time: ${startTimeStr} to ${startTimeDate.toISOString()}`, ); } else { // Parse ISO format try { startTimeDate = new Date(startTimeStr); if (isNaN(startTimeDate.getTime())) { throw new Error("Invalid date format"); } logger.debug( `Parsed ISO time: ${startTimeStr} to ${startTimeDate.toISOString()}`, ); } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; logger.error(`Error parsing time: ${errorMessage}`); throw new GcpMcpError( `Invalid start time format: "${startTimeStr}". Use ISO format or relative time (e.g., "1h", "2d").`, "INVALID_ARGUMENT", 400, ); } } } else { // Default to 1 hour ago startTimeDate = new Date(endTime); startTimeDate.setHours(startTimeDate.getHours() - 1); } // Build the request body const requestBody: any = { projectId: actualProjectId, startTime: startTimeDate.toISOString(), endTime: endTime.toISOString(), pageSize: limit, }; if (filter) { requestBody.filter = filter; } // Initialize Google Auth client const auth = await initGoogleAuth(true); if (!auth) { throw new GcpMcpError( "Google Cloud authentication not available. Please configure authentication to access trace data.", "UNAUTHENTICATED", 401, ); } const client = await auth.getClient(); const token = await client.getAccessToken(); // Build the query parameters for the request const queryParams = new URLSearchParams(); // Format timestamps in RFC3339 UTC "Zulu" format const startTimeUTC = new Date( startTimeDate.toISOString(), ).toISOString(); const endTimeUTC = new Date(endTime.toISOString()).toISOString(); // Add required query parameters according to the API documentation queryParams.append("startTime", startTimeUTC); queryParams.append("endTime", endTimeUTC); queryParams.append("pageSize", limit.toString()); // Add view parameter to specify the type of data returned queryParams.append("view", "MINIMAL"); if (filter) { queryParams.append("filter", filter); } // Construct the URL for the Cloud Trace API v1 endpoint const apiUrl = `https://cloudtrace.googleapis.com/v1/projects/${actualProjectId}/traces`; const requestUrl = `${apiUrl}?${queryParams.toString()}`; logger.debug(`Fetching traces from: ${requestUrl}`); // Fetch traces from the Cloud Trace API const response = await fetch(requestUrl, { method: "GET", headers: { Authorization: `Bearer ${token.token}`, Accept: "application/json", }, }); if (!response.ok) { const errorText = await response.text(); throw new GcpMcpError( `Failed to list traces: ${errorText}`, "FAILED_PRECONDITION", response.status, ); } const tracesData = await response.json(); // Check if we have valid traces data // In v1 API, the response contains a traces array if (!tracesData.traces || tracesData.traces.length === 0) { return { content: [ { type: "text", text: `No traces found matching the criteria in project: ${actualProjectId}`, }, ], }; } // Format the traces for display let markdown = `# Traces for ${actualProjectId}\n\n`; markdown += `Time Range: ${startTimeDate.toISOString()} to ${endTime.toISOString()}\n`; markdown += `Filter: ${filter || "None"}\n\n`; markdown += `Found ${tracesData.traces.length} traces:\n\n`; // Table header markdown += "| Trace ID | Display Name | Start Time | Duration | Status |\n"; markdown += "|----------|--------------|------------|----------|--------|\n"; // Table rows for (const trace of tracesData.traces) { const traceId = trace.traceId; const displayName = trace.displayName || "Unknown"; const startTimeStr = new Date(trace.startTime).toISOString(); const duration = calculateDuration(trace.startTime, trace.endTime); const status = trace.status?.code === 0 ? "OK" : `Error: ${trace.status?.message || "Unknown error"}`; markdown += `| ${traceId} | ${displayName} | ${startTimeStr} | ${duration} | ${status} |\n`; } return { content: [ { type: "text", text: markdown, }, ], }; } catch (error: any) { // Error handling for natural-language-trace-query tool throw new GcpMcpError( `Failed to process natural language query: ${error.message}`, error.code || "UNKNOWN", error.statusCode || 500, ); } },
  • Zod schema for tool inputs: required 'query' string for natural language description, optional 'projectId' string.
    { query: z .string() .describe( 'Natural language query about traces (e.g., "Show me failed traces from the last hour")', ), projectId: z .string() .optional() .describe("Optional Google Cloud project ID"), },

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