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 "Install 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-triathlonWhat'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: GC-MCP
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 |
| 22 | list, get, 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)
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- FlicenseBqualityDmaintenanceIntegrates 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.14
- Flicense-qualityBmaintenanceExposes Garmin Connect data and workout management to AI agents, supporting tools, resources, and prompts for health data, workout creation, and coaching workflows.1
- Alicense-qualityDmaintenanceEnables AI agents to access Garmin Connect activities, workouts, and workout templates for querying and creating workout plans.5MIT
Related MCP Connectors
List, fetch, create, edit (replace), delete and schedule structured workouts on Garmin Connect (runn
Garmin data in Claude & ChatGPT via the Garmin Health API. OAuth sign-in, no password sharing.
Garmin data in Claude: 135 tools — activities, sleep, HRV, training, workouts. Free, open source.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/pluton74mac/Garmin_MCP_Triathlon'
If you have feedback or need assistance with the MCP directory API, please join our Discord server