garmin-mcp-server 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- garmin_mcp_server-0.1.1/.env.example +34 -0
- garmin_mcp_server-0.1.1/.github/workflows/ci.yml +37 -0
- garmin_mcp_server-0.1.1/.github/workflows/release.yml +121 -0
- garmin_mcp_server-0.1.1/.gitignore +88 -0
- garmin_mcp_server-0.1.1/CHANGELOG.md +53 -0
- garmin_mcp_server-0.1.1/LICENSE +21 -0
- garmin_mcp_server-0.1.1/PKG-INFO +432 -0
- garmin_mcp_server-0.1.1/README.md +403 -0
- garmin_mcp_server-0.1.1/garmin_mcp/__init__.py +9 -0
- garmin_mcp_server-0.1.1/garmin_mcp/__main__.py +6 -0
- garmin_mcp_server-0.1.1/garmin_mcp/client.py +127 -0
- garmin_mcp_server-0.1.1/garmin_mcp/config.py +55 -0
- garmin_mcp_server-0.1.1/garmin_mcp/helpers.py +82 -0
- garmin_mcp_server-0.1.1/garmin_mcp/login.py +75 -0
- garmin_mcp_server-0.1.1/garmin_mcp/server.py +651 -0
- garmin_mcp_server-0.1.1/pyproject.toml +70 -0
- garmin_mcp_server-0.1.1/tests/__init__.py +0 -0
- garmin_mcp_server-0.1.1/tests/test_client.py +85 -0
- garmin_mcp_server-0.1.1/tests/test_helpers.py +59 -0
- garmin_mcp_server-0.1.1/tests/test_server.py +52 -0
- garmin_mcp_server-0.1.1/uv.lock +1584 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# ---------------------------------------------------------------------------
|
|
2
|
+
# Garmin Connect MCP server configuration
|
|
3
|
+
# ---------------------------------------------------------------------------
|
|
4
|
+
# Copy this file to ".env" and fill in your values. The ".env" file is
|
|
5
|
+
# git-ignored and will NEVER be committed.
|
|
6
|
+
#
|
|
7
|
+
# cp .env.example .env
|
|
8
|
+
#
|
|
9
|
+
# RECOMMENDED: do NOT store your password here. Instead run the one-time
|
|
10
|
+
# interactive login which handles MFA and caches OAuth tokens locally:
|
|
11
|
+
#
|
|
12
|
+
# garmin-mcp-login
|
|
13
|
+
#
|
|
14
|
+
# After that, the server logs in using the cached tokens only — no password
|
|
15
|
+
# is ever needed at runtime.
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
# Your Garmin Connect account email (only needed for the initial login).
|
|
19
|
+
GARMIN_EMAIL=
|
|
20
|
+
|
|
21
|
+
# Your Garmin Connect password (optional; prefer token-based auth above).
|
|
22
|
+
# If your account has MFA enabled, password-only login will fail in the
|
|
23
|
+
# server context — use `garmin-mcp-login` instead.
|
|
24
|
+
GARMIN_PASSWORD=
|
|
25
|
+
|
|
26
|
+
# Directory where OAuth tokens are cached (garth token store).
|
|
27
|
+
# Defaults to ~/.garminconnect when unset. GARMIN_TOKEN_DIR is also accepted.
|
|
28
|
+
GARMINTOKENS=~/.garminconnect
|
|
29
|
+
|
|
30
|
+
# Set to "true" if your account is registered on garmin.cn (China).
|
|
31
|
+
GARMIN_IS_CN=false
|
|
32
|
+
|
|
33
|
+
# Optional: logging verbosity for the server (DEBUG, INFO, WARNING, ERROR).
|
|
34
|
+
GARMIN_MCP_LOG_LEVEL=INFO
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Install uv
|
|
19
|
+
uses: astral-sh/setup-uv@v5
|
|
20
|
+
|
|
21
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
22
|
+
run: uv python install ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
# Create a venv pinned to the matrix Python. Without this, `uv pip
|
|
25
|
+
# install --system` would target the runner's system interpreter and
|
|
26
|
+
# ignore the matrix version entirely.
|
|
27
|
+
- name: Create virtual environment
|
|
28
|
+
run: uv venv --python ${{ matrix.python-version }}
|
|
29
|
+
|
|
30
|
+
- name: Install dependencies
|
|
31
|
+
run: uv pip install -e ".[dev]"
|
|
32
|
+
|
|
33
|
+
- name: Lint (ruff)
|
|
34
|
+
run: uv run ruff check garmin_mcp tests
|
|
35
|
+
|
|
36
|
+
- name: Test (pytest)
|
|
37
|
+
run: uv run pytest
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# On a version tag (e.g. v0.1.0):
|
|
4
|
+
# 1. lint + test + build sdist/wheel,
|
|
5
|
+
# 2. publish a GitHub Release with auto-generated notes + artifacts,
|
|
6
|
+
# 3. publish to PyPI via Trusted Publishing (OIDC - no API tokens stored).
|
|
7
|
+
#
|
|
8
|
+
# git tag -a v0.1.0 -m "v0.1.0"
|
|
9
|
+
# git push origin v0.1.0
|
|
10
|
+
#
|
|
11
|
+
# One-time PyPI setup (see README "Releasing"): register this repo + the
|
|
12
|
+
# "pypi" environment as a Trusted Publisher at
|
|
13
|
+
# https://pypi.org/manage/account/publishing/
|
|
14
|
+
|
|
15
|
+
on:
|
|
16
|
+
push:
|
|
17
|
+
tags:
|
|
18
|
+
- "v*"
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
build:
|
|
22
|
+
name: Build & test
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
permissions:
|
|
25
|
+
contents: write # create the GitHub Release
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v4
|
|
28
|
+
with:
|
|
29
|
+
fetch-depth: 0 # full history so we can diff against the previous tag
|
|
30
|
+
|
|
31
|
+
- name: Install uv
|
|
32
|
+
uses: astral-sh/setup-uv@v5
|
|
33
|
+
|
|
34
|
+
- name: Set up Python
|
|
35
|
+
run: uv python install 3.12
|
|
36
|
+
|
|
37
|
+
- name: Create virtual environment
|
|
38
|
+
run: uv venv --python 3.12
|
|
39
|
+
|
|
40
|
+
- name: Install project (with dev extras)
|
|
41
|
+
run: uv pip install -e ".[dev]" build
|
|
42
|
+
|
|
43
|
+
- name: Lint
|
|
44
|
+
run: uv run ruff check garmin_mcp tests
|
|
45
|
+
|
|
46
|
+
- name: Test
|
|
47
|
+
run: uv run pytest
|
|
48
|
+
|
|
49
|
+
- name: Build sdist and wheel
|
|
50
|
+
run: uv run python -m build
|
|
51
|
+
|
|
52
|
+
- name: Verify version matches the tag
|
|
53
|
+
run: |
|
|
54
|
+
TAG="${GITHUB_REF_NAME#v}"
|
|
55
|
+
PKG="$(uv run python -c 'import garmin_mcp; print(garmin_mcp.__version__)')"
|
|
56
|
+
echo "tag=$TAG package=$PKG"
|
|
57
|
+
if [ "$TAG" != "$PKG" ]; then
|
|
58
|
+
echo "::error::Tag ($TAG) does not match package version ($PKG)"
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
- name: Generate release notes
|
|
63
|
+
run: |
|
|
64
|
+
set -euo pipefail
|
|
65
|
+
CURRENT="${GITHUB_REF_NAME}"
|
|
66
|
+
PREV="$(git describe --tags --abbrev=0 "${CURRENT}^" 2>/dev/null || true)"
|
|
67
|
+
{
|
|
68
|
+
echo "## What's changed in ${CURRENT}"
|
|
69
|
+
echo
|
|
70
|
+
if [ -n "$PREV" ]; then
|
|
71
|
+
echo "Changes since ${PREV}:"
|
|
72
|
+
echo
|
|
73
|
+
git log --pretty=format:'- %s (%h)' "${PREV}..${CURRENT}"
|
|
74
|
+
echo
|
|
75
|
+
echo
|
|
76
|
+
echo "**Full diff:** https://github.com/${GITHUB_REPOSITORY}/compare/${PREV}...${CURRENT}"
|
|
77
|
+
else
|
|
78
|
+
echo "First release."
|
|
79
|
+
echo
|
|
80
|
+
git log --pretty=format:'- %s (%h)'
|
|
81
|
+
fi
|
|
82
|
+
} > RELEASE_NOTES.md
|
|
83
|
+
echo "Generated release notes:"
|
|
84
|
+
cat RELEASE_NOTES.md
|
|
85
|
+
|
|
86
|
+
- name: Upload build artifacts
|
|
87
|
+
uses: actions/upload-artifact@v4
|
|
88
|
+
with:
|
|
89
|
+
name: dist
|
|
90
|
+
path: dist/*
|
|
91
|
+
|
|
92
|
+
- name: Publish GitHub Release
|
|
93
|
+
uses: softprops/action-gh-release@v2
|
|
94
|
+
with:
|
|
95
|
+
body_path: RELEASE_NOTES.md
|
|
96
|
+
files: |
|
|
97
|
+
dist/*.whl
|
|
98
|
+
dist/*.tar.gz
|
|
99
|
+
fail_on_unmatched_files: true
|
|
100
|
+
|
|
101
|
+
pypi-publish:
|
|
102
|
+
name: Publish to PyPI
|
|
103
|
+
needs: build
|
|
104
|
+
runs-on: ubuntu-latest
|
|
105
|
+
environment:
|
|
106
|
+
name: pypi
|
|
107
|
+
url: https://pypi.org/p/garmin-mcp-server
|
|
108
|
+
steps:
|
|
109
|
+
- name: Download build artifacts
|
|
110
|
+
uses: actions/download-artifact@v4
|
|
111
|
+
with:
|
|
112
|
+
name: dist
|
|
113
|
+
path: dist
|
|
114
|
+
|
|
115
|
+
- name: Publish to PyPI
|
|
116
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
117
|
+
with:
|
|
118
|
+
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
119
|
+
# Attestations require OIDC (id-token: write); disable them when
|
|
120
|
+
# authenticating with an API token instead of Trusted Publishing.
|
|
121
|
+
attestations: false
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# ---------------------------------------------------------------------------
|
|
2
|
+
# SECURITY: never commit credentials or Garmin auth tokens
|
|
3
|
+
# ---------------------------------------------------------------------------
|
|
4
|
+
.env
|
|
5
|
+
.env.*
|
|
6
|
+
!.env.example
|
|
7
|
+
*.env
|
|
8
|
+
secrets.*
|
|
9
|
+
credentials.*
|
|
10
|
+
|
|
11
|
+
# Garmin Connect OAuth token stores (garth dumps oauth1/oauth2 tokens here)
|
|
12
|
+
.garminconnect/
|
|
13
|
+
.garth/
|
|
14
|
+
tokens/
|
|
15
|
+
*.garth
|
|
16
|
+
oauth1_token.json
|
|
17
|
+
oauth2_token.json
|
|
18
|
+
garmin_tokens/
|
|
19
|
+
|
|
20
|
+
# Downloaded activity files (may contain GPS / personal data)
|
|
21
|
+
downloads/
|
|
22
|
+
*.fit
|
|
23
|
+
*.tcx
|
|
24
|
+
*.gpx
|
|
25
|
+
*.zip
|
|
26
|
+
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
# Python
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
__pycache__/
|
|
31
|
+
*.py[cod]
|
|
32
|
+
*$py.class
|
|
33
|
+
*.so
|
|
34
|
+
.Python
|
|
35
|
+
build/
|
|
36
|
+
develop-eggs/
|
|
37
|
+
dist/
|
|
38
|
+
eggs/
|
|
39
|
+
.eggs/
|
|
40
|
+
lib64/
|
|
41
|
+
parts/
|
|
42
|
+
sdist/
|
|
43
|
+
var/
|
|
44
|
+
wheels/
|
|
45
|
+
*.egg-info/
|
|
46
|
+
.installed.cfg
|
|
47
|
+
*.egg
|
|
48
|
+
MANIFEST
|
|
49
|
+
|
|
50
|
+
# Virtual environments
|
|
51
|
+
.venv/
|
|
52
|
+
venv/
|
|
53
|
+
env/
|
|
54
|
+
ENV/
|
|
55
|
+
.python-version
|
|
56
|
+
|
|
57
|
+
# Test / coverage
|
|
58
|
+
.pytest_cache/
|
|
59
|
+
.coverage
|
|
60
|
+
.coverage.*
|
|
61
|
+
htmlcov/
|
|
62
|
+
.tox/
|
|
63
|
+
.nox/
|
|
64
|
+
coverage.xml
|
|
65
|
+
*.cover
|
|
66
|
+
.cache/
|
|
67
|
+
|
|
68
|
+
# Type checkers / linters
|
|
69
|
+
.mypy_cache/
|
|
70
|
+
.dmypy.json
|
|
71
|
+
.ruff_cache/
|
|
72
|
+
.pyright/
|
|
73
|
+
|
|
74
|
+
# IDE / editor
|
|
75
|
+
.idea/
|
|
76
|
+
.vscode/
|
|
77
|
+
*.swp
|
|
78
|
+
*.swo
|
|
79
|
+
*~
|
|
80
|
+
.DS_Store
|
|
81
|
+
|
|
82
|
+
# Logs
|
|
83
|
+
*.log
|
|
84
|
+
|
|
85
|
+
# Local tooling / agent artifacts (not part of the project)
|
|
86
|
+
.claude/
|
|
87
|
+
.tldr/
|
|
88
|
+
.tldrignore
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **Auth for garminconnect 0.3.2.** Authentication now delegates entirely to
|
|
12
|
+
garminconnect's own `login(tokenstore)`, which natively loads/refreshes
|
|
13
|
+
Garmin DI session tokens (the `{di_token, di_refresh_token, di_client_id}`
|
|
14
|
+
mobile token format), a token directory, or email/password — and persists
|
|
15
|
+
fresh tokens. Removes the obsolete `.garth` attribute access that broke on
|
|
16
|
+
0.3.2. `garmin-mcp-login` now uses garminconnect's `prompt_mfa` flow.
|
|
17
|
+
Verified live against a real account (curated + auto-exposed tools).
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- **Full garminconnect 0.3.2 API coverage.** In addition to the 48 curated
|
|
21
|
+
tools, every remaining public `garminconnect` method is now auto-registered as
|
|
22
|
+
a tool at startup (endurance score, intensity minutes, weekly steps/stress,
|
|
23
|
+
FTP, lactate threshold, running tolerance, nutrition, golf, gear stats,
|
|
24
|
+
scheduled workouts, training plans, badge challenges, and all write/upload/
|
|
25
|
+
delete actions). The set stays in sync with the library automatically — new
|
|
26
|
+
methods become tools on upgrade.
|
|
27
|
+
|
|
28
|
+
## [0.1.0] - 2026-05-31
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
- Initial release of the Garmin Connect MCP server.
|
|
32
|
+
- 48 MCP tools spanning:
|
|
33
|
+
- **Profile & devices** — user profile, units, registered devices.
|
|
34
|
+
- **Daily health** — daily summary, steps, floors, heart rate, resting HR.
|
|
35
|
+
- **Wellness** — sleep, stress, Body Battery, SpO₂, respiration, HRV,
|
|
36
|
+
hydration, blood pressure.
|
|
37
|
+
- **Training** — readiness, status, VO₂max, fitness age, race predictions,
|
|
38
|
+
Hill Score, progress summary.
|
|
39
|
+
- **Activities** — list/by-date, summary, details, splits, weather, HR zones,
|
|
40
|
+
rename, download (FIT/TCX/GPX/CSV/KML), count.
|
|
41
|
+
- **Body** — body composition, weigh-ins, add weigh-in.
|
|
42
|
+
- **Records & more** — personal records, goals, badges, gear, workouts,
|
|
43
|
+
menstrual data, and a raw `connect_api` passthrough.
|
|
44
|
+
- Token-based authentication with MFA support via a one-time `garmin-mcp-login`.
|
|
45
|
+
- Resilient client: lazy auth, thread-safe, transparent re-authentication on
|
|
46
|
+
session expiry, friendly date inputs (`today`/`yesterday`/`-7`/ISO).
|
|
47
|
+
- Automatic trimming of large time-series payloads to stay context-friendly.
|
|
48
|
+
- Unit-test suite (mocked, offline) and a live integration script.
|
|
49
|
+
- GitHub Actions CI (lint + tests across Python 3.10–3.12) and a tag-driven
|
|
50
|
+
release workflow that builds artifacts and publishes auto-generated notes.
|
|
51
|
+
|
|
52
|
+
[Unreleased]: https://github.com/stitrace/garmin-mcp-server/compare/v0.1.0...HEAD
|
|
53
|
+
[0.1.0]: https://github.com/stitrace/garmin-mcp-server/releases/tag/v0.1.0
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 stitrace
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|