Skip to main content
Glama
Garmin MCP for macOS

Garmin MCP for macOS

by devnjw

Garmin MCP

A local MCP server for all 26 documented Garmin Connect Personal API GET endpoints, plus 8 convenient summary tools. Runs on macOS, Windows and Linux with an API key supplied through an environment variable. Uses Garmin's official Python client. Independent community project; not affiliated with or endorsed by Garmin.

한국어 설치 안내 · All tools and API coverage · Security and privacy · Contributing

Requirements

  • uv and Git on macOS, Windows or Linux.

  • Python 3.12.4 or newer; tested on 3.12, 3.13 and 3.14. uv can provision Python for you.

  • Your own Garmin Connect Personal API key.

  • An MCP client supporting local STDIO servers, such as Codex, Claude Desktop or Cursor.

Related MCP server: garmin-mcp

Install

The same commands work in a POSIX shell or Windows PowerShell:

git clone https://github.com/devnjw/garmin-mcp.git
cd garmin-mcp
uv sync --frozen --no-dev --python 3.12 --no-editable

The installed executable is .venv/bin/garmin-mcp on macOS/Linux or .venv/Scripts/garmin-mcp.exe on Windows. Keep the clone in place and use an absolute path in your MCP client. This project is distributed through GitHub; do not assume an identically named PyPI package belongs to this repository.

Configure your API key

Set GARMIN_PERSONAL_API_KEY in the MCP server's environment. The server never saves the key, prompts for it, reads a keychain, or automatically loads .env files. Access tokens remain in process memory.

The simplest setup is the MCP client's per-server env configuration. A literal key in a client configuration is stored as plaintext by that client. Keep that file private, outside Git, and out of chat or issue reports. Alternatively, have your shell or secret manager supply the environment variable at launch.

Codex

Add this to your user-level ~/.codex/config.toml, replacing the path and placeholder locally:

[mcp_servers.garmin]
command = "/absolute/path/to/garmin-mcp/.venv/bin/garmin-mcp"
tool_timeout_sec = 180

[mcp_servers.garmin.env]
GARMIN_PERSONAL_API_KEY = "YOUR_GARMIN_PERSONAL_API_KEY"
GARMIN_MCP_TIMEZONE = "Europe/Berlin"

For Windows, use a TOML literal string for the command, for example:

command = 'C:\Users\YOUR_USER\garmin-mcp\.venv\Scripts\garmin-mcp.exe'

To keep the key out of the configuration file, omit the key from env and add env_vars = ["GARMIN_PERSONAL_API_KEY"] directly under [mcp_servers.garmin]. The Codex process must already have that environment variable; setting it in an unrelated terminal does not update a running desktop app. These alternatives are documented in Codex's official MCP guide.

On macOS/Linux, restrict a configuration containing a key with chmod 600 ~/.codex/config.toml. On Windows, keep the file under your private user profile with access limited to your account and administrators. Restart the garmin MCP server after changing settings. Ask it to call garmin_status(verify=true) to verify authentication without returning profile data.

Other STDIO clients

For clients accepting an mcpServers JSON configuration:

{
  "mcpServers": {
    "garmin": {
      "command": "/absolute/path/to/garmin-mcp/.venv/bin/garmin-mcp",
      "env": {
        "GARMIN_PERSONAL_API_KEY": "YOUR_GARMIN_PERSONAL_API_KEY",
        "GARMIN_MCP_TIMEZONE": "Europe/Berlin"
      }
    }
  }
}

On Windows, use the .exe path and escape backslashes in JSON (C:\\Users\\...) or use forward slashes. Environment-variable interpolation such as ${VAR} is client-specific; this server does not expand it.

Check from a terminal

With the key already present in that terminal's environment, run:

uv run --frozen --no-dev garmin-mcp check

check verifies authentication and today's summary plus recent sleep, HRV, Body Battery and activities. It prints statuses and counts, not measurements or credentials. A key configured only in your MCP client is not automatically available in a separate terminal. The default command, or serve, starts STDIO MCP; --version reports the package version.

Timezone

