garmin-mcp
Privacy-focused Garmin Connect MCP server for triathlon training, enabling analysis of health metrics, activities, training load, and structured workout creation.
README
garmlink
A privacy-focused Garmin Connect MCP server for triathlon training. ~47 tools covering daily health metrics, activity analysis, training load, running, cycling, swimming, strength training, and workout creation — plus triathlon-specific analysis (brick workouts, sport volume balance, cross-sport fitness snapshots).
Deployed as a remote MCP server over HTTPS. Connects to Claude Desktop or Claude Code via the streamable-HTTP transport.
One-Time Auth Setup
Run this locally once to generate tokens:
-
Install locally:
pip install -e . -
Authenticate with Garmin:
garmlink-authSaves tokens to
~/.garminconnect/garmin_tokens.jsonand prints the base64 export command. -
Copy the printed
GARMIN_TOKENS_JSON=...value — you'll need it for the secrets step below.
Deploy to Google Cloud Run
Runs on Cloud Run's perpetual free tier. The service scales to zero when idle, so
the first request after a quiet period takes ~1-3s to wake — no dashboard step,
it just waits. --min-instances=0 is deliberate: one always-warm instance would
far exceed the free vCPU-second allowance.
Prerequisites: gcloud and gh installed.
-
Log in as yourself and create (or pick) a project:
gcloud auth login gcloud projects create garmlink # skip if you already have oneCloud Run's free tier requires billing to be enabled on the project. You are not charged inside the free limits, but a card must be on file.
-
Run the one-time setup — enables APIs, stores your three secrets in Secret Manager, creates a deploy service account, and wires up keyless GitHub auth via Workload Identity Federation:
./scripts/setup-cloudrun.shIt prompts for
GARMIN_EMAIL,GARMIN_TOKENS_JSON, andMCP_AUTH_TOKEN(generate one withopenssl rand -hex 32— it must be at least 32 characters, and the server refuses to start without it). Save that token for the Claude Desktop config below.Edit the variables at the top of the script first if you want a different project id, region, or service name.
-
Deploy — push to
main, or trigger the workflow by hand:gh workflow run "Deploy to Cloud Run" -
Verify:
URL=$(gcloud run services describe garmlink --region us-central1 --format='value(status.url)') curl "$URL/health" # {"status":"ok"} curl -o /dev/null -w '%{http_code}\n' "$URL/mcp" # 401 - auth is working -
Check the Garmin session. The server no longer logs in to Garmin at startup — it authenticates on the first tool call and re-authenticates itself if the session dies. That means expired tokens show up as failing tool calls rather than a failed deploy, so check readiness explicitly:
curl -H "Authorization: Bearer $MCP_AUTH_TOKEN" "$URL/readyz"Reports
neveruntil the first tool call, thenauthenticated. A503with"garmin": "error"means the tokens are bad — re-rungarmlink-authand update theGARMIN_TOKENS_JSONsecret.
Auto-Deploy via GitHub Actions
Every push to main deploys via .github/workflows/deploy.yml. Authentication
is keyless — GitHub mints a short-lived OIDC token that Google exchanges for
credentials, so there is no long-lived service-account key in your repo secrets.
The setup script sets the three repo variables the workflow reads
(GCP_PROJECT_ID, GCP_WIF_PROVIDER, GCP_DEPLOY_SA).
Claude Desktop Config
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"garmlink": {
"url": "https://<your-cloud-run-url>/mcp",
"headers": { "Authorization": "Bearer <MCP_AUTH_TOKEN>" }
}
}
}
Replace <your-cloud-run-url> with the URL printed at the end of the deploy
workflow (or from step 4 above), and <MCP_AUTH_TOKEN> with the token you set
during setup.
Coaching Workflows
Four guided workflows ship with the server as MCP prompts, so they work in any MCP client rather than only in this project directory.
| Prompt | Purpose |
|---|---|
morning_check |
Daily readiness briefing (HRV, sleep, body battery) |
analyze_week |
Weekly training load and sport balance review |
race_readiness |
Pre-race fitness assessment across all disciplines |
create_workout_guide |
Guided structured workout builder → pushes to Garmin |
How they surface depends on the client: Claude Desktop lists them in its prompt
menu, and Claude Code exposes them as /mcp__garmlink__morning_check and so on.
Environment Variables
| Variable | Description |
|---|---|
GARMIN_EMAIL |
Your Garmin Connect email |
GARMIN_TOKENS_JSON |
Base64-encoded token file (from garmlink-auth) |
MCP_AUTH_TOKEN |
Required. Bearer token protecting the MCP endpoint; must be at least 32 characters. The server refuses to start without it. |
GARMIN_PASSWORD |
Optional. Only used to re-authenticate if the stored tokens expire. |
ALLOW_UNAUTHENTICATED |
Set to 1 to run with no authentication. Localhost development only — never on a public address. |
PORT |
Server port (default: 8000; Cloud Run injects 8080) |
LOG_LEVEL |
DEBUG, INFO (default), WARNING, or ERROR |
LOG_FORMAT |
json or text. Defaults to json on Cloud Run (detected via K_SERVICE), text elsewhere. |
Local Development
Use a Python 3.12 virtualenv. This is not optional: garminconnect 0.3.3+
requires 3.12, so a 3.11 interpreter silently resolves to 0.3.11's predecessor
0.3.2 — a different library from the one CI and production run, with different
return types. Tests then pass against an API that production never executes.
python3.12 -m venv .venv
.venv/bin/pip install -e .
Run the suite (the same six files CI runs):
.venv/bin/python tests/test_garmin_contract.py
.venv/bin/python tests/test_critical_fixes.py
.venv/bin/python tests/test_auth_lifecycle.py
GARMIN_EMAIL=x@y.z ALLOW_UNAUTHENTICATED=1 .venv/bin/python tests/test_tool_dispatch.py
GARMIN_EMAIL=x@y.z ALLOW_UNAUTHENTICATED=1 .venv/bin/python tests/test_prompts.py
GARMIN_EMAIL=x@y.z ALLOW_UNAUTHENTICATED=1 .venv/bin/python tests/test_logging.py
Note that real Garmin tokens in ~/.garminconnect mean a carelessly constructed
test client will reach the live Garmin API. Tests patch the GarminClient
constructor to prevent this; follow that pattern.
Logs
The server emits one structured line per notable event. On Cloud Run these are
JSON, and the platform lifts severity into the log viewer, so filtering by
error works:
{"severity":"INFO","message":"startup","tools":45,"prompts":4,"token_source":"secret","auth":"bearer"}
{"severity":"INFO","message":"tool.call","name":"get_daily_summary","args":{"date":"2026-08-20"},"outcome":"ok","dur_ms":214.0,"cache":"0h/1m"}
{"severity":"WARNING","message":"auth.reject","path":"/mcp","reason":"bad_token"}
{"severity":"WARNING","message":"garmin.retry","method":"get_stats","attempt":1,"outcome":"rate_limited"}
tool.call is the important one: to Cloud Run's own request log every MCP call
is an indistinguishable POST /mcp, so this is the only place you can see
which of the 45 tools ran, how long it took, and whether it was served from
cache (cache counts hits/misses, since range tools make one call per day).
Two things are deliberately never logged: tool results, which are the health data this server exists to protect, and presented credentials on a rejected request. Arguments and error messages are passed through a redactor that strips token-shaped strings.
Reading them:
gcloud run services logs read garmlink --region us-central1 --project garmlink --limit 50
Recommended Servers
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.
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.
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.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
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.
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.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
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.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.