health-export-mcp 1.4.0 → 1.4.1

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/README.md CHANGED
@@ -31,6 +31,11 @@ It's an MCP server that turns your Apple Health export into a tool your AI agent
31
31
  - **What it isn't:** a cloud service. There's no developer server in the path — your data goes only where *you* point it.
32
32
  - **Setup:** point the server at your exported data and add it to your AI client.
33
33
 
34
+ > **Just exported and the Mac has not seen it yet?** iCloud can take a few minutes to sync the
35
+ > file across, so a check 60 seconds after tapping Run in the app can still show the old
36
+ > timestamp. `get_mcp_status` reports `lastDataDate` and the intraday `lastWrite`, which is the
37
+ > quickest way to tell "still syncing" from "never arrived".
38
+
34
39
  > **Try it with no iPhone needed:** `node server.mjs --demo` serves a deterministic synthetic dataset (400 days, workouts, events, sleep sessions) with every answer watermarked as synthetic. Or run `npm test` to write a sample cache and exercise every tool.
35
40
 
36
41
  ---
Binary file
package/manifest.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "manifest_version": "0.3",
3
3
  "name": "health-export-ai",
4
4
  "display_name": "Health Export AI",
5
- "version": "1.4.0",
5
+ "version": "1.4.1",
6
6
  "description": "Query your Apple Health data, exported by the Health Export AI iOS app to iCloud Drive, from any MCP agent. Local-first, read-only, zero dependencies.",
7
7
  "long_description": "A zero-dependency MCP server that exposes 190 Apple Health metrics (exported by the Health Export AI 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. 14 tools: get_mcp_status, 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. 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",
8
8
  "author": {
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.4.0",
4
+ "version": "1.4.1",
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/receiver.mjs CHANGED
@@ -23,7 +23,7 @@ import fs from 'node:fs';
23
23
  import path from 'node:path';
24
24
  import os from 'node:os';
25
25
  import net from 'node:net';
26
- import { pathToFileURL } from 'node:url';
26
+ import { fileURLToPath } from 'node:url';
27
27
 
28
28
  const WS_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
29
29
  const MAX_BODY = 64 * 1024 * 1024;
@@ -232,7 +232,16 @@ function encodeTextFrame(str) {
232
232
  return Buffer.concat([header, payload]);
233
233
  }
234
234
 
235
- // Standalone run (also importable by server.mjs). pathToFileURL handles spaces in the path.
236
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
235
+ // Standalone run (also importable by server.mjs). Both sides are resolved to REAL paths: a bin
236
+ // symlink (npx, npm i -g) otherwise makes this false and the receiver silently never starts.
237
+ const isEntryPoint = (() => {
238
+ if (!process.argv[1]) return false;
239
+ try {
240
+ return fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
241
+ } catch {
242
+ return false;
243
+ }
244
+ })();
245
+ if (isEntryPoint) {
237
246
  startReceiver();
238
247
  }
package/server.json CHANGED
@@ -6,13 +6,13 @@
6
6
  "url": "https://github.com/PhilipAD/health-export-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "1.4.0",
9
+ "version": "1.4.1",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "registryBaseUrl": "https://registry.npmjs.org",
14
14
  "identifier": "health-export-mcp",
15
- "version": "1.4.0",
15
+ "version": "1.4.1",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  }
package/server.mjs CHANGED
@@ -15,13 +15,13 @@
15
15
  // INSTEAD of the protocol loop, so their stdout is theirs to print to.
16
16
 
17
17
  import fs from 'node:fs';
18
- import { pathToFileURL } from 'node:url';
18
+ import { fileURLToPath } from 'node:url';
19
19
  import * as store from './healthstore.mjs';
20
20
  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.4.0' };
24
+ const SERVER = { name: 'health-export-ai', version: '1.4.1' };
25
25
  const DEFAULT_PROTOCOL = '2025-06-18';
26
26
  const log = (...a) => process.stderr.write('[mcp] ' + a.join(' ') + '\n');
27
27
 
@@ -487,7 +487,20 @@ function serveStdio() {
487
487
 
488
488
  // Importable on purpose (tests read TOOLS/SERVER; a future embedder reads handle): the protocol
489
489
  // loop and the CLI run only when this file IS the entry point, mirroring receiver.mjs.
490
- const IS_MAIN = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
490
+ // `npx pkg` and `npm i -g pkg` BOTH invoke the bin through a SYMLINK, so process.argv[1] is the
491
+ // link (…/node_modules/.bin/health-export-mcp) while import.meta.url is the real file. Comparing
492
+ // the two directly made this false under npx: the protocol loop never started, the process exited
493
+ // 0 with no stdout and no stderr, and an MCP client saw only "connection closed" ~700 ms in.
494
+ // Reported 2026-08-19 by a user whose client failed four times in a row, working only when he
495
+ // bypassed the shim with a direct `node …/server.mjs`. Resolve BOTH sides to real paths.
496
+ const IS_MAIN = (() => {
497
+ if (!process.argv[1]) return false;
498
+ try {
499
+ return fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
500
+ } catch {
501
+ return false; // argv[1] deleted mid-flight, or an unreadable path: not the entry point
502
+ }
503
+ })();
491
504
  if (IS_MAIN) {
492
505
  const argv = process.argv.slice(2);
493
506
  if (argv.includes('--help') || argv.includes('-h')) {