garmin-personal-mcp
This read-only MCP server lets you query your own Garmin Connect data through Garmin's official Personal API across dates and activity filters.
Get daily overviews and wellness summaries: steps, calories, intensity minutes, heart rate, stress, Body Battery, hydration, SpO2, and respiration.
Review sleep and HRV: full nightly records, trends, scores, stages, overnight vitals, HRV readings, baselines, and status.
Track body metrics: weight, body composition, weight goals, fitness age, heart rate zones, and profile settings.
Inspect nutrition: food logs, meals, nutrients, goals, and optional food photos.
Search and inspect activities: runs, rides, swims, strength, dives, and more by date, type, text, distance, duration, elevation, power, location, and dive filters; retrieve splits and workout steps.
Download an activity's original file to the server's configured download folder.
Access menstrual cycle summaries and daily symptom, flow, and cycle details.
Handle long date ranges automatically, default to the recent 7 days, and omit bulky sample arrays unless requested.
Read-only access to a Garmin Connect account through Garmin's official Personal API. Provides tools for daily wellness summaries (steps, stress, hydration, Body Battery, heart rate, SpO2), sleep and HRV records, weight and weight goals, the nutrition/food log, fitness age, heart rate zones, menstrual cycle data, and activity search/retrieval with splits, structured workouts, and original .fit file downloads. Supports arbitrary date ranges by splitting requests into endpoint-sized chunks.
Garmin Personal MCP
Ask your AI about your Garmin data, on Garmin's official API.
Quickstart · Tools · Authentication · Changelog
Garmin Personal MCP is a free, open-source MCP server that connects your Garmin Connect account to Claude, Cursor, VS Code, or any other MCP client. Ask about last night's sleep, HRV trends, Body Battery, stress, training load, weight, or a specific run, all from your real Garmin account. It runs on Garmin's official Personal API with a revocable key, so it never needs your Garmin password, and it's read-only.
Published on PyPI as garmin-personal-mcp
and in the official MCP Registry as io.github.Mason-Levyy/garmin-personal-mcp.
Unofficial project, official API. This isn't made by Garmin, but it only talks to Garmin's documented Personal API through Garmin's own Python client.
How is this different from other Garmin MCP servers?
Most Garmin MCP servers sign in with your Garmin password through the
unofficial garminconnect library, which breaks whenever Garmin changes its
login. In October 2026 Garmin opened a Personal
API for individuals. This server uses it:
No password, no scraping: a revocable Personal API key you create in Garmin Connect, exchanged for short-lived tokens
The whole Personal API: every working endpoint in Garmin's reference has a tool, with every documented filter
Any date range: ranges longer than an endpoint allows (28 days for steps, 31 for sleep, and so on) are split into requests for you
Model-sized responses: all-day sample arrays are left out unless you ask for them
Read-only: the Personal API can't change your account, and neither can this server
Related MCP server: Garmin MCP Worker
Quickstart
Save your Personal API key (one-time; see Authentication):
uvx garmin-personal-mcp authAdd the server to your client. The install buttons at the top do this in one click for Cursor and VS Code.
Claude Code
claude mcp add garmin -- uvx garmin-personal-mcpOr install it as a plugin, which asks for your key in place of step 1:
/plugin install garmin-personal-mcp --marketplace Mason-Levyy/garmin-personal-mcpClaude Desktop (
claude_desktop_config.json), Cursor (~/.cursor/mcp.json), Windsurf (~/.codeium/windsurf/mcp_config.json), and Cline (cline_mcp_settings.json) all take the same entry:{ "mcpServers": { "garmin": { "command": "uvx", "args": ["garmin-personal-mcp"] } } }VS Code (or use the button above):
code --add-mcp '{"name":"garmin","command":"uvx","args":["garmin-personal-mcp"]}'Codex CLI:
codex mcp add garmin -- uvx garmin-personal-mcpGemini CLI:
gemini mcp add garmin uvx garmin-personal-mcpAsk it something: "how did I sleep last night?", "is my HRV trending down this month?", "compare my last three long runs".
Requires uv. Any MCP client that speaks stdio or streamable HTTP works, not just Claude.
Finding this useful? A ⭐ on GitHub helps other Garmin users find it.
Authentication
Sign in to Garmin Connect on the web and open Settings → Personal API.
Create a key and choose how long it lasts (30 days to a year). Garmin shows it once.
Run
garmin-personal-mcp authand paste it at the hidden prompt. The server checks it with Garmin before saving it to~/.config/garmin/connect-personal-api-keywith owner-only permissions.
Where there's no terminal to prompt in (a script, or a shell an agent runs),
pipe the key in instead, e.g. pbpaste | garmin-personal-mcp auth.
garmin-personal-mcp auth --check reports whether the saved key still works,
without prompting. Garmin emails you about a week before a key expires: create
a new one and run auth again. No restart is needed; the server reads the key
on every call.
Instead of the key file you can set GARMIN_PERSONAL_API_KEY, which takes
precedence. The key file and bearer-token cache are the same ones Garmin's
gc-personal CLI uses, so a key saved by either works for both.
Docker Compose
Run the server as a background HTTP service using the prebuilt image. Save
this as compose.yaml:
services:
garmin:
image: ghcr.io/mason-levyy/garmin-personal-mcp:latest
command: ["--http", "--host", "0.0.0.0"]
environment:
- GARMIN_PERSONAL_API_KEY
- TZ
ports:
- "127.0.0.1:8491:8491"
volumes:
- garmin-config:/home/garmin/.config/garmin
- ./downloads:/home/garmin/Downloads/garmin
restart: unless-stopped
volumes:
garmin-config:latest is the newest release, the same version PyPI serves. To stay on one
version, use a version tag such as :0.2.0 instead.
From the directory containing compose.yaml, create the downloads folder and
pull the image:
mkdir -p downloads
docker compose pullActivity files from garmin_download_activity land in ./downloads. The tool
reports the path inside the container (/home/garmin/Downloads/garmin/...),
which is the same file. The container runs as uid 1000, so on Linux, if your
uid isn't 1000, run sudo chown 1000 downloads.
Don't mount an existing ~/.config/garmin from the host into the container.
The key file there is readable only by its owner, so the container can't read
it unless your uid is 1000. Use one of the options below instead.
The container paths above and uid 1000 stay the same across releases.
The container runs on UTC unless you set TZ, and tools that default to
today use the container's date. In the evening west of UTC that's already
tomorrow, and in the morning east of it, still yesterday. Add your time zone
to .env (or your shell) so "today" matches your day:
TZ=America/Los_AngelesAuthentication
Choose one of these authentication options:
1. Add your API key to a .env file
Create .env next to compose.yaml and add your Personal API key:
GARMIN_PERSONAL_API_KEY='your-personal-api-key'Compose loads this file automatically. You do not need to run auth.
2. Use the interactive auth command
Leave GARMIN_PERSONAL_API_KEY unset in your shell and .env, then run:
docker compose run --rm garmin authPaste your Personal API key at the prompt. It is saved for subsequent starts.
Start the server
docker compose up -dIn your MCP client, add a Streamable HTTP server with the URL
http://127.0.0.1:8491/mcp.
The server only accepts requests addressed to 127.0.0.1 or localhost, and
it has no authentication. To reach it from another machine, keep the port on
127.0.0.1, put an authenticating proxy or tailnet in front (see
Remote / HTTP mode), and add the hostname clients use to
command:
command: ["--http", "--host", "0.0.0.0", "--allowed-host", "garmin.example.ts.net"]Without --allowed-host, requests to any other hostname are rejected.
Build from source
Clone the repository and use compose.yaml in its root, which builds the image from source. From the repository directory, run:
docker compose buildThen follow the authentication and startup steps above. After updating the source, rebuild and restart with:
docker compose up --build -dTools
All tools take dates as YYYY-MM-DD. Range tools default to the 7 days ending
today. Tools that return a list wrap it as {"results": [...]}.
Tool | What it returns |
| One day at a glance: summary, sleep, HRV, Body Battery, stress, and activities |
| Every all-day wellness total for one date |
| Daily summaries across a range (trends over weeks or months) |
| Daily steps, distance, and goal |
| Daily stress level and time in each stress band |
| Daily intake, goal, and sweat loss |
| Daily charge/drain and the events behind it; |
| Resting/min/max HR, 7-day resting average, abnormal HR alerts; samples on request |
| Pulse ox averages, hourly averages, acclimation; readings on request |
| Wellness activities with summary values and time in zones |
| One night in full: score, stages, timings, overnight vitals |
| Nightly sleep records across a range |
| One night of HRV with every reading |
| Nightly HRV, weekly average, baseline, and status across a range |
| Daily weight summaries, range average, and adjacent weigh-ins; every weigh-in with |
| Weight goals in effect and their target ranges |
| Garmin Connect nutrition log: foods, nutrients, goals, meals |
| Activities with every filter Garmin supports (type, dates, distance, power, area, dives, ...) |
| One activity in full, with splits |
| The structured workout followed during an activity |
| Save an activity's original file (ZIP with the .fit) on the server's machine |
| Units, formats, time zone, and training-day preferences |
| Zone floors per sport and the HRs they're based on |
| Fitness age, achievable fitness age, and its components |
| Cycle phase, day, fertile window, and predictions |
| One day's logged symptoms, flow, and cycle detail |
Weights are in grams, distances in meters, and durations in seconds, as Garmin returns them.
Remote / HTTP mode
The default transport is stdio. For network clients:
garmin-personal-mcp --http --host 127.0.0.1 --port 8491This serves streamable HTTP at /mcp. There is no built-in authentication.
Never expose it to the internet. Bind to localhost and front it with
something that authenticates for you: a VPN or tailnet (e.g. tailscale serve), an authenticating reverse proxy, or an OAuth-aware MCP gateway. Behind
a proxy, pass --allowed-host <proxy hostname> (repeatable) so the
DNS-rebinding check accepts its Host header.
Configuration
Variable | Purpose | Default |
| Personal API key; overrides the saved key file | – |
| Where |
|
Troubleshooting
"Garmin rejected the Personal API key": the key expired or was deleted. Create a new one and run
garmin-personal-mcp auth."No usable Garmin Personal API key was found": run
garmin-personal-mcp auth, or setGARMIN_PERSONAL_API_KEYin the client config.{"no_data": ...}: Garmin has nothing for that date yet (today's HRV before you've slept, for example), or your device doesn't record that metric.HTTP 400 on a long range: an endpoint without a documented limit rejected the range. Ask for a shorter one.
FAQ
Is this the official Garmin API? Yes. It uses Garmin's Personal API, which anyone with a Garmin Connect account can enable without applying, through Garmin's Apache-licensed Python client. The project itself is independent.
Can it change my Garmin data? No. The Personal API is read-only. The one
tool that writes anything, garmin_download_activity, saves a file on your
own machine.
Which MCP clients does it work with? Any client that runs a stdio or streamable HTTP server, including Claude Desktop, Claude Code, Cursor, Windsurf, Cline, and VS Code. AI coding agents can follow llms-install.md.
Where does my data go? The server talks only to Garmin. Your key and
bearer token stay in ~/.config/garmin/. Tool results go to whichever MCP
client and model you connect it to.
Can I use it for other people's accounts? No. Garmin's Personal API is for your own data. Apps serving other users need Garmin's business developer program.
How it works
garmin-personal-mcp authsaves the key; each call exchanges it for a bearer token through Garmin'sPersonalApiAuthenticator, which caches the token until an hour before it expires. A token rejected early gets one retry with a fresh exchange.Each tool maps to one endpoint on
personal.connectapi.garmin.com(garmin_get_daycombines several). Ranges longer than an endpoint's documented maximum are fetched in consecutive windows and concatenated.Bulky per-minute sample arrays are dropped by default to keep responses readable for a model;
include_samplesorchunksbring them back.
Development
git clone https://github.com/Mason-Levyy/garmin-personal-mcp
cd garmin-personal-mcp
uv sync
uv run pytestTests run against synthetic fixtures; no account needed. Lint and formatting
are enforced with ruff (uv run ruff check ., uv run ruff format .).
Contributing
Issues and pull requests are welcome, especially when Garmin adds endpoints to the Personal API. See CONTRIBUTING.md for setup, style, and the PR checklist, and SECURITY.md for how to report vulnerabilities privately. Release history is in CHANGELOG.md.
Support the project
If this server saves you time, please star the repo. Stars are how other Garmin users find it in GitHub search and MCP directories. Bug reports and endpoint requests are just as welcome in issues.
License
MIT. Garmin and Garmin Connect are trademarks of Garmin Ltd. or its subsidiaries; this project is not affiliated with or endorsed by Garmin.
Available Tools
26 toolsgarmin_download_activityDownload activity fileAIdempotent
Download an activity's original file (a ZIP, usually holding a .fit) and save it on the machine running this server as activity-<activity_id>.zip, replacing any earlier download. Returns the saved path, size, and the files inside.
activity_id: activityId from garmin_search_activities. directory: a subfolder of the download folder to save in, e.g. "races" (default: the download folder itself). The download folder is GARMIN_MCP_DOWNLOAD_DIR, else a garmin folder in the Downloads directory; paths that lead outside it are rejected.
Inputs: activity_id (required), directory.
garmin_get_activityGet activityARead-only
Full detail for one activity: summary metrics (distance, time, pace, HR, power, cadence, elevation, training effect, calories), device and location metadata, and split/lap summaries.
activity_id: activityId from garmin_search_activities or garmin_get_day.
Inputs: activity_id (required).
garmin_get_activity_workoutsGet activity workoutsARead-only
The structured workout(s) followed during an activity, step by step with targets.
activity_id: activityId from garmin_search_activities or garmin_get_day.
Inputs: activity_id (required).
garmin_get_body_batteryGet body batteryARead-only
Daily Body Battery: amount charged and drained, plus the events that moved it (sleep, activities, stress) with Garmin's feedback.
start, end: YYYY-MM-DD inclusive (default: the 7 days ending today). Any length works; long ranges are fetched 31 days at a time. chunks: split each day into this many intervals (e.g. 24 for hourly) and include each interval's lowest and highest level. Omitted, the every-few-minutes readings are left out to keep the response small.
Inputs: end, start, chunks.
garmin_get_daily_summariesGet daily summariesARead-only
Daily wellness summaries across a date range, grouped per day (movement, calories, heart rate, intensity minutes, stress, body battery, hydration, respiration, pulse ox, jet lag). About 1-2 KB per day, so for a long trend in one metric prefer garmin_get_steps, garmin_get_stress, or garmin_get_sleep_summaries.
start, end: YYYY-MM-DD inclusive (default: the 7 days ending today). Ranges over a year are fetched in yearly requests.
Inputs: end, start.
garmin_get_daily_summaryGet daily summaryBRead-only
Every all-day wellness total for one date: steps, distance, floors, total/active/BMR calories, intensity minutes, heart rate, stress, body battery, SpO2, respiration, and time active/sedentary/asleep.
date: YYYY-MM-DD (default: today).
Inputs: date.
garmin_get_dayGet day overviewARead-only
One day at a glance: daily summary (steps, calories, intensity minutes, resting HR, stress, body battery), last night's sleep, HRV, body battery, stress, and the day's activities. A section Garmin has no data for comes back as null or [], or as {"error": ...} when Garmin rejects that section's request; the other sections still load.
date: YYYY-MM-DD (default: today).
Inputs: date.
garmin_get_fitness_ageGet fitness ageARead-only
Fitness age versus chronological age as of a date, the achievable fitness age, and the components behind it (vigorous days and minutes, resting heart rate, BMI or body fat %), or the reason none is available.
date: YYYY-MM-DD (default: today). full_history_scan: search all history for the latest value; false limits it to the 28 days before date.
Inputs: date, full_history_scan.
garmin_get_food_logGet food logARead-only
Foods logged in Garmin Connect's nutrition tracker for a day, with serving sizes, calories and nutrients consumed, nutrition goals, and a per-meal breakdown.
date: YYYY-MM-DD (default: today). meal_id: limit to one meal; ids appear in mealDetails of an unfiltered call. include_media: also return logged food photos.
Inputs: date, meal_id, include_goals, include_media, +1 more.
garmin_get_heart_rateGet heart rateARead-only
Resting, minimum, and maximum heart rate for a day, the 7-day resting average, and any abnormal high/low heart rate alerts.
date: YYYY-MM-DD (default: today). include_samples: also return the all-day readings (large).
Inputs: date, include_samples.
garmin_get_heart_rate_zonesGet heart rate zonesARead-only
Heart rate zone floors per sport, with the training method and the resting, max, and lactate threshold heart rates the zones were built on.
generate_defaults: return Garmin's default zones when none are configured.
Inputs: generate_defaults.
garmin_get_hrvGet HRV nightARead-only
One night of heart-rate variability: the summary (last-night average, 5-minute high, weekly average, baseline band, status) plus every reading taken during sleep.
date: YYYY-MM-DD, the morning you woke up (default: today).
Inputs: date.
garmin_get_hrv_summariesGet HRV summariesARead-only
Nightly HRV summaries across a date range: last-night average, weekly average, baseline band, status, and Garmin's feedback.
start, end: YYYY-MM-DD inclusive (default: the 7 days ending today).
Inputs: end, start.
garmin_get_hydrationGet hydrationARead-only
Daily water intake, goal, estimated sweat loss, and intake logged during activities, all in mL.
start, end: YYYY-MM-DD inclusive (default: the 7 days ending today). Any length works; long ranges are fetched 31 days at a time.
Inputs: end, start.
garmin_get_menstrual_cycleGet menstrual cycleARead-only
Menstrual cycle summary as of a date: current phase, day in cycle, days until the next phase, period length, fertile window, predicted ovulation and cycle length, and pregnancy details when tracking a pregnancy.
date: YYYY-MM-DD (default: today).
Inputs: date.
garmin_get_menstrual_dayGet menstrual cycle dayARead-only
One day in detail: logged symptoms, flow, mood, and other entries, that day's cycle summary, and weekly weight goal ranges during pregnancy.
date: YYYY-MM-DD (default: today). today: the reference date for predictions (default: today). reduced: return a lighter response. skip_baby_movement: leave out kick counts. include_analysis: add Garmin's cycle analysis.
Inputs: date, today, reduced, include_analysis, +1 more.
garmin_get_profileGet profile settingsARead-only
Profile settings: display name, measurement system and display formats, time zone, hydration units and containers, and preferred training days. Check measurementSystem before converting units for the user.
Inputs: no named parameters.
garmin_get_sleepGet sleepARead-only
The full sleep record for the night ending on date: sleep score and its components, deep/light/REM/awake seconds, start and end times, overnight heart rate, SpO2, respiration, sleep stress, naps, and Garmin's feedback.
date: YYYY-MM-DD, the morning you woke up (default: today, i.e. last night).
Inputs: date.
garmin_get_sleep_summariesGet sleep summariesARead-only
Nightly sleep records across a date range, same fields as garmin_get_sleep. Use it for sleep trends.
start, end: YYYY-MM-DD inclusive (default: the 7 days ending today). Any length works; long ranges are fetched 31 days at a time.
Inputs: end, start.
garmin_get_spo2Get SpO2ARead-only
Blood oxygen (pulse ox) for a day: average, lowest, latest, sleep average, 7-day average, hourly averages, and altitude acclimation.
date: YYYY-MM-DD (default: today). include_samples: also return every individual reading (large).
Inputs: date, include_samples.
garmin_get_stepsGet stepsARead-only
Daily total steps, distance (meters), and step goal.
start, end: YYYY-MM-DD inclusive (default: the 7 days ending today). Any length works; long ranges are fetched 28 days at a time.
Inputs: end, start.
garmin_get_stressGet stressARead-only
Daily overall stress level (0-100) and seconds spent in high, medium, low, and rest stress.
start, end: YYYY-MM-DD inclusive (default: the 7 days ending today). Any length works; long ranges are fetched 28 days at a time.
Inputs: end, start.
garmin_get_weightGet weightARead-only
Daily weight summaries over a range, the range average, and the nearest weigh-ins before and after it (handy when the range itself has none). Weights are in grams; body composition (BMI, body fat %, muscle and bone mass) appears when a smart scale recorded it.
start, end: YYYY-MM-DD inclusive (default: the 7 days ending today). include_all: list every weigh-in per day with its time and body composition, not just the latest.
Inputs: end, start, include_all.
garmin_get_weight_goalsGet weight goalsARead-only
Weight goals in effect during a date range and their target ranges.
start, end: YYYY-MM-DD inclusive (default: the 7 days ending today).
Inputs: end, start.
garmin_get_wellness_activitiesGet wellness activitiesARead-only
Wellness activities recorded by the device, each with its type, start and end time, per-metric summary values, and time in zones. Regular workouts like runs and rides are in garmin_search_activities instead.
start, end: YYYY-MM-DD inclusive (default: the 7 days ending today).
Inputs: end, start.
garmin_search_activitiesSearch activitiesARead-only
Recorded activities (runs, rides, swims, strength, dives, ...), newest first, with name, type, start time, distance, duration, pace/speed, HR, power, cadence, calories, elevation, and training effect. Pass an activityId to garmin_get_activity for splits and full detail.
All filters are optional and combine: start, end: YYYY-MM-DD inclusive, by the activity's local start date. Omit both to search all dates. With only end, the search covers the 7 days ending on it; with only start, it runs through today. search: text in the activity name or description. activity_type: parent type key, e.g. running, cycling, swimming, strength_training, hiking, walking. activity_subtype: a child key such as trail_running or indoor_cycling. excluded_activity_subtypes: child keys to leave out. event_type: race, training, recreation, fitness, specialEvent, or uncategorized. favorite: true for favorites only, false for non-favorites. exclude_children: hide individual legs of multisport activities. sort_by: a field such as distance, duration, or name (default: start time). sort_order: asc or desc (default desc). min_/max_distance (meters), min_/max_duration (timer seconds), min_/max_elapsed_duration (seconds including pauses), min_/max_elevation (gain, meters), min_avg_power (watts). north, south, east, west: a bounding box in decimal degrees around the start location; give all four or none. Dives: min_/max_max_depth (centimeters), deco_dive, min_/max_bottom_time (seconds). required_fields: activity fields that must be present, e.g. avgPower. limit, offset: page through results (each activity is about 3-4 KB).
Inputs: end, east, west, limit, +28 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 26 tools
Most tools target distinct Garmin data domains, and single-vs-range pairs such as sleep/sleep_summaries and HRV/HRV_summaries are explicitly differentiated. Some overlap remains between aggregate tools like garmin_get_day, garmin_get_daily_summary, and garmin_get_daily_summaries and metric-specific tools, but the descriptions guide selection well.
All tools use a consistent garmin_ prefix and snake_case with predictable get_/search_/download_ verbs. Singular/plural variants are readable and follow the same convention throughout.
At 26 tools the set is on the high side, but the breadth of Garmin personal-health data justifies most tools, each covering a distinct data area. It is slightly over the ideal range but not excessive for a comprehensive read-only server.
The surface covers activities, sleep, HRV, stress, body battery, heart rate, SpO2, hydration, weight, nutrition, profile, zones, fitness age, menstrual tracking, and daily summaries. Minor gaps such as dedicated respiration or training-readiness tools exist, but core retrieval coverage is strong.
Maintenance
Related MCP Connectors
Garmin data in Claude & ChatGPT via the Garmin Health API. OAuth sign-in, no password sharing.
Garmin watch data for AI assistants: activities, sleep, HRV, recovery. Never writes to Garmin.
Garmin data in Claude: 135 tools — activities, sleep, HRV, training, workouts. Free, open source.
Unofficial Garmin Connect access: sleep, HRV, training, activities, workouts and weight.
Related MCP Servers
- AlicenseBqualityBmaintenanceConnects to Garmin Connect and exposes your fitness and health data (activities, sleep, heart rate, steps, body composition) to Claude and other MCP-compatible clients.21481,488MIT
- AlicenseNot gradedqualityNot gradedmaintenanceA 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
- AlicenseNot gradedqualityNot gradedmaintenanceEnables Claude and other remote MCP clients to query your own read-only Garmin Connect data — recovery trends, sleep, HRV, Body Battery, stress, heart rate, and activities — through Garmin's official Personal API, with OAuth sign-in that works from the mobile apps as well as desktop. Deploys on Cloudflare Workers' free tier with no password sharing or scraping.MIT
- AlicenseNot gradedqualityNot gradedmaintenanceEnables users to query their own Garmin Connect data—such as recovery, sleep, HRV, activities, and daily wellness—through a read-only remote MCP connection secured with OAuth, including from the Claude mobile app.854 npmMIT