Skip to main content
Glama

healthfolder

Read a Health Access folder of Apple Watch workouts and vitals, and write training plans back into it — as plain files. The folder an app writes from Apple Health (workouts, day files, an index, a profile) is the whole API: this package is a local stdio MCP server plus a CLI over that folder. No account, no network, nothing leaves the machine.

How the folder is found

First hit wins:

  1. --dir <folder> on the command line

  2. the HEALTHACCESS_DIR environment variable (alias TIDE_DIR)

  3. dir = "..." in ~/.config/healthaccess/config.toml (the path can be overridden with HEALTHACCESS_CONFIG; zone bounds can also be set there as zones = [133, 146, 158, 169])

  4. discovery: iCloud Drive, then ~/Library/CloudStorage/*, ~/Dropbox, ~/Google Drive, ~/OneDrive*, ~/Box, then ~/Documents. A Health Access folder holds an index.json of schema healthaccess.index.v1 and a workouts/ directory. If discovery finds several, the server refuses to guess and lists them.

Run healthfolder --check to print the resolved folder, where it came from, and the zone bounds in effect.

Related MCP server: Apple Health AI Bridge MCP Server

CLI

npx -y @reneturcios/healthfolder --check
npx -y @reneturcios/healthfolder list --sport swim --limit 5
npx -y @reneturcios/healthfolder show 7A1C3E90
npx -y @reneturcios/healthfolder vitals --days 14
npx -y @reneturcios/healthfolder summary --weeks 4
npx -y @reneturcios/healthfolder rate 7A1C3E90 7 "legs heavy, breathing fine"
npx -y @reneturcios/healthfolder plan push ./s30.json
npx -y @reneturcios/healthfolder plan status 2026-10-24T20:22:05Z-s30
npx -y @reneturcios/healthfolder plan list
npx -y @reneturcios/healthfolder day 2026-05-14
npx -y @reneturcios/healthfolder days --from 2026-05-01 --to 2026-05-14 --groups heart,sleep
npx -y @reneturcios/healthfolder catalog
npx -y @reneturcios/healthfolder profile
npx -y @reneturcios/healthfolder ecgs

Every command prints pretty JSON. Exit codes: 0 success, 1 domain or IO error, 2 usage error. --dir works on every subcommand.

MCP server

npx -y @reneturcios/healthfolder mcp

MCP client config:

{
  "mcpServers": {
    "healthfolder": {
      "command": "npx",
      "args": ["-y", "@reneturcios/healthfolder", "mcp"]
    }
  }
}

Add "--dir", "<absolute path>" to args to skip the folder search.

Tools (13)

Tool

What it does

list_workouts

Workouts from the index (deduplicated, newest first), one summary line each

get_workout

One workout in full: document, summary, parts text, zones, cardiac cost

get_vitals

Resting HR, HRV, body mass, sleep, steps, VO2max, blood pressure over the last N days

training_summary

One entry per ISO week: volume, zones, effort, cardiac cost, rings from day files

rate_workout

Store the athlete's own effort rating (1–10) in the workout file

push_plan

Validate a training plan and write plans/<id>.json for the app to put on the Watch

get_plan_status

A plan's state: pending, scheduled, failed or cancelled

list_plans

Every plan with its state and planned distance/time

get_day

One day file with every Health group it holds

get_days

Day files in a range (max 366 days), optionally reduced to named groups

get_catalog

catalog.json plus the export-coverage check

get_profile

profile.json as written

list_ecgs

Every ECG summary in ecg/, newest first

All results are pretty-printed JSON text with floats rounded to one decimal. Malformed arguments are JSON-RPC InvalidParams errors; domain problems (unknown id, failed validation, a refusal) come back as isError results with the reason in plain words.

Two safety rules are built in:

  • rate_workout only ever stores a number the athlete said. A workout that already carries Apple's own rating (effort_source: "apple") is refused unless the call passes override: true — the refusal names the file, the existing rating, and the remedy, and nothing is written.

  • push_plan reports every validation violation in one message (sport, name length, block/step shape, goal type and value, zone, label length), so a plan can be fixed in one pass.

Resources: healthaccess://index (the deduplicated index) and the template healthaccess://workout/{id} (one workout, heart-rate series included). MCP prompts are not implemented in this build.

Derived numbers

  • Zones. Four bounds, five zones. Bounds come from config.toml, else are estimated from the age in profile.json: max HR = 208 − 0.7 × age, bounds at 72/79/86/92 % of it (age 34 → [133, 146, 158, 169]). Zone seconds are computed from the heart-rate series: each sample's time until the next sample goes to that sample's zone, only time inside the workout counts, gaps over 60 s count as 60 s. With no bounds known, a file's own legacy zones are shown, marked bounds_source: "file".

  • Cardiac cost (swims only) = avg HR × best freestyle 100 m pace (s) / 60 = heart beats per 100 m; lower is better. The best 100 is the fastest work step of at least 95 m whose overlapping laps were at least half freestyle, normalised to 100 m; if no step qualifies and the swim is at least 100 m, the whole-workout pace is used.

  • Summary lines look like: 2026-05-14 Pool swim "S12" · 1200 m · 36:06 · avg HR 129 (max 162) · Z1–2 93% · effort 6/10 (apple) · best FR 100 2:12 → 284 beats/100 m — each segment appears only when its data exists.

Presence handshake

On initialize, and after tool calls at most once a minute, the server writes mcp/clients/<id>.json in the folder so the app can show "Connected". The id is the FNV-1a 64 hash of machine name + client name; the file carries the client name/version from the initialize request, the machine name (HEALTHACCESS_MACHINE overrides it), call counts carried over between writes, and the last tool name — never arguments or results. A failed write never fails a tool call. The CLI writes the same file, as client healthfolder-cli, only from the commands that write into the folder anyway (rate, plan push).

Docker

docker run -i ghcr.io/cloudcorprecords/healthfolder            # stdio MCP server
docker run --rm ghcr.io/cloudcorprecords/healthfolder list --dir /data

Mount the Health Access folder into the container and pass --dir (or set HEALTHACCESS_DIR) so the server can find it.

Development

npm install
npm run build   # tsc -> dist/
npm test        # tsx --test test/*.test.ts (synthetic folders, no network)
npm run dev     # run the CLI from source

License

MIT — Copyright (c) 2026 Rene Turcios.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    B
    maintenance
    Local-first MCP server that reads Apple Health export files (export.xml/zip) and exposes activity, sleep, HRV, and workout data to AI agents, keeping all data on your machine.
    18
    367 npm
    3
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    Read-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.
    9
    30
    Apache 2.0
  • A
    license
    A
    quality
    C
    maintenance
    Local MCP server that connects Claude Desktop with Garmin and Apple Health data to read training and recovery, estimate heart rate and pace zones, analyze performance, and create structured workouts.
    22
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Local stdio MCP server providing private Garmin coaching tools for Codex, ChatGPT desktop, and Claude, enabling preview and safe application of training weeks and heart-rate profile updates.
    7
    16 npm
    MIT