Skip to main content
Glama
Garmin Connect MCP

Garmin Connect MCP

by pedrokehl

Garmin Connect MCP

Let Claude, Gemini, ChatGPT or any other MCP client read your own Garmin data (activities, sleep, HRV, Body Battery, stress, steps, heart rate, weight and more) through Garmin's official Connect Personal API.

"How did I sleep this week compared to last week?" · "Summarize my last five runs." · "Which days this month did I miss my step goal?"

It exposes all 26 Personal API endpoints as read-only tools, and you can host it yourself for free on Vercel.

Unofficial community project, not affiliated with or endorsed by Garmin.

Set up on Vercel

1. Create a Garmin API key. In Garmin Connect on the web, open Settings → Personal API Token, select Create Key and copy it. Garmin shows it only once.

2. Generate two secrets: a client secret for the app to store, and a sign-in password that only you type. Run this twice:

openssl rand -base64 32

3. Deploy and fill in the variables when Vercel asks:

Deploy with Vercel

Environment variables

Variable

Value

What it's for

GARMIN_API_KEY

The key from step 1

Reads your Garmin data. Required.

MCP_OAUTH_CLIENT_SECRET

First secret from step 2

The client secret you enter in Claude, Gemini or ChatGPT.

MCP_AUTH_TOKEN

Second secret from step 2

Your sign-in password, typed on your server's sign-in page when you connect an app (step 5). Also lets header-based clients like Claude Code connect directly.

MCP_OAUTH_CLIENT_ID

garmin-mcp (pre-filled)

The client ID you enter in the app. Keep the default.

MCP_OAUTH_REDIRECT_URIS

Optional, add later if needed

Extra allowed sign-in callback URLs, comma-separated. Claude's, Gemini's and ChatGPT's are already allowed.

MCP_OAUTH_CLIENT_SECRET and MCP_AUTH_TOKEN are each optional, but at least one must be set. Without MCP_AUTH_TOKEN, the sign-in page asks for the client secret instead.

Your server is now at https://<your-project>.vercel.app/mcp. Vercel's free Hobby plan is enough. Recommended: under Settings → Environment Variables, recreate the two secrets as Sensitive (their values can't be read back afterwards), then redeploy. The deploy button can't do this for you.

4. Add a custom connector in your app with:

Field

Value

Server URL

https://<your-project>.vercel.app/mcp

Client ID

garmin-mcp

Client secret

Your MCP_OAUTH_CLIENT_SECRET

  • Claude (claude.ai and Claude Desktop): Settings → Connectors → Add custom connector. The client ID and secret go under Advanced settings.

  • Gemini: add a custom MCP connector and fill in Client ID and Client secret. If it also asks for URLs, use https://<your-project>.vercel.app/oauth/authorize (authorization), https://<your-project>.vercel.app/oauth/token (token) and scope garmin:read.

  • ChatGPT: create a connector in developer mode (on plans that offer custom connectors), choose OAuth with a user-defined OAuth client, and leave the registration URL empty.

5. Sign in. When you connect, Claude, Gemini or ChatGPT opens a sign-in page from your own server that asks for MCP_AUTH_TOKEN. Paste it and select Allow access. You only do this once per connection.

This is expected. The client ID and secret identify the app; the sign-in page proves the person connecting is you, so a leaked client secret alone isn't enough to read your data. If the page says a redirect URL isn't allowed, add that URL to MCP_OAUTH_REDIRECT_URIS and redeploy.

Claude Code connects with the token directly:

claude mcp add --transport http garmin https://<your-project>.vercel.app/mcp --header "Authorization: Bearer <MCP_AUTH_TOKEN>"

Other clients, running locally, and other hosts: see docs/deployment.md.

Related MCP server: Garmin MCP Worker

Tools

All tools are read-only. Dates are YYYY-MM-DD in your local calendar.

Area

Tools

Profile

garmin_get_user_settings

Activities

garmin_search_activities, garmin_get_activity, garmin_get_activity_workouts, garmin_download_activity_file (original FIT/ZIP)

Daily wellness

garmin_get_daily_summary, garmin_get_daily_summaries, garmin_get_steps, garmin_get_stress, garmin_get_hydration, garmin_get_wellness_activities

Sleep

garmin_get_sleep, garmin_get_sleep_summaries

Heart & recovery

garmin_get_heart_rate, garmin_get_heart_rate_zones, garmin_get_hrv, garmin_get_hrv_summaries, garmin_get_body_battery, garmin_get_spo2, garmin_get_fitness_age

Body

garmin_get_weight_entries, garmin_get_weight_summary, garmin_get_weight_goals

Nutrition

garmin_get_nutrition_log

Women's health

garmin_get_menstrual_cycle_summary, garmin_get_menstrual_cycle_day

Each tool's description tells the model what it returns, its units and its limits, so you don't need to learn them.

Good to know

  • Your own data only, read-only. That's the scope of Garmin's Personal API. It doesn't cover commercial or multi-user apps, and it can't upload workouts.

  • Keys expire. You choose 30 days to 1 year when creating the key, and Garmin emails you before it expires. Create a new key, update GARMIN_API_KEY in Vercel, and redeploy.

  • Keep your secrets secret. MCP_AUTH_TOKEN and MCP_OAUTH_CLIENT_SECRET are what protect your data. If one leaks, change it in Vercel and redeploy. Changing either also signs out every OAuth connection.

Development

npm install && npm test

npm run smoke checks every tool against the real API using GARMIN_API_KEY from .env, and prints only response shapes, never your data. Architecture and conventions are in AGENTS.md.

License

MIT © Pedro Kehl

Available Tools

26 tools
garmin_download_activity_fileDownload original activity file (FIT)A
Read-onlyIdempotent

Download the original file recorded by the device for an activity, usually a ZIP containing the .FIT file with every sample (GPS track, heart rate, power, cadence…). When running locally with GARMIN_DOWNLOAD_DIR set, the file is saved there and the path is returned. Otherwise the file is returned inline as a base64 embedded resource, up to 3 MB. For summaries, splits or metrics, prefer garmin_get_activity: it is much smaller.

Inputs: activityId (required).

garmin_get_activityGet activity detailsA
Read-onlyIdempotent

Get the full detail of one activity: name, type, event type, time zone, device and metadata, the summary (distance m, duration s, speeds m/s, heart rate, power, cadence, calories, training effect, elevation), split/lap summaries, and dive or multisport info when relevant. Get the activityId from garmin_search_activities. Does not include per-second samples; use garmin_download_activity_file for the raw FIT data.

Inputs: activityId (required).

garmin_get_activity_workoutsGet workouts performed in an activityA
Read-onlyIdempotent

List the structured workouts that were followed during an activity (workout name, sport, steps with targets, creation time). Returns an empty array when the activity was not a structured workout.

Inputs: activityId (required).

garmin_get_body_batteryGet Body Battery (date range)A
Read-onlyIdempotent

Get Body Battery (energy level 0–100) per day: charged and drained totals, a time series bodyBatteryValuesArray, feedback events and the activities that impacted it. Maximum range: 31 days. Set chunks to get the lowest and highest values of each part of the day instead of reading the full series.

Inputs: endDate (required), startDate (required), chunks.

garmin_get_daily_summariesGet daily wellness summaries (date range)A
Read-onlyIdempotent

Get daily wellness summaries (steps, calories, distance, intensity minutes, heart rate, stress, Body Battery, floors…) for every day in a range, returned as { data: [...] } with one entry per day. Maximum range: 365 days (Garmin documents 1 year). Each day is a large object, so a year of data is very long: prefer a few weeks or months per call unless the whole year is needed. For one day garmin_get_daily_summary is simpler.

Inputs: endDate (required), startDate (required).

garmin_get_daily_summaryGet daily wellness summary (one day)A
Read-onlyIdempotent

Get the user's complete wellness summary for one day: total/active/BMR kilocalories, steps and step goal, distance (meters), floors climbed, intensity minutes (moderate/vigorous) and goal, highly active/active/sedentary/sleeping seconds, min/max/resting heart rate, stress averages and durations, Body Battery charged/drained/highest/lowest, SpO2 and respiration. Best single call for "how was my day?". For many days at once use garmin_get_daily_summaries.

Inputs: calendarDate (required).

garmin_get_fitness_ageGet Fitness AgeA
Read-onlyIdempotent

Get the latest Fitness Age as of a date: chronologicalAge, fitnessAge, achievableFitnessAge, previousFitnessAge, and the components behind it (BMI or body fat, resting heart rate, vigorous activity days/minutes) with improvement targets.

Inputs: calendarDate (required), fullHistoryScan.

garmin_get_heart_rateGet all-day heart rate (one day)A
Read-onlyIdempotent

Get heart rate for one day: restingHeartRate, minHeartRate, maxHeartRate, lastSevenDaysAvgRestingHeartRate, abnormal-HR alerts, and heartRateValues, a time series of [timestamp, bpm] rows whose columns are described by heartRateValueDescriptors. The time series is large; for resting HR trends over many days prefer garmin_get_daily_summaries.

Inputs: date (required).

garmin_get_heart_rate_zonesGet heart rate zonesA
Read-onlyIdempotent

Get the user's heart rate zone configuration, one entry per sport (default, running, cycling…): training method (%max HR, %HRR or %LTHR), zone1Floor..zone5Floor (bpm), and the max, resting and lactate threshold heart rates used.

Inputs: generateDefaults.

garmin_get_hrvGet heart rate variability (one night)A
Read-onlyIdempotent

Get overnight HRV for one date: hrvSummary (weeklyAvg, lastNightAvg, lastNight5MinHigh, baseline, status: NONE, POOR, LOW, UNBALANCED, BALANCED or INVALID, feedbackPhrase) and hrvReadings (5-minute HRV values in ms during sleep), plus the sleep window timestamps. Requires a compatible watch worn overnight.

Inputs: calendarDate (required).

garmin_get_hrv_summariesGet HRV daily summaries (date range)A
Read-onlyIdempotent

Get one HRV summary per day for a date range (weeklyAvg, lastNightAvg, baseline, status, feedback), without the 5-minute readings. Use for HRV trends and recovery analysis.

Inputs: toDate (required), fromDate (required).

garmin_get_hydrationGet daily hydration (date range)A
Read-onlyIdempotent

Get logged water intake per day: calendarDate, valueInML (consumed), goalInML, sweatLossInML (estimated from activities), activityIntakeInML and lastEntryTimestampLocal. Only has data if the user logs hydration in Garmin Connect. Maximum range: 31 days.

Inputs: endDate (required), startDate (required).

garmin_get_menstrual_cycle_dayGet menstrual cycle day viewA
Read-onlyIdempotent

Women's health: detailed view of one day: the user's daily log (flow, symptoms, mood and other entries), the cycle summary for that day, and, during pregnancy tracking, weekly weight goal ranges and optionally baby movement (kick count) data.

Inputs: calendarDate (required), skipbm, todayDate, includeAnalysis, +1 more.

garmin_get_menstrual_cycle_summaryGet menstrual cycle summaryA
Read-onlyIdempotent

Women's health: get the menstrual cycle that contains a date: cycle start/end, dayInCycle, currentPhase and days until the next phase, period length, predicted cycle length, fertile window, predicted ovulation date, and pregnancy info (due date, week) when pregnancy tracking is on. Only has data if the user tracks their cycle in Garmin Connect.

Inputs: calendarDate (required).

garmin_get_nutrition_logGet nutrition / food log (one day)A
Read-onlyIdempotent

Get the user's food log for one day from Garmin Connect nutrition tracking: daily nutrition goals, totals of calories and nutrients consumed (protein, carbs, fat, fiber…), logged foods with serving sizes, and meal-by-meal details (breakfast, lunch, dinner, snacks). Only has data if the user logs food in Garmin Connect.

