#
tokens: 14147/50000 18/18 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .gitignore
├── LICENSE
├── pyproject.toml
├── README.md
├── src
│   └── garmin_mcp
│       ├── __init__.py
│       ├── activity_management.py
│       ├── challenges.py
│       ├── data_management.py
│       ├── devices.py
│       ├── gear_management.py
│       ├── health_wellness.py
│       ├── training.py
│       ├── user_profile.py
│       ├── weight_management.py
│       ├── womens_health.py
│       └── workouts.py
├── test_mcp_server.py
├── tests
│   ├── test_garmin.py
│   └── test_mcp_debug.py
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
__pycache__
.env
.venv/

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
[![MseeP.ai Security Assessment Badge](https://mseep.net/pr/taxuspt-garmin-mcp-badge.png)](https://mseep.ai/app/taxuspt-garmin-mcp)

# Garmin MCP Server

This Model Context Protocol (MCP) server connects to Garmin Connect and exposes your fitness and health data to Claude and other MCP-compatible clients.

## Features

- List recent activities
- Get detailed activity information
- Access health metrics (steps, heart rate, sleep)
- View body composition data

## Setup

1. Install the required packages on a new environment:

```bash
uv sync
```

## Running the Server

### With Claude Desktop

1. Create a configuration in Claude Desktop:

Edit your Claude Desktop configuration file:

- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`

Add this server configuration:

```json
{
  "mcpServers": {
    "garmin": {
      "command": "uvx",
      "args": [
        "--python",
        "3.12",
        "--from",
        "git+https://github.com/Taxuspt/garmin_mcp",
        "garmin-mcp"
      ],
      "env": {
        "GARMIN_EMAIL": "YOUR_GARMIN_EMAIL",
        "GARMIN_PASSWORD": "YOUR_GARMIN_PASSWORD"
      }
    }
  }
}
```

Replace the path with the absolute path to your server file.

2. Restart Claude Desktop

### With MCP Inspector

For testing, you can use the MCP Inspector from the project root:

```bash
npx @modelcontextprotocol/inspector uv run garmin-mcp
```

## Usage Examples

Once connected in Claude, you can ask questions like:

- "Show me my recent activities"
- "What was my sleep like last night?"
- "How many steps did I take yesterday?"
- "Show me the details of my latest run"

## Security Note

## Troubleshooting

If you encounter login issues:

1. Verify your credentials are correct
2. Check if Garmin Connect requires additional verification
3. Ensure the garminconnect package is up to date

For other issues, check the Claude Desktop logs at:

- macOS: `~/Library/Logs/Claude/mcp-server-garmin.log`
- Windows: `%APPDATA%\Claude\logs\mcp-server-garmin.log`

```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
[build-system]
requires = [ "hatchling",]
build-backend = "hatchling.build"

[project]
name = "garmin-mcp"
version = "0.1.0"
description = "MCP server to access Garmin data"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
    "python-dotenv==1.0.1",
    "garminconnect==0.2.25",
    "requests==2.32.3",
    "mcp==1.3.0",
    "garth==0.5.2",
]

[project.scripts]
garmin-mcp = "garmin_mcp:main"

[tool.uv.sources]
garmin-mcp = { workspace = true }

```

--------------------------------------------------------------------------------
/tests/test_mcp_debug.py:
--------------------------------------------------------------------------------

```python
"""
Debug version of the MCP server for direct testing
"""

import asyncio
import datetime
import os
from pathlib import Path

from dotenv import load_dotenv
from garminconnect import Garmin

# Load environment variables from .env file
env_path = Path(__file__).parent / '.env'
load_dotenv(dotenv_path=env_path)

# Direct test function
async def test_direct():
    # Get credentials from environment
    email = os.environ.get("GARMIN_EMAIL")
    password = os.environ.get("GARMIN_PASSWORD")

    print(f"Logging in with email: {email}")

    try:
        # Create and initialize Garmin client
        client = Garmin(email, password)
        client.login()
        print("Login successful!")

        # Test activities
        print("\nGetting recent activities...")
        activities = client.get_activities(0, 2)

        if activities:
            print(f"Found {len(activities)} activities")
            for idx, activity in enumerate(activities, 1):
                print(f"\n--- Activity {idx} ---")
                print(f"Name: {activity.get('activityName', 'Unknown')}")
        else:
            print("No activities found")

        print("\nTest completed successfully!")

    except Exception as e:
        print(f"Error: {str(e)}")

if __name__ == "__main__":
    asyncio.run(test_direct())

```

--------------------------------------------------------------------------------
/src/garmin_mcp/user_profile.py:
--------------------------------------------------------------------------------

```python
"""
User Profile functions for Garmin Connect MCP Server
"""
import datetime
from typing import Any, Dict, List, Optional, Union

# The garmin_client will be set by the main file
garmin_client = None


def configure(client):
    """Configure the module with the Garmin client instance"""
    global garmin_client
    garmin_client = client


def register_tools(app):
    """Register all user profile tools with the MCP server app"""
    
    @app.tool()
    async def get_full_name() -> str:
        """Get user's full name from profile"""
        try:
            full_name = garmin_client.get_full_name()
            return full_name
        except Exception as e:
            return f"Error retrieving user's full name: {str(e)}"

    @app.tool()
    async def get_unit_system() -> str:
        """Get user's preferred unit system from profile"""
        try:
            unit_system = garmin_client.get_unit_system()
            return unit_system
        except Exception as e:
            return f"Error retrieving unit system: {str(e)}"
    
    @app.tool()
    async def get_user_profile() -> str:
        """Get user profile information"""
        try:
            profile = garmin_client.get_user_profile()
            if not profile:
                return "No user profile information found."
            return profile
        except Exception as e:
            return f"Error retrieving user profile: {str(e)}"

    @app.tool()
    async def get_userprofile_settings() -> str:
        """Get user profile settings"""
        try:
            settings = garmin_client.get_userprofile_settings()
            if not settings:
                return "No user profile settings found."
            return settings
        except Exception as e:
            return f"Error retrieving user profile settings: {str(e)}"

    return app
```

--------------------------------------------------------------------------------
/src/garmin_mcp/gear_management.py:
--------------------------------------------------------------------------------

```python
"""
Gear management functions for Garmin Connect MCP Server
"""
import datetime
from typing import Any, Dict, List, Optional, Union

# The garmin_client will be set by the main file
garmin_client = None


def configure(client):
    """Configure the module with the Garmin client instance"""
    global garmin_client
    garmin_client = client


def register_tools(app):
    """Register all gear management tools with the MCP server app"""
    
    @app.tool()
    async def get_gear(user_profile_id: str) -> str:
        """Get all gear registered with the user account
        
        Args:
            user_profile_id: User profile ID (can be obtained from get_device_last_used)
        """
        try:
            gear = garmin_client.get_gear(user_profile_id)
            if not gear:
                return "No gear found."
            return gear
        except Exception as e:
            return f"Error retrieving gear: {str(e)}"

    @app.tool()
    async def get_gear_defaults(user_profile_id: str) -> str:
        """Get default gear settings
        
        Args:
            user_profile_id: User profile ID (can be obtained from get_device_last_used)
        """
        try:
            defaults = garmin_client.get_gear_defaults(user_profile_id)
            if not defaults:
                return "No gear defaults found."
            return defaults
        except Exception as e:
            return f"Error retrieving gear defaults: {str(e)}"
    
    @app.tool()
    async def get_gear_stats(gear_uuid: str) -> str:
        """Get statistics for specific gear
        
        Args:
            gear_uuid: UUID of the gear item
        """
        try:
            stats = garmin_client.get_gear_stats(gear_uuid)
            if not stats:
                return f"No stats found for gear with UUID {gear_uuid}."
            return stats
        except Exception as e:
            return f"Error retrieving gear stats: {str(e)}"

    return app
```

--------------------------------------------------------------------------------
/src/garmin_mcp/womens_health.py:
--------------------------------------------------------------------------------

```python
"""
Women's health functions for Garmin Connect MCP Server
"""
import datetime
from typing import Any, Dict, List, Optional, Union

# The garmin_client will be set by the main file
garmin_client = None


def configure(client):
    """Configure the module with the Garmin client instance"""
    global garmin_client
    garmin_client = client


def register_tools(app):
    """Register all women's health tools with the MCP server app"""
    
    @app.tool()
    async def get_pregnancy_summary() -> str:
        """Get pregnancy summary data"""
        try:
            summary = garmin_client.get_pregnancy_summary()
            if not summary:
                return "No pregnancy summary data found."
            return summary
        except Exception as e:
            return f"Error retrieving pregnancy summary: {str(e)}"
    
    @app.tool()
    async def get_menstrual_data_for_date(date: str) -> str:
        """Get menstrual data for a specific date
        
        Args:
            date: Date in YYYY-MM-DD format
        """
        try:
            data = garmin_client.get_menstrual_data_for_date(date)
            if not data:
                return f"No menstrual data found for {date}."
            return data
        except Exception as e:
            return f"Error retrieving menstrual data: {str(e)}"
    
    @app.tool()
    async def get_menstrual_calendar_data(start_date: str, end_date: str) -> str:
        """Get menstrual calendar data between specified dates
        
        Args:
            start_date: Start date in YYYY-MM-DD format
            end_date: End date in YYYY-MM-DD format
        """
        try:
            data = garmin_client.get_menstrual_calendar_data(start_date, end_date)
            if not data:
                return f"No menstrual calendar data found between {start_date} and {end_date}."
            return data
        except Exception as e:
            return f"Error retrieving menstrual calendar data: {str(e)}"

    return app
```

--------------------------------------------------------------------------------
/test_mcp_server.py:
--------------------------------------------------------------------------------

```python
"""
Test script for MCP server functionality
This script tests the MCP server directly without needing Claude Desktop
"""

import asyncio
import sys
import json
from pathlib import Path
from dotenv import load_dotenv

# Import MCP client for testing
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

# Load environment variables
load_dotenv()


async def test_mcp_server():
    """Test MCP server by simulating a client connection"""
    # Path to the server script
    server_script = Path(__file__).parent / "garmin_mcp_server.py"

    if not server_script.exists():
        print(f"ERROR: Server script not found at {server_script}")
        return

    print(f"Testing MCP server at: {server_script}")

    # Create server parameters
    server_params = StdioServerParameters(
        command="python",
        args=[str(server_script)],
        env=None,  # Uses current environment which includes .env variables
    )

    try:
        # Connect to server
        print("Connecting to MCP server...")
        async with stdio_client(server_params) as (read, write):
            async with ClientSession(read, write) as session:
                # Initialize the connection
                print("Initializing connection...")
                await session.initialize()

                # List available tools
                print("\nListing available tools:")
                tools = await session.list_tools()
                for tool in tools.tools:
                    print(f"  - {tool.name}: {tool.description}")

                # Test each tool with sample parameters
                print("\nTesting tools:")

                # Test list_activities
                print("\nTesting list_activities...")
                try:
                    result = await session.call_tool(
                        "list_activities", arguments={"limit": 2}
                    )
                    print(f"Result: {result.content[0].text[:500]}...")
                except Exception as e:
                    print(f"ERROR: {str(e)}")

                # Test get_steps_data
                print("\nTesting get_steps_data...")
                try:
                    result = await session.call_tool(
                        "get_steps_data", arguments={}  # Uses default date (today)
                    )
                    print(f"Result: {result.content[0].text[:500]}...")
                except Exception as e:
                    print(f"ERROR: {str(e)}")

                print("\nMCP server test completed")

    except Exception as e:
        print(f"ERROR: Failed to connect to MCP server: {str(e)}")


if __name__ == "__main__":
    asyncio.run(test_mcp_server())

```

--------------------------------------------------------------------------------
/src/garmin_mcp/workouts.py:
--------------------------------------------------------------------------------

```python
"""
Workout-related functions for Garmin Connect MCP Server
"""
import datetime
from typing import Any, Dict, List, Optional, Union

# The garmin_client will be set by the main file
garmin_client = None


def configure(client):
    """Configure the module with the Garmin client instance"""
    global garmin_client
    garmin_client = client


def register_tools(app):
    """Register all workout-related tools with the MCP server app"""
    
    @app.tool()
    async def get_workouts() -> str:
        """Get all workouts"""
        try:
            workouts = garmin_client.get_workouts()
            if not workouts:
                return "No workouts found."
            return workouts
        except Exception as e:
            return f"Error retrieving workouts: {str(e)}"
    
    @app.tool()
    async def get_workout_by_id(workout_id: int) -> str:
        """Get details for a specific workout
        
        Args:
            workout_id: ID of the workout to retrieve
        """
        try:
            workout = garmin_client.get_workout_by_id(workout_id)
            if not workout:
                return f"No workout found with ID {workout_id}."
            return workout
        except Exception as e:
            return f"Error retrieving workout: {str(e)}"
    
    @app.tool()
    async def download_workout(workout_id: int) -> str:
        """Download a workout as a FIT file (this will return a message about how to access the file)
        
        Args:
            workout_id: ID of the workout to download
        """
        try:
            workout_data = garmin_client.download_workout(workout_id)
            if not workout_data:
                return f"No workout data found for workout with ID {workout_id}."
            
            # Since we can't return binary data directly, we'll inform the user
            return f"Workout data for ID {workout_id} is available. The data is in FIT format and would need to be saved to a file."
        except Exception as e:
            return f"Error downloading workout: {str(e)}"
    
    @app.tool()
    async def upload_workout(workout_json: str) -> str:
        """Upload a workout from JSON data
        
        Args:
            workout_json: JSON string containing workout data
        """
        try:
            result = garmin_client.upload_workout(workout_json)
            return result
        except Exception as e:
            return f"Error uploading workout: {str(e)}"
            
    @app.tool()
    async def upload_activity(file_path: str) -> str:
        """Upload an activity from a file (this is just a placeholder - file operations would need special handling)
        
        Args:
            file_path: Path to the activity file (.fit, .gpx, .tcx)
        """
        try:
            # This is a placeholder - actual implementation would need to handle file access
            return f"Activity upload from file path {file_path} is not supported in this MCP server implementation."
        except Exception as e:
            return f"Error uploading activity: {str(e)}"

    return app
```

--------------------------------------------------------------------------------
/src/garmin_mcp/devices.py:
--------------------------------------------------------------------------------

```python
"""
Device-related functions for Garmin Connect MCP Server
"""
import datetime
from typing import Any, Dict, List, Optional, Union

# The garmin_client will be set by the main file
garmin_client = None


def configure(client):
    """Configure the module with the Garmin client instance"""
    global garmin_client
    garmin_client = client


def register_tools(app):
    """Register all device-related tools with the MCP server app"""
    
    @app.tool()
    async def get_devices() -> str:
        """Get all Garmin devices associated with the user account"""
        try:
            devices = garmin_client.get_devices()
            if not devices:
                return "No devices found."
            return devices
        except Exception as e:
            return f"Error retrieving devices: {str(e)}"

    @app.tool()
    async def get_device_last_used() -> str:
        """Get information about the last used Garmin device"""
        try:
            device = garmin_client.get_device_last_used()
            if not device:
                return "No last used device found."
            return device
        except Exception as e:
            return f"Error retrieving last used device: {str(e)}"
    
    @app.tool()
    async def get_device_settings(device_id: str) -> str:
        """Get settings for a specific Garmin device
        
        Args:
            device_id: Device ID
        """
        try:
            settings = garmin_client.get_device_settings(device_id)
            if not settings:
                return f"No settings found for device ID {device_id}."
            return settings
        except Exception as e:
            return f"Error retrieving device settings: {str(e)}"

    @app.tool()
    async def get_primary_training_device() -> str:
        """Get information about the primary training device"""
        try:
            device = garmin_client.get_primary_training_device()
            if not device:
                return "No primary training device found."
            return device
        except Exception as e:
            return f"Error retrieving primary training device: {str(e)}"
    
    @app.tool()
    async def get_device_solar_data(device_id: str, date: str) -> str:
        """Get solar data for a specific device
        
        Args:
            device_id: Device ID
            date: Date in YYYY-MM-DD format
        """
        try:
            solar_data = garmin_client.get_device_solar_data(device_id, date)
            if not solar_data:
                return f"No solar data found for device ID {device_id} on {date}."
            return solar_data
        except Exception as e:
            return f"Error retrieving solar data: {str(e)}"
    
    @app.tool()
    async def get_device_alarms() -> str:
        """Get alarms from all Garmin devices"""
        try:
            alarms = garmin_client.get_device_alarms()
            if not alarms:
                return "No device alarms found."
            return alarms
        except Exception as e:
            return f"Error retrieving device alarms: {str(e)}"

    return app
```

--------------------------------------------------------------------------------
/src/garmin_mcp/data_management.py:
--------------------------------------------------------------------------------

```python
"""
Data management functions for Garmin Connect MCP Server
"""
import datetime
from typing import Any, Dict, List, Optional, Union

# The garmin_client will be set by the main file
garmin_client = None


def configure(client):
    """Configure the module with the Garmin client instance"""
    global garmin_client
    garmin_client = client


def register_tools(app):
    """Register all data management tools with the MCP server app"""
    
    @app.tool()
    async def add_body_composition(
        date: str,
        weight: float,
        percent_fat: Optional[float] = None,
        percent_hydration: Optional[float] = None,
        visceral_fat_mass: Optional[float] = None,
        bone_mass: Optional[float] = None,
        muscle_mass: Optional[float] = None,
        basal_met: Optional[float] = None,
        active_met: Optional[float] = None,
        physique_rating: Optional[int] = None,
        metabolic_age: Optional[float] = None,
        visceral_fat_rating: Optional[int] = None,
        bmi: Optional[float] = None
    ) -> str:
        """Add body composition data
        
        Args:
            date: Date in YYYY-MM-DD format
            weight: Weight in kg
            percent_fat: Body fat percentage
            percent_hydration: Hydration percentage
            visceral_fat_mass: Visceral fat mass
            bone_mass: Bone mass
            muscle_mass: Muscle mass
            basal_met: Basal metabolic rate
            active_met: Active metabolic rate
            physique_rating: Physique rating
            metabolic_age: Metabolic age
            visceral_fat_rating: Visceral fat rating
            bmi: Body Mass Index
        """
        try:
            result = garmin_client.add_body_composition(
                date,
                weight=weight,
                percent_fat=percent_fat,
                percent_hydration=percent_hydration,
                visceral_fat_mass=visceral_fat_mass,
                bone_mass=bone_mass,
                muscle_mass=muscle_mass,
                basal_met=basal_met,
                active_met=active_met,
                physique_rating=physique_rating,
                metabolic_age=metabolic_age,
                visceral_fat_rating=visceral_fat_rating,
                bmi=bmi
            )
            return result
        except Exception as e:
            return f"Error adding body composition data: {str(e)}"
    
    @app.tool()
    async def set_blood_pressure(
        systolic: int,
        diastolic: int,
        pulse: int,
        notes: Optional[str] = None
    ) -> str:
        """Set blood pressure values
        
        Args:
            systolic: Systolic pressure (top number)
            diastolic: Diastolic pressure (bottom number)
            pulse: Pulse rate
            notes: Optional notes
        """
        try:
            result = garmin_client.set_blood_pressure(
                systolic, diastolic, pulse, notes=notes
            )
            return result
        except Exception as e:
            return f"Error setting blood pressure values: {str(e)}"
    
    @app.tool()
    async def add_hydration_data(
        value_in_ml: int,
        cdate: str,
        timestamp: str
    ) -> str:
        """Add hydration data
        
        Args:
            value_in_ml: Amount of liquid in milliliters
            cdate: Date in YYYY-MM-DD format
            timestamp: Timestamp in YYYY-MM-DDThh:mm:ss.sss format
        """
        try:
            result = garmin_client.add_hydration_data(
                value_in_ml=value_in_ml,
                cdate=cdate,
                timestamp=timestamp
            )
            return result
        except Exception as e:
            return f"Error adding hydration data: {str(e)}"

    return app
```

--------------------------------------------------------------------------------
/src/garmin_mcp/weight_management.py:
--------------------------------------------------------------------------------

```python
"""
Weight management functions for Garmin Connect MCP Server
"""
import datetime
from typing import Any, Dict, List, Optional, Union

# The garmin_client will be set by the main file
garmin_client = None


def configure(client):
    """Configure the module with the Garmin client instance"""
    global garmin_client
    garmin_client = client


def register_tools(app):
    """Register all weight management tools with the MCP server app"""
    
    @app.tool()
    async def get_weigh_ins(start_date: str, end_date: str) -> str:
        """Get weight measurements between specified dates
        
        Args:
            start_date: Start date in YYYY-MM-DD format
            end_date: End date in YYYY-MM-DD format
        """
        try:
            weigh_ins = garmin_client.get_weigh_ins(start_date, end_date)
            if not weigh_ins:
                return f"No weight measurements found between {start_date} and {end_date}."
            return weigh_ins
        except Exception as e:
            return f"Error retrieving weight measurements: {str(e)}"

    @app.tool()
    async def get_daily_weigh_ins(date: str) -> str:
        """Get weight measurements for a specific date
        
        Args:
            date: Date in YYYY-MM-DD format
        """
        try:
            weigh_ins = garmin_client.get_daily_weigh_ins(date)
            if not weigh_ins:
                return f"No weight measurements found for {date}."
            return weigh_ins
        except Exception as e:
            return f"Error retrieving daily weight measurements: {str(e)}"
    
    @app.tool()
    async def delete_weigh_ins(date: str, delete_all: bool = True) -> str:
        """Delete weight measurements for a specific date
        
        Args:
            date: Date in YYYY-MM-DD format
            delete_all: Whether to delete all measurements for the day
        """
        try:
            result = garmin_client.delete_weigh_ins(date, delete_all=delete_all)
            return result
        except Exception as e:
            return f"Error deleting weight measurements: {str(e)}"
    
    @app.tool()
    async def add_weigh_in(weight: float, unit_key: str = "kg") -> str:
        """Add a new weight measurement
        
        Args:
            weight: Weight value
            unit_key: Unit of weight ('kg' or 'lb')
        """
        try:
            result = garmin_client.add_weigh_in(weight=weight, unitKey=unit_key)
            return result
        except Exception as e:
            return f"Error adding weight measurement: {str(e)}"
    
    @app.tool()
    async def add_weigh_in_with_timestamps(
        weight: float, 
        unit_key: str = "kg", 
        date_timestamp: str = None, 
        gmt_timestamp: str = None
    ) -> str:
        """Add a new weight measurement with specific timestamps
        
        Args:
            weight: Weight value
            unit_key: Unit of weight ('kg' or 'lb')
            date_timestamp: Local timestamp in format YYYY-MM-DDThh:mm:ss
            gmt_timestamp: GMT timestamp in format YYYY-MM-DDThh:mm:ss
        """
        try:
            if date_timestamp is None or gmt_timestamp is None:
                # Generate timestamps if not provided
                now = datetime.datetime.now()
                date_timestamp = now.strftime('%Y-%m-%dT%H:%M:%S')
                gmt_timestamp = now.astimezone(datetime.timezone.utc).strftime('%Y-%m-%dT%H:%M:%S')
                
            result = garmin_client.add_weigh_in_with_timestamps(
                weight=weight,
                unitKey=unit_key,
                dateTimestamp=date_timestamp,
                gmtTimestamp=gmt_timestamp
            )
            return result
        except Exception as e:
            return f"Error adding weight measurement with timestamps: {str(e)}"

    return app
```

--------------------------------------------------------------------------------
/tests/test_garmin.py:
--------------------------------------------------------------------------------

```python
"""
Test functions for the Garmin Connect API integration
This script allows you to test the connection and API functions without MCP
"""

import os
import datetime
from pathlib import Path
import json

from dotenv import load_dotenv
from garminconnect import Garmin

# Load environment variables from .env file
env_path = Path(__file__).parent / '.env'
load_dotenv(dotenv_path=env_path)

def test_garmin_login():
    """Test Garmin Connect login"""
    email = os.environ.get("GARMIN_EMAIL")
    password = os.environ.get("GARMIN_PASSWORD")

    if not email or not password:
        print("ERROR: GARMIN_EMAIL and GARMIN_PASSWORD environment variables must be set in .env file")
        return False

    print(f"Attempting to login with email: {email}")

    try:
        client = Garmin(email, password)
        client.login()
        print("SUCCESS: Login successful")
        return client
    except Exception as e:
        print(f"ERROR: Login failed: {str(e)}")
        print("\nNote: Garmin Connect might require additional verification.")
        print("If this is the first time using this API, try logging in through the official Garmin website first.")
        return False

def test_activities(client, limit=3):
    """Test retrieving activities"""
    if not client:
        return

    try:
        activities = client.get_activities(0, limit)
        print(f"\nRetrieved {len(activities)} activities:")

        for idx, activity in enumerate(activities, 1):
            print(f"\n--- Activity {idx} ---")
            print(f"Name: {activity.get('activityName', 'Unknown')}")
            print(f"Type: {activity.get('activityType', {}).get('typeKey', 'Unknown')}")
            print(f"Date: {activity.get('startTimeLocal', 'Unknown')}")
            print(f"ID: {activity.get('activityId', 'Unknown')}")

        if activities:
            # Save the first activity ID for testing get_activity_details
            return activities[0].get('activityId')
    except Exception as e:
        print(f"ERROR: Failed to retrieve activities: {str(e)}")

def test_activity_details(client, activity_id):
    """Test retrieving activity details"""
    if not client or not activity_id:
        return

    try:
        activity = client.get_activity_details(activity_id)
        print(f"\nActivity Details for ID {activity_id}:")
        print(json.dumps(activity, indent=2)[:1000] + "... (truncated)")
    except Exception as e:
        print(f"ERROR: Failed to retrieve activity details: {str(e)}")

def test_health_data(client):
    """Test retrieving health data for today"""
    if not client:
        return

    today = datetime.date.today().strftime("%Y-%m-%d")
    print(f"\nTesting health data for {today}:")

    # Test steps data
    try:
        steps_data = client.get_steps_data(today)
        print("\nSteps Data:")
        print(f"Steps: {steps_data.get('steps', 0)}")
        print(f"Goal: {steps_data.get('dailyStepGoal', 0)}")
    except Exception as e:
        print(f"ERROR: Failed to retrieve steps data: {str(e)}")

    # Test heart rate data
    try:
        hr_data = client.get_heart_rates(today)
        print("\nHeart Rate Data:")
        print(f"Resting HR: {hr_data.get('restingHeartRate', 0)} bpm")
    except Exception as e:
        print(f"ERROR: Failed to retrieve heart rate data: {str(e)}")

    # Test sleep data
    try:
        sleep_data = client.get_sleep_data(today)
        daily_sleep_data = sleep_data.get('dailySleepDTO', sleep_data)
        print("\nSleep Data:")
        sleep_score = daily_sleep_data.get('sleepScoreTotal', 0)
        print(f"Sleep Score: {sleep_score}")
    except Exception as e:
        print(f"ERROR: Failed to retrieve sleep data: {str(e)}")

if __name__ == "__main__":
    client = test_garmin_login()
    if client:
        activity_id = test_activities(client)
        if activity_id:
            test_activity_details(client, activity_id)
        test_health_data(client)

```

--------------------------------------------------------------------------------
/src/garmin_mcp/training.py:
--------------------------------------------------------------------------------

```python
"""
Training and performance functions for Garmin Connect MCP Server
"""
import datetime
from typing import Any, Dict, List, Optional, Union

# The garmin_client will be set by the main file
garmin_client = None


def configure(client):
    """Configure the module with the Garmin client instance"""
    global garmin_client
    garmin_client = client


def register_tools(app):
    """Register all training-related tools with the MCP server app"""
    
    @app.tool()
    async def get_progress_summary_between_dates(
        start_date: str, end_date: str, metric: str
    ) -> str:
        """Get progress summary for a metric between dates

        Args:
            start_date: Start date in YYYY-MM-DD format
            end_date: End date in YYYY-MM-DD format
            metric: Metric to get progress for (e.g., "elevationGain", "duration", "distance", "movingDuration")
        """
        try:
            summary = garmin_client.get_progress_summary_between_dates(
                start_date, end_date, metric
            )
            if not summary:
                return f"No progress summary found for {metric} between {start_date} and {end_date}."
            return summary
        except Exception as e:
            return f"Error retrieving progress summary: {str(e)}"
    
    @app.tool()
    async def get_hill_score(start_date: str, end_date: str) -> str:
        """Get hill score data between dates

        Args:
            start_date: Start date in YYYY-MM-DD format
            end_date: End date in YYYY-MM-DD format
        """
        try:
            hill_score = garmin_client.get_hill_score(start_date, end_date)
            if not hill_score:
                return f"No hill score data found between {start_date} and {end_date}."
            return hill_score
        except Exception as e:
            return f"Error retrieving hill score data: {str(e)}"
    
    @app.tool()
    async def get_endurance_score(start_date: str, end_date: str) -> str:
        """Get endurance score data between dates

        Args:
            start_date: Start date in YYYY-MM-DD format
            end_date: End date in YYYY-MM-DD format
        """
        try:
            endurance_score = garmin_client.get_endurance_score(start_date, end_date)
            if not endurance_score:
                return f"No endurance score data found between {start_date} and {end_date}."
            return endurance_score
        except Exception as e:
            return f"Error retrieving endurance score data: {str(e)}"
    
    @app.tool()
    async def get_training_effect(activity_id: int) -> str:
        """Get training effect data for a specific activity
        
        Args:
            activity_id: ID of the activity to retrieve training effect for
        """
        try:
            effect = garmin_client.get_training_effect(activity_id)
            if not effect:
                return f"No training effect data found for activity with ID {activity_id}."
            return effect
        except Exception as e:
            return f"Error retrieving training effect data: {str(e)}"
    
    @app.tool()
    async def get_max_metrics(date: str) -> str:
        """Get max metrics data (like VO2 Max and fitness age)
        
        Args:
            date: Date in YYYY-MM-DD format
        """
        try:
            metrics = garmin_client.get_max_metrics(date)
            if not metrics:
                return f"No max metrics data found for {date}."
            return metrics
        except Exception as e:
            return f"Error retrieving max metrics data: {str(e)}"
    
    @app.tool()
    async def get_hrv_data(date: str) -> str:
        """Get Heart Rate Variability (HRV) data
        
        Args:
            date: Date in YYYY-MM-DD format
        """
        try:
            hrv_data = garmin_client.get_hrv_data(date)
            if not hrv_data:
                return f"No HRV data found for {date}."
            return hrv_data
        except Exception as e:
            return f"Error retrieving HRV data: {str(e)}"
    
    @app.tool()
    async def get_fitnessage_data(date: str) -> str:
        """Get fitness age data
        
        Args:
            date: Date in YYYY-MM-DD format
        """
        try:
            fitness_age = garmin_client.get_fitnessage_data(date)
            if not fitness_age:
                return f"No fitness age data found for {date}."
            return fitness_age
        except Exception as e:
            return f"Error retrieving fitness age data: {str(e)}"
    
    @app.tool()
    async def request_reload(date: str) -> str:
        """Request reload of epoch data
        
        Args:
            date: Date in YYYY-MM-DD format
        """
        try:
            result = garmin_client.request_reload(date)
            return result
        except Exception as e:
            return f"Error requesting data reload: {str(e)}"

    return app
```

--------------------------------------------------------------------------------
/src/garmin_mcp/challenges.py:
--------------------------------------------------------------------------------

```python
"""
Challenges and badges functions for Garmin Connect MCP Server
"""
import datetime
from typing import Any, Dict, List, Optional, Union

# The garmin_client will be set by the main file
garmin_client = None


def configure(client):
    """Configure the module with the Garmin client instance"""
    global garmin_client
    garmin_client = client


def register_tools(app):
    """Register all challenges-related tools with the MCP server app"""
    
    @app.tool()
    async def get_goals(goal_type: str = "active") -> str:
        """Get Garmin Connect goals (active, future, or past)

        Args:
            goal_type: Type of goals to retrieve. Options: "active", "future", or "past"
        """
        try:
            goals = garmin_client.get_goals(goal_type)
            if not goals:
                return f"No {goal_type} goals found."
            return goals
        except Exception as e:
            return f"Error retrieving {goal_type} goals: {str(e)}"

    @app.tool()
    async def get_personal_record() -> str:
        """Get personal records for user"""
        try:
            records = garmin_client.get_personal_record()
            if not records:
                return "No personal records found."
            return records
        except Exception as e:
            return f"Error retrieving personal records: {str(e)}"

    @app.tool()
    async def get_earned_badges() -> str:
        """Get earned badges for user"""
        try:
            badges = garmin_client.get_earned_badges()
            if not badges:
                return "No earned badges found."
            return badges
        except Exception as e:
            return f"Error retrieving earned badges: {str(e)}"

    @app.tool()
    async def get_adhoc_challenges(start: int = 0, limit: int = 100) -> str:
        """Get adhoc challenges data

        Args:
            start: Starting index for challenges retrieval
            limit: Maximum number of challenges to retrieve
        """
        try:
            challenges = garmin_client.get_adhoc_challenges(start, limit)
            if not challenges:
                return "No adhoc challenges found."
            return challenges
        except Exception as e:
            return f"Error retrieving adhoc challenges: {str(e)}"

    @app.tool()
    async def get_available_badge_challenges(start: int = 1, limit: int = 100) -> str:
        """Get available badge challenges data

        Args:
            start: Starting index for challenges retrieval (starts at 1)
            limit: Maximum number of challenges to retrieve
        """
        try:
            challenges = garmin_client.get_available_badge_challenges(start, limit)
            if not challenges:
                return "No available badge challenges found."
            return challenges
        except Exception as e:
            return f"Error retrieving available badge challenges: {str(e)}"

    @app.tool()
    async def get_badge_challenges(start: int = 1, limit: int = 100) -> str:
        """Get badge challenges data

        Args:
            start: Starting index for challenges retrieval (starts at 1)
            limit: Maximum number of challenges to retrieve
        """
        try:
            challenges = garmin_client.get_badge_challenges(start, limit)
            if not challenges:
                return "No badge challenges found."
            return challenges
        except Exception as e:
            return f"Error retrieving badge challenges: {str(e)}"

    @app.tool()
    async def get_non_completed_badge_challenges(start: int = 1, limit: int = 100) -> str:
        """Get non-completed badge challenges data

        Args:
            start: Starting index for challenges retrieval (starts at 1)
            limit: Maximum number of challenges to retrieve
        """
        try:
            challenges = garmin_client.get_non_completed_badge_challenges(start, limit)
            if not challenges:
                return "No non-completed badge challenges found."
            return challenges
        except Exception as e:
            return f"Error retrieving non-completed badge challenges: {str(e)}"

    @app.tool()
    async def get_race_predictions() -> str:
        """Get race predictions for user"""
        try:
            predictions = garmin_client.get_race_predictions()
            if not predictions:
                return "No race predictions found."
            return predictions
        except Exception as e:
            return f"Error retrieving race predictions: {str(e)}"

    @app.tool()
    async def get_inprogress_virtual_challenges(start_date: str, end_date: str) -> str:
        """Get in-progress virtual challenges/expeditions between dates

        Args:
            start_date: Start date in YYYY-MM-DD format
            end_date: End date in YYYY-MM-DD format
        """
        try:
            challenges = garmin_client.get_inprogress_virtual_challenges(
                start_date, end_date
            )
            if not challenges:
                return f"No in-progress virtual challenges found between {start_date} and {end_date}."
            return challenges
        except Exception as e:
            return f"Error retrieving in-progress virtual challenges: {str(e)}"

    return app
```

--------------------------------------------------------------------------------
/src/garmin_mcp/__init__.py:
--------------------------------------------------------------------------------

```python
"""
Modular MCP Server for Garmin Connect Data
"""

import os

import requests
from mcp.server.fastmcp import FastMCP

from garth.exc import GarthHTTPError
from garminconnect import Garmin, GarminConnectAuthenticationError

# Import all modules
from garmin_mcp import activity_management
from garmin_mcp import health_wellness
from garmin_mcp import user_profile
from garmin_mcp import devices
from garmin_mcp import gear_management
from garmin_mcp import weight_management
from garmin_mcp import challenges
from garmin_mcp import training
from garmin_mcp import workouts
from garmin_mcp import data_management
from garmin_mcp import womens_health

def get_mfa() -> str:
    """Get MFA code from user input"""
    print("\nGarmin Connect MFA required. Please check your email/phone for the code.")
    return input("Enter MFA code: ")

# Get credentials from environment
email = os.environ.get("GARMIN_EMAIL")
password = os.environ.get("GARMIN_PASSWORD")
tokenstore = os.getenv("GARMINTOKENS") or "~/.garminconnect"
tokenstore_base64 = os.getenv("GARMINTOKENS_BASE64") or "~/.garminconnect_base64"


def init_api(email, password):
    """Initialize Garmin API with your credentials."""

    try:
        # Using Oauth1 and OAuth2 token files from directory
        print(
            f"Trying to login to Garmin Connect using token data from directory '{tokenstore}'...\n"
        )

        # Using Oauth1 and Oauth2 tokens from base64 encoded string
        # print(
        #     f"Trying to login to Garmin Connect using token data from file '{tokenstore_base64}'...\n"
        # )
        # dir_path = os.path.expanduser(tokenstore_base64)
        # with open(dir_path, "r") as token_file:
        #     tokenstore = token_file.read()

        garmin = Garmin()
        garmin.login(tokenstore)

    except (FileNotFoundError, GarthHTTPError, GarminConnectAuthenticationError):
        # Session is expired. You'll need to log in again
        print(
            "Login tokens not present, login with your Garmin Connect credentials to generate them.\n"
            f"They will be stored in '{tokenstore}' for future use.\n"
        )
        try:
            garmin = Garmin(
                email=email, password=password, is_cn=False, prompt_mfa=get_mfa
            )
            garmin.login()
            # Save Oauth1 and Oauth2 token files to directory for next login
            garmin.garth.dump(tokenstore)
            print(
                f"Oauth tokens stored in '{tokenstore}' directory for future use. (first method)\n"
            )
            # Encode Oauth1 and Oauth2 tokens to base64 string and safe to file for next login (alternative way)
            token_base64 = garmin.garth.dumps()
            dir_path = os.path.expanduser(tokenstore_base64)
            with open(dir_path, "w") as token_file:
                token_file.write(token_base64)
            print(
                f"Oauth tokens encoded as base64 string and saved to '{dir_path}' file for future use. (second method)\n"
            )
        except (
            FileNotFoundError,
            GarthHTTPError,
            GarminConnectAuthenticationError,
            requests.exceptions.HTTPError,
        ) as err:
            print(err)
            return None

    return garmin


def main():
    """Initialize the MCP server and register all tools"""

    # Initialize Garmin client
    garmin_client = init_api(email, password)
    if not garmin_client:
        print("Failed to initialize Garmin Connect client. Exiting.")
        return

    print("Garmin Connect client initialized successfully.")

    # Configure all modules with the Garmin client
    activity_management.configure(garmin_client)
    health_wellness.configure(garmin_client)
    user_profile.configure(garmin_client)
    devices.configure(garmin_client)
    gear_management.configure(garmin_client)
    weight_management.configure(garmin_client)
    challenges.configure(garmin_client)
    training.configure(garmin_client)
    workouts.configure(garmin_client)
    data_management.configure(garmin_client)
    womens_health.configure(garmin_client)

    # Create the MCP app
    app = FastMCP("Garmin Connect v1.0")

    # Register tools from all modules
    app = activity_management.register_tools(app)
    app = health_wellness.register_tools(app)
    app = user_profile.register_tools(app)
    app = devices.register_tools(app)
    app = gear_management.register_tools(app)
    app = weight_management.register_tools(app)
    app = challenges.register_tools(app)
    app = training.register_tools(app)
    app = workouts.register_tools(app)
    app = data_management.register_tools(app)
    app = womens_health.register_tools(app)

    # Add activity listing tool directly to the app
    @app.tool()
    async def list_activities(limit: int = 5) -> str:
        """List recent Garmin activities"""
        try:
            activities = garmin_client.get_activities(0, limit)

            if not activities:
                return "No activities found."

            result = f"Last {len(activities)} activities:\n\n"
            for idx, activity in enumerate(activities, 1):
                result += f"--- Activity {idx} ---\n"
                result += f"Activity: {activity.get('activityName', 'Unknown')}\n"
                result += (
                    f"Type: {activity.get('activityType', {}).get('typeKey', 'Unknown')}\n"
                )
                result += f"Date: {activity.get('startTimeLocal', 'Unknown')}\n"
                result += f"ID: {activity.get('activityId', 'Unknown')}\n\n"

            return result
        except Exception as e:
            return f"Error retrieving activities: {str(e)}"

    # Run the MCP server
    app.run()


if __name__ == "__main__":
    main()

```

--------------------------------------------------------------------------------
/src/garmin_mcp/activity_management.py:
--------------------------------------------------------------------------------

```python
"""
Activity Management functions for Garmin Connect MCP Server
"""
import datetime
from typing import Any, Dict, List, Optional, Union

# The garmin_client will be set by the main file
garmin_client = None


def configure(client):
    """Configure the module with the Garmin client instance"""
    global garmin_client
    garmin_client = client


def register_tools(app):
    """Register all activity management tools with the MCP server app"""
    
    @app.tool()
    async def get_activities_by_date(start_date: str, end_date: str, activity_type: str = "") -> str:
        """Get activities data between specified dates, optionally filtered by activity type
        
        Args:
            start_date: Start date in YYYY-MM-DD format
            end_date: End date in YYYY-MM-DD format
            activity_type: Optional activity type filter (e.g., cycling, running, swimming)
        """
        try:
            activities = garmin_client.get_activities_by_date(start_date, end_date, activity_type)
            if not activities:
                return f"No activities found between {start_date} and {end_date}" + \
                       (f" for activity type '{activity_type}'" if activity_type else "")
            
            return activities
        except Exception as e:
            return f"Error retrieving activities by date: {str(e)}"

    @app.tool()
    async def get_activities_fordate(date: str) -> str:
        """Get activities for a specific date
        
        Args:
            date: Date in YYYY-MM-DD format
        """
        try:
            activities = garmin_client.get_activities_fordate(date)
            if not activities:
                return f"No activities found for {date}"
            
            return activities
        except Exception as e:
            return f"Error retrieving activities for date: {str(e)}"

    @app.tool()
    async def get_activity(activity_id: int) -> str:
        """Get basic activity information
        
        Args:
            activity_id: ID of the activity to retrieve
        """
        try:
            activity = garmin_client.get_activity(activity_id)
            if not activity:
                return f"No activity found with ID {activity_id}"
            
            return activity
        except Exception as e:
            return f"Error retrieving activity: {str(e)}"

    @app.tool()
    async def get_activity_splits(activity_id: int) -> str:
        """Get splits for an activity
        
        Args:
            activity_id: ID of the activity to retrieve splits for
        """
        try:
            splits = garmin_client.get_activity_splits(activity_id)
            if not splits:
                return f"No splits found for activity with ID {activity_id}"
            
            return splits
        except Exception as e:
            return f"Error retrieving activity splits: {str(e)}"

    @app.tool()
    async def get_activity_typed_splits(activity_id: int) -> str:
        """Get typed splits for an activity
        
        Args:
            activity_id: ID of the activity to retrieve typed splits for
        """
        try:
            typed_splits = garmin_client.get_activity_typed_splits(activity_id)
            if not typed_splits:
                return f"No typed splits found for activity with ID {activity_id}"
            
            return typed_splits
        except Exception as e:
            return f"Error retrieving activity typed splits: {str(e)}"

    @app.tool()
    async def get_activity_split_summaries(activity_id: int) -> str:
        """Get split summaries for an activity
        
        Args:
            activity_id: ID of the activity to retrieve split summaries for
        """
        try:
            split_summaries = garmin_client.get_activity_split_summaries(activity_id)
            if not split_summaries:
                return f"No split summaries found for activity with ID {activity_id}"
            
            return split_summaries
        except Exception as e:
            return f"Error retrieving activity split summaries: {str(e)}"

    @app.tool()
    async def get_activity_weather(activity_id: int) -> str:
        """Get weather data for an activity
        
        Args:
            activity_id: ID of the activity to retrieve weather data for
        """
        try:
            weather = garmin_client.get_activity_weather(activity_id)
            if not weather:
                return f"No weather data found for activity with ID {activity_id}"
            
            return weather
        except Exception as e:
            return f"Error retrieving activity weather data: {str(e)}"

    @app.tool()
    async def get_activity_hr_in_timezones(activity_id: int) -> str:
        """Get heart rate data in different time zones for an activity
        
        Args:
            activity_id: ID of the activity to retrieve heart rate time zone data for
        """
        try:
            hr_zones = garmin_client.get_activity_hr_in_timezones(activity_id)
            if not hr_zones:
                return f"No heart rate time zone data found for activity with ID {activity_id}"
            
            return hr_zones
        except Exception as e:
            return f"Error retrieving activity heart rate time zone data: {str(e)}"

    @app.tool()
    async def get_activity_gear(activity_id: int) -> str:
        """Get gear data used for an activity
        
        Args:
            activity_id: ID of the activity to retrieve gear data for
        """
        try:
            gear = garmin_client.get_activity_gear(activity_id)
            if not gear:
                return f"No gear data found for activity with ID {activity_id}"
            
            return gear
        except Exception as e:
            return f"Error retrieving activity gear data: {str(e)}"

    @app.tool()
    async def get_activity_exercise_sets(activity_id: int) -> str:
        """Get exercise sets for strength training activities
        
        Args:
            activity_id: ID of the activity to retrieve exercise sets for
        """
        try:
            exercise_sets = garmin_client.get_activity_exercise_sets(activity_id)
            if not exercise_sets:
                return f"No exercise sets found for activity with ID {activity_id}"
            
            return exercise_sets
        except Exception as e:
            return f"Error retrieving activity exercise sets: {str(e)}"

    return app

```

--------------------------------------------------------------------------------
/src/garmin_mcp/health_wellness.py:
--------------------------------------------------------------------------------

```python
"""
Health & Wellness Data functions for Garmin Connect MCP Server
"""
import datetime
from typing import Any, Dict, List, Optional, Union

# The garmin_client will be set by the main file
garmin_client = None


def configure(client):
    """Configure the module with the Garmin client instance"""
    global garmin_client
    garmin_client = client


def register_tools(app):
    """Register all health and wellness tools with the MCP server app"""
    
    @app.tool()
    async def get_stats(date: str) -> str:
        """Get daily activity stats
        
        Args:
            date: Date in YYYY-MM-DD format
        """
        try:
            stats = garmin_client.get_stats(date)
            if not stats:
                return f"No stats found for {date}"
            
            return stats
        except Exception as e:
            return f"Error retrieving stats: {str(e)}"

    @app.tool()
    async def get_user_summary(date: str) -> str:
        """Get user summary data (compatible with garminconnect-ha)
        
        Args:
            date: Date in YYYY-MM-DD format
        """
        try:
            summary = garmin_client.get_user_summary(date)
            if not summary:
                return f"No user summary found for {date}"
            
            return summary
        except Exception as e:
            return f"Error retrieving user summary: {str(e)}"

    @app.tool()
    async def get_body_composition(start_date: str, end_date: str = None) -> str:
        """Get body composition data for a single date or date range
        
        Args:
            start_date: Date in YYYY-MM-DD format or start date if end_date provided
            end_date: Optional end date in YYYY-MM-DD format for date range
        """
        try:
            if end_date:
                composition = garmin_client.get_body_composition(start_date, end_date)
                if not composition:
                    return f"No body composition data found between {start_date} and {end_date}"
            else:
                composition = garmin_client.get_body_composition(start_date)
                if not composition:
                    return f"No body composition data found for {start_date}"
            
            return composition
        except Exception as e:
            return f"Error retrieving body composition data: {str(e)}"

    @app.tool()
    async def get_stats_and_body(date: str) -> str:
        """Get stats and body composition data
        
        Args:
            date: Date in YYYY-MM-DD format
        """
        try:
            data = garmin_client.get_stats_and_body(date)
            if not data:
                return f"No stats and body composition data found for {date}"
            
            return data
        except Exception as e:
            return f"Error retrieving stats and body composition data: {str(e)}"

    @app.tool()
    async def get_steps_data(date: str) -> str:
        """Get steps data
        
        Args:
            date: Date in YYYY-MM-DD format
        """
        try:
            steps_data = garmin_client.get_steps_data(date)
            if not steps_data:
                return f"No steps data found for {date}"
            
            return steps_data
        except Exception as e:
            return f"Error retrieving steps data: {str(e)}"

    @app.tool()
    async def get_daily_steps(start_date: str, end_date: str) -> str:
        """Get steps data for a date range
        
        Args:
            start_date: Start date in YYYY-MM-DD format
            end_date: End date in YYYY-MM-DD format
        """
        try:
            steps_data = garmin_client.get_daily_steps(start_date, end_date)
            if not steps_data:
                return f"No daily steps data found between {start_date} and {end_date}"
            
            return steps_data
        except Exception as e:
            return f"Error retrieving daily steps data: {str(e)}"

    @app.tool()
    async def get_training_readiness(date: str) -> str:
        """Get training readiness data
        
        Args:
            date: Date in YYYY-MM-DD format
        """
        try:
            readiness = garmin_client.get_training_readiness(date)
            if not readiness:
                return f"No training readiness data found for {date}"
            
            return readiness
        except Exception as e:
            return f"Error retrieving training readiness data: {str(e)}"

    @app.tool()
    async def get_body_battery(start_date: str, end_date: str) -> str:
        """Get body battery data
        
        Args:
            start_date: Start date in YYYY-MM-DD format
            end_date: End date in YYYY-MM-DD format
        """
        try:
            battery_data = garmin_client.get_body_battery(start_date, end_date)
            if not battery_data:
                return f"No body battery data found between {start_date} and {end_date}"
            
            return battery_data
        except Exception as e:
            return f"Error retrieving body battery data: {str(e)}"

    @app.tool()
    async def get_body_battery_events(date: str) -> str:
        """Get body battery events data
        
        Args:
            date: Date in YYYY-MM-DD format
        """
        try:
            events = garmin_client.get_body_battery_events(date)
            if not events:
                return f"No body battery events found for {date}"
            
            return events
        except Exception as e:
            return f"Error retrieving body battery events: {str(e)}"

    @app.tool()
    async def get_blood_pressure(start_date: str, end_date: str) -> str:
        """Get blood pressure data
        
        Args:
            start_date: Start date in YYYY-MM-DD format
            end_date: End date in YYYY-MM-DD format
        """
        try:
            bp_data = garmin_client.get_blood_pressure(start_date, end_date)
            if not bp_data:
                return f"No blood pressure data found between {start_date} and {end_date}"
            
            return bp_data
        except Exception as e:
            return f"Error retrieving blood pressure data: {str(e)}"

    @app.tool()
    async def get_floors(date: str) -> str:
        """Get floors climbed data
        
        Args:
            date: Date in YYYY-MM-DD format
        """
        try:
            floors_data = garmin_client.get_floors(date)
            if not floors_data:
                return f"No floors data found for {date}"
            
            return floors_data
        except Exception as e:
            return f"Error retrieving floors data: {str(e)}"

    @app.tool()
    async def get_training_status(date: str) -> str:
        """Get training status data
        
        Args:
            date: Date in YYYY-MM-DD format
        """
        try:
            status = garmin_client.get_training_status(date)
            if not status:
                return f"No training status data found for {date}"
            
            return status
        except Exception as e:
            return f"Error retrieving training status data: {str(e)}"

    @app.tool()
    async def get_rhr_day(date: str) -> str:
        """Get resting heart rate data
        
        Args:
            date: Date in YYYY-MM-DD format
        """
        try:
            rhr_data = garmin_client.get_rhr_day(date)
            if not rhr_data:
                return f"No resting heart rate data found for {date}"
            
            return rhr_data
        except Exception as e:
            return f"Error retrieving resting heart rate data: {str(e)}"

    @app.tool()
    async def get_heart_rates(date: str) -> str:
        """Get heart rate data
        
        Args:
            date: Date in YYYY-MM-DD format
        """
        try:
            hr_data = garmin_client.get_heart_rates(date)
            if not hr_data:
                return f"No heart rate data found for {date}"
            
            return hr_data
        except Exception as e:
            return f"Error retrieving heart rate data: {str(e)}"

    @app.tool()
    async def get_hydration_data(date: str) -> str:
        """Get hydration data
        
        Args:
            date: Date in YYYY-MM-DD format
        """
        try:
            hydration_data = garmin_client.get_hydration_data(date)
            if not hydration_data:
                return f"No hydration data found for {date}"
            
            return hydration_data
        except Exception as e:
            return f"Error retrieving hydration data: {str(e)}"

    @app.tool()
    async def get_sleep_data(date: str) -> str:
        """Get sleep data
        
        Args:
            date: Date in YYYY-MM-DD format
        """
        try:
            sleep_data = garmin_client.get_sleep_data(date)
            if not sleep_data:
                return f"No sleep data found for {date}"
            
            return sleep_data
        except Exception as e:
            return f"Error retrieving sleep data: {str(e)}"

    @app.tool()
    async def get_stress_data(date: str) -> str:
        """Get stress data
        
        Args:
            date: Date in YYYY-MM-DD format
        """
        try:
            stress_data = garmin_client.get_stress_data(date)
            if not stress_data:
                return f"No stress data found for {date}"
            
            return stress_data
        except Exception as e:
            return f"Error retrieving stress data: {str(e)}"

    @app.tool()
    async def get_respiration_data(date: str) -> str:
        """Get respiration data
        
        Args:
            date: Date in YYYY-MM-DD format
        """
        try:
            respiration_data = garmin_client.get_respiration_data(date)
            if not respiration_data:
                return f"No respiration data found for {date}"
            
            return respiration_data
        except Exception as e:
            return f"Error retrieving respiration data: {str(e)}"

    @app.tool()
    async def get_spo2_data(date: str) -> str:
        """Get SpO2 (blood oxygen) data
        
        Args:
            date: Date in YYYY-MM-DD format
        """
        try:
            spo2_data = garmin_client.get_spo2_data(date)
            if not spo2_data:
                return f"No SpO2 data found for {date}"
            
            return spo2_data
        except Exception as e:
            return f"Error retrieving SpO2 data: {str(e)}"

    @app.tool()
    async def get_all_day_stress(date: str) -> str:
        """Get all-day stress data
        
        Args:
            date: Date in YYYY-MM-DD format
        """
        try:
            stress_data = garmin_client.get_all_day_stress(date)
            if not stress_data:
                return f"No all-day stress data found for {date}"
            
            return stress_data
        except Exception as e:
            return f"Error retrieving all-day stress data: {str(e)}"

    @app.tool()
    async def get_all_day_events(date: str) -> str:
        """Get daily wellness events data
        
        Args:
            date: Date in YYYY-MM-DD format
        """
        try:
            events = garmin_client.get_all_day_events(date)
            if not events:
                return f"No daily wellness events found for {date}"
            
            return events
        except Exception as e:
            return f"Error retrieving daily wellness events: {str(e)}"

    return app
```