garmin-mcp

garmin-mcp

This MCP server exposes Garmin Connect health data—sleep, heart rate, HRV, stress, VO2max, and activities—to Claude through local tools, enabling natural language queries, data syncing, and statistical analysis like correlations and night-out detection.

Category
Visit Server

README

garmin-mcp

A local MCP server that exposes Garmin Connect data — sleep, heart rate, HRV, stress, VO2max, activities — to Claude, plus the statistical analysis built on top of it.

It was written to answer one question: does how hard I train change how well I sleep? The short answer, on a year of one person's data, is no. What the analysis actually turned up is in Findings.

The data stays on your machine. Nothing is uploaded anywhere; the server runs as a local subprocess and reads a SQLite file in your home directory.

What it does

sync_garmin_data pulls a date range from Garmin Connect into a local SQLite cache (~/.garmin-mcp/data.db). Everything else reads from that cache, so queries are fast and you are not re-hitting Garmin's API — which rate-limits aggressively — every time you ask a question.

Tools

Tool What it returns
sync_garmin_data(start, end, force=False) Fetches and caches. Commits per day, so a failure partway through keeps what it already wrote. Skips days already synced unless force.
get_daily_summary(date) Everything for one day, merged
get_range_summary(start, end) The same across a range
get_sleep / get_activities / get_hrv / get_vo2max / get_stress One table each
detect_nights_out(start, end) Nights flagged as "was out late", inferred from physiology alone
log_night_out(date, note) Manual override
correlate(metric_a, metric_b, start, end) Spearman correlation between two metrics
moving_average(metric, window, start, end) Rolling mean

detect_nights_out compares four signals — bedtime, sleep duration, resting heart rate and HRV — against your own 30-day baseline, and flags a night when at least two of them deviate. It knows nothing about the calendar.

Setup

python3 -m venv venv && source venv/bin/activate    # Python 3.10+
pip install -r requirements.txt
cp .env.example .env                                 # then fill in your credentials
pytest -v

.env is gitignored. Credentials are read from the environment; they are never written to the database or logged.

Register with Claude Code

claude mcp add garmin -s user \
  -- /absolute/path/to/garmin-mcp/venv/bin/python /absolute/path/to/garmin-mcp/server.py

Restart Claude Code, then ask it to sync a range and query away.

No credentials go on that command line. server.py loads the .env sitting next to it, anchored to the script's own path rather than the working directory — an MCP client launches the server with a CWD you do not control. Passing -e GARMIN_PASSWORD=… would work too, but it writes your password in plaintext into the client's config file, so prefer the .env.

Register with Claude Desktop

Add to claude_desktop_config.json:

{
  "mcpServers": {
    "garmin": {
      "command": "/absolute/path/to/garmin-mcp/venv/bin/python",
      "args": ["/absolute/path/to/garmin-mcp/server.py"]
    }
  }
}

Credentials come from .env, for the same reason as above.

Both use stdio: the client launches the server as a subprocess and talks to it over stdin/stdout. No port is opened and no authentication layer is needed, because both processes run as you on your own machine. MCP also supports an HTTP transport, which you would need only to reach the server from another machine — not the case here.

Findings

One person, 349 nights, 175 workouts. Full write-up with charts: the analysis.

  • Training intensity barely touches sleep. Spearman ρ = −0.18 against the following night's sleep score — statistically detectable, but it explains 3% of the variance. Splitting by workout type (zone 2, threshold, HIIT, etc.) makes even that disappear (Kruskal-Wallis p = 0.58).
  • Bedtime is what moves the needle — ρ = −0.68 — but the mechanism is mundane: with a fixed alarm, a later bedtime is less sleep. The two are collinear at ρ = −0.945, so they cannot be separated statistically.
  • The threshold is worth knowing anyway. Crossing midnight (personal to a 06:40 wake time) takes the share of nights scoring under 60 from 1% to 29%.
  • The night-out detector rediscovered the weekend on its own. It flagged 37% of Fridays and 2% of Sundays (odds ratio 6.4, p = 4·10⁻⁸) using only heart rate, HRV, and sleep timing.

Three bugs worth knowing about

If you build something similar against the Garmin API, these will bite you:

  1. sleepStartTimestampLocal already has the UTC offset applied. Reading it with datetime.fromtimestamp() applies your machine's offset a second time — every bedtime lands 1h late in winter and 2h in summer.
  2. A sleep record is labelled with the wake-up date, not the evening you went to bed. Pair training on day D with sleep[D+1], or you will correlate a workout with the sleep that came before it.
  3. bodyBatteryMostRecentValue is the end-of-day reading, not the day's peak. That is bodyBatteryHighestValue.

A fourth mistake was mine, not the API's, and is the one I would warn hardest about: I concluded that bedtime mattered independently of hours slept, based on a partial correlation. With a fixed wake time those two variables are near-identical, and partial correlation in that regime is unstable. Every statistical check passed. The common-sense objection — "if I always get up at 6:40, how can going to bed later not mean less sleep?" — did not.

Layout

server.py           MCP tools (FastMCP, stdio)
garmin_client.py    Garmin API wrapper + response parsing
db.py               SQLite schema and queries
stats.py            Pure functions: correlation, moving average, night-out signals
analysis/           Standalone analysis scripts; report.html is the write-up
tests/              Unit tests, no network or credentials required

The analysis scripts read your local database and print to stdout. They are research code, kept as they were run, and the docstring at the top of each says what it was trying to find out.

Notes and limits

  • Uses garminconnect, which talks to Garmin's private API. It is not an officially supported interface and can break when Garmin changes something.
  • Garmin rate-limits by IP. Sync a long range in one go rather than in many small calls, and expect 429s if you retry too quickly.
  • Sleep score, stress, and body battery are Garmin's own algorithms, not measurements. Stress in particular is derived from HRV, so correlating the two mostly measures Garmin's formula rather than your physiology.
  • n = 1. These findings are one person's; the code is what generalises.

Licence

MIT

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured