Skip to main content
Glama

health-mcp

Ask your AI chatbot about your own health data, from a server you own.

License: MIT Node >=22 MCP · read-only Cloudflare Workers · Node · Docker Android · iPhone

An AI assistant answering "How did I sleep and train this week?" with health-mcp tool calls (sample data)

health-mcp takes the steps, calories, weight, body fat, workouts and nutrition your phone already collects (iPhone or Android) and makes them available to any AI chatbot that supports MCP connectors. Sleep can be added as an option with the Android apps. Your data stays on your server.

Not medical advice. See Disclaimer.

Quick start · Troubleshooting · Hosted version

How it works

  • Your phone app sends your data. An export app on your phone (HC Webhook or Life Dashboard Companion on Android, Apple Shortcuts on iPhone, built in with no extra app) posts your health data to your server.

  • Your server keeps it. health-mcp stores one row per metric per day in a database that you control.

  • Your chatbot connects. You add your server to your chatbot as an MCP connector. It can read your data, but never change it.

Related MCP server: apple-health-shortcuts-mcp

Quick start

What you need

  • A computer with a terminal

  • Node.js 22 or newer

  • A free Cloudflare account

  • The phone app: HC Webhook or Life Dashboard Companion (Android), or Apple Shortcuts (iPhone, built in)

  • A chatbot that supports MCP connectors

  • About 15 minutes

The terminal is the app where you type commands: Terminal on Mac, PowerShell on Windows. Keep it open for every step below.

1. Deploy your server

The simplest option is Cloudflare Workers with a D1 database. Copy and paste these commands one at a time:

git clone https://github.com/Airbloomer/health-mcp.git
cd health-mcp
npm install
cp wrangler.example.toml wrangler.toml
npx wrangler login
npx wrangler d1 create health-mcp

The last command prints a database_id. Open wrangler.toml in a text editor and paste that value in place of replace-with-d1-database-id. While you are there, change HEALTH_TIMEZONE to your own time zone, for example Europe/Rome.

