health-export-mcp 1.6.2 → 1.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Copy-paste setup for every supported agent. The server is a single Node script (`server.mjs`,
4
4
  zero dependencies). Point `HEALTH_DATA_DIR` at the folder that holds your exported
5
5
  `.health-cache.json`, by default the [MetricBridge](https://www.healthexport.dev) iOS app
6
- ([App Store](https://apps.apple.com/app/id6784185201)) writes it to your iCloud Drive container:
6
+ ([App Store](https://apps.apple.com/app/apple-store/id6784185201?pt=129083290&ct=githubreadme&mt=8)) writes it to your iCloud Drive container:
7
7
 
8
8
  ```
9
9
  ~/Library/Mobile Documents/iCloud~ai~healthexport~app/Documents
package/README.md CHANGED
@@ -44,7 +44,7 @@ It's an MCP server that turns your Apple Health export into a tool your AI agent
44
44
  - **Who it's for:** anyone who wants their AI to reason over their *real* health data instead of a stale CSV.
45
45
  - **What it isn't:** a cloud service. There's no developer server in the path, your data goes only where *you* point it.
46
46
  - **Setup:** point the server at your exported data and add it to your AI client.
47
- - **Where the data comes from:** the [MetricBridge](https://www.healthexport.dev) iOS app (formerly Health Export AI), [available on the App Store](https://apps.apple.com/app/id6784185201), writes the export this server reads. The server still reports itself as `health-export-ai` in `serverInfo`, and the npm package and repository names are unchanged.
47
+ - **Where the data comes from:** the [MetricBridge](https://www.healthexport.dev) iOS app (formerly Health Export AI), [available on the App Store](https://apps.apple.com/app/apple-store/id6784185201?pt=129083290&ct=githubreadme&mt=8), writes the export this server reads. The server still reports itself as `health-export-ai` in `serverInfo`, and the npm package and repository names are unchanged.
48
48
 
49
49
  > **Just exported and the Mac has not seen it yet?** iCloud can take a few minutes to sync the
50
50
  > file across, so a check 60 seconds after tapping Run in the app can still show the old
@@ -61,7 +61,7 @@ It's an MCP server that turns your Apple Health export into a tool your AI agent
61
61
 
62
62
  The companion iOS app **[MetricBridge](https://www.healthexport.dev)** exports your Apple Health data, read-only, automatic, private. For this MCP server, export to a destination it can read:
63
63
 
64
- <p align="center"><a href="https://apps.apple.com/app/id6784185201"><img alt="Download MetricBridge on the App Store" src="https://img.shields.io/badge/Download_on_the-App_Store-0D96F6?style=for-the-badge&logo=apple&logoColor=white" height="38" /></a></p>
64
+ <p align="center"><a href="https://apps.apple.com/app/apple-store/id6784185201?pt=129083290&ct=githubreadme&mt=8"><img alt="Download MetricBridge on the App Store" src="https://img.shields.io/badge/Download_on_the-App_Store-0D96F6?style=for-the-badge&logo=apple&logoColor=white" height="38" /></a></p>
65
65
 
66
66
  | Destination | Notes |
67
67
  |---|---|
@@ -221,7 +221,7 @@ node server.mjs --help
221
221
 
222
222
  <p align="center"><sub><a href="https://www.healthexport.dev">MetricBridge</a>, exports your Apple Health to your agent, automatically.</sub></p>
223
223
 
224
- <p align="center"><a href="https://apps.apple.com/app/id6784185201"><b>Download on the App Store →</b></a></p>
224
+ <p align="center"><a href="https://apps.apple.com/app/apple-store/id6784185201?pt=129083290&ct=githubreadme&mt=8"><b>Download on the App Store →</b></a></p>
225
225
 
226
226
  ---
227
227
 
@@ -231,6 +231,7 @@ node server.mjs --help
231
231
  - **Local-first.** It runs on your machine over stdio. There is **no developer server** in the path.
232
232
  - **Optional pairing.** Set `PAIRING_SECRET` to the code the iOS app shows (Settings → Agent pairing) to gate access.
233
233
  - **Encrypted exports (optional).** If you switch on Export encryption in the iOS app, set `HEALTH_EXPORT_PASSPHRASE` (or `HEALTH_EXPORT_PASSPHRASE_FILE`) to the same passphrase and the server decrypts on your machine. See [Encrypted exports](#encrypted-exports).
234
+ - **No telemetry.** The server makes no network requests. When it finds no usable export (none yet, a stale one, or `--demo`), `get_mcp_status` includes one static link to the companion app's website. That link carries fixed campaign labels (`utm_source=mcp`, `utm_medium=agent-tool`, `utm_campaign=mcp-unlock`) that are identical for every user and contain nothing about you or your data; they only count if you choose to open it.
234
235
  - **Auditable.** Zero dependencies and a few hundred lines of readable JavaScript, read every line.
235
236
  - **Signed releases.** Hosted artifacts are minisign-signed and checksummed, see [Verifying releases](#verifying-releases).
236
237
 
Binary file
package/healthstore.mjs CHANGED
@@ -44,6 +44,32 @@ const DATA_DIR = (RAW_DIR === '~' || RAW_DIR.startsWith('~/'))
44
44
  * elsewhere would silently diverge the moment tilde expansion or env handling changed here. */
45
45
  export const dataPath = (file) => path.join(DATA_DIR, file);
46
46
 
47
+ // THE INSTALL-INTENT -> UNLOCK MIDDLE, MADE VISIBLE (2026-10-13).
48
+ // The companion iOS app writes the export this server reads, and the funnel into it used to be
49
+ // unmeasurable: install intent runs orders of magnitude above unlocks, and no tool answer carried
50
+ // an attributable link to the app. This is that link. It is TEXT ONLY -- adding it changes no
51
+ // network behaviour, so the server still makes zero runtime calls and the offline privacy promise
52
+ // is untouched.
53
+ //
54
+ // `utm_campaign=mcp-unlock` names THIS surface, so a visit lands under one campaign in the site's
55
+ // first-party analytics (Umami) instead of the undifferentiated direct bucket. site/app.js then
56
+ // stamps the same value onto the App Store link's Apple campaign token (ct) on the way through, so
57
+ // App Store Connect attributes the DOWNLOAD, not just the click on our site. `utm_source=mcp` and
58
+ // `utm_medium=agent-tool` keep the surface separable from paid and organic traffic.
59
+ export const UNLOCK_URL = 'https://www.healthexport.dev/?utm_source=mcp&utm_medium=agent-tool&utm_campaign=mcp-unlock';
60
+
61
+ /** The user-facing app card. get_mcp_status (the tool agents call first) returns it ONLY when this
62
+ * machine has no usable export: none at all, a stale one (a paused trial stops the writes), or the
63
+ * synthetic demo dataset. A user with a working export already has the app, and a link on every
64
+ * answer would be promotion rather than a description of what is missing, which is what MCP
65
+ * directory review rules object to. Exported so the tests read the same string. */
66
+ export const APP_INFO = {
67
+ name: 'MetricBridge',
68
+ unlockUrl: UNLOCK_URL,
69
+ note: 'The companion iOS app writes the export this server reads. unlockUrl is its website and carries fixed campaign labels (utm_source, utm_medium, utm_campaign) that are the same for every user. One-time unlock, no subscription.',
70
+ };
71
+
72
+
47
73
  // Guard against reading something pathological, but sized for a real full-history cache.
48
74
  // Measured on this cache's exact shape (189 metrics x 3,653 days): 22.0 MB compact. The old
49
75
  // 64 MB ceiling was within reach of a long history once workouts were included -- and worse, the
@@ -459,6 +485,8 @@ export async function status() {
459
485
  // Write times are still readable when the contents are not.
460
486
  freshness: await freshness({ detail: false }),
461
487
  metrics: [],
488
+ // No `app` block here: an export that exists but cannot be decrypted was written by the app,
489
+ // so the fix is the passphrase in `note`, not a link to the app.
462
490
  };
463
491
  }
464
492
  const names = Object.keys(metrics);
@@ -467,9 +495,15 @@ export async function status() {
467
495
  const d = m?.daily?.[m.daily.length - 1]?.d;
468
496
  if (d && (!lastDate || d > lastDate)) lastDate = d;
469
497
  }
498
+ const fresh = await freshness({ detail: false });
499
+ // Locked is neither case: a pairing gate file exists only because the app wrote it.
500
+ const noUsableExport = DEMO || (p.ok && (names.length === 0 || fresh.stale === true));
470
501
  return {
471
502
  ok: names.length > 0 && p.ok,
472
503
  source: sourceLabel(),
504
+ // The campaign-tagged companion-app link, only when there is no usable export (see APP_INFO).
505
+ // Additive and text only: no runtime network call is made.
506
+ ...(noUsableExport && { app: APP_INFO }),
473
507
  // Demo mode is reported LOUDLY and first-class here, not only in the per-answer watermark:
474
508
  // get_mcp_status is the tool agents are told to call first, so this is where a human debugging
475
509
  // "why do my numbers look wrong" finds out they are not their numbers at all.
@@ -489,7 +523,7 @@ export async function status() {
489
523
  lastDataDate: lastDate,
490
524
  // Additive: the compact staleness signal (get_freshness has the per-file breakdown), so an
491
525
  // agent's first call already tells it whether to trust today's numbers.
492
- freshness: await freshness({ detail: false }),
526
+ freshness: fresh,
493
527
  // Late-arrival recompute (cache schema minor 1). `supported: false` means the app that wrote
494
528
  // this cache predates it: past days are only as fresh as the run that last covered them.
495
529
  recompute: (() => {
package/manifest.json CHANGED
@@ -2,9 +2,9 @@
2
2
  "manifest_version": "0.3",
3
3
  "name": "health-export-ai",
4
4
  "display_name": "MetricBridge",
5
- "version": "1.6.2",
5
+ "version": "1.6.4",
6
6
  "description": "Query your Apple Health data, exported by the MetricBridge iOS app to iCloud Drive, from any MCP agent. Local-first, read-only, zero dependencies.",
7
- "long_description": "A zero-dependency MCP server that exposes 190 Apple Health metrics (exported by the MetricBridge iOS app into an iCloud Drive folder) to any MCP-compatible agent: Claude Desktop, opencode, Cursor, OpenClaw, Hermes. No bridge, no Docker, no HTTP receiver: the server reads the synced cache file directly over stdio. 16 tools: get_mcp_status, get_freshness, list_metrics, get_health_metrics, get_trends, compare_periods, get_structured_export, get_intraday, query_health_data, list_events, get_profile, get_workouts, get_sleep_sessions, get_cycle_context, correlate_metrics, resolve_metric. Plus 22 MCP prompts and a --demo mode with a synthetic dataset. Get the companion iOS app on the App Store: https://apps.apple.com/app/id6784185201",
7
+ "long_description": "A zero-dependency MCP server that exposes 190 Apple Health metrics (exported by the MetricBridge iOS app into an iCloud Drive folder) to any MCP-compatible agent: Claude Desktop, opencode, Cursor, OpenClaw, Hermes. No bridge, no Docker, no HTTP receiver: the server reads the synced cache file directly over stdio. 16 tools: get_mcp_status, get_freshness, list_metrics, get_health_metrics, get_trends, compare_periods, get_structured_export, get_intraday, query_health_data, list_events, get_profile, get_workouts, get_sleep_sessions, get_cycle_context, correlate_metrics, resolve_metric. Plus 22 MCP prompts and a --demo mode with a synthetic dataset. Get the companion iOS app on the App Store: https://apps.apple.com/app/apple-store/id6784185201?pt=129083290&ct=githubreadme&mt=8",
8
8
  "author": {
9
9
  "name": "PhilipAD",
10
10
  "url": "https://github.com/PhilipAD"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "health-export-mcp",
3
3
  "mcpName": "io.github.PhilipAD/health-export-mcp",
4
- "version": "1.6.2",
4
+ "version": "1.6.4",
5
5
  "type": "module",
6
6
  "description": "Open-source, zero-dependency Model Context Protocol (MCP) server to query Apple Health / HealthKit data (190 metrics) from Claude, ChatGPT, Cursor, OpenClaw, Hermes and any MCP agent.",
7
7
  "keywords": [
package/server.json CHANGED
@@ -8,22 +8,22 @@
8
8
  "url": "https://github.com/PhilipAD/health-export-mcp",
9
9
  "source": "github"
10
10
  },
11
- "version": "1.6.2",
11
+ "version": "1.6.4",
12
12
  "packages": [
13
13
  {
14
14
  "registryType": "npm",
15
15
  "registryBaseUrl": "https://registry.npmjs.org",
16
16
  "identifier": "health-export-mcp",
17
- "version": "1.6.2",
17
+ "version": "1.6.4",
18
18
  "transport": {
19
19
  "type": "stdio"
20
20
  }
21
21
  },
22
22
  {
23
23
  "registryType": "mcpb",
24
- "identifier": "https://github.com/PhilipAD/health-export-mcp/releases/download/v1.6.2/health-export.mcpb",
25
- "version": "1.6.2",
26
- "fileSha256": "e560da33c230c00b4a6a947c89ee274ff702f7356954b2b3b123ce5066ef4a8b",
24
+ "identifier": "https://github.com/PhilipAD/health-export-mcp/releases/download/v1.6.4/health-export.mcpb",
25
+ "version": "1.6.4",
26
+ "fileSha256": "54e038874281809f6d217eb76a94684951f94718bfe4189b2d6d6171e4bb3c78",
27
27
  "transport": {
28
28
  "type": "stdio"
29
29
  }
package/server.mjs CHANGED
@@ -21,7 +21,7 @@ import * as ev from './events.mjs';
21
21
  import { PROMPTS } from './prompts.mjs';
22
22
  import { DEMO } from './demo.mjs';
23
23
 
24
- const SERVER = { name: 'health-export-ai', version: '1.6.2' };
24
+ const SERVER = { name: 'health-export-ai', version: '1.6.4' };
25
25
  const DEFAULT_PROTOCOL = '2025-06-18';
26
26
  const log = (...a) => process.stderr.write('[mcp] ' + a.join(' ') + '\n');
27
27
 
@@ -57,7 +57,7 @@ const ANNOTATIONS = { readOnlyHint: true, idempotentHint: true, openWorldHint: f
57
57
  const TOOLS = [
58
58
  {
59
59
  name: 'get_mcp_status',
60
- description: 'Health check: data source, how many metrics/workouts are available, which optional context files exist, the most recent data date, and a `recompute` summary of past days rebuilt after late-arriving samples (with any days too old to rebuild). Call this first to confirm the bridge is connected.',
60
+ description: 'Health check: data source, how many metrics/workouts are available, which optional context files exist, the most recent data date, and a `recompute` summary of past days rebuilt after late-arriving samples (with any days too old to rebuild). Call this first to confirm the bridge is connected. When there is no usable export (none found, a stale one, or demo data), the result also includes `app.unlockUrl`, the website of the companion iOS app that writes the export; the link carries fixed campaign attribution labels.',
61
61
  inputSchema: { type: 'object', properties: {} },
62
62
  handler: () => store.status(),
63
63
  },