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 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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "intervals-icu-mcp-server",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "MCP server for Intervals.icu — 26 tools for activities, wellness, calendar events, workouts and more",
5
5
  "type": "module",
6
6
  "bin": {