Skip to main content

MCP server for Garmin Connect data and activity analysis

Project description

garmin-mcp

PyPI version Python 3.12+ pipeline status coverage report

Read-only MCP server for Garmin Connect: exposes live data tools plus analyze_* tools that cache activity data locally and compute aggregated statistics. Built on the unofficial garminconnect library — see "Unofficial API — risks & rate limits" below. See Privacy for what's read, cached and stored locally.

Tools

Live data

Tool Description
list_activities Recent activities (newest first, pageable), enriched summary metrics
list_activities_by_date Activities in a date range, optional type filter
get_activity_details Deep per-activity data: summary, HR time-in-zones, exercise sets, splits, laps (real per-lap distance/speed/HR/elevation), weather, gear
get_training_readiness Training Readiness score + factors (sleep, recovery, load balance/ACWR, stress, HRV)
get_training_status VO2max, training-status phrase, acute/chronic load + ACWR, load focus, heat acclimation
get_fitness_metrics VO2max (running + cycling), fitness age (with chronological and achievable fitness age)
get_race_predictions Predicted finish times (seconds) for 5K / 10K / half marathon / marathon
get_personal_records All-time personal records: fastest times, longest distances/durations, most steps
get_daily_stats Daily summary: steps, calories, resting HR, stress, body battery
get_sleep Sleep summary: total/deep/light/REM/awake, SpO2, respiration
get_hrv HRV summary: last-night average, status, baseline
get_spo2 Pulse-ox (SpO2): on-demand/sleep average, lowest, latest reading, 7-day average
get_respiration Respiration rate: waking/sleep average, lowest/highest breaths per minute
get_body_composition Weigh-in history in a date range: weight, body-fat %, muscle mass, BMI

Analysis (cache + aggregates)

