Skip to main content
Glama
getAthleteZones.ts4.73 kB
import { z } from "zod"; import { getAthleteZones as fetchAthleteZones, StravaAthleteZones } from "../stravaClient.js"; import { formatDuration } from "../server.js"; // Shared helper const name = "get-athlete-zones"; const description = "Retrieves the authenticated athlete's configured heart rate and power zones."; // No input schema needed for this tool const inputSchema = z.object({}); type GetAthleteZonesInput = z.infer<typeof inputSchema>; // Helper to format a single zone range function formatZoneRange(zone: { min: number; max?: number }): string { return zone.max ? `${zone.min} - ${zone.max}` : `${zone.min}+`; } // Helper to format distribution buckets function formatDistribution(buckets: { max: number; min: number; time: number }[] | undefined): string { if (!buckets || buckets.length === 0) return " Distribution data not available."; return buckets.map(bucket => ` - ${bucket.min}-${bucket.max === -1 ? '∞' : bucket.max}: ${formatDuration(bucket.time)}` ).join('\n'); } // Format the zones response 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; } 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 }; } } };

Implementation Reference

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