Set GARMIN_MCP_TIMEZONE to your IANA timezone, such as America/New_York or Asia/Seoul. Without it, a system /etc/localtime zoneinfo link is used when available; otherwise the default is UTC, including Windows. The bundled tzdata dependency provides IANA zones on Windows. Garmin calendar dates are preserved; summary timestamps use the selected timezone.

Try: “Show my sleep, HRV and workouts for the last seven days, including missing dates.”

What is supported?

Summary tools: garmin_status, garmin_daily_summary, garmin_sleep, garmin_hrv, garmin_body_battery, garmin_activities, garmin_activity, garmin_day.

Full API tools: 26 garmin_api_* tools expose all operations and documented parameters in the official Personal API 1.0.0 Swagger snapshot checked on 2026-10-24. This includes every documented activity-search filter, original JSON fields, profile, food logs, menstrual health, heart-rate zones, weight endpoints and original activity ZIP downloads. Complete mapping and limits.

  • Summary ranges default to seven days and allow 90 days, split into 31-day requests.

  • Raw range tools default to seven days and allow 31 days per call. Query consecutive intervals for longer history; raw responses are not merged or silently truncated.

  • Raw activity search allows 1–100 records per page and all documented filters, with next_start. Its date filters can span longer history.

  • Raw JSON preserves Garmin field names, units, nulls, negative sentinels and placeholders. Use summary tools for normalized metrics and explicit missing dates.

  • garmin_api_download_activity creates a new private ZIP file, never extracts or overwrites files, and returns its path/size/hash. Files can contain GPS tracks.

  • garmin_api_heart_rate_zones defaults generate_defaults to false. Explicitly enabling it may generate zone settings in Garmin. This tool and the local download tool are marked as potentially writing; the other 32 tools are marked read-only.

API coverage does not imply that every Garmin app feature is available, every device records every metric, or every account has data for every endpoint. There are no workout-upload or record-editing endpoints in this API snapshot. Empty data and denied access remain distinguishable.

Known live-API limitation: weight/all returned HTTP 400 during verification even with documented ISO dates. Its tool preserves the error and suggests garmin_api_weight_range(include_all=true), which succeeded. It does not silently change endpoints or response shapes. Verification details.

Privacy and storage

Item

Location

Personal API key

Environment supplied by your MCP client; never saved by this server

Access token

Process memory only; no SDK token cache file

Installed runtime

.venv inside the clone

Downloads on macOS/Linux

~/.local/share/garmin-mcp/downloads (directory 0700, files 0600)

Downloads on Windows

%LOCALAPPDATA%/garmin-mcp/downloads/<unique-directory> (current-user/administrator ACL)

Ordinary API responses

Returned to the MCP client; no server-side database or response log

Downloads are saved only on explicit tool calls, never extracted, and never overwrite existing files. Use a local filesystem supporting native permissions (NTFS on Windows) for private downloads. Data returned through MCP is available to your AI client/model and may be retained by that client. Raw tools can return identifiers, GPS and sensitive health information. There is no telemetry or hosted relay. Only Garmin's documented HTTPS host is contacted during use; uv contacts package sources during installation.

Garmin describes the Personal API as access to an individual's own data, not a commercial multi-user service. Distributing this client does not change those terms. See Garmin's FAQ.

Update and troubleshoot

git pull --ff-only
uv sync --frozen --no-dev --python 3.12 --no-editable --reinstall-package garmin-mcp

Restart the MCP server after updating. Your client-managed environment settings are unaffected. If a key expires or is revoked, replace GARMIN_PERSONAL_API_KEY and restart.

  • not_authenticated: the launched server has no key. Check its env or the client's environment forwarding.

  • invalid_api_key: remove whitespace, line breaks or other non-key characters from the value.

  • authentication_failed: create a fresh Personal API key, update the environment and restart.

  • access_denied: Garmin declined access; this is not an empty measurement.

  • rate_limited: wait before retrying; the server does not loop on HTTP 429.

  • response_too_large: use a smaller range or page. Limits are 8 MiB for JSON and 64 MiB for ZIPs.

  • schema_changed: report the tool and sanitized structural details, without a private API response.

