Skip to main content
Glama
RedlineTriad

Garmin Cloud MCP

by RedlineTriad

Garmin Cloud MCP

A personal, stateless Model Context Protocol server for Cloudflare Workers. The protected MCP endpoint exposes nine bounded, read-only tools for one securely linked personal Garmin Connect account.

The MCP endpoint uses Cloudflare's createMcpHandler() with a fresh MCP server for every request. There are no MCP protocol sessions, SSE endpoints, or retained MCP transport state. Inbound OAuth grants/codes/tokens and encrypted Garmin linking state are retained in separate Cloudflare KV namespaces. A narrowly scoped, single-owner Durable Object coordinates one-time MFA consumption; it is application security state, not MCP transport state.

Tools

All returned values are normalized into stable JSON shapes and also rendered as MCP text content. Unavailable scalar metrics are null; fields Garmin adds later are omitted. GPS polylines, charts, raw health/sleep samples, and other bulky arrays are never passed through.

Tool

Purpose

Input and bounds

get_profile

Concise owner profile

No input

list_activities

Newest-first activity summaries

start defaults to 0 (maximum 10000); limit defaults to 20 (maximum 50)

get_activity

One activity summary with at most 20 basic splits

Positive Garmin activity ID as a decimal string

get_daily_health

Steps, heart rate, stress, body battery, intensity, and respiration

One real YYYY-MM-DD local Garmin calendar date

get_sleep

Sleep total, stages, scores, SpO2, stress, and respiration

One real YYYY-MM-DD local Garmin calendar date

get_training_readiness

Recovery score and bounded sleep, load, HRV, and stress factors

One real YYYY-MM-DD local Garmin calendar date; at most 8 snapshots

get_training_status

Training status, acute/chronic load, workload ratio, and load focus

One real YYYY-MM-DD local Garmin calendar date

get_hrv

Overnight HRV summary, status, and personal baseline

One real YYYY-MM-DD local Garmin calendar date; no raw readings

get_vo2_max

Generic and cycling VO2 max and fitness-age estimates

One real YYYY-MM-DD local Garmin calendar date

The training/recovery category deliberately has four non-overlapping, single-date tools: readiness explains immediate recovery factors, status describes training load, HRV describes overnight variability against a personal baseline, and VO2 max reports aerobic-capacity estimates. The single-date contracts keep calls bounded and match the current endpoint division verified against python-garminconnect commit 414b540.

Examples:

{ "name": "list_activities", "arguments": { "start": 0, "limit": 10 } }
{ "name": "get_activity", "arguments": { "activityId": "1234567890" } }
{ "name": "get_daily_health", "arguments": { "date": "2026-08-12" } }
{ "name": "get_sleep", "arguments": { "date": "2026-08-12" } }
{ "name": "get_training_readiness", "arguments": { "date": "2026-08-12" } }
{ "name": "get_training_status", "arguments": { "date": "2026-08-12" } }
{ "name": "get_hrv", "arguments": { "date": "2026-08-12" } }
{ "name": "get_vo2_max", "arguments": { "date": "2026-08-12" } }

Each normalized result is capped at 64 KiB. Garmin's upstream JSON is capped at 2 MiB before normalization; responses above either limit fail safely. Activity list results are capped to the requested page, activity splits to 20, and training-readiness snapshots to 8. This service sends the requested dates to Garmin Connect and stores encrypted Garmin authorization tokens in Cloudflare KV. It does not retain Garmin response data or MCP transport/session state. Training readiness, training status, HRV, and VO2 max are sensitive health and fitness data returned to the authorized MCP client. Raw HRV readings, device identifiers/details, user/profile identifiers, long-form feedback prose, and other unlisted upstream fields are omitted; concise Garmin feedback keys are retained where they explain a normalized metric.

Related MCP server: Garmin Open MCP

Authorization

/mcp is protected by the current @cloudflare/workers-oauth-provider OAuth 2.1 implementation. The Worker provides:

  • authorization code flow with S256 PKCE; implicit flow and plain PKCE are disabled;

  • rotating refresh tokens when offline_access is granted;

  • exact resource/audience binding to the MCP URL;

  • RFC 9728 protected-resource metadata and RFC 8414 authorization-server discovery;

  • garmin:read and offline_access scopes, with unknown or excess scopes rejected;

  • Client ID Metadata Documents (CIMD) as the preferred registration mechanism; and

  • constrained dynamic client registration (DCR) at /register only as a fallback for older MCP clients.

