# Whoop Mcp

> Read-only WHOOP v2 data for MCP clients, with local SQLite cache and CSV/JSONL/Parquet exports.

- **Type:** MCP server
- **Install:** `agentstack add mcp-ashwanthramkl-whoop-mcp`
- **Verified:** Pending review
- **Seller:** [AshwanthramKL](https://agentstack.voostack.com/s/ashwanthramkl)
- **Installs:** 0
- **Category:** [Databases](https://agentstack.voostack.com/c/databases)
- **Latest version:** 0.8.3
- **License:** MIT
- **Upstream author:** [AshwanthramKL](https://github.com/AshwanthramKL)
- **Source:** https://github.com/AshwanthramKL/whoop-mcp

## Install

```sh
agentstack add mcp-ashwanthramkl-whoop-mcp
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# WHOOP MCP Server

[](https://github.com/AshwanthramKL/whoop-mcp/actions/workflows/ci.yml)
[](https://pypi.org/project/whoop-mcp/)
[](https://www.python.org/downloads/)
[](./LICENSE)
[](https://modelcontextprotocol.io)
[](./tests)

A local Model Context Protocol (MCP) server that gives an LLM **read-only**
access to your WHOOP fitness data. Authentication is direct OAuth against
your own WHOOP developer app — there is no third-party proxy in the path.
All records are mirrored into a local SQLite cache at
`~/.whoop-mcp-server/whoop.db`, and **no data ever leaves your machine**
except for the authenticated calls the server itself makes to the WHOOP
v2 API.

Current version: **0.8.5** — see [CHANGELOG.md](./CHANGELOG.md).

> **Pre-1.0 status.** The API surface (tool names, response shapes, error
> codes) is stabilizing but not frozen. Breaking changes may land in
> `0.x` minor bumps during dogfooding. Patch bumps (`0.8.x`) are
> bugfix-only. 1.0.0 will be cut when the surface has been stable for
> 2+ weeks of real use. Pin the minor version in CI if you're building
> on top of this.

> **Just want to try it?** Copy the prompt in
> [docs/AGENT_INSTALL_PROMPT.md](./docs/AGENT_INSTALL_PROMPT.md), paste
> it into Claude Code / Claude Desktop / Cursor / Windsurf, and your
> agent will do the install end-to-end.
>
> **Building on this repo?** Start with [AGENTS.md](./AGENTS.md) and
> [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) — they're the
> load-bearing conventions and one-page system map.

## Table of contents

- [What this is](#what-this-is)
- [Install (one line via uvx)](#install-one-line-via-uvx)
- [Install with your agent](#install-with-your-agent)
- [Install from source](#install-from-source-for-development)
- [Analysis skill: whoop-insights](#analysis-skill-whoop-insights)
- [Quick start](#quick-start)
- [Tool catalog](#tool-catalog)
- [MCP resources](#mcp-resources)
- [Data model](#data-model)
- [Sync model](#sync-model)
- [Event feed](#event-feed)
- [Exports](#exports)
- [What's not supported](#whats-not-supported)
- [Updating](#updating)
- [Operations](#operations)
- [Security and privacy](#security-and-privacy)
- [Development](#development)
- [For agents building on this repo](#for-agents-building-on-this-repo)
- [Versioning](#versioning)

## What this is

The WHOOP MCP Server is a small Python process that speaks MCP over stdio.
It exposes WHOOP v2 data (profile, body measurement, cycles, recoveries,
sleeps, workouts) to any MCP-capable client — primarily Claude Desktop and
Claude Code. The server is read-only. It authenticates with WHOOP using an
OAuth app you register yourself, so your tokens never travel through a
third-party server. The WHOOP records you fetch are written to a local
SQLite cache (mode `0o600`) so subsequent reads are free and offline, and
the cache file never leaves your machine.

## Install (one line via uvx)

```bash
# 1. Create a WHOOP developer app (one-time, free, self-serve):
#    https://developer-dashboard.whoop.com/apps/create
#    Redirect URI:  http://localhost:8000/callback
#    Scopes:        read:profile read:body_measurement read:recovery
#                   read:cycles read:sleep read:workout offline

# 2. One-shot OAuth — opens browser, catches callback, saves encrypted tokens.
WHOOP_CLIENT_ID="" \
WHOOP_CLIENT_SECRET="" \
  uvx --from whoop-mcp whoop-mcp-oauth

# 3. Register with Claude Code. The --env flags are required so the
#    server can refresh tokens when the 1-hour access token expires.
claude mcp add whoop --scope user \
  --env WHOOP_CLIENT_ID="$WHOOP_CLIENT_ID" \
  --env WHOOP_CLIENT_SECRET="$WHOOP_CLIENT_SECRET" \
  -- uvx --from whoop-mcp whoop-mcp
```

No clone, no venv, no absolute paths. Don't have `uv`? Install it once:
`curl -LsSf https://astral.sh/uv/install.sh | sh` (or `brew install uv`).

## Install with your agent

Paste the prompt in [docs/AGENT_INSTALL_PROMPT.md](./docs/AGENT_INSTALL_PROMPT.md)
into any MCP-aware agent (Claude Code, Claude Desktop, Cursor,
Windsurf, Zed, Aider). The agent will run the install for you.

## Install from source (for development)

```bash
git clone https://github.com/AshwanthramKL/whoop-mcp.git
cd whoop-mcp
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt -r requirements-dev.txt
.venv/bin/pytest -q   # 183 tests, ~6s, all respx-mocked
```

Then register with `-- /abs/path/to/whoop-mcp/.venv/bin/python /abs/path/to/whoop-mcp/src/whoop_mcp_server.py`.

For Claude Desktop, add the equivalent entry to
`~/Library/Application Support/Claude/claude_desktop_config.json` (macOS),
pointing `command` at the venv Python and `args` at `src/whoop_mcp_server.py`.
Run `./scripts/fresh_install_check.sh` if you want a clean-env smoke
of the install path end-to-end (minus the browser OAuth flow).

## Quick start

Once the server is registered, try these prompts:

1. **Sync the cache.** "Sync my WHOOP data from the last 30 days."
   Claude calls `sync_whoop()` and reports per-resource counts.
2. **Inspect a day.** "Give me yesterday's WHOOP daily summary."
   Claude calls `get_whoop_daily_summary(date="YYYY-MM-DD")` and shows a
   joined cycle + recovery + primary sleep + workouts record.
3. **Export.** "Export all my cached workouts to `~/whoop-workouts.csv`."
   Claude calls `export_whoop(kind="workouts", format="csv", path="...")`.

## Analysis skill: whoop-insights

Bundled with the repo: [skills/whoop-insights](./skills/whoop-insights/SKILL.md).

**Install (one command):**

```bash
whoop-mcp-install-skills            # writes to ~/.claude/skills/
```

That fetches the latest skill from this repo's `main` branch and drops
it where Claude Code (and Cursor / Windsurf / Zed) auto-discovers
user-scope skills. Restart your MCP client session and ask:

> *"How am I doing? Run the whoop-insights skill."*
> *"Generate my weekly WHOOP report."*
> *"Am I overtraining? Check the last 30 days."*

The skill pulls 30 days from the cache, computes personal baselines
(HRV, recovery, sleep, strain), flags anomalies with evidence, runs two
correlations (sleep→next-day-recovery, strain→next-day-recovery), and
optionally generates a self-contained HTML dashboard with Chart.js
visualizations. Every claim cites a specific date and a specific number
— the skill is instructed never to fabricate.

**For repo-native developers** iterating on the skill itself:
`whoop-mcp-install-skills --source ./skills` installs from your local
working tree instead of GitHub.

## Tool catalog

17 tools. All list tools accept `start` / `end` as ISO-8601 and auto-paginate.
Every read tool takes `fresh: bool = False` — pass `True` to bypass the
cache and hit the WHOOP API, write-through to cache, and return the live
response. Errors are returned as a structured `{"error": {...}}` envelope;
tools never raise.

| Tool | What it does | Key parameters |
|------|--------------|----------------|
| `get_whoop_auth_status` | Report OAuth token status. Call first if other tools return `AUTH_FAILED`. | — |
| `get_whoop_profile` | Authenticated user's WHOOP profile (name, email). | `fresh` |
| `get_whoop_body_measurement` | Latest body measurements: height, weight, max HR. | `fresh` |
| `list_whoop_cycles` | Physiological cycles in a time window. | `start`, `end`, `limit`, `fresh` |
| `get_whoop_cycle` | One cycle by integer ID. | `cycle_id`, `fresh` |
| `get_whoop_cycle_sleep` | Sleep record tied to a given cycle. | `cycle_id`, `fresh` |
| `get_whoop_cycle_recovery` | Recovery record tied to a given cycle. | `cycle_id`, `fresh` |
| `list_whoop_recoveries` | Recoveries (HRV / RHR / recovery score) in a window. | `start`, `end`, `limit`, `fresh` |
| `list_whoop_sleeps` | Sleep activities incl. naps in a window. | `start`, `end`, `limit`, `fresh` |
| `get_whoop_sleep` | One sleep activity by UUID. | `sleep_id`, `fresh` |
| `list_whoop_workouts` | Workouts with zone durations (seconds). | `start`, `end`, `limit`, `fresh` |
| `get_whoop_workout` | One workout by UUID. | `workout_id`, `fresh` |
| `get_whoop_daily_summary` | Joined cycle + recovery + primary sleep + workouts for a UTC date. | `date` |
| `sync_whoop` | Refresh cache from WHOOP API. Idempotent, incremental by default. | `full`, `since`, `resources` |
| `get_whoop_events` | Chronological "what's new" feed across cached resources. | `since`, `until`, `resources`, `limit` |
| `export_whoop` | Dump cached records to CSV / JSONL / Parquet. | `kind`, `format`, `path`, `start`, `end`, `overwrite` |
| `health_check` | Composite status (auth, API, cache, schema). Never raises. | `live` |

Error codes: `AUTH_FAILED`, `RATE_LIMITED`, `NOT_FOUND`, `UPSTREAM_ERROR`,
`VALIDATION_ERROR`, `CACHE_ERROR`, `CACHE_EMPTY`, `FILE_EXISTS`,
`EXPORT_ERROR`, `SYNC_ERROR`.

## MCP resources

The cache is also exposed as read-only MCP resources, so the client can
browse date slices without invoking a tool.

| URI | Content |
|-----|---------|
| `whoop://db/cycles/{start}/{end}` | Cached cycles in `[start, end)` (dates `YYYY-MM-DD`). |
| `whoop://db/recoveries/{start}/{end}` | Cached recoveries. |
| `whoop://db/sleeps/{start}/{end}` | Cached sleeps (including naps). |
| `whoop://db/workouts/{start}/{end}` | Cached workouts. |
| `whoop://db/profile` | Latest profile snapshot. |
| `whoop://db/body_measurement` | Latest body_measurement snapshot. |
| `whoop://db/sync_runs/{limit}` | Most recent sync audit rows. |
| `whoop://db/events/{since}` | Event feed since `since`, `until` = now. |
| `whoop://db/events/{since}/{until}` | Event feed for explicit window. |

All resources return `application/json`. Bad inputs return the same
`{"error": {"code","message"}}` envelope used by tools.

## Data model

Responses are **flattened**: the WHOOP `score` wrapper is lifted, milliseconds
become seconds (`*_seconds`, 1 decimal), kilojoules become calories
(`calories`, rounded int), heart-rate keys are renamed to `avg_hr_bpm` /
`max_hr_bpm`, and sleep stages are named `deep_sleep_seconds`,
`rem_sleep_seconds`, `light_sleep_seconds`, `awake_seconds`,
`in_bed_seconds`. Raw `user_id`, `v1_id`, and per-record `created_at` are
dropped.

**`score_state` convention.** WHOOP scores are not always computed. When
`score_state != "SCORED"` (e.g. `PENDING_SCORE`, `UNSCORABLE`) every
derived score field is `null` and the top-level `score_state` is preserved
so callers know why.

One flattened record per resource:

```json
// Cycle
{
  "id": 123456,
  "start": "2026-04-20T04:00:00.000Z",
  "end": "2026-04-21T04:00:00.000Z",
  "timezone_offset": "+00:00",
  "score_state": "SCORED",
  "strain": 12.4,
  "avg_hr_bpm": 62,
  "max_hr_bpm": 168,
  "calories": 2810
}
```

```json
// Recovery
{
  "cycle_id": 123456,
  "sleep_id": "bb68db7b-...",
  "score_state": "SCORED",
  "recovery_score": 74,
  "resting_heart_rate_bpm": 48,
  "hrv_rmssd_ms": 87.3,
  "spo2_pct": 97.4,
  "skin_temp_c": 33.1,
  "user_calibrating": false
}
```

```json
// Sleep
{
  "id": "bb68db7b-...",
  "cycle_id": 123456,
  "start": "2026-04-20T03:10:00.000Z",
  "end": "2026-04-20T10:42:00.000Z",
  "nap": false,
  "score_state": "SCORED",
  "sleep_performance_pct": 88.0,
  "in_bed_seconds": 27120.0,
  "light_sleep_seconds": 12540.0,
  "rem_sleep_seconds": 5280.0,
  "deep_sleep_seconds": 6840.0,
  "awake_seconds": 1080.0
}
```

```json
// Workout
{
  "id": "a91f...",
  "start": "2026-04-20T17:00:00.000Z",
  "end": "2026-04-20T17:48:00.000Z",
  "sport_name": "Running",
  "score_state": "SCORED",
  "strain": 9.3,
  "avg_hr_bpm": 142,
  "max_hr_bpm": 176,
  "calories": 511,
  "distance_meter": 8030.0,
  "zone_durations_seconds": {
    "zone_zero": 0.0, "zone_one": 120.0, "zone_two": 900.0,
    "zone_three": 1440.0, "zone_four": 420.0, "zone_five": 0.0
  }
}
```

## Sync model

- **Cache-first reads.** Every list/get tool reads from SQLite by default.
  An empty window transparently triggers a targeted sync and re-reads.
- **`fresh=True`** bypasses the cache, hits the WHOOP API, write-throughs
  the result into the cache, and returns the live response.
- **Incremental sync** uses `MAX(updated_at)` per resource as the cursor.
  A fresh DB falls back to the last 90 days. `sync_whoop(full=True)` pulls
  from 2010-01-01 for every resource (do this once on a brand-new cache).
- **Idempotent.** Re-running `sync_whoop` with no new upstream data is a
  no-op. Combined with snapshot hash dedupe (below), this means the event
  feed stays quiet.
- **Snapshot hash dedupe.** `profile` and `body_measurement` are singleton
  snapshots. Before writing, the server compares SHA-256 of the canonical
  raw payload against the stored blob. Byte-identical payloads do not
  advance `updated_at`, so no spurious events are generated.

## Event feed

`get_whoop_events(since, until=None, resources=None, limit=500)` is a
chronological feed across all cached resources — pure cache read, no API
calls. The window is **exclusive on both ends**:
`updated_at > since AND updated_at "}
```

`next_cursor` is an **opaque** base64 encoding of
`updated_at|resource|id` for the last returned event. Pass it back as
`since` to continue. The full triple is used as a tie-broken lower bound,
so events sharing an `updated_at` are never skipped at a pagination
boundary. Plain ISO-8601 strings as `since` still work (M5 compat).

Also exposed as MCP resources — see the [MCP resources](#mcp-resources)
table above.

## Exports

`export_whoop(kind, format, path, start=None, end=None, overwrite=False)`
writes flat cached records to disk. Pure data layer — never hits the API.
Run `sync_whoop()` first.

- **`kind`**: `cycles` | `recoveries` | `sleeps` | `workouts` | `all`.
  `"all"` writes one file per resource (`cycles.*`, `recoveries.*`, …)
  into the given directory.
- **`format`**: `csv` (RFC 4180, alphabetically sorted header, nested
  values JSON-encoded), `jsonl` (one record per line, sorted keys), or
  `parquet` (pyarrow, snappy).
- **`overwrite`**: default `False`. If the destination has content,
  returns `FILE_EXISTS`. `True` replaces silently.

An empty window still yields a file (header-only CSV / empty JSONL /
empty Parquet) so downstream tooling sees a consistent artifact.

## What's not supported

Spelled out so nobody wastes an issue.

| Ask | Why not |
|-----|---------|
| `claude.ai` (the web app) | We ship as a **stdio** MCP (local subprocess). `claude.ai` wants a remote HTTPS MCP. A hosted deployment would also break our single-user / local-only threat model. |
| Mobile Claude apps | Same stdio constraint. |
| Writing back to WHOOP (logging activities, updating weight, etc.) | Read-only by design. Adding writes would require reverse-engineered endpoints outside WHOOP's stable v2 API — too brittle and ToS-risky. See [`jd1207/whoop-mcp`](https://github.com/jd1207/whoop-mcp) if you need that. |
| Multi-user / shared server | One user, one WHOOP account, one machine. The cache + encrypted tokens live under `~/.whoop-mcp-server/`; there's no tenancy model. |
| More than 10 users per WHOOP dev app | WHOOP's dev-app cap is 10 users until app-level approval. Each user should create their own dev app — it's free and self-serve. |
| Push notifications / webhooks | Event feed is poll-driven from the cache. For proactive alerts, run `sync_whoop` + `get_whoop_events` on a cron from whatever scheduler you already have. |
| Python < 3.10 | The code uses `X \| Y` union syntax. `pip install` will refuse on older Pythons. |
| Windows-specific install paths | Code works, but OAuth callback (`localhost:8000`) and path handling haven't been dogfooded on Windows. Ubuntu + macOS are the currently-tested surface. |

## Updating

MCP servers don't auto-update. When you installed is when you pinned.
How to pick up a new release depends on how you installed:

| Install path | How to update |
|---|---|
| `uvx --from whoop-mcp whoop-mcp` | `uvx --refresh --from whoop-mcp whoop-mcp`, or `uv cache clean whoop-mcp` and re-invoke |
| `uvx --from whoop-mcp@latest whoop-mcp` | Update happens on next cache miss (no manual step) |
| `pipx install wh

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [AshwanthramKL](https://github.com/AshwanthramKL)
- **Source:** [AshwanthramKL/whoop-mcp](https://github.com/AshwanthramKL/whoop-mcp)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Versions

- **0.8.3** — security scan: flagged — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-ashwanthramkl-whoop-mcp
- Seller: https://agentstack.voostack.com/s/ashwanthramkl
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
