Health4AI
Use the Health4AI MCP server to query and analyze your own Apple Health data through any MCP-compatible AI client.
Summarize recent health metrics such as steps, sleep, HRV, resting heart rate, and workouts.
Break down sleep by night with REM, deep/core, light, and awake stages.
Track HRV trends, daily snapshots, and long-term/monthly trends.
Query time-series data for any HealthKit metric, with raw recent data or daily aggregates.
List recent workouts with type, duration, distance, and calories.
Generate a coaching brief covering recovery, sleep quality, training load, and fitness trajectory.
Search for days where a metric crossed thresholds, such as low HRV, high steps, or short sleep.
Compute personal baseline statistics and percentiles for any metric.
Compare a metric between two date ranges.
Use data_status flags to distinguish real data gaps from unshared Health permissions.
health4.ai — Apple Health × AI
What it does
iPhone HealthKit → Your Supabase project → MCP server → Any AIApple Health holds years of your biometric data — sleep stages, HRV, heart rate, workouts, VO₂ max, and 150+ other metrics. health4.ai makes all of it queryable from Claude, ChatGPT, Cursor, Ollama, or any MCP-compatible client through purpose-built MCP tools.
You control the data. It syncs to a Supabase project you create and own. health4.ai runs no backend and never stores or has access to your health data.
Related MCP server: Wellness Project MCP
Why it exists
Problem | health4.ai fix |
Apple has no HealthKit server API — all access requires an on-device app | Native iOS app with |
Health Auto Export only works on the same WiFi | Your data lands in Postgres — queryable from any AI, anywhere |
The claude.ai Apple Health connector doesn't reach Claude Code CLI or Cursor | Standard MCP (stdio) — one config block works everywhere |
Most solutions require a managed cloud service | Bring your own Supabase project — health4ai runs no server |
Demo
Demo video coming. Here's what a typical conversation looks like:
You: How was my recovery this week? Anything standing out?
Claude: Your recovery this week was mixed but trending positive.
• HRV averaged 58ms (Tue–Thu strong at 64–68ms; Monday low at 44ms after Saturday's long run)
• Sleep averaged 7h 12m — Wednesday and Thursday were your best nights (7h 40m+)
• Resting HR dropped from 54bpm Monday to 49bpm Friday — a good sign
Recommendation: today looks like a solid day for a moderate-intensity session.Quick start
health4ai needs a Supabase project you own. The app signs in with Supabase Auth and writes through a Supabase Edge Function, so plain Postgres (Neon, a local Docker container) is not a supported backend. Full walkthrough: docs/SETUP.md.
1. Create the schema. Create a project at supabase.com, open its SQL editor, and run web/public/schema.sql (also at https://health4.ai/schema.sql). It is generated from supabase/bootstrap/: tables, row-level security, and grants that deny clients direct access. Do not use supabase db push — the numbered migrations do not apply to a fresh project.
2. Deploy the ingest function (needs the Supabase CLI):
git clone https://github.com/jefflitt1/health4ai.git
cd health4ai
supabase functions deploy healthkit-ingest --project-ref <your-project-ref> --no-verify-jwt--no-verify-jwt is deliberate: the function verifies the signed-in user's token itself and writes only under that user's ID.
3. Create your user. In the dashboard, Authentication → Users → add a user, and copy its UID. The app signs in; it does not sign up.
Then set up the MCP server (from the same checkout):
cp mcp-server/.env.example mcp-server/.envEdit mcp-server/.env:
DATABASE_URL=postgresql://... # your project's pooler connection string (database password, not a key)
HEALTHKIT_USER_ID=... # your Supabase Auth user's UID (see mcp-server/.env.example)Add to your AI client:
{
"mcpServers": {
"health4ai": {
"command": "python",
"args": ["/path/to/health4ai/mcp-server/main.py"],
"env": {
"DATABASE_URL": "postgresql://...",
"HEALTHKIT_USER_ID": "<your auth user UID>"
}
}
}
}Same block → ~/.cursor/mcp.json
Pair with mcphost or mcp-client-for-ollama:
mcphost --model ollama/llama3.2 \
--mcp-server "health4ai:python /path/to/health4ai/mcp-server/main.py"The model runs on your hardware and the MCP server runs locally; your health data is read from your own Supabase project.
Install the iOS app from the App Store, then connect it to that Supabase project (Project URL + anon key), sign in as the user you created, and tap Start Sync. For a TestFlight beta build, follow the tester-isolation guide; never use another person's backend or credentials.
MCP tools
Tool | What it answers |
| Overview of key metrics for the past N days |
| Per-night sleep breakdown with REM, Deep, Core stages. One source per night: Oura > Apple Watch > Whoop > Garmin > Withings > whichever other source (iPhone included) has the most stage records |
| Daily HRV (SDNN) with rolling comparison and trend |
| Everything recorded for a specific date |
| Recent workouts with type, duration, distance, calories |
| Raw time-series for any HealthKit metric type |
| Monthly aggregates over years (raw + summary tiers) |
| Recovery status, sleep quality (same one-source-per-night rule as |
| Find days where a metric crossed a threshold |
| Personal baseline: min/max/mean/percentiles |
| Compare a metric between two date ranges |
If a metric is empty, read data_status before believing it
iOS never tells an app that a Health permission was denied. A type you have not
shared returns an empty result, byte-for-byte identical to a day where you genuinely
did nothing. Nothing in HealthKit's API can distinguish the two, so an assistant reading
a bare 0 will confidently tell you that you took no steps.
Tools that can return an empty result therefore attach a data_status block:
never_recorded— this metric has never produced a sample for you. For steps, heart rate, active energy or walking distance that is not possible if the data were being shared, so it almost certainly is not. Open Health → Sharing → Apps → health4ai, switch the metric on, then re-run the import from the app's Home tab.empty_window— nothing in the window you asked about, but the metric has data at other times. A real gap, not a permission problem.
This is not hypothetical. On the author's own account, step count, heart rate, active energy and walking distance were silently unshared for nearly three months while every other metric synced normally, and the app displayed a green "Complete" throughout.
Architecture
┌─────────────────────────────────────────────────────────────┐
│ iPhone │
│ HKObserverQuery + BGTaskScheduler │
│ → continuous background sync │
└──────────────────────────┬──────────────────────────────────┘
│ HTTPS → your healthkit-ingest Edge Function
▼
┌─────────────────────────────────────────────────────────────┐
│ Your Supabase project (you own it) │
│ healthkit_metrics · healthkit_daily_summaries │
│ row-level security on · no direct client access │
└──────────────────────────┬──────────────────────────────────┘
│ Postgres connection (database password, from your machine)
▼
┌─────────────────────────────────────────────────────────────┐
│ FastMCP server (mcp-server/main.py) │
│ FastMCP tools · stdio transport │
└──────────────────────────┬──────────────────────────────────┘
│ MCP
▼
Claude · ChatGPT · Cursor · Ollama · any clientData tiers: queries within the last 30 days return raw samples. Older days use a pre-aggregated row from healthkit_daily_summaries where one exists and are otherwise aggregated per day inside Postgres from the raw samples, so results are complete whether or not the summariser has ever run on your project (on a fresh self-hosted project it never has). Responses carry a tier block saying how many days each tier served. Calendar days follow HEALTH4AI_TZ (default UTC; set it in mcp-server/.env).
Repo structure
health4ai/
├── ios/ # Swift/SwiftUI iOS app (iOS 17+)
│ └── Health4AI/ # HealthKit sync engine, auth, settings
├── mcp-server/
│ ├── main.py # FastMCP server entry point
│ ├── tools.py # MCP tool implementations
│ └── .env.example # Required environment variables
├── web/
│ ├── public/schema.sql # Generated from supabase/bootstrap (Supabase only)
│ └── src/ # Astro marketing site
├── scripts/
│ ├── import_health_export.py # One-time XML backfill from Apple Health export
│ └── summarize_historical.py # Backfill daily summaries table
└── docs/
└── SETUP.md # Detailed setup guidePrivacy
Your health data goes directly from your iPhone to your own Supabase project. health4.ai never receives, stores, or has access to it. The MCP server runs locally with your own credentials — your data never touches our infrastructure.
See the Privacy Policy for full details.
Contributing
MIT licensed. PRs welcome.
Good first areas: additional metric aggregations, multi-user support with JWT/RLS, Android, and more MCP client integration guides.
SQL changes need explicit grants. Supabase no longer auto-grants anon / authenticated / service_role access on new public tables (existing projects from 2026-10-30), so a table is unreachable through the Data API until its SQL says who may use it. Start from scripts/migration-templates/public_table.sql; a table that customers install belongs in supabase/bootstrap/ (then run scripts/build_schema_sql.sh). scripts/lint-migration-grants.sh checks web/public/schema.sql and the migration folders, and CI fails a table without a grant. Findings in the old numbered migrations are frozen in scripts/migration-grants-baseline.txt.
License
MIT — see LICENSE.
Available Tools
11 toolscompare_periodsA
Compare a health metric between two date ranges. Dates: YYYY-MM-DD.
Examples:
Sleep before vs after starting magnesium: period_a = two weeks before, period_b = two weeks after
HRV this month vs last month: period_a_start='2026-05-01', period_a_end='2026-05-31', period_b_start='2026-06-01', period_b_end='2026-06-18'
Steps during a work trip vs home baseline
Returns per-period stats and a delta showing which period was better.
Inputs: metric_type (required), period_a_start (required), period_a_end (required), period_b_start (required), +3 more. Returns: object
get_coaching_briefA
Pre-session coaching brief for Brett — combines recent trends across all key metrics. Returns a structured summary optimized for performance coaching context: recovery status, sleep quality, training load, and fitness trajectory. Call this at the start of every coaching session.
Inputs: no named parameters. Returns: object
get_daily_snapshotA
Everything recorded for a specific date (YYYY-MM-DD). Defaults to today. Returns steps, sleep, workouts, HRV, resting HR, active energy, and all other metrics.
Inputs: date. Returns: object
get_health_summaryA
Overview of key health metrics for the past N days. Returns avg steps, avg sleep, avg HRV, avg resting HR, workout count.
Inputs: days. Returns: object
get_hrv_trendA
HRV (SDNN) trend over the past N days. Returns daily averages, 7-day rolling comparison, and trend direction. Tier-aware: windows beyond 30 days transparently use daily summaries.
Inputs: days. Returns: object
get_long_term_trendA
Long-term trend for any metric. Tier-aware: merges recent raw data (last 30 days, aggregated to daily) with historical daily summaries, so the trend has no recency gap. Best for multi-year / seasonal analysis. metric_type examples: HKQuantityTypeIdentifierHeartRateVariabilitySDNN, HKQuantityTypeIdentifierRestingHeartRate, HKQuantityTypeIdentifierBodyMass, HKQuantityTypeIdentifierStepCount months: how many months of history to return (default 24)
Inputs: metric_type (required), months. Returns: object
get_metric_statsA
Personal baseline statistics for any health metric. Returns min, max, mean, std dev, and percentile distribution (p10-p90).
Use to answer: 'Is today's reading good or bad for me personally?' Pair with get_daily_snapshot to compare today's value against your baseline.
The 'thresholds' field translates percentiles into plain English: good_day_above = your 75th percentile (a genuinely above-average day) poor_day_below = your 25th percentile (a below-average day worth noting)
Inputs: metric_type (required), days. Returns: object
get_sleepA
Sleep analysis for the past N days. Returns per-night breakdown with stage durations (REM, Deep/Core, Light, Awake).
Inputs: days. Returns: object
get_workoutsC
Recent workouts with type, duration, distance, and calories.
Inputs: days, limit. Returns: object
query_metricB
Time-series for any HealthKit metric type. metric_type: e.g. 'HKQuantityTypeIdentifierStepCount', 'HKQuantityTypeIdentifierHeartRate' Windows <= 30 days return raw samples; longer windows return daily aggregates (raw samples beyond 30 days are summarized and no longer stored individually).
Inputs: metric_type (required), days, limit. Returns: object
search_recordsA
Find days where a health metric crossed a threshold. For cumulative metrics (steps, calories) filters on daily total. For rate metrics (HRV, heart rate) filters on daily average.
Examples:
All days with HRV below 40ms: metric_type='HKQuantityTypeIdentifierHeartRateVariabilitySDNN', max_value=40
Days with 10k+ steps: metric_type='HKQuantityTypeIdentifierStepCount', min_value=10000
Nights under 6 hours sleep (360 min): metric_type='HKCategoryTypeIdentifierSleepAnalysis', max_value=360
Results sorted highest-to-lowest so outliers surface first.
Inputs: metric_type (required), days, min_value, max_value, +1 more. Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 11 tools
Most tools have distinct purposes like comparing periods, getting daily snapshots, and querying metrics. However, 'get_health_summary' and 'get_daily_snapshot' overlap somewhat in content, and 'get_hrv_trend' is redundant with 'query_metric' for HRV, causing minor ambiguity.
Tool names follow a consistent verb_noun pattern in snake_case, with most using 'get_' prefix. 'compare_periods', 'query_metric', and 'search_records' deviate from the 'get_' pattern but still maintain clear verb-first naming, resulting in only minor inconsistency.
11 tools is well-scoped for a health analytics server, covering diverse needs like trend analysis, comparisons, summaries, and searches without being excessive.
The tool set provides comprehensive read-only access to health metrics including trend, comparison, stats, and search. Missing write operations (create/update/delete) are acceptable for an analytics-focused server, but there is no correlation or export tool, which could be useful.
Maintenance
Related MCP Connectors
Hosted Apple Health MCP: workouts, sleep and HRV in ChatGPT, Claude, Gemini, Grok. Free, no export.
Apple Health history for Claude, ChatGPT and any MCP client: sleep, HRV, workouts, daily metrics.
Apple Health training load, recovery, HRV and workout detail for Claude, ChatGPT and any MCP client.
Your Apple Health data, food diary and lifts, for ChatGPT, Claude and Gemini. Approved on iPhone.
Related MCP Servers
- AlicenseNot gradedqualityNot gradedmaintenanceExposes read-only Apple Health data (current status, sleep details, trends) to AI via a Cloudflare-deployed MCP server and iPhone Shortcuts.MIT
- AlicenseAqualityBmaintenanceHosted MCP server that syncs health data from Apple Health, Fitbit, Oura, and Google Health Connect, enabling Claude and ChatGPT to query workouts, sleep, nutrition, and recovery in plain English with interactive charts.733MIT
- AlicenseNot gradedqualityCmaintenanceAn MCP server that securely syncs and queries your health data from Apple Health, storing it in your own Supabase Postgres database and exposing tools for AI assistants to retrieve weight, calories, macros, and more.20 npmMIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to read your own Apple Health data through a self-hosted SQLite mirror, allowing them to query health metrics, workouts, meals, and strength logs without sending data to the cloud.56 PyPI4MIT