Tool Description
analyze_backfill Backfill the last N days into the local cache; rebuild aggregates. Returns counts
analyze_day Sync one day with deep details; rebuild aggregates; return that day's summary
analyze_brief Refresh today + return the morning brief (yesterday, today's recovery/readiness, 7/14/30-day trends); appends to the dated brief history

list_activities* returns enriched fields per activity: training effect (aerobic/ anaerobic), training load, intensity minutes, HR time-in-zones, moving/elapsed time, speed, elevation, respiration, body-battery delta, lap count and location.

The analyze_* tools cache raw daily data under ~/.garmin-activity/days/ and write aggregated stats (daily/weekly summaries, morning brief + dated history) to GARMIN_OUTPUT_DIR (defaults to ~/.garmin-mcp/output).

Requirements

  • Python >= 3.12 (managed by uv/uvx)
  • A Garmin Connect account
  • Any OS — no platform-specific credential storage

Unofficial API — risks & rate limits

Not affiliated with, endorsed by, or supported by Garmin Ltd.

Garmin has no official API for individuals. This server talks to Garmin Connect through the reverse-engineered garminconnect library, which drives the same endpoints the website and mobile app use. That has consequences worth understanding before you rely on it:

  • It can break without warning. Garmin can change or retire these endpoints at any time; when they do, tools may start returning errors until garminconnect (and then this server) is updated. Pin versions if you need stability, and expect occasional breakage.
  • Terms of Service. Automated access to Garmin Connect is not covered by an official individual-developer agreement. Use this only against your own account, for personal use, and review Garmin's Terms of Service — you are responsible for how you use it. Abusive or high-volume use risks account rate-limiting or suspension.
  • Built-in pacing. Bulk history sync (analyze_backfill) sleeps ~0.3s between days to stay gentle on Garmin's servers. Individual live-data tools issue only a few requests per call.
  • Keep request volume modest. Prefer incremental syncs (analyze_day / analyze_brief) over repeatedly backfilling large ranges. Backfill once for history, then refresh a day at a time. Avoid tight loops or scheduling many concurrent syncs — no official rate limit is published, so err on the side of fewer, spaced-out requests.

Setup

1. One-time interactive login

uvx --from rogo-garmin-mcp rogo-garmin-mcp-login

Prompts for your Garmin email, password and the emailed MFA code. Your credentials are used only to obtain OAuth tokens and are never stored; only the resumable tokens are written to ~/.garmin-mcp/tokens (0700). The headless server resumes from those tokens and refreshes them silently, so it never needs your email or password again. Re-run this login if the tokens eventually expire.

2. Add to Claude Code (~/.claude/settings.json)

Installed from PyPI (recommended):

"mcpServers": {
  "garmin": {
    "type": "stdio",
    "command": "uvx",
    "args": ["rogo-garmin-mcp"]
  }
}

Or from a source checkout: uv sync, then use "command": "uv", "args": ["run", "--project", "/path/to/garmin-mcp", "rogo-garmin-mcp"].

3. Add to Claude Desktop (claude_desktop_config.json)

Edit (or create) the config file — macOS: ~/Library/Application Support/Claude/claude_desktop_config.json; Windows: %APPDATA%\Claude\claude_desktop_config.json:

{
  "mcpServers": {
    "garmin": {
      "command": "uvx",
      "args": ["rogo-garmin-mcp"]
    }
  }
}

Restart Claude Desktop to pick up the new server.

Client compatibility

This is a standard stdio MCP server launched with uvx rogo-garmin-mcp, so any MCP client that supports the stdio transport should work. Only the Claude Code and Claude Desktop setups are documented and exercised here; the others use the same command and are expected to work but are not separately verified.

Client Status Notes
Claude Code Documented / used Config shown in Setup (~/.claude/settings.json), or claude mcp add.
Claude Desktop Documented / used Config shown in Setup.
Cursor Expected to work Register as an MCP server (stdio): command uvx, args ["rogo-garmin-mcp"].
Antigravity Expected to work Register as an MCP server (stdio): command uvx, args ["rogo-garmin-mcp"].
Generic MCP clients Expected to work Any client speaking the MCP stdio transport; point it at uvx rogo-garmin-mcp.

What you can ask

Once configured, you talk to your agent in plain language and it picks the right tool. A few prompts that map cleanly onto the tools above:

  • "Summarize my last 5 runs."list_activities / list_activities_by_date
  • "Am I recovered enough to train hard today?"get_training_readiness
  • "What's my training status, VO2max and fitness age?"get_training_status / get_fitness_metrics
  • "How did I sleep last night, and what was my HRV?"get_sleep / get_hrv
  • "What are my all-time personal records and predicted race times?"get_personal_records / get_race_predictions
  • "Backfill the last 90 days, then give me my morning brief."analyze_backfill then analyze_brief

Dates: all date parameters use YYYY-MM-DD. For single-date tools (get_training_readiness, get_sleep, get_hrv, analyze_brief, etc.) it's optional — omit it and it defaults to today. Tools that take a date range or a specific day (list_activities_by_date, get_body_composition, analyze_day) require the date(s) to be passed explicitly.

Example output

Tools return plain JSON. The samples below are trimmed for illustration — real payloads carry more fields (see the tables above). The health-summary tools (get_daily_stats, get_sleep, get_hrv) additionally omit any null fields.

list_activities(limit=2):

[
  {
    "id": 12345678901,
    "name": "Morning Run",
    "type": "running",
    "start": "2026-07-06 19:12:44",
    "duration_min": 41.8,
    "distance_km": 7.35,
    "calories": 512,
    "avg_hr": 152,
    "max_hr": 176,
    "aerobic_te": 3.4,
    "anaerobic_te": 0.6,
    "te_label": "TEMPO",
    "training_load": 118,
    "location": "Riverside"
  },
  {
    "id": 12344556677,
    "name": "Strength Training",
    "type": "strength_training",
    "start": "2026-07-05 08:03:10",
    "duration_min": 24.5,
    "calories": 268,
    "avg_hr": 138,
    "vigorous_intensity_min": 12,
    "training_load": 74
  }
]

get_training_readiness():

{
  "date": "2026-07-07",
  "score": 78,
  "level": "READY",
  "feedback": "READY_TO_TRAIN",
  "recovery_time_min": 180,
  "acute_load": 302,
  "hrv_weekly_avg": 31,
  "factors": {
    "sleep": { "pct": 90, "rating": "GOOD" },
    "recovery_time": { "pct": 100, "rating": "GOOD" },
    "load_balance": { "pct": 65, "rating": "MODERATE" },
    "hrv": { "pct": 80, "rating": "GOOD" }
  }
}

get_daily_stats():

{
  "calendarDate": "2026-07-07",
  "totalSteps": 9432,
  "totalDistanceMeters": 7120,
  "totalKilocalories": 2568,
  "restingHeartRate": 52,
  "averageStressLevel": 34,
  "bodyBatteryMostRecentValue": 61
}

Troubleshooting

Symptom Fix
Error: "the saved session is invalid or expired" Re-run the one-time login: uvx --from rogo-garmin-mcp rogo-garmin-mcp-login (see Setup).
Error: "too many requests" / rate-limited Garmin is rate-limiting the account. Wait a while, then prefer analyze_day / analyze_brief over repeated large analyze_backfill calls (see "Unofficial API — risks & rate limits" above).
Error: "could not reach Garmin Connect" Network/connectivity issue — check your connection and retry.
A metric comes back null instead of a value Expected, not an error — Garmin has nothing recorded for it. VO2max (get_fitness_metrics, get_training_status) and race predictions (get_race_predictions) need a compatible watch and enough recorded runs; HRV (get_hrv) and the HRV factor in get_training_readiness need overnight wear; SpO2, respiration and body-battery fields are device-dependent and may simply be absent on some hardware or days.
get_body_composition returns an empty list No weigh-ins recorded in that date range — expected, not an error.

Privacy

Your data stays on your machine. This server only ever reads from Garmin Connect; the analyze_* tools cache what they fetch locally so trends can be computed offline. Nothing is uploaded anywhere, and there is no telemetry.

What is stored, and where:

Path Contents
~/.garmin-mcp/tokens (dir, 0700) Resumable Garmin OAuth tokens. Your email/password are never stored — only these tokens.
~/.garmin-activity/days/YYYY-MM-DD.json Raw per-day cache: activities, sleep, HRV, recovery, stress, readiness.
$GARMIN_OUTPUT_DIR (default ~/.garmin-mcp/output) Aggregated stats: daily/weekly summaries, morning brief and dated brief history.

Set GARMIN_OUTPUT_DIR to relocate the aggregate output (e.g. to a directory your agent reads from).

Retention: files are kept until you delete them. The cache only grows as you backfill or analyze more days; it is never pruned automatically.

How to wipe everything:

rm -rf ~/.garmin-activity ~/.garmin-mcp
# and, if you set a custom output dir:
rm -rf "$GARMIN_OUTPUT_DIR"

Deleting ~/.garmin-mcp/tokens also signs the server out; re-run the interactive login to use it again.

Development

uv run pytest                         # run tests with coverage
uv run pytest --cov-report=html       # HTML coverage report

License

Apache-2.0

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rogo_garmin_mcp-0.8.0.tar.gz (124.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

rogo_garmin_mcp-0.8.0-py3-none-any.whl (31.3 kB view details)

Uploaded Python 3

File details

Details for the file rogo_garmin_mcp-0.8.0.tar.gz.

File metadata

  • Download URL: rogo_garmin_mcp-0.8.0.tar.gz
  • Upload date:
  • Size: 124.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for rogo_garmin_mcp-0.8.0.tar.gz
Algorithm Hash digest
SHA256 004bec38c4dae2fb0bf7ab357dbaa1867a9a825a748f4c454e3eff3bc7682396
MD5 0eb1572c4c780077a55165c31a431c44
BLAKE2b-256 73a1ac8f0603f900284e40fb2b87a6eeed272009152c98091f72643c7cbb3bf9

See more details on using hashes here.

File details

Details for the file rogo_garmin_mcp-0.8.0-py3-none-any.whl.

File metadata

  • Download URL: rogo_garmin_mcp-0.8.0-py3-none-any.whl
  • Upload date:
  • Size: 31.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for rogo_garmin_mcp-0.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0179c568ddd3d36ffe7705dcf84be55123dde280cbd1ffa2ca37da286fd93c55
MD5 15ca8d10fa5a0bcca068ca2f8a09881c
BLAKE2b-256 5eeb66341f891cdda499788036e1885cad6c23c78d1110d3996889866640772d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page