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
| Name | Required | Description | Default |
|---|---|---|---|
| projectId | No | Optional Google Cloud project ID | |
| traceId | Yes | The trace ID to retrieve |
Implementation Reference
- src/services/trace/tools.ts:33-266 (handler)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, ); } },
- src/services/trace/tools.ts:26-32 (schema)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"), },
- src/services/trace/tools.ts:24-267 (registration)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, ); } }, );
- src/services/trace/index.ts:18-18 (registration)Invocation of registerTraceTools within registerTraceService, which registers all trace tools including 'gcp-trace-get-trace'.await registerTraceTools(server);