MetricBridge (formerly Health Export AI)
This local MCP server lets an AI agent query a MetricBridge Apple Health export over stdio, with read-only analysis plus limited event logging.
Check connection, data freshness, stale iCloud copies, and export status (
get_mcp_status,get_freshness).Discover and query 194 Apple Health metrics with units, date ranges, coverage, aggregates, rollups, and structured JSON exports (
list_metrics,get_health_metrics,get_structured_export,query_health_data,resolve_metric).Compare recent trends or arbitrary periods, including around logged events and excluding timezone-change days (
get_trends,compare_periods).Read recent hour-by-hour intraday data (
get_intraday).Analyze workouts, sleep sessions, strength logs, lab results, morning check-ins, profile context, tags, cycle context, and logged events (
get_workouts,get_sleep_sessions,get_strength_log,get_lab_results,get_check_ins,get_profile,list_tags,get_cycle_context,list_events).Correlate two metrics with a 0–3 day lag, with association-only caveats (
correlate_metrics).Log or remove agent-created context events in
health-events-agent.json, never writing to Apple Health (add_event,remove_event).Use 23 built-in prompts for daily/weekly reviews, doctor-visit prep, experiments, medication before/after, sleep, training, caregiver checks, glucose, nutrition, and coverage audits.
Run locally with zero dependencies and no network calls; supports demo mode, encrypted exports, and optional pairing.
Allows querying Apple Health data (190 metrics) through natural language, enabling AI agents to analyze personal health metrics.
Reads exported Apple Health data from a Dropbox folder, enabling access from synced files.
Provides MCP tools for Hermes AI agents to query personal health data in natural language.
Allows Home Assistant to receive Apple Health data via webhook for automations and monitoring.
Reads exported Apple Health data from iCloud Drive, providing access to synced health metrics.
Enables n8n workflows to receive Apple Health data via webhook for integration into custom automations.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@MetricBridge (formerly Health Export AI) Compare my HRV this week vs last week"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
health-export-mcp
An MCP server for your own Apple Health data. 194 metrics, 22 tools (20 read-only, plus two that log events to their own file) and 23 prompts over local stdio, with zero dependencies, no account and no server of ours in the path. Your agent gets JSON with units, date ranges and coverage, and answers from your numbers.
Docs and per-client config: healthexport.dev/mcp · agents can run the setup from SKILL.md · privacy: the server reads a local file and makes no network requests, so your health data never reaches us or a cloud AI through it.
Install: one command
claude mcp add health-export -- npx -y health-export-mcpOn a Mac that is the whole install: since 1.7.3 the server finds the MetricBridge app's iCloud Drive folder by itself. On Windows or Linux, or for an export in another folder, add --env "HEALTH_DATA_DIR=/path/to/folder" before the --. Needs Node.js 18 or later. Claude Desktop: open health-export.mcpb instead (no JSON, no Node on your PATH). Cursor, VS Code, opencode, OpenClaw, Hermes: AGENTS.md.
Then ask "Use health-export: how did my HRV this week compare with last week?" The agent calls get_trends and gets numbers that carry their own provenance: unit, exact date ranges, whether each window was complete, and how much history the export holds. This is the real output on 1.7.3 (from the built-in --demo dataset, which is synthetic so it can be published; a real export answers with the same fields and without the watermark):
[SYNTHETIC DEMO DATA] {"metric":"heart_rate_variability","unit":"ms","window":7,"recent":50.859,"prior":51.306,
"change":-0.447,"changePercent":-0.871,"direction":"flat","recentRange":{"from":"2026-08-12","to":"2026-08-18"},
"recentDays":7,"priorDays":7,"windowSatisfied":true,"coverage":{"firstDate":"2025-07-15","lastDate":"2026-08-18","days":400}}No iPhone yet? claude mcp add health-export-demo -- npx -y health-export-mcp --demo serves that watermarked synthetic dataset, so you can try every tool in 30 seconds before installing anything on a phone.
Related MCP server: Apple Health MCP Server
Use your real data (about 5 minutes the first time, needs an iPhone)
Export. Install MetricBridge on the iPhone, allow Health read access and tap Export now. It writes
.health-cache.jsonto iCloud Drive (or a folder, or your LAN). The app has a 7-day free trial, then one purchase ($24.99), no subscription. This server is MIT and free.Connect. The one command above (Claude Code), the
.mcpb(Claude Desktop), or the block for your client in AGENTS.md.Check it arrived.
npx -y health-export-mcp --doctorprints the folder it chose and why, each file's age, and freshness. iCloud can take a few minutes to bring a new export to the Mac.
What is health-export-mcp?
It's an MCP server that turns your Apple Health export into tools your AI agent can query in natural language: HRV, sleep, resting heart rate, steps, workouts, VO₂ max and 180+ more.
Who it's for: anyone who wants their AI to reason over their real health data instead of a stale CSV.
What it isn't: a cloud service. There's no developer server in the path, your data goes only where you point it.
Setup: point the server at your exported data and add it to your AI client.
Where the data comes from: the MetricBridge iOS app (formerly Health Export AI), available on the App Store, writes the export this server reads. The server still reports itself as
health-export-aiinserverInfo, and the npm package and repository names are unchanged.
Just exported and the Mac has not seen it yet? iCloud can take a few minutes to sync the file across, so a check 60 seconds after tapping Run in the app can still show the old timestamp.
get_mcp_statusreportslastDataDateand the intradaylastWrite, which is the quickest way to tell "still syncing" from "never arrived".
Try it with no iPhone needed:
npx -y health-export-mcp --demo(ornode server.mjs --demoin a clone) serves a deterministic synthetic dataset (400 days, workouts, events, sleep sessions) with every answer watermarked as synthetic. Or runnpm testto write a sample cache and exercise every tool.
Connect Apple Health to your AI agent: quickstart
1. Get your Apple Health data flowing
The companion iOS app MetricBridge exports your Apple Health data, read-only, automatic, private. For this MCP server, export to a destination it can read:
Destination | Notes |
iCloud Drive (default) | Your Mac reads the synced folder automatically |
Local folder | Any folder that syncs to your Mac (Dropbox, Google Drive, OneDrive, …) |
LAN (HTTP / WebSocket) | Direct push to the server, great over Tailscale. Carries the daily summary and, when you switch on Settings > Local network > Strength log, the strength log (written to |
Using a non-MCP tool (ChatGPT, n8n, Home Assistant)? The app can also POST to a webhook those tools read directly, see Works with.
2. Add the server to your agent
Fastest, one command (Claude Code):
claude mcp add health-export -- npx -y health-export-mcp
# Windows, Linux, or an export outside iCloud Drive:
claude mcp add health-export --env "HEALTH_DATA_DIR=/path/to/folder" -- npx -y health-export-mcpEverything before -- is an option for claude mcp add, everything after it is the command that starts the server. On a Mac the server reads the app's iCloud Drive folder when HEALTH_DATA_DIR is unset (1.7.3+); if you set it to that folder by hand, double-quote it (the folder name contains a space) and use $HOME, because a quoted ~ is not expanded by the shell. Add --scope user to make the server available in every project (the default scope is the current project only). Check it with claude mcp list.
Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json), and any client that takes an mcpServers block:
{
"mcpServers": {
"health-export": {
"command": "npx",
"args": ["-y", "health-export-mcp"]
}
}
}Off a Mac, or for another folder, add
"env": { "HEALTH_DATA_DIR": "/path/to/folder" }to the entry (a leading~is expanded by the server).npx -yfetches the published package at run time without an install prompt (npm verifies package integrity), so it runs anywhere with no clone or absolute path. The server expands a leading~inHEALTH_DATA_DIRitself, so the JSON form can keep it. Prefer to pin a vetted local checkout instead? Use"command": "node", "args": ["REPLACE_WITH_ABSOLUTE_PATH/server.mjs"], get the path withnode -e "console.log(process.cwd()+'/server.mjs')"inside the repo. Or skip JSON entirely, draghealth-export.mcpbinto Claude Desktop → Settings → Extensions.
Auto-configure from a clone:
git clone https://github.com/PhilipAD/health-export-mcp.git
cd health-export-mcp
node apply-mcp-config.mjs # detects installed clients and writes the config for youCursor / VS Code: node gen-deeplinks.mjs prints one-click install links.
opencode / OpenClaw / Hermes: see AGENTS.md for the exact block, same shape, one per client.
3. Ask your agent
Restart the client and try:
"Use health-export: what's my average HRV this week vs last week?"
Works with Claude, Cursor, ChatGPT, OpenClaw, Hermes
Client / Agent | Integration | How |
Claude Code | Native MCP |
|
Claude Desktop | Native MCP |
|
Cursor | Native MCP | One-click deeplink, or |
opencode | Native MCP |
|
OpenClaw | Native MCP | Add the server block to your MCP config |
Hermes | Native MCP | Add the server block to your agent's MCP config |
VS Code (Copilot / Continue) | Native MCP | One-click deeplink |
ChatGPT · Gemini · Grok | Webhook* | Consume the app's webhook export |
n8n · Home Assistant | Webhook* | Trigger automations on the exported JSON |
*MCP clients query this server directly over stdio. ChatGPT / Gemini / Grok / n8n / Home Assistant don't speak MCP, they consume the same Apple Health data via the iOS app's token-authenticated webhook export.
The 22 MCP tools
Full reference with request/response examples: healthexport.dev/mcp.
Tool | What it does |
| Health check: source, metric/workout counts, which context files exist, latest data date, and a |
| How current the export is: |
| Every available metric with unit, day count, and date range. |
| Daily values for a metric (or all) over a date range + an aggregate (avg/sum/min/max/latest). Supports |
| Recent N-day window vs the prior N days: change, % change, direction. Supports |
| A metric across two arbitrary date periods (A vs B), or around a logged event via |
| Clean JSON for chosen metrics/range, paginated with a cursor. |
| The hour-by-hour window from the app's hourly automations ( |
| Natural-language convenience: "average HRV last month" routed to structured results. |
| Logged context events (medication, habit, visit, life, shift, episode, travel) with type/tag/date filters, from the app and from |
| Log an event the user states (a medication start, a trip) into |
| Remove an event logged with |
| Opted-in context fields (conditions, medications, goals, allergies, notes) plus |
| Workouts by activity/date with pagination; includes heart rate, running dynamics, cycling power, intervals and |
| Clustered sleep sessions attributed to the waking day; split nights returned as separate sessions, duplicate copies of one night collapsed ( |
| The strength log typed into the iOS app ( |
| Lab results from Apple Health Records ( |
| Morning check-ins the user chose to share ( |
| Day-in-cycle and coarse phase derived from logged period starts. Never predictive. |
| Tags logged in the app ( |
| Pearson correlation between two metrics with a 0..3 day lag. Association, not causation, always stated. |
| Canonical name and unit for any spelling ( |
Coverage: 194 Apple Health metrics across activity, heart, HRV, mobility, respiratory, body, sleep, hearing, and nutrition, plus workouts. Data answers carry honest coverage blocks, and single-metric answers list logged events inside the window as segmentBoundaries so an average across a medication start or life change cannot masquerade as one regime.
Data files
The daily cache (.health-cache.json) and workouts cache are joined by optional context files, all optional: health-events.json (the logging surface), health-profile.json (opt-in context), health-sessions.json (sleep sessions), health-cycles.json (observed cycle starts), health-days.json (timezone change log), health-readings.json (individual blood pressure readings), health-check-ins.json (morning check-ins, opt-in), health-strength.json (the strength log, when its export is switched on, or pushed by the app over LAN when Local network > Strength log is on), health-labs.json (lab results from Apple Health Records, opt-in, folder export only, never iCloud Drive), health-tags.json (logged tags with supplements marked, opt-in in the app) and .health-manifest.json (what the phone last wrote, for the stale-copy check). The server reads all of them and writes only health-events-agent.json (events logged with add_event). An absent file is reported as available:false with a note, never as "no data". Formats: docs/SCHEMA-CONTRACTS.md.
Two patterns worth knowing: query a parent's Apple Health from your own AI (consent-first, the parent's phone is the authority) and logging data into Apple Health via Shortcuts (the server never writes to Apple Health).
MCP prompts
The server ships 23 prompts over prompts/list / prompts/get: daily brief, weekly review, doctor visit prep, what changed since my last visit, sleep quality and regularity, HRV trend, training and race week reviews, zone minutes, n-of-1 experiment, medication before/after, GLP-1 dose-step compare, sobriety milestone, shift block compare, travel-honest monthly review, cycle-aware trend read, caregiver check-in, glucose day summary, long-term activity narrative, data coverage audit, a nutrition week summary, and a profile-aware context bootstrap. Every prompt leads with coverage honesty and never turns data into diagnosis.
Demo mode
node server.mjs --demo (or HEALTH_DEMO=1) serves a deterministic synthetic dataset: ~15 metrics over 400 days, 30 workouts with intervals, 8 events, a profile, 60 sleep sessions, cycle starts, a timezone change and 30 days of tags, anchored to a fixed date with a seeded PRNG. Every answer carries demo: true and a [SYNTHETIC DEMO DATA] text prefix, so demo output can never pass for a real export.
CLI
node server.mjs --doctor # diagnostics: files, sizes, schema, freshness, pairing
node server.mjs status --max-age 24 # exit 0 if data is fresh, 1 if stale or a stale iCloud copy (cron gate)
node server.mjs receive # standalone LAN receiver (binds 127.0.0.1 by default)
node server.mjs --helpExample AI queries
"What has my resting heart rate done over the last 30 days?"
"Compare my deep sleep this week vs last week."
"Is my VO₂ max trending up or down this quarter?"
"Give me a clean JSON export of HRV, RHR and sleep for the last 14 days."
"Correlate my step count with my sleep duration this month."Use cases
AI health coach: let an agent reason over your real HRV, resting heart rate, and sleep to suggest when to push and when to recover, grounded in your actual numbers instead of generic advice.
Training-load analysis: pull workouts, VO₂ max, and heart-rate trends so your agent can flag overreaching, plot fitness progression, and pace a training block.
Sleep correlations: have your agent correlate deep-sleep duration against steps, caffeine, late workouts, or screen time to see what tends to go with your better nights (associations, not causes).
Quantified-self dashboards: feed clean JSON for any metric set and date range straight into a notebook, spreadsheet, or LLM-built dashboard for your own self-tracking.
Personal research & experiments: run n-of-1 experiments (supplement, routine, or protocol changes) and let an agent compare before/after periods across 194 metrics to see what changed.
Building a wellness or coaching product?
This server reads a file the MetricBridge iOS app writes on the user's own device, with no central health-data processor in the path. The same app can POST signed JSON (Standard Webhooks v1, published JSON Schemas) straight to your endpoint, so your users bring their own Apple Health to your product without an SDK in your app and without any vendor server holding the data. The offer, flat pricing and boundaries: healthexport.dev/platforms. The integration guide and the zero-dependency conformance kit: healthexport.dev/docs/platforms. Wellness-scoped and end-user-directed; no clinical or HIPAA work.
The app that feeds it
Privacy & security
The one non-health thing the server reads from the export is _meta.license, a marker the app writes
with exactly two shapes: {"state": "trial", "daysRemaining": N} or {"state": "unlocked"}. A fresh
export from a trial adds one sentence to get_freshness naming when the trial ends; an unlocked export
adds nothing. No network call is involved and nothing is sent anywhere.
Reads your export, never Apple Health. The server never touches HealthKit. Its only write is
health-events-agent.json, the events an agent logs withadd_event/remove_event; it never modifies a file the app writes. Both tools are refused in demo mode and on a read-only folder, and the file is sealed like the rest of an encrypted export.Local-first. It runs on your machine over stdio. There is no developer server in the path.
Optional pairing. Set
PAIRING_SECRETto the code the iOS app shows (Settings → Agent pairing) to gate access.Encrypted exports (optional). If you switch on Export encryption in the iOS app, set
HEALTH_EXPORT_PASSPHRASE(orHEALTH_EXPORT_PASSPHRASE_FILE) to the same passphrase and the server decrypts on your machine. See Encrypted exports.No telemetry. The server makes no network requests.
get_mcp_statusalways includes one static link to the companion app's website (and its App Store page), so the bridge is attributable back to install. Both links carry fixed campaign labels (utm_source=mcp,utm_medium=agent-tool,utm_campaign=mcp-unlock;ct=mcpserver,ppid=…) that are identical for every user and contain nothing about you or your data; they only count if you choose to open one.Auditable. Zero runtime dependencies, about 4,000 lines of plain ES modules and no build step: what's on npm is what's in this repo, and npm provenance links each tarball to the commit and workflow that built it.
Signed releases. Hosted artifacts are minisign-signed and checksummed, see Verifying releases.
Encrypted exports
The iOS app can encrypt what it exports with a passphrase you choose (Settings > Export encryption, switched on per destination: iCloud Drive, a folder, your server, the local network). Give the server the same passphrase and every file is decrypted transparently, in memory, on this machine:
HEALTH_EXPORT_PASSPHRASE='your passphrase' HEALTH_DATA_DIR=... node server.mjs
# or keep it out of your shell history and process list:
HEALTH_EXPORT_PASSPHRASE_FILE=~/.config/metricbridge/passphrase HEALTH_DATA_DIR=... node server.mjsIf both are set, HEALTH_EXPORT_PASSPHRASE wins; one trailing newline in the file is ignored and an
empty value counts as unset. The .mcpb bundle has an optional Export passphrase field. Plaintext
exports keep working either way. A missing or wrong passphrase, or a file that fails its integrity
check, is reported as such (get_mcp_status says encrypted: true plus the reason, --doctor prints
an encryption: line), never as "no data". The passphrase is never logged or echoed.
LAN receiver and the strength log. POST /health-cache (and the single text frame of /health-cache-ws) takes two bodies. The daily summary is merged into .health-cache.json as before. A document with "type": "strength.log" (the app's opt-in, off-by-default Local network > Strength log switch; same body as the webhook's strength.log, see docs/SCHEMA-CONTRACTS.md sections 10 and 14) is validated, rebuilt from an allowlist and written whole to health-strength.json in HEALTH_DATA_DIR, which get_strength_log already reads. It is a replace, never a merge, so a session deleted in the app disappears on the next push. The reply is 200 {"ok":true,"type":"strength.log","sessions":N}, or 400 for a document with no data.strengthLog.sessions. Tokens, the Host/Origin checks, HEALTH_EXPORT_PASSPHRASE and HEALTH_REQUIRE_ENCRYPTED=1 apply exactly as for the summary. No new tool and no new endpoint; an older receiver ignores the document.
The LAN receiver (HEALTH_LISTEN=1) decrypts encrypted pushes with the same variable and answers
422 with a machine reason (passphrase_missing, passphrase_mismatch, tampered, malformed,
unsupported_version, passphrase_file_unreadable, encryption_required). The pairing token rides
in a plain http header on that leg, so set HEALTH_REQUIRE_ENCRYPTED=1 to refuse plaintext pushes
once the app encrypts that leg. To decrypt one body yourself (a webhook you run, a script):
HEALTH_EXPORT_PASSPHRASE='...' node envelope.mjs open body.json # prints the decrypted JSONFormat (metricbridge.enc v1, envelope.mjs, node:crypto only): a JSON object
{format, v, alg: "A256GCM", kdf: "PBKDF2-HMAC-SHA256", iter, salt, kcv, nonce, ct}.
master = PBKDF2-HMAC-SHA256(NFC(passphrase), salt[16], iter) (600,000 iterations, the OWASP
figure); an AES-256-GCM key and an 8-byte key-check value come off it with HKDF-SHA256; each message
gets a random 12-byte nonce; every header field is bound in as AAD as the string
metricbridge.enc|v=1|alg=A256GCM|kdf=PBKDF2-HMAC-SHA256|iter=<n>|salt=<b64>|kcv=<b64>|nonce=<b64>.
Base64 is canonical with padding. test/fixtures/ holds envelopes made by the iOS app's Swift encoder
and by this module; envelope.test.mjs proves both open and regenerate byte for byte.
What it protects: the export at rest in the destination (iCloud Drive, a synced folder, a webhook host's storage) and the LAN push on the wire, plus integrity of every sealed file. What it does not: an unlocked phone, this computer (it holds the passphrase), metadata (sizes, timing), rollback or replay of a whole sealed file, files you share by hand from the app, or a weak passphrase (anyone with the files can guess offline; the app requires at least 8 characters).
Verifying releases
The server artifacts hosted at healthexport.dev/mcp/ (used by the setup skill) are minisign-signed, and every download is SHA-256 checksummed. The signing public key is published in this repo (minisign.pub) and in SKILL.md, pin it from here, not only from the website, so a compromise of the website alone cannot swap both an artifact and its key.
PUBKEY='RWS6TxVWSKUblYkx7Db6ZpmvHALwHpznZpjaED/FlZj+PpxSlel0MxHZ' # = minisign.pub in this repo
curl -fsSL https://healthexport.dev/mcp/SHA256SUMS -o SHA256SUMS
curl -fsSL https://healthexport.dev/mcp/SHA256SUMS.minisig -o SHA256SUMS.minisig
minisign -Vm SHA256SUMS -P "$PUBKEY" || { echo "signature INVALID, do not run"; exit 1; } # fail closed
curl -fsSL https://healthexport.dev/mcp/server.mjs -o server.mjs
shasum -a 256 --ignore-missing -c SHA256SUMS || { echo "checksum mismatch, do not run"; exit 1; }The pinned, checksum-verified download above is the locked-down path. npx -y health-export-mcp instead resolves the latest version published to npm at run time (npm provides its own package integrity). GitHub releases carry a SHA256SUMS file and a CycloneDX SBOM (*.cdx.json); a SHA256SUMS.minisig made with the same key is attached once the maintainer has signed that release locally, so check for it before relying on it. Full policy, including npm provenance and how to report a vulnerability privately: SECURITY.md. Security overview: https://healthexport.dev/security.
Requirements
Node.js ≥ 18 (
node -v).A folder containing a
.health-cache.jsonexported by MetricBridge, or runnpm testto generate a sample one.An MCP-compatible client (Claude Desktop, Cursor, opencode, OpenClaw, Hermes, VS Code), or any tool that can read the webhook export.
FAQ
How do I get my Apple Health data into Claude / ChatGPT / my AI agent?
Install health-export-mcp, export your Apple Health data with the MetricBridge iOS app (to iCloud, a folder, or your LAN), then add the MCP server to your AI client. Your agent can then query your Apple Health metrics in natural language. (Non-MCP tools like ChatGPT read the app's webhook export instead.)
Is my health data sent to a server? Not to us. The MCP server runs locally and reads only the export files or endpoints you configure, there's no developer server in the path. Where your iOS export is delivered (iCloud, your LAN, a webhook) is entirely your choice.
Which agents are supported? Any MCP client, Claude Desktop, Cursor, opencode, OpenClaw, Hermes, VS Code, natively. ChatGPT, Gemini, Grok, n8n, and Home Assistant consume the same data via webhook.
Do I need the iOS app?
For your own data, yes: the server reads the file the MetricBridge app writes and never talks to HealthKit itself. To evaluate first, --demo serves a watermarked synthetic dataset with no app and no iPhone.
Troubleshooting
"No metrics found": run
npx -y health-export-mcp --doctor. It prints the folder the server chose and why (HEALTH_DATA_DIR, the iCloud default on a Mac, or the current directory) and whether.health-cache.jsonis there. Confirm the app has exported at least once;get_mcp_statusshows the same source and the latest date.Server not visible in the client: run
npx -y health-export-mcp --doctorin a terminal. If it works there, the client can't findnpx(common for Claude Desktop with Node from nvm or Homebrew): use the.mcpb, or set"command"to the full path fromwhich npx. Then fully quit and reopen the client.Exports stopped after a week: the app's free trial ended.
get_freshnessreports the export as stale and links the unlock page; once the app is unlocked, exports resume with no change on this side.Answers miss last night, or
state: "stale_copy": this Mac's iCloud Drive has an older copy of the export than the phone wrote. Wait a few minutes, or open the MetricBridge folder in iCloud Drive in Finder and choose Download Now on the file (Remove Download drops only this Mac's local copy, and it downloads again).--doctorlists each stale copy.Locked data error: the export file is protected until first unlock after reboot; unlock your device once.
Run it locally
# Node ≥18, no install needed
HEALTH_DATA_DIR=~/Library/Mobile\ Documents/iCloud~ai~healthexport~app/Documents node server.mjs
# integration test, writes a 14-day sample cache and exercises every tool
npm teststdio transport (newline-delimited JSON-RPC 2.0), the universal MCP transport. Optionally set HEALTH_LISTEN=1 to also accept LAN pushes from the iOS app in the same process (see receiver.mjs).
How it works
The iOS app reads Apple Health (read-only) and writes a compact .health-cache.json (plus optional context files) to the destination you choose. This server reads those files and exposes the 22 tools above over MCP. No bridge, no Docker, no database: just files and stdio.
Apple Health → MetricBridge (iOS) → .health-cache.json → health-export-mcp → MCP client → youRelated projects
Other Apple Health MCP servers in the ecosystem, worth a look depending on your setup:
neiltron/apple-health-mcp: an MCP server that runs SQL-style queries over an Apple Health export.
the-momentum/apple-health-mcp-server: an MCP server for analyzing Apple Health data exported from the Health app.
HealthyApps/health-auto-export-mcp-server: an MCP server for the Health Auto Export app's data.
How health-export-mcp differs: the iPhone app keeps the export current in the background, so there's no manual export.xml and no re-import; the server has zero dependencies and needs no Docker, database or Mac app; every answer carries its units, date range and coverage; and it never writes to Apple Health: its only write is the events file your agent logs to.
Related
Companion iOS app: MetricBridge, exports 194 Apple Health metrics to your agent, automatically.
Model Context Protocol: modelcontextprotocol.io
Per-agent setup: AGENTS.md
If this helps your setup, a ⭐ makes it easier for others to find.
License
MIT, see LICENSE. Contributions welcome.
Available Tools
22 toolsadd_eventAIdempotent
Log a context event the user tells you about (a medication start, a habit change, a visit, a trip) so later analysis can segment around it. Writes ONLY health-events-agent.json in the export folder, never Apple Health and never a file the iOS app writes; the app shows these events read-only. Returns the stored event with its id (starting "agent-"). Idempotent: repeating a call with the same clientEventId (or, without one, the same date, type, title and endDate) returns the event already logged instead of adding a duplicate. Ask the user before logging anything they did not state. Refused in demo mode, when the folder is not writable, and on an encrypted export without HEALTH_EXPORT_PASSPHRASE (the file is sealed like the rest of the export when the passphrase is set).
Inputs: date (required), type (required), title (required), note, +3 more.
compare_periodsARead-onlyIdempotent
Compare a metric between two arbitrary date periods (A vs B): each aggregate plus the change and percent change. Pass periodA/periodB explicitly, or pass anchor {eventId, days} to build both periods around a logged event (the before/after question, with the event day excluded from both sides).
Inputs: metric (required), anchor, periodA, periodB, +1 more.
correlate_metricsARead-onlyIdempotent
Pearson correlation between two metrics' daily values: {metricA, metricB, lag (0 to 3, default 0), start, end}. lag pairs metricA on day d with metricB on day d+lag, so lag 1 compares against the FOLLOWING day. Returns alignedPairs, r (withheld below 10 aligned pairs), and both means. The answer always carries the association-not-causation caveat; treat every r as alignment in this file, not mechanism.
Inputs: metricA (required), metricB (required), end, lag, +1 more.
get_check_insARead-onlyIdempotent
Morning check-ins the user chose to share (health-check-ins.json): per day a self-rated feeling (1 rough to 5 great), an optional note, an intention for the day and whether it happened (answered the next morning). Optional {start, end} filters. Returns the rows plus a summary (rated, averageFeeling, intentionsAnswered, intentionsMet). Opt-in in the iOS app and off by default, so available:false is the usual answer and means not shared, never "felt nothing". A self-rating is the person's own words, not a measurement.
Inputs: end, start.
get_cycle_contextARead-onlyIdempotent
Cycle context derived from user-logged period starts (health-cycles.json): day-in-cycle and a coarse phase label (follicular from day 1 to the observed midpoint of that cycle, luteal for the remainder). Derived from logged periods only, never predictive, and no ovulation estimate is made. Optional {date} for a single day; default returns per-day context for the last cycle.
Inputs: date.
get_freshnessARead-onlyIdempotent
How current the exported files are, so you can decide whether to trust what you read or ask the user to refresh. stale is judged on the core files (the daily cache and health-sessions.json), never on the hourly intraday file: true when their newest write is older than stale_after_hours (default 26, or HEALTH_STALE_AFTER_HOURS, or your maxAgeHours), or when this machine holds a stale copy. state is fresh, stale, stale_copy, no_data, locked or demo. stale_copy means the phone's manifest (.health-manifest.json) or iCloud says the phone wrote a newer version of a core file than the one on this machine (stale_copy lists each file with localWrittenAt, phoneWrittenAt and reason); relay the recommendation, which says how to make iCloud Drive download it. Also returns age_hours, as_of (when the phone wrote the daily cache), last_data_date, data_lag_days, per-file write times with each file's match against the manifest, stale_files, manifest_behind (the manifest lags the data, not stale), sessions_last_night (present or absent; without a manifest, advisory only, people skip nights) and, on a Mac, an advisory icloud status per core file. Same rule as the status --max-age cron gate. When no export is found, recommendation also names the companion iOS app's App Store page and website (the same links as app.appStoreUrl and app.unlockUrl); when the newest readable export is older than the server's own window (not a stricter maxAgeHours), it names the website. Both carry fixed campaign labels. Cheap: call it again before a long analysis.
Inputs: maxAgeHours.
get_health_metricsARead-onlyIdempotent
Get values for a metric (or all metrics) over an optional date range, with an aggregate (avg/sum/min/max/latest). The core data-retrieval tool. Every result carries a coverage block giving the metric's real firstDate/lastDate/days: check it before trusting a long window, and note that aggregate is always computed over the full range even when points are rolled up. Single-metric answers also list any logged point events inside the window as segmentBoundaries. For blood_pressure_systolic or blood_pressure_diastolic at day granularity, the individual readings behind the daily mean are attached as readings [{t, systolic, diastolic, source, manual}] when the app exported them (health-readings.json), the most recent 150 in range, with readingsOmitted counting the rest. A day the app rebuilt after samples arrived late (or were deleted) carries recomputed_at, and a metric with rebuilt or possibly stale past days carries a recompute block (backfilled_days, recomputed_at, capped); untouched days carry neither.
Inputs: end, limit, start, metric, +3 more.
get_intradayARead-onlyIdempotent
The current hour-by-hour window from the iOS app's HOURLY automations (health-intraday.json, app 1.4+): each metric's hourly points (t, v = the hour's value) plus its latest value. The window covers a 48-hour span, yesterday 00:00 local to now, and the file is REPLACED on every run, so this is a live recent view, not history; use get_health_metrics for day-level questions. Discrete metrics (heart rate, SpO2, respiratory rate, HRV, resting heart rate) carry the hour's Min and Max beside v (the hour's mean) when the app version writes them; older exports cover today only and have no Min/Max. Returns available:false with setup guidance when no hourly automation has delivered yet.
Inputs: metric.
get_lab_resultsARead-onlyIdempotent
The user's lab results from Apple Health Records (health-labs.json), exported only when they switched on Lab results > Include in exports in the MetricBridge iOS app, and only into a picked folder (never iCloud Drive). Off by default. Each test (LOINC code when the lab sent one, name, unit) lists its results oldest first, with the value, date, institution and the lab's own reference range, all exactly as reported. Nothing is flagged, graded or interpreted: do not call a result normal or abnormal, and never convert between units (a test in two units is listed once per unit, splitByUnit: true). Filters: {test (case-insensitive part of the name, e.g. ldl), loinc (exact code, e.g. 13457-7), start, end (local days, inclusive)}. Above 400 matching results the newest 400 are returned with resultsOmitted. No match returns count 0 with knownTests. When the file is missing the answer is available:false, which means the user has not exported lab results, not that they have none.
Inputs: end, test, loinc, start.
get_mcp_statusARead-onlyIdempotent
Health check: data source, how many metrics/workouts are available, which optional context files exist, the most recent data date, a compact freshness block (its state is stale_copy, with a stale_copy list, when this machine holds an older copy of the export than the phone last wrote), and a recompute summary of past days rebuilt after late-arriving samples (with any days too old to rebuild; supported: null when the cache cannot tell). Call this first to confirm the bridge is connected. The result also includes app.unlockUrl and app.appStoreUrl, the website and App Store page of the companion iOS app that writes the export; both links carry fixed campaign attribution labels.
Inputs: no named parameters.
get_profileARead-onlyIdempotent
The context fields the user explicitly opted in to sharing (health-profile.json): conditions, medications, goals, allergies, notes. Returns the fields plus a presentFields list. An absent field was withheld by the user or never enabled; absence must never be read as "none".
Inputs: no named parameters.
get_sleep_sessionsARead-onlyIdempotent
Clustered sleep sessions from health-sessions.json with {start, end, day} filters. Sessions are attributed to the WAKING day, matching the daily cache's sleep_analysis, so the two surfaces never disagree; a split night appears as separate sessions with the same day. Duplicate copies of one night left by older app versions (sharing an end time with, or lying inside, a longer session on the same day) are collapsed to the longer one and counted in collapsedDuplicates. A session with manualInBed:true was built from an In Bed entry typed into the Health app with no asleep sample. Timestamps carry the local UTC offset at the time of the sample.
Inputs: day, end, start.
get_strength_logARead-onlyIdempotent
The strength log the user keeps in the MetricBridge app (health-strength.json, opt-in, off by default): each session with its exercises and every set as entered (reps, kg, warmup or working, RPE), plus volumeKg, workingSets and an Epley bestEstimateKg per exercise, all-time records per exercise, and the routines the user saved (their own templates). Filters: {exercise (exact name, case-insensitive, e.g. "Back squat"), start, end (local days, inclusive)}. Sessions come oldest first; above 150 matches the most recent 150 are listed and sessionsOmitted counts the rest. An unknown exercise returns count 0 with knownExercises. Logged by hand, never read from Apple Health; when the file is missing the answer is available:false, which means the user has not switched the export on, not that they do not train.
Inputs: end, start, exercise.
get_structured_exportARead-onlyIdempotent
Return clean structured JSON for the chosen metrics/date range. Paginated: the result carries nextCursor when more metrics remain; pass it back as cursor for the next page. Prefer naming the metrics you need and a date range; calling it bare over a full history is a lot of data.
Inputs: end, limit, start, cursor, +2 more.
get_trendsARead-onlyIdempotent
Compare the most recent N-day window against the prior N days for a metric: change, percent change and direction (up/down/flat). Also returns daysAvailable and windowSatisfied: if windowSatisfied is false the file does not hold enough history for the window you asked for, and the comparison is over less data than requested. Logged point events inside the compared span are listed as segmentBoundaries.
Inputs: metric (required), window, excludeTravelDays.
get_workoutsARead-onlyIdempotent
Query the workouts cache with {activityType (a name like Running, or a raw HealthKit id like 37), start, end} filters and pagination ({limit} default 50 max 200, {cursor} from a previous nextCursor). Records are returned exactly as stored, including the newer optional keys (avgHeartRate, maxHeartRate, running dynamics, cycling power, intervals, hasRoute) when the app exported them; older caches simply lack those keys and nothing is fabricated. Also returns summary {count, byActivityType} over everything that matched.
Inputs: end, limit, start, cursor, +1 more.
list_eventsARead-onlyIdempotent
Logged context events: medication starts, habit changes, doctor visits, life events, shift blocks, episodes, travel, and any type a newer app adds. Merges the app's health-events.json with health-events-agent.json (events logged with add_event); every event carries source "app" or "agent". Optional {type, tag, start, end} filters; a range event matches a window it overlaps. Sorted ascending by date. Returns available:false when neither file exists; absence means nothing was logged or exported, not that nothing happened.
Inputs: end, tag, type, start.
list_metricsARead-onlyIdempotent
List every available Apple Health metric with its unit, day count, and date range. Use this to discover metric names before querying.
Inputs: no named parameters.
list_tagsARead-onlyIdempotent
Tags the user logged in the iOS app (health-tags.json): lifestyle tags such as Alcohol or Sauna, and supplements such as Creatine or Magnesium, by name only and never an amount. Optional {kind: all | supplement | lifestyle, start, end}. Returns one row per tag (kind, days logged, firstDay, lastDay, most logged first) and the tag names per day. Tag export is opt-in in the app and off by default, so available:false is the usual answer and means the file was not exported, not that nothing was tagged. Treat any difference after tagged days as an association in this person's own data, never as evidence that a supplement works.
Inputs: end, kind, start.
query_health_dataARead-onlyIdempotent
Natural-language convenience: pass a question and get routed structured results. Prefer the specific tools above when you can, and call list_metrics first to see how much history exists, since this tool answers over whatever the file holds.
Inputs: question (required).
remove_eventADestructiveIdempotent
Remove an event that was logged with add_event (id starting "agent-", source "agent" in list_events) from health-events-agent.json. Events logged in the iOS app are refused: they are edited in the app. Removing an id that is already gone changes nothing and says so. Refused in demo mode and when the folder is not writable.
Inputs: id (required).
resolve_metricARead-onlyIdempotent
Look a metric name up in the canonical naming and unit table. Accepts the export's own name (step_count), a HealthKit identifier (StepCount, HKQuantityTypeIdentifierStepCount), a display title (Step Count) or a common alias (steps, hrv, spo2, weight) and returns the canonical metric, its unit (percentages are 0 to 1 fractions), the unit variants other apps use with the exact formula into the canonical unit, and whether this export holds it. Pass {value, unit} to convert one value, e.g. 180 lb to kg. A word naming several metrics (distance, calories) comes back ambiguous with candidates, and a different measure (RMSSD vs the SDNN that Apple Health stores) is refused with the reason: nothing is guessed. get_health_metrics, get_trends, compare_periods, get_structured_export and correlate_metrics accept the same aliases and report resolvedFrom when they used one; other tools take the export's own names. The export holds one value per metric per day, merged across sources by Apple Health, so this tool states honestly whether per-source disagreement can be seen in the file (exports that record per-source attribution carry src/by and a sources block; others report it as not available) rather than inventing it.
Inputs: name (required), unit, value.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 22 tools
Several clusters overlap: get_health_metrics, get_structured_export, and query_health_data all retrieve metric values, and get_trends, compare_periods, and correlate_metrics all do comparison/analysis. The very detailed descriptions do spell out when to prefer each (e.g. intraday vs day-level, NL vs specific tools), so an agent can mostly recover, but the boundaries are not self-evident from names alone.
Nearly every tool follows a clean verb_noun pattern: get_workouts, list_metrics, correlate_metrics, add_event, remove_event, compare_periods, resolve_metric, query_health_data. The verb choices (get/list/query) are used consistently and predictably across the whole set.
22 tools is on the heavy side, but each maps to a distinct data surface (workouts, sleep, cycle, check-ins, strength, labs, tags, intraday, events, profile, plus status/freshness). The count is justified by the breadth of exported files rather than redundant tooling.
The surface covers discovery (list_metrics, resolve_metric), retrieval across day/hour/structured views, analysis (trends, compare, correlate), freshness/status, and event lifecycle (list/add/remove). Minor gaps: no update_event (must remove and re-add) and writes are limited to agent events by design, but core workflows have no dead ends.
Maintenance
Related MCP Connectors
Hosted Apple Health MCP: workouts, sleep and HRV in ChatGPT, Claude, Gemini, Grok. Free, no export.
Hosted MCP server exposing US hospital procedure cost data to AI assistants
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.
Related MCP Servers
- AlicenseAqualityAmaintenanceAn MCP server that allows users to query and analyze their Apple Health data using SQL and natural language, utilizing DuckDB for fast and efficient health data analysis.391 npm574MIT
- AlicenseAqualityDmaintenanceA Model Context Protocol server that enables seamless interaction between LLM-based agents and Apple Health data, allowing users to query, analyze, and manage health records through natural language commands.7286MIT
- AlicenseAqualityAmaintenanceRead-only MCP server that exposes Apple Health data (steps, workouts, sleep, etc.) from a local SQLite store, allowing AI agents to query health metrics without sending data to hosted services.932Apache 2.0
- AlicenseNot gradedqualityNot gradedmaintenanceAn MCP server that gives AI read-only access to Apple Health data through shortcuts and Cloudflare, enabling queries of current stats, detailed records, and trends.MIT