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

Workouts

list_workouts, get_scheduled_workouts

Writes (off unless GARMIN_ALLOW_WRITES=true)

create_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.

Related MCP Connectors

Related MCP Servers