Garmin Running MCP
Provides read-only access to a personal Garmin Connect running account, with cached tools for latest runs, run lists/details/charts, comparisons, trends, recovery metrics, training context, and sync status; supports explicit rate-limited refresh of the latest run without modifying Garmin.
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 Running MCPwhat was my latest run?"
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 Running MCP
Self-hosted Garmin Connect → ChatGPT/AI-agent integration for a personal running account. No Strava, subscription sync service, or public activity files. Built with a Cloudflare Worker, D1, private R2 and Alchemy. Garmin Connect's consumer API is unofficial and may change. This is an independent project, not affiliated with Garmin.
What it does
Cloudflare Access Managed OAuth limits the
/mcpendpoint to one exact email address. The Worker verifies the signed Access assertion as well. OAuth grants are configured for 365 days (8760h), with 15-minute access tokens refreshed silently by compatible clients. This is the requested grant lifetime, not a guarantee against earlier revocation or client-side reconnects; Cloudflare acceptance must be verified at deployment. Garmin token expiry is separate. No anonymous data tools or Garmin write actions.Sync is fully on-demand; no cron trigger is configured. Previously cached history, recovery metrics and private R2 originals are retained, but are not automatically refreshed or backfilled.
After a run, call
refresh_latest_run: it checks up to five recent activities and fetches the newest run's summary, per-lap splits, actual Garmin HR-zone seconds and up to 500 chart samples. Safe summary, laps and zones are returned immediately; userun_chartfor opt-in chart samples. Missing sections are retried on the next explicit refresh even if the summary is unchanged. The tool has a one-minute cooldown, a single-flight lease and five-second spacing between detail requests; a new run typically adds about 15 seconds of spacing plus Garmin response time. Do not use it for automated polling.All other MCP tools are cache-only:
latest_run,list_runs,run_details,run_chart,compare_runs,running_trends,recovery,training_context,sync_status. No agent can modify the Garmin account.Garmin login is interactive on your machine. Password and MFA are not stored. Renewable OAuth tokens are AES-256-GCM encrypted in D1, with the key stored separately as a Worker secret. A revoked session requires repeating the interactive login.
Privacy: The D1 cache includes original Garmin JSON, and private R2 files can contain precise GPS tracks. Protect Cloudflare account access and the encryption key. The MCP tools do not expose the private R2 files or raw chart responses;
run_chartexposes only a bounded allowlist of non-coordinate metrics. Activity names and timestamps may still be identifying.
Related MCP server: hybridlog
Requirements
Node 24.11.1+ (required by Alchemy beta.83), pnpm 10.17.0, a Cloudflare account with Workers, D1, R2 and Zero Trust Access. R2 may require a billing method. A ChatGPT plan/UI that supports private custom MCP apps and OAuth (or another compatible MCP client).
An Alchemy
defaultCloudflare OAuth profile with access to your account (pnpm exec alchemy profile show). Configure it withALCHEMY_TUI=1 pnpm exec alchemy profile edit --profile default --add Cloudflarein an interactive terminal. The override is needed when Pi is on PATH because Alchemy otherwise detects even a human shell as non-interactive. Include the deployment permissions listed below, including Zero Trust organization/IdP reads and Access app/policy management. For one-time Garmin-session upload, Wrangler must separately have D1 access (pnpm exec wrangler login). Use the same Cloudflare account in both.Your exact Cloudflare Access login email. If you don't have a Zero Trust organization, choose an available
*.cloudflareaccess.comteam domain; otherwise Alchemy discovers the existing domain without changing the organization.
Deploy
pnpm install
cp .env.example .env.prod
chmod 600 .env.prod
# Edit .env.prod with your CLOUDFLARE_ACCOUNT_ID, ACCESS_EMAIL and
# GARMIN_TOKEN_KEY (generate it with: openssl rand -base64 32).
# Set NEW_ACCESS_TEAM_DOMAIN only if there is no existing Zero Trust organization.
pnpm check && pnpm test
pnpm run plan
pnpm run deployReview the plan before deployment: the Access application must have Managed OAuth enabled, one exact-email allow rule, and your Worker as its destination. Do not connect an unauthenticated Worker to ChatGPT. Deployment prints the /mcp URL. Alchemy creates the Access app/policy, Worker, D1 schema, private R2 bucket (no schedule). Keep the encryption key backed up securely; losing it prevents decrypting the Garmin session.
On a machine with an interactive terminal and Wrangler authenticated to the same account:
pnpm garmin:connectEnter your Garmin email, hidden password and MFA code if requested. The bootstrap forces an OAuth1→OAuth2 refresh through the Worker-side client and reads one activity before uploading only encrypted tokens and IV to remote D1. Do not paste credentials, tokens or FIT/GPX data into a GitHub issue or commit them. .env.prod, Alchemy state, Wrangler state and .private/ are gitignored.
Optional unattended Cloudflare API token
Local deployments can use Alchemy's OAuth profile; no Cloudflare API token is needed. For unattended CI, provision an account-scoped token (or use an Alchemy admin profile to mint one) with only the permissions needed for Zero Trust Access organization/IdP and app/policy management, Workers Scripts (including Alchemy state-store bootstrap), D1, R2 and Secrets Store. Minting API tokens requires an additional API Tokens — Write bootstrap credential; do not grant it to routine deployments. Follow the Cloudflare permissions reference and Alchemy CI guide.
Connect and verify
In ChatGPT, enable developer mode and create a private custom MCP app using the deployed
https://…workers.dev/mcpURL with OAuth authentication. Approve the Access email PIN for your allowlisted email. If the client uses a different OAuth callback, add only its exact callback URL todynamicClientRegistration.allowedUrisinalchemy.run.tsand redeploy; do not remove authentication.Call
refresh_latest_run, then inspectsync_status(sync_mode: on_demand),latest_runandrun_details. Compare distance, time, laps and HR against Garmin Connect. Testlist_runs,running_trendsandrecoverywhere available. After a new run appears on Garmin, explicitly callrefresh_latest_run;updated,no_new_run,cooldownandbusydescribe what happened. Try again after its retry interval if Garmin is still processing the activity.An unauthenticated request to
/mcpmust receive 401. No tool should offer Garmin modification.run_chartis opt-in and paginates up to 100 samples from the first 500 cached chart points; it never returns GPS coordinates or the original ZIP/FIT.
No scheduled sync runs. refresh_latest_run only retrieves the newest run among five recent Garmin activities; it does not paginate history, fetch recovery metrics or download originals. Historical runs and recovery data imported before this change remain available with their cache timestamps, but new older imports and missed runs are not automatically backfilled. The legacy syncOnce implementation remains in src/garmin.ts for maintenance, but the Worker has no scheduled handler. Legacy sync/backfill checkpoints in sync_status are historical, not evidence of current background syncing. Deploy this change to remove the existing Cloudflare cron trigger.
Tool semantics and limits
Dates on runs are Garmin-local dates. Previously imported recovery queries use UTC calendar dates and device-dependent metrics may be missing (
null, not zero). Recovery is cache-only and is not updated by run refresh; check timestamps before discussing readiness.Pace = moving seconds × 1000 / distance in meters; elapsed time is preserved separately. Cadence uses Garmin's steps-per-minute summary or a moving-time-weighted lap average.
compare_runsreturns newest-minus-older numeric deltas (negative pace is faster). Different distances/intensities may limit comparisons.At most 100 runs per range, 10 comparison IDs, 365 trend days, 100 samples per
run_chartcall.run_detailsexcludes the chart;details_pendingmeans usable laps are missing;full_details_pendingmeans at least one of laps, HR zones or chart samples is missing.data_statusdistinguishesnot_fetched(no cached response),unavailable(Garmin returned null/empty on the last request),available, andunrecognized_response(HR-zone schema not recognized). None of the missing states proves Garmin will never provide the data.hr_time_in_zonescontains Garmin'szoneNumber,secsInZoneand optionalzoneLowBoundary, not estimates from lap-average HR.refresh_latest_runis not marked MCP read-only: it reads Garmin and mutates the local cache, never the Garmin account. It makes one short activity-list request plus up to three detail requests (laps, HR zones, chart), each spaced by five seconds, only for missing/invalidated sections; it won't perform history pagination, recovery calls or original activity downloads. A concurrent refresh returnsbusyinstead of racing token updates.sync_statustracks manual refresh status and exposes legacy sync checkpoints; after a manual failure,refresh_latest_error_stagecontains only a fixed checkpoint and HTTP status (never an exception body), and is cleared on success.training_contextgives a starting goal of sub-30-minute 5K (6:00/km) and eventual 10K. Suggestions are for the runner to review, not medical advice or automatic workout writes.
Failure modes
needs_initial_login / needs_reauthentication: run the interactive bootstrap again and investigate revoked or expired Garmin tokens. rate_limited: wait rather than retrying rapidly. upstream_error: Garmin might be unavailable or its undocumented API may have changed. Do not log third-party exception bodies: they can contain credentials or OAuth tokens. The adapter is isolated in src/garmin.ts and the bootstrap in scripts/connect-garmin.ts for repair or replacement. Back up D1 and R2; keep the key separately.
Local checks: pnpm check && pnpm test; Worker bundle: pnpm exec wrangler deploy src/worker.ts --dry-run --outdir /tmp/garmin-build --compatibility-date 2026-09-20 --compatibility-flags nodejs_compat. Tests and dry-runs cannot prove production Access OAuth or Garmin token renewal; validate against your own account.
This server cannot be deployed
Maintenance
Related MCP Connectors
Garmin data in Claude & ChatGPT via the Garmin Health API. OAuth sign-in, no password sharing.
Remote streamable-HTTP MCP server running on a single Cloudflare Worker. Your assistant gets live Airbnb, Amazon, Booking.com, Google Flights, Maps and Reddit data, social search on X, Instagram and TikTok, the Meta Ad Library, and image/video generation without any keys. Connect your own accounts to let it send WhatsApp or Telegram messages, work an IMAP inbox, manage Meta Ads campaigns and publish to X and LinkedIn. OAuth 2.1 with PKCE; stored credentials are AES-256-GCM encrypted.
Garmin data in Claude: 135 tools — activities, sleep, HRV, training, workouts. Free, open source.
Ask your AI about your fitness: activities and data from Garmin, COROS, Strava, GPX and more
Related MCP Servers
- FlicenseNot gradedqualityBmaintenanceA stateless MCP server for Cloudflare Workers that exposes read-only Garmin Connect data (activities, health, sleep, training status, HRV, VO2 max) for a single securely linked personal account.-
- AlicenseNot gradedqualityAmaintenanceMCP server that mirrors your Garmin data into a personal database and exposes tools for health summaries, training load, muscle readiness, and race analysis, with optional chat-driven insights via stdio or HTTP.1MIT
- FlicenseNot gradedqualityBmaintenanceRemote MCP server for Garmin Connect on Cloudflare Workers (free tier), providing 139 tools and 5 resources covering activities, health, training, workouts, nutrition, challenges, devices, gear, weight, women's health, courses, FIT-file analysis, and user profile, with OAuth 2.1 login flow.1-
- AlicenseNot gradedqualityBmaintenanceEnables reading and analyzing Garmin Connect data—activities, health metrics, FIT files, and challenges—through natural language, for AI assistants like Claude and ChatGPT.MIT