Skip to main content
Glama
README.md
**English** · [繁體中文](README.zh-TW.md) · [日本語](README.ja.md)

# Health Data Hub: iPhone collector + local Mac database + MCP

> A **lightweight, personal** health-data collector. It moves the data in your iPhone's Health app to your own Mac, stores it as one SQLite file, and lets AI assistants such as Claude and Codex query it. No cloud, no account, no server.
>
> **Version 0.3.2** ([changelog](CHANGELOG.md)) · **Status:** in daily use by the author since 2026-10 (as of 2026-10-06) · iPhone with iOS 18+ and a Mac on the same Wi-Fi.

Want an AI assistant to install it for you? Hand **[docs/AGENT_INSTALL.md](docs/AGENT_INSTALL.md)** to Claude Code or Codex and it will follow the steps.

## What it is

Three small parts in a line:

1. **iPhone collector app** (native Swift): reads the Health app and pushes new data to the Mac.
2. **Mac receiver** (Python, FastAPI): receives the data on your home Wi-Fi and writes it to the local SQLite file `data/health.sqlite`.
3. **MCP server** (Python, no dependencies): lets Claude Code, Codex and other AI assistants query the data, build weekly reports, and log workouts and meals by hand.

## What it does

- Collects 11 data types automatically: body mass, lean body mass, body fat %, BMI, waist circumference, steps, heart rate, resting heart rate, heart rate variability (HRV), sleep and workouts. Field meanings: [docs/health-data-fields.md](docs/health-data-fields.md).
- Anything a smart scale or watch writes into the Health app is collected too — the collector reads the Health app and does not care which device wrote the data.
- Ask your AI assistant directly: "How did my weight change last week?", "Am I sleeping enough?", "Give me this week's report."
- Log workouts and meals by hand (`log_workout` / `log_meal`).

## Why

The Health app keeps its data on the phone: exporting means tapping through menus, the export is one huge XML file, and AI assistants cannot see it. Most sync services upload the data to someone else's server.

This project takes another route: **the data only travels between your own phone and your own Mac**, lands in a SQLite file you can open directly, and reaches your local AI assistant through MCP.

## Scope

**Built for one person, one iPhone and one Mac.** It is not a multi-user platform.

Advantages:

- **Private:** phone → home Wi-Fi → your Mac. No third party in between.
- **No loss, no duplicates:** the phone keeps a sync position per data type; batches that fail to send wait in a local queue on the phone and are resent unchanged; the Mac deduplicates by sample uuid, so a resend is stored once.
- **Open format:** one SQLite file any tool can read; schema in `server/schema.sql`.
- **Ready for AI:** the MCP server plugs into Claude Code and Codex as is.
- **Light:** no cloud service, database server or Docker. The Mac runs one Python process.
- **Tested:** automated tests on both the Python and Swift sides, plus on-device acceptance.

Limits — what it does not do:

- **iPhone + Mac only.** No Android, Windows or Linux phone side. The receiver can run anywhere Python runs, but `server/run.sh` uses macOS `networksetup` and `dns-sd`.
- **Read-only.** It never writes back to the Health app.
- **iOS background wake-ups are unreliable.** iOS decides when to wake the app; the author measured 2 background wake-ups in 33 hours. So a Shortcuts automation opens the app on a schedule, which guarantees **at least one sync a day**, not real time.
- **Phone and Mac must share a Wi-Fi network** (or you set up Tailscale as a backup path). While the Mac is off or asleep the data waits in the phone's queue and is resent later.
- **Scale data must reach the Health app first.** A Eufy scale, for example, writes through its own app; only then can the collector read it.
- **Only the 11 types above.** No ECG, blood oxygen, medications, etc.
- **No web UI.** You query through MCP (an AI assistant) or read SQLite directly.
- **The app is not on the App Store.** You build it with Xcode and install it yourself. A free Apple ID works, but the installed app expires after 7 days and must be reinstalled.

## How it works

```text
iPhone
  ┌─ Health app (HealthKit)
  │      │ read only
  │      ▼
  └─ Collector app
       · one sync position per data type
       · failed batches are queued and resent
       · syncs when the app opens / on background wake
             │
             │  HTTP POST + Bearer token  →  healthhub.local:8765
             ▼
Mac (same Wi-Fi)
  ┌─ Receiver: server/run.sh (FastAPI :8765)
  │      POST /ingest · GET /health
  │      │ dedupe by uuid, write
  │      ▼
  ├─ data/health.sqlite
  │      │
  │      ▼
  └─ MCP server health-hub  ←  Claude / Codex
```

