# Garmin Connect (pypi · garmin-connect-api)

Read and write Garmin Connect: activities, strength sets, health data, workouts and weigh-ins.

- Trust score: 69/100 (medium)
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-10-24

## Components

- pypi · `garmin-connect-api`: 69/100 (this document), [markdown](https://verifymcp.io/servers/happyc0der-garmin-connect-api/garmin-connect-api.md), [page](https://verifymcp.io/servers/happyc0der-garmin-connect-api/garmin-connect-api)

## Channel facts

- Registry: `pypi`
- Package: `garmin-connect-api`
- Version: `0.3.1`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-10-24.

- **Supply Chain Security**: 50/100
  - Malware scan not yet available for this package.
  - No known CVEs affecting this package version or its production dependencies.
  - Runs hatchling.build at install time, a recognised build step with no custom scripting around it.
  - 3 of 40 dependencies flagged as unhealthy.
- **Provenance & Transparency**: 100/100
  - Source repository is publicly reachable at the declared URL.
  - Cryptographically verified build provenance (signed, bound to happyc0der/garmin-connect-api).
  - Clear OSI-approved license (MIT).
  - Actively maintained (last published 4 days ago).
  - Publishes a security disclosure policy (SECURITY.md).
- **Schema Quality & AI Usability**: 82/100
  - 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 5792 tokens (~148/item across 39 items; 38 tools + 1 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 9/100
  - Stability check failed: the tool surface changed between 0.2.3 and 0.3.1: 3 tool removals, 0 breaking changes, 0 additions.
- **Tool Coverage**: 100/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 100% of tool parameters carry a description.
  - Structured output schemas are declared (100% of tools); any adoption earns full credit.
- **Tool Safety**: 100/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - All 4 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation.
  - An AI judge read all 40 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a current MCP spec version (2026-07-28).

## Install

### How do I install the Garmin Connect MCP server?

Garmin Connect runs locally as a PyPI package, launched with uvx garmin-connect-api. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add happyc0der-garmin-connect-api -- uvx garmin-connect-api
```

### Cursor

```json
{
  "mcpServers": {
    "happyc0der-garmin-connect-api": {
      "command": "uvx",
      "args": [
        "garmin-connect-api"
      ]
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "happyc0der-garmin-connect-api": {
      "command": "uvx",
      "args": [
        "garmin-connect-api"
      ]
    }
  }
}
```

### Codex

```bash
codex mcp add happyc0der-garmin-connect-api -- uvx garmin-connect-api
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "happyc0der-garmin-connect-api": {
      "type": "local",
      "command": [
        "uvx",
        "garmin-connect-api"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add happyc0der-garmin-connect-api --command uvx --arg garmin-connect-api
```

### Hermes

```yaml
mcp_servers:
  happyc0der-garmin-connect-api:
    command: "uvx"
    args: ["garmin-connect-api"]
```

### Netclaw

```json
{
  "McpServers": {
    "happyc0der-garmin-connect-api": {
      "Transport": "stdio",
      "Command": "uvx",
      "Arguments": [
        "garmin-connect-api"
      ]
    }
  }
}
```

### Vellum

```bash
assistant mcp add happyc0der-garmin-connect-api -t stdio -c uvx -a garmin-connect-api
```

### Other

```json
{
  "mcpServers": {
    "happyc0der-garmin-connect-api": {
      "command": "uvx",
      "args": [
        "garmin-connect-api"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-10-23 (score 69, +1)

No change was recorded against any check on this day. Stability & Change Management went from 3 to 6.

### 2026-10-20 (score 68, +3)

- [security regression] Stability: unverified → fail
- [functional regression] Schema quality: pass → fail
- [functional improvement] Tool coverage: 30% → 100%
- [functional] Schema quality: good → excellent
- [functional] Package version: 0.2.3 → 0.3.1
- [functional] Package version: 0.2.3 → 0.3.0

### 2026-10-19 (score 65)

First indexed and scored.

## MCP tools (38)

### `garmin_status` (~60 tokens)

Garmin status

Check whether this server holds a Garmin login.

Call this first if anything fails. It reports whether tokens exist and
how old they are, without calling Garmin, so tokens Garmin has since
revoked still show as authenticated; any other call reveals that.

### `find_exercise` (~193 tokens)

Find exercise

Search Garmin's strength exercise catalogue by name, best match first; each
result has the display name, category and exercise (the variant).

Use it before create_strength_workout, log_strength_session or
edit_activity_sets whenever the Garmin pair is not known: those reject any
other name, and accept a result's category and exercise as they are.
Matching is by word starts ("lat pull" finds "Lat Pull-down"), with plurals
and common gym names (rdl, ohp, skull crusher) understood. To turn one
free-text name into exactly one exercise or nothing, use match_exercise.
Answered from a bundled copy of the catalogue: no Garmin request, no login
needed.

Input parameters:

- `limit` (integer): Most matches to return.
- `query` (string, required): Part or all of an exercise name, e.g. "bench", "lat pulldown", "rdl".

Output parameters:

- `result` (array)

### `recent_strength_sessions` (~180 tokens)

Recent strength sessions

List the most recent completed strength sessions, newest first, each with
its exercises, reps and weights and its total volume (reps times weight over
the working sets).

One call instead of list_activities plus get_activity_sets per session. It
takes no dates: for a period, use list_activities with activity_type
strength_training and start_date. Its sets carry no set_number, so read a
session with get_activity_sets before correcting it with edit_activity_sets.
One Garmin request per session plus one, and one more the first time in a
process (the activity types); a session whose sets cannot be read
carries sets_error, and a rate limit stops the call.

Input parameters:

- `count` (integer): How many sessions, newest first.
- `include_sets` (boolean): False for the list alone, in one request.

Output parameters:

- `result` (array)

### `week_summary` (~145 tokens)

Week summary

Summarise the seven days ending on end_day (default today, in the account's
timezone): each day's steps, resting heart rate, sleep hours and morning
readiness, plus the activities started in that window (up to 50).

For one day in full use get_day_summary; for one metric over longer ranges,
get_trend, which costs far fewer requests per day. This makes three Garmin
requests per day plus one for the activities, over 20 in all, so do not call
it repeatedly. A night's sleep belongs to the day it ended on.

Input parameters:

- `end_day`: Last day of the seven, YYYY-MM-DD. Default: today.

### `list_activities` (~139 tokens)

List activities

List activities, newest first.

Completed activities, not planned workouts. Filter by type and by date
range; end_date needs start_date. For strength sessions pass
activity_type=strength_training, then fetch the sets of one to see the
exercises, reps and weights.

Input parameters:

- `activity_type`: Garmin type key, e.g. "strength_training", "running", "cycling".
- `end_date`: Latest date, inclusive.
- `limit` (integer): Most activities to return.
- `start` (integer): Activities to skip, for paging. An offset, not a date.
- `start_date`: Earliest date, inclusive.

Output parameters:

- `result` (array)

### `get_activity` (~118 tokens)

Get activity

Return one activity by id: type, start time (UTC and local wall clock),
duration, distance, heart rate and calories, plus Garmin's full record under raw.

Ids come from list_activities, recent_strength_sessions or week_summary. It
has no per-set or per-lap detail: for a strength session's exercises, reps and
weights use get_activity_sets; for a run, ride or swim's laps and pacing,
get_activity_laps.

Input parameters:

- `activity_id` (integer, required): Garmin's activity id, from list_activities.

Output parameters:

- `activity_id` (integer)
- `activity_type`
- `average_hr`
- `calories`
- `distance_m`
- `duration_s`
- `elapsed_duration_s`
- `has_sets` (boolean): Whether this is a strength activity, the kind that carries per-set detail.
- `max_hr`
- `moving_duration_s`
- `name`
- `raw`
- `start_time`: Start instant, UTC.
- `start_time_local`: Wall-clock start where the activity happened, as YYYY-MM-DDTHH:MM:SS. Deliberately has no timezone: Garmin gives the time, not the zone.
- `steps`

### `delete_activity` (~96 tokens)

Delete activity

Permanently delete one activity from Garmin Connect. There is no undo.

Use it for an activity that should not exist, such as a session logged twice.
To fix a strength session's sets use edit_activity_sets, and to rename it,
update_activity; both keep the activity. A copy already shared to Strava or
another connected app stays there.

Input parameters:

- `activity_id` (integer, required): Garmin's activity id, from list_activities.

### `update_activity` (~117 tokens)

Update activity

Rename an activity, replace its description, or both; what is not given is left as it is.

For a strength session's exercises, reps or weights use edit_activity_sets; to
remove the activity, delete_activity. The new text replaces the old. Name and
description are separate Garmin requests, so if the second fails the first
still stands.

Input parameters:

- `activity_id` (integer, required): Garmin's activity id, from list_activities.
- `description`: The new description, replacing the old.
- `name`: The new title.

### `get_activity_sets` (~144 tokens)

Get activity sets

Return a strength activity's sets: exercise category and name, reps, weight in
kg and duration, with the total volume.

Each working set has a set_number (1, 2, 3...), which edit_activity_sets takes
to correct or delete sets; rests have none and are left out unless
include_rests. Only strength activities have sets: for runs, rides and swims
use get_activity_laps. For the last few sessions at once,
recent_strength_sessions saves a call per session.

Input parameters:

- `activity_id` (integer, required): Garmin's activity id, from list_activities.
- `include_rests` (boolean): Include the rest periods between sets.

Output parameters:

- `activity_id` (integer)
- `note`: Set when something about this answer needs saying.
- `sets` (array)
- `total_volume_kg` (number): Sum of reps times weight over the working sets. The usual definition of training volume. Sets with no weight recorded (bodyweight work) contribute nothing, which is the conventional treatment rather…

### `edit_activity_sets` (~169 tokens)

Edit activity sets

Correct a strength activity's sets: exercise, reps or weight, or delete sets.

Address sets by the set_number get_activity_sets shows; within one call,
every number means the set as numbered before it. Deleting renumbers the
sets after it, so repeating a delete removes a different set: use the
numbers this returns for any further edit. Name exercises with Garmin's
category and exercise_name; find_exercise looks them up. To rename the
activity or change its description, use update_activity.

Returns the sets as Garmin now holds them.

Input parameters:

- `activity_id` (integer, required): Garmin's activity id, from list_activities.
- `changes` (array): Edits, each applied to the working sets it lists.
- `delete` (array): Working set numbers to remove.

Output parameters:

- `activity_id` (integer)
- `note`: Set when something about this answer needs saying.
- `sets` (array)
- `total_volume_kg` (number): Sum of reps times weight over the working sets. The usual definition of training volume. Sets with no weight recorded (bodyweight work) contribute nothing, which is the conventional treatment rather…

### `get_activity_laps` (~104 tokens)

Get activity laps

Return the laps of a run or ride, or the pace per 100 m or yd of a pool swim,
with a summary: average pace or speed, first half against second half, and the
fastest and slowest lap or 100.

Answers "did my pace hold up?". Strength sessions have sets, not laps: use
get_activity_sets. Two Garmin requests.

Input parameters:

- `activity_id` (integer, required): Garmin's activity id, from list_activities.

Output parameters:

- `activity_id` (integer)
- `laps` (array)
- `pool_length`: In pool_unit.
- `pool_unit`: "m" or "yd".
- `segments`: Pool swims: pace per 100 of the pool's unit.
- `sport`
- `summary` (object)

### `get_day_summary` (~187 tokens)

Get day summary

Return one calendar day's numbers in a single answer: steps, calories, heart
rate, stress, body battery, last night's sleep, HRV, the morning readiness score
and the day's last weigh-in.

It does not list the day's activities: use list_activities with start_date and
end_date both set to the day. For one metric, its own tool (get_sleep,
get_hrv, get_stress, get_steps, get_training_readiness) gives more detail in
one Garmin request instead of five; for seven days use week_summary, for a
range get_trend. A part whose request fails is null and named under
unavailable; if every part fails, or Garmin rate limits, the call returns an
error instead. A night's sleep belongs to the day it ended on.

Input parameters:

- `day` (string, required): The calendar date, YYYY-MM-DD.

Output parameters:

- `active_calories`
- `average_stress`
- `body_battery_high`
- `body_battery_low`
- `day` (string)
- `distance_m`
- `floors_climbed`: Garmin reports floors fractionally, e.g. 2.25.
- `hrv_last_night_avg`
- `hrv_status`
- `max_hr`
- `min_hr`
- `moderate_intensity_minutes`
- `raw`
- `resting_hr`
- `sleep`
- `step_goal`
- `steps`
- `total_calories`
- `training_readiness`: The morning score Garmin sets on waking.
- `unavailable`: Parts Garmin did not answer for, and why: a part missing from this summary is listed here if it failed, rather than looking like a day with nothing recorded.
- `vigorous_intensity_minutes`: Garmin counts these double toward the weekly goal.
- `weight`

### `get_sleep` (~118 tokens)

Get sleep

Return one night's sleep: start and end, total, deep, light, REM and awake
time, sleep score and average respiration.

A night belongs to the day it ended on: last night is today's date. Null when
no sleep was recorded. The same summary is inside get_day_summary, which costs
five Garmin requests to this one's one; for sleep hours or score over many
nights use get_trend with metric sleep or sleep_score.

Input parameters:

- `day` (string, required): The date the night ended on; last night is today.

Output parameters:

- `result`

### `get_hrv` (~153 tokens)

Get HRV

Return one night's heart rate variability in milliseconds: the night's average
and five-minute high, the weekly average, Garmin's status (such as BALANCED,
UNBALANCED or LOW) and the baseline band.

A night belongs to the day it ended on: last night is today's date. With no
HRV for that night the reply is null, or its fields are. The nightly
average and status are also in get_day_summary; over weeks or months use
get_trend with metric hrv.
include_readings adds the five-minute readings.

Input parameters:

- `day` (string, required): The date the night ended on; last night is today.
- `include_readings` (boolean): Add the five-minute readings.

Output parameters:

- `result`

### `get_stress` (~141 tokens)

Get stress

Return one day's stress: the average and peak, and the minutes at rest, in
Garmin's low, medium and high bands, and unmeasured.

The day's average alone is also in get_day_summary; for the daily average over
weeks or months use get_trend with metric stress. With no stress data for
the day the reply is null, or its fields are. include_samples adds the
three-minute readings, which run to hundreds; leave it off unless the shape
of the day matters.

Input parameters:

- `day` (string, required): The calendar date, YYYY-MM-DD.
- `include_samples` (boolean): Add the three-minute readings.

Output parameters:

- `result`

### `get_body_battery` (~141 tokens)

Get body battery

Return Garmin's own body battery record for each day from start to end, unprocessed.

Use it for the detail behind a day's number. For one day's high and low,
get_day_summary already has them; for daily highs over weeks or months, with
an average and the change, use get_trend with metric body_battery. One Garmin
request whatever the range, so keep it to days. The reply is Garmin's JSON
as it comes: field names are Garmin's and nothing is converted.

Input parameters:

- `end`: Last day, inclusive. Left out, only the start day.
- `start` (string, required): First day, inclusive.

Output parameters:

- `result`

### `get_steps` (~120 tokens)

Get steps

Return one day's steps: the total, and 24 hourly totals by GMT hour, not local time.

For the total beside the day's other numbers use get_day_summary; for daily
totals over a range with an average and the change, get_trend with metric
steps. total is null, not 0, when Garmin did not answer. include_buckets adds
Garmin's quarter-hour buckets.

Input parameters:

- `day` (string, required): The calendar date, YYYY-MM-DD.
- `include_buckets` (boolean): Add Garmin's quarter-hour buckets.

Output parameters:

- `buckets`: Garmin's quarter-hour buckets, only when asked for.
- `day` (string)
- `hourly_gmt` (array): 24 values, hour 0 to 23 GMT.
- `total`: None when Garmin did not answer.

### `get_training_readiness` (~137 tokens)

Get training readiness

Return every training readiness entry Garmin recorded for one day: the score,
its level and the factors behind it.

Use it for why the score is what it is. Garmin recomputes readiness through
the day; the entry whose inputContext is AFTER_WAKEUP_RESET is the morning
score, which get_day_summary gives on its own and get_trend with metric
readiness follows over weeks or months. One Garmin request. The reply is
Garmin's JSON as it comes, usually a list of entries; null or empty when
nothing was recorded.

Input parameters:

- `day` (string, required): The calendar date, YYYY-MM-DD.

Output parameters:

- `result`

### `get_training_status` (~115 tokens)

Get training status

Return Garmin's training status for one date: the status, training load and
VO2 max context behind it, as Garmin's unprocessed JSON.

For VO2 max over time use get_trend with metric vo2max (running) or
vo2max_cycling; for whether to train today, get_training_readiness. One Garmin
request; null when Garmin reports nothing for the date. Field names are Garmin's
own.

Input parameters:

- `day` (string, required): The date to report on, YYYY-MM-DD.

Output parameters:

- `result`

### `get_personal_records` (~109 tokens)

Get personal records

List every personal record Garmin holds for the account, each named and in
units (e.g. "Fastest 1 km run"), newest first; records with no date come last.

A record set in an activity carries its activity_id, which get_activity
opens; step and goal-streak records have none. For
how a metric moved over a range use get_trend. Two Garmin requests the first
time (the record types are then kept for the process), one after.

Output parameters:

- `result` (array)

### `list_weigh_ins` (~112 tokens)

List weigh-ins

List every weigh-in from start to end (end defaults to start), oldest first,
with body composition and the sample_pk that delete_weigh_in takes.

Includes every reading on days with more than one. For weight or body fat as
one value a day with an average and the change, use get_trend with metric
weight or body_fat; get_day_summary gives the day's last weigh-in.

Input parameters:

- `end`: Last day, inclusive.
- `start` (string, required): First day, inclusive.

Output parameters:

- `result` (array)

### `add_weigh_in` (~158 tokens)

Add weigh-in

Record one body weight in Garmin Connect, in kg or lbs, with no body composition.

For a smart scale reading with fat, water, muscle or bone, use
add_body_composition instead. Without a timestamp it is filed now. A timestamp
without an offset is taken as local time in the Garmin account's timezone, and
one with an offset is moved into that timezone, so it lands on the right day.
To correct a reading, delete it with delete_weigh_in (list_weigh_ins gives its
sample_pk) rather than adding another.

Input parameters:

- `timestamp`: ISO 8601. Defaults to now.
- `unit` (string): kg or lbs.
- `weight` (number, required): In the given unit.

### `add_body_composition` (~307 tokens)

Add body composition

Record a weigh-in with body composition: fat, water, muscle, bone, BMI.

This is the way to bring a smart scale's full reading into Garmin. A plain
add_weigh_in carries weight only. Garmin reports every upload as
"processed"; an identical measurement sent again is still kept only once,
but a different reading at the same moment is kept as a second weigh-in.
To correct one, delete the wrong one first: list_weigh_ins gives its id.

Input parameters:

- `active_met_kcal`: Active metabolic rate, kcal a day.
- `basal_met_kcal`: Basal metabolic rate, kcal a day.
- `bmi`: Body mass index.
- `bone_mass_kg`: Bone mass, kg.
- `metabolic_age`: In years.
- `muscle_mass_kg`: Skeletal muscle mass, kg: Garmin shows this field under that name.
- `percent_fat`: Body fat, %.
- `percent_hydration`: Body water, %.
- `physique_rating`: The scale's physique rating, 1 to 9.
- `timestamp` (string, required): When it was measured. Without an offset, it is taken as local time in the Garmin account's own timezone.
- `visceral_fat_mass_kg`: Visceral fat mass, kg.
- `visceral_fat_rating`: The scale's visceral fat rating, 1 to 59.
- `weight_kg` (number, required): Body weight, kg.

### `delete_weigh_in` (~110 tokens)

Delete weigh-in

Permanently delete one weigh-in, identified by the sample_pk and day that
list_weigh_ins gives for it.

Use it to remove a wrong or duplicate reading; to correct one, delete it and
record the right value with add_weigh_in or add_body_composition. Nothing here
restores it, and day summaries and weight trends change with it.

Input parameters:

- `day` (string, required): The weigh-in's calendar date.
- `sample_pk` (integer, required): From list_weigh_ins.

### `get_trend` (~319 tokens)

Get trend

How one metric moved over a range, with average, extremes and change.

Metrics: steps, resting_hr, sleep (hours), sleep_score, hrv, readiness
(the morning score), stress (the day's average), body_battery (the day's
high), weight, body_fat, vo2max (running), vo2max_cycling. Up to two months
comes back daily, up to a year weekly, longer monthly. Ranges are limited
to two years. Steps, sleep, sleep_score, stress and body_battery are
fetched four weeks per Garmin request, so a year of one is about 14
requests; hrv, readiness and resting_hr a year per request; weight,
body_fat and VO2 max one request.

For one day use get_day_summary or that metric's own tool; for every
weigh-in rather than one a day, list_weigh_ins.

The summary gives days_with_data (periods_with_data for VO2 max, which
Garmin groups itself), the average, min and max of the days, the first and
last day, and change: the last whole period's average minus the first
whole period's, so a week the range cuts short does not count.

Input parameters:

- `aggregation`: day, week or month. Chosen from the range when left out.
- `end` (string, required): Last day, inclusive.
- `metric` (string, required): One of the metrics listed above.
- `start` (string, required): First day, inclusive.

Output parameters:

- `aggregation` (string)
- `end` (string)
- `metric` (string)
- `points` (array)
- `start` (string)
- `summary` (object)
- `unit` (string)

### `list_workouts` (~124 tokens)

List workouts

List the saved workouts in the account's library: plans to follow, with name,
sport and estimated duration but not their steps.

A saved workout is not on the calendar until scheduled: for what is planned on
which date use list_scheduled_workouts, and for sessions already done,
list_activities. For one workout's full step structure, get_workout. Paged:
start skips that many workouts.

Input parameters:

- `limit` (integer): Most workouts to return.
- `start` (integer): Workouts to skip, for paging. An offset, not a date.

Output parameters:

- `result` (array)

### `list_scheduled_workouts` (~140 tokens)

List scheduled workouts

List the workouts scheduled on the Garmin calendar in one month, with each
entry's schedule_id, workout_id and date.

Only planned workouts are returned: the completed activities, naps and badges
Garmin's calendar also holds are dropped, as are the neighbouring months' days
its grid spills into. For sessions done on those dates use list_activities;
for the saved workouts whether scheduled or not, list_workouts. The schedule_id
is what unschedule_workout takes.

Input parameters:

- `month` (integer, required): 1 for January to 12 for December.
- `year` (integer, required): Calendar year, e.g. 2026.

Output parameters:

- `result` (array)

### `get_workout` (~124 tokens)

Get workout

Return one saved workout by id: name, sport, description and estimated
duration, with Garmin's full definition (every step, target and repeat) under raw.

Use it to see what a plan asks for before scheduling or pushing it, or to check
what create_strength_workout or create_endurance_workout built. Ids come from
list_workouts or list_scheduled_workouts. A completed session is an activity:
read it with get_activity or get_activity_sets.

Input parameters:

- `workout_id` (integer, required): From list_workouts or list_scheduled_workouts.

Output parameters:

- `description`
- `estimated_duration_s`
- `name`
- `raw`
- `schedule_id`: Set when the workout was just scheduled: what unschedule_workout takes.
- `scheduled_on`: Set when the workout was just scheduled: its calendar day.
- `sport`
- `updated`
- `workout_id` (integer)

### `delete_workout` (~117 tokens)

Delete workout

Permanently delete a saved workout (a plan) from the account's workout library.

To take a workout off one date but keep it, use unschedule_workout with the
calendar entry's schedule_id instead. Completed sessions are activities, not
workouts: delete those with delete_activity. Nothing here restores a deleted
workout; get_workout first if its structure may be wanted again, since its
raw field holds the full definition.

Input parameters:

- `workout_id` (integer, required): From list_workouts or list_scheduled_workouts.

### `create_strength_workout` (~199 tokens)

Create strength workout

Create a strength workout to follow later.

Each exercise becomes a block of sets with a rest between them. Name
exercises with Garmin's category and exercise name, which find_exercise
gives; an unknown name is rejected with suggestions. For runs, rides and
swims use create_endurance_workout; to record a session already done,
log_strength_session.

Without schedule_date it is saved, not scheduled. With it, it is put on
the calendar in the same call, and the reply carries the schedule_id. If
scheduling fails the workout still exists and the error gives its id:
schedule that one rather than creating it again.

Input parameters:

- `description`: Notes shown with the workout.
- `exercises` (array, required): The exercises in order, each a block of sets.
- `name` (string, required): The workout's title.
- `schedule_date`: If set, the workout is scheduled on this date after creation.

Output parameters:

- `description`
- `estimated_duration_s`
- `name`
- `raw`
- `schedule_id`: Set when the workout was just scheduled: what unschedule_workout takes.
- `scheduled_on`: Set when the workout was just scheduled: its calendar day.
- `sport`
- `updated`
- `workout_id` (integer)

### `create_endurance_workout` (~242 tokens)

Create endurance workout

Create a run, ride or pool swim workout to follow on the watch.

Steps end on a time, a distance or the lap button, and can repeat. Runs
and rides take a target: pace per km or mile, speed, heart-rate zone or
range, power zone or watts, or cadence. Swims take a stroke, equipment and
a pool length; distances must be whole lengths. Give schedule_date to put
it on the calendar too; the reply then carries the schedule_id, and if
scheduling fails the error gives the new workout's id. For strength
workouts use create_strength_workout.

Input parameters:

- `description`: Notes shown with the workout.
- `name` (string, required): The workout's title.
- `pool_length`: Swims only.
- `pool_unit` (string): The pool length's unit, m or yd; swims only.
- `schedule_date`: If set, the workout is scheduled on this date after creation.
- `sport` (string, required): running, cycling, or swimming in a pool.
- `steps` (array, required): Steps in order; a repeat holds steps to run several times.

Output parameters:

- `description`
- `estimated_duration_s`
- `name`
- `raw`
- `schedule_id`: Set when the workout was just scheduled: what unschedule_workout takes.
- `scheduled_on`: Set when the workout was just scheduled: its calendar day.
- `sport`
- `updated`
- `workout_id` (integer)

### `create_workout_from_dto` (~70 tokens)

Create workout from DTO

Create a workout from a full Garmin DTO, for shapes the builder omits.

Sent to Garmin as given: exercise names are not checked against the
catalogue here, so prefer create_strength_workout where it can express it.

Input parameters:

- `workout` (object, required): A complete Garmin workout DTO.

Output parameters:

- `description`
- `estimated_duration_s`
- `name`
- `raw`
- `schedule_id`: Set when the workout was just scheduled: what unschedule_workout takes.
- `scheduled_on`: Set when the workout was just scheduled: its calendar day.
- `sport`
- `updated`
- `workout_id` (integer)

### `schedule_workout` (~98 tokens)

Schedule workout

Put a saved workout on the calendar for a date.

Returns the calendar entry. Its schedule_id, not the workout id, is what
unschedule_workout takes. Scheduling again adds a second entry; to move a
workout to another day, unschedule the old entry first.

Input parameters:

- `day` (string, required): The date to schedule it on.
- `workout_id` (integer, required): From list_workouts or list_scheduled_workouts.

Output parameters:

- `day`
- `name`
- `raw`
- `schedule_id`: The calendar entry's id, which unschedule takes.
- `sport`
- `workout_id`

### `unschedule_workout` (~106 tokens)

Unschedule workout

Remove one entry from the Garmin calendar; the saved workout itself is kept.

Takes the schedule_id from list_scheduled_workouts, schedule_workout, or a
create_* call made with schedule_date; not the workout_id. To move a workout,
unschedule it and schedule_workout it on the new date. To delete the workout
itself, use delete_workout.

Input parameters:

- `schedule_id` (integer, required): The calendar entry's id, not the workout's.

### `push_workout_to_device` (~157 tokens)

Push workout to device

Queue a saved workout for one of the account's watches; it arrives at the
watch's next sync, not immediately.

Without device_id it goes to the device last used, if it is on the account's
list, else the primary activity tracker, else the only device; when none of
those settles it, the call is refused and asks for a device_id. list_devices
gives the ids, and the reply names the device chosen. Pushing does
not schedule it: to put it on a date, use schedule_workout.

Input parameters:

- `device_id`: From list_devices. Left out, chosen as described.
- `workout_id` (integer, required): From list_workouts or list_scheduled_workouts.

### `list_devices` (~80 tokens)

List devices

List the Garmin devices paired with the account: device_id, name, product, and
which one is the primary activity tracker.

Use it to pick the device_id push_workout_to_device takes. A push without one
prefers the device last used, which this does not show, so pass device_id
when the device matters. One Garmin request.

Output parameters:

- `result` (array)

### `log_strength_session` (~244 tokens)

Log strength session

Log a completed strength session to Garmin Connect as an activity.

Takes the session as it happened, exercise by exercise with the reps and
weight of every set, encodes it as a FIT activity file and uploads it. It
appears in Garmin Connect as a Strength Training activity with the sets
intact, named after the session, with its notes as the description.

If the account shares to Strava or another app, the activity may be posted
there too, publicly, and deleting it from Garmin does not delete it there.

Name exercises with Garmin's category and exercise_name pair, which
find_exercise gives. Logging the identical session again is
reported as a duplicate and changes nothing; logging it with any set
changed creates a second activity, so correct one with edit_activity_sets.

Input parameters:

- `exercises` (array, required): The exercises in the order done, each with its sets.
- `name` (string): The activity's title in Garmin Connect.
- `notes`: Saved as the activity's description.
- `start_time` (string, required): When the session started. Without an offset, it is taken as local time in the Garmin account's own timezone.

### `match_exercise` (~150 tokens)

Match exercise

Map one free-text exercise name, such as one exported from another app, onto
exactly one catalogue exercise, or return null when no single match is certain.

Use it when converting names from another source, where a wrong guess would
log the wrong lift. To browse candidates and choose, use find_exercise, which
returns a ranked list. Only a certain match is returned: the exact or
normalised name, the same words up to a typo, plural or hyphenation, or a
search with one hit that adds at most an implement (barbell, dumbbell,
kettlebell). No Garmin request.

Input parameters:

- `name` (string, required): A free-text exercise name.

Output parameters:

- `result`

## Diagnostics

Captured diagnostic sections: Provenance, Install scripts, Dependencies. The full working is on the page: https://verifymcp.io/servers/happyc0der-garmin-connect-api/garmin-connect-api#diagnostics

## Score history

- 2026-10-24: 69
- 2026-10-23: 69
- 2026-10-22: 68
- 2026-10-21: 68
- 2026-10-20: 68
- 2026-10-19: 65

## Common questions

### What is the Garmin Connect MCP server?

Garmin Connect is an MCP server listed in the public MCP registry as io.github.happyc0der/garmin-connect-api. Read and write Garmin Connect: activities, strength sets, health data, workouts and weigh-ins. This page covers its PyPI package (garmin-connect-api).

### Is the Garmin Connect MCP server safe to use?

Garmin Connect scores 69 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 24 October 2026. Its build provenance is signed and verified. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the Garmin Connect MCP server expose?

Garmin Connect exposes 38 tools: garmin_status, find_exercise, recent_strength_sessions, week_summary, list_activities, and 33 more. Their descriptions and schemas cost roughly 5,543 tokens of context every time the server is loaded.

### Is the Garmin Connect MCP server still maintained?

Garmin Connect is still listed as active in the MCP registry. We last reached this channel on 24 October 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

### What licence is the Garmin Connect MCP server under?

Garmin Connect declares the MIT licence, which is OSI-approved. That covers the source only, and says nothing about the cost of any service it calls.

## Links

- PyPI project: https://pypi.org/project/garmin-connect-api/
- Socket report: https://socket.dev/pypi/package/garmin-connect-api
- Repository: https://github.com/happyc0der/garmin-connect-api
- Changelog RSS feed: https://verifymcp.io/servers/happyc0der-garmin-connect-api/garmin-connect-api.xml
- Changelog JSON feed: https://verifymcp.io/servers/happyc0der-garmin-connect-api/garmin-connect-api.json
- HTML version of this page: https://verifymcp.io/servers/happyc0der-garmin-connect-api/garmin-connect-api
