Skip to main content
Glama
README.md
# MCP Server for garth-ng

A thin MCP server layer over the [garth-ng](https://pypi.org/project/garth-ng/) library, exposing your own Garmin Connect data to an LLM via the [Model Context Protocol](https://modelcontextprotocol.io).

> **Disclaimer:** This is an unofficial, personal project, not affiliated with or endorsed by Garmin. It uses `garth-ng`, which talks to undocumented, reverse-engineered Garmin Connect endpoints. Use at your own risk, with your own account and credentials.

## Project Structure

```
mcp-garmin/
├── src/
│   ├── config.py            # Central config: env vars, validated on import
│   ├── server_instance.py  # Creates the shared MCPServer instance
│   ├── mcp_server.py       # Composition root: wires up the server + tools
│   ├── tools/               # One module per MCP tool
│   │   ├── _shared.py        # local_iso: normalizes garth's inconsistent timestamp types
│   │   ├── ping.py
│   │   ├── sleep.py
│   │   ├── hrv.py
│   │   ├── activity.py
│   │   ├── strength.py
│   │   ├── steps.py
│   │   ├── calories.py
│   │   ├── weight.py
│   │   └── training_readiness.py
│   ├── garmin_client.py    # Garth login/session handling + data access
│   ├── main.py              # Entry point (starts the server over stdio)
│   └── explore_sleep.py     # Standalone script for manually testing the Garth integration
├── tests/                    # Mirrors src/, mocks the garth boundary
│   └── tools/
│       ├── test_ping.py
│       ├── test_sleep.py
│       ├── test_hrv.py
│       ├── test_activity.py
│       ├── test_strength.py
│       ├── test_steps.py
│       ├── test_calories.py
│       ├── test_weight.py
│       └── test_training_readiness.py
├── .env.template            # Template for credentials and settings
├── .pylintrc
├── pytest.ini
├── requirements.txt
├── requirements-dev.txt     # Adds pytest/pytest-cov on top of requirements.txt
├── LICENSE
└── README.md
```

## Installation

```bash
# Create and activate a virtual environment
python3 -m venv venv
source venv/bin/activate

# Install dependencies
pip install -r requirements.txt
```

## Setting Up Credentials

```bash
cp .env.template .env
```

Fill in your Garmin Connect credentials in `.env` (`GARMIN_USERNAME`, `GARMIN_PASSWORD`, both required). On first run, this logs in once and saves the session under `~/.garth` — after that, the saved session is reused without logging in again.

Two optional settings, with sensible defaults if omitted:
- `LOG_LEVEL` (default `INFO`) — one of `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`
- `GARTH_SESSION_DIR` (default `~/.garth`) — where the cached session is stored

## Usage

**Start the MCP server** (waits over stdio for a client, e.g. Claude Desktop):

```bash
python src/main.py
```

**For manually testing the Garth integration** (without the MCP protocol):

```bash
python src/explore_sleep.py
```

**For interactively testing the MCP server** with the [MCP Inspector](https://github.com/modelcontextprotocol/inspector):

```bash
npx -y @modelcontextprotocol/inspector ./venv/bin/mcp run src/mcp_server.py:server
```

Opens a browser tab with the Inspector UI, where the available tools can be called directly.

> **Known issue:** `mcp dev src/mcp_server.py:server` (the CLI's own dev command) currently fails — it spins up an isolated `uv run --with mcp==<version>` environment that's missing the `cli` extras `mcp run` itself needs. The command above works around this by pointing the Inspector directly at this project's own virtual environment instead.

## Running Tests

```bash
pip install -r requirements-dev.txt
pytest -v
pytest --cov=src --cov-report=term-missing  # with coverage
```

Tests mock the `garth` boundary (no real network calls or credentials needed) and live under `tests/`, mirroring the `src/` structure.

## Deployment (Hermes on the ThinkCentre)

This repo is a development repo only — nothing runs from it. Hermes runs the server from a
read-only release built from a **committed** ref:

```bash
git commit …                                  # changes must be committed to be deployable
sudo hermes-deploy mcp-garmin --restart       # → /opt/hermes-apps/mcp-garmin/releases/<sha>, restarts the gateways
sudo hermes-deploy --rollback mcp-garmin      # back to the previous release
```

Hermes registration (`mcp_servers` in the profile's `config.yaml`, runs as user `hermes`):

```yaml
mcp-garmin:
  command: /opt/hermes-apps/mcp-garmin/current/.venv/bin/python
  args: [/opt/hermes-apps/mcp-garmin/current/src/main.py]
  env:
    GARMIN_USERNAME: ${GARMIN_USERNAME}
    GARMIN_PASSWORD: ${GARMIN_PASSWORD}
    GARTH_SESSION_DIR: ${GARTH_SESSION_DIR}   # /home/hermes/.garth
```

`hermes-deploy` and the overall layout are documented in the `tc-ops` repo.

## Available Tools

| Tool | Description |
|---|---|
| `ping` | Health check, returns `"pong"` |
| `get_sleep_summary(target_date)` | Summary of sleep data for a date: total sleep time, sleep phases, scores, respiration rate, and more |
| `get_hrv_summary(target_date)` | Overnight heart rate variability: last-night/weekly averages and Garmin's status label (e.g. `BALANCED`) |
| `get_activity_list(target_date, period)` | Overview (id, timestamp, type) of activities in the `period` days up to `target_date` |
| `get_activity_detail(activity_id)` | Full detail for one activity: distance, duration, pace, heart rate, power, cadence, and more, depending on activity type |
| `get_strength_exercises(activity_id, include_rest=False)` | Sets of a strength training activity: exercise (category/name), repetitions, weight in kg, duration and local start time; REST sets only on request |
| `get_calorie_summary(target_date, period=1)` | Daily calorie expenditure for the `period` days (1-31) up to `target_date`: total = active + resting (BMR), plus the Garmin calorie goal; for today additionally the whole-day projection shown on the watch (resting calories scaled to 24 h + active so far) |
| `get_steps_summary(target_date, period)` | Daily step counts, distance, and step goal for each day in the `period` days up to `target_date` |
| `get_weight_summary(target_date)` | Weigh-in data for a date: weight, BMI, body composition (fat/water/bone/muscle), if measured |
| `get_training_readiness_summary(target_date)` | Garmin's most recent training readiness score and its contributing factors (sleep, HRV, recovery time, acute load, stress) for a date |

All dates are ISO 8601 (`YYYY-MM-DD`).

## License

[MIT](LICENSE)

TDQS

C2.9/5.0

Scored across 40 tools

Disambiguation2/5

Many tools have unclear or overlapping boundaries: get_sleep, get_sleep_detail, and get_sleep_summary all appear to return daily sleep data with no crisp distinction, while get_training_readiness and get_morning_readiness are explicit duplicates. The many body battery/stress and daily/weekly variants further blur selection.

Naming Consistency4/5

All tools follow a consistent get_<domain> snake_case pattern, which is predictable. However, suffixes like _detail, _summary, _history, and _status are used inconsistently, and duplicate alias names add minor confusion.

Tool Count2/5

40 tools is well beyond the typical well-scoped range and feels heavy for a read-only health data server. Many endpoints could be consolidated into parameterized fetch functions (e.g., period, date range, and detail level).

Completeness4/5

The server covers a broad range of Garmin health data: weight, heart, HRV, sleep, stress, training, steps, hydration, activities, records, devices, nutrition, and goals. It is mostly complete for read-only access, though a few history variants and some single-day-only metrics are missing.

Maintenance

ActivityMaintained
ResponsivenessNo issues