vpetersson/apple-health-mcp-server vs MetricBridge: Rust, DuckDB and the 2.5 GB export.xml
Two open-source Apple Health MCP servers, both local, both free. One is a Rust importer that turns a multi-gigabyte export into a DuckDB database. The other reads a small JSON file your iPhone keeps current. The real tradeoff is not speed. It is who owns the parse, and how fresh the answers stay.
Quick answer. If your export.xml is a multi-gigabyte monster and you already live in a terminal on macOS or Linux, vpetersson/apple-health-mcp-server is the fastest way to get SQL over it: a Rust importer materializes the export into a local DuckDB database and serves 12 read-only tools over HTTP or stdio. If you want current numbers without owning a parse step, a Rust toolchain or a re-import, MetricBridge is the shorter path: the iOS app writes 190 metrics as JSON on a background schedule, and its zero-dependency Node server exposes 14 read-only tools. Same privacy shape, different plumbing.
What each server actually is
vpetersson/apple-health-mcp-server is a Rust project, MIT licensed, on GitHub since 22 February 2026 (6 stars, 2 forks, last push 23 September 2026). Its own tagline is "probably the fastest Apple Health MCP server". You run import once against a folder you exported by hand from the Health app, and it parses export.xml, the ECG recordings, and GPX workout routes into a local DuckDB file. Re-running import is safe: records are deduplicated by content hash. Versioning is calendar based, in the form YY.MM.MICRO.
MetricBridge (npm: health-export-mcp) is zero-dependency Node, MIT licensed, currently 1.4.3, running on Node 18 or newer. It has no database and no import step. It reads .health-cache.json, plus an hourly health-intraday.json, from a folder the iOS app writes to on a schedule: iCloud Drive, a folder you sync, your LAN, or a webhook you run. It serves 14 read-only tools and 22 prompts over stdio.
Where the Rust importer genuinely wins
Credit where it is due, because the case is real:
- Raw XML at scale. Walking a several-gigabyte XML file is exactly the job a compiled parser is for. If your pain is "the export will not finish parsing on my laptop", this is the tool built for that pain. The maintainer wrote that he built it after an exporter he depended on could not handle longer time ranges, and that his own archive was 1.2 GB compressed with 700+ activities (vpetersson.com, 22 February 2026).
- Actual SQL.
run_custom_queryaccepts read-onlySELECTandWITHstatements against the DuckDB tables. If you like writing SQL against two years of heart rate records, nothing in the JSON-tool world replaces that. - ECG and route payloads.
get_ecg_datareturns full waveform voltage samples andget_workout_routereturns GPS tracks, both pulled straight from the export folder. That is a lot of raw signal in one local database. - Two transports. Streamable HTTP is the default (
serve --port 8080, endpoint athttp://127.0.0.1:8080/mcp), or stdio for clients that spawn the process themselves.
Read the current state before you plan around binaries. The README documents Homebrew installs with an explicit tap trust step (needed since Homebrew 6.0.0) and prebuilt bundles per release. At the time of writing, the repository has no published releases or tags, so the prebuilt route is not live yet and a source build needs a Rust toolchain (1.88+, the MSRV of the rmcp SDK it uses). Check the releases page before you promise anyone a one-line install.
Here is the config shape that works today, once you have built or installed the binary:
{
"mcpServers": {
"apple-health": {
"command": "apple-health-mcp",
"args": ["serve", "--transport", "stdio"]
}
}
}
Or point a Streamable HTTP client at the running server:
{
"mcpServers": {
"apple-health": {
"type": "streamable-http",
"url": "http://127.0.0.1:8080/mcp"
}
}
}
Where zero-dependency Node wins
- Install is one line and needs nothing installed first. No Rust, no Homebrew tap trust, no Python, no Docker. One command against a published npm package with zero runtime dependencies.
- You do not own a parse. An importer is a job with a lifecycle: it needs the file, it takes the write lock, and the numbers are as old as the last time you ran it. MetricBridge parses inside the app as it reads HealthKit, so there is no import to schedule, fail, or forget.
- Freshness is checkable. One tool call tells you the most recent date in the data, the metric count, and whether the hourly window is present.
- It runs anywhere Node runs. The Rust project publishes macOS (Apple Silicon and Intel) and Linux x86_64 binaries. Node runs on Windows too, and so does the server.
The whole setup, for Claude Code:
claude mcp add health-export \
-e HEALTH_DATA_DIR="/Users/you/Library/Mobile Documents/iCloud~ai~healthexport~app/Documents" \
-- node "/Users/you/.health-export-mcp/server.mjs"
Then confirm the bridge before you trust a single number. get_mcp_status returns the resolved source, the metric count, the last data date, and which optional context files exist:
{
"ok": true,
"source": "file /Users/you/Library/Mobile Documents/iCloud~ai~healthexport~app/Documents/.health-cache.json",
"metricCount": 190,
"workoutCount": 412,
"lastDataDate": "2026-09-28",
"intraday": { "present": true, "lastWrite": "2026-09-28T06:00:11.402Z" },
"contextFiles": { "events": true, "profile": false, "sessions": true, "cycles": false, "days": true }
}
If lastDataDate is not today, your export is not running, and the agent is quietly reasoning over old data. That check exists because the failure mode is otherwise invisible.
The tool surfaces do not overlap much
The Rust server is record-centric. Its 12 tools are list_record_types, query_records, get_record_statistics, list_workouts, get_workout_details, get_workout_route, get_activity_summaries, list_ecg_readings, get_ecg_data, list_data_sources, get_import_history and run_custom_query. The mental model is tables: records, workouts, routes, ECG, plus SQL for anything else.
The MetricBridge server is day-centric, with a context layer. Its 14 tools are get_mcp_status, list_metrics, get_health_metrics, get_trends, compare_periods, get_structured_export, get_intraday, query_health_data, list_events, get_profile, get_workouts, get_sleep_sessions, get_cycle_context and correlate_metrics. The mental model is a metric per day, plus optional files for logged events, sleep sessions, cycle context and profile fields.
Differences that change answers, not just ergonomics. filterDays restricts a query to days covered by logged events (HRV on night shifts versus days off, with negate for the inverse). correlate_metrics returns a Pearson correlation with a 0 to 3 day lag and always states that it is an association, not causation. get_trends reports windowSatisfied so an agent can tell you when the file simply does not hold enough history for the window you asked about. And when a context file is missing, the tool says it is absent rather than reporting "none", which is the difference between "you did not export that" and "you have no medications".
Both keep the data local, and that is the point
Neither server needs a cloud service, an account, or an API key. Both read local files on hardware you control, both are read-only against your health data, and both keep your export out of a vendor's database. MetricBridge's server makes zero network calls of its own; the folder it reads is one you picked. If a health MCP server needs a hosted endpoint to work, it has already lost the argument.
Which one should you run?
- Pick the Rust server if you have a raw multi-gigabyte export, you are comfortable on macOS or Linux, and you want SQL and ECG waveforms in one local database.
- Pick MetricBridge if you want fresh numbers without touching the Health app's manual export again, you are on Windows, you would rather not own a build toolchain, or you want the on-device answer side of the product on the same data.
- Run both if you like SQL for archaeology and want a current, small JSON feed for the daily questions. Give them different names in your client config and both tool sets appear together.
Skip the import step entirely
190 metrics as clean JSON, refreshed on a schedule by the iOS app, read by a zero-dependency MCP server with 14 read-only tools. On-device answers with provenance. One-time $24.99, no subscription.
Frequently asked questions
Is vpetersson/apple-health-mcp-server free?
Yes. The repository is MIT licensed and the README documents no paid tier or account. You supply the Apple Health export yourself and the DuckDB database is a local file, so the only cost is the machine time to parse your export.
Does MetricBridge's Apple Health MCP server need DuckDB or Python?
No. health-export-mcp on npm is zero-dependency Node 18 or newer. It reads the .health-cache.json file the MetricBridge iOS app writes and exposes 14 read-only tools. There is no database to build, no Rust toolchain and no Python environment.
Which server is faster on a 2.5 GB export.xml?
For the one-off parse of a raw multi-gigabyte export.xml, a compiled Rust importer into DuckDB will normally beat anything that has to walk XML in a scripting runtime. But that parse happens once at import, not per question. MetricBridge sidesteps the parse: the iOS app writes JSON as it reads Apple Health, so the agent queries a small file that never needs a re-import.
Do either of these Apple Health MCP servers send my data anywhere?
Neither one requires a cloud service. Both read a local file or local database on your own machine, and neither needs an account. MetricBridge's MCP server makes zero network calls of its own, and the export folder is one you pick.
Can I run both Apple Health MCP servers at the same time?
Yes, and they do not conflict. They read different sources: one reads a DuckDB database built from your raw export, the other reads the JSON folder the iOS app keeps current. Give them different server names in your MCP client config and both tool sets appear side by side.