intervals-icu-mcp-server 0.4.0 → 0.5.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
@@ -78,10 +78,16 @@ Add to `~/.claude/settings.json`:
78
78
  ### Athlete
79
79
  | Tool | Description |
80
80
  |------|-------------|
81
+ | `list_athletes` | List athletes you follow or coach (including yourself) — find athlete IDs for a coach account |
81
82
  | `get_athlete_profile` | FTP, LTHR, weight, VO2max, resting HR |
82
83
  | `get_athlete_zones` | Power, HR and pace training zones |
83
84
  | `get_athlete_summary` | Current CTL, ATL, TSB and ramp rate snapshot |
84
85
 
86
+ **Coach accounts:** every tool accepts an optional `athlete_id` argument that overrides the
87
+ `ATHLETE_ID` env var for that call. Log in with a coach's `API_KEY`, call `list_athletes` to get
88
+ IDs for the athletes they coach, then pass `athlete_id: "iXXXXX"` on any tool call to work with
89
+ that specific athlete — no need to swap accounts or `.env` files.
90
+
85
91
  ### Calendar Events
86
92
  | Tool | Description |
87
93
  |------|-------------|
package/dist/client.d.ts CHANGED
@@ -498,6 +498,8 @@ export declare const AthleteSchema: z.ZodObject<{
498
498
  country: z.ZodOptional<z.ZodNullable<z.ZodString>>;
499
499
  icu_resting_hr: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
500
500
  icu_weight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
501
+ icu_coach: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
502
+ icu_permission: z.ZodOptional<z.ZodNullable<z.ZodString>>;
501
503
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
502
504
  id: z.ZodOptional<z.ZodString>;
503
505
  name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -509,6 +511,8 @@ export declare const AthleteSchema: z.ZodObject<{
509
511
  country: z.ZodOptional<z.ZodNullable<z.ZodString>>;
510
512
  icu_resting_hr: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
511
513
  icu_weight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
514
+ icu_coach: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
515
+ icu_permission: z.ZodOptional<z.ZodNullable<z.ZodString>>;
512
516
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
513
517
  id: z.ZodOptional<z.ZodString>;
514
518
  name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -520,6 +524,8 @@ export declare const AthleteSchema: z.ZodObject<{
520
524
  country: z.ZodOptional<z.ZodNullable<z.ZodString>>;
521
525
  icu_resting_hr: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
522
526
  icu_weight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
527
+ icu_coach: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
528
+ icu_permission: z.ZodOptional<z.ZodNullable<z.ZodString>>;
523
529
  }, z.ZodTypeAny, "passthrough">>;
524
530
  export type Athlete = z.infer<typeof AthleteSchema>;
525
531
  export declare const SportSettingsSchema: z.ZodObject<{
@@ -678,6 +684,7 @@ export declare function createEvent(config: ClientConfig, input: EventInput): Pr
678
684
  export declare function updateEvent(config: ClientConfig, eventId: string, input: EventInput): Promise<Event>;
679
685
  export declare function deleteEvent(config: ClientConfig, eventId: string): Promise<void>;
680
686
  export declare function getAthleteProfile(config: ClientConfig): Promise<Athlete>;
687
+ export declare function listAthletes(config: ClientConfig): Promise<Athlete[]>;
681
688
  export declare function getAthleteZones(config: ClientConfig): Promise<SportSettings[]>;
682
689
  export declare function getAthleteSummary(config: ClientConfig): Promise<AthleteFitness | undefined>;
683
690
  export declare function getWellnessDay(config: ClientConfig, date: string): Promise<Wellness>;
package/dist/client.js CHANGED
@@ -161,6 +161,10 @@ export const AthleteSchema = z.object({
161
161
  country: z.string().nullable().optional(),
162
162
  icu_resting_hr: z.number().nullable().optional(),
163
163
  icu_weight: z.number().nullable().optional(),
164
+ icu_coach: z.boolean().nullable().optional(),
165
+ // Present only on entries returned by /athletes: the caller's access level on this athlete
166
+ // (NONE/READ/WRITE for someone they follow/coach, absent for their own profile).
167
+ icu_permission: z.string().nullable().optional(),
164
168
  }).passthrough();
165
169
  export const SportSettingsSchema = z.object({
166
170
  types: z.array(z.string()).nullable().optional(),
@@ -293,6 +297,12 @@ export async function getAthleteProfile(config) {
293
297
  const data = await request(`/athlete/${config.athleteId}`, config);
294
298
  return AthleteSchema.parse(data);
295
299
  }
300
+ // Requires an API_KEY (not a bearer token). Athlete ID in `config` is ignored — this call
301
+ // is not athlete-scoped, it lists everyone the key's account follows or coaches.
302
+ export async function listAthletes(config) {
303
+ const data = await request(`/athletes`, config);
304
+ return z.array(AthleteSchema).parse(data);
305
+ }
296
306
  export async function getAthleteZones(config) {
297
307
  const data = await request(`/athlete/${config.athleteId}/sport-settings`, config);
298
308
  return z.array(SportSettingsSchema).parse(data);
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, 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, 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 ?? "";
@@ -17,6 +17,14 @@ 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
+ // list_athletes isn't athlete-scoped (it lists everyone the key's account can access), so it
21
+ // only needs the API key — requiring athlete_id here would break coaches with no default athlete.
22
+ function getApiKey(args) {
23
+ const apiKey = args["api_key"] ?? process.env.API_KEY ?? "";
24
+ if (!apiKey)
25
+ throw new Error("No API key provided. Set API_KEY in .env or pass api_key.");
26
+ return apiKey;
27
+ }
20
28
  function toDateStr(d) {
21
29
  const y = d.getFullYear();
22
30
  const m = String(d.getMonth() + 1).padStart(2, "0");
@@ -175,6 +183,16 @@ const TOOLS = [
175
183
  },
176
184
  },
177
185
  },
186
+ {
187
+ name: "list_athletes",
188
+ description: "List athletes you follow or coach, including yourself. Use this with a coach account to find athlete IDs, then pass athlete_id on other tools (get_activities, get_wellness_data, get_events, etc.) to work with a specific athlete under that same account.",
189
+ inputSchema: {
190
+ type: "object",
191
+ properties: {
192
+ api_key: { type: "string", description: "API key (defaults to API_KEY env var)" },
193
+ },
194
+ },
195
+ },
178
196
  {
179
197
  name: "get_athlete_profile",
180
198
  description: "Get athlete profile: name, sex, DOB, weight, resting HR, city/country. For FTP/LTHR/zones use get_athlete_zones.",
@@ -602,6 +620,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
602
620
  await deleteEvent(config, eventId);
603
621
  return { content: [{ type: "text", text: `Event ${eventId} deleted.` }] };
604
622
  }
623
+ case "list_athletes": {
624
+ const apiKey = getApiKey(args);
625
+ const athletes = await listAthletes({ apiKey, athleteId: "" });
626
+ if (!athletes.length)
627
+ return { content: [{ type: "text", text: "No athletes found." }] };
628
+ const lines = athletes.map((a) => {
629
+ const perm = a.icu_permission ? ` [${a.icu_permission}]` : " (self)";
630
+ const coach = a.icu_coach ? " · coach" : "";
631
+ const email = a.email ? ` · ${a.email}` : "";
632
+ return `- **${a.name ?? "Unnamed"}** — id: ${a.id}${perm}${coach}${email}`;
633
+ });
634
+ return { content: [{ type: "text", text: lines.join("\n") }] };
635
+ }
605
636
  case "get_athlete_profile": {
606
637
  const config = getConfig(args);
607
638
  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.4.0",
3
+ "version": "0.5.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": {