Skip to main content
Glama

Garmin Local Archive

Platform License No Cloud Release

Archive and analyze your Garmin Health data local.

Privacy first — inspired by European principles.


A look at the app

Home · Files · Settings · Chat · MCP Server — click any tab to view full size.


Related MCP server: garmin-givemydata

Download

Version

Description

Requires

Garmin_Local_Archive_Standalone.zip

Recommended — no setup needed

Nothing

Garmin_Local_Archive.zip

Standard version

Python 3.10+

No install, no terminal. Download, unzip, run. Standard version: install dependencies first — pip install -r requirements.txt.

Both versions (v1.7.2.4): an Update button appears next to the usual update notification once a new version is available — downloads, verifies, and applies it in place. Verified against a published checksum before anything is touched; if that check fails, nothing is changed. There's also an opt-in "Auto-apply updates in Daily Sync" setting for unattended updates via the scheduled daily sync. (v1.7.2.4.1) the Standalone version restarts itself automatically afterward, and if the new version fails to start, it's automatically rolled back to the previous working version — no manual recovery needed. The standard version applies the update the same way it always has: it closes, and you start it again yourself.


Try it first

No Garmin account yet, or just want to see it in action? A synthetic 180-day demo archive (fictional data, clearly marked as such) lets you explore dashboards, Chat and the MCP server without connecting a real account. See QUICKSTART.txt, "Try it first".


Project status & disclaimer

GNU General Public License v3.0 — provided as-is, without warranty of any kind, express or implied.

  • Not an official Garmin product: This tool is not affiliated with, endorsed, or supported by Garmin.

  • Not medical advice: All health metrics, reference ranges, and dashboard data are for personal informational use only — not a substitute for medical advice.

  • AI and health data — handle with care: If you use an external AI service (ChatGPT, Claude, Gemini) to interpret your data: never upload documents containing your name, date of birth, or other identifying information. Cloud AI services store what you send — linked to your account. Use a local model (Ollama) or at minimum a session without login. This includes the app's own in-app Chat tab — its optional Cloud backend (Anthropic/OpenAI, v1.7.2) sends your question and any archive data the model requests to that provider's API, same as pasting into their website; the Ollama backend stays fully local, always. AI responses on health topics are statistically generated — not medically validated. Treat them as a first orientation, not a conclusion.

  • Context data: Weather data is provided by Open-Meteo and Brightsky (DWD), pollen data and air quality data by Open-Meteo — accuracy and availability are not guaranteed. Air quality data (CAMS dataset) is available from approximately 2020 onwards.

  • Early stage: Core functionality is stable. APIs and internal structure may still change.

  • No guaranteed support: Development happens when time and interest allow.

  • Use at your own risk: I am not responsible for data loss or Garmin account issues.

  • Feedback welcome: If something feels off — logic, structure, results — open an issue.

Scope & limitations: Local-first, personal use, no enterprise ambitions.

  • Relies on python-garminconnect. Since Garmin does not offer a public API for personal use, moste non-commercial tools share this dependency. To mitigate unannounced upstream changes, GLA automatically detects and logs structural API shifts.

  • Local test suites cover the full pipeline plus a separate build-output validation suite — no automated build/test CI yet; CodeQL security scanning runs via GitHub Actions on every push/PR to main

  • HTML dashboards require a one-time internet connection to download Plotly (~3 MB) — cached locally after that

  • Per-day checkpointing: an interrupted sync resumes from the last completed day, no full re-sync required

  • Historical data quality depends on Garmin servers

This project is built for my own use. If it happens to be useful to others, feel free to use it — but evaluate it like any other unverified open-source tool.

What this is not: Garmin Connect is still required — the app pulls data from there via API.

A note on cloud folders: the archive itself is stored as plaintext on disk — if garmin_data/ lives inside a cloud-synced folder, that data gets uploaded automatically. See SECURITY.md for details and the encrypted Mirror alternative. This tool does not replace Connect, the Garmin app, or your device sync. It has no cloud component, no remote access, and no sharing features. The GUI and EXE are Windows-only.


Why this exists

I wanted to ask an AI questions about my health data without sending that data to another cloud service. So I built a local alternative instead.

There's a second reason that matters more over time: Garmin silently degrades intraday data resolution. Empirical analysis of archive data (April–June 2026) shows the threshold at approximately 120–135 days, and appears to trend lower over time. Once full resolution is lost, it's gone permanently. This tool exists to capture it while it's still available.

What "intraday resolution" actually means in practice:

Metric

API resolution

Data points / day

Heart Rate

~1 minute

up to 1,440

Stress

~3 minutes

up to 480

Body Battery

~15 minutes

up to 96

SpO2

~1 hour

up to 24

Respiration

variable

variable

After ~120 days, Garmin stops serving this data entirely. The daily summary (resting HR, average stress, etc.) remains — but the curves, the detail, the full timeline: gone. GLA captures it while it's still there.

→ For the full story, see MINDSET.md.


What makes this different

This project is as much a statement as it is a tool.

This is not a data export script — it maintains a complete, consistent local copy of your Garmin data over time. Your data stays in open formats, readable and analyzable with any tool you choose. Local AI, cloud AI, or no AI at all. Your data, your call.

Feature

Garmin Connect

Cloud-AI Bridges

Garmin Local Archive

