MetricBridge (formerly Health Export AI)
This server turns a MetricBridge/Apple Health export into read-only MCP tools your AI agent can query in natural language — with units, date ranges, and coverage honesty built into every answer.
Check the bridge and data freshness —
get_mcp_status(source, metric/workout counts, context files present, last data date,recomputesummary) andget_freshness(stale state, age in hours, per-file write times, stale copies, configurablemaxAgeHours).Discover metrics —
list_metricsfor every metric with unit, day count and date range, andresolve_metricto map any spelling/alias (steps, hrv, StepCount) to a canonical name, plus unit conversion (180 lb to kg) and honest refusal of ambiguous or different measures.Pull daily values —
get_health_metricsfor one metric or all, over a date range, with avg/sum/min/max/latest aggregation, granularity roll-up (day→year),filterDaysevent-based day restriction, and coverage/segmentBoundary/recompute blocks.Analyze trends and compare periods —
get_trends(recent N days vs prior N, withwindowSatisfiedandexcludeTravelDays) andcompare_periods(arbitrary A vs B periods, or anchored before/after a logged event).Export clean JSON —
get_structured_export, paginated with cursors, for chosen metrics and ranges.Look inside the current day —
get_intradayfor the hourly automation window (yesterday/today), with per-hour Min/Max.Ask in plain English —
query_health_dataroutes a natural-language question to structured results.Inspect logged context —
list_events(medication, habit, visit, life, shift, episode, travel) andget_profile(opted-in conditions, medications, goals, allergies, notes; absent ≠ none).Review training and sleep —
get_workouts(activity/date filters, pagination, heart rate, running dynamics, cycling power, intervals, routes) andget_sleep_sessions(clustered, waking-day attributed, split nights preserved).Add cycle and correlation context —
get_cycle_context(day-in-cycle and coarse phase from logged period starts, never predictive) andcorrelate_metrics(Pearson r with 0–3 day lag, r withheld below 10 pairs, always association-not-causation).Also available per the README — 22 MCP prompts (daily brief, doctor visit prep, n-of-1 experiments, coverage audits, etc.), a
--demosynthetic dataset, and CLI helpers (--doctor,status --max-age, LANreceive); the only writes areadd_event/remove_eventto the agent events file.
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, 18 tools (16 read-only, plus two that log events to their own file) and 22 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 |
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 18 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 ( |
| Day-in-cycle and coarse phase derived from logged period starts. Never predictive. |
| 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) 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 22 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, 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 and a timezone change, 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.
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 18 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
18 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).
| Name | Required | Description | Default |
|---|---|---|---|
| date | Yes | Local day the event happened or started, YYYY-MM-DD. | |
| note | No | Optional longer note, at most 2000 characters. | |
| tags | No | Optional tags for filtering, e.g. ["magnesium"]. At most 20. | |
| type | Yes | Event type: medication, habit, visit, life, shift, episode, travel or other. Range types (shift, travel, episode) usually carry an endDate. | |
| title | Yes | Short description, e.g. "Started magnesium 300 mg". At most 200 characters. | |
| endDate | No | Last day of a range event (inclusive), YYYY-MM-DD, not before date. Omit for a point event. | |
| clientEventId | No | Optional idempotency key: the same key never logs a second event. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Adds substantially beyond annotations: exact file written, explicit statement that Apple Health and app-owned files are untouched, read-only visibility in the app, the returned id prefix, idempotency fallback rules, and three distinct refusal conditions including passphrase sealing.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose is front-loaded, then constraints, then return value, then failure modes — a sensible order. Dense but every clause carries information; the file-path clause is slightly verbose but justified by the safety point it makes.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description still specifies the return shape (stored event with agent-prefixed id). Mutation side effects, idempotency, and failure modes are all covered, so an agent can call it safely without further context.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3; the description earns extra by explaining the idempotency fallback (same date/type/title/endDate when no clientEventId is supplied), which the schema does not state. Range-vs-point event semantics are left to the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Specific verb+resource ("Log a context event") with concrete examples (medication start, habit change, visit, trip) that map onto the type enum. Clearly a write-side counterpart to list_events/remove_event in the sibling set.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives a clear trigger ("the user tells you about") and an explicit precondition ("Ask the user before logging anything they did not state"). Refusal conditions (demo mode, unwritable folder, encrypted export without passphrase) are enumerated, though no sibling tool is named as an alternative for reading these events back.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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).
| Name | Required | Description | Default |
|---|---|---|---|
| anchor | No | Build both periods around a logged event instead of passing dates: periodA is the {days} days before the event date, periodB the {days} days after, the event day itself excluded from both. Get event ids from list_events (app and agent-logged events both work). Pass either anchor or periodA/periodB, not both. | |
| metric | Yes | ||
| periodA | No | ||
| periodB | No | ||
| excludeTravelDays | No | Drop days on which a timezone change landed (health-days.json): those days were not 24 hours long, so their totals are stretched or shortened by the clock. The answer reports how many days were excluded and why. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover safety (readOnlyHint, idempotentHint, openWorldHint=false), so the description's job is to add context — and it does: the event day is excluded from both sides, and the answer contains aggregate, change, and percent change. It stops short of noting how missing data or partial periods are handled.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two dense sentences that front-load the core comparison and then the anchor shortcut; nearly every clause earns its place. The nested parentheticals make it slightly harder to scan than a truly crisp definition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description helpfully sketches the return values, and the anchor semantics are covered. The gap is the required metric argument: an agent still doesn't know what strings are legal or whether it must call resolve_metric first.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is only 40%, so the description must compensate. It explains the anchor-vs-periodA/periodB tradeoff and the event-day exclusion well, but the required 'metric' parameter is documented nowhere — no valid values or pointer to list_metrics/resolve_metric — and excludeTravelDays is left entirely to the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific verb and resource — 'Compare a metric between two arbitrary date periods (A vs B)' — and even states the shape of the answer (each aggregate plus the change and percent change). It does not name a sibling such as get_trends or correlate_metrics to sharpen the boundary, which keeps it below a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives two clear invocation modes: pass periodA/periodB explicitly, or pass anchor {eventId, days} for the 'before/after question'. That is real usage guidance, but it offers no exclusions or comparison against alternatives like get_trends or correlate_metrics.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| end | No | YYYY-MM-DD | |
| lag | No | 0 to 3 days. lag 1 pairs metricA on day d with metricB on the following day. | |
| start | No | YYYY-MM-DD | |
| metricA | Yes | ||
| metricB | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint and idempotentHint annotations, the description discloses important quirks: r is withheld below 10 aligned pairs, the function returns both means, and the answer always carries an association-not-causation caveat. It also clarifies lag orientation clearly. This is exactly the kind of behavioral information an agent needs and cannot infer from annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three concise, information-dense sentences. Every sentence contributes: the first defines the operation, the second explains the lag and return values, the third provides the necessary interpretation caveat. There is no filler or repetition of schema content.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only correlation tool, the description covers the key computation, lag behavior, return fields, sample size threshold, and the contextual caveat. Its main omission is clear default behavior for unlimited or unspecified start/end windows, and it does not describe how alignedPairs is structured. Given there is no output schema, slightly more return-format exposure would make it complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The description adds useful meaning beyond the schema: it explains lag direction, default value, and relationship between aligned daily values. It also identifies the roles of metricA and metricB. Schema coverage is about 60%, and the description compensates for the missing metricA/metricB descriptions, though it does not elaborate on start/end bound semantics.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific operation with a clear verb and resource: 'Pearson correlation between two metrics' daily values.' This is distinct from sibling tools like get_trends or compare_periods because it focuses on pairwise correlation rather than trend direction or period comparison.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The correlation intent is clear, and the lag semantics are explicitly explained, but the description does not state when to prefer this over alternatives like get_trends, compare_periods, or list_metrics. The decision context is implied rather than explicit, and no exclusions or alternative tools are named.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| date | No | YYYY-MM-DD |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and idempotentHint, and the description adds meaningful behavioral detail beyond those: the phase-label rule, the derived-not-predictive nature, and the observed-midpoint method. It also discloses that no ovulation estimate is made, which is important for an agent considering this tool for health data interpretation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a compact, information-dense two-sentence definition. It front-loads the core purpose and source, then adds necessary behavior constraints and parameter behavior. Each sentence earns its place with no filler, though the organization could be slightly tighter.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only, zero-required-parameter tool with no output schema, the description covers the important context: input semantics, default behavior, derivation source, and the exact output dimensions. The return values are sufficiently described at a high level (day-in-cycle and phase label) to make the tool usable.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema description coverage is 100% for the single date parameter, and the description enriches it further: the date is optional, it applies to a single day, and the default behavior returns per-day context for the last cycle. This gives the agent a complete understanding of parameter behavior beyond the bare YYYY-MM-DD schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific resource (user-logged period starts), a specific output (day-in-cycle and phase label), and the source file (health-cycles.json). It also distinguishes itself by explicitly noting it is derived from logged periods only and never predictive, which separates it from potential ovulation or prediction tools even without naming them.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives a clear context for use: it is for cycle context based purely on logged period starts, and it explicitly excludes predictive or ovulation-estimation use cases. It does not explicitly name sibling tools or provide direct when-to-use-alternative language, but the scope and exclusions are strong enough to guide an agent.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| maxAgeHours | No | Treat the data as stale when the newest write is older than this many hours (greater than 0, at most 8760). Default 26, or HEALTH_STALE_AFTER_HOURS when set. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes far beyond the readOnly/idempotent annotations: it defines exactly how staleness is judged (core files only, never the intraday file), the threshold precedence (default 26, HEALTH_STALE_AFTER_HOURS, maxAgeHours), and every state value including the non-obvious stale_copy and manifest_behind cases. It also discloses side-adjacent behavior such as embedded campaign-labeled links and the 'advisory only' caveat for sessions_last_night.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose is front-loaded, but the body is a dense single paragraph that mixes threshold rules, state enumeration, return-field inventory, and link/campaign behavior. Every clause is arguably informative, yet the lack of structure makes it harder to scan than it needs to be.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, and the description compensates by naming the returned fields (state, age_hours, as_of, last_data_date, data_lag_days, stale_files, manifest_behind, sessions_last_night, icloud) and explaining the ambiguous ones. Edge cases such as no export found and manifest-behind-data are covered, leaving nothing essential for a correct call unstated.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds real meaning the schema does not: the precedence chain between maxAgeHours, the 26-hour default, and the environment variable, plus the note that the rule mirrors the `status --max-age` cron gate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific, agent-relevant purpose: reporting how current the exported files are so the agent can decide whether to trust results. This is unmistakably distinct from sibling read tools like get_structured_export or query_health_data, and the follow-on rules elaborate on the same single job rather than blurring it.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit decision context ('decide whether to trust what you read or ask the user to refresh') and a concrete reuse instruction ('Cheap: call it again before a long analysis'). It stops short of naming alternatives or explicit when-not conditions, but the intended call pattern is clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| end | No | YYYY-MM-DD | |
| limit | No | Maximum data points to return (default 365, max 3000). The server rolls up rather than truncating. | |
| start | No | YYYY-MM-DD | |
| metric | No | Metric name, e.g. step_count, heart_rate, sleep_analysis. Omit for all. | |
| filterDays | No | Restrict to days covered by matching logged events (health-events.json and health-events-agent.json): endDate ranges cover every day inclusive, point events cover their single day, negate:true keeps only days NOT covered. The answer states how many days matched. Example: HRV on night-shift blocks vs days off. | |
| aggregation | No | ||
| granularity | No | Roll daily values up before returning them. 'auto' (default) picks the finest granularity that fits the response budget, so a multi-year range returns monthly points instead of thousands of daily ones. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare read-only/idempotent; the description adds substantial behavior the agent could not infer: coverage block with firstDate/lastDate/days, aggregate always computed over the full range even when points are rolled up, segmentBoundaries for single-metric answers, readings capped at 150 with readingsOmitted, and recomputed_at/recompute blocks for backfilled days.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose is front-loaded in the first two sentences, which is good. But the remainder is a dense run-on covering coverage, aggregations, segmentBoundaries, blood-pressure readings, recompute blocks in one paragraph — the readings/recompute detail could be tightened or split so the calling-critical facts stand out.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description correctly carries the return-shape burden and does so thoroughly for a 7-param tool with a nested filterDays object. It stops short of describing empty-range behavior or what happens when filterDays matches zero days, which a data-retrieval tool's consumers would want.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is high (86%), so the baseline is 3; the description earns above that by clarifying that aggregate is computed over the full range regardless of rollup, and that multi-year ranges degrade to monthly points rather than truncating. It adds little on filterDays or metric naming beyond the schema, but the rollup/aggregate semantics are genuinely new.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
First sentence states a specific verb and resource — get metric values over an optional date range with an aggregate — and the second sentence positions it as 'the core data-retrieval tool'. It never names or contrasts with plausible siblings such as query_health_data, get_trends, get_intraday, or get_structured_export, so an agent can't distinguish them from this text alone.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied: 'check coverage before trusting a long window' and 'use single-metric to get segmentBoundaries' are hints rather than explicit when/when-not rules. No alternative tool is named, and there is no statement about when to prefer query_health_data or compare_periods over this.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| metric | No | Metric name to filter, e.g. heart_rate. Omit for every metric in the window. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare read-only/idempotent/closed-world, but the description adds material behavior the agent cannot infer: the file is REPLACED on every run, the window spans yesterday 00:00 to now (48h), older exports cover today only with no Min/Max, and discrete metrics pack hour Min/Max beside the mean. These are exactly the non-obvious traits that change how a result should be read.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core payload and tightly packed with no filler, but the clauses chain into long, semicolon-heavy sentences that take effort to parse. Slightly long for one resource, though essentially every clause carries distinct information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description does the work of describing the return shape (t, v = hour's value, Min/Max for discrete metrics) and the degraded/empty responses. Combined with the window and replacement semantics, an agent has enough to call and interpret results correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the single parameter's own description already says 'Omit for every metric in the window.' The description implies filtering via 'each metric's hourly points' but adds no syntax, naming convention, or resolution guidance beyond the schema (e.g. use resolve_metric for names). Baseline 3 applies when the schema carries the load.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb/resource ('hour-by-hour window' of 'hourly points' with latest value) sourced from named automations (health-intraday.json, app 1.4+). It explicitly distinguishes itself from get_health_metrics, the day-level sibling, so an agent can route without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Names the alternative ('use get_health_metrics for day-level questions') and the condition that selects it, plus the boundary case that this is a live recent view, not history. It also describes the empty-state path ('Returns available:false with setup guidance when no hourly automation has delivered yet'), which tells the agent how to interpret a failure.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With readOnlyHint/idempotentHint already covering safety, the description adds meaningful return semantics: what stale_copy means, that supported is null when the cache cannot tell, and that the app links carry fixed campaign attribution. It stops short of describing what a disconnected bridge looks like in the response, which is the key thing the 'call this first' advice is meant to detect.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The lead 'Health check:' is well front-loaded, but the rest is one long run-on sentence with nested parentheticals that packs freshness, recompute and URL attribution into a single breath. Every clause adds information, yet the structure makes it harder to scan than a short bulleted list would.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, so the description must carry return-value meaning, and it does so for the freshness and recompute blocks plus the app links. What is missing is the failure/disconnected state, the one behavior an agent calling this as a pre-flight check most needs to interpret.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so there is nothing for the description to disambiguate; baseline for a no-arg tool applies. The description correctly asks for no inputs.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Health check') and enumerates exactly what the result contains: data source, metric/workout counts, context files, latest data date, freshness block, recompute summary and app links. It does not, however, distinguish itself from the sibling get_freshness, which appears to cover overlapping freshness information.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'Call this first to confirm the bridge is connected' gives an explicit trigger and ordering, which is strong guidance for the agent. It never says when to prefer the sibling get_freshness instead, so the overlap with that tool is left unresolved.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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".
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations establish read-only and idempotent behavior, and the description genuinely adds behavioral value: it discloses the presentFields list and, crucially, warns that an absent field must not be interpreted as a negative value. This semantic caveat goes well beyond annotation signals and helps the agent avoid a dangerous misinterpretation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, each earning its place: the resource scope, the return shape, and the semantic caveat. No fluff, no redundancy, minimum well-organized and front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no params and no output schema, the description correctly explains both what the returned fields are and what the extra presentFields list means. The 'absent does not equal none' caveat closes the main path for user surprise/misuse. Nothing required to call or interpret this tool is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes no parameters, so there is no schema coverage burden. The description correctly focuses on the return payload, which is the only remaining semantic surface. It receives a baseline 4 for handling the zero-parameter case well.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific action ('get') and a clear resource ('health-profile.json' shared context fields), and distinguishes that resource from sibling tools by spelling out exactly what data is returned (conditions, medications, goals, allergies, notes). No ambiguity about what the tool operates on.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description clearly defines the narrow scope—context fields explicitly opted into sharing—so an agent can infer when this tool is relevant. However, it does not state when not to use it or explicitly contrast it with sibling tools such as get_health_metrics or get_cycle_context. Usage is implied rather than spelled out.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| day | No | A single waking day, YYYY-MM-DD. | |
| end | No | Last waking day (inclusive), YYYY-MM-DD. | |
| start | No | First waking day (inclusive), YYYY-MM-DD. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Despite annotations covering the read-only/idempotent profile, the description adds real behavioral context: duplicate nights from older app versions are collapsed into the longer session and counted in `collapsedDuplicates`, split nights appear as separate sessions sharing a day, and timestamps carry the local UTC offset. These are non-obvious output behaviors an agent could not infer from annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core purpose and filters, then layered with behavioral detail. It is dense and mostly earns its sentences, though it reads as a run of separate caveats rather than a structured hierarchy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description must carry return-value semantics, and it does: collapsedDuplicates, separate sessions for split nights, manualInBed:true provenance, and timezone-offset timestamps. An agent has enough to interpret results correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, and the description earns extra credit by explaining that filters operate on the WAKING day and why split nights share a day. That adds interpretive meaning beyond the schema's terse 'A single waking day' phrasing.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific resource (clustered sleep sessions from health-sessions.json) and its filters {start, end, day}, which is more precise than the name alone. It does not name or differentiate against siblings like query_health_data or get_intraday, so an agent gets the what but not the routing signal.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It implies the tool's niche by explaining the waking-day attribution and that it matches the daily cache's sleep_analysis, but there is no explicit 'use this when / use X instead' guidance for choosing between this and the other health-data tools. Usage is inferred rather than stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| end | No | YYYY-MM-DD | |
| limit | No | Maximum data points to return (default 365, max 3000). The server rolls up rather than truncating. | |
| start | No | YYYY-MM-DD | |
| cursor | No | Opaque cursor from a previous call's nextCursor. | |
| metrics | No | ||
| granularity | No | Roll daily values up before returning them. 'auto' (default) picks the finest granularity that fits the response budget, so a multi-year range returns monthly points instead of thousands of daily ones. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses pagination behavior, including nextCursor/cursor, and warns about large results. Since annotations already mark this as read-only and idempotent, no contradiction exists and no hidden mutation or side effect is suggested.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, each with a distinct purpose: describing what it returns, explaining pagination, and providing usage advice. There is no redundant wording or unnecessary context.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, so the description should have provided a clearer picture of the returned JSON shape; it only says 'clean structured JSON' and mentions nextCursor. Pagination, date range, and metrics guidance are covered, but the actual output format remains under-specified.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema property descriptions cover most parameters, so the baseline is already appropriate. The description does not add much semantic value beyond the schema: cursor behavior, limit, and granularity are already described in the input schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first sentence clearly describes the tool's deliverable: clean structured JSON for chosen metrics and a date range. It is distinct enough from analytics-oriented siblings like get_trends or compare_periods, though it does not explicitly name an alternative or scope how it differs from query_health_data.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives concrete usage guidance: name metrics and a date range, and avoid bare calls over a full history because they return a lot of data. It provides a clear practical context but does not explicitly say when to choose this tool over a sibling.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| metric | Yes | ||
| window | No | days per window (default 7) | |
| excludeTravelDays | No | Drop days on which a timezone change landed (health-days.json): those days were not 24 hours long, so their totals are stretched or shortened by the clock. The answer reports how many days were excluded and why. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already convey read-only and non-destructive behavior. The description adds useful behavioral context beyond annotations: windowSatisfied can be false when not enough history exists, and comparisons then run over less data than requested. It also discloses that segmentBoundaries lists logged point events in the span.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and front-loaded, opening with the main action and expected outputs. It adds technical details about windowSatisfied and segmentBoundaries without excessive filler. It is slightly dense but every sentence contributes semantic value.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, so the description carries the burden of explaining return data, which it does well: change, percent change, direction, daysAvailable, windowSatisfied, and segmentBoundaries. It does not fully address possible error/precondition cases or clarify metric validity, but for a read-only idempotent trend tool the essentials are present.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 67%, with window and excludeTravelDays already documented in the input schema. The description adds a general 'N-day window' concept but does not explain the required metric parameter or add meaning for excludeTravelDays beyond the schema. It compensates only partially for the undocumented metric parameter.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states it compares the most recent N-day window against the prior N days for a metric and returns change, percent change, and direction. This distinguishes it from more generic query tools, but it does not explicitly contrast it with the closely named sibling compare_periods, so it stops short of full sibling differentiation.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The usage context is implied rather than stated: an agent can see it is meant for window-over-window trend comparisons. However, there is no explicit guidance about when not to use it, when to prefer compare_periods or get_health_metrics instead, or what prerequisite conditions apply.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| end | No | YYYY-MM-DD | |
| limit | No | Workouts per page (default 50, max 200). | |
| start | No | YYYY-MM-DD | |
| cursor | No | Opaque cursor from a previous call's nextCursor. | |
| activityType | No | Activity name (e.g. Running) or raw HealthKit activity id (e.g. 37). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark it read-only and idempotent, and the description adds meaningful behavioral detail: records are returned exactly as stored, optional newer keys may be absent, nothing is fabricated, and the summary applies over all matches. This is precisely the extra non-obvious behavior agents benefit from.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long enough to capture filters, pagination, caching behavior, optional fields, and summary semantics, but every clause contributes. The core query action and paging semantics are front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 5-parameter read-only tool with no output/output schema, this description covers the important call/response semantics: which filters are allowed, how pagination works, what shape the records take, and what the summary provides. Nothing essential is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema already describes all five parameters with examples, defaults, and format. The description restates them compactly and adds only marginal grouping/narrative value, so it earns the baseline 3 for a schema with high coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a concrete action ('Query'), a specific resource ('the workouts cache'), and enumerates filters and pagination. The scope is clearly workout data, which distinguishes it from sibling metric/trend/period tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives clear context: this is for querying raw cached workout records within date/activity/filter bounds with pagination. It does not explicitly name alternatives or when not to use it, so it falls short of a full 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| end | No | YYYY-MM-DD | |
| tag | No | Match events carrying this tag. | |
| type | No | Event type, e.g. medication, habit, visit, life, shift, episode, travel, other. Unknown types pass through and can be filtered by their stored string. | |
| start | No | YYYY-MM-DD |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the readOnly/idempotent annotations: discloses the merge of health-events.json with health-events-agent.json, that every event carries a source of "app" or "agent", the ascending sort order, and the available:false shape when neither file exists. That is exactly the kind of provenance and empty-state context annotations cannot convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads what the tool returns, then provenance/merge, then filters, then ordering, then the empty-state caveat. Dense but every clause carries information an agent needs; nothing is redundant.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description carries the return-value burden and does so: source field, sort order, and available:false. Combined with the filter overlap rule and the empty-result interpretation, an agent has everything needed to call and interpret it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds real semantics: the optional {type, tag, start, end} set is described as filters and the range rule 'a range event matches a window it overlaps' explains behavior the schema does not. It does not detail date format or default window, but the schema already supplies YYYY-MM-DD.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Names a specific verb (list) plus the concrete resource: logged context events across a named set of types. The enumerations (medication starts, habit changes, doctor visits, shift blocks...) make it unmistakable, and it explicitly distinguishes its contents from the sibling add_event by noting agent-logged events.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explains filter semantics and the overlap rule, and gives the important interpretive caveat that an empty result means nothing was logged/exported rather than nothing happened. It does not explicitly route the agent against alternatives like query_health_data or get_structured_export, so it falls short of full when/when-not guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark this as readOnly and idempotent, so no safety contradiction exists. The description adds useful context about the listing including unit, day count, and date range, which goes beyond raw annotation data.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences with no filler. The main action and return content are front-loaded, and the usage guidance is immediately actionable.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter discovery tool, the description fully covers what the agent needs: what will be listed, what fields are included, and when to use it. Sibling tool context supports the discovery-to-query flow, and nothing critical is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, so parameter semantics are inherently simple. The description clarifies what the returned listing contains, which is useful given no output schema is present.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description identifies a specific action (list), a clear resource (every available Apple Health metric), and the exact information included (unit, day count, date range). It clearly distinguishes this as a discovery tool from sibling querying tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly states when to use this tool: 'discover metric names before querying.' This gives clear usage context and implies this is a prerequisite step rather than an analysis tool, though it does not explicitly name alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| question | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover readOnlyHint=true, openWorldHint=false, and idempotentHint=true, so the read-only and idempotent nature is established. The description adds one important behavioral fact: results are scoped to whatever data is currently in the file, and users should check list_metrics for the amount of history available. It doesn't explain routing behavior in detail (e.g., how ambiguity is handled), but the additional context about the underlying file scope is useful.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, efficient passage that front-loads the core purpose and then adds practical usage guidance. No words are wasted; the routing concept, the preference for alternatives, and the prerequisite call are all packed into two sentences.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
In the context of a simple one-parameter tool with handers that mark it read-only and idempotent, the main missing pieces for an agent would be the shape of the structured results and any question formatting limits. The instructions to check list_metrics and set expectations on available history partially fill the context gaps, and the description does not need to explain return values due to a lack of an output schema summary line. Still, the absence of any return-format hint keeps it from a 5, but against the overall simplicity it's fairly complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate for parameter meaning. The description does explain the question parameter's role by saying pass a question and get routed structured results, which is some semantic content. But it doesn't give examples, constraints, or format guidance for a well-formed question, leaving the parameter meaning incomplete despite a 0% schema coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states that this tool takes a natural-language question and returns routed structured results, and identifies it as a convenience tool with a specific purpose. It also explicitly distinguishes it from the specific tools by referring to them as alternatives, which helps differentiate it from siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly says to prefer specific tools when possible, and instructs calling list_metrics first to check the data history before using this tool. This gives an explicit usage context and a clear prerequisite. It doesn't list specific alternatives by name or state when-not conditions in detail, but the guidance provided is concrete and actionable.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | The agent event id from list_events or add_event. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already flag destructive, non-readonly, idempotent, and closed-world, but the description adds real context beyond them: the target file, the source-restriction refusal path, the demo/write-permission refusals, and the observable no-op message when an id is already gone. This is unusually complete for a destructive tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the action and target file, and every sentence carries actionable constraint or refusal information. The prose is dense and the parenthetical provenance note is slightly compressed, but there is no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given a destructive, idempotent, single-parameter tool with no output schema, the description covers target storage, eligibility, refusal conditions, and no-op behavior. An agent has enough to call it correctly and predict failure modes.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The single id parameter already has 100% schema description coverage, so baseline is 3. The description adds provenance semantics by tying accepted ids to add_event/list_events and identifying agent-prefixed ids, which is useful validation context beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource (remove an event) plus the exact backing store (health-events-agent.json) and the provenance link to add_event. It is unmistakable against siblings like add_event and list_events.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says which events are removable (agent-logged ids, source 'agent' in list_events) and which are refused (iOS app events), and adds preconditions: demo mode and non-writable folders are refused. That is a clear when/when-not contract with the alternative behavior named.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Any spelling of a metric, e.g. steps, StepCount, Body Weight, VO2 max. | |
| unit | No | Unit of value, e.g. lb, mmol/L, degF, km, %. Unknown units are refused, never guessed. | |
| value | No | Optional value to convert into the canonical unit. Pass with unit. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover the safety profile (readOnly, idempotent), but the description goes well beyond them: percentages are 0-to-1 fractions, unit variants come with exact conversion formulas, ambiguous multi-metric words return candidates, and a different measure (RMSSD vs SDNN) is refused rather than guessed. It also discloses that per-source attribution is only present when exports carry src/by, which is genuine behavioral context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose and accepted inputs are front-loaded and every sentence carries information, but the closing sentence about source attribution and merged-across-sources semantics is long and somewhat peripheral to the lookup job, making the whole denser than necessary.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, yet the description fully describes what comes back (canonical metric, unit, unit variants with formulas, whether the export holds it) plus conversion, ambiguity and refusal behavior. An agent has everything needed to select and invoke it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3; the description still adds value by explaining the conversion pairing ('Pass {value, unit} to convert one value, e.g. 180 lb to kg') and the refusal rule for unknown units, beyond what the schema states. It does not add format constraints on `name` that the schema lacks.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence states a precise verb and resource: 'Look a metric name up in the canonical naming and unit table.' It then enumerates accepted input forms and return contents, making it clearly distinguishable from siblings like list_metrics or get_health_metrics without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It names which siblings accept the same aliases (get_health_metrics, get_trends, compare_periods, get_structured_export, correlate_metrics) and notes that other tools require the export's own names, which tells the agent when resolution is needed. It stops short of an explicit directive ('call this first when the name is ambiguous'), so it is clear context rather than full when/when-not guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
4 tool updates
v1.8.0- Added
add_event - Changed
compare_periods1 field changed- changed
Input schema / properties / anchor / descriptionPrevious value: -"Build both periods around a logged event instead of passing dates: periodA is the {days} days before the event date, periodB the {days} days after, the event day itself excluded from both. Get event ids from list_events. Pass either anchor or periodA/periodB, not both."New value: +"Build both periods around a logged event instead of passing dates: periodA is the {days} days before the event date, periodB the {days} days after, the event day itself excluded from both. Get event ids from list_events (app and agent-logged events both work). Pass either anchor or periodA/periodB, not both."
- Changed
get_health_metrics1 field changed- changed
Input schema / properties / filterDays / descriptionPrevious value: -"Restrict to days covered by matching logged events (health-events.json): endDate ranges cover every day inclusive, point events cover their single day, negate:true keeps only days NOT covered. The answer states how many days matched. Example: HRV on night-shift blocks vs days off."New value: +"Restrict to days covered by matching logged events (health-events.json and health-events-agent.json): endDate ranges cover every day inclusive, point events cover their single day, negate:true keeps only days NOT covered. The answer states how many days matched. Example: HRV on night-shift blocks vs days off."
- Added
remove_event
2 tool updates
v1.6.3- Added
get_freshness - Added
resolve_metric
10 tool updates
v1.4.0- Changed
compare_periods3 fields changed- added
Input schema / properties / anchorAdded value: +{ + "description": "Build both periods around a logged event instead of passing dates: periodA is the {days} days before the event date, periodB the {days} days after, the event day itself excluded from both. Get event ids from list_events. Pass either anchor or periodA/periodB, not both.", + "properties": { + "days": { + "description": "Days on each side of the event.", + "type": "integer" + }, + "eventId": { + "description": "Event id from list_events.", + "type": "string" + } + }, + "type": "object" +} - added
Input schema / properties / excludeTravelDaysAdded value: +{ + "description": "Drop days on which a timezone change landed (health-days.json): those days were not 24 hours long, so their totals are stretched or shortened by the clock. The answer reports how many days were excluded and why.", + "type": "boolean" +} - changed
Input schema / requiredPrevious value: -[ - "metric", - "periodA", - "periodB" -]New value: +[ + "metric" +]
- Added
correlate_metrics - Added
get_cycle_context - Changed
get_health_metrics1 field changed- added
Input schema / properties / filterDaysAdded value: +{ + "description": "Restrict to days covered by matching logged events (health-events.json): endDate ranges cover every day inclusive, point events cover their single day, negate:true keeps only days NOT covered. The answer states how many days matched. Example: HRV on night-shift blocks vs days off.", + "properties": { + "eventTag": { + "description": "Event tag to match, e.g. nights.", + "type": "string" + }, + "eventType": { + "description": "Event type to match, e.g. shift, medication, travel.", + "type": "string" + }, + "negate": { + "description": "Keep only days NOT covered by matching events.", + "type": "boolean" + } + }, + "type": "object" +}
- Added
get_intraday - Added
get_profile - Added
get_sleep_sessions - Changed
get_trends1 field changed- added
Input schema / properties / excludeTravelDaysAdded value: +{ + "description": "Drop days on which a timezone change landed (health-days.json): those days were not 24 hours long, so their totals are stretched or shortened by the clock. The answer reports how many days were excluded and why.", + "type": "boolean" +}
- Added
get_workouts - Added
list_events
2 tool updates
v1.2.2- Changed
get_health_metrics2 fields changed- added
Input schema / properties / granularityAdded value: +{ + "description": "Roll daily values up before returning them. 'auto' (default) picks the finest granularity that fits the response budget, so a multi-year range returns monthly points instead of thousands of daily ones.", + "enum": [ + "auto", + "day", + "week", + "month", + "quarter", + "year" + ], + "type": "string" +} - added
Input schema / properties / limitAdded value: +{ + "description": "Maximum data points to return (default 365, max 3000). The server rolls up rather than truncating.", + "type": "integer" +}
- Changed
get_structured_export3 fields changed- added
Input schema / properties / cursorAdded value: +{ + "description": "Opaque cursor from a previous call's nextCursor.", + "type": "string" +} - added
Input schema / properties / granularityAdded value: +{ + "description": "Roll daily values up before returning them. 'auto' (default) picks the finest granularity that fits the response budget, so a multi-year range returns monthly points instead of thousands of daily ones.", + "enum": [ + "auto", + "day", + "week", + "month", + "quarter", + "year" + ], + "type": "string" +} - added
Input schema / properties / limitAdded value: +{ + "description": "Maximum data points to return (default 365, max 3000). The server rolls up rather than truncating.", + "type": "integer" +}
7 tool updates
v1.0.0- First observed
compare_periods - First observed
get_health_metrics - First observed
get_mcp_status - First observed
get_structured_export - First observed
get_trends - First observed
list_metrics - First observed
query_health_data
TDQS
Scored across 18 tools
Most tools target distinct resources or analyses, but several retrieval tools overlap: get_health_metrics, get_structured_export, and query_health_data all return metric data over a date range, and get_mcp_status includes freshness information also covered by get_freshness. The descriptions help clarify preferred use cases, but an agent still has to choose carefully between these pairs.
The tool set consistently uses snake_case verb_noun names: get_*, list_*, add_event, remove_event, query_health_data, correlate_metrics, resolve_metric, and compare_periods. The pattern is predictable throughout, with no mixed conventions.
Eighteen tools are slightly above the typical compact range but reasonable for a health-data bridge covering metrics, workouts, sleep, events, freshness, profile, and cycle context. Each tool has a defensible role, though the set could be tightened by consolidating overlapping retrieval and freshness tools.
The surface is broad and well covered: metric discovery and resolution, day-level and intraday retrieval, trends, period comparison, correlation, workouts, sleep sessions, cycle context, profile, freshness/status, and event logging. Minor gaps remain, such as no update_event operation and limited workout-route detail, but core workflows are complete.
Maintenance
Related MCP Connectors
Hosted MCP server exposing US hospital procedure cost data to AI assistants
- SomviaOAuthapp.somvia
Apple Health training load, recovery, HRV and workout detail for Claude, ChatGPT and any MCP client.
MCP server for building and testing AI agents with multi-model experimentation and insights.
MCP server for Withings health data — sleep, activity, heart, and body metrics.
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.354 npm576MIT
- 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.7281MIT
- 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.927Apache 2.0
- AlicenseNot gradedqualityCmaintenanceAn 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