健身机mcp
MCP服务器,控制BLE FTMS健身机并将完成的锻炼上传到Garmin Connect。
支持的设备类别:
- 任何FTMS跑步机(测试:ADIDAS 1380--大多数固件不支持速度控制;倾斜+数据读取工作)
- 任何具有功率目标设置的FTMS自行车训练器(测试:eHuntSun T-SoX——完全ERG控制,0-2000 W)
- Garmin HRM Pro+--完整配置文件支持:
- 心率+RR间期(用于HRV分析) - RSC(跑步速度和步频)——跑步步频+步幅 - CSC(自行车速度和节奏)——自行车节奏回退
- 任何带自行车动力服务的单面BLE踏板功率计(0x1818)
(测试目标:Wahoo-Powrlink Zero——左踏板,×2双侧估计)
建筑
BLE FTMS device ──▶ fitness-machine-mcp ──▶ MCP client (Claude / any AI)
│
Garmin Connect upload (TCX)快速入门(Docker)
docker run -d \
--network host --privileged \
-e TREADMILL_BLE_ADDR=XX:XX:XX:XX:XX:XX \
-e TRAINER_BLE_ADDR=XX:XX:XX:XX:XX:XX \
-e HRM_BLE_ADDR=XX:XX:XX:XX:XX:XX \
-e GARMIN_TOKEN_PATH=/tokens/garmin \
-v /path/to/tokens:/tokens \
-p 38097:38097 \
fitness-machine-mcp--network host --privileged 在Linux上访问BLE需要。
环境变量
| 变量 | 默认值 | 描述 |
|---|---|---|
TREADMILL_BLE_ADDR | -- | FTMS跑步机的BLE MAC地址 |
TRAINER_BLE_ADDR | -- | FTMS自行车教练的BLE MAC地址 |
HRM_BLE_ADDR | -- | Garmin HRM Pro+的BLE MAC地址 |
POWER_METER_BLE_ADDR | *(空)* | 踏板功率计的BLE MAC——保持未设置跳过 |
POWER_METER_SIDE | left | 仪表踩在哪个踏板上(left 或 right);双边估算的功率加倍 |
GARMIN_TOKEN_PATH | ~/.garmin-tokens | Garmin Connect身份验证令牌目录的路径 |
TREADMILL_WORKOUTS_DIR | /app/workouts | 包含锻炼JSON文件的目录 |
PORT | 38097 | HTTP端口 |
MCP_HOST | 0.0.0.0 | 绑定地址 |
MCP_PATH | /mcp | MCP端点路径 |
MCP_ALLOWED_HOSTS | 127.0.0.1:*,localhost:* | 允许的主机标头(逗号分隔) |
MCP_ALLOWED_ORIGINS | http://127.0.0.1:*,http://localhost:* | 允许的CORS来源 |
OTEL_EXPORTER_OTLP_ENDPOINT | *(残疾)* | OpenTetry收集器端点 |
OTEL_SERVICE_NAME | fitness-machine-mcp | 遥测服务名称 |
OTEL_RESOURCE_ATTRIBUTES | service.namespace=ftms-mcp | 其他OTEL资源属性 |
MCP工具
| 工具 | 运动 | 描述 |
|---|---|---|
list_workouts | 两者 | 列出可用的锻炼计划 |
get_workout(workout_id) | 两者都有 | 完整的锻炼计划细节 |
create_workout(...) | 两者 | 创建或更新锻炼计划 |
delete_workout(workout_id) | 两者 | 删除锻炼计划 |
start_workout(workout_id) | 两者 | 连接到设备并开始锻炼 |
get_status | 两者 | 当前状态、指标、人力资源、距离 |
stop_workout | 两者 | 紧急停止 |
set_speed(speed_kmh) | running | 直接速度超控(设备必须正在运行) |
set_incline(incline_pct) | 运行 | 直接倾斜超控 |
set_target_power(power_w) | 循环 | 直接ERG功率超控 |
训练文件格式
跑步
{
"name": "Z2 Run 45min",
"sport": "running",
"incline_pct": 1.0,
"steps": [
{"name": "Warm-up", "duration_secs": 300, "speed_kmh": 8.0},
{"name": "Z2 Run", "duration_secs": 2400, "speed_kmh": 10.0},
{"name": "Cool-down", "duration_secs": 300, "speed_kmh": 7.0}
]
}自行车(ERG)
{
"name": "Sweet Spot 60min",
"sport": "cycling",
"steps": [
{"name": "Warm-up", "duration_secs": 600, "power_w": 140},
{"name": "SS Block", "duration_secs": 1800, "power_w": 210},
{"name": "Cool-down", "duration_secs": 600, "power_w": 120}
]
}power_w 值以瓦特为单位。典型目标:55-75%FTP=耐力,88-95%FTP=最佳点。
自行车锻炼开始行为
当 start_workout 需要一个自行车计划:
- 培训师连接+
REQUEST_CONTROL+START_RESUME - 状态变为
ready--HRM Pro+和功率计已经上线 - 服务器最多等待120秒,节奏>0(从功率计、HRM Pro+CSC或培训师内部检测到踏板——按优先级顺序)
- 一旦检测到踏板,锻炼计时器就会启动,并设置第一步的ERG功率
热身是 第一步 在训练JSON中。没有发动机喷射预热。
传感器数据优先级
| 度量 | 主要来源 | 回退 |
|---|---|---|
| 功率 | 踏板功率计(power_w_pm) | 培训师FTMS(power_w) |
| Cadence | 踏板功率计(cadence_rpm_pm) | HRM Pro+CSC(cadence_rpm_csc) → 培训师(cadence_rpm) |
| 心率 | HRM Pro+(hr_bpm) | 培训师FTMS |
| HRV(RR) | HRM Pro+HR字符RR标志(rr_latest_ms) | — |
| 跑步节奏 | HRM Pro+RSC(cadence_spm) | — |
所有字段始终存在于 get_status。零值表示源未连接或尚未报告。
TCX导出对功率和节奏使用最高优先级的非零值。
配对Wahoo零号电力线
- 安装踏板并旋转一次以唤醒仪表
- 在主机上:
python3 -c "
import asyncio
from bleak import BleakScanner
async def scan():
devices = await BleakScanner.discover(timeout=10)
for d in devices:
print(d.address, d.name)
asyncio.run(scan())
"- 查找名为的设备
WAHOO POWRLINK(或类似)并复制其MAC地址 - 添加到1Password保管库
Casper MCP→ itemfitness-machine→ fieldpower_meter_ble_addr - 重新生成机密:
bash ~/ai-platform/mcps/refresh-secrets.sh fitness-machine-mcp - 重建容器:
cd ~/ai-platform/mcps && docker compose up -d --build fitness-machine-mcp
FTMS速度控制注意事项
大多数消费者跑步机都宣传FTMS速度目标设置,但默默地忽略了该命令——固件只使用它来报告数据和接受倾斜变化。这是一个 全行业已知问题。此服务器会很好地处理它(记录警告,不会崩溃)。
自行车教练ERG(Set Target Power,操作码 0x05)在正确实现FTMS目标功能的设备上正常工作。
Garmin上传
已完成的训练将导出为TCX并自动上传:
- 跑步→
Sport="Running",TPX扩展中的速度+节奏 - 骑行→
Sport="Biking",电源在 `` TPX扩展字段
通过以下方式进行预身份验证 加思 和点 GARMIN_TOKEN_PATH 在生成的令牌目录中。
监控
OpenTetry跟踪是可选的。集 OTEL_EXPORTER_OTLP_ENDPOINT 以启用跟踪导出。如果未设置,服务器将在没有遥测的情况下正常启动。
许可证
麻省理工学院