Data storage

Garmin servers (USA)

US AI servers

Your machine

Privacy risk

Medium

High (training data risk)

Minimal

Access

Online only

Requires subscription

100% offline

History

Erodes over time

Depends on source

Permanent local copy


AI-assisted development

I can't write Python. The architecture, module boundaries, and decisions are mine. Every line of code is Claude's.

→ How this collaboration actually worked — who had which idea, where Claude was wrong — is documented in MINDSET.md.


How it works

The app works in two modes: live sync pulls recent data directly from Garmin Connect via API; Bulk Import loads your complete history from a Garmin GDPR export ZIP — this is the primary path for recovering years of data that the API no longer serves.

Everything is stored locally in structured formats (JSON, Excel, HTML dashboards). Once downloaded, nothing is transmitted anywhere.


Bulk Import

The GDPR export from Garmin contains your complete daily history — but in our testing, no intraday data was found (no heart rate curves, no stress timelines, no body battery graphs). That resolution appears to be available only through the API, and only for recent days.

The Bulk Import feature fills in the rest: request your full data export from Garmin (typically ready in 20–30 minutes), point the app at the ZIP, and your complete daily history lands in the local archive — in the same format as live API data. Days already present with good quality are skipped automatically.


Dashboards

The built-in dashboards cover roughly 90% of what most users are looking for — without any AI at all. For deeper analysis, your data is prepared in a format any local AI can work with directly.

Dashboard

What it shows

Output

Health Analysis

HRV, Resting HR, SpO2, Sleep, Body Battery, Stress — daily values vs 90-day personal baseline vs age/fitness-adjusted reference ranges. Flags days outside range.

HTML, Mobile HTML, JSON + AI prompt

Timeseries

Intraday heart rate, stress, SpO2, body battery and respiration as zoomable charts across any date range.

HTML, Excel

Heatmap

Six intraday metrics (Heart Rate, Steps, Stress, Body Battery, SpO2, Respiration) as time-of-day × date grids — spot daily rhythms and irregularities at a glance.

HTML

Daily Overview

All summary fields in one flat table, one row per day.

Excel

Health + Context

Garmin health metrics alongside local weather and pollen data.

HTML, Excel

Sleep Dashboard

One row per night — segmented phase bar (Deep / Light / REM / Awake), sleep duration, score, quality badge, feedback label, HRV, Body Battery, and 7-day HRV moving average (computed from archive, no extra API call). Color-coded numbers via continuous gradient against personal reference ranges. Inspired by Garmin's own HRV pattern guide.

HTML, Excel

Sleep & Recovery

HRV, Body Battery, Sleep duration and phase breakdown (Deep / Light / REM / Awake) alongside weather and pollen context. Intraday detail per day.

HTML

Explorer

Free metric exploration — choose up to 4 metrics from all Garmin daily fields plus weather, pollen, and air quality on a shared time axis. Sleep phase breakdown and sleep quality log included. Built-in field descriptions and air quality interpretation guide.

HTML

Custom Dashboard

Pick any combination of Garmin daily fields and Context fields, set a date range, and build a one-off dashboard — no fixed field list, no specialist file written to disk. Field selections can be saved as named presets for reuse. Optional AES-256 encryption for the HTML output.

HTML, Excel

Live Tracking (v1.6.5) — a separate, always-current view: today's progression (Body Battery, Heart Rate, Steps, Stress) plus last night's sleep summary, refreshed automatically after every sync and on demand via an "Update Live" button in the Home tab. Not part of the Create Reports selection above — it has its own trigger, by design.


Chat

A native chat panel is built into the app (Chat tab, v1.6.6, renamed from "Ollama-Chat" in v1.7.2) — no separate setup beyond having Ollama itself installed and a model pulled, or a Cloud API key configured. Two independent choices, made before Start:

  • Backend — a local Ollama model (fully local, no setup beyond Ollama itself), or a Cloud LLM (Anthropic or OpenAI, v1.7.2 — requires an API key, configured on the MCP Server tab; see the AI disclaimer above for what that means for your data).

  • Source — the archived daily-summary snapshot (fast, no live query, the original v1.6.6 behavior), or live MCP tool-calling (v1.7.2) — the model queries your archive on demand via the same MCP server external tools use, for questions the daily snapshot alone can't answer.

Replies stream in as they're generated for every combination except Ollama + MCP tool-calling (Ollama's own streaming support for tool-calling is not yet reliable upstream, so that one path waits for the complete answer). Chat sessions auto-save and can be reloaded later via the Chat History button — a session using live MCP data can always be resumed; a session using the daily snapshot can only be resumed if that snapshot hasn't changed since, otherwise it opens read-only. For connecting external tools (Open WebUI, AnythingLLM, Claude Desktop) for more advanced document/RAG workflows instead, see docs/README_APP.md and the MCP Server section below.


MCP Server

Garmin Local Archive exposes your archive to local LLMs via the Model Context Protocol — ask an LLM about your health and context data directly, without exporting files or copy-pasting into a chat window. Runs as its own standalone process, independent of the main app.

Two ways to run it: a ▶️ Start MCP Server button on the app's own MCP Server tab (uses the same archive path and settings as the rest of the app), or the standalone mcp_server.exe — a self-contained tool with its own small window, usable even without the main app installed.

