health-export-mcp 1.6.3 → 1.6.5
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 +1 -1
- package/README.md +3 -3
- package/health-export.mcpb +0 -0
- package/healthstore.mjs +21 -1
- package/manifest.json +2 -2
- package/package.json +1 -1
- package/server.json +5 -5
- package/server.mjs +2 -2
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
|
|
package/health-export.mcpb
CHANGED
|
Binary file
|
package/healthstore.mjs
CHANGED
|
@@ -712,7 +712,14 @@ export async function freshness({ maxAgeHours, detail = true, now = Date.now() }
|
|
|
712
712
|
as_of: null, as_of_source: null, newest_write: null, age_hours: null,
|
|
713
713
|
last_data_date: null, data_lag_days: null,
|
|
714
714
|
...(detail && { files: {}, stale_files: [] }),
|
|
715
|
-
|
|
715
|
+
// The wall message. This is the highest-intent moment in the whole funnel: an agent that
|
|
716
|
+
// cannot read a single metric has already installed the MCP server and hit the one thing
|
|
717
|
+
// only the iOS app removes. The `app.unlockUrl` card below is correct but buried in
|
|
718
|
+
// structuredContent, and agents summarise rather than echo deep JSON -- measured result:
|
|
719
|
+
// 1 visit to that link in 90 days. So the tagged URL goes where the agent actually relays
|
|
720
|
+
// it, inside the plain-language recommendation, with the SAME campaign tag (the test pins
|
|
721
|
+
// the tag, not just presence, or a refactor can silently drop the campaign).
|
|
722
|
+
recommendation: `No export files were found in the data folder. Export from the MetricBridge iOS app (${UNLOCK_URL}), then check again.`,
|
|
716
723
|
};
|
|
717
724
|
}
|
|
718
725
|
const rawAge = rawAgeHours(newest.mtimeMs, now);
|
|
@@ -721,6 +728,12 @@ export async function freshness({ maxAgeHours, detail = true, now = Date.now() }
|
|
|
721
728
|
// A cache that could not be read must not read as plainly usable even when its file is recent:
|
|
722
729
|
// the data tools will fail on it. `state` stays a write-time verdict (stable values); the
|
|
723
730
|
// recommendation carries the problem.
|
|
731
|
+
// The trial hint (and its link) goes out on the SERVER's own staleness rule, never on a stricter
|
|
732
|
+
// window a caller asked for: an agent passing maxAgeHours: 1 must not turn a paying user's
|
|
733
|
+
// two-hour-old export into a purchase hint. It is also withheld when the cache exists but cannot
|
|
734
|
+
// be read (an encrypted export without its passphrase): the app wrote that file, so the fix is
|
|
735
|
+
// the passphrase, not the app -- the same rule as status()'s encrypted early return.
|
|
736
|
+
const trialHint = stale && rawAge > Math.max(staleAfterHours(), DEFAULT_STALE_AFTER_HOURS) && !cacheReadError;
|
|
724
737
|
const readNote = cacheReadError
|
|
725
738
|
? ` The daily cache could not be read (${cacheReadError}), so data tools will fail until that is fixed; run get_mcp_status for the details.`
|
|
726
739
|
: '';
|
|
@@ -737,7 +750,14 @@ export async function freshness({ maxAgeHours, detail = true, now = Date.now() }
|
|
|
737
750
|
...(cacheReadError && { cacheReadError }),
|
|
738
751
|
...(detail && { files, stale_files: staleFiles }),
|
|
739
752
|
recommendation: (stale
|
|
753
|
+
// A stale export is usually a sync the phone never finished or an ended free trial (the
|
|
754
|
+
// app's LicenseGate stops exporting then) -- the second is the money moment. The app IS
|
|
755
|
+
// installed here (only it writes these files), so the hint names the trial, not an install.
|
|
756
|
+
// The unlock is an in-app purchase; the URL is labelled as the app's website so nobody reads
|
|
757
|
+
// it as a payment link. `app.unlockUrl` is returned along the same path; repeating it in
|
|
758
|
+
// prose is what makes the agent's answer carry the tagged link, not just the nested field.
|
|
740
759
|
? `The newest export is ${age} hours old, older than the ${threshold}-hour window. Answers only cover data up to ${lastDataDate ?? 'the last export'}; if current numbers matter, ask the user to open MetricBridge or run an export, then check again.`
|
|
760
|
+
+ (trialHint ? ` MetricBridge also stops exporting when its free trial ends; if that is why, the one-time unlock inside the iOS app resumes the exports (app website: ${UNLOCK_URL}).` : '')
|
|
741
761
|
: `Within the ${threshold}-hour freshness window: the newest export landed ${age} hours ago.`)
|
|
742
762
|
+ readNote,
|
|
743
763
|
};
|
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.
|
|
5
|
+
"version": "1.6.5",
|
|
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.
|
|
4
|
+
"version": "1.6.5",
|
|
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.
|
|
11
|
+
"version": "1.6.5",
|
|
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.
|
|
17
|
+
"version": "1.6.5",
|
|
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.
|
|
25
|
-
"version": "1.6.
|
|
26
|
-
"fileSha256": "
|
|
24
|
+
"identifier": "https://github.com/PhilipAD/health-export-mcp/releases/download/v1.6.5/health-export.mcpb",
|
|
25
|
+
"version": "1.6.5",
|
|
26
|
+
"fileSha256": "7db2702f41eac4761900d555676fc11e23117265cc1cf39c7c2500682984cdff",
|
|
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.
|
|
24
|
+
const SERVER = { name: 'health-export-ai', version: '1.6.5' };
|
|
25
25
|
const DEFAULT_PROTOCOL = '2025-06-18';
|
|
26
26
|
const log = (...a) => process.stderr.write('[mcp] ' + a.join(' ') + '\n');
|
|
27
27
|
|
|
@@ -63,7 +63,7 @@ const TOOLS = [
|
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
name: 'get_freshness',
|
|
66
|
-
description: `How current the exported files are, so you can decide whether to trust what you read or ask the user to refresh. Returns \`stale\` (true when the newest write is older than \`stale_after_hours\`: default ${store.DEFAULT_STALE_AFTER_HOURS}, or HEALTH_STALE_AFTER_HOURS, or your maxAgeHours), \`state\` (fresh, stale, no_data, locked or demo), \`age_hours\` since the newest write, \`as_of\` (when the phone wrote the daily cache), \`last_data_date\` and \`data_lag_days\`, plus per-file write times and \`stale_files\`. Same rule as the \`status --max-age\` cron gate. Cheap: call it again before a long analysis.`,
|
|
66
|
+
description: `How current the exported files are, so you can decide whether to trust what you read or ask the user to refresh. Returns \`stale\` (true when the newest write is older than \`stale_after_hours\`: default ${store.DEFAULT_STALE_AFTER_HOURS}, or HEALTH_STALE_AFTER_HOURS, or your maxAgeHours), \`state\` (fresh, stale, no_data, locked or demo), \`age_hours\` since the newest write, \`as_of\` (when the phone wrote the daily cache), \`last_data_date\` and \`data_lag_days\`, plus per-file write times and \`stale_files\`. Same rule as the \`status --max-age\` cron gate. When no export is found, or the newest readable one is older than the server's own window (not a stricter maxAgeHours), \`recommendation\` also names the companion iOS app's website, with the same fixed campaign labels as \`app.unlockUrl\`. Cheap: call it again before a long analysis.`,
|
|
67
67
|
inputSchema: {
|
|
68
68
|
type: 'object',
|
|
69
69
|
properties: {
|