garmin-mcp-local
Provides tools to sync and query your Garmin Connect data, including activities, wellness, and health metrics, backed by a local SQLite cache.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@garmin-mcp-localshow my last 5 activities"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
garmin-mcp-local
A personal Garmin Connect MCP server: a local SQLite cache of your Garmin
data, a resumable historical backfill (from Garmin's bulk data export and/or
the live API), and a rate-limited sync so nothing gets silently dropped or
duplicated. Exposes read-only MCP tools backed entirely by the local cache,
plus one explicit sync tool that talks to the live API.
Built on garminconnect
(cyberjunky) as the only Garmin API dependency.
Why a local cache instead of calling the API every time?
Garmin Connect's API is not designed for bulk historical queries and will rate-limit (429) or block (403) you if you hit it too hard. This project fetches once, caches locally in SQLite, and answers all normal queries from that cache. A live sync is a separate, explicit step.
Related MCP server: garmin-coach-mcp
Setup
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
cp .env.example .env
# edit .env: set GARMIN_EMAIL / GARMIN_PASSWORD (only needed for the first
# login -- after that, the session token is cached under GARMIN_TOKEN_STORE
# and credentials aren't read again unless the token expires/is revoked)Nothing in .env is ever committed -- .gitignore excludes it, the SQLite
DB file, and the token cache directory. .env.example only has placeholders.
First-run backfill
1. Seed from Garmin's bulk data export (recommended -- avoids re-fetching years of history through the rate-limited API)
Request your data from Garmin: Garmin Connect → Account Settings → Export
Your Data. Garmin emails you a download link for a zip archive (despite
often being called a "CSV export" request, the archive Garmin actually
ships is JSON files organized under DI_CONNECT/ by category -- this
project parses that directly, no CSV involved).
garmin-mcp-import-export /path/to/your-export.zipThis prints a report of what was imported per category, and explicitly
flags what the export can't populate (see Known gaps
below). Re-running the import against the same (or an updated) export file
is always safe -- every table has a natural-key UNIQUE constraint and the
importer upserts, so nothing is ever duplicated.
2. Fill in anything the export didn't cover, via the live API
garmin-mcp-backfill --batch-days 30 --earliest-date 2015-01-01Each invocation walks one controlled batch further back into history per
category (rather than trying to fetch everything at once) and persists a
resume cursor, so you can run it repeatedly (e.g. on a cron job, or via
the MCP backfill_batch_now tool) until it reaches --earliest-date.
Ongoing incremental sync
garmin-mcp-syncPulls everything new since the last successful sync, for every category.
This is never triggered automatically by a query -- run it explicitly,
or wire it into a cron job / the MCP sync_now tool.
Every API call in this project goes through one rate-limited wrapper
(garmin_mcp/garmin_client/rate_limiter.py) that:
enforces a minimum delay between requests (
GARMIN_MIN_REQUEST_INTERVAL_SECONDS),retries 429/403 with exponential backoff + jitter, capped at
GARMIN_MAX_RETRIES,persists the backoff/cooldown window to
sync_log, so if a run gets rate-limited and dies, the next run (even a fresh process) sees the still-active cooldown and refuses to hammer the API again until it expires,logs a clear failure (not a silent give-up) once retries are exhausted.
Truncation is never silent: every batch fetch records records_expected
vs. records_fetched in sync_log, and anything short of a full,
unambiguous fetch is marked status='partial' with a specific warning
(e.g. which dates failed, or which paginated batch came back short without
an explicit end-of-data signal from the API).
Check SELECT * FROM sync_log ORDER BY started_at DESC LIMIT 20 (or the
MCP get_sync_status tool) any time you want to know what's actually been
fetched vs. what's stale or failed.
Scheduling (launchd)
sync and backfill only run when invoked -- to run them automatically,
three launchd LaunchAgents are provided under launchd/:
local.garmin-mcp.sync-- daily at 06:00, incremental catch-up.local.garmin-mcp.backfill-- daily at 06:30 (staggered 30 min after sync so the two never write to the SQLite DB concurrently), 30-day batches, scoped to the 7 wellness/health categories (activitiesis excluded by default since a CSV-export seed already covers full activity history -- edit the plist's--categoryflags to include it if you didn't seed from a CSV export).local.garmin-mcp.watchdog-- daily at 08:00 and 20:00, catches a scheduled run that failed silently (see "Failure alerting" below).
launchd requires literal absolute paths (no ~ expansion, no env vars),
so the plists in launchd/ are templates with a __REPO_ROOT__
placeholder -- install generates the real plists (with your actual path
baked in) directly into ~/Library/LaunchAgents/, rather than symlinking
the template in place:
REPO_ROOT="$(pwd)"
for job in sync backfill watchdog; do
sed "s|__REPO_ROOT__|$REPO_ROOT|g" \
"launchd/local.garmin-mcp.$job.plist" \
> ~/Library/LaunchAgents/"local.garmin-mcp.$job.plist"
done
launchctl load ~/Library/LaunchAgents/local.garmin-mcp.sync.plist
launchctl load ~/Library/LaunchAgents/local.garmin-mcp.backfill.plist
launchctl load ~/Library/LaunchAgents/local.garmin-mcp.watchdog.plistLogs land in logs/sync.log / logs/sync.error.log (and the backfill
and watchdog equivalents). Trigger a run immediately (e.g. to test) with:
launchctl start local.garmin-mcp.syncBecause the generated files in ~/Library/LaunchAgents/ are a one-time
copy (not a symlink back to the template), re-run the sed step above and
launchctl unload/load again after editing a template in launchd/ for
the change to take effect.
Failure alerting
A scheduled job can fail silently two different ways: it can crash (or
never run at all -- disabled agent, sleeping laptop at 06:00), or it can
exit 0 while a per-date fetch failed internally and got logged to
sync_log as status='partial'/'failed'/'rate_limited' without
raising (see garmin_mcp/sync/engine.py -- one bad date is swallowed
rather than aborting the whole batch). Neither shows up unless something
reads the logs.
Set ALERT_EMAIL_TO in .env to get an email for either case, sent via
the local mail command (this machine already relays outbound mail
through Postfix to a real SMTP provider -- confirm echo test | mail -s test you@example.com reaches your inbox before relying on this). This is
deliberately not a hosted heartbeat/uptime service: nothing pings out on
a normal successful day, so there's no regular outbound signal revealing
when this machine is online. The only network traffic this ever generates
is the alert itself, sent only when there's actually something to report.
garmin-mcp-sync/garmin-mcp-backfillemail immediately on any unhandled exception (login failure, DB error, etc.), then re-raise -- the real exit code and traceback still land inlogs/*.error.logexactly as before, alerting never masks it.garmin-mcp-watchdog(the third launchd job above) is what catches the other two failure modes -- a job that never ran, or one that ran but loggedpartial/failed/rate_limited. It readssync_logfor the most recent run per category and perrun_type, and emails one summary if anything's stale (>27h old, one day of slack over the daily cadence) or unsuccessful. It sends nothing when everything's healthy.
Leaving ALERT_EMAIL_TO unset disables alerting entirely (failures are
still visible in logs/*.error.log and sync_log, just not pushed to
you). Note the watchdog job has the same "did launchd even run this"
blind spot as the jobs it's watching -- there's no way to fully close that
loop with a local-only design, which is the tradeoff for not running a
regular external heartbeat. Scheduling it twice a day (08:00 and 20:00)
mitigates but doesn't eliminate this.
macOS Documents-folder permission (if the project lives under ~/Documents)
If your project sits under ~/Documents (or Desktop/Downloads), macOS's
TCC privacy protection blocks launchd-spawned processes from reading
files there -- including the venv's own pyvenv.cfg -- unless the
Python interpreter has an explicit grant. This will surface as:
PermissionError: [Errno 1] Operation not permitted: '.../.venv/pyvenv.cfg'This is genuinely fiddly to resolve, because System Settings' Full Disk
Access / Files and Folders "+" picker generally only lets you select .app
bundles, not a raw CLI Python binary -- and a background launchd job has
no GUI session to show an interactive consent prompt in the first place, so
it just fails silently rather than asking. What actually works:
Find the real interpreter binary your venv resolves to (pyenv/asdf installs are usually a symlink chain):
python3 -c "import os; print(os.path.realpath('.venv/bin/python3'))"Build a minimal
.appbundle wrapper around it (a real bundle is selectable in the TCC picker, and launching it from Finder -- with no privileged GUI-app ancestor to silently inherit permission from -- gives it a chance to earn its own independent, persisted grant via a genuine consent dialog). Keep the wrapper narrowly scoped to the specific command(s) you need, not a generic pass-through that can exec anything it's handed.Double-click that
.apponce in Finder. If a system permission dialog appears, click Allow.Verify the grant landed on the interpreter, not the wrapper app:
sqlite3 "$HOME/Library/Application Support/com.apple.TCC/TCC.db" \ "SELECT service, client, auth_value FROM access WHERE client LIKE '%python3.12%';"auth_value=2forkTCCServiceSystemPolicyDocumentsFoldermeans it worked. The grant is tied to that interpreter binary's identity, not to how it's invoked -- so once this is confirmed,launchdcan call the venv binaries directly (no wrapper needed going forward; the plists in this repo already do this).If
launchd's job also setsWorkingDirectoryto a path under Documents, drop it --/bin/bash(or whatever shell interprets the job) has no grant of its own, andlaunchdchdir()-ing it into a protected folder before your granted interpreter ever runs breaks the shell's owngetcwd()at startup. This project's config (garmin_mcp/config.py) resolves.envand all paths relative to the repo root explicitly, independent ofcwd, specifically soWorkingDirectoryisn't needed.
MCP server (Claude Desktop / Claude Code)
garmin-mcp-server # runs on stdioAdd to Claude Desktop's config (~/Library/Application Support/Claude/claude_desktop_config.json
on macOS) under mcpServers:
{
"mcpServers": {
"garmin": {
"command": "/absolute/path/to/garmin-mcp-local/.venv/bin/garmin-mcp-server"
}
}
}No env overrides needed -- garmin_mcp/config.py resolves .env, the DB
path, and the token store relative to the repo root explicitly, independent
of whatever working directory Claude Desktop launches the process with.
Restart Claude Desktop to pick up the change.
Tools exposed:
Tool | Hits the network? | Purpose |
| No | Activities by date range / type |
| No | Full detail: laps, HR/power zones, gear |
| No | Steps/HR/stress/body battery/SpO2/respiration by date range |
| No | Nightly sleep stages + score breakdown |
| No | Training status/readiness/VO2max/load/race predictions |
| No | Recent sync_log entries + resume cursors |
| No | Ad hoc read-only SQL ( |
| Yes | Explicit incremental sync |
| Yes | One controlled backfill batch per category |
Schema
One normalized table per data category (see garmin_mcp/db/schema.sql for
the authoritative, commented definition):
Activities:
activities(full per-activity detail),activity_laps,activity_hr_zones,activity_power_zones,gear,activity_gear.Daily health:
daily_health_metrics(steps/calories/HR/stress/body battery/SpO2/respiration),daily_stress_periods(TOTAL/AWAKE/ASLEEP breakdown),sleep,hrv_daily,body_composition.Training metrics:
training_status(VO2max, training status, load/ACWR, endurance & hill scores),training_readiness,race_predictions.Sync state:
sync_log(full audit trail of every import/sync/backfill attempt, success or not) andsync_cursor(current resume point per category, in both the forward/incremental and backward/backfill direction).
Every table has a stable natural key from Garmin's own IDs (activity_id,
calendar_date) with a UNIQUE/PRIMARY KEY constraint. All writes go
through one generic upsert() helper (garmin_mcp/db/connection.py), so
re-running any import or sync is always idempotent.
Known gaps in the bulk export
Verified against a real Garmin "export all data" archive. The importer's
report flags these explicitly at the end of every garmin-mcp-import-export
run:
Nightly HRV detail (
hrv_daily.last_night_avg, baseline, status): the bulk export has no dedicated nightly-HRV file. Only a weekly average is derivable indirectly (fromTrainingReadinessDTO), imported withsource='csv_export_approx'. Run a live sync (get_hrv_data) to backfill nightly detail.Body composition (
body_composition): only populated if your account has Garmin Index smart scale data in the export. If you don't own one, this table stays empty until/unless you add manual weigh-ins via the API.Raw GPS tracks / second-by-second streams: intentionally out of scope for this schema (per-lap summaries and HR/power zone time-in-zone are captured instead) -- keeps the DB small and avoids one API call per activity during backfill. The original FIT files are included separately in Garmin's export archive (
DI_CONNECT/DI-Connect-Uploaded-Files/) if you need them.
A note on units in the bulk export
Garmin's raw export uses internal units for activity-level and per-lap
fields that don't match their own field-name suffixes: distance-like
fields are centimeters, speed-like fields are centimeters/millisecond,
duration-like fields are milliseconds. This was verified empirically
against real ride/run data during development (see comments in
garmin_mcp/bulk_import/units.py) and converted to meters/mps/seconds on
import. A few running-dynamics fields (vertical oscillation, ground
contact time, vertical ratio, cadence) are left as raw export values,
since their exact unit wasn't independently verifiable from the sample
data available -- cross-check against Garmin Connect's UI before relying
on them for anything precise.
A note on live-API field mappings
Both the bulk-export importer and the live-API sync mappings
(garmin_mcp/sync/daily_categories.py, garmin_mcp/sync/activities_sync.py)
have been verified against a real account -- including a direct
field-by-field diff between CSV-imported and freshly API-backfilled data
for the same historical date. A few real discrepancies from that process
are now documented inline in the code and worth knowing about:
get_user_summary(the live daily-health endpoint) returns a flat structure -- unlike the bulk export's nestedallDayStress/bodyBattery/respirationobjects, and with several different field names (averageStressLevelvs. nestedallDayStress.aggregatorList[TOTAL],bodyBatteryHighestValuevs. abodyBatteryStatListlookup, etc.). Once mapped correctly, values match the CSV export exactly for the same date.daily_stress_periods' AWAKE/ASLEEP breakdown is CSV-only -- the live endpoint only exposes a TOTAL-equivalent, so live sync writes just that row and leaves any existing CSV-sourced AWAKE/ASLEEP rows alone.training_status/fitness_trendare numeric codes live (7,1, ...), not the plain strings the bulk export gives ("MAINTAINING","DECREASING") -- there's no public code-to-string mapping, so expectsource='api'rows to look different in flavor fromsource='csv_export'rows in this column specifically.sleep's per-stage quality subscores (deep_score,rem_score, etc.) andrestless_moment_countaren't available from the live endpoint at all (only an overall score, feedback string, and raw stage percentages) -- leftNULLonsource='api'rows rather than approximated.body_compositionandhrv_daily(nightly detail) fill in correctly from live sync where the CSV export couldn't provide them.
If you spot a live-synced row that looks wrong, SELECT ... WHERE source='api' vs. source='csv_export' for the same date is the fastest
way to compare and confirm before assuming it's a bug.
Tests
pytestFocused specifically on the two failure modes this project exists to avoid:
tests/test_idempotency.py: re-running an import or sync never duplicates rows; a partially-failed sync batch resumes from the right date instead of silently skipping the gap or re-processing everything.tests/test_rate_limiter.py: exponential backoff math, persisted cooldown blocking a fresh process from hammering the API again, and that retries are capped with a clear logged failure rather than an infinite loop or a silent give-up.
Available Tools
9 toolsbackfill_batch_nowB
Run one controlled batch of API-driven backfill per category, walking further back into history than the bulk export (or a prior backfill run) reached. Call repeatedly to walk the full history without one giant fetch.
`categories`, if given, restricts the batch to that subset (e.g.
["hrv_daily"]) instead of every category -- one of: activities,
daily_health_metrics, sleep, hrv_daily, training_readiness,
training_status, race_predictions, body_composition.| Name | Required | Description | Default |
|---|---|---|---|
| batch_days | No | ||
| categories | No | ||
| earliest_date | No | 2000-01-01 |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations provided; description mentions 'controlled batch' and 'walk further back' but does not disclose whether it is destructive, idempotent, or what side effects occur. Lacks details on concurrency, rate limits, or result handling beyond walking history.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two concise sentences plus list of categories. Purpose is front-loaded. No unnecessary words. Could be slightly more structured but efficient.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given output schema exists, return values not needed. But the tool has 3 parameters, only one partially explained. The missing explanation for batch_days and earliest_date leaves some ambiguity. Sibling context is provided, but the description does not fully address how this tool fits into the overall workflow with other sync tools.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage 0%; description explains categories well but batch_days and earliest_date are not described. The parameter names are self-explanatory, but the description adds no extra meaning for those two. Only partially compensates for lack of schema descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Clear verb and resource: 'backfill batch' per category. Distinguishes from siblings by being a controlled incremental walk, not a full sync. Explicitly states this tool is for repeated calls to cover full history.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Says to call repeatedly, and explains the categories parameter to restrict. But no explicit comparison to sibling tools like sync_now or bulk export, nor conditions for when not to use. Implied usage is clear but not exhaustive.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
execute_sqlA
Run an ad hoc read-only SQL query against the local cache. Only SELECT/WITH/EXPLAIN/PRAGMA table_info statements are allowed.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden. It discloses read-only nature, ad hoc execution, local cache target, and allowed statement types. Could mention potential side effects from EXPLAIN or PRAGMA, but overall transparent.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences with no fluff. First sentence states purpose, second adds critical constraints. Front-loaded and efficient.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Sufficient for a simple tool with one parameter and an output schema. Explains allowed statements and read-only nature. Could mention error handling or caching implications, but not essential.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, yet the description only says 'query' without adding format, length limits, or examples. The parameter meaning is implied but not elaborated beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Clearly states the verb 'Run', resource 'ad hoc read-only SQL query against the local cache', and distinguishes itself from sibling tools that handle health metrics, sync, and activities.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly lists allowed statement types (SELECT/WITH/EXPLAIN/PRAGMA), providing clear constraints. Does not mention alternatives or when not to use, but the constraint is strong enough.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_activity_detailA
Full detail for one activity: summary row + laps + HR/power zones + gear.
| Name | Required | Description | Default |
|---|---|---|---|
| activity_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden. It lists what data is returned (laps, zones, gear) but does not disclose any behavioral traits such as error conditions, performance implications, or data freshness. The output schema likely provides structure, but the description lacks depth.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, well-structured sentence that immediately communicates the tool's purpose and output components. No wasted words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the simple interface (1 required parameter, output schema exists), the description covers the main functionality. However, it lacks any guidance on error handling or when to use, which are minor gaps.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema has 0% parameter descriptions, and the description does not explain the 'activity_id' parameter beyond its existence. No format, validation, or context is provided for the integer ID.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states it returns 'full detail for one activity' including specific components like summary, laps, zones, and gear. This distinguishes it from sibling tools like 'list_activities' (which likely returns summaries) and 'get_training_trends' (trends).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies use when a single activity's detailed data is needed, but does not explicitly state when to use versus alternatives, nor does it provide any context about prerequisites or exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_daily_health_metricsB
Daily steps/HR/stress/body battery/SpO2/respiration for a date range (inclusive, 'YYYY-MM-DD').
| Name | Required | Description | Default |
|---|---|---|---|
| end_date | Yes | ||
| start_date | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description must fully disclose behavior. It lists the returned metrics but omits critical details: whether data is per-day aggregated, how missing dates are handled, any authentication requirements, or potential side effects. This is insufficient for a tool with zero annotation coverage.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, well-structured sentence that fronts the key information (metrics and date range). It is free of redundancy. A minor improvement would be adding examples or splitting into bullet points for readability, but overall it is efficient.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers the tool's core functionality and format, but given the presence of an output schema (not shown), return values need not be elaborated. However, it lacks usage context, error behavior, and parameter range, making it only minimally complete for a tool with unannotated parameters.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 add the date format requirement ('YYYY-MM-DD') and notes inclusivity, which clarifies the parameters' role. However, it doesn't describe error conditions or bounds, leaving some ambiguity. This meets a baseline but doesn't excel.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description specifies a clear verb ('get') and resource ('daily health metrics') along with a concrete list of metrics (steps, HR, stress, body battery, SpO2, respiration). This differentiates it from siblings like get_activity_detail (single metric) and get_sleep (specific domain), making the tool's purpose unmistakable.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No guidance is provided on when to use this tool versus alternatives like get_activity_detail or get_training_trends. The description mentions the date range and inclusivity but fails to offer any contextual cues for selection among siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_sleepA
Nightly sleep stages + sleep score breakdown for a date range (inclusive, 'YYYY-MM-DD').
| Name | Required | Description | Default |
|---|---|---|---|
| end_date | Yes | ||
| start_date | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description must carry the full burden. It describes the data returned but does not disclose behavioral traits such as authentication requirements, rate limits, or read-only nature. The 'get' prefix implies a safe read operation, but not explicitly stated.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, front-loaded sentence with no wasted words. It covers the key elements: resource, action, and parameter format efficiently.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the existence of an output schema, the description does not need to explain return values. It provides essential information about the data and parameters. Minor gap: it does not specify the user context or scope of data, but this is acceptable for a simple 'get' tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With 0% schema description coverage, the description adds value by explaining the date range format ('YYYY-MM-DD') and inclusiveness. However, it does not differentiate between start_date and end_date or describe their individual semantics beyond the general range context.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool retrieves 'Nightly sleep stages + sleep score breakdown' for a date range, specifying the inclusive format and date string pattern. It distinguishes from sibling tools like 'get_activity_detail' or 'get_daily_health_metrics' by focusing solely on sleep data.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description mentions the date range scope and format, but does not provide explicit guidance on when to use this tool versus alternatives, or any prerequisites or exclusions. Usage is implied but not fully clarified.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_sync_statusA
Recent sync_log entries -- what's been fetched, what failed/was rate-limited, and the current resume cursors for every category. Check this before trusting 'no data' as a real gap vs. a not-yet-synced range.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It implies a read-only operation by describing it as a status check, but does not explicitly state side effects, authentication needs, or rate limits. The description is adequate but not explicit.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is composed of two efficient sentences: the first describes the output, the second provides usage guidance. There is no superfluous information, and it is well-structured.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool has zero parameters, an output schema, and a clear purpose, the description is complete. It covers the key information needed for an agent to decide when and why to invoke it.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema has zero parameters, so the schema coverage is trivially 100%. With no parameters, the description does not need to add parameter meaning. The baseline of 4 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states what the tool returns: sync_log entries covering fetched items, failures, rate-limits, and resume cursors. It distinguishes the tool from siblings like sync_now and backfill_batch_now by focusing on status inspection rather than triggering actions.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit guidance to check this tool before interpreting 'no data' as a real gap vs. an unsynced range. This offers context for when to use it, though it does not explicitly mention alternatives or when not to use it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_training_trendsB
Training status, readiness, VO2max, load (ACWR), endurance/hill scores, and race predictions for a date range (inclusive, 'YYYY-MM-DD').
| Name | Required | Description | Default |
|---|---|---|---|
| end_date | Yes | ||
| start_date | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description must fully disclose behavior. It lists output fields but does not mention side effects, authentication needs, rate limits, or that it is read-only. The inclusive date range is noted, but deeper behavioral traits are missing.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single sentence with no extraneous information, making it efficient. It could be improved by front-loading the action (e.g., 'Retrieve training trends...') but remains concise.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool has a simple structure with 2 parameters and no nested objects. The description covers the return values and input format adequately, but lacks details on aggregation logic or default behaviors. It is complete enough for a straightforward retrieval tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema has 0% coverage, so the description adds value by specifying the date format (YYYY-MM-DD) and that the range is inclusive. This clarifies the intended input beyond raw schema types.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description lists specific metrics returned (training status, readiness, VO2max, etc.) for a date range, clearly indicating the tool's function. It distinguishes from sibling tools like get_activity_detail or get_daily_health_metrics by focusing on aggregated training trends over a period.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description states the tool is for a date range with inclusive YYYY-MM-DD format, providing clear context for when to use it. However, it does not mention alternatives or when not to use it, relying on the agent to infer from sibling names.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_activitiesA
List activities from the local cache, most recent first.
start_date/end_date: 'YYYY-MM-DD', inclusive, optional.
activity_type: exact match against Garmin's granular activity_type
(e.g. 'road_biking', 'gravel_cycling', 'indoor_cycling', 'treadmill_running'), optional.
sport_type: exact match against Garmin's broader sport_type grouping
(e.g. 'CYCLING', 'RUNNING', 'WALKING') -- prefer this over activity_type
for questions like "how many bike rides" or "how many runs", since it
groups all the granular variants (road/gravel/indoor cycling, etc.)
together in one filter instead of requiring you to enumerate and
manually classify a mixed activity list yourself. Optional.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| end_date | No | ||
| sport_type | No | ||
| start_date | No | ||
| activity_type | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Discloses data source ('local cache') and exact match semantics. Without annotations, could be clearer about non-destructive nature, but 'list' implies read-only.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Concise with clear bullet points for parameters. Slightly verbose with Garmin examples, but overall efficient and front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers all parameters and provides examples. With output schema present, return values need no explanation. Missing mention of limit behavior, but minor gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Adds substantial meaning beyond empty schema descriptions: formats, optionality, filtering behavior, and guidance on when to use each filter. Compensates fully for 0% schema coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Clearly states 'List activities from the local cache, most recent first' – a specific verb and resource with ordering. Distinguishes from sibling tools like get_activity_detail and get_sync_status.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly advises preferring sport_type over activity_type for grouping questions, with concrete examples. Also explains date format and optionality.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
sync_nowA
Explicitly hit the live Garmin API to pull new data since the last successful sync, for every category. Rate-limited; respects any active cooldown from a previous 429. Never called automatically by other tools.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description fully covers behavioral traits: hits live API, pulls new data since last sync, respects 429 cooldown, and is not triggered automatically. No contradictions or omissions.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four concise sentences, each adding valuable information. First sentence front-loads the core purpose. No wasted words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given no parameters and an existing output schema, the description covers the tool's behavior, constraints, and usage context completely. No gaps identified.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Tool has zero parameters, baseline 4 as per guidelines. Description adds no additional parameter info since none exist.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the verb 'hit' and resource 'live Garmin API', specifying 'pull new data since last successful sync, for every category'. It distinguishes from siblings like get_sync_status and backfill_batch_now by emphasizing an explicit, forced sync.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Mentions rate-limiting and cooldown respect, and that it's never called automatically, implying manual use. However, it does not explicitly compare to alternatives like get_sync_status for status checking or backfill_batch_now for historical data.
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.
9 tool updates
v0.1.0- First observed
backfill_batch_now - First observed
execute_sql - First observed
get_activity_detail - First observed
get_daily_health_metrics - First observed
get_sleep - First observed
get_sync_status - First observed
get_training_trends - First observed
list_activities - First observed
sync_now
TDQS
Scored across 9 tools
Each tool targets a distinct aspect of Garmin data: sync management, data retrieval for specific domains (activities, health, sleep, training), and direct SQL access. There is no functional overlap.
All tools use a consistent snake_case verb_noun pattern (e.g., get_sleep, list_activities, sync_now), with 'get' and 'list' as common prefixes, making the naming predictable.
9 tools is ideal for a Garmin data server: it covers core operations (sync, backfill, query) without being overwhelming or sparse. Each tool serves a clear purpose.
The set provides direct tools for activities, health metrics, sleep, and training trends, but lacks dedicated tools for backfilled categories like body composition or race predictions. However, the execute_sql tool allows ad hoc queries to fill these gaps.
Maintenance
Related MCP Connectors
MCP server for Withings health data — sleep, activity, heart, and body metrics.
Remote MCP server for training, nutrition, wellness, and performance data with OAuth 2.0.
Multi-tenant hosted MCP server for Oura Ring — 21 read-only tools, OAuth per user.
Hosted MCP server with managed OAuth for 15+ toolkits: Google Workspace, Fitbit, Oura, Kalshi, etc.
Related MCP Servers
- AlicenseAqualityAmaintenanceLocal-first Garmin data warehouse with an analysis-grade MCP server. Sync once, analyze forever, even when the API is down.125MIT
- AlicenseAqualityBmaintenanceMCP server for reading and querying Garmin Connect data, including activities, strength history, recovery, trends, and optionally creating workouts.12MIT
- AlicenseBqualityAmaintenanceMCP server for local fitness-data extraction and analysis from Garmin Connect, Intervals.icu, and Strava. Provides read-only analytical tools over DuckDB and targeted Strava enrichment.35425 npmAGPL 3.0
- FlicenseNot gradedqualityBmaintenanceThis MCP server provides read-only access to your local Garmin Connect history, enabling Claude to query workouts, sleep, training load, fitness scores, records, and gear via natural language. It uses a sync script to pull data into a local SQLite database, ensuring privacy and offline operation.-