For existing macOS/Linux installations using scripts/install.sh and scripts/run.sh, those compatibility launchers remain available. They install into ~/.local/share/garmin-mcp/venv and now pass through the environment key. The old interactive auth command has been removed. When moving from the initial macOS-only release, set your key in the client environment and clone afresh because the repository history was reset; old clones cannot update with git pull --ff-only. An obsolete garmin-mcp Keychain item can be deleted after the new setup works; the server never reads it.

To uninstall, remove the MCP server configuration and its key, delete the clone and any downloads or compatibility runtime. Revoke the key in Garmin Connect when no longer needed.

Development and verification

uv sync --frozen --python 3.12
uv run --frozen pytest -q
uv run --frozen ruff check .
uv run --frozen ruff format --check .
uv build

CI covers macOS, Windows and Linux with Python 3.12, 3.13 and 3.14. Tests use synthetic fixtures without Garmin credentials. They exercise the real MCP protocol, all endpoint/parameter mappings, environment authentication, platform-specific downloads and summary regressions. See verification scope.

License

Apache-2.0. Garmin's official garmin-connect-personal-api-python dependency is also Apache-2.0. See third-party notices.

Available Tools

34 tools
garmin_activitiesA
Read-only

Search workouts by Garmin's local activity dates; optional type e.g. running. Default last 7 days. limit 1–50, offset >=0. Follow next_offset to check for more pages.

Inputs: end, limit, start, offset, +1 more. Returns: object

garmin_activityA
Read-only

Summary measurements for one activity: meters, seconds, bpm, watts and training effect. Use activity_id from garmin_activities. Does not download FIT files or return GPS tracks.

Inputs: activity_id (required). Returns: object

garmin_api_activityB
Read-only

Full activity details, including fields omitted from the summary tool. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: activity_id (required). Returns: object

garmin_api_activity_workoutsB
Read-only

Workouts associated with an activity. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: activity_id (required). Returns: object

garmin_api_body_batteryA
Read-only

Body Battery readings and optional interval grouping using chunks. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: chunks, end_date, start_date. Returns: object

garmin_api_daily_summariesB
Read-only

User summary range response, including the original data envelope. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: end_date, start_date. Returns: object

garmin_api_daily_summaryA
Read-only

Full daily user summary, preserving all Garmin fields. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: calendar_date. Returns: object

garmin_api_download_activityA

Download the original activity ZIP to a private local directory. Returns file metadata; never extracts the archive. This tool writes a new file on this Mac. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: activity_id (required). Returns: object

garmin_api_fitness_ageA
Read-only

Fitness-age data as of a date. full_history_scan controls the look-back search. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: calendar_date, full_history_scan. Returns: object

garmin_api_food_logsA
Read-only

Food logs with optional goals, nutrition, media and meal filters. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: date, meal_id, include_goals, include_media, +1 more. Returns: object

garmin_api_heart_rateA
Read-only

Daily heart-rate readings and summary. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: date. Returns: object

garmin_api_heart_rate_zonesC

Heart-rate zone settings. generate_defaults defaults to false; setting it true may generate account zone settings when none exist. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: generate_defaults. Returns: object

garmin_api_hrv_dailyA
Read-only

Full daily HRV summary and readings. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: calendar_date. Returns: object

garmin_api_hrv_summariesA
Read-only

HRV summaries across a date interval. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: to_date, from_date. Returns: object

garmin_api_hydrationA
Read-only

Daily hydration statistics. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: end_date, start_date. Returns: object

garmin_api_menstrual_dayC
Read-only

Menstrual-cycle day view, including optional analysis and prediction reference date. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: skipbm, today_date, calendar_date, include_analysis, +1 more. Returns: object

garmin_api_menstrual_summaryC
Read-only

Menstrual-cycle summary for a date. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: calendar_date. Returns: object

garmin_api_profileC
Read-only

Full profile settings. May include account identifiers and personal information. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: no named parameters. Returns: object

garmin_api_sleep_dailyC
Read-only

Full sleep response for a date, with fields preserved. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: date. Returns: object

garmin_api_sleep_summariesA
Read-only

Sleep summaries across a date interval, including empty placeholders from Garmin. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: end_date, start_date. Returns: object

garmin_api_spo2B
Read-only

Daily oxygen saturation and acclimation data. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: calendar_date. Returns: object

