Connect an agent in 10 minutes
This path uses the signed healthmd-mcp helper bundled with the released Mac app. It does not use the portable CLI preview, Direct CLI Access, a pairing QR code, or port 17647.
You will connect a local MCP host and verify readiness without reading health values. Then refresh one small iPhone scope and query the encrypted Mac context. Allow about ten minutes when both apps are installed and use the same local network.
1. Install and open Health.md
Section titled “1. Install and open Health.md”Download Health.md from the App Store on both the Mac and iPhone. Open both apps.
HealthKit stays on iPhone. The Mac app hosts the signed MCP helper and an encrypted, disposable query context. It does not read HealthKit directly.
2. Connect iPhone and Mac
Section titled “2. Connect iPhone and Mac”- On Mac, leave Health.md open.
- On iPhone, open Health.md → Sync and enable Mac connectivity.
- Keep both devices on the same reachable local network and keep Health.md foreground on iPhone while starting fresh work.
- Confirm the Mac app shows the intended iPhone connection. If it does not, reopen both apps and review Mac Sync readiness.
This is the released Mac connection. Do not run healthmd direct pair. That command belongs to the separate portable preview.
3. Copy the signed helper path
Section titled “3. Copy the signed helper path”Open Health.md for Mac → CLI and copy the displayed MCP helper path. A normal /Applications install uses:
/Applications/Health.md.app/Contents/Helpers/healthmd-mcpUse the displayed path if the app is installed elsewhere. Configure the helper directly. Do not wrap it in a shell or launch it as an interactive command.
4. Configure Codex or Claude
Section titled “4. Configure Codex or Claude”Add this to ~/.codex/config.toml, substituting the helper path when needed:
[mcp_servers.healthmd]command = "/Applications/Health.md.app/Contents/Helpers/healthmd-mcp"args = []startup_timeout_sec = 10tool_timeout_sec = 1200default_tools_approval_mode = "prompt"
[mcp_servers.healthmd.tools.healthmd_export_files]approval_mode = "prompt"
[mcp_servers.healthmd.tools.healthmd_export_job_resume]approval_mode = "prompt"
[mcp_servers.healthmd.tools.healthmd_export_job_cancel]approval_mode = "prompt"Restart Codex after saving the file.
Claude Desktop or Claude Code
Section titled “Claude Desktop or Claude Code”Add this local stdio entry to Claude Desktop’s MCP configuration or a trusted Claude Code .mcp.json:
{ "mcpServers": { "healthmd": { "command": "/Applications/Health.md.app/Contents/Helpers/healthmd-mcp", "args": [] } }}Restart Claude Desktop, or trust the Claude Code workspace and approve the server. Keep approval prompts enabled for refresh, export, resume, and cancel operations.
5. Check readiness
Section titled “5. Check readiness”Call healthmd_doctor. It reads health-free readiness only.
A ready result contains these fields:
{ "schema": "healthmd.local_readiness", "schema_version": 1, "status": "ready"}The complete result also includes checks and next actions. Resolve every blocking check before continuing. A connected helper does not prove that encrypted context is fresh.
Next, call healthmd_metrics and confirm the canonical metric ID and unit you intend to request. This walkthrough uses steps only as an example.
6. Explicitly refresh one small scope
Section titled “6. Explicitly refresh one small scope”Resolve the dates you actually want, then call healthmd_refresh with an exact inclusive range. The example requests one day of summary data:
{ "dates": { "type": "exact", "range": { "start_date": "2026-07-14", "end_date": "2026-07-14" } }, "metrics": { "type": "explicit", "metric_ids": ["steps"] }, "sources": { "type": "all_available" }, "detail_level": "summary", "wait_timeout_seconds": 300}Review the arguments, approve the acquisition, and keep both apps open. Refresh writes no export files and does not change saved iPhone export settings. Retain the returned job_id until the job reaches a terminal state.
7. Run the first bounded query
Section titled “7. Run the first bounded query”After refresh completes, call healthmd_metric_chart with the same dates, metric, source selection, and detail level:
{ "dates": { "type": "exact", "range": { "start_date": "2026-07-14", "end_date": "2026-07-14" } }, "metrics": { "type": "explicit", "metric_ids": ["steps"] }, "sources": { "type": "all_available" }, "detail_level": "summary", "all_pages": true}all_pages: true traverses opaque cursors only within the helper’s aggregate page and byte ceilings. For sleep, call healthmd_sleep_sessions instead of substituting canonical extraction.
8. Verify completeness before answering
Section titled “8. Verify completeness before answering”Do not treat tool success as proof of complete health coverage. Check all of the following:
- the refresh reached a terminal successful state for the same exact dates, metrics, sources, and detail level.
- the response schema and version are recognized.
- the requested range and timezone match the question.
- each stated value retains its canonical metric ID and unit.
- coverage status, days considered, days with values, and every missing interval are reported.
complete_empty,partial,failed,unsupported,skipped, andcancelledare not converted to zero.- traversal completed, or any remaining cursor or aggregate ceiling is disclosed.
- evidence/source descriptors and limitations remain attached to the answer.
- factual direction is not turned into diagnosis, treatment advice, causation, or “better/worse” language.
Read partial results without discarding useful data
Section titled “Read partial results without discarding useful data”A typed query can return a valid healthmd.query_response while only part of the requested scope completed. The generated partial query response fixture retains an available Steps item and reports the failed day separately:
{ "schema": "healthmd.query_response", "schema_version": 1, "coverage": { "status": "partial", "days_considered": 2, "days_with_values": 1, "missing": [ { "status": "failed", "range": { "start_date": "2026-03-16", "end_date": "2026-03-16" } } ] }, "items": ["one retained typed item"], "limitations": ["one or more requested days did not complete"]}The strings inside items and limitations above are explanatory abbreviations. Use the downloadable generated fixture for exact fields and evidence. Preserve the retained item, failed interval, coverage counts, and limitation together.
Do not add status: "partial_success" to healthmd.query_response. That status belongs to higher-level CLI and export envelopes when acquisition, traversal, or file generation is incomplete. A timeout is different again: it is an unknown durable-job outcome that must be inspected by job ID.
Structured failures use healthmd.query_error v1 rather than a partial response. Inspect agent-query-error.json for the generated production shape with stable code, message, retryability, and typed details.
9. Recover from a timeout safely
Section titled “9. Recover from a timeout safely”A timeout, closed host, or cancelled MCP waiter does not cancel an accepted refresh.
- Keep the returned
job_id. - Call
healthmd_job_statuswith that ID. - If the immutable job is resumable, review and approve
healthmd_job_resumewith the same ID and a finite wait timeout. - Start a new refresh only after status proves that no accepted job can still complete.
- Use
healthmd_job_cancelonly when you intend to terminate the job. Cancellation is terminal only after iPhone acknowledgement.
Never retry blindly after an unknown outcome. Durable refresh jobs preserve the accepted scope and committed frontier.
You are connected
Section titled “You are connected”The first read-only workflow is complete when doctor is ready and the explicit refresh is terminal. The bounded query must also have complete traversal. You must inspect coverage, evidence, units, and limitations.
Generated-file exports are a separate approval-gated workflow. The released Mac tool writes into the folder already selected in Health.md for Mac. It does not accept an arbitrary destination argument.