Garmin MCP for Claude
Provides access to Garmin Connect personal health and training data, including sleep, HRV, resting heart rate, Body Battery, stress, activities, laps, VO2 max, Fitness Age, and training load. Also exposes connector-computed metrics such as readiness, ACWR, and recovery time after hard sessions.
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
Garmin MCP for Claude
Your own Garmin Connect data in Claude. This is a personal, self-hosted remote MCP server on Cloudflare Workers (free plan) that you add to claude.ai as a custom connector.
It covers:
Recovery: sleep, HRV, resting heart rate, Body Battery and stress.
Training: activities, laps, VO2 max, Fitness Age and training load.
Computed metrics: readiness, ACWR and recovery after hard sessions.
Typical use: ask Claude "How did I sleep, and how hard should I train today?", or schedule a daily morning brief.
Single user: you deploy your own instance with your own Garmin key, and nobody else can use it.
Uses the official Garmin Connect Personal API. Personal, non-commercial use only.
Not affiliated with or endorsed by Garmin or Anthropic.
What you need
A Garmin Connect account and a Personal API key. Create the key at https://connect.garmin.com/app/settings/personalApiToken. It is shown only once; you choose an expiry of 30, 90 or 180 days or 1 year.
A free Cloudflare account.
Node.js 22+ and npm.
A Claude plan that supports custom connectors.
Related MCP server: Garmin MCP Worker
Setup (about 10 minutes)
git clone https://github.com/<you>/garmin-mcp.git
cd garmin-mcp
npm install
cp .env.example .env # then put your key in .env: GARMIN_API_KEY=...
npx wrangler login # log in to YOUR Cloudflare account
npm run setup # creates everything and deploys (see below)
node scripts/backfill.mjs # loads all your Garmin history into the databasenpm run setup (scripts/setup-instance.mjs) is safe to run again; steps that are done are skipped. It:
pins
wrangler.jsoncto your Cloudflare account id, so it can never deploy into another account;creates the KV namespaces and the D1 database and writes their ids into
wrangler.jsonc;applies the D1 migrations;
uploads your Garmin key as a Worker secret, and creates a login passphrase in
CONNECTOR_LOGIN.txt(local only, gitignored);registers a workers.dev subdomain if your account has none, sets the connector URL, and deploys.
At the end it prints your connector URL, e.g. https://garmin-mcp.<subdomain>.workers.dev/mcp.
Add it to Claude:
Go to Settings → Connectors → Add custom connector.
Paste the URL and leave the OAuth Client ID and Secret empty.
Click Connect and enter the passphrase from
CONNECTOR_LOGIN.txt.
After
npm run setup,wrangler.jsonccontains your account and resource ids. They are not secrets, but if you publish a fork, keep your setup-modifiedwrangler.jsoncout of it.
Tools
tool | what it does | source |
| One day: sleep (score, duration, stages, times), HRV (overnight average, status, baseline range), resting HR, Body Battery (at wake, high/low), stress, plus a | Garmin |
| Daily rows plus 7- and 28-day averages of HRV and resting HR | Garmin + averages |
| Activities with distance, time, pace, HR, Training Effect, training load, cadence, power, stride and VO2 max, plus totals per type | Garmin |
| Per-lap data from the activity's FIT file: pace, HR, cadence (steps/min), step length, power, running dynamics | Garmin (FIT) |
| The watch's VO2 max estimate per activity, read from the FIT file | Garmin (FIT) |
| Garmin Fitness Age with its components and targets | Garmin |
| Daily load, acute (7d) and chronic (28d) load, ACWR and a status label | computed |
| Deterministic 0–100 readiness score with components and a one-line explanation | computed |
| Nights until HRV and resting HR were back in your baseline band after that day's activity | computed |
| Verifies the Garmin token exchange. Returns no health data | – |
Garmin data vs. computed metrics
Every result starts with a provenance block:
garmin_provided: fields supplied by Garmin, passed through unchanged except for unit conversion.computed_by_connector: fields this connector calculates itself.
The server's MCP instructions tell Claude to say which is which, and never to present a computed metric (such as readiness) as a Garmin feature. Tool descriptions are labelled [Garmin data] or [Connector's own calculation; not Garmin …].
Not available from the Garmin Personal API
Training Readiness and Training Status.
get_readinessandget_training_loadare this connector's own metrics instead, and they will differ from the watch.Race predictions.
Lactate threshold.
Workout or course push: the API is read-only.
VO2 max as an API field. It is read from the FIT file instead; see Formulas.
See ENDPOINTS.md for the endpoints used and their response shapes.
How it works
claude.ai ──OAuth 2.1 (DCR + PKCE)──▶ Cloudflare Worker
├─ workers-oauth-provider: /.well-known/*, /register, /token, /authorize (passphrase login)
├─ /mcp (token required) → MCP server (Streamable HTTP)
│ ├─ recovery service D1 first (completed days), Garmin API for the rest
│ └─ activity service activities, FIT laps, load / readiness / recovery
└─ cron 05:30 local → nightly sync of the last 3 days into D1
Garmin client → personal.connectapi.garmin.com (token cached in KV; 401 → refresh + retry; 429 → backoff)
FIT parsing → @garmin/fitsdk (unneeded messages are stripped first to stay within the free plan's CPU limit)storage | content |
Worker secrets |
|
D1 |
|
KV | OAuth clients, grants, tokens |
KV | Cached Garmin access token, login rate limits, login state |
Security
Protected endpoint: only
/mcpserves data, and it requires a valid OAuth access token.Login: a passphrase checked against a PBKDF2 hash. The login form has a one-time CSRF state bound to a
__Host-cookie.Brute-force protection: 5 failed logins per IP per 15 minutes, and 20 per hour in total.
Secrets: they live only in your local
.envand in Worker secrets. The scripts pipe them in and never print them.Logs: they contain counts only, never health data.
Formulas
Training load / ACWR:
Daily load = the sum of Garmin's per-activity
activityTrainingLoad.Acute load = the 7-day mean; chronic load = the 28-day mean; ACWR = acute / chronic.
Status: < 0.8
detraining, < 1.0maintaining, ≤ 1.3productive, above thatoverreaching_risk. It isinsufficient_datawith less than 28 days of history or very low load.
Readiness (0–100):
component
weight
score
HRV
30%
70 + 15·zvs your 60-day baselineResting HR
20%
70 + 15·z, inverted (lower is better)Sleep score
20%
Garmin sleep score
Sleep duration
15%
100 · min(1, h/8)Load
15%
ACWR through yesterday: ≤ 1.0 → 100, 1.3 → 70, 1.5 → 30, ≥ 2.0 → 0
Missing parts are dropped and the weights renormalized. The same inputs always give the same score.
Recovery after a hard day:
Bands: HRV uses Garmin's "balanced" range; resting HR uses your 60-day mean ± 1 sd.
Counting: only the first night after the activity decides whether a value left its band (day 2 if day 1 has no data). If it did, the count runs to the first night back in band.
Body Battery at wake: the 15-minute reading closest to the end of sleep. Garmin's Body Battery report covers roughly the last 4 months.
VO2 max:
Source: the watch stores its estimate in each outdoor activity's FIT file, in the undocumented message 140, field 7: VO2 max = raw × 3.5 / 65536.
Validation: the format is community-documented and was validated against a watch, which shows the value rounded to an integer.
Treadmill: treadmill activities don't update it, so they are stored as null.
Configuration
Time zone: dates are interpreted in
Europe/Oslo. To change it:set
TIMEZONEinsrc/dates.ts;adjust the two UTC crons in
wrangler.jsoncso that one of them hits 05:30 local time in summer and winter;adjust the hour check in
isOsloSyncHour(src/sync/sync.ts).
Worker name / URL: change
nameinwrangler.jsoncbefore runningnpm run setup.Strength training: strength sessions are listed and count towards training load, but set, rep and weight details are not parsed.
Maintenance
task | command |
Redeploy |
|
New migrations |
|
New Garmin key (when it expires) | put it in |
New login passphrase |
|
Reload all history |
|
Local development |
|
Tests: npm test runs the unit and integration tests. They use mocked Garmin responses, FIT fixtures and D1 on node:sqlite. npm run type-check checks the types.
Contributing
Issues and pull requests are welcome. Please:
run
npm testandnpm run type-check;never commit
.env,CONNECTOR_LOGIN.txtor real health data (test fixtures must be synthetic).
License
MIT. Use of the Garmin Connect Personal API is subject to Garmin's own terms (personal, non-commercial use).
This server cannot be deployed
Maintenance
Related MCP Connectors
Garmin data in Claude & ChatGPT via the Garmin Health API. OAuth sign-in, no password sharing.
Garmin data in Claude: 135 tools — activities, sleep, HRV, training, workouts. Free, open source.
Connect Claude to your Intervals.icu watch data for fitness, workout review, and plan writing.
WHOOP recovery, strain, sleep and workouts in Claude via official WHOOP OAuth. Free, open source.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceA read-only remote MCP server that lets Claude query your own Garmin Connect data — sleep, HRV, Body Battery, stress, heart rate, and activities — through Garmin's official Personal API. It runs on Cloudflare Workers with OAuth sign-in, so it works from both the Claude mobile app and desktop.MIT
- AlicenseNot gradedqualityNot gradedmaintenanceA read-only remote MCP server that connects Claude to your own Garmin Connect account through Garmin's official Personal API, running on Cloudflare Workers with OAuth sign-in so it also works in the Claude mobile app. It lets you ask natural-language questions about sleep, HRV, resting heart rate, Body Battery, stress, daily summaries and activities across date ranges.MIT
- AlicenseNot gradedqualityNot gradedmaintenanceEnables Claude and other remote MCP clients to query your own read-only Garmin Connect data — recovery trends, sleep, HRV, Body Battery, stress, heart rate, and activities — through Garmin's official Personal API, with OAuth sign-in that works from the mobile apps as well as desktop. Deploys on Cloudflare Workers' free tier with no password sharing or scraping.MIT
- AlicenseNot gradedqualityCmaintenanceEnables connecting your own Garmin Connect data to Claude or another MCP client so you can ask about sleep, training, heart rate, weight, and more through read-only tools running on a Cloudflare Worker.378 npm1MIT