intervals-icu-mcp-server 0.1.4 → 0.1.5

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.d.ts CHANGED
@@ -160,6 +160,10 @@ export type Wellness = z.infer<typeof WellnessSchema>;
160
160
  export declare const EventSchema: z.ZodObject<{
161
161
  id: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
162
162
  date: z.ZodOptional<z.ZodString>;
163
+ start_date_local: z.ZodOptional<z.ZodNullable<z.ZodString>>;
164
+ end_date_local: z.ZodOptional<z.ZodNullable<z.ZodString>>;
165
+ category: z.ZodOptional<z.ZodNullable<z.ZodString>>;
166
+ show_as_note: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
163
167
  name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
164
168
  description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
165
169
  race: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
@@ -168,6 +172,10 @@ export declare const EventSchema: z.ZodObject<{
168
172
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
169
173
  id: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
170
174
  date: z.ZodOptional<z.ZodString>;
175
+ start_date_local: z.ZodOptional<z.ZodNullable<z.ZodString>>;
176
+ end_date_local: z.ZodOptional<z.ZodNullable<z.ZodString>>;
177
+ category: z.ZodOptional<z.ZodNullable<z.ZodString>>;
178
+ show_as_note: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
171
179
  name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
172
180
  description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
173
181
  race: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
@@ -176,6 +184,10 @@ export declare const EventSchema: z.ZodObject<{
176
184
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
177
185
  id: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
178
186
  date: z.ZodOptional<z.ZodString>;
187
+ start_date_local: z.ZodOptional<z.ZodNullable<z.ZodString>>;
188
+ end_date_local: z.ZodOptional<z.ZodNullable<z.ZodString>>;
189
+ category: z.ZodOptional<z.ZodNullable<z.ZodString>>;
190
+ show_as_note: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
179
191
  name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
180
192
  description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
181
193
  race: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
@@ -197,6 +209,7 @@ export interface EventInput {
197
209
  name?: string;
198
210
  description?: string;
199
211
  start_date_local?: string;
212
+ end_date_local?: string;
200
213
  category?: string;
201
214
  type?: string;
202
215
  race?: boolean;
@@ -248,43 +261,44 @@ export declare const AthleteSchema: z.ZodObject<{
248
261
  icu_vo2max: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
249
262
  }, z.ZodTypeAny, "passthrough">>;
250
263
  export type Athlete = z.infer<typeof AthleteSchema>;
251
- export declare const AthleteSummarySchema: z.ZodObject<{
252
- ctl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
253
- atl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
254
- tsb: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
255
- rampRate: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
256
- ctlLoad: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
257
- atlLoad: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
258
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
259
- ctl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
260
- atl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
261
- tsb: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
262
- rampRate: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
263
- ctlLoad: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
264
- atlLoad: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
265
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
266
- ctl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
267
- atl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
268
- tsb: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
269
- rampRate: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
270
- ctlLoad: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
271
- atlLoad: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
272
- }, z.ZodTypeAny, "passthrough">>;
273
- export type AthleteSummary = z.infer<typeof AthleteSummarySchema>;
274
- export declare const ZonesSchema: z.ZodObject<{
275
- power: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
276
- hr: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
277
- pace: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
264
+ export declare const SportSettingsSchema: z.ZodObject<{
265
+ types: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>;
266
+ ftp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
267
+ lthr: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
268
+ max_hr: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
269
+ threshold_pace: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
270
+ power_zones: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodNumber, "many">>>;
271
+ power_zone_names: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>;
272
+ hr_zones: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodNumber, "many">>>;
273
+ hr_zone_names: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>;
274
+ pace_zones: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodNumber, "many">>>;
275
+ pace_zone_names: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>;
278
276
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
279
- power: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
280
- hr: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
281
- pace: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
277
+ types: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>;
278
+ ftp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
279
+ lthr: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
280
+ max_hr: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
281
+ threshold_pace: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
282
+ power_zones: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodNumber, "many">>>;
283
+ power_zone_names: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>;
284
+ hr_zones: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodNumber, "many">>>;
285
+ hr_zone_names: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>;
286
+ pace_zones: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodNumber, "many">>>;
287
+ pace_zone_names: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>;
282
288
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
283
- power: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
284
- hr: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
285
- pace: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">>;
289
+ types: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>;
290
+ ftp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
291
+ lthr: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
292
+ max_hr: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
293
+ threshold_pace: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
294
+ power_zones: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodNumber, "many">>>;
295
+ power_zone_names: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>;
296
+ hr_zones: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodNumber, "many">>>;
297
+ hr_zone_names: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>;
298
+ pace_zones: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodNumber, "many">>>;
299
+ pace_zone_names: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>;
286
300
  }, z.ZodTypeAny, "passthrough">>;
287
- export type Zones = z.infer<typeof ZonesSchema>;
301
+ export type SportSettings = z.infer<typeof SportSettingsSchema>;
288
302
  export declare const WorkoutSchema: z.ZodObject<{
289
303
  id: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
290
304
  name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -352,8 +366,8 @@ export declare function createEvent(config: ClientConfig, input: EventInput): Pr
352
366
  export declare function updateEvent(config: ClientConfig, eventId: string, input: EventInput): Promise<Event>;
353
367
  export declare function deleteEvent(config: ClientConfig, eventId: string): Promise<void>;
354
368
  export declare function getAthleteProfile(config: ClientConfig): Promise<Athlete>;
355
- export declare function getAthleteZones(config: ClientConfig): Promise<Zones>;
356
- export declare function getAthleteSummary(config: ClientConfig): Promise<AthleteSummary>;
369
+ export declare function getAthleteZones(config: ClientConfig): Promise<SportSettings[]>;
370
+ export declare function getAthleteSummary(config: ClientConfig): Promise<AthleteFitness | undefined>;
357
371
  export declare function getWellnessDay(config: ClientConfig, date: string): Promise<Wellness>;
358
372
  export declare function updateWellness(config: ClientConfig, date: string, input: WellnessInput): Promise<Wellness>;
359
373
  export declare function getActivityStreams(config: ClientConfig, activityId: string, types?: string[]): Promise<Record<string, unknown[]>>;
package/dist/client.js CHANGED
@@ -79,6 +79,10 @@ export const WellnessSchema = z.object({
79
79
  export const EventSchema = z.object({
80
80
  id: z.union([z.string(), z.number()]),
81
81
  date: z.string().optional(),
82
+ start_date_local: z.string().nullable().optional(),
83
+ end_date_local: z.string().nullable().optional(),
84
+ category: z.string().nullable().optional(),
85
+ show_as_note: z.boolean().nullable().optional(),
82
86
  name: z.string().nullable().optional(),
83
87
  description: z.string().nullable().optional(),
84
88
  race: z.boolean().nullable().optional(),
@@ -104,18 +108,18 @@ export const AthleteSchema = z.object({
104
108
  icu_weight: z.number().nullable().optional(),
105
109
  icu_vo2max: z.number().nullable().optional(),
106
110
  }).passthrough();
107
- export const AthleteSummarySchema = z.object({
108
- ctl: z.number().nullable().optional(),
109
- atl: z.number().nullable().optional(),
110
- tsb: z.number().nullable().optional(),
111
- rampRate: z.number().nullable().optional(),
112
- ctlLoad: z.number().nullable().optional(),
113
- atlLoad: z.number().nullable().optional(),
114
- }).passthrough();
115
- export const ZonesSchema = z.object({
116
- power: z.array(z.record(z.unknown())).optional(),
117
- hr: z.array(z.record(z.unknown())).optional(),
118
- pace: z.array(z.record(z.unknown())).optional(),
111
+ export const SportSettingsSchema = z.object({
112
+ types: z.array(z.string()).nullable().optional(),
113
+ ftp: z.number().nullable().optional(),
114
+ lthr: z.number().nullable().optional(),
115
+ max_hr: z.number().nullable().optional(),
116
+ threshold_pace: z.number().nullable().optional(),
117
+ power_zones: z.array(z.number()).nullable().optional(),
118
+ power_zone_names: z.array(z.string()).nullable().optional(),
119
+ hr_zones: z.array(z.number()).nullable().optional(),
120
+ hr_zone_names: z.array(z.string()).nullable().optional(),
121
+ pace_zones: z.array(z.number()).nullable().optional(),
122
+ pace_zone_names: z.array(z.string()).nullable().optional(),
119
123
  }).passthrough();
120
124
  export const WorkoutSchema = z.object({
121
125
  id: z.union([z.string(), z.number()]).optional(),
@@ -202,12 +206,14 @@ export async function getAthleteProfile(config) {
202
206
  return AthleteSchema.parse(data);
203
207
  }
204
208
  export async function getAthleteZones(config) {
205
- const data = await request(`/athlete/${config.athleteId}/zones`, config);
206
- return ZonesSchema.parse(data);
209
+ const data = await request(`/athlete/${config.athleteId}/sport-settings`, config);
210
+ return z.array(SportSettingsSchema).parse(data);
207
211
  }
208
212
  export async function getAthleteSummary(config) {
209
- const data = await request(`/athlete/${config.athleteId}/summary`, config);
210
- return AthleteSummarySchema.parse(data);
213
+ const today = new Date().toISOString().slice(0, 10);
214
+ const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
215
+ const entries = await getAthleteFitness(config, { startDate: weekAgo, endDate: today });
216
+ return entries.at(-1);
211
217
  }
212
218
  export async function getWellnessDay(config, date) {
213
219
  const data = await request(`/athlete/${config.athleteId}/wellness/${date}`, config);
@@ -240,7 +246,7 @@ export async function bulkCreateEvents(config, events) {
240
246
  return z.array(EventSchema).parse(data);
241
247
  }
242
248
  export async function bulkDeleteEvents(config, eventIds) {
243
- await mutate("POST", `/athlete/${config.athleteId}/events/bulk-delete`, config, eventIds);
249
+ await mutate("PUT", `/athlete/${config.athleteId}/events/bulk-delete`, config, eventIds.map((id) => ({ id: Number(id) })));
244
250
  }
245
251
  export const AthleteFitnessSchema = z.object({
246
252
  date: z.string().optional(),
@@ -254,7 +260,7 @@ export const AthleteFitnessSchema = z.object({
254
260
  weight: z.number().nullable().optional(),
255
261
  }).passthrough();
256
262
  export async function getAthleteFitness(config, params) {
257
- const data = await request(`/athlete/${config.athleteId}/athlete-summary.json`, config, { oldest: params.startDate, newest: params.endDate, athleteId: config.athleteId });
263
+ const data = await request(`/athlete/${config.athleteId}/athlete-summary.json`, config, { start: params.startDate, end: params.endDate });
258
264
  const all = z.array(AthleteFitnessSchema).parse(data);
259
265
  return all.filter((e) => e["athlete_id"] === config.athleteId);
260
266
  }
package/dist/format.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import type { Activity, Wellness, Event } from "./client.js";
1
+ import type { Activity, Wellness, Event, SportSettings } from "./client.js";
2
2
  export declare function formatActivity(a: Activity): string;
3
3
  export declare function formatWellness(date: string, w: Wellness): string;
4
4
  export declare function formatEvent(e: Event): string;
5
+ export declare function formatSportZones(s: SportSettings): string;
5
6
  export declare function formatIntervalRow(interval: Record<string, unknown>, idx: number): string;
package/dist/format.js CHANGED
@@ -63,15 +63,64 @@ export function formatWellness(date, w) {
63
63
  }
64
64
  export function formatEvent(e) {
65
65
  const type = e.race ? "Race" : e.workout ? "Workout" : "Event";
66
+ const start = e.start_date_local ?? e.date ?? "Unknown";
67
+ const dateLabel = e.end_date_local && e.end_date_local !== e.start_date_local
68
+ ? `${start} to ${e.end_date_local}`
69
+ : start;
66
70
  return [
67
71
  `**${e.name ?? "Unnamed"}** (${e.id})`,
68
- `Type: ${type} | Date: ${e.date ?? "Unknown"}`,
72
+ `Type: ${type} | Date: ${dateLabel}`,
73
+ e.category ? `Category: ${e.category}` : "",
69
74
  e.description ? `Description: ${e.description}` : "",
70
75
  e.priority ? `Priority: ${e.priority}` : "",
71
76
  ]
72
77
  .filter(Boolean)
73
78
  .join("\n");
74
79
  }
80
+ function zoneRanges(breakpoints, names) {
81
+ const count = names?.length ?? (breakpoints?.length ?? 0) + 1;
82
+ const ranges = [];
83
+ let from = 0;
84
+ for (let i = 0; i < count; i++) {
85
+ const raw = breakpoints && i < breakpoints.length ? breakpoints[i] : null;
86
+ // Intervals.icu caps the top zone with a 999 sentinel meaning "no upper bound".
87
+ const to = raw != null && raw < 900 ? raw : null;
88
+ ranges.push({ name: names?.[i] ?? `Z${i + 1}`, from, to });
89
+ if (to != null)
90
+ from = to;
91
+ }
92
+ return ranges;
93
+ }
94
+ export function formatSportZones(s) {
95
+ const title = s.types?.length ? s.types.join(", ") : "Default";
96
+ const lines = [`## ${title}`, ""];
97
+ if (s.power_zones?.length) {
98
+ lines.push(`Power Zones${s.ftp != null ? ` (FTP: ${s.ftp}W)` : ""}:`);
99
+ for (const z of zoneRanges(s.power_zones, s.power_zone_names)) {
100
+ const abs = s.ftp != null ? ` (${Math.round((s.ftp * z.from) / 100)}-${z.to != null ? Math.round((s.ftp * z.to) / 100) + "W" : "W+"})` : "";
101
+ const pct = z.to != null ? `${z.from}-${z.to}%` : `${z.from}%+`;
102
+ lines.push(` ${z.name}: ${pct}${abs}`);
103
+ }
104
+ lines.push("");
105
+ }
106
+ if (s.hr_zones?.length) {
107
+ lines.push(`HR Zones${s.lthr != null ? ` (LTHR: ${s.lthr}bpm)` : ""}:`);
108
+ for (const z of zoneRanges(s.hr_zones, s.hr_zone_names)) {
109
+ const abs = s.lthr != null ? ` (${Math.round((s.lthr * z.from) / 100)}-${z.to != null ? Math.round((s.lthr * z.to) / 100) + "bpm" : "bpm+"})` : "";
110
+ const pct = z.to != null ? `${z.from}-${z.to}%` : `${z.from}%+`;
111
+ lines.push(` ${z.name}: ${pct}${abs}`);
112
+ }
113
+ lines.push("");
114
+ }
115
+ if (s.pace_zones?.length) {
116
+ lines.push("Pace Zones (% of threshold pace):");
117
+ for (const z of zoneRanges(s.pace_zones, s.pace_zone_names)) {
118
+ const pct = z.to != null ? `${z.from}-${z.to}%` : `${z.from}%+`;
119
+ lines.push(` ${z.name}: ${pct}`);
120
+ }
121
+ }
122
+ return lines.join("\n").trimEnd();
123
+ }
75
124
  export function formatIntervalRow(interval, idx) {
76
125
  const label = interval["label"] ?? `Interval ${idx}`;
77
126
  const type = interval["type"] ?? "Unknown";
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
5
5
  import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
6
6
  import { z } from "zod";
7
7
  import { getActivities, getActivityDetails, getActivityIntervals, getWellness, getEvents, getEventById, getNotesByDate, createEvent, updateEvent, deleteEvent, getAthleteProfile, getAthleteZones, getAthleteSummary, getWellnessDay, updateWellness, getActivityStreams, getActivityPowerCurves, listWorkouts, createWorkout, bulkCreateEvents, bulkDeleteEvents, getAthleteFitness, } from "./client.js";
8
- import { formatActivity, formatWellness, formatEvent, formatIntervalRow, } from "./format.js";
8
+ import { formatActivity, formatWellness, formatEvent, formatIntervalRow, formatSportZones, } from "./format.js";
9
9
  function getConfig(args) {
10
10
  const apiKey = args["api_key"] ?? process.env.API_KEY ?? "";
11
11
  const athleteId = args["athlete_id"] ??
@@ -119,9 +119,10 @@ const TOOLS = [
119
119
  required: ["start_date_local"],
120
120
  properties: {
121
121
  start_date_local: { type: "string", description: "Date/time in ISO 8601 format, e.g. 2024-06-01T08:00:00" },
122
+ end_date_local: { type: "string", description: "End date in ISO 8601 format for a multi-day event (e.g. a NOTE, HOLIDAY, SICK, or INJURED entry spanning several days), e.g. 2024-06-05" },
122
123
  name: { type: "string", description: "Event name" },
123
124
  description: { type: "string", description: "Event description. To create a structured workout, write it in Intervals.icu's Workout Builder plain-text syntax — Intervals.icu parses this into the workout structure (steps, duration, zones) automatically. Syntax: each step is a line starting with '-', e.g. '-10m 50-60%' (duration as h/m/s combos like '1h20m', '3m30s', '20s'; intensity as a %FTP value/range, or a zone like 'z1'/'z4'). Append 'power=Ns' to set the power-smoothing window (power=10s for steady efforts, power=3s or power=1s for short hard efforts/sprints/kicks). HR-based targets: 'z4 90% hr'. Optional cadence suffix: '85-95rpm'. Repeats: put 'Nx' on its own line, then the repeated step lines, then a blank line before the next block. Example: \"- Warmup 10m 50%\\n\\n4x\\n- Hard 4m 105% power=10s\\n- Easy 2m 50%\\n\\n- Cooldown 5m 50%\\n\"" },
124
- category: { type: "string", description: "Event category: WORKOUT, NOTE, RACE_A, RACE_B, RACE_C (race priority A/B/C)" },
125
+ category: { type: "string", description: "Event category: WORKOUT, NOTE, RACE_A, RACE_B, RACE_C (race priority A/B/C), PLAN, HOLIDAY, SICK, INJURED, TARGET, and others" },
125
126
  type: { type: "string", description: "Activity type: Ride, Run, Swim, WeightTraining, etc." },
126
127
  race: { type: "boolean", description: "Mark as a race event (default: false)" },
127
128
  distance: { type: "number", description: "Distance in meters (e.g. 100000 for 100km)" },
@@ -141,6 +142,7 @@ const TOOLS = [
141
142
  properties: {
142
143
  event_id: { type: "string", description: "The event ID to update" },
143
144
  start_date_local: { type: "string", description: "New date/time (moves the event), e.g. 2024-06-05T08:00:00" },
145
+ end_date_local: { type: "string", description: "New end date for a multi-day event (e.g. a NOTE, HOLIDAY, SICK, or INJURED entry spanning several days), e.g. 2024-06-08" },
144
146
  name: { type: "string", description: "New event name" },
145
147
  description: { type: "string", description: "New description. Use Intervals.icu's Workout Builder plain-text syntax to have Intervals.icu recompute the workout structure — steps as '-<duration> <intensity>' lines (duration as h/m/s combos, intensity as %FTP or a zone like 'z1'), optional 'power=Ns' smoothing suffix, 'Nx' repeat blocks, HR targets ('z4 90% hr'), cadence suffix ('85-95rpm'). Example: \"- Warmup 10m 50%\\n\\n4x\\n- Hard 4m 105% power=10s\\n- Easy 2m 50%\\n\\n- Cooldown 5m 50%\\n\"" },
146
148
  category: { type: "string", description: "New category" },
@@ -511,6 +513,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
511
513
  name: args["name"],
512
514
  description: args["description"],
513
515
  start_date_local: z.string().parse(args["start_date_local"]),
516
+ end_date_local: args["end_date_local"],
514
517
  category: args["category"],
515
518
  type: args["type"],
516
519
  race: args["race"],
@@ -524,7 +527,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
524
527
  const eventId = z.string().parse(args["event_id"]);
525
528
  const config = getConfig(args);
526
529
  const input = {};
527
- for (const key of ["name", "description", "start_date_local", "category", "type", "race", "distance", "sub_type", "workout_doc"]) {
530
+ for (const key of ["name", "description", "start_date_local", "end_date_local", "category", "type", "race", "distance", "sub_type", "workout_doc"]) {
528
531
  if (args[key] !== undefined)
529
532
  input[key] = args[key];
530
533
  }
@@ -554,36 +557,25 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
554
557
  }
555
558
  case "get_athlete_zones": {
556
559
  const config = getConfig(args);
557
- const zones = await getAthleteZones(config);
558
- const lines = [];
559
- const formatZones = (label, zoneList) => {
560
- if (!zoneList?.length)
561
- return;
562
- lines.push(`## ${label} Zones`, "");
563
- zoneList.forEach((z, i) => {
564
- const name = z["name"] ?? `Zone ${i + 1}`;
565
- const min = z["min"] ?? z["from"] ?? "";
566
- const max = z["max"] ?? z["to"] ?? "";
567
- lines.push(` Z${i + 1} ${name}: ${min}–${max}`);
568
- });
569
- lines.push("");
570
- };
571
- formatZones("Power", zones.power);
572
- formatZones("Heart Rate", zones.hr);
573
- formatZones("Pace", zones.pace);
574
- if (!lines.length)
575
- lines.push("No zone data available.");
576
- return { content: [{ type: "text", text: lines.join("\n") }] };
560
+ const sportSettings = await getAthleteZones(config);
561
+ if (!sportSettings.length) {
562
+ return { content: [{ type: "text", text: "No zone data available." }] };
563
+ }
564
+ const text = sportSettings.map(formatSportZones).join("\n\n---\n\n");
565
+ return { content: [{ type: "text", text }] };
577
566
  }
578
567
  case "get_athlete_summary": {
579
568
  const config = getConfig(args);
580
569
  const summary = await getAthleteSummary(config);
570
+ if (!summary) {
571
+ return { content: [{ type: "text", text: "No fitness data available." }] };
572
+ }
581
573
  const text = [
582
574
  "## Current Fitness Snapshot",
583
575
  "",
584
- `CTL (Fitness): ${summary.ctl ?? "N/A"}`,
585
- `ATL (Fatigue): ${summary.atl ?? "N/A"}`,
586
- `TSB (Form): ${summary.tsb ?? "N/A"}`,
576
+ `CTL (Fitness): ${summary.fitness ?? "N/A"}`,
577
+ `ATL (Fatigue): ${summary.fatigue ?? "N/A"}`,
578
+ `TSB (Form): ${summary.form ?? "N/A"}`,
587
579
  `Ramp Rate: ${summary.rampRate ?? "N/A"}`,
588
580
  ].join("\n");
589
581
  return { content: [{ type: "text", text }] };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "intervals-icu-mcp-server",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
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": {