Skip to content

xichen-de/garmin-owl

v0.2.5MIT

Local, read-only access to Garmin health and training data.

garmin-owl

garmin-owl lets Claude Desktop, ChatGPT Desktop, or another MCP client answer questions about your Garmin health and training data: sleep, HRV, recovery, activities, training load, and more. It runs entirely on your computer, can only read from Garmin Connect, and never uploads a separate copy of your data anywhere.

It works on macOS and Ubuntu Linux and uses the unofficial python-garminconnect client, so a change on Garmin's side can occasionally break a read until garmin-owl is updated.

Want to change the code or build a release? See CONTRIBUTING.md.

What you need

  • macOS or Ubuntu Linux
  • Claude Desktop with Extensions support, ChatGPT Desktop, or another MCP client
  • A Garmin Connect account with data from a Garmin device
  • uv and Git. uv installs Python 3.12+ for you, so you never need to set up or activate an environment.

Setup

1. Sign in to Garmin (once)

In a terminal, run this as the same OS user that runs Claude Desktop:

git clone https://github.com/xichen-de/garmin-owl.git
cd garmin-owl
uv sync --locked
uv run garmin-owl-auth

You'll be asked for your Garmin email, password, and MFA code if your account uses one. Your password is not stored: only a reusable session token is saved, to ~/.garminconnect (or to the directory in the GARMINTOKENS environment variable, if you set it). Treat that directory like a password.

Re-run uv run garmin-owl-auth any time to check your sign-in. It only asks for credentials again if the saved session no longer works.

Why does this ask for my Garmin password?

Garmin has no public API for personal data, so the unofficial client signs in the way the Garmin Connect website does. Your credentials are used once, in the terminal, to obtain session tokens. They never enter an MCP conversation, and Claude never sees them.

2. Install the Claude Desktop extension

Using ChatGPT Desktop instead? Skip to ChatGPT Desktop.

  1. Download the latest .mcpb file from Releases.
  2. Open Claude Desktop → Settings → Extensions and drag the .mcpb file in.
  3. Enable the extension, restart Claude Desktop if prompted, and start a new chat.

The extension reuses the session saved in step 1. It contains the program only, never credentials, tokens, or health data.

Using a different MCP client

For clients that accept mcpServers JSON, add the entry below. Replace the two placeholder paths with the output of command -v uv and of pwd run inside your garmin-owl folder. Where the config file lives depends on your client.

{
  "mcpServers": {
    "garmin-owl": {
      "command": "/absolute/path/to/uv",
      "args": ["--directory", "/absolute/path/to/garmin-owl", "run", "garmin-owl"]
    }
  }
}

Use absolute paths, because desktop apps often don't inherit your terminal's PATH. Running uv run garmin-owl by hand just waits silently for an MCP client, so it isn't an interactive program.

3. Try it

Ask Claude things like:

  • "Summarize my recovery today."
  • "How did I sleep last night?"
  • "Compare my last two runs."
  • "Show my 28-day recovery trend."
  • "What did my training week look like around 3 March 2025?"
  • "How has my weight changed over the last year?"
  • "How does my cycle day line up with my recovery?"

ChatGPT Desktop

garmin-owl can also run as a local plugin in ChatGPT Desktop. It is the same Python server, packaged for ChatGPT's local plugin marketplace. It does not work in ChatGPT on the web or on mobile, and a URL-based developer-mode connector can't run it.

ChatGPT requirements

  • A current ChatGPT Desktop build on macOS or Ubuntu with Work mode or Codex, local plugin marketplaces, and stdio MCP plugins. Availability can vary by app version, account, and workspace policy.
  • The codex CLI with marketplace support. Check with codex plugin marketplace add --help.
  • Git and uv, as in What you need.

See OpenAI's local plugin documentation and the Agent Plugins 1.0.0 specification for background.

Install

  1. In your garmin-owl folder, as the same OS user that runs ChatGPT, sign in (skip if you did step 1 already) and build the local marketplace:

    uv sync --locked
    uv run garmin-owl-auth
    uv run --locked python scripts/build-chatgpt-plugin.py
    
  2. Register the marketplace (once):

    codex plugin marketplace add ./dist/chatgpt-marketplace
    
  3. Restart ChatGPT Desktop, open the Plugins Directory, choose Garmin Owl Local, then install and enable Garmin Owl.

  4. Start a new local Work/Codex chat and ask, for example, "Summarize my recovery today."

The first launch needs internet access: it installs the locked Python dependencies into the plugin's private data folder (PLUGIN_DATA/venv). After that it runs offline apart from reading Garmin.

What does the build script put in the plugin?

scripts/build-chatgpt-plugin.py writes dist/chatgpt-marketplace/, containing the plugin under plugins/garmin-owl and a catalog at .agents/plugins/marketplace.json that uses only relative paths. It copies an explicit list of program files and never your .venv, tokens, credentials, SQLite files, or other checkout state. Register this generated folder, not the checkout itself.

Updating the ChatGPT plugin

