vitals
Vitals — self-hosted WHOOP / Google Health alternative
Your data. Your server. Your AI.
A personal recovery / strain / sleep / body-age dashboard — the kind of thing you'd pay a monthly subscription for — except it runs on hardware you own, reads from wearables you already have (Google Health, Oura, WHOOP, Apple HealthKit), and never sends a byte of your health data to a third-party cloud.
Agent-first install
Already have an AI agent and a machine that's always on? Don't install anything by hand. Paste the prompt below to Claude Code, OpenClaw, Hermes, or any agent that can run shell commands — it clones the repo, installs and launches Vitals, and wires itself in over MCP so it can read your recovery, sleep, and HRV. From zero to a working, agent-connected health server in one message:
Set up Vitals for me end-to-end on this machine, then connect yourself to it over MCP.
Vitals is a self-hosted health dashboard: https://github.com/DocStream-Oficial/vitals
1. Clone it and remember the absolute path as REPO:
git clone https://github.com/DocStream-Oficial/vitals.git
2. Install it (needs Python 3.10+). Run the non-interactive setup so it doesn't block:
cd REPO && python install.py --no-launch --yes
Then start the app in the background using the venv install.py just created
(it prints the exact uvicorn command; venv is .venv/bin on macOS/Linux,
.venv\Scripts on Windows). Confirm the dashboard responds at http://localhost:8700.
To preview it before I add real credentials, launch with demo data instead:
python install.py --demo --yes
3. Wire up the MCP server so YOU can query my data:
pip install -r REPO/requirements-mcp.txt
Register REPO/vitals_mcp.py as a stdio MCP server named "vitals" in your MCP
config, then reload your MCP connections.
4. Verify: confirm the 9 vitals_* tools loaded, call vitals_today, and give me
today's recovery, sleep and strain in one line.
5. Then tell me the one manual step left: which OAuth credentials to add to
REPO/.env to connect my real wearables (Google / Oura / WHOOP) and where to
get them — follow the repo README.
6. Ongoing: send me vitals_morning_brief each morning and vitals_bedtime_brief at
night, and alert me proactively whenever recovery drops below 40% or an
illness-risk insight appears in vitals_insights.That's the whole point of Vitals being MCP-native: your agent doesn't just use it, it installs it. No agent yet? Jump to Quick start for the one-command manual install.
Why Vitals
Wearable dashboards (WHOOP, Oura, Fitbit Premium) are subscription products: your recovery, sleep, and HRV history live on someone else's server, gated behind a monthly fee, and the scoring logic is a black box.
Vitals takes the opposite bet:
Self-hosted, always — Docker Compose or a plain Python venv on a box you control (a spare Mac mini, a home server, a $5 VPS). No subscription, ever.
Bring your own OAuth — you create your own Google/Oura/WHOOP developer credentials; Vitals never proxies your data through a shared backend.
Transparent scoring — recovery, strain, sleep performance, and body-age are plain Python you can read in an afternoon. See
docs/ALGORITHMS.mdfor the exact formulas, weights, and — just as important — their honest limitations.AI-native, via MCP —
vitals_mcp.pyexposes your health data as 9 MCP tools, so a local AI agent (OpenClaw, Hermes, Claude, or anything speaking MCP) can reason over your recovery trend, flag an early illness signal, or answer "should I train hard today?" without any of that data leaving your network. Your agent can even install the whole thing itself from a single copy-paste prompt.Multi-source, source-agnostic — connect Google Health, Oura, WHOOP, or Apple HealthKit (or several at once); the scoring engine normalizes whatever comes in to one internal schema.
One install, your whole family — Vitals is multi-profile out of the box. Each person (partner, kids, parents) gets their own fully isolated data, scores, and coach. WHOOP and Oura charge per person — a family of 5 is
$150/month. One $5 VPS running Vitals is **$1/person/month**, and it's the same math whether you're 2 people or 8.
If you've ever wanted your quantified-self data to actually be yours — inspectable, exportable, and running on infrastructure you control — this is that.
Features
Area | What it does |
Today | Recovery ring (HRV/RHR/sleep composite), strain, sleep breakdown, HRV/RHR vs. rolling baseline |
Trends | 7d/30d rolling views of recovery, HRV, sleep, training load (ACWR) |
Coach | Conversational AI coach (multi-conversation, ChatGPT/Claude-style) via your local |
Journal + Behavior Impact | Daily habit tracker (~33 habits: supplements, alcohol, meditation, screens-in-bed, etc.) + a statistical engine that finds which habits actually move your recovery/HRV/sleep — Spearman correlation with Benjamini-Hochberg correction, not vibes |
Narrative reports | Weekly/monthly plain-language summaries of what changed and why |
Labs | Manual blood-test tracking (~20 common biomarkers) with reference ranges and out-of-range flags |
Body-age & Healthspan | VO2max-based fitness age (NTNU/Nes 2011 formula) plus a monthly body-age-vs-chronological-age trend |
Household / multi-profile | One install serves your whole family — each person (partner, kids, parents) gets fully isolated data, scores & coach. Split a $5 VPS 5 ways = ~$1/person/month vs. ~$30/person on WHOOP or Oura |
Push notifications | ntfy/Telegram alerts for recovery drops, illness-risk signals, sleep debt |
Offline-capable PWA | Add to Home Screen on iOS/Android; service worker caches the shell |
Native iOS app (BYO dev account) | Capacitor shell with a native HealthKit plugin: Apple Watch HRV, sleep stages, VO₂max, ECG — built and signed by you, distributed via TestFlight |
AI agent integration (MCP) | 9 read-only MCP tools for OpenClaw / Hermes / Claude — morning briefs, bedtime reminders, proactive alerts from your own agent |
Female health module | Opt-in cycle tracking (phase, fertile window, delay detection, peri/menopause signals) — zero data exposure unless explicitly enabled |
ECG viewer | Isolated viewer for Apple Watch ECG waveforms pushed from the iOS companion app |
Data export | Full JSON or flattened CSV export of your own history, any time |
Supported data sources
Source | Auth | Notes |
Google Health | OAuth 2.0 (BYO client) | The new Google Health API ( |
Oura | OAuth 2.0 (BYO client) | Oura Ring v2 API |
WHOOP | OAuth 2.0 (BYO client) | WHOOP v2 API — requires |
Apple HealthKit | Push token ( | Companion iOS app reads HealthKit on-device and pushes to |
You can connect more than one source at once; the sync engine merges them into a single daily record per metric.
Quick start
Docker (recommended)
No Python setup, no venv — just Docker.
git clone https://github.com/DocStream-Oficial/vitals.git
cd vitals
cp .env.example .env # fill in your credentials (see below), or skip for demo mode
docker compose up -d
# open http://localhost:8700Want to try it before connecting any real credentials? Run it in demo mode
instead — a deterministic synthetic dataset (150 days of realistic
recovery/sleep/HRV/strain, plus sample journal entries and lab results) with
no OAuth, no .env, no wearable required:
docker compose run --rm -e VITALS_DEMO=1 -p 8700:8700 vitals
# open http://localhost:8700 — fully populated dashboard, no loginIn demo mode, every write-y endpoint (sync, OAuth login, source connect/disconnect,
HealthKit ingest) responds 200 {"status": "demo"} instead of touching real
credentials or data/. Journal/labs entries you add in demo mode go to a
throwaway temp directory that's discarded when the process exits — nothing you
do in demo mode can affect a real install running alongside it.
No Docker — one command (install.py)
Prefer a plain Python venv over Docker? install.py is a single cross-platform
script (Windows/Mac/Linux, stdlib only) that creates the venv, installs
dependencies, generates .env, and launches the app — no manual steps.
git clone https://github.com/DocStream-Oficial/vitals.git
cd vitals
python install.py
# open http://localhost:8700It's idempotent — re-run it any time; it reuses an existing .venv and never
overwrites an existing .env. Try demo mode the same way, with zero credentials:
python install.py --demoFlags
Flag | What it does |
(none) | Interactive wizard (all fields optional, Enter = default/skip), then launches the app |
| Sets |
| Only does setup (venv + deps + |
| Non-interactive: copies |
| Port for uvicorn (default |
| Full usage |
install.py requires Python 3.9+ (same minimum as the rest of the project) and
uses whichever interpreter you invoked it with (python, python3, or py -3
on Windows) to create the venv — no need to guess the right command for your
platform.
Manual setup (venv, no installer)
If you'd rather run the steps yourself:
git clone https://github.com/DocStream-Oficial/vitals.git
cd vitals
python3 -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env # fill in your credentials (see below)
uvicorn main:app --host 127.0.0.1 --port 8700 --reload
# open http://localhost:8700Windows service (NSSM)
For a persistent background service (not just a dev session), see
deploy_windows.ps1 for a step-by-step PowerShell script.
Requires Python 3.9+, NSSM, and Tailscale (for remote HTTPS access).
Connecting your wearables (Google Health, Oura, WHOOP)
Vitals never uses a shared account — each person creates their own free OAuth
app on the provider's site and drops two values (CLIENT_ID / CLIENT_SECRET)
into .env. It's the same three-step pattern everywhere: create the app, trust
http://localhost:8700/auth/callback, copy the two keys.
📖 Full step-by-step walkthrough (beginner-friendly, no experience needed):
docs/CONNECT-DATA-SOURCES.md — click-by-click
for Google Health, Oura, and WHOOP, with the exact scopes and redirect URIs, plus
troubleshooting.
Quick reference:
Provider | Developer portal |
|
Google Health | console.cloud.google.com → enable the Google Health API |
|
Oura |
| |
WHOOP | developer.whoop.com (needs the |
|
Vitals uses Google's current Google Health API (
health.googleapis.com) — not the legacy Google Fit / Fitness API that Google is retiring at the end of 2026.
Apple HealthKit doesn't use OAuth; the native iOS app pushes data on-device — see
docs/IOS-HEALTHKIT.md.
Configuration (.env)
cp .env.example .envSee .env.example for the full list of variables with inline
comments (OAuth credentials per source, profile defaults, INGEST_TOKEN,
notification settings, Claude CLI path, and VITALS_DEMO).
Coach with a local LLM (Ollama, LM Studio, llama.cpp)
By default the Coach (chat, headline, and narrative reports) shells out to
your local claude CLI — no API key needed. If you'd rather run everything
fully offline against a self-hosted model, set COACH_BACKEND=openai_compat
in .env and point it at any OpenAI-compatible /chat/completions endpoint:
# .env
COACH_BACKEND=openai_compat
COACH_API_BASE=http://localhost:11434/v1 # Ollama default
COACH_MODEL=llama3.1 # must already be pulled/loaded
COACH_API_KEY= # leave empty for local serversThis works with Ollama (ollama pull llama3.1 first),
LM Studio's local server, or any llama.cpp-based
server that speaks the OpenAI chat-completions format. The request is a
single stdlib urllib POST (no new pip dependency, no SDK) — same
best-effort semantics as the CLI: a slow/unreachable server times out and
falls back gracefully, it never blocks a request or crashes the app.
COACH_BACKEND unset (or any unrecognized value) keeps the default
claude_cli behavior — nothing changes if you don't touch this.
Dashboard login (DASHBOARD_TOKEN)
By default, anyone who can reach the app's port can view your dashboard — no
login screen. Set DASHBOARD_TOKEN in .env to require authentication:
# .env
DASHBOARD_TOKEN=$(python3 -c "import secrets; print(secrets.token_urlsafe(32))")Then log in once at /login (sets a long-lived cookie), or send
Authorization: Bearer <DASHBOARD_TOKEN> on every request (handy for curl
or Grafana). /api/ingest + /api/ecg (the iOS app's own push endpoints) and
/api/v1/* (the public read-only API) keep their own independent auth either
way — this only gates the dashboard itself. Full details, including the CSRF
side-benefit, in SECURITY.md.
Architecture / Tech stack
Backend: FastAPI (Python 3.9+), vanilla JS frontend (no build step), served as a single process —
uvicorn main:app.Persistence: flat JSON files under
data/(no database). Atomic writes (.tmp+os.replace) everywhere; each domain (profile, journal, labs, cycle, coach history) has its own file. Household/multi-profile mode namespaces these underdata/users/<uid>/. Seedocs/ALGORITHMS.mdfor how the raw per-source payloads get normalized into the daily schema.Scheduler: APScheduler background job for the daily sync (
SYNC_HOURin.env), plus a best-effort sync on boot. Seedocs/ALGORITHMS.mdfor the scoring pipeline that runs on every sync (recovery, strain, sleep performance, body-age).AI coach: shells out to your local
claudeCLI (no API key required if you already run OpenClaw); degrades gracefully to a deterministic fallback narrative if the CLI isn't available.MCP server:
vitals_mcp.py(separate process, Python 3.10+/mcpSDK) exposes your data as MCP tools for any MCP-speaking agent.Mobile: Capacitor-wrapped iOS app (
ios/) for native HealthKit/ECG access, talking to the same FastAPI backend over/api/ingest.Interactive API docs: FastAPI auto-generates OpenAPI docs at
/docsonce the server is running — every endpoint, request/response schema, and a "Try it out" console, for free.
Endpoints
Full interactive reference at /docs once running. Complete list of all
routes in main.py, with their auth model:
none — no auth of its own; open by design (login page, static PWA assets) or intentionally public.
dashboard — protected by
DASHBOARD_TOKENwhen you set one (cookie orAuthorization: Bearer); wide open if you leave it empty (default). SeeSECURITY.md.ingest-token — requires the
X-Vitals-Tokenheader (INGEST_TOKEN), independent ofDASHBOARD_TOKEN.api-key — requires
Authorization: Bearer vk_...(a key you generate under More → API), independent ofDASHBOARD_TOKEN.
Method | Path | Description | Auth |
GET |
| Dashboard HTML | dashboard |
GET |
| PWA manifest | none |
GET |
| PWA service worker | none |
GET |
| Shows the current | dashboard |
GET |
| SVG QR code for pairing (embeds the token in the URL) | dashboard |
GET |
| Dashboard login form | none |
POST |
| Validate | none |
GET |
| Rule-based insight cards | dashboard |
GET |
| Suggested Coach questions | dashboard |
GET |
| Statistical drivers of recovery | dashboard |
GET |
| Weekly/monthly narrative report | dashboard |
GET |
| JSON of your health data | dashboard |
GET |
| Export full dataset ( | dashboard |
POST |
| Trigger a manual sync (no-op in demo mode) | dashboard |
POST |
| HealthKit push ingestion (iOS app) | ingest-token |
POST |
| ECG reading push ingestion (iOS app) | ingest-token |
GET |
| List ECG readings (no voltages) | dashboard |
GET |
| Full ECG reading incl. voltages | dashboard |
GET |
| Start OAuth flow (disabled in demo mode) | dashboard |
GET |
| Read profile | dashboard |
PUT |
| Update profile | dashboard |
POST |
| Connect a data source | dashboard |
DELETE |
| Disconnect a data source | dashboard |
GET |
| List connected sources | dashboard |
GET |
| Cycle tracking state | dashboard |
POST |
| Log a period | dashboard |
DELETE |
| Delete a logged period | dashboard |
POST |
| Log a cycle symptom | dashboard |
GET |
| Habit journal entries | dashboard |
PUT |
| Update a journal entry | dashboard |
POST |
| Add a custom journal habit | dashboard |
GET |
| Behavior Impact engine findings | dashboard |
GET |
| Dose-response curve for a habit | dashboard |
GET |
| Sleep coach card | dashboard |
GET |
| Coach programs | dashboard |
GET |
| Active plan card | dashboard |
POST |
| Start/update a plan | dashboard |
DELETE |
| Clear the active plan | dashboard |
POST |
| Check off a plan step | dashboard |
GET |
| Blood-test entries | dashboard |
POST |
| Add a blood-test entry | dashboard |
POST |
| Import blood tests from CSV | dashboard |
DELETE |
| Delete a blood-test entry | dashboard |
GET |
| Body-age-vs-chronological-age trend | dashboard |
GET |
| List Coach conversations | dashboard |
POST |
| Start a new Coach conversation | dashboard |
GET |
| Read a Coach conversation | dashboard |
DELETE |
| Delete a Coach conversation | dashboard |
POST |
| Ask the Coach (chat) | dashboard |
GET |
| Legacy Coach history | dashboard |
DELETE |
| Clear legacy Coach history | dashboard |
GET |
| List household users | dashboard |
POST |
| Create a household user | dashboard |
DELETE |
| Delete a household user | dashboard |
POST |
| Create a public read-only API key | dashboard |
GET |
| List your API keys (metadata only) | dashboard |
DELETE |
| Revoke an API key | dashboard |
GET |
| Public read-only API: full dataset | api-key |
GET |
| Public read-only API: insight cards | api-key |
GET |
| OAuth callback — saves your token | dashboard |
Public read-only API
Build on top of your own data (same pattern as wger/Open
Wearables) without exposing INGEST_TOKEN (the HealthKit/ECG write secret) to
third-party integrations. From More → API, generate a vk_... key — it is
shown only once; save it, it cannot be recovered later.
curl -H "Authorization: Bearer vk_..." https://your-instance.example/api/v1/data
curl -H "Authorization: Bearer vk_..." https://your-instance.example/api/v1/insightsPer-user, revocable, read-only keys — there is no write endpoint under
/api/v1/*.Only the SHA-256 hash of the key is persisted; the raw key is never stored and cannot be recovered after creation.
Missing, invalid, or revoked key →
401(never500).Up to 10 active keys per user; revoke them from the same section.
Onboarding & profile
On first launch, Vitals walks you through a short onboarding flow where you set your language, units (metric / imperial), and profile. You can update these later under the More tab → Profile.
Native iOS app (Apple Watch / HealthKit) — bring your own dev account
The repo ships a full native iOS companion app (ios/, Capacitor + WKWebView):
a thin shell that loads your self-hosted instance and adds what a PWA can't —
native HealthKit access:
Reads ~13 HealthKit metrics, including Apple Watch data: HRV, resting HR, respiratory rate, SpO₂, wrist temperature, VO₂max, sleep stages, workouts, steps, active energy.
Pushes them to your instance (
POST /api/ingest, authenticated withX-Vitals-Token) and auto-syncs every time the app comes to the foreground.Apple Watch ECG waveforms push too — viewable in the dashboard's ECG viewer.
You build it yourself with your own Apple account — no binaries distributed, no App Store middleman for your health data:
Free Apple ID → run it on your own iPhone (7-day signing).
Paid Apple Developer account ($99/yr) → distribute to yourself and family via TestFlight (~30 min setup).
Step-by-step guides: docs/IOS-BETA.md (build + TestFlight) and docs/IOS-HEALTHKIT.md (HealthKit wiring in Xcode).
PWA — Add to Home Screen
Open http://localhost:8700 (or your HTTPS URL) in Safari or Chrome.
Tap Share → Add to Home Screen (iOS) or the install prompt (Android/Chrome).
Vitals runs as a full-screen, offline-capable app with no browser chrome.
Connect your AI agent (OpenClaw / Hermes / any MCP client)
This is where Vitals stops being a dashboard and becomes infrastructure: if you
run OpenClaw, Hermes Agent,
Claude, or any other MCP-speaking agent, vitals_mcp.py turns your health data
into 9 read-only tools your agent can call — locally, with nothing sent to
a third party. Your agent can answer "should I train hard today?" from your
actual HRV, message you a morning brief over WhatsApp/Telegram, or flag an
illness-risk signal before you feel it.
# Install extra deps (Python 3.10+ required for the mcp SDK)
pip install -r requirements-mcp.txt
# Run the MCP server (stdio transport — your agent will spawn it)
python3 vitals_mcp.pyAvailable tools: vitals_today, vitals_trends, vitals_insights, vitals_bodyage,
vitals_morning_brief, vitals_bedtime_brief, vitals_drivers, vitals_cycle_summary,
vitals_ask_coach.
Already running Vitals? Just add the MCP
If you installed manually and Vitals is already up, this shorter prompt wires only the MCP layer (the Agent-first install prompt at the top does the full clone-install-connect flow from scratch):
I run a self-hosted Vitals instance (https://github.com/DocStream-Oficial/vitals).
The repo is cloned at <PATH-TO-REPO> and the app runs at <MY-VITALS-URL>.
Set up its MCP server so you can read my health data:
1. Install the MCP extras: pip install -r <PATH-TO-REPO>/requirements-mcp.txt
(needs Python 3.10+).
2. Register <PATH-TO-REPO>/vitals_mcp.py as a stdio MCP server named "vitals"
in your MCP config, then reload your MCP connections.
3. Verify the 9 vitals_* tools are available, call vitals_today, and give me
today's recovery, sleep and strain in one line.
4. From now on: include vitals_morning_brief in my morning routine, send me
vitals_bedtime_brief at night, and alert me proactively whenever recovery
drops below 40% or an illness-risk insight appears in vitals_insights.That last step is the fun part — a scheduled agent + these tools = a proactive health copilot that costs you $0/month in subscriptions.
Tests
source .venv/bin/activate
pip install -r requirements-dev.txt
pytest -q1,600+ tests, all against synthetic fixtures — no real health data is required
or shipped in the repo. CI runs the full suite on Python 3.9 and 3.12 on every
push/PR (see .github/workflows/ci.yml).
iOS sleep aggregator
The Xcode project has no test target, so the sleep-night reconstruction logic
lives in ios/App/App/SleepAggregator.swift as a Foundation-only file (no
import HealthKit) precisely so it can be compiled and exercised on a Mac:
swiftc -o /tmp/verify_sleep ios/App/App/SleepAggregator.swift scripts/verify_sleep_union.swift && /tmp/verify_sleep17 cases covering interval union, per-source night reconstruction and the
hypnogram contract. Run it after touching SleepAggregator.swift — it is the
only automated check that code has.
python scripts/i18n_audit.py # verifies all 4 locales (ES/EN/FR/PT) are in syncSecurity & publishing your own fork
See SECURITY.md for how to report a vulnerability, and
CONTRIBUTING.md for the pre-publish
checklist (never commit .env or data/, rotate credentials before making a
fork public, etc.). scripts/preflight_publish.sh automates that check.
License
MIT © 2026 DocStream. See LICENSE.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/DocStream-Oficial/vitals'
If you have feedback or need assistance with the MCP directory API, please join our Discord server