Skip to main content
Glama

garmin-mcp

CI npm License: MIT

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

  1. Download garmin-mcp.mcpb.

  2. 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-mcp

Cursor, 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? ──▶ token

Each 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.jsonc

wrangler.jsonc is git-ignored, so your settings never get committed.

2. Create the KV namespace

npx wrangler kv namespace create OAUTH_KV

Paste 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.dev

  • Authorization 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_KEY

If wrangler asks to create the Worker first, say yes.

8. Deploy

npm run deploy

This 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 /mcp to 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_wellness and summarize_activities fetch, 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, yesterday resolve 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

get_readiness

"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

summarize_wellness

Sleep, resting HR, HRV, stress, steps and Body Battery trends: by week, month, weekday or overall

summarize_activities

Training totals: distance, time, pace, HR, training load and time in HR zones, by week, month, weekday or overall

compare_activities

2–5 activities side by side

get_activity_analysis

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

get_daily_summaries

One row per day: steps, calories, HR, intensity minutes, stress, Body Battery

get_sleep_summaries / get_sleep

Sleep hours by stage, score, HR, respiration, SpO2, sleep need and what drove it, over a range or for one night

get_hrv_summaries / get_hrv

HRV over a range, or one night's readings

get_body_battery, get_stress, get_steps

Daily series over a range (Body Battery includes the day's key readings)

get_heart_rate

One day, as hourly min/avg/max, plus every reading in a from/to window if given

get_spo2, get_fitness_age

Single-day detail

get_weight, get_weight_goals, get_hydration, get_wellness_activity_summaries

Ranges

search_activities

Individual activities, filterable by type, subtype, race, distance, duration, elevation gain, name and area, sortable by distance or speed

get_activity, get_activity_workouts

One activity in detail

get_nutrition, get_menstrual_cycle

Single-day detail

get_profile

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:

  1. Garmin's per-activity training load, on watches that record it

  2. Heart-rate zone load (minutes in each zone × zone number), on any watch or strap with heart rate

  3. 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_KEY in your AI app's MCP config and restart the app. For Claude Code, run claude mcp remove garmin and 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: npx fetches the latest version each time your AI app starts the server, so just restart the app. To stay on a fixed version, use garmin-mcp@1.3.0 in place of garmin-mcp in your config.

  • Cloud: git pull, then npm install and npm run deploy. Your wrangler.jsonc isn't touched by updates. If wrangler.example.jsonc gains 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 /mcp request 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 /mcp resource, 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_public blocks 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_analysis reads 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

MIT

Available Tools

26 tools
compare_activitiesCompare activitiesA
Read-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 detailA
Read-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)A
Read-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 workoutsA
Read-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 BatteryA
Read-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 summariesA
Read-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 ageA
Read-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)A
Read-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)A
Read-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 summariesA
Read-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_hydrationHydrationA
Read-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)A
Read-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)A
Read-onlyIdempotent

Food log for one day: nutrition goals, calories and nutrients consumed, and per-meal breakdown.

Inputs: date (required).

get_profileProfile and HR zonesA
Read-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 snapshotA
Read-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)A
Read-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 summariesA
Read-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)A
Read-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_stepsStepsA
Read-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_stressStressA
Read-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_weightWeightA
Read-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 goalsA
Read-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 summariesA
Read-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 activitiesA
Read-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 activitiesA
Read-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 trendsA
Read-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

    A3.9/5.0

    Scored across 26 tools

    Disambiguation4/5

    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.

    Naming Consistency5/5

    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.

    Tool Count3/5

    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.

    Completeness4/5

    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

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      B
      quality
      D
      maintenance
      Enables 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.
      6
      39
      MIT
    • A
      license
      A
      quality
      A
      maintenance
      Read/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.
      70
      1,969 PyPI
      121
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables 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 npm
      MIT