garmin-mcp-triathlon
Provides tools for interacting with Garmin Connect, enabling AI agents to manage health data, activities, workouts, devices, gear, and triathlon coaching features including workout builders and analytics.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@garmin-mcp-triathlon What's my readiness and load for today?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Garmin_MCP_Triathlon
Installs two commands: garmin-mcp-triathlon (the server your MCP client launches) and garmin-mcp-triathlon-auth (one-time Garmin authentication).
Fork of Taxuspt/garmin_mcp (749★, MIT) — a Garmin Connect MCP server purpose-built for triathlon & endurance coaching.
171 tools in total. 140 come from upstream — Garmin health data, activities, workouts, devices, gear and the rest. 31 are new: 18 triathlon workout builders and 13 coaching tools across analytics, composite views, bulk data retrieval and plan automation.
The coaching tools return measurements only. Thresholds, verdicts and recommendations live in a separate coaching skill — see Design Principle.
What's Different from Upstream
31 New Tools
Module | Tools | What They Do |
Workout Builders | 18 | Cycling, running, swimming, brick/multi-sport and strength — natural params → verified Garmin JSON → upload. (The module holds 23; five are upstream builders kept as-is, listed below.) |
Bulk Data | 3 |
|
Coaching Analytics | 7 | Readiness score + factors, load breakdown, zone distribution, scheduled-vs-completed pairing, performance trend, cardiac drift, weekly minutes |
Composite Views | 2 | Morning brief (5 endpoints in one round trip), athlete status snapshot with baseline deviations |
Plan Execution | 1 | Weekly plan creator — YAML → built, uploaded and scheduled on the Garmin calendar |
Every one of them returns measurements. What the numbers mean is the coaching skill's job — see Design Principle.
3 Critical Mapping Bugs Fixed
The upstream workout_builders.py and the old json_encoder.py had silent bugs. Our builder tools fix them:
Target | Bug (Old) | Fix (Our Fork) | Device Display |
Exact cycling watts | ID 6 | ID 2 | "240-270W" not a pace target |
Custom HR range (e.g. 130-145 bpm) | Silent drop ( | ID 4 | "130-145 bpm" not blank |
No validation pipeline | Raw JSON, silent upload failures | All builders go through | Catches errors before upload |
On cycling watt targets. Earlier versions of this table recommended target ID
6withpower.between, following upstream's docstrings. A live upload/read-back against a real account shows Garmin silently rewrites that topace.zoneon a cycling workout — the watt bounds survive but are reinterpreted as pace. Target ID2(power.zone) with the bounds intargetValueOne/targetValueTworound trips intact and is what the builders now emit.
Every builder passes integration tests, and the coaching tools are
verified against a live Garmin account — mocks alone hid several payload
shape bugs (see normalize_sleep, normalize_readiness).
Verified on device
Uploaded from these builders, synced to an Instinct 2X Solar, and read off the watch. Anything not in this table is verified against the API only.
Target | Encoding | Watch shows |
Cycling watts | ID 2 |
|
Cycling power zone | ID 2 |
|
Custom HR range | ID 4 |
|
Named HR zone | ID 4 |
|
Repeat groups |
|
|
Swim pace | ID 6 |
|
Swim pace band | ID 6 |
|
The one that does not work: ID 6 power.between on a cycling workout. It
uploads without error and reads back with the watt bounds intact, but Garmin
stores it as pace.zone and the watch renders 240 m/s as 864.00km/h.
That is what motivated the ID 2 correction above — see
upstream issue #245.
power.between is now rejected on upload, whatever workoutTargetTypeId
it arrives with, and the error names power.zone as the replacement. Garmin
treats the id as authoritative and ignores the key string, so a wrong pairing
cannot be caught by an id/key cross-check — the key has to be refused by name.
Accepting it for backwards compatibility only preserved a silent wrong answer.
Upstream PR #194 reached the
same conclusion independently, by its own live round trip.
Related MCP server: Garmin Workouts MCP Server
Quick Start (Hermes Agent)
git clone https://github.com/pluton74mac/Garmin_MCP_Triathlon.git
cd Garmin_MCP_TriathlonOne-Command Setup
./scripts/hermes-setup.shThis handles everything: installs uv standalone (required — pip-installed won't work), creates the wrapper script, and writes the MCP config to ~/.hermes/config.yaml. Then type /reload-mcp in your Hermes chat.
If you run garmin-mcp-triathlon under a dedicated Hermes profile rather than the default one, pass --profile <name> so the config is written to ~/.hermes/profiles/<name>/config.yaml instead of the global config:
./scripts/hermes-setup.sh --profile triathlon-coachManual Setup (if you prefer step-by-step)
1. Install uv standalone
# Required — pip-installed uv is NOT in Hermes' PATH
curl -LsSf https://astral.sh/uv/install.sh | sh2. Authenticate
cd Garmin_MCP_Triathlon
uv run garmin-mcp-triathlon-authEnter your Garmin email, password, and MFA code. Tokens saved to ~/.garminconnect/.
3. Create wrapper script
REPO_DIR="$(cd Garmin_MCP_Triathlon && pwd)" # absolute path to your clone
cat > ~/.local/bin/garmin-mcp-triathlon << EOF
#!/usr/bin/env bash
cd "$REPO_DIR"
exec "\$HOME/.local/bin/uv" run garmin-mcp-triathlon
EOF
chmod +x ~/.local/bin/garmin-mcp-triathlonUse the actual absolute path to your clone here, not a placeholder — a wrapper that can't resolve REPO_DIR will silently no-op the cd and uv run will fail to find pyproject.toml.
Why a wrapper? Hermes Agent may not parse command + args arrays in config.yaml correctly — it can spawn the server name as the command instead of uv. The wrapper bundles the cd + exec uv run into one executable, bypassing this bug entirely.
4. Configure Hermes
Write the MCP config with Python YAML (Hermes guards config.yaml from file tools). Target the global config only if you're not using a dedicated Hermes profile — if you are, write to ~/.hermes/profiles/<name>/config.yaml instead, or the global default profile gets silently reconfigured:
import os
import yaml
p = '~/.hermes/config.yaml' # or ~/.hermes/profiles/<name>/config.yaml for a dedicated profile
p = os.path.expanduser(p)
with open(p) as f:
c = yaml.safe_load(f)
c['mcp_servers']['garmin-mcp-triathlon'] = {
'command': os.path.expanduser('~/.local/bin/garmin-mcp-triathlon'),
'timeout': 300 # cold start: Garmin auth takes 10-15s
}
with open(p, 'w') as f:
yaml.safe_dump(c, f, default_flow_style=False, allow_unicode=True, sort_keys=False)5. Load Tools
In a Hermes chat session: /reload-mcp
Verify with: get_user_profile — should return your Garmin profile.
Common Pitfalls
Symptom | Cause | Fix |
| Hermes misparsing | Use wrapper script (Step 3) |
| Cold-start timeout (Garmin auth takes 10-15s) | Bump |
| pip-installed uv, not standalone | Install standalone (Step 1) |
Other MCP servers disappeared | Overwriting | Use Python YAML to merge, not replace |
Tools not visible after | Config cached or parsing error | Verify with |
Nutrition tools return | Garmin nutrition/food-log API not enabled for the account | Account-level, not a bug here — reads and writes both fail before this code runs |
Brick workout says not compatible on the watch | Device does not support multi-sport structured workouts | See the note under Brick / Multi-Sport builders |
A workout step shows no target on the watch | Step list often omits it | Press into the step — the target is usually there |
Workout Builder Catalog
Cycling (6 builders)
create_cycling_endurance_workout(name, duration_min, hr_zone="Z2", warmup_min=15, cooldown_min=15)
create_cycling_tempo_workout(name, duration_min, hr_zone="Z3", warmup_min=15, cooldown_min=15)
create_cycling_sweet_spot_workout(name, reps=3, work_min=20, rest_min=5, warmup_min=15, cooldown_min=10)
create_cycling_interval_workout(name, reps=5, work_sec=180, rest_sec=180, power_low=250, power_high=270, ...)
create_cycling_over_under_workout(name, reps=3, over_sec=60, under_sec=120, over_pct=105, under_pct=90, ...)
create_cycling_ftp_test_workout(name="FTP Test", warmup_min=20, test_min=20, cooldown_min=15)Running (6 builders)
create_run_easy_workout(name, duration_min, hr_zone="Z2", warmup_min=10, cooldown_min=10)
create_run_tempo_workout(name, duration_min, hr_zone="Z4", warmup_min=10, cooldown_min=10)
create_run_long_workout(name, duration_min, hr_min=130, hr_max=145, ...) # custom BPM range!
create_run_intervals_workout(name, reps=6, distance_m=400, rest_sec=120, hr_zone="Z5", ...)
create_run_hills_workout(name, reps=8, hill_sec=60, jog_down_sec=90, ...)
create_run_progression_workout(name, blocks=[...], warmup_min=15, cooldown_min=10)Swimming (4 builders)
create_swim_endurance_workout(name, distance_m=1500, pace="1:45/100m", stroke="freestyle", pool_length=25)
create_swim_intervals_workout(name, reps=4, distance_m=200, rest_sec=30, pace="1:40/100m", ...)
create_swim_threshold_workout(name, distance_m=800, pace="1:42/100m", ...)
create_swim_drills_workout(name, drills=[{name, distance_m, equipment, stroke}, ...], ...)Brick / Multi-Sport (2 builders)
create_brick_bike_run_workout(name, bike_duration_min=60, run_duration_min=20, bike_hr_zone="Z2", run_hr_zone="Z3")
create_brick_swim_bike_workout(name, swim_distance_m=1500, bike_duration_min=60, swim_pace="1:45/100m", ...)Check your watch supports multi-sport workouts before relying on these. Both builders upload valid
multi_sportworkouts, but many Garmin watches cannot run a structured multi-sport workout and will report the workout as not compatible when you try to send it to the device. Confirmed on an Instinct 2X Solar and a Forerunner 245 Music — and a multi-sport workout created natively in the Garmin Connect app is rejected identically, so this is a device limitation rather than an encoding fault. Multi-sport structured workouts are generally a higher-tier feature (Forerunner 745/945/955/965, Fenix 6 and later, Enduro).
Upstream builders (preserved)
create_walk_run_workout, create_z2_walk_workout, create_strength_workout, create_run_workout, upload_workout, schedule_week
Coaching Analytics Catalog
All 13 coaching tools, and only the 13 that exist. Each returns measurements; none returns a verdict. Thresholds live in the coaching skill — see Design Principle.
Bulk Data (3 tools)
Tool | Returns |
| Per-day body battery (4 values), HRV, resting HR, sleep, stress, training load, readiness — plus |
| Per-activity date, sport, duration, distance, HR, power, training effect, optional HR-zone seconds |
| LTHR, cycling/running FTP with |
Individual Analytics (7 tools)
Tool | Returns |
| Garmin's readiness score, its level, six factor percentages |
| Minutes per sport plus Garmin's acute/chronic load, ACWR and TSB |
| Seconds per HR zone as percentages, by sport |
| Scheduled workouts paired with same-day activities |
| Per-activity pace or power + avg HR, regression slope |
|
|
| Minutes per ISO week, week-over-week change |
Composite Views (2 tools)
Tool | Impact |
| 5 calls → 1 — sleep, recovery, readiness, today's workout |
| Current values, Garmin baselines, deviations |
Plan Automation (1 tool)
Tool | What It Does |
| Reads YAML/JSON → creates all workouts → schedules each on its own |
Nine tools were removed, not relocated
run_safety_check, check_overtraining_risk, get_injury_risk_assessment,
get_reds_risk_assessment, get_load_adjustment_recommendation,
get_recovery_trend, get_weekly_health_summary, generate_taper_plan and
validate_weekly_plan no longer exist, and neither does
src/garmin_mcp/coaching_safety.py. Each of them encoded a coaching judgement
— a threshold, a load curve, a gate — inside the data layer. Two of them
returned opposite verdicts on identical data. That reasoning now lives in
skills/triathlon-coaching/, where every threshold is one line of
rules.yaml with its provenance recorded.
If you are looking for a safety gate, injury screen or taper, it is in the
skill, not here. See docs/coaching-split-audit.md.
Architecture
Garmin_MCP_Triathlon/
├── src/garmin_mcp/ # Preserved upstream namespace
│ ├── *.py # Upstream modules (UNCHANGED)
│ ├── workout_builders.py # EXTENDED: +18 triathlon builders
│ │
│ ├── coaching_data.py # NEW: 3 bulk retrieval tools
│ ├── coaching_analytics.py # NEW: 7 measurement tools
│ ├── coaching_composite.py # NEW: 2 aggregated view tools
│ └── coaching_planning.py # NEW: 1 plan execution tool
│
├── skills/triathlon-coaching/ # NEW: the judgement layer
│ ├── rules.yaml # every threshold, one file
│ ├── scripts/evaluate.py # contains no numbers
│ └── references/ # provenance for each threshold
│
├── tests/
│ ├── unit/ # Unit tests for builders
│ ├── integration/ # Integration tests (mocked Garmin API)
│ │ ├── test_workout_builders_tools.py # EXTENDED
│ │ ├── test_coaching_data_tools.py # NEW
│ │ ├── test_thinned_surface.py # NEW: no verdicts leak
│ │ ├── test_fetch_failures_surface.py # NEW
│ │ └── test_*_reads.py # NEW: payload-shape guards
│ └── e2e/ # End-to-end (real Garmin creds)Every new module follows the upstream pattern: configure(client) + register_tools(app).
Design Principle
┌────────────────────────────────────────┐
│ garmin-mcp-triathlon (DATA LAYER) │
│ "What does the data say?" │
│ Raw Garmin data → structured JSON │
└────────────┬───────────────────────────┘
│ MCP tool calls
▼
┌────────────────────────────────────────┐
│ Hermes Coaching Skills (INTELLIGENCE) │
│ "What should we do about it?" │
│ Interpret, recommend, plan │
└────────────────────────────────────────┘The MCP returns data. The coach decides what to do.
This is enforced, not aspirational. No coaching tool returns a threshold,
a severity, a gate or a sentence of advice; a test walks every tool's output
looking for that vocabulary. Nine tools that did were removed and seven were
thinned — docs/coaching-split-audit.md
records what each one encoded and why.
The judgement lives in skills/triathlon-coaching/,
where every threshold sits in one editable rules.yaml and the evaluator
contains no numbers at all. references/rationale.md records where each
number came from and what live data says about it.
Two rules the data layer keeps:
A failed fetch is never silence. Every tool that walks a date range returns an
errors[]array. A day with no data is absent from the results; a day whose request raised is inerrors. Collapsing those two is how an expired token used to produce a confident all-clear.Nothing is substituted for a missing reading. No zeros, no plausible defaults. An absent measurement is absent.
The errors[] contract
Every tool that walks a date range or a list of activities returns an errors array. It is part of the tool's output contract, not a debugging aid, and the coaching skill depends on it.
Schema
{"date": "2026-08-02", "metric": "hrv", "error": "429 Too Many Requests"}Field | Type | Meaning |
|
| the day whose request failed |
| string | the metric that was lost, never the endpoint |
| string | the exception text, unedited |
get_activity_series adds activity_id for per-activity failures (HR-zone
lookups) and omits date when the failure is not day-scoped.
get_athlete_context uses {"source": ..., "error": ...} — its calls are not
per-day.
The three states it exists to separate
State | results |
|
Everything worked | full |
|
Athlete has genuine gaps — watch not worn | short |
|
Fetch failed — expired token, 429, outage | short | populated |
Rows two and three are byte-identical in the results. Without errors
they are indistinguishable, and that is precisely how an expired token used to
produce a confident all-clear from the safety gate.
Rules
A day with no data is absent from the results. A day whose request raised is in
errors. Never both, never neither.metric, not endpoint.body_batteryandstressshareget_stats; when that call fails, both metric names appear. A caller should not have to know Garmin's endpoint topology to understand what it just lost.Nothing is substituted. No zeros, no plausible defaults, no backfilling a missing value from a neighbouring field.
A non-empty
errorsadds awarningstring saying in prose that the gap is not a negative finding. The consumer is usually a language model, and a sentence is harder to skip than an integer.api_callsreports the real cost, so the price of a wide date range is visible rather than inferred.A rate limit aborts the walk. A 429 sets
rate_limited: trueand stops immediately rather than working through the rest of the range. See below.
Rate limiting
Garmin publishes no limits for this API. What is known from the community is
that the aggressive limiting sits on the login/SSO endpoints and is keyed
per account — not per IP or user agent — with reported blocks lasting from
about an hour to 48+ hours. Token-based auth keeps this server off that path
almost entirely: it resumes from ~/.garminconnect/ rather than signing in.
garminconnect 0.3.2 paces and retries login only — both of its anti-WAF
sleeps live inside the SSO functions. Data calls have no backoff whatsoever;
a 429 raises straight through. A 60-day, 6-source get_health_series walk is
roughly 360 unpaced requests, so on hitting a limit the walk stops at the first
refusal instead of firing hundreds more. Days already retrieved are returned
and are complete; everything after the stop is unknown, and the warning says
so.
If you see rate_limited: true, wait before retrying and ask for a shorter
range or fewer metrics. Do not loop.
For consumers
Never draw a negative conclusion from a short result set while errors is
non-empty. "No overtraining signals" and "we could not look" are different
statements. The coaching skill turns its safety gate to unknown — never
green — whenever errors is populated, and a real trigger still outranks it
so a red gate is not downgraded by an unrelated 429.
Emitted by
get_health_series, get_activity_series, get_athlete_context,
get_morning_brief and get_athlete_status_snapshot (the last two as
fetch_errors, since they are single-date tools rather than range walks).
Garmin payload shapes worth knowing
These cost real debugging time. Each was found by reading a live payload,
never by inferring from a plausible key name — and each one, before it was
found, produced a confidently wrong number rather than an error. Most are
handled by a named helper (in coaching_analytics.py unless noted); use the
helper rather than reading the field directly.
Endpoint | Actual shape | Helper |
| summary nested under |
|
| one-element list; no |
|
|
|
|
|
|
|
|
|
|
| the seven-day figure is | read |
| takes an |
|
| returns | — |
HR zone floors | live at | raw |
| no | — |
| event series, not a daily summary; use | — |
| list, newest first — sort before treating position as time |
|
| a JSON scalar taking |
|
Sport keys must be enumerated explicitly. activityType.parentTypeId is
not a usable grouping key — 17 is shared by running, cycling, hiking and
walking, while trail_running reports 1 and road_biking reports 2. The
discipline lists live in SPORT_TYPE_KEYS; note that outdoor rides are
road_biking, not cycling, and open water is open_water_swimming. Missing
those two silently dropped activities from load, zone and injury analysis.
pace.zone bounds are metres per second. For swim paces use
_pace_to_mps (100 / seconds_per_100m). Inverting this is easy to miss
because the common default 1:40/100m is exactly 100 s — the one value where
the correct and inverted expressions agree.
Multi-segment workouts need workout-unique stepOrder. Restarting at 1 per
segment makes Garmin reject the upload outright; _renumber_steps_across_segments
numbers continuously and descends into repeat groups.
Tool Filtering
171 tools is a lot of context. Filter per skill with GARMIN_ENABLED_TOOLS:
Skill | Enable these |
Health Dashboard |
|
Workout Review |
|
Workout Manager | All |
Weekly Insights |
|
Coaching Skill |
|
Names are checked at startup: anything in GARMIN_ENABLED_TOOLS that matches
no registered tool is reported on stderr rather than silently ignored.
Set via MCP server env:
"env": {
"GARMIN_ENABLED_TOOLS": "get_morning_brief,get_sleep_data,get_training_readiness_composite,..."
}Testing
# All tests (unit + integration) — 634 tests
uv run pytest tests/unit/ tests/integration/ -v
# Specific module
uv run pytest tests/integration/test_workout_builders_tools.py -v
# End-to-end (requires real Garmin credentials)
uv run pytest tests/e2e/ -m e2e -v634 tests pass across tests/unit and tests/integration; 664 including the coaching skill's own suite (pytest -m "not e2e"). pytest -m e2e is 10 passed, 6 skipped — the skips are the nutrition tests, gated on a live probe because that API is 403 on accounts without the feature. Zero regressions on upstream tests.
The mock is specced against the real client
tests/conftest.py builds the Garmin client with create_autospec against a
real Garmin instance, so a call with the wrong arity — or to a method that
does not exist — fails immediately. This matters: an earlier revision used a
bare Mock(), which accepts anything, and the suite was fully green while
seven tools were calling the API incorrectly and failing on every invocation.
Two rules when extending the fixtures:
Spec against an instance, not the class.
Garmin.__init__assigns.clientand thegarmin_connect_*URLs, which a class-level autospec cannot see.Set defaults with
client.method.return_value = ..., neverclient.method = Mock(...)— the latter replaces the autospec'd child and silently discards signature checking.
Autospec is necessary but not sufficient
Autospec constrains call shapes; it says nothing about whether the payload
you assert on matches what Garmin actually returns. Several bugs survived a
green suite because the fixtures encoded shapes the API does not produce —
sleep summaries nested under dailySleepDTO, training readiness returned as a
one-element list, HR zones served from a separate endpoint. Fixtures in this
repo are kept faithful to live payloads for that reason.
Manual Display Test (Required for Builders)
Uploading successfully is not the same as displaying correctly — Garmin silently rewrites some targets on save. To verify:
Call a builder via MCP (e.g.
create_cycling_interval_workout)Open Garmin Connect → Workouts → verify name, sport, steps, targets
Sync to device → start workout → press into each step to see its target; the step list alone often does not show it
Confirm the target reads in the units you asked for (watts, bpm, min/100m)
Critical Mapping Reference
Target Type | Correct ID | Correct Key | Extra Fields |
Exact power (watts) | 2 |
|
|
Power zone (FTP%) | 2 |
|
|
HR zone (named) | 4 |
|
|
HR custom (BPM) | 4 |
|
|
Pace zone | 6 |
|
|
Always set BOTH workoutTargetTypeId AND workoutTargetTypeKey — the validation pipeline catches mismatches.
Upstream Features Preserved
171 tools total once the coaching modules are registered — counted from
the @app.tool() registrations, with no duplicate names. The upstream surface
is preserved in full:
Module | Tools | |
| 29 | sleep, HRV, body battery, stress, respiration, steps |
| 23 | list, get, GPS track, edit, rename, retype, manual entry, delete |
| 15 | CTL/ATL/TSB, HRV trend, VO2 max, FTP, lactate threshold |
| 14 | upload, schedule, unschedule, delete, list, download |
| 14 | food log, custom foods, meals, hydration targets |
| 9 | badges, ad-hoc and virtual challenges |
| 6 | device list, settings, solar data, alarms |
| 5 | weigh-ins by day and range, add, delete |
| 4 | profile, settings, personal records |
| 4 | FIT parsing, power duration curve, Di2 shift summary |
| 3 | menstrual cycle and pregnancy data |
| 3 | gear list with stats, associate/dissociate per activity |
| 4 | body composition, blood pressure (add + delete), hydration |
| 3 | list, upload GPX, delete |
That is 135 tools, plus the 5 upstream workout builders kept inside
workout_builders.py (create_walk_run_workout, create_run_workout,
create_z2_walk_workout, create_strength_workout, schedule_week) — 140
upstream-derived. The remaining 31 are new: 18 triathlon builders and
13 coaching tools.
delete_activity and delete_blood_pressure were added so that
create_manual_activity and set_blood_pressure are undoable through the
server — garminconnect had both deletes and neither was registered.
Note that nutrition returns HTTP 403 on accounts without Garmin's
nutrition feature, reads and writes alike, before any of this code runs.
That is account-level, not a defect here.
Synced with upstream through a16f057, which adds search_foods,
set_nutrition_daily_settings and Garmin Coach workout access, and carries
upstream's DXT, stdio-corruption and nested-target-bounds fixes.
Two upstream defects were found here and submitted back:
get_device_solar_dataread six fields that do not exist in the response, so it reported no data for solar watches that had a full day of readings. It now readssolarDailyDataDTOs[].localConnectDateand derives utilisation fromsolarInputReadings[]. Verified against an Instinct 2X Solar with 1254 readings. (upstream PR #247)get_endurance_scorecrashed on Garmin's explicitnullfor a section with no data —.get("enduranceScoreDTO", {})does not help when the key is present and the value isNone. (upstream PR #246)
Both fixes are carried here regardless of whether upstream merges them.
Upstream Setup (Claude Desktop, Codex, Docker)
See upstream documentation for:
Claude Desktop configuration
Codex/opencode TOML config
Docker deployment
HTTP transport mode
Garmin Connect China
Credits
Upstream: Taxuspt/garmin_mcp — the Garmin MCP server this is forked from (749★, MIT license)
Garmin API: python-garminconnect by cyberjunky
Fork: pluton74mac/Garmin_MCP_Triathlon — 13 coaching tools, 18 new triathlon workout builders, and the coaching skill
License
MIT (same as upstream)
Available Tools
172 toolsadd_body_compositionC
Add body composition data
Args: date: Date in YYYY-MM-DD format weight: Weight in kg percent_fat: Body fat percentage percent_hydration: Hydration percentage visceral_fat_mass: Visceral fat mass bone_mass: Bone mass muscle_mass: Muscle mass basal_met: Basal metabolic rate active_met: Active metabolic rate physique_rating: Physique rating metabolic_age: Metabolic age visceral_fat_rating: Visceral fat rating bmi: Body Mass Index
Inputs: date (required), weight (required), bmi, basal_met, +9 more. Returns: result
add_gear_to_activityB
Associate gear with an activity
Links a specific piece of gear (like shoes, bike, etc.) to an activity.
Args: activity_id: ID of the activity gear_uuid: UUID of the gear to add (get from get_gear)
Inputs: gear_uuid (required), activity_id (required). Returns: result
add_hydration_dataA
Add a hydration entry for a date.
Entries accumulate: logging 500 then 250 leaves the day at 750. A NEGATIVE value_in_ml subtracts, which is how an over-log is corrected — verified live.
A day cannot go below zero: Garmin answers a further subtraction with "Daily hydration is already at 0 and cannot be reduced." There is no delete for hydration in the Garmin API, so zeroing a day is as close to removing it as the platform allows — the entry stays, reading 0 mL.
Args:
value_in_ml: Amount of liquid in millilitres. Negative subtracts
from the day's running total.
date: Date in YYYY-MM-DD format. (Named to match
get_hydration_data; the argument was once called cdate.)
timestamp: Timestamp in YYYY-MM-DDThh:mm:ss.sss format
Inputs: date (required), timestamp (required), value_in_ml (required). Returns: result
add_weigh_inB
Add a new weight measurement
Args: weight: Weight value unit_key: Unit of weight — 'kg' or 'lbs'. Not 'lb': the underlying client validates against {'kg', 'lbs'} and rejects anything else with "unitKey must be one of {'kg', 'lbs'}".
Inputs: weight (required), unit_key. Returns: result
add_weigh_in_with_timestampsB
Add a new weight measurement with specific timestamps
Args: weight: Weight value unit_key: Unit of weight — 'kg' or 'lbs'. Not 'lb'; see add_weigh_in. date_timestamp: Local timestamp in format YYYY-MM-DDThh:mm:ss gmt_timestamp: GMT timestamp in format YYYY-MM-DDThh:mm:ss
Inputs: weight (required), unit_key, gmt_timestamp, date_timestamp. Returns: result
count_activitiesA
Get total count of activities in the user's Garmin account
Returns the total number of activities recorded.
Inputs: no named parameters. Returns: result
create_brick_bike_run_workoutA
Create a brick workout (bike → run) and upload it to Garmin Connect.
Multi-sport workout with two segments. Garmin auto-adds T1/T2 transitions.
Args: name: Workout name (e.g. "Brick 60m/20m") bike_duration_min: Bike segment duration in minutes (default 60) run_duration_min: Run segment duration in minutes (default 20) bike_hr_zone: HR zone for bike (default Z2) run_hr_zone: HR zone for run (default Z3) bike_warmup_min: Bike warmup in minutes (default 15) run_cooldown_min: Run cooldown in minutes (default 5)
Inputs: name (required), run_hr_zone, bike_hr_zone, bike_warmup_min, +3 more. Returns: result
create_brick_swim_bike_workoutA
Create a swim → bike brick workout and upload it to Garmin Connect.
Multi-sport workout: swim segment with pace target → bike segment with HR zone.
Args: name: Workout name (e.g. "Brick Swim/Bike") swim_distance_m: Swim distance in meters (default 1500) bike_duration_min: Bike segment duration in minutes (default 60) swim_pace: Target swim pace as "M:SS/100m" bike_hr_zone: HR zone for bike (default Z2) swim_warmup_m: Swim warmup distance in meters (default 400) bike_cooldown_min: Bike cooldown in minutes (default 10) pool_length: Pool length in meters (default 25)
Inputs: name (required), swim_pace, pool_length, bike_hr_zone, +4 more. Returns: result
create_custom_foodA
Create a custom food in the user's Garmin nutrition library
Creates a new food item with nutritional information per serving. On success the response includes foodId and servingId needed for log_custom_food. If the API returns no data (204), use get_custom_foods(search=food_name) to retrieve those IDs.
All nutrient amounts are ABSOLUTE values per serving, not %DV. Nutrition labels often print %DV for calcium/iron/vitamin D — convert to absolute units before passing.
Args: food_name: Name of the custom food (e.g. "Homemade Chocolate Cookies") calories: Calories per serving serving_unit: Unit for serving size (e.g. "G", "ML", "OZ"). Default "G" number_of_units: Serving size in the specified unit. Default 100 brand_name: Brand or vendor name (e.g. "Three Bridges") carbs: Carbohydrates in grams per serving protein: Protein in grams per serving fat: Total fat in grams per serving fiber: Fiber in grams per serving sugar: Sugar in grams per serving saturated_fat: Saturated fat in grams per serving sodium: Sodium in mg per serving cholesterol: Cholesterol in mg per serving potassium: Potassium in mg per serving trans_fat: Trans fat in grams per serving calcium: Calcium in mg per serving (NOT %DV) iron: Iron in mg per serving (NOT %DV) vitamin_d: Vitamin D in mcg per serving (NOT %DV)
Inputs: calories (required), food_name (required), fat, iron, +14 more. Returns: result
create_cycling_endurance_workoutA
Create a steady endurance cycling workout and upload it to Garmin Connect.
Builds a single continuous aerobic ride with HR zone target.
Args: name: Workout name (e.g. "Z2 Endurance 90m") duration_min: Duration of the main ride in minutes hr_zone: Target heart-rate zone (Z1-Z5, default Z2) warmup_min: Warmup duration in minutes (default 15) cooldown_min: Cooldown duration in minutes (default 15)
Inputs: name (required), duration_min (required), hr_zone, warmup_min, +1 more. Returns: result
create_cycling_ftp_test_workoutA
Create a 20-minute FTP test cycling workout and upload it to Garmin Connect.
No power target — free effort. The athlete rides at maximum sustainable pace and records the average watts from the test block.
Args: name: Workout name (default "FTP Test") warmup_min: Warmup duration in minutes (default 20) test_min: FTP test duration in minutes (default 20) cooldown_min: Cooldown duration in minutes (default 15)
Inputs: name, test_min, warmup_min, cooldown_min. Returns: result
create_cycling_interval_workoutA
Create a power-based VO2max cycling interval workout and upload it to Garmin Connect.
Uses exact watt targets (power.between, ID 6) — the device displays "250-270W" not "Zone X".
Args: name: Workout name (e.g. "VO2max 5x3m") reps: Number of intervals (default 5) work_sec: Duration of each interval in seconds (default 180) rest_sec: Recovery duration between intervals in seconds (default 180) power_low: Lower power target in watts (default 250) power_high: Upper power target in watts (default 270) warmup_min: Warmup duration in minutes (default 15) cooldown_min: Cooldown duration in minutes (default 10)
Inputs: name (required), reps, rest_sec, work_sec, +4 more. Returns: result
create_cycling_over_under_workoutA
Create an over/under threshold cycling workout and upload it to Garmin Connect.
Alternates between above-threshold (over) and sub-threshold (under) power targets based on FTP percentages.
Args: name: Workout name (e.g. "Over/Under 3x(1m/2m)") reps: Number of over/under pairs (default 3) over_sec: Duration of the over segment in seconds (default 60) under_sec: Duration of the under segment in seconds (default 120) over_pct: FTP percentage for over segment (default 105) under_pct: FTP percentage for under segment (default 90) warmup_min: Warmup duration in minutes (default 15) cooldown_min: Cooldown duration in minutes (default 10)
Inputs: name (required), reps, over_pct, over_sec, +4 more. Returns: result
create_cycling_sweet_spot_workoutA
Create a sweet spot interval cycling workout and upload it to Garmin Connect.
Repeated sweet spot blocks (Z4) with active recovery between.
Args: name: Workout name (e.g. "Sweet Spot 3x20") reps: Number of sweet spot repeats (default 3) work_min: Duration of each sweet spot block in minutes (default 20) rest_min: Recovery duration between blocks in minutes (default 5) warmup_min: Warmup duration in minutes (default 15) cooldown_min: Cooldown duration in minutes (default 10)
Inputs: name (required), reps, rest_min, work_min, +2 more. Returns: result
create_cycling_tempo_workoutA
Create a tempo/threshold cycling workout and upload it to Garmin Connect.
Same structure as endurance but with a higher HR zone default (Z3).
Args: name: Workout name (e.g. "Tempo 60m") duration_min: Duration of the main tempo block in minutes hr_zone: Target heart-rate zone (Z1-Z5, default Z3) warmup_min: Warmup duration in minutes (default 15) cooldown_min: Cooldown duration in minutes (default 15)
Inputs: name (required), duration_min (required), hr_zone, warmup_min, +1 more. Returns: result
create_manual_activityA
Log a manual activity in Garmin Connect — useful for activities done without a watch.
The type_key must match a Garmin activity type. Use get_activity_types to see the full list. Common values: yoga, strength_training, meditation, indoor_cycling, pilates, bouldering, fitness_equipment.
Args: type_key: Activity type key (e.g. "yoga", "strength_training") date: Date of the activity in YYYY-MM-DD format duration_minutes: Duration of the activity in minutes start_time: Start time as HH:MM (24-hour, default 09:00) activity_name: Optional title; defaults to the type_key if not provided distance_km: Distance in kilometres (default 0.0 for non-distance activities) time_zone: IANA time zone for the activity (default UTC)
Inputs: date (required), type_key (required), duration_minutes (required), time_zone, +3 more. Returns: result
create_run_easy_workoutA
Create an easy aerobic run workout and upload it to Garmin Connect.
Simpler than create_run_workout — defaults to Z2 with shorter warmup/cooldown.
Args: name: Workout name (e.g. "Easy Run 45m") duration_min: Duration of the main run in minutes hr_zone: Target heart-rate zone (Z1-Z5, default Z2) warmup_min: Warmup duration in minutes (default 10) cooldown_min: Cooldown duration in minutes (default 10)
Inputs: name (required), duration_min (required), hr_zone, warmup_min, +1 more. Returns: result
create_run_hills_workoutA
Create a hill repeat running workout and upload it to Garmin Connect.
Hill reps with jog-down recovery. No target — effort-based.
Args: name: Workout name (e.g. "Hills 8x1m") reps: Number of hill repeats (default 8) hill_sec: Duration of each hill effort in seconds (default 60) jog_down_sec: Jog-down recovery duration in seconds (default 90) warmup_min: Warmup duration in minutes (default 15) cooldown_min: Cooldown duration in minutes (default 10)
Inputs: name (required), reps, hill_sec, warmup_min, +2 more. Returns: result
create_run_intervals_workoutA
Create a track-style running interval workout and upload it to Garmin Connect.
Uses distance-based end condition for the work intervals (e.g. 400m).
Args: name: Workout name (e.g. "Track 6x400m") reps: Number of intervals (default 6) distance_m: Distance per interval in meters (default 400) rest_sec: Recovery duration between intervals in seconds (default 120) hr_zone: Target heart-rate zone for intervals (Z1-Z5, default Z5) warmup_min: Warmup duration in minutes (default 10) cooldown_min: Cooldown duration in minutes (default 10)
Inputs: name (required), reps, hr_zone, rest_sec, +3 more. Returns: result
create_run_long_workoutA
Create a long run with custom BPM range and upload it to Garmin Connect.
Uses targetValueOne/Two for a custom HR band — the device displays "130-145 bpm" instead of "Zone X". This fixes the old encoder's silent-drop bug on heart.rate custom ranges.
Args: name: Workout name (e.g. "Long Run 90m") duration_min: Duration of the long run in minutes hr_min: Lower HR target in bpm (default 130) hr_max: Upper HR target in bpm (default 145) warmup_min: Warmup duration in minutes (default 10) cooldown_min: Cooldown duration in minutes (default 10)
Inputs: name (required), duration_min (required), hr_max, hr_min, +2 more. Returns: result
create_run_progression_workoutA
Create a progressive run workout and upload it to Garmin Connect.
Sequential blocks (e.g. Z2→Z3→Z4) without recovery between them. The athlete progresses through increasing intensity zones.
Args: name: Workout name (e.g. "Progression Z2-Z3-Z4") blocks: List of dicts with keys: - duration_sec (int): duration in seconds - hr_zone (str): target HR zone (Z1-Z5) Example: [{"duration_sec": 900, "hr_zone": "Z2"}, {"duration_sec": 900, "hr_zone": "Z3"}, {"duration_sec": 900, "hr_zone": "Z4"}] warmup_min: Warmup duration in minutes (default 15) cooldown_min: Cooldown duration in minutes (default 10)
Inputs: name (required), blocks (required), warmup_min, cooldown_min. Returns: result
create_run_tempo_workoutA
Create a tempo/threshold run workout and upload it to Garmin Connect.
Same structure as easy run but with a higher HR zone default (Z4).
Args: name: Workout name (e.g. "Tempo Run 30m") duration_min: Duration of the tempo block in minutes hr_zone: Target heart-rate zone (Z1-Z5, default Z4) warmup_min: Warmup duration in minutes (default 10) cooldown_min: Cooldown duration in minutes (default 10)
Inputs: name (required), duration_min (required), hr_zone, warmup_min, +1 more. Returns: result
create_run_workoutA
Create a continuous run workout and upload it to Garmin Connect.
Builds a single uninterrupted run interval with warmup and cooldown walks.
Targets a named Garmin heart-rate zone by default. Named zones (Z1-Z5) don't line up with every real training target -- e.g. a 136-148 bpm Zone 2 goal straddles Garmin's Z2 (118-137) and Z3 (138-157). Pass hr_min and hr_max together to target that exact bpm range instead; the watch will then show "in range" only for the range you actually want, not a whole zone that over- or under-shoots it.
Args: name: Workout name (e.g. "Step 8 - 30min continuous") run_seconds: Duration of the run in seconds warmup_min: Warmup walk duration in minutes cooldown_min: Cooldown walk duration in minutes hr_zone: Target heart-rate zone (Z1-Z5, default Z3). Ignored if hr_min/hr_max are given. hr_min: Optional custom target heart rate range, minimum bpm (must be given with hr_max) hr_max: Optional custom target heart rate range, maximum bpm (must be given with hr_min)
Inputs: name (required), warmup_min (required), run_seconds (required), cooldown_min (required), +3 more. Returns: result
create_strength_workoutA
Create a strength workout and upload it to Garmin Connect.
Each exercise becomes a reps-based step. The name is kept in the step description; it is also sent as exerciseName, which Garmin only retains when it matches one of its own exercise keys (e.g. "FARMERS_CARRY").
Args: name: Workout name exercises: List of dicts with keys: name, sets, reps, rest_seconds and an optional category. Category is omitted from the payload when not given; Garmin accepts that. When given it must be one of Garmin's exercise categories (e.g. SQUAT, DEADLIFT, PUSH_UP, CARRY, SLED) — anything else, including "UNASSIGNED" and "OTHER", is rejected with 400 Invalid category. Full list: https://connect.garmin.com/web-data/exercises/Exercises.json
Inputs: name (required), exercises (required). Returns: result
create_swim_drills_workoutA
Create a swim drill set and upload it to Garmin Connect.
Each drill step includes name, distance, equipment, and stroke.
Args: name: Workout name (e.g. "Drills 1200m") drills: List of dicts with keys: - name (str): drill name (e.g. "Catch-up") - distance_m (int): drill distance - equipment (str): "none", "fins", "paddles", "pull_buoy", "kickboard" - stroke (str): stroke type warmup_m: Warmup distance in meters (default 300) cooldown_m: Cooldown distance in meters (default 200) pool_length: Pool length in meters (default 25)
Inputs: name (required), drills (required), warmup_m, cooldown_m, +1 more. Returns: result
create_swim_endurance_workoutA
Create a continuous swim workout with pace target and upload it to Garmin Connect.
Args: name: Workout name (e.g. "Endurance Swim 1500m") distance_m: Main set distance in meters (default 1500) pace: Target pace as "M:SS/100m" (default "1:45/100m") stroke: Stroke type — freestyle, backstroke, breaststroke, butterfly warmup_m: Warmup distance in meters (default 400) cooldown_m: Cooldown distance in meters (default 200) pool_length: Pool length in meters — 25 or 50 (default 25)
Inputs: name (required), pace, stroke, warmup_m, +3 more. Returns: result
create_swim_intervals_workoutB
Create a CSS threshold swim interval workout and upload it to Garmin Connect.
Args: name: Workout name (e.g. "CSS 4x200m") reps: Number of intervals (default 4) distance_m: Distance per interval in meters (default 200) rest_sec: Rest between intervals in seconds (default 30) pace: Target pace as "M:SS/100m" (default "1:40/100m") stroke: Stroke type warmup_m: Warmup distance in meters (default 400) cooldown_m: Cooldown distance in meters (default 200) pool_length: Pool length in meters (default 25)
Inputs: name (required), pace, reps, stroke, +5 more. Returns: result
create_swim_threshold_workoutA
Create a single threshold swim set and upload it to Garmin Connect.
Good for CSS testing — one continuous threshold block after warmup.
Args: name: Workout name (e.g. "CSS Test 800m") distance_m: Threshold set distance in meters (default 800) pace: Target pace as "M:SS/100m" stroke: Stroke type warmup_m: Warmup distance in meters (default 400) cooldown_m: Cooldown distance in meters (default 200) pool_length: Pool length in meters (default 25)
Inputs: name (required), pace, stroke, warmup_m, +3 more. Returns: result
create_walk_run_workoutA
Create a walk/run interval workout and upload it to Garmin Connect.
Builds the internal Garmin JSON automatically and returns the new workout ID.
Args: name: Workout name (e.g. "W3 Mié 2:2") run_seconds: Duration of each run interval in seconds walk_seconds: Duration of each walk/recovery interval in seconds repeats: Number of run/walk repetitions warmup_min: Warmup duration in minutes cooldown_min: Cooldown duration in minutes hr_zone: Target heart-rate zone (Z1-Z5, default Z3)
Inputs: name (required), repeats (required), warmup_min (required), run_seconds (required), +3 more. Returns: result
create_weekly_planA
Create and schedule a full week of workouts from a plan file.
Parses a JSON/YAML plan file, maps each entry to the appropriate
workout builder, uploads all workouts, schedules each on its own
date, and returns a summary.
Plan file format (JSON): [ { "date": "2026-07-13", "sport": "cycling", "type": "endurance", "name": "Z2 Endurance 90m", "duration_min": 90, "hr_zone": "Z2" } ]
Every entry needs its own date; an entry without one is uploaded
but reported with schedule_error rather than scheduled.
Args: plan_yaml_path: Absolute path to the plan JSON/YAML file
Inputs: plan_yaml_path (required). Returns: result
create_z2_walk_workoutA
Create a steady Z2 walking workout and upload it to Garmin Connect.
Args: name: Workout name duration_min: Main walking block duration in minutes hr_min: Minimum heart rate in bpm (used for description; target is Z2) hr_max: Maximum heart rate in bpm (used for description; target is Z2)
Inputs: name (required), hr_max (required), hr_min (required), duration_min (required). Returns: result
delete_activityA
Permanently delete an activity from Garmin Connect.
This cannot be undone — the activity and its recorded data are gone. Intended for removing a manual activity logged in error; think twice before pointing it at a recorded session.
Exists because create_manual_activity could write a record the server had no way to remove (§D9k).
Args: activity_id: ID of the activity to delete
Inputs: activity_id (required). Returns: result
delete_blood_pressureA
Delete a single blood pressure measurement.
set_blood_pressure returns the date and version of the reading it wrote; pass both back here to remove it. Without this the server could create a record it had no way to delete (§D9k).
Args: date: Date of the measurement in YYYY-MM-DD format version: The measurement's version, as returned by set_blood_pressure or get_blood_pressure
Inputs: date (required), version (required). Returns: result
delete_courseA
Delete a course from Garmin Connect.
Args: course_id: ID of the course to delete (get IDs from get_courses).
Inputs: course_id (required). Returns: result
delete_custom_foodA
Delete a custom food from the user's Garmin nutrition library
Permanently removes a custom food entry. The food must not be actively referenced in a logged meal to be deleted. Use get_custom_foods to find the foodId.
Args: food_id: ID of the custom food to delete — a 32-char hex string (from get_custom_foods or create_custom_food)
Inputs: food_id (required). Returns: result
delete_food_logA
Delete a food log entry
Permanently removes a logged food item from the nutrition log. Works for both QUICK_ADD and REGULAR_LOG entry types. Use get_nutrition_daily_food_log to find the logId and date.
Args: log_id: Log entry ID to delete — a 32-char hex UUID (from get_nutrition_daily_food_log) meal_date: Date of the log entry in YYYY-MM-DD format
Inputs: log_id (required), meal_date (required). Returns: result
delete_weigh_insB
Delete weight measurements for a specific date
Args: date: Date in YYYY-MM-DD format delete_all: Whether to delete all measurements for the day
Inputs: date (required), delete_all. Returns: result
delete_workoutA
Delete a workout from Garmin Connect
Permanently removes a workout from your Garmin Connect workout library.
Args: workout_id: ID of the workout to delete (get IDs from get_workouts)
Inputs: workout_id (required). Returns: result
delete_workoutsA
Delete multiple workouts from Garmin Connect in a single call
Permanently removes multiple workouts from your Garmin Connect workout library.
Args: workout_ids: List of workout IDs to delete (get IDs from get_workouts)
Inputs: workout_ids (required). Returns: result
download_activity_fileA
Download an activity and save it to disk as a file.
Saves the activity in the requested format. Defaults to the original .fit file; Garmin also supports gpx, tcx, and csv.
Directory resolution (first match wins):
output_dir argument (one-off; not persisted)
GARMIN_FIT_DOWNLOAD_DIR environment variable
persisted config (set via set_fit_download_dir) If none is configured, returns status "needs_setup" with a suggested default (the server's current working directory). In that case, ask the user where to save, call set_fit_download_dir(path), then call this tool again.
Files are named "{activity_id}.{ext}" and overwrite any existing file.
Args: activity_id: Garmin activity ID format: One of fit, gpx, tcx, csv (default fit) output_dir: Optional one-off directory override (not persisted)
Inputs: activity_id (required), format, output_dir. Returns: result
download_workoutA
Download a workout as a FIT file
Downloads the workout in FIT format. The binary data cannot be returned directly through the MCP interface, but this confirms the workout is available.
Args: workout_id: ID of the workout to download
Inputs: workout_id (required). Returns: result
get_activitiesA
Get activities with pagination support.
Retrieves a paginated list of activities ordered newest-first. Use this for browsing through large activity lists when you do not need to filter by date range, or as a complement to get_activities_by_date.
Each activity includes an event_type field. Common values: "race", "training", "uncategorized" (no event type set by the user — common for Peloton imports and untagged runs). Filter for races with event_type == "race" rather than excluding "training", as many non-race activities appear as "uncategorized" rather than "training".
Args: start: Starting index (default 0) limit: Maximum number of activities to return (default 20, max 100)
Inputs: limit, start. Returns: result
get_activities_by_dateA
Get activities between specified dates with pagination support.
For accounts with large activity histories, broad date ranges can return thousands of activities in a single response. Use page and page_size to retrieve activities in manageable chunks and avoid "result too large" errors. Activities are ordered newest-first.
Pagination: when has_more is true the response includes next_page — pass that value as page on the next call to retrieve the following page. Repeat until has_more is false.
Note: total_count for a date range is not available from the Garmin API without fetching all results. Use has_more / next_page to walk pages.
Each activity includes an event_type field with values such as:
"race" — explicitly tagged as a race by the user
"training" — explicitly tagged as a training activity
"uncategorized" — no event type set; common for Peloton imports and untagged outdoor runs. Distinct from "training": filter for races with event_type == "race" rather than excluding "training", since many non-race activities appear as "uncategorized" not "training"
field absent — API returned no eventType for this activity; not observed in practice in any activity back to 2012 (oldest activities sampled on this account)
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format activity_type: Optional activity type filter (e.g., cycling, running, swimming) page: Zero-based page number (default 0) page_size: Number of activities per page, max 200 (default 100)
Inputs: end_date (required), start_date (required), page, page_size, +1 more. Returns: result
get_activities_fordateA
Get activities for a specific date
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_activityA
Get detailed information for a single activity.
Returns a comprehensive summary including timing, distance, heart rate, elevation, training effect, and an event_type field. Common event_type values: "race", "training", "uncategorized" (no event type set by the user). The field is omitted for very old activities that pre-date event type support in the Garmin API.
Args: activity_id: ID of the activity to retrieve
Inputs: activity_id (required). Returns: result
get_activity_detailsA
Get the GPS track and per-point route data for an activity.
Returns the recorded GPS polyline (latitude, longitude, altitude, time, distance, speed per point) plus the track's bounding box and start/end coordinates. Garmin downsamples the track server-side to at most max_points points, evenly spread over the activity, so the route shape survives at any size.
Use get_activity for the summary metrics and get_activity_fit_data for full per-second sensor series; this tool is for the route itself (mapping, course analysis, where a climb or interval happened).
Indoor activities carry no GPS track; the response then says so and still lists which per-point metric keys Garmin recorded.
Args: activity_id: ID of the activity to retrieve the track for max_points: Maximum number of track points to return (default 500). Raise it (e.g. 4000) for a finer trace on long routes.
Inputs: activity_id (required), max_points. Returns: result
get_activity_exercise_setsB
Get exercise sets for strength training activities
Args: activity_id: ID of the activity to retrieve exercise sets for
Inputs: activity_id (required). Returns: result
get_activity_fit_dataA
Download and parse FIT file for an activity to expose advanced cycling data.
Returns data not available through the standard REST API, including:
DI2 / electronic shifting events with cadence at time of shift, grade at shift, gear combinations, shift quality classification, and terrain-grouped shift analysis
Cycling dynamics per session and lap: platform center offset (PCO), left/right power balance, torque effectiveness, pedal smoothness
Variability Index (NP / avg_power) per session and lap
Climb detection with VAM (vertical ascent rate), avg power/cadence/HR per climb, and W/kg per climb (using auto-fetched body weight from Garmin)
Grade-correlated stats: avg power, cadence, HR broken down by terrain steepness
HR drift / cardiac drift coefficient (aerobic decoupling for rides ≥60 min)
Temperature correlation: avg HR/power in hottest vs. coolest portions of ride
Power Duration Curve: best mean maximal power at 5s, 30s, 1min, 5min, 10min, 20min, 60min
Optional full per-second time series when include_records=True
Shift quality:
proactive: shifted at 70-100 rpm (ideal cadence range)
reactive: shifted below 70 rpm (already grinding before shifting)
coasting: shifted at 0 rpm (mid-stop or freewheeling)
spun_out: shifted above 100 rpm (waited too long in easy gear)
Note: DI2 data requires Shimano Di2 / SRAM eTap. Cycling dynamics require a compatible power meter (e.g., Garmin Rally, Favero Assioma, PowerTap P1 pedals).
Args: activity_id: Garmin activity ID include_records: Include full per-second time series (default False). Warning: adds significant data volume for long rides.
Inputs: activity_id (required), include_records. Returns: result
get_activity_gearA
Get gear data used for an activity
Args: activity_id: ID of the activity to retrieve gear data for
Inputs: activity_id (required). Returns: result
get_activity_hr_in_timezonesB
Get heart rate data in different time zones for an activity
Args: activity_id: ID of the activity to retrieve heart rate time zone data for
Inputs: activity_id (required). Returns: result
get_activity_power_in_timezonesA
Get power distribution across training zones for an activity.
Returns time spent in each power zone with watt thresholds and duration. Requires a power meter. Zones are based on the athlete's FTP configured in Garmin Connect.
Args: activity_id: ID of the activity to retrieve power zone data for
Inputs: activity_id (required). Returns: result
get_activity_seriesA
Get raw per-activity measurements for a date range, with no interpretation.
One record per activity: date, discipline, duration, distance, average and max HR, power, elevation and Garmin's training effect. Pace is deliberately not computed — what counts as a comparable effort is a coaching decision.
Activities are returned oldest-first (Garmin serves them newest-first, which silently reverses anything treating list position as time).
Args: start_date: Start date in YYYY-MM-DD format (inclusive) end_date: End date in YYYY-MM-DD format (inclusive) sports: Optional filter, any of ["running", "cycling", "swimming"]. Activities outside the three triathlon disciplines have sport: null and are excluded when this filter is set. include_hr_zones: Attach per-zone seconds. Costs one extra Garmin request per activity — HR zones are not in the activity payload. Off by default.
Inputs: end_date (required), start_date (required), sports, include_hr_zones. Returns: result
get_activity_splitsC
Get splits for an activity
Args: activity_id: ID of the activity to retrieve splits for
Inputs: activity_id (required). Returns: result
get_activity_split_summariesC
Get split summaries for an activity
Args: activity_id: ID of the activity to retrieve split summaries for
Inputs: activity_id (required). Returns: result
get_activity_typed_splitsB
Get typed splits for an activity
Args: activity_id: ID of the activity to retrieve typed splits for
Inputs: activity_id (required). Returns: result
get_activity_typesA
Get all available activity types
Returns a list of all activity types supported by Garmin Connect, useful for filtering activities by type.
Inputs: no named parameters. Returns: result
get_activity_weatherA
Get weather data for an activity.
Garmin's weather endpoint returns temperatures in Fahrenheit (from the weather-station source) with no unit indicator, regardless of account settings. This tool converts them to the account's display unit: metric accounts get Celsius, statute_us accounts keep Fahrenheit. The temperature_unit field ("F" or "C") states which unit was returned.
Wind speed, unlike temperature, is already returned in the account's display unit (km/h for metric, mph for statute_us), so it is passed through unconverted and labeled via the wind_speed_unit field.
Args: activity_id: ID of the activity to retrieve weather data for
Inputs: activity_id (required). Returns: result
get_adhoc_challengesA
Get user-created social/group challenges (e.g., step competitions with friends)
Returns challenges created by users to compete with connections. These are different from official Garmin badge challenges.
Args: start: Starting index for pagination (default 0) limit: Maximum number of challenges to return (default 20, max 100)
Inputs: limit, start. Returns: result
get_all_day_eventsC
Get daily wellness events data
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_all_day_stressB
Get all-day stress data
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_athlete_contextA
Get the athlete's own thresholds, HR zones and physical profile.
Returns LTHR, cycling and running FTP, per-sport HR zone floors, VO2max, weight, height and unit preferences — the numbers that make an absolute heart rate or power target meaningful for this athlete rather than for a generic one.
Thresholds carry as_of and is_stale. Garmin marks a threshold stale
once it has stopped reflecting the athlete; prescribing against a stale
FTP means prescribing against a test nobody has repeated.
not_available names what Garmin does not expose, so a missing field
reads as a platform limitation rather than a gap in this profile.
No arguments.
Inputs: no named parameters. Returns: result
get_athlete_status_snapshotA
Get a point-in-time athlete snapshot with baseline deviations.
Current HRV, body battery, resting HR, readiness and sleep, each alongside Garmin's own seven-day baseline and the deviation from it. Deviation against a published baseline is arithmetic; what a −22% HRV deviation means for today's session is not, and no gate is applied.
Args: date: Date in YYYY-MM-DD format. Defaults to today — but before the watch syncs, today holds nothing, so pass yesterday when a morning call comes back empty.
Inputs: date. Returns: result
get_available_badge_challengesA
Get official Garmin badge challenges available to join
Returns monthly/seasonal challenges from Garmin that the user can join. These challenges award badges and points upon completion.
Args: start: Starting index for pagination (starts at 1) limit: Maximum number of challenges to return (default 20, max 100)
Inputs: limit, start. Returns: result
get_badge_challengesA
Get all badge challenges the user has joined (completed and in-progress)
Returns the user's history of badge challenges including progress, completion status, and earned dates.
Args: start: Starting index for pagination (starts at 1) limit: Maximum number of challenges to return (default 20, max 100)
Inputs: limit, start. Returns: result
get_blood_pressureC
Get blood pressure data
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_body_batteryC
Get body battery data with events
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_body_battery_eventsC
Get body battery events data
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_body_compositionA
Get body composition data for a single date or date range
Args: start_date: Date in YYYY-MM-DD format or start date if end_date provided end_date: Optional end date in YYYY-MM-DD format for date range
Inputs: start_date (required), end_date. Returns: result
get_cardiac_drift_analysisA
Measure cardiac drift (aerobic decoupling) for a specific activity.
Returns the percentage change in the power:HR ratio from the first half of the activity to the second. Where the boundary between "coupled" and "decoupled" sits is a coaching decision, so no label is attached — a common reading is that beyond about 5% the athlete was decoupling, but that number belongs in the coaching layer.
TWO PRECONDITIONS, both checked before you waste a call:
The activity must carry POWER. Drift is a power:HR ratio, so heart rate alone cannot produce it. Cycling needs a power meter. Running usually does not — most modern Garmin watches estimate running power natively and record it on every sample.
At least 3600 records with both power and heart rate, which is 60 minutes at 1-second sampling. Shorter sessions do not qualify, and a device set to smart recording rather than 1-second sampling may not reach it even on a long one.
When either fails the response is an error with a reason naming
which, plus records_total, records_with_power and
records_usable so the gap is visible. That is a limitation of the
recorded data, not a fault in the activity — do not report it as a
training finding.
Args: activity_id: Garmin activity ID (numeric or string)
Inputs: activity_id (required). Returns: result
get_coursesA
List all courses saved on Garmin Connect.
Returns a curated list of courses with id, name, distance, activity type and creation date.
Inputs: no named parameters. Returns: result
get_custom_foodsA
Search or list user's custom foods
Returns custom foods the user has created. Use the search parameter to find existing foods by name before creating duplicates — the response includes foodId and servingId needed for log_custom_food.
For branded catalog foods (FatSecret), use search_foods instead.
Args: search: Search term to filter foods by name (default: list all) start: Starting index for pagination (default 0) limit: Maximum number of results (default 20)
Inputs: limit, start, search. Returns: result
get_custom_food_serving_unitsA
Get available serving units for custom foods
Returns the list of valid serving units (e.g. G, ML, OZ) that can be used when creating custom foods.
Inputs: no named parameters. Returns: result
get_cycling_ftpA
Get the latest cycling Functional Threshold Power (FTP) data.
Returns the most recent cycling FTP estimate available from Garmin.
Inputs: no named parameters. Returns: result
get_daily_stepsB
Get steps data for a date range
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_daily_weigh_insA
Get weight measurements for a specific date
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_device_alarmsA
Get alarms from all Garmin devices
Returns all configured alarms with their schedules, sounds, and enabled status.
Inputs: no named parameters. Returns: result
get_device_last_usedB
Get information about the last used Garmin device
Inputs: no named parameters. Returns: result
get_devicesA
Get all Garmin devices associated with the user account
Inputs: no named parameters. Returns: result
get_device_settingsA
Get settings for a specific Garmin device
Returns device configuration including time/date format, units, activity tracking settings, and alarm information.
Args: device_id: Device ID (optional; defaults to the most recently used device when omitted; can be obtained from get_devices or get_device_last_used)
Inputs: device_id. Returns: result
get_device_solar_dataA
Get solar data for a specific device
Returns solar charging data for devices with solar panels (e.g., Instinct Solar, Fenix Solar). Only applicable to solar-capable devices.
Args: device_id: Device ID (can be obtained from get_devices) date: Date in YYYY-MM-DD format
Inputs: date (required), device_id (required). Returns: result
get_earned_badgesB
Get earned badges for user
Inputs: no named parameters. Returns: result
get_endurance_scoreA
Get endurance score data between dates
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_fitnessage_dataB
Get fitness age data
Args: date: Date in YYYY-MM-DD format details: If True, include component breakdown (BMI, RHR, vigorous activity) with targets and improvement suggestions
Inputs: date (required), details. Returns: result
get_floorsA
Get floors climbed data
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_full_nameA
Get user's full name from profile
Inputs: no named parameters. Returns: result
get_garmin_coach_workoutsA
Get Garmin Coach workouts around the given date
Returns workouts from the active Garmin Coach/training plan, including plan metadata, workout identifiers, dates, sport, duration, completion status, rest days, race days, and workout intent when Garmin provides them. Adaptive plans expose only Garmin's currently generated window, typically the current week; future dates may return no workouts even while a plan is active. The count includes rest-day entries.
Garmin's standalone Daily Suggested Workouts are generated on compatible devices. As of July 31, 2026, no supported or known Garmin Connect web/API endpoint, including those exposed by this project's python-garminconnect dependency, returns the device's upcoming DSW schedule. This tool returns Garmin Coach/training-plan workouts and does not synthesize device-generated suggestions.
This is the preferred tool for Garmin Coach requests. The legacy get_training_plan_workouts tool returns the same data; do not call both.
Adaptive Coach plans typically expose workout_uuid; other plan families may expose numeric workout_id. Pass whichever identifier is present to get_workout_by_id. Rest-day UUIDs may return minimal detail without workout segments.
Args: calendar_date: Reference date in YYYY-MM-DD format (returns week's workouts)
Inputs: calendar_date (required). Returns: result
get_gearA
Get all gear registered with the user account
Returns complete gear inventory including usage statistics and default activity associations. No parameters required - user profile is fetched automatically.
Args: include_stats: Include usage statistics for each gear item (default True). Set to False for faster response with large gear collections.
Inputs: include_stats. Returns: result
get_goalsA
Get Garmin Connect goals (active, future, or past)
Args: goal_type: Type of goals to retrieve. Options: "active", "future", or "past"
Inputs: goal_type. Returns: result
get_health_seriesA
Get raw multi-day health measurements in one call, with no interpretation.
Returns one record per day carrying only what Garmin measured. No thresholds are applied and no verdict is rendered — this is the input a coaching layer reasons over.
Days with no data are absent from days. Days whose fetch FAILED are
listed in errors; an empty errors array is what makes a short
series trustworthy. Never read a gap as a negative finding when
errors is non-empty.
Body Battery returns all four values Garmin records — at_wake is the
overnight recharge, most_recent is the end-of-day drain. They mean
different things; pick deliberately.
Args: start_date: Start date in YYYY-MM-DD format (inclusive) end_date: End date in YYYY-MM-DD format (inclusive, max 60 days) metrics: Subset of ["body_battery", "hrv", "resting_hr", "sleep", "stress", "training_load", "readiness"]. Defaults to all seven. Fewer metrics means fewer Garmin requests; body_battery and stress share one endpoint.
Inputs: end_date (required), start_date (required), metrics. Returns: result
get_heart_ratesA
Get full heart rate time-series data
Note: This returns detailed 2-minute interval data (~25KB). For a compact summary, use get_heart_rates_summary().
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_heart_rates_summaryA
Get heart rate summary with essential metrics (lightweight version)
Returns a compact summary (~500 bytes) instead of full time-series data (~25KB). Ideal for daily health checkups and LLM integrations.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_hill_scoreA
Get hill score data between dates
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_hrv_dataB
Get Heart Rate Variability (HRV) data
Args: date: Date in YYYY-MM-DD format return_timeseries: If True, include detailed 5-minute HRV readings (can be large)
Inputs: date (required), return_timeseries. Returns: result
get_hrv_trendA
Get HRV (Heart Rate Variability) trend over a date range.
Returns daily HRV values and weekly rolling averages. Single-day HRV is too noisy to act on — use this tool to identify baseline shifts that signal accumulated fatigue or recovery. A drop of >10ms from the 7-day baseline warrants reducing training load.
Recommended range: 7-21 days. Maximum: 30 days.
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_hydration_dataB
Get hydration data
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_inprogress_virtual_challengesA
Get in-progress virtual challenges/expeditions
Returns virtual challenges (like walking expeditions on famous trails) that the user is currently participating in.
Args: start: Starting index for pagination (default 1, must be >= 1; garminconnect 0.3.2 rejects 0 for this endpoint) limit: Maximum number of challenges to return (default 20, max 100)
Inputs: limit, start. Returns: result
get_lactate_thresholdA
Get lactate threshold data
Returns lactate threshold information, which is the exercise intensity at which lactate starts to accumulate in the blood. This is a key metric for endurance training.
Args: start_date: Start date in YYYY-MM-DD format (optional, omit for latest) end_date: End date in YYYY-MM-DD format (optional, omit for latest)
Inputs: end_date, start_date. Returns: result
get_lifestyle_logging_dataB
Get lifestyle logging data for a specific date
Returns lifestyle logging data which allows users to track behaviors and their impact on health metrics.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_menstrual_calendar_dataA
Get menstrual calendar data between specified dates
Automatically chunks requests longer than 92 days, Garmin's server-side limit, and stitches the responses together.
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_menstrual_data_for_dateB
Get menstrual data for a specific date
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_morning_briefA
Get the morning's measurements in a single call.
Aggregates sleep, recovery (body battery, HRV, resting HR), Garmin's training readiness and today's scheduled workout — five endpoints in one round trip. Body battery is reported both at wake and most-recent, because they answer different questions.
No alerts and no recommendation: those thresholds live in the coaching skill, where they can be read and changed in one place.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_morning_training_readinessA
Get morning training readiness score
Returns the morning training readiness assessment, which evaluates recovery status and readiness to train based on overnight metrics.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_non_completed_badge_challengesA
Get badge challenges currently in progress (not yet completed)
Returns active challenges the user has joined but hasn't completed yet. Useful for tracking current progress toward badge goals.
Args: start: Starting index for pagination (starts at 1) limit: Maximum number of challenges to return (default 20, max 100)
Inputs: limit, start. Returns: result
get_nutrition_daily_food_logA
Get daily food consumption records for a date
Returns food items logged throughout the day including calories, macronutrients, and meal associations.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_nutrition_daily_mealsA
Get daily meal summaries for a date
Returns meal-level summaries (breakfast, lunch, dinner, snacks) with nutritional totals for each meal. Each meal includes a mealId needed for logging food items to that meal.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_nutrition_daily_settingsA
Get nutrition plan/settings for a date
Returns the user's nutrition goals and targets including calorie targets, macronutrient goals, and plan configuration.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_performance_trendA
Get per-activity pace or power over time, with the regression slope.
Returns each activity's raw value and average HR, plus the slope across them. No HR normalisation is applied — normalise against the athlete's own thresholds from get_athlete_context if you want that.
Args: metric: Metric to track — "pace" (default) or "power" sport: Sport type — "running" or "cycling" days: Number of days to analyze (default 90)
Inputs: days, sport, metric. Returns: result
get_personal_recordD
Get personal records for user
Inputs: no named parameters. Returns: result
get_power_duration_curveA
Get season-best Power Duration Curve across recent activities.
Downloads FIT files for recent cycling activities and computes best mean maximal power at each standard duration. Returns season bests with which activity and date each best came from.
Durations: 5s (sprint), 30s, 1min, 5min (VO2 max proxy), 10min, 20min (FTP proxy), 60min
Use the 20-minute best × 0.95 as a strong FTP estimate without a formal test.
Warning: downloads multiple FIT files — may take 30-60 seconds for 20 activities.
Args: num_activities: Number of recent activities to analyze (default 20, max 50) activity_type: Activity type to filter (default "cycling")
Inputs: activity_type, num_activities. Returns: result
get_pregnancy_summaryA
Get pregnancy summary data
Inputs: no named parameters. Returns: result
get_primary_training_deviceB
Get information about the primary training device
Returns details about the device designated as primary for training metrics, along with other wearable devices on the account.
Inputs: no named parameters. Returns: result
get_progress_summary_between_datesB
Get progress summary for a metric between dates
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format metric: Metric to get progress for (e.g., "elevationGain", "duration", "distance", "movingDuration")
Inputs: metric (required), end_date (required), start_date (required). Returns: result
get_race_predictionsA
Get predicted race times based on current fitness level
Returns Garmin's predictions for 5K, 10K, half marathon, and marathon finish times based on the user's recent training data and VO2 max.
Inputs: no named parameters. Returns: result
get_respiration_dataA
Get full respiration time-series data
Note: This returns detailed interval data (~20KB). For a compact summary, use get_respiration_summary().
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_respiration_summaryA
Get respiration summary with essential metrics (lightweight version)
Returns a compact summary (~300 bytes) instead of full time-series data (~20KB).
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_respiration_trendA
Get overnight respiration rate trend over a date range.
Elevated resting respiration rate (compared to personal baseline) is an early warning sign for overreaching, illness, or poor recovery. Use this alongside HRV trend for a complete recovery picture.
Recommended range: 7-21 days. Maximum: 30 days.
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_rhr_dayC
Get resting heart rate data
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_scheduled_workoutsA
Get scheduled workouts between two dates with curated summary list
Returns workouts that have been scheduled on the Garmin Connect calendar, including their scheduled dates and completion status.
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_sleep_dataA
Get full sleep data with all details
Note: This returns detailed sleep data (~50KB). For a compact summary, use get_sleep_summary().
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_sleep_summaryA
Get sleep summary with only essential metrics (lightweight version)
This endpoint returns a compact summary of sleep data (~350 bytes) instead of the full granular data (~50KB). Ideal for daily health checkups and LLM integrations where the full time-series data would overwhelm the context window.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_spo2_dataB
Get SpO2 (blood oxygen) data
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_statsA
Get daily activity stats with curated essential metrics
Returns a summary of daily health and activity data including steps, calories, heart rate, stress, body battery, and sleep metrics.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_stats_and_bodyB
Get stats and body composition data
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_steps_dataA
Get detailed steps data with 15-minute intervals
Note: This returns full interval data (~14KB). For a compact summary, use get_stats() which includes total_steps.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_stress_dataA
Get full stress time-series data
Note: This returns detailed interval data (~35KB) including body battery. For a compact summary, use get_stress_summary().
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_stress_summaryA
Get stress summary with essential metrics (lightweight version)
Returns a compact summary (~400 bytes) instead of full time-series data (~35KB). Ideal for daily health checkups and LLM integrations.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_training_effectB
Get training effect data for a specific activity
Args: activity_id: ID of the activity to retrieve training effect for
Inputs: activity_id (required). Returns: result
get_training_load_balanceA
Get Garmin's Load Focus — the distribution of the trailing-month training load across Aerobic Low, Aerobic High, and Anaerobic intensity bands, plus the system's feedback phrase (e.g. AEROBIC_HIGH_SHORTAGE, BALANCED, ANAEROBIC_SHORTAGE).
Use this to assess whether the athlete's training mix is balanced or
deficient in a particular intensity band. Each band reports its load
alongside Garmin's target range; a status of "below", "within", or
"above" is computed from the load relative to that range.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_training_load_breakdownB
Get training load breakdown by sport for a date range.
Returns total minutes and percentage distribution across running, cycling, swimming and other sports, plus Garmin's acute/chronic load, ACWR and TSB as of the end date.
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_training_load_trendA
Get the Performance Management Chart (CTL/ATL/TSB) over a date range.
Returns Chronic Training Load (CTL, 42-day fitness), Acute Training Load (ATL, 7-day fatigue), Training Stress Balance (TSB = CTL - ATL, form/freshness), and Acute:Chronic Workload Ratio (ACWR) per day. Use this to assess whether the athlete is building fitness, peaking, or accumulating too much fatigue.
Recommended range: 4-8 weeks. Maximum: 90 days.
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_training_plan_workoutsA
Compatibility alias for get_garmin_coach_workouts
Prefer get_garmin_coach_workouts for new requests. This legacy tool returns the same Garmin Coach/training-plan data; do not call both for one request. Adaptive plans expose only Garmin's currently generated window, typically the current week; future dates may return no workouts even while a plan is active.
Adaptive training plans typically expose workout_uuid; other plan families may expose numeric workout_id. Pass whichever identifier is present to get_workout_by_id. The returned count includes rest days.
Args: calendar_date: Reference date in YYYY-MM-DD format (returns week's workouts)
Inputs: calendar_date (required). Returns: result
get_training_readinessB
Get training readiness data with curated metrics
Returns training readiness score and contributing factors.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_training_readiness_compositeB
Get Garmin's training readiness score with its factor breakdown.
Returns Garmin's own score and the percentage each of its six factors contributed — sleep, recovery time, HRV, acute load, sleep history, stress history. No interpretation: what the score means for today's session is a coaching decision.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_training_statusB
Get training status with curated metrics
Returns comprehensive training status including load, VO2 max, recovery, and training readiness indicators.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_unit_systemA
Get user's preferred unit system from profile
Inputs: no named parameters. Returns: result
get_user_profileB
Get user profile information
Inputs: no named parameters. Returns: result
get_userprofile_settingsC
Get user profile settings
Inputs: no named parameters. Returns: result
get_user_summaryB
Get user summary data (compatible with garminconnect-ha)
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
get_vo2max_trendA
Get VO2 max trend over a date range.
Returns daily VO2 max estimates from Garmin's FirstBeat algorithm. Use this to track whether training is producing fitness gains over weeks or months. Flat or declining VO2 max over 4+ weeks suggests insufficient training stimulus or overreaching.
Note: VO2 max estimates are smoothed and update gradually — daily changes of <0.5 are within normal noise. Focus on the 4-6 week trend direction.
Garmin records a new VO2 max value only on days with a recompute (after an activity). Days in between carry the last known value forward and are marked with "carried_forward": true, matching the trend chart in Garmin Connect.
If historical values are unavailable, the current profile estimate is returned separately and is not represented as a historical trend point.
Recommended range: 4-12 weeks. Maximum: 90 days.
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_weekly_intensity_minutesA
Get weekly intensity minutes data aggregates
Returns weekly intensity minutes (moderate and vigorous) for the specified number of weeks ending at end_date.
Args: end_date: End date in YYYY-MM-DD format weeks: Number of weeks to fetch (default 4, max 52)
Inputs: end_date (required), weeks. Returns: result
get_weekly_load_progressionA
Get training minutes per ISO week and the week-over-week change.
Arithmetic only. Whether a given ramp rate is too fast is a coaching decision.
Args: weeks: Number of weeks to analyze (default 12, max 52)
Inputs: weeks. Returns: result
get_weekly_stepsA
Get weekly step data aggregates
Returns weekly step totals for the specified number of weeks ending at end_date.
Args: end_date: End date in YYYY-MM-DD format weeks: Number of weeks to fetch (default 4, max 52)
Inputs: end_date (required), weeks. Returns: result
get_weekly_stressC
Get weekly stress data aggregates
Returns weekly stress values for the specified number of weeks ending at end_date.
Args: end_date: End date in YYYY-MM-DD format weeks: Number of weeks to fetch (default 4, max 52)
Inputs: end_date (required), weeks. Returns: result
get_weigh_insA
Get weight measurements between specified dates
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_workout_by_idA
Get detailed information for a specific workout
Returns workout details including segments and step structure.
Accepts either:
Numeric workout ID (from get_workouts, get_scheduled_workouts, or training-plan families that expose workout_id)
Workout UUID (from adaptive Garmin Coach/training-plan workouts)
Rest-day UUIDs can resolve to a minimal record without a workout name or segments.
Args: workout_id: Workout ID (numeric) or UUID (for training plan workouts)
Inputs: workout_id (required). Returns: result
get_workout_complianceA
List scheduled workouts alongside activities recorded the same day.
Returns the pairing, not a compliance score. Matching is on date only — Garmin's schedule query returns no sport to match against — so whether a given activity completes a given workout is left to the caller.
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
get_workoutsA
Get all workouts with curated summary list
Returns a count and list of workout summaries with essential metadata only. For detailed workout information including segments, use get_workout_by_id.
Inputs: no named parameters. Returns: result
get_zone_distributionB
Get heart rate zone distribution across sports for a date range.
Calculates the percentage of time spent in each HR zone (Z1-Z5) for running, cycling, and swimming activities.
Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format
Inputs: end_date (required), start_date (required). Returns: result
log_custom_foodA
Log a food item to a meal on a date
Adds a food entry to the nutrition log. The meal is determined automatically by matching meal_time against each meal's startTime/endTime window; falls back to SNACKS if no window matches.
Food sources:
"GARMIN" (default): user's custom food library. Use get_custom_foods to find food_id and serving_id.
"FATSECRET": branded/catalog food from FatSecret. Use search_foods to find food_id and serving_id. Pass the source value from the search_foods result (e.g. "FATSECRET").
Garmin custom food IDs are 32-char hex UUIDs; FatSecret IDs are numeric strings (e.g. "4132350"). Passing the wrong source for a given food_id returns a 400 from Garmin.
Args: meal_date: Date in YYYY-MM-DD format meal_time: Time in HH:MM:SS format (e.g. "12:30:00", account timezone) food_id: Food ID from get_custom_foods (GARMIN) or search_foods (FATSECRET) serving_id: Serving ID from get_custom_foods or search_foods serving_qty: Number of servings (default 1) source: Food namespace — "GARMIN" (default) or "FATSECRET"
Inputs: food_id (required), meal_date (required), meal_time (required), serving_id (required), +2 more. Returns: result
log_foodA
Quick-add a food entry with macro values to the nutrition log
Logs food directly by name and macros without requiring a food ID. Uses Garmin's Quick Add feature. The meal is determined automatically by matching meal_time against each meal's startTime/endTime window; falls back to SNACKS if no window matches.
Args: meal_date: Date in YYYY-MM-DD format name: Display name for the food entry calories: Calories (kcal) carbs: Carbohydrates in grams protein: Protein in grams fat: Fat in grams meal_time: Time in HH:MM:SS format (account timezone)
Inputs: fat (required), name (required), carbs (required), protein (required), +3 more. Returns: result
remove_gear_from_activityA
Remove gear association from an activity
Unlinks a specific piece of gear from an activity.
Args: activity_id: ID of the activity gear_uuid: UUID of the gear to remove
Inputs: gear_uuid (required), activity_id (required). Returns: result
request_reloadA
Ask Garmin to re-process the epoch data for a date.
Use when a day's metrics look incomplete after a sync. The response says whether the reload was accepted, not whether new data appeared — re-read the day afterwards to see that.
Args: date: Date in YYYY-MM-DD format
Inputs: date (required). Returns: result
schedule_weekA
Schedule a list of workouts for the week in a single call.
Idempotent: if a workout is already scheduled for that date, it is reported as already scheduled and the POST is skipped (avoids duplicating calendar entries).
A bad entry is reported and the rest of the batch continues; nothing aborts the whole week.
Args: week: List of dicts with keys: calendar_date (YYYY-MM-DD) and workout_id (int). The key is calendar_date, matching schedule_workout and schedule_workouts.
Inputs: week (required). Returns: result
schedule_workoutA
Schedule a workout to a specific calendar date
This adds an existing workout from your Garmin workout library to your Garmin Connect calendar on the specified date.
Idempotent: if the workout is already scheduled for that date, this is a no-op that reports success without creating a duplicate entry.
Args: workout_id: ID of the workout to schedule (get IDs from get_workouts) calendar_date: Date to schedule the workout in YYYY-MM-DD format
Inputs: workout_id (required), calendar_date (required). Returns: result
schedule_workoutsA
Schedule multiple workouts to specific calendar dates
This adds workouts to your Garmin Connect calendar in a single call. Each item can either reference an existing workout by ID, or provide inline workout_data to upload-and-schedule in one step.
Args: schedules: List of workout schedules, each with: - calendar_date (str): Date to schedule the workout in YYYY-MM-DD format (required) - workout_id (int): ID of an existing workout to schedule (required unless workout_data is provided) - workout_data (dict): Inline workout JSON to upload first, then schedule (optional). When provided, workout_id is not required. Uses the same structure and target-value rules as upload_workout.
Examples: Schedule existing workouts by ID: [{"workout_id": 123456, "calendar_date": "2024-01-15"}, {"workout_id": 789012, "calendar_date": "2024-01-17"}]
Upload and schedule inline:
[{"calendar_date": "2024-01-15", "workout_data": {"workoutName": "Easy Run", ...}},
{"workout_id": 789012, "calendar_date": "2024-01-17"}]
Inputs: schedules (required). Returns: result
search_foodsA
Search Garmin's general food catalog (FatSecret + Garmin custom foods)
Searches across the entire food catalog including FatSecret-sourced branded and generic foods, not just the user's Garmin custom foods. Use this to find branded packaged foods by name before logging them.
Returns food_id, source, name, brand, and all available servings with macros. The source field ("FATSECRET" or "GARMIN") and food_id together identify the right routing for log_custom_food — pass both to log_custom_food's food_id and source parameters respectively.
For the user's own custom foods only, use get_custom_foods instead.
Args: query: Food name or brand to search for (e.g. "Cheerios", "Greek yogurt") start: Starting index for pagination (default 0) limit: Maximum number of results per page (default 20)
Inputs: query (required), limit, start. Returns: result
set_activity_descriptionA
Set or update the free-text description (notes) of an activity.
This is the notes field shown on the activity page — useful for recording how a session felt, kit used, conditions, niggles, etc. Pass an empty string to clear an existing description.
Args: activity_id: ID of the activity to update description: New description text (empty string clears it)
Inputs: activity_id (required), description (required). Returns: result
set_activity_event_typeA
Set the event type of an activity.
Event type categorises the activity's purpose. Valid keys: race, recreation, specialEvent, training, transportation, touring, geocaching, fitness, uncategorized.
Args: activity_id: ID of the activity to update event_type: Target event type key (e.g. 'race', 'training')
Inputs: event_type (required), activity_id (required). Returns: result
set_activity_feelA
Set how an activity felt ('How did you feel?').
Mirrors Garmin Connect's 5-point feel rating, stored as one of: 0 = very tired / poor 25 = tired 50 = normal 75 = good 100 = strong Higher is better.
Args: activity_id: ID of the activity to update feel: One of 0, 25, 50, 75, 100
Inputs: feel (required), activity_id (required). Returns: result
set_activity_nameA
Set or update the name of an activity.
Args: activity_id: ID of the activity to update activity_name: New activity name
Inputs: activity_id (required), activity_name (required). Returns: result
set_activity_typeA
Change the activity type (sport) of an activity.
Useful for reclassifying a mislabelled activity, e.g. flipping a run logged as 'trail_running' to 'running', or a 'treadmill_running' walk to 'treadmill_walking'. Call get_activity_types to see all valid type keys.
Args: activity_id: ID of the activity to update type_key: Target activity type key (e.g. 'running', 'trail_running', 'treadmill_running', 'cycling', 'lap_swimming')
Inputs: type_key (required), activity_id (required). Returns: result
set_blood_pressureC
Set blood pressure values
Args: systolic: Systolic pressure (top number) diastolic: Diastolic pressure (bottom number) pulse: Pulse rate notes: Optional notes
Inputs: pulse (required), systolic (required), diastolic (required), notes. Returns: result
set_fit_download_dirA
Set and persist the default directory for downloaded activity files.
Stores the absolute path in a small JSON config file (~/.garminconnect_fit_config.json, overridable via GARMIN_FIT_CONFIG) so download_activity_file can save files without asking again.
Args: path: Directory where activity files (.fit/.gpx/.tcx/.csv) are saved. Pass the current working directory to keep files where the server runs.
Inputs: path (required). Returns: result
set_nutrition_daily_settingsA
Update daily nutrition goals (calorie target and macronutrient targets).
Reads the current settings for the date, applies the supplied overrides, and writes the merged result back. Only the fields you provide are changed; omitted fields keep their existing values.
Garmin stores macros as grams. The calorie goal should match 4carbs + 4protein + 9*fat to within a small rounding margin — Garmin accepts minor mismatches but will silently correct large discrepancies.
Args: date: Date in YYYY-MM-DD format (settings are typically set once and inherited across days, but Garmin accepts per-day overrides) calorie_goal: Daily calorie target in kcal carbs_grams: Daily carbohydrate target in grams fat_grams: Daily fat target in grams protein_grams: Daily protein target in grams
Inputs: date (required), fat_grams, carbs_grams, calorie_goal, +1 more. Returns: result
set_perceived_effortA
Set the perceived effort (RPE) for an activity.
Mirrors Garmin Connect's 'Perceived Effort' rating on a 0-10 scale, where 0 clears the rating. Internally Garmin stores this multiplied by 10 (so RPE 7 is stored as 70); this tool handles the conversion.
Args: activity_id: ID of the activity to update rpe: Perceived effort from 0 to 10 (0 clears the rating)
Inputs: rpe (required), activity_id (required). Returns: result
unschedule_workoutA
Remove a scheduled workout from the Garmin Connect calendar
Deletes a calendar entry without deleting the underlying workout template — the workout stays in your library and can be re-scheduled.
IMPORTANT: scheduled_workout_id is the calendar-entry id, which is different from the workout's id. Get it from get_scheduled_workouts (the "scheduled_workout_id" field), not from get_workouts.
Note: the scheduled-workouts listing is an eventually-consistent index. If you just scheduled this workout, allow a moment before unscheduling so the id is available.
Args: scheduled_workout_id: Calendar-entry id from get_scheduled_workouts
Inputs: scheduled_workout_id (required). Returns: result
unschedule_workoutsA
Remove multiple scheduled workouts from the Garmin Connect calendar
Deletes multiple calendar entries in a single call. The underlying workout templates are left intact in your library.
IMPORTANT: each id is a calendar-entry id (the "scheduled_workout_id" field from get_scheduled_workouts), not a workout id.
Args: scheduled_workout_ids: List of calendar-entry ids from get_scheduled_workouts
Inputs: scheduled_workout_ids (required). Returns: result
update_custom_foodA
Update an existing custom food in the user's Garmin nutrition library
Fetches the food's current record before writing so that omitted optional fields (brand, carbs, protein, fat, micros, etc.) preserve their existing values rather than being cleared. Only the fields you explicitly pass are changed; everything else is carried forward from the current record.
All nutrient amounts are ABSOLUTE values per serving, not %DV. Nutrition labels often print %DV for calcium/iron/vitamin D — convert to absolute units before passing.
Use get_custom_foods first to find the foodId and servingId.
Args: food_id: ID of the custom food to update (from get_custom_foods) serving_id: Serving ID of the food (from get_custom_foods) food_name: Name of the custom food calories: Calories per serving serving_unit: Unit for serving size (e.g. "G", "ML", "OZ"). Default "G" number_of_units: Serving size in the specified unit. Default 100 brand_name: Brand or vendor name; omit to preserve the existing value carbs: Carbohydrates in grams per serving protein: Protein in grams per serving fat: Total fat in grams per serving fiber: Fiber in grams per serving sugar: Sugar in grams per serving saturated_fat: Saturated fat in grams per serving sodium: Sodium in mg per serving cholesterol: Cholesterol in mg per serving potassium: Potassium in mg per serving trans_fat: Trans fat in grams per serving calcium: Calcium in mg per serving (NOT %DV) iron: Iron in mg per serving (NOT %DV) vitamin_d: Vitamin D in mcg per serving (NOT %DV)
Inputs: food_id (required), calories (required), food_name (required), serving_id (required), +16 more. Returns: result
upload_courseA
Upload a GPX file as a Garmin Connect Course.
The course can then be loaded onto the watch (sync or "Send to Device") and used as a navigation course or to build a PacePro strategy.
Args: gpx_path: Absolute path to the .gpx file on disk. course_name: Override the course name. Defaults to the name parsed from the GPX file. activity_type: One of running, cycling, hiking, walking, trail_running, mountain_biking, road_biking, gravel_cycling. Defaults to running. description: Optional description shown on the course detail page.
Inputs: gpx_path (required), course_name, description, activity_type. Returns: result
upload_workoutA
Upload a workout from JSON data
Creates a new workout in Garmin Connect from structured workout data.
IMPORTANT: Step types must use Garmin's DTO format:
Use "ExecutableStepDTO" for regular steps (warmup, interval, cooldown, recovery)
Use "RepeatGroupDTO" for repeat/interval groups with numberOfIterations. Always include endCondition with conditionTypeId 7 and conditionTypeKey "iterations"; omitting conditionTypeId causes the API to silently corrupt the repeat count.
IMPORTANT: Heart rate targets come in two forms:
Named zone (e.g. Zone 2): set targetType to "heart.rate.zone" and use "zoneNumber" (1-5). Do NOT put the zone number in targetValueOne.
Custom HR range (e.g. 105-143 bpm): set targetType to "heart.rate.zone" and use "targetValueOne" (low bpm) / "targetValueTwo" (high bpm). Do NOT set "zoneNumber". This matches Garmin Connect's "Custom" heart rate target. For non-HR targets (pace, power, cadence), use targetValueOne/targetValueTwo directly. Target values are fields on the workout step, alongside targetType; do not put targetValueOne, targetValueTwo, or zoneNumber inside the targetType object. Use either zoneNumber or targetValueOne/targetValueTwo, not both. Garmin silently discards a custom range when a named zone is also present.
Note: a safety check converts targetValueOne 1-5 to zoneNumber when zoneNumber is missing, to catch the common mistake of putting a zone index in targetValueOne. Typical bpm values (e.g. 105, 143) are not affected.
IMPORTANT: Target type IDs and keys must match Garmin's canonical mapping. Garmin treats workoutTargetTypeId as authoritative, so mismatches are rejected before upload. Known mappings:
workoutTargetTypeId 1 -> "no.target"
workoutTargetTypeId 2 -> "power.zone" (cycling power zone 1-7, use zoneNumber)
workoutTargetTypeId 4 -> "heart.rate.zone"
workoutTargetTypeId 6 -> "pace.zone" (running/swim). NOT power: a cycling upload with "power.between" is stored as pace.zone.
IMPORTANT: For cycling power targets use the correct target type:
Power zone (zone 1-7 based on FTP %): use workoutTargetTypeId 2, key "power.zone", and "zoneNumber" (1-7).
Absolute watt range (e.g. 200-250 W): use workoutTargetTypeId 2, key "power.zone", and "targetValueOne" (low watts) / "targetValueTwo" (high watts). Using workoutTargetTypeId 2 with key "power.between" is a silent Garmin bug: the workout uploads but Garmin stores it as "power.zone" and the intent is lost.
Use {"workoutTargetTypeId": 4, "workoutTargetTypeKey": "heart.rate.zone"} with targetValueOne/targetValueTwo for custom heart-rate ranges.
IMPORTANT: Sport type IDs for workouts (different from activity API!):
1 = running, 2 = cycling, 5 = strength_training, 6 = cardio, 11 = walking
IMPORTANT: End condition IDs and keys must match Garmin's canonical mapping. Garmin treats conditionTypeId as authoritative, so mismatches such as {"conditionTypeId": 4, "conditionTypeKey": "heart.rate"} are rejected before upload because Garmin would interpret them as "calories". Use {"conditionTypeId": 6, "conditionTypeKey": "heart.rate"} for heart-rate end conditions.
Available Templates: Instead of building workout JSON from scratch, you can use these MCP resources as starting points:
workout://templates/simple-run - Basic warmup/run/cooldown structure
workout://templates/interval-running - Interval training with repeat groups
workout://templates/tempo-run - Tempo run with heart rate zone targets
workout://templates/strength-circuit - Strength training with exercises, reps, rest
workout://reference/structure - Complete JSON structure reference with all fields
Access these resources using your MCP client's resource reading capability, modify the template as needed, and pass the resulting JSON as the workout_data parameter.
Strength training workouts require these additional fields on each exercise step:
"category": exercise category (e.g. "BENCH_PRESS", "PULL_UP", "CURL", "SHOULDER_PRESS", "ROW", "SQUAT", "DEADLIFT", "TRICEPS_EXTENSION", "PLANK", "LUNGE", "CARDIO")
"exerciseName": specific exercise (e.g. "BARBELL_BENCH_PRESS", "PULL_UP", "DUMBBELL_BICEPS_CURL", "DUMBBELL_SHOULDER_PRESS", "BENT_OVER_ROW_WITH_DUMBELL", "BODY_WEIGHT_DIP", "BARBELL_SQUAT", "BARBELL_DEADLIFT")
"weightValue" (optional): weight as number (e.g. 24.0)
"weightUnit" (optional): {"unitId": 8, "unitKey": "kilogram", "factor": 1000.0} Use endCondition reps (conditionTypeId: 10) for exercises, rest (stepTypeId: 5) between sets.
Example strength exercise step: { "type": "ExecutableStepDTO", "stepOrder": 1, "stepType": {"stepTypeId": 3, "stepTypeKey": "interval"}, "endCondition": {"conditionTypeId": 10, "conditionTypeKey": "reps"}, "endConditionValue": 10.0, "targetType": {"workoutTargetTypeId": 1, "workoutTargetTypeKey": "no.target"}, "category": "BENCH_PRESS", "exerciseName": "BARBELL_BENCH_PRESS", "weightValue": 60.0, "weightUnit": {"unitId": 8, "unitKey": "kilogram", "factor": 1000.0} }
Example running workout with HR zone target: { "workoutName": "My Workout", "sportType": {"sportTypeId": 1, "sportTypeKey": "running"}, "workoutSegments": [{ "segmentOrder": 1, "sportType": {"sportTypeId": 1, "sportTypeKey": "running"}, "workoutSteps": [{ "type": "ExecutableStepDTO", "stepOrder": 1, "stepType": {"stepTypeId": 3, "stepTypeKey": "interval"}, "endCondition": {"conditionTypeId": 2, "conditionTypeKey": "time"}, "endConditionValue": 1200.0, "targetType": {"workoutTargetTypeId": 4, "workoutTargetTypeKey": "heart.rate.zone"}, "zoneNumber": 3 }] }] }
Args: workout_data: Dictionary containing workout structure (name, sport type, segments, etc.)
Inputs: workout_data (required). Returns: result
upload_workoutsA
Upload multiple workouts from JSON data in a single call
Creates multiple new workouts in Garmin Connect. Each item in the list uses the same structure as upload_workout.
IMPORTANT: Step types must use Garmin's DTO format:
Use "ExecutableStepDTO" for regular steps (warmup, interval, cooldown, recovery)
Use "RepeatGroupDTO" for repeat/interval groups with numberOfIterations. Always include endCondition with conditionTypeId 7 and conditionTypeKey "iterations"; omitting conditionTypeId causes the API to silently corrupt the repeat count.
IMPORTANT: For named heart rate zone targets, use "zoneNumber" (1-5), NOT targetValueOne/targetValueTwo. For custom heart-rate ranges, use targetType {"workoutTargetTypeId": 4, "workoutTargetTypeKey": "heart.rate.zone"} with targetValueOne/targetValueTwo. Target values belong on the workout step, alongside targetType, not inside it. For cycling power zone targets (zone-based), use workoutTargetTypeId 2, key "power.zone". For cycling absolute watt range targets, use workoutTargetTypeId 2, key "power.zone", with targetValueOne (low watts) and targetValueTwo (high watts). Target type IDs and keys must match Garmin's canonical mapping.
IMPORTANT: End condition IDs and keys must match Garmin's canonical mapping. Garmin treats conditionTypeId as authoritative, so mismatches are rejected before upload.
Args: workouts: List of workout dictionaries, each containing workout structure (name, sport type, segments, etc.) — same format as upload_workout.
Inputs: workouts (required). Returns: result
upsert_and_logA
Find-or-create a custom food then log it in one step
Searches the user's custom food library for food_name. If found, logs it immediately. If not found, creates it with the provided nutrition data and then logs it. This avoids duplicate food entries and removes the need for separate search → create → log round-trips.
Args: meal_date: Date in YYYY-MM-DD format meal_time: Time in HH:MM:SS format (account timezone); used to determine the meal automatically food_name: Name of the food to find or create calories: Calories per serving carbs: Carbohydrates in grams per serving protein: Protein in grams per serving fat: Total fat in grams per serving serving_unit: Unit for serving size (e.g. "G", "ML", "OZ"). Default "G" number_of_units: Serving size in the specified unit. Default 100 serving_qty: Number of servings to log (default 1)
Inputs: calories (required), food_name (required), meal_date (required), meal_time (required), +6 more. Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 172 tools
With 172 tools there is heavy overlap: get_activities_fordate / get_activities_by_date / get_activities / count_activities, get_stats / get_user_summary / get_stats_and_body, and a large family of run-workout builders (create_run_workout vs create_run_easy_workout/tempo/long/intervals/hills/progression) that differ only by defaults. The very detailed descriptions and explicit 'use X instead' notes (e.g. summary vs full variants, the get_garmin_coach_workouts alias) mitigate much of the confusion, but several boundaries remain fuzzy.
Almost everything follows a snake_case verb_noun pattern (get_*, set_*, create_*, delete_*, upload_*, schedule_*, log_*, search_*), which is highly predictable. Minor deviations exist: get_activities_fordate lacks the underscore used in get_activities_by_date, singular/plural varies (get_personal_record vs get_race_predictions), and upsert_and_log uses a non-standard verb.
172 tools is far beyond any reasonable agent-facing surface and reflects extreme specialization, redundant aliases (schedule_workout/schedule_workouts/schedule_week, upload_workout/upload_workouts), and near-duplicate summary/full pairs. This is the extreme-mismatch end of the scale even for a broad platform like Garmin Connect.
Coverage is remarkably broad: activity read/update/delete/create, health and sleep metrics, nutrition CRUD plus logging, workout build/upload/schedule/unschedule/delete, courses, gear, challenges and rich training analytics. Only minor gaps remain (no workout edit, no true hydration delete, no update for scheduled entries), and several are documented platform limitations the agent can work around.
Maintenance
Related MCP Connectors
List, fetch, create, edit (replace), delete and schedule structured workouts on Garmin Connect (runn
Access COROS activity, training and health data, and manage training plans and workouts.
Unofficial Garmin Connect access: sleep, HRV, training, activities, workouts and weight.
Your swim, bike and run training: readiness, HRV, sleep, workouts and race predictions. Read-only.
Related MCP Servers
- AlicenseBqualityDmaintenanceIntegrates with Garmin Connect to retrieve activity data, health metrics, and provide AI-powered training insights and personalized coaching recommendations based on your fitness activities and performance trends.14270 npmMIT
- AlicenseNot gradedqualityDmaintenanceEnables AI agents to access Garmin Connect activities, workouts, and workout templates for querying and creating workout plans.6MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI assistants to read Garmin activities and create/schedule structured workouts and multi-week training plans on Garmin Connect, syncing to the user's watch.1MIT
- FlicenseNot gradedqualityCmaintenanceProvides read access to Garmin Connect health and training data (sleep, heart rate, stress, HRV, activities) and supports writes like creating workouts, editing activities, and exporting files.-