garmin_api_stepsA
Read-only

Daily step statistics. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: end_date, start_date. Returns: object

garmin_api_stressA
Read-only

Daily stress statistics. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: end_date, start_date. Returns: object

garmin_api_weight_allA
Read-only

All weight measurements from the weight/all endpoint. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: end_date, start_date. Returns: object

garmin_api_weight_goalsB
Read-only

Effective weight goals for a date interval. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: end_date, start_date. Returns: object

garmin_api_weight_rangeB
Read-only

Weight range response; include_all controls all versus latest daily entries. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: end_date, start_date, include_all. Returns: object

garmin_api_wellness_activitiesB
Read-only

Wellness activity summaries across a date interval. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).

Inputs: from_date, until_date. Returns: object

garmin_body_batteryA
Read-only

Body Battery charge/drain points and observed sample minimum/maximum. Inclusive dates, default last 7 days, maximum 90. No invented recovery score.

Inputs: end, start. Returns: object

garmin_daily_summaryB
Read-only

Daily steps, energy, heart rate, stress, Body Battery and intensity minutes. Date YYYY-MM-DD; default today in the configured timezone.

Inputs: date. Returns: object

garmin_dayA
Read-only

Combine daily summary, sleep, HRV, Body Battery and workouts for one date. Check all section statuses and activity pagination; incomplete data is marked partial.

Inputs: date. Returns: object

garmin_hrvA
Read-only

Nightly HRV and weekly average in milliseconds, Garmin baseline and status. Inclusive wake-up dates, default last 7 days, maximum 90.

Inputs: end, start. Returns: object

garmin_sleepA
Read-only

Sleep duration/stages in seconds, score and overnight vitals. Inclusive wake-up dates; defaults to the last 7 days, maximum 90. Missing days are explicit.

Inputs: end, start. Returns: object

garmin_statusA
Read-only

Check whether GARMIN_PERSONAL_API_KEY is configured without revealing its value. verify=True performs a Garmin authentication check, without returning profile data.

Inputs: verify. Returns: object

Tool Schema Changelog

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

Loading tool schema changelog…

    TDQS

    B3/5.0

    Scored across 34 tools

    Disambiguation2/5

    There are two overlapping tool families: raw `garmin_api_*` endpoints and normalized `garmin_*` convenience tools. Many pairs are easily confused, such as `garmin_api_daily_summary` vs `garmin_api_daily_summaries` vs `garmin_daily_summary`, or `garmin_api_sleep_summaries` vs `garmin_api_sleep_daily` vs `garmin_sleep`. Descriptions help somewhat, but an agent must carefully distinguish raw-vs-normalized and single-vs-range variants.

    Naming Consistency3/5

    All names use snake_case, but the set mixes a `garmin_api_` raw-endpoint family with a shorter `garmin_` normalized family. Singular/plural distinctions like `garmin_activity`/`garmin_activities` and `daily_summary`/`daily_summaries` are meaningful but add surface inconsistency. The pattern is readable but not fully uniform.

    Tool Count2/5

    34 tools is heavy for a personal health-data server and includes many near-duplicate raw/normalized variants. Several tools earn their place, but the surface feels bloated rather than tightly scoped. This exceeds the typical 3–15 well-scoped range.

    Completeness4/5

    The surface broadly covers Garmin health data: activities, sleep, HRV, Body Battery, steps, stress, hydration, weight, profile, menstrual cycle, heart-rate zones, food logs, and activity downloads. It is mostly read-only, which is appropriate, but some Garmin domains such as devices, training status, and badges are absent. These are minor gaps rather than dead ends for core workflows.

    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
    • A
      license
      C
      quality
      B
      maintenance
      Enables access to Garmin Connect fitness data through 39 fine-grained tools covering body, heart, sleep, stress, activity, steps, hydration, devices, nutrition, goals, and user profile.
      40
      3
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Syncs your own Garmin Personal API data into a local SQLite archive and lets any MCP-capable AI assistant query sleep, HRV, stress, body battery, training and activity trends through natural language, with optional interactive charts. Everything runs locally with your own API key, so private health data stays on your machine.
      671 PyPI
      1
      Apache 2.0