内容
# fitness_mcp
一款通用的、跨平台的健身MCP服务器。它从 **佳明健身**、**Strava**、**谷歌健身** 和 **苏unto** 中拉取数据,并将所有数据存储在单个 **DuckDB** 文件中,通过stdio向 **MCP工具** 公开分析数据——可从Claude Desktop、Cursor、Windsurf、VS Code或任何MCP客户端使用。
- **零运行基础设施** — 同步是手动的,服务器按需运行。
- **完全可扩展** — 添加一个平台 = 实现一个抽象类并注册它。工具层永远不会改变。
- **设计只读** — MCP工具只读;`fitness_query`强制只读SELECT。
## 架构
```
server.py MCP入口点(stdio) providers/base.py 抽象接口 + 数据类
sync.py CLI + 共享同步引擎 providers/garmin.py 佳明健身(garth)
db/database.py DuckDB连接 + 更新 providers/strava.py Strava(OAuth2 + httpx)
db/schema.sql 表定义 tools/*.py MCP工具(活动,健康,...)
```
数据流:`providers`获取标准化记录 → `db`将它们更新到DuckDB → `tools`查询DuckDB并返回统一的JSON信封。
每个工具都返回:
```json
{ "success": true, "data": [...], "error": null, "meta": { "count": 42 } }
```
## 安装
### 自动安装(macOS / Linux)
```bash
./setup.sh # 虚拟环境 + 依赖 + .env 脚手架,然后打印下一步
```
然后编辑 `.env`,并可选地让脚本完成其余操作:
```bash
./setup.sh --login # 交互式佳明登录(密码从不存储)
./setup.sh --sync # 所有平台的初始同步
./setup.sh --claude # 安装Claude Desktop MCP配置
./setup.sh --dev # 安装开发依赖并运行测试套件
# 标志组合: ./setup.sh --dev --login --sync --claude
```
`Makefile` 包装了常见操作 — `make help`、`make setup`、`make login`、`make sync`、`make serve`、`make test`、`make claude-install`。
### 自动安装(Windows / PowerShell)
```powershell
.\setup.ps1 # 虚拟环境 + 依赖 + .env 脚手架(锁定到您的用户),然后下一步
.\setup.ps1 -Dev -Login -Sync -Claude # 开关组合,含义与上述相同
```
如果PowerShell阻止脚本,请以以下方式运行:
```powershell
powershell -ExecutionPolicy Bypass -File .\setup.ps1
```
然后直接使用虚拟环境进行循环命令:
```powershell
.\.venv\Scripts\python.exe login.py
.\.venv\Scripts\python.exe sync.py --platform garmin --full-history
.\.venv\Scripts\python.exe scripts\claude_config.py --write # Claude Desktop 配置
```
在Windows上,安装脚本通过`icacls`将`.env`限制在您的用户帐户下(POSIX `chmod`警告是无操作的);garth令牌缓存位于`%USERPROFILE%\.garth`下,默认情况下已经是用户范围。
可以单独生成或安装Claude Desktop条目:
```bash
python scripts/claude_config.py # 打印JSON片段
python scripts/claude_config.py --write # 合并到您的Claude配置(带备份)
```
### 手动安装
```bash
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # 然后填写凭证
```
### 凭证(`.env`)
```bash
DUCKDB_PATH=./fitness.duckdb
# 佳明健身(通过garth)
GARMIN_EMAIL=your@email.com
GARMIN_PASSWORD=yourpassword
GARTH_HOME=~/.garth # 缓存会话令牌位于此处
# Strava
STRAVA_CLIENT_ID=...
STRAVA_CLIENT_SECRET=...
STRAVA_REFRESH_TOKEN=...
```
`.env` 和 DuckDB 文件被 gitignore。
#### Strava:获取刷新令牌(一次性)
1. 在 <https://www.strava.com/settings/api> 创建一个API应用程序。记下 **客户端ID** 和 **客户端密钥**。
2. 授权您自己的帐户,请求 `activity:read_all` 范围。访问(替换 `CLIENT_ID`):
```
https://www.strava.com/oauth/authorize?client_id=CLIENT_ID&response_type=code&redirect_uri=http://localhost&approval_prompt=force&scope=activity:read_all
```
批准后,浏览器重定向到 `http://localhost/?...&code=AUTH_CODE&...`。从URL中复制 `AUTH_CODE`。
3. 交换代码以获取令牌:
```bash
curl -X POST https://www.strava.com/oauth/token \
-d client_id=CLIENT_ID -d client_secret=CLIENT_SECRET \
-d code=AUTH_CODE -d grant_type=authorization_code
```
将响应中的 `refresh_token` 置于 `STRAVA_REFRESH_TOKEN`。服务器在每次同步时自动刷新短时访问令牌。
> 佳明使用 `garth`。**首选登录** — 交互式运行一次,这样您的密码永远不会写入磁盘:
>
> ```bash
> python login.py # 提示输入电子邮件/密码(+ MFA);缓存令牌
> ```
>
> 之后,您可以省略 `.env` 中的 `GARMIN_PASSWORD`;同步重用缓存的会话令牌。(在 `.env` 中设置密码仍然作为非交互式/无头使用的后备。)
## 凭证和会话安全
- **没有任何秘密被提交** — `.env`、DuckDB 文件和 garth 令牌缓存(`.garth/`、 `*.token`)都被 gitignore。
- **密码保留在磁盘之外** — `python login.py` 通过 `getpass` 读取密码,并且只保留生成的会话令牌。保持 `GARMIN_PASSWORD` 为空。
- **仅所有者令牌缓存** — 每次佳明登录后,`GARTH_HOME` 目录及其令牌文件被 `chmod` 为 `0700`/`0600`(POSIX)。
- **暴露警告** — `sync.py` 和 `server.py` 在启动时警告,如果您的 `.env` 是组/世界可读的,并带有确切的 `chmod 600` 修复。
- **像密码一样对待会话令牌** — 缓存的 garth 令牌授予对您的佳明帐户的访问权限。如果机器被泄露,请删除 `GARTH_HOME` 并重新运行 `login.py`。OAuth 刷新令牌(Strava/Google/Suunto)同样敏感;如果泄露,请在每个平台的应用设置中撤销它们。
## 同步数据
```bash
python sync.py --platform all # 最近 30 天,所有平台
python sync.py --platform garmin # garmin | strava | google_fit | suunto
python sync.py --platform garmin --from 2025-01-01 --to 2025-06-30
python sync.py --platform garmin --full-history # 从 2010 年开始(首次运行)
```
支持的平台:`garmin`、`strava`、`google_fit`、`suunto`。佳明是最丰富的来源(活动、睡眠、心率变异性、身体电池、压力);Strava 和 Suunto 提供活动;谷歌健身提供活动、睡眠和体重。
所有平台同步后,会删除重复的锻炼(同一天 + 运动,持续时间和距离在 5% 内):保留佳明记录(更丰富的指标),并将 Strava ID 合并到其 `raw_json` 中。
### 循环(自动)同步
一次性 `python login.py` 后,佳明令牌自动刷新,因此计划同步在数月内无需人工运行(仅当长期令牌过期时重新运行 `login.py`)。运行程序将每次运行记录到 `logs/sync.log`,如果任何平台出错,则退出非零:
```bash
python scripts/scheduled_sync.py --platform all
```
**Windows(任务计划程序):**
```powershell
.\scripts\register_sync_task.ps1 # 每天 07:00,所有平台
.\scripts\register_sync_task.ps1 -Time 06:30 -Platform garmin
.\scripts\register_sync_task.ps1 -Daily2x # 07:00 和 19:00
.\scripts\register_sync_task.ps1 -Unregister # 删除它
```
任务以您的用户身份通过 S4U 运行(无存储密码,运行无论您是否登录),并补上错过的运行。触发测试运行,请输入 `Get-ScheduledTask fitnessmcp-sync | Start-ScheduledTask`。
**macOS/Linux(cron):** 将 cron 指向同一个运行程序 —
```cron
0 7 * * * cd /path/to/fitMCP && .venv/bin/python scripts/scheduled_sync.py --platform all
```
## 运行 MCP 服务器
```bash
python server.py # 通过 stdio 提供服务
```
### Claude Desktop 配置
`~/Library/Application Support/Claude/claude_desktop_config.json`(macOS)或 `%APPDATA%\Claude\claude_desktop_config.json`(Windows):
```json
{
"mcpServers": {
"fitness": {
"command": "/absolute/path/to/fitness-mcp/.venv/bin/python",
"args": ["/absolute/path/to/fitness-mcp/server.py"],
"env": { "PYTHONPATH": "/absolute/path/to/fitness-mcp" }
}
}
}
```
## MCP 工具
| 工具 | 用途 |
|---|---|
| `fitness_sync(platform)` | 将 garmin/strava/all 同步到 DuckDB |
| `fitness_sync_status()` | 最后同步时间 + 每个平台的记录数 |
| `fitness_get_database_stats()` | 行数,日期范围,文件大小 |
| `fitness_get_activities(date_from, date_to, platform, sport_type, limit, offset)` | 分页活动列表 |
| `fitness_get_activity_detail(activity_id)` | 完整详细信息,包括原始有效载荷 |
| `fitness_get_personal_bests(sport_type)` | 最长,最快,最大心率,最多海拔 |
| `fitness_get_sleep(date_from, date_to, platform)` | 睡眠记录 |
| `fitness_get_hrv(date_from, date_to, platform)` | 心率变异性 + 身体电池 |
| `fitness_get_body_battery(date_from, date_to)` | 身体电池趋势 |
| `fitness_get_recovery_status(date)` | 一日恢复快照 |
| `fitness_get_training_load(weeks, platform)` | 每周负荷趋势 |
| `fitness_get_vo2max_trend(months)` | 随时间变化的 VO2max 估计值 |
| `fitness_get_weekly_summary(week_offset)` | 周总计 + 运动拆分 |
| `fitness_get_sport_breakdown(date_from, date_to)` | 每个运动的时间/距离/计数 |
| `fitness_compare_platforms(metric, date_from, date_to)` | 每个平台的相同指标 |
| `fitness_correlate(metric_a, metric_b, date_from, date_to)` | 皮尔森相关性 + 散点图 |
| `fitness_get_trends(metric, date_from, date_to, granularity)` | 按天/周/月的时间序列 |
| `fitness_query(sql)` | 数据库的只读 SELECT |
`fitness_correlate` / `fitness_get_trends` 的指标:`sleep_score`、`sleep_duration`、`hrv`、`hrv_score`、`body_battery`、`training_load`、`distance`、`duration`、`avg_hr`、`stress`、`weight`。
## FitBrain — 辅导层
为了获得数据驱动的辅导体验,请创建一个名为 **FitBrain** 的Claude项目,并将 [`prompts/fitbrain_system_prompt.md`](prompts/fitbrain_system_prompt.md) 粘贴到其自定义指令中(编辑“我是谁”/“目标”以匹配您)。它使Claude始终检查新鲜度并在回答之前拉取实时数据,寻找相关性,标记过度训练信号,并保持直接。有关设置和示例问题,请参见 [`prompts/README.md`](prompts/README.md)。
## 测试
```bash
pip install -r requirements-dev.txt
PYTHONPATH=. pytest
```
测试套件涵盖了DuckDB层(更新幂等性,重复数据删除,同步日志),所有四个平台的提供者有效载荷解析, 只读SQL防护,以及每个MCP工具的端到端测试 — 无需网络或凭证。CI在每次推送和拉取请求时运行它( `.github/workflows/tests.yml` )。
## 添加新平台
1. 创建 `providers/newplatform.py` 扩展 `FitnessProvider`。
2. 将凭证添加到 `.env`。
3. 在 `sync.py` 中注册它:`PROVIDERS["newplatform"] = NewPlatformProvider`。
完成 — 每个工具在 `platform="all"` 时自动包含它。
## 项目状态
已实现:基础架构,所有四个提供者(佳明,Strava,谷歌健身,苏unto),跨平台重复数据删除,完整的工具层(活动,健康,训练,分析,同步),凭证/会话强化,自动化设置(macOS / Linux / Windows),计划循环同步,FitBrain辅导提示和带有CI的pytest套件。谷歌健身没有心率变异性,苏unto仅通过其公共API公开锻炼;两者仍然满足通用`FitnessProvider`接口,因此工具层统一对待它们。
剩下的是针对真实帐户的实时验证(需要您的凭证)。