ChatGPT runs its own installed copy of the plugin, so after git pull:

  1. Rebuild with uv run --locked python scripts/build-chatgpt-plugin.py.
  2. Restart ChatGPT Desktop.
  3. Uninstall Garmin Owl and install it again from Garmin Owl Local.

codex plugin marketplace upgrade only refreshes Git marketplaces, so it doesn't apply here.

Sign-in, data, and settings

  • Sign in only with uv run garmin-owl-auth in your terminal. Never type your Garmin password into ChatGPT.
  • The plugin reuses ~/.garminconnect and the same local cache as the terminal commands. Both stay outside the plugin package.
  • Tool results are sent to ChatGPT as part of your conversation, as with any MCP client.
  • GARMINTOKENS and GARMIN_OWL_DB exported in your shell don't reach GUI apps such as ChatGPT. The plugin uses the default locations unless those variables are set in ChatGPT's own environment.

ChatGPT troubleshooting

  • "uv was not found": desktop apps often don't inherit your shell's PATH. The launcher looks for uv on PATH and in ~/.local/bin, ~/.cargo/bin, /opt/homebrew/bin, /home/linuxbrew/.linuxbrew/bin, /usr/local/bin, /usr/bin, and /snap/bin. Install uv with its official installer (or brew install uv on macOS), or link a custom install into ~/.local/bin (ln -s "$(command -v uv)" ~/.local/bin/uv), then restart ChatGPT.
  • Garmin Owl Local isn't listed: check codex plugin marketplace list, restart ChatGPT, and confirm your build and account support local marketplaces and stdio plugins.
  • Old behaviour after updating: reinstall the plugin as described in Updating the ChatGPT plugin.

What it can tell you

Your assistant chooses among 19 read-only tools. None of them can change anything in your Garmin account.

One day at a time

ToolAnswers
get_daily_summarySteps, activity/sedentary time, goals, calories, HR, stress, respiration, SpO2, Body Battery
get_sleepSleep score/need, stages, timing, sleeping HR/stress, respiration, SpO2, Body Battery change, skin-temperature deviation
get_hrvHRV status, nightly and weekly averages
get_body_batteryCharged, drained, and start/end/highest/lowest levels
get_stressAverage and max stress, plus durations by intensity band
get_training_readinessGarmin's readiness score, components, and factor feedback
get_cycleCycle phase, day, and Garmin predictions

Activities and training

ToolAnswers
get_activitiesActivity summaries over up to 366 days (defaults to the last 14), at most 100 results
get_recent_activitiesActivities from the last 1–90 days, optionally filtered by type (for example running)
get_activityOne activity's laps, training effect, and HR/power zones
compare_activitiesSide-by-side metrics for 2–10 activities
get_training_weekMon–Sun totals and zone time, with per-metric coverage
get_training_loadAcute/chronic load, ratio/status, load focus/targets, VO2 max, endurance, hill, acclimation
get_training_zonesConfigured HR and cycling-power zone thresholds
get_running_toleranceRunning distance, impact load, tolerance, and feedback over 1–90 days

Body, combined, and trends

ToolAnswers
get_body_compositionWeight and related measurements over up to 366 days (defaults to the last 30)
get_recoverySleep, HRV, Body Battery, stress, RHR, and readiness for one day
get_recovery_trendSleep score, sleep HR, skin-temperature deviation, HRV, RHR, readiness, and Body Battery over the last 7, 14, or 28 days
get_training_contextOne day's recovery plus the 7 days of training before it

Dates and range limits

You can ask about any past date. The only limit on history is how much Garmin still returns for your account and device. Leave the date out and the tools use today. What is limited is how much one request covers:

Coverage per requestTools
One day (any date)The "one day at a time" tools, get_recovery, get_training_load
Up to 366 daysget_activities, get_body_composition
Up to 90 daysget_recent_activities (ending today), get_running_tolerance (ending on any date)
Fixed windowget_recovery_trend: last 7, 14, or 28 days, ending todayget_training_context: the 7 days ending on the chosen dateget_training_week: the Mon–Sun week containing the chosen date

get_activity and compare_activities take activity IDs, not dates, and get_training_zones reads your current settings. Activity lists return at most 100 activities per request.

You don't need to know these limits: describe the period in plain language and Claude picks the dates. For longer periods, such as several years of activities, Claude can split the question into several requests. A request that is too long returns an error that states the limit.

How to read the answers

  • Garmin's numbers and garmin-owl's calculations are kept apart. Anything calculated (such as "12% above your recent average") states its baseline dates, how many days it used, and its formula.
  • Missing stays missing. Nothing is guessed, and an absent value is never counted as zero.
  • Totals say how complete they are, for example "distance covers 3 of 4 activities".
  • Gaps are explained in an availability list: Garmin had no data, the metric is unsupported on your device, or the read failed or was rate-limited.
  • get_cycle deliberately leaves out notes, symptoms, moods, sexual activity, and raw daily logs.

Faster answers with the local cache (optional)

