Bem Estar MCP Server
Ingests Apple Health exports from Health Auto Export and exposes health metrics such as sleep, HRV, resting heart rate, readiness, recovery, training load, and workouts through MCP tools and resources.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Bem Estar MCP Servercompare my sleep and training load this week to last week"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Bem Estar ingests Apple Health exports from Health Auto Export, keeps the raw evidence, normalizes it into a canonical SQLite model, computes deterministic and versioned metrics, and exposes the result through a bilingual dashboard, a REST API, and an MCP server for AI clients.
It is a single-user, local-first platform: no telemetry, no vendor account, no data broker.
What it is not: not a medical device, not a diagnosis tool, and not a hosted service. Metric outputs are operational signals for your own routine, not medical advice.
Table of contents
Related MCP server: Apple Health Shortcuts MCP
Why Bem Estar
Privacy by construction | Raw payloads, the canonical database and derived metrics live in SQLite on your host. The public surface is a Caddy reverse proxy and the endpoints you choose to expose. |
Deterministic, explainable metrics | Readiness, recovery, sleep, TRIMP, CTL/ATL/TSB and ACWR are computed from explicit formulas and stored with algorithm versions and input hashes. |
MCP-native | The same application services power REST, the dashboard and MCP resources/tools/prompts. AI clients get typed access without a second business-logic layer. |
Boring operations | One SQLite writer, systemd units, Caddy for TLS, Prometheus/Grafana optional. Docker is used only for observability, not for the application runtime. |
No subscription | MIT licensed, self-hosted, and designed for a small VPS or a home server. |
Screenshots
Overview | Training load | Ingestion |
Profile and settings | Mobile (390 px) |
The login screen is intentionally minimal; the dashboard is a PWA-style single-page app with PT-BR and English strings.
What it does
Surface | Capabilities |
Ingest | HAE v2 webhook and compatibility route, bearer-token authentication, streaming body ceiling, durable compressed raw inbox, idempotent ingestion rows, reconciliation updates, worker leases. |
Analyze | Sleep duration/efficiency, HRV and resting-HR baselines, heart-rate zones, TRIMP (Banister/Edwards), CTL/ATL/TSB, ACWR, recovery and morning/live readiness, VO2max and workout trends. |
Dashboard | React + Vite PWA, bilingual PT-BR/EN, readiness/recovery/sleep/HRV/RHR/load cards, training load, ingestion history, profile controls, mobile layout at 390 px. |
MCP | Stateless Streamable HTTP, separate bearer token, tools for summaries/history/sleep/load/workouts/period comparison/ingestion/recompute, resources and prompts. |
Operate | systemd units, Caddy example with body/rate limits, Alembic migrations, backup/restore scripts, 180-day raw retention, Prometheus metrics and Grafana dashboards. |
Protect | Signed HttpOnly session cookie + CSRF, constant-work password verification, bounded rate-limit keys, readiness disk floor, secrets excluded from backups by default. |
Quick start
Requirements
Python 3.12–3.14
SQLite 3.35+
Node.js 22+ and npm (dashboard only)
Optional:
uvfor the locked dependency set
1. Clone and install
git clone https://github.com/luiz-izidoro/bem-estar.git
cd bem-estar
python3 -m venv .venv
. .venv/bin/activate
pip install -e '.[dev]'For the exact dependency set used by CI and the release gate, use the committed lockfile instead:
uv sync --frozen --extra dev2. Create the environment file
cp .env.example .env
python scripts/generate_secrets.pyThe generator prompts for a dashboard password (12+ characters) and prints four lines:
BEMESTAR_WEBHOOK_TOKEN, BEMESTAR_MCP_TOKEN, BEMESTAR_SESSION_SECRET and
BEMESTAR_DASHBOARD_PASSWORD_HASH. Replace the four CHANGE_ME values in .env with those lines.
BEMESTAR_TIMEZONEcontrols daily aggregation. Set it to your IANA zone (for exampleAmerica/Sao_PauloorEurope/Lisbon) before ingesting data.
3. Migrate and run
alembic upgrade head
uvicorn bemestar.interfaces.api.app:create_app --factory --reload --port 8787The first migration creates var/ and the SQLite database. In a second terminal, start the worker:
. .venv/bin/activate
python -m bemestar.interfaces.workerStart the dashboard in a third terminal:
cd dashboard
npm ci
npm run devOpen http://localhost:5173, sign in with the dashboard username (admin by default) and the
password you chose. The Vite dev server proxies /api and /auth to the API on port 8787.
4. Verify
curl -fsS http://127.0.0.1:8787/healthz
curl -fsS http://127.0.0.1:8787/readyzExpected: {"status":"ok"} and {"status":"ready","storage":"ok"}.
Run the full local quality gate:
make checkBuild the dashboard for production with make dashboard-build. The stricter release gate, which also
checks the empty-database migration and the dashboard build, is:
./scripts/verify_release.shConfiguration
All runtime settings use the BEMESTAR_ prefix and can be supplied through environment variables
or .env. The committed .env.example is the source of truth; the table below
covers the settings most operators change.
Variable | Default | Description |
|
| Set to |
|
| IANA zone used for local-day aggregation and sleep boundaries. |
|
| Public origin; |
|
| SQLite URL. Production uses |
|
| Compressed durable inbox for raw HAE payloads. |
|
| CSV of allowed |
|
| CSV of allowed browser origins. |
|
| Bearer token for the HAE webhook. Generate it. |
|
| Separate bearer token for MCP clients. Generate it. |
|
| Signs dashboard session cookies. Generate it. |
|
| Dashboard login name. |
|
| PBKDF2 hash produced by |
|
| Retention for successfully processed raw payloads. |
|
|
|
| empty | Optional OpenAI-compatible provider for narrative insights; deterministic fallback stays active. |
Secrets must never be committed. generated-config/, .env, var/ and databases are gitignored.
See docs/17-PORTALS-AND-CREDENTIALS.md for credential
rotation and production file permissions.
Architecture
flowchart LR
HAE["Health Auto Export<br/>iPhone"] -->|"HTTPS webhook<br/>Bearer token"| API["FastAPI REST<br/>:8787"]
API --> RAW[("Durable raw inbox")]
API --> QUEUE[("SQLite<br/>ingestion queue")]
WORKER["Worker"] --> QUEUE
WORKER --> DB[("Canonical SQLite<br/>samples, workouts, derived metrics")]
API --> DB
DB --> DASH["React PWA dashboard<br/>:5173 dev / Caddy prod"]
DB --> MCP["MCP server<br/>:8788"]
MCP --> CLIENT["MCP client"]
API --> PROM["Prometheus"]The dependency direction is enforced by scripts/verify_architecture.py:
interfaces -> application -> domain
adapters -> application -> domain
infrastructure may be used by adapters/interfaces bootstrap onlydomain/is pure Python: metric formulas, entities, no FastAPI, SQLAlchemy, I/O or settings.application/owns use cases, ports and the repository gateway.adapters/translate HAE, LLM and notification providers into canonical commands.interfaces/expose REST, MCP and the worker. They never implement metric formulas.SQLite is the only persistence adapter. One writer is expected: run one worker and one API process per database. Do not share the SQLite file between hosts.
The full model is in docs/03-DATA-MODEL.md; metric formulas and versions
are in docs/04-METRICS-SPEC.md.
Apple Health ingestion
Install Health Auto Export on the iPhone and create a REST API automation.
Point it to
https://<your-host>/webhooks/hae/v2.Add
Authorization: Bearer <BEMESTAR_WEBHOOK_TOKEN>.Use JSON export v2, enable Batch Requests, and set a stream header:
X-Bemestar-Stream: vitals | activity | sleep | workouts | history | reconciliation.Prefer bounded time ranges (for example "Since Last Sync") over multi-year full exports.
The machine-readable plan is in config/hae/automation-plan.yaml;
the step-by-step guide is docs/16-HAE-WEBHOOK-SETUP.md.
curl -fsS -X POST http://127.0.0.1:8787/webhooks/hae/v2 \
-H "Authorization: Bearer $BEMESTAR_WEBHOOK_TOKEN" \
-H "X-Bemestar-Stream: vitals" \
-H "Content-Type: application/json" \
--data @tests/fixtures/hae_v2_sample.jsonThe compatibility alias /v1/health-auto-export accepts the same payloads during migration.
REST API
Endpoint | Purpose |
| Dashboard login; sets HttpOnly session and CSRF cookies. |
| Daily summary: derived metrics, raw values, versions, workouts. |
| Metric history for charting. |
| Recent workouts. |
| Ingestion queue/history. |
| Recompute derived metrics (CSRF required). |
| Liveness and readiness (disk floor). |
| Prometheus exposition; keep it on loopback. |
Interactive OpenAPI docs are served at /docs outside production.
MCP server
The MCP server exposes the same application services over stateless Streamable HTTP on port 8788. Use a separate bearer token from the HAE webhook.
Tool | Returns |
| Readiness, recovery, sleep and load for a local day. |
| Daily values and versions for a metric. |
| Sleep duration, efficiency, stages and debt context. |
| TRIMP, CTL, ATL, TSB and ACWR. |
| Workout summaries and per-workout insights. |
| Day-aggregated period comparison. |
| Recent ingestion rows and errors. |
| Explicit recomputation. |
Example client configuration:
{
"mcpServers": {
"bem-estar": {
"url": "https://bemestar.example.com/mcp",
"headers": {
"Authorization": "Bearer ${BEMESTAR_MCP_TOKEN}"
}
}
}
}Example prompts once connected:
"Como está minha prontidão hoje? Explique os componentes e a confiança."
"Compare meu sono dos últimos 7 dias com os 7 anteriores."
"Minha carga de treino está subindo rápido demais para o meu histórico?"
Contract details and OAuth migration notes are in
docs/09-API-MCP-CONTRACT.md and
docs/14-MCP-CLIENT-INTEGRATION.md.
Deployment
Mode | Use it when | Entry point |
Local development | You are evaluating or developing |
|
systemd + Caddy | You want a private always-on server | |
Docker (observability only) | You want Prometheus/Grafana without packaging the app |
|
Production expects Ubuntu 26.04 LTS, Caddy terminating TLS, a dedicated bem-estar service user,
/opt/bem-estar/releases plus a current symlink, /etc/bem-estar/*.env and
/var/lib/bem-estar. The staging and activation helpers are:
./scripts/bootstrap_server.sh
./scripts/install_release.sh "$(readlink -f /opt/bem-estar/current-next)"The application intentionally does not require Docker. Grafana and Prometheus are optional and run as containers; the app stays under systemd.
Security and privacy
This repository processes health data. Treat the database, raw payloads, backups and environment files as sensitive. Never attach them to an issue, pull request or release.
Dashboard: username/password → signed HttpOnly session cookie; CSRF for mutations.
HAE and MCP use separate bearer tokens; never reuse one secret across surfaces.
The API binds to
127.0.0.1; Caddy is the only public ingress.Raw payloads are never served as static files; backups exclude secrets by default.
Production refuses placeholder secrets and disables API docs.
Report vulnerabilities privately through GitHub Security Advisories; see
SECURITY.md.
Operations
Backups:
scripts/backup.shuses SQLite's backup API, compresses raw data, and can copy to an off-host rclone target. Restore withscripts/restore.sh; test restores on a disposable copy.Retention: processed raw payloads are pruned after
BEMESTAR_RAW_RETENTION_DAYS; canonical and derived data are kept unless an explicit policy changes that.Readiness:
/readyzreports storage and free-disk status;/metricsexposes Prometheus counters and latency histograms.Logs: error logs identify ingestion IDs, never payload contents. Raw health data is not logged.
See docs/08-OPERATIONS.md for timers, backup drills and maintenance.
Troubleshooting
Symptom | Likely cause | Fix |
|
| Use the default SQLite URL or |
Dashboard login returns 401 |
| Re-run |
| A value was written as JSON instead of CSV | Use |
API returns 421 |
| Add the hostname (comma-separated) and restart the API. |
| More than one writer/process shares the SQLite file | Run one API and one worker per database. Do not share the file across hosts. |
Dashboard shows no data | Worker not running or | Start the worker, check |
MCP returns 401 | Wrong token or HAE token used | Use |
Documentation map
Document | Contents |
Product scope, actors, functional requirements. | |
Layers, dependency rules, process topology. | |
Canonical tables, identities, migrations. | |
Metric formulas, versions and parameters. | |
Deployment layout and cutover. | |
Threat model and controls. | |
Backups, retention, observability, maintenance. | |
REST/MCP contracts. | |
iPhone automation setup. | |
Credentials and rotation. | |
First production deploy. | |
Hardening audit. | |
GitHub settings and release checklist. | |
Operating contract for AI coding agents. |
Contributing
See CONTRIBUTING.md for setup, architecture rules, metric-change protocol and
the pull-request checklist. Security reports must follow SECURITY.md and must not
be filed as public issues.
License
MIT. See LICENSE. Third-party dependencies, runtime services and trademark attribution
are listed in THIRD_PARTY_NOTICES.md.
Bem Estar is independent and is not affiliated with, endorsed by, or sponsored by Apple Inc., Health Auto Export, or any MCP client vendor. Apple Health and Apple Watch are trademarks of Apple Inc.; product names are used descriptively only.
This server cannot be deployed
Maintenance
Related MCP Connectors
Your Garmin, Oura, WHOOP, Hevy and 35 more fitness apps, queryable from your own AI.
- SomviaOAuthapp.somvia
Apple Health training load, recovery, HRV and workout detail for Claude, ChatGPT and any MCP client.
Your Apple Health data, food diary and lifts, for ChatGPT, Claude and Gemini. Approved on iPhone.
- freddyOAuthcoach.freddy
Connect your wearables, rings and training apps, then ask your AI about your own health data.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceLoads Apple Health export data into a local SQLite database and exposes tools to query health metrics and workout records via natural language.26 PyPI3MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI to query Apple Health data through three read-only tools: current status, detailed sleep/metrics, and trends over 7/14/30 days. It deploys to Cloudflare quickly, keeping health data private and access-controlled.MIT
- AlicenseNot gradedqualityCmaintenanceExposes Apple Health data as three read-only MCP tools (now, detail, trends) for AI to query health metrics, sleep, and trends via HTTP.MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to query personal health data collected by any wearable or phone fitness app through Android Health Connect or Apple Health, exposing read-only tools for metrics like heart rate, sleep stages, VO2 max, and body composition. It can run locally over USB with nothing stored, or be self-hosted on Cloudflare Workers with D1 to retain history beyond Health Connect's 30-day limit.20 npmMIT