Files
mcp-garmin/docs/superpowers/specs/2026-05-17-mcp-garmin-design.md
2026-05-17 19:37:05 +02:00

5.2 KiB

MCP Garmin Server — Design Spec

Date: 2026-05-17
Status: Approved


Overview

A local MCP server that exposes Garmin Connect data to Claude Desktop via the garminconnect Python package. The server runs as a single-file FastMCP stdio server, authenticated via environment variables, targeting personal use with Claude Desktop as the sole client.


Architecture

Approach: Single-file FastMCP (Option A)

mcp-garmin/
├── server.py          # FastMCP server — all tools and startup logic
├── pyproject.toml     # Dependencies: mcp[cli], garminconnect
└── .env               # GARMIN_EMAIL, GARMIN_PASSWORD (not committed)

server.py initializes a FastMCP("garmin") instance, registers all tools, and serves via stdio. A module-level Garmin client instance is shared across all tool calls — no re-authentication per call.

Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "garmin": {
      "command": "python",
      "args": ["/path/to/mcp-garmin/server.py"],
      "env": {
        "GARMIN_EMAIL": "your@email.com",
        "GARMIN_PASSWORD": "yourpassword"
      }
    }
  }
}

Tools

All tools return JSON-serializable dicts. Dates are accepted as YYYY-MM-DD strings and default to today when omitted.

Authentication

Tool Parameters Description
authenticate() Initiates login from env vars. Returns "Authenticated" on success, or the MFA prompt (URL or code instruction) if Garmin requires MFA.
complete_mfa(code) code: str Accepts the MFA code and completes login, caching the session in ~/.garth.

Activities

Tool Parameters Description
get_activities(start_date, end_date, limit) start_date: str, end_date: str, limit: int = 20 List of activities with type, distance, duration, average HR.
get_activity_details(activity_id) activity_id: str Full detail for a single activity: splits, laps, all metrics.

Health Stats

Tool Parameters Description
get_sleep(date) date: str = today Sleep score, stages (deep/light/REM/awake), total duration.
get_heart_rate(date) date: str = today Resting HR and daily HR timeline.
get_stress(date) date: str = today Stress level timeline throughout the day.
get_body_battery(date) date: str = today Body battery charge/drain values throughout the day.
get_hrv(date) date: str = today HRV status and nightly average.
get_spo2(date) date: str = today Blood oxygen readings.

Steps & Daily Metrics

Tool Parameters Description
get_steps(date) date: str = today Step count, daily goal, and distance.
get_daily_stats(date) date: str = today Full daily summary: calories, floors, intensity minutes, active time.

Gear & Devices

Tool Parameters Description
get_devices() List of paired Garmin devices with model and firmware version.
get_gear() Gear items (shoes, bikes) with mileage/usage stats.

Profile

Tool Parameters Description
get_user_profile() Display name, age, weight, HR zones.

Authentication & Session Management

  • garminconnect uses garth under the hood, which caches OAuth tokens in ~/.garth after first login.
  • Startup: The server attempts a silent login using cached ~/.garth tokens. If valid, all tools are ready immediately. If no cache exists, tools return "Not authenticated. Ask Claude to call authenticate() first." until auth is completed.
  • First-time setup: The user calls authenticate() from Claude Desktop chat. If Garmin requires MFA, the prompt (URL or email code instruction) is returned as a string visible in the chat. The user then calls complete_mfa(code) with the received code. On success, garth caches the session — subsequent server restarts skip MFA entirely.
  • MFA URL handling: If garth surfaces a URL as part of the MFA flow, it is returned verbatim in the authenticate() response so Claude Desktop renders it as a clickable link.

Error Handling

Scenario Behavior
Missing GARMIN_EMAIL or GARMIN_PASSWORD env vars Fail at startup with a clear message; do not start server
No cached session and no auth attempted Tools return "Not authenticated. Call authenticate() first."
GarminConnectAuthenticationError Return readable error string to the LLM
No data for requested date Return "No <data type> found for <date>"
Network error Catch and return readable error string

No custom retry logic — garth handles token refresh automatically.


Dependencies

  • mcp[cli] — FastMCP framework
  • garminconnect — Garmin Connect API wrapper (uses garth for auth)
  • Python 3.14 (existing venv)

Out of Scope

  • Write operations (creating workouts, updating data)
  • Multi-user support
  • Hosting / remote deployment
  • GPS track/polyline data (raw GPX export)