Running Open WebUI (or any other MCP client) inside Docker? An opt-in "Extra allowed hosts" field on the MCP Server tab (v1.7.0.2) lets you add host.docker.internal — pre-filled by default — to the server's allowed- host list, since the underlying MCP SDK otherwise rejects connections that don't arrive as 127.0.0.1/localhost.

Works with Ollama (fully local, default) or an optional cloud LLM backend — your choice, no default push toward either. Point your MCP-compatible client (Claude Desktop, Open WebUI, or similar) at the server and start asking questions.

Connect: Streamable HTTP at http://127.0.0.1:<port>/mcp (default port 8756, configurable on the MCP Server tab) — bound to localhost only, not reachable from other machines by default. See "Extra allowed hosts" above for Docker clients.

Tools exposed (see docs/REFERENCE_MCP.md for full signatures):

Tool

What it does

query_health

Daily health metrics (e.g. sleep, stress, heart rate, body battery) for a date or date range

query_context

Weather, pollen and air-quality data for a date or date range

query_fit_activities

FIT activity data — planned, not yet available (see docs/ROADMAP.md, "FIT Pipeline")

query_raw

Unprocessed archive data for a given domain, for deeper inspection

get_archive_metadata

Archive coverage and quality info (date ranges, missing days, data quality)

list_available_fields

Which fields the archive actually contains, per domain

refresh_cache

Rebuilds the server's local query cache on demand, e.g. right after a sync

Example: "How did my sleep and stress compare over the last two weeks?" — the model calls the tools above itself and answers from your real archive, no manual export needed. No archive yet? See Try it first above.


Architecture

Token security & Login

Garmin login works via SSO — logging in with email and password on every run triggers Captcha or MFA. The solution: log in once manually, and Garmin returns an OAuth token that handles all subsequent runs for approximately one year. This token is equivalent to a logged-in session and must not sit unprotected on disk.

The token is encrypted at rest. Details on the encryption design and threat model: SECURITY.md


Pipeline

Live sync and Bulk Import both flow through the same validation and quality pipeline before anything lands in the local archive — the diagram below shows the full picture.

TIP

Pipeline Architecture: For a detailed view of the v1.3.4 data flow including the validation layer and self-healing loop, open screenshots/flowchart_v134.html in your browser.


System Architecture

The diagram below shows how all components relate to each other as of v1.6.x — from API ingestion and context collection through the broker layer to dashboard export. The broker layer also includes gateway_map (v1.6.7), a cross-domain routing layer used by the local MCP server (v1.7, see above) among other consumers — existing dashboards are unaffected and continue to query health_map/context_map directly.

System Architecture v1.6.x


What is included

The project is structured into five focused layers — Garmin pipeline, Context pipeline, Data brokers, Dashboard layer, Desktop app. Each layer has a single responsibility — collect, validate, assess, broker, or render. No crossover between layers.

The diagram above shows how the layers connect. Each module is self-contained and designed to be extended — for a script-by-script reference (what each module does, owns, and how to add new ones), see docs/MAINTENANCE_GLOBAL.md.

The desktop app includes a Background Timer — once started, it automatically repairs failed/incomplete days, upgrades bulk-imported days within Garmin's intraday resolution window (~120 days), fills missing days, keeps a raw API-response backup current, retroactively adds newly supported data fields (like step count) to already-archived days, and re-fetches bulk-imported days for the handful of data fields the official Garmin export never contains at all (HRV, SpO2, Body Battery, respiration, training status, race predictions, max metrics) — regardless of how old the day is, since that gap has nothing to do with intraday resolution — with no further manual steps in between. The timer must be started manually and only runs while the app is open — it does not resume automatically after a restart.

Data is stored in two root folders:

garmin_data/
├── raw/        – complete API dumps (~500 KB/day) — permanent archive / basis for dashboards and analysis
├── source/     – unmodified API responses (~250 KB/day) — replay-safe intraday backup
├── summary/    – compact daily JSONs (~2 KB/day)  — basis for dashboards and analysis
└── log/        – session logs, quality register, encrypted token

context_data/
├── weather/summary/     – daily weather archive (Open-Meteo)
├── pollen/summary|raw/  – daily / hourly pollen archive (Open-Meteo Air Quality)
├── brightsky/summary|raw/    – daily / hourly weather archive (Brightsky DWD)
└── airquality/summary|raw/   – daily / hourly air quality archive (Open-Meteo Air Quality)

See docs/MAINTENANCE_GLOBAL.md for full technical documentation, how to add new fields, troubleshooting, and developer notes.


Testing