Inputs: date (required), mealId, includeGoals, includeMedia, +1 more.

garmin_get_sleepGet sleep (one night)A
Read-onlyIdempotent

Get sleep data for one calendar date (the night ending on that morning): sleep start/end timestamps, sleepTimeSeconds, deepSleepSeconds, lightSleepSeconds, remSleepSeconds, awakeSleepSeconds, awakeCount, napTimeSeconds, sleep scores and feedback, average/lowest SpO2, respiration, avgHeartRate and avgSleepStress. Durations are in seconds. For several nights use garmin_get_sleep_summaries.

Inputs: date (required).

garmin_get_sleep_summariesGet sleep summaries (date range)A
Read-onlyIdempotent

Get one sleep summary per night for a date range, each with sleep start/end timestamps, sleepTimeSeconds, deepSleepSeconds, lightSleepSeconds, remSleepSeconds, awakeSleepSeconds, awakeCount, napTimeSeconds, sleep scores and feedback, average/lowest SpO2, respiration, avgHeartRate and avgSleepStress. Durations are in seconds. Maximum range: 31 days per request.

Inputs: endDate (required), startDate (required).

garmin_get_spo2Get blood oxygen / SpO2 (one day)A
Read-onlyIdempotent

Get Pulse Ox (SpO2, %) for one day: averageSpO2, lowestSpO2, latestSpO2, lastSevenDaysAvgSpO2, avgSleepSpO2, single readings and hourly averages, plus altitude acclimation data. Requires a watch with Pulse Ox enabled.

Inputs: calendarDate (required).

garmin_get_stepsGet daily steps (date range)A
Read-onlyIdempotent

Get daily step totals for a date range: one entry per day with calendarDate, totalSteps, totalDistance (meters) and stepGoal. Maximum range: 28 days per request; split longer periods, or use garmin_get_daily_summaries (up to 365 days), which also includes steps.

Inputs: endDate (required), startDate (required).

garmin_get_stressGet daily stress (date range)A
Read-onlyIdempotent

Get daily stress statistics for a date range: one entry per day with calendarDate and a values map containing overallStressLevel (0–100) and restStressDuration, lowStressDuration, mediumStressDuration, highStressDuration (seconds). Stress is derived from heart rate variability. Maximum range: 28 days per request.

Inputs: endDate (required), startDate (required).

garmin_get_user_settingsGet user profile settingsA
Read-onlyIdempotent

Get the user's Garmin Connect settings: displayName, timeZone, preferredLocale, measurementSystem (metric/statute), first day of week, date/time/number formats, heart rate and power display formats, hydration and glucose units, golf units, wheelchair mode and preferred training days. Call this first to learn the user's time zone (so you know what "today" is) and preferred units.

Inputs: no named parameters.

garmin_get_weight_entriesGet all weight entries (date range)A
Read-onlyIdempotent

Get every individual weigh-in in a date range (from Index scales, other smart scales or manual entries): date, timestampGMT, weight, bmi, bodyFat, bodyWater, boneMass, muscleMass, visceralFat, metabolicAge, physiqueRating and weightDelta (change since the previous weigh-in); weight, boneMass and muscleMass are in GRAMS (75000 = 75 kg); bodyFat and bodyWater are percentages; timestampGMT and date are epoch milliseconds. For one value per day plus the range average use garmin_get_weight_summary.

Inputs: endDate (required), startDate (required).

garmin_get_weight_goalsGet weight goals (date range)A
Read-onlyIdempotent

Get the user's weight goals in effect during a date range: userGoals (goalValue = target weight in GRAMS, category such as MANUAL or PREGNANCY, tracking period, start/end dates) and userGoalRanges (low/high target ranges, also in grams).

Inputs: endDate (required), startDate (required).

garmin_get_weight_summaryGet weight summary (date range)A
Read-onlyIdempotent

