intervals-icu-mcp-server 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js CHANGED
@@ -144,7 +144,7 @@ export async function getWellness(config, params) {
144
144
  const result = {};
145
145
  for (const entry of data) {
146
146
  const parsed = WellnessSchema.parse(entry);
147
- const key = entry["date"] ?? parsed.id ?? String(Object.keys(result).length);
147
+ const key = parsed.id ?? String(Object.keys(result).length);
148
148
  result[key] = parsed;
149
149
  }
150
150
  return result;
package/dist/format.js CHANGED
@@ -23,7 +23,7 @@ export function formatActivity(a) {
23
23
  "Other:",
24
24
  ` Cadence: ${n(a.average_cadence, " rpm")} | Calories: ${n(a.calories)} | Speed: ${n(a.average_speed, " m/s")}`,
25
25
  ` Temp: ${n(a.average_temp, "°C")} | Trainer: ${a.trainer ? "Yes" : "No"}`,
26
- ` RPE: ${n(a.icu_rpe ?? a.perceived_exertion)}/10 | Feel: ${n(a.feel)}/10`,
26
+ ` RPE: ${n(a.icu_rpe ?? a.perceived_exertion)}/10 | Feel: ${n(a.feel)}/5`,
27
27
  "",
28
28
  "Fitness:",
29
29
  ` CTL: ${n(a.icu_ctl)} | ATL: ${n(a.icu_atl)} | TRIMP: ${n(a.trimp)}`,
@@ -110,19 +110,23 @@ export function formatSportZones(s) {
110
110
  lines.push("");
111
111
  }
112
112
  if (s.hr_zones?.length) {
113
+ // Unlike power_zones (%FTP), hr_zones stores absolute bpm thresholds directly.
113
114
  lines.push(`HR Zones${s.lthr != null ? ` (LTHR: ${s.lthr}bpm)` : ""}:`);
114
115
  for (const z of zoneRanges(s.hr_zones, s.hr_zone_names)) {
115
- const abs = s.lthr != null ? ` (${Math.round((s.lthr * z.from) / 100)}-${z.to != null ? Math.round((s.lthr * z.to) / 100) + "bpm" : "bpm+"})` : "";
116
- const pct = z.to != null ? `${z.from}-${z.to}%` : `${z.from}%+`;
117
- lines.push(` ${z.name}: ${pct}${abs}`);
116
+ const pctFrom = s.lthr != null ? Math.round((z.from / s.lthr) * 100) : null;
117
+ const pctTo = s.lthr != null && z.to != null ? Math.round((z.to / s.lthr) * 100) : null;
118
+ const pct = pctFrom != null ? ` (${pctFrom}${pctTo != null ? "-" + pctTo : ""}% LTHR)` : "";
119
+ const bpm = z.to != null ? `${z.from}-${z.to} bpm` : `${z.from}+ bpm`;
120
+ lines.push(` ${z.name}: ${bpm}${pct}`);
118
121
  }
119
122
  lines.push("");
120
123
  }
121
124
  if (s.pace_zones?.length) {
122
- lines.push("Pace Zones (% of threshold pace):");
125
+ // Same as hr_zones — absolute thresholds, not percentages of threshold_pace.
126
+ lines.push(`Pace Zones${s.threshold_pace != null ? ` (Threshold: ${s.threshold_pace} m/s)` : ""}:`);
123
127
  for (const z of zoneRanges(s.pace_zones, s.pace_zone_names)) {
124
- const pct = z.to != null ? `${z.from}-${z.to}%` : `${z.from}%+`;
125
- lines.push(` ${z.name}: ${pct}`);
128
+ const range = z.to != null ? `${z.from}-${z.to} m/s` : `${z.from}+ m/s`;
129
+ lines.push(` ${z.name}: ${range}`);
126
130
  }
127
131
  }
128
132
  return lines.join("\n").trimEnd();
package/dist/index.js CHANGED
@@ -17,18 +17,24 @@ function getConfig(args) {
17
17
  throw new Error("No athlete ID provided. Set ATHLETE_ID in .env or pass athlete_id.");
18
18
  return { apiKey, athleteId };
19
19
  }
20
+ function toDateStr(d) {
21
+ const y = d.getFullYear();
22
+ const m = String(d.getMonth() + 1).padStart(2, "0");
23
+ const day = String(d.getDate()).padStart(2, "0");
24
+ return `${y}-${m}-${day}`;
25
+ }
20
26
  function today() {
21
- return new Date().toISOString().slice(0, 10);
27
+ return toDateStr(new Date());
22
28
  }
23
29
  function daysAgo(n) {
24
30
  const d = new Date();
25
31
  d.setDate(d.getDate() - n);
26
- return d.toISOString().slice(0, 10);
32
+ return toDateStr(d);
27
33
  }
28
34
  function daysFromNow(n) {
29
35
  const d = new Date();
30
36
  d.setDate(d.getDate() + n);
31
- return d.toISOString().slice(0, 10);
37
+ return toDateStr(d);
32
38
  }
33
39
  const TOOLS = [
34
40
  {
@@ -125,7 +131,7 @@ const TOOLS = [
125
131
  category: { type: "string", description: "Event category: WORKOUT, NOTE, RACE_A, RACE_B, RACE_C (race priority A/B/C — this is what makes it a race, there is no separate race flag), PLAN, HOLIDAY, SICK, INJURED, TARGET, and others" },
126
132
  type: { type: "string", description: "Activity type: Ride, Run, Swim, WeightTraining, etc." },
127
133
  distance: { type: "number", description: "Distance in meters (e.g. 100000 for 100km)" },
128
- sub_type: { type: "string", description: "Race category/sub-type (e.g. A, B, C)" },
134
+ sub_type: { type: "string", description: "One of NONE, COMMUTE, WARMUP, COOLDOWN, RACE. Not race priority — that's set via category (RACE_A/RACE_B/RACE_C)." },
129
135
  color: { type: "string", description: "Event color, e.g. '#1f77b4' (hex) or a named color like 'blue', 'green', 'sky'" },
130
136
  workout_doc: { type: "object", description: "Pre-computed Intervals.icu workout_doc (as returned by get_event_by_id/list_workouts on an existing structured workout) to copy verbatim onto this event. Hand-written partial docs won't render correctly — prefer setting `description` with the Workout Builder syntax and omitting this field so Intervals.icu computes it." },
131
137
  athlete_id: { type: "string", description: "Athlete ID (defaults to ATHLETE_ID env var)" },
@@ -148,7 +154,7 @@ const TOOLS = [
148
154
  category: { type: "string", description: "New category" },
149
155
  type: { type: "string", description: "New activity type" },
150
156
  distance: { type: "number", description: "Distance in meters (e.g. 100000 for 100km)" },
151
- sub_type: { type: "string", description: "Race category/sub-type (e.g. A, B, C)" },
157
+ sub_type: { type: "string", description: "One of NONE, COMMUTE, WARMUP, COOLDOWN, RACE. Not race priority — that's set via category (RACE_A/RACE_B/RACE_C)." },
152
158
  color: { type: "string", description: "Event color, e.g. '#1f77b4' (hex) or a named color like 'blue', 'green', 'sky'" },
153
159
  workout_doc: { type: "object", description: "Pre-computed Intervals.icu workout_doc to copy verbatim. Hand-written partial docs won't render correctly — prefer updating `description` with Workout Builder syntax instead." },
154
160
  athlete_id: { type: "string", description: "Athlete ID (defaults to ATHLETE_ID env var)" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "intervals-icu-mcp-server",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "MCP server for Intervals.icu — 20 tools for activities, wellness, calendar events, workouts and more",
5
5
  "type": "module",
6
6
  "bin": {