Skip to main content
Glama
mvilanova

Intervals.icu MCP Server

by mvilanova

get_wellness_data

Retrieve wellness metrics for an athlete from Intervals.icu by specifying athlete ID, API key, and date range. Defaults to past 30 days if no dates are provided.

Instructions

Get wellness data for an athlete from Intervals.icu

Args: athlete_id: The Intervals.icu athlete ID (optional, will use ATHLETE_ID from .env if not provided) api_key: The Intervals.icu API key (optional, will use API_KEY from .env if not provided) start_date: Start date in YYYY-MM-DD format (optional, defaults to 30 days ago) end_date: End date in YYYY-MM-DD format (optional, defaults to today)

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
api_keyNo
athlete_idNo
end_dateNo
start_dateNo

Implementation Reference

  • The primary handler function for the 'get_wellness_data' MCP tool. It fetches wellness data from the Intervals.icu API using make_intervals_request and formats the response using format_wellness_entry, handling both dict and list responses.
    @mcp.tool() async def get_wellness_data( athlete_id: str | None = None, api_key: str | None = None, start_date: str | None = None, end_date: str | None = None, ) -> str: """Get wellness data for an athlete from Intervals.icu Args: athlete_id: The Intervals.icu athlete ID (optional, will use ATHLETE_ID from .env if not provided) api_key: The Intervals.icu API key (optional, will use API_KEY from .env if not provided) start_date: Start date in YYYY-MM-DD format (optional, defaults to 30 days ago) end_date: End date in YYYY-MM-DD format (optional, defaults to today) """ # Use provided athlete_id or fall back to global ATHLETE_ID athlete_id_to_use = athlete_id if athlete_id is not None else ATHLETE_ID if not athlete_id_to_use: return "Error: No athlete ID provided and no default ATHLETE_ID found in environment variables." # Parse date parameters if not start_date: start_date = (datetime.now() - timedelta(days=30)).strftime("%Y-%m-%d") if not end_date: end_date = datetime.now().strftime("%Y-%m-%d") # Call the Intervals.icu API params = {"oldest": start_date, "newest": end_date} result = await make_intervals_request( url=f"/athlete/{athlete_id_to_use}/wellness", api_key=api_key, params=params ) if isinstance(result, dict) and "error" in result: return f"Error fetching wellness data: {result.get('message')}" # Format the response if not result: return ( f"No wellness data found for athlete {athlete_id_to_use} in the specified date range." ) wellness_summary = "Wellness Data:\n\n" # Handle both list and dictionary responses if isinstance(result, dict): for date_str, data in result.items(): # Add the date to the data dictionary if it's not already present if isinstance(data, dict) and "date" not in data: data["date"] = date_str wellness_summary += format_wellness_entry(data) + "\n\n" elif isinstance(result, list): for entry in result: if isinstance(entry, dict): wellness_summary += format_wellness_entry(entry) + "\n\n" return wellness_summary
  • Helper function used by get_wellness_data to format a single wellness data entry into a categorized, human-readable multi-line string covering training metrics, vitals, sleep, subjective feelings, etc.
    def format_wellness_entry(entries: dict[str, Any]) -> str: lines = ["Wellness Data:"] lines.append(f"Date: {entries.get('id', 'N/A')}") lines.append("") # Training Metrics training_metrics = [] for k, label in [ ("ctl", "Fitness (CTL)"), ("atl", "Fatigue (ATL)"), ("rampRate", "Ramp Rate"), ("ctlLoad", "CTL Load"), ("atlLoad", "ATL Load"), ]: if entries.get(k) is not None: training_metrics.append(f"- {label}: {entries[k]}") if training_metrics: lines.append("Training Metrics:") lines.extend(training_metrics) lines.append("") # Sport-Specific Info sport_info_list = [] if entries.get("sportInfo"): for sport in entries.get("sportInfo", []): if isinstance(sport, dict): if sport.get("eftp") is not None: sport_info_list.append( f"- {sport.get('type')}: eFTP = {sport['eftp']}" ) if sport_info_list: lines.append("Sport-Specific Info:") lines.extend(sport_info_list) lines.append("") # Vital Signs vital_signs = [] for k, label, unit in [ ("weight", "Weight", "kg"), ("restingHR", "Resting HR", "bpm"), ("hrv", "HRV", ""), ("hrvSDNN", "HRV SDNN", ""), ("avgSleepingHR", "Average Sleeping HR", "bpm"), ("spO2", "SpO2", "%"), ("systolic", "Systolic BP", ""), ("diastolic", "Diastolic BP", ""), ("respiration", "Respiration", "breaths/min"), ("bloodGlucose", "Blood Glucose", "mmol/L"), ("lactate", "Lactate", "mmol/L"), ("vo2max", "VO2 Max", "ml/kg/min"), ("bodyFat", "Body Fat", "%"), ("abdomen", "Abdomen", "cm"), ("baevskySI", "Baevsky Stress Index", ""), ]: if entries.get(k) is not None: value = entries[k] if k == "systolic" and entries.get("diastolic") is not None: vital_signs.append(f"-Blood Pressure: {entries['systolic']}/{entries['diastolic']} mmHg") elif k not in ("systolic", "diastolic"): vital_signs.append(f"- {label}: {value}{(' ' + unit) if unit else ''}") if vital_signs: lines.append("Vital Signs:") lines.extend(vital_signs) lines.append("") # Sleep & Recovery sleep_lines = [] sleep_hours = None if entries.get("sleepSecs") is not None: sleep_hours = f"{entries['sleepSecs'] / 3600:.2f}" elif entries.get("sleepHours") is not None: sleep_hours = f"{entries['sleepHours']}" if sleep_hours is not None: sleep_lines.append(f" Sleep: {sleep_hours} hours") for k, label, unit in [ ("sleepScore", "Sleep Score", "/100"), ("sleepQuality", "Sleep Quality", "/10"), ("readiness", "Readiness", "/10"), ]: if entries.get(k) is not None: sleep_lines.append(f" {label}: {entries[k]}{unit}") if sleep_lines: lines.append("Sleep & Recovery:") lines.extend(sleep_lines) lines.append("") # Menstrual Tracking menstrual_lines = [] if entries.get("menstrualPhase") is not None: menstrual_lines.append(f" Menstrual Phase: {str(entries['menstrualPhase']).capitalize()}") if entries.get("menstrualPhasePredicted") is not None: menstrual_lines.append(f" Predicted Phase: {str(entries['menstrualPhasePredicted']).capitalize()}") if menstrual_lines: lines.append("Menstrual Tracking:") lines.extend(menstrual_lines) lines.append("") # Subjective Feelings subjective_lines = [] for k, label in [ ("soreness", "Soreness"), ("fatigue", "Fatigue"), ("stress", "Stress"), ("mood", "Mood"), ("motivation", "Motivation"), ("injury", "Injury Level"), ]: if entries.get(k) is not None: subjective_lines.append(f" {label}: {entries[k]}/10") if subjective_lines: lines.append("Subjective Feelings:") lines.extend(subjective_lines) lines.append("") # Nutrition & Hydration nutrition_lines = [] for k, label in [ ("kcalConsumed", "Calories Consumed"), ("hydrationVolume", "Hydration Volume"), ]: if entries.get(k) is not None: nutrition_lines.append(f"- {label}: {entries[k]}") if entries.get("hydration") is not None: nutrition_lines.append(f" Hydration Score: {entries['hydration']}/10") if nutrition_lines: lines.append("Nutrition & Hydration:") lines.extend(nutrition_lines) lines.append("") # Activity if entries.get("steps") is not None: lines.append("Activity:") lines.append(f"- Steps: {entries['steps']}") lines.append("") # Comments, Status, Updated if entries.get("comments"): lines.append(f"Comments: {entries['comments']}") if "locked" in entries: lines.append(f"Status: {'Locked' if entries.get('locked') else 'Unlocked'}") return "\n".join(lines)

Other Tools

Related Tools

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/mvilanova/intervals-mcp-server'

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