SC

shape-calendar/garmin-personal-mcp

Developer tools
4 stars 19 forks Quality 84 Trend 26MIT

MCP server for your own Garmin data, using Garmin's Personal API key.

Overview

Your whole Garmin history, in Claude. Every activity since your first watch, the original FIT files, sleep, HRV, Body Battery. Runs on your machine with a key only you hold. In October 2026 Garmin opened a Personal tier of the Connect API: one key, one person, your own data, no application to file. This is a TypeScript MCP server over that API, so Claude Desktop, Claude Code, Cursor, ChatGPT or any other MCP client can ask questions of your data directly. It is read-only. Nothing is written to your Garmin account and nothing is sent anywhere except to the AI client you connect. Not affiliated with Garmin. Garmin and Garmin Connect are trademarks of Garmin Ltd. 1. Create a key at connect.garmin.com/app/settings/personalApiToken. Pick . 2. Save it: ChatGPT and the Claude mobile app can only reach servers over HTTPS. See Running it on a server. Use the same node .../dist/index.js as the command in any other client's config. Needs Node 20 or newer.

README

garmin-personal-mcp

Your whole Garmin history, in Claude. Every activity since your first watch, the original FIT files, sleep, HRV, Body Battery. Runs on your machine with a key only you hold.

In October 2026 Garmin opened a Personal tier of the Connect API: one key, one person, your own data, no application to file. This is a TypeScript MCP server over that API, so Claude Desktop, Claude Code, Cursor, ChatGPT or any other MCP client can ask questions of your data directly. It is read-only. Nothing is written to your Garmin account and nothing is sent anywhere except to the AI client you connect.

Not affiliated with Garmin. Garmin and Garmin Connect are trademarks of Garmin Ltd.

Quick start

  1. Create a key at connect.garmin.com/app/settings/personalApiToken. Pick 1 year.

  2. Save it:

    mkdir -p ~/.config/garmin && chmod 700 ~/.config/garmin
    printf '%s' 'YOUR_KEY' > ~/.config/garmin/connect-personal-api-key
    chmod 600 ~/.config/garmin/connect-personal-api-key
    
  3. Connect a client:

    # Claude Code
    claude mcp add garmin -- npx -y garmin-personal-mcp
    
    // Claude Desktop, Cursor, Windsurf, Zed: claude_desktop_config.json or the equivalent
    { "mcpServers": { "garmin": { "command": "npx", "args": ["-y", "garmin-personal-mcp"] } } }
    

    ChatGPT and the Claude mobile app can only reach servers over HTTPS. See Running it on a server.

    From a clone (no npm needed):

    git clone https://github.com/Shape-Calendar/garmin-personal-mcp && cd garmin-personal-mcp
    pnpm install && pnpm build
    claude mcp add garmin -- node "$PWD/dist/index.js"
    

    Use the same node .../dist/index.js as the command in any other client’s config.

Needs Node 20 or newer. The key can also come from GARMIN_PERSONAL_API_KEY or --api-key-file . The file path is the one Garmin’s own Python client reads, so the two share a key.

What you can ask

  • “How has my weekly running volume changed over the last three years?”
  • “Compare my resting heart rate and sleep this month with the same month last year.”
  • “Pull the FIT file for yesterday’s ride and show me power and heart rate per lap.”
  • “Find every half marathon I’ve run and list the finish times.”
  • “What was my longest ride each year since 2020?”
  • “Did my HRV drop before each time I got sick last winter?”

The activity list pages all the way back to the first activity on the account, 50 at a time, each with a compact summary (distance, time, HR, power, training effect, cadence) so the model can scan years without drowning in fields.

Tools

Tool Returns
list_activities Activity summaries, newest first. Page with start/limit (max 50), filter with startDate/endDate/activityType.
get_activity Everything Garmin Connect stores for one activity.
get_activity_workout The structured workout followed during the activity, if any.
get_activity_recording The original FIT file decoded: session totals, every lap, and a downsampled stream of heart rate, power, speed, cadence, altitude and position.
get_daily_summaries Steps, floors, intensity minutes, resting HR, stress, Body Battery and calories per day. Up to 180 days per call.
get_sleep / get_sleep_summaries One night in full (stages, score, respiration, SpO2), or per-night summaries for up to 28 days.
get_hrv / get_hrv_summaries Overnight HRV for one date, or daily summaries for up to 180 days.
get_body_battery Per day, up to 28 days.
get_stress, get_steps Per day, up to 7 days.
get_hydration Per day, up to 28 days.
get_heart_rate Resting HR plus timestamped readings for one day.
get_spo2 Pulse ox and altitude acclimation for one day.
get_wellness_activity_summaries All-day movement summaries for a range.
get_weight, get_weight_goals Measurements and goals, up to a year.
get_food_logs Nutrition for one day.
get_profile, get_heart_rate_zones, get_fitness_age Settings, zones per sport, fitness age.
get_menstrual_cycle_summary, get_menstrual_cycle_day Cycle data for a date.
get_raw GET any other Personal API path, for endpoints Garmin adds later.

