Skip to main content
Glama

garmin-mcp

CI

English | Русский

Unofficial. This project is not affiliated with, endorsed by or supported by Garmin Ltd. "Garmin" is a trademark of Garmin Ltd. or its subsidiaries; it is used here only to describe compatibility.

An MCP server that gives Claude (or any other MCP client) read-only access to your own Garmin Connect data: sleep, HRV, heart rate, stress, Body Battery, daily activity, workouts and weight. It uses the official Garmin Connect Personal API.

The main use case is personal advice on sleep and wellbeing. The server computes averages, sleep regularity, sleep shortfall, deviations from your own baseline and links between your day and your sleep; Claude turns that into concrete recommendations.

MCP (Model Context Protocol) is the standard an AI assistant uses to call external tools. Claude Code starts garmin-mcp as a child process and talks to it over stdin/stdout.

Quick start

  1. API key. Open https://connect.garmin.com/app/settings/personalApiToken → Create Key → accept the terms → choose an expiry (30, 90, 180 days or 1 year) → copy the key. The key is shown once. Garmin emails you about a week before it expires.

  2. Install — pick the option that fits your client:

    Client

    How

    Claude Desktop

    Download garmin-mcp.mcpb from the latest release and open it. Claude Desktop asks for the API key (stored securely) and time zone. Bundled for macOS (Intel and Apple Silicon), Windows x64 and Linux x64

    Claude Code (plugin)

    /plugin marketplace add sasha-sup/garmin-mcp, then /plugin install garmin-mcp@garmin-mcp; Claude Code asks for the key. On first start the plugin downloads the release binary and verifies its SHA-256. macOS and Linux

    Claude Code (binary)

    Install the binary (row below), then claude mcp add -s user garmin -- garmin-mcp -env-file /path/to/.env

    Binary

    Archives for Linux, macOS and Windows on the releases page, or go install github.com/sasha-sup/garmin-mcp/cmd/garmin-mcp@latest

    Docker

    docker run -i --rm -e GARMIN_API_KEY -e GARMIN_TZ ghcr.io/sasha-sup/garmin-mcp as the MCP command (images for amd64 and arm64)

    Any MCP client

    Listed in the MCP Registry as io.github.sasha-sup/garmin-mcp

    From source

    make build (Go 1.27.4+; Go 1.21+ downloads it automatically) → bin/garmin-mcp

  3. Configure (binary and source installs; the plugin and the bundle ask for the values instead):

    cp .env.example .env
    chmod 600 .env        # only your user can read the key
    $EDITOR .env          # set GARMIN_API_KEY and GARMIN_TZ
  4. Check:

    garmin-mcp -env-file .env -check
    # auth ok: access token valid until 2026-11-13 10:33:12
    # profile ok: Garmin time zone Europe/Berlin, server time zone Europe/Berlin (CET +01:00)
    # data ok: 7 of the last 7 days have a daily summary

    Claude Desktop, in claude_desktop_config.json, if you prefer the binary over the bundle:

    {
      "mcpServers": {
        "garmin": {
          "command": "/absolute/path/garmin-mcp",
          "args": ["-env-file", "/absolute/path/.env"]
        }
      }
    }
  5. Ask Claude, for example:

    • "Analyse my sleep over the last month and give me recommendations"

    • "How recovered am I today? Is a hard workout a good idea?"

    • "How have my resting heart rate and HRV changed over six months?"

    • "What hurts my sleep more: stress or late workouts?"

    Or run a ready-made prompt. In Claude Code they are /mcp__garmin__sleep_review, /mcp__garmin__wellbeing_review (both take an optional number of days) and /mcp__garmin__readiness_check.

Related MCP server: garmin-mcp

Tools

Tool

Returns

Period

garmin_sleep_analysis

Sleep analysis: average duration vs Garmin's baseline sleep need and weekly shortfall, median sleep onset and wake time and their spread, weekday/weekend shift (social jetlag), stage shares, sleep score trend, sleep heart rate/HRV/SpO2/breathing, and factors from the previous day (stress above your usual, late or hard workouts, low step count, late sleep onset → score, HRV and sleep heart rate with and without; differences are shown only with ≥ 5 nights in each group)

up to 366 days, default 28

garmin_sleep

Per night: bedtime, wake time, stages, score and sub-scores, heart rate, stress, SpO2, breathing, sleep need, Garmin's feedback

up to 366, 7

garmin_recovery_status