Get a weight summary for a date range: dailyWeightSummaries (latest weigh-in per day, or all of them with includeAll), totalAverage for the range, and the closest weigh-ins before (previousDateWeight) and after (nextDateWeight) the range; weight, boneMass and muscleMass are in GRAMS (75000 = 75 kg); bodyFat and bodyWater are percentages; timestampGMT and date are epoch milliseconds.

Inputs: endDate (required), startDate (required), includeAll.

garmin_get_wellness_activitiesGet wellness activity summaries (date range)A
Read-onlyIdempotent

Get wellness activities (e.g. breathwork, meditation, relaxation and other non-fitness sessions recorded on the device) for a date range: name, type, notes, start/end timestamps, summary values and time in zones. Both dates are inclusive. Fitness activities such as runs or rides are not here; use garmin_search_activities for those.

Inputs: fromDate (required), untilDate (required).

garmin_search_activitiesSearch activitiesA
Read-onlyIdempotent

Search and list the user's recorded activities (runs, rides, swims, strength sessions, hikes, dives…), newest first by default. Returns an array of activity summaries with: activityId, activityName, description, startTimeLocal/startTimeGMT, activityType, eventType, distance (meters), duration / movingDuration / elapsedDuration (seconds), elevationGain/Loss (meters), averageSpeed/maxSpeed (m/s), calories, averageHR/maxHR (bpm), cadence, power, training effect and more. Use this first to find activity IDs, then call garmin_get_activity for splits and full detail, or garmin_download_activity_file for the original FIT file. All filters are optional and combined with AND. Paginate with start and limit.

Inputs: limit, start, search, sortBy, +28 more.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4/5.0

    Scored across 26 tools

    Disambiguation4/5

    Most tools are clearly separated by metric and resource, with explicit distinctions between singular/range variants and summary/detail variants. There is some intentional overlap between broad daily-summary tools and metric-specific tools, but the descriptions guide selection well.

    Naming Consistency5/5

    All tools use a consistent snake_case `garmin_` prefix followed by a clear action such as `get`, `search`, or `download`. The verb-noun pattern is predictable throughout, including range/detail variants.

    Tool Count4/5

    The set is slightly large at 26 tools, but the Garmin health/fitness domain is broad and most tools cover distinct metrics or lifecycle variants. It is heavy but reasonably scoped rather than bloated.

    Completeness4/5

    The read-only surface is extensive, covering sleep, activities, daily wellness, heart rate, HRV, body battery, weight, nutrition, and menstrual health. Some adjacent areas such as training status, VO2 max, devices, or workouts-as-a-list are missing, but core health-data retrieval is well covered.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      C
      maintenance
      A read-only remote MCP server that lets Claude query your own Garmin Connect data — sleep, HRV, Body Battery, stress, heart rate, and activities — through Garmin's official Personal API. It runs on Cloudflare Workers with OAuth sign-in, so it works from both the Claude mobile app and desktop.
      MIT
    • A
      license
      Not graded
      quality
      Not graded
      maintenance
      A read-only remote MCP server that connects Claude to your own Garmin Connect account through Garmin's official Personal API, running on Cloudflare Workers with OAuth sign-in so it also works in the Claude mobile app. It lets you ask natural-language questions about sleep, HRV, resting heart rate, Body Battery, stress, daily summaries and activities across date ranges.
      MIT
    • A
      license
      Not graded
      quality
      Not graded
      maintenance
      A read-only remote MCP server that lets Claude query your own Garmin Connect wellness and activity data — sleep, HRV, Body Battery, stress, heart rate, daily summaries, and workout details — through Garmin's official Personal API, with OAuth sign-in so it works from mobile and desktop clients.
      MIT
    • A
      license
      Not graded
      quality
      Not graded
      maintenance
      A read-only remote MCP server that lets Claude query your own Garmin Connect data — sleep, HRV, Body Battery, stress, heart rate and activities — through Garmin's official Personal API, with OAuth sign-in so it works in the mobile app as well as on desktop.
      864 npm
      MIT