Skip to main content
Glama

get-athlete-zones

Retrieve heart rate and power zones for the authenticated athlete, providing both a formatted summary and raw JSON data. Integrates with the Strava MCP Server for seamless data access.

Instructions

Retrieves the authenticated athlete's configured heart rate and power zones.

Output includes both a formatted summary and the raw JSON data.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • MCP tool definition and handler (execute function) for 'get-athlete-zones'. Fetches zones using Strava client, formats output with summary and raw JSON, handles auth and errors.
    export const getAthleteZonesTool = { name, description: description + "\n\nOutput includes both a formatted summary and the raw JSON data.", inputSchema, execute: async (_input: GetAthleteZonesInput) => { 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 athlete zones..."); const zonesData = await fetchAthleteZones(token); // Format the summary const formattedText = formatAthleteZones(zonesData); // Prepare the raw data const rawDataText = `\n\nRaw Athlete Zone Data:\n${JSON.stringify(zonesData, null, 2)}`; console.error("Successfully fetched athlete zones."); // Return both summary and raw data return { content: [ { type: "text" as const, text: formattedText }, { type: "text" as const, text: rawDataText } ] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.error(`Error fetching athlete zones: ${errorMessage}`); let userFriendlyMessage; // Check for common errors like missing scope (403 Forbidden) if (errorMessage.includes("403")) { userFriendlyMessage = "🔒 Access denied. This tool requires 'profile:read_all' permission. Please re-authorize with the correct scope."; } else if (errorMessage.startsWith("SUBSCRIPTION_REQUIRED:")) { // In case Strava changes this later userFriendlyMessage = `🔒 Accessing zones might require a Strava subscription. Details: ${errorMessage}`; } else { userFriendlyMessage = `An unexpected error occurred while fetching athlete zones. Details: ${errorMessage}`; } return { content: [{ type: "text" as const, text: `❌ ${userFriendlyMessage}` }], isError: true }; } } };
  • src/server.ts:156-161 (registration)
    Registers the 'get-athlete-zones' tool with the MCP server using server.tool().
    server.tool( getAthleteZonesTool.name, getAthleteZonesTool.description, getAthleteZonesTool.inputSchema?.shape ?? {}, getAthleteZonesTool.execute );
  • Zod schema (AthleteZonesSchema) and type (StravaAthleteZones) for validating the Strava /athlete/zones API response used by the tool.
    const AthleteZonesSchema = z.object({ heart_rate: HeartRateZoneSchema.optional(), // Heart rate zones might not be set power: PowerZoneSchema.optional(), // Power zones might not be set }); export type StravaAthleteZones = z.infer<typeof AthleteZonesSchema>;
  • Low-level helper function that makes the Strava API call to /athlete/zones, validates with Zod schema, and handles errors/token refresh. Called by the tool handler.
    export async function getAthleteZones(accessToken: string): Promise<StravaAthleteZones> { if (!accessToken) { throw new Error("Strava access token is required."); } try { const response = await stravaApi.get<unknown>("/athlete/zones", { headers: { Authorization: `Bearer ${accessToken}` }, }); const validationResult = AthleteZonesSchema.safeParse(response.data); if (!validationResult.success) { console.error(`Strava API validation failed (getAthleteZones):`, validationResult.error); throw new Error(`Invalid data format received from Strava API: ${validationResult.error.message}`); } return validationResult.data; } catch (error) { // Note: This endpoint requires profile:read_all scope // Handle potential 403 Forbidden if scope is missing, or 402 if it becomes sub-only? return await handleApiError<StravaAthleteZones>(error, `getAthleteZones`, async () => { // Use new token from environment after refresh const newToken = process.env.STRAVA_ACCESS_TOKEN!; return getAthleteZones(newToken); }); } }
  • Helper function to format the athlete zones data into a readable markdown summary string, used in the tool handler.
    function formatAthleteZones(zonesData: StravaAthleteZones): string { let responseText = "**Athlete Zones:**\n"; if (zonesData.heart_rate) { responseText += "\n❤️ **Heart Rate Zones**\n"; responseText += ` Custom Zones: ${zonesData.heart_rate.custom_zones ? 'Yes' : 'No'}\n`; zonesData.heart_rate.zones.forEach((zone, index) => { responseText += ` Zone ${index + 1}: ${formatZoneRange(zone)} bpm\n`; }); if (zonesData.heart_rate.distribution_buckets) { responseText += " Time Distribution:\n" + formatDistribution(zonesData.heart_rate.distribution_buckets) + "\n"; } } else { responseText += "\n❤️ Heart Rate Zones: Not configured\n"; } if (zonesData.power) { responseText += "\n⚡ **Power Zones**\n"; zonesData.power.zones.forEach((zone, index) => { responseText += ` Zone ${index + 1}: ${formatZoneRange(zone)} W\n`; }); if (zonesData.power.distribution_buckets) { responseText += " Time Distribution:\n" + formatDistribution(zonesData.power.distribution_buckets) + "\n"; } } else { responseText += "\n⚡ Power Zones: Not configured\n"; } return responseText; }

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