Garmin MCP
A read-only MCP server that lets your AI assistant query your own Garmin Connect data (via Garmin's official Personal API) to answer questions about training, sleep, recovery and wellness.
Readiness & recovery snapshot —
get_readiness: last night's sleep, HRV vs baseline, resting HR, Body Battery compared with your previous 7 days, plus training load and recent activities.Wellness trends —
summarize_wellness: sleep, resting HR, stress, steps, Body Battery, intensity minutes and overnight HRV by week, month, weekday or overall (up to 366 days).Training totals & trends —
summarize_activities: distance, time, pace, HR, training load and time in HR zones per period and activity type.Activity detail & comparison —
search_activities,get_activity,compare_activities(2–5 side by side) andget_activity_workouts(the linked planned workout).Deep activity analysis —
get_activity_analysis: per-km/mile splits, watch laps and reps, best efforts (1 km to marathon), HR-zone time and first-vs-second-half drift, computed from the FIT file.Sleep detail —
get_sleep_summariesandget_sleep: stages, score, HR, respiration, SpO2, sleep need and what drove it.HRV —
get_hrv_summariesandget_hrv(one night's readings).Daily metrics —
get_daily_summaries,get_body_battery,get_stress,get_steps,get_heart_rate(hourly, or full readings for a time window),get_spo2,get_fitness_age.Body & lifestyle logs —
get_weight,get_weight_goals,get_hydration,get_nutrition,get_menstrual_cycle,get_wellness_activity_summaries.Profile context —
get_profile: units, time zone and heart-rate zones.Smart conveniences — named periods in your time zone (
today,last_30_days,this_month…), compact-by-default answers withdetail: "full"for raw data, server-side fetching/aggregation, caching, and partial results.Prompt templates (in apps that support MCP prompts) — Am I ready to train today?, Sleep report, Training review, Weekly check-in, Compare recent sessions.
All tools are read-only and fixed-path; there is no write access, no golf/training plans/Gear, and no raw GPS or activity files exposed.
Provides read-only access to Garmin Connect data via Garmin's official Personal API, enabling queries about sleep, HRV, resting heart rate, stress, Body Battery, steps, training load, workouts, and activity summaries or comparisons.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Garmin MCP Am I ready to train hard today?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
garmin-mcp
Ask your AI assistant about your training, sleep and recovery, using your own Garmin data. It's an MCP server that gives Claude, ChatGPT and other assistants read-only access to your Garmin Connect data through Garmin's official Personal API.
Run it whichever way suits you. The tools are identical:
Local. Runs on your computer, started by your AI app. About a minute to set up, and all you need is your Garmin key. In Claude Desktop it's a one-click install.
Cloud. Runs on your own Cloudflare account (the free plan is plenty), behind GitHub sign-in. About 20 minutes to set up, and it then works on all your devices: web, desktop and phone.
It works with Claude, ChatGPT, Claude Code, Codex, Gemini CLI, Cursor, VS Code and other MCP clients. See Local or cloud? for which apps each option covers.
Either way, you get:
Insight, not another chart. Ask a question and get an answer based on your own data.
No password. It uses Garmin's official API key, which you can revoke at any time, not your Garmin login.
No middleman. Data goes straight from Garmin to your computer or your own Cloudflare account. No third-party service stores it.
Always current. It fetches from Garmin when you ask, so there's no sync to remember.
No extra cost. It uses the AI subscription you already have, and the cloud option fits Cloudflare's free plan.
Ask things like:
"Am I ready to train hard today?"
"How has my resting heart rate and HRV changed since May?"
"What were my running totals and average pace each month this year?"
"How much of my running is easy vs hard?"
"Which day of the week do I sleep worst?"
"Compare my last two long runs."
"Give me my km splits for Sunday's race. Did I negative split?"
Here it is in Claude on a phone (cloud version), projecting a 10k time from recent runs:
Not affiliated with or endorsed by Garmin. "Garmin" and "Garmin Connect" are trademarks of Garmin Ltd. or its subsidiaries.
Local or cloud?
Local | Cloud | |
Claude Desktop (one-click install), Claude Code | ✅ | ✅ |
Codex (CLI, and in the ChatGPT desktop app) | ✅ | ✅ |
Gemini CLI, Cursor, VS Code | ✅ | ✅ |
The ChatGPT app: web, desktop chat and phone | ✅ | |
Claude on the web | ✅ | |
AI apps on your phone or tablet | ✅ | |
Works when your computer is off | ✅ | |
Set up in about 1 minute | ✅ | |
No Cloudflare or GitHub account needed | ✅ | |
Garmin key stored only on your computer | ✅ | |
Nothing listening on the network | ✅ | |
Cache kept between sessions | ✅ | |
Free to run | ✅ | ✅ |
All 26 tools and 5 prompts | ✅ | ✅ |
Still deciding?
Go local. You get everything, in about a minute. In Claude Desktop it's a single download: no Node, no config file.
Go cloud. Phone apps can't start a local process, so they need a server they can reach over the internet.
Go cloud. The ChatGPT app itself (web, desktop chat and phone) connects to remote MCP servers only. Codex is the exception: the Codex CLI, and Codex in the ChatGPT desktop app, can run it locally.
Go cloud. Local means installing it on each computer; cloud means setting it up once and adding the URL to each app.
Start local. If you later want it on your phone, set up cloud. There's nothing to migrate, because your data stays in Garmin.
Yes. They can share a Garmin key.
Both are locked down, in different ways. Local has no sign-in because there's nothing to sign in to: only the app that started it can talk to it, and your key sits in that app's config on your computer. Cloud is reachable from the internet, so it's locked to your GitHub account, and your key sits in Cloudflare secrets. See Security model.
Local: you paste one command or one config snippet. Cloud: about a dozen commands, plus a few settings in the Cloudflare and GitHub dashboards.
Related MCP server: TrainingPeaks-MCP
Running locally
Your AI app starts the server as a local process and talks to it directly. There's no Cloudflare or GitHub setup.
All you need is a Garmin Personal API key (see Create a Garmin Personal API key).
Claude Desktop: one click
Download garmin-mcp.mcpb.
Double-click it, paste your Garmin key when asked, done.
Claude Desktop runs the extension itself, so you don't need Node or a config file, and your key is stored in your system keychain.
Claude Code, Cursor, VS Code and other apps
These need Node 22 or newer. There's nothing to install separately: your app
runs the garmin-mcp package with
npx.
Claude Code:
claude mcp add garmin --env GARMIN_API_KEY=<your key> -- npx -y garmin-mcpCursor, VS Code and other apps (or Claude Desktop, if you'd rather not use the extension): add this to the app's MCP config:
{
"mcpServers": {
"garmin": {
"command": "npx",
"args": ["-y", "garmin-mcp"],
"env": {
"GARMIN_API_KEY": "<your key>"
}
}
}
}VS Code's mcp.json uses "servers" instead of "mcpServers".
Codex
For the Codex CLI and Codex in the ChatGPT desktop app, which share one
config. Add this to ~/.codex/config.toml (needs Node 22 or newer):
[mcp_servers.garmin]
command = "npx"
args = ["-y", "garmin-mcp"]
env = { GARMIN_API_KEY = "<your key>" }Gemini CLI
Add this under "mcpServers" in ~/.gemini/settings.json (needs Node 22 or
newer):
"garmin": {
"command": "npx",
"args": ["-y", "garmin-mcp"],
"env": { "GARMIN_API_KEY": "<your key>" }
}Restart the app, then try "Am I ready to train today?".
Running in the cloud
MCP client ──OAuth──▶ Worker (/mcp) ──Bearer JWT──▶ Garmin Personal API
│
└─ /authorize ──▶ GitHub sign-in ──▶ allowed user ID? ──▶ tokenEach person deploys their own copy to their own Cloudflare account. It's locked to a single GitHub account, so your Garmin data is only ever reachable by you.
What you need
A Garmin Connect account and a Garmin device
A Cloudflare account (free plan)
A GitHub account
Node 22 or newer
About 20 minutes
Run all commands from the repo folder.
1. Install and log in to Cloudflare
git clone https://github.com/colincooperio/garmin-mcp.git
cd garmin-mcp
npm install
npx wrangler login
cp wrangler.example.jsonc wrangler.jsoncwrangler.jsonc is git-ignored, so your settings never get committed.
2. Create the KV namespace
npx wrangler kv namespace create OAUTH_KVPaste the id it prints into wrangler.jsonc, replacing YOUR_KV_NAMESPACE_ID.
3. Lock it to your GitHub account
Open https://api.github.com/users/<your-github-username> and copy the numeric
id. Put it in wrangler.jsonc as ALLOWED_GITHUB_USER_ID.
It uses the numeric ID rather than your username because usernames can be changed and then claimed by someone else.
4. Work out your Worker's URL
It will be https://garmin-cloud-mcp.<your-subdomain>.workers.dev. Your
workers.dev subdomain is on the Workers & Pages overview in the Cloudflare
dashboard.
Set MCP_RESOURCE in wrangler.jsonc to that URL with /mcp on the end.
5. Create a GitHub OAuth app
Go to GitHub → Settings → Developer settings → OAuth Apps → New OAuth App and fill in:
Homepage URL:
https://garmin-cloud-mcp.<your-subdomain>.workers.devAuthorization callback URL:
https://garmin-cloud-mcp.<your-subdomain>.workers.dev/callback
Then generate a client secret.
6. Create a Garmin Personal API key
Go to Garmin Connect → Personal API Token → Create Key. Pick 1 year and copy the key; Garmin shows it only once.
7. Store the secrets
Each command prompts you to paste the value.
npx wrangler secret put GITHUB_CLIENT_ID
npx wrangler secret put GITHUB_CLIENT_SECRET
npx wrangler secret put COOKIE_ENCRYPTION_KEY # paste output of: openssl rand -hex 32
npx wrangler secret put GARMIN_API_KEYIf wrangler asks to create the Worker first, say yes.
8. Deploy
npm run deployThis type-checks and runs the full test suite before it deploys. If anything
fails, nothing ships. The test output includes a few labelled expected error
lines: those come from tests that check the Worker refuses bad requests.
9. Connect your AI apps
Your server URL is https://garmin-cloud-mcp.<your-subdomain>.workers.dev/mcp.
Every app opens a GitHub sign-in the first time; sign in and approve.
Claude: Settings → Connectors → Add custom connector, and paste the URL. The connector follows your Claude account onto every device.
ChatGPT: add it as a custom connector (MCP server) with OAuth authentication. Custom connectors need developer mode and depend on your plan; see OpenAI's help pages for the current steps.
Claude Code:
claude mcp add --transport http garmin <url>, then run/mcpto sign in.Cursor, VS Code and other MCP apps: add a remote (HTTP) MCP server with the URL. Apps that support MCP OAuth handle the sign-in themselves.
Try "Am I ready to train today?". In apps that show MCP prompts, such as Claude's connector menu, you can also pick a ready-made prompt.
Why this one
Compared with password-based Garmin MCP servers
Most Garmin MCP servers predate the Personal API. They log in with your Garmin email and password and call Garmin Connect's private web endpoints, running on your own machine.
Password-based | This, local | This, cloud | |
Garmin access | Your email + password, session tokens on disk | Official Personal API key: revocable, expires (30 days to 1 year) | Same as local |
Breaks when Garmin changes its login or MFA | Yes, regularly | No, it uses the documented API | No |
Works in web and phone apps | No | No | Yes |
If the credential leaks | Your whole Garmin account | Read-only health data, until you delete the key | Same as local, and the key sits in Cloudflare secrets, not on a laptop |
The trade-off: the Personal API covers less. It has no golf scorecards, training plans, Garmin Training Status, race predictions, gear, courses or write access. The server tells the model this up front, so it says it isn't available instead of hunting for it.
Compared with a basic wrapper of the API
A one-tool-per-endpoint wrapper works, but it's slow and expensive in tokens: Garmin returns 100+ fields per activity and a reading every two minutes for heart rate. This server does the heavy lifting itself:
Questions answered in one call.
get_readiness,summarize_wellnessandsummarize_activitiesfetch, join and aggregate in the server. Five months of runs comes back as about 1,000 characters instead of about 190,000.Compact by default. Readable units, flattened rows, hourly heart rate instead of every reading. Every tool has
detail: "full"for the raw data.Garmin's per-call limits handled. Some endpoints only allow 28 or 31 days per request. Longer ranges are split and fetched in parallel, so the model makes one call, not four plus a failure.
Named periods in your time zone.
last_30_days,this_week,last_month,today,yesterdayresolve using the time zone in your Garmin profile.Times in your local time, even on DST nights. Garmin sends times as raw epochs, GMT strings and "local" values, and its local values are an hour out on the nights the clocks change. Every time comes back as your local
YYYY-MM-DD HH:MM, worked out from the exact moment and your profile's time zone, or from your watch's time zone when you're travelling.Partial results. If one source fails (say HRV for a night), the rest still comes back with a short note, so the model doesn't retry everything.
Caching. Data at least 3 days old has settled, so it's cached for 7 days, and activity details for a day. Activity analyses never change, so they're cached indefinitely (only the derived tables, never the file). Follow-up questions skip Garmin. (Locally, the cache lasts until the app restarts the server.)
Things Garmin's API doesn't give you directly: per-km/mile splits and best efforts (computed from the activity's FIT file in the server, so a two-hour run comes back as a few KB of tables, not 7,000 samples), training load (acute:chronic ratio), time in heart-rate zones (for one activity, the watch's own figures with the zones you had then, so they match Garmin Connect), weekday patterns, distance-weighted pace, side-by-side activity comparison.
Prompt templates for apps that support MCP prompts (such as Claude's connector menu): Am I ready to train today?, Sleep report, Training review, Weekly check-in, Compare recent sessions.
Tools
All tools are read-only. Ranges can be up to 92 days (366 for the
summarize_* tools). Every range tool also takes period, and single-day
tools take today or yesterday. For the summarize_* tools, a start_date
on its own means "since then": the range ends today.
Start here. These answer common questions in one call with a small response:
Tool | Answers |
| "How recovered am I?", "How did I sleep?": that night's sleep, HRV vs baseline, resting HR, Body Battery, each against your previous 7 days; training load; the last 3 days of training |
| Sleep, resting HR, HRV, stress, steps and Body Battery trends: by week, month, weekday or overall |
| Training totals: distance, time, pace, HR, training load and time in HR zones, by week, month, weekday or overall |
| 2–5 activities side by side |
| One activity from its original FIT file: per-km or per-mile splits, the watch's laps and workout reps, best efforts (1 km to marathon), time in HR zones as the watch recorded it, first- vs second-half drift |
Detail. Compact by default; pass detail: "full" for Garmin's raw data.
Tool | Data |
| One row per day: steps, calories, HR, intensity minutes, stress, Body Battery |
| Sleep hours by stage, score, HR, respiration, SpO2, sleep need and what drove it, over a range or for one night |
| HRV over a range, or one night's readings |
| Daily series over a range (Body Battery includes the day's key readings) |
| One day, as hourly min/avg/max, plus every reading in a |
| Single-day detail |
| Ranges |
| Individual activities, filterable by type, subtype, race, distance, duration, elevation gain, name and area, sortable by distance or speed |
| One activity in detail |
| Single-day detail |
| Units, time zone and heart-rate zones |
Together these cover every endpoint in
Garmin's Personal API spec,
including the activity file download that get_activity_analysis reads.
There's deliberately no "GET any path" tool: with one, the model guessed at
endpoints that don't exist and wasted calls.
Training load
The acute:chronic ratio compares the last 7 days with the average week of the last 28. Garmin's own Training Status isn't in the Personal API, so the server computes it from the best measure every activity in the window has, and says which it used:
Garmin's per-activity training load, on watches that record it
Heart-rate zone load (minutes in each zone × zone number), on any watch or strap with heart rate
Moving minutes, when there's no heart-rate data
It uses one measure for the whole window, because mixing them would make the ratio meaningless. Treat it as a guide, not Garmin's figure.
Looking after it
Rotating the Garmin key. Garmin emails you before the key expires. Create a new key, then:
Local: replace
GARMIN_API_KEYin your AI app's MCP config and restart the app. For Claude Code, runclaude mcp remove garminand add it again. For the Claude Desktop extension, paste the new key into the extension's settings in Claude Desktop.Cloud: run
npx wrangler secret put GARMIN_API_KEY. It takes effect immediately, and cached data is tied to the key that fetched it.
Then delete the old key in Garmin Connect.
Updating.
Claude Desktop extension: download the new garmin-mcp.mcpb from the latest release and double-click it.
Local with
npx:npxfetches the latest version each time your AI app starts the server, so just restart the app. To stay on a fixed version, usegarmin-mcp@1.3.0in place ofgarmin-mcpin your config.Cloud:
git pull, thennpm installandnpm run deploy. Yourwrangler.jsoncisn't touched by updates. Ifwrangler.example.jsoncgains a new setting, copy it across.
Logs. Locally, the server logs to your AI app's MCP log. In the cloud, run
npm run tail, or use Workers → garmin-cloud-mcp → Logs in the Cloudflare
dashboard. Logs record errors and rejected logins, never your Garmin data.
Cutting off all access at once. Delete the Garmin key in Garmin Connect;
that stops both local and cloud. For cloud, you can also set
ALLOWED_GITHUB_USER_ID to "0" and run npx wrangler deploy: every
connected app is refused on its next request.
Rolling back a bad cloud deploy. npx wrangler rollback.
A value is empty? Some fields depend on your device. Ask again with
detail: "full" to see everything Garmin sent. If the field is there, the
compact answer is missing it: please open an issue.
Security model
Both
Read-only, fixed paths. Every tool calls a fixed Garmin path with GET. There's no tool that writes to Garmin or fetches an arbitrary URL.
Your data's path. Garmin → your computer or your Worker → your AI app. It ends up in whichever chats you use the tools in, under that provider's data policies.
Local
No network listener. The server talks over stdin and stdout to the app that started it, and nothing else can connect. That's why there's no sign-in. Never wrap it in an HTTP proxy or expose it on a network; use the cloud version for remote access.
Garmin credentials. The key lives in your AI app's MCP config on your computer. The bearer token and cached responses are held in memory and gone when the process exits.
Cloud
Only you can get a token. After GitHub sign-in, the callback compares GitHub's numeric user ID with
ALLOWED_GITHUB_USER_ID. Anyone else gets a 403 before an MCP token exists. Every/mcprequest re-checks the ID, so changing it revokes existing sessions.Fails closed. An unset,
"0"or non-numeric allowed ID lets nobody in.Minimal GitHub access. The GitHub token is requested with no scopes, used once to read your user ID, and discarded.
Standard OAuth. OAuth 2.1 with PKCE (S256), CSRF-protected consent, and tokens bound to the
/mcpresource, via@cloudflare/workers-oauth-provider.Garmin credentials. The key lives only in Cloudflare secrets. The 24-hour bearer token is cached in KV under a hash of the key, never the key itself. Cached past-date responses live in the same KV namespace, in your Cloudflare account only, and expire after 7 days. Activity analyses (split and lap tables, never the FIT file or GPS positions) are kept there indefinitely, since an activity's file never changes.
No private addresses.
global_fetch_strictly_publicblocks the Worker from fetching private addresses.
Found a security problem? Please report it privately through GitHub's Security → Report a vulnerability on this repo rather than opening an issue.
Contributing
Bug reports and pull requests are welcome; see CONTRIBUTING.md for how to report issues, run the tests and cut a release.
Known limits
One Garmin account per cloud deployment or local install. To share with family, each person sets up their own copy with their own Garmin key.
Personal API coverage only. No golf, training plans, Garmin Training Status, race predictions, VO2 max history, personal records, gear, courses, badges or social.
Read-only. It can't log activities, weight, hydration or food, or change goals, settings or workouts. The Personal API has no write access, so your AI app can read your Garmin data but never change it.
No raw activity files or GPS tracks.
get_activity_analysisreads the FIT file in the server and returns only derived tables; the file, samples and positions never reach the model. Manual entries and GPX/TCX imports have no FIT data, so the tool says so instead.Rate limits unpublished. Garmin hasn't published Personal API rate limits. A 429 comes back as a clear tool error.
Credits
The OAuth flow is adapted from Cloudflare's
remote-mcp-github-oauth
demo. src/workers-oauth-utils.ts is vendored from it under its MIT licence.
Author
Built by Colin Cooper.
License
Available Tools
26 toolscompare_activitiesCompare activitiesARead-onlyIdempotent
Side-by-side compact detail for 2-5 activities (same fields as get_activity), fetched in parallel. Use for 'compare my last two long runs' or 'how did this race compare with last year's'; get the IDs from search_activities.
Inputs: activity_ids (required).
get_activityActivity detailARead-onlyIdempotent
One activity in detail: distance, times, pace (per km and mile), speed, HR, cadence, running dynamics, power, training effect and load, temperature, RPE/feel, and split summaries (warm-up/active/cool-down totals, not per-km; use get_activity_analysis for km/mile splits and laps). detail='full' returns Garmin's raw record (200+ fields).
Inputs: activity_id (required), detail.
get_activity_analysisActivity analysis (splits, laps, best efforts)ARead-onlyIdempotent
Per-km or per-mile splits, the watch's laps / workout reps, best efforts, time in HR zones and first-vs-second-half drift for ONE recorded activity, computed from Garmin's original FIT file. Use for 'my km splits', 'did I negative split', 'fastest 5 km in this run', 'how long in zone 2', 'did my HR drift'. splits: one row per km/mile (time, pace, cumulative time, avg/max HR, cadence, power, elevation gain/loss), timer time so pauses don't count, final partial split labelled with its distance. laps: the watch's own laps with intensity (warmup/active/cooldown/rest) and trigger (manual, distance, ...). best_efforts: fastest continuous 1 km, 1 mile, 5 km, 10 km, half and marathon the activity is long enough for, with where each started. hr_zones: time in each HR zone, as the watch recorded it with the zones in effect then (matches Garmin Connect and summarize_activities). drift: first vs second half pace, HR and aerobic decoupling. Get the ID from search_activities. Manual entries and GPX/TCX imports return a short 'unavailable' note.
Inputs: activity_id (required), unit, include.
get_activity_workoutsActivity workoutsARead-onlyIdempotent
The planned workout linked to one recorded activity, if any: workoutName, sport and steps, each with intensity, durationType/durationValue (metres for DISTANCE) and a target (e.g. targetType SPEED with targetValueLow/High in m/s). An activity started from one of Garmin's daily suggested workouts may carry only a workoutPhrase (e.g. EASY_WEEK_LOAD_BASE); an empty result means no workout was linked. This is the plan; for how each rep actually went use get_activity_analysis (laps). Get the ID from search_activities.
Inputs: activity_id (required).
get_body_batteryBody BatteryARead-onlyIdempotent
Daily Body Battery (Garmin's 0-100 energy estimate) over a range, one compact row per day: charged, drained, highest and lowest; the key readings the Personal API provides, in local time (about 6 a day: midnight, overnight peak, morning, afternoon, last); the events that moved it (sleep, activities, stress); and Garmin's end-of-day feedback. Event and feedback times are local too. For today's state use get_readiness; for trends over weeks or months use summarize_wellness (Body Battery high/low per period). Up to 92 days per call: pass a period (e.g. last_7_days, last_30_days, this_month) or start_date/end_date. detail='full' returns Garmin's raw report.
Inputs: detail, period, end_date, start_date.
get_daily_summariesDaily summariesARead-onlyIdempotent
One flat row per day: steps, distance, floors, calories, resting/min/max HR, intensity minutes, stress, Body Battery, respiration, SpO2, hydration. For trends over more than a few weeks use summarize_wellness instead.
Inputs: detail, period, end_date, start_date.
get_fitness_ageFitness ageARead-onlyIdempotent
Garmin's fitness age as of a date: chronologicalAge, fitnessAge (a decimal; round it) and previousFitnessAge, plus the components behind it, each with a value and a stale flag: rhr (resting HR), vigorousDaysAvg and vigorousMinutesAvg (weekly averages over numOfWeeksForIm recent weeks), and bmi with its lastMeasurementDate. BMI comes from the last weight entry, so it is often old. It changes slowly, so date='today' is usually all you need. This is not VO2 max, which the Personal API doesn't provide.
Inputs: date (required).
get_heart_rateHeart rate (one day)ARead-onlyIdempotent
Heart rate for one day: resting, min, max, 7-day resting average, and hourly min/avg/max in local time. Give from/to (local HH:MM) to also get every ~2-minute reading in that window, e.g. overnight or around a workout. detail='full' returns Garmin's raw payload (every reading, large).
Inputs: date (required), to, from, detail.
get_hrvHRV detail (one night)ARead-onlyIdempotent
HRV summary plus individual readings for one night, times in local 'YYYY-MM-DD HH:MM'. Use get_hrv_summaries unless the readings themselves matter.
Inputs: date (required).
get_hrv_summariesHRV summariesARead-onlyIdempotent
Daily heart rate variability over a range: overnight average, 5-minute high, 7-day average, baseline band, status.
Inputs: period, end_date, start_date.
get_hydrationHydrationARead-onlyIdempotent
Water intake the user logged in Garmin Connect, day by day over a range, as Garmin returns it. Usually empty: Garmin only records hydration that is logged, so an empty result means nothing was logged, not that the user drank nothing. get_daily_summaries also carries hydration_ml per day. Up to 92 days per call: pass a period (e.g. last_7_days, last_30_days, this_month) or start_date/end_date.
Inputs: period, end_date, start_date.
get_menstrual_cycleMenstrual cycle (one day)ARead-onlyIdempotent
Menstrual cycle tracking as of one date, in two parts returned as Garmin sends them: summary (Garmin's cycle summary for that date) and dayView (Garmin's day view for that date, with anything logged). Empty unless the user tracks their cycle in Garmin Connect. date accepts YYYY-MM-DD, 'today' or 'yesterday'; call once per date.
Inputs: date (required).
get_nutritionNutrition (one day)ARead-onlyIdempotent
Food log for one day: nutrition goals, calories and nutrients consumed, and per-meal breakdown.
Inputs: date (required).
get_profileProfile and HR zonesARead-onlyIdempotent
User profile settings (units, measurement system, birth year, etc.) and heart-rate zones. Useful context before interpreting training data.
Inputs: no named parameters.
get_readinessReadiness snapshotARead-onlyIdempotent
Best first call for 'how recovered am I', 'should I train hard today', 'how did I sleep last night'. One compact object: that night's sleep (score, stages, HR, respiration), HRV vs baseline and status, resting HR, Body Battery, each compared with the user's previous 7 days; training load (last 7 days vs 28-day weekly average); and activities from the last 3 days. Sleep is filed under the date the user woke up, so use 'today' for last night.
Inputs: date.
get_sleepSleep (one night)ARead-onlyIdempotent
One night's sleep, same compact shape as get_sleep_summaries. Use detail='full' for Garmin's complete record.
Inputs: date (required), detail.
get_sleep_summariesSleep summariesARead-onlyIdempotent
Night-by-night sleep over a range, one compact row per night filed under the date the user woke up: start_local/end_local as local 'YYYY-MM-DD HH:MM'; hours of sleep, deep, light, REM and awake (and naps); sleep score with its quality and factor ratings; average HR, stress and respiration (avg/low/high); SpO2 average/lowest; sleep need vs baseline in minutes with what drove it (training load, e.g. HIGH_ACWR_AND_CHRONIC, HRV, sleep history, naps) and next night's need; Garmin's insights. For last night alone use get_readiness or get_sleep; for trends over weeks or months use summarize_wellness. Up to 92 days per call: pass a period (e.g. last_7_days, last_30_days, this_month) or start_date/end_date. detail='full' returns Garmin's raw records.
Inputs: detail, period, end_date, start_date.
get_spo2Pulse Ox (one day)ARead-onlyIdempotent
SpO2 for one day: average, lowest, sleep average, 7-day average and hourly averages, times in local 'YYYY-MM-DD HH:MM'. detail='full' adds every reading and altitude data.
Inputs: date (required), detail.
get_stepsStepsARead-onlyIdempotent
Day-by-day steps over a range, one row per day as Garmin returns it: calendarDate, totalSteps, totalDistance (metres) and stepGoal. get_daily_summaries has the same steps alongside floors, calories, HR and stress; for weekly or monthly averages over longer spans use summarize_wellness. Up to 92 days per call: pass a period (e.g. last_7_days, last_30_days, this_month) or start_date/end_date.
Inputs: period, end_date, start_date.
get_stressStressARead-onlyIdempotent
Day-by-day all-day stress over a range, one row per day as Garmin returns it: calendarDate, then values with overallStressLevel (Garmin's 0-100 score) and highStressDuration, mediumStressDuration, lowStressDuration and restStressDuration in seconds. For trends over weeks or months, or 'which weekday am I most stressed', use summarize_wellness (stress per period, group_by='weekday'); stress during sleep is avg_stress in get_sleep_summaries. Up to 92 days per call: pass a period (e.g. last_7_days, last_30_days, this_month) or start_date/end_date.
Inputs: period, end_date, start_date.
get_weightWeightARead-onlyIdempotent
Weight and body composition measurements over a range, from a Garmin smart scale or entered manually in Garmin Connect, returned as Garmin sends them: weights are in grams (74000 = 74 kg); date is the local measurement time, 'YYYY-MM-DD HH:MM'. Often empty, since it needs a smart scale or manual entries. When nothing was recorded in the range, the result holds only previousDateWeight, the last measurement before it: check its calendarDate before treating it as current. Up to 92 days per call: pass a period (e.g. last_7_days, last_30_days, this_month) or start_date/end_date. For longer histories, make several calls.
Inputs: period, end_date, start_date.
get_weight_goalsWeight goalsARead-onlyIdempotent
The weight goal the user set in Garmin Connect that was in effect over a range, with its goal range, returned as Garmin sends it. Empty ({}) when no weight goal is set, which is common. Pair with get_weight to track progress. Up to 92 days per call: pass a period (e.g. last_7_days, last_30_days, this_month) or start_date/end_date.
Inputs: period, end_date, start_date.
get_wellness_activity_summariesWellness activity summariesARead-onlyIdempotent
Garmin's wellness activity summaries over a range: all-day movement records kept apart from recorded workouts, returned as Garmin sends them. Often empty; for daily movement use get_daily_summaries or get_steps, and for workouts search_activities or summarize_activities. Up to 92 days per call: pass a period (e.g. last_7_days, last_30_days, this_month) or start_date/end_date.
Inputs: period, end_date, start_date.
search_activitiesSearch activitiesARead-onlyIdempotent
List individual recorded activities (runs, rides, swims, strength, etc.) with the activity_id needed for get_activity and get_activity_analysis. Filter by date, type, race, distance, duration, elevation gain, name text or a lat/long box. For totals, averages or trends over weeks/months, use summarize_activities instead: it is far smaller. Default detail='compact' returns key stats only (startTimeLocal as local 'YYYY-MM-DD HH:MM', distance m, duration/movingDuration s, averageSpeed/maxSpeed m/s, HR, elevation, calories, cadence, power, training effect). Use detail='full' only when a field you need is missing, and with a small limit. Sport-specific extras such as golf scorecards, shot data or dive logs are NOT available.
Inputs: area, limit, start, detail, +16 more.
summarize_activitiesSummarize activitiesARead-onlyIdempotent
Best tool for activity totals and trends (e.g. 'how far did I run each month', 'has my pace improved'). Fetches every matching activity in the range and returns only aggregates per period and type: count, distance (km and mi), moving time, elevation, longest, average speed and pace (distance-weighted), fastest average pace, average HR, training load, and time in HR zones 1-5 (minutes and %, for zone 2 / 80-20 questions). Range up to 366 days.
Inputs: area, period, end_date, group_by, +12 more.
summarize_wellnessSummarize wellness trendsARead-onlyIdempotent
Best tool for wellness trends over weeks or months ('has my sleep improved', 'resting HR trend this year', 'stress lately'). Returns per-period averages only: steps, resting HR, stress, Body Battery high/low, intensity minutes (totals), respiration, sleep hours/deep/REM/score, overnight HRV, with min-max ranges for key metrics and day counts where data is missing. group_by='weekday' answers 'which days do I sleep worst / most stressed'. Range up to 366 days; fetches and aggregates in the server.
Inputs: period, include, end_date, group_by, +1 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 26 tools
The tool set has many near-pairs (get_hrv vs get_hrv_summaries, get_sleep vs get_sleep_summaries, get_steps vs get_daily_summaries, get_activity vs get_activity_analysis vs compare_activities), but descriptions consistently disambiguate with explicit 'use X instead' guidance and scope hints. A cluster of day-by-day range tools (get_steps, get_stress, get_hydration, get_daily_summaries, get_wellness_activity_summaries) still overlap enough that an agent may hesitate, and get_wellness_activity_summaries is a near-dead-end trap.
Every name is snake_case and verb-first, with a small, predictable vocabulary (get_, summarize_, search_, compare_). The suffix convention (_summaries for ranges) is applied consistently across domains.
26 tools is at the heavy end, and the count is inflated by per-domain single-day/range duplication that could be collapsed via a period parameter. Each tool maps to a real Garmin endpoint, so nothing is arbitrary, but the surface feels broad for typical agent use.
Coverage across sleep, HRV, stress, HR, Body Battery, SpO2, steps, hydration, weight, activities, nutrition, readiness, fitness age and cycle tracking is broad and well cross-referenced, with unavailable data (VO2 max, golf/dive) explicitly flagged. Minor gaps remain: no scheduled/planned workout listing beyond activity-linked workouts, and the surface is entirely read-only.
Maintenance
Related MCP Connectors
Garmin data in Claude & ChatGPT via the Garmin Health API. OAuth sign-in, no password sharing.
Your Garmin, Oura, WHOOP, Hevy and 35 more fitness apps, queryable from your own AI.
Remote streamable-HTTP MCP server running on a single Cloudflare Worker. Your assistant gets live Airbnb, Amazon, Booking.com, Google Flights, Maps and Reddit data, social search on X, Instagram and TikTok, the Meta Ad Library, and image/video generation without any keys. Connect your own accounts to let it send WhatsApp or Telegram messages, work an IMAP inbox, manage Meta Ads campaigns and publish to X and LinkedIn. OAuth 2.1 with PKCE; stored credentials are AES-256-GCM encrypted.
Garmin data in Claude: 135 tools — activities, sleep, HRV, training, workouts. Free, open source.
Related MCP Servers
- AlicenseBqualityDmaintenanceEnables querying sleep, readiness, and resilience data from the Oura API via the Model Context Protocol (MCP), allowing natural language interaction and seamless integration with tools like Claude.639MIT
- AlicenseBqualityCmaintenanceTrainingPeaks MCP server for Claude Desktop and AI assistants. Query workouts, analyze fitness, power peaks and more.85196MIT
- AlicenseAqualityAmaintenanceRead/write MCP server for Intervals.icu — 51 tools spanning activities, streams, wellness, calendar, gear, and sport zones, plus structured workout generation with a built-in syntax reference for cycling, running, and swimming.701,969 PyPI121MIT
- AlicenseNot gradedqualityBmaintenanceEnables users to connect ChatGPT or remote MCP clients to their own Garmin cloud health and activity data through a self-hosted, read-only OAuth-protected Cloudflare Worker.378 npmMIT