内容
# 缺失的MCP
*存在的MCP服务器。连接它们并不复杂。*
[](https://smithery.ai/servers/vaclav/garmin)
一个支持多用户、受OAuth 2.1保护的网关,托管克劳德(Claude)缺少的连接器,因此一个小型的信任圈可以连接他们自己的账户,来自任何克劳德客户端(iOS、Android、Web、桌面)。旗舰连接器是 [Garmin Connect](https://connect.garmin.com):网关封装了 **未修改** 的 [`garmin_mcp`](https://github.com/Taxuspt/garmin_mcp) 工作程序,并添加了OAuth、每个用户的令牌隔离和反向代理。[WHOOP](https://www.whoop.com) 以同样的方式提供,但是在进程内,在WHOOP自己的官方OAuth v2 API上。核心是基于适配器的,支持三种转发策略 — **工作程序**(garmin),**本地**(whoop,在进程内,无子进程),和 **远程**(MCP +头部注入,对于具有托管MCP的服务,但缺乏自己的OAuth)— 参见 [连接器](#连接器)。参考部署在 [missingmcp.com](https://missingmcp.com) 运行。
```
克劳德 → POST /garmin/mcp (Bearer) → 网关 → 127.0.0.1:<端口>/mcp (每个用户 garmin_mcp) → connect.garmin.com
```
## 为什么
`garmin_mcp` 是一个优秀的MCP服务器,但它是单用户且仅支持标准输入输出:每个人都必须使用自己的Garmin令牌在本地运行它。这个网关使其成为一个 **远程MCP服务器**,任何克劳德客户端都可以通过HTTP连接,带有适当的OAuth登录流程 — 因此,非技术用户只需点击“连接”并使用他们的Garmin凭证登录,永远不需要触摸终端或令牌文件。
## 功能
- **OAuth 2.1** — 授权码 + PKCE(S256)与动态客户端注册。从任何克劳德客户端连接;无需手动令牌管理。
- **Garmin密码从未存储** — 仅在登录时使用(支持MFA);仅存储生成的会话令牌。
- **静态加密** — 令牌使用AES-256-GCM密封;数据库在没有 `GATEWAY_SECRET` 的情况下是无用的。Bearer令牌仅存储为SHA-256哈希。
- **每个用户隔离** — 每个账户都有自己的 `garmin_mcp` 工作程序,绑定到 `127.0.0.1`,按需启动,空闲时终止。
- **强化** — 一次性10分钟授权码,表单上的CSRF,每个IP/令牌速率限制,`garmin_mcp` 工作程序固定到审查的提交。
- **外部备份** — 定期加密的SQLite快照到S3兼容的存储桶(参见 [备份](#备份))。
- **教学登录页面** 在 `/` 处提供,并作为未知路径的友好备用。
## 部署
**Railway(missingmcp.com 运行方式):** 一个服务从 `Dockerfile` 构建,一个卷挂载在 `/data`,一个副本(网关按设计保持进程本地状态),并且在 Railway 边缘终止TLS。在服务上设置 [配置](#配置) 中的环境变量;推送到 `main` 在服务连接到 GitHub存储库后自动部署。可选的Railway存储桶启用 [备份](#备份)(`railway bucket create backups`,然后将凭证设置为 `BACKUP_S3_*` 变量)。
**自托管(普通Docker):**
```bash
cp .env.example .env # 设置 GATEWAY_SECRET, PUBLIC_URL, 固定 GARMIN_MCP_REF
docker build -t missingmcp .
docker run -d --name missingmcp --restart unless-stopped --env-file .env \
-p 127.0.0.1:8080:8080 -v missingmcp-data:/data missingmcp
```
在前面放置任何TLS终止代理(Caddy,nginx,Traefik)。一个不明显的
要求:`/<adapter>/mcp` 流式传输SSE,因此禁用响应缓冲并
增加读取超时(nginx:`proxy_buffering off; proxy_read_timeout 3600s;`)。
然后添加 `https://<your-domain>/garmin/mcp` 作为克劳德中的远程MCP服务器。
## 本地开发
```bash
uv pip install -e ".[dev]"
uv run --extra dev pytest -q # 运行测试套件
# 在本地运行网关(不需要Garmin账户即可使用OAuth界面)。
# garmin-mcp 在本地不在 PATH 上,因此将 GARMIN_MCP_CMD 指向 uvx。
GATEWAY_SECRET="$(openssl rand -base64 48)" \
PUBLIC_URL=http://localhost:8088 PORT=8088 DATA_DIR=./.localdata \
GARMIN_MCP_CMD="uvx --python 3.12 --from git+https://github.com/Taxuspt/garmin_mcp garmin-mcp" \
uv run missingmcp
```
工作目录中的 `.env` 文件自动加载(实际环境
变量优先),因此您可以将相同的值放入其中。
## 连接器
每个连接器都挂载在其自己的路径前缀下,具有自己的OAuth流程和
自己的Bearer令牌(没有裸 `/mcp`)。登录页面在 `/` 列出
可用的连接器。
### Garmin — `/garmin/mcp`
使用您的Garmin Connect邮箱+密码登录(支持MFA)。密码
仅在Garmin登录时使用,然后丢弃;仅存储生成的Garmin
会话令牌(加密)。每个账户都有自己的 `garmin_mcp` 工作程序
进程,绑定到 `127.0.0.1`,按需启动,空闲时终止。
### WHOOP — `/whoop/mcp`
在WHOOP自己的OAuth页面上登录 — 网关从未看到您的WHOOP密码,
只有生成的(加密,只读)令牌。涵盖恢复、睡眠、
应激 & 每日周期、锻炼和身体测量。在WHOOP的官方v2 API上
提供(无工作程序子进程,无共享上游)。需要
操作员注册一个WHOOP开发者应用程序 — 参见 [WHOOP连接器
设置](#whoop-连接器设置) 在配置中。
### Rohlík — 不再缺失
网关曾短暂提供Rohlík连接器(远程策略:注入
到托管的Rohlík MCP的凭证头)。它在2026-07被淘汰,
当时Rohlík发布了 **自己的OAuth保护的MCP** — 将
`https://mcp.rohlik.cz/mcp`直接作为克劳德中的自定义连接器,
并在浏览器中登录([Rohlík指南](https://www.rohlik.cz/stranka/mcp-server))。
远程转发策略仍然是核心的一个一流的、经过测试的部分
(`tests/test_remote_forward.py`)供下一个需要它的服务使用。
## 从克劳德连接
1. 在任何克劳德客户端中:**设置 → 连接器 → 添加自定义连接器**,或在
命令行中:`claude mcp add --transport http garmin https://<your-domain>/garmin/mcp`
(whoop:`claude mcp add --transport http whoop https://<your-domain>/whoop/mcp`)。
2. 克劳德打开网关的登录页面 — 输入服务邮箱 +
密码(Garmin还提示输入MFA代码,如有必要),或对于WHOOP,
在WHOOP自己的OAuth页面上登录。
3. 完成 — 服务的工具现在可在克劳德中使用。
## 配置
通过环境(或 `.env`)设置。参见 [`.env.example`](.env.example)。
| 变量 | 必需 | 默认值 | 描述 |
|---|---|---|---|
| `GATEWAY_SECRET` | **是** | — | ≥32字符的令牌加密密钥。拒绝启动时使用占位符。使用 `openssl rand -base64 48` 生成。 |
| `PUBLIC_URL` | 是 | `http://localhost:8080` | 用于OAuth元数据 + 重定向的公共URL。 |
| `PORT` | 否 | `8080` | 监听端口。 |
| `DATA_DIR` | 否 | `/data` | SQLite数据库和每个用户令牌目录的位置。 |
| `DB_PATH` | 否 | `$DATA_DIR/gateway.db` | 重写数据库路径。 |
| `GARMIN_MCP_CMD` | 否 | `garmin-mcp` | 启动工作程序的命令。使用 `uvx …` 调用当 `garmin-mcp` 不在PATH上时。 |
| `GARMIN_MCP_REF` | 否 | `Dockerfile`中的固定SHA | Docker构建参数:要安装的`garmin_mcp`提交。增加它是一个刻意、审查过的操作 — 之后运行 `python scripts/gen_garmin_tools.py` 以刷新 `/garmin` 页面的工具列表。 |
| `WORKER_PORT_START` / `WORKER_PORT_END` | 否 | `9000` / `9099` | 每个用户工作程序的端口范围。 |
| `WORKER_IDLE_TTL` | 否 | `900` | 空闲工作程序被回收前的秒数。 |
| `WORKER_STARTUP_TIMEOUT` | 否 | `20` | 等待工作程序变得健康的时间(秒)。 |
| `MAX_WORKERS` | 否 | `10` | 最大并发每个用户工作程序。 |
| `ACCESS_TOKEN_TTL_DAYS` | 否 | `90` | Bearer令牌生命周期;用户在到期后重新认证。`0` 禁用过期。 |
| `OPERATOR_NAME` / `OPERATOR_EMAIL` | 否 | — | 登录页面上显示的内容。 |
| `OPERATOR_URL` | 否 | — | 操作员名称链接到的首页(页脚,信任备注)。未设置 → 普通文本。 |
| `WHOOP_CLIENT_ID` / `WHOOP_CLIENT_SECRET` | 否 | — | 您的WHOOP开发者应用程序的凭证(参见WHOOP连接器设置);两者未设置 ⇒ whoop连接器被禁用。 |
| `WHOOP_API_BASE` | 否 | `https://api.prod.whoop.com` | WHOOP API源;仅用于测试时覆盖。 |
| `BACKUP_S3_ENDPOINT` / `BACKUP_S3_BUCKET` / `BACKUP_S3_ACCESS_KEY` / `BACKUP_S3_SECRET_KEY` | 否 | — | S3兼容存储桶用于异地数据库备份(参见备份)。除非全部设置,否则备份被禁用。 |
| `BACKUP_S3_REGION` | 否 | `auto` | 存储桶的SigV4区域。 |
| `BACKUP_S3_URL_STYLE` | 否 | `virtual-host` | `virtual-host`(Railway存储桶)或 `path`。 |
| `BACKUP_INTERVAL_HOURS` | 否 | `6` | 备份上传之间的小时数。第一个备份在启动后立即运行。 |
| `GATEWAY_LOG_FILE` | 否 | — | 如果设置,将结构化和标准日志记录到此文件中。 |
| `GATEWAY_LOG_LEVEL` | 否 | `info` | `debug`\|`info`\|`warning`\|`error`\|`critical`。`debug`很详细(记录garminconnect/urllib3内部信息)— 在生产环境中避免使用。 |
### WHOOP连接器设置
1. 在 <https://developer-dashboard.whoop.com> 创建一个应用程序(即时自助服务)。
2. 重定向URI:`https://<your-domain>/whoop/oauth/callback` — 需要精确匹配。
3. 作用域:`read:recovery read:cycles read:workout read:sleep read:profile read:body_measurement offline`
(`offline`是必需的 — 如果没有它,WHOOP不会发出刷新令牌,会话将在一小时后过期)。
4. 将应用程序的客户端ID/密钥放入 `WHOOP_CLIENT_ID` / `WHOOP_CLIENT_SECRET`。
注意:未批准的WHOOP应用程序限制为 **10个WHOOP成员**。要解除
限制,请提交应用程序审批:<https://developer.whoop.com/docs/developing/app-approval/>
(要求:[API使用条款](https://developer.whoop.com/api-terms-of-use/)
合规性,至少有 ≥1 名成员测试,准确的应用名称/联系邮箱/隐私政策
网址在仪表盘中,[品牌指南](https://developer.whoop.com/docs/developing/design-guidelines)
合规性,以及他们的Typeform提交)。
根据WHOOP API使用条款(网关设计
已经涵盖技术要求 — 无WHOOP数据存储,加密令牌,
自动清除已撤销的帐户):
- 将涉及WHOOP成员数据的任何安全事件报告给
<apisupport@whoop.com> 而无需延迟,并报告给受影响的用户。
- 任何新闻稿或公共声明引用WHOOP未经WHOOP事先书面批准。
- 将应用的客户端ID/密钥保持在存储库之外(仅限环境变量),并且
绝不重复用于另一个应用程序。
- 当成员在WHOOP撤销应用程序时,网关在下一次刷新时检测到,
删除存储的令牌,并撤销成员的网关访问令牌(日志事件
`whoop-account-revoked`)。
## 备份
当设置了 `BACKUP_S3_*` 变量时,网关会将一致的
SQLite快照(通过SQLite备份API,WAL安全)上传到存储桶
在启动后立即和每 `BACKUP_INTERVAL_HOURS` 次。密钥按星期几
轮换(`db/gateway-mon.db` … `db/gateway-sun.db`),给予
七天的保留期,无需清理逻辑。监视 `backup-ok` / `backup-failed`
日志事件。
只有数据库被备份 — `DATA_DIR/users/` 下的每个用户令牌目录
按需重新生成自数据库。
> **备份在没有 `GATEWAY_SECRET` 的情况下是无用的**(帐户数据块
> 保持 AES-256-GCM 加密)— 这就是重点。保持
> `GATEWAY_SECRET` 的副本在 *不是* 存储桶和 *不是* Railway
> 的地方(例如您的密码管理器)。丢失密钥 = 丢失每个帐户。
**恢复:** 下载最新的对象,将其放在 `$DATA_DIR/gateway.db`
(删除旁边可能存在的陈旧 `gateway.db-wal`/`-shm`),设置
相同的 `GATEWAY_SECRET`,启动网关。Bearer令牌和登录
信息存活;工作程序延迟生成。
## 监控
三个帮助脚本直接在网关的数据库上运行(在网关运行时安全):
```bash
python scripts/status.py # 快照:帐户、其设备(令牌
# 前缀)、用法摘要、正在运行的工作程序
python scripts/revoke.py --list # 帐户 + 令牌计数
python scripts/revoke.py --account [<adapter>:]<key> # 杀死开关:撤销所有
# 帐户的令牌(裸密钥 = garmin)
python scripts/revoke.py --account <key> --purge # + 删除存储的帐户和用法
python scripts/revoke.py --device <hash-prefix> # 撤销一个设备(前缀来自 status.py)
python scripts/usage.py # 每个帐户的工具用法 + 排行榜
python scripts/usage.py --account [<adapter>:]<key> # 某个帐户的每个工具的明细
python scripts/subscribers.py # 通讯稿订阅 + 建议
python scripts/subscribers.py --emails # 订阅者电子邮件,一行一个
python scripts/daily_report.py # 昨天的活跃/新增/总用户(打印)
python scripts/daily_report.py --post # + 发布到Slack($SLACK_WEBHOOK_URL)
```
网关还会在每天早上在其上设置时将此每日用户统计报告发布到Slack
(`DAILY_REPORT_HOUR`,默认为08:00 `DAILY_REPORT_TZ`)。`scripts/daily_report.py`
在测试时为需求运行相同的报告。
**每小时健康摘要** — GitHub Actions工作流
(`.github/workflows/hourly-digest.yml`)每小时运行 `scripts/hourly_digest.py`:
它通过Railway API读取网关的最后60分钟日志,进行活动检查,
并仅在异常(≥3 5xx/错误,任何
`critical`,或失败的探测 → `<!here>`)或每天作为健康的心跳
发布到Slack — 否则保持沉默。需要存储库机密 `RAILWAY_API_TOKEN` + `SLACK_WEBHOOK_URL`
(服务/环境ID设置为工作流环境变量)。`RAILWAY_API_TOKEN` 可以是Railway **项目**
令牌(最小范围 — 仅限此项目;通过 `Project-Access-Token` 头发送)
或帐户/工作区令牌(作为 `Authorization: Bearer` 发送);脚本尝试两者。
在本地运行它,使用 `python scripts/hourly_digest.py --dry-run`(需要 `RAILWAY_API_TOKEN`,
`RAILWAY_SERVICE_ID`,`RAILWAY_ENVIRONMENT_ID` 环境变量)。
**使用Docker**,脚本被放入镜像的 `/app/scripts` 目录;在此容器内运行它们。
`status.py` 自动在 `/data` 下找到数据库:
```bash
docker exec missingmcp python /app/scripts/status.py
docker logs -f missingmcp # 实时结构化 JSON 事件
```
**在Railway上**,通过 `railway ssh` 运行它们;日志实时显示在Railway仪表盘上
(`railway logs --service gateway` 获取实时日志流):
```bash
railway ssh --service gateway "python3 /app/scripts/status.py"
railway ssh --service gateway "python3 /app/scripts/revoke.py --account <email>"
```
所有日志记录都是结构化的JSON,带有正确的 `level` 属性 — 包括uvicorn/标准库记录
(事件 `stdlib-log`)和每个工作程序的输出(事件 `worker-log`,带有 `account` 属性;
匹配ERROR/Traceback的行被提升为错误严重性)。在Railway上,这使得所有内容都可在日志资源管理器中搜索 — 例如
过滤 ` @event:worker-log @account:<email>` 以跟踪某个用户的worker,或
` @level:error` 以查找问题。请求延迟在每个调用中记录
`mcp-response` 事件(`ttfb_ms`,`total_ms`,`bytes`,`tool`,`account`);
登录/验证和工作程序生成事件带有 `ms` 持续时间。网关还记录
`stats` 事件(帐户/令牌/拥有令牌的人/客户端/活动工作程序)在启动和计数更改时,
而 `status.py` 列出正在运行的工作程序。
工作原理
1. Claude 注册一个客户端(DCR)并启动 OAuth 1(授权码 + PKCE)。
2. 在授权页面,用户使用 Garmin(邮箱 + 密码 多因素身份验证(MFA)如果需要)登录。网 `garminconnect` 登录,存储 **仅生成的令牌加密),并丢弃密码。
3. Claude 用承载令牌交换授权码。
4. 在每次min/mcp` 调用网关确保用户的 `garcp` 工作进程正在运行(其自己的令牌,绑定到 `127.0.0.1`)并反向代理到使用远程策略的适配器用针对上游 MCP 的探测验证和直接头部注入转发替换步骤 2 和 4 - 无需工作进程。
使用上游 OAuth 登录(whoop)的适配器用重定向到提供者的 OAuth 页面替换步骤 2;提供者使用令牌回调关持久化这些令牌(验证后持久化不变)。策略(whoop)的适配器替换步骤 4:在进程中处理请求 - 无需工作进程,无需共享上游 - 网关根据需要刷新帐户的旋转 WHOOP 令牌。
## 安全性
- Garmin 密码从未持久保存;WHOOP 密码甚至从未被看到(OP 自己的 OAuth 页面上进行登录,只读范围)。
- 在存储时加密(AES-256-GCM);如果没有 `GATEWAY_SECRET`,数据库将变得无用。
- 成员在 WHO撤销应用将在下一次刷新时到,其存储的令牌将自动清除。
- 承载令存储为 SHA-256 。
- OAuth 2.CE256),一次性 分钟授权码,表单上的 CSRF/令牌速率限制。
- 工作进程仅绑定 `127.0.0.1`;`garmin_mcp` 绑定到审查过的提交。
> 仅在您控制和信任的基础设施上部署。备份 `DATA_DIR`;单独保留 `GATEWAY_SECRET`。
## 部署前
- **设置真正的随机 `GATEWAY_SECRET`** (`openssl rand -base64 48`) - 应用程序拒绝使用 `.env.example` 中的占位符启动。
- **将 `GARMIN_MCP_REF` 固定到审查过的提交 SHA** - `main` 是一个浮动引用,可能会在不通知的情况下更改(供应链)。
- **撤销访问** - 访问令牌在 `ACCESS_TOKEN_TTL_DAYS`(默认为 90 天)后过期(用户只需在 Claude 中重新身份验证)。要更快地撤销 - 泄露的令牌或删除的用户 - 运行 `python scripts/revoke.py --account [<adapter>:]<email>`(该帐户所有令牌的紧急开关)。可以使用 `--device <hash-prefix>` 撤销单个设备(前缀由 `status.py` 显示)。
- **运行手动端到端烟雾测试**,使用真实的 Garmin 帐户(包括 MFA 路径),在连接真实用户之前 - 上自动化测试中被模拟。
## 支持
如果这个网关对您有帮助,您可以[请一杯啤酒 https://buymeacoffee.com/venik)。
## 许可证MIT) 2026 Vaclav Slajs 版权所有
## 致谢
包装了优秀的 [`garmin_mcp`](https://github.com/Taxuspt/garmin_mcp),无修改min 和 Garmin Connect 是 Garmin Ltd. 的商标;本项目与关联或 endorsement。
连接信息
猜你喜欢
暂无推荐服务器