Probably the most complete Apple Health MCP server
Project description
apple-health-mcp-server
Make Claude your personal health AI — locally.
apple-health-mcp-server exposes the contents of your Apple Health export
(export.xml plus the ECG CSV and GPX route files Apple ships alongside it)
to any Model Context Protocol client —
including Claude Desktop — through 17 read-oriented tools backed by a local
DuckDB database.
Features
- Comprehensive ingestion. Imports
Record,Workout(withWorkoutEvent,WorkoutStatistics,WorkoutRoute, andWorkoutMetadataEntry),ActivitySummary,Correlation,Me,ExportDate, ECG voltage samples, and GPX route points. Categorical state-of-mind entries (iOS 17+) land in a dedicated table. - All data stays local — no external transmission. The importer reads files from disk, the server speaks MCP over stdio (HTTP is opt-in), and the only network artefact is whatever the client itself decides to send.
- DuckDB-backed. Re-imports are idempotent thanks to deterministic
deduplication; ad-hoc analysis through
run_custom_queryruns at native DuckDB speed. - Time-zone aware. GPX route timestamps are aligned to each parent workout's local offset so joins against XML-derived rows are clean.
- Cross-platform. Tested on Ubuntu, macOS, and Windows against Python 3.12 / 3.13 / 3.14.
- One-click Claude Desktop install. Drag-and-drop the MCPB bundle attached to every GitHub Release; see the Installation → Claude Desktop (MCPB bundle) section below.
- 100% branch-tested. Every release gates on full coverage with
pytest --cov-branch --cov-fail-under=100.
Installation
The recommended entry point is uvx, which fetches a one-shot virtualenv on demand and never pollutes the system Python:
uvx apple-health-mcp-server --help
Claude Desktop (one-click via MCPB bundle)
The easiest path on Claude Desktop is the MCPB bundle attached to each GitHub Release.
Prerequisite: the bundle wraps
uvx apple-health-mcp-server serve, so installuvfirst (brew install uvon macOS, the official installer on Windows). WithoutuvonPATHClaude Desktop will fail with a generic spawn error after install.
Then:
- Download the latest
apple-health-mcp-server-vX.Y.Z.mcpbfrom the release assets - Open Claude Desktop's Settings → Connectors panel
- Drag-and-drop the
.mcpbfile onto the panel — Claude Desktop will install it and prompt to enable the server
The MCPB format is documented at https://github.com/anthropics/mcpb;
both .dxt (legacy) and .mcpb extensions are accepted by Claude
Desktop.
Claude Desktop (manual JSON config)
If you prefer to wire the server up by hand, edit
claude_desktop_config.json:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux: Claude Desktop is not yet released on Linux; use Claude Code below instead.
{
"mcpServers": {
"apple-health": {
"command": "uvx",
"args": ["apple-health-mcp-server", "serve"]
}
}
}
Then fully quit Claude Desktop and reopen — the config is only re-read at startup (closing the window is not enough).
Source: https://modelcontextprotocol.io/quickstart/user (fetched 2026-06-22).
Claude Code
Easiest path is the CLI helper, which writes the entry into the right scope and survives future schema tweaks:
claude mcp add --transport stdio --scope user apple-health -- uvx apple-health-mcp-server serve
--scope userregisters the server for every project (writes to~/.claude.json). Use--scope projectto share via a version-controlled.mcp.jsonat the repo root, or--scope local(the default) for the current project only.- The
--separator is mandatory when the server command takes its own arguments — without it Claude Code would try to parseserveas one of its own flags.
Equivalent manual entry inside the chosen JSON file:
{
"mcpServers": {
"apple-health": {
"type": "stdio",
"command": "uvx",
"args": ["apple-health-mcp-server", "serve"],
"env": {}
}
}
}
A running session does not auto-reload .mcp.json edits; restart
Claude Code to pick them up. Stdio servers are not automatically
reconnected after a crash either — restart the session if the server
goes away mid-conversation.
Source: https://code.claude.com/docs/en/mcp (fetched 2026-06-22).
Codex CLI
Codex CLI stores MCP servers in TOML, not JSON. The simplest path
is the helper command, which writes into ~/.codex/config.toml:
codex mcp add apple-health -- uvx apple-health-mcp-server serve
Equivalent manual entry in ~/.codex/config.toml (override the path
with CODEX_HOME= if needed):
[mcp_servers.apple-health]
command = "uvx"
args = ["apple-health-mcp-server", "serve"]
Edits to config.toml take effect on the next codex invocation —
restart any running session to apply them. The CLI also exposes
codex mcp list / codex mcp get <name> / codex mcp remove <name>
for inspection and cleanup.
Source: https://developers.openai.com/codex/mcp (fetched 2026-06-22).
Importing your export
Before any tool returns data you have to ingest your export once.
Apple gives you a directory containing export.xml, an
electrocardiograms/ folder, and a workout-routes/ folder; point
the importer at the directory itself:
uvx apple-health-mcp-server import /path/to/apple_health_export
The import is idempotent — re-running it with a newer export merges
the new rows into the existing database via the import_id column.
Phase 1 (XML parse) emits a single-line progress entry every
10 seconds (INFO progress: xml NN% (X / Y MB, ~Z min remaining))
so a streaming agent or human can confirm forward motion during a
multi-minute parse. Tune the cadence via
APPLE_HEALTH_IMPORT_PROGRESS_SECS (positive integer, clamped to
1..600); set it to 60 for quiet runs or 1 for debugging. Exports
smaller than 1 MB skip the emitter entirely.
Database location
By default the database lands at the XDG-resolved data directory:
- Linux / macOS:
~/.local/share/apple-health-mcp/health.duckdb - Windows:
%LOCALAPPDATA%\apple-health-mcp\health.duckdb
Override with --db /custom/path/health.duckdb on either subcommand.
Locales
Apple Health localises the ECG CSV header labels to the iPhone language
setting (the export.xml itself is locale-neutral). The importer
recognises:
- Verified: English, Japanese (both
記録日and記録日時variants) - Best-effort: Chinese Simplified, Chinese Traditional, Korean — header strings are educated guesses and have not been confirmed against real exports from those locales
The authoritative source of truth for which locales the parser supports
is the _VERIFIED_LOCALES and _BEST_EFFORT_LOCALES tuples in
src/apple_health_mcp/importers/ecg.py (alongside the per-header
_*_LABELS tuples they describe). Add a locale by extending both that
file and the tuples above; this README section reflects them.
When the parser fails to match any locale, the warning log points to the
GitHub issue tracker and asks for the first ten lines of the CSV so the
locale can be added. The full guidance is emitted once per import run
(further files in the same run get a short reference back to it). There
is no privacy concern in those header lines — the importer skips Name
and Date of Birth by design.
Distance and energy units (km, mi, kcal) come straight from the
underlying HealthKit identifiers and are not localised; the
total_distance_unit column on the workouts table records them
faithfully.
Tools
17 tools are registered with FastMCP, grouped by family:
| Family | Tools |
|---|---|
| Record types & data | list_record_types, query_records, get_record_statistics |
| Workouts | list_workouts, get_workout_details, get_workout_route |
| Activity summaries | get_activity_summaries |
| Heart rate | get_heart_rate_samples |
| Correlations | list_correlations, get_correlation_details |
| ECG | list_ecg_readings, get_ecg_data |
| State of mind | list_state_of_mind |
| Me characteristics | get_me_attributes |
| Metadata & ops | list_data_sources, get_import_history |
| Escape hatch | run_custom_query (read-only validated SQL) |
Compatibility
apple-health-mcp-server follows
Semantic Versioning from v1.0.0
onward. While the project remains in the v0.x.y series, breaking
changes can land in any minor release; the project minimises them but
does not formally guarantee against them yet.
Two-tier contract
The public surface is split into two tiers so that internal storage choices can keep evolving without dragging the wire-facing contract into major-bump territory every time.
Layer 1 — Wire-facing contract (strict; changes require a major bump):
- MCP tool names, parameter signatures (including defaults), and top-level response field names — adding a new tool, parameter, or response field is a minor bump; renaming, removing, or changing the type of an existing one is a major bump. Tool responses are consumed by downstream LLM prompt templates, so renaming a returned key is as breaking as renaming a parameter.
- CLI subcommand names and their required parameters (positional arguments and required flags alike), and environment variable names and parsing rules — same major-bump rules apply to renames / removals / semantic changes.
- CLI exit codes (see the table below).
- Top-level Python identifiers exported via
__all__from the package root (apple_health_mcp) — e.g.__version__,REPO_URL,ISSUES_URL. Removing one of these or changing its type is a major bump.
Layer 2 — Internal escape hatch (best-effort; changes ride a minor
bump and are called out under Changed in CHANGELOG.md):
- DuckDB schema — table names, column names, types, and NOT NULL
constraints.
run_custom_queryusers read against these directly, so the project will not break them lightly, but the schema is a storage detail rather than the wire contract: a column rename or a type widening can ship in a minor release as long as the CHANGELOG flags it underChanged. Layer 1 still gates the tool responses built on top, so a schema migration that does not affect any tool's output stays invisible to non-run_custom_querycallers. - Default DuckDB file path conventions (see Database location). The XDG-resolved defaults on each OS are stable in practice — users back them up, point monitoring at them, or symlink them across machines — but reserving them as Layer 2 leaves room to add an override mechanism or shift the default in response to an OS convention change without forcing a major bump.
- Module-internal helpers — anything not re-exported through
apple_health_mcp.__all__. These are documented inline for contributors but are not part of the SemVer contract at any tier.
run_custom_query callers depend on Layer 2 by construction. The
project treats their stability as best-effort: the goal is to avoid
breaking the schema between minor versions whenever possible, and to
document any change that does land under Changed in CHANGELOG.md so
existing custom queries can be updated in one pass.
Schema migrations are forward-only. Downgrading to a prior version
after a schema bump (e.g. v0.3.0-rc2 → v0.2.x) requires re-importing
from export.xml or restoring a pre-bump DB backup.
Layer 1 reference tables
Environment variables the server and importer read from the process environment. The current set:
| Name | Purpose | Default |
|---|---|---|
APPLE_HEALTH_TZ |
DuckDB session timezone used to render TIMESTAMPTZ columns. Overridden by --tz on the CLI when both are set. |
OS timezone |
APPLE_HEALTH_IMPORT_PROGRESS_SECS |
Cadence of the Phase 1 progress emitter on import. Integer seconds; out-of-range integers are clamped to 1..600, non-integer strings fall back to the default with a warning. Exports smaller than 1 MB skip the emitter entirely. |
10 |
APPLE_HEALTH_LOG_LEVEL |
stdlib logging level applied to the root logger (DEBUG/INFO/WARNING/ERROR). All logs land on stderr; stdout is reserved for the MCP stdio transport. |
INFO |
APPLE_HEALTH_LOG_FORMAT |
Log formatter shape. human is plain text; json emits one JSON object per line for log aggregators. |
human |
The server also honours the OS-standard XDG_DATA_HOME (Linux/macOS) and LOCALAPPDATA (Windows) when resolving the default DB path; those are part of the platform contract, not project-specific.
Renaming, removing, or changing the parsing rules of any of these is a major bump. Adding a new env var is a minor bump.
CLI parameters — used by callers that pipe apple-health-mcp-server into shell scripts, service supervisors, or wire it into Claude Desktop / Claude Code configs:
- Subcommands:
import <export-dir>,serve - Top-level flags:
--db <path>(DB path override, applies to both subcommands),--tz <name>(overridesAPPLE_HEALTH_TZ) serveflags:--transport stdio|http(defaultstdio),--host <addr>(HTTP bind host),--port <int>(HTTP port)
Renaming a subcommand or flag, removing one, or changing the semantics of an existing one is a major bump. Adding a new optional flag or subcommand is a minor bump.
CLI exit codes — observed by shell-script callers:
| Code | Meaning |
|---|---|
0 |
Success |
1 |
Any AppleHealthMCPError from the import or serve path (missing export, malformed DB, importer failure, server startup failure) |
2 |
Usage error from the CLI argument parser (unknown subcommand, missing required argument, bad flag value) |
Adding a new specific exit code (e.g. carving off 3 for "DB locked by another process") is a minor bump; collapsing or repurposing an existing code is a major bump.
Outside both layers
Anything not enumerated in Layer 1 or Layer 2 — helper modules without
an MCP-tool / CLI / __all__ / env-var / exit-code surface,
identifiers prefixed with _ (private constants, helpers, internal
exceptions), and module-internal constants — is not part of the
public API at any tier and may change in any release. In particular:
- Log-line format (e.g.
progress: xml NN% (X / Y MB, ~Z min remaining)) is not part of the public API contract; the human-readable shape may change between releases without a SemVer bump.APPLE_HEALTH_LOG_FORMAT=jsoncurrently wraps the same human-readable string inside a JSON envelope'smessagefield — it doesn't currently emit per-progress-event structured fields. If you need machine-parseable progress, please open an issue describing the use case; until a structured progress contract is published, treat all progress output as informational only. - MCP tool description text (the LLM-facing prose embedded in each tool registration) is not part of the public API contract; descriptions may be tightened, reworded, or reorganised without a SemVer bump as long as the parameter and return shape stay stable. Clients that rely on a tool should lock onto its name and signature, not its description prose.
Deprecation policy
(Applies from v1.0.0 onward — during v0.x.y, breaking changes can land in any minor release without going through this cadence; see the headline above.)
When something in the public API is scheduled for removal or rename:
- The deprecated item is marked in the CHANGELOG.md
Deprecatedsection of the release that announces it, alongside the planned replacement and removal version - The deprecated item keeps working for at least one minor release
before being removed (e.g.
1.5.0announces deprecation,1.6.xcontinues to ship the old name,2.0.0removes it) - The actual removal lands in the next major version bump
Security exception
A CVE-grade flaw inside a deprecated surface (e.g. a run_custom_query
parameter that turns out to leak data, or a tool whose response shape
exposes something it shouldn't) may break the deprecation cadence
above: the fix can ship as a removal or breaking change in any
release, including a patch. Such breaks are called out under a
Security heading in CHANGELOG.md so downstream consumers can spot
them in a single scan, and a security advisory is published on the
GitHub repository's Security tab. Without this carve-out, the
deprecation policy would bind the maintainer to keep a known-bad
surface alive for a full minor cycle, which is worse than the surprise
break it would prevent.
Updating
uvx caches the package on first run and re-uses that cached copy on
subsequent invocations, so a new release does not install itself
automatically. Pick one:
-
Always run the latest — use the
@latestsuffix whenever you want the newest published version:uvx apple-health-mcp-server@latest serve
Why not
--refresh?--refreshrevalidates PyPI metadata but does not always rebuild the cached tool environment, so a freshly published release can be silently shadowed by the previously-cached version (see astral-sh/uv#16991).@latestis the method recommended by the uv docs and is unambiguous. -
Pin a specific version — write the version directly in your Claude Desktop / Codex / Cursor config so an unrelated
uvxcache eviction cannot move you off it:{ "mcpServers": { "apple-health": { "command": "uvx", "args": ["apple-health-mcp-server==0.1.0", "serve"] } } }
See CHANGELOG.md for the per-release notes.
Troubleshooting
Every tool returns "No Apple Health data has been imported yet."
The MCP server boots even when the local DuckDB file is empty so the client still sees the full tool list, but every tool that needs data returns this guidance string until you run the importer:
apple-health-mcp-server import /path/to/apple_health_export
After the import finishes, restart the MCP server (quit and reopen
Claude Desktop / Claude Code / Codex, or stop and re-run the serve
process). The server keeps a read-only DuckDB snapshot for the lifetime
of the process; new rows only become visible to a fresh connection.
get_import_history is the one tool that stays callable on an empty
DB — it returns an empty list, which is how you confirm "no imports
yet" from the client side.
Development
uv sync
uv run pytest
See CLAUDE.md for the full command list, conventions, and
the mandatory /code-review --fix policy on every pull request.
Contributing
Issues and pull requests in English or Japanese are both first class; see CLAUDE.md §6 for the full language policy.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file apple_health_mcp_server-0.3.0rc2.tar.gz.
File metadata
- Download URL: apple_health_mcp_server-0.3.0rc2.tar.gz
- Upload date:
- Size: 287.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
52e6577a8c1c8078ddf1adb684cb833257a221640000d8fdf71bf0266846ae48
|
|
| MD5 |
4f30bf4ab30e767409a120420e4e9cf7
|
|
| BLAKE2b-256 |
07c15fcb74c881be16fe75e6cfe27352a9c805ae20fa059d89a977fe13f2c189
|
Provenance
The following attestation bundles were made for apple_health_mcp_server-0.3.0rc2.tar.gz:
Publisher:
release.yml on rinoshiyo/apple-health-mcp-server
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
apple_health_mcp_server-0.3.0rc2.tar.gz -
Subject digest:
52e6577a8c1c8078ddf1adb684cb833257a221640000d8fdf71bf0266846ae48 - Sigstore transparency entry: 1946020206
- Sigstore integration time:
-
Permalink:
rinoshiyo/apple-health-mcp-server@493cd8a833597a512d1e23f73dc95347f6464cdf -
Branch / Tag:
refs/tags/v0.3.0-rc2 - Owner: https://github.com/rinoshiyo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@493cd8a833597a512d1e23f73dc95347f6464cdf -
Trigger Event:
push
-
Statement type:
File details
Details for the file apple_health_mcp_server-0.3.0rc2-py3-none-any.whl.
File metadata
- Download URL: apple_health_mcp_server-0.3.0rc2-py3-none-any.whl
- Upload date:
- Size: 109.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e3ca495f516c31706aa98aea65c157faab61377442367fa4920349cabae3abe5
|
|
| MD5 |
27fe28c185188c661d93a4229978dd50
|
|
| BLAKE2b-256 |
c7a5c9d2fbc397dea65e3d54767ef077ce18a8962cacea120f63c9169e6f1491
|
Provenance
The following attestation bundles were made for apple_health_mcp_server-0.3.0rc2-py3-none-any.whl:
Publisher:
release.yml on rinoshiyo/apple-health-mcp-server
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
apple_health_mcp_server-0.3.0rc2-py3-none-any.whl -
Subject digest:
e3ca495f516c31706aa98aea65c157faab61377442367fa4920349cabae3abe5 - Sigstore transparency entry: 1946020281
- Sigstore integration time:
-
Permalink:
rinoshiyo/apple-health-mcp-server@493cd8a833597a512d1e23f73dc95347f6464cdf -
Branch / Tag:
refs/tags/v0.3.0-rc2 - Owner: https://github.com/rinoshiyo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@493cd8a833597a512d1e23f73dc95347f6464cdf -
Trigger Event:
push
-
Statement type: