Garmin Health MCP Server
by AryanP45
README.md
# Garmin Health MCP Server
A **remote MCP server** that exposes your Garmin Connect health data as tools
that Claude (or any MCP-compatible client) can call. Runs over Streamable HTTP
so it can be added as a custom connector on claude.ai.
> ⚠️ **Disclaimer:** This project uses the unofficial
> [`garminconnect`](https://github.com/cyberjunky/python-garminconnect) library,
> which authenticates with your Garmin email/password instead of Garmin's
> official OAuth partner API. This approach is common for personal projects but
> is against Garmin's Terms of Service — use it knowing that risk.
---
## Features
- 10 ready-to-use health tools (steps, sleep, HR, HRV, stress, Body Battery, activities, and more)
- Bearer-token auth so only you can access your data
- Session caching — logs in once, reuses the token for months
- Docker-ready for deployment on any host (Fly.io, Render, Railway, VPS, etc.)
---
## Project structure
| File | Purpose |
|---|---|
| [`server.py`](server.py) | MCP server (FastMCP + Streamable HTTP) — defines all tools |
| [`garmin_client.py`](garmin_client.py) | Garmin session handling, retry-on-expiry |
| [`login_once.py`](login_once.py) | Run **locally** once to complete MFA and save the session |
| [`test_connection.py`](test_connection.py) | Sanity-check that the cached token is still valid |
| [`Dockerfile`](Dockerfile) | Container for deployment |
| [`.env.example`](.env.example) | Required environment variables |
---
## Available tools
| Tool | Description |
|---|---|
| `get_daily_summary(target_date)` | Steps, calories, floors climbed, resting HR, distance |
| `get_heart_rate(target_date)` | Resting HR, min/max, and full time-series |
| `get_sleep(target_date)` | Duration, stages (Deep/Light/REM/Awake), sleep score |
| `get_body_battery(target_date)` | Body Battery (energy reserve) levels |
| `get_stress(target_date)` | Stress level readings throughout the day |
| `get_hrv(target_date)` | Heart Rate Variability status |
| `get_body_composition(target_date)` | Weight, body fat %, muscle mass |
| `get_recent_activities(limit)` | Latest recorded workouts, newest first |
| `get_activity_details(activity_id)` | Full details for one activity by ID |
| `get_week_summary(start_date)` | 7-day rollup of daily summaries |
All `target_date` / `start_date` parameters accept `YYYY-MM-DD`. Omit them to default to **today**.
---
## Setup
### 1. Clone & install
```bash
git clone https://github.com/<you>/garmin-mcp-server.git
cd garmin-mcp-server
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
```
### 2. Configure environment
```bash
cp .env.example .env
# Edit .env — fill in GARMIN_EMAIL, GARMIN_PASSWORD, and MCP_AUTH_TOKEN
```
Generate a strong `MCP_AUTH_TOKEN`:
```bash
python -c "import secrets; print(secrets.token_urlsafe(32))"
```
### 3. Handle MFA locally (one-time)
Garmin may challenge the first login with an MFA code. A headless server can't
answer that prompt, so do the interactive login on your own machine first:
```bash
python login_once.py
```
This saves a `garmin_tokens/` directory containing the session token. Garmin
tokens typically stay valid for months — the server auto-refreshes them.
Verify it worked:
```bash
python test_connection.py
```
### 4. Deploy the server
Build and run locally:
```bash
docker build -t garmin-mcp .
docker run -d -p 8000:8000 \
-v $(pwd)/garmin_tokens:/data/garmin_tokens \
--env-file .env \
garmin-mcp
```
For cloud deployment (Fly.io, Render, Railway, etc.):
1. Mount a persistent volume at `/data` — copy your local `garmin_tokens/`
folder into `/data/garmin_tokens` on it so the container resumes the session
without needing a fresh login.
2. Set these environment variables on the host:
- `GARMIN_EMAIL` and `GARMIN_PASSWORD` — fallback credentials for full re-login
- `MCP_AUTH_TOKEN` — the secret you generated above
3. Expose port `8000`. Your MCP endpoint will be `https://your-domain.com/mcp`.
---
## Connect to Claude
1. In **claude.ai** → Settings → Connectors → **Add custom connector**
2. Enter your server URL: `https://your-domain.com/mcp`
3. Open **Advanced settings → Request headers** and add:
- Key: `Authorization`
- Value: `Bearer <your MCP_AUTH_TOKEN>`
4. Save, then enable the connector in a conversation.
> **Note:** Request-header auth is currently a beta feature on claude.ai. If
> you don't see it yet, check back soon or swap the middleware in `server.py`
> for OAuth.
---
## Running locally (without Docker)
```bash
source venv/bin/activate
python server.py
# Server starts at http://localhost:8000/mcp
```
---
## Notes & limitations
- **Single-user only** — this server is designed for your personal Garmin account.
- **Unofficial API** — Garmin may change internal endpoints at any time and break
the `garminconnect` library. A quick `pip install -U garminconnect` usually fixes it.
- **Keep your token secret** — anyone with `MCP_AUTH_TOKEN` can read your Garmin
health data through this server. Treat it like a password.
- **Token expiry** — if Garmin invalidates your session (e.g. after a password
change), re-run `login_once.py` locally and upload/mount the refreshed token store.
---
## License
MIT
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues