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 +6 -0
- package/dist/client.d.ts +7 -0
- package/dist/client.js +10 -0
- package/dist/index.js +32 -1
- package/package.json +1 -1
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);
|