关于 hybridlog
hybridlog 是可自托管的 Garmin 数据 MCP 服务和仪表板,面向跑步者、力量训练者及混合训练者。它镜像个人健康数据,计算训练负荷、肌肉恢复度和准备度,并通过只读权限向 LLM 提供数据查询。
主要功能
- 01同步并镜像 Garmin 数据
- 02计算训练负荷、准备度和肌肉恢复度
- 03查看训练及比赛分段分析
- 04提供健康数据仪表板
- 05限制查询为只读 SELECT
- 06按账户控制可供聊天读取的数据
适用场景
- 01比较本周与之前的训练负荷
- 02查看当天准备度和适合训练的肌群
- 03分析比赛中跑步与站点项目的分段
- 04记录症状并结合健康数据讨论
MCP 相关
提供 stdio 和带 OAuth 2.1 的 Streamable HTTP MCP 连接,向客户端开放健康摘要、训练分析、数据查询等工具及周报提示词。数据读取受权限开关控制;症状记录是聊天侧唯一写入功能,且写入应用自身的数据。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://{hybridlog_host}/mcp/garminclaude mcp add --transport http imgrund-hybridlog https://{hybridlog_host}/mcp/garmin{"mcpServers":{"imgrund-hybridlog":{"type":"http","url":"https://{hybridlog_host}/mcp/garmin"}}}在 Cursor 中打开{"servers":{"imgrund-hybridlog":{"type":"http","url":"https://{hybridlog_host}/mcp/garmin"}}}在 VS Code 中打开{"mcpServers":{"imgrund-hybridlog":{"type":"http","url":"https://{hybridlog_host}/mcp/garmin"}}}{"mcpServers":{"imgrund-hybridlog":{"serverUrl":"https://{hybridlog_host}/mcp/garmin"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
ghcr.io/imgrund/hybridlog:0.1.13stdio工具
详细介绍
hybridlog
基于你自己的 Garmin 数据运行的可自托管 MCP 服务。它会将 Garmin 返回的所有数据镜像到由你控制的数据库中,计算手表无法计算的模型(训练负荷、肌肉恢复情况、结合上下文的准备度),并通过标准输入/输出或 HTTP 将所有数据交给大模型,这样,Garmin 无法测量的内容就可以直接口述到聊天中,而不必填写表单。它还附带一个仪表板:身体肌肉图、训练负荷,以及连接器用于身份验证的登录界面。它没有托管实例,而这正是设计初衷:这是一份健康记录,大模型通过只能读取数据的角色访问它。
无论是跑步、力量训练,还是同一天两者兼有,这些模型都不在意。跑步与站点训练交替进行的训练课程会单独获得逐圈分析,这正是 HYROX 比赛的形式。
安装前可以先体验演示:使用 [email protected] 登录,密码为 demo-demo-demo。演示使用生成的数据,并会在每晚重置。

|
|
|
这三张图中的所有数字均由 fetcher/seed_demo.py 生成。界面为英文,并附带德语翻译;除非个人资料另有设置,否则会根据浏览器语言显示。
依赖之前请先阅读。 数据抓取器使用 Garmin Connect 的非官方 Web API。Garmin 可能会在不另行通知的情况下更改或关闭该 API。本项目与 Garmin 无关联,也未获得 Garmin 背书。项目计算的任何内容都不构成医疗声明:准备度、HRV 区间、负荷比和教练文本都是训练辅助信息,并非诊断结果。这是一个个人项目,发布时希望它能有所帮助,但不承诺提供支持或保持接口稳定。
它是什么
- MCP server:基于
laravel/mcp提供 13 个工具和一个提示词,支持两种传输方式。本地方式(stdio)用于 Claude Code 和 Claude Desktop;托管方式(支持流式传输的 HTTP,搭配 OAuth 2.1、PKCE 和动态客户端注册)用于 claude.ai、ChatGPT、Mistral 的 Le Chat(已更名为 Vibe)、LM Studio,以及任何支持该协议的其他产品。 - 仪表板:使用 Laravel、Alpine 和 Chart.js 构建。它绘制身体地图和训练负荷,同时也是连接器进行身份验证所用的登录入口,也是设置聊天可查看内容的地方。
- Fetcher:Python 程序(
fetcher/),通过 python-garminconnect 拉取每日指标和活动,写入 PostgreSQL 数据库中属于单个运动员的架构。上文提到的 MCP 服务和仪表板都通过只读连接读取该架构,且只能读取该运动员的数据。每个回答也会连同从中提取数据的列一起完整保留,因此,即使某个字段尚未有对应的数据列,也仍然可以查询;即使 Garmin Connect 不再提供某一天的数据,镜像中仍会保留这些数据。
“没有托管实例”在实践中意味着:Garmin 会话存放在你自己的数据库架构中,任何读取角色都无法访问,因此模型生成的 SQL 无法查询它,也只有你自己持有该会话。除 Garmin 本身外,只有两项可选服务会向外部发出请求:天气服务会使用你自行填写的一对坐标;通知服务则通过一个空 POST 唤醒推送服务,且绝不会传递通知内容。
这些工具
十三个工具和一个提示词。右侧列是 /connect 中控制各项工具的开关;默认情况下全部启用。
| 工具 | 能回答什么 | 需要 |
|---|---|---|
get-health-summary-tool | 一次调用即可了解当前状况:准备度、睡眠、负荷、数据新鲜度 | 读取健康数据 |
get-insights-tool | 应用针对各身体系统给出的判断、建议和早期疾病模式 | 读取健康数据、读取身体指标 |
get-muscle-map-tool | 各区域的新鲜度、每周各区域的训练量、今天该练什么 | 读取健康数据 |
get-training-load-tool | CTL/ATL/TSB、急慢性负荷比、每周训练刺激分布 | 读取健康数据 |
get-strength-progress-tool | 按周查看各运动类别:次数、记录过的训练吨位、最高重量,以及哪些方面没有进步 | 读取健康数据 |
get-race-splits-tool | 按分段查看一次训练:跑步与站点训练、每段配速、配速变化幅度 | 读取健康数据 |
get-activity-detail-tool | 深入查看一次训练:各区间的分钟数和占比与运动员区间下限的对比、分段、组数、心率曲线,以及与此前同类训练的对比 | 读取健康数据 |
describe-schema-tool | 镜像数据库中的表和列,以便模型自行编写查询 | 读取健康数据、读取身体指标 |
query-health-data-tool | 其他所有查询:执行一条只读 SELECT,最多返回 500 行 | 读取健康数据、读取身体指标 |
refresh-data-tool | 从 Garmin 同步今天和昨天的数据(即标题栏按钮的功能,但范围缩小到两天),并等待同步完成 | 启动一次抓取 |
log-symptom-tool | 将随口提到的身体不适记录为身体图上的标记 | 记录身体感受 |
delete-symptom-tool | 身体不适痊愈后将其移除 | 记录身体感受 |
give-feedback-tool | 将一条修正作为连接器的长期指南 | 处理反馈 |
weekly-report(提示词) | 驱动周日回顾;报告是对话的回答,不会存储在任何地方 | 读取健康数据 |
大多数工具都能回答应用围绕其构建的问题。其中有两个例外:
describe-schema 会把镜像数据库中的表及说明各列含义的注释交给模型,query-health-data 则会执行模型据此编写的 SELECT。这就是一组报告与一个可供人思考和查询的数据库之间的区别,也正是原始回答值得保留在磁盘上的原因:尚未提升为列的字段,只需一个问题就能查询。
除了一个有明确说明的例外,所有操作都只读。聊天中唯一可以写入的内容是身体不适记录,而且它们会写入应用自身的架构,绝不会写入 Garmin 镜像。自由格式 SQL 通过 app/Garmin/ReadOnlyGarminQuery 执行:仅允许单条 SELECT 或 WITH 语句,并设有关键字阻止列表、只读事务和行数上限;连接会切换到一个角色,该角色只对一名运动员的架构拥有 SELECT 权限,不具备其他权限。
快速开始(Docker、演示数据、无需 Garmin 账号)
git clone https://github.com/Imgrund/hybridlog.git
cd hybridlog
cp .env.example .env
docker compose up -d
这会启动 PostgreSQL、仪表板、队列工作进程和调度器。
发布的镜像支持 amd64 和 arm64,因此首次启动会下载镜像,而不是编译。
执行 git pull 后,直接运行 up -d 仍会使用已有镜像:只有加上 --build 才会载入新代码。
接下来,用 120 天看似真实的数据填充镜像,创建用于登录的账号,然后打开仪表板:
docker compose exec app /opt/fetcher/bin/python fetcher/seed_demo.py
docker compose exec app php artisan app:create-user [email protected] --admin
open http://localhost:8080
我们刻意不提供注册页面:没有人能注册的登录入口,就没有可供攻击的入口。如果是给自己以外的人使用,请交给他们一个链接,而不是密码:
docker compose exec app php artisan app:invite [email protected] --name="Them"
它会生成一个一次性链接,有效期为七天(--days),对方可通过该链接设置自己的密码。--admin 会将安装所有者标记为管理员;本地 stdio 传输代表的就是这个账号。每个账号都有自己的个人资料、权限、症状日志、Garmin 登录、通知和镜像,也看不到其他人的任何信息。
DEMO_MODE=true 会将安装转为展示窗口:所有人共用一个账号,所有会向外部发起连接的功能都会关闭(尤其是 Garmin 登录),并由 php artisan demo:reset 每晚将其重置。
展示窗口也是唯一值得统计访客数量的地方;设置 UMAMI_SCRIPT_URL 和 UMAMI_WEBSITE_ID 后,就会渲染你自行托管的 Umami 实例的脚本标签:统计页面浏览量,不使用 cookie,也不使用会让任何人离开网站后仍被追踪的标识符。必须同时设置这两个值;默认情况下,只要其中任意一个为空,就不会渲染任何内容,也不会向除此以外的主机发送页面请求。它不依赖于 DEMO_MODE,因此只需保持默认设置,私有安装就不会加载外部脚本。
连接 AI
服务读取的镜像与仪表板使用的相同,因此聊天和页面上的数据始终一致。但页面无法回答它未被设计来回答的问题:本周与上周相比表现如何、今天是否该休息、身体的不适对明天有什么影响。
重要
这会将你的健康记录交给一个语言模型。它通过只能读取数据且只能访问你自己的架构的角色来读取,但会读取其中的全部内容。 你可以在
/connect中逐项设置允许读取的内容;开关将在下一次工具调用时生效,而不是等到重新连接后才生效。
所有客户端使用同一个地址:
https://<your-domain>/mcp/garmin
Claude、ChatGPT、Langdock、Le Chat 和 LM Studio 都支持该地址,并通过仪表板自己的登录流程进行 OAuth 身份验证,因此客户端永远看不到密码;你也可以随时在 /connect 中切断它们的访问。Claude Code 和 Claude Desktop 则可以跳过部署,直接通过 stdio 与代码库通信。docs/connect-ai.md 介绍了各客户端的操作步骤。
无论使用哪种连接方式,它都能使用相同的 13 个工具和相同的开关。
连接后,不妨试着问:
我的训练周与前一周相比如何?
我准备好进行高强度训练了吗,还是需要休息一天?
哪些肌肉状态良好,今天该练什么?
周六比赛时我的配速哪里出了问题,计时中有多少时间花在了站点项目上?
箱式跳跃时我的左膝疼了。
使用自己的 Garmin 账户进行设置
在账户菜单中的 Garmin 下登录,或访问 /connect/garmin:
输入邮箱、密码,以及 Garmin 要求时提供的 MFA 验证码。系统存储的是一对 OAuth 令牌,而非密码。登录由队列工作进程执行,因此必须有一个工作进程处于运行状态。
首次登录会自动填充镜像:队列会回填九十天的数据,大约需要一刻钟,历史数据到达时页面会逐步填充。九十天大致是模型能够给出结果所需的最短时长,因为 HRV 基线需要三周的夜间数据,负荷比则需要滚动六周的数据。此后,调度器每天抓取三次,Fetch from Garmin 按钮则可按需抓取。
每条命令只对一名运动员执行(--tenant <user id>;省略时使用所有者)。详情、回填和手动登录说明见
docs/install.md;关于如何记录才能获得值得分析的数据,请参阅 docs/recording.md。
文档
- docs/install.md:不使用 Docker 进行安装、部署到平台以及备份。
- docs/connect-ai.md:各客户端的操作步骤、OAuth 和服务发现,以及如何在公共主机上运行 MCP 服务。
- docs/recording.md:记录规则、天气和通知。
架构说明
- 派生指标(CTL/ATL/TSB、ACWR 回退值、以约 28 小时半衰期计算并根据 90 天历史自校准的肌肉恢复度)会在
app/Garmin/中即时计算。 garmin连接与 Laravel 的默认连接分开。它们指向同一个数据库,但每个查询都针对一名运动员的架构:App\Garmin\Mirror会将search_path设为garmin_t{id},并在执行第一条语句前切换到该租户的只读角色。- 运动员仍需输入的只有界面语言,在
/profile中设置并存储在数据库中,而非.env中:语言属于运动员,而非部署。 - 颜色遵循经过验证的数据可视化调色板(通过
prefers-color-scheme支持浅色和深色模式),角色定义在resources/css/app.css中。
贡献
发布版本均带有标签,CHANGELOG.md 说明了每个版本的变更内容。CONTRIBUTING.md 说明了如何运行测试套件、测试对代码的要求,以及哪些变更明确不在范围内。行为规范见 CODE_OF_CONDUCT.md。安全问题不应通过 issue 提交:SECURITY.md 说明了私密报告渠道。
许可证
MIT,详见 LICENSE。resources/data/body-polygons.json 中的肌肉多边形来自 body-highlighter 项目系列,同样采用 MIT 许可证。
两个捆绑资源各自适用独立条款。3D 人体模型 resources/models/body-zones.glb 改编自 BodyParts3D(© The Database Center for Life Science),并继续遵循 CC Attribution-Share Alike 2.1 Japan 许可证;resources/models/CREDITS.md 列出了完整署名信息及许可证约束。resources/fonts/ 中的 Mulish 和 Oswald 字体采用 SIL Open Font License 1.1 许可证,许可证文本随字体一同提供。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。

