intervals-icu-mcp-server 0.5.0 → 0.6.0
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/README.md +2 -0
- package/dist/client.d.ts +45 -0
- package/dist/client.js +19 -0
- package/dist/format.js +3 -0
- package/dist/index.js +50 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -79,6 +79,8 @@ Add to `~/.claude/settings.json`:
|
|
|
79
79
|
| Tool | Description |
|
|
80
80
|
|------|-------------|
|
|
81
81
|
| `list_athletes` | List athletes you follow or coach (including yourself) — find athlete IDs for a coach account |
|
|
82
|
+
| `get_coach_ticks` | Get coach-tick presets on an athlete's profile (in practice a fixed 1-5 rating scale) |
|
|
83
|
+
| `set_activity_coach_tick` | Set or clear the coach's 1-5 rating on a specific activity |
|
|
82
84
|
| `get_athlete_profile` | FTP, LTHR, weight, VO2max, resting HR |
|
|
83
85
|
| `get_athlete_zones` | Power, HR and pace training zones |
|
|
84
86
|
| `get_athlete_summary` | Current CTL, ATL, TSB and ramp rate snapshot |
|
package/dist/client.d.ts
CHANGED
|
@@ -99,6 +99,7 @@ export declare const ActivitySchema: z.ZodObject<{
|
|
|
99
99
|
carbs_used: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
100
100
|
carbs_ingested: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
101
101
|
avg_lr_balance: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
102
|
+
coach_tick: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
102
103
|
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
103
104
|
id: z.ZodString;
|
|
104
105
|
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -195,6 +196,7 @@ export declare const ActivitySchema: z.ZodObject<{
|
|
|
195
196
|
carbs_used: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
196
197
|
carbs_ingested: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
197
198
|
avg_lr_balance: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
199
|
+
coach_tick: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
198
200
|
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
199
201
|
id: z.ZodString;
|
|
200
202
|
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -291,8 +293,20 @@ export declare const ActivitySchema: z.ZodObject<{
|
|
|
291
293
|
carbs_used: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
292
294
|
carbs_ingested: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
293
295
|
avg_lr_balance: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
296
|
+
coach_tick: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
294
297
|
}, z.ZodTypeAny, "passthrough">>;
|
|
295
298
|
export type Activity = z.infer<typeof ActivitySchema>;
|
|
299
|
+
export declare const CoachTickSchema: z.ZodObject<{
|
|
300
|
+
id: z.ZodOptional<z.ZodNumber>;
|
|
301
|
+
text: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
302
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
303
|
+
id: z.ZodOptional<z.ZodNumber>;
|
|
304
|
+
text: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
305
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
306
|
+
id: z.ZodOptional<z.ZodNumber>;
|
|
307
|
+
text: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
308
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
309
|
+
export type CoachTick = z.infer<typeof CoachTickSchema>;
|
|
296
310
|
export declare const WellnessSchema: z.ZodObject<{
|
|
297
311
|
id: z.ZodOptional<z.ZodString>;
|
|
298
312
|
ctl: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
@@ -500,6 +514,16 @@ export declare const AthleteSchema: z.ZodObject<{
|
|
|
500
514
|
icu_weight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
501
515
|
icu_coach: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
502
516
|
icu_permission: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
517
|
+
coach_ticks: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
|
|
518
|
+
id: z.ZodOptional<z.ZodNumber>;
|
|
519
|
+
text: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
520
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
521
|
+
id: z.ZodOptional<z.ZodNumber>;
|
|
522
|
+
text: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
523
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
524
|
+
id: z.ZodOptional<z.ZodNumber>;
|
|
525
|
+
text: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
526
|
+
}, z.ZodTypeAny, "passthrough">>, "many">>>;
|
|
503
527
|
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
504
528
|
id: z.ZodOptional<z.ZodString>;
|
|
505
529
|
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -513,6 +537,16 @@ export declare const AthleteSchema: z.ZodObject<{
|
|
|
513
537
|
icu_weight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
514
538
|
icu_coach: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
515
539
|
icu_permission: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
540
|
+
coach_ticks: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
|
|
541
|
+
id: z.ZodOptional<z.ZodNumber>;
|
|
542
|
+
text: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
543
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
544
|
+
id: z.ZodOptional<z.ZodNumber>;
|
|
545
|
+
text: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
546
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
547
|
+
id: z.ZodOptional<z.ZodNumber>;
|
|
548
|
+
text: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
549
|
+
}, z.ZodTypeAny, "passthrough">>, "many">>>;
|
|
516
550
|
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
517
551
|
id: z.ZodOptional<z.ZodString>;
|
|
518
552
|
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -526,6 +560,16 @@ export declare const AthleteSchema: z.ZodObject<{
|
|
|
526
560
|
icu_weight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
527
561
|
icu_coach: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
528
562
|
icu_permission: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
563
|
+
coach_ticks: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
|
|
564
|
+
id: z.ZodOptional<z.ZodNumber>;
|
|
565
|
+
text: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
566
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
567
|
+
id: z.ZodOptional<z.ZodNumber>;
|
|
568
|
+
text: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
569
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
570
|
+
id: z.ZodOptional<z.ZodNumber>;
|
|
571
|
+
text: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
572
|
+
}, z.ZodTypeAny, "passthrough">>, "many">>>;
|
|
529
573
|
}, z.ZodTypeAny, "passthrough">>;
|
|
530
574
|
export type Athlete = z.infer<typeof AthleteSchema>;
|
|
531
575
|
export declare const SportSettingsSchema: z.ZodObject<{
|
|
@@ -685,6 +729,7 @@ export declare function updateEvent(config: ClientConfig, eventId: string, input
|
|
|
685
729
|
export declare function deleteEvent(config: ClientConfig, eventId: string): Promise<void>;
|
|
686
730
|
export declare function getAthleteProfile(config: ClientConfig): Promise<Athlete>;
|
|
687
731
|
export declare function listAthletes(config: ClientConfig): Promise<Athlete[]>;
|
|
732
|
+
export declare function setActivityCoachTick(config: ClientConfig, activityId: string, coachTickId: number | null): Promise<Activity>;
|
|
688
733
|
export declare function getAthleteZones(config: ClientConfig): Promise<SportSettings[]>;
|
|
689
734
|
export declare function getAthleteSummary(config: ClientConfig): Promise<AthleteFitness | undefined>;
|
|
690
735
|
export declare function getWellnessDay(config: ClientConfig, date: string): Promise<Wellness>;
|
package/dist/client.js
CHANGED
|
@@ -97,6 +97,14 @@ export const ActivitySchema = z.object({
|
|
|
97
97
|
carbs_used: z.number().nullable().optional(),
|
|
98
98
|
carbs_ingested: z.number().nullable().optional(),
|
|
99
99
|
avg_lr_balance: z.number().nullable().optional(),
|
|
100
|
+
// id of one of the athlete's coach_ticks presets (see AthleteSchema), or null if unset.
|
|
101
|
+
coach_tick: z.number().nullable().optional(),
|
|
102
|
+
}).passthrough();
|
|
103
|
+
// A coach-configured quick-rating preset (e.g. "🔥 Great", "⚠️ Review") an athlete's activities
|
|
104
|
+
// can be tagged with via Activity.coach_tick (which stores just the preset's id).
|
|
105
|
+
export const CoachTickSchema = z.object({
|
|
106
|
+
id: z.number().optional(),
|
|
107
|
+
text: z.string().nullable().optional(),
|
|
100
108
|
}).passthrough();
|
|
101
109
|
export const WellnessSchema = z.object({
|
|
102
110
|
id: z.string().optional(),
|
|
@@ -165,6 +173,7 @@ export const AthleteSchema = z.object({
|
|
|
165
173
|
// Present only on entries returned by /athletes: the caller's access level on this athlete
|
|
166
174
|
// (NONE/READ/WRITE for someone they follow/coach, absent for their own profile).
|
|
167
175
|
icu_permission: z.string().nullable().optional(),
|
|
176
|
+
coach_ticks: z.array(CoachTickSchema).nullable().optional(),
|
|
168
177
|
}).passthrough();
|
|
169
178
|
export const SportSettingsSchema = z.object({
|
|
170
179
|
types: z.array(z.string()).nullable().optional(),
|
|
@@ -303,6 +312,16 @@ export async function listAthletes(config) {
|
|
|
303
312
|
const data = await request(`/athletes`, config);
|
|
304
313
|
return z.array(AthleteSchema).parse(data);
|
|
305
314
|
}
|
|
315
|
+
// coach_tick is a fixed 1-5 rating scale (verified against the live API — PUT /athlete/{id}
|
|
316
|
+
// with a custom coach_ticks list is accepted but never persists, so presets aren't
|
|
317
|
+
// API-configurable). A JSON null is silently ignored by the server (treated as "no change");
|
|
318
|
+
// -1 is what actually clears an existing rating.
|
|
319
|
+
export async function setActivityCoachTick(config, activityId, coachTickId) {
|
|
320
|
+
const data = await mutate("PUT", `/activity/${activityId}`, config, {
|
|
321
|
+
coach_tick: coachTickId ?? -1,
|
|
322
|
+
});
|
|
323
|
+
return ActivitySchema.parse(data);
|
|
324
|
+
}
|
|
306
325
|
export async function getAthleteZones(config) {
|
|
307
326
|
const data = await request(`/athlete/${config.athleteId}/sport-settings`, config);
|
|
308
327
|
return z.array(SportSettingsSchema).parse(data);
|
package/dist/format.js
CHANGED
|
@@ -44,6 +44,9 @@ export function formatActivity(a) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
lines.push("", "Other:", ` Cadence: ${n(a.average_cadence, " rpm")} | Calories: ${n(a.calories)} | Speed: ${n(a.average_speed, " m/s")} (max ${n(a.max_speed, " m/s")})`, ` Temp: ${n(a.average_temp, "°C")} | Trainer: ${a.trainer ? "Yes" : "No"} | Coasting: ${fmtDuration(a.coasting_time)}`, ` RPE: ${n(a.icu_rpe ?? a.perceived_exertion)}/10 | Feel: ${n(a.feel)}/5 | S-RPE: ${n(a.session_rpe)}`, ` L/R Balance: ${a.avg_lr_balance != null ? `${(100 - a.avg_lr_balance).toFixed(0)}/${a.avg_lr_balance.toFixed(0)}` : "N/A"}`, "", "Fitness:", ` CTL: ${n(a.icu_ctl)} | ATL: ${n(a.icu_atl)} | TRIMP: ${n(a.trimp)} | Polarization Index: ${n(a.polarization_index)}`, ` Power Load: ${n(a.power_load)} | HR Load: ${n(a.hr_load)} | Strain: ${n(a.strain_score)}`, ` SS CP: ${n(a.ss_cp, " W")} | SS W': ${n(a.ss_w_prime)} | SS Pmax: ${n(a.ss_p_max)}`, ` Weight: ${n(a.icu_weight, " kg")} | CHO Used: ${n(a.carbs_used, " g")} | CHO In: ${n(a.carbs_ingested, " g")}`, "", "Equipment:", ` Gear: ${n(a.gear?.name)} | Device: ${n(a.device_name)} | Power Meter: ${n(a.power_meter)}`, ` Source: ${n(a.source)} | Compliance: ${a.compliance != null ? `${a.compliance}%` : "N/A"}`);
|
|
47
|
+
if (a.coach_tick != null) {
|
|
48
|
+
lines.push(` Coach Tick: [${a.coach_tick}] (see get_coach_ticks for the label)`);
|
|
49
|
+
}
|
|
47
50
|
if (a.description)
|
|
48
51
|
lines.push("", `Description: ${a.description}`);
|
|
49
52
|
return lines.join("\n");
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
4
4
|
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
|
-
import { getActivities, getActivityDetails, getActivityIntervals, getWellness, getEvents, getEventById, getNotesByDate, createEvent, updateEvent, deleteEvent, getAthleteProfile, getAthleteZones, getAthleteSummary, listAthletes, getWellnessDay, updateWellness, getActivityStreams, getActivityPowerCurves, listWorkouts, createWorkout, bulkCreateEvents, bulkDeleteEvents, getAthleteFitness, getActivityMessages, addActivityMessage, deleteActivityMessage, updateActivityMessage, } from "./client.js";
|
|
7
|
+
import { getActivities, getActivityDetails, getActivityIntervals, getWellness, getEvents, getEventById, getNotesByDate, createEvent, updateEvent, deleteEvent, getAthleteProfile, getAthleteZones, getAthleteSummary, listAthletes, setActivityCoachTick, getWellnessDay, updateWellness, getActivityStreams, getActivityPowerCurves, listWorkouts, createWorkout, bulkCreateEvents, bulkDeleteEvents, getAthleteFitness, getActivityMessages, addActivityMessage, deleteActivityMessage, updateActivityMessage, } from "./client.js";
|
|
8
8
|
import { formatActivity, formatWellness, formatEvent, formatIntervalRow, formatSportZones, formatMessage, } from "./format.js";
|
|
9
9
|
function getConfig(args) {
|
|
10
10
|
const apiKey = args["api_key"] ?? process.env.API_KEY ?? "";
|
|
@@ -193,6 +193,31 @@ const TOOLS = [
|
|
|
193
193
|
},
|
|
194
194
|
},
|
|
195
195
|
},
|
|
196
|
+
{
|
|
197
|
+
name: "get_coach_ticks",
|
|
198
|
+
description: "Get the coach-tick rating configured on an athlete's profile presets, if any. In practice coach_tick is a fixed 1-5 rating scale on Intervals.icu — use set_activity_coach_tick to rate an activity.",
|
|
199
|
+
inputSchema: {
|
|
200
|
+
type: "object",
|
|
201
|
+
properties: {
|
|
202
|
+
athlete_id: { type: "string", description: "Athlete ID (defaults to ATHLETE_ID env var)" },
|
|
203
|
+
api_key: { type: "string", description: "API key (defaults to API_KEY env var)" },
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
name: "set_activity_coach_tick",
|
|
209
|
+
description: "Set or clear the coach's rating on a specific activity (Intervals.icu's coach-tick feature: a 1-5 scale). Pass coach_tick_id 1-5 to rate it, or omit to clear the current rating.",
|
|
210
|
+
inputSchema: {
|
|
211
|
+
type: "object",
|
|
212
|
+
required: ["activity_id"],
|
|
213
|
+
properties: {
|
|
214
|
+
activity_id: { type: "string", description: "The activity ID" },
|
|
215
|
+
coach_tick_id: { type: "number", description: "Rating 1-5. Omit to clear the current rating." },
|
|
216
|
+
athlete_id: { type: "string", description: "Athlete ID (defaults to ATHLETE_ID env var)" },
|
|
217
|
+
api_key: { type: "string", description: "API key (defaults to API_KEY env var)" },
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
},
|
|
196
221
|
{
|
|
197
222
|
name: "get_athlete_profile",
|
|
198
223
|
description: "Get athlete profile: name, sex, DOB, weight, resting HR, city/country. For FTP/LTHR/zones use get_athlete_zones.",
|
|
@@ -633,6 +658,30 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
633
658
|
});
|
|
634
659
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
635
660
|
}
|
|
661
|
+
case "get_coach_ticks": {
|
|
662
|
+
const config = getConfig(args);
|
|
663
|
+
const athlete = await getAthleteProfile(config);
|
|
664
|
+
const ticks = athlete.coach_ticks ?? [];
|
|
665
|
+
if (!ticks.length) {
|
|
666
|
+
return { content: [{ type: "text", text: "No coach-tick presets on this athlete's profile — coach_tick is a fixed 1-5 rating scale. Use set_activity_coach_tick to rate an activity." }] };
|
|
667
|
+
}
|
|
668
|
+
const lines = ticks.map((t) => `- [${t.id}] ${t.text ?? ""}`);
|
|
669
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
670
|
+
}
|
|
671
|
+
case "set_activity_coach_tick": {
|
|
672
|
+
const activityId = z.string().parse(args["activity_id"]);
|
|
673
|
+
const coachTickId = args["coach_tick_id"];
|
|
674
|
+
const config = getConfig(args);
|
|
675
|
+
await setActivityCoachTick(config, activityId, coachTickId ?? null);
|
|
676
|
+
return {
|
|
677
|
+
content: [{
|
|
678
|
+
type: "text",
|
|
679
|
+
text: coachTickId != null
|
|
680
|
+
? `Coach tick ${coachTickId} set on activity ${activityId}.`
|
|
681
|
+
: `Coach tick cleared on activity ${activityId}.`,
|
|
682
|
+
}],
|
|
683
|
+
};
|
|
684
|
+
}
|
|
636
685
|
case "get_athlete_profile": {
|
|
637
686
|
const config = getConfig(args);
|
|
638
687
|
const athlete = await getAthleteProfile(config);
|