CIMD clients are public clients: their metadata must select token_endpoint_auth_method: none, and they must use S256 PKCE. The Worker sets clientIdMetadataDocumentEnabled: true; the matching global_fetch_strictly_public compatibility flag is required to keep metadata fetches SSRF-safe.

The /authorize endpoint uses a reusable personal-owner sign-in and explicit consent screen. The owner password is a Worker secret, never a client secret. Login and consent use CSRF protection, signed Secure/HttpOnly/SameSite cookies, bounded request bodies, and generic authentication errors.

Open /garmin on the HTTPS Worker host and sign in with the same OWNER_PASSWORD used by the authorization screen. The page shows whether the personal account is linked and supports:

  • entering the Garmin email and password to start or retry linking;

  • entering a Garmin multi-factor code in a second, one-time browser step;

  • verifying the resulting Garmin token with the Garmin profile endpoint before saving it; and

  • unlinking all locally stored Garmin tokens, MFA continuation, and rate-limit state.

Garmin credentials and MFA codes are submitted only over HTTPS and are never logged or written to KV. The password exists only for the duration of the first Worker request. Garmin's widget flow allows MFA to continue without the password: the Worker retains only Garmin cookies, an MFA CSRF token, the sign-in context, a random state, and timestamps. That continuation is AES-GCM encrypted in GARMIN_KV, expires after ten minutes, and is deleted before its single use.

KV does not offer atomic get-and-delete. To prevent two concurrent MFA submissions from both reaching Garmin, GARMIN_MFA_COORDINATOR transactionally grants one claim for the SHA-256 hash of the random continuation state. It never stores Garmin cookies, credentials, MFA codes, tokens, or MCP session state. The encrypted continuation itself remains in GARMIN_KV.

Garmin DI access and refresh tokens are stored as one authenticated AES-GCM record in GARMIN_KV. Non-secret owner and created/updated/expiry metadata is present on the versioned envelope; token values remain encrypted. Refresh uses Garmin's DI OAuth token endpoint, and the complete replacement pair is committed with one KV write only after Garmin accepts and verifies it. Records created by the previous OAuth1-based flow remain readable and refreshable during migration.

Unlinking deletes local state. This project does not claim to revoke tokens at Garmin because no supported Garmin token-revocation endpoint was verified.

Requirements

  • Node.js 24 or newer

  • Corepack with pnpm 11.3.0

Local setup

corepack enable
pnpm install
pnpm typegen
GARMIN_KEY="$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=')"
printf 'OWNER_PASSWORD=replace-with-a-long-random-password\nGARMIN_TOKEN_ENCRYPTION_KEY=v1:%s\n' "$GARMIN_KEY" > .dev.vars
pnpm dev

.dev.vars is ignored by Git. Use a long, unique password; never put it in wrangler.jsonc or commit it. Local Wrangler supplies local KV namespaces for the configured OAUTH_KV and GARMIN_KV bindings and local Durable Object storage for GARMIN_MFA_COORDINATOR. Vite starts the Worker locally and prints its URL. Credential linking is deliberately rejected over plain HTTP; use an HTTPS tunnel or deployed preview when exercising /garmin with a real account. Check the public health endpoint with:

curl http://localhost:5173/health

An unauthenticated request to /mcp returns a Bearer challenge pointing to:

/.well-known/oauth-protected-resource/mcp

The authorization-server metadata is at /.well-known/oauth-authorization-server.

Cloudflare configuration and deployment

The committed OAUTH_KV and GARMIN_KV IDs identify this Worker's production namespaces. For a new Cloudflare account, create replacement namespaces, update those IDs, put the independent secrets, generate fresh types, and deploy with Wrangler:

pnpm wrangler kv namespace create OAUTH_KV --binding OAUTH_KV
pnpm wrangler kv namespace create GARMIN_KV --binding GARMIN_KV
pnpm wrangler secret put OWNER_PASSWORD
pnpm wrangler secret put GARMIN_TOKEN_ENCRYPTION_KEY
pnpm typegen
pnpm wrangler deploy

Set GARMIN_TOKEN_ENCRYPTION_KEY to v1: followed by a base64url-encoded 32-byte random key, as in the local setup example. It must be independent of OWNER_PASSWORD. Alternatively, add --update-config to each KV command and review its wrangler.jsonc edit. Never commit a real password, encryption key, or .dev.vars. Namespace IDs are non-secret configuration and are intentionally committed for this private application.

Rotating the encryption key makes existing Garmin records unreadable. For recovery, restore the old key or use the authenticated /garmin recovery button to discard unreadable state, then link again. The current envelope/key version is v1; unsupported versions fail closed.

Unofficial Garmin integration and recovery

Garmin Connect does not provide a supported public API for this personal workflow. The endpoint sequence is adapted from the MIT-licensed garth, garmin-connect, and python-garminconnect projects (see NOTICE.md), uses an explicit cookie jar and Worker-native Web Crypto/fetch, and may break when Garmin changes SSO HTML or private endpoints. There is no runtime dependency on those projects.

Garmin and Cloudflare aggressively limit automated sign-in (commonly HTTP 429 with Cloudflare 1015 pages). The Worker never automatically retries a credential or MFA submission. It records generic failure counts and increasing backoff in GARMIN_KV; an upstream limit enforces at least a one-hour cooldown. If limited, stop retrying, wait for the displayed/reported cooldown, confirm Garmin's service status, and sign in through Garmin's official site if account recovery is required.

If linking reports an account lock or required phone-number update, complete that recovery on Garmin's official site and retry later. If saved tokens stop refreshing, unlink and link again.

Connect from ChatGPT

ChatGPT needs the publicly reachable HTTPS Worker deployment; it cannot connect directly to the local Vite URL. Garmin account linking and ChatGPT app creation remain interactive owner steps.

  1. In ChatGPT, enable developer mode for the workspace/account under Settings → Apps → Advanced settings (the exact controls depend on plan and workspace role).

  2. Create a custom app/MCP connection and enter https://YOUR_WORKER_HOST/mcp as the server URL.

  3. Choose OAuth and select Client ID Metadata Document (CIMD) for client registration when that choice is shown. CIMD is the preferred path for this server.

  4. Request garmin:read offline_access. offline_access is required for ChatGPT to retain access by refreshing expired access tokens.

  5. Complete the owner-password sign-in, review the explicit consent page, authorize, and let ChatGPT scan the nine read-only tools.

The /register DCR endpoint remains advertised for older MCP clients that cannot use CIMD. It is a compatibility fallback, not the registration option to select in ChatGPT. DCR accepts only supported HTTPS/loopback redirects, authorization-code plus refresh-token grants, the code response type, and supported token authentication methods.

See OpenAI's developer mode and custom MCP app guidance and the MCP authorization specification for the client-side discovery flow.

Checks

pnpm format:check
pnpm lint
pnpm typecheck
pnpm test
pnpm build
pnpm wrangler:dry-run

pnpm test runs the endpoint tests inside the Cloudflare Workers runtime. The MCP tests issue independent authenticated initialize, list, and call requests and assert that no Mcp-Session-Id is returned. The suite also covers discovery, CIMD advertisement, DCR constraints, PKCE/scope/ resource rejection, login and consent CSRF, bearer challenges, refresh rotation, secret leakage, public health, streamed body bounds, encrypted storage, concurrent one-time MFA, Garmin failure mapping, endpoint contracts, response normalization and bounds, and atomic Garmin token refresh persistence. Training/recovery coverage also fixes the upstream URL contracts, rejects malformed roots and invalid dates, verifies strict normalized allowlists and explicit nulls, caps readiness snapshots, and excludes raw HRV samples and device/profile identifiers.

After deployment, open https://YOUR_WORKER_HOST/garmin and link the Garmin account before using the tools. If Garmin authorization expires, use the same page to unlink/relink. For code updates, run all checks, push main, wait for GitHub Actions, then run pnpm wrangler deploy. For encryption key rotation, retain the previous key until the existing record is deliberately discarded and the account is linked again; changing the key alone makes the current record unreadable.

F
license - not found
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    -
    quality
    C
    maintenance
    Personal MCP server for interacting with your Garmin Connect data. Exposes 62 tools across 11 domains including activities, health, training, and workouts.
    1
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Unofficial MCP server bridging Garmin Connect to MCP clients and ChatGPT, providing tools to access health data, activities, and trends via a self-hosted API.
    11
    MIT
  • A
    license
    -
    quality
    C
    maintenance
    MCP server for Garmin Connect that enables users to access and manage their personal health and fitness data, including daily summaries, heart rate, sleep, HRV, stress, body composition, activities, and training readiness, with secure per-user authentication.
    Apache 2.0

View all related MCP servers

Related MCP Connectors

  • Hosted remote MCP server for YNAB on Cloudflare Workers with OAuth

  • MCP server for Withings health data — sleep, activity, heart, and body metrics.

  • Garmin data in Claude & ChatGPT via the Garmin Health API. OAuth sign-in, no password sharing.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/RedlineTriad/garmin-connect-cloudflare-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server