health-mcp

health-mcp

A local-first MCP server that stores nutrition, biomarker, and wearable data in SQLite and exposes it as Model Context Protocol tools, enabling MCP-aware agents to log meals, query trends, and run correlations.

Category
Visit Server

README

<p align="center"> <img src="docs/health-mcp-logo.png" alt="health-mcp logo" width="160" /> </p>

health-mcp

Your personal health database. Your agent does the typing.

A local-first server that stores your nutrition, biomarker, and wearable data. The whole thing is exposed as Model Context Protocol tools, so any MCP-aware agent (Hermes, OpenClaw) can read and write it. A web dashboard ships in the same process for when you want to look at the data instead of talk to it.

Everything runs on your machine. One SQLite file. No accounts, no SaaS, no telemetry.

<p align="center"> <img src="docs/dashboard-today.png" alt="health-mcp dashboard, Today view" width="900" /> </p> <p align="center"> <sub>The bundled dashboard's <strong>Today</strong> view: macros vs goals, sleep / recovery / weight / hydration, and the day's meals.</sub> </p>

<p align="center"> <img src="docs/architecture.png" alt="health-mcp architecture: an MCP agent and a browser dashboard both talk to the health-mcp Node process, which stores data in SQLite and syncs Whoop / Oura over OAuth2" width="900" /> </p>

Instead of building another photo-CV calorie app or a freeform-text parser, you let the agent handle the squishy parts ("I had two eggs and toast", "log this lab PDF", "what affects my sleep score?") and let this server handle the durable parts: typed schema, atomic transactions, range queries, a capability-gated tool surface, and a UI that doesn't lie about the data underneath.


What it tracks

Nutrition. Foods (USDA, Open Food Facts, manual entries), meals composed of food / recipe-serving / batch / custom components, hydration, weight, body measurements, macro goals as {min, max} bounds, and daily / weekly rollups.

Recipes and cooked batches. Recipes scale to per-serving macros. A batch is a cooked instance that depletes as you eat against it, with atomic decrements inside log_meal and refunds on delete.

Remembered meals. Label your usual breakfast once, relog it in a single tool call. Holds either pre-resolved components (deterministic) or canonical free text (the agent re-estimates on each call).

Biomarkers and labs. About 60 curated biomarkers seeded with LOINC codes, default units, and reference + optimal ranges. Lab panels insert atomically with all their results. A three-tier range walk decides each result's status: per-result lab snapshot → per-biomarker default → curated optimal. Unit conversion for the common dual-unit pairs (mg/dL ↔ mmol/L, ng/mL ↔ nmol/L, etc.). Trend and "latest per marker" queries.

Wearables. Whoop and Oura over OAuth2 today. Each provider mirror keeps the raw payload (raw_json per row) so a future migration can promote any field to a normalized column without re-syncing. Normalized tables (wearable_sleep, wearable_activity, wearable_readiness, wearable_daily) let you read across vendors without caring which one is connected. Refresh tokens rotate; concurrent 401s can't double-spend because the auth store is mutex-guarded per provider.

Insights. correlate runs Pearson or Spearman over any two metric series, bucketed by day / week / month. Signed lag buckets shift one series in time. Forward-fill carries the last value through gaps so sparse lab data correlates cleanly against daily wearable scores. The tool stays hidden in the agent's catalog until there's enough data to be meaningful.


Quick start

npx

Node ≥ 20, no clone or build:

npx health-mcp            # http://127.0.0.1:7777, opens the dashboard
npx health-mcp --stdio    # headless MCP server over stdio

State lives in ~/.health-mcp/ (one SQLite file). npx health-mcp --help lists every flag; npx health-mcp doctor runs a self-check.

Docker

git clone https://github.com/lukaisailovic/health-mcp.git
cd health-mcp
cp .env.example .env

# Put a strong token in .env (required to bind off-loopback)
openssl rand -hex 32

docker compose up -d
open http://127.0.0.1:7777

Data persists in the health-mcp-data named volume. If you'd rather see the files on disk, swap the volume mapping in docker-compose.yml for ./.health-mcp-data:/data.

docker compose down stops the container; data survives across restarts.

Prefer the prebuilt image over building locally? Point docker-compose.yml at the published image and drop its build: block:

image: ghcr.io/lukaisailovic/health-mcp:latest   # or pin :0.1.0 / :0.1

Each release pushes :X.Y.Z, :X.Y, and :latest; :main tracks the newest commit. All images carry a build-provenance attestation. See Releasing.

From source

Node ≥ 20 and pnpm.

git clone https://github.com/lukaisailovic/health-mcp.git
cd health-mcp
pnpm install
pnpm build
pnpm start                      # http://127.0.0.1:7777, browser opens automatically

For development with hot reload on the dashboard, shared types, and the server, run all three in watch mode:

pnpm dev
# server on :7777, dashboard dev on :5173 (proxies /api/* to :7777)

Pass --no-open to keep the browser shut, or --no-dashboard to run as a headless MCP / REST server.

Subcommands

