内容
# garmin-mcp
只读MCP服务器,暴露佳明健康和健身数据,供AI代理使用。
## 工具列表
| 工具 | 描述 |
|------|-------------|
| `get_user_id` | 获取认证的佳明用户ID |
| `get_daily_summaries` | 获取每日活动数据(步数,卡路里,距离,有效时间) |
| `list_activities` | 列出锻炼、跑步、骑行和其他活动 |
| `get_activity_details` | 获取特定活动的详细分解 |
| `get_sleep_data` | 获取睡眠会话,含阶段和质量指标 |
| `get_heart_rate_data` | 获取心率和心率变异性数据 |
| `get_body_composition` | 获取体重、BMI、体脂和肌肉量 |
| `get_stress_data` | 获取压力水平和身体电池 |
| `get_respiration_data` | 获取呼吸率数据 |
| `get_pulse_ox_data` | 获取血氧饱和度(SpO2)读数 |
所有日期范围工具默认使用7天窗口(`list_activities`和`get_body_composition`为30天)。内部它们被分成24小时块(佳明的每个请求限制)并发出去 — 参见 [为什么分块?](#为什么分块).
## 设置
参见 [SETUP.md](SETUP.md) 进行OAuth配置。你需要 **两个** 环境中的凭证 — 参见下面的 [为什么需要两个凭证?](#为什么需要两个凭证).
```env
GARMIN_ACCESS_TOKEN=... # OAuth 2.0 PKCE Bearer(每个用户)
GARMIN_PULL_TOKEN=... # 合作伙伴拉取令牌(每个应用,从开发者门户)
```
## 运行
```bash
# 本地(标准输入输出)
uv run python server.py
# Docker(HTTP)
docker compose up
```
## API 参考
- **基准URL**: `https://apis.garmin.com/wellness-api/rest`
- **认证**: OAuth 2.0 Bearer **加** 合作伙伴拉取令牌(`?token=…`)在每个数据端点。参见下面。
- **文档**: <https://developer.garmin.com/gc-developer-program/health-api/>
- **实时Swagger**: 开发者门户 → API 文档 → "wellness-api" 定义
## 为什么需要两个凭证?
佳明的健康/健康API需要 **两个凭证一起** 才能访问任何数据端点。这在公共文档中并不明显,症状是 `400 {"errorMessage":"InvalidPullTokenException failure"}` — 我们在阅读开发者门户的实时Swagger之前就遇到了这个问题。记录在这里以便下一个人不会遇到同样的问题。
| 凭证 | 标识 | 去向 | 所需 |
|---|---|---|---|
| `GARMIN_ACCESS_TOKEN` (OAuth 2.0 PKCE Bearer) | 用户 | `Authorization: Bearer …` 头部 | 所有端点(用户必须授权应用) |
| `GARMIN_PULL_TOKEN` (合作伙伴拉取令牌) | 集成/应用 | `?token=…` 查询参数 | 每个数据端点 — `/sleeps`, `/dailies`, `/activities`, `/stressDetails`, `/epochs`, `/respiration`, `/pulseOx`, `/hrv`, `/healthSnapshot`, `/activityDetails/*`, … |
唯一可以使用Bearer单独工作(无需拉取令牌)的端点是 `/user/id` 和 `/user/permissions` — 它们是认证/身份路由,佳明不通过集成限制它们。Swagger中列出的健康API中的其他每个端点都有 `token` 查询参数,文档中注明为 *"OAuth2集成需要拉取令牌."* 省略它,你会得到 `InvalidPullTokenException`.
**在哪里获取拉取令牌**: 开发者门户 (`developerportal.garmin.com`) → 您的健康API应用 → **API拉取令牌** 标签。它是一个静态值;创建一次并重复使用。(较旧的论坛线程建议使用OAuth 1.0a HMAC-SHA1签名 — 那是旧路径,计划于2026-12-31淘汰。实时Swagger是权威来源。)
## 为什么分块?
健康API限制每个数据端点请求的 **24小时窗口** 大小在 `uploadStartTimeInSeconds`/`uploadEndTimeInSeconds` 上。任何更大的数据都会导致佳明返回 `400` 错误。MCP通过 `_split_into_chunks` 透明地将任何范围分成24小时块,通过 `httpx.AsyncClient` 并发发送,并使用 `asyncio.Semaphore(8)` 绑定,连接JSON数组。失败的块将被记录并跳过 — 部分数据对于探索性代理来说比任何单个块上的硬故障更有用。
注意: `uploadStartTimeInSeconds` 按 **设备上传时间** 过滤,而不是活动实际发生的时间。如果同步延迟或用户最近未打开佳明Connect应用,最近记录的数据可能不会出现在最近的块中。某些汇总端点上有 `summaryStart…` 系列,如果上传时间过滤成为问题,值得探索。