🏃 Garmin Connect MCP Server
Bring your Garmin health, fitness, and activity data to any LLM.
A Model Context Protocol server that turns your Garmin Connect account into 40+ ready-to-use tools for Claude, Cursor, and any other MCP-compatible client.
Built for cyclosport.app.
"How did I sleep last week? What's my training readiness today? Summarise my last 5 runs and tell me if I'm overtraining."
Ask your assistant — it now has the data.
✨ Highlights
- Full API coverage — 48 hand-crafted tools for the everyday questions
(with friendly date handling) plus every remaining
garminconnectmethod auto-exposed (100+ tools total), so the complete Garmin Connect surface is available: endurance score, intensity minutes, FTP, lactate threshold, nutrition, golf, gear stats, training plans, scheduled workouts, and all write/upload/delete actions. - Token-based auth with MFA support — log in once, no password stored at runtime. Tokens are cached locally and auto-refreshed.
- Secure by default — credentials and OAuth tokens are
.gitignored and can never be committed. - LLM-friendly responses — huge time-series payloads are automatically trimmed so they fit comfortably in a model's context window.
- Resilient — transparent re-authentication on session expiry, friendly
human-readable date inputs (
today,yesterday,-7). - Tested — a mocked, offline unit-test suite plus a live integration script you can point at your own account.
📊 What you can ask
| Category | Example questions |
|---|---|
| 😴 Sleep & recovery | "How was my deep sleep last night?" · "What's my HRV trend?" |
| ❤️ Daily health | "Resting heart rate this week?" · "Stress and Body Battery yesterday?" |
| 🏋️ Training | "What's my training readiness?" · "Am I overreaching?" · "VO₂max?" |
| 🏃 Activities | "Summarise my last 5 runs" · "Splits for my long ride?" · "Weather during my race?" |
| ⚖️ Body | "Weight trend over 30 days?" · "Body-fat percentage?" |
| 🏆 Progress | "My personal records?" · "Race-time predictions?" · "Badges earned?" |
🚀 Quick start
1. Install
git clone https://github.com/stitrace/garmin-mcp-server.git
cd garmin-mcp-server
# with uv (recommended)
uv venv && uv pip install -e .
# …or with pip
python -m venv .venv && source .venv/bin/activate
pip install -e .
2. Authenticate (one time)
Run the interactive login. It handles multi-factor authentication and caches
OAuth tokens to ~/.garminconnect so the server never needs your password again.
garmin-mcp-server-login
Garmin Connect — interactive login
----------------------------------------
Email: you@example.com
Password: ********
MFA / 2FA code: 123456
----------------------------------------
Success! Logged in as: Jane Runner
Tokens cached to: /home/you/.garminconnect
🔐 Your password is used only for this one login and is never written to disk. From here on, authentication uses the cached tokens.
3. Run
garmin-mcp-server # starts the MCP server over stdio
That's it. Now wire it into your favourite MCP client. 👇
🔌 Connect your client
Claude Desktop
Add to claude_desktop_config.json
(~/Library/Application Support/Claude/ on macOS,
%APPDATA%\Claude\ on Windows):
{
"mcpServers": {
"garmin": {
"command": "garmin-mcp-server"
}
}
}
If garmin-mcp-server isn't on your PATH, use the absolute path to the executable in
your virtual environment (e.g. /path/to/garmin-mcp-server/.venv/bin/garmin-mcp-server), or:
{
"mcpServers": {
"garmin": {
"command": "uv",
"args": ["run", "--directory", "/path/to/garmin-mcp-server", "garmin-mcp-server"]
}
}
}
Claude Code
claude mcp add garmin -- garmin-mcp-server
Cursor / other MCP clients
Point the client at the garmin-mcp-server command (stdio transport). Any
MCP-compliant client works the same way.
🧰 Tool reference
Profile & devices
| Tool | Description |
|---|---|
get_user_profile |
Full user profile, settings & preferences |
get_full_name |
Account holder's display name |
get_unit_system |
Preferred measurement units |
get_devices |
Registered Garmin devices |
get_device_last_used |
Most recently used device |
Daily health
| Tool | Description |
|---|---|
get_daily_summary |
All-in-one daily summary (steps, calories, RHR, stress…) |
get_stats_and_body |
Daily summary + body composition |
get_steps |
Intraday step counts |
get_daily_steps |
Daily step totals over a range |
get_floors |
Floors climbed/descended |
get_heart_rates |
Intraday heart-rate values |
get_resting_heart_rate |
Resting heart rate |
Wellness — sleep, stress, Body Battery, SpO₂, respiration, HRV
| Tool | Description |
|---|---|
get_sleep |
Detailed sleep (stages, scores, SpO₂) |
get_stress |
All-day stress |
get_body_battery |
Body Battery energy levels over a range |
get_spo2 |
Pulse-ox / blood oxygen |
get_respiration |
Breaths per minute |
get_hrv |
Heart-rate variability |
get_hydration |
Fluid intake |
get_blood_pressure |
Blood-pressure readings over a range |
Training & performance
| Tool | Description |
|---|---|
get_training_readiness |
Training Readiness score & factors |
get_training_status |
Load, acute/chronic balance, VO₂max trend |
get_max_metrics |
VO₂max (run & bike), fitness age |
get_fitness_age |
Fitness-age data |
get_race_predictions |
Predicted 5K/10K/half/marathon times |
get_hill_score |
Hill Score (climbing strength) |
get_progress_summary |
Aggregated progress between two dates |
Activities
| Tool | Description |
|---|---|
get_activities |
List recent activities |
get_activities_by_date |
List activities in a date range |
get_activity |
Single activity summary |
get_activity_details |
Detailed metrics / time-series |
get_activity_splits |
Per-split (lap) data |
get_activity_weather |
Weather during the activity |
get_activity_hr_zones |
Time in heart-rate zones |
count_activities |
Total activity count |
set_activity_name |
✍️ Rename an activity |
download_activity |
Download FIT/TCX/GPX/CSV/KML to disk |
Body composition & weight
| Tool | Description |
|---|---|
get_body_composition |
Weight, BMI, body fat, muscle mass |
get_weigh_ins |
Weigh-in entries over a range |
add_weigh_in |
✍️ Record a manual weigh-in |
Records, goals, gear & workouts
| Tool | Description |
|---|---|
get_personal_records |
Personal records (PRs) |
get_goals |
Goals by status (active/future/past) |
get_earned_badges |
Earned badges |
get_gear |
Registered gear (shoes, bikes…) |
get_workouts |
Saved workouts |
get_workout |
Single workout by ID |
get_menstrual_data |
Menstrual-cycle data for a date |
Advanced
| Tool | Description |
|---|---|
connect_api |
Raw read-only passthrough to any Garmin Connect API path |
✍️ = tool writes to your Garmin account. Everything else is read-only.
Auto-exposed tools
Beyond the curated tools above, every other public garminconnect method is
registered automatically using the library's native signature. This means the
full API is available — for example get_endurance_score,
get_intensity_minutes_data, get_weekly_steps, get_cycling_ftp,
get_lactate_threshold, get_running_tolerance, get_nutrition_daily_food_log,
get_gear_stats, get_training_plans, get_scheduled_workouts, the badge and
golf endpoints, and write/upload/delete actions (upload_activity,
set_blood_pressure, delete_activity, …).
These auto-exposed tools take the same arguments as the underlying
garminconnect method. The
list updates itself whenever you upgrade the library. Destructive actions
(delete_*) are labelled in their tool descriptions — use with care.
Friendly date inputs
Every date argument accepts natural values — no need to compute calendar dates:
| You can pass | Means |
|---|---|
(omitted) / today |
today |
yesterday |
yesterday |
-7 |
7 days ago |
2024-01-31 |
that exact day |
⚙️ Configuration
All configuration is via environment variables (a local .env file is
supported and auto-loaded). See .env.example.
| Variable | Default | Description |
|---|---|---|
GARMIN_EMAIL |
– | Account email (only needed for initial login) |
GARMIN_PASSWORD |
– | Password (optional; prefer token auth) |
GARMINTOKENS |
~/.garminconnect |
OAuth token cache directory (GARMIN_TOKEN_DIR also accepted) |
GARMIN_IS_CN |
false |
Set true for garmin.cn (China) accounts |
GARMIN_MCP_LOG_LEVEL |
INFO |
Log verbosity |
🔒 Security
Your health data is sensitive — this project treats it that way.
- No secrets in git.
.env,*.env, token stores (oauth*_token.json,.garminconnect/,.garth/) and downloaded activity files are all.gitignored. Rungit check-ignore .envto confirm. - Password-optional runtime. After
garmin-mcp-server-login, the server runs on cached tokens alone — no password is read or stored. - Local only. Tokens live on your machine; nothing is sent anywhere except Garmin's own API.
⚠️ Never paste real credentials into issues, PRs, or commits. If you think a token leaked, revoke access in your Garmin account and delete the token cache.
🛠️ Development
uv pip install -e ".[dev]"
ruff check garmin_mcp_server tests # lint
pytest # unit tests (mocked, no network)
The unit tests mock the Garmin client and run offline. There is also a manual
live integration check — point GARMINTOKENS at a valid token cache and call the
tools in garmin_mcp_server.server directly.
garmin_mcp_server/
├── server.py # FastMCP app + all tool definitions
├── client.py # lazy, resilient, thread-safe Garmin client wrapper
├── login.py # one-time interactive login (handles MFA)
├── config.py # environment configuration
└── helpers.py # date parsing + response trimming
🚢 Releasing
Releases are automated by .github/workflows/release.yml.
Pushing a version tag lints, tests, builds the wheel/sdist, publishes a GitHub
Release with auto-generated notes, and publishes to PyPI via
Trusted Publishing (OIDC — no API
tokens are stored anywhere).
To cut a release:
- Bump the version in both
pyproject.tomlandgarmin_mcp_server/__init__.py(the workflow fails if the tag doesn't match the package version), and move theCHANGELOG.mdentries under the new version. - Tag and push:
git tag -a v0.1.1 -m "v0.1.1" git push origin v0.1.1
One-time PyPI setup (Trusted Publishing)
Before the first PyPI publish, register this repo as a trusted publisher — no secrets required. At https://pypi.org/manage/account/publishing/ add a pending publisher with:
| Field | Value |
|---|---|
| PyPI project name | garmin-mcp-server |
| Owner | stitrace |
| Repository name | garmin-mcp-server |
| Workflow name | release.yml |
| Environment name | pypi |
Then create a GitHub Environment named pypi (repo Settings → Environments) to
gate publishes. After that, every v* tag lands on PyPI and
pip install garmin-mcp-server just works.
❓ Troubleshooting
| Symptom | Fix |
|---|---|
No valid Garmin tokens found… |
Run garmin-mcp-server-login first |
| Login fails with MFA enabled | Use garmin-mcp-server-login (handles MFA); password-only login can't |
| Empty data for "today" | Garmin may not have synced yet — try yesterday |
429 Too Many Requests |
You're being rate-limited; wait and retry |
| Tokens expired | Re-run garmin-mcp-server-login to refresh |
🙏 Acknowledgements
- Built for cyclosport.app.
python-garminconnect— the excellent library this server builds on (it now talks to Garmin directly; the formerly-usedgarthis no longer a dependency).- Model Context Protocol by Anthropic.
📄 License
MIT © Andrey Chubarov
Metadata
Release files for garmin-mcp-server 0.3.7
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| garmin_mcp_server-0.3.7.tar.gz | 85.6 kB | Without provenance | Details for garmin_mcp_server-0.3.7.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| garmin_mcp_server-0.3.7-py3-none-any.whl | Python 3 | none | any | Details for garmin_mcp_server-0.3.7-py3-none-any.whl |
Total release size: 109.1 kB
Release files / garmin_mcp_server-0.3.7.tar.gz
| Download URL | garmin_mcp_server-0.3.7.tar.gz |
|---|---|
| Size | 85.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b432581780adbee30b0e1813e01f928942d16f074a841ebf56231e85b66a9e53
|
|
BLAKE2b-256 checksum How to use checksums |
628b87e0968031d584199f61cd4cdbe1de39dcbd689d90ed5be6f7afb8803d92
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / garmin_mcp_server-0.3.7-py3-none-any.whl
| Download URL | garmin_mcp_server-0.3.7-py3-none-any.whl |
|---|---|
| Size | 23.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
abfa2ee54032885652d40b9190fae63c7e64b348bb8a14d4b2d495fbd3b83d3b
|
|
BLAKE2b-256 checksum How to use checksums |
d3bc27dd3f5856ed425e6eb17a17606ffd9b3090f25eddc63978aae83c553071
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Security for garmin-mcp-server 0.3.7
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.