danielsuri

garmin-unofficial-mcp

Community danielsuri
Updated

garmin-unofficial-mcp

garmin-unofficial-mcp

CIPython 3.12+License: MIT

A local, single-user, read-only MCP serverthat gives Claude access to your own Garmin health and training data. Itexposes composite tools — health snapshot, training status, recent runs, rundetail, body metrics and training analysis — so Claude can ground recoveryadvice and workout recommendations in your actual sleep, HRV, training loadand run history instead of guessing. Everything Garmin returns is cachedday-by-day in a local SQLite database, so repeated questions don't repeatedlyhit Garmin's servers.

Before you start

This uses the unofficial garminconnect library, which signs in with yourGarmin password and MFA code — not OAuth. There is no scoped, read-onlyGarmin grant available to third-party apps today, so the token this stores isfull-account access. It is stored only on your own machine.

The server never writes to your Garmin account: no set_, add_, delete_,upload_, import_, create_ or remove_ call exists anywhere in it,deliberately.

See docs/privacy.md for exactly what is stored and where.

Requirements

  • Python 3.12 or newer
  • A Garmin Connect account
  • Optionally, the claude CLI, so install.sh can register the server for you

Install

git clone https://github.com/danielsuri/garmin-unofficial-mcp.git
cd garmin-unofficial-mcp
./install.sh
.venv/bin/garmin-unofficial-mcp login [email protected]
.venv/bin/garmin-unofficial-mcp doctor

login prompts for your password and, if enabled, an MFA code. It needs a realterminal — getpass fails without a TTY. doctor then checks that the storedtokens are accepted, that a live call works, and that the cache directory iswritable.

install.sh creates .venv, installs the package into it, and finds theclaude CLI to register the server at user scope if found. It's idempotent —safe to re-run any time; it never clobbers an existing venv or registration itdoesn't own. Pass --dev to also install the test extras, --dry-run topreview every action without doing it, or --help for the full rundown.

On success, tokens are written to ~/.garminconnect. They're good for roughlya year; after that (or if Garmin invalidates them) re-run login.

Check the install

.venv/bin/garmin-unofficial-mcp doctor

doctor checks three things: that the stored tokens are accepted, that a livecall to Garmin actually succeeds, and that the cache directory(~/.garmin-unofficial-mcp/) is writable. Healthy output looks like:

auth: OK (tokenstore accepted)
live call: OK (userProfileId=12345678)
cache path: OK (/Users/you/.garmin-unofficial-mcp/cache.db)

Any FAIL line points at what to fix — usually re-running login if authfails, or a permissions problem if the cache path fails.

Register with Claude Code

install.sh does this automatically when it can find the claude CLI. To doit by hand instead (or if it couldn't find claude):

claude mcp add -s user garmin-unofficial -- <your/install/dir>/garmin-unofficial-mcp/.venv/bin/garmin-unofficial-mcp serve

This runs the server over stdio. Once registered, a fresh Claude Code sessionwill have the tools below available — MCP servers are loaded at startup, sothe session you register from won't see them.

-s user matters: without it claude mcp add defaults to local scope, andthe tools appear only when you're working inside this repo. User scope makesthem available in every project, which is what you want for a personal healthserver.

Note the registration is an absolute path into this repo's venv, so movingor deleting the repo breaks it (re-run install.sh, or the command above,after moving it). Tokens (~/.garminconnect) and the cache(~/.garmin-unofficial-mcp/cache.db) live outside the repo and are unaffected by scope.

The tools

  • get_health_snapshot(days=7) — sleep (total/deep/light/REM/awakeminutes, sleep score), HRV, body battery (the day's true high and low), restingheart rate, and stress over the window, plus rolling baselines. Use this tojudge how recovered you are.
  • get_training_status(weeks=4) — acute and chronic training load, theacute:chronic load ratio, VO2max, and training readiness score per day. Usethis to judge whether training load is ramping safely.
  • get_recent_runs(n=10) — your last n runs with pace (average,moving, grade-adjusted, best), heart-rate zones, training effect andtraining load, VO2max, fastest splits, PR flag, calories, body-batterycost and elevation, plus weekly mileage rollups and race-time predictions.Use this to spot trends and prescribe a specific run.
  • get_run_detail(activity_id=None) — one run in full: everythingget_recent_runs reports for that run, plus running dynamics (groundcontact time and balance, vertical oscillation and ratio, stride length),power (average/normalized/max watts and power zones), respiration(average/max/min breaths per minute), min/max temperature, lap count,step count, activity name, and run/walk/idle time detection(run_walk: seconds spent running vs. walking vs. stopped, from Garmin'styped splits). Omit activity_id for the most recent run found in thelast 56 days. Use this to deep-dive a specific run. Costs one extraGarmin API call beyond get_recent_runs (typed splits, for run/walkdetection) — each of the other fields comes from the same activity recordget_recent_runs already fetches.
  • get_body_metrics(days=30) — weight, body fat, steps, calories, floorsclimbed, and intensity minutes, with simple weight and step trends.
  • get_training_analysis(weeks=8) — the marathon-block view: within-runform indicators (efficiency factor, aerobic decoupling, ground-contact-timedrift, duty factor, split pattern) for every run in the window, plusacross-block trends (weekly mileage, ramp rate with the base it was computedfrom, HR-zone polarization, efficiency and decoupling trend, recoverycoupling, VO2max trend). It computes; it does not advise. Costs two extraGarmin API calls per run it hasn't seen before (laps and typed splits),both cached permanently.

Arguments are clamped to a range the server can actually serve — days to1–365, weeks to 1–52, n to 1–100, activity_id to a non-negative64-bit value — so an out-of-range value narrows or widens to the nearestbound (or, for an activity_id that matches nothing, yields a cleanall-null result) rather than failing.

Every tool returns a partial: true flag when it couldn't be certain all ofits data is fresh or complete — see Troubleshooting below.

Testing

.venv/bin/pytest

The suite covers the MCP tools, cache, fetcher and assemblers with fakeGarmin clients — no network, no real account required.

Troubleshooting

  • Auth errors / "Garmin rejected the stored tokens" — your tokens expiredor were revoked. Re-run garmin-unofficial-mcp login [email protected] from a realterminal.
  • partial: true in a tool's response — Garmin rate-limited a request ora connection failed transiently. The tool still returns whatever it hascached; treat the numbers as possibly incomplete or stale and try againlater rather than assuming Garmin has no data.
  • Weight fields are null — this is normal on any day without a weigh-in.weight_kg, body_fat_pct, and the weight trend fields inget_body_metrics only populate on days you actually stepped on aconnected scale.

Licence

MIT — see LICENSE.

MCP Server · Populars

MCP Server · New

    Bevel-Software

    hexis

    Git-backed skills, tools & context for AI agents

    Community Bevel-Software
    jonashertner

    OpenCaseLaw

    Open Swiss legal corpus + MCP server: 1M+ court decisions (1875–today), 21k laws, 10M-edge citation graph, 42 MCP tools. CC0 data, MIT code. Live at mcp.opencaselaw.ch

    Community jonashertner
    SystemCraftsman

    Strimzi Kafka CLI

    Command Line Interface for the Strimzi Kafka Operator

    Community SystemCraftsman
    DROOdotFOO

    Raxol

    Write one app, render it to a terminal, a browser, or as agent tools. The terminal for your Gundam.

    Community DROOdotFOO
    morluto

    REA: Reverse Engineer Anything

    Reverse engineer anything with agents, from app behavior down to native binaries.

    Community morluto