内容
# 我的健身日志 MCP 服务
可部署的 [模型上下文协议(MCP)](https://modelcontextprotocol.io/) 服务器,用于我的健身日志,充当 **远程克劳德连接器**:食物日记、食物搜索、锻炼、身体测量、营养目标、水摄入量和营养报告。
这是 [AdamWalt/myfitnesspal-mcp-python](https://github.com/AdamWalt/myfitnesspal-mcp-python) 的一个分支(MIT,工具实现),为远程部署而重构,带有来自 [garmin-mcp-service](https://github.com/delize/garmin-mcp-service) 的 OAuth 2.1 / 流式 HTTP 传输骨架。我的健身日志访问通过 [coddingtonbear/python-myfitnesspal](https://github.com/coddingtonbear/python-myfitnesspal)。
## 工具列表
| 工具 | 类型 | 描述 |
|------|------|-------------|
| `mfp_get_diary` | 读取 | 获取某日期的食物日记(餐食、记录、营养、总数、目标) |
| `mfp_search_food` | 读取 | 搜索我的健身日志食物数据库 |
| `mfp_get_food_details` | 读取 | 获取某食物的完整营养分解,依据 MFP ID |
| `mfp_get_recent_foods` | 读取 | 获取认证账户中最近使用的食物 |
| `mfp_get_frequent_foods` | 读取 | 获取认证账户中最常使用的食物 |
| `mfp_get_my_foods` | 读取 | 获取认证账户中创建或保存的食物 |
| `mfp_get_measurements` | 读取 | 获取身体测量历史(体重、体脂等) |
| `mfp_set_measurement` | 写入 | 记录今天的身体测量 |
| `mfp_get_exercises` | 读取 | 获取某日期记录的有氧/力量锻炼 |
| `mfp_get_goals` | 读取 | 获取每日营养目标 |
| `mfp_set_goals` | 写入 | 更新每日营养目标 |
| `mfp_get_water` | 读取 | 获取某日期的水摄入量 |
| `mfp_set_water` | 写入 | 记录某日期的水摄入量 |
| `mfp_add_food_to_diary` | 写入 | 将食物记录添加到餐食中 |
| `mfp_create_food` | 写入 | 在我的健身日志数据库中创建新的自定义食物 |
| `mfp_update_food_entry` | 写入 | 更新现有日记记录,依据 `entry_id` |
| `mfp_delete_food_entry` | 写入 | 删除现有日记记录,依据 `entry_id` |
| `mfp_get_report` | 读取 | 获取某日期范围内的营养报告(例如净卡路里) |
### 食物收藏(最近/常用/我的食物)
`mfp_get_recent_foods`、`mfp_get_frequent_foods` 和 `mfp_get_my_foods` 每个都接受可选的 `limit`(最近/常用默认 10,我的食物默认 100,最大 100)和 `response_format`(`markdown` 或 `json`)。它们故意使用 **遗留添加到日记的 AJAX 端点** (`/food/load_recent`、`/food/load_most_used`、`/food/load_my_foods`),而不是较新的 `/food/mine`、`/meal/mine` 或 `/food/new` 页面,这些页面可能会重定向到 `/account/logout`,即使日记读取和 API 令牌获取仍然有效。
### 编辑日记记录
`mfp_get_diary` 使用 `response_format=json` 现在公开每个餐食记录的 `entry_id`。将该 ID 传递给:
- `mfp_update_food_entry` - 更改 `meal`、`quantity`、`unit`(份量标签,例如 `"350 ml"`)或 `weight_id`(原始 MFP 份量选项 ID,覆盖 `unit`)为记录;需要 `date` 用于历史记录。我的健身日志可以在编辑时重写记录,因此响应会报告 `current_entry_id` 和 `entry_id_changed`,以便您可以跟踪正确的行。
- `mfp_delete_food_entry` - 删除 `entry_id` 的记录(需要 `date` 用于历史记录)。
### 创建自定义食物
`mfp_create_food` 在 `mfp_search_food` 找不到合适的食物时,将新的食物提交到我的健身日志数据库。必填字段是 `description` 和每份核心宏量营养素(`calories`、`fat`、`carbs`、`protein`);`brand`、`serving_size`、`servings_per_container`、`share_public` 和可选的微量营养素(纤维、糖、钠、胆固醇、维生素等)则完善了它。所有营养值都是按照 `serving_size` 定义的单个份量输入的(例如 `serving_size="125 g"`,数字为 125 克份量)。当份量单位是质量单位(`g`、`mg`、`kg`、`oz`、`lb`)时,会记录克重,以便 MFP 基于克的缩放保持正确。
成功时,它返回新的食物 `mfp_id`;将该 ID 传递给 `mfp_add_food_to_diary` 以记录它(它可能需要短时间才能在 `mfp_search_food` 中显示)。重新运行相同的详细信息会创建重复的食物。
**`share_public=True` 是不可逆的** - 它将食物提交到我的健身日志的共享公共数据库,公共食物不能再编辑或删除。将其留为 `False`(默认)以创建您可以删除的私有食物。
> 实现注意:[`python-myfitnesspal`](https://github.com/coddingtonbear/python-myfitnesspal) 库的 `set_new_food()` 不再有效 - 我的健身日志用客户端 SPA 替换了服务器渲染的 `/food/new` Rails 表单,该表单没有 `authenticity_token` 输入,因此库的 HTML 抓取会引发 `IndexError: list index out of range`。`mfp_create_food` 而是使用帐户的 bearer token(库仍用于目标的相同机制)将 POST 请求发送到 `v2/foods` API。
## 身份验证:cookie 策略
我的健身日志登录页面受到 **验证码保护**,因此无头密码登录无效 - 此服务器从以下位置读取会话 cookie:
1. **Firefox 配置文件边车(推荐)**:在 Firefox 配置文件中登录 myfitnesspal.com 一次,交互式地;在容器中将配置文件目录以只读方式挂载到 `/profile`。服务器每次刷新时都会复制 `cookies.sqlite`(及其 WAL)到临时文件 - Firefox 的锁无关紧要 - 并提取 `myfitnesspal.com` cookie。副本被缓存,只有在文件更改时才会重新读取,因此 Firefox 可以继续运行(例如,您偶尔会 VNC 进入以重新登录的无头 Firefox 边车容器)。
2. **JSON cookie 文件**:`MFP_COOKIES_FILE` 指向 `{"cookies": {name: value}}`(AdamWalt 的 `~/.mfp_mcp/cookies.json` 格式)或纯 `{name: value}` 字典。
会话 cookie 最终会过期(~30 天);当工具开始因身份验证错误而失败时,请在 Firefox 配置文件中再次登录我的健身日志。
## 环境变量
| 变量 | 默认值 | 描述 |
|----------|---------|-------------|
| `MFP_FIREFOX_PROFILE_DIR` | `/profile`(Docker) | Firefox 配置文件目录(或配置文件父目录)包含带有登录 MFP 会话的 `cookies.sqlite` |
| `MFP_COOKIES_FILE` | - | JSON cookie 文件;如果设置且未找到 `cookies.sqlite`,则使用 |
| `MCP_TRANSPORT` | `stdio`(Docker 中的 `streamable-http`) | 传输:`stdio` 或 `streamable-http` |
| `MCP_HOST` | `127.0.0.1`(Docker 中的 `0.0.0.0`) | HTTP 模式的绑定地址 |
| `MCP_PORT` | `8000` | HTTP 模式的端口 |
| `MCP_ALLOWED_HOSTS` | - | 以逗号分隔的允许 `Host` 头(反向代理域)。启用 DNS 重新绑定保护;如果未设置,则在 HTTP 模式下禁用保护 |
| `MCP_OAUTH_PASSCODE` | - | OAuth 登录页面的共享通行码(远程连接器)。省略用于未经身份验证的仅限 LAN 使用 |
| `MCP_RESOURCE_URL` | - | 客户端使用的确切公共 URL(无路径),例如 `https://mfp.example.com`。与通行码一起必填 |
| `MCP_ACCESS_TOKEN_TTL` | `2592000`(30 天) | 访问令牌寿命(**秒**)。当令牌过期时,连接器重新授权,这意味着重新输入通行码;短值(旧的 24 小时默认值)强制客户端不静默刷新时每天重新登录。将其降低(例如 `86400`)以获得更严格的令牌 |
## Docker
```bash
docker build -t myfitnesspal-mcp-service .
docker run -d -p 8000:8000 \
-v ~/.mozilla/firefox/abcd1234.default-release:/profile:ro \
-e MCP_ALLOWED_HOSTS=mfp.example.com \
-e MCP_RESOURCE_URL=https://mfp.example.com \
-e MCP_OAUTH_PASSCODE="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')" \
ghcr.io/delize/myfitnesspal-mcp-service:latest
```
CI 在推送到 `main` 和 `v*` 标签时构建并推送 `ghcr.io/delize/myfitnesspal-mcp-service`(amd64 + arm64)。
## 克劳德连接器设置
与 garmin-mcp-service 相同流程:
1. 在 HTTPS(反向代理)后面部署,并设置 `MCP_TRANSPORT=streamable-http`、`MCP_RESOURCE_URL` 和 `MCP_OAUTH_PASSCODE`。
2. 在克劳德中,添加一个自定义连接器,网址为 `https://mfp.example.com/mcp`。留空 OAuth 客户端 ID/密钥 - 服务器支持动态客户端注册 (`/register`)、授权 + PKCE (`/authorize`、`/token`)。
3. 克劳德会将您重定向到 `/login` 通行码页面;输入 `MCP_OAUTH_PASSCODE`。之后,客户端持有并刷新其自己的令牌。
通行码证明了“调用者知道通行码”,而不是身份 - 在任何面向互联网的部署前面保持网络级访问控制(IP 白名单、VPN)。省略 `MCP_OAUTH_PASSCODE`/`MCP_RESOURCE_URL` 运行 HTTP 服务器未经身份验证(会记录警告);仅在受信任的网络上执行此操作。
用于本地 stdio 使用(克劳德桌面):
```json
{
"mcpServers": {
"myfitnesspal": {
"command": "python",
"args": ["-m", "myfitnesspal_mcp.server"],
"env": {
"MFP_FIREFOX_PROFILE_DIR": "/home/you/.mozilla/firefox/abcd1234.default-release"
}
}
}
}
```
## 故障排除
### 工具未显示,即使连接器显示“已连接”
**问题**:连接器授权并显示为已连接,但其工具从未在对话中显示 - 询问模型使用它们或搜索它们时没有任何结果。没有显示错误。
**原因**:这几乎总是 **客户端工具预算限制**,而不是此服务器的问题。克劳德限制了单个对话中可以激活的工具数量,跨所有连接的服务器累计。如果另一个连接器公开了大量工具集,它可能会消耗该预算并无声地挤出此服务器的工具。(实践中看到具有约 170 个工具的连接器会饿死此服务器的少数工具。)
**确认/修复**:
1. 在 **全新对话**中,禁用其他大型连接器并检查这些工具是否现在显示。如果它们显示,则是预算问题。
2. 将高工具计数连接器保持在 **单独对话**中,或如果客户端支持每工具切换,则修剪其活动工具。
3. 此服务器始终返回其完整的工具列表 - 您可以通过 `/mcp` 上的身份验证 `tools/list` 调用独立验证。如果该调用返回工具,但客户端不显示它们,则差距在客户端,不在此处。
## 致谢
- 工具实现:[AdamWalt/myfitnesspal-mcp-python](https://github.com/AdamWalt/myfitnesspal-mcp-python)(MIT,此仓库是一个分支 - 保留完整的历史)
- 我的健身日志客户端库:[coddingtonbear/python-myfitnesspal](https://github.com/coddingtonbear/python-myfitnesspal)
- OAuth/传输骨架:garmin-mcp-service
## 许可证
MIT - 请参阅 [LICENSE](LICENSE)(保留原始版权)。