A command-line tool for extracting health, activity, workout, and performance data from Garmin Connect. Designed for both human use (table output) and LLM agent consumption (JSON output).
From a local checkout:
pip install .Verify the installation:
garmin-cli --versiongarmin-cli authenticates via the maintained python-garminconnect backend. The primary session-home surface is now GARMIN_HOME / --garmin-home; GARTH_HOME / --garth-home remains as a deprecated compatibility alias.
garmin-cli login
# Email: your@email.com
# Password: (hidden)
# Login successful. Token store saved at: ~/.garminconnect/garmin_tokens.jsonUse --email / --password to skip the prompts (useful for scripting):
garmin-cli login --email your@email.com --password yourpasswordCheck login status at any time:
garmin-cli login status
# Logged in. Token store at: ~/.garminconnect/garmin_tokens.json
garmin-cli --json login status
# {"ok": true, "command": "login status", "count": 1, "data": [{"authenticated": true, "garmin_home": "..."}]}export GARMIN_EMAIL="your@email.com"
export GARMIN_PASSWORD="yourpassword"
garmin-cli health sleep --days 1garmin-cli --garmin-home /path/to/session login
garmin-cli --garmin-home /path/to/session health sleep --days 1The default session directory is ~/.garminconnect. It is created with 0o700 permissions. Symlinks are rejected. Do not share this directory.
New sessions are stored as garmin_tokens.json inside GARMIN_HOME. If you still have an existing ~/.garth/garmin_tokens.json, the CLI will copy it into the new default home on first use. GARTH_HOME / --garth-home still work as deprecated aliases when you need to keep an older path explicitly.
All commands default to table output. Use --json or --format csv to change:
garmin-cli health sleep --days 7 # table (default)
garmin-cli --json health sleep --days 7 # JSON envelope
garmin-cli --format csv health sleep --days 7 # CSVJSON envelope structure:
{
"ok": true,
"command": "health sleep",
"date_range": {"from": "2026-03-05", "to": "2026-03-11"},
"count": 7,
"data": [
{"date": "2026-03-05", "duration_hours": 7.2, "score": 78, ...}
]
}Error envelope:
{
"ok": false,
"command": "health sleep",
"error": "Rate limited by Garmin. Try again later.",
"error_code": "RATE_LIMITED"
}Exit code is always 0 on success, 1 on error.
garmin-cli health sleep [--date DATE | --from DATE --to DATE | --days N]
garmin-cli health hrv [--date DATE | --from DATE --to DATE | --days N]
garmin-cli health weight [--date DATE | --from DATE --to DATE | --days N]
garmin-cli health body-battery [--date DATE | --from DATE --to DATE | --days N]
garmin-cli health stress [--date DATE | --from DATE --to DATE | --days N]
garmin-cli health spo2 [--date DATE | --from DATE --to DATE | --days N]
garmin-cli health resting-hr [--date DATE | --from DATE --to DATE | --days N]
garmin-cli health readiness [--date DATE | --from DATE --to DATE | --days N]
garmin-cli health status [--date DATE]garmin-cli activity list [--limit N] [--type TYPE] [--search TEXT] [--date DATE | --from DATE --to DATE | --days N]
garmin-cli activity get ACTIVITY_ID [--detail] # --detail/-d shows power, cadence, elevation, etc.
garmin-cli activity weather ACTIVITY_ID--limit defaults to 20, max 100. --type filters by activity type key (e.g., running, cycling).
# Read
garmin-cli workout list [--limit N]
garmin-cli workout get WORKOUT_ID
garmin-cli workout calendar [--from DATE --to DATE | --days N | --ahead N]
# Write
garmin-cli workout create FILE # JSON or YAML file
garmin-cli workout create --stdin # read JSON from stdin
garmin-cli workout update WORKOUT_ID FILE # partial update (only fields provided change)
garmin-cli workout delete WORKOUT_ID [--confirm] # --confirm skips interactive prompt
garmin-cli workout schedule WORKOUT_ID DATE # DATE = YYYY-MM-DD--ahead N shows the next N days of planned workouts (future-facing). --days N shows past N days.
YAML input requires pyyaml: pip install pyyaml. See SKILL.md for the full workout JSON schema reference and step/target types.
garmin-cli performance thresholds
garmin-cli performance zones
garmin-cli performance vo2maxRecent fixes normalized several JSON payloads for agent-safe output:
performance vo2maxreturnsdate,vo2max,sportperformance zonesreturnssport,lt_hr_bpm,lt_paceworkout calendarincludesidworkout getincludesstepsandsteps_summaryhealth hrvreadslastNightAvgand still falls back to legacylastNight
| Option | Meaning | Example |
|---|---|---|
--date DATE |
Single day | --date 2026-03-11 |
--days N |
Past N days (inclusive) | --days 7 |
--from DATE --to DATE |
Explicit range (both inclusive) | --from 2026-03-01 --to 2026-03-11 |
--ahead N |
Next N days (calendar only) | --ahead 7 |
Maximum range: 90 days. Conflicting options (e.g., --date with --days) produce a clear error.
# Last week of sleep data
garmin-cli health sleep --days 7
# HRV for a specific date range
garmin-cli health hrv --from 2026-03-01 --to 2026-03-10
# List recent running activities as CSV
garmin-cli --format csv activity list --limit 10 --type running
# Activities from a specific date range
garmin-cli activity list --from 2026-03-01 --to 2026-03-31
# Activities from the past 7 days
garmin-cli activity list --days 7
# Upcoming planned workouts
garmin-cli workout calendar --ahead 7
# Create a workout from a JSON file
garmin-cli --json workout create my_workout.json
# Create and schedule a workout via stdin
echo '{"name":"Easy Run","sport":"running","steps":[{"type":"interval","duration":{"type":"time","value":1800},"target":{"type":"heart.rate.zone","zone":2}}]}' \
| garmin-cli --json workout create --stdin
# Schedule an existing workout
garmin-cli --json workout schedule 12345678901 2026-04-01
# All performance thresholds
garmin-cli performance thresholds
# Agent use: JSON output for scripting
garmin-cli --json health hrv --date 2026-03-11
garmin-cli --json activity list --limit 5| Code | Meaning |
|---|---|
AUTH_MISSING |
No credentials found (no session and no env vars) |
AUTH_FAILED |
Credentials rejected by Garmin |
NOT_FOUND |
API endpoint unavailable (404) |
RATE_LIMITED |
429 after 3 retries |
SERVER_ERROR |
5xx after 3 retries |
NETWORK_ERROR |
Connection or timeout failure |
INVALID_INPUT |
Bad arguments or conflicting options |
INTERNAL_ERROR |
Unexpected error |
Expose garmin-cli as an MCP tool server (26 read-only tools) for local or remote MCP clients.
The mcp extra depends on a pinned pre-release commit of the MCP Python SDK v2, installed directly from GitHub. PyPI does not allow git-source dependencies in published packages. Until the MCP SDK publishes a stable v2 release on PyPI, garmin-cli must be installed from source.
The recommended install method is uv tool install, which places the binary in ~/.local/bin — a stable, venv-independent location that desktop applications can access without macOS sandbox issues:
uv tool install --editable "/path/to/garmin-py[mcp]"To uninstall:
uv tool uninstall garmin-cliAvoid pointing MCP clients at a binary inside a project virtualenv (e.g. .venv/bin/garmin-cli). On macOS, desktop applications run in a sandbox and cannot read pyvenv.cfg inside directories they have not been granted access to, which causes a fatal Python startup error:
PermissionError: [Errno 1] Operation not permitted: '/path/to/.venv/pyvenv.cfg'
The uv tool install approach avoids this entirely. Alternatively, grant Claude Desktop Full Disk Access in System Settings → Privacy & Security → Full Disk Access.
Add to your Claude Desktop config file:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"garmin": {
"command": "/Users/YOU/.local/bin/garmin-cli",
"args": ["mcp-server"]
}
}
}With a custom session directory:
{
"mcpServers": {
"garmin": {
"command": "/Users/YOU/.local/bin/garmin-cli",
"args": ["--garmin-home", "/path/to/.garminconnect", "mcp-server"]
}
}
}claude mcp add --transport stdio garmin -- garmin-cli mcp-serverChatGPT does not natively support MCP. To connect, run the server with an HTTP transport and use an MCP-to-OpenAI bridge such as mcp-openai-bridge or a similar proxy:
# Start the MCP server with streamable HTTP
garmin-cli mcp-server --transport streamable-http --host 127.0.0.1 --port 8000Then point the bridge at http://127.0.0.1:8000/mcp and configure it as a ChatGPT plugin or custom GPT action. Refer to the bridge project's documentation for setup details.
SSE and streamable HTTP use the MCP SDK's built-in HTTP server. By default it binds to 127.0.0.1:8000.
Streamable HTTP (recommended for remote clients):
garmin-cli mcp-server --transport streamable-http --host 127.0.0.1 --port 8000SSE (for clients that require it):
garmin-cli mcp-server --transport sse --host 127.0.0.1 --port 8000Optional HTTP flags: --sse-path, --message-path (SSE only), --streamable-http-path, --stateless-http, --json-response (streamable-http only). Use --host 0.0.0.0 only when intentionally exposing beyond localhost.
For remote clients, prefer a dedicated session directory with --garmin-home rather than exporting credentials into another process.
See SKILL.md for the full tool list and parameter reference.
pip install -e ".[dev]"
pytest tests/ # unit tests (730+ tests)
pytest tests/ --e2e # unit + e2e tests (requires GARMIN_HOME/garmin_tokens.json)To run MCP server tests, also install the mcp extra:
pip install -e ".[dev,mcp]"E2E tests make real Garmin Connect API calls. They require a valid garmin_tokens.json session in ~/.garminconnect (or GARMIN_HOME; GARTH_HOME still works as an alias). Set E2E_RATE_LIMIT_SECONDS (default: 5) to adjust the inter-request delay.