garmin-mcp
A local, read-only MCP server that lets Claude Desktop query and analyze your own Garmin Connect data.
Check login/account with
whoami.Retrieve daily wellness metrics: summary, sleep, stress, heart rate, HRV, SpO2, respiration, hydration, steps, floors, intensity minutes, Body Battery, and stats.
Retrieve training metrics: readiness, morning readiness, status, VO2max, and fitness age.
Retrieve fitness/performance data: race predictions, cycling FTP, lactate threshold, personal records, endurance/hill score, resting heart rate, and weekly intensity minutes.
Inspect weight and body composition over a date range.
View daily or weekly step history.
Fetch one activity’s summary, splits, weather, streams, HR zones, and exercise sets.
List recent activities or filter by date range/type.
Read paired devices, user profile/settings, goals, saved workouts, and scheduled workouts/calendar.
Use date ranges and metric/include selectors to keep results focused.
Provides read-only access to a user's Garmin Connect data, including activities, sleep, HRV, Body Battery, stress, heart rate, SpO2, respiration, hydration, steps, floors, intensity minutes, weight/body composition, training readiness, VO2 max, fitness age, race predictions, personal records, devices, goals, workouts, and scheduled workouts.
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 What's my training readiness today?"
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 → Claude MCP server
Analyze your own Garmin Connect data — activities, sleep, HRV, Body Battery, training readiness — right inside Claude Desktop.
A fully local, read-only MCP server that lets you analyze your own Garmin Connect data in Claude Desktop. The server runs as a local child process of Claude Desktop and talks to it over stdio.
Guiding principles
100% local — nothing is hosted or exposed to the network (stdio).
Read-only — there is no write endpoint to Garmin.
No third party — the only network contact is Garmin. No telemetry, no external logging.
Passwordless at runtime — the password is never stored; after the one-time login only local tokens are used.
Related MCP server: Garmin Health MCP Server
Requirements
Node.js ≥ 20 (tested with Node 22 LTS), npm.
A Garmin Connect account.
Setup (4 steps)
# 1) Install dependencies
npm install
# 2) Milestone test: does the client get through Garmin's Cloudflare protection?
npm run smoke
# Expectation: "Status 200" and "Challenge: no".
# 3) One-time interactive login (prompts for email, hidden password, MFA if enabled)
npm run login
# Creates ~/.garmin-mcp/tokens.json (mode 0600). The password is NOT stored.
# 4) Compile TypeScript to build/
npm run buildConnecting Claude Desktop
Claude Desktop → Settings → Developer → Edit Config. Add the server with its
absolute path (claude_desktop_config.json):
{
"mcpServers": {
"garmin": {
"command": "node",
"args": ["/absolute/path/to/garmin-mcp/build/mcp/server.js"]
}
}
}Then restart Claude Desktop. Try it in chat, e.g.:
"Am I logged in to Garmin? Use
whoami." "Show me my daily summary and my recent activities."
Updating
git pull
npm install # dependencies may have changed
npm run build # build/ is not part of the repoThen restart Claude Desktop. It runs the server as a child process, and a running instance keeps the code it loaded at startup — so an update only takes effect after a restart.
Your login survives an update: ~/.garmin-mcp/tokens.json is untouched and the
path in claude_desktop_config.json stays the same. Only re-run npm run login
if a tool actually reports 🔒 Not logged in.
Available tools
13 read-only tools. Date parameters are YYYY-MM-DD (default = today); ranges
default to the last ~4 weeks. Several tools take a metrics[] / include[] selector,
so one tool covers many data types (this keeps the tool list small for good tool
selection). Long time series in responses are truncated to stay compact.
Tool | Description |
| Connection check + account profile |
| Daily wellness for a date — |
| Training for a date — |
| Fitness/performance — |
| Body weight & composition over a range ( |
| Step totals over a range — |
| One activity by |
| Recent activities, or by date range/type ( |
| Paired Garmin devices |
| User profile & settings (units, preferences) |
| Goals ( |
| Saved workouts ( |
| Scheduled workouts / calendar ( |
How it works
The server registers each Garmin data method as an MCP tool and talks to Claude
Desktop over stdio. Every call goes through a generic connectapi() request to
connectapi.garmin.com with a bearer token, sent via
cycletls so the TLS fingerprint looks
like a real browser (Garmin sits behind Cloudflare).
Authentication happens once (npm run login): a login cascade — mobile iOS JSON
login first, the classic widget/CSRF flow as fallback — yields a CAS service ticket,
which is exchanged in Garmin's DI-OAuth2 flow for an access + refresh token. The
tokens are cached locally and the access token is refreshed automatically on expiry;
your password is never stored.
Security model
The password is never stored — it is only used for the one-time login.
Tokens live at
~/.garmin-mcp/tokens.jsonwith file mode0600(only you can read them).The access token is renewed automatically via the refresh token when it expires.
Read-only: there is no code path that changes anything on Garmin.
No telemetry, no external logging. Diagnostics go to
stderronly (never over the stdio MCP channel).
Architecture (layers)
src/http/impersonate.ts— TLS impersonation viacycletls(JA3 fingerprint) + cookie jar, so Garmin's bot protection lets us through.src/http/smoke.ts— milestone test againstsso/embed.src/garmin/auth.ts— login cascade (mobile iOS JSON login → widget/CSRF fallback) + MFA + DI-OAuth2 ticket exchange + refresh.src/garmin/tokens.ts— local token cache (0600).src/garmin/client.ts— genericconnectapi()+ typed, read-only data methods.src/mcp/server.ts— MCP server and tool registration; served over stdio viaserveStdio(MCP TypeScript SDK v2), which answers both 2025-era clients such as Claude Desktop and the 2026-07-28 protocol revision.src/cli/login.ts— one-time interactive login.
Development
npm run build # tsc -> build/
npm test # unit tests for the pure helpers (no network, no credentials)
npm run smoke # milestone test against Garmin's Cloudflare (needs network)
npm run login # one-time interactive login (needs your credentials)
npm start # run the built server directly (normally Claude Desktop does this)CI (.github/workflows/ci.yml) runs npm ci, npm run build and npm test on
Node 22. build doubles as the type-check. smoke and login are deliberately
excluded — they need live Garmin access and real credentials, and would be flaky.
npm test covers the pure helpers only (trimLongArrays, decodeJwtExp,
toSearchParams) and never imports the MCP SDK, so a green test run says nothing
about tool registration. After changing tools or upgrading dependencies, probe the
server over stdio. No Garmin credentials are needed — registration and argument
validation both happen before any authentication:
npm run build
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"1"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| node build/mcp/server.jsExpect an initialize reply plus a tools/list reply containing all 13 tools with
their input schemas. To also confirm that argument validation still bites, add one
more line to the printf above — it should come back as a tool result with
"isError": true and an Input validation error: … text, not as a login error:
'{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_activity","arguments":{"activityId":"not-a-number"}}}' \Dependencies are updated by Dependabot (.github/dependabot.yml, weekly). The npm
updates arrive as one grouped PR that can include major versions, so read its
package.json diff before merging, and run the stdio probe above rather than
trusting a green build alone.
Maintenance & risks (named honestly)
This uses Garmin's unofficial internal API, ported from the open-source Python
package python-garminconnect.
Expected breakage points and how to fix them:
Unofficial API — endpoint paths can change. On errors, compare the paths in
src/garmin/client.tsagainst the currentpython-garminconnectsource.JA3 / User-Agent — on a
403or a "Just a moment …" Cloudflare page, update the JA3/User-Agent pair insrc/http/impersonate.tsto a current Chrome (both together).Client ids — the DI-OAuth2 client ids (
GARMIN_CONNECT_MOBILE_ANDROID_DI_*) rotate; update the list insrc/garmin/auth.tson auth errors.garthis discontinued — the previously common auth librarygarthis no longer maintained; the authoritative reference ispython-garminconnect.Keep the token folder private —
~/.garmin-mcp/holds valid session tokens; don't share it, don't commit it.Terms of service — access via the unofficial API may be in tension with Garmin's terms of service. Intended for private personal use with your own data.
Troubleshooting
403or a "Just a moment …" page — Garmin's Cloudflare is blocking the TLS fingerprint. Update the JA3/User-Agent pair insrc/http/impersonate.tsto a current Chrome (both together), then re-runnpm run smoke.🔒 Not logged in/ session expired — runnpm run loginagain.⏳rate limited (HTTP 429) — Garmin is throttling; wait a bit and retry.First run is slow / cycletls errors —
cycletlsdownloads a small Go helper binary on first use; make sure it can execute and isn't blocked by the OS.Calls hang right after an update — the previous server process may have left its
cycletlsGo helper behind. Check withpgrep -fl cycletlsand clear it withpkill -f cycletls; the next call starts a fresh one.
Re-login
If tools report 🔒 Not logged in (token expired/invalid):
npm run loginLicense
MIT © Marcel Fortmann
Disclaimer
This is an unofficial, community project. It is not affiliated with, endorsed by, or sponsored by Garmin. Garmin® and Garmin Connect™ are trademarks of Garmin Ltd. or its subsidiaries.
Available Tools
13 toolsget_activityA
Data for a single activity by activityId. Pick via include: summary, splits, weather, details (GPS/HR/power streams), hr_zones, exercise_sets.
Inputs: activityId (required), include.
get_daily_healthB
Daily wellness metrics for a date. Pick one or more via metrics: summary, sleep, stress, heart_rate, hrv, spo2, respiration, hydration, steps, floors, intensity_minutes, body_battery, body_battery_events, stats_and_body.
Inputs: date, metrics.
get_devicesA
Garmin devices paired to the account (model, firmware, last sync).
Inputs: no named parameters.
get_fitnessA
Fitness/performance metrics. Pick via metrics: race_predictions, cycling_ftp, lactate_threshold, personal_records (all latest, date-agnostic), and endurance_score, hill_score, resting_heart_rate, weekly_intensity_minutes (over the given date range).
Inputs: endDate, metrics, startDate.
get_goalsC
Goals filtered by status (active, future, or past).
Inputs: limit, status.
get_scheduled_workoutsB
Scheduled workouts / calendar for a month (defaults to the current month).
Inputs: year, month.
get_steps_historyA
Step totals over a date range. granularity 'daily' (Garmin caps the span at 28 days) or 'weekly'. Default: daily, last ~4 weeks.
Inputs: endDate, startDate, granularity.
get_trainingC
Training metrics for a date. Pick via metrics: readiness, morning_readiness, status, vo2max, fitness_age.
Inputs: date, metrics.
get_user_profileA
User profile & settings: units, preferences, activity level, etc.
Inputs: no named parameters.
get_weightA
Body weight & body composition (weight, BMI, body-fat %) over a date range. Set include_raw for the individual weigh-in entries too. Default range: the last ~4 weeks.
Inputs: endDate, startDate, include_raw.
get_workoutsC
Saved workouts on the account.
Inputs: limit.
list_activitiesA
List activities. Without dates: the most recent ones (use limit=1 for the last activity). With startDate/endDate and/or type: activities in that range, optionally filtered by type (e.g. running, cycling, swimming).
Inputs: type, limit, endDate, startDate.
whoamiA
Check the Garmin connection and return the logged-in account profile (display name, full name).
Inputs: no named parameters.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 13 tools
Most tools are clearly separated by domain (daily health, training, fitness, weight, steps, activities, workouts, devices). Some overlap exists, such as steps and heart-rate metrics appearing in both get_daily_health and related history/fitness tools, and whoami vs get_user_profile could be slightly confusing, but the descriptions make the intended boundaries mostly clear.
The tool names are consistently snake_case and nearly all use a get_ verb prefix, with list_activities as a sensible variant. whoami is the only clear naming outlier, but it is a conventional and readable exception.
With 13 tools, the server is well within a reasonable range and each tool maps to a distinct Garmin data area. The grouping is not bloated, and no tool feels redundant purely from a count perspective.
The surface covers the main read-only Garmin domains: health, training, fitness, body composition, steps, activities, goals, workouts, scheduled workouts, devices, and profile. Minor gaps such as retrieving individual workout or goal details may exist, but core workflows appear covered and agents can work around them.
Maintenance
Related MCP Connectors
Garmin data in Claude & ChatGPT via the Garmin Health API. OAuth sign-in, no password sharing.
Garmin data in Claude: 135 tools — activities, sleep, HRV, training, workouts. Free, open source.
Connect Claude to your Intervals.icu watch data for fitness, workout review, and plan writing.
Explore Garmin activity and health data with your AI assistant.
Related MCP Servers
- AlicenseBqualityBmaintenanceConnects Claude Desktop to Garmin Connect, enabling natural language queries of fitness activity data, health metrics, sleep analysis, workout management, and device information with 94 available tools.1101MIT
- AlicenseAqualityDmaintenanceEnables Claude Desktop to access and analyze Garmin wearable health data including sleep, HRV, Body Battery, and activity metrics. Users can query their health trends, track recovery, and generate interactive HTML dashboards using natural language.97MIT
- FlicenseNot gradedqualityDmaintenanceConnects Garmin watch data to Claude Desktop, allowing users to ask natural language questions about their health and activity data from Garmin Connect.-
- FlicenseAqualityDmaintenanceExposes your Garmin Connect data (activities, sleep, recovery, training status) to Claude in read-only mode.7-