whoopmcp
# whoopmcp
[](https://github.com/kurok/whoopmcp/actions/workflows/ci.yml)
[](LICENSE)
[](https://www.python.org/downloads/)
A read-only [MCP](https://modelcontextprotocol.io) server for the
[WHOOP API v2](https://developer.whoop.com/api/). It lets an MCP client —
Claude Desktop, Claude Code, Cursor, or anything else that speaks the
protocol — read and analyse **your own** WHOOP data: recovery, sleep, strain,
cycles and workouts.
Runs locally by default, and your WHOOP credentials never leave your machine
in that mode. Run with `WHOOPMCP_TRANSPORT=streamable-http` (#27) instead and
that stops being true: the operator of that server now holds other members'
tokens and health data server-side, which makes them a data controller, not
a bystander. See [PRIVACY.md](PRIVACY.md)'s local-mode/hosted-mode split
before hosting this for anyone but yourself.

Install and usage patterns live in the
[wiki](https://github.com/kurok/whoopmcp/wiki); [docs/SETUP.md](docs/SETUP.md)
is the from-scratch walkthrough.
> **Status.** Local mode works today against the real WHOOP v2 API,
> read-only. The hosted surface (streamable HTTP, OAuth resource server,
> per-tenant isolation, encrypted token storage, webhooks, metrics) is
> implemented and has been through the roadmap's security audits (#69, #37);
> the findings they raised are fixed and closed. Running it hosted for
> others still needs WHOOP's app approval, not yet granted, which caps a
> shared deployment at 10 members in the meantime (#33). See
> [Roadmap](#roadmap).
> **Not affiliated with WHOOP, Inc.** "WHOOP" is their trademark. This is an
> independent client of their public developer API.
---
## What it does
| Area | Tools |
| --- | --- |
| Auth | `whoop_auth_status`, `whoop_login`, `whoop_complete_login`, `whoop_logout` |
| Profile | `get_profile`, `get_body_measurement` |
| Records | `list_recoveries`, `list_sleeps`, `list_cycles`, `list_workouts`, `get_sleep`, `get_workout` |
| Local store | `whoop_sync`, `whoop_data_coverage` |
| Analysis | `summarize_period`, `metric_trend`, `correlate_metrics`, `compare_periods`, `whoop_timeseries`, `whoop_outliers`, `whoop_streaks` |
Alongside the 21 tools it registers 3 prompts (`morning_readiness_briefing`,
`weekly_training_review`, `sleep_debt_investigation`) and one resource
template, `whoop://user/{item}`, serving `profile`, `latest-recovery`,
`latest-sleep` and `latest-cycle`.
Every tool that only reads is annotated `readOnlyHint`; the one exception is
`whoop_sync`, annotated as a non-destructive, idempotent write because it
upserts fetched records into the **local** store — it never writes to your
WHOOP account. No MCP tool can mutate your
WHOOP account — the one mutating endpoint WHOOP exposes
(`DELETE /v2/user/access`) is never registered as an MCP tool, so no model
can revoke your grant. It is reachable only from a terminal on the machine
running the server, as the operator-run `whoopmcp delete-member
--whoop-user-id N` (see [Privacy](#privacy)); `whoop_logout` itself only
deletes the token stored on your own disk.
Questions it is meant to answer:
- *"How did my recovery trend over the last month?"*
- *"Is my HRV correlated with how long I sleep?"*
- *"Compare my strain in July against June."*
### What it is not
It reports numbers and the sample size behind them. It is not a medical
device, it does not diagnose, and a correlation across a few weeks of your
own data is not a causal finding. Talk to a clinician about health decisions.
---
## Install
Requires **Python 3.13 or 3.14** — the two newest stable releases, which are
the two CI tests.
```bash
git clone https://github.com/kurok/whoopmcp
cd whoopmcp
uv venv --python 3.14
source .venv/bin/activate
uv pip install -e .
```
## Setup
You need your own WHOOP developer app — this server ships no shared
credentials, by design. Full walkthrough in **[docs/SETUP.md](docs/SETUP.md)**.
The short version:
1. Create an app at [developer.whoop.com](https://developer.whoop.com/) and
note the client ID and secret.
2. Register a redirect URL. **WHOOP does not accept `http://`, including
`http://localhost`** — use `https://` or a custom scheme such as
`whoopmcp://callback`.
3. Point the server at them via environment variables.
### Claude Desktop / Claude Code
```jsonc
{
"mcpServers": {
"whoop": {
"command": "uvx",
"args": ["whoopmcp"],
"env": {
"WHOOP_CLIENT_ID": "your-client-id",
"WHOOP_CLIENT_SECRET": "your-client-secret",
"WHOOP_REDIRECT_URI": "whoopmcp://callback"
}
}
}
}
```
Then ask your client to run `whoop_login`, open the URL it returns, approve
the consent screen, and pass the `code` and `state` from the redirect back
via `whoop_complete_login`.
### Configuration
| Variable | Required | Default | Meaning |
| --- | --- | --- | --- |
| `WHOOP_CLIENT_ID` | yes | — | OAuth client ID from the WHOOP dashboard |
| `WHOOP_CLIENT_SECRET` | yes | — | OAuth client secret |
| `WHOOP_REDIRECT_URI` | yes | — | Must match a registered redirect URL exactly |
| `WHOOPMCP_SCOPES` | no | all read scopes + `offline` | Space-separated scope list |
| `WHOOPMCP_TOKEN_BACKEND` | no | `file` | `file`, `keyring`, or `encrypted-file` |
| `WHOOPMCP_STATE_DIR` | no | `~/.local/state/whoopmcp` | Token and cache location |
| `WHOOPMCP_TRANSPORT` | no | `stdio` | `stdio` or `streamable-http` — local vs. hosted mode (#27) |
| `WHOOPMCP_CACHE` | no | `false` | Cache responses on disk |
| `WHOOPMCP_BACKFILL_FLOOR_DATE` | no | — | ISO 8601 lower bound for `whoopmcp backfill`; unset walks until history is exhausted |
| `WHOOPMCP_TIMEOUT` | no | `30` | Per-request timeout, seconds |
| `WHOOPMCP_METRICS_TOKEN` | no | — | Bearer token required on `/metrics` (#31); unset means the route 404s and exports nothing |
| `WHOOPMCP_METRICS_SALT` | no | — | HMAC key for `/metrics`' opaque per-member label; unset means every per-member series is withheld |
The `offline` scope is requested by default. Without it WHOOP issues no
refresh token and you would re-authorise through a browser every hour.
For a token in your OS keychain rather than a file on disk:
```bash
pip install 'whoopmcp[keyring]'
export WHOOPMCP_TOKEN_BACKEND=keyring
```
**Recommended on Windows**, where the default file backend cannot protect the
token: Windows uses ACLs rather than POSIX modes, so the `0600` the file
backend requests is ignored and the token lands world-readable. The server
warns when it first writes one.
---
## Privacy
Read **[PRIVACY.md](PRIVACY.md)** before connecting real data — it is split
into local-mode and hosted-mode sections, since they are not the same
document. The essential points:
- **This server sends nothing to its maintainers.** No telemetry, no
analytics, no phone-home. Traffic goes to `api.prod.whoop.com` and nowhere
else.
- **Your MCP client does send your data onward.** Anything a tool returns is
passed to whatever model your client is configured to use — Anthropic,
OpenAI, a local model — under *that provider's* terms, not this project's.
This is inherent to how MCP works, and it is health data. Know where it is
going.
- Tokens are stored locally at mode `0600`, or in your OS keychain. **On
Windows file modes are not enforced** — use the keychain backend there.
- **Local mode:** delete everything with `whoop_logout`, then remove
`WHOOPMCP_STATE_DIR`, then revoke the app in the WHOOP app under Settings.
- **Hosted mode:** an operator holds other members' health data server-side
(#13) and is a data controller for it (GDPR Article 9). Per-member export
and erasure are operator-run CLI commands, deliberately not MCP tools —
`whoopmcp export-member --whoop-user-id N`,
`whoopmcp delete-member --whoop-user-id N` (revokes the member's WHOOP
grant upstream and forgets their local token and principal link — nothing
more), and `whoopmcp erase-member --whoop-user-id N` (also revokes the
grant, then additionally erases the member's stored health data, webhook
events, and audit rows) — and `whoopmcp enforce-retention --max-age-days N`
deletes data past a configured age when an operator schedules it. This
project takes no backups of its own in either mode.
---
## Rate limits
WHOOP's documented defaults are **100 requests/minute** and **10,000/day**,
with `X-RateLimit-*` headers and a `429` on breach. Collections page at 25
records maximum. Ask for explicit date ranges; an unbounded walk over years
of history will exhaust the quota and the model's context window alike.
Confirmed with WHOOP: the limit is **per application** (your `client_id`),
shared across every member who has authorised it — not a separate budget
per member. Running this locally for one person, that distinction is
invisible. Hosting it for several, it is the whole budget: one member's
two-year backfill is roughly 110 requests, about a minute of the *entire*
app's per-minute quota (#9).
---
## Development
```bash
git clone https://github.com/kurok/whoopmcp
cd whoopmcp
uv venv && uv pip install -e '.[dev,lint]'
pytest # tests
ruff check . && ruff format --check .
mypy # strict on src/
```
Built on the official Python SDK's `MCPServer` — the class FastMCP became
when the SDK went to 2.0. The layering is deliberate:
```
config.py environment -> Config, validated once at startup
auth.py OAuth 2.0 flow + token storage (file, encrypted file, keychain)
client.py one method per documented WHOOP endpoint, nothing more
store.py persistent store: schema, tenancy enforcement, erasure
analysis.py pure functions over already-fetched records
server.py MCP tool definitions
```
Only `server.py`, `webhooks.py` and `mcpauth.py` import `mcp` — enforced by
`tests/test_module_map.py`; CONTRIBUTING.md carries the full 18-module map.
`analysis.py` holds no network code and `client.py` holds no statistics, so
each can be tested without the other.
## Roadmap
| Issue | Work |
| --- | --- |
| #33 | [human] Submit the WHOOP app for approval; lifts the 10-member cap on hosted deployments |
Everything else the roadmap tracked — including both security audits (#69,
#37), the `whoopmcp login` terminal flow (#76) and the PyPI/MCP-registry
release plumbing (#34) — is implemented and closed. The package is not yet
on PyPI: publication waits on the name-clash resolution recorded in #34, so
install from a clone (see Install above) until then.
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md). Bug reports and PRs welcome; by
participating you agree to the [Code of Conduct](CODE_OF_CONDUCT.md). To
report a security issue, follow [SECURITY.md](SECURITY.md) rather than
opening a public issue.
## Prior art
Several other WHOOP MCP servers exist — among them
[AshwanthramKL/whoop-mcp](https://github.com/AshwanthramKL/whoop-mcp),
[shashankswe2020-ux/whoop-mcp](https://github.com/shashankswe2020-ux/whoop-mcp)
and [JedPattersonn/whoop-mcp](https://github.com/JedPattersonn/whoop-mcp).
If one of them already does what you need, use it.
## License
[MIT](LICENSE).
TDQS
Scored across 16 tools
Each tool targets a distinct resource or analytical operation: auth lifecycle (status, login, complete_login, logout), raw data (profile, body measurement, recoveries, sleeps, cycles, workouts, single gets), and analysis (summarize, trend, correlate, compare). Even similar-sounding tools like list_sleeps vs get_sleep are clearly differentiated by singular vs plural and description.
The naming is mostly verb_noun (get_profile, list_sleeps, summarize_period) but inconsistent: auth tools carry a whoop_ prefix (whoop_login, whoop_complete_login) while data tools do not, and metric_trend breaks the verb_noun pattern by being noun_noun. The style is readable but not uniform.
16 tools is slightly above the typical 3-15 range, but each tool earns its place by addressing a distinct need (auth, each data type, individual records, and analysis). The count is a bit heavy but not bloated, and the domain of WHOOP data access justifies the breadth.
The surface covers the core WHOOP data types (profile, body measurement, recoveries, sleeps, cycles, workouts) plus individual getters for sleep and workout, and adds useful analytical tools. Minor gaps persist: there is no get_recovery or get_cycle by ID, and no way to retrieve a single cycle or recovery directly, but these can be worked around via the list functions.