Optional, Android only: sleep is not stored by default. To keep sleep from HC Webhook or Life Dashboard Companion, add "sleep_analysis" to HEALTH_METRIC_ALLOWLIST in wrangler.toml (it's already there, commented out, in wrangler.example.toml). Remove the # from that line and set it to HEALTH_METRIC_ALLOWLIST = "sleep_analysis", or add sleep_analysis to its comma-separated list, rather than adding a second HEALTH_METRIC_ALLOWLIST line.

Then create the database tables. Do this before you deploy:

node src/cli/d1-migrate.js --remote

✅ Checkpoint: the command prints 0001_init.sql through 0006_ingest_backfill_deadline.sql, with no error. --remote or --local is required.

Prefer your own machine, a VPS, or Docker? See Self-hosting for the Node.js and Docker options (SQLite or Postgres).

2. Create your token

The token is the password for your server. Generate a random one:

openssl rand -hex 32

Copy the line it prints. That is your <HEALTH_INGEST_TOKEN>. Save it somewhere private, such as a password manager. Then store it on your server and deploy:

npx wrangler secret put HEALTH_INGEST_TOKEN
npx wrangler deploy

Paste the token when secret put asks for it. wrangler deploy prints your server's address, something like https://health-mcp.<your-subdomain>.workers.dev. Copy it. That address without https:// is your <your-host>.

Anyone with this token can read and write your data. Never share it or post it in a screenshot. More in Security.

✅ Checkpoint: ask your server for a day's summary (on Windows, type curl.exe instead of curl):

curl -H "Authorization: Bearer <HEALTH_INGEST_TOKEN>" "https://<your-host>/summary?date=2026-01-01"

You should get back JSON that starts with {"date":"2026-01-01", with empty values for now. {"error":"unauthorized"} means the token doesn't match.

3. Connect your phone

Both apps read Health Connect, so anything Health Connect already collects (for example from a smart scale or a fitness band app) can be sent. Install one of them, grant it Health Connect read access, and add a webhook:

  • HC Webhook: URL https://<your-host>/ingest/hc-webhook, format JSON.

  • Life Dashboard Companion: on the Health tab, URL https://<your-host>/ingest/life-dashboard. Leave Daily totals in payload on.

Then add a header with key Authorization and value Bearer <HEALTH_INGEST_TOKEN> (the word Bearer, a space, then the token). Run a sync (in Life Dashboard Companion, tap Test ping, then Sync Now).

Some setups can't send a header, for example a URL added by QR pairing in Life Dashboard Companion. In that case, create a second, separate token and put it in the URL:

openssl rand -hex 32
npx wrangler secret put HEALTH_INGEST_URL_TOKEN

Then use the path form: https://<your-host>/ingest/hc-webhook/<HEALTH_INGEST_URL_TOKEN> (or /ingest/life-dashboard/<HEALTH_INGEST_URL_TOKEN>).

Security note: this URL token can only send data, never read it. Still, a URL can end up in logs, history, screenshots and QR codes, and Cloudflare's request logs keep the full URL. Prefer the header when you can. Never put your main HEALTH_INGEST_TOKEN in a URL. If the URL may have leaked, run npx wrangler secret put HEALTH_INGEST_URL_TOKEN again with a new value. See Security.

Full setup for both apps: Data sources.

Shortcuts is built into iPhone, so there's no extra app to install. A shortcut reads your Health data in daily mode (Find Health Samples with Group By set to Day, one total per metric per day) and sends it to https://<your-host>/ingest/shortcuts with the header key Authorization and value Bearer <HEALTH_INGEST_TOKEN> (the word Bearer, a space, then the token).

Full setup: Shortcuts guide

Note: these steps are tested against the server but not yet verified on a device.

Prefer a dedicated app? See Data sources.

✅ Checkpoint: after your first sync (on iPhone, run the shortcut once), run the same curl command from step 2 with yesterday's date (YYYY-MM-DD). You should now see numbers, such as "steps", instead of empty values.

4. Connect your chatbot

Every chatbot that supports remote MCP servers needs the same two things:

  • Server URL: https://<your-host>/mcp

  • Authorization header: Bearer <HEALTH_INGEST_TOKEN>

Your server must be reachable on the public internet (the workers.dev address is).

Go to Customize > Connectors, click Add, then add a custom connector. Enter a name and the server URL, choose No sign in, and under Request headers add Authorization with the value Bearer <HEALTH_INGEST_TOKEN>. Request headers are a beta feature; see Claude's help article. On Team and Enterprise plans, an owner adds the connector first.

Not supported yet: ChatGPT custom connectors need OAuth; health-mcp uses a bearer token. OpenAI's custom MCP server documentation lists OAuth or no authentication as the only options.

Add a remote MCP server with the URL and header above. A Cursor mcp.json example is in the API reference.

✅ Checkpoint: the connector shows four tools: get_daily_summary, get_metric, list_metrics, and list_workouts.

5. Try it

Start a new chat with the connector turned on and ask:

  • "How did I train this week?"

  • "How many steps did I take yesterday?"

  • "How did I sleep this week?" (Android, with sleep enabled)

  • "Show my weight over the last 30 days."

Your data appears only after the phone app's first sync. The sleep question works only with Android, after you enable sleep with the optional HEALTH_METRIC_ALLOWLIST setting in step 1.

✅ Checkpoint: the chatbot calls a health-mcp tool and answers with your numbers.

Health Auto Export should post recent days, not a full history and not today alone. Create two REST API automations (the app allows one data type each) that both post to https://<your-host>/ingest with the header Authorization: Bearer <HEALTH_INGEST_TOKEN>.

  • Format: JSON, export version 2.

  • Metrics: Dietary Energy, Protein, Carbohydrates, Total Fat, Active Energy, Basal Energy Burned, Step Count, Weight, and Body Fat Percentage. Use one total per metric per day, not hourly buckets.

  • Date range: today and yesterday. Yesterday is the finished day. Today is the running total, sent again as the day fills in. A range of today only never sends yesterday's finished total. After the initial import window closes, points older than yesterday are dropped (INGEST_MAX_AGE_DAYS, default 1, counted in the configured time zone). A date more than one day ahead is dropped too. Dates before INGEST_MIN_DATE (default 2000-01-01) are dropped as well.

  • Schedule: a few times a day, not every hour. The two automations share one token, and each run is a post. The default limit is 6 accepted posts per token per hour (INGEST_RATE_LIMIT_PER_HOUR).

  • Workouts: a second automation with the same URL, header, format, version, and date range. Leave route and per-second workout metrics off.

  • Batch requests: leave them off, so one day is not split across posts.

A one-time full history can be posted here during the initial import window, or at any time on POST /import/apple-health. Import that history in chunks of 5 MB or less. An Apple Health import is stored in batches of at most 366 dates, and it is not rejected because of the date count. That import does not open, close, or seal the initial import window, it does not draw down the window byte total, and it does not apply the recent-day filter. It still drops dates before INGEST_MIN_DATE. On D1, a request that would run more than 45 statements returns 413 and names node src/cli/import-apple-health.js --chunk-url. SQLite and Postgres do not use that statement budget. Setup details are in Data sources.

Ingest limits

The first accepted ingest opens an initial import window. There is no separate setup step. Keeping past dates is limited to Health Auto Export JSON on POST /ingest. The raised JSON body cap and the higher hourly cap apply to JSON ingest routes while the window is open. Each of those bodies may be up to INGEST_BACKFILL_MAX_BYTES (default 5,000,000, and never above 10,000,000), and each token may send INGEST_BACKFILL_MAX_PER_HOUR posts (default 60). Import history in chunks of that size or less. The hint text uses the configured cap, and the default wording is 5 MB. The window still rejects a date more than one day ahead, and a date before INGEST_MIN_DATE (default 2000-01-01). A configured minimum before 1970-01-01 is raised to that day, a configured minimum after today is lowered to today in UTC, and a value that is not a real calendar day uses the default. A JSON ingest request may cover at most 366 distinct dates. A total of INGEST_BACKFILL_MAX_TOTAL_BYTES (default 500,000,000) applies to non-Apple JSON bodies while the window is open. An Apple Health import does not draw that total down. Unchanged days are still skipped.

A request whose content-type does not contain json stays on the steady size cap. An Apple Health export on POST /ingest uses the 20,000,000 byte cap, not the import-window per-request cap.

The window closes for good at the first of these: the deadline stored when it opened (INGEST_BACKFILL_HOURS, default 72), the total stored when it opened, or a request with "backfill": "done" or the header X-Backfill: done. Changing those settings later does not extend a window that is already open, and it does not reopen a closed one. A database that already holds points, workouts, or raw payloads receives a closed row when its schema is updated, so those rows do not start a window. A database that already has non-Apple data and no window row is treated as closed. An Apple Health import does not open, close, or seal the window, and it does not draw down the window byte total. An Apple import before the first accepted ingest still lets that ingest open the window. On D1, one request may run at most 45 statements. INGEST_MAX_STATEMENTS can only lower that ceiling. A Health Auto Export request over the budget returns 413 and names POST /import/apple-health/chunk. SQLite and Postgres do not apply this budget. A missing row is not an open window, and a response before that row exists reports it closed. A failed write does not reopen a closed window. Postgres keeps each request's reservation and its writes on one connection, so a failure rolls all of them back. SQLite and D1 keep the bytes for each group that was stored. After the window is closed, bytes_remaining is 0 and the steady limits apply again: today and yesterday, INGEST_MAX_BODY_BYTES (default 1,000,000), and INGEST_RATE_LIMIT_PER_HOUR (default 6). Every ingest response includes backfill with open, closes_at, and bytes_remaining. A chunk that is too large during the window returns 413 and asks for a shorter date range. After the window is closed, an old date returns 422 and an oversized or too-frequent post returns 413 or 429, and the message says the initial import window is closed.

MemoryStorage keeps this window in the process only. It is not persistent. A new process has no row and no stored data, so the next stored point can open a window. A restart does not keep a closed window.

Troubleshooting

  • The chatbot or phone app says unauthorized (401). Check that the header is exactly Bearer, one space, then the token. The URL token works only on three POST routes: the two Android webhook routes (/ingest/hc-webhook, /ingest/life-dashboard) and /ingest/shortcuts. It never works for /mcp or /summary.

  • Sending data fails right after deploying. Run node src/cli/d1-migrate.js --remote (or --local for a local database), then npx wrangler deploy again.

  • A day is missing or shows up on the wrong date. Set HEALTH_TIMEZONE in wrangler.toml to your zone, then run npx wrangler deploy again.

  • Totals look too low. Send whole days. On the app routes that compare sample counts, a later sync with fewer samples for a day does not replace a fuller one. A Shortcuts daily total sent with the Bearer token replaces the day. See Data sources.

  • Nothing arrives from the iPhone. Run the shortcut manually with the phone unlocked, and check the Authorization header.

  • export_hint is set on a summary. A recent post looked like too much history, every date in a post was too old, or posts arrived too often. Follow Recommended export settings. A later accepted post clears it.

More documentation

  • API reference: routes, payload formats, limits, and MCP tools

  • Data sources: every supported exporter and full app setup

  • Self-hosting: Cloudflare, Docker, Node, configuration, migrations, development

  • Security: tokens, rotation, URL-token caveats, logs, and browser clients

Disclaimer

health-mcp is an independent open-source project by Airbloomer, LLC. It is not affiliated with, endorsed by, or sponsored by Apple Inc. or Google LLC. Apple, iPhone, Apple Watch, Apple Health, HealthKit and Shortcuts are trademarks of Apple Inc., registered in the U.S. and other countries. Google, Android and Health Connect are trademarks of Google LLC. They are used here only to describe compatibility.

health-mcp is not a medical device and does not provide medical advice, diagnosis or treatment. Talk to a qualified professional about any health decision.

health-mcp is self-hosted software. You run it and you control your data, and you are responsible for securing your server. Airbloomer, LLC does not host, receive or access your health data.

Any third-party apps mentioned in this project's docs are owned by their respective owners and are not affiliated with or endorsed by Airbloomer, LLC. Use them under their own terms and privacy policies.

Provided under the license in LICENSE, "as is", without warranty of any kind.

Hosted version

We're exploring a hosted health-mcp for people who don't want to run their own server. It isn't available, and there's no commitment to launch it, or to any pricing or timing. To hear if it happens, email oss@airbloomer.com with the subject 'hosted waitlist'. Please don't include any health information in your email. We'll use your email address only to contact you about a hosted version, keep it no longer than 24 months, and delete it sooner if you ask (reply 'remove' at any time). Details: https://airbloomer.com/privacy. Nothing in this section changes the self-hosted software: Airbloomer, LLC still does not host, receive or access any of your health data.

License

MIT

Related MCP Connectors

Related MCP Servers