whoop-mcp
# whoop-mcp
A small [Model Context Protocol](https://modelcontextprotocol.io) server that
gives Claude read access to your [WHOOP](https://www.whoop.com) health data —
recovery, sleep, strain, and workouts — through the **official WHOOP API v2**.
Ask Claude things like *"Did I sleep well?"*, *"Am I recovered enough to train
hard today?"*, or *"What's my resting heart rate trend this week?"* and it will
pull live data from your WHOOP.
> **Note on "current heart rate":** WHOOP's API exposes *completed* summaries
> (last night's sleep, today's recovery, per-workout HR), not a live real-time
> pulse. `daily_summary` gives the freshest snapshot available — the latest
> recovery score, resting heart rate, HRV, and today's accumulated strain.
## Tools
| Tool | What it returns |
|------|-----------------|
| `daily_summary` | One-call snapshot: latest recovery (score, HRV, resting HR), last night's sleep, today's strain. **Start here.** |
| `get_recovery` | Recovery records for the last N days (default 7). |
| `get_sleep` | Sleep records with stage breakdown for the last N days. |
| `get_strain` | Daily strain / cycle data (day strain, avg & max HR). |
| `get_workouts` | Workout activities for the last N days (default 14). |
| `get_profile` | Name, email, user id. |
| `get_body_measurement` | Height, weight, max heart rate. |
## Setup
### 1. Create a WHOOP developer app
1. Go to the [WHOOP Developer Dashboard](https://developer-dashboard.whoop.com)
and sign in with your WHOOP account.
2. Create an app. Copy the **Client ID** and **Client Secret**.
3. Add a **Redirect URI** of exactly `http://localhost:8080/callback`.
4. Make sure these scopes are enabled: `read:recovery`, `read:cycles`,
`read:sleep`, `read:workout`, `read:profile`, `read:body_measurement`,
and `offline`.
### 2. Configure credentials
```bash
cp .env.example .env
# edit .env and paste in your Client ID + Secret
```
### 3. Authorize once
```bash
uv run whoop-authorize
```
This opens your browser, you approve access, and tokens are saved to
`~/.whoop-mcp/tokens.json` (readable only by you). The server refreshes them
automatically from then on — you won't need to log in again.
### 4. Register the server with Claude
```bash
claude mcp add whoop -- uv --directory /Users/adam/dev/whoop-mcp run whoop-mcp
```
Restart Claude, then ask: *"How's my WHOOP recovery today?"*
## Using it from the Claude apps (remote connector)
The `stdio` setup above only works in Claude Code, which can launch the server
as a local subprocess. To use WHOOP from **claude.ai, the desktop app, and
mobile**, run the server remotely over HTTP and add it as a *custom connector*.
The same code serves an HTTP transport via the `whoop-mcp-http` entrypoint. The
MCP endpoint lives at `/mcp` behind a minimal **single-user OAuth 2.1** layer
(see `oauth.py`) — claude.ai custom connectors force an OAuth handshake and
won't connect to a no-auth server. When you add the connector, Claude bounces
you to a login page; enter your `WHOOP_MCP_SHARED_SECRET` and it's authorized.
Tokens are stateless HMAC-signed blobs, so nothing is stored and auth survives
restarts.
### Deploy (Render free tier — no monthly cost)
1. Create a free **Upstash Redis** database ([upstash.com](https://upstash.com))
and copy its **REST URL** and **REST token**. This is where the rotating
WHOOP token is persisted, so it survives restarts without a paid disk.
2. Push this repo to GitHub and create a Render **Blueprint** from
[`render.yaml`](./render.yaml) (uses the `free` web plan).
3. Set the secret env vars in the Render dashboard (all marked `sync: false`):
- `UPSTASH_REDIS_REST_URL`, `UPSTASH_REDIS_REST_TOKEN` — from step 1.
- `WHOOP_CLIENT_ID`, `WHOOP_CLIENT_SECRET` — from the WHOOP dashboard.
- `WHOOP_REFRESH_TOKEN` — the `refresh_token` field from your local
`~/.whoop-mcp/tokens.json` after `uv run whoop-authorize`.
4. Render generates `WHOOP_MCP_SHARED_SECRET` for you — copy it from the
dashboard.
> **Why Redis?** WHOOP rotates refresh tokens on every use, so the rotated token
> must outlive restarts. The free Render web tier has no persistent disk, so the
> server stores the token blob in Upstash instead (see the token-backend section
> of `auth.py`). Locally, with no Redis env vars set, it falls back to a 0600
> file in `~/.whoop-mcp/`.
>
> **Cold starts:** free web services sleep after ~15 min idle, so the first query
> after a lull takes ~30-60s and may need one retry. To keep it warm, ping
> `/healthz` every ~10 min from a free scheduler like cron-job.org.
>
> **Prefer a persistent disk instead?** On a paid Starter plan you can drop the
> Upstash vars, add a `disk:` mounted at `/data`, and set `WHOOP_MCP_HOME=/data`.
### Add the connector
In claude.ai → **Settings → Connectors → Add custom connector**, paste:
```
https://<your-app>.onrender.com/mcp
```
Claude opens a login page — enter your `WHOOP_MCP_SHARED_SECRET` to authorize.
Once connected there it syncs to the desktop and mobile apps too.
Run the HTTP server locally to test first:
```bash
WHOOP_MCP_SHARED_SECRET=$(uuidgen) PORT=8080 uv run whoop-mcp-http
# health check: curl localhost:8080/healthz -> ok
```
## How auth works
WHOOP uses OAuth 2.0 with **rotating refresh tokens** — each refresh returns a
new refresh token and invalidates the old one. `auth.py` persists the new token
after every refresh, so the connection stays alive indefinitely without
re-authorizing. No secrets are stored in your Claude config; everything lives in
`~/.whoop-mcp/`.
TDQS
Scored across 7 tools
Each tool targets a distinct data category: profile, body measurements, recovery, sleep, strain, workouts, and a summary snapshot. The daily_summary tool could be confused with the historical get_recovery/get_sleep/get_strain tools, but descriptions clarify (current state vs historical). Overall clear.
Six tools follow the consistent 'get_' prefix pattern, while 'daily_summary' deviates. All use snake_case, so the deviation is minor. The naming is generally clear and predictable.
With 7 tools, the server covers the main WHOOP data categories without being overwhelming. Each tool serves a distinct purpose, and the count feels appropriate for a health/fitness API.
The server provides read access to core WHOOP metrics: recovery, sleep, strain, workouts, body measurements, profile, and a daily summary. Missing update functionality or fine-grained heart rate data, but for querying it covers the essential domain adequately.