Garmin MCP for macOS
Provides access to a user's Garmin Connect data through Garmin's official Personal API, with summary tools for daily stats, sleep, HRV, Body Battery, activities, and per-day views, plus raw tools covering all 26 documented GET endpoints (activity search filters, heart-rate zones, weight, food logs, menstrual health, profile) and downloading original activity ZIP files.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Garmin MCP for macOS Show my sleep, HRV and workouts for the last seven days."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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-editableThe 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 checkcheck 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_activitycreates 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_zonesdefaultsgenerate_defaultsto 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 |
|
Downloads on macOS/Linux |
|
Downloads on Windows |
|
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-mcpRestart 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 itsenvor 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 buildCI 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 toolsgarmin_activitiesARead-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_activityARead-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_activityBRead-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_searchARead-only
Search activities with every documented filter. Distances and elevations are meters, durations are seconds, dive depth is centimeters, and power is watts. Array filters become repeated query parameters. Supply all four coordinates for a bounding box. Follow pagination with start and limit. Raw JSON fields are not normalized. Date ranges default to 7 days and allow at most 31 days (activity search uses pagination).
Inputs: limit, start, search, sort_by, +28 more. Returns: object
garmin_api_activity_workoutsBRead-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_batteryARead-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_summariesBRead-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_summaryARead-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_ageARead-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_logsARead-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_rateARead-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_dailyARead-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_summariesARead-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_hydrationARead-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_dayCRead-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_summaryCRead-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_profileCRead-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_dailyCRead-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_summariesARead-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_spo2BRead-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_stepsARead-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_stressARead-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_allARead-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_goalsBRead-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_rangeBRead-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_activitiesBRead-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_batteryARead-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_summaryBRead-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_dayARead-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_hrvARead-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_sleepARead-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_statusARead-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
Scored across 34 tools
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.
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.
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.
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
Related MCP Connectors
Unofficial Garmin Connect access: sleep, HRV, training, activities, workouts and weight.
Garmin watch data for AI assistants: activities, sleep, HRV, recovery. Never writes to Garmin.
Read-only Apple Health data synced from your iPhone: sleep, heart rate, HRV, workouts and trends.
Garmin data in Claude & ChatGPT via the Garmin Health API. OAuth sign-in, no password sharing.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceExposes personal Garmin wellness data through MCP tools for accessing summary, sleep, HRV, heart rate, stress, body battery, and historical data.MIT
- AlicenseNot gradedqualityCmaintenanceProvides 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 npmMIT
- AlicenseCqualityBmaintenanceEnables 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.403MIT
- AlicenseNot gradedqualityBmaintenanceSyncs 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 PyPI1Apache 2.0