Skip to main content
Glama

mcp-whoop

An MCP server that exposes the WHOOP v2 API over Streamable HTTP, authenticated with GitHub OAuth.

It is built for remote use — as a custom connector in claude.ai / Claude Desktop, or over claude mcp add --transport http — rather than as a local stdio server. Two consequences follow from that:

  • Clients authenticate with OAuth, handled by FastMCP's GitHubProvider. The server publishes /.well-known/oauth-authorization-server and supports Dynamic Client Registration, which is what claude.ai requires.

  • WHOOP credentials are held server-side. The refresh token is stored on a mounted volume and rotated automatically, so the browser OAuth flow is done once, not after every restart.

Why a GitHub OAuth app?

It is reasonable to ask what GitHub has to do with reading your own WHOOP data. Nothing — GitHub is only the login screen. The chain that puts it there:

  1. claude.ai will not accept a static token. Its connector dialog can send fixed request headers, but that feature is in limited beta. Without it the only way to add a remote MCP server is OAuth.

  2. OAuth needs something that authenticates a human. The server must run an authorization flow, which means a browser sign-in that proves who you are.

  3. The server has no user database, and should not have one. FastMCP's OAuthProxy translates between the MCP client and an existing identity provider; it does not store users or passwords itself.

So an identity provider is required, and GitHub is a convenient one. The app requests the user scope only — it reads your profile, not your repositories — and the single field taken from it is your login, which is compared against ALLOWED_GITHUB_LOGINS.

Nothing depends on GitHub specifically. FastMCP ships providers for Google, Azure, Auth0, Keycloak, Discord, WorkOS and others; swapping is a one-line import change in server.py plus the matching credentials. What will not work is FastMCP's InMemoryOAuthProvider — it simulates the flow for tests and authenticates nobody, so on a public URL it would admit anyone.

Related MCP server: whoop-mcp

Access control

Holding a valid GitHub identity is not enough. ALLOWED_GITHUB_LOGINS lists the accounts permitted to use the server, and every message is checked against it. If the list is empty the server refuses to start — an unset allowlist fails closed rather than exposing health data to any GitHub user.

Tools

Diagnosticswhoop_status

Userwhoop_get_profile, whoop_get_body_measurement, whoop_revoke_access (destructive, requires confirm=true)

Cycleswhoop_get_cycles, whoop_get_cycle, whoop_get_current_cycle, whoop_get_sleep_for_cycle, whoop_get_recovery_for_cycle

Recoverywhoop_get_recoveries, whoop_get_latest_recovery, whoop_get_recovery_summary

Sleepwhoop_get_sleeps, whoop_get_sleep, whoop_get_latest_sleep, whoop_get_sleep_summary

Workoutswhoop_get_workouts, whoop_get_workout, whoop_get_strain_summary

A WHOOP cycle is a physiological day that begins at sleep onset, not at midnight — which is why cycle ids, not dates, tie sleep and recovery together.

Setup

1. GitHub OAuth app

Create one at Settings → Developer settings → OAuth Apps with the callback URL <BASE_URL>/auth/callback. Copy the client id and secret.

2. WHOOP developer app

Create one at developer.whoop.com with:

  • redirect URI <BASE_URL>/oauth/callback

  • these scopes ticked: read:profile, read:body_measurement, read:cycles, read:recovery, read:sleep, read:workout

You will not find an offline scope in the dashboard, and that is expected. offline is not configured on the app — it is sent in the authorization request, and it is what makes WHOOP return a refresh token instead of a one-hour access token. The server adds it automatically; nothing to do.

3. Configure and run

cp .env.example .env   # then fill it in
docker build -t mcp-whoop .
docker run -d --name mcp-whoop --env-file .env -v /srv/whoop-data:/data -p 8000:8000 mcp-whoop

4. Connect WHOOP (once)

Open <BASE_URL>/oauth/start?token=<SETUP_TOKEN> in a browser and approve. The refresh token is written to /data/whoop_token.json with mode 0600. Verify with the whoop_status tool.

5. Add the connector

  • Claude Codeclaude mcp add --transport http whoop <BASE_URL>/mcp

  • claude.ai / Claude Desktop — Settings → Connectors → Add custom connector → <BASE_URL>/mcp

Both then send you through GitHub to sign in.

Configuration

Variable

Purpose

BASE_URL

Public URL of this server, no trailing slash

GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET

GitHub OAuth app credentials

ALLOWED_GITHUB_LOGINS

Comma-separated logins permitted to connect

JWT_SIGNING_KEY

Stable key for client tokens; unset means clients are signed out on restart

WHOOP_CLIENT_ID / WHOOP_CLIENT_SECRET

WHOOP developer app credentials

SETUP_TOKEN

Guards /oauth/start

DATA_DIR

Token store location (default /data)

HOST / PORT

Bind address (default 0.0.0.0:8000)

Notes

WHOOP rotates refresh tokens: each refresh returns a new one and invalidates the old. The store therefore writes atomically and under a lock, so a crash or two concurrent requests cannot strand the server without a valid token.

Licence

MIT

Related MCP Connectors

Related MCP Servers