{"id":"80867edb-13c4-4ff0-9288-db594f108beb","slug":"garmin-coach-mcp-server","kind":"mcp","name":"garmin-coach-mcp-server","summary":"MCP Server for Garmin Connect — read activities, health data, and push training plans from any AI assistant","owner":"mau240987","repoUrl":"https://github.com/mau240987/garmin-coach-mcp-server","license":"AGPL-3.0","licenseTier":"strong-copyleft","trust":"community","stars":0,"match":null,"fewStars":true,"format":"mcp","createdAt":"2026-05-30T12:26:00.000Z","addedAt":"2026-10-21T04:38:56.990Z","logoUrl":"https://avatars.githubusercontent.com/u/29376176?u=eb7dfae9acc6f5af9384e73554539644300577b8&v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["cloud-infra","data-analytics","memory-knowledge"],"tags":["ai","claude","fitness","garmin","garmin-connect","running","training"],"runtime":"python","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-24T03:55:15.219Z","sample":false,"url":"https://conduits.dev/tools/garmin-coach-mcp-server","language":"Python","transports":["stdio"],"install":{"pypi":"garmin-coach-mcp-server"},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"# garmin-coach-mcp-server\n\n<!-- mcp-name: io.github.mau240987/garmin-mcp-server -->\n\n![Python](https://img.shields.io/badge/python-3.10+-blue.svg)\n![License: AGPL 3.0](https://img.shields.io/badge/License-AGPL3.0-green.svg)\n![MCP](https://img.shields.io/badge/MCP-compatible-8A2BE2.svg)\n![PyPI](https://img.shields.io/pypi/v/garmin-mcp-server.svg)\n\nMCP Server for Garmin Connect — read activities, health metrics, and push structured training plans from any AI assistant that supports the [Model Context Protocol](https://modelcontextprotocol.io).\n\nBuilt on the battle-tested [garminconnect](https://github.com/cyberjunky/python-garminconnect) Python library.\n\n---\n\n## Features\n\n| Tool | Type | Description |\n|---|---|---|\n| `get_activities` | Read | Recent activities with distance, pace, HR, calories |\n| `get_activity_details` | Read | Full detail: splits, laps, running dynamics |\n| `get_health_summary` | Read | Steps, resting HR, HRV, stress, sleep, body battery, SpO2, training readiness |\n| `get_training_status` | Read | Weekly volume: total km, session count, avg pace |\n| `get_workouts` | Read | List workouts saved on Garmin Connect |\n| `push_workout` | Write | Create a structured workout with dual targets (pace + HR) |\n| `push_training_plan` | Write | Schedule a multi-day plan on the Garmin calendar |\n| `delete_workout` | Write | Delete a specific workout |\n| `delete_plan_workouts` | Write | Bulk-delete workouts by name pattern |\n\n### Resilience\n\nAll tools include automatic retry with exponential backoff:\n\n- **3 retries** by default (2 for `push_training_plan` to avoid duplicates)\n- **Exponential backoff**: 2s → 4s → 8s (capped at 30s)\n- **Token refresh**: 401/403 errors trigger automatic re-authentication\n- **Rate limiting**: 429 responses get triple the normal delay\n- **Smart skip**: input validation errors (bad JSON, missing args) fail immediately without retry\n\n---\n\n## Setup\n\n### Prerequisites\n\n- Python 3.10+\n- A Garmin Connect account\n- A Garmin device (for workout sync)\n\n### Install via pip\n\n```bash\npip install garmin-mcp-server\n```\n\nThis installs the `garmin-mcp-server` command. You can also run it without\ninstalling using [uvx](https://docs.astral.sh/uv/):\n\n```bash\nuvx garmin-mcp-server\n```\n\n### Or from source\n\n```bash\ngit clone https://github.com/mau240987/garmin-mcp-server.git\ncd garmin-mcp-server\npip install -r requirements.txt\n```\n\n---\n\n## Option 1 — Claude Desktop / Claude Code (recommended)\n\nThe simplest and most reliable setup. Claude Desktop launches the Python script directly as a subprocess via **stdio** — no Docker, no ports, no intermediaries.\n\n### Claude Desktop\n\nEdit `claude_desktop_config.json`:\n\n- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`\n- **Windows:** `%APPDATA%\\Claude\\claude_desktop_config.json`\n\n**If installed via pip** (cleanest):\n\n```json\n{\n  \"mcpServers\": {\n    \"garmin\": {\n      \"command\": \"garmin-mcp-server\",\n      \"env\": {\n        \"GARMIN_EMAIL\": \"your@email.com\",\n        \"GARMIN_PASSWORD\": \"yourpassword\"\n      }\n    }\n  }\n}\n```\n\n**If running from source**, point to the script directly:\n\n```json\n{\n  \"mcpServers\": {\n    \"garmin\": {\n      \"command\": \"python3\",\n      \"args\": [\"/full/path/to/garmin_mcp_server.py\"],\n      \"env\": {\n        \"GARMIN_EMAIL\": \"your@email.com\",\n        \"GARMIN_PASSWORD\": \"yourpassword\"\n      }\n    }\n  }\n}\n```\n\nTo find the full path:\n```bash\ncd garmin-mcp-server && pwd\n# e.g. /Users/you/garmin-mcp-server\n# → full path: /Users/you/garmin-mcp-server/garmin_mcp_server.py\n```\n\nFully restart Claude Desktop (`Cmd+Q` on macOS, not just close the window), then reopen it. The Garmin tools will appear under the 🔨 icon in the input bar.\n\n> After the first successful login, garth saves OAuth tokens to `~/.garth/`.\n> You can then remove the credentials from the config — the server will\n> resume the session automatically on subsequent starts.\n\n### Claude Code\n\n```bash\nexport GARMIN_EMAIL=\"your@email.com\"\nexport GARMIN_PASSWORD=\"yourpassword\"\nclaude mcp add garmin -- python3 /full/path/to/garmin_mcp_server.py\n```\n\n---\n\n## Option 2 — Docker (HTTP mode, remote clients)\n\nBest for hosting the server as a persistent HTTP service — useful for remote AI clients or if you want to keep the server always running independently of Claude Desktop.\n\n> **Note:** Do not use Docker + mcp-remote for Claude Desktop. The bridge\n> outputs log lines to stdout that Claude Desktop tries to parse as JSON,\n> causing connection errors. Use Option 1 for Claude Desktop.\n\n### Build\n\n```bash\ndocker build -t garmin-mcp-server .\n# or with buildx:\ndocker buildx build -t garmin-mcp-server .\n```\n\n### First run (credentials required)\n\n```bash\ndocker run -d \\\n  --name garmin-mcp \\\n  -p 8000:8000 \\\n  -e GARMIN_EMAIL=your@email.com \\\n  -e GARMIN_PASSWORD=yourpassword \\\n  -v garmin-tokens:/data/garth \\\n  garmin-mcp-server\n```\n\n### Subsequent runs (tokens already saved)\n\n```bash\ndocker run -d \\\n  --name garmin-mcp \\\n  -p 8000:8000 \\\n  -v garmin-tokens:/data/garth \\\n  garmin-mcp-server\n```\n\nThe server starts at `http://localhost:8000/mcp`.\n\n### Or with Docker Compose\n\n```bash\nGARMIN_EMAIL=your@email.com GARMIN_PASSWORD=yourpassword docker compose up -d\n```\n\n---\n\n## Authentication flow\n\n```\nFirst run                          Subsequent runs\n─────────────────────              ────────────────────\nGARMIN_EMAIL + PASSWORD            (not needed)\n        │                                │\n        ▼                                ▼\n  garminconnect login            garth tokens on disk\n        │                          (~/.garth/ or volume)\n        ▼                                │\n  Save OAuth tokens ──────────────►  Resume session\n        │                                │\n        ▼                                ▼\n   Ready to use                    Ready to use\n```\n\nCredentials are only needed **once**. After the first login, OAuth tokens\nare persisted by [garth](https://github.com/matin/garth) with a ~1 year\nlifetime. No passwords are ever stored on disk.\n\nIf your account has **MFA** enabled, run the interactive login once first:\n\n```bash\npython3 -c \"from garminconnect import Garmin; g = Garmin('email', 'pass'); g.login()\"\n```\n\n---\n\n## Workout step format\n\nThe `push_workout` tool accepts a JSON array of steps:\n\n```json\n[\n  {\n    \"type\": \"warmup\",\n    \"duration_type\": \"time\",\n    \"duration_value\": 600,\n    \"pace_low\": \"7:05\",\n    \"pace_high\": \"6:25\",\n    \"hr_low\": 120,\n    \"hr_high\": 140,\n    \"primary\": \"hr\"\n  },\n  {\n    \"type\": \"repeat\",\n    \"repeat_count\": 4,\n    \"steps\": [\n      {\n        \"type\": \"run\",\n        \"duration_type\": \"distance\",\n        \"duration_value\": 1000,\n        \"pace_low\": \"5:10\",\n        \"pace_high\": \"4:50\",\n        \"primary\": \"pace\"\n      },\n      {\n        \"type\": \"recover\",\n        \"duration_type\": \"time\",\n        \"duration_value\": 120,\n        \"hr_low\": 120,\n        \"hr_high\": 145,\n        \"primary\": \"hr\"\n      }\n    ]\n  },\n  {\n    \"type\": \"cooldown\",\n    \"duration_type\": \"time\",\n    \"duration_value\": 600,\n    \"pace_low\": \"7:30\",\n    \"pace_high\": \"6:30\",\n    \"primary\": \"hr\"\n  }\n]\n```\n\n### Step types\n\n| Type | Description |\n|---|---|\n| `warmup` | Warm-up phase |\n| `run` | Main interval / work phase |\n| `recover` | Recovery between intervals |\n| `cooldown` | Cool-down phase |\n| `rest` | Full rest (standing) |\n| `repeat` | Repeat group — wraps nested `steps` × `repeat_count` |\n\n### Duration types\n\n| Type | `duration_value` unit |\n|---|---|\n| `time` | seconds |\n| `distance` | meters |\n| `open` | lap button press |\n\n### Target priority\n\n| `primary` | Behavior on watch |\n|---|---|\n| `\"hr\"` | Heart rate is the main gauge, pace is secondary |\n| `\"pace\"` | Pace is the main gauge, HR is secondary |\n| `\"none\"` | No target (feel-based effort) |\n\nBoth targets are shown simultaneously on the watch. The `targetValueOne/Two`\nfields are hoisted to the step root level — required for Garmin to correctly\nparse dual targets.\n\n---\n\n## Example prompts\n\n```\n\"Show me my last 10 runs with pace and heart rate\"\n\n\"How has my sleep been this week?\"\n\n\"Create a tempo run for tomorrow: 15 min warmup,\n 4x1km at 5:00/km with 2min recovery, 10 min cooldown.\n HR primary on easy parts, pace primary on intervals.\"\n\n\"Push a 4-week training plan starting Monday: Tuesday easy 10km,\n Thursday progressive 12km, Sunday long run growing from 20 to 30km.\"\n\n\"What's my training load for the last 30 days?\"\n\n\"Delete all workouts from the old plan and re-upload the new ones.\"\n```\n\n---\n\n## Troubleshooting\n\n**`ModuleNotFoundError`** — dependencies not installed in the right environment:\n```bash\npip install -r requirements.txt\n# if using a venv, use its full python path in the config:\n# \"command\": \"/Users/you/garmin-mcp-server/.venv/bin/python3\"\n```\n\n**`RuntimeError: Missing Garmin credentials`** — no saved tokens and no env vars set.\nAdd `GARMIN_EMAIL` and `GARMIN_PASSWORD` to the config for the first login.\n\n**`MFA / 2FA error`** — run the interactive login once to complete MFA:\n```bash\npython3 -c \"from garminconnect import Garmin; g = Garmin('email', 'pass'); g.login()\"\n```\n\n**Tools not appearing in Claude Desktop** — make sure you fully quit (`Cmd+Q`)\nand restart Claude Desktop after editing the config.\n\n---\n\n## Disclaimer\n\nThis project is not affiliated with, endorsed by, or sponsored by Garmin Ltd.\nIt uses `garminconnect`, which relies on reverse-engineered endpoints.\nUse at your own risk, for personal use only.\n\n## License\n\nAGPL-3.0\n"}