Skip to main content
Glama
getAthleteStats.ts7.08 kB
// import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; // Removed import { z } from "zod"; import { // getAuthenticatedAthlete as fetchAuthenticatedAthlete, // Removed getAthleteStats as fetchAthleteStats, // handleApiError, // Removed unused import StravaStats // Type needed for formatter } from "../stravaClient.js"; // formatDuration is now local or in utils, not imported from server.ts // Input schema: Now requires athleteId const GetAthleteStatsInputSchema = z.object({ athleteId: z.number().int().positive().describe("The unique identifier of the athlete to fetch stats for. Obtain this ID first by calling the get-athlete-profile tool.") }); // Define type alias for input type GetAthleteStatsInput = z.infer<typeof GetAthleteStatsInputSchema>; // Remove unused formatDuration function /* 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(':'); } */ // Helper function to format numbers as strings with labels (metric) function formatStat(value: number | null | undefined, unit: 'km' | 'm' | 'hrs'): string { if (value === null || value === undefined) return 'N/A'; let formattedValue: string; if (unit === 'km') { formattedValue = (value / 1000).toFixed(2); } else if (unit === 'm') { formattedValue = Math.round(value).toString(); } else if (unit === 'hrs') { formattedValue = (value / 3600).toFixed(1); } else { formattedValue = value.toString(); } return `${formattedValue} ${unit}`; } // Format athlete stats (metric only) function formatStats(stats: StravaStats): string { const format = (label: string, total: number | null | undefined, unit: 'km' | 'm' | 'hrs', count?: number | null, time?: number | null) => { let line = ` - ${label}: ${formatStat(total, unit)}`; if (count !== undefined && count !== null) line += ` (${count} activities)`; if (time !== undefined && time !== null) line += ` / ${formatStat(time, 'hrs')} hours`; return line; }; let response = "📊 **Your Strava Stats:**\n"; if (stats.biggest_ride_distance !== undefined) { response += "**Rides:**\n"; response += format("Biggest Ride", stats.biggest_ride_distance, 'km') + '\n'; } if (stats.recent_ride_totals) { response += "*Recent Rides (last 4 weeks):*\n"; response += format("Distance", stats.recent_ride_totals.distance, 'km', stats.recent_ride_totals.count, stats.recent_ride_totals.moving_time) + '\n'; response += format("Elevation Gain", stats.recent_ride_totals.elevation_gain, 'm') + '\n'; } if (stats.ytd_ride_totals) { response += "*Year-to-Date Rides:*\n"; response += format("Distance", stats.ytd_ride_totals.distance, 'km', stats.ytd_ride_totals.count, stats.ytd_ride_totals.moving_time) + '\n'; response += format("Elevation Gain", stats.ytd_ride_totals.elevation_gain, 'm') + '\n'; } if (stats.all_ride_totals) { response += "*All-Time Rides:*\n"; response += format("Distance", stats.all_ride_totals.distance, 'km', stats.all_ride_totals.count, stats.all_ride_totals.moving_time) + '\n'; response += format("Elevation Gain", stats.all_ride_totals.elevation_gain, 'm') + '\n'; } // Similar blocks for Runs and Swims if needed... if (stats.recent_run_totals || stats.ytd_run_totals || stats.all_run_totals) { response += "\n**Runs:**\n"; if (stats.recent_run_totals) { response += "*Recent Runs (last 4 weeks):*\n"; response += format("Distance", stats.recent_run_totals.distance, 'km', stats.recent_run_totals.count, stats.recent_run_totals.moving_time) + '\n'; response += format("Elevation Gain", stats.recent_run_totals.elevation_gain, 'm') + '\n'; } if (stats.ytd_run_totals) { response += "*Year-to-Date Runs:*\n"; response += format("Distance", stats.ytd_run_totals.distance, 'km', stats.ytd_run_totals.count, stats.ytd_run_totals.moving_time) + '\n'; response += format("Elevation Gain", stats.ytd_run_totals.elevation_gain, 'm') + '\n'; } if (stats.all_run_totals) { response += "*All-Time Runs:*\n"; response += format("Distance", stats.all_run_totals.distance, 'km', stats.all_run_totals.count, stats.all_run_totals.moving_time) + '\n'; response += format("Elevation Gain", stats.all_run_totals.elevation_gain, 'm') + '\n'; } } // Add Swims similarly if needed return response; } // Tool definition export const getAthleteStatsTool = { name: "get-athlete-stats", description: "Fetches the activity statistics (recent, YTD, all-time) for a specific athlete using their ID. Requires the athleteId obtained from the get-athlete-profile tool.", inputSchema: GetAthleteStatsInputSchema, execute: async ({ athleteId }: GetAthleteStatsInput) => { 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 stats for athlete ${athleteId}...`); const stats = await fetchAthleteStats(token, athleteId); const formattedStats = formatStats(stats); console.error(`Successfully fetched stats for athlete ${athleteId}.`); return { content: [{ type: "text" as const, text: formattedStats }] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.error(`Error fetching stats for athlete ${athleteId}: ${errorMessage}`); const userFriendlyMessage = errorMessage.includes("Record Not Found") || errorMessage.includes("404") ? `Athlete with ID ${athleteId} not found (when fetching stats).` : `An unexpected error occurred while fetching stats for athlete ${athleteId}. Details: ${errorMessage}`; return { content: [{ type: "text" as const, text: `❌ ${userFriendlyMessage}` }], isError: true }; } } }; // Removed old registration function /* export function registerGetAthleteStatsTool(server: McpServer) { server.tool( getAthleteStats.name, getAthleteStats.description, getAthleteStats.execute // No input schema ); } */

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