health-mcp
health_mcp
Personal health data for Claude: Garmin Connect and Technogym mywellness, served as one MCP endpoint that runs on a Windows machine, starts with it, and is reachable from claude.ai anywhere through a Cloudflare Tunnel.
claude.ai ─HTTPS─▶ Cloudflare Tunnel ─▶ cloudflared (service)
│ http://127.0.0.1:18080/s/<secret>/mcp
▼
health-caddy (Caddy, loopback :18080)
│ strips /s/<secret>/, 404s everything else
▼
health-mcp (aggregator, loopback :18001)
├─ garmin_* ─▶ garmin-mcp (loopback :18000)
└─ technogym_* ─▶ technogym-mcp (loopback :<port>/<secret>/mcp)Everything is a Windows service running as LOCAL SYSTEM. No inbound ports are opened; cloudflared dials out. The only thing between the internet and your data is the secret path, so the URL is treated like a password.
Repository layout
Path | What |
| The aggregator. FastMCP 4 |
| technogym-mcp: MCP server for Technogym mywellness (own |
| One idempotent installer for all four services. |
| Installs the Cloudflare Tunnel connector as a service (token based). |
| The secret-path gate. |
| Garmin tool allowlist (read-only tools only). |
| Lists every tool the installed garmin-mcp offers. |
| Lists the tools health-mcp exposes. |
Garmin's MCP server itself is upstream Taxuspt/garmin_mcp,
installed as a uv tool; nothing of it is vendored here.
Machine layout
Path | Purpose |
| URL secret. Treat like a password. |
| Rendered gate config (contains the secret). |
| Active Garmin allowlist. |
| One rotated log per service. |
| Garmin OAuth tokens. |
| Garmin server binary. |
| Technogym credentials, port and secret path (never committed). |
| Upstream URLs for the aggregator (never committed). |
Services: garmin-mcp, technogym-mcp, health-mcp (depends on both), health-caddy
(depends on health-mcp), cloudflared.
Setup from scratch
1. Tools
winget install --id Git.Git
winget install --id astral-sh.uv
winget install --id CaddyServer.Caddy
winget install --id Cloudflare.cloudflared
winget install --id NSSM.NSSMReopen PowerShell, then:
uv tool install --python 3.12 git+https://github.com/Taxuspt/garmin_mcp
garmin-mcp-auth # interactive Garmin login (email, password, MFA). Once.
garmin-mcp-auth --verify2. Configure
git clone https://github.com/ErikAnkerKilbergSkallevold/health_mcp
cd health_mcp
copy technogym\.env.example technogym\.env # mywellness email+password, a port (e.g. 38472),
# and TECHNOGYM_MCP_PATH=/<32 hex>/mcp
copy .env.example .env # TECHNOGYM_MCP_URL = http://127.0.0.1:<port><that path>3. Install the services
Elevated PowerShell:
.\deploy\install.ps1Generates the secret on first run, renders the Caddyfile, syncs both Python environments,
(re)creates the four services and prints probe results plus the connector path. Re-run it after
changing deploy\enabled_tools.txt or either .env.
Expected:
garmin http://127.0.0.1:18000/healthz -> 200 ok
technogym http://127.0.0.1:38472/healthz -> 200 ok
health http://127.0.0.1:18001/healthz -> 200 {"status":"ok","upstreams":{"garmin":"ok","technogym":"ok"}}
gated http://127.0.0.1:18080/s/<secret>/healthz -> 200
nosecret http://127.0.0.1:18080/healthz -> 4044. Cloudflare Tunnel
Needs a Cloudflare account (Free) and a domain whose nameservers point at Cloudflare. For a
.no domain from Domeneshop: buy it with the free DNS option, add the domain in Cloudflare,
then set the two Cloudflare nameservers under "Egne navnetjenere". Norid publishes the change
within about an hour.
Zero Trust → Networks → Tunnels → Create a tunnel → Cloudflared. Name it after the machine.
Pick Windows and copy the token from the shown
service installcommand.Published application route: subdomain
health, your domain, HTTP,localhost:18080, Path empty.On the machine, elevated:
.\deploy\install-cloudflared.ps1 -Token <token>
One tunnel per machine. More apps later are just more routes on the same tunnel.
In the zone's AI Crawl Control, set Agent to Allow, or claude.ai's requests may be blocked.
5. claude.ai
Settings → Connectors → Yours → Add custom connector. URL:
https://health.<domain>/s/<contents of C:\health-mcp\secret.txt>/mcpNo OAuth fields. Custom connectors are per account: on Team/Enterprise plans only Owners can add them, a Free account gets one, Pro/Max unlimited.
Tools
deploy\enabled_tools.txt limits Garmin to read-only tools (every get_*/search_* plus
count_activities, 106 of 150). Write tools such as uploading workouts, editing activities or
logging food are excluded because the URL alone guards the account. Trim it further to save
context per chat and re-run the installer. Technogym is read-only by design.
uv run python scripts\list_tools.py # what the connector exposes
& "$env:APPDATA\uv\tools\garmin-mcp\Scripts\python.exe" deploy\list_garmin_tools.py "$env:USERPROFILE\.local\bin\garmin-mcp.exe"Operations
Task | Command |
Status |
|
Logs |
|
Restart everything |
|
Upgrade garmin-mcp |
|
Update this repo |
|
Re-auth Garmin |
|
Rotate the secret | delete |
Uninstall |
|
Troubleshooting
Symptom | Cause / fix |
| An upstream rejects non-local Host headers. Caddy sets |
|
|
Cloudflare shows "Invalid nameservers" on a new domain | Registry publishing lag. Wait, then "Check nameservers". |
Connector gets 403 from Cloudflare | AI Crawl Control → Agent → Allow; Security → Settings → "Block AI bots" off. |
Service stuck in | The installer kills the nssm host after 15 s; manually: |
Tokens invalid after a while | Garmin tokens expire eventually: |