Sixteen test suites cover the full pipeline — no network, no API required (the Chat tab's Cloud LLM/MCP-tool-calling tests mock every SDK/HTTP call, same as everything else here):

python tests/test_local.py                    # Garmin pipeline
python tests/test_local_context.py            # Context pipeline (external APIs mocked)
python tests/test_dashboard.py                # Dashboard pipeline
python tests/test_broker.py                   # Broker layer (health_map / gateway_map routing, metadata_map)
python tests/test_mcp.py                      # MCP layer (mcp_map protocol translation)
python tests/test_app_logic.py                # App layer (entry points, path resolution)
pytest tests/test_qt_app.py                   # PyQt6 App layer
python tests/test_static.py                   # ruff + bandit + regression guards
python tests/test_build_output.py             # Build output validation (run after build)
pytest tests/test_cloud_llm.py                # Chat tab — Cloud LLM connector (v1.7.2)
pytest tests/test_mcp_tool_chat.py            # Chat tab — Ollama + MCP tool-calling (v1.7.2)
pytest tests/test_cloud_tool_chat.py          # Chat tab — Cloud + MCP tool-calling, streaming (v1.7.2)
pytest tests/test_chat_session_store.py       # Chat tab — session save/load/resume (v1.7.2)
pytest tests/test_cloud_credential_store.py   # Chat tab — API key storage in Windows Credential Manager (v1.7.2)
pytest tests/test_mcp_process.py              # MCP Server Start/Stop process control (v1.7.2)
python tests/test_updater.py                  # T2 + T3 self-updater (v1.7.2.4, v1.7.2.4.1)

build_all.py runs test_local.py, test_local_context.py, test_dashboard.py, test_broker.py, and test_static.py as pre-build gates — a failing test aborts the build before either target is built. test_build_output.py and test_app_logic.py run automatically after both builds complete, as post-build gates. test_qt_app.py and the six Chat-tab/MCP-process suites above are run manually via pytest.

GUI changes are verified manually before release. Full CI/CD with automated builds and release packaging is planned for a later version.


⚠️ API Usage Notice: This project uses an unofficial interface. Large-scale data retrieval (e.g., syncing long time ranges in a single run) may trigger rate limiting or temporary IP blocks by Garmin (HTTP 429).

It is recommended to:

  • fetch data in smaller increments

  • include delays between requests

  • allow cool-down periods between sync sessions


Built with Claude · ☕ buy me a coffee

Available Tools

7 tools
get_archive_metadataA

Request archive-state metadata. kind selects the artefact: "stats" (coverage/quality overview — use this for "how big/healthy is my archive" questions), "device_table", "quality_log", "source_api_log", "token_log", "capability_config", "daily_logs", "fail_logs", "recent_logs".

date_from/date_to (ISO "YYYY-MM-DD", inclusive) optionally narrow "quality_log", "source_api_log", "daily_logs", "fail_logs", and "recent_logs" to a date range — ignored for the other four kinds.

v1.7.1.16 clarification (no behavior change): the 30-day-default- plus-"note" convenience described below only exists on the LIVE path (mcp_map.get_archive_metadata() -> metadata_map.py). On the SQLite-cached path (mcp_sql.get_metadata_range() — the one actually taken today, see _route_query()), omitting both dates for one of the five date-filterable kinds instead returns an empty result with no "note" at all; this is deliberate on that path (see mcp_sql.get_metadata_range()'s own docstring: "no 30-day-default fallback in this cache read"), not a bug — but the difference was previously undocumented at this public tool's own docstring level.

Live path: omit both to get the last 30 days of that kind rather than the full archive history; the response then includes a "note" field saying so. Pass both explicitly for a specific or wider range on either path.

ParametersJSON Schema
NameRequiredDescriptionDefault
kindYes
date_toNo
date_fromNo

TDQS

A3.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so well: it discloses the path-dependent behaviour (live path applies a 30-day default plus a 'note' field; the SQLite-cached path actually taken returns an empty result with no note when dates are omitted) and frames it as deliberate rather than a bug. It omits error conditions, permissions, and result shape, but the gotcha disclosure is genuinely valuable.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The core content is front-loaded, but the body is padded with a versioned changelog entry and internal call-path references (mcp_map.get_archive_metadata(), metadata_map.py, mcp_sql.get_metadata_range(), _route_query()) that belong in code comments, not a tool docstring. The actual invocation-relevant rule is buried inside that machinery discussion.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 3-parameter read tool with no output schema, it covers parameter semantics, defaults, and a non-obvious empty-result edge case, which is most of what an agent needs. Residual gaps are the unexplained non-stats kind return values, which are not inferable from the schema or annotations.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description is the only source of parameter meaning and it delivers: it lists every legal `kind` value (none present in the schema) and fully specifies date_from/date_to as inclusive ISO YYYY-MM-DD, including per-kind applicability and default behaviour. It does not explain what the less obvious kinds (e.g. 'capability_config', 'token_log') actually return.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific resource ('archive-state metadata') and enumerates the artefact kinds selectable via `kind`, with an explicit gloss on the most common one ('stats' = coverage/quality overview for size/health questions). It does not differentiate itself from siblings like query_raw or query_health, which is the main gap.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives real usage guidance for one kind ('use this for "how big/healthy is my archive" questions') and states which kinds date_from/date_to applies to versus which four ignore it. However it never says when to prefer this tool over the sibling query_* tools, so routing guidance is only partial.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_available_fieldsA

List all queryable fields, grouped by domain and source. Use this first if the set of available fields is unknown — omit domain for a full overview, or pass "health"/"context"/"fit" to narrow it.

v1.7.1.6 unit field (this session): the result gains a "units" key alongside the existing "fields" key — a flat {field_name: unit} dict covering every field returned under "fields" for the requested domain(s). Additive only: "fields" itself keeps its original shape unchanged (a nested {domain: {source: [field, ...]}} name list), so existing callers reading "fields" (e.g. mcp_context.py's _resolve_context_bundle(), which iterates the plain name lists) are unaffected. See FIELD_UNITS in mcp_field_registry.py for the unit values and their source.

ParametersJSON Schema
NameRequiredDescriptionDefault
domainNo

TDQS

A3.7/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations exist, so the description carries the full burden. It does disclose the result shape ('fields' nested name list plus an additive 'units' dict) and that the change is backward-compatible, which is real behavioral context. However, it says nothing about read-only safety, rate limits, or caching, and a large share of its behavioral text is version-scoped changelog material rather than invocation-relevant behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The first two sentences are tight, front-loaded, and exactly what an agent needs. The following paragraph is over half the definition and is largely internal engineering notes ('v1.7.1.6', 'mcp_context.py's _resolve_context_bundle()', 'FIELD_UNITS in mcp_field_registry.py') that consume space without helping selection or invocation.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description takes on the job of explaining the return shape and does so (nested {domain:{source:[...]}} plus a flat {field:unit} map). Purpose, usage trigger, and the lone parameter are all covered, so an agent can call this correctly; only the exhaustive domain list is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0% and the single 'domain' param is undocumented in the schema, so the description must compensate. It does: null/omitted yields a full overview, and it supplies three concrete narrowing values ('health', 'context', 'fit'). It stops short of enumerating the complete valid domain set, so the agent must still infer whether other values are legal.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence states a specific verb+resource ('List all queryable fields') plus its organization ('grouped by domain and source'). It implicitly distinguishes itself from the query_* siblings by being the metadata/introspection call, but it never names an alternative, so it stops short of a clean 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

'Use this first if the set of available fields is unknown' gives an explicit triggering condition, and the follow-up tells the agent how to call it (omit domain for a full overview, pass health/context/fit to narrow). There is no statement of when NOT to use it or which sibling to prefer once fields are known.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_contextC

Query external context data (weather, pollen, air quality) for a field over a date range. Fans out across all sources that recognize the field.

v1.7.1.3 field-filter fix: field is now passed through to the SQLite branch — previously it was silently dropped (this call site never forwarded it at all), so every call returned all four context categories (weather/brightsky/airquality/pollen) regardless of what was asked for, inflating a single-value answer to hundreds of KB and confusing small local LLMs summarizing the result. Same fix as query_health()'s v1.7.1.1/v1.7.1.2 field-filter, applied here with a one-session delay.

v1.7.1.4 unknown-field detection (this session): a field that is valid for query_context() but unregistered anywhere in the context domain previously returned the same silent {"context": {}} as a registered field with no data in the requested range — the caller (LLM or human) could not tell "field does not exist" apart from "field exists, no data here". This is checked BEFORE the _route_query() switch below, so the check applies regardless of which branch (sqlite/live) ends up serving the request — the field registry itself (mcp_map.list_available_fields) is unrelated to that routing decision.

Three unknown-field outcomes, checked in this order:

  1. Unambiguous near-match against the known context field names (e.g. a typo) -> auto-resolved, field_used replaces the caller's input transparently, but the substitution is always visible via _meta.field_resolved_from / _meta.field_used — never a silent rewrite.

  2. The field IS registered, but under query_health's domain, not query_context's (e.g. "sleep") -> a domain-specific error naming query_health, no did_you_mean list (a context-domain suggestion would be wrong here).

  3. Neither of the above (e.g. a category name like "weather", or no close match at all) -> a generic "unknown field" error, with a did_you_mean suggestion list when difflib found any candidates, without one when it found none.

A valid field's result (with or without data in range) is returned exactly as before this session — none of the above runs unless field is unrecognized.

v1.7.1.5 category bundles (this session): a field value naming a known bundle key ("weather"/"pollen"/"air") is resolved BEFORE any of the three unknown-field outcomes above -- a bundle name is never a registered field itself, so without this check it would always fall through to the generic "unknown field" branch. Each bundle field is queried individually through the SAME sqlite/live routing weiche used everywhere else in this function -- the bundle path only adds collection, flattening, and collision tie-breaking on top, it does not bypass or duplicate the existing data-access path. See _CONTEXT_CATEGORY_BUNDLES above for the priority-list mechanics.

v1.7.1.11 Session 4 -- resolution is decided by the field name itself, same principle as query_health(): a "_series" suffix always means intraday/timeseries data, a plain field name always means a single daily value -- no field in this archive offers both under one name, so the caller already knows which shape to expect before the query even runs. This holds regardless of which branch (sqlite/live) below ends up serving the request -- both branches return the same "values" contract (see mcp_sql.get_context_range() / clients/mcp_sql.py, and maps/_context_io.py's read_summary_field()/ read_raw_field() for the underlying {"date","value"} vs. {"date","series"} shapes).

v1.7.1.12 -- CONTEXT_FIELD_ALIASES / CONTEXT_FIELD_AMBIGUOUS checked here, BEFORE the bundle check, mirroring query_health()'s HEALTH_FIELD_ALIASES ordering (an alias hit is more certain than a near-match and should not have to pass through the bundle or difflib logic). Three outcomes now precede the pre-existing bundle/ unknown-field handling below:

  1. CONTEXT_FIELD_ALIASES hit -> auto-resolved, field_used/ field_resolved_from set, same as the alias path in query_health().

  2. CONTEXT_FIELD_AMBIGUOUS hit -> NOT resolved. Returns the existing error/did_you_mean shape with a field-specific error message and the known candidate list as did_you_mean -- no new response shape (see CONTEXT_FIELD_AMBIGUOUS's own comment for the rationale). field_used/field_resolved_from are NOT set.

  3. Neither -> falls through unchanged to the bundle check and the existing unknown-field difflib logic below. See NOTES_v1.7.1.12.md for the full candidate-by-candidate analysis behind both tables.

ParametersJSON Schema
NameRequiredDescriptionDefault
fieldYes
date_toYes
date_fromYes
resolutionNodaily

TDQS

C2.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the behavioral burden and does disclose genuinely useful traits: unknown-field detection with three ordered outcomes, transparent alias/bundle auto-resolution surfaced via _meta.field_resolved_from/_meta.field_used, and the guarantee that valid-field results are unchanged. That is substantial disclosure about error semantics and silent-rewrite prevention, even if the delivery is changelog-shaped.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness1/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is dominated by session-by-session release notes (v1.7.1.3 through v1.7.1.12), internal class/file references (mcp_sql.get_context_range(), clients/mcp_sql.py, NOTES_v1.7.1.12.md) and duplicated logic explanations that add no value for an agent. Only the first two sentences are agent-facing; the remaining bulk is noise and is not front-loaded around caller needs.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There are no annotations, no output schema and 0% schema coverage, so the description must carry everything. It does cover return shapes ({"date","value"} vs {"date","series"}) and field-resolution behavior well, but it omits date-format expectations, the 'resolution' parameter semantics, and any read-only/permission context. Partially complete but with clear gaps for a 4-parameter query tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must compensate for all four parameters. It thoroughly covers 'field' (aliases, bundles, series suffix), but date_from/date_to formats are never specified and the 'resolution' parameter (default 'daily', and how it relates to the '_series' shape) is never tied back to the schema. Two of four parameters remain semantically undocumented.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence gives a clear verb+resource+scope: 'Query external context data (weather, pollen, air quality) for a field over a date range. Fans out across all sources that recognize the field.' That is enough to distinguish it from query_fit_activities or query_raw, though it never explicitly contrasts itself with sibling query_health beyond a passing mention of its domain. The purpose is buried under heavy version-history prose.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description never states when an agent should choose query_context over query_health, query_raw, or list_available_fields. It explains internal resolution mechanics at length but offers no when-to-use / when-not-to-use context, leaving the routing decision entirely to the caller's inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_fit_activitiesA

Query FIT activity data for a field over a date range. Not yet available (FIT pipeline is v1.8) — returns a clean "not available" result until then, never an error.

ParametersJSON Schema
NameRequiredDescriptionDefault
fieldYes
date_toYes
date_fromYes
resolutionNodaily

TDQS

A3.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does add genuinely non-obvious behavior: the call succeeds with a clean 'not available' result and never errors. That prevents an agent from misreading a placeholder as real data. It still omits auth requirements, what an invalid field returns, and how resolution affects output.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, zero filler. The core purpose leads and the availability caveat, which is the single most decision-relevant fact, follows immediately.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a low-complexity, 4-parameter query tool with no output schema, the description adequately covers the behavioural quirk but leaves parameter formats (field naming, resolution values, date format) undocumented in both schema and text. An agent could call it successfully but not interpret the arguments well.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the schema documents nothing and the description must compensate. It covers the 'field' and date-range parameters implicitly but says nothing about 'resolution' (default 'daily'), its allowed values, or the expected date format, leaving the coverage gap half-filled.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource ('Query FIT activity data for a field over a date range'), which is concrete and distinct from sibling names like query_health or query_raw. It does not explicitly contrast itself with those siblings, so it loses the top tier, but the purpose is unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It discloses an important negative condition — the tool is not yet functional (FIT pipeline v1.8) and returns a placeholder rather than an error. However, it never names an alternative (e.g., query_raw) for an agent that actually needs this data now, so routing guidance is implied rather than explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_healthB

Query Garmin health data (e.g. heart rate, sleep, stress, body battery) for a field over a date range. resolution is "daily" or "intraday" — most fields only support one of the two (e.g. resting_heart_rate is daily-only, heart_rate_series is intraday-only); pass the field name that matches what you want, see list_available_fields() for the full list. This parameter is accepted for forward compatibility but not currently used to pick between two resolutions of the same field, since no field in this archive currently offers both — each field's own stored resolution already determines whether the answer is a single daily value or a full timeseries.

v1.7.1.1 field-filter fix (2026-08-28 session): field is now passed through to the SQLite branch — previously it was silently dropped, so every call returned all ~26 health fields regardless of what was asked for, including this archive's intraday *_series fields (full day-long timeseries), inflating a single-value answer to hundreds of KB and confusing small local LLMs summarizing the result.

v1.7.1.6 unit field: every field in the returned result now carries a "unit" key alongside "values"/"fallback"/ "source_resolution" — see FIELD_UNITS in mcp_field_registry.py. Applied AFTER the routing weiche below, so it covers both branches identically (today, only the SQLite branch is ever actually taken — see _route_query()'s docstring).

v1.7.1.9 unknown-field detection (this session): mirrors query_context()'s v1.7.1.4 fix, applied here with a delayed session (see that function's docstring for the original rationale -- a valid-but-dataless field and an unregistered field previously returned the identical silent {"health": {}}, leaving the caller unable to tell the two apart). Checked BEFORE the _route_query() switch below, so it applies regardless of which branch (sqlite/ live) ends up serving the request -- the field registry itself (mcp_map.list_available_fields) is unrelated to that routing decision.

Three unknown-field outcomes, checked in this order:

  1. Unambiguous near-match against the known health field names (e.g. a typo) -> auto-resolved, field_used replaces the caller's input transparently, but the substitution is always visible via _meta.field_resolved_from / _meta.field_used — never a silent rewrite.

  2. The field IS registered, but under query_context's domain, not query_health's (e.g. "temperature_max") -> a domain-specific error naming query_context, no did_you_mean list (a health-domain suggestion would be wrong here).

  3. Neither of the above (no close match, and not a query_context field either) -> a generic "unknown field" error, with a did_you_mean suggestion list when difflib found any candidates, without one when it found none.

A valid field's result (with or without data in range) is returned exactly as before this session — none of the above runs unless field is unrecognized.

Deliberately NOT addressed here (see AKTIONSPLAN_v1.7.1.9_ health_fallback.md Abschnitt 3/4 for the full analysis): a model that picks a completely unrelated but real, registered field instead of a near-match typo (verified empirically against the 2026-09-05 test run's Hermes3 cases, e.g. resting_heart_rate returned for a steps question) is not a field-registry problem — no near-match exists for the fallback to catch, since the wrong field is itself a valid, unrelated field name. Tracked as a parking-lot item (query_health docstring example-field guidance), not pulled into this fix.

v1.7.1.9 Session 2 -- sleep_score fan-out: "sleep_score" is itself an already-valid, registered field (unlike the alias candidates below), so it would never reach the unknown-field checks above -- it always short-circuits straight to the normal valid-field path. Checked here, BEFORE the bundle check, precisely because it is valid and would otherwise never trigger any of the outcomes below. Fans out to the two closely related fields sleep_score_feedback and sleep_score_qualifier and returns all three together in the same {"garmin": {field: {...}}} shape a normal multi-field result already has -- no new result shape, _enrich_with_units() handles it unchanged. _meta.field_resolved_from is set to "sleep_score" so the fan-out is visible; no field_used, since all three delivered field names are already the dict's own keys, unlike the 1:1 alias case where the substitution would otherwise be invisible. A direct call to "sleep_score_feedback" or "sleep_score_qualifier" is NOT affected -- only the exact bare "sleep_score" triggers this.

v1.7.1.9 Session 2 -- short-form alias mapping: three short-form field names (steps, hrv, hill) sit far enough below any workable difflib cutoff against their real target field names (steps_series, hrv_last_night, hill_score -- confirmed down to cutoff=0.7, see NOTES_v1.7.1.9.md Session 2) that no cutoff tuning can catch them without introducing new ambiguities elsewhere. HEALTH_FIELD_ALIASES below resolves these explicitly, checked before outcome 1's near-match logic (an alias hit is more certain than a near-match and should not have to pass through it). "spo2" was considered and explicitly excluded (real collision between spo2_avg and spo2_series, no reliable disambiguation signal available -- see NOTES_v1.7.1.9.md Session 2 for the full analysis).

ParametersJSON Schema
NameRequiredDescriptionDefault
fieldYes
date_toYes
date_fromYes
resolutionNodaily

TDQS

B3.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden, and it actually discloses meaningful behavior: transparent typo auto-resolution surfaced via _meta.field_resolved_from, three distinct unknown-field error outcomes with did_you_mean behavior, the sleep_score fan-out and its _meta marker, and the 'unit' key added to every returned field. Deeper operational traits (auth, limits, date formats, payload size bounds) remain unstated, but the error-handling disclosure is unusually rich.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The vast majority of the text is an internal changelog: version numbers (v1.7.1.1, v1.7.1.6, v1.7.1.9), session dates, source file and function names, and references to markdown planning documents. Only the first paragraph and the sleep_score/alias paragraphs serve the calling agent; the rest does not earn its place and pushes critical guidance far from the front.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 4-parameter, no-output-schema, no-annotation tool, the description covers error behavior and the multi-field result shape ({'garmin': {field: {...}}}) well enough that an agent knows what comes back. It still omits practical invocation basics — date string format, maximum range, and expected response size — which matter more here than the retained release history.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must compensate. It does explain the resolution parameter in depth (daily vs intraday, per-field restrictions, and that it is currently a no-op for routing) and the semantics of valid/aliased/unknown field values. But date_from and date_to — two of three required parameters — are given no format, range, or inclusivity guidance at all.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence gives a specific verb, resource and scope ('Query Garmin health data ... for a field over a date range') with concrete field examples, and it explicitly separates this domain from query_context's domain. However, the purpose is buried under version-history noise, and the distinction from sibling tools like query_fit_activities or query_raw is never stated.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It tells the caller to pass a field name matching the desired resolution and to consult list_available_fields(), and it implies query_context is the tool for other domains. But there is no explicit statement of when to choose query_health over query_fit_activities/query_raw/query_context, and no prerequisites or exclusions beyond the routing aside.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_rawB

Query raw, unprocessed archive data for a passthrough field over a date range. domain restricts the query to one domain ("health", "fit", "context") — omit to search all domains.

ParametersJSON Schema
NameRequiredDescriptionDefault
fieldYes
domainNo
date_toYes
date_fromYes

TDQS

B3.1/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the entire behavioral burden. 'Raw, unprocessed' hints that results aren't aggregated, but there is no mention of permissions, caching (a sibling is refresh_cache, suggesting data may be stale), rate limits, or result volume for a raw archive query. This is a meaningful gap for a data-heavy read tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with the core action and followed by the parameter caveat. Slight redundancy in 'raw, unprocessed,' but nothing else is wasted.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 4-parameter tool with 0% schema coverage, no annotations and no output schema, the description leaves too much unspecified: no date format, no field-name convention, no statement of what raw records look like. An agent can guess the shape but cannot call this reliably without trial and error.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It does explain domain and supplies the enum values ('health', 'fit', 'context') that the schema does not declare, which is genuine added value. But field, date_from and date_to receive no format, naming convention, or discovery hint (e.g., pointing at list_available_fields).

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Query raw, unprocessed archive data for a passthrough field over a date range'), which tells the agent exactly what the tool returns. It implicitly differentiates from the domain-specific siblings (query_health, query_context, query_fit_activities) by explaining that domain narrows the scope. The term 'passthrough field' is jargon that isn't defined anywhere.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The note that domain 'restricts the query to one domain... omit to search all domains' gives real usage guidance for that one parameter. However, it never states when to prefer this tool over the sibling query_health/query_context/query_fit_activities, nor prerequisites for calling it. Usage is implied rather than explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

refresh_cacheA

Manually trigger a SQLite cache sync against the archive — use this if recent archive changes (a sync just run, a backfill/recheck just completed) might not yet be reflected in query results. Runs the same sync the server already performs automatically at startup. May take a while on a large pending delta (long idle period since the last sync) — this call blocks until the sync finishes.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden and does well: it discloses that the call blocks until the sync finishes, that it may take a long time on a large pending delta, and that it reuses the startup sync path. It omits any concurrency concern (e.g. what happens if a sync is already running) and any indication of what the call returns.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The action and its trigger are front-loaded in the first sentence, with blocking behavior last. It is slightly longer than needed — the em-dash aside about backfill/recheck could be tightened — but every sentence carries real information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a parameterless, annotation-free maintenance call with no output schema, the description covers the essential agent-facing facts: what it does, when to invoke it, and that it blocks. It does not say what happens on failure or whether the call is idempotent, which are minor gaps for a zero-arg tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, which is the baseline-4 case; there is nothing in the schema for the description to clarify or compensate for.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description gives a specific verb and resource — "Manually trigger a SQLite cache sync against the archive" — and frames it as the manual counterpart to the sync the server runs at startup. This is the only maintenance action among the query_* siblings, so an agent can distinguish it without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It states the triggering conditions precisely: use when a sync, backfill, or recheck has just run and results may be stale. It does not explicitly say when to avoid calling it (e.g. during normal operation, or that query tools are the alternative), so it stops short of full when/when-not/alternatives guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 7 tool updatesv0.1.0
    • First observedget_archive_metadata
    • First observedlist_available_fields
    • First observedquery_context
    • First observedquery_fit_activities
    • First observedquery_health
    • First observedquery_raw
    • First observedrefresh_cache

TDQS

A3.7/5.0

Scored across 7 tools

Disambiguation4/5

The core split is clear: query_health vs query_context vs query_fit_activities vs query_raw are separated by domain and by processed-vs-raw semantics, which the descriptions state explicitly. Minor overlap remains because query_raw also accepts a date range, field, and domain filter, so an agent could plausibly reach for it instead of the domain-specific query tools, and query_fit_activities is a placeholder that always returns 'not available'.

Naming Consistency5/5

Every tool follows a clean snake_case verb_noun pattern (query_health, query_context, query_raw, query_fit_activities, get_archive_metadata, list_available_fields, refresh_cache). The verb varies appropriately with the operation type (query/get/list/refresh) rather than being arbitrarily inconsistent.

Tool Count5/5

Seven tools is well-scoped for a local archive reader: three domain queries, a raw passthrough, metadata, field discovery, and cache refresh. Nothing feels padded, and no obvious capability is missing for the stated scope.

Completeness4/5

Coverage of the archive lifecycle is solid — field discovery, health/context/raw queries, metadata of many kinds, and a manual sync trigger address the main workflows. The one real gap is that activity/FIT data is unreachable until v1.8, so any fitness-activity question dead-ends at query_fit_activities.

Maintenance

ActivityActive
ResponsivenessResponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    A
    maintenance
    Local-first MCP server that connects AI agents to your Garmin sleep, HRV, Body Battery, stress, training readiness and activities, keeping tokens on your machine.
    42
    262 npm
    12
    MIT
  • 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
    165 PyPI
    153
    AGPL 3.0
  • A
    license
    Not graded
    quality
    C
    maintenance
    A local MCP server that exposes your Garmin Connect data—sleep, HRV, training readiness, workouts, and more—to any MCP-compatible AI assistant. Runs entirely on your machine and keeps your Garmin credentials private.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    This MCP server exposes Garmin Connect health data—sleep, heart rate, HRV, stress, VO2max, and activities—to Claude through local tools, enabling natural language queries, data syncing, and statistical analysis like correlations and night-out detection.
    MIT