A TypeScript MCP (Model Context Protocol) server that integrates with Intervals.icu, Whoop and TrainerRoad to provide unified access to fitness data across all activities and sports.
- Completed workouts from Intervals.icu with matched Whoop strain
- Recovery, HRV, sleep, and strain from Whoop
- Planned workouts from TrainerRoad and Intervals.icu
- Sync TrainerRoad running workouts to Intervals.icu (for Zwift/Garmin)
- Fitness trends (CTL/ATL/TSB) and detailed workout analysis with intervals, notes, and weather
- Heat strain and heat adaptation score from a CORE Body Temperature sensor
- Weather, AQI, and pollen forecasts up to 10 days out for Intervals.icu weather locations or any geocoded place
- Generates activity descriptions for Strava
Note: Workouts imported from Strava can't be analyzed due to API restrictions. Sync them from Zwift, Garmin Connect, Dropbox, etc. instead.
get_todays_summary- Full snapshot of today: recovery, sleep, HRV, strain, fitness (CTL/ATL/TSB), wellness, completed and planned workouts, race, calendar annotations (sick/injured/holiday/notes), and weather.get_todays_activities- Today's completed and planned workouts, race, and calendar annotations (sick/injured/holiday/notes). Leaner alternative toget_todays_summary.
get_athlete_profile- Athlete profile, unit preferences, age, and location.get_sports_settings- FTP, zones, and thresholds per sport.
get_strain_history- Whoop strain and activities over a date range.get_activity_history- Past completed workouts with matched Whoop strain and calendar annotations.get_recovery_trends- HRV, sleep, and recovery patterns over time.get_wellness_trends- Daily Intervals.icu wellness metrics over a date range.get_activity_totals- Aggregated totals over a date range: duration, distance, training load, and zone distributions by sport.
get_upcoming_activities- Planned workouts (TrainerRoad + Intervals.icu) and races for a future date range, plus calendar annotations.
create_workout- Create a structured cycling/running/swimming workout from a plain-language description.update_workout- Update a Domestique-created workout..delete_workout- Delete a Domestique-created workout.sync_trainerroad_runs- Sync running workouts from TrainerRoad to Intervals.icu, detecting changes and orphans.set_workout_intervals- Set intervals on a completed activity.update_activity- Update name and/or description of a completed activity.regenerate_descriptions- Regenerate the descriptions of a day's completed activities, or a single activity by ID.
create_annotation- Add a sick/injured/holiday/note annotation to the Intervals.icu calendar.update_annotation- Update a Domestique-created annotation.delete_annotation- Delete a Domestique-created annotation.
get_training_load_trends- CTL, ATL, TSB, ramp rate, and ACWR over time.get_workout_details- All details for one workout: intervals, notes, weather, zones, music, and matched Whoop strain.
get_power_curve- Best watts at various durations with W/kg, estimated FTP, and period comparison.get_pace_curve- Best running/swimming times at key distances.get_hr_curve- Max sustained HR at various durations.
get_weather_forecast- Forecast for a date and optional location, with AQI and pollen.
- Node.js 20+
- Intervals.icu account with API key
- Whoop account with OAuth credentials
- TrainerRoad account with calendar feed URL
Copy .env.example to .env and fill in your credentials:
cp .env.example .envRequired variables:
MCP_AUTH_TOKEN- Secret token for MCP authentication. You can quickly generate one with:
openssl rand -hex 32For Intervals.icu integration:
INTERVALS_API_KEY- Your Intervals.icu API keyINTERVALS_ATHLETE_ID- Your Intervals.icu athlete ID
For Whoop integration:
WHOOP_CLIENT_IDWHOOP_CLIENT_SECRETREDIS_URL- Required for token storage (e.g.,redis://localhost:6379)WHOOP_REDIRECT_URI- Optional. Auto-detected based on environment:- On Fly.io:
https://{FLY_APP_NAME}.fly.dev/callback - Otherwise:
http://localhost:3000/callback
- On Fly.io:
For TrainerRoad integration:
TRAINERROAD_CALENDAR_URL- Private iCal feed URL
For weather forecasts (optional):
GOOGLE_API_KEY- Google Cloud API key with the Weather, Air Quality, Pollen, Elevation, Geocoding, and Time Zone APIs enabled.
For Last.fm integration (optional):
LASTFM_USERNAME- Last.fm username.LASTFM_API_KEY- Last.fm API key.
When both are set, get_workout_details and get_todays_activities include tracks played during the workout. ("Why not use Spotify?" you may be wondering. Ingesting Spotify data into AI is against their developer policy.)
For Anthropic API integration:
ANTHROPIC_API_KEY- Required forcreate_workoutandupdate_workouton cycling and running (server converts the plain-languagestructurefield into Intervals.icu workout-doc syntax). Also enables, when set, Claude for TrainerRoad annotation categorization (Sick/Injured/Holiday/Note), triathlon race priority extraction (A/B/C from the umbrella description), auto-generated activity descriptions onworkout.updatedWhoop webhooks, and debug token counting.ANTHROPIC_CLASSIFIER_MODEL- Optional override for the model used by the annotation and race-priority classifiers. Defaults toclaude-haiku-4-5.ANTHROPIC_DESCRIPTION_MODEL- Optional override for the model used by the activity-description generator. Defaults toclaude-sonnet-4-6.ANTHROPIC_WORKOUT_MODEL- Optional override for the model used by thecreate_workout/update_workoutstructure-to-syntax converter. Defaults toclaude-sonnet-4-6.ANTHROPIC_TOKEN_COUNTER_MODEL- Optional override for the model used by dev-only debug token counting. Defaults toclaude-haiku-4-5.
For the /api endpoints (optional):
API_SECRET- Authenticates the/apiendpoints viaAuthorization: Bearer. Generate withopenssl rand -hex 32.
For error reporting (optional):
BUGSNAG_API_KEY- Reports upstream API failures (Intervals.icu, Whoop, TrainerRoad) to Bugsnag.
Whoop uses OAuth 2.0 with single-use refresh tokens stored in Redis. One-time setup:
- Create a Whoop developer app at https://developer.whoop.com to get your
WHOOP_CLIENT_IDandWHOOP_CLIENT_SECRET. - Ensure Redis is running and
REDIS_URLis set in.env. - Start Docker:
docker compose up -d - Run the OAuth script:
docker compose exec domestique npm run whoop:auth - Open the displayed URL, authorize, and paste the resulting code back into the script.
The server refreshes tokens automatically thereafter.
When Whoop is configured, Domestique exposes POST /webhooks/whoop and uses it to:
- Sync the day's Whoop strain to Intervals.icu wellness.
- Set per-workout Whoop strain on the matching Intervals.icu activity.
- Auto-generate a description for the completed activity (requires
ANTHROPIC_API_KEY).
One-time setup in Intervals.icu — create these custom fields:
- Wellness:
WhoopStrain(Number) - Activity:
WhoopWorkoutStrain(Number)
One-time setup in Whoop — in your Whoop developer dashboard, add the webhook URL https://{your-host}/webhooks/whoop and select Model Version: v2.
When API_SECRET is set, Domestique exposes two HTTP endpoints for callers like iOS Shortcuts, authenticated with Authorization: Bearer <API_SECRET>:
PUT /api/location— sets the athlete's current location from a JSON body{ "latitude", "longitude" }(also requiresGOOGLE_API_KEY). Same as theupdate_locationtool.POST /api/activities/descriptions— regenerates a day's activity descriptions. Optional JSON body{ "date": "YYYY-MM-DD" }(defaults to today) or{ "activity_id": "..." }to regenerate a single activity (takes precedence overdate). Same as theregenerate_descriptionstool.
Recommended: docker compose up (hot reload, server at http://localhost:3000). Use docker compose exec domestique <command> to run commands inside the container (e.g., npm run typecheck, npm run whoop:auth).
Without Docker:
npm install
npm run dev # hot reload
npm run build && npm startnpx @modelcontextprotocol/inspector --server-url "http://localhost:3000/mcp?token=YOUR_SECRET_TOKEN"In development mode, set ANTHROPIC_API_KEY to include a token_count field in _meta on tool responses. _meta is out-of-band per the MCP spec, so it isn't shown to the model.
Set LOG_MCP_REQUESTS=true to log incoming JSON-RPC requests on /mcp.
- Install Fly CLI and log in:
curl -L https://fly.io/install.sh | sh && fly auth login - Deploy Redis (required for Whoop tokens):
cd fly-redis fly apps create domestique-redis fly volumes create redis_data --region iad --size 1 fly deploy cd ..
- Deploy Domestique:
fly apps create domestique fly secrets set MCP_AUTH_TOKEN=... INTERVALS_API_KEY=... INTERVALS_ATHLETE_ID=... \ WHOOP_CLIENT_ID=... WHOOP_CLIENT_SECRET=... REDIS_URL=redis://domestique-redis.internal:6379 fly deploy - Whoop tokens (if using Whoop):
fly ssh console -C "npm run whoop:auth". The redirect URI is auto-set tohttps://{your-app}.fly.dev/callback— register it in your Whoop app.
Add as a connector with: https://{FLY_APP_NAME}.fly.dev/mcp?token=YOUR_SECRET_TOKEN (substitute your MCP_AUTH_TOKEN and host).
- "How did my workout go today?"
- "What's my recovery like this morning?"
- "Show me my fitness trends for the last month"
- "How has my HRV trended compared to my training load?"
- "What's my power curve for the last 90 days?"
- "Show me my running pace curve—what's my best 5km time?"
- "How has my weight changed over the last 30 days?"
- "Sync my TrainerRoad runs to Intervals.icu"
- "What's the weather like for my race in Boulder next Saturday?"
Tested with Claude and ChatGPT. Notes:
- Tool responses: Every tool declares an
outputSchemaper the 2025-11-25 MCP spec. The payload is returned asstructuredContentand also serialized intocontentfor older clients. Field descriptions are delivered viatools/list, not embedded per response. _metafields: ChatGPT provides_meta(location, locale, etc.) on tool inputs; Claude doesn't.