We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Marholoubek/health_mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
"""Base adapter interface for health data providers."""
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Optional
@dataclass
class UserProfile:
"""Unified user profile data."""
user_id: str
email: Optional[str] = None
first_name: Optional[str] = None
last_name: Optional[str] = None
provider: str = ""
@dataclass
class BodyMeasurements:
"""Unified body measurement data."""
height_meters: Optional[float] = None
weight_kg: Optional[float] = None
max_heart_rate: Optional[int] = None
provider: str = ""
@dataclass
class SleepStages:
"""Sleep stage durations."""
total_in_bed_ms: int = 0
awake_ms: int = 0
light_sleep_ms: int = 0
deep_sleep_ms: int = 0 # Slow wave sleep
rem_sleep_ms: int = 0
sleep_cycles: int = 0
disturbances: int = 0
@dataclass
class SleepNeeds:
"""Sleep need calculations."""
baseline_ms: int = 0
debt_ms: int = 0
strain_ms: int = 0
nap_credit_ms: int = 0
@property
def total_need_ms(self) -> int:
"""Calculate total sleep need."""
return self.baseline_ms + self.debt_ms + self.strain_ms - self.nap_credit_ms
@dataclass
class SleepRecord:
"""Unified sleep record."""
id: str
start: datetime
end: datetime
is_nap: bool = False
score_state: str = "SCORED"
# Sleep metrics
stages: Optional[SleepStages] = None
needs: Optional[SleepNeeds] = None
respiratory_rate: Optional[float] = None
performance_percentage: Optional[float] = None
consistency_percentage: Optional[float] = None
efficiency_percentage: Optional[float] = None
provider: str = ""
raw_data: dict = field(default_factory=dict)
@property
def duration_ms(self) -> int:
"""Get total sleep duration in milliseconds."""
return int((self.end - self.start).total_seconds() * 1000)
@property
def duration_hours(self) -> float:
"""Get total sleep duration in hours."""
return self.duration_ms / (1000 * 60 * 60)
@dataclass
class RecoveryRecord:
"""Unified recovery record."""
id: str
date: datetime
score_state: str = "SCORED"
# Recovery metrics
recovery_score: Optional[float] = None # 0-100
resting_heart_rate: Optional[float] = None
hrv_ms: Optional[float] = None # Heart rate variability in ms
spo2_percentage: Optional[float] = None
skin_temp_celsius: Optional[float] = None
provider: str = ""
raw_data: dict = field(default_factory=dict)
@dataclass
class HRZoneDurations:
"""Heart rate zone durations."""
zone_0_ms: int = 0 # Below zone 1
zone_1_ms: int = 0 # Light
zone_2_ms: int = 0 # Moderate
zone_3_ms: int = 0 # Hard
zone_4_ms: int = 0 # Very hard
zone_5_ms: int = 0 # Maximum
@dataclass
class WorkoutRecord:
"""Unified workout record."""
id: str
start: datetime
end: datetime
sport_name: str
sport_id: Optional[int] = None
score_state: str = "SCORED"
# Workout metrics
strain: Optional[float] = None
average_heart_rate: Optional[int] = None
max_heart_rate: Optional[int] = None
calories_kj: Optional[float] = None
distance_meters: Optional[float] = None
altitude_gain_meters: Optional[float] = None
zone_durations: Optional[HRZoneDurations] = None
provider: str = ""
raw_data: dict = field(default_factory=dict)
@property
def duration_minutes(self) -> float:
"""Get workout duration in minutes."""
return (self.end - self.start).total_seconds() / 60
@property
def calories_kcal(self) -> Optional[float]:
"""Get calories in kilocalories."""
if self.calories_kj is not None:
return self.calories_kj / 4.184
return None
@dataclass
class CycleRecord:
"""Unified physiological cycle record (daily strain cycle)."""
id: str
start: datetime
end: Optional[datetime] = None
score_state: str = "SCORED"
# Cycle metrics
strain: Optional[float] = None
calories_kj: Optional[float] = None
average_heart_rate: Optional[int] = None
max_heart_rate: Optional[int] = None
provider: str = ""
raw_data: dict = field(default_factory=dict)
@property
def calories_kcal(self) -> Optional[float]:
"""Get calories in kilocalories."""
if self.calories_kj is not None:
return self.calories_kj / 4.184
return None
class HealthAdapter(ABC):
"""Abstract base class for health data adapters."""
provider_name: str = "unknown"
@abstractmethod
async def is_authenticated(self) -> bool:
"""Check if the adapter is authenticated.
Returns:
True if authenticated and ready to make API calls.
"""
pass
@abstractmethod
async def authenticate(self) -> bool:
"""Initiate authentication flow.
Returns:
True if authentication successful.
"""
pass
@abstractmethod
async def get_profile(self) -> UserProfile:
"""Get user profile.
Returns:
UserProfile with user information.
"""
pass
@abstractmethod
async def get_body_measurements(self) -> BodyMeasurements:
"""Get body measurements.
Returns:
BodyMeasurements with height, weight, etc.
"""
pass
@abstractmethod
async def get_sleep(
self,
start: Optional[datetime] = None,
end: Optional[datetime] = None,
limit: int = 10,
) -> list[SleepRecord]:
"""Get sleep records.
Args:
start: Start date for query range.
end: End date for query range.
limit: Maximum number of records to return.
Returns:
List of SleepRecord objects.
"""
pass
@abstractmethod
async def get_recovery(
self,
start: Optional[datetime] = None,
end: Optional[datetime] = None,
limit: int = 10,
) -> list[RecoveryRecord]:
"""Get recovery records.
Args:
start: Start date for query range.
end: End date for query range.
limit: Maximum number of records to return.
Returns:
List of RecoveryRecord objects.
"""
pass
@abstractmethod
async def get_workouts(
self,
start: Optional[datetime] = None,
end: Optional[datetime] = None,
limit: int = 10,
) -> list[WorkoutRecord]:
"""Get workout records.
Args:
start: Start date for query range.
end: End date for query range.
limit: Maximum number of records to return.
Returns:
List of WorkoutRecord objects.
"""
pass
@abstractmethod
async def get_cycles(
self,
start: Optional[datetime] = None,
end: Optional[datetime] = None,
limit: int = 10,
) -> list[CycleRecord]:
"""Get physiological cycle records.
Args:
start: Start date for query range.
end: End date for query range.
limit: Maximum number of records to return.
Returns:
List of CycleRecord objects.
"""
pass