Skip to main content
Glama

garmin-mcp

Export your Garmin Connect data (built for the Garmin Cirqa, works with any Garmin device) into a local SQLite database, then read it from a React dashboard or from an AI agent through an MCP server. Everything runs on your own machine.

How it works

A Python script pulls your data twice a day with the community python-garminconnect library, reusing a cached login token instead of a password. Every API response is first stored untouched in SQLite; replayable transformers then turn those responses into clean tables (daily summary, sleep stages, HRV, readiness, training status, and per-minute series for heart rate, stress, Body Battery and SpO2). Views on top (v_day, v_week, v_trends with 7/28-day baselines and z-scores) and a metric_catalog table describe what each metric means. One read-only service layer feeds two front ends: a FastAPI server with a React dashboard, and an MCP server whose tools (get_day, compare_periods, get_sleep_detail, get_timeseries, a restricted run_sql, ...) let an agent answer questions such as "how did I sleep this week versus last week, and why?" against your own baselines. The MCP server runs over stdio, so there is no open network port and no credentials, and it cannot read the raw payloads (they contain GPS coordinates and your name).

flowchart LR
    W["Garmin band"] --> GC["Garmin Connect<br/>(cloud)"]

    subgraph Machine["Your machine"]
        T["systemd timer<br/>twice a day"] --> S["garmin-optimizer sync<br/>(cached token)"]
        S --> RAW[("raw_response<br/>JSON as received")]
        RAW --> TR["Transformers<br/>(replayable)"]
        TR --> DB[("SQLite garmin.db<br/>tables, views,<br/>metric_catalog")]
        DB --> SVC["Service layer<br/>(read-only)"]
        SVC --> API["FastAPI<br/>127.0.0.1:8000"]
        SVC --> MCP["MCP server<br/>(stdio, 10 tools)"]
        API --> UI["React dashboard"]
        AG["AI agent<br/>(Claude Code, Hermes, ...)"] -- "starts on demand" --> MCP
    end

    GC -- "HTTPS" --> S

Status: early. Export (login, dump, sync, backfill), the transformers into clean tables, a read-only HTTP API, an MCP server for AI agents and a React dashboard work. Manual inputs (food, weight, training log) are next. Activities and body composition are implemented from the library's documented fields; activities are verified against a real workout, weigh-ins are not yet.

Related MCP server: GarminSynapse

Disclaimer

This uses the unofficial python-garminconnect library, which talks to the same private API as the Garmin Connect app. It is not endorsed by Garmin, may break when Garmin changes its login, and may be against their terms of service. Use it for your own data at personal scale. Everything stays on your machine; if you connect an AI agent, the data it reads is sent to that agent's provider.

Quick start

uv sync
uv run garmin-optimizer login                       # once: asks for email, password, MFA code
uv run garmin-optimizer dump --date 2026-10-16      # raw JSON per endpoint in data/dumps/
uv run garmin-optimizer sync                        # store new days in data/garmin.db
uv run garmin-optimizer backfill --since 2026-01-01 # resumable history
uv run garmin-optimizer rebuild                     # re-derive all tables from stored raw data
uv run garmin-optimizer doctor                      # what has been fetched, per endpoint

Only the cached token is used after login; a password is never stored.

Automatic sync (Linux, systemd)

./deploy/install-systemd.sh              # installs a user timer: 07:20 and 21:40 local time (+ up to 20 min random delay)
./deploy/install-systemd.sh --uninstall

It runs as your user (no root), uses the cached token only, and catches up after the machine was off (Persistent=true). Useful commands:

systemctl --user list-timers garmin-optimizer-sync.timer   # next run
systemctl --user start garmin-optimizer-sync.service       # run now
journalctl --user -u garmin-optimizer-sync -e              # logs
systemctl --user --failed                                  # did the last run fail?
uv run garmin-optimizer doctor                             # token, last sync time, per-endpoint counts

A failed run (expired token, Garmin 429) is not retried immediately: the next scheduled run resumes where it stopped. If the token expires, run garmin-optimizer login again. Each run re-fetches the last 3 days; if your band went more than 3 days without syncing to the phone, use backfill --since <date> once. Change the times with systemctl --user edit --full garmin-optimizer-sync.timer.

Using the data

uv run garmin-optimizer api                  # dashboard at http://127.0.0.1:8000/ (after building web/, see web/README.md)
                                             # API docs at /docs. Read-only, localhost only.

# AI agents (MCP). Run from anywhere; --directory makes the relative data/ folder resolve:
claude mcp add garmin -- uv --directory /path/to/garmin-mcp run garmin-optimizer mcp

# Hermes Agent (use the absolute path to uv: the gateway service has its own PATH)
hermes mcp add garmin --command /path/to/uv --args --directory /path/to/garmin-mcp run garmin-optimizer mcp

The dashboard has no login and serves health data, so the API binds to localhost only. To view it from another machine, use an SSH tunnel: ssh -L 8000:127.0.0.1:8000 user@host. The MCP server offers status, get_day, get_range_summary, compare_periods, get_sleep_detail, get_timeseries, get_activities, list_metrics and a read-only run_sql. run_sql can read the cleaned tables and the views v_day, v_week, v_trends, but never the raw API payloads (they contain GPS coordinates and your name). Intraday times are returned in local time.

Privacy

  • Your password is never stored; only the session token is, in ~/.garminconnect/ (mode 600). Anyone who can read that file can access your Garmin account, so protect it.

  • data/ (database, raw dumps), .env and tokens are git-ignored. Tests use made-up payloads.

  • An AI agent that reads this data sends what it reads to its provider, and may store it in its own memory.

Development

uv sync
uv run ruff check . && uv run pytest        # Python
cd web && npm ci && npm run typecheck && npm test && npm run build   # dashboard

Adding an endpoint

Add one Endpoint entry in src/garmin_optimizer/garmin/endpoints.py. A transformer that turns the raw JSON into tables goes in src/garmin_optimizer/transform/.

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    A
    maintenance
    Downloads all your Garmin health and fitness data into a local SQLite database and exposes 45 MCP tools for AI analysis, enabling assistants to query sleep, training load, HRV, and more.
    48
    107 PyPI
    170
    AGPL 3.0
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables AI assistants to retrieve and analyze Garmin Connect health data, including daily summaries, sleep, HRV, workouts, and raw FIT files, while also supporting SQL queries on the underlying database.
    10
    -
  • 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.
    1
    Apache 2.0
  • A
    license
    A
    quality
    B
    maintenance
    Enables AI assistants to query a user's own Garmin Connect training, health, and recovery history through Garmin's official Personal API, backed by a local cache and built-in analysis tools.
    21
    379 npm
    2
    Apache 2.0