Skip to main content
Glama
strava-client.ts8.46 kB
import { StravaAuth } from './auth.js'; import { StravaActivity, StravaAthlete, StravaAthleteStats, StravaRoute, StravaSegment, StravaLeaderboard, StravaClub, StravaGear, StravaZones, StravaActivityStream, CreateActivityParams, UpdateActivityParams, } from './types/strava.js'; export class StravaClient { private auth: StravaAuth; private baseUrl = 'https://www.strava.com/api/v3'; constructor(auth: StravaAuth) { this.auth = auth; } /** * Make authenticated request to Strava API */ private async request<T>( endpoint: string, options: RequestInit = {} ): Promise<T> { const accessToken = await this.auth.getValidAccessToken(); const response = await fetch(`${this.baseUrl}${endpoint}`, { ...options, headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', ...options.headers, }, }); if (!response.ok) { const error = await response.json().catch(() => ({ message: response.statusText })); throw new Error( `Strava API error: ${error.message || response.statusText} (${response.status})` ); } return response.json() as Promise<T>; } // ========== ATHLETE APIs ========== /** * Get the authenticated athlete */ async getAthlete(): Promise<StravaAthlete> { return this.request<StravaAthlete>('/athlete'); } /** * Get athlete stats */ async getAthleteStats(athleteId: number): Promise<StravaAthleteStats> { return this.request<StravaAthleteStats>(`/athletes/${athleteId}/stats`); } /** * Get athlete zones */ async getAthleteZones(): Promise<StravaZones> { return this.request<StravaZones>('/athlete/zones'); } // ========== ACTIVITY APIs ========== /** * Get logged-in athlete activities */ async getActivities(params?: { before?: number; after?: number; page?: number; per_page?: number; }): Promise<StravaActivity[]> { const queryParams = new URLSearchParams(); if (params?.before) queryParams.set('before', params.before.toString()); if (params?.after) queryParams.set('after', params.after.toString()); if (params?.page) queryParams.set('page', params.page.toString()); if (params?.per_page) queryParams.set('per_page', params.per_page.toString()); const query = queryParams.toString(); return this.request<StravaActivity[]>( `/athlete/activities${query ? `?${query}` : ''}` ); } /** * Get activity by ID */ async getActivity(id: number, includeAllEfforts: boolean = false): Promise<StravaActivity> { return this.request<StravaActivity>( `/activities/${id}?include_all_efforts=${includeAllEfforts}` ); } /** * Create a new activity */ async createActivity(params: CreateActivityParams): Promise<StravaActivity> { return this.request<StravaActivity>('/activities', { method: 'POST', body: JSON.stringify(params), }); } /** * Update an activity */ async updateActivity( id: number, params: UpdateActivityParams ): Promise<StravaActivity> { return this.request<StravaActivity>(`/activities/${id}`, { method: 'PUT', body: JSON.stringify(params), }); } /** * Delete an activity */ async deleteActivity(id: number): Promise<void> { await this.request<void>(`/activities/${id}`, { method: 'DELETE', }); } /** * Get activity streams (GPS, heart rate, power, etc.) */ async getActivityStreams( id: number, keys: string[] = ['time', 'latlng', 'distance', 'altitude', 'heartrate', 'watts'], keyByType: boolean = true ): Promise<StravaActivityStream[]> { const keysParam = keys.join(','); return this.request<StravaActivityStream[]>( `/activities/${id}/streams?keys=${keysParam}&key_by_type=${keyByType}` ); } /** * Get activity comments */ async getActivityComments( id: number, page: number = 1, perPage: number = 30 ): Promise<unknown[]> { return this.request<unknown[]>( `/activities/${id}/comments?page=${page}&per_page=${perPage}` ); } /** * Get activity kudos */ async getActivityKudos( id: number, page: number = 1, perPage: number = 30 ): Promise<unknown[]> { return this.request<unknown[]>( `/activities/${id}/kudos?page=${page}&per_page=${perPage}` ); } // ========== ROUTE APIs ========== /** * Get athlete routes */ async getRoutes(athleteId: number, page: number = 1, perPage: number = 30): Promise<StravaRoute[]> { return this.request<StravaRoute[]>( `/athletes/${athleteId}/routes?page=${page}&per_page=${perPage}` ); } /** * Get route by ID */ async getRoute(id: number): Promise<StravaRoute> { return this.request<StravaRoute>(`/routes/${id}`); } // ========== SEGMENT APIs ========== /** * Get starred segments */ async getStarredSegments(page: number = 1, perPage: number = 30): Promise<StravaSegment[]> { return this.request<StravaSegment[]>( `/segments/starred?page=${page}&per_page=${perPage}` ); } /** * Get segment by ID */ async getSegment(id: number): Promise<StravaSegment> { return this.request<StravaSegment>(`/segments/${id}`); } /** * Get segment leaderboard */ async getSegmentLeaderboard( id: number, params?: { gender?: 'M' | 'F'; age_group?: string; weight_class?: string; following?: boolean; club_id?: number; date_range?: string; page?: number; per_page?: number; } ): Promise<StravaLeaderboard> { const queryParams = new URLSearchParams(); if (params?.gender) queryParams.set('gender', params.gender); if (params?.age_group) queryParams.set('age_group', params.age_group); if (params?.weight_class) queryParams.set('weight_class', params.weight_class); if (params?.following !== undefined) queryParams.set('following', params.following.toString()); if (params?.club_id) queryParams.set('club_id', params.club_id.toString()); if (params?.date_range) queryParams.set('date_range', params.date_range); if (params?.page) queryParams.set('page', params.page.toString()); if (params?.per_page) queryParams.set('per_page', params.per_page.toString()); const query = queryParams.toString(); return this.request<StravaLeaderboard>( `/segments/${id}/leaderboard${query ? `?${query}` : ''}` ); } /** * Explore segments in a geographic area */ async exploreSegments(params: { bounds: [number, number, number, number]; // [sw_lat, sw_lng, ne_lat, ne_lng] activity_type?: 'running' | 'riding'; min_cat?: number; max_cat?: number; }): Promise<{ segments: StravaSegment[] }> { const queryParams = new URLSearchParams({ bounds: params.bounds.join(','), }); if (params.activity_type) queryParams.set('activity_type', params.activity_type); if (params.min_cat !== undefined) queryParams.set('min_cat', params.min_cat.toString()); if (params.max_cat !== undefined) queryParams.set('max_cat', params.max_cat.toString()); return this.request<{ segments: StravaSegment[] }>( `/segments/explore?${queryParams.toString()}` ); } // ========== CLUB APIs ========== /** * Get athlete clubs */ async getAthleteClubs(page: number = 1, perPage: number = 30): Promise<StravaClub[]> { return this.request<StravaClub[]>( `/athlete/clubs?page=${page}&per_page=${perPage}` ); } /** * Get club by ID */ async getClub(id: number): Promise<StravaClub> { return this.request<StravaClub>(`/clubs/${id}`); } /** * Get club members */ async getClubMembers( id: number, page: number = 1, perPage: number = 30 ): Promise<StravaAthlete[]> { return this.request<StravaAthlete[]>( `/clubs/${id}/members?page=${page}&per_page=${perPage}` ); } /** * Get club activities */ async getClubActivities( id: number, page: number = 1, perPage: number = 30 ): Promise<StravaActivity[]> { return this.request<StravaActivity[]>( `/clubs/${id}/activities?page=${page}&per_page=${perPage}` ); } // ========== GEAR APIs ========== /** * Get gear by ID */ async getGear(id: string): Promise<StravaGear> { return this.request<StravaGear>(`/gear/${id}`); } }

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

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