Skip to content

[intervals-mcp-server] Expose custom activity fields, all streams and per-interval stream metrics - #153

Open
futureweb wants to merge 1 commit into
mvilanova:mainfrom
futureweb:feat/custom-fields-and-streams
Open

futureweb wants to merge 1 commit into
mvilanova:mainfrom
futureweb:feat/custom-fields-and-streams

Conversation

@futureweb

Copy link
Copy Markdown

Motivation

Intervals.icu stores the values of custom items (ACTIVITY_FIELD, INTERVAL_FIELD, ACTIVITY_STREAM, INPUT_FIELD) as plain keys of the activity, interval, stream and wellness payloads, named by the item's content.code. Until now the MCP server rendered a fixed set of standard fields, requested a fixed set of stream types and only previewed the first/last five samples, so an assistant could not see metrics that devices write into custom fields and streams (e.g. Garmin training effect, EPOC, recovery time, VO2max, performance condition, stamina, sweat loss, grade adjusted speed, gear selection, a second power meter). This PR makes all of that available, read-only and data-driven: whatever the athlete has defined is resolved against /athlete/{id}/custom-item; nothing device-specific is hard-coded.

Changes

  • get_activity_details: new section Custom Activity Fields listing every custom activity field with a value: display name, technical code, value, units and (select fields) option label; definitions without a value are named. New optional args include_custom_fields (default True) and include_all_fields (default False, appends every other non-empty payload field such as time in zones or running dynamics). null/NaN (the API serialises NaN as the string "NaN") is reported as no value; a 0 in a field filled from the device file is marked as possibly missing, because Intervals.icu stores 0 when the source field is absent from the file.
  • get_activity_streams: stream_types accepts any stream type, custom codes included, or "all". New optional args output_format (summary as before, full = CSV with one row per sample, json), start_index/end_index, start_time/end_time, downsample (keeps every n-th recorded sample, never averages) and max_points (paging with a note on how to continue). The summary now shows units, custom flag, description and start/end/min/max/mean statistics per stream. The time stream is always included in full/json output because recording pauses appear as jumps in it.
  • list_activity_streams (new): lists the standard and custom streams an activity has, with names, units and descriptions from the definitions; include_stats=True adds per-stream statistics.
  • get_activity_intervals: custom interval fields are listed per interval; new optional stream_types evaluates the raw samples between start_index and end_index of every interval and over all member intervals of a group (start, end, min, max, mean, delta, non-null sample count). "custom" selects every custom stream plus secondary_power, "all" every stream.
  • get_wellness_data(include_all_fields=True): custom wellness fields are annotated with name, units and select label.
  • tools/custom_items.py: per-process cache of the custom item definitions (errors are not cached), invalidated by create_custom_item / update_custom_item / delete_custom_item.
  • New helpers utils/custom_fields.py (definition index, value rendering) and utils/streams.py (range statistics, index/time range resolution, CSV/JSON rendering). format_intervals was split into per-interval/per-group blocks; the legacy text is unchanged.
  • README: tool list and a short Custom fields and streams section.

All existing tool signatures and their default behaviour are unchanged (new arguments are optional); only read-only endpoints are used.

Testing

  • uv run --locked pytest: 99 passed (38 new tests in tests/test_custom_fields.py, tests/test_streams.py, tests/test_custom_streams_tools.py).
  • ruff check .: no new findings (one pre-existing unused import in tools/power_curves.py).
  • mypy src tests: success.
  • pylint --disable=C0301 $(git ls-files '*.py'): no new findings apart from the usual duplicate-code note for the test boilerplate.
  • Manual: ran all extended tools against a real Garmin Edge 1040 ride (3×12 min sweet spot, 4861 samples, 25 streams, 25 custom fields incl. stamina, training effect, EPOC, performance condition, second power meter) both directly and through the SSE transport with an MCP client. Per-interval stamina drop, group statistics, paging and NaN handling matched the raw API payloads.

🤖 Generated with Claude Code

…trics

- get_activity_details lists every custom activity field (name, code, value,
  units, select label) resolved from the athlete's custom item definitions;
  include_all_fields adds the remaining payload fields
- get_activity_streams accepts any stream type (standard or custom) or "all"
  and adds full-resolution CSV/JSON output with index/time slicing,
  downsampling and paging via max_points
- new list_activity_streams tool lists the standard and custom streams of an
  activity with names, units, descriptions and optional statistics
- get_activity_intervals lists custom interval fields and, with stream_types,
  evaluates streams per interval and group (start/end/min/max/mean/delta,
  non-null sample count) from the raw samples between start_index/end_index
- get_wellness_data(include_all_fields=True) labels custom wellness fields
  with name, units and select label
- custom item definitions are cached per process (tools/custom_items.py) and
  invalidated when items are created, updated or deleted
- null/NaN (including the API's "NaN" string) is reported as "no value"; a 0
  in a field filled from the device file is marked as possibly missing

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant