AgentStack
MCP verified MIT Self-run

Garmin Mcp

mcp-marcelfortmann-garmin-mcp · by marcelfortmann

Local, read-only MCP server that brings your own Garmin Connect data (activities, sleep, HRV, Body Battery, training readiness…) into Claude Desktop over stdio.

No reviews yet
0 installs
1 views
0.0% view→install

Install

$ agentstack add mcp-marcelfortmann-garmin-mcp

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Garmin Mcp? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

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.

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 build

Connecting 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."

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 | |---|---| | whoami | Connection check + account profile | | get_daily_health | Daily wellness for a date — metrics[]: summary, sleep, stress, heartrate, hrv, spo2, respiration, hydration, steps, floors, intensityminutes, bodybattery, bodybatteryevents, statsandbody | | get_training | Training for a date — metrics[]: readiness, morningreadiness, status, vo2max, fitnessage | | get_fitness | Fitness/performance — metrics[]: racepredictions, cyclingftp, lactatethreshold, personalrecords, endurancescore, hillscore, restingheartrate, weeklyintensityminutes (startDate/endDate) | | get_weight | Body weight & composition over a range (include_raw also returns weigh-ins) | | get_steps_history | Step totals over a range — granularity: daily or weekly | | get_activity | One activity by activityIdinclude[]: summary, splits, weather, details, hrzones, exercise_sets | | list_activities | Recent activities, or by date range/type (limit/startDate/endDate/type) | | get_devices | Paired Garmin devices | | get_user_profile | User profile & settings (units, preferences) | | get_goals | Goals (status: active/future/past) | | get_workouts | Saved workouts (limit) | | get_scheduled_workouts | Scheduled workouts / calendar (year/month) |

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.json with file mode 0600 (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 stderr only (never over the stdio MCP channel).

Architecture (layers)

  • src/http/impersonate.ts — TLS impersonation via cycletls (JA3 fingerprint) + cookie jar, so Garmin's bot protection lets us through.
  • src/http/smoke.ts — milestone test against sso/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 — generic connectapi() + typed, read-only data methods.
  • src/mcp/server.ts — MCP server, tool registration, stdio transport.
  • src/cli/login.ts — one-time interactive login.

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.ts against the current python-garminconnect source.
  • JA3 / User-Agent — on a 403 or a "Just a moment …" Cloudflare page, update the JA3/User-Agent pair in src/http/impersonate.ts to a current Chrome (both together).
  • Client ids — the DI-OAuth2 client ids (GARMIN_CONNECT_MOBILE_ANDROID_DI_*) rotate; update the list in src/garmin/auth.ts on auth errors.
  • garth is discontinued — the previously common auth library garth is no longer maintained; the authoritative reference is python-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

  • 403 or a "Just a moment …" page — Garmin's Cloudflare is blocking the TLS fingerprint. Update the JA3/User-Agent pair in src/http/impersonate.ts to a current Chrome (both together), then re-run npm run smoke.
  • 🔒 Not logged in / session expired — run npm run login again.
  • rate limited (HTTP 429) — Garmin is throttling; wait a bit and retry.
  • First run is slow / cycletls errorscycletls downloads a small Go helper binary on first use; make sure it can execute and isn't blocked by the OS.

Re-login

If tools report 🔒 Not logged in (token expired/invalid):

npm run login

License

[MIT](LICENSE) © 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.

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.