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 +5 -0
- package/health-export.mcpb +0 -0
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/receiver.mjs +12 -3
- package/server.json +2 -2
- package/server.mjs +16 -3
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
|
---
|
package/health-export.mcpb
CHANGED
|
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.
|
|
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.
|
|
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 {
|
|
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).
|
|
236
|
-
|
|
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.
|
|
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.
|
|
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 {
|
|
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.
|
|
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
|
-
|
|
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')) {
|