Skip to main content
Glama

garmin-mcp

An MCP server that connects Claude to your Garmin Connect account. Ask Claude things like "how did I sleep this week?", "am I recovered enough for intervals today?" or "put a 6x800 session on my calendar for Thursday" and it reads (and, if you allow it, writes) your Garmin data.

It uses the unofficial Garmin Connect API via python-garminconnect, for your own account only. Garmin's official developer APIs require business approval; all Garmin calls live in garmin_client.py so that route can be added later.

Tools

Area

Tools

Profile

get_profile

Daily health

get_daily_summary, get_sleep, get_hrv, get_body_battery, get_stress, get_heart_rate, get_body_composition, get_health_range (multi-day table)

Training

get_training_readiness, get_training_status, get_race_predictions, get_personal_records, list_activities, get_recent_activities, get_activity, get_activity_timeseries (per-second charts, downsampled)

Workouts

list_workouts, get_workout, get_scheduled_workouts

Writes (off unless GARMIN_ALLOW_WRITES=true)

create_workout (run/bike/swim/walk/strength), update_workout, schedule_workout, unschedule_workout, delete_workout, update_activity, add_weigh_in

Prompts

morning_check, weekly_review

Responses are trimmed summaries; every read tool accepts raw=true for Garmin's full JSON.

Related MCP server: garmin-mcp

1. Log in to Garmin once (on your own computer)

Your password never leaves your machine and is never stored. The login script prints a token that the server uses instead.

pipx install git+https://github.com/alyssindewonderland/garmin_mcp   # or: pip install -e .
garmin-mcp-login                 # asks for email, password and MFA code
garmin-mcp-login --save ~/.garmin-tokens   # also keep a copy for local use

The last line it prints is your GARMIN_TOKENS value. Treat it like a password: it grants access to your Garmin account. The server refreshes it automatically and keeps the refreshed copy in GARMIN_TOKEN_DIR; if it ever stops working, run the script again.

2a. Use it locally (Claude Desktop / Claude Code)

Claude Code:

claude mcp add garmin -e GARMIN_TOKEN_DIR=$HOME/.garmin-tokens -- garmin-mcp

Claude Desktop (claude_desktop_config.json):

{
  "mcpServers": {
    "garmin": {
      "command": "garmin-mcp",
      "env": { "GARMIN_TOKEN_DIR": "/Users/you/.garmin-tokens" }
    }
  }
}

2b. Deploy it so claude.ai (web, mobile) can use it

The server runs streamable HTTP at /mcp, protected by a random secret (MCP_AUTH_SECRET). Clients either call https://<host>/mcp/<secret> or send Authorization: Bearer <secret>. Everything else gets a 404.

  1. In Vercel: Add New → Project, import this GitHub repo, keep the defaults, and deploy.

  2. In the project: Storage → Create Database → Upstash for Redis (free tier) and connect it to the project. This adds KV_REST_API_URL and KV_REST_API_TOKEN, where the server keeps refreshed Garmin tokens (Garmin rotates them, and Vercel has no disk).

  3. In Settings → Environment Variables add:

    • GARMIN_TOKENS: the output of garmin-mcp-login

    • MCP_AUTH_SECRET: a random string of 32+ characters (python3 -c 'import secrets; print(secrets.token_urlsafe(32))')

    • optionally GARMIN_ALLOW_WRITES=true

  4. Redeploy (Deployments → ⋯ → Redeploy) so the variables take effect. Every push to main deploys automatically after that.

Your server is at https://<project>.vercel.app/mcp/<MCP_AUTH_SECRET>.

Fly.io

fly launch --no-deploy --copy-config      # pick a unique app name, keep fly.toml
fly volumes create garmin_data --size 1
fly secrets set \
  GARMIN_TOKENS='<output of garmin-mcp-login>' \
  MCP_AUTH_SECRET="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')" \
  MCP_ALLOWED_HOSTS=<app>.fly.dev
fly deploy

Anywhere else

Any Docker host with HTTPS in front works (Railway, Render, a VPS, a home server behind Cloudflare Tunnel). Run the image with the same env vars and a persistent volume at /data:

docker build -t garmin-mcp .
docker run -d -p 8000:8000 -v garmin_data:/data \
  -e GARMIN_TOKENS='...' -e MCP_AUTH_SECRET='...' garmin-mcp

Add the connector in Claude

In claude.ai: Settings → Connectors → Add custom connector, name it "Garmin", and use https://<host>/mcp/<MCP_AUTH_SECRET> as the URL. Then enable it in a chat from the tools menu.

For Claude Code against the deployed server:

claude mcp add --transport http garmin https://<host>/mcp --header "Authorization: Bearer <secret>"

Configuration

Variable

Default

Purpose

GARMIN_TOKENS

Token JSON from garmin-mcp-login

GARMIN_TOKEN_DIR

~/.garmin-tokens

Where refreshed tokens are kept (use a volume)

KV_REST_API_URL, KV_REST_API_TOKEN

Upstash Redis for refreshed tokens instead of disk (also UPSTASH_REDIS_REST_URL/_TOKEN)

GARMIN_EMAIL, GARMIN_PASSWORD

Fallback login; fails if your account has MFA

GARMIN_IS_CN

false

Use garmin.cn

GARMIN_ALLOW_WRITES

false

Enable write tools

GARMIN_CACHE_TTL

300

Seconds to cache Garmin responses

MCP_AUTH_SECRET

Required for HTTP mode, 24+ characters

MCP_ALLOWED_HOSTS

Comma-separated public hostnames for Host header checks

HOST, PORT

0.0.0.0, 8000

HTTP bind

Development

python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
pytest && ruff check .

Tests use a fake Garmin client, so they never touch your account.

Caveats

  • This uses Garmin's private API. Garmin can change it at any time; pin and update garminconnect when things break. Use it for your own account only.

  • Logins from cloud data-center IPs are more likely to be challenged, which is why login happens on your computer and the server only refreshes tokens.

Available Tools

24 tools
add_weigh_inC

Log a weight measurement.

Inputs: weight (required), day, unit. Returns: object

create_workoutA

Create a structured workout in Garmin Connect, optionally scheduling it on a date.

    It syncs to the user's watch on the next sync. Requires GARMIN_ALLOW_WRITES=true.
    

Inputs: spec (required), schedule_on. Returns: object

delete_workoutC
Destructive

Delete a saved workout from the user's Garmin library.

Inputs: workout_id (required). Returns: object

get_activityB
Read-only

One activity in detail: summary plus optional laps/splits, time in HR zones, weather.

Inputs: activity_id (required), raw, include. Returns: object

get_body_batteryB
Read-only

Body Battery charged/drained/high/low per day (defaults to the last 7 days).

Inputs: end, raw, start. Returns: object

get_body_compositionB
Read-only

Weight and body composition entries (defaults to the last 30 days).

Inputs: end, raw, start. Returns: object

get_daily_summaryC
Read-only

Steps, calories, resting HR, stress, Body Battery, intensity minutes, SpO2 for one day.

Inputs: day, raw. Returns: object

get_health_rangeA
Read-only

Per-day table of health metrics for trend questions (defaults to the last 7 days).

One row per day; much cheaper for the conversation than calling the day tools in a loop.

Inputs: end, start, metrics. Returns: object

get_heart_rateA
Read-only

Resting, min and max heart rate for one day (raw=true for the intraday series).

Inputs: day, raw. Returns: object

get_hrvB
Read-only

Overnight HRV: last-night average, 7-day average, baseline and status.

Inputs: day, raw. Returns: object

get_personal_recordsB
Read-only

The user's personal records across sports.

Inputs: no named parameters. Returns: object

get_profileB
Read-only

The user's name, unit system, devices and heart-rate zones.

Inputs: no named parameters. Returns: object

get_race_predictionsC
Read-only

Garmin's predicted 5K, 10K, half marathon and marathon times.

Inputs: no named parameters. Returns: object

get_recent_activitiesC
Read-only

The most recent activities.

Inputs: raw, limit. Returns: object

get_scheduled_workoutsB
Read-only

Workouts and other items on the Garmin calendar for a month.

Inputs: year, month. Returns: object

get_sleepB
Read-only

Sleep for the night ending on day: duration, stages, score, HRV, SpO2, respiration.

Inputs: day, raw. Returns: object

get_stressB
Read-only

Average and max stress for one day.

Inputs: day, raw. Returns: object

get_training_readinessC
Read-only

Training readiness score and its contributing factors for a day.

Inputs: day, raw. Returns: object

get_training_statusC
Read-only

Training status, acute/chronic load, load focus, VO2max and fitness age.

Inputs: day, raw. Returns: object

list_activitiesB
Read-only

Activities between two dates, newest first (defaults to the last 14 days).

Inputs: end, raw, start, activity_type. Returns: object

list_workoutsC
Read-only

Saved workouts in the user's Garmin library.

Inputs: limit. Returns: object

schedule_workoutA

Put an existing saved workout on the Garmin calendar.

Inputs: workout_id (required), day. Returns: object

unschedule_workoutA
Destructive

Remove a workout from the calendar (the saved workout itself stays).

Inputs: scheduled_workout_id (required). Returns: object

update_activityB

Rename an activity and/or set its description.

Inputs: activity_id (required), name, description. Returns: object

Tool Schema Changelog

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

Loading tool schema changelog…

    TDQS

    B3.2/5.0

    Scored across 24 tools

    Disambiguation4/5

    Most tools have clearly distinct purposes by resource and action, such as sleep, HRV, stress, and training readiness. The main overlap is between list_activities and get_recent_activities, and to a lesser extent get_daily_summary versus the individual day-level metric tools, but the descriptions make the intended use mostly clear.

    Naming Consistency5/5

    Tool names consistently use snake_case with predictable verb_noun or get_noun patterns: get_sleep, list_activities, create_workout, schedule_workout, delete_workout, update_activity. Minor verb variation such as add_weigh_in does not meaningfully break the pattern.

    Tool Count4/5

    At 24 tools the surface is somewhat heavy, but the breadth is justified by a wide health and fitness domain spanning daily metrics, training, activities, and workouts. It is near the upper reasonable range but not excessive for a comprehensive Garmin integration.

    Completeness4/5

    The server covers core Garmin workflows: health metrics, trends, sleep, HRV, body battery, training status, activities, and workout scheduling/deletion. Minor gaps remain, such as updating an existing workout or retrieving a single saved workout in detail, but agents can work around these for most tasks.

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      C
      maintenance
      Enables Claude to access and query your Garmin Connect data, including sleep, activities, training load, and health metrics, through a set of read-only MCP tools.
      28
      165 PyPI
      5
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      Daash connects Garmin Connect to ChatGPT and Claude. Your assistant reads your runs, splits, sleep, HRV, Body Battery and recovery, and writes structured workouts and whole training plans to your Garmin calendar, so they're on your watch at the next sync. Free, hosted, open source. Works on free Claude.
      26
      4
      MIT