Releases: briangaoo/totem
v1.4.3
Hardening pass on the totem update flow from 1.4.2:
- Version picker dates each version by its commit date (so backfilled tags like v1.1.0 read 2026-05-26, in chronological order); dropped the duplicate v1.0.0 tag.
- Every deployment state handled — none / local / Fly·Railway·Cloud Run / self-hosted Docker — branching on your recorded deploy, never a hardcoded assumption. Self-host gets exact rebuild/restart steps instead of a
/healthcheck that falsely passed against the old container. - Missing platform CLI now gives a clear "local updated, deploy not pushed" message instead of a cryptic spawn ENOENT.
- Token carry-over after a cloud redeploy is surfaced (a restart can strand a rotated refresh token; points at
totem auth).
v1.4.2
totem update is now an interactive updater — a version picker (every release, newest first, with dates + notes), newest as default, older versions warn. Applies your choice with a guided animated build (fast-forward main or check out a pinned tag; reinstall + rebuild), then redeploys to your host and health-checks /health.
Opt-in auto-update — totem update --auto on|off installs/removes a background job (launchd on macOS, cron on Linux) that checks for new releases every 6h and applies them the same way (pull → build → redeploy → health-check), logging to .totem-autoupdate.log. Scheduled poll (no push channel to your machine); released, CI-gated versions only — never bleeding-edge main.
Versions are sourced from git tags + GitHub Releases (git installs) and the npm registry (global installs) — nothing extra to host. --check previews installed-vs-latest + auto state. Test suite 219 → 233.
v1.4.1
Bug-fix release for #2.
Fixed: whoop_journal always returned behaviors: [] even when entries were logged + confirmed in the app. The v3 drafts endpoint nests each logged behavior as { behavior_tracker:{id,…}, tracker_input:{behavior_tracker_id, answered_yes, magnitude_input_*,…} }, but the projection read behavior_tracker_id off the top level of each entry (always undefined) and dropped them all — while cycle_id, read from a different field, still came through. Now reads the nested shape (and still handles the flat one). Added a populated-draft fixture + regression tests; suite 212 → 219.
Get it: totem update, or npm i -g @briangaoo/totem (1.4.1), or pull main.
v1.4.0
Renamed the project to Totem. The Whoop adapter is byte-for-byte unchanged — every whoop_* tool, WHOOP_* env var, and the private-iOS-API client are identical; only the project/package/CLI name changed (whoop-mcp → totem). Totem is becoming a device-agnostic wearables→AI bridge; Whoop is the first shipping adapter, with Fitbit/Apple Watch/Garmin in progress.
Also: totem update (pull latest + redeploy in place, opt-in) and a CI verify gate (tsc + vitest + build on push/PR). See CHANGELOG for full details.
v1.3.0
Direct setup for the major AI clients — in both the local and cloud flows. No more "Claude only."
Added
whoop-mcp localnow wires the server into the client you pick — Claude Desktop, Claude Code, Cursor, VS Code (Copilot), Gemini CLI, Codex CLI, or Windsurf — writing the right config to the right path automatically (or printing a universal block for any other MCP client).whoop-mcp cloud's connect step prints ready-to-paste setup for claude.ai, ChatGPT, Claude Code (remote), and Cursor / Windsurf / any HTTP MCP client — URL + password for the OAuth connectors, a bearer-token config block for the header-auth ones.
Every stdio client uses the identical launch entry, so the server self-loads its .env no matter which app starts it.
Upgrade: npm i -g @briangaoo/whoop-mcp@1.3.0
v1.2.4
A bug-fix release for a remote-connector regression introduced in 1.2.3. If you're on 1.2.3, upgrade — the claude.ai web/desktop/mobile connector can't connect on it. (stdio / Claude Code was unaffected.)
Fixed
- The claude.ai connector couldn't connect on 1.2.3. The 1.2.3 security pass set a
Content-Security-Policyon every HTTP response. On the JSON OAuth-metadata responses Claude flagged it as a "server configuration issue"; on the consent (password) page theform-action 'self'directive blocked the OAuth redirect back to Claude, so submitting the password silently did nothing. The CSP is removed from the API/metadata responses and from the consent page — which keepsX-Frame-Options: DENY, the clickjacking control that page actually needs. - Audience binding (RFC 8707) is now log-only, not enforced — a strict
resource-claim check risked 401-ing a valid token. Returns as strict once verified against a live token. - Reverted
redirect: "error"on the Whoop API client to the default (follow). - The consent endpoint now logs which check failed instead of always saying "incorrect password."
Upgrade: npm i -g @briangaoo/whoop-mcp@1.2.4, then re-deploy (whoop-mcp cloud) or just fly deploy your existing app.
v1.2.3
A security-hardening release — no API or tool changes, fully behavior-compatible. Came out of a full codebase security audit.
Secrets at rest
- Token files (
.env+ token store) are written0600(owner-only), with a chmod repair so files from older versions get tightened. - Your Whoop password is removed from
.envafter a successful login — it was only ever needed for the one bootstrap call. - Deploy/rotation secrets are pushed to Fly over stdin (
secrets import), not argv — no longer visible inps//proc.
HTTP auth server
- OAuth JWTs are signed with a key derived from
MCP_AUTH_TOKEN(HMAC), not the token itself. - Access tokens are audience-bound (RFC 8707): a token's
resourceclaim must match this server's/mcpURL. - Security headers on every response (
X-Frame-Options: DENY,nosniff, CSP) — the connector password form can no longer be framed (clickjacking). - Connector-password gate gets a global brute-force ceiling (independent of the spoofable per-IP key) and a 16-char + character-class floor for user-chosen passwords.
whoop-mcp cloudnow verifies the auth gate post-deploy (asserts unauthenticated/mcp→ 401).
Traffic realism
whoop_compare/whoop_lift_historyfan-outs are paced (≤3 concurrent, jittered) instead of one simultaneous burst;whoop_coach_askpolls on a jittered interval, not a fixed 1.000 s metronome.- Read-only hosts derive a stable install id from the account email; a boot warning fires if the bundled iOS app version goes stale.
.gitignore now excludes every .env* variant; .dockerignore excludes .env* + the deploy record. Full detail in CHANGELOG.md and SECURITY.md.
v1.2.2
A small polish release.
Highlights since v1.2.1
- Masked password input. During
whoop-mcp auth/cloud/local, the Whoop password no longer echoes in plaintext — it's read in raw mode and never rendered, so it's safe on a screen-share or recording. Email, MFA code, and the auto-generated connector password stay visible. - New demo video. The README now shows a ~2-minute screen recording of the full
whoop-mcp cloudflow — install → Whoop login → Fly deploy → Claude connector → first query — replacing the static screenshot.
Full detail in CHANGELOG.md.
v1.2.1
A correctness + stealth pass. Every read and write tool was exercised individually against a live account — comparing each call's raw API exchange to the projected output and reading the state back — which surfaced a class of "returns HTTP 200 but the payload is empty or wrong" bugs that receipt-only testing missed.
Highlights since v1.2.0
- iOS-app identity headers on every data request. Data requests now carry the WHOOP iOS app's own header set (
user-agent: iOS, thex-whoop-*device headers, a per-install identifier, capitalBearer), captured from a live mitmproxy session, so traffic blends with the legitimate app instead of being a bare bearer token. Static values are the app's shared constants (camouflage, not a per-user signature); the installation id is a per-install random UUID persisted to.env. whoop_behavior_impactresurrected. It was effectively un-callable — it required an impact UUID that no other tool exposed. It now lists every behavior + itsimpact_uuidwhen called with no argument, then returns the full detail for a UUID.- Seven read tools fixed that returned 200 with an empty or wrong projection:
whoop_stress,whoop_trend(every time/duration metric was null),whoop_cycle(all-null despite real data; also needs the?dateparam),whoop_workout(HR curve was always empty),whoop_lift_progression(mislabeled segments),whoop_recovery(SpO2 / skin temp), andwhoop_behavior_impact. - Write fixes.
whoop_coach_askreturned a truncated streamed reply ("56"instead of the full answer) — now polls until the turn isCOMPLETE.whoop_symptom_logcrashed on menstruation-only calls. - Tool descriptions rewritten across the surface — terser and example-free for id-taking tools (sample ids led the model to hallucinate ones outside the list) — with the catalog read enforced by a hard gate.
- Docs reconciled with the codebase; descriptions clarified for
whoop_journal_log(replaces the day — read first),whoop_smart_alarm_set(wake time goes throughmode=schedule),whoop_cycle, andwhoop_workout.
212 tests (was 178). TypeScript 6, Node 24.
Full detail in CHANGELOG.md.
v1.1.0
Remote hosting — the MCP now speaks HTTP, deployable to any Docker host. (Originally released 2026-05-26.)
Highlights since v1.0.0
- HTTP transport (
MCP_TRANSPORT=http) — Streamable HTTP at/mcpbehind a static bearer-token gate (MCP_AUTH_TOKEN, constant-time compare), a no-auth/healthprobe, and CORS preconfigured. stdio stays the default for local Claude Desktop / Claude Code. Dockerfile— multi-stage Alpine build (~150 MB), runs as non-root, ships aHEALTHCHECK.TokenStoreabstraction —EnvFileTokenStore(default, persists refreshed tokens to.env) +MemoryTokenStore(read-only filesystems), selectable viaWHOOP_TOKEN_STORE.- 9 HTTP-auth tests; a full "Remote hosting" README walkthrough (Fly / Railway / Render / VPS).
Existing local stdio installs are unaffected (MCP_TRANSPORT=stdio is the default).
Full detail in CHANGELOG.md.