Skip to main content

MCP Server

The CLI includes an MCP (Model Context Protocol) server that exposes WHOOP data to AI assistants like Claude.

What is MCP?

MCP is Anthropic's protocol for connecting AI assistants to external tools and data sources. It provides:

  • Standardized tool definitions
  • Request/response handling
  • Multiple transport options

Learn more at modelcontextprotocol.io.

Starting the Server

STDIO Transport (Default)

For Claude Desktop integration:

wellpipe serve

The server communicates via stdin/stdout, which is what Claude Desktop expects.

HTTP Transport

For custom integrations:

wellpipe serve --transport http --port 3000

The server exposes an HTTP endpoint at http://localhost:3000/mcp.

Claude Desktop Configuration

Add Wellpipe to your Claude Desktop config:

macOS: ~/Library/Application Support/Claude/claude_desktop_config.json

Windows: %APPDATA%\Claude\claude_desktop_config.json

{
"mcpServers": {
"wellpipe": {
"command": "npx",
"args": ["@wellpipe/cli", "serve"],
"env": {
"WHOOP_ACCESS_TOKEN": "your-access-token",
"WHOOP_REFRESH_TOKEN": "your-refresh-token"
}
}
}
}

Or using a config file:

{
"mcpServers": {
"wellpipe": {
"command": "npx",
"args": ["@wellpipe/cli", "serve"],
"cwd": "/path/to/your/.env/directory"
}
}
}

Server Architecture

┌─────────────────────────────────────────────────────┐
│ MCP Server │
├─────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────────────────┐ │
│ │ Transport │◀──▶│ Tool Registry │ │
│ │ (stdio/http)│ │ │ │
│ └──────────────┘ │ ┌────────────────────┐ │ │
│ │ │ get-sleep-data │ │ │
│ ┌──────────────┐ │ ├────────────────────┤ │ │
│ │ Token │◀──▶│ │ get-recovery-data │ │ │
│ │ Provider │ │ ├────────────────────┤ │ │
│ └──────────────┘ │ │ get-workout-data │ │ │
│ │ ├────────────────────┤ │ │
│ ┌──────────────┐ │ │ ...21 tools │ │ │
│ │ WHOOP Client │◀──▶│ └────────────────────┘ │ │
│ └──────────────┘ └──────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘

Implementation

Server Setup

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { WhoopClient, getAllTools } from '@wellpipe/whoop';

export async function createServer(tokenProvider: TokenProvider) {
const client = new WhoopClient(tokenProvider);
const tools = getAllTools();

const server = new Server(
{ name: 'wellpipe', version: '1.0.0' },
{ capabilities: { tools: {} } }
);

// Register tool list handler
server.setRequestHandler('tools/list', async () => ({
tools: tools.map(t => ({
name: t.name,
description: t.description,
inputSchema: t.inputSchema,
})),
}));

// Register tool call handler
server.setRequestHandler('tools/call', async (request) => {
const tool = tools.find(t => t.name === request.params.name);
if (!tool) {
throw new Error(`Unknown tool: ${request.params.name}`);
}

const result = await tool.handler(request.params.arguments, client);
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
});

return server;
}

Token Provider

The CLI uses an environment-based token provider:

import dotenv from 'dotenv';
import fs from 'fs';

class EnvTokenProvider implements TokenProvider {
private envPath: string;

constructor(envPath: string = '.env') {
this.envPath = envPath;
dotenv.config({ path: envPath });
}

async getAccessToken(): Promise<string> {
if (await this.isExpired()) {
await this.refresh();
}
return process.env.WHOOP_ACCESS_TOKEN!;
}

async getRefreshToken(): Promise<string | undefined> {
return process.env.WHOOP_REFRESH_TOKEN;
}

async updateTokens(
accessToken: string,
refreshToken?: string,
expiresAt?: Date
): Promise<void> {
// Update environment variables
process.env.WHOOP_ACCESS_TOKEN = accessToken;
if (refreshToken) process.env.WHOOP_REFRESH_TOKEN = refreshToken;
if (expiresAt) process.env.WHOOP_TOKEN_EXPIRES_AT = expiresAt.toISOString();

// Persist to .env file
const content = `
WHOOP_ACCESS_TOKEN=${accessToken}
WHOOP_REFRESH_TOKEN=${refreshToken || ''}
WHOOP_TOKEN_EXPIRES_AT=${expiresAt?.toISOString() || ''}
`.trim();

fs.writeFileSync(this.envPath, content);
}

async isExpired(): Promise<boolean> {
const expiresAt = process.env.WHOOP_TOKEN_EXPIRES_AT;
if (!expiresAt) return false;

const expiry = new Date(expiresAt);
const buffer = 5 * 60 * 1000; // 5 minutes
return Date.now() >= expiry.getTime() - buffer;
}

private async refresh(): Promise<void> {
const refreshToken = await this.getRefreshToken();
if (!refreshToken) {
throw new Error('No refresh token available');
}

// Call WHOOP token endpoint
const response = await fetch('https://api.prod.whoop.com/oauth/oauth2/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'refresh_token',
client_id: process.env.WHOOP_CLIENT_ID!,
refresh_token: refreshToken,
}),
});

const tokens = await response.json();
await this.updateTokens(
tokens.access_token,
tokens.refresh_token,
new Date(Date.now() + tokens.expires_in * 1000)
);
}
}

Environment Variables

The server reads these environment variables:

VariableRequiredDescription
WHOOP_ACCESS_TOKENYesCurrent access token
WHOOP_REFRESH_TOKENYesRefresh token for renewal
WHOOP_TOKEN_EXPIRES_ATNoToken expiration timestamp
WHOOP_CLIENT_IDFor refreshDeveloper app client ID

Debugging

Verbose Logging

DEBUG=wellpipe:* wellpipe serve

Test Tool Calls

With HTTP transport, you can test tools directly:

curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "get-last-sleep",
"arguments": {}
},
"id": 1
}'

Check Available Tools

curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tools/list",
"params": {},
"id": 1
}'

Error Handling

The server returns structured errors:

{
"jsonrpc": "2.0",
"error": {
"code": -32000,
"message": "WHOOP API error",
"data": {
"status": 401,
"detail": "Token expired"
}
},
"id": 1
}

Common error codes:

CodeMeaning
-32600Invalid request
-32601Unknown tool
-32602Invalid parameters
-32000Provider error