durely insights
Summary: An MCP server that connects an AI assistant to your Garmin Connect Personal API and lets it query, analyze and export your locally cached Garmin data, read-only against Garmin.
Check Garmin connection status, profile, heart-rate zones and fitness age.
List/search activities by date, sport, name, distance or duration; get full details, laps, swim lengths and second-by-second streams.
Analyze activities for HR/power zones, NP/IF/TSS, aerobic decoupling, best efforts and intervals; compare 2–6 activities.
Summarize training by week/month with volume, load, intensity distribution and fitness/fatigue/form.
Review recovery trends: sleep, HRV, resting HR, Body Battery and stress against your baselines and training load.
Query daily health, sleep, HRV, intraday HR/Body Battery/SpO2, body composition, nutrition/hydration and women’s health.
Run read-only SQL against the local Garmin cache for custom analysis.
Sync Garmin data manually, monitor sync status, and export original activity FIT files.
Keep data local and private, with identity/GPS excluded unless explicitly requested.
Connects to a Garmin Connect account through Garmin's official Personal API to sync and analyze full training history, activities, health, sleep, HRV, Body Battery, training load, and recovery data. Enables AI assistants to answer detailed questions about workouts, trends, laps, zones, and comparisons using local synced data, with read-only access and privacy controls.
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., "@durely insights How did I sleep this week, and how was my HRV?"
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.
durely garmin-insights-mcp
Ask your AI assistant about your own Garmin data: your full training history, health and recovery, analysed.
durely insights is an MCP server that connects your Garmin Connect account to your AI assistant (Claude, Cursor, GitHub Copilot, Gemini CLI, Codex or any other MCP client) through Garmin's official Personal API. It keeps a copy of your history on your own machine, so you can ask about a single run or about years of training and get an answer right away.
Early development. Everything below works, but tools and options can still change before version 1.0.
Unofficial project: not affiliated with or endorsed by Garmin.
What you can ask
Activities
"What did I do this week?" / "Show my last 10 runs"
"Tell me about yesterday's run": splits, heart rate, training effect, load
"How did my power and heart rate evolve over the intervals of Tuesday's ride?"
"Compare the bike legs of my last two triathlons lap by lap"
"Am I getting fitter on my regular Tuesday run?"
Training
"How has my training load and fitness developed over the last 3 months?"
"Analyse Saturday's ride: time in zones, decoupling, best efforts"
"How many kilometres did I run, ride and swim per year?"
Health and recovery
"How did I sleep this week, and how was my HRV?"
"Does a hard training day affect my HRV the next morning?"
"Compare my resting heart rate and sleep score per month this year"
"Show my heart rate and Body Battery through yesterday"
Related MCP server: GarminSynapse
What makes it different
Official API, no password. It uses a Personal API key that you create in Garmin Connect and can revoke at any time. No scraping, nothing that breaks when Garmin changes its login.
Your whole history, instantly. Everything is synced once to a local database and kept up to date, so trend questions over months or years don't wait on Garmin.
Second-by-second detail. Laps, swim lengths, heart rate, power, cadence and more come from each activity's original FIT file, including VO2max, recovery time and lactate threshold when your device records them.
Analysis built in. Time in zones, training load and fitness/fatigue/form, aerobic decoupling, best efforts, recovery trends and comparisons of similar sessions. For anything else, your assistant can run read-only SQL over your data.
Private by default. Your data stays on your machine. Your name, account ID and GPS positions are kept out of answers unless you ask for a route.
Read-only. It can't change anything in your Garmin account.
Quick start
You need Garmin Connect, an MCP client (Claude Code, Claude Desktop, Cursor, …) and either Node.js 24+ or Docker.
1. Create your Personal API key
In Garmin Connect on the web, open Settings → Personal API Token.
Create a key and pick how long it stays valid (30 days to 1 year). Garmin shows it only once, so copy it.
Save it by running this in your own terminal:
npx -y @durely/garmin-insights-mcp authIt asks for the key (your input stays hidden), checks it with Garmin and saves it to
~/.config/garmin/connect-personal-api-key, readable only by you. If Garmin rejects the key, nothing is saved. The server reads the key from this file, so it doesn't go in your client config.With Docker (no Node.js), save it to that file yourself:
mkdir -p ~/.config/garmin && chmod 700 ~/.config/garmin pbpaste > ~/.config/garmin/connect-personal-api-key # macOS; elsewhere, paste it into this file with an editor chmod 600 ~/.config/garmin/connect-personal-api-key
Don't paste the key into a chat with your AI assistant.
2. Choose how to run it
With Node.js (npx): nothing to install. Your server command is:
npx -y @durely/garmin-insights-mcpnpx downloads the npm package on first use and picks up new releases by itself. Check your key with npx -y @durely/garmin-insights-mcp auth --check. Prefer a fixed install? npm install -g @durely/garmin-insights-mcp gives you the garmin-insights command.
Or with Docker (no Node.js needed; Docker must be running). Your server command is:
docker run -i --rm \
-v ~/.config/garmin/connect-personal-api-key:/run/secrets/garmin_api_key:ro \
-v garmin-mcp-data:/data \
-e GARMIN_MCP_TIMEZONE=Europe/Brussels \
ghcr.io/durely/garmin-insights-mcpReplace Europe/Brussels with your own time zone. To check your key, run the same command with auth --check at the end. The image runs on Intel and ARM (Apple silicon, Raspberry Pi). More Docker tips are in How it works.
Or from source: clone the repo and build it with npm or Docker. See How it works.
Updating: with npx, restart your client. With Docker, run docker pull ghcr.io/durely/garmin-insights-mcp. Your synced data is kept.
3. Add it to your AI client
Claude Code:
claude mcp add garmin --scope user -- npx -y @durely/garmin-insights-mcpWith Docker:
claude mcp add garmin --scope user -- docker run -i --rm \
-v ~/.config/garmin/connect-personal-api-key:/run/secrets/garmin_api_key:ro \
-v garmin-mcp-data:/data \
-e GARMIN_MCP_TIMEZONE=Europe/Brussels \
ghcr.io/durely/garmin-insights-mcp--scope user makes it available in all your projects.
Claude Desktop: open Settings → Developer → Edit Config, add this, and restart Claude Desktop:
{
"mcpServers": {
"garmin": {
"command": "/opt/homebrew/bin/npx",
"args": ["-y", "@durely/garmin-insights-mcp"]
}
}
}Desktop apps don't see your shell's PATH, so use the full path to npx (find it with which npx). If the log then says node isn't found, add "env": { "PATH": "/opt/homebrew/bin:/usr/bin:/bin" }, with the folder from which node first.
Claude Desktop with Docker:
{
"mcpServers": {
"garmin": {
"command": "/usr/local/bin/docker",
"args": [
"run",
"-i",
"--rm",
"-v",
"/Users/you/.config/garmin/connect-personal-api-key:/run/secrets/garmin_api_key:ro",
"-v",
"garmin-mcp-data:/data",
"-e",
"GARMIN_MCP_TIMEZONE=Europe/Brussels",
"ghcr.io/durely/garmin-insights-mcp"
]
}
}
}Use full paths here; ~ isn't expanded in JSON configs.
Cursor: the same JSON as Claude Desktop, in ~/.cursor/mcp.json (global) or .cursor/mcp.json (project).
VS Code (GitHub Copilot agent mode): in .vscode/mcp.json, or via the command palette → MCP: Add Server:
{
"servers": {
"garmin": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@durely/garmin-insights-mcp"]
}
}
}Gemini CLI:
gemini mcp add garmin npx -- -y @durely/garmin-insights-mcpOpenAI Codex CLI:
codex mcp add garmin -- npx -y @durely/garmin-insights-mcpOthers (Windsurf, Cline, Zed, …): look for "MCP servers" in their settings. Any client that supports stdio servers works.
4. Try it
Start a new session and ask: "Check my Garmin connection." It should report connected.
The first question starts a sync in the background. Your activities are there within seconds; the rest of your history follows in a few minutes. After that, the data refreshes automatically when it's more than an hour old.
Good to know
Keep your key's expiry date in mind. Garmin's API doesn't tell the server when your key expires. Set
GARMIN_API_KEY_EXPIRES(e.g.2027-10-24) in your client config andgarmin_statusreminds you in time. With Docker, pass it to the container instead: add-e GARMIN_API_KEY_EXPIRES=2027-10-24to thedocker runarguments. Replacing the key file is enough; no restart is needed.Sync at least every few months. Garmin deletes intraday detail (heart rate through the day, overnight HRV readings) after about 4–5 months. Whatever the server has synced stays in your local copy.
Desktop and CLI clients only, for now. The server runs on your own machine, so it doesn't work in claude.ai on the web or mobile yet.
Your data goes to your AI client. The server only talks to Garmin, but the answers it gives go to the MCP client and model you use.
Troubleshooting
Problem | Fix |
Tools don't show up | Start a new session or restart the client. In Claude Code, check |
| Use the full path to |
"No Garmin Personal API key configured" | The key file is missing or not mounted. Check the path (and the |
"Garmin rejected the Personal API key" | The key expired or was revoked. Create a new one and replace the file (or run |
Dates are off by a day | Set |
Want to see the tools without an AI client |
|
Tools
You don't need to call these yourself; your assistant picks them. For reference:
Tool | What it does |
| Connection check: key, token, time zone, key expiry |
| Profile settings, heart rate zones per sport, fitness age |
| Search your activities by date, sport, name, distance or duration |
| One activity in full: summary metrics, splits, structured workout |
| Laps from the FIT file: pace, HR, power, cadence, swim lengths, VO2max, recovery time |
| Second-by-second HR, power, speed, cadence, altitude, …; zoom into any part |
| Time in zones, NP/IF/TSS, aerobic decoupling, best efforts, intervals |
| 2–6 activities side by side |
| Per week or month: volume per sport, load, intensity, fitness/fatigue/form |
| Sleep, HRV, resting HR, Body Battery and stress against your baseline, and their link with load |
| Per day, week or month: steps, resting HR, stress, Body Battery, calories, sleep, HRV |
| Nightly sleep stages, times and sleep score |
| Nightly HRV, 7-day average, baseline and status |
| One day hour by hour: heart rate, Body Battery, overnight HRV, SpO2 |
| Weigh-ins, body composition, weight goal |
| Food and hydration logged in Garmin Connect or a linked app |
| Menstrual cycle tracking |
| Read-only SQL over your synced data |
| Save an activity's original FIT file, e.g. to import elsewhere |
| Sync now, go further back in time, or download all FIT files |
| What's synced, and sync progress |
In clients that support prompts (often as slash commands), weekly_review, activity_deep_dive and recovery_check start a guided analysis.
Configuration
All optional; set them as environment variables in your client config.
Variable | Default | Purpose |
| Your key's expiry date ( | |
| system time zone | Your time zone (IANA name, e.g. |
| The key itself, instead of the key file | |
|
| Where the key file is |
|
| Where your synced data is kept |
|
| Refresh automatically when the data is over an hour old |
|
| Days of intraday detail (HR, overnight HRV, SpO2) to keep synced |
|
| Request throttle; Garmin publishes no limits |
|
|
|
In Docker, the key file and data directory are /run/secrets/garmin_api_key and /data.
Privacy and security
Your key stays on your machine. It's exchanged for a 24-hour token, cached with owner-only permissions. The server never writes the key itself. To revoke access, delete the key in Garmin Connect.
No telemetry. The server only talks to Garmin's Personal API.
Your synced data is sensitive. The local database and FIT files hold your health and activity history, including GPS tracks. The data directory is readable only by you (in Docker it lives in the
garmin-mcp-datavolume).Identity and location stay out of answers. Your name, profile picture and account ID are removed before anything is stored. GPS positions are only returned when you ask for route or location analysis (or query an activity's start and end point with SQL).
More
How it works: the local cache and sync, FIT files, how the numbers are calculated, Docker details, development
License
Apache-2.0. See LICENSE and NOTICE.
Data provided by Garmin devices via Garmin Connect; the analyses are insights derived in part from Garmin device-sourced data. Garmin and Garmin Connect are trademarks of Garmin Ltd. or its subsidiaries. This project is not affiliated with or endorsed by Garmin.
Available Tools
21 toolsgarmin_analyze_activityAnalyze an activityARead-onlyIdempotent
Compute training metrics for one activity from its second-by-second FIT data:
time in heart rate zones (the user's Garmin zones for that sport) and, with an FTP, in power zones (Coggan)
power: average, normalized power (NP), variability index, intensity factor and TSS (needs FTP)
aerobic decoupling: drift of power or pace per heartbeat between the first and second half (<5% is good for steady efforts of 45+ min)
first vs second half (negative/positive split)
best efforts: power over 5 s–60 min, heart rate over 1–60 min, fastest 400 m–marathon (running), 5–90 km (cycling), 100 m–3.8 km (swimming)
Edwards TRIMP (zone-weighted minutes) and detected work intervals with their consistency
FTP comes from ftp_w, else from the activity's own record (Garmin cycling FTP). Works for multisport legs too.
Inputs: activity_id (required), ftp_w, response_format.
garmin_compare_activitiesCompare activitiesARead-onlyIdempotent
Compare 2–6 activities side by side: date, type, duration, distance, pace/speed, heart rate, power (avg/NP), elevation, training effect and load, cadence, and efficiency factor (speed or NP per heartbeat; higher means more output for the same heart rate, a classic way to track aerobic progress on similar sessions).
With deep=true, also analyzes each one's FIT data (downloads files if needed): aerobic decoupling, time in low/moderate/high HR zones and best efforts.
Example: "Am I getting fitter on my Tuesday interval run?" → list those runs, then compare them.
Inputs: activity_ids (required), deep, response_format.
garmin_download_activity_fitExport activity FIT fileAIdempotent
Save the original FIT file of an activity to the server's exports folder and return its path, e.g. to import it into another tool (Intervals.icu, Golden Cheetah, a spreadsheet converter).
The file is exactly what the device recorded. Saved as /exports/<activity_id>.fit; an existing export is kept, not overwritten. For a multisport leg, exports the whole multisport file.
Inputs: activity_id (required).
garmin_get_activityGet Garmin activity detailsARead-onlyIdempotent
Get full details for one Garmin activity by ID.
Returns: name, description, type, start time and time zone, device, summary metrics (duration, distance, speeds, HR min/avg/max, power avg/max/NP/20-min, FTP, cadence, stride, calories, training effect + message, training load, TSS, IF, temperature, intensity minutes), split summaries (e.g. run/walk/stand intervals), the structured workout if one was followed, and multisport parent/child IDs.
Get IDs from garmin_list_activities. Example: "Tell me about yesterday's run" -> list activities for that date, then call this with its id.
Inputs: activity_id (required), response_format.
garmin_get_activity_lapsGet activity lapsARead-onlyIdempotent
Get the laps (and sessions, swim lengths and Garmin's physiological metrics) of one activity, from its original FIT file.
Returns per lap: start offset, duration (elapsed and timer), distance, pace/speed, avg/max HR, avg/max/normalized power, cadence, ascent/descent, intensity (active/rest/warmup/cooldown) and lap trigger; for swims, stroke and lengths. Also: device, multisport sessions, and FIT-only metrics when the device recorded them (VO2max, recovery time, lactate threshold HR/power).
Use it for interval sessions, splits and pacing. Downloads the FIT file on first use (one request), then it's cached. For a multisport leg ID, returns only that leg's laps.
Inputs: activity_id (required), lap_offset, response_format.
garmin_get_activity_streamsGet activity time seriesARead-onlyIdempotent
Get second-by-second data from one activity's FIT file, downsampled to fit in context.
Returns per field min/avg/max over the window, plus columnar series: t (seconds from the start of the activity, or of the leg for a multisport leg) and one array per field. Units: heart_rate bpm, power W, speed m/s, cadence steps/min (running) or rpm (cycling) or strokes/min, altitude m, distance m (cumulative), temperature °C, respiration_rate breaths/min, performance_condition (Garmin's -20..+20 score), vertical_oscillation mm, stance_time ms. Cadence averages leave out zeros.
Defaults to heart_rate, power, speed, cadence, altitude (whichever the activity has) at 10 s resolution, at most 500 points; the resolution coarsens automatically for long activities. Zoom in with start_s/end_s (e.g. one interval from garmin_get_activity_laps' start offsets) and a smaller resolution_s.
"position" adds GPS latitude/longitude and reveals where the user was: only request it when the user asks for location or route analysis. Developer fields (e.g. running power from a Connect IQ app) can be requested by the name listed in available_fields, like "dev_RP_Power".
Inputs: activity_id (required), end_s, fields, start_s, +3 more.
garmin_get_body_compositionWeight and body compositionARead-onlyIdempotent
Get weigh-ins from the local cache: weight (kg), BMI, body fat %, body water %, muscle and bone mass (kg), visceral fat, and where each came from (scale, manual, linked app). Includes the change over the period and the weight goal if one is set.
Only days with a weigh-in appear. Defaults to the last 365 days.
Inputs: end_date, start_date, response_format.
garmin_get_daily_healthDaily health and recoveryARead-onlyIdempotent
Get day-by-day wellness and recovery data, from the local cache (all history).
Per day: steps (+ goal), floors, resting/min/max heart rate, stress (average, max, % of the day at rest/low/medium/high), Body Battery (min, max, charged, drained), intensity minutes (moderate, vigorous), calories (total, active), respiration, hydration, plus that night's sleep (duration, score) and HRV (last-night average, status).
group_by="week" (Monday-based) or "month" aggregates: averages per day, minimums/maximums for min_hr/max_hr/stress_max, and totals plus per-day averages for steps and intensity minutes.
Defaults to the last 14 days. Daily rows are limited to 92 days per call; use group_by="week" or "month" for longer periods (e.g. a year by week). Today's values are partial.
Examples: "How's my recovery this week?" → defaults. "Resting HR trend this year" → start_date="2026-01-01", group_by="week".
Inputs: end_date, group_by, start_date, response_format.
garmin_get_hrvHeart rate variabilityARead-onlyIdempotent
Get nightly HRV (heart rate variability, in ms) from the local cache: last-night average, highest 5-minute value, 7-day average, the personal baseline range (balanced low/high) and Garmin's status (BALANCED, UNBALANCED, LOW, POOR).
With readings_date, also returns that night's 5-minute HRV readings (fetched from Garmin if older than the synced detail window).
Defaults to the last 28 nights, at most 366 per call. HRV needs a compatible watch worn overnight; history starts when that began.
Inputs: end_date, start_date, readings_date, response_format.
garmin_get_intradayIntraday health dataARead-onlyIdempotent
Get one day's health data through the day, averaged into intervals: heart rate (bpm), Body Battery (0-100), overnight HRV readings (ms) and SpO2 (pulse ox, %).
Returns per metric: min/avg/max, the day's summary values (e.g. resting HR, Body Battery charged/drained, SpO2 averages), and a columnar series (local times "HH:MM" + values). Synced for the last 150 days; older days are fetched from Garmin on demand. Garmin itself keeps intraday heart rate and HRV readings for only about 4–5 months (Body Battery longer), so older days may have just the day summary.
Defaults to today, all four metrics, 15-minute intervals. HRV and SpO2 need a compatible watch (SpO2 also needs pulse ox enabled).
Inputs: date, metrics, interval_min, response_format.
garmin_get_nutritionNutrition and hydrationARead-onlyIdempotent
Get food logged in Garmin Connect (or a linked app such as MyFitnessPal) per day: calories, carbs, protein and fat against the day's goals, and meals; plus hydration (ml) from the daily summary.
Food logs are fetched from Garmin on demand and cached. Defaults to the last 7 days, at most 31 per call.
Inputs: end_date, start_date, response_format.
garmin_get_profileGet Garmin profile and zonesARead-onlyIdempotent
Get the user's Garmin profile settings, heart rate zones per sport, and fitness age.
Returns: units and locale, time zone, preferred training days, HR zone floors per sport (with the max HR / LTHR / resting HR used to compute them), and the latest fitness age.
Use this before interpreting heart rate data (to know the user's zones), or when the user asks about their zones, max HR, lactate threshold or fitness age.
Inputs: response_format.
garmin_get_sleepSleepARead-onlyIdempotent
Get nightly sleep from the local cache: bed and wake time, total sleep, deep/light/REM/awake time, naps, sleep score with quality and Garmin's feedback, awake count, average respiration and sleep stress. JSON mode adds the sleep score's components (duration, stress, deep/light/REM %, restlessness, awake count) with Garmin's rating each.
A night belongs to the date you woke up. Defaults to the last 14 nights, at most 92 per call.
Inputs: end_date, start_date, response_format.
garmin_get_womens_healthMenstrual cycleARead-onlyIdempotent
Get menstrual cycle tracking from Garmin Connect for a date: cycle day, current phase and days until the next, period and cycle length, predicted next cycle, fertile window and ovulation date, plus that day's logged symptoms and notes (day view). Fetched live from Garmin.
Returns a clear "not tracked" message when cycle tracking isn't set up. Defaults to today.
Inputs: date, response_format.
garmin_list_activitiesList Garmin activitiesARead-onlyIdempotent
Search the user's Garmin activities (all history, from the local cache), newest first by default.
Returns one page of compact summaries plus the total number of matches: id, date, type, name, duration, distance, pace/speed, HR, power, elevation, training effect, training load, TSS/IF and VO2max where available, plus time in HR/power zones (JSON mode).
Examples:
"My runs this month" -> activity_type="running", start_date="2026-10-01"
"Longest rides ever" -> activity_type="cycling", sort_by="distance"
"Zwift sessions" -> activity_type="virtual_ride"
"Triathlon bike legs" -> include_multisport_legs=true, activity_type="road_biking"
The cache refreshes automatically when it's more than an hour old. Use garmin_get_activity for one activity's full details (splits, FTP, workout).
Inputs: limit, offset, search, sort_by, +10 more.
garmin_queryQuery the Garmin cache with SQLARead-onlyIdempotent
Run one read-only SQLite query against the local Garmin cache, for analyses the other tools don't cover (custom aggregations, correlations, records, comparisons across years).
Views (one row per item; use these first):
activities(id, parent_id, is_multisport_parent, type_key, sport, name, start_local, start_date, duration_s, moving_s, distance_m, elevation_gain_m, avg_speed_mps, avg_hr, max_hr, avg_power_w, norm_power_w, calories, aerobic_te, anaerobic_te, training_load, tss, vo2max) sport = Garmin parent type (running, cycling, swimming, walking, ...); type_key = exact type (road_biking, virtual_ride, lap_swimming, ...). Multisport: parent rows have is_multisport_parent=1, legs have parent_id; to count volume use is_multisport_parent = 0.
daily(date, steps, step_goal, walk_distance_m, floors_up, resting_hr, min_hr, max_hr, stress_avg, stress_max, stress_rest_pct, stress_low_pct, stress_medium_pct, stress_high_pct, body_battery_min, body_battery_max, body_battery_charged, body_battery_drained, intensity_moderate_min, intensity_vigorous_min, calories_total, calories_active, respiration_avg, hydration_ml)
sleep(date, start_local, end_local, sleep_s, deep_s, light_s, rem_s, awake_s, nap_s, score, quality, feedback, awake_count, respiration_avg, sleep_stress_avg) -- date = the morning you woke up
hrv(date, last_night_avg, last_night_5min_high, weekly_avg, baseline_low, baseline_high, status)
weight(date, weight_kg, bmi, body_fat_pct, body_water_pct, muscle_mass_kg, bone_mass_kg, visceral_fat, source, entries) Raw JSON (for fields not in the views, via json_extract):
activity(id, ..., raw_json) -- Garmin's full activity summary, e.g. json_extract(raw_json, '$.maxAvgPower_1200'), '$.hrTimeInZone_2', '$.fastestSplit_5000', '$.avgStrideLength'
daily_raw(domain, date, json) -- domains: daily_summaries, sleep, hrv, weight, body_battery, hrv_readings, heart_rate, spo2, food_logs Dates are 'yyyy-MM-dd' text in the user's local calendar; durations in seconds, distances in meters, speeds in m/s. SQLite dialect: strftime('%Y-%W', date) for weeks, date(date, '-7 days'), window functions (AVG(x) OVER (ORDER BY date ROWS 6 PRECEDING)) are supported.
Rules: one SELECT or WITH statement; at most max_rows rows returned (default 200); 10-second timeout. Prefer aggregating in SQL over returning many rows. Example: SELECT strftime('%Y', start_date) AS year, sport, ROUND(SUM(distance_m)/1000) AS km FROM activities WHERE is_multisport_parent = 0 GROUP BY 1, 2 ORDER BY 1, 3 DESC
Inputs: sql (required), max_rows, response_format.
garmin_recovery_trendsRecovery trendsARead-onlyIdempotent
Relate training load to recovery markers, day by day: resting HR, overnight HRV, sleep score and duration, Body Battery max and stress, each against the user's own rolling 28-day baseline.
Returns: a status (last 7 days vs the previous 28: HRV, resting HR, sleep, load; plus current fitness/fatigue/form), days flagged for low HRV, elevated resting HR or poor sleep, weekly averages, and correlations such as "previous day's load vs next morning's HRV" with their strength. Correlation is not causation, and short periods give noisy results.
Defaults to the last 8 weeks; at most 180 days.
Inputs: end_date, start_date, response_format.
garmin_statusGarmin connection statusARead-onlyIdempotent
Check that the Garmin Personal API connection works and report its configuration.
Reports: where the API key comes from, whether a token could be obtained and when it expires, the API key's expiry (if GARMIN_API_KEY_EXPIRES is set), the time zone used for calendar dates, and the server version.
Use this first when another garmin_* tool fails with an authentication error, or when the user asks whether their Garmin connection is set up.
Inputs: response_format.
garmin_syncSync Garmin dataAIdempotent
Start downloading Garmin data into the local cache. Runs in the background and returns immediately; follow progress with garmin_sync_status.
You rarely need this: other tools sync automatically on first use and when the cache is over an hour old. Use it to:
fetch the latest data right now (e.g. "I just finished a run"),
extend history further back (from="2019-01-01"),
re-download everything (mode="full"), e.g. after editing many activities in Garmin Connect.
Domains (default: all except fit_files): activities, daily_summaries, sleep, hrv, weight, body_battery, hrv_readings, heart_rate, spo2. "fit_files" downloads the original FIT file of every activity (one request each, ~2 activities/s). That's optional: FIT files are otherwise fetched on demand by garmin_get_activity_laps and garmin_get_activity_streams.
Inputs: from, mode, domains.
garmin_sync_statusGarmin sync statusARead-only
Show what's in the local Garmin cache and the progress of the current or last sync.
Returns: the running/last sync (progress, errors), and per data domain the date range covered, number of days, whether all history is in, and the last successful sync.
Inputs: response_format.
garmin_training_summaryTraining summaryARead-onlyIdempotent
Summarize training per week or month: sessions, hours, distance, elevation and training load, per sport; intensity distribution (share of time in low = HR zones 1–2, moderate = zone 3, high = zones 4–5, with a rough pattern: polarized, pyramidal, threshold-heavy); and the load trend at the end of each period: fitness (CTL, 42-day), fatigue (ATL, 7-day), form (TSB = CTL − ATL) and the acute:chronic ratio (ACWR; above ~1.5 is a commonly cited risk zone).
Load is Garmin's training load per activity; activities without it (older devices, some third-party uploads) get an estimate from average heart rate (Banister TRIMP) scaled to Garmin's load using the user's own activities. The response says how much is estimated. Multisport legs count toward their sport; walking is included as its own sport.
Defaults to the last 12 weeks by week. Filter with sport (e.g. running, cycling, or a type like virtual_ride).
Inputs: sport, end_date, group_by, start_date, +1 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 21 tools
Most tools target clearly distinct Garmin resources or analysis layers: daily health vs. sleep vs. HRV vs. intraday, and activity summary vs. laps vs. streams vs. computed analysis. There is some overlap because daily health includes sleep/HRV summaries, and several analysis tools operate on activities, but descriptions make the intended boundaries mostly clear.
All tools use a consistent garmin_ snake_case prefix, which makes the set predictable. Most names follow verb_noun patterns (get_*, list_*, analyze_*, compare_*), though a few are noun phrases or slightly irregular (garmin_status, garmin_training_summary, garmin_recovery_trends, garmin_download_activity_fit).
At 21 tools, the surface is on the heavy side for an MCP server and includes several closely related health and activity-analysis tools that could be consolidated. The breadth of Garmin domains justifies many of them, but the count is borderline rather than clearly well-scoped.
The set covers the Garmin insights domain thoroughly: activity listing/detail/laps/streams/FIT export, daily wellness, sleep, HRV, intraday health, body composition, nutrition, women's health, profile zones, training analysis, recovery trends, sync management, and a SQL escape hatch. No obvious core lifecycle gap exists for a read-only analytics server.
Maintenance
Related MCP Connectors
Garmin watch data for AI assistants: activities, sleep, HRV, recovery. Never writes to Garmin.
Explore Garmin activity and health data with your AI assistant.
Your Garmin, Oura, WHOOP, Hevy and 35 more fitness apps, queryable from your own AI.
Ask your AI about your fitness: activities and data from Garmin, COROS, Strava, GPX and more
Related MCP Servers
- FlicenseNot gradedqualityCmaintenanceEnables AI assistants to access and query Garmin Connect health and fitness data, including sleep, HRV, training load, and activities, with an optional coaching plugin for personalized training plans.9 npm4-
- FlicenseNot gradedqualityBmaintenanceEnables AI assistants to retrieve and analyze Garmin Connect health data, including daily summaries, sleep, HRV, workouts, and raw FIT files, while also supporting SQL queries on the underlying database.10-
- AlicenseNot gradedqualityCmaintenanceEnables AI assistants to retrieve and discuss personal fitness activities, training splits, heart-rate zones, and daily health metrics from a Garmin Connect account.MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI assistants to privately explore personal Garmin history, including activities, health metrics, HRV, sleep, and strength training, through self-hosted infrastructure.MIT