pnpm start -- migrate                  # apply pending DB migrations and exit
pnpm start -- doctor                   # self-check (DB pragmas, file modes, token entropy)
pnpm start -- export /tmp/dump.jsonl   # JSONL dump; raw_json redacted unless --include-raw
pnpm start -- import-usda dump.json    # ingest a USDA FoodData Central bulk JSON

Connect an MCP agent

Hermes / OpenClaw (stdio)

Both use the standard MCP config, so the setup is identical: add health-mcp to the agent's mcpServers. No clone or build needed; point it at the published package:

{
  "mcpServers": {
    "health": {
      "command": "npx",
      "args": ["-y", "health-mcp", "--stdio"]
    }
  }
}

Where that block lives differs by agent; check its MCP settings for the path.

Then ask your agent:

  • "Log eggs and toast for breakfast"log_meal
  • "How's my fasting glucose trending?"biomarker_trend
  • "Does my protein intake correlate with Whoop recovery the next day?"correlate with lag_buckets: 1

Running from a local checkout instead? Use "command": "node" with "args": ["/path/to/health-mcp/apps/server/dist/index.js", "--stdio"] after pnpm build, or "args": ["--import", "tsx", "/path/to/health-mcp/apps/server/src/index.ts", "--stdio"] to skip the build.

MCP Inspector

cd apps/server
pnpm inspect

Opens the MCP Inspector against a stdio child for poking at tools by hand.

HTTP / custom client

The Streamable-HTTP transport is mounted at POST /mcp on the same port as the dashboard. Point any HTTP-aware MCP client at http://127.0.0.1:7777/mcp and send Authorization: Bearer <HEALTH_MCP_TOKEN> when a token is set.

The first OAuth link to a wearable provider needs the HTTP server running so the callback route can receive the redirect. Once linked, refresh tokens persist in auth.json and stdio mode can sync from there indefinitely.


The dashboard

Served at / from the same process. Pages today:

  • Today — meals for the day, totals against goals, hydration, weight
  • Log — add meals, hydration, weight, body measurements
  • Foods, Recipes, Batches — the food graph
  • Goals — macro bounds, weight target
  • Labs — panels, results, trends, per-biomarker About card
  • Trends — weekly rollups
  • Wearables — provider status, sleep / activity / readiness / daily reads
  • Insightscorrelate UI
  • Settings — token, timezone, theme

Built on TanStack Router + Query, Kumo UI over Tailwind v4, and Recharts. Dark mode follows your OS by default; you can pin it in Settings.


Configuration

Precedence: CLI flag > env var > JSON config file > default.

Env var Purpose Default
HEALTH_MCP_TOKEN Bearer token. Required to bind off-loopback. unset (loopback-only)
HEALTH_MCP_PORT HTTP port 7777
HEALTH_MCP_HOST Bind host 127.0.0.1
HEALTH_MCP_DATA_DIR Storage dir for data.db and auth.json ~/.health-mcp
HEALTH_MCP_TZ IANA timezone for day-bucket queries system TZ
HEALTH_MCP_WHOOP_CLIENT_ID / _SECRET Whoop OAuth app credentials
HEALTH_MCP_OURA_CLIENT_ID / _SECRET Oura OAuth app credentials
HEALTH_MCP_USDA_API_KEY Enables USDA FoodData Central remote search local search only
HEALTH_MCP_DASHBOARD Serve the dashboard at / true
HEALTH_MCP_LOG_LEVEL debug / info / warn / error info

Every flag, every env var, the JSON config-file schema, and the security invariants enforced at startup are in docs/CONFIGURATION.md.


Privacy and security

The server is built to fail closed.

  • Loopback is the only safe default. To bind anywhere else you must set HEALTH_MCP_TOKEN to a 32+ character high-entropy string (openssl rand -hex 32); the server refuses to start otherwise, with no soft fallback.
  • data.db and auth.json are created 0600 inside a 0700 parent. Looser modes refuse to open unless you pass --allow-insecure-db / --allow-insecure-auth.
  • Wearable OAuth credentials live in ~/.health-mcp/auth.json, separate from data.db, so health-mcp export can ship the database without leaking provider tokens.
  • Providers like Whoop rotate refresh tokens on every use. The auth store serializes refresh per provider so two concurrent 401s can't both spend the same token and lock you out.
  • The OAuth callback uses an HMAC-signed state payload with a 10-minute expiry and a single-use nonce persisted in SQLite. No replay, no forgery.

What this protects against, what it doesn't, and how to safely expose the server beyond localhost (TLS-terminating tunnel; check the doctor output) are covered in docs/SECURITY.md.


How it's put together

One Node process. A Hono app mounts the MCP Streamable-HTTP transport at /mcp, the REST mirror at /api/*, the wearable OAuth callback at /auth/wearable/callback, and the dashboard SPA at /. Storage is SQLite via better-sqlite3 with journal_mode=WAL and foreign_keys=ON. All business logic lives in apps/server/src/services/*.ts; MCP tool handlers and REST routes are thin Zod-validated wrappers that delegate. Wearable data flows through a WearableProvider interface that writes raw per-vendor mirrors and normalized cross-vendor tables in one transaction per sync page.

In HTTP mode a cron job (*/30 * * * * by default, configurable) calls syncWearables() for every linked provider. Stdio mode skips the scheduler; agents call sync_wearables on demand.

