Health Vault
by ilyanehay
README.md
# Health Vault
Self-hosted **Xiaomi Fitness / Smart Band → SQLite → authenticated MCP** for [Grok](https://grok.com) (CLI and grok.com Custom Connector).
```text
Mi Band → Mi Fitness (phone) → Xiaomi cloud
↓
health sync (this project)
↓
SQLite
↓
MCP tools (OAuth or API key)
↓
Grok answers with real data
```
**Not affiliated with Xiaomi or xAI.** Uses an unofficial reverse-engineered Health API. Personal / research use. Wearable data is **wellness only**, not medical advice.
---
## Requirements
| Need | Notes |
|------|--------|
| **Python 3.12+** | Required (`requires-python >= 3.12`) |
| **Xiaomi Smart Band** + **Mi Fitness** app | Band must sync to the phone app first |
| **Xiaomi account** | QR login from this tool |
| Optional: **cloudflared** or **ngrok** | For grok.com (public HTTPS) |
| Optional: **Grok CLI / Grok Build** | Local MCP with Bearer API key |
---
## Quick install
```bash
git clone <this-repo> health-vault && cd health-vault
# Option A: uv
uv venv --python 3.12 .venv
uv pip install --python .venv/bin/python -e .
# Option B: plain venv
python3.12 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -U pip
pip install -e .
```
CLI entrypoint: `.venv/bin/health` (or `health` after activate).
Runtime data goes to `./data/` (gitignored): Xiaomi token, SQLite DB, OAuth state.
---
## Scenario A — Local data + Grok CLI
Best for development and private use on one machine.
### 1. Pull data from Xiaomi
1. On the phone, open **Mi Fitness** and sync the band.
2. On the computer:
```bash
.venv/bin/health login # QR → scan with Xiaomi Account app
# QR is also saved as data/qr_login.png if you cannot see the terminal
.venv/bin/health sync --days 7
.venv/bin/health status # expect sync_health: ok
.venv/bin/health show --days 14
```
### 2. API key + MCP server
```bash
.venv/bin/health key create --name grok # prints Bearer token ONCE — save it
.venv/bin/health serve --host 127.0.0.1 --port 8787
```
### 3. Point Grok CLI / Grok Build at the server
`~/.grok/config.toml` (or your agent’s MCP config):
```toml
[mcp_servers.health]
url = "http://127.0.0.1:8787/mcp"
headers = { Authorization = "Bearer hlt_PASTE_YOUR_TOKEN" }
```
Restart the CLI/agent so it reloads MCP. Ask something concrete, e.g. *“How did I sleep in the last 7 days?”*
### Smoke checks (A)
```bash
curl -sS http://127.0.0.1:8787/healthz
# → ok
curl -sS -o /dev/null -w '%{http_code}\n' -X POST http://127.0.0.1:8787/mcp \
-H 'Content-Type: application/json' -H 'Accept: application/json' -d '{}'
# → 401 (auth required)
.venv/bin/health tool health_status
.venv/bin/health tool health_context --hint sleep --days 7
```
---
## Scenario B — grok.com Custom Connector (tunnel)
Grok’s cloud must call a **public HTTPS** MCP URL. Easiest path: local server + [Cloudflare quick tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) or [ngrok](https://ngrok.com). See also [xAI tunneling notes](https://docs.x.ai/grok/connectors/custom-mcp-tunneling).
### 1. Data + owner password
```bash
.venv/bin/health login
.venv/bin/health sync --days 7
.venv/bin/health oauth set-password # password for the browser login page
```
### 2. Serve with a public URL
**One-shot (integrated tunnel):**
```bash
# brew install cloudflared # if needed
.venv/bin/health serve --host 127.0.0.1 --port 8787 --tunnel
```
Copy the printed URL, e.g. `https://xxxx.trycloudflare.com`.
**More reliable (two terminals):**
```bash
# Terminal 1
.venv/bin/health serve --host 127.0.0.1 --port 8787 --public-url https://PLACEHOLDER
# Terminal 2 — after Uvicorn is up
cloudflared tunnel --url http://127.0.0.1:8787
# copy https://xxxx.trycloudflare.com
# Restart terminal 1 with the real public URL (OAuth issuer must match HTTPS host):
.venv/bin/health serve --host 127.0.0.1 --port 8787 \
--public-url https://xxxx.trycloudflare.com
```
> Free tunnel URLs often **change on restart** — update the Grok connector when they do.
> If the public URL returns **530 / Error 1033**, wait a few seconds or use the two-terminal flow.
### 3. Connect in Grok
1. Open [grok.com/connectors](https://grok.com/connectors) → **New Connector** → **Custom**.
2. Server URL: `https://xxxx.trycloudflare.com/mcp` (must include **`/mcp`**).
3. If Grok shows an OAuth form: use **PKCE**; authorization/token endpoints from
`https://xxxx.trycloudflare.com/.well-known/oauth-authorization-server`;
scope **`health:read`** (Client Secret usually empty for public + PKCE).
4. Browser opens **Health Vault** login → enter the password from `health oauth set-password`.
5. Chat with a real question (not only “list tools”).
More detail: [docs/GROK_CONNECT.md](docs/GROK_CONNECT.md).
### Smoke checks (B)
```bash
curl -sS https://xxxx.trycloudflare.com/healthz
curl -sS https://xxxx.trycloudflare.com/.well-known/oauth-authorization-server | head
curl -sS -o /dev/null -w '%{http_code}\n' -X POST https://xxxx.trycloudflare.com/mcp \
-H 'Content-Type: application/json' -d '{}'
# → 401 without token
```
---
## Scenario C — VPS / home server (generic production)
Pattern: **app listens on HTTP on a private port**; **TLS and domain** are terminated by a reverse proxy (Caddy, nginx, Traefik, etc.) in front. Do **not** put secrets in git.
Example names below are placeholders — choose your own domain and port.
### 1. Install on the host
```bash
sudo mkdir -p /opt/health-vault/data
sudo chown "$USER:$USER" /opt/health-vault /opt/health-vault/data
chmod 700 /opt/health-vault/data
# copy or git clone the project into /opt/health-vault
cd /opt/health-vault
python3.12 -m venv .venv
.venv/bin/pip install -U pip
.venv/bin/pip install -e .
```
### 2. Environment file (secrets, not in git)
```bash
sudo tee /etc/health-vault.env >/dev/null <<'EOF'
HEALTH_ROOT=/opt/health-vault
HEALTH_DATA_DIR=/opt/health-vault/data
PUBLIC_BASE_URL=https://health.example.com
HOST=0.0.0.0
PORT=8002
# Optional: bootstrap owner password once if DB has none yet
OAUTH_LOGIN_PASSWORD=change-me-to-a-long-random-string
EOF
sudo chmod 640 /etc/health-vault.env
# owner should be root + the service user (e.g. root:deploy)
```
`PUBLIC_BASE_URL` **must** be the public HTTPS origin users and Grok hit (no path).
### 3. systemd unit
Example unit (also see `deploy/health-mcp.service` as a template — rename paths):
```ini
[Unit]
Description=Health Vault MCP server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=YOUR_USER
Group=YOUR_GROUP
WorkingDirectory=/opt/health-vault
EnvironmentFile=/etc/health-vault.env
ExecStart=/opt/health-vault/.venv/bin/python -m health_mcp
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/health-vault/data
[Install]
WantedBy=multi-user.target
```
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now health-vault.service
systemctl is-active health-vault
curl -sS http://127.0.0.1:8002/healthz
```
### 4. Reverse proxy (TLS)
Terminate HTTPS on the proxy and reverse-proxy to `127.0.0.1:8002` (or the app host). Preserve `Host` and `X-Forwarded-Proto`.
**Caddy example:**
```caddyfile
health.example.com {
reverse_proxy 127.0.0.1:8002
}
```
**nginx sketch:**
```nginx
server {
listen 443 ssl;
server_name health.example.com;
# ssl_certificate ...;
location / {
proxy_pass http://127.0.0.1:8002;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
```
Point DNS A/AAAA for `health.example.com` at your server (or your edge).
### 5. Data + Grok
```bash
cd /opt/health-vault
# if OAUTH_LOGIN_PASSWORD was not used:
HEALTH_ROOT=/opt/health-vault .venv/bin/health oauth set-password
HEALTH_ROOT=/opt/health-vault .venv/bin/health login
HEALTH_ROOT=/opt/health-vault .venv/bin/health sync --days 7
```
Optional cron (every 30 minutes):
```cron
*/30 * * * * cd /opt/health-vault && HEALTH_ROOT=/opt/health-vault .venv/bin/health sync --days 3 >>/var/log/health-sync.log 2>&1
```
Connect Grok Custom Connector to:
```text
https://health.example.com/mcp
```
### Smoke checks (C)
```bash
curl -sS -o /dev/null -w '%{http_code}\n' https://health.example.com/ # 200
curl -sS -o /dev/null -w '%{http_code}\n' https://health.example.com/healthz # 200
curl -sS https://health.example.com/.well-known/oauth-authorization-server | head
curl -sS -o /dev/null -w '%{http_code}\n' -X POST https://health.example.com/mcp \
-H 'Content-Type: application/json' -d '{}' # 401
```
---
## Auth model
| Method | Use case |
|--------|----------|
| **Bearer API key** (`health key create`) | Grok CLI / local agents |
| **OAuth 2.1 + PKCE + owner password** | grok.com Custom Connector |
| **Xiaomi QR token** | Ingest only — never sent to Grok |
- API keys are stored as **SHA-256 hashes**; plaintext shown once.
- OAuth access/refresh tokens hashed in SQLite under `data/`.
- Unauthenticated `/mcp` → **401**.
---
## MCP tools
All tools return `sync_health`, `attention_required`, and a wellness disclaimer.
| Tool | Purpose |
|------|---------|
| `health_status` | Last sync, coverage, token presence |
| `health_get_daily` | Daily rows (steps, sleep, HR, SpO2, stress, …), max 90 days |
| `health_context` | Compact brief for one question (`question_hint`) |
| `health_trends` | Mean / min / max / half-window delta for one metric |
| `health_anomalies` | Points outside mean ± 2σ |
If `attention_required` is true (stale/broken sync), Grok should **warn first** and suggest `health login` / `health sync`.
Env thresholds: `HEALTH_STALE_AFTER_HOURS` (default 36), `HEALTH_BROKEN_AFTER_HOURS` (default 72).
---
## CLI reference
```text
health login # Xiaomi QR → data/token.json
health sync [--days N] [--uid …] [--base-url …]
health status
health show [--days N]
health key create|list|revoke
health oauth set-password|status
health serve [--host] [--port] [--public-url] [--tunnel]
health tool <name> [--days N] [--hint …] [--metric …]
```
Production process entry: `python -m health_mcp` (reads env above).
---
## Layout
```text
src/health_core/ SQLite schema, queries, sync_health
src/health_ingest/ Xiaomi cloud → daily_metrics
src/health_auth/ API keys, owner password, OAuth AS
src/health_mcp/ MCP server, tools, tunnel helper
src/health_cli/ `health` CLI
vendor/mi-fitness-python/ vendored SDK (GPL-3.0)
deploy/ example systemd unit (template only)
docs/GROK_CONNECT.md Grok connection details
data/ runtime (gitignored)
```
---
## Troubleshooting
| Symptom | What to try |
|---------|-------------|
| Login QR invisible | Open `data/qr_login.png`; use Xiaomi **Account** app to scan |
| `Token not found` / `TokenExpiredError` | `health login` again |
| Sync OK but empty metrics | Sync the band in **Mi Fitness** on the phone; wait; re-run `health sync` |
| `not support HlthRelativesCareClient` | Expected on some accounts; own `user_id` path still works |
| Wrong region / empty cloud | Try `--base-url` / `HEALTH_API_BASE` if your account is not on default RU IDC |
| MCP always 401 with key | Use full `Bearer hlt_…` header; create a new key if revoked |
| grok.com cannot connect | Must be public **HTTPS** + path `/mcp`; OAuth password set; tunnel alive |
| Tunnel 530 / hostname fails | Restart tunnel; two-terminal setup; confirm `cloudflared` has network |
| Stale data in Grok answers | Check `health status`; run sync on a schedule |
| `attention_required` in every tool | Fix ingest; do not treat metrics as current until `sync_health.status` is `ok` |
---
## Security notes
- Never commit `data/`, `.env`, tokens, or passwords (see `.gitignore`).
- Prefer long random `OAUTH_LOGIN_PASSWORD`; rotate via `health oauth set-password`.
- Expose only HTTPS to the internet; keep the app port private when possible.
- Single-owner design: one vault password / API keys for that host.
---
## Acknowledgements
This project stands on reverse-engineering and open-source work that made Xiaomi Fitness data reachable outside the official app.
- **[alexgetmancom/miband-bot](https://github.com/alexgetmancom/miband-bot)** — personal self-hosted Telegram bot for Xiaomi Fitness / Mi Band (auth, cloud sync, SQLite, parsers). The approach and practical stack around Band 10 / Mi Fitness cloud were a major starting point for this vault. **Thank you** to the author for publishing the research and code. See also the write-up [Reverse Engineering the Xiaomi Smart Band 10](https://habr.com/en/articles/1057044/) (Habr).
- **[MistEO/MiSDK](https://github.com/MistEO/MiSDK)** (`mi-fitness` / `mi_fitness`) — Python SDK for Xiaomi Health / “relatives” APIs (QR login, RC4 transport, typed clients). Vendored under `vendor/mi-fitness-python/` (GPL-3.0).
Neither project is affiliated with this repo; any bugs here are ours.
---
## License
This project is free software under the **[GNU General Public License v3.0 or later](LICENSE)** (`GPL-3.0-or-later`).
You may run, study, share, and modify it. If you **distribute** modified versions (or a product that combines this code with the vendored SDK), you must keep them under GPL-compatible terms and preserve copyright/license notices. See [LICENSE](LICENSE) for the full text.
- Includes **`vendor/mi-fitness-python`** ([MistEO/MiSDK](https://github.com/MistEO/MiSDK)), also **GPL-3.0** — attribution required.
- Unofficial Xiaomi Health protocol — may break without notice when Xiaomi changes apps or servers.
---
## Optional reading
- [docs/GROK_CONNECT.md](docs/GROK_CONNECT.md) — Grok auth modes and endpoints
- [PLAN.md](PLAN.md) — architecture and design notes
- [NOTES_EXPLORE.md](NOTES_EXPLORE.md) — which metrics the API returned in testing
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessSyncing