Skip to main content
Glama

get-activity-laps

Retrieve detailed lap data from Strava activities to analyze performance metrics, compare segments, and visualize training progress.

Instructions

Retrieves detailed lap data for a specific Strava activity.

Use Cases:

  • Get complete lap data including timestamps, speeds, and metrics

  • Access raw values for detailed analysis or visualization

  • Extract specific lap metrics for comparison or tracking

Parameters:

  • id (required): The unique identifier of the Strava activity.

Output Format: Returns both a human-readable summary and complete JSON data for each lap, including:

  1. A text summary with formatted metrics

  2. Raw lap data containing all fields from the Strava API:

    • Unique lap ID and indices

    • Timestamps (start_date, start_date_local)

    • Distance and timing metrics

    • Speed metrics (average and max)

    • Performance metrics (heart rate, cadence, power if available)

    • Elevation data

    • Resource state information

    • Activity and athlete references

Notes:

  • Requires activity:read scope for public/followers activities, activity:read_all for private activities

  • Returns complete data as received from Strava API without omissions

  • All numeric values are preserved in their original precision

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
idYesThe identifier of the activity to fetch laps for.

Implementation Reference

  • The execute function implementing the core tool logic: fetches laps from Strava API via client helper, generates human-readable summary and raw JSON output.
    execute: async ({ id }: GetActivityLapsInput) => { const token = process.env.STRAVA_ACCESS_TOKEN; if (!token) { console.error("Missing STRAVA_ACCESS_TOKEN environment variable."); return { content: [{ type: "text" as const, text: "Configuration error: Missing Strava access token." }], isError: true }; } try { console.error(`Fetching laps for activity ID: ${id}...`); const laps = await getActivityLapsClient(token, id); if (!laps || laps.length === 0) { return { content: [{ type: "text" as const, text: `✅ No laps found for activity ID: ${id}` }] }; } // Generate human-readable summary const lapSummaries = laps.map(lap => { const details = [ `Lap ${lap.lap_index}: ${lap.name || 'Unnamed Lap'}`, ` Time: ${formatDuration(lap.elapsed_time)} (Moving: ${formatDuration(lap.moving_time)})`, ` Distance: ${(lap.distance / 1000).toFixed(2)} km`, ` Avg Speed: ${lap.average_speed ? (lap.average_speed * 3.6).toFixed(2) + ' km/h' : 'N/A'}`, ` Max Speed: ${lap.max_speed ? (lap.max_speed * 3.6).toFixed(2) + ' km/h' : 'N/A'}`, lap.total_elevation_gain ? ` Elevation Gain: ${lap.total_elevation_gain.toFixed(1)} m` : null, lap.average_heartrate ? ` Avg HR: ${lap.average_heartrate.toFixed(1)} bpm` : null, lap.max_heartrate ? ` Max HR: ${lap.max_heartrate} bpm` : null, lap.average_cadence ? ` Avg Cadence: ${lap.average_cadence.toFixed(1)} rpm` : null, lap.average_watts ? ` Avg Power: ${lap.average_watts.toFixed(1)} W ${lap.device_watts ? '(Sensor)' : ''}` : null, ]; return details.filter(d => d !== null).join('\n'); }); const summaryText = `Activity Laps Summary (ID: ${id}):\n\n${lapSummaries.join('\n\n')}`; // Add raw data section const rawDataText = `\n\nComplete Lap Data:\n${JSON.stringify(laps, null, 2)}`; console.error(`Successfully fetched ${laps.length} laps for activity ${id}`); return { content: [ { type: "text" as const, text: summaryText }, { type: "text" as const, text: rawDataText } ] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.error(`Error fetching laps for activity ${id}: ${errorMessage}`); const userFriendlyMessage = errorMessage.includes("Record Not Found") || errorMessage.includes("404") ? `Activity with ID ${id} not found.` : `An unexpected error occurred while fetching laps for activity ${id}. Details: ${errorMessage}`; return { content: [{ type: "text" as const, text: `❌ ${userFriendlyMessage}` }], isError: true }; }
  • Zod input schema defining the required 'id' parameter for the activity.
    const inputSchema = z.object({ id: z.union([z.number(), z.string()]).describe("The identifier of the activity to fetch laps for."), });
  • src/server.ts:149-154 (registration)
    Registration of the get-activity-laps tool with the MCP server using server.tool().
    server.tool( getActivityLapsTool.name, getActivityLapsTool.description, getActivityLapsTool.inputSchema?.shape ?? {}, getActivityLapsTool.execute );
  • The Strava API client function that retrieves laps data from /activities/{id}/laps endpoint, with Zod validation and error handling including token refresh.
    export async function getActivityLaps(accessToken: string, activityId: number | string): Promise<StravaLap[]> { if (!accessToken) { throw new Error("Strava access token is required."); } try { const response = await stravaApi.get(`/activities/${activityId}/laps`, { headers: { Authorization: `Bearer ${accessToken}` }, }); const validationResult = StravaLapsResponseSchema.safeParse(response.data); if (!validationResult.success) { console.error(`Strava API validation failed (getActivityLaps: ${activityId}):`, validationResult.error); throw new Error(`Invalid data format received from Strava API: ${validationResult.error.message}`); } return validationResult.data; } catch (error) { return await handleApiError<StravaLap[]>(error, `getActivityLaps(${activityId})`, async () => { // Use new token from environment after refresh const newToken = process.env.STRAVA_ACCESS_TOKEN!; return getActivityLaps(newToken, activityId); }); } }
  • Helper function to format seconds into HH:MM:SS or MM:SS string, used in lap summaries.
    export function formatDuration(seconds: number): string { if (isNaN(seconds) || seconds < 0) { return 'N/A'; } const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = Math.floor(seconds % 60); const parts: string[] = []; if (hours > 0) { parts.push(hours.toString().padStart(2, '0')); } parts.push(minutes.toString().padStart(2, '0')); parts.push(secs.toString().padStart(2, '0')); return parts.join(':'); }

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/r-huijts/strava-mcp'

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