Your AI agent reads your E2EE Apple Health data (sleep, HRV, cycle) — decrypted only locally
Copy the AI prompt to install this server into Claude Code, Cursor, or another agent — or use 1-click editor setup below.
💡 Paste the JSON block into your client's configuration file under mcpServers, then restart the application.
Inspect callable tools, capabilities, and parameters exposed to AI agents by Vaultbeat.
vaultbeat_statuslocal binding state (no keys/tokens in the result)
vaultbeat_start_bindinggenerate a fresh QR binding payload
vaultbeat_poll_bindingpoll once for the iOS authorization
vaultbeat_doctordiagnose this install end to end, and report which data types
vaultbeat_sync_sleeprecent sleep records (incl. heart-rate samples) with per-day
get_sleep_detailper-night HR+RR+stage timeline with stage intervals
Your Apple Health data — sleep stages, cycle, HRV, resting heart rate, workouts, weight, VO₂ max, meals, lifts, notes — readable and writable by your own AI agent (Claude Code, Hermes, OpenClaw, anything MCP), end-to-end encrypted so that only your machine ever sees plaintext. The Vaultbeat iPhone app captures from HealthKit; this package is the local server that decrypts for the agent.
Technically: the local service program for Vaultbeat's encrypted health-data recipient flow.
Published externally as Fino-wind/vaultbeat-apple-health
(public package name vaultbeat-apple-health since 0.6.2; vaultbeat-mcp and
vaultbeat-mcp-local remain back-compat console scripts). This directory is the source of truth — after any user-visible change here,
re-export the public repo and update its README tool table + the website /mcp page
(see CLAUDE.md "Sync duty").
It runs on the user's computer, generates the Curve25519 keypair used by the iOS app,
shows a QR binding payload, receives a one-time server token from the cloud API, and
then exposes decrypted health data — sleep, water, weight, cycle, activity, vitals —
through the MCP server, and only through it. Read-only for health data: writes come
from the iOS app or from this server's own log_* tools.
There are six, and none of them reads health data. Health data has exactly one exit — the MCP protocol — so the commands here only pair a machine, check that pairing, and start the server. See "Why the CLI cannot read health data" below.
Until 0.7.4 this package shipped fifteen data subcommands (sleep, water, weight,
menstrual, hrv, …) that printed decrypted health JSON to stdout. They were removed,
and nothing was lost: every one of them had an MCP tool doing the same job
(sleep → vaultbeat_sync_sleep, water → get_water_intake, and so on), while the MCP
side additionally carries whole capabilities the CLI never had — food, basal energy, total
energy, VO₂ max, the metric-series tools, and every log_* writer.
The CLI half was not a second feature, it was a second door into the same room — and a door that skipped everything the MCP tools state about what they return: how many days are actually covered, why an empty result is not a zero, which plan clamps the window. Any agent with shell access could read a person's cycle history by running a command, bypassing all of it. One exit is the property worth having, so there is one exit.
--demo is a global flag, not a subcommand: it goes before the subcommand
(vaultbeat-apple-health --demo serve), and VAULTBEAT_DEMO=1 does the same thing. It serves a
deterministic synthetic dataset — the same records on every machine, every run — so demo
output can be pasted into a bug report as a shared baseline. Nothing is fetched and nothing
is decrypted; there is no private key involved at all. Every payload carries demo_mode: true plus a [SYNTHETIC DEMO DATA] banner, the tool descriptions say so, and the server
lists itself as Vaultbeat Health [DEMO — SYNTHETIC DATA], so demo output cannot pass for a
real export. Read tools only — the log_* write tools refuse, because writing needs a
real key and a real account, and a write that pretends to succeed is worse than one that says
it needs pairing. It applies to that one invocation and is never written to the config file.
http is a CLI alias for MCP's streamable-http transport.
The default transport remains stdio for local desktop MCP clients.
The config file defaults to ~/.tether/mcp-local/config.json and is written with
0600 permissions. It contains the cloud-issued server token and your public
key; do not commit or share it.
Not in config.json. It is looked for in three places, in order:
VAULTBEAT_PRIVATE_KEY — read if set, never written back, for operators
who inject it from systemd-creds / a vault / a KMS.~/.tether/mcp-local/identity.key, mode 0600 — the automatic fallback
on a machine with no keyring backend at all.Keeping it out of config.json is a boundary, not tidiness: the server token
alone can download your ciphertext but not read it, and the private key alone
has nothing to decrypt. config.json is the file people cat into bug reports.
🔴 Never delete
config.jsonto "start clean". The private key is not in it, so deleting does not clear a bad key — it mints a brand-new identity, and every record already encrypted for the old one becomes permanently unreadable. If a command reports missing key material, the error names all three locations and what was found in each; read that before removing anything.
Headless servers: if the keyring is unreachable, do not set
PYTHON_KEYRING_BACKEND to the null backend. That backend accepts writes and
stores nothing; since 0.4.3 every keyring write is verified by reading it back,
so a null backend just lands the key in layer 3's identity.key file — the same
outcome as having no keyring, with a keyring you might have reached hidden behind
it. Either let layer 3 handle it (automatic when no backend exists) or, if a
D-Bus session exists but this process cannot see it, pass
DBUS_SESSION_BUS_ADDRESS through explicitly — XDG_RUNTIME_DIR on its own is
not enough.
.tether, not.vaultbeat— that is deliberate, do not "fix" it. The app was renamed but this path is frozen at the pre-rename location, because the Keychain username embeds the resolved config path (_keychain_usernameinstore.py). Moving the directory orphans the bound config and its private-key Keychain entry for every existing install. Until 2026-07-28 this README wrote~/.vaultbeat/..., which does not exist — so anyone who came here to destroy their credentialsrm -rf'd an empty path, got no error, and left the real key in place.
When using HTTP transport, the server binds to 127.0.0.1:8000 and serves MCP at
/mcp by default, and requires a bearer token (see "Authenticating HTTP transport"
below). Binding a non-loopback address fails closed unless you pass both a token and
--allow-remote; always front a network-exposed server with TLS (a reverse proxy).
vaultbeat-apple-health bind generates a fresh pollID and prints a QR payload:
{"pollID":"...","publicKeyBase64":"...","serverName":"..."}mcp-bind-local Edge Function.mcp-poll-binding Edge Function.serverID and serverToken.mcp-sync Edge Function, decrypts the returned envelopes
locally, and returns plaintext JSON to the agent. (All privileged routes are Supabase
Edge Functions at /functions/v1/<name>.)A QR code is drawn with block characters, and some encodings cannot represent them — GBK (the default on simplified-Chinese Windows) is missing two of the four outright. Since 0.7.2 the code is written as UTF-8 bytes underneath the terminal's own encoder, so this is usually invisible: agents and modern terminals decode UTF-8 and get an intact code.
If you do see mojibake, or rows of uneven length:
bind — that
mints a new pollID and invalidates anything already scanned. Leave it polling.qrencode -o pair.png '<payload>', or any QR library — send it to your phone,
and use import from Photos in the app's scanner rather than the camera.chcp 65001 and try again in a fresh terminal.bind --no-qr to get the payload as text only.vaultbeat-apple-health doctorIf binding or reads fail, run the self-diagnosis:
It checks, in order: config file → identity key (Keychain) → cloud reachability → binding state → a real fetch-and-decrypt round trip, and prints a targeted hint for the first thing that's broken (e.g. "codes expire after 10 minutes — re-run bind for a fresh QR", or "the stored key can no longer decrypt your data — delete this server in the iOS app and bind again"). Exit code 0 = all healthy, 1 = something needs the hint above.
Factual signals from GitHub, npm, and our automated checks — not a rating.
No reviews yet — be the first to share how this listing worked for you.
Showcase your server listing on GitHub or your project documentation. Embed this dynamic SVG badge to highlight official listing status and live engagement.
[](https://allmcps.com/mcp/vaultbeat)<a href="https://allmcps.com/mcp/vaultbeat"><img src="https://allmcps.com/api/badge/vaultbeat?style=directory" alt="Vaultbeat on AllMCPs" /></a>