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
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。
mcp-server-qdrant
这个仓库展示了如何为向量搜索引擎 Qdrant 创建一个 MCP (Managed Control Plane) 服务器的示例。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。