intervals-icu-mcp-server 0.4.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 +8 -0
- package/dist/client.d.ts +52 -0
- package/dist/client.js +29 -0
- package/dist/format.js +3 -0
- package/dist/index.js +81 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -78,10 +78,18 @@ 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 |
|
|
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 |
|
|
81
84
|
| `get_athlete_profile` | FTP, LTHR, weight, VO2max, resting HR |
|
|
82
85
|
| `get_athlete_zones` | Power, HR and pace training zones |
|
|
83
86
|
| `get_athlete_summary` | Current CTL, ATL, TSB and ramp rate snapshot |
|
|
84
87
|
|
|
88
|
+
**Coach accounts:** every tool accepts an optional `athlete_id` argument that overrides the
|
|
89
|
+
`ATHLETE_ID` env var for that call. Log in with a coach's `API_KEY`, call `list_athletes` to get
|
|
90
|
+
IDs for the athletes they coach, then pass `athlete_id: "iXXXXX"` on any tool call to work with
|
|
91
|
+
that specific athlete — no need to swap accounts or `.env` files.
|
|
92
|
+
|
|
85
93
|
### Calendar Events
|
|
86
94
|
| Tool | Description |
|
|
87
95
|
|------|-------------|
|
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>>;
|
|
@@ -498,6 +512,18 @@ export declare const AthleteSchema: z.ZodObject<{
|
|
|
498
512
|
country: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
499
513
|
icu_resting_hr: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
500
514
|
icu_weight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
515
|
+
icu_coach: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
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">>>;
|
|
501
527
|
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
502
528
|
id: z.ZodOptional<z.ZodString>;
|
|
503
529
|
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -509,6 +535,18 @@ export declare const AthleteSchema: z.ZodObject<{
|
|
|
509
535
|
country: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
510
536
|
icu_resting_hr: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
511
537
|
icu_weight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
538
|
+
icu_coach: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
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">>>;
|
|
512
550
|
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
513
551
|
id: z.ZodOptional<z.ZodString>;
|
|
514
552
|
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -520,6 +558,18 @@ export declare const AthleteSchema: z.ZodObject<{
|
|
|
520
558
|
country: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
521
559
|
icu_resting_hr: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
522
560
|
icu_weight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
561
|
+
icu_coach: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
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">>>;
|
|
523
573
|
}, z.ZodTypeAny, "passthrough">>;
|
|
524
574
|
export type Athlete = z.infer<typeof AthleteSchema>;
|
|
525
575
|
export declare const SportSettingsSchema: z.ZodObject<{
|
|
@@ -678,6 +728,8 @@ export declare function createEvent(config: ClientConfig, input: EventInput): Pr
|
|
|
678
728
|
export declare function updateEvent(config: ClientConfig, eventId: string, input: EventInput): Promise<Event>;
|
|
679
729
|
export declare function deleteEvent(config: ClientConfig, eventId: string): Promise<void>;
|
|
680
730
|
export declare function getAthleteProfile(config: ClientConfig): Promise<Athlete>;
|
|
731
|
+
export declare function listAthletes(config: ClientConfig): Promise<Athlete[]>;
|
|
732
|
+
export declare function setActivityCoachTick(config: ClientConfig, activityId: string, coachTickId: number | null): Promise<Activity>;
|
|
681
733
|
export declare function getAthleteZones(config: ClientConfig): Promise<SportSettings[]>;
|
|
682
734
|
export declare function getAthleteSummary(config: ClientConfig): Promise<AthleteFitness | undefined>;
|
|
683
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(),
|
|
@@ -161,6 +169,11 @@ export const AthleteSchema = z.object({
|
|
|
161
169
|
country: z.string().nullable().optional(),
|
|
162
170
|
icu_resting_hr: z.number().nullable().optional(),
|
|
163
171
|
icu_weight: z.number().nullable().optional(),
|
|
172
|
+
icu_coach: z.boolean().nullable().optional(),
|
|
173
|
+
// Present only on entries returned by /athletes: the caller's access level on this athlete
|
|
174
|
+
// (NONE/READ/WRITE for someone they follow/coach, absent for their own profile).
|
|
175
|
+
icu_permission: z.string().nullable().optional(),
|
|
176
|
+
coach_ticks: z.array(CoachTickSchema).nullable().optional(),
|
|
164
177
|
}).passthrough();
|
|
165
178
|
export const SportSettingsSchema = z.object({
|
|
166
179
|
types: z.array(z.string()).nullable().optional(),
|
|
@@ -293,6 +306,22 @@ export async function getAthleteProfile(config) {
|
|
|
293
306
|
const data = await request(`/athlete/${config.athleteId}`, config);
|
|
294
307
|
return AthleteSchema.parse(data);
|
|
295
308
|
}
|
|
309
|
+
// Requires an API_KEY (not a bearer token). Athlete ID in `config` is ignored — this call
|
|
310
|
+
// is not athlete-scoped, it lists everyone the key's account follows or coaches.
|
|
311
|
+
export async function listAthletes(config) {
|
|
312
|
+
const data = await request(`/athletes`, config);
|
|
313
|
+
return z.array(AthleteSchema).parse(data);
|
|
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
|
+
}
|
|
296
325
|
export async function getAthleteZones(config) {
|
|
297
326
|
const data = await request(`/athlete/${config.athleteId}/sport-settings`, config);
|
|
298
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, 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 ?? "";
|
|
@@ -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,41 @@ 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
|
+
},
|
|
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
|
+
},
|
|
178
221
|
{
|
|
179
222
|
name: "get_athlete_profile",
|
|
180
223
|
description: "Get athlete profile: name, sex, DOB, weight, resting HR, city/country. For FTP/LTHR/zones use get_athlete_zones.",
|
|
@@ -602,6 +645,43 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
602
645
|
await deleteEvent(config, eventId);
|
|
603
646
|
return { content: [{ type: "text", text: `Event ${eventId} deleted.` }] };
|
|
604
647
|
}
|
|
648
|
+
case "list_athletes": {
|
|
649
|
+
const apiKey = getApiKey(args);
|
|
650
|
+
const athletes = await listAthletes({ apiKey, athleteId: "" });
|
|
651
|
+
if (!athletes.length)
|
|
652
|
+
return { content: [{ type: "text", text: "No athletes found." }] };
|
|
653
|
+
const lines = athletes.map((a) => {
|
|
654
|
+
const perm = a.icu_permission ? ` [${a.icu_permission}]` : " (self)";
|
|
655
|
+
const coach = a.icu_coach ? " · coach" : "";
|
|
656
|
+
const email = a.email ? ` · ${a.email}` : "";
|
|
657
|
+
return `- **${a.name ?? "Unnamed"}** — id: ${a.id}${perm}${coach}${email}`;
|
|
658
|
+
});
|
|
659
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
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
|
+
}
|
|
605
685
|
case "get_athlete_profile": {
|
|
606
686
|
const config = getConfig(args);
|
|
607
687
|
const athlete = await getAthleteProfile(config);
|