Daash
This server lets you read Garmin Connect training and health data, and create/manage structured workouts and training plans that sync to your watch.
Read activities, activity details, splits, pace, HR zones, power, swimming metrics, routes, weather, shoes and historical comparisons.
Read daily health: steps, calories, heart rate, Body Battery, stress, sleep stages, HRV, SpO2 and breathing rate.
Check readiness, fitness markers, race predictions, lactate threshold, FTP, personal records, fitness trends and recovery trends.
Create, update, schedule, unschedule and delete structured running/cycling/swimming/walking/hiking workouts.
Create strength workouts and search Garmin’s exercise catalogue.
Build and manage multi-session training plans, track plan progress and remove future plan sessions.
View shoe mileage and update recorded activity names, notes, shoes or activity type.
Check Garmin connection and login status.
Provides tools for reading Garmin Connect data (activities, splits, heart-rate zones, daily health metrics, sleep) and for creating and scheduling structured workouts so they sync to a Garmin watch.
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., "@Daash what should I run on Thursday based on my last three runs?"
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 Claude about your Garmin data, then have it write the session onto your watch.
An MCP server for your own Garmin Connect account. It reads your runs, splits, heart-rate zones and daily health metrics — and, unlike the read-only Garmin integrations out there, it can build a structured workout and schedule it, so the answer to "what should I run on Thursday?" ends up on your wrist instead of in a chat log.
Run it yourself and it is a local subprocess on your machine: no hosting, no server holding your credentials, no network exposure. That is the rest of this README, and it takes a Mac and a Terminal window.
Or don't run anything. Daash's hosted Garmin MCP server is the same tools behind OAuth 2.1 — paste one address into Claude or ChatGPT, sign in to Garmin once, and it works on your phone as well as your laptop with nothing installed. The trade is that a server holds your Garmin session, encrypted; the privacy policy sets out exactly what is kept and what is not.
You: My last three runs are all at the same effort. Give me something harder
for Thursday, based on what my recent paces actually support.
Claude: [reads your activities and splits, then proposes]
Thursday Threshold (running, about 52m 55s)
warmup: 15m
5 x
interval: 1.00 km @ 4:00/km-4:10/km
recovery: 1m 30s
cooldown: 10m
Create this and put it on Thursday?Install
macOS 10.15 (Catalina) or newer, with Claude Desktop already installed:
curl -fsSL https://raw.githubusercontent.com/Bartolome69/garmin-mcp/main/scripts/bootstrap.sh | bashThat fetches the code to ~/garmin-mcp, installs a modern Python via
uv, signs you in to Garmin, and registers the
server with Claude Desktop. It's the only command most people need. Read it
first if you'd rather —
scripts/bootstrap.sh is short.
Then quit Claude Desktop completely (⌘Q) and reopen it.
git clone https://github.com/Bartolome69/garmin-mcp.git
cd garmin-mcp
./scripts/setup.sh # venv + dependencies
./scripts/login.sh # sign in to Garmin once, caches the sessionThen register it with your MCP client. For Claude Desktop on macOS,
./scripts/install-claude-desktop.sh does it (with the app quit). For anything
else, copy .mcp.json.example, fill in the absolute paths, and point your client
at python -m garmin_mcp over stdio.
Related MCP server: garmin-mcp
Tools
Tool | What it returns |
| Runs, rides and workouts: distance, duration, pace per km and mile, average and max HR, HR zones, cadence, training effect, plus running dynamics and power when the watch records them |
| One activity in detail: per-split distance, pace, HR, cadence, running dynamics and power, full heart-rate time-in-zone, and what the recording says inside each lap: HR drift from first third to last at the paces run, plus per-km splits when the watch did not lap by kilometre |
| Today's recovery picture in one call: Garmin's readiness score and its factors, training status, acute against chronic load, four-week load focus, overnight HRV against baseline, body battery, stress, resting HR and sleep |
| Fitness markers: VO2 max, predicted race times from 5k to marathon, lactate threshold pace and HR, endurance score, hill score and running tolerance, each classified where Garmin classifies them |
| Steps, distance, calories, resting/min/max HR, body battery, stress, intensity minutes |
| Sleep stages with durations and percentages, sleep score, overnight HRV, resting HR |
| Structured workouts saved in the account |
| Builds a structured workout and adds it to Garmin Connect |
| Changes a workout in place. Same id, so dates it is scheduled on stay scheduled and the watch gets the new version at the next sync |
| Puts a workout on a date, which is what syncs it to the watch |
| Takes a workout off a day. Reversible — the workout itself is kept |
| Deletes a workout. The first call only reads its name back; removing it needs a second call quoting that name |
| Builds and schedules a whole training block in one call. Every session is validated first, so a mistake fails the call without leaving half a plan behind. Each workout is named with a short plan code, "Threshold 5x1k · HM", which is how the plan is found again; nothing about it is stored outside Garmin |
| The plan week by week, each session done, done on another day, missed, today or ahead, with completion so far, the next session, and what slipped in the last week. In Claude, ChatGPT and VS Code it also opens as an interactive plan view in the chat |
| Takes the rest of a plan off the calendar and out of the library. Past sessions stay. The first call only reports what would go |
| Draws the plan as an image for the conversation: each day's sessions beside what was planned |
| Which planned sessions actually got done, week by week. Counts a session if it happened within a day either side of its scheduled day |
| VO2 max and personal records — 5k, 10k, half, marathon and the rest |
| Whether the server is signed in, which account (masked), and the state of the token cache |
Dates accept YYYY-MM-DD, today, yesterday, tomorrow, or a signed offset
like -7 or +3.
Writing workouts
create_workout takes an ordered list of steps. Each has a type (warmup,
interval, recovery, rest, cooldown or repeat), exactly one of
duration_seconds or distance_meters, and an optional target — either pace
(minutes per km, as "4:05" or a range ["4:00","4:10"]) or hr ([150, 165]).
15 minute warmup, 5×1km at 4:05 with 90 second recoveries, 10 minute cooldown:
[{"type": "warmup", "duration_seconds": 900},
{"type": "repeat", "times": 5, "steps": [
{"type": "interval", "distance_meters": 1000, "pace": "4:05"},
{"type": "recovery", "duration_seconds": 90}]},
{"type": "cooldown", "duration_seconds": 600}]A single pace is widened by 5 s/km either side, because Garmin alerts on a range and an exact target beeps constantly. Repeat groups don't nest. Creating a workout only saves it — schedule it on a date for it to reach the watch.
What it can and can't do to your account
Reading is unrestricted. Writing is confined to the workout library: create a workout, schedule it, unschedule it, delete it. Nothing reaches your training history — no tool deletes or edits a recorded activity, so a run you did cannot be lost here however wrong a tool call goes.
delete_workout is gated rather than trusted to a docstring. The first call
never deletes: it reads the workout back and returns its name, and only a second
call passing that name as confirm removes it. A model cannot destroy a workout
it has not first named.
Your password is read from the environment, sent straight to Garmin, and never
written to disk. Only the session token Garmin issues is cached, at
~/.garmin-mcp/tokens.json, written 0600 inside a 0700 directory. No tool
returns the password or the token — get_connection_status reports a masked
address and the cache's age and permissions, nothing more. Logs go to stderr, so
they never corrupt the MCP stream on stdout.
After the first sign-in the server runs off the cached token. Set
GARMIN_EMAIL and GARMIN_PASSWORD in the server's environment if you want it
to re-authenticate unattended when that token eventually expires; leave
GARMIN_PASSWORD out and you'll re-run scripts/login.sh instead.
If it doesn't work
Start here — it walks from the interpreter to a live Garmin call and names the first broken link:
./scripts/doctor.sh"Garmin is rate-limiting logins from this IP address (429)" — the most common failure, and it isn't your password: Garmin blocks by network address before it checks credentials. Office wifi, university networks and VPNs get hit hardest. Sign in once over a phone hotspot; afterwards the cached session is used instead.
"Garmin is asking for a multi-factor code" — the server can't prompt over
stdio, so run ./scripts/login.sh in a terminal once. It handles the code and
caches the session.
Claude can't see the tools — Claude Desktop loads its config at launch and
writes its own copy back when it closes, so a change made while it's running
disappears. Quit it fully, run ./scripts/install-claude-desktop.sh, reopen.
Install fails mentioning Rust, OpenSSL or a compiler — macOS is too old.
The hard floor is 10.15 (Catalina), set by the Python interpreter itself:
uv's Intel build is compiled with minos 10.15 and will not launch below it, so
no amount of package pinning helps. The installers pass --only-binary :all: so
this fails fast rather than becoming a doomed source build, and retry against
constraints-legacy.txt in case a package has simply dropped a wheel.
No sleep data — the watch wasn't worn overnight, or hasn't synced. Sleep, HRV and overnight body battery only exist if you sleep in it.
Training skills
skills/ holds training methodology that builds on the tools — how
to read the data and what to prescribe from it. The first is a half marathon
skill in Pete Pfitzinger's style.
Development
.venv/bin/python tests/smoke_test.pyDrives the server over real stdio like an MCP client would, against a stubbed Garmin account — no network, no credentials. Covers every tool's response shape, workout construction, bad input, and the no-credentials startup path.
.venv/bin/python -m garmin_mcp.checkThe same code path against your real account, printing what comes back. Useful for confirming a setup end to end.
.venv/bin/python tests/http_test.pyThe HTTP transport, as Claude drives it: discovery, client registration, Google sign-in (stubbed), consent, token exchange and rotation, and tool calls with the token.
Other MCP clients, and ChatGPT
Nothing here is Claude-specific: it speaks MCP over stdio, so any client that
launches a local server will run it — Claude Code, Cursor, VS Code, Zed,
Windsurf. Copy .mcp.json.example, fill in absolute paths, point the client at
python -m garmin_mcp.
ChatGPT can't run this. Its connectors take a public HTTPS URL over SSE, because ChatGPT executes on OpenAI's servers and cannot start a process on your machine — there's no local-server option to enable. Serving it to ChatGPT would mean hosting it publicly and holding users' Garmin credentials, which is exactly what this project avoids.
Caveats
Not affiliated with Garmin. It uses the same private API the Garmin Connect
website does, via
garminconnect, because
Garmin publishes no consumer OAuth API. That API can change without notice and
take this with it.
MIT licensed. Built with Claude Code.
Available Tools
26 toolscreate_planA
Create a whole training block at once: every session built and put on its date.
You design the plan, from the user's goal, current fitness (get_fitness), readiness, history and a methodology; this builds each session as a structured workout and schedules it, so the whole block is on the watch in one go. Show the user the plan and get their agreement first: it writes to their real calendar. It answers with the new block drawn as a card, the same as get_plan, so no other call is needed to show it.
Each workout is named with a short plan code after a middle dot, "Threshold 5x1k · HM", which is how get_plan finds the block; nothing is stored outside Garmin. Every session is checked before anything is created, so a mistake in session 40 leaves no half plan behind.
Args: goal: What the block is for, e.g. "Half marathon, 1:40, 15 November". sessions: One per session, any order: "date" (YYYY-MM-DD, today to 30 weeks out), "name" (short, as on the watch: "Long run 18k"), "steps" (as for create_workout), and optional "sport" (running by default), "description", "pool_length_meters" for a swim (25 by default) and "phase": one short word such as "Base", "Build", "Peak" or "Taper", the same for every session in that phase; the card bands the weeks by it. Rest days are days with no session. At most 150. label: 2 or 3 letters or digits for the plan code, short because it ends every workout name: "HM", "10K", "MAR", "B1" for a base block. From the goal when omitted. name: A short name for the block, as the card's title: "Base block", "Marathon build". Two or three words, not the goal. method: The methodology in a few words, shown under the name: "Norwegian singles", "80/20", "Pfitzinger 18/55". milestones: Up to 4 points on the way to the goal, drawn as a timeline: {"label": "Half check", "date": "2026-11-29", "target": "1:22"}, date and target optional. The goal race is usually last.
Inputs: goal (required), sessions (required), name, label, +2 more. Returns: object
create_strength_workoutA
Create a strength training workout in Garmin Connect.
Adds a new workout; it never edits or replaces an existing one. Use schedule_workout afterwards to put it on a date so it syncs to the watch.
Exercise names come from Garmin's own catalogue. Common names usually work as typed; if one is ambiguous this says so and lists the candidates rather than guessing, since a wrong guess is the wrong movement on a watch. Call find_exercises to search.
Args: name: Name shown in Garmin Connect and on the watch. exercises: Ordered list of blocks. Each block is an object: - "exercise": catalogue name, e.g. "Barbell Bench Press" - "sets": number of sets (default 3) - "reps": repetitions per set (default 10) - "rest_seconds": rest after each set (default 90) - "weight_kg": optional target weight Example — a push day: [{"exercise": "Barbell Bench Press", "sets": 4, "reps": 8, "rest_seconds": 120, "weight_kg": 70}, {"exercise": "Barbell Overhead Press", "sets": 3, "reps": 10, "rest_seconds": 90}, {"exercise": "Cable Triceps Pushdown", "sets": 3, "reps": 12, "rest_seconds": 60}] description: Optional note stored with the workout.
Inputs: name (required), exercises (required), description. Returns: object
create_workoutA
Create a structured workout in Garmin Connect: always a new one, never an edit.
schedule_workout then puts it on a date so it syncs to the watch.
Args: name: Shown in Garmin Connect and on the watch. steps: Ordered steps, each with "type" (warmup, interval, recovery, rest, cooldown or repeat) and exactly one of "duration_seconds", "distance_meters" or "until_lap": true (ends on the lap button: a walk recovery between strides, a warm-up by feel, a rep to a landmark; still its own lap, so recoveries stay out of the rep splits). A repeat is {"type": "repeat", "times": 5, "steps": [...]} and can't contain another repeat. Optional target: "pace" ("4:05" or ["4:00", "4:10"], min/km), "hr" ([150, 165] bpm, or their Garmin zone: "z2", "z3-z4") or "power" (watts, 250 or [240, 260]; a share of the FTP set in Garmin, "95%" or ["88%", "94%"]; or a power zone, "z3"). By default only the work reps carry a target, pace for a run; warm-ups, cool-downs and recoveries carry none, so the watch stays quiet, and the chat and the workout's description say what easy pace to run them at: marathon pace plus 45-75 s/km, from get_fitness. The watch beeps off target, so a pace target is never narrower than 5 s/km either side ("4:00" becomes 3:55-4:05; tighter is widened around its middle): about 5 either side for reps, 10 for tempo and threshold, 15-20 for steady and long runs, or heart rate for easy running. A ride uses power on the work steps when the rider has a power meter or smart trainer (power shows on rides in get_activities) and an FTP (get_fitness): sweet spot 88-94%, threshold 95-105%, VO2 max 106-120%, endurance 56-75%. One value becomes a range 5% either side. Without a power meter, heart rate. Example, 15 min warm-up, 5 x 1 km at 4:05 with 90 s recoveries, 10 min cool-down: [{"type": "warmup", "duration_seconds": 900}, {"type": "repeat", "times": 5, "steps": [{"type": "interval", "distance_meters": 1000, "pace": "4:05"}, {"type": "recovery", "duration_seconds": 90}]}, {"type": "cooldown", "duration_seconds": 600}] sport: running (default), cycling, swimming, walking or hiking. description: Optional note stored with the workout. pool_length_meters: For a swim, 25 (default) or 50; the watch counts lengths with it.
A swim's steps are whole pool lengths (100, 200...), each with an optional "stroke": freestyle, backstroke, breaststroke, butterfly, im, drill or any. "pace" is per 100 m ("1:45") and shows on the step as a note, not a beeping target. A "rest" with no duration waits for the lap button, as swimmers rest at the wall; "duration_seconds" fixes it. A "note" (e.g. "kick with board") shows on the step. Set paces from recent swims (get_activities, per 100 m) or their CSS. Example, 8 x 100 free at 1:45 with 15 s rest: [{"type": "warmup", "distance_meters": 400, "stroke": "any"}, {"type": "repeat", "times": 8, "steps": [{"type": "interval", "distance_meters": 100, "stroke": "freestyle", "pace": "1:45"}, {"type": "rest", "duration_seconds": 15}]}, {"type": "cooldown", "distance_meters": 200, "stroke": "any"}]
Inputs: name (required), steps (required), sport, description, +1 more. Returns: object
delete_workoutADestructive
Delete a workout from the Garmin account. Permanent.
This asks before it acts, and the check is real rather than advisory: the
first call never deletes. It reads the workout back and returns its name,
and only a second call passing that name as confirm goes through. Show
the person what is about to go and let them answer before confirming.
Recorded activities are untouchable here. This removes a workout from the workout library — a plan for a session, not a session you ran.
Args: workout_id: Id from list_workouts or create_workout. confirm: The workout's exact name, which the first call returns.
Inputs: workout_id (required), confirm. Returns: object
find_exercisesARead-only
Search Garmin's exercise catalogue by name.
Use this before create_strength_workout when unsure what an exercise is called, or when it reports that a name was ambiguous. Matching ignores hyphens and spacing, so "pull up" and "pull-up" both work.
Args: query: Part of an exercise name, e.g. "bench press" or "row". limit: How many matches to return. Defaults to 25.
Inputs: query (required), limit. Returns: object
get_activitiesARead-only
List recent runs, rides, swims and workouts with distance, duration, pace and HR zones.
Swims come with pace per 100 m, pool length, SWOLF and stroke rate instead of pace per km.
Args: limit: Maximum activities to return (1-50). Defaults to 10. start_date: Optional first day of a date range, YYYY-MM-DD. end_date: Optional last day of a date range. Defaults to today when start_date is given.
Inputs: limit, end_date, start_date. Returns: object
get_activity_detailsARead-only
Splits and heart-rate detail for one activity.
Args: activity_id: The activityId from get_activities.
For a run, also returns the weather during it (temperature, dew point, wind, and what that does to pace), the shoes worn, the terrain (climbs, pace on uphill, flat and downhill, the pace the effort was worth on the flat, heart-rate decoupling and where it faded) and earlier runs of the same route with their pace and heart rate. Use these to explain a run rather than guessing why it was slow or hard. For a ride: normalised, average and max power, intensity factor and training stress against FTP, best efforts from 5 seconds to an hour, variability, time in power zones, climbs with speed, VAM and power, the weather during it, and earlier rides of the same route. For a swim: pace per 100 m, pool length, SWOLF, stroke rate and strokes, and the set as swum, each interval with its pace, stroke and the rest after it. Talk about a swim in those terms, never pace per km.
Inputs: activity_id (required). Returns: object
get_connection_statusARead-only
Check whether the server is logged in to Garmin Connect.
Reports which credentials are present and whether the cached session is usable. Never returns the password or the cached token itself.
Inputs: no named parameters. Returns: object
get_daily_summaryARead-only
Daily health summary: steps, calories, resting heart rate, body battery and stress.
With what charged or drained Body Battery and the longest stressful spells: "why is my Body Battery low", "when was I stressed".
Args: date: Day to report on. YYYY-MM-DD, 'today', 'yesterday' or an offset like '-3'. Defaults to today.
Inputs: date. Returns: object
get_fitnessARead-only
Garmin's read on the user's current fitness, as markers rather than history.
VO2 max and fitness age, predicted race times for 5k to marathon, lactate threshold heart rate and pace, endurance score, hill score and running tolerance, each with its own classification where Garmin gives one. Use it to set goals and training paces from evidence: "Garmin thinks you are a 1:42 half today" is a better starting point than asking. Also the heart rate and power zones set in Garmin, which workouts can target as "z2".
Also returns the cycling FTP set in Garmin, with watts per kilo when the user's weight is set. For a general "how's my training going", get_plan is the first call; this adds the fitness markers. For whether they are improving over months, get_fitness_trends.
Inputs: no named parameters. Returns: object
get_fitness_trendsARead-only
Whether the user is getting fitter: Garmin's fitness markers month by month.
VO2 max, predicted 5k, 10k, half and marathon times, lactate threshold pace and heart rate, endurance and hill score, and cycling FTP, each as a value per month with the change from the first month to now and whether that is improving, steady or declining. Alongside them, weight by month from the weigh-ins logged in Garmin Connect, with body fat and muscle where a scale records them. Use it for "am I getting fitter", "how's my fitness trending", "is my VO2 max going up", "has my half prediction come down" or "was I faster when I was lighter", and answer in writing: name the markers that moved and by how much, in plain terms. For weight, line the months up and say how the markers moved as weight changed, as a pattern rather than a cause. With no weigh-ins logged, say so and that weighing in through Garmin Connect or a Garmin scale is what fills it. For where they stand today, get_fitness is enough; for whether recovery is keeping up, get_recovery_trends.
Args: months: Months to look back, including this one (1-12). Defaults to 6.
Inputs: months. Returns: object
get_heart_rateARead-only
Heart rate through a day or night, from the watch's all-day readings.
For "was there a gap overnight", "when did my heart rate settle", "how low did it go". Answers with gaps (no reading for 10+ min), blocks with low, average and high, and the lowest and highest readings. A run's heart rate is get_activity_details.
Args: date: The day the window starts. Defaults to today; last night is yesterday with an evening start. start: Local start time, HH:MM. Defaults to midnight. end: Local end time, HH:MM. Before start runs into the next day.
Inputs: end, date, start. Returns: object
get_planTraining plan and progressARead-only
How the user's training is going, drawn as an interactive plan card: their plan week by week, each session done, missed or ahead, and the next one; with nothing scheduled, their recent weeks of running and riding.
Call it first for any general question about training or a plan ("how's my training going", "how am I doing", "this week", "show my plan", "what's next", "today's session", "am I on track"), wherever the plan came from: a coach, an app, Garmin Coach or create_plan. Call it alone: its situation says where the user is (a block under way, about to start or just done; a race ahead with nothing scheduled; regular running; or getting going) and offers one next step. Recovery, readiness or fitness follow only when asked, or when the user takes up that offer. Without a create_plan plan it reads the calendar around this week. Sessions carry workout_id and schedule_id for moving or retuning; the next one carries its steps and target paces. For any other session's steps, paces, recoveries or cool-down, call get_workout with its workout_id: never guess them.
Done means run within a day either side of its date, or any day that week if started from its workout on the watch. A strength session counts if any strength activity is recorded in its own week, early included, never another week's. Off its own day it shows as moved.
When the user says they have just done a session, call this before saying whether Garmin has it: don't guess. A strength activity is the session done even if it was logged in a gym app; the sets and weights aren't needed to count it. If a session isn't in Garmin but the user says they did it, trust them and suggest recording it on the watch next time. If what they did matters for what comes next and they haven't said, ask for a line. After a hard or long session in the last two days, ask once, in one short line, how it felt, and use the answer; not after an easy run.
The card draws the plan, so don't list it: add what it can't say, such as how the week is going. Each call draws it again, so answer follow-ups from that result; call again only when something may have changed (a session done, a workout moved or added, a new day) or the user asks to see it again: runs sync through the day, so after any of those the earlier answer goes stale. Don't call it for a specific question about a run, sleep, readiness, recovery, fitness or shoes.
Args: label: The code of a plan made with create_plan, e.g. "HM". Omit it for the plan running now, or the calendar.
Inputs: label. Returns: object
get_profileARead-only
Fitness profile: VO2 max and personal records.
Use this instead of asking the user for their PBs. Note that Garmin only knows records it has recorded itself — a race run without the watch, or before they owned it, will be missing.
Inputs: no named parameters. Returns: object
get_progressARead-only
Which planned sessions actually got done, week by week.
Answers "am I keeping up" rather than "what did I run". A session counts if it happened within a day either side of its scheduled day and ran at least roughly as long as planned — people move sessions around, and a week where everything got done on shifted days is a good week, not a failed one.
Each completed session carries a plain-English reason. Read those back when something looks wrong: a mismatch is visible there rather than hidden.
Effort is not judged here. This says whether a session happened; whether it was run at the right intensity is yours to assess from get_activity_details.
Args: weeks: How many weeks back to report, including this one (1-12).
Its result is drawn for the user as an interactive plan card, so there is no need to draw the weeks yourself, and calling it again draws the card again: fetch it fresh only when a session may have been done since. For "how is my plan going", get_plan is the better first call, and once it has been shown this is rarely needed too.
Inputs: weeks. Returns: object
get_readinessARead-only
Whether the user should train hard today or hold back.
For that question, and for checking today before prescribing a hard session. Not for reviewing a week, a run or how training is going: get_plan and the activity tools answer those. Has Garmin's readiness score and its factors, training status (productive, strained, recovery...), acute against chronic load and the four-week load focus ("too much intensity?"), with last night's sleep, HRV against baseline, body battery, stress and resting heart rate, so get_sleep_data and get_daily_summary aren't needed with it.
Args: date: YYYY-MM-DD, 'today', 'yesterday' or an offset like '-1'. Defaults to today.
Inputs: date. Returns: object
get_recovery_trendsARead-only
How the user is recovering across weeks, beside the training that caused it.
Week by week: overnight HRV, resting heart rate, sleep hours and score, Body Battery peak and stress, next to training load, run km and sessions, plus the last two weeks day by day and any marker that has moved off its usual range (resting HR up, HRV down, short sleep, a jump in load). Use it for "am I recovering well", "is this block too much" or "why do I feel flat". For whether to train hard today, get_readiness is the one.
Args: weeks: Weeks to look back, including this one (1-12). Defaults to 4.
For a general "how's my training going", get_plan is the first call; this adds how recovery is keeping up with it.
Inputs: weeks. Returns: object
get_shoesARead-only
The user's running shoes and how far each pair has gone.
For each active pair: km so far, runs, the limit set in Garmin (or a typical 650 km when none is set) and km left, km in the last four weeks, when it was last worn, and whether Garmin adds it to new runs by default. Pairs near the end of their life are flagged. Retired pairs are listed by name and km. Use it for "do I need new shoes" or "which shoes do I run in most".
Inputs: no named parameters. Returns: object
get_sleep_dataARead-only
Sleep stages and sleep score for a night, with overnight breathing rate and SpO2 against usual.
Args: date: The date you woke up on. YYYY-MM-DD, 'today', 'yesterday' or an offset like '-3'. Defaults to today.
Inputs: date. Returns: object
get_workoutARead-only
The steps of one workout, saved or scheduled: each step with its distance or time and target.
Use it for any question about what a session involves ("what's the pace for Wednesday's workout", "how long is the cool-down", "what are the recoveries"), with the workout_id from get_plan's sessions or from list_workouts. Never guess a workout's paces, recoveries or warm-up from other sessions: read them here. Repeats keep their structure, e.g. 8 x [1 km at a target, 60 s recovery].
Args: workout_id: The workout's id, from get_plan or list_workouts.
Inputs: workout_id (required). Returns: object
list_workoutsARead-only
List structured workouts saved in the Garmin account.
Args: limit: Maximum workouts to return (1-100). Defaults to 20.
Inputs: limit. Returns: object
remove_planADestructive
Take the rest of a plan off the calendar and out of the workout library.
For starting again or abandoning a block. Only sessions from today onward are removed; past ones stay, so the record of what was planned against what was run is kept. The first call only reports what would go. Removing it needs a second call with confirm set to the plan code, after the user has agreed.
Args: label: The plan code, e.g. "HM". confirm: The plan code again, to go ahead.
Inputs: label (required), confirm. Returns: object
schedule_workoutA
Put an existing workout on a date in the Garmin calendar.
Scheduling is what makes a workout sync to the watch.
Args: workout_id: Id from create_workout or list_workouts. date: The day to schedule it on. YYYY-MM-DD, 'today', 'tomorrow', or an offset like '+3'.
Inputs: date (required), workout_id (required). Returns: object
unschedule_workoutADestructive
Take a workout off a day in the Garmin calendar.
This is the reversible one: the workout itself is kept and can be scheduled again. Use it to clear a session the watch should no longer show. Deleting the workout outright is delete_workout.
Args: date: The day to clear. YYYY-MM-DD, 'today', 'tomorrow', or an offset like '+3'. If more than one workout sits on that day, they are listed back rather than guessed between. schedule_id: Remove one specific entry, from a previous listing or from what schedule_workout returned. Takes precedence over date.
Inputs: date, schedule_id. Returns: object
update_activityA
Change a recorded run or ride: its name, notes, shoes or type. Pass only what changes; the answer says what each was.
Args: activity_id: From get_activities. name: The new title. notes: Replaces the notes; read them with get_activity_details to add to them. shoes: A pair's name from get_shoes, replacing any on it; "none" takes them off. activity_type: e.g. trail_running, treadmill_running, track_running, indoor_cycling, walking.
Inputs: activity_id (required), name, notes, shoes, +1 more. Returns: object
update_workoutADestructive
Change an existing workout in place: its name, its steps, or both.
The workout keeps its id, so any dates it is already scheduled on stay
scheduled and the watch picks up the new version at the next sync. This is
how a plan adapts: retune the paces on next week's sessions rather than
deleting and recreating them. Give steps in the same shape create_workout
takes; leave it out to change only the name or description.
New steps replace the old list entirely, so to change part of a workout (one pace, the recoveries, the cool-down) read it with get_workout first and send back every step, changing only what the user asked for. The answer carries the steps it replaced, so say exactly what changed.
Args: workout_id: The workoutId from get_plan, list_workouts or create_workout. name: New name. Omit to keep the current one. steps: New step list, replacing the old one entirely. Omit to keep it. sport: Only needed with steps, and only to change the sport. description: New note. Omit to keep the current one. pool_length_meters: For a swim, a new pool length. Omit to keep it.
Inputs: workout_id (required), name, sport, steps, +2 more. Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 26 tools
Most tools target clearly distinct resources and the descriptions explicitly steer between overlapping calls such as get_plan, get_progress, and get_readiness. However, get_profile overlaps with get_fitness on VO2 max and records, and several recovery/health tools surface related signals, creating some risk of misselection.
Names consistently follow a snake_case verb_noun pattern: get_*, list_*, create_*, update_*, delete_*, schedule_*, and unschedule_*. The few noun-like names such as get_heart_rate and get_connection_status still fit the same predictable convention.
At 26 tools, the surface is heavy for a single assistant, though the broad Garmin training, health, workout, and plan domain justifies many of them. Some tools feel padded or duplicative, especially get_profile versus get_fitness, get_progress versus get_plan, and multiple overlapping health-read tools.
Coverage is broad: workouts can be created, read, updated, scheduled, unscheduled, and deleted; plans can be created, read, and removed; activities can be listed, inspected, and updated; and health, recovery, fitness, shoes, and connection status are covered. Minor gaps remain, such as deleting activities, managing shoes or goals directly, and editing a plan other than through individual workouts or full removal.
Maintenance
Related MCP Connectors
Garmin data in Claude & ChatGPT via the Garmin Health API. OAuth sign-in, no password sharing.
Connect Claude to your Intervals.icu watch data for fitness, workout review, and plan writing.
Garmin data in Claude: 135 tools — activities, sleep, HRV, training, workouts. Free, open source.
Log meals, water, and weight to Garmin Connect from Claude or ChatGPT.
Related MCP Servers
- AlicenseBqualityBmaintenanceConnects Claude Desktop to Garmin Connect, enabling natural language queries of fitness activity data, health metrics, sleep analysis, workout management, and device information with 94 available tools.1101MIT
- FlicenseNot gradedqualityDmaintenanceConnects Garmin watch data to Claude Desktop, allowing users to ask natural language questions about their health and activity data from Garmin Connect.-
- AlicenseBqualityBmaintenanceEnables Claude to access and query your Garmin Connect data—sleep, training, heart rate, and more—through a suite of read-only tools, with an optional write path for creating strength workouts.63MIT
- AlicenseBqualityBmaintenanceConnects Claude to a personal Garmin Connect account so users can ask natural-language questions about sleep, HRV, body battery, stress, heart rate, training readiness and activities, and optionally create or schedule workouts and log weigh-ins. Reads are returned as trimmed summaries with optional full JSON, and writes stay disabled unless explicitly enabled.24MIT