-
Notifications
You must be signed in to change notification settings - Fork 1
LLM Agent Recipes
日本語版: LLM Agent Recipes (日本語)
This MCP server gives your AI client read access to Intervals.icu plus calendar writes — and that covers day-to-day analysis. But some setup and maintenance jobs need things the server (and the official Intervals.icu API) cannot reach: Stryd-native values, per-second ILR streams, custom-field registration, gear (shoe) assignment.
It turns out an LLM agent that can drive your logged-in browser — e.g. Claude (Cowork mode) with the Claude-in-Chrome extension, or any agent that can execute JavaScript in the page context — can do all of these interactively, as supervised one-off operations. This page collects the recipes we validated on 2026-06-07 (single athlete, real data).
You need: an LLM agent with browser control · you logged in to intervals.icu / Stryd PowerCenter in that browser · ~30–60 min per recipe.
- Your own data only. Everything below reads/writes the account you are logged in to. Don't point these techniques at anyone else's data.
- Unofficial APIs break. The Stryd endpoints below are the ones the PowerCenter web app itself calls. They are undocumented, may change without notice, and using them is at your own risk (check the services' Terms of Use).
-
Tokens stay in the page. The agent should use
localStorage.getItem("token")inside page-context JS and never print, copy, or export the token itself. -
Pace your requests. ~250–400 ms between calls. Intervals.icu will answer
429 Too Many Requestsif you hammer it. - Read freely, write carefully. For bulk writes, have the agent build a dry-run plan first (counts per target), show it to you, then execute with progress reporting. All writes below are idempotent (safe to re-run).
Intervals.icu's web UI talks to cookie-authenticated endpoints under
/api/... (no API key needed — your browser session is the auth). Stryd
PowerCenter talks to https://api.stryd.com/b/api/v1/... with a Bearer token
that lives in localStorage. An agent that can run JS in the page can call
both, aggregate in-page (histograms, plans), and only ship compact results out.
When you don't know an endpoint, do the action once in the UI and read the
network log.
What the MCP server can't see, the PowerCenter API can
(H = {Authorization: "Bearer " + localStorage.getItem("token")}, your user id
is in the PowerCenter URL):
| Endpoint | What you get |
|---|---|
GET /b/api/v1/users/{uid}/calendar?from={unix}&to={unix} |
per-activity critical_impact (CI), lower_body_stress (native LBSS), ftp (Stryd auto-CP), apparel_ids (shoes) |
GET /b/api/v1/activities/{id} |
full 1 Hz streams incl. impact_list (per-second ILR) |
GET /b/api/v1/users/{uid}/apparel |
shoe catalog: brand/model/nickname, lifetime distance/time/runs, retired flag |
We verified impact_list is the same series as the StrydILR developer
field that reaches Intervals.icu (means match to 2 decimals).
Fitting Stryd's own per-activity CI against 10 activities' ILR streams (140k seconds, flat easy → hills → a 100 km race) reproduced native LBSS with 0.0 % error on all 10:
LBSS = (100 / 3600) · Σ ILR_i / CI_activity (no exponent, no offset)
- The community's "exponent ≈ 3.5–4" estimate applies to RSS (power), not LBSS.
- Stryd's CI is time-varying (a long rolling window, ~90 days, stepwise updates). After a shoe change it can take ~3 months to converge.
- Because β = 1, native LBSS does not over-weight high-impact (downhill)
seconds. If you care about eccentric/downhill load, add a companion metric —
we use a linear-excess field:
Σ max(0, (ILR−CI)/CI) · 100/3600(anchor: 1 h at 2×CI = 100), which separates hills from flat easy runs by ~13–23× per hour.
You do not need the Stryd API for CI. Using only Intervals streams
(watts, StrydILR, altitude, distance) and your CP:
CI_est = a + b·CP where (a, b) = weighted-OLS( ILR ~ watts )
data = last ~90 days of flat seconds (|grade| ≤ 1.5 %, ±10 s window),
watts ∈ [0.50, 1.05] × CP, ILR > 0, 10 W bins (n ≥ 5)
gates = ≥ 8 bins, power span ≥ 40 W, ≥ 60 days of ILR history
Validated against Stryd's own CI series across a shoe transition (CI 72 → 56): r = 0.93, RMSE = 1.9 bw/s over 102 activities. Two caveats: the window needs ~60 days of warm-up, and taper/recovery-only months lack intensity coverage (the regression extrapolation degrades — pool over longer ranges). Partitioning the same regression by shoe (gear) gives per-shoe CI months earlier than Stryd's own mixed-window value.
Lessons learned registering LBSS fields (Settings → CUSTOM FIELDS):
- Field Code must be CamelCase — underscores are rejected (
StrydLBSS_v2→ useStrydLBSSv2). -
No top-level
let/constin field scripts (scope is shared; you get "Variable already declared"). Wrap the whole script in a{ ... }block. - Numeric fields must return a number (strings fail validation).
- Scripts can read
activity.gear_id/activity.gear(null when unassigned) — so agear_id → CIlookup table inside the script works. Don't callObject.keys(activity)(host-object enumeration throws). -
Backfill for existing activities: Activities list view → select all →
EDIT → Analyze→ check Keep existing intervals → OK.
→ Complete setup guide for the StrydLBSSv2 / EccLBSS fields (CI selection, scripts, backfill, verification): Stryd LBSS v2 Field Setup
If you've been assigning shoes in Stryd but not in Intervals, the full history can be migrated in ~15 minutes:
- Pull the apparel catalog and the calendar (with
apparel_ids) for the period you want (we did 12 months). - Create missing shoes:
POST /api/athlete/{athleteId}/gearwith{name, type:"Shoes", purchased, notes}— put{"stryd_apparel_id": "...", "synced": "YYYY-MM-DD"}innotesas an idempotency key for future syncs. - Match Stryd ↔ Intervals activities by start time (±2 h tolerance worked: 302/302 matched, 0 errors).
- Assign:
PUT /api/activity/{id}with body{"gear": {"id": "<gearId>"}}(note: a flatgear_idkey is rejected as an unknown custom field). - Odometers: gear
distance/timeare odometer-semantics fields — auto-incremented by assignments, and writable. Set them to Stryd'slifetime_distance(+additional_distance) andlifetime_durationso lifetime totals are exact even for usage predating your stream history.
Result: accurate per-shoe distance/time in Intervals, and per-activity
gear_id available to custom-field scripts (per-shoe CI tables).
With per-shoe assignments + the CI estimator (§4), you can track CI versus the shoe's odometer. A drifting CI on the same shoe = changing impact behaviour as the foam dies — an evidence-based replacement signal that neither Stryd nor Intervals surfaces today.
Everything here was executed end-to-end by an LLM agent under human supervision (Claude Opus, Cowork mode + Chrome extension), 2026-06-07. Accuracy figures are from a single athlete; treat them as a method demonstration, not a guarantee. This project is not affiliated with Stryd or Intervals.icu.