# 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
[](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
```