The service-by-service breakdown, transport plumbing, and capability-gating mechanics are in docs/ARCHITECTURE.md.


Tools surface

About 60 tools. discover_capabilities returns the live catalog grouped by area, with the current enable flag, so agents can call it first instead of guessing what's available.

<details> <summary>Show all tool names</summary>

ping, discover_capabilities

# food
search_food, search_foods, lookup_barcode, get_food
create_custom_food, bulk_upsert_custom_foods, update_custom_food, delete_custom_food

# meals
log_meal, list_meals, get_meal, update_meal, delete_meal, undo_last_meal,
add_meal_component, update_meal_component, remove_meal_component

# recipes + batches
create_recipe, update_recipe, delete_recipe, list_recipes, get_recipe
create_batch, list_batches, get_batch, archive_batch, delete_batch

# remembered meals  (read tools hidden until you save one)
remember_meal, list_remembered_meals, get_remembered_meal,
update_remembered_meal, forget_meal, log_remembered_meal

# simple logs
log_hydration, list_hydration, delete_hydration
log_weight,    list_weight,    delete_weight
log_measurement, list_measurements, delete_measurement
get_goals, set_goals

# summaries
daily_summary, weekly_summary, range_summary

# biomarkers + labs
search_biomarker, get_biomarker, create_custom_biomarker, update_biomarker, set_optimal_range
log_lab_panel, log_lab_result, list_lab_results, latest_biomarkers, biomarker_trend
list_lab_panels, get_lab_panel, delete_lab_result, delete_lab_panel

# insights  (hidden until ≥7 days intake AND (≥1 wearable_daily row OR ≥3 lab_results))
correlate, list_correlate_metrics

# wearables  (most hidden until a provider is linked)
wearables_list_providers, wearables_status,
wearable_connect_url, wearable_disconnect, sync_wearables,
wearable_sleep, wearable_activity, wearable_readiness, wearable_daily, wearable_metric_minutes,
set_activity_type_map

# whoop  (hidden until linked)
whoop_recovery, whoop_cycles, whoop_sleep_raw, whoop_workouts_raw,
whoop_profile, whoop_body_measurement

</details>

Capability gating hides tools the agent can't currently use, so the surface stays small. Wearable reads stay invisible until a provider is linked; correlate stays invisible until there's data worth correlating. The full catalog with parameters, return shapes, and gating rules is in docs/MCP.md.


Documentation

Doc What it covers
Architecture Process layout, transports, service layer, scheduler
Configuration Flags, env vars, JSON config, subcommands, startup invariants
MCP tools Tool catalog, capability gates, item shapes, agent-client wiring
REST API /api/* mirror used by the dashboard
Data model SQLite schema, indexes, raw-vs-normalized wearable split
Biomarkers Three-tier range model, status walk, unit-conversion table
Wearables Provider interface, OAuth flow, refresh rotation, provider matrix
Security Bearer auth, loopback rule, file modes, OAuth state, threat model
Releasing Version bump → tag → npm (OIDC) + GHCR, all from one Actions run

Contributing

Issues and PRs welcome.

pnpm install
pnpm typecheck && pnpm lint && pnpm test

A few ground rules:

  • Business logic lives in apps/server/src/services/. MCP tools (src/mcp/tools/) and REST routes (src/rest/) are thin wrappers around it. Don't put logic in handlers.
  • Migrations are checked-in TypeScript modules under apps/server/src/db/sql/000N-*.ts. Forward-only.
  • Shared Zod schemas live in packages/shared. The server and dashboard agree there.
  • New services want a Vitest case (*.test.ts) or coverage through the integration suite (apps/server/src/integration.test.ts).
  • pnpm lint:fix before pushing — Biome.

Adding a new wearable provider is a self-contained job: create apps/server/src/wearables/providers/<id>/, add a migration with the raw mirror tables, register in the wearable registry. The normalized read tools pick it up automatically. Walkthrough in docs/WEARABLES.md.

Cutting a release is a one-click GitHub Actions run — bump, tag, npm publish, and GHCR tags in one go. See docs/RELEASING.md.


Status

Solo project, actively developed. The data model is stable for nutrition, biomarkers, and Whoop / Oura sync; migrations are forward-only and run on boot. Expect breaking changes to tool parameters and dashboard routes until a 1.0 tag. File an issue if something stops you cold.

This is a personal-use tool, not medical advice or a medical device. The values, ranges, and correlations it surfaces are for self-quantification, not diagnosis.


Tech

Node ≥ 20 · pnpm · TypeScript (ESM, strict) · Hono · @modelcontextprotocol/sdk v1 · better-sqlite3 · Zod · croner · Vitest · Biome.

Dashboard: Vite · React 18 · TanStack Router + Query · Tailwind v4 · Kumo UI · Recharts.


License

MIT.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured