A Model Context Protocol (MCP) server for the Intervals.icu API. Connects Claude, Cursor, ChatGPT and any other MCP client to your training data — activities, events, wellness metrics, power curves, gear and custom items.
Zero-config first run: npx intervals-mcp-server@latest walks you through
authentication in your terminal and saves everything for future runs.
- Node.js ≥ 18.19
- An Intervals.icu account
npx intervals-mcp-server authThe wizard will:
- Open https://intervals.icu/settings in your browser
- Ask you to copy two values from that page:
- API Key — the "API Key" row, click Show
- Athlete ID — shown on the same page (e.g.
123456ori12345)
- Verify them against the live API
- Save them to
~/.config/intervals-mcp-server/config.json(permissions0600) - Print ready-to-paste snippets for your MCP client
Claude Code
claude mcp add intervals -s user -- npx intervals-mcp-server@latestClaude Desktop — ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"intervals": { "command": "npx", "args": ["intervals-mcp-server@latest"] }
}
}Cursor — ~/.cursor/mcp.json:
{
"mcpServers": {
"intervals": { "command": "npx", "args": ["intervals-mcp-server@latest"] }
}
}Done — the client spawns the server via npx and all tools are available.
Why a config file instead of env vars? MCP clients spawn servers with a minimal environment, so shell exports like
API_KEYare not inherited automatically. The wizard-written config file works everywhere. Env vars are still supported and take precedence when declared in the client config.
If you prefer declaring credentials in your MCP client config:
{
"mcpServers": {
"intervals": {
"command": "npx",
"args": ["intervals-mcp-server@latest"],
"env": {
"API_KEY": "your-api-key",
"ATHLETE_ID": "your-athlete-id"
}
}
}
}Resolution order: API_KEY / ATHLETE_ID env vars → config file.
INTERVALS_API_BASE_URL overrides the API base URL (default https://intervals.icu/api/v1).
| Area | Tools |
|---|---|
| Activities | get_activities, get_activity_details, get_activity_intervals, get_activity_streams, get_activity_messages, add_activity_message |
| Events & calendar | get_events, get_event_by_id, add_or_update_event, add_or_update_note, delete_event, delete_events_by_date_range |
| Wellness | get_wellness_data |
| Power curves | get_athlete_power_curves |
| Gear | get_gear_list |
| Custom items | get_custom_items, get_custom_item_by_id, create_custom_item, update_custom_item, delete_custom_item |
Tool names use stable snake_case identifiers, so prompts and integrations keep working across updates.
Activities can arrive partially — the activity record lands before all streams and server-side analysis finish. To keep the model from answering confidently from half-synced data, the activity tools surface native API signals and derive a verdict from them:
get_activities— one-lineSync:verdict per activity (complete / pending / incomplete) plus last-sync time and upstream sourceget_activity_details— full Sync & Data Completeness section:analyzed,icu_sync_date,icu_sync_error,analysis_issues,stream_types,source/external_idget_activity_streams— warns when a requested stream type is missing (it may still be syncing) instead of silently omitting it
The verdict is marked (derived) — the API has no explicit completeness
flag. Caveat per the Intervals.icu maintainer: analyzed may stay null on
Strava-sourced activities while the data is fine; those are reported as
pending rather than incomplete.
Stream quirk handled for you: the latlng stream stores latitude in
data and longitude in a sibling data2 field; the streams tool zips
them into [lat, lng] points so the longitude half isn't lost.
intervals-mcp-server # serve over stdio; with no credentials yet and an
# interactive terminal, run the setup wizard instead
# (exits after saving so clients spawn a clean process)
intervals-mcp-server auth # (re)run the credential wizard
intervals-mcp-server serve # start the server without the onboarding check
--transport stdio|streamable-http # transport (default stdio)
--host <host> # HTTP host (default 127.0.0.1)
--port <port> # HTTP port (default 8765)
For remote setups (e.g. behind a tunnel for ChatGPT custom connectors):
npx intervals-mcp-server serve --transport streamable-http --port 8765
# MCP endpoint: http://127.0.0.1:8765/mcppnpm install
pnpm build # tsup → dist/
pnpm test # vitest (114 tests)
pnpm typecheck # tsc --noEmit
pnpm smoke # end-to-end: spawn server, listTools, live API calls
# (needs API_KEY / ATHLETE_ID)Releases are published by GitHub Actions (.github/workflows/publish.yml):
# bump "version" in package.json, commit, then:
git tag vX.Y.Z
git push origin vX.Y.ZThe workflow installs dependencies, runs typecheck + tests + build, then
publishes to npm with provenance using the NPM_TOKEN repository secret.