intervals-icu-mcp-server 0.3.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 +46 -0
- package/dist/client.js +23 -0
- package/dist/format.js +4 -2
- 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
|
@@ -86,6 +86,19 @@ export declare const ActivitySchema: z.ZodObject<{
|
|
|
86
86
|
source: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
87
87
|
compliance: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
88
88
|
icu_chat_id: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
89
|
+
coasting_time: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
90
|
+
ss_cp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
91
|
+
ss_w_prime: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
92
|
+
ss_p_max: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
93
|
+
icu_pm_ftp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
94
|
+
icu_w_prime: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
95
|
+
p_max: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
96
|
+
icu_max_wbal_depletion: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
97
|
+
icu_joules_above_ftp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
98
|
+
icu_weight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
99
|
+
carbs_used: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
100
|
+
carbs_ingested: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
101
|
+
avg_lr_balance: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
89
102
|
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
90
103
|
id: z.ZodString;
|
|
91
104
|
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -169,6 +182,19 @@ export declare const ActivitySchema: z.ZodObject<{
|
|
|
169
182
|
source: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
170
183
|
compliance: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
171
184
|
icu_chat_id: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
185
|
+
coasting_time: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
186
|
+
ss_cp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
187
|
+
ss_w_prime: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
188
|
+
ss_p_max: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
189
|
+
icu_pm_ftp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
190
|
+
icu_w_prime: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
191
|
+
p_max: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
192
|
+
icu_max_wbal_depletion: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
193
|
+
icu_joules_above_ftp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
194
|
+
icu_weight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
195
|
+
carbs_used: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
196
|
+
carbs_ingested: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
197
|
+
avg_lr_balance: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
172
198
|
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
173
199
|
id: z.ZodString;
|
|
174
200
|
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -252,6 +278,19 @@ export declare const ActivitySchema: z.ZodObject<{
|
|
|
252
278
|
source: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
253
279
|
compliance: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
254
280
|
icu_chat_id: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
281
|
+
coasting_time: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
282
|
+
ss_cp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
283
|
+
ss_w_prime: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
284
|
+
ss_p_max: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
285
|
+
icu_pm_ftp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
286
|
+
icu_w_prime: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
287
|
+
p_max: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
288
|
+
icu_max_wbal_depletion: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
289
|
+
icu_joules_above_ftp: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
290
|
+
icu_weight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
291
|
+
carbs_used: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
292
|
+
carbs_ingested: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
293
|
+
avg_lr_balance: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
255
294
|
}, z.ZodTypeAny, "passthrough">>;
|
|
256
295
|
export type Activity = z.infer<typeof ActivitySchema>;
|
|
257
296
|
export declare const WellnessSchema: z.ZodObject<{
|
|
@@ -459,6 +498,8 @@ export declare const AthleteSchema: z.ZodObject<{
|
|
|
459
498
|
country: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
460
499
|
icu_resting_hr: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
461
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>>;
|
|
462
503
|
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
463
504
|
id: z.ZodOptional<z.ZodString>;
|
|
464
505
|
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -470,6 +511,8 @@ export declare const AthleteSchema: z.ZodObject<{
|
|
|
470
511
|
country: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
471
512
|
icu_resting_hr: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
472
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>>;
|
|
473
516
|
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
474
517
|
id: z.ZodOptional<z.ZodString>;
|
|
475
518
|
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -481,6 +524,8 @@ export declare const AthleteSchema: z.ZodObject<{
|
|
|
481
524
|
country: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
482
525
|
icu_resting_hr: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
483
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>>;
|
|
484
529
|
}, z.ZodTypeAny, "passthrough">>;
|
|
485
530
|
export type Athlete = z.infer<typeof AthleteSchema>;
|
|
486
531
|
export declare const SportSettingsSchema: z.ZodObject<{
|
|
@@ -639,6 +684,7 @@ export declare function createEvent(config: ClientConfig, input: EventInput): Pr
|
|
|
639
684
|
export declare function updateEvent(config: ClientConfig, eventId: string, input: EventInput): Promise<Event>;
|
|
640
685
|
export declare function deleteEvent(config: ClientConfig, eventId: string): Promise<void>;
|
|
641
686
|
export declare function getAthleteProfile(config: ClientConfig): Promise<Athlete>;
|
|
687
|
+
export declare function listAthletes(config: ClientConfig): Promise<Athlete[]>;
|
|
642
688
|
export declare function getAthleteZones(config: ClientConfig): Promise<SportSettings[]>;
|
|
643
689
|
export declare function getAthleteSummary(config: ClientConfig): Promise<AthleteFitness | undefined>;
|
|
644
690
|
export declare function getWellnessDay(config: ClientConfig, date: string): Promise<Wellness>;
|
package/dist/client.js
CHANGED
|
@@ -84,6 +84,19 @@ export const ActivitySchema = z.object({
|
|
|
84
84
|
source: z.string().nullable().optional(),
|
|
85
85
|
compliance: z.number().nullable().optional(),
|
|
86
86
|
icu_chat_id: z.number().nullable().optional(),
|
|
87
|
+
coasting_time: z.number().nullable().optional(),
|
|
88
|
+
ss_cp: z.number().nullable().optional(),
|
|
89
|
+
ss_w_prime: z.number().nullable().optional(),
|
|
90
|
+
ss_p_max: z.number().nullable().optional(),
|
|
91
|
+
icu_pm_ftp: z.number().nullable().optional(),
|
|
92
|
+
icu_w_prime: z.number().nullable().optional(),
|
|
93
|
+
p_max: z.number().nullable().optional(),
|
|
94
|
+
icu_max_wbal_depletion: z.number().nullable().optional(),
|
|
95
|
+
icu_joules_above_ftp: z.number().nullable().optional(),
|
|
96
|
+
icu_weight: z.number().nullable().optional(),
|
|
97
|
+
carbs_used: z.number().nullable().optional(),
|
|
98
|
+
carbs_ingested: z.number().nullable().optional(),
|
|
99
|
+
avg_lr_balance: z.number().nullable().optional(),
|
|
87
100
|
}).passthrough();
|
|
88
101
|
export const WellnessSchema = z.object({
|
|
89
102
|
id: z.string().optional(),
|
|
@@ -148,6 +161,10 @@ export const AthleteSchema = z.object({
|
|
|
148
161
|
country: z.string().nullable().optional(),
|
|
149
162
|
icu_resting_hr: z.number().nullable().optional(),
|
|
150
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(),
|
|
151
168
|
}).passthrough();
|
|
152
169
|
export const SportSettingsSchema = z.object({
|
|
153
170
|
types: z.array(z.string()).nullable().optional(),
|
|
@@ -280,6 +297,12 @@ export async function getAthleteProfile(config) {
|
|
|
280
297
|
const data = await request(`/athlete/${config.athleteId}`, config);
|
|
281
298
|
return AthleteSchema.parse(data);
|
|
282
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
|
+
}
|
|
283
306
|
export async function getAthleteZones(config) {
|
|
284
307
|
const data = await request(`/athlete/${config.athleteId}/sport-settings`, config);
|
|
285
308
|
return z.array(SportSettingsSchema).parse(data);
|
package/dist/format.js
CHANGED
|
@@ -23,7 +23,9 @@ export function formatActivity(a) {
|
|
|
23
23
|
"Power:",
|
|
24
24
|
` Avg: ${n(a.icu_average_watts, " W")} | Weighted Avg: ${n(a.icu_weighted_avg_watts, " W")} | FTP: ${n(a.icu_ftp, " W")}`,
|
|
25
25
|
` Training Load: ${n(a.icu_training_load)} | Intensity: ${n(a.icu_intensity)} | VI: ${n(a.icu_variability_index)}`,
|
|
26
|
-
` Critical Power: ${n(a.icu_pm_cp, " W")} | Rolling FTP: ${n(a.icu_rolling_ftp, " W")} |
|
|
26
|
+
` Critical Power: ${n(a.icu_pm_cp, " W")} | Rolling FTP: ${n(a.icu_rolling_ftp, " W")} | Activity eFTP: ${n(a.icu_pm_ftp, " W")}`,
|
|
27
|
+
` Energy: ${a.icu_joules != null ? `${Math.round(a.icu_joules / 1000)} kJ` : "N/A"} | Above FTP: ${a.icu_joules_above_ftp != null ? `${Math.round(a.icu_joules_above_ftp / 1000)} kJ` : "N/A"}`,
|
|
28
|
+
` W': ${n(a.icu_w_prime, " J")} | Pmax: ${n(a.p_max, " W")} | W'bal Drop: ${a.icu_max_wbal_depletion != null ? `${(a.icu_max_wbal_depletion / 1000).toFixed(1)} kJ` : "N/A"}`,
|
|
27
29
|
"",
|
|
28
30
|
"Heart Rate:",
|
|
29
31
|
` Avg: ${n(a.average_heartrate, " bpm")} | Max: ${n(a.max_heartrate, " bpm")}`,
|
|
@@ -41,7 +43,7 @@ export function formatActivity(a) {
|
|
|
41
43
|
lines.push(` HR: ${a.icu_hr_zone_times.map((secs, i) => `Z${i + 1} ${fmtDuration(secs)}`).join(" | ")}`);
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
|
-
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"}`, ` RPE: ${n(a.icu_rpe ?? a.perceived_exertion)}/10 | Feel: ${n(a.feel)}/5`, "", "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)}`, "", "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"}`);
|
|
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"}`);
|
|
45
47
|
if (a.description)
|
|
46
48
|
lines.push("", `Description: ${a.description}`);
|
|
47
49
|
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, 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);
|