Skip to main content
Glama

get-all-activities

Fetch your complete Strava activity history with filtering options for date range, activity type, and sport type. Supports pagination to retrieve all activities efficiently.

Instructions

Fetches complete activity history with optional filtering by date range and activity type. Supports pagination to retrieve all activities.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
startDateNoISO date string for activities after this date (e.g., '2024-01-01')
endDateNoISO date string for activities before this date (e.g., '2024-12-31')
activityTypesNoArray of activity types to filter (e.g., ['Run', 'Ride'])
sportTypesNoArray of sport types for granular filtering (e.g., ['MountainBikeRide', 'TrailRun'])
maxActivitiesNoMaximum activities to return after filtering (default: 500)
maxApiCallsNoMaximum API calls to prevent quota exhaustion (default: 10 = ~2000 activities)
perPageNoActivities per API call (default: 200, max: 200)

Implementation Reference

  • The core handler function that orchestrates fetching all Strava activities with date/type filtering, pagination via helper, result limiting, summary formatting with emojis/URLs/stats, and comprehensive error handling including rate limits.
    execute: async (input: GetAllActivitiesInput) => { const token = process.env.STRAVA_ACCESS_TOKEN; if (!token || token === 'YOUR_STRAVA_ACCESS_TOKEN_HERE') { console.error("Missing or placeholder STRAVA_ACCESS_TOKEN in .env"); return { content: [{ type: "text" as const, text: "❌ Configuration Error: STRAVA_ACCESS_TOKEN is missing or not set in the .env file." }], isError: true, }; } const { startDate, endDate, activityTypes, sportTypes, maxActivities = 500, maxApiCalls = 10, perPage = 200 } = input; try { // Convert dates to epoch timestamps if provided const before = endDate ? Math.floor(new Date(endDate).getTime() / 1000) : undefined; const after = startDate ? Math.floor(new Date(startDate).getTime() / 1000) : undefined; // Validate date inputs if (before && isNaN(before)) { return { content: [{ type: "text" as const, text: "❌ Invalid endDate format. Please use ISO date format (e.g., '2024-12-31')." }], isError: true }; } if (after && isNaN(after)) { return { content: [{ type: "text" as const, text: "❌ Invalid startDate format. Please use ISO date format (e.g., '2024-01-01')." }], isError: true }; } console.error(`Fetching activities with filters:`); console.error(` Date range: ${startDate || 'any'} to ${endDate || 'any'}`); console.error(` Activity types: ${activityTypes?.join(', ') || 'any'}`); console.error(` Sport types: ${sportTypes?.join(', ') || 'any'}`); console.error(` Max activities: ${maxActivities}, Max API calls: ${maxApiCalls}`); const allActivities: any[] = []; const filteredActivities: any[] = []; let apiCalls = 0; let currentPage = 1; let hasMore = true; // Progress callback const onProgress = (fetched: number, page: number) => { console.error(` Page ${page}: Fetched ${fetched} total activities...`); }; // Fetch activities page by page while (hasMore && apiCalls < maxApiCalls && filteredActivities.length < maxActivities) { apiCalls++; // Fetch a page of activities const pageActivities = await fetchAllActivities(token, { page: currentPage, perPage, before, after, onProgress }); // Check if we got any activities if (pageActivities.length === 0) { hasMore = false; break; } // Add to all activities allActivities.push(...pageActivities); // Apply filters if specified let toFilter = pageActivities; // Filter by activity type if (activityTypes && activityTypes.length > 0) { toFilter = toFilter.filter(a => activityTypes.some(type => a.type?.toLowerCase() === type.toLowerCase() ) ); } // Filter by sport type (more specific) if (sportTypes && sportTypes.length > 0) { toFilter = toFilter.filter(a => sportTypes.some(type => a.sport_type?.toLowerCase() === type.toLowerCase() ) ); } // Add filtered activities filteredActivities.push(...toFilter); // Check if we should continue hasMore = pageActivities.length === perPage; currentPage++; // Log progress console.error(` After page ${currentPage - 1}: ${allActivities.length} fetched, ${filteredActivities.length} match filters`); } // Limit results to maxActivities const resultsToReturn = filteredActivities.slice(0, maxActivities); // Prepare summary statistics const stats = { totalFetched: allActivities.length, totalMatching: filteredActivities.length, returned: resultsToReturn.length, apiCalls: apiCalls }; console.error(`\nFetch complete:`); console.error(` Total activities fetched: ${stats.totalFetched}`); console.error(` Activities matching filters: ${stats.totalMatching}`); console.error(` Activities returned: ${stats.returned}`); console.error(` API calls made: ${stats.apiCalls}`); if (resultsToReturn.length === 0) { return { content: [{ type: "text" as const, text: `No activities found matching your criteria.\n\nStatistics:\n- Fetched ${stats.totalFetched} activities\n- ${stats.totalMatching} matched filters\n- Used ${stats.apiCalls} API calls` }] }; } // Format activities for display const summaries = resultsToReturn.map(activity => formatActivitySummary(activity)); // Build response text let responseText = `**Found ${stats.returned} activities**\n\n`; responseText += `📊 Statistics:\n`; responseText += `- Total fetched: ${stats.totalFetched}\n`; responseText += `- Matching filters: ${stats.totalMatching}\n`; responseText += `- API calls: ${stats.apiCalls}\n\n`; if (stats.returned < stats.totalMatching) { responseText += `⚠️ Showing first ${stats.returned} of ${stats.totalMatching} matching activities (limited by maxActivities)\n\n`; } responseText += `**Activities:**\n${summaries.join('\n')}`; return { content: [{ type: "text" as const, text: responseText }] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : "An unknown error occurred"; console.error("Error in get-all-activities tool:", errorMessage); // Check for rate limiting if (errorMessage.includes('429')) { return { content: [{ type: "text" as const, text: `⚠️ Rate limit reached. Please wait a few minutes before trying again.\n\nStrava API limits: 100 requests per 15 minutes, 1000 per day.` }], isError: true, }; } return { content: [{ type: "text" as const, text: `❌ API Error: ${errorMessage}` }], isError: true, }; } }
  • Zod input schema defining optional parameters for date range, activity/sport type filters, result limits, and pagination controls.
    const GetAllActivitiesInputSchema = z.object({ startDate: z.string().optional().describe("ISO date string for activities after this date (e.g., '2024-01-01')"), endDate: z.string().optional().describe("ISO date string for activities before this date (e.g., '2024-12-31')"), activityTypes: z.array(z.string()).optional().describe("Array of activity types to filter (e.g., ['Run', 'Ride'])"), sportTypes: z.array(z.string()).optional().describe("Array of sport types for granular filtering (e.g., ['MountainBikeRide', 'TrailRun'])"), maxActivities: z.number().int().positive().optional().default(500).describe("Maximum activities to return after filtering (default: 500)"), maxApiCalls: z.number().int().positive().optional().default(10).describe("Maximum API calls to prevent quota exhaustion (default: 10 = ~2000 activities)"), perPage: z.number().int().positive().min(1).max(200).optional().default(200).describe("Activities per API call (default: 200, max: 200)") });
  • src/server.ts:164-170 (registration)
    MCP server registration of the 'get-all-activities' tool using the imported tool object.
    // --- Register get-all-activities tool --- server.tool( getAllActivities.name, getAllActivities.description, getAllActivities.inputSchema?.shape ?? {}, getAllActivities.execute );
  • Helper function to format individual activity summaries with emoji, distance, duration, date, and Strava URL.
    function formatActivitySummary(activity: any): string { const date = activity.start_date ? new Date(activity.start_date).toLocaleDateString() : 'N/A'; const distance = activity.distance ? `${(activity.distance / 1000).toFixed(2)} km` : 'N/A'; const duration = activity.moving_time ? formatDuration(activity.moving_time) : 'N/A'; const type = activity.sport_type || activity.type || 'Unknown'; const activityId = activity.id ?? 'N/A'; let emoji = '🏃'; if (type.toLowerCase().includes('ride') || type.toLowerCase().includes('bike')) emoji = '🚴'; else if (type.toLowerCase().includes('swim')) emoji = '🏊'; else if (type.toLowerCase().includes('ski')) emoji = '⛷️'; else if (type.toLowerCase().includes('hike') || type.toLowerCase().includes('walk')) emoji = '🥾'; else if (type.toLowerCase().includes('yoga')) emoji = '🧘'; else if (type.toLowerCase().includes('weight')) emoji = '💪'; const stravaUrl = activityId !== 'N/A' ? `https://www.strava.com/activities/${activityId}` : ''; return `${emoji} ${activity.name} (ID: ${activityId}) - ${type} - ${distance} in ${duration} on ${date}${stravaUrl ? `\n URL: ${stravaUrl}` : ''}`; }
  • Core API client helper for paginated retrieval of all athlete activities from Strava API, used by the tool handler.
    export async function getAllActivities( accessToken: string, params: GetAllActivitiesParams = {} ): Promise<any[]> { if (!accessToken) { throw new Error("Strava access token is required."); } const { page = 1, perPage = 200, // Max allowed by Strava before, after, onProgress } = params; const allActivities: any[] = []; let currentPage = page; let hasMore = true; try { while (hasMore) { // Build query parameters const queryParams: Record<string, any> = { page: currentPage, per_page: perPage }; // Add date filters if provided if (before !== undefined) queryParams.before = before; if (after !== undefined) queryParams.after = after; // Fetch current page const response = await stravaApi.get<unknown>("athlete/activities", { headers: { Authorization: `Bearer ${accessToken}` }, params: queryParams }); const validationResult = StravaActivitiesResponseSchema.safeParse(response.data); if (!validationResult.success) { console.error(`Strava API response validation failed (getAllActivities page ${currentPage}):`, validationResult.error); throw new Error(`Invalid data format received from Strava API: ${validationResult.error.message}`); } const activities = validationResult.data; // Add activities to collection allActivities.push(...activities); // Report progress if callback provided if (onProgress) { onProgress(allActivities.length, currentPage); } // Check if we should continue // Stop if we got fewer activities than requested (indicating last page) hasMore = activities.length === perPage; currentPage++; // Add a small delay to be respectful of rate limits if (hasMore) { await new Promise(resolve => setTimeout(resolve, 100)); } } return allActivities; } catch (error) { // If it's an auth error and we're on first page, try token refresh if (currentPage === 1) { return await handleApiError<any[]>(error, 'getAllActivities', async () => { const newToken = process.env.STRAVA_ACCESS_TOKEN!; return getAllActivities(newToken, params); }); } // For subsequent pages, just throw the error throw error; } }

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