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:
| Variable | Required | Description |
|---|---|---|
WHOOP_ACCESS_TOKEN | Yes | Current access token |
WHOOP_REFRESH_TOKEN | Yes | Refresh token for renewal |
WHOOP_TOKEN_EXPIRES_AT | No | Token expiration timestamp |
WHOOP_CLIENT_ID | For refresh | Developer 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:
| Code | Meaning |
|---|---|
| -32600 | Invalid request |
| -32601 | Unknown tool |
| -32602 | Invalid parameters |
| -32000 | Provider error |