Intervals.icu MCP Server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Intervals.icu MCP Server What was my training load and fatigue over the last week?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Intervals.icu MCP Server
Local FastMCP server for evidence reads and typed, durable Intervals.icu operations. The active write interface is intentionally small: one typed operation engine plus status, read-only recovery, and an audited admin release.
Environment
Use Python 3.12 and uv. The declared tzdata dependency is required on
Windows; use the synced project environment rather than an arbitrary global
interpreter.
uv venv --python 3.12
uv sync --all-extras
uv run pytest -q
uv run ruff check .
uv run mypy src testsCopy .env.example to an ignored .env only for a manually started local
server. Tests use synthetic transports and must not load credentials or access a
live account.
Related MCP server: intervals.mcp
Run locally
$env:MCP_TRANSPORT = 'stdio'
uv run python -m intervals_mcp_server.serverFor the repository's loopback-only Streamable HTTP setup, use the existing
startup script and http://127.0.0.1:8000/mcp. Do not expose that endpoint:
the local server has no separate inbound HTTP authentication.
Public tools
The retained read surface is:
get_capabilitiesget_activities,get_activity_details,get_activity_intervals,get_activity_messages,get_activity_streams,get_activity_data_qualityexport_activity_data,get_artifact_chunkget_activity_interval_stats,get_activity_best_efforts,get_activity_power_hrget_athlete_power_curves,get_activity_power_curvesget_metric_definitions,get_sport_settings,get_wellness_dataget_events,get_event_by_id,get_workout_snapshotget_custom_items,get_custom_item_by_id
The only operation tools are:
execute_operationget_operation_statusrecover_operationrelease_operation_risk
admin exposes all four. coach omits risk release. readonly exposes
status and read-only recovery. Administrative operations carry explicit user
direction and do not fabricate a TATRA decision or session identity.
Exact pairing, resource schemas, outcome semantics, locks and read-back rules are documented in docs/agent-tools-contracts.md.
Durable state and offline migration
Operation records are self-hashed and history-chained. unknown is not
partial; uncertain effects keep their exact hold. Recovery only reconciles
durable evidence and never resends a mutation.
scripts/migrate_operation_state.py classifies a frozen copy of older workout
and analysis-comment journals. A record is promoted to typed-v1 only when the
source already retained ready modern evidence refs and any required
evidence-bound preview; the migrated request must pass semantic scope binding.
Missing evidence, an unsafe delete date, and prepared are retained as
hash-sealed, non-executable legacy archives with explicit limitations. The tool
never invents evidence, previews or preconditions.
For a previously confirmed create, the migrated request retains the exact
canonical semantic create_identity. The upstream event_id or message_id
remains in the historical result, execution identity, read-back basis and
provenance; it is not substituted into request identity. Exact retry returns
that historical result, changed content conflicts, and neither path calls an
adapter.
The utility requires explicit offline confirmation, writes a deterministic
external backup first, rejects drive/root/UNC/traversal/backslash paths and
duplicate or case-colliding entries before writing, refuses occupied targets,
and rejects a destination equal to or nested below the canonical source before
creating backup or staging paths. Normalized .., symlink-parent and Windows
case aliases follow the same rule; backup and staging remain external and
distinct. It makes no upstream request and stages a candidate directory only;
switching a running installation is a separate operator action.
Evidence boundary
Passing tests and synthetic replay do not establish live publication, physiological validity, or account correctness. Live actions require explicit configuration and user authority; they are outside routine test execution.
License
GNU General Public License v3.0.
Available Tools
26 toolsexecute_operationC
Execute one version-negotiated typed operation through the durable engine.
The exact operation UID is the retry identity. Changed content conflicts; an uncertain attempt is never sent again.
Inputs: operation (required), client_offer (required). Returns: Result semantics keep partial and unknown effects explicit.
export_activity_dataA
Export complete raw activity data to the configured local artifact.
Use this after a compact read when the client needs all stream samples and
interval records. The stream arrays retain their upstream index and null
values; this read validates their shape before handing them to the local
artifact store. An optional stream_types filter narrows the streams,
and an optional half-open sample range slices each returned array. The
manifest reports source unit conflicts and time-axis ambiguity; optional
type-based unit hints are separate from source declarations. Streams
and intervals come from separate
HTTP reads: the returned hash verifies the local composite bytes, not an
atomic upstream snapshot. Use get_artifact_chunk with the opaque ID when
the MCP client cannot access the server filesystem. Source completeness
remains unknown until the upstream data contract says otherwise.
Tymewear VT/VE remain in raw device units, with no conversion to liters;
use get_metric_definitions for respiratory field and FIT mapping context.
Inputs: activity_id (required), api_key, end_index, start_index, +2 more. Returns: data, error, query, source, +6 more
get_activitiesA
List activities in a half-open local-date range with bounded paging.
start_date is inclusive and end_date_exclusive is exclusive. A
valid empty list is different from a malformed upstream response. The
first page is snapshotted for cursor continuation, and a cursor is valid
only for the same athlete, range, timezone, and sport filter. The source
endpoint does not expose a trustworthy completeness marker, so source
completeness stays null even when the returned list is short. A
Hidden source stub without a sport field is retained for identity, but a
requested sport match is reported as unverified.
Inputs: limit, cursor, sports, api_key, +5 more. Returns: data, error, query, source, +6 more
get_activity_best_effortsA
Find upstream best efforts by one duration or distance selector.
Choose this to ask Intervals.icu for ranked efforts on a named stream.
duration is positive seconds or distance is positive finite metres;
exactly one is required. start_index and the exclusive end_index
are sample indices. end_index=0 is the upstream whole-stream
sentinel, while end_index=None omits that optional parameter.
count is locally limited to 1..100. The MCP preserves
each Effort and its metadata, reports average units (W, bpm, m/s, or
unknown for custom streams), and performs no FTP, VO2, or numeric
calculations. min_value may expand an effort, so returned durations
and bounds remain upstream facts; source completeness is unknown.
Inputs: stream (required), activity_id (required), count, api_key, +6 more. Returns: data, error, query, source, +6 more
get_activity_data_qualityA
Summarize all returned stream samples and activity metadata using GETs only.
Finite fractions use source sample count, not elapsed time. Time gaps are relative to a 1-second reference and do not prove dropped sensor data. Optional secondary arrays, missing primary arrays, zero and null are distinct. Multiple time streams are reported as an ambiguous axis, without choosing the first. Source unit conflicts and inferred display hints are separate. Metadata and stream failures are independent. No sensor-source, physiological or readiness inference is made, and only 20 gap examples are returned. Conditional Tymewear documentation is in provenance.respiratory_interpretation: VT/VE use relative device volume units, BR uses breaths/min. Finite raw VT values such as 186 are not invalid merely because they are not in liters. No volume conversion or VE=VT*BR consistency check is performed.
Inputs: activity_id (required), api_key. Returns: data, error, query, source, +6 more
get_activity_detailsA
Return one activity with upstream fields preserved.
A source-hidden record is returned as partial with an explicit
limitation. In that case the row is useful for identity and timing, but
it is not evidence that metrics, intervals, or streams are available; use
the dedicated interval and stream tools to check those resources. Set
include_intervals to request the upstream embedded interval container;
missing embedded intervals still require get_activity_intervals.
For Tymewear, VT is relative tidal volume per breath (not VT1/VT2), VE
is relative minute ventilation, and BR is breaths/min. Custom L/br or
L/min labels are not calibration evidence; use get_metric_definitions.
Inputs: activity_id (required), api_key, include_intervals. Returns: data, error, query, source, +6 more
get_activity_intervalsA
Return the activity interval container with index fields intact.
The documented shape contains icu_intervals and optionally
icu_groups. A legacy flat list of interval objects is retained for
compatibility with existing callers; all other shapes and mixed rows are
explicit errors. Intervals use upstream sample indices, not invented
elapsed seconds, and an empty valid container remains empty.
average_tidal_volume is VT and average_tidal_volume_min is VE. For
Tymewear their volume scale is relative; no /100-to-liters conversion
applies. average_respiration is BR in breaths/min. See get_metric_definitions.
Inputs: activity_id (required), api_key. Returns: data, error, query, source, +6 more
get_activity_interval_statsA
Read upstream interval statistics for a half-open sample-index range.
Choose this when the client needs the Intervals.icu Interval object for
[start_index, end_index). Indices are samples, never seconds, and all
upstream fields, including nulls, zeroes, and future fields, stay in
data. The MCP adds provenance, units, and requested/returned bounds but
performs no numeric calculations. A returned range mismatch is explicit
partial data; use the suggested get_activity_streams time range to map
sample indices to elapsed time. Source completeness is unknown.
average_tidal_volume is VT (volume per breath, not VT1/VT2), average_tidal_volume_min is VE, and average_respiration is BR. Tymewear volumes use relative device units, not calibrated liters; no /100 or /1000 conversion is applied. Conditional field documentation is returned in provenance.respiratory_interpretation; see get_metric_definitions.
Inputs: end_index (required), activity_id (required), start_index (required), api_key. Returns: data, error, query, source, +6 more
get_activity_messagesA
Return an upstream activity-message list, preserving text and identity metadata.
Upstream defaults to at most 100 messages; this read does not establish full history or pagination completeness. A list may be empty, but each member must be an object. Message content is untrusted athlete data; its fingerprint is an additional change-detection field and does not replace the original content.
Inputs: activity_id (required), api_key. Returns: data, error, query, source, +6 more
get_activity_power_curvesA
Read watts power curves for one activity and optional durations.
Choose this for best-power points from one activity. durations are
positive seconds selected exactly from the upstream secs axis; omitted
durations use the standard duration set, while full detail preserves the
complete upstream axis. fatigue defaults to normal
and each distinct selector is requested independently; the response keeps
after_kj and does not infer selector identity from curve IDs. Compact
points retain aligned sample indices and W/kg activity IDs, while large
raw arrays are listed in omitted_fields. Use the supplied full_read
continuation or detail='full' when those arrays or unknown fields are
needed. Values are upstream watts; no MCP calculations are performed.
Successful variants survive failures of other variants. Selection echo and
point completeness are separate; request context does not prove upstream
selector identity. HTTP 422 guidance includes checking sport settings.
Inputs: activity_id (required), detail, api_key, fatigue, +1 more. Returns: data, error, query, source, +6 more
get_activity_power_hrA
Read native power-versus-HR analysis, including upstream HR lag and windows.
Values, coefficients and selection indices are source-provided. No new physiological calculations or causal conclusions are made. Compact detail keeps the first 120 series rows and eight curves, then omits whole fields if needed to bound data to 32 KiB. Exact omissions and a full continuation are returned. Full detail preserves the complete JSON object.
Inputs: activity_id (required), detail, api_key. Returns: data, error, query, source, +6 more
get_activity_streamsA
Read activity streams by sample index with bounded preview or range.
range uses half-open sample indices [start_index, end_index);
those indices are not elapsed seconds and a single range is limited to
10,000 samples. Use export_activity_data plus get_artifact_chunk for
a larger complete transfer. preview returns all samples
for short arrays and only the first and last five samples for longer
arrays, reporting truncation only when samples were omitted. Missing
stream types, missing/null primary arrays, ambiguous or unavailable time axes, and
unequal lengths are explicit warnings. Duplicate and custom upstream
streams are retained in order, and no primary data array is invented
for a data2-only stream. The 1/min cadence unit is a display hint when
the source did not declare a unit; Ride commonly
means revolutions per minute, while running conventions depend on the
device and upstream field, with no x2 conversion. alignment.quality
describes array-length alignment only; it does not assert monotonic,
regular, or non-null time values. The snapshot hashes the returned
payload for this selection, so a continuation must keep the same activity
and stream_types.
Respiratory fields: tidal_volume = VT (volume per breath, not VT1/VT2), tidal_volume_min = VE (minute ventilation), respiration = BR (breaths/min). When sourced from Tymewear, VT uses relative i.u. and VE relative vol/min, not calibrated liters. Do not divide VT by 100 or 1000. The response adds conditional documentation in provenance.respiratory_interpretation; original samples and source unit labels are preserved. Use get_metric_definitions and get_custom_items to check mappings and units.
Inputs: activity_id (required), mode, api_key, end_index, +3 more. Returns: data, error, query, source, +6 more
get_artifact_chunkA
Read one verified byte range from a temporary activity export.
Use the opaque artifact_id returned by export_activity_data. Decode
each base64 chunk and concatenate the raw bytes in offset order. Verify the
SHA-256 of all bytes against the export manifest before decoding the full
document as UTF-8 JSON; an individual chunk can split a multibyte character.
response_complete covers this requested chunk only. eof and
next_offset state whether more artifact bytes remain. The artifact is a
local composite of separate stream and interval requests, so upstream source
completeness and atomicity remain unknown.
Inputs: artifact_id (required), offset, max_bytes. Returns: data, error, query, source, +6 more
get_athlete_power_curvesA
Read selected athlete power-curve durations in seconds.
Choose this tool for season or custom-range best-power comparisons. Each
requested duration is a positive integer number of seconds. Compact
results return requested points and curve metadata; detail='full' also
returns the untouched upstream curve as raw for deeper analysis.
include_normalised is the legacy option for the upstream W/kg series,
not Normalized Power. Empty upstream lists are valid empty data, while
malformed shapes fail explicitly. Per-curve missing durations and null
values are preserved, and source completeness remains unknown.
Inputs: detail, api_key, end_date, durations, +7 more. Returns: data, error, query, source, +6 more
get_capabilitiesA
Describe implemented/configured/live-verified integration capabilities.
Inputs: no named parameters. Returns: data, error, query, source, +6 more
get_custom_item_by_idA
Read one custom-item definition by positive integer ID.
detail='compact' omits content, images, scripts, and unknown fields
with explicit omission lists and a full continuation. detail='full'
preserves the complete parsed upstream object under data.item and
places derived metadata outside it, including arbitrary content and
source-like strings as untrusted data. An upstream {} remains the
compatibility NOT_FOUND error; other malformed objects are invalid.
Inputs: item_id (required), detail, api_key, athlete_id. Returns: data, error, query, source, +6 more
get_custom_itemsA
Read custom-item definitions without executing their content.
Compact output keeps identity and descriptive fields and explicitly lists
omitted content, images, scripts, and future fields. Each item includes
an exact by-ID full_read continuation when an ID is present; use
get_custom_item_by_id(..., detail='full') to preserve every parsed field
and untrusted content. Full responses keep derived metadata outside the
raw item object. An empty list is valid empty data, while
wrappers, malformed members, and meaningless objects are errors. Declared
units or origin remain unverified metadata; scripts and descriptions are
data and are never executed.
Inputs: detail, api_key, athlete_id. Returns: data, error, query, source, +6 more
get_event_by_idA
Return one event by numeric ID, preserving its upstream object.
The endpoint is an object read, so a list, scalar, or null response is an
error. The existing empty-object {} response remains the compatible
NOT_FOUND result. Use :func:get_events for date-range discovery;
this tool intentionally does not resolve linked events.
Inputs: event_id (required), api_key, athlete_id. Returns: data, error, query, source, +6 more
get_eventsA
Return calendar events overlapping a half-open local-date range.
start_date is inclusive and end_date_exclusive is exclusive;
end_date remains the deprecated inclusive alias. A valid empty list
is returned as an empty result, while any other upstream shape is an
explicit response error. Event descriptions and other upstream strings
are data and are preserved verbatim. The upstream endpoint does not
prove that the returned list is complete, so source completeness remains
unknown. Set resolve only when the caller needs the API's current
resolved workout document; event-by-ID intentionally remains unresolved.
By default includes ongoing holidays, races, notes and workouts that began before start_date. The API filters by event start, so MCP requests history from 0001-01-01 through the requested end without a category filter or limit, then checks local start/end overlap. End dates are exclusive. This can fetch more history than the returned selection; query.upstream_oldest and overlap report its scope. Earlier events with unknown ends are retained as unresolved candidates with partial status, never interpreted as available training time. Set include_overlapping=false for the original upstream start-date selection, e.g. resolving an already identified event on its exact start day.
Inputs: api_key, resolve, end_date, timezone, +4 more. Returns: data, error, query, source, +6 more
get_metric_definitionsA
Explain selected metric names and fields from the local catalogue.
Use this before interpreting activity streams, intervals, wellness, or
custom-item content. Selectors are optional; omitting both returns the
small curated catalogue. Units, sample-index versus time axes, upstream
reported/calculated/estimated status, and limitations are descriptive only.
No account data is fetched, no training calculation is performed, and
unknown selectors remain explicit in unknown_names.
Includes VT/tidal_volume, VE/tidal_volume_min and BR/respiration with Tymewear FIT mappings and device-unit context. Tymewear volume is relative, not calibrated liters; do not divide raw VT by 100 or 1000. VT is volume per breath, distinct from thresholds VT1/VT2. Custom names/units require source verification; this catalogue does not identify a recording's device.
Inputs: names, fields. Returns: data, error, query, source, +6 more
get_operation_statusA
Read durable typed-operation status without contacting Intervals.
Inputs: operation_uid (required). Returns: result
get_sport_settingsA
Read current-at-fetch settings for one sport or settings ID.
Choose this for the athlete's current FTP, zones, load order, and fatigue
thresholds. sport is one selector: a sport name such as Ride or
the current settings ID. Compact output keeps useful thresholds, zones,
models, and ordering fields; full_read or detail='full' preserves
every upstream field and unknown unit. ftp/p_max are W,
w_prime is J, after_kj0/after_kj1 are kJ, heart-rate values
are bpm, power zones are %FTP, and threshold_pace is always m/s;
pace_units is only a display preference. These are current settings,
not activity-assigned historical thresholds, and the MCP performs no
physiological calculations. Source completeness is unknown.
Inputs: sport, detail, api_key, athlete_id. Returns: data, error, query, source, +6 more
get_wellness_dataA
Return raw wellness records using a half-open local-date range.
start_date is inclusive and end_date_exclusive is exclusive;
end_date remains the deprecated inclusive alias. The upstream API
may return an array of records or a date-keyed object whose keys are
ISO-local dates. Empty arrays/maps are valid empty data, while null,
scalar, mixed-member, and meaningless date-map shapes are errors. All
native and custom fields, including null and zero, are preserved and
source completeness remains unknown. Consult get_metric_definitions
before interpreting fields: native hrv is rMSSD and hrvSDNN is a
separate millisecond field; generic VO2max method and custom-field units
remain unknown unless explicitly declared by the source.
Inputs: api_key, end_date, timezone, athlete_id, +3 more. Returns: data, error, query, source, +6 more
get_workout_snapshotA
Read a workout's raw event, server version and execution precondition.
Bind data.event_fingerprint unchanged in an execute_operation
update/delete precondition. This token is versioned by MCP, not an upstream
ETag. Missing fields, null and zero remain distinct.
Pairing evidence is read by exact identity, using the event's calendar day when event-by-ID omits paired_activity_id. Positive activity links are reverse-checked by activity.paired_event_id. Only unpaired_observed permits the execution precondition; it does not prove the athlete did not train. Unknown, completed and linked states block safe update/delete. The writer repeats these checks with fresh reads. Source completeness and atomic conditional writes remain unverified. This read does not authorize a write.
Inputs: event_id (required), api_key, athlete_id. Returns: data, error, query, source, +6 more
recover_operationB
Reconcile by independent read-back only; never resend the mutation.
Inputs: operation_uid (required), candidate_identity. Returns: result
release_operation_riskA
Audit an explicit admin risk acceptance and release only its hold.
This does not change the historical unknown result and cannot replay it.
Inputs: release (required). Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 26 tools
The tools are mostly distinct: activity streams, intervals, power curves, best efforts, and data quality each map to different upstream resources. However, several names are near-siblings (get_activity_intervals vs get_activity_interval_stats, get_activity_power_curves vs get_athlete_power_curves) and require reading descriptions to avoid misselection.
Almost all reads use get_<resource>_<detail> in snake_case, with mutation verbs like export/execute/release/recover. The pattern is readable but not perfectly uniform: some singular reads use _by_id (get_event_by_id, get_custom_item_by_id) while get_activity_details and get_workout_snapshot do not follow that suffix.
At 26 tools, the server crosses the 'too many' threshold; even though each tool has a distinct purpose, the dense activity-reader family and operation-management cluster create a large selection surface. A more consolidated design would be more appropriate.
The read/export side is very thorough: activities, streams, intervals, power curves, events, wellness, settings, custom items, and metrics are covered. However, there are no direct create/update/delete tools for activities, events, or custom items; the generic execute_operation is an indirect workaround, and there is no athlete-profile or dedicated workout-list tool beyond get_events.
Maintenance
Related MCP Connectors
Connect Intervals.icu to Claude and ChatGPT: read training, plan workouts. Training never stored.
Connect Claude to your Intervals.icu watch data for fitness, workout review, and plan writing.
Garmin data in Claude & ChatGPT via the Garmin Health API. OAuth sign-in, no password sharing.
tri-train is a training platform for triathletes that syncs activities from Strava and Garmin and exposes them to Claude through a remote MCP server. Ask Claude how your last block went, why your run pace dropped, or whether you are fresh enough for Sunday's race, and it answers from your real data: activities with laps and grade-adjusted pace, weekly load and the performance management chart (CTL/ATL/TSB), sleep, HRV and resting heart rate, and your race calendar with predictions. Claude can also act: create and adjust planned workouts with structured intervals, push them to your Garmin watch, add races and write notes. Every change is journaled and can be undone. Read and write access are separate OAuth scopes, and the server enforces them on every call. Built and hosted in Switzerland by a triathlete; data stays on our own servers (Swiss revDSG / GDPR).
Related MCP Servers
- AlicenseAqualityDmaintenanceConnects Claude with the Intervals.icu API to retrieve fitness data including activities, workouts, wellness metrics, and training events.610389GPL 3.0
- FlicenseAqualityDmaintenanceEnables AI assistants to access and analyze intervals.icu training data including activities, fitness metrics (CTL/ATL/TSB), wellness stats, and calendar events. Supports natural language querying of athletic performance for training insights and workout planning.6-
- AlicenseAqualityDmaintenanceEnables interaction with Intervals.icu data, including activities, wellness, and calendar events, via natural language.1017 npm1MIT
- AlicenseNot gradedqualityDmaintenanceEnables Claude AI to access and manage intervals.icu training data, including workouts, wellness, and fitness trends, through natural language conversation.MIT