A semantic MCP server for Garmin Connect data access optimized for fitness, health, and coaching applications.
The Garmin MCP Server provides intelligent tools that combine Garmin Connect data into useful abstractions for personal coach agents and fitness applications. Built with FastMCP framework and leveraging the python-garminconnect library.
- Semantic Coaching Tools: Combines multiple API calls into coaching-relevant abstractions
- 8 Specialized Tools: Daily overview, recovery status, activity analysis, training zones, fitness assessment, progress tracking, and wellness monitoring
- Agent-Optimized: Designed for LLM agent consumption with consistent response formats
- Production Ready: FastMCP framework with streamable_http transport
- OAuth Authentication: Secure token management with MFA support
Get started in under 2 minutes:
git clone https://github.com/MAnders333/garmin-mcp-server.git
cd garmin-mcp-server
# Automated setup with validation
./scripts/docker-dev-setup.sh
# Or manual setup:
cp .env.example .env
# Edit .env with your Garmin credentials
docker-compose up -d
For comprehensive Docker instructions, see Docker Quick Start Guide.
Quick start with Docker Compose:
git clone https://github.com/MAnders333/garmin-mcp-server.git
cd garmin-mcp-server
# Copy and configure environment
cp .env.example .env
# Edit .env with your Garmin credentials (GARMIN_EMAIL and GARMIN_PASSWORD)
# Start server with Docker Compose
docker-compose up -d
# View logs
docker-compose logs -f
Using Docker directly:
# Build the image
docker build -t garmin-mcp-server .
# Run with environment file
docker run -d \
--name garmin-mcp-server \
--env-file .env \
-p 8000:8000 \
-v garmin-tokens:/app/.garmin \
garmin-mcp-server
-
Clone and Setup
git clone https://github.com/MAnders333/garmin-mcp-server.git cd garmin-mcp-server # Quick setup (recommended) make quick-start source .venv/bin/activate # Linux/macOS # Or manual setup: # uv venv --python 3.11 # source .venv/bin/activate # uv pip install -e ".[dev]"
-
Configuration
# Copy the example configuration cp .env.example .env # Edit .env with your Garmin Connect credentials # Required: # [email protected] # GARMIN_PASSWORD=your_secure_password # Optional: # GARMIN_TOKENS_PATH=~/.garminconnect # MCP_HOST=127.0.0.1 # MCP_PORT=8000
-
Run Server
# Start the MCP server (streamable_http transport on port 8000) python scripts/start_server.py # Or using the server module directly python src/garmin_mcp/server.py
On first run, you'll authenticate with Garmin Connect (MFA required):
Starting Garmin MCP Server on 127.0.0.1:8000
Enter MFA code: 123456
✅ Successfully authenticated and saved tokens to ~/.garminconnect
For Docker users: OAuth tokens are automatically persisted in the garmin-tokens
Docker volume, so you only need to authenticate once.
For detailed authentication setup and troubleshooting, see docs/authentication.md
.
get_daily_coaching_overview
- Complete daily health and activity summaryget_recovery_status
- Sleep, HRV, body battery, and stress analysisget_recent_activities
- Recent workouts with coaching-relevant metricsanalyze_activity
- Deep performance analysis with splits and zones
get_training_zones
- Training zones based on lactate threshold and VO2 maxget_fitness_assessment
- VO2 max, training status, and performance trendsget_progress_summary
- Long-term trends and goal progressget_wellness_snapshot
- Comprehensive health and wellness status
Following the principle of "Minimal Viable Complexity", this server uses a clean, modular architecture:
- Direct Integration: Uses
garminconnect
library directly without unnecessary wrappers - Server-Level Authentication: OAuth token management integrated into the main server module
- Simple Dependencies: Only essential libraries (FastMCP, garminconnect, pydantic)
- Clear Data Flow: Authentication → API calls → Response formatting with consistent error handling
This server provides semantic tools that:
- Combine multiple API calls into coaching-relevant data structures
- Filter data by returning only coaching-relevant metrics
- Optimize for agents with consistent response formats and graceful degradation
- Use coaching terminology instead of technical API field names
{
"date": "2025-10-11",
"activity_summary": {
"data": {
"steps": 8542,
"distance_meters": 6200,
"calories_burned": 2145,
"active_minutes": 67
},
"api_method": "get_user_summary",
"status": "success"
},
"sleep_data": {
"data": {
"sleep_score": 82,
"total_sleep_hours": 7.5,
"deep_sleep_minutes": 89
},
"api_method": "get_sleep_data",
"status": "success"
},
"body_battery": {
"data": [
{"timestamp": "2025-10-11T06:00:00", "level": 85},
{"timestamp": "2025-10-11T12:00:00", "level": 67}
],
"api_method": "get_body_battery",
"status": "success"
}
}
The easiest way to run the Garmin MCP Server is with Docker Compose:
# 1. Copy environment template
cp .env.example .env
# 2. Edit .env with your credentials
[email protected]
GARMIN_PASSWORD=your_secure_password
# 3. Start the server
docker-compose up -d
# 4. Check logs
docker-compose logs -f garmin-mcp-server
# 5. Health check
curl http://localhost:8000/health
The docker-compose.yml
provides two services:
Production Service (default):
docker-compose up -d
- Optimized production build
- Minimal attack surface
- Persistent OAuth token storage
- Health monitoring
Development Service (optional):
docker-compose --profile dev up -d garmin-mcp-dev
- Hot code reload with volume mounts
- Debug logging enabled
- Runs on port 8001 by default
Build and run:
# Build image
docker build -t garmin-mcp-server .
# Run container
docker run -d \
--name garmin-mcp-server \
--env-file .env \
-p 8000:8000 \
-v garmin-tokens:/app/.garmin \
garmin-mcp-server
Container management:
# View logs
docker logs -f garmin-mcp-server
# Stop and remove
docker stop garmin-mcp-server
docker rm garmin-mcp-server
# Clean up volumes (removes OAuth tokens)
docker volume rm garmin-tokens
All environment variables from .env.example
are supported in Docker:
# Required
[email protected]
GARMIN_PASSWORD=your_password
# Optional Docker-specific settings
MCP_HOST=0.0.0.0 # Docker internal host (don't change)
MCP_PORT=8000 # Internal container port
GARMIN_TOKENS_PATH=/app/.garmin # Token storage path in container
# Optional general settings
GARMIN_IS_CN=false # China region support
LOG_LEVEL=INFO # Logging level
RATE_LIMIT_PER_MINUTE=60 # API rate limiting
CACHE_TIMEOUT_SECONDS=300 # Cache duration
- Non-root user: Container runs as
garmin
user (non-root) - Minimal base image: Uses Python slim image with minimal packages
- Token persistence: OAuth tokens stored in secure Docker volume
- Health monitoring: Built-in health check endpoint
- Network isolation: Runs in dedicated Docker network
Container won't start:
# Check logs for errors
docker-compose logs garmin-mcp-server
# Verify environment variables
docker-compose config
Authentication issues:
# Remove token volume to force re-authentication
docker-compose down
docker volume rm garmin-mcp-server_garmin-tokens
docker-compose up -d
Port conflicts:
# Change port in .env file
echo "MCP_PORT=8001" >> .env
docker-compose down && docker-compose up -d
Health check failures:
# Check if server is responding
docker exec garmin-mcp-server curl -f http://localhost:8000/health
# Check container health status
docker inspect garmin-mcp-server --format='{{.State.Health.Status}}'
# Required
[email protected]
GARMIN_PASSWORD=your_password
# Optional
GARMIN_TOKENS_PATH=~/.garminconnect # OAuth token storage (local) / /app/.garmin (Docker)
MCP_HOST=127.0.0.1 # Server bind address (0.0.0.0 for Docker)
MCP_PORT=8000 # Server port
GARMIN_IS_CN=false # China region support
LOG_LEVEL=INFO # Logging level
RATE_LIMIT_PER_MINUTE=60 # API rate limiting
CACHE_TIMEOUT_SECONDS=300 # Cache timeout
- OAuth tokens stored locally (1-year lifetime)
- No Garmin data stored permanently
- Secure credential handling with environment variables
- MFA support for enhanced account security
- Docker containers run as non-root user
src/garmin_mcp/
├── __init__.py # Package initialization
├── server.py # Main MCP server with 8 tools and authentication
├── models.py # Pydantic response models for structured responses
└── utils.py # Utility functions for data processing and error handling
# Docker files
├── Dockerfile # Multi-stage Docker build
├── docker-compose.yml # Docker Compose configuration
├── .dockerignore # Docker build exclusions
├── .env.docker # Docker-optimized environment template
└── scripts/
├── docker-dev-setup.sh # Automated Docker setup
└── validate-docker.sh # Docker validation script
-
Define tool in
server.py
:@mcp.tool() def my_coaching_tool(param: str) -> Dict[str, Any]: """Tool description for agents""" # Use handle_api_call for consistent error handling result = handle_api_call( lambda: _garmin_client.get_some_data(param), "get_some_data", "No data available for this request" ) return result
-
Tool is automatically registered when decorated with
@mcp.tool()
-
Update tool documentation in
docs/tools-reference.md
# Setup development environment (if not done already)
make quick-start
source .venv/bin/activate
# Run all tests with coverage
make test
# Or run specific test categories
make test-unit # Unit tests only
make test-cov # Tests with detailed coverage
make ci # Full CI pipeline
For comprehensive testing instructions, see docs/testing.md
.
This server takes a coaching-first approach to data presentation:
- Semantic Abstraction: Rather than exposing raw API endpoints, tools combine related data points into coaching-relevant structures
- Intelligent Filtering: Responses include only actionable metrics, reducing noise for LLM agents
- Advanced Metrics: Includes training zones, VO2 max, HRV, lactate threshold, and other performance indicators
- Graceful Degradation: Tools return partial data when some metrics are unavailable
- Coaching Insights: Where appropriate, tools provide contextual recommendations
# Get daily coaching overview
overview = await call_tool("get_daily_coaching_overview")
# Returns: structured response with status indicators for each API call
# Analyze recent workout
analysis = await call_tool("analyze_activity", {"activity_id": "12345"})
# Returns: activity details, splits data, HR zones with status indicators
# Check training readiness
recovery = await call_tool("get_recovery_status")
# Returns: sleep analysis, HRV, body battery, resting HR with status indicators
# Get comprehensive fitness assessment
fitness = await call_tool("get_fitness_assessment")
# Returns: fitness metrics, training status, race predictions with status indicators
# Track progress over time
progress = await call_tool("get_progress_summary", {
"start_date": "2025-09-11",
"end_date": "2025-10-11"
})
# Returns: progress data, goals, personal records with status indicators
- Fork the repository
- Create a feature branch
- Make your changes with tests
- Submit a pull request
- Follow semantic tool design principles
- Include comprehensive error handling
- Add coaching insights where valuable
- Maintain response format consistency
- Test with real Garmin data
MIT License - see LICENSE file for details.
Authentication Fails
Local installation:
# Clear existing tokens and retry
rm -rf ~/.garminconnect
python scripts/start_server.py
Docker:
# Remove token volume to force re-authentication
docker-compose down
docker volume rm garmin-mcp-server_garmin-tokens
docker-compose up -d
MFA Code Required
- Check email/SMS for Garmin Connect MFA code
- Code expires quickly - enter immediately when prompted
- If authentication still fails, verify credentials in
.env
- For Docker: Monitor logs with
docker-compose logs -f
during first authentication
No Data Available
- Some metrics require specific Garmin devices (Body Battery, HRV, etc.)
- Recent activities might not appear immediately after syncing
- Historical data access depends on your Garmin Connect account history
Server Won't Start
Local installation:
# Check if port 8000 is already in use
lsof -i :8000
# Or specify different port in .env
echo "MCP_PORT=8001" >> .env
Docker:
# Check if port is in use
docker ps | grep 8000
# Change port in .env and restart
echo "MCP_PORT=8001" >> .env
docker-compose down && docker-compose up -d
Docker-Specific Issues
Container Health Check Failing
# Check container logs
docker-compose logs garmin-mcp-server
# Manual health check
docker exec garmin-mcp-server curl -f http://localhost:8000/health
# Restart unhealthy container
docker-compose restart garmin-mcp-server
Container Won't Start
# Check Docker configuration
docker-compose config
# View detailed error logs
docker-compose up --no-daemonize
# Check Docker daemon is running
docker info
Environment Variable Issues
# Validate environment file
docker-compose config
# Check variables are loaded correctly
docker-compose run --rm garmin-mcp-server env | grep GARMIN
Tools Return No Data Status
- Verify Garmin Connect account has activity data
- Some tools require recent activities within specified date ranges
- Check tool responses - they include status indicators for each API call
- Tools return status "no_data" instead of failing when data is unavailable
- Quick Start:
docs/quickstart.md
- Step-by-step setup guide - Docker Guide:
docs/docker-quickstart.md
- Docker setup and deployment - Authentication:
docs/authentication.md
- OAuth setup and troubleshooting - API Reference:
docs/tools-reference.md
- Complete tool documentation - Testing Guide:
docs/testing.md
- Unit tests and HTTP testing
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Built with FastMCP framework
- Uses python-garminconnect library
- Thanks to the MCP and Garmin Connect communities for their excellent tools and libraries