garmin-owl keeps what it reads in a small local database, so each day is fetched from Garmin only once. That happens automatically, but you can pre-load history so your first questions are quick:

uv run garmin-owl-sync                                       # last 7 days
uv run garmin-owl-sync --days 30                             # last 30 days (max 366)
uv run garmin-owl-sync --start 2025-01-01 --end 2025-12-31   # any past window
  • A single run covers at most 366 days. --days counts back from today. --start can be any past date, and --end defaults to today.
  • To load more than a year, run one window per year. Days already cached are skipped.
  • --refresh-today or --refresh-date YYYY-MM-DD re-fetches a day even if it's cached.
  • Sync pre-loads daily summaries, sleep, HRV, training readiness, and activity lists. Body Battery, stress, activity details, training load, body composition, and cycle data are fetched the first time you ask, then cached.

Inspect or clear the cache (clearing never touches your Garmin sign-in):

uv run garmin-owl-cache-info
uv run garmin-owl-cache-clear

The cache is stored at:

  • macOS: ~/Library/Application Support/garmin-owl/garmin.sqlite
  • Ubuntu/Linux: ~/.local/share/garmin-owl/garmin.sqlite, or under $XDG_DATA_HOME when that is set to an absolute path

Set GARMIN_OWL_DB to use a different file. If you used a version before 0.2.1 on Linux, your old cache is at the macOS-style path above. Point GARMIN_OWL_DB at it to keep it, or let the new cache fill up by itself.

When is cached data refreshed?

Watches and scales upload late, so a day counts as final only from noon the next day. Data fetched after that point is kept for good. Data fetched earlier, while the day could still change, is reused for at most 20 minutes and then fetched again. Today's numbers therefore stay current, and a half-synced day never gets stuck in the cache.

Updating

  1. Download the new .mcpb from Releases and drag it into Settings → Extensions again. It replaces the old version. For ChatGPT Desktop, follow Updating the ChatGPT plugin after step 2 instead.

  2. Update your terminal copy, which is used for garmin-owl-auth, sync, and the cache commands:

    cd garmin-owl
    git pull
    uv sync --locked
    

The cache upgrades itself when needed. Your saved sign-in carries over.

Troubleshooting

First, check that sign-in and the main reads work. The output shows only pass/fail per read, never your data:

uv run garmin-owl-smoke

garmin-owl never retries automatically and never shows raw Garmin responses, so error messages are short:

Message containsMeaningFix
"No local Garmin tokens found"Not signed in yet, or ~/.garminconnect was deletedRun uv run garmin-owl-auth
"authentication expired or was rejected"Garmin ended the session, for example after a password changeRun uv run garmin-owl-auth again
"rate limit reached"Too many Garmin requests in a short timeWait a few minutes. Sync smaller ranges.
"Garmin Connect is unavailable"A network problem or Garmin outageTry again later
"unexpected response shape"Garmin changed a private endpointOpen an issue naming the tool (never paste your Garmin data)
"no data for this request"That metric isn't recorded for that date or deviceExpected for unsupported metrics
"date range cannot exceed" / "days must be between"The request was longer than the tool allowsAsk for a shorter period, or several periods
"Unsupported garmin-owl cache schema"The cache was created by a newer versionUpdate garmin-owl, or run uv run garmin-owl-cache-clear
  • Extension doesn't appear: make sure you installed a .mcpb from Releases (or one built from the same version as your checkout), then restart Claude Desktop.
  • Tools time out the first time: run uv run garmin-owl-sync once to warm the cache.
  • Sign-in works in the terminal but not in Claude or ChatGPT: run garmin-owl-auth as the same OS user that runs the desktop app, and if you set GARMINTOKENS, make sure the app sees it too.
  • ChatGPT Desktop problems: see ChatGPT troubleshooting.

Privacy and safety

  • Everything runs locally and talks to your MCP client only over local stdio. There is no network listener, telemetry, or remote database.
  • Garmin access is read-only. garmin-owl has no tool that can change your account.
  • Tokens stay in ~/.garminconnect. Only normalized numbers are stored in the local cache, never raw Garmin responses.
  • Answers exclude credentials, account identifiers, raw GPS coordinates, and private cycle logs.
  • Health summaries are informational, not medical advice.

What you ask Claude, and the answers it gets from garmin-owl, are handled under your MCP client's privacy and data-retention settings, so review those before sharing health information with any model.

Limitations and removal

Garmin Connect's API is private, and which metrics exist depends on your device and account. When Garmin changes an endpoint, sign-in or individual reads can fail until garmin-owl is updated.

To remove garmin-owl:

  1. Uninstall the extension in Claude Desktop. For ChatGPT Desktop, uninstall Garmin Owl in the Plugins Directory, then run codex plugin marketplace remove garmin-owl-local.
  2. Delete your garmin-owl folder.
  3. Optionally delete the cache file listed above.
  4. Delete ~/.garminconnect only if no other Garmin tool uses those tokens.

Contributing

Bug reports and pull requests are welcome. CONTRIBUTING.md explains the project layout, design rules, tests, and release process.