vitals
Integrates with Apple HealthKit via a companion iOS app to read HRV, sleep stages, VO2max, and ECG.
Integrates with Google Health API to pull wearable data from Fitbit, Pixel Watch, Garmin, and other Health Connect devices.
Sends push notifications via ntfy for alerts like recovery drops or illness-risk signals.
Sends push notifications via Telegram for alerts like recovery drops or illness-risk signals.
Click on "Install 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., "@vitalswhat's my recovery score today?"
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.
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.
Related MCP server: Ultrahuman MCP Server
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).
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.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- 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