- **Incremental sync:** the app uses HealthKit anchored queries to fetch only what is new. A type's anchor moves forward only after the Mac confirms receipt, so a failure halfway never loses data.
- **When it syncs:** when the app opens (with the "打开 App 时自动同步" — *auto-sync on open* — switch on), and when iOS wakes it in the background, which is rare. A daily Shortcuts automation is the safety net.
- **Finding the Mac:** the receiver binds to the Mac's Wi-Fi address and announces the Bonjour name `healthhub.local`, so the phone keeps working when the Mac's IP changes.
- **Auth:** the Mac generates a 32-character access token; it is baked into the app at build time (locally, never in git) and sent with every request.
- Full protocol: [docs/sync-protocol.md](docs/sync-protocol.md).

## Install

### Prerequisites

- A Mac with Xcode (iOS 18 SDK or later), [uv](https://docs.astral.sh/uv/), [XcodeGen](https://github.com/yonaskolb/XcodeGen) (`brew install xcodegen`) and Python 3.9+.
- An iPhone on iOS 18+, connected by cable, with Developer Mode on.
- An Apple ID signed in under Xcode → Settings → Accounts (a paid developer account is easier).

### Step 1: Mac receiver

```bash
git clone <this-repo-url> health-hub && cd health-hub
uv sync                                   # install Python dependencies
uv run python -m server.token new         # create the access token in data/ingest_token (mode 0600)
server/run.sh                             # start the receiver: Wi-Fi address :8765, announces healthhub.local
```

Check from another terminal: `curl http://healthhub.local:8765/health` returns `{"status":"ok"}`.
On first run macOS may ask whether to accept incoming connections — choose Allow.
To keep it running, start `server/run.sh` from `launchd` or your login items.

### Step 2: build and install the iPhone app

1. Create `ios/local.xcconfig` (already in `.gitignore`):

   ```
   DEVELOPMENT_TEAM = <your 10-character Team ID>
   PRODUCT_BUNDLE_IDENTIFIER = com.<your-name>.healthcollector
   ```

   `security find-identity -v -p codesigning` shows the Team ID in parentheses.

2. Bake the Mac address and token into the app, generate the project and build:

   ```bash
   ios/make_local_config.sh                 # writes ios/HealthCollector/LocalConfig.plist (not in git; never prints the token)
   cd ios && xcodegen generate
   xcodebuild -project HealthCollector.xcodeproj -scheme HealthCollector \
     -destination 'generic/platform=iOS' -configuration Debug \
     -allowProvisioningUpdates build
   ```

   Or, after `xcodegen generate`, open `ios/HealthCollector.xcodeproj` in Xcode, pick your iPhone and press Run.

3. Install on the phone from the command line:

   ```bash
   APP=$(find ~/Library/Developer/Xcode/DerivedData -path '*Debug-iphoneos/HealthCollector.app' | head -1)
   xcrun devicectl list devices                                   # find your device name
   xcrun devicectl device install app --device "<device-name>" "$APP"
   ```

   On first install, trust the developer on the phone under Settings → General → VPN & Device Management.

### Step 3: first run on the phone

The app's interface is in Chinese; the English meaning is in parentheses.

1. Open the collector, tap **授权读取健康数据** (*allow reading health data*) and allow everything in the system sheet.
2. Tap **测试连接** (*test connection*); it should succeed — the Mac address and token are already filled in.
3. Tap **立即同步** (*sync now*). The first sync uploads your full history to the Mac.
4. Turn on **打开 App 时自动同步** (*auto-sync on open*). The same switch also controls background sync.

### Step 4: Shortcuts automations for a daily sync (recommended)

iOS rarely wakes the app in the background, so in the Shortcuts app → Automation, create two automations:

- **Time of Day:** every day at 12:00 → action "Open App" → the collector → "Run Immediately".
- **Charger:** when connected → open the collector → "Run Immediately".

Opening the app triggers a sync. Automations can only be created on the phone, not from the Mac.

## Usage

### Connect an AI assistant (MCP)

**Claude Code:** `.mcp.json` at the repo root already registers `health-hub`. Start `claude` in the repo; the first time it asks whether to enable this project MCP server — approve it.

**Codex:** add to `~/.codex/config.toml` (use your absolute path):

```toml
[mcp_servers.health-hub]
command = "python3"
args = ["/absolute/path/health-hub/src/health_hub_mcp/server.py"]
```

Then ask: "health-hub weight for the last 7 days", "weekly report", "how is sync doing?"

| Tool | What it does |
|---|---|
| `query_samples` | query samples by type and time range |
| `weekly_report` | one-week summary (weight, steps, sleep, heart rate…) |
| `sync_status` | last sync time and row counts per type |
| `log_workout` / `log_meal` | log a workout / meal by hand |
| `ios_env_check` / `ios_build` / `ios_install` / `ios_logs` | for developing the app: check the environment, build, install, read logs (output redacted) |
| `kb_list` / `kb_search` | search the bundled Apple HealthKit documentation |

Without an AI assistant, from the command line:

```bash
python3 src/health_hub_mcp/server.py sync-status     # sync status
python3 src/health_hub_mcp/server.py env-check       # iOS development environment check
python3 src/health_hub_mcp/server.py kb-search "background delivery"
```

| Variable | Purpose | Default |
|---|---|---|
| `HEALTH_HUB_DATA_DIR` | data directory | `data/` in the repo |
| `HEALTH_HUB_INGEST_TOKEN` | access token (wins over `data/ingest_token`) | — |
| `HEALTH_HUB_BUNDLE_ID` | bundle ID used by `ios_install` / `ios_logs` | `PRODUCT_BUNDLE_IDENTIFIER` from `ios/local.xcconfig` |
| `HEALTH_HUB_DEVICE` | device name used by `ios_install` / `ios_logs` | — (put it in the git-ignored `local.env`) |

### Agent skill: `ios-dev-env`

The repo ships a Claude Code skill at [.claude/skills/ios-dev-env/SKILL.md](.claude/skills/ios-dev-env/SKILL.md). It teaches the assistant to check the iOS toolchain, generate the project, build, install, read logs and debug signing, and it keeps secrets (Team ID, certificates, device IDs) out of chat and git.

- **In this project only:** nothing to install. Claude Code loads project skills from `.claude/skills/` when started in the repo. Say "build the collector", "install on my phone", "signing error", or type `/ios-dev-env`.
- **In every project:** copy it into your personal skills directory:

  ```bash
  mkdir -p ~/.claude/skills && cp -R .claude/skills/ios-dev-env ~/.claude/skills/
  ```

- **Codex:** ask Codex to read `.claude/skills/ios-dev-env/SKILL.md` and follow it; if your Codex version supports skills, you can copy the folder into its skills directory (see the Codex docs).
- **Before first use:** keep your own values out of git — device name in `local.env`, bundle ID in `ios/local.xcconfig`, and anything else about your machine in a git-ignored `.claude/skills/ios-dev-env/local.md`, which the skill reads first.

## Documentation

| Document | What is in it |
|---|---|
| [Health data fields](docs/health-data-fields.md) | What the Health app can give you: the 11 types collected today with meaning and unit, every database column, sleep states, common metadata keys, and HealthKit types that could be added later |
| [Sync protocol](docs/sync-protocol.md) | How the phone talks to the Mac: request and response fields, examples, timestamps, deduplication, transport, auth |
| [Agent install guide](docs/AGENT_INSTALL.md) | Step-by-step install for Claude Code / Codex, with a check after every step |
| [Project structure](STRUCTURE.md) | Every file and folder, and where new things go |
| [Changelog](CHANGELOG.md) | What changed in each version |

## Project layout

| Path | Purpose |
|---|---|
| `ios/CollectorCore/` | Swift package: sync engine, queue, encoding — all testable logic |
| `ios/HealthCollector/` | thin iPhone app shell (HealthKit access, UI, background delivery) |
| `server/` | Mac receiver: FastAPI app, token tool, `run.sh`, SQLite schema |
| `src/health_hub_mcp/` | MCP server, also usable from the command line |
| `kb/` | bundled Apple documentation for the MCP knowledge-base tools |
| `docs/` | protocol, field reference, plans, device test records, agent install guide |
| `tests/` | pytest suites and fixtures |
| `data/` | your database and token — git-ignored, never leaves the Mac |

Every file and folder: [STRUCTURE.md](STRUCTURE.md).

## For developers

```bash
uv run pytest                                          # Python: unit / integration / contract / e2e
swift test --package-path ios/CollectorCore            # Swift collector core
```

Changes per version: [CHANGELOG.md](CHANGELOG.md) · How to contribute: [CONTRIBUTING.md](CONTRIBUTING.md) · Reporting security problems: [SECURITY.md](SECURITY.md).

## Privacy

- `data/` (database, token), `ios/local.xcconfig`, `ios/HealthCollector/LocalConfig.plist` and the generated `.xcodeproj` are all git-ignored.
- `tests/_privacy.py` checks that identifiers such as Team IDs, device UDIDs, Tailscale IPs and certificate fingerprints never enter the repo.

## License

[MIT](LICENSE). The optional reference material described in [kb/README.md](kb/README.md) is not included and keeps its own terms.