import json from mcp.server.fastmcp import FastMCP, Context from mcp.types import TextContent from strava_mcp_server.strava_client import StravaClient def register(mcp: FastMCP, strava: StravaClient) -> None: @mcp.tool() async def get_athlete_profile(ctx: Context) -> list[TextContent]: """ Get the authenticated Strava athlete's profile. Returns name, city, country, follower count, and other profile details. """ try: await ctx.info("Fetching athlete profile...") athlete = await strava.get_athlete() from datetime import datetime def format_date(d_str): if not d_str: return "N/A" try: d = datetime.fromisoformat(d_str.replace('Z', '+00:00')) return f"{d.day}.{d.month}.{d.year}" except Exception: return d_str location_parts = [p for p in (athlete.get('city'), athlete.get('state'), athlete.get('country')) if p] location = ", ".join(location_parts) if location_parts else "N/A" essential_data = { "id": athlete.get("id"), "username": athlete.get("username"), "name": f"{athlete.get('firstname')} {athlete.get('lastname')}".strip(), "location": location, "sex": athlete.get("sex"), "weight": athlete.get("weight"), "measurement_units": athlete.get("measurement_preference"), "is_premium": athlete.get("premium", False), "profile_medium": athlete.get("profile_medium"), "created_at": athlete.get("created_at"), "updated_at": athlete.get("updated_at"), "bio": athlete.get("bio"), "follower_count": athlete.get("follower_count"), "friend_count": athlete.get("friend_count"), } markdown_summary = f""" 👤 **Profile for {essential_data['name']}** (ID: {essential_data['id']}) - Username: {essential_data['username'] or 'N/A'} - Location: {essential_data['location']} - Sex: {essential_data['sex'] or 'N/A'} - Weight: {essential_data['weight'] or 'N/A'} kg - Measurement Units: {essential_data['measurement_units'] or 'N/A'} - Strava Summit Member: {'Yes' if essential_data['is_premium'] else 'No'} - Profile Image (Medium): {essential_data['profile_medium'] or 'N/A'} - Joined Strava: {format_date(essential_data['created_at'])} - Last Updated: {format_date(essential_data['updated_at'])} """.strip() return [ TextContent(type="text", text=markdown_summary), TextContent( type="text", text=f"Raw Data for Analysis: {json.dumps(essential_data, indent=2)}" ) ] except Exception as e: error_msg = f"Error fetching athlete profile: {str(e)}" await ctx.error(error_msg) return [TextContent(type="text", text=error_msg)] @mcp.tool() async def get_athlete_zones(): """ Get the heart rate and power zones configured for the authenticated athlete. Returns zone boundaries for both heart rate and power (if a power meter is configured). """ try: return await strava.get_athlete_zones() except Exception as e: return f"Error fetching athlete zones: {str(e)}" @mcp.tool() async def get_athlete_stats(): """ Get cumulative training statistics for the authenticated Strava athlete. Includes all-time, year-to-date, and recent (4-week) totals for runs, rides, and swims. """ import json try: stats = await strava.get_athlete_stats() def fmt_sport(s: dict) -> dict: return { "count": s.get("count", 0), "distance": f"{s.get('distance', 0) / 1000:.1f} km", "moving_time": f"{s.get('moving_time', 0) / 3600:.1f} h", "elevation_gain": f"{s.get('elevation_gain', 0):.0f} m", } result = { "all_time": { "runs": fmt_sport(stats.get("all_run_totals", {})), "rides": fmt_sport(stats.get("all_ride_totals", {})), "swims": fmt_sport(stats.get("all_swim_totals", {})), }, "ytd": { "runs": fmt_sport(stats.get("ytd_run_totals", {})), "rides": fmt_sport(stats.get("ytd_ride_totals", {})), "swims": fmt_sport(stats.get("ytd_swim_totals", {})), }, "recent_4_weeks": { "runs": fmt_sport(stats.get("recent_run_totals", {})), "rides": fmt_sport(stats.get("recent_ride_totals", {})), "swims": fmt_sport(stats.get("recent_swim_totals", {})), }, } return json.dumps(result, indent=2) except Exception as e: return f"Error fetching athlete stats: {str(e)}"