Skip to main content
Glama
devnjw

Garmin MCP for macOS

by devnjw
README.md
# Garmin MCP for macOS

A local MCP server for **all 26 documented Garmin Connect Personal API GET endpoints**, plus **8 convenient summary tools**. Uses Garmin's official Python client and your own Personal API key. Independent community project; not affiliated with or endorsed by Garmin.

[한국어 설치 안내](docs/INSTALL.ko.md) · [All tools and API coverage](docs/API.md) · [Security and privacy](SECURITY.md) · [Contributing](CONTRIBUTING.md)

## Requirements

- macOS with an unlocked login Keychain. Windows and Linux credential storage are not supported in this release.
- [uv](https://docs.astral.sh/uv/getting-started/installation/) and Git. The installer provisions Python 3.12 through uv if needed.
- A Garmin account with a [Personal API key](https://connect.garmin.com/app/settings/personalApiToken). Each user creates and uses their own key.
- An MCP client that supports local STDIO servers, such as Codex.

## Install and connect

```sh
git clone https://github.com/devnjw/garmin-mcp.git
cd garmin-mcp
./scripts/install.sh
./scripts/run.sh auth
./scripts/run.sh check
```

`auth` asks for the key in a **hidden Terminal prompt**, verifies it with Garmin, then saves it in macOS Keychain. You can also open `scripts/Garmin-API-Key.command` in Terminal. Do not enter the key in chat, source files, shell arguments or MCP configuration. A macOS Keychain prompt may request permission for the installed Python executable.

`check` verifies authentication and checks today's summary plus the last seven days of sleep, HRV, Body Battery and activities. It prints statuses and counts, not health measurements or credentials. `partial` and `no_data` describe missing records; `error` means a request failed. This command does not exercise every API endpoint.

From the cloned repository directory, register with Codex:

```sh
codex mcp add garmin -- "$(pwd)/scripts/run.sh"
```

In Codex Settings → MCP servers, restart `garmin`, then use a new chat. For other STDIO clients, use the **absolute path to `scripts/run.sh`** as the server command. Keep the cloned directory in place. See [official Codex MCP configuration](https://learn.chatgpt.com/docs/extend/mcp).

Optional Codex settings in `~/.codex/config.toml`:

```toml
[mcp_servers.garmin]
command = "/absolute/path/to/garmin-mcp/scripts/run.sh"
tool_timeout_sec = 180

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

Choose your own IANA timezone. Without this setting, the server reads the Mac's configured zone through `/etc/localtime`, falling back to UTC if unavailable. Garmin's calendar dates are preserved; summary timestamps are displayed in 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](docs/API.md).

- 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](docs/VERIFICATION.md).

## Privacy and storage

| Item | Location |
| --- | --- |
| Personal API key | macOS Keychain: service `garmin-mcp`, account `personal-api-key` |
| Access token | Process memory only; no SDK token cache file |
| Installed runtime | `~/.local/share/garmin-mcp/venv` |
| Explicitly requested downloads | `~/.local/share/garmin-mcp/downloads` (directory 0700, files 0600) |
| Ordinary API responses | Returned to the MCP client; no server-side database or response log |

Data returned through MCP is available to your AI client/model and may be retained by that client. Raw tools can return account identifiers, precise location and sensitive health information. There is no telemetry or hosted relay in this project. Only the documented Garmin 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 API terms. See [Garmin's FAQ](https://developer.garmin.com/gc-developer-program/personal/personal-faq/).

## Update and troubleshoot

```sh
git pull --ff-only
./scripts/install.sh
./scripts/run.sh check
```

Restart the MCP server after updating. Re-running the installer preserves the Keychain entry. A key that expires or is revoked requires `./scripts/run.sh auth` again.

- `keychain_locked`: unlock the login Keychain or allow the local Python executable access.
- `authentication_failed`: create a fresh Personal API key and run `auth`.
- `access_denied`: Garmin declined access; this is not an empty measurement.
- `rate_limited`: wait before retrying. The server does not loop on 429 responses.
- `response_too_large`: use a smaller range or page. JSON responses are limited to 8 MiB; activity ZIPs to 64 MiB.
- `schema_changed`: report the affected tool and sanitized structural details. Do not attach a real API response containing private data.

To uninstall, remove the MCP server entry, delete the clone and `~/.local/share/garmin-mcp`, and delete the `garmin-mcp` item in Keychain Access. Revoke the key in Garmin Connect if no longer needed.

## Development and verification

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

Tests use synthetic data and block network requests. CI checks supported Python versions on macOS, including the MCP protocol, all endpoint/parameter mappings, credential handling, downloads and summary regressions. See [verification scope](docs/VERIFICATION.md). CI never requires a Garmin key.

## License

[Apache-2.0](LICENSE). Garmin's official [`garmin-connect-personal-api-python`](https://github.com/garmin/connect-personal-api-python) dependency is also Apache-2.0. See [third-party notices](THIRD_PARTY_NOTICES.md).