One-day readiness snapshot: sleep; HRV, resting HR, sleep HR, breathing rate and yesterday's stress each compared with your own previous 28 days (mean, SD, z-score, days of history); overnight Body Battery charge; training load of the last 7 vs 28 days; plain-language signals and the method used. Baseline-relative signals need ≥ 14 days of history

1 day

garmin_trends

Weekly or monthly averages: resting HR, HRV, sleep, steps, stress, Body Battery, intensity minutes, workouts, training load, weight

up to 731 days, 84

garmin_daily_summary

Per day: steps, distance, calories, heart rate, intensity minutes, stress, Body Battery, breathing, SpO2

up to 366, 7

garmin_hrv

Overnight HRV, 7-day average, personal baseline band, Garmin status

up to 366, 14

garmin_body_battery

Charged/drained, min/max, events (sleep, workouts), Garmin's feedback, optional intraday timeline

up to 31, 3

garmin_heart_rate

Heart rate for one day in local-time intervals (5, 10, 15, 20, 30, 60, 120, 180, 240 min)

1 day

garmin_activities

Workouts: type, duration, distance, pace/speed, heart rate, time in zones, training load, training effect, VO2max

default 30 days, up to 100 items

garmin_activity

Full summary of one workout

—

garmin_profile

Time zone, heart rate zones, Fitness Age, latest weight

—

garmin_weight

Weigh-ins: weight, BMI, body fat, muscle mass, body water

up to 731, 90

garmin_api_get

Raw JSON from an allow-listed endpoint (hourly SpO2, daily stress and steps, hydration, …), truncated at 100 KB. Nutrition and menstrual cycle only with GARMIN_ALLOW_SENSITIVE=1

—

All dates are local calendar dates (YYYY-MM-DD). Sleep is filed under the wake-up date: the night from 10 to 11 November is 2026-11-11.

Not in the API

The Personal API does not expose Training Readiness, Training Status, Acute Load or a VO2max trend. garmin_recovery_status computes comparable signals itself (HRV, resting HR, sleep, the sum of activityTrainingLoad over 7 and 28 days) and labels them as computed by this server, not by Garmin.

Configuration

Variable

Purpose

Default

GARMIN_API_KEY (or GARMIN_API)

Personal API key. Without it the server starts and lists its tools, but every call returns an error explaining how to set it

— (required for data)

GARMIN_TZ

Your IANA time zone, e.g. Europe/Berlin. Decides what "today" is. garmin_profile warns if it differs from your Garmin profile

system zone

GARMIN_CACHE_DIR

Cache directory, or off

user cache dir: ~/.cache/garmin-mcp on Linux, ~/Library/Caches/garmin-mcp on macOS

GARMIN_REGION

global or cn

global

GARMIN_LOG_LEVEL

debug, info, warn, error (logs go to stderr)

info

GARMIN_ALLOW_SENSITIVE

1 enables the nutrition and menstrual-cycle endpoints in garmin_api_get

off

Flags: -env-file PATH (the environment wins over the file; the file may only set GARMIN_*), -check, -clear-cache, -version.

How it works

  • Authentication. The key is exchanged for an access token (a JWT valid for 24 hours; there is no refresh token): POST /personalapitoken-service/personal-api/personal-api-key/token with Authorization: ApiKey <key>. Every other request carries Authorization: Bearer <token>. The token lives only in memory and is renewed 5 minutes before expiry, and once on HTTP 401. One request performs the renewal; concurrent requests wait for it, but never longer than their own timeout.

  • Range limits. Garmin answers 400 when a period is too long: sleep and Body Battery 31 days, stress and steps 28, daily summaries 1 year. The dedicated tools split requests into chunks and run them in parallel (at most 4 at a time). garmin_api_get passes dates through unchanged, so raw stress_daily/steps_daily requests must stay within 28 days.

  • Cache. Responses are stored per day in GARMIN_CACHE_DIR. A day with data that was fetched more than 72 hours after it ended is final and never requested again; more recent days are cached for 10 minutes because the watch keeps syncing. A day without data (watch not worn or not synced yet) is re-checked every 6 hours and becomes final only after 30 days. Re-analysing a year costs a handful of requests, not hundreds. If the watch synced very late and past days came in partially, run garmin-mcp -clear-cache.

  • Retries. On 429 and 5xx: up to 3 retries with exponential backoff, honouring Retry-After. Garmin does not document a rate limit.

Privacy and security

  • The server only reads: the API has no write methods and every tool is marked readOnlyHint.

  • The key and token are sent only to Garmin's host; redirects are not followed, so the key cannot leak to another address. Logs and error messages never contain the key or token.

  • The cache holds health data: the v1 directory inside GARMIN_CACHE_DIR gets mode 0700, files 0600; symlinks and oversized files in the cache are ignored. Clear it with garmin-mcp -clear-cache or by deleting the directory.

  • -env-file accepts only GARMIN_* variables, so a tampered file cannot set HTTPS_PROXY or SSL_CERT_FILE to intercept the key.

  • Nutrition and menstrual-cycle data never reach the model unless you set GARMIN_ALLOW_SENSITIVE=1.

  • Whatever the tools return is sent to the model provider (Anthropic for Claude) as part of the conversation.

  • .env and *.db are in .gitignore. If the key leaks, delete it on the Personal API Key page; every token issued for it stops working too.

  • Recommendations based on this data are not medical advice.

Development

make test    # unit tests with -race against a synthetic fake Garmin API (no real data needed)
make lint    # golangci-lint v2
make vuln    # govulncheck
make live    # every tool against the real API (needs .env); prints response sizes only
make all     # lint + test + vuln + build
make ci-lint # validate the GitHub Actions workflows (actionlint)

Releases

Push a tag vX.Y.Z from a commit where plugin/.claude-plugin/plugin.json has version X.Y.Z. .github/workflows/release.yml then builds the archives and garmin-mcp.mcpb (GoReleaser), pushes ghcr.io/sasha-sup/garmin-mcp:X.Y.Z (amd64 and arm64) and publishes server.json to the MCP Registry with GitHub OIDC (no stored secrets).

CI

GitHub Actions (.github/workflows/ci.yml) runs on pushes to main, on pull requests, weekly (new vulnerabilities appear without code changes) and on demand:

Job

What it does

Lint

go mod tidy leaves go.mod/go.sum unchanged; golangci-lint v2.15.0 with .golangci.yml

Test

go vet, go test -race with coverage on Ubuntu and macOS, builds the binary

Vulnerability scan

govulncheck over the code and its dependencies

Actions are pinned to commit SHAs, the workflow has only contents: read, and checkout does not persist the token. Dependabot proposes Go module and action updates weekly. The live test against the real API does not run in CI: no key is given to CI.

Layout:

cmd/garmin-mcp/      entry point: flags, config, stdio server
internal/config/     environment and .env loading
internal/garmin/     HTTP client (token, retries, limits), response models, cached range-aware service
internal/cache/      file cache with atomic writes
internal/tools/      MCP tools, sleep and recovery analysis, prompts
internal/civil/      calendar date without time

Garmin's official OpenAPI spec is embedded in https://developer.garmin.com/gc-developer-program/personal/swagger-docs/; the response formats in internal/garmin/models.go were checked against live API responses (November 2026).

License

MIT. Contributor and AI-agent guide: AGENTS.md.

Available Tools

13 tools
garmin_activitiesA
Read-onlyIdempotent

List workouts in a date range, newest first: type, duration, distance, pace or speed, heart rate, time in HR zones, training load, training effect, VO2max, Body Battery impact, sets/reps. Default last 30 days, up to 100 items.

Inputs: days, limit, search, end_date, +2 more. Returns: to, from, count, truncated, +1 more

garmin_activityA
Read-onlyIdempotent

Full summary of one workout by activity_id (from garmin_activities): all measurements Garmin computed, including temperature, cadence, power, training effect messages.

Inputs: activity_id (required). Returns: name, type, units, summary, +2 more

garmin_api_getA
Read-onlyIdempotent

Raw JSON from a Garmin Personal API endpoint not covered by other tools (SpO2 detail, stress/steps series, hydration, nutrition, menstrual cycle, fitness age history, HRV readings). Responses are large and truncated at 100 KB: prefer the dedicated tools.

Inputs: endpoint (required), date, query, end_date, +2 more.

garmin_body_batteryA
Read-onlyIdempotent

Per-day Body Battery (Garmin's 0-100 energy estimate): charged, drained, min/max, events that changed it (sleep, activities, naps) and Garmin's feedback. Optional intraday timeline. Range up to 31 days, default last 3.

Inputs: days, end_date, start_date, include_timeline. Returns: to, days, from

garmin_daily_summaryA
Read-onlyIdempotent

Per-day wellness totals: steps, distance, calories, resting/min/max heart rate, intensity minutes, stress, Body Battery range, respiration, SpO2. Range up to 366 days, default last 7.

Inputs: days, end_date, start_date. Returns: to, days, from

garmin_heart_rateA
Read-onlyIdempotent

Intraday heart rate for one day, aggregated into local-time intervals (avg/min/max bpm per interval), plus resting, min, max and 7-day resting average.

Inputs: date, interval_minutes. Returns: date, series, max_hr_bpm, min_hr_bpm, +4 more

garmin_hrvA
Read-onlyIdempotent

Per-night heart rate variability (HRV, ms): last-night average, 5-minute high, 7-day average, personal baseline band and Garmin's status (BALANCED, UNBALANCED, LOW, POOR). Range up to 366 days, default last 14.

Inputs: days, end_date, start_date. Returns: to, from, nights

garmin_profileA
Read-onlyIdempotent

User context for interpreting other data: time zone and units, heart rate zones per sport, Fitness Age with its components, latest weight.

Returns: hr_zones, warnings, fitness_age, latest_weight, +3 more

garmin_recovery_statusA
Read-onlyIdempotent

Readiness snapshot for one day (default today): last night's sleep; HRV, resting HR, sleep HR, breathing rate and yesterday's stress each compared with the user's own previous 28 days (mean, SD, z-score, days of history); Body Battery recharge; training load of the last 7 vs 28 days; plain-language signals with the method used. Use this first for 'how recovered am I' questions.

Inputs: date. Returns: hrv, date, method, signals, +9 more

garmin_sleepA
Read-onlyIdempotent

Per-night sleep: bedtime, wake time, duration, deep/light/REM/awake minutes, sleep score with sub-scores, average heart rate, sleep stress, SpO2, respiration, sleep need and Garmin's feedback. Filed under the wake-up date. Range up to 366 days, default last 7.

Inputs: days, end_date, start_date. Returns: to, from, nights

garmin_sleep_analysisA
Read-onlyIdempotent

Sleep quality analysis over a period (default 28 days, up to 366): average duration vs Garmin's baseline sleep need and weekly shortfall, regularity (median sleep onset and wake time, spread, weekday-weekend shift), stage shares, sub-score counts, score trend, sleep heart rate, HRV, resting HR, SpO2, respiration, and factors from the previous day (stress above usual, late or hard workouts, low activity, late sleep onset) compared with sleep score, HRV and sleep HR. Use this first for sleep questions and recommendations.

Inputs: days, end_date, start_date. Returns: to, from, notes, factors, +9 more

garmin_weightA
Read-onlyIdempotent

Daily weigh-ins: weight (kg), BMI, body fat, muscle mass, body water where available. Range up to 2 years, default last 90 days.

Inputs: days, end_date, start_date. Returns: to, from, entries

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4/5.0

    Scored across 13 tools

    Disambiguation4/5

    Each tool targets a distinct Garmin data domain, and descriptions explicitly prioritize tools (e.g., garmin_recovery_status for recovery questions, garmin_sleep_analysis for sleep questions). Potential confusion exists between closely related analytical tools like garmin_daily_summary, garmin_trends, and garmin_recovery_status, but the descriptions clarify their boundaries well.

    Naming Consistency5/5

    All tools consistently follow the garmin_ prefix with snake_case naming throughout. The singular/plural distinction between garmin_activity and garmin_activities is clear and intentional.

    Tool Count5/5

    13 tools is well within the ideal 3-15 range and each covers a distinct, useful slice of Garmin health and fitness data. No tool feels redundant or excessive.

    Completeness4/5

    The surface covers a broad range of wellness domains with a strategic raw-API escape hatch for uncovered endpoints like hydration or nutrition. Minor gaps include dedicated structured workout or training plan tools, but the raw API fallback mitigates these.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Exposes personal Garmin wellness data through MCP tools for accessing summary, sleep, HRV, heart rate, stress, body battery, and historical data.
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Provides read access to Garmin Connect health and training data (sleep, heart rate, stress, HRV, activities) and supports writes like creating workouts, editing activities, and exporting files.
      854 npm
      MIT
    • F
      license
      A
      quality
      D
      maintenance
      Provides read-only access to your Garmin Connect health data, including sleep, HRV, body battery, stress, training readiness, and activities, through an MCP server.
      2
      14
      -
    • A
      license
      A
      quality
      B
      maintenance
      Garmin Connect for Claude and any MCP client, on Garmin's official Personal API. Read-only access to sleep, HRV, Body Battery, stress, heart rate, activities and weight with a revocable key, no password or scraping.
      26
      1,568 PyPI
      69
      MIT