The per-call range limits are what Garmin’s servers accepted on 21 October 2026. Above them Garmin answers 400 or 500 with an empty body, so the tools check the span first and return an error that names the limit. The model splits the range and carries on.

Running it on a server

The default stdio transport is for a client on the same machine. For ChatGPT, the Claude mobile app, or anything else that connects over HTTPS, run the HTTP mode.

It is stateless: each request builds its own server and transport, no session ids, nothing kept between calls. That is what lets the same code run on a serverless platform or a Raspberry Pi.

Deploy this repo on Vercel, Railway, Render or Fly: import it, set two environment variables, deploy.

Variable Value
GARMIN_PERSONAL_API_KEY the key from Garmin Connect
MCP_BEARER_TOKEN a secret you make up, e.g. openssl rand -hex 32

The endpoint is https:///mcp on all of them. Railway, Render and Fly run pnpm start, which binds to the port they assign; Vercel imports the same entry and serves it as a function. On Vercel, use the production URL: preview URLs sit behind Vercel’s own login, which MCP clients can’t pass.

Run it on a machine you own (a VPS, a home server, a Pi):

MCP_BEARER_TOKEN=$(openssl rand -hex 32) \
MCP_HOST=0.0.0.0 \
GARMIN_PERSONAL_API_KEY=... \
npx -y garmin-personal-mcp --http 3333

The endpoint is http://host:3333/mcp. Put TLS in front (Caddy, a Cloudflare tunnel, the host’s own proxy).

Embed it: createFetchHandler({ bearerToken, apiKey }) returns a (Request) => Promise for any web-standard runtime (Cloudflare Workers, Deno, Bun, Hono).

Whichever way, clients connect with the URL plus Authorization: Bearer . The token is required: without it, anyone who finds the URL can read your health data.

Limits, honestly

  • One person. Garmin’s Personal API terms make the key personal to you, usable only in tools you run for your own use. Deploying your own copy is fine. A hosted service that takes other people’s keys is not, and this project will never become one.
  • The key expires. Thirty days to a year, chosen when you create it. Once a year, make a new one on Garmin Connect and update the file or environment variable. There is no way to renew a key from code. An expired key makes every tool fail with “Token exchange failed”.
  • No push or webhooks. Nothing arrives when you finish a workout; the server only looks when a client asks.
  • No writes. You can’t write workouts to your devices: the Personal API has no write scope.
  • Unpublished rate limits. Bursts of a few dozen calls caused no throttling in testing. Garmin can change that without notice.
  • Garmin’s response shapes are undocumented. The endpoints are the ones behind the Garmin Connect web app, listed by Garmin’s own client. Fields can change.

How the key is handled: it is exchanged for a 24-hour bearer token, which is cached under ~/.config/garmin-personal-mcp with mode 0600 and refreshed when under an hour remains. The key itself is never written by this server.

Development

pnpm install
pnpm dev                     # stdio from source, no build step
pnpm dev --http 3333         # HTTP from source; needs MCP_BEARER_TOKEN
pnpm build && pnpm start     # HTTP from the built output, binds to PORT when a platform sets it
pnpm test

Scripts: dev runs the TypeScript directly, build compiles to dist/, start serves HTTP from dist/. There is no script for stdio from the build because MCP clients spawn node dist/index.js themselves.

This server reads your Garmin. Shape writes to it too.

Let Claude plan your training and Shape sends every workout to your watch. Garmin, Apple Health and Wahoo in Claude and ChatGPT, nothing to install, and a beautiful iOS app and web interface. Shape uses Garmin’s partner API, which can write workouts to your devices and receives new activities the moment they arrive.

View the full README on GitHub

Install

npx -y garmin-personal-mcp
New to MCP? Start with the mcpskill.org guide →

Configuration

// Claude Desktop, Cursor, Windsurf, Zed: claude_desktop_config.json or the equivalent { "mcpServers": { "garmin": { "command": "npx", "args": ["-y", "garmin-personal-mcp"] } } }