Skip to main content
Glama
RadiumGu

GCP Billing and Monitoring MCP Server

by RadiumGu

gcp-trace-get-trace

Retrieve Google Cloud trace data by trace ID to analyze request flows and diagnose performance issues in GCP services.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
projectIdNoOptional Google Cloud project ID
traceIdYesThe trace ID to retrieve

Implementation Reference

  • The main execution handler for the 'gcp-trace-get-trace' tool. It authenticates with Google Cloud, fetches the trace by ID from the Cloud Trace API v1, validates the trace ID, builds a hierarchical representation using buildTraceHierarchy, formats the output with formatTraceData, and returns a markdown-formatted response with trace details or raw span info if hierarchy fails.
    async ({ traceId, projectId }, context) => { try { // Use provided project ID or get the default one from state manager first const actualProjectId = projectId || stateManager.getCurrentProjectId() || (await getProjectId()); // 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 v1 // API Reference: https://cloud.google.com/trace/docs/reference/v1/rest/v1/projects.traces/get const apiUrl = `https://cloudtrace.googleapis.com/v1/projects/${actualProjectId}/traces/${traceId}`; logger.debug(`Fetching trace from: ${apiUrl}`); 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 trace: ${errorText}`, "FAILED_PRECONDITION", response.status, ); } const traceData = await response.json(); // Log the raw trace data for debugging logger.debug(`Raw trace data: ${JSON.stringify(traceData, null, 2)}`); // Debug: Log the exact structure of the trace data logger.debug("Trace data structure:"); logger.debug(`- Type: ${typeof traceData}`); logger.debug(`- Keys: ${Object.keys(traceData).join(", ")}`); logger.debug(`- Has spans array: ${Array.isArray(traceData.spans)}`); logger.debug(`- Spans array length: ${traceData.spans?.length || 0}`); // Check if we have valid trace data // In v1 API, the response is a Trace object with spans array if (!traceData || !traceData.spans || traceData.spans.length === 0) { return { content: [ { type: "text", text: `No trace found with ID: ${traceId} in project: ${actualProjectId}`, }, ], }; } // Log the trace structure for debugging logger.debug( `Trace structure: projectId=${traceData.projectId}, traceId=${traceData.traceId}, spans count=${traceData.spans.length}`, ); // Log the first span to help with debugging if (traceData.spans && traceData.spans.length > 0) { const firstSpan = traceData.spans[0]; logger.debug( `First span example: ${JSON.stringify(firstSpan, null, 2)}`, ); logger.debug( `First span fields: ${Object.keys(firstSpan).join(", ")}`, ); // Debug: Log specific fields that we're looking for in the span logger.debug("Span field details:"); logger.debug(`- spanId: ${firstSpan.spanId}`); logger.debug(`- name: ${firstSpan.name}`); logger.debug(`- displayName: ${firstSpan.displayName}`); logger.debug(`- startTime: ${firstSpan.startTime}`); logger.debug(`- endTime: ${firstSpan.endTime}`); logger.debug(`- parentSpanId: ${firstSpan.parentSpanId}`); logger.debug(`- kind: ${firstSpan.kind}`); logger.debug(`- Has labels: ${!!firstSpan.labels}`); if (firstSpan.labels) { logger.debug( `- Label keys: ${Object.keys(firstSpan.labels).join(", ")}`, ); logger.debug( `- HTTP path label: ${firstSpan.labels["/http/path"]}`, ); logger.debug( `- HTTP method label: ${firstSpan.labels["/http/method"]}`, ); logger.debug( `- Component label: ${firstSpan.labels["/component"]}`, ); } } // Add additional metadata to the response for better context let responseText = `\`\`\`json\n${JSON.stringify( { traceId: traceId, projectId: actualProjectId, spanCount: traceData.spans.length, }, null, 2, )}\n\`\`\`\n`; // Log the number of spans found logger.debug( `Found ${traceData.spans.length} spans in trace ${traceId}`, ); try { logger.debug("Starting to build trace hierarchy..."); // Debug: Log each span before processing traceData.spans.forEach((span: any, index: number) => { logger.debug(`Span ${index} (ID: ${span.spanId}):`); logger.debug(`- Name: ${span.name || "undefined"}`); logger.debug(`- Parent: ${span.parentSpanId || "None"}`); logger.debug(`- Has labels: ${!!span.labels}`); if (span.labels) { logger.debug(`- Label count: ${Object.keys(span.labels).length}`); } }); // Build the trace hierarchy const hierarchicalTrace = buildTraceHierarchy( actualProjectId.toString(), traceId.toString(), traceData.spans, ); logger.debug("Trace hierarchy built successfully"); logger.debug( `Root spans count: ${hierarchicalTrace.rootSpans.length}`, ); // Format the trace data for display logger.debug("Formatting trace data..."); const formattedTrace = formatTraceData(hierarchicalTrace); // Combine the response text with the formatted trace responseText += formattedTrace; logger.debug("Trace formatting complete"); } catch (hierarchyError: any) { // If we encounter an error building the hierarchy, log it and provide raw span info logger.error( `Error building trace hierarchy: ${hierarchyError.message}`, ); // Provide a simplified trace summary responseText += "## Error Building Trace Hierarchy\n\n"; responseText += `Error: ${hierarchyError.message}\n\n`; responseText += "## Raw Span Summary\n\n"; // List spans with basic information for (const span of traceData.spans) { const spanId = span.spanId || "Unknown"; const name = span.name || "Unknown"; const parentId = span.parentSpanId || "None"; responseText += `- **Span ID**: ${spanId}\n`; responseText += ` - Name: ${name}\n`; responseText += ` - Parent: ${parentId}\n`; // Add timing if available if (span.startTime && span.endTime) { const startDate = new Date(span.startTime); const endDate = new Date(span.endTime); const durationMs = endDate.getTime() - startDate.getTime(); responseText += ` - Duration: ${durationMs}ms\n`; } // Add a few important labels if available if (span.labels) { responseText += ` - Labels: ${Object.keys(span.labels).length} total\n`; const importantLabels = [ "/http/method", "/http/path", "/http/status_code", "/component", "g.co/agent", ]; for (const key of importantLabels) { if (span.labels[key]) { responseText += ` - ${key}: ${span.labels[key]}\n`; } } } responseText += "\n"; } } return { content: [ { type: "text", text: responseText, }, ], }; } catch (error: any) { // Error handling for get-trace tool throw new GcpMcpError( `Failed to fetch trace: ${error.message}`, error.code || "UNKNOWN", error.statusCode || 500, ); } },
  • Zod schema defining input parameters: required traceId (string) and optional projectId (string).
    { traceId: z.string().describe("The trace ID to retrieve"), projectId: z .string() .optional() .describe("Optional Google Cloud project ID"), },
  • MCP tool registration call using server.tool() with name 'gcp-trace-get-trace', input schema, and handler function within registerTraceTools.
    server.tool( "gcp-trace-get-trace", { traceId: z.string().describe("The trace ID to retrieve"), projectId: z .string() .optional() .describe("Optional Google Cloud project ID"), }, async ({ traceId, projectId }, context) => { try { // Use provided project ID or get the default one from state manager first const actualProjectId = projectId || stateManager.getCurrentProjectId() || (await getProjectId()); // 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 v1 // API Reference: https://cloud.google.com/trace/docs/reference/v1/rest/v1/projects.traces/get const apiUrl = `https://cloudtrace.googleapis.com/v1/projects/${actualProjectId}/traces/${traceId}`; logger.debug(`Fetching trace from: ${apiUrl}`); 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 trace: ${errorText}`, "FAILED_PRECONDITION", response.status, ); } const traceData = await response.json(); // Log the raw trace data for debugging logger.debug(`Raw trace data: ${JSON.stringify(traceData, null, 2)}`); // Debug: Log the exact structure of the trace data logger.debug("Trace data structure:"); logger.debug(`- Type: ${typeof traceData}`); logger.debug(`- Keys: ${Object.keys(traceData).join(", ")}`); logger.debug(`- Has spans array: ${Array.isArray(traceData.spans)}`); logger.debug(`- Spans array length: ${traceData.spans?.length || 0}`); // Check if we have valid trace data // In v1 API, the response is a Trace object with spans array if (!traceData || !traceData.spans || traceData.spans.length === 0) { return { content: [ { type: "text", text: `No trace found with ID: ${traceId} in project: ${actualProjectId}`, }, ], }; } // Log the trace structure for debugging logger.debug( `Trace structure: projectId=${traceData.projectId}, traceId=${traceData.traceId}, spans count=${traceData.spans.length}`, ); // Log the first span to help with debugging if (traceData.spans && traceData.spans.length > 0) { const firstSpan = traceData.spans[0]; logger.debug( `First span example: ${JSON.stringify(firstSpan, null, 2)}`, ); logger.debug( `First span fields: ${Object.keys(firstSpan).join(", ")}`, ); // Debug: Log specific fields that we're looking for in the span logger.debug("Span field details:"); logger.debug(`- spanId: ${firstSpan.spanId}`); logger.debug(`- name: ${firstSpan.name}`); logger.debug(`- displayName: ${firstSpan.displayName}`); logger.debug(`- startTime: ${firstSpan.startTime}`); logger.debug(`- endTime: ${firstSpan.endTime}`); logger.debug(`- parentSpanId: ${firstSpan.parentSpanId}`); logger.debug(`- kind: ${firstSpan.kind}`); logger.debug(`- Has labels: ${!!firstSpan.labels}`); if (firstSpan.labels) { logger.debug( `- Label keys: ${Object.keys(firstSpan.labels).join(", ")}`, ); logger.debug( `- HTTP path label: ${firstSpan.labels["/http/path"]}`, ); logger.debug( `- HTTP method label: ${firstSpan.labels["/http/method"]}`, ); logger.debug( `- Component label: ${firstSpan.labels["/component"]}`, ); } } // Add additional metadata to the response for better context let responseText = `\`\`\`json\n${JSON.stringify( { traceId: traceId, projectId: actualProjectId, spanCount: traceData.spans.length, }, null, 2, )}\n\`\`\`\n`; // Log the number of spans found logger.debug( `Found ${traceData.spans.length} spans in trace ${traceId}`, ); try { logger.debug("Starting to build trace hierarchy..."); // Debug: Log each span before processing traceData.spans.forEach((span: any, index: number) => { logger.debug(`Span ${index} (ID: ${span.spanId}):`); logger.debug(`- Name: ${span.name || "undefined"}`); logger.debug(`- Parent: ${span.parentSpanId || "None"}`); logger.debug(`- Has labels: ${!!span.labels}`); if (span.labels) { logger.debug(`- Label count: ${Object.keys(span.labels).length}`); } }); // Build the trace hierarchy const hierarchicalTrace = buildTraceHierarchy( actualProjectId.toString(), traceId.toString(), traceData.spans, ); logger.debug("Trace hierarchy built successfully"); logger.debug( `Root spans count: ${hierarchicalTrace.rootSpans.length}`, ); // Format the trace data for display logger.debug("Formatting trace data..."); const formattedTrace = formatTraceData(hierarchicalTrace); // Combine the response text with the formatted trace responseText += formattedTrace; logger.debug("Trace formatting complete"); } catch (hierarchyError: any) { // If we encounter an error building the hierarchy, log it and provide raw span info logger.error( `Error building trace hierarchy: ${hierarchyError.message}`, ); // Provide a simplified trace summary responseText += "## Error Building Trace Hierarchy\n\n"; responseText += `Error: ${hierarchyError.message}\n\n`; responseText += "## Raw Span Summary\n\n"; // List spans with basic information for (const span of traceData.spans) { const spanId = span.spanId || "Unknown"; const name = span.name || "Unknown"; const parentId = span.parentSpanId || "None"; responseText += `- **Span ID**: ${spanId}\n`; responseText += ` - Name: ${name}\n`; responseText += ` - Parent: ${parentId}\n`; // Add timing if available if (span.startTime && span.endTime) { const startDate = new Date(span.startTime); const endDate = new Date(span.endTime); const durationMs = endDate.getTime() - startDate.getTime(); responseText += ` - Duration: ${durationMs}ms\n`; } // Add a few important labels if available if (span.labels) { responseText += ` - Labels: ${Object.keys(span.labels).length} total\n`; const importantLabels = [ "/http/method", "/http/path", "/http/status_code", "/component", "g.co/agent", ]; for (const key of importantLabels) { if (span.labels[key]) { responseText += ` - ${key}: ${span.labels[key]}\n`; } } } responseText += "\n"; } } return { content: [ { type: "text", text: responseText, }, ], }; } catch (error: any) { // Error handling for get-trace tool throw new GcpMcpError( `Failed to fetch trace: ${error.message}`, error.code || "UNKNOWN", error.statusCode || 500, ); } }, );
  • Invocation of registerTraceTools within registerTraceService, which registers all trace tools including 'gcp-trace-get-trace'.
    await registerTraceTools(server);

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/RadiumGu/gcp-billing-and-monitoring-mcp'

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