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 +50 -36
- package/dist/client.js +24 -18
- package/dist/format.d.ts +2 -1
- package/dist/format.js +50 -1
- package/dist/index.js +18 -26
- package/package.json +1 -1
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
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
|
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<
|
|
356
|
-
export declare function getAthleteSummary(config: ClientConfig): Promise<
|
|
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
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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}/
|
|
206
|
-
return
|
|
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
|
|
210
|
-
|
|
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("
|
|
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, {
|
|
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: ${
|
|
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
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
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.
|
|
585
|
-
`ATL (Fatigue): ${summary.
|
|
586
|
-
`TSB (Form): ${summary.
|
|
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 }] };
|