Metadata-Version: 2.4
Name: rogo-garmin-mcp
Version: 0.7.0
Summary: MCP server for Garmin Connect data and activity analysis
Project-URL: Homepage, https://gitlab.com/therogoman-public/garmin-mcp
Project-URL: Repository, https://gitlab.com/therogoman-public/garmin-mcp
Project-URL: Changelog, https://gitlab.com/therogoman-public/garmin-mcp/-/blob/main/CHANGELOG.md
License-File: LICENSE
Requires-Python: >=3.12
Requires-Dist: fastmcp>=2.0
Requires-Dist: garminconnect>=0.2.19
Requires-Dist: pydantic>=2.0
Requires-Dist: requests>=2.28
Description-Content-Type: text/markdown

# garmin-mcp

[![pipeline status](https://gitlab.com/therogoman-public/garmin-mcp/badges/main/pipeline.svg)](https://gitlab.com/therogoman-public/garmin-mcp/-/pipelines)
[![coverage report](https://gitlab.com/therogoman-public/garmin-mcp/badges/main/coverage.svg)](https://gitlab.com/therogoman-public/garmin-mcp/-/pipelines)

Read-only MCP server for Garmin Connect: exposes live data tools plus `analyze_*`
tools that cache activity data locally and compute aggregated statistics. Built on the
unofficial [`garminconnect`](https://github.com/cyberjunky/python-garminconnect) library
— see "Unofficial API — risks & rate limits" below. See [Privacy](#privacy) for
what's read, cached and stored locally.

## Tools

### Live data
| Tool | Description |
|---|---|
| `list_activities` | Recent activities (newest first, pageable), enriched summary metrics |
| `list_activities_by_date` | Activities in a date range, optional type filter |
| `get_activity_details` | Deep per-activity data: summary, HR time-in-zones, exercise sets, splits, weather, gear |
| `get_training_readiness` | Training Readiness score + factors (sleep, recovery, load balance/ACWR, stress, HRV) |
| `get_training_status` | VO2max, training-status phrase, acute/chronic load + ACWR, load focus, heat acclimation |
| `get_daily_stats` | Daily summary: steps, calories, resting HR, stress, body battery |
| `get_sleep` | Sleep summary: total/deep/light/REM/awake, SpO2, respiration |
| `get_hrv` | HRV summary: last-night average, status, baseline |

### Analysis (cache + aggregates)
| Tool | Description |
|---|---|
| `analyze_backfill` | Backfill the last N days into the local cache; rebuild aggregates. Returns counts |
| `analyze_day` | Sync one day with deep details; rebuild aggregates; return that day's summary |
| `analyze_brief` | Refresh today + return the morning brief (yesterday, today's recovery/readiness, 7/14/30-day trends); appends to the dated brief history |

`list_activities*` returns enriched fields per activity: training effect (aerobic/
anaerobic), training load, intensity minutes, HR time-in-zones, moving/elapsed time,
speed, elevation, respiration, body-battery delta, lap count and location.

The `analyze_*` tools cache raw daily data under `~/.garmin-activity/days/` and write
aggregated stats (daily/weekly summaries, morning brief + dated history) to
`GARMIN_OUTPUT_DIR` (defaults to `~/.garmin-mcp/output`).

## Requirements

- Python >= 3.12 (managed by `uv`/`uvx`)
- A Garmin Connect account
- Any OS — no platform-specific credential storage

## Unofficial API — risks & rate limits

Garmin has no official API for individuals. This server talks to Garmin Connect
through the reverse-engineered
[`garminconnect`](https://github.com/cyberjunky/python-garminconnect) library,
which drives the same endpoints the website and mobile app use. That has
consequences worth understanding before you rely on it:

- **It can break without warning.** Garmin can change or retire these endpoints
  at any time; when they do, tools may start returning errors until
  `garminconnect` (and then this server) is updated. Pin versions if you need
  stability, and expect occasional breakage.
- **Terms of Service.** Automated access to Garmin Connect is not covered by an
  official individual-developer agreement. Use this only against **your own
  account**, for personal use, and review Garmin's Terms of Service — you are
  responsible for how you use it. Abusive or high-volume use risks account
  rate-limiting or suspension.
- **Built-in pacing.** Bulk history sync (`analyze_backfill`) sleeps ~0.3s
  between days to stay gentle on Garmin's servers. Individual live-data tools
  issue only a few requests per call.
- **Keep request volume modest.** Prefer incremental syncs (`analyze_day` /
  `analyze_brief`) over repeatedly backfilling large ranges. Backfill once for
  history, then refresh a day at a time. Avoid tight loops or scheduling many
  concurrent syncs — no official rate limit is published, so err on the side of
  fewer, spaced-out requests.

## Setup

### 1. One-time interactive login

```bash
uvx --from rogo-garmin-mcp rogo-garmin-mcp-login
```

Prompts for your Garmin email, password and the emailed MFA code. Your
credentials are used **only** to obtain OAuth tokens and are never stored;
only the resumable tokens are written to `~/.garmin-mcp/tokens` (0700). The
headless server resumes from those tokens and refreshes them silently, so it
never needs your email or password again. Re-run this login if the tokens
eventually expire.

### 2. Add to Claude Code (`~/.claude/settings.json`)

Installed from PyPI (recommended):

```json
"mcpServers": {
  "garmin": {
    "type": "stdio",
    "command": "uvx",
    "args": ["rogo-garmin-mcp"]
  }
}
```

Or from a source checkout: `uv sync`, then use
`"command": "uv", "args": ["run", "--project", "/path/to/garmin-mcp", "rogo-garmin-mcp"]`.

### Client compatibility

This is a standard stdio MCP server launched with `uvx rogo-garmin-mcp`, so any
MCP client that supports the stdio transport should work. Only the Claude Code
setup is documented and exercised here; the others use the same command and are
expected to work but are not separately verified.

| Client | Status | Notes |
|---|---|---|
| Claude Code | Documented / used | Config shown in [Setup](#2-add-to-claude-code-claudesettingsjson) (`~/.claude/settings.json`), or `claude mcp add`. |
| Claude Desktop | Expected to work | Add the same `mcpServers` block to `claude_desktop_config.json`. |
| Cursor | Expected to work | Register as an MCP server (stdio): command `uvx`, args `["rogo-garmin-mcp"]`. |
| Antigravity | Expected to work | Register as an MCP server (stdio): command `uvx`, args `["rogo-garmin-mcp"]`. |
| Generic MCP clients | Expected to work | Any client speaking the MCP stdio transport; point it at `uvx rogo-garmin-mcp`. |

## Usage

Once configured, you talk to your agent in plain language and it picks the
right tool. Some prompts that map cleanly onto the tools above:

- *"What were my last few workouts?"* → `list_activities`
- *"Show the details of that strength session — reps and heart-rate zones."* → `get_activity_details`
- *"How ready am I to train today?"* → `get_training_readiness`
- *"What's my VO2max and training status?"* → `get_training_status`
- *"How did I sleep last night?"* → `get_sleep`
- *"Backfill the last 90 days, then give me my morning brief."* → `analyze_backfill` then `analyze_brief`

Tools return plain JSON. The samples below are **trimmed for illustration** —
real payloads carry more fields (see the tables above). The health-summary
tools (`get_daily_stats`, `get_sleep`, `get_hrv`) additionally omit any null
fields.

`list_activities(limit=2)`:

```json
[
  {
    "id": 12345678901,
    "name": "Morning Run",
    "type": "running",
    "start": "2026-07-06 19:12:44",
    "duration_min": 41.8,
    "distance_km": 7.35,
    "calories": 512,
    "avg_hr": 152,
    "max_hr": 176,
    "aerobic_te": 3.4,
    "anaerobic_te": 0.6,
    "te_label": "TEMPO",
    "training_load": 118,
    "location": "Riverside"
  },
  {
    "id": 12344556677,
    "name": "Strength Training",
    "type": "strength_training",
    "start": "2026-07-05 08:03:10",
    "duration_min": 24.5,
    "calories": 268,
    "avg_hr": 138,
    "vigorous_intensity_min": 12,
    "training_load": 74
  }
]
```

`get_training_readiness()`:

```json
{
  "date": "2026-07-07",
  "score": 78,
  "level": "READY",
  "feedback": "READY_TO_TRAIN",
  "recovery_time_min": 180,
  "acute_load": 302,
  "hrv_weekly_avg": 31,
  "factors": {
    "sleep": { "pct": 90, "rating": "GOOD" },
    "recovery_time": { "pct": 100, "rating": "GOOD" },
    "load_balance": { "pct": 65, "rating": "MODERATE" },
    "hrv": { "pct": 80, "rating": "GOOD" }
  }
}
```

`get_daily_stats()`:

```json
{
  "calendarDate": "2026-07-07",
  "totalSteps": 9432,
  "totalDistanceMeters": 7120,
  "totalKilocalories": 2568,
  "restingHeartRate": 52,
  "averageStressLevel": 34,
  "bodyBatteryMostRecentValue": 61
}
```

## Privacy

Your data stays on your machine. This server only ever **reads** from Garmin
Connect; the `analyze_*` tools cache what they fetch locally so trends can be
computed offline. Nothing is uploaded anywhere, and there is no telemetry.

What is stored, and where:

| Path | Contents |
|---|---|
| `~/.garmin-mcp/tokens` (dir, `0700`) | Resumable Garmin OAuth tokens. Your email/password are **never** stored — only these tokens. |
| `~/.garmin-activity/days/YYYY-MM-DD.json` | Raw per-day cache: activities, sleep, HRV, recovery, stress, readiness. |
| `$GARMIN_OUTPUT_DIR` (default `~/.garmin-mcp/output`) | Aggregated stats: daily/weekly summaries, morning brief and dated brief history. |

Set `GARMIN_OUTPUT_DIR` to relocate the aggregate output (e.g. to a directory
your agent reads from).

**Retention:** files are kept until you delete them. The cache only grows as
you backfill or analyze more days; it is never pruned automatically.

**How to wipe everything:**

```bash
rm -rf ~/.garmin-activity ~/.garmin-mcp
# and, if you set a custom output dir:
rm -rf "$GARMIN_OUTPUT_DIR"
```

Deleting `~/.garmin-mcp/tokens` also signs the server out; re-run the
interactive login to use it again.

## Development

```bash
uv run pytest                         # run tests with coverage
uv run pytest --cov-report=html       # HTML coverage report
```

## License

Apache-2.0
