Skip to main content
Glama
andyl25

Google Cloud MCP Server

by andyl25

list-traces

Retrieve and filter traces from Google Cloud Trace by specifying project ID, time range, or status. Set a limit to control the number of results returned.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
filterNoOptional filter for traces (e.g., "status.code != 0" for errors)
limitNoMaximum number of traces to return
projectIdNoOptional Google Cloud project ID
startTimeNoStart time in RFC3339 format (e.g., "2023-01-01T00:00:00Z") or relative time (e.g., "1h", "2d")

Implementation Reference

  • Main handler function that authenticates with Google Cloud, parses input parameters including relative times, queries the Cloud Trace API to list traces, handles errors, and returns a formatted Markdown table of traces with details like ID, display name, start time, duration, and status.
    async ({ projectId, filter, limit, startTime }, context) => { try { // Use provided project ID or get the default one from state manager first const actualProjectId = projectId || stateManager.getCurrentProjectId() || await getProjectId(); // 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); } // 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(); // Format timestamps in RFC3339 UTC "Zulu" format as required by the API // Example format: "2014-10-02T15:01:23Z" // The Cloud Trace API requires RFC3339 format timestamps const startTimeUTC = startTimeDate.toISOString(); const endTimeUTC = endTime.toISOString(); logger.debug(`Using formatted timestamps: startTime=${startTimeUTC}, endTime=${endTimeUTC}`); // Build the query parameters for the request according to the API documentation const queryParams = new URLSearchParams(); // Required parameters - format must be RFC3339 UTC "Zulu" format // The Cloud Trace API requires timestamps in RFC3339 format // Example: "2014-10-02T15:01:23Z" queryParams.append('startTime', startTimeUTC); // Start of the time interval (inclusive) queryParams.append('endTime', endTimeUTC); // End of the time interval (inclusive) // Optional parameters queryParams.append('pageSize', limit.toString()); // Maximum number of traces to return // The view parameter is optional and defaults to MINIMAL // ROOTSPAN includes the root span with the trace // COMPLETE includes all spans with the trace queryParams.append('view', 'COMPLETE'); // Type of data returned (MINIMAL, ROOTSPAN, COMPLETE) // Add orderBy parameter to sort by most recent traces first queryParams.append('orderBy', 'start desc'); // Sort by start time descending // Optional filter parameter if (filter) { queryParams.append('filter', filter); // Filter against labels for the request } // Construct the URL for the Cloud Trace API v1 endpoint // The correct endpoint format according to the documentation is: // GET https://cloudtrace.googleapis.com/v1/projects/{projectId}/traces const apiUrl = `https://cloudtrace.googleapis.com/v1/projects/${actualProjectId}/traces`; const requestUrl = `${apiUrl}?${queryParams.toString()}`; logger.debug(`List Traces URL: ${requestUrl}`); logger.debug(`List Traces Query Params: ${JSON.stringify(Object.fromEntries(queryParams.entries()))}`); logger.debug(`List Traces Time Range: ${startTimeDate.toISOString()} to ${endTime.toISOString()}`); logger.debug(`List Traces Raw Query String: ${queryParams.toString()}`); // Fetch traces from the Cloud Trace API logger.debug(`Sending request to Cloud Trace API: ${requestUrl}`); let tracesData; try { const response = await fetch( requestUrl, { method: 'GET', headers: { 'Authorization': `Bearer ${token.token}`, 'Accept': 'application/json' } } ); logger.debug(`List Traces Response Status: ${response.status}`); if (!response.ok) { const errorText = await response.text(); logger.error(`List Traces Error: ${errorText}`); throw new GcpMcpError( `Failed to list traces: ${errorText}`, 'FAILED_PRECONDITION', response.status ); } // Log the full response headers to help debug const responseHeaders: Record<string, string> = {}; response.headers.forEach((value, key) => { responseHeaders[key] = value; }); logger.debug(`List Traces Response Headers: ${JSON.stringify(responseHeaders)}`); tracesData = await response.json(); logger.debug(`List Traces Response Data: ${JSON.stringify(tracesData, null, 2)}`); } catch (fetchError: any) { logger.error(`Fetch error: ${fetchError.message}`); throw new GcpMcpError( `Failed to fetch traces: ${fetchError.message}`, 'INTERNAL', 500 ); } // 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}` }] }; } // Use the helper function to format the response return formatTracesResponse(tracesData, actualProjectId, startTimeDate, endTime, filter); } catch (error: any) { // Error handling for list-traces tool throw new GcpMcpError( `Failed to list traces: ${error.message}`, error.code || 'UNKNOWN', error.statusCode || 500 ); } }
  • Zod schema defining input parameters for the list-traces tool.
    { projectId: z.string().optional().describe('Optional Google Cloud project ID'), filter: z.string().optional().describe('Optional filter for traces (e.g., "status.code != 0" for errors)'), limit: z.number().min(1).max(100).default(10).describe('Maximum number of traces to return'), startTime: z.string().optional().describe('Start time in RFC3339 format (e.g., "2023-01-01T00:00:00Z") or relative time (e.g., "1h", "2d")') },
  • Registration of the list-traces tool on the MCP server within the registerTraceTools function.
    server.tool( 'list-traces', { projectId: z.string().optional().describe('Optional Google Cloud project ID'), filter: z.string().optional().describe('Optional filter for traces (e.g., "status.code != 0" for errors)'), limit: z.number().min(1).max(100).default(10).describe('Maximum number of traces to return'), startTime: z.string().optional().describe('Start time in RFC3339 format (e.g., "2023-01-01T00:00:00Z") or relative time (e.g., "1h", "2d")') }, async ({ projectId, filter, limit, startTime }, context) => { try { // Use provided project ID or get the default one from state manager first const actualProjectId = projectId || stateManager.getCurrentProjectId() || await getProjectId(); // 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); } // 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(); // Format timestamps in RFC3339 UTC "Zulu" format as required by the API // Example format: "2014-10-02T15:01:23Z" // The Cloud Trace API requires RFC3339 format timestamps const startTimeUTC = startTimeDate.toISOString(); const endTimeUTC = endTime.toISOString(); logger.debug(`Using formatted timestamps: startTime=${startTimeUTC}, endTime=${endTimeUTC}`); // Build the query parameters for the request according to the API documentation const queryParams = new URLSearchParams(); // Required parameters - format must be RFC3339 UTC "Zulu" format // The Cloud Trace API requires timestamps in RFC3339 format // Example: "2014-10-02T15:01:23Z" queryParams.append('startTime', startTimeUTC); // Start of the time interval (inclusive) queryParams.append('endTime', endTimeUTC); // End of the time interval (inclusive) // Optional parameters queryParams.append('pageSize', limit.toString()); // Maximum number of traces to return // The view parameter is optional and defaults to MINIMAL // ROOTSPAN includes the root span with the trace // COMPLETE includes all spans with the trace queryParams.append('view', 'COMPLETE'); // Type of data returned (MINIMAL, ROOTSPAN, COMPLETE) // Add orderBy parameter to sort by most recent traces first queryParams.append('orderBy', 'start desc'); // Sort by start time descending // Optional filter parameter if (filter) { queryParams.append('filter', filter); // Filter against labels for the request } // Construct the URL for the Cloud Trace API v1 endpoint // The correct endpoint format according to the documentation is: // GET https://cloudtrace.googleapis.com/v1/projects/{projectId}/traces const apiUrl = `https://cloudtrace.googleapis.com/v1/projects/${actualProjectId}/traces`; const requestUrl = `${apiUrl}?${queryParams.toString()}`; logger.debug(`List Traces URL: ${requestUrl}`); logger.debug(`List Traces Query Params: ${JSON.stringify(Object.fromEntries(queryParams.entries()))}`); logger.debug(`List Traces Time Range: ${startTimeDate.toISOString()} to ${endTime.toISOString()}`); logger.debug(`List Traces Raw Query String: ${queryParams.toString()}`); // Fetch traces from the Cloud Trace API logger.debug(`Sending request to Cloud Trace API: ${requestUrl}`); let tracesData; try { const response = await fetch( requestUrl, { method: 'GET', headers: { 'Authorization': `Bearer ${token.token}`, 'Accept': 'application/json' } } ); logger.debug(`List Traces Response Status: ${response.status}`); if (!response.ok) { const errorText = await response.text(); logger.error(`List Traces Error: ${errorText}`); throw new GcpMcpError( `Failed to list traces: ${errorText}`, 'FAILED_PRECONDITION', response.status ); } // Log the full response headers to help debug const responseHeaders: Record<string, string> = {}; response.headers.forEach((value, key) => { responseHeaders[key] = value; }); logger.debug(`List Traces Response Headers: ${JSON.stringify(responseHeaders)}`); tracesData = await response.json(); logger.debug(`List Traces Response Data: ${JSON.stringify(tracesData, null, 2)}`); } catch (fetchError: any) { logger.error(`Fetch error: ${fetchError.message}`); throw new GcpMcpError( `Failed to fetch traces: ${fetchError.message}`, 'INTERNAL', 500 ); } // 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}` }] }; } // Use the helper function to format the response return formatTracesResponse(tracesData, actualProjectId, startTimeDate, endTime, filter); } catch (error: any) { // Error handling for list-traces tool throw new GcpMcpError( `Failed to list traces: ${error.message}`, error.code || 'UNKNOWN', error.statusCode || 500 ); } } );
  • Helper function that formats the raw traces data from the API into a user-friendly Markdown table response.
    function formatTracesResponse( tracesData: any, projectId: string, startTime: Date, endTime: Date, filter?: string ): any { // Format the traces for display let markdown = `# Traces for ${projectId}\n\n`; markdown += `Time Range: ${startTime.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' : trace.status?.code > 0 ? '❌ ERROR' : '⚪ UNKNOWN'; markdown += `| \`${traceId}\` | ${displayName} | ${startTimeStr} | ${duration} | ${status} |\n`; } markdown += '\n\nTo view a specific trace, use the `get-trace` tool with the trace ID.'; return { content: [{ type: 'text', text: markdown }] }; }
  • Top-level registration call to registerTraceTools within the registerTraceService function, which registers all trace tools.
    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/andyl25/googlecloud-mcp'

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