Skip to main content
Glama
garmin-coach-mcp

garmin-coach-mcp

by jhoy1020

garmin-coach-mcp

An MCP server that lets Claude read and answer questions about your Garmin Connect data — recent activities, per-set strength history, sleep and recovery, and simple trends — and optionally build workouts back onto your watch.

Same code, two ways to run it:

Mode

Where

Use it for

Setup

Local

Your PC, launched by Claude Code

Day-to-day at your desk

~2 min, free

Hosted

Azure Container Apps

Asking from your phone, PC off

~20 min, ~$5/mo

Local is the default: nothing listens on a socket, so nothing needs authenticating. Hosted is opt-in and sits behind Microsoft Entra sign-in.

Garmin's API is unofficial. This uses python-garminconnect, which is not sanctioned by Garmin and can break when they change their backend. Fine for personal use; don't build anything load-bearing on it.

Quick start

Local

git clone https://github.com/jhoy1020/garmin-coach-mcp.git
cd garmin-coach-mcp
./scripts/setup.sh          # or .\scripts\setup.ps1 on Windows

Creates the virtualenv, installs the package, runs the one-time Garmin login, and registers the server with Claude Code using this checkout's own path. Nothing to edit. Verify with python scripts/doctor.py, then restart Claude Code and ask it something.

Hosted — after the local setup works:

python scripts/deploy.py

Provisions Azure, registers the Entra application, seeds your Garmin token, and prints the connector URL.

Related MCP server: garth-mcp-server

What leaves your machine

  • Local mode: nothing. The server talks to Garmin directly from your PC. Your Garmin password is typed into a subprocess and never stored — only a refreshable token, in ~/.garminconnect.

  • Hosted mode: that token is uploaded to an Azure file share in your own subscription, and your Garmin data passes through your own container. Requests come from claude.ai's servers. See docs/security.md.

Tools

Read (9)

Tool

What it answers

garmin_status

Is the connection working? Whose account?

list_activities(limit, activity_type)

"Show my last N workouts"

get_activity(activity_id)

Detailed metrics for one activity

get_exercise_sets(activity_id)

Per-set exercise / reps / weight / est. 1RM

get_strength_history(exercise_name, weeks)

"Is my squat going up?"

get_recovery(date)

Sleep + daily stats + training readiness

get_trends(metric, weeks, sample_days)

Resting HR, steps, stress over time

suggest_next_weight(exercise_name, ...)

Progressive-overload recommendation

list_workouts(limit)

Saved workouts (name + id)

Write (3) — set GARMIN_COACH_READONLY=1 to drop these from the manifest entirely.

Tool

What it does

create_strength_workout(name, exercises, schedule_date)

Build a strength workout on Garmin (experimental)

schedule_workout(workout_id, date)

Put an existing workout on your calendar

delete_workout(workout_id)

Undo / remove a workout

Strength answers only have data if you log Strength activities with weights on your watch. create_strength_workout uses Garmin's unofficial workout schema — check the result in the Garmin Connect app and delete_workout if it's wrong.

Layout

src/garmin_coach_mcp/
  server.py            tool definitions + entry point
  config.py            settings; refuses to start on unsafe combinations
  http_app.py          SecretGate - the auth ladder for hosted requests
  oauth.py             validates Entra-issued tokens
  oauth_broker.py      a small OAuth 2.1 server (the path real clients use)
  ratelimit.py         token buckets for the unauthenticated endpoints
  garmin_client.py     cached, token-authenticated Garmin client
  analysis.py          strength-history and trend computations, argument clamps
  strength_workout.py  Garmin workout payload builder
  login.py             one-time interactive Garmin login
scripts/               setup, deploy, doctor, rotate, secret scanner
infra/                 Bicep for the hosted deployment
tests/                 109 tests
docs/                  setup, architecture, security, troubleshooting

Documentation

docs/local-setup.md

Run it on your PC

docs/remote-setup.md

Deploy to Azure with Microsoft sign-in

docs/architecture.md

How it's built and why

docs/security.md

Threat model, what protects what, rotating secrets

docs/troubleshooting.md

Organised by what you actually see

CLAUDE.md

Context for AI agents working on this repo

Scripts

Task

Command

Set up locally

python scripts/setup_local.py

Diagnose anything

python scripts/doctor.py (--remote, --json)

Deploy to Azure

python scripts/deploy.py

Register the Entra app

python scripts/entra_app.py

Upload the Garmin token

python scripts/seed_token.py

Rotate secrets

python scripts/rotate_secrets.py --help

Get the connector URL

python scripts/doctor.py --connector-url

Scan for secrets

python scripts/check_secrets.py

Configuration

.env.example documents every setting. The ones you are most likely to touch:

Variable

Default

Meaning

GARMIN_COACH_TRANSPORT

stdio

stdio or http

GARMIN_COACH_READONLY

off

Hide the three write tools entirely

GARMINTOKENS

~/.garminconnect

Where the Garmin OAuth token lives

GARMIN_COACH_TIMEZONE

your machine's

Container TZ; wrong value makes "today" wrong

GARMIN_COACH_ALLOWED_REDIRECT_HOSTS

claude.ai,claude.com + loopback

Hosts that may receive an authorization code

Hosted mode also needs the Entra values, which scripts/deploy.py sets for you — see docs/remote-setup.md for the name mapping between what you type and what the container reads.

config.py refuses to start on unsafe combinations rather than running half-secured: a partial Entra configuration, an http transport with no way to authenticate, a too-short secret, a non-https public URL, access logging alongside a URL secret, or a wildcard redirect-host allowlist without an explicit second opt-in.

Tests

python scripts/setup_local.py --dev    # installs pytest
python -m pytest

109 tests. The security-relevant ones are written to fail if the fix is reverted — the concurrency test genuinely serialises without the threadpool, and the rate-limiter test fails if the limiter key goes back to the spoofable leftmost X-Forwarded-For entry.

Contributing

python scripts/install_hooks.py enables the pre-commit scanner (setup does this for you). It blocks credentials and personal data — this repository is public, and the thing that actually needed cleaning up was absolute paths and resource names, not keys. CI runs the same checks.

Licence

MIT.

Available Tools

12 tools
create_strength_workoutA

Create a strength workout in Garmin Connect (and optionally schedule it).

EXPERIMENTAL: built from Garmin's unofficial workout schema. After creating, open the Garmin Connect app to confirm it looks right; use delete_workout to undo.

Args: name: Workout name shown on the watch / in Garmin Connect. exercises: List of exercises. Each item: {"name": "Bench Press", "sets": 3, "reps": 10, "weight_kg": 60, "rest_seconds": 90, "category": "BENCH_PRESS" (optional Garmin key)} schedule_date: Optional YYYY-MM-DD to place it on your Garmin calendar.

Inputs: name (required), exercises (required), schedule_date.

delete_workoutA

Delete a saved Garmin workout (undo a created workout).

Args: workout_id: The workout's ID from list_workouts / create_strength_workout.

Inputs: workout_id (required).

garmin_statusA

Check that the Garmin connection works and show whose account is connected.

Use this first to confirm authentication before other tools.

Inputs: no named parameters. Returns: object

get_activityA

Get detailed metrics for a single activity by its ID.

Args: activity_id: The activityId from list_activities.

Inputs: activity_id (required).

get_exercise_setsA

Get the per-set strength breakdown (exercise, reps, weight, est. 1RM) for one activity.

Only meaningful for strength_training activities logged with sets on the watch.

Args: activity_id: The activityId of a strength workout.

Inputs: activity_id (required).

get_recoveryA

Get sleep, daily stats, and training readiness for one day (default today).

Args: date: YYYY-MM-DD. Defaults to today.

Inputs: date.

get_strength_historyA

Progressive-overload view: strength sets grouped by session over recent weeks.

Walks your recent strength_training activities, pulls their sets, and returns per-session weight x reps so you can see whether a lift is going up.

Args: exercise_name: Optional case-insensitive substring to filter to one lift (e.g. "bench", "squat"). Omit to include all exercises. weeks: How many weeks back to look (default 8). max_sessions: Cap on how many strength sessions to fetch (default 20).

Inputs: weeks, max_sessions, exercise_name.

list_activitiesA

List your most recent Garmin activities.

Args: limit: How many recent activities to return (default 10). activity_type: Optional Garmin type key filter, e.g. "running", "strength_training", "cycling". Omit for all types.

Inputs: limit, activity_type.

list_workoutsA

List saved Garmin workouts (name + id), newest first.

Args: limit: How many workouts to return (default 20).

Inputs: limit.

schedule_workoutA

Schedule an existing Garmin workout onto your calendar.

Args: workout_id: The workout's ID (from create_strength_workout or list_workouts). date: Target date, YYYY-MM-DD.

Inputs: date (required), workout_id (required).

suggest_next_weightA

Recommend the next weight & reps for a lift using progressive overload.

Reads your recent strength history for the exercise and applies a double-progression rule. This is read-only (it does not change anything on Garmin).

Args: exercise_name: Case-insensitive substring of the lift, e.g. "bench", "squat". reps_low: Bottom of your target rep range (default 8). reps_high: Top of your target rep range (default 12). increment_kg: Weight jump when you graduate the range (default 2.5). weeks: How many weeks of history to consider (default 8).

Inputs: exercise_name (required), weeks, reps_low, reps_high, +1 more.

Tool Schema Changelog

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

Loading tool schema changelog…

    TDQS

    A4.1/5.0

    Scored across 12 tools

    Disambiguation5/5

    Each tool targets a distinct concern: status, activity retrieval, strength analysis, recovery trends, and workout management. Even the strength-related tools are clearly separated by scope (single activity vs. history vs. recommendation), so an agent can reliably select the right one.

    Naming Consistency4/5

    Most tools follow a verb_noun pattern (list_activities, get_activity, create_strength_workout, schedule_workout, delete_workout, suggest_next_weight), but garmin_status deviates from this pattern, making the naming slightly inconsistent overall.

    Tool Count5/5

    With 12 tools, the server is well-scoped for its purpose: it covers activity data, health metrics, strength analysis, and workout management without unnecessary redundancy or bloat.

    Completeness4/5

    The server covers the main coach workflows: reading activities, analyzing strength progression, suggesting next weights, and managing workouts. Minor gaps such as listing scheduled workouts or updating an existing workout are easy to work around and do not cripple the overall tool surface.

    Maintenance

    ActivityInactive
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      C
      maintenance
      Personal MCP server for interacting with your Garmin Connect data. Exposes 62 tools across 11 domains including activities, health, training, and workouts.
      1
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      MCP server that exposes Garmin Connect health and activity data (steps, sleep, stress, activities, etc.) via tools for querying, analysis, and visualization.
      17
      Apache 2.0