@trainheroic-unofficial/athlete-mcp 3.2.0 → 3.3.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/dist/server.mjs +473 -79
- package/package.json +4 -4
package/dist/server.mjs
CHANGED
|
@@ -24,12 +24,14 @@ const numLikeOrNull = z.union([
|
|
|
24
24
|
z.string(),
|
|
25
25
|
z.null()
|
|
26
26
|
]);
|
|
27
|
-
|
|
27
|
+
/** `/user/simple` — the identity + tenant key (numeric `id`) for any logged-in account. */
|
|
28
|
+
const userSimpleSchema = z.looseObject({
|
|
28
29
|
id: intLike$1,
|
|
29
30
|
roles: z.array(z.string()).optional(),
|
|
30
31
|
org_id: intLikeOrNull$1.optional()
|
|
31
32
|
});
|
|
32
|
-
|
|
33
|
+
/** `/v5/athleteProfile/summary` — lifetime training totals. Needs `use_metric` in the query. */
|
|
34
|
+
const athleteProfileSummarySchema = z.looseObject({
|
|
33
35
|
reps_sum: z.number().optional(),
|
|
34
36
|
volume_sum: z.number().optional(),
|
|
35
37
|
sessions_count: z.number().optional(),
|
|
@@ -37,7 +39,8 @@ z.looseObject({
|
|
|
37
39
|
last_logged_date: z.string().optional(),
|
|
38
40
|
duration_hours: z.number().optional()
|
|
39
41
|
});
|
|
40
|
-
|
|
42
|
+
/** `/v5/users/{id}` — the detailed athlete profile (only the fields we surface). */
|
|
43
|
+
const athleteUserSchema = z.looseObject({
|
|
41
44
|
id: intLike$1,
|
|
42
45
|
email: z.string().optional(),
|
|
43
46
|
name_first: z.string().optional(),
|
|
@@ -47,7 +50,8 @@ z.looseObject({
|
|
|
47
50
|
date_of_birth: z.string().optional(),
|
|
48
51
|
use_metric: z.boolean().optional()
|
|
49
52
|
});
|
|
50
|
-
|
|
53
|
+
/** `/1.0/athlete/prefs` — notification + display preference flags. */
|
|
54
|
+
const athletePrefsSchema = z.looseObject({ id: intLike$1 });
|
|
51
55
|
/** One item of `/2.0/athlete/workingMax` — the athlete's working max for an exercise. */
|
|
52
56
|
const athleteWorkingMaxSchema = z.looseObject({
|
|
53
57
|
exercise_id: intLike$1,
|
|
@@ -57,7 +61,7 @@ const athleteWorkingMaxSchema = z.looseObject({
|
|
|
57
61
|
type_suffix: z.string().optional(),
|
|
58
62
|
working_max_id: intLikeOrNull$1.optional()
|
|
59
63
|
});
|
|
60
|
-
z.array(athleteWorkingMaxSchema);
|
|
64
|
+
const athleteWorkingMaxListSchema = z.array(athleteWorkingMaxSchema);
|
|
61
65
|
/** One item of `/v5/users/exercises/history` — an exercise the athlete has logged. */
|
|
62
66
|
const exerciseHistoryListItemSchema = z.looseObject({
|
|
63
67
|
id: intLike$1,
|
|
@@ -107,7 +111,8 @@ const liftPRSchema = z.looseObject({
|
|
|
107
111
|
isMetric: z.boolean().optional(),
|
|
108
112
|
description: z.string().optional()
|
|
109
113
|
});
|
|
110
|
-
|
|
114
|
+
/** `/v5/exercises/{id}/history` — the per-exercise PRs + session history. */
|
|
115
|
+
const exerciseHistoryDetailSchema = z.looseObject({
|
|
111
116
|
liftPRs: z.array(liftPRSchema).optional(),
|
|
112
117
|
singleParamPRs: z.array(z.unknown()).optional(),
|
|
113
118
|
history: z.array(historyEntrySchema).optional()
|
|
@@ -123,8 +128,9 @@ const personalRecordSchema = z.looseObject({
|
|
|
123
128
|
units: z.string().optional(),
|
|
124
129
|
isMetric: z.boolean().optional()
|
|
125
130
|
});
|
|
126
|
-
z.array(personalRecordSchema);
|
|
127
|
-
|
|
131
|
+
const personalRecordListSchema = z.array(personalRecordSchema);
|
|
132
|
+
/** `/v5/exercises/{id}/stats` — last performance + PR for an exercise. Needs `date` in the query. */
|
|
133
|
+
const exerciseStatsSchema = z.looseObject({
|
|
128
134
|
isLift: z.boolean().optional(),
|
|
129
135
|
lastPerformance: z.unknown().optional(),
|
|
130
136
|
personalRecord: z.unknown().optional()
|
|
@@ -144,7 +150,7 @@ const programWorkoutSchema = z.looseObject({
|
|
|
144
150
|
team_title: z.string().optional(),
|
|
145
151
|
summarizedSavedWorkout: z.unknown().optional()
|
|
146
152
|
});
|
|
147
|
-
z.array(programWorkoutSchema);
|
|
153
|
+
const programWorkoutListSchema = z.array(programWorkoutSchema);
|
|
148
154
|
/** A `YYYY-MM-DD` date argument. The single definition reused across athlete tool inputs. */
|
|
149
155
|
const dateString = z.string().regex(/^\d{4}-\d{2}-\d{2}$/u, "expected YYYY-MM-DD");
|
|
150
156
|
z.object({
|
|
@@ -251,6 +257,163 @@ const athleteSessionRemoveArgsSchema = z.object({
|
|
|
251
257
|
programWorkoutId: idArgSchema,
|
|
252
258
|
date: dateString
|
|
253
259
|
});
|
|
260
|
+
const presentedNullNumber = z.number().nullable();
|
|
261
|
+
const presentedNullString = z.string().nullable();
|
|
262
|
+
/** Flattened exercise within a presented workout: prescriptions, logged results, units. */
|
|
263
|
+
const athleteWorkoutExerciseSchema = z.object({
|
|
264
|
+
exerciseId: presentedNullNumber,
|
|
265
|
+
title: z.string(),
|
|
266
|
+
instruction: presentedNullString,
|
|
267
|
+
units: z.array(presentedNullString),
|
|
268
|
+
prescribed: z.array(z.string()),
|
|
269
|
+
performed: z.array(z.string())
|
|
270
|
+
});
|
|
271
|
+
const athleteWorkoutBlockSchema = z.object({
|
|
272
|
+
order: z.number(),
|
|
273
|
+
title: presentedNullString,
|
|
274
|
+
instruction: presentedNullString,
|
|
275
|
+
isTest: z.boolean(),
|
|
276
|
+
exercises: z.array(athleteWorkoutExerciseSchema)
|
|
277
|
+
});
|
|
278
|
+
const athleteWorkoutViewSchema = z.object({
|
|
279
|
+
id: presentedNullNumber,
|
|
280
|
+
date: z.string(),
|
|
281
|
+
title: z.string(),
|
|
282
|
+
program: presentedNullString,
|
|
283
|
+
team: presentedNullString,
|
|
284
|
+
instruction: presentedNullString,
|
|
285
|
+
logged: z.boolean(),
|
|
286
|
+
personal: z.boolean(),
|
|
287
|
+
blocks: z.array(athleteWorkoutBlockSchema)
|
|
288
|
+
});
|
|
289
|
+
const athleteWorkoutSummarySchema = z.object({
|
|
290
|
+
id: presentedNullNumber,
|
|
291
|
+
date: z.string(),
|
|
292
|
+
title: z.string(),
|
|
293
|
+
program: presentedNullString,
|
|
294
|
+
team: presentedNullString,
|
|
295
|
+
logged: z.boolean(),
|
|
296
|
+
personal: z.boolean(),
|
|
297
|
+
exerciseCount: z.number().int().nonnegative(),
|
|
298
|
+
performedCount: z.number().int().nonnegative()
|
|
299
|
+
});
|
|
300
|
+
/** Presented views first so a summary/detail row cannot be swallowed by the loose raw list. */
|
|
301
|
+
const athleteWorkoutsOutputSchema = z.union([
|
|
302
|
+
z.array(athleteWorkoutSummarySchema),
|
|
303
|
+
z.array(athleteWorkoutViewSchema),
|
|
304
|
+
programWorkoutListSchema
|
|
305
|
+
]);
|
|
306
|
+
const athleteProfileOutputSchema = z.looseObject({
|
|
307
|
+
summary: athleteProfileSummarySchema,
|
|
308
|
+
user: athleteUserSchema
|
|
309
|
+
});
|
|
310
|
+
const athleteExerciseCatalogOutputSchema = z.array(z.object({
|
|
311
|
+
id: z.union([z.number(), z.string()]),
|
|
312
|
+
title: z.string(),
|
|
313
|
+
isCircuit: z.boolean(),
|
|
314
|
+
units: z.array(presentedNullString)
|
|
315
|
+
}));
|
|
316
|
+
const coachAthleteExerciseSchema = z.object({
|
|
317
|
+
exerciseId: presentedNullNumber,
|
|
318
|
+
title: z.string(),
|
|
319
|
+
summary: presentedNullString,
|
|
320
|
+
completed: z.boolean()
|
|
321
|
+
});
|
|
322
|
+
const coachAthleteSessionSchema = z.object({
|
|
323
|
+
workoutId: presentedNullNumber,
|
|
324
|
+
savedWorkoutId: presentedNullNumber,
|
|
325
|
+
title: z.string(),
|
|
326
|
+
logged: z.boolean(),
|
|
327
|
+
completed: z.boolean(),
|
|
328
|
+
rpe: presentedNullNumber,
|
|
329
|
+
durationMin: presentedNullNumber,
|
|
330
|
+
notes: presentedNullString,
|
|
331
|
+
exercises: z.array(coachAthleteExerciseSchema)
|
|
332
|
+
});
|
|
333
|
+
z.object({
|
|
334
|
+
athleteId: presentedNullNumber,
|
|
335
|
+
athleteName: presentedNullString,
|
|
336
|
+
year: z.number().int(),
|
|
337
|
+
month: z.number().int(),
|
|
338
|
+
sessions: z.array(coachAthleteSessionSchema)
|
|
339
|
+
});
|
|
340
|
+
const rosterActivityRowSchema = z.object({
|
|
341
|
+
athleteId: z.number(),
|
|
342
|
+
sessionsCount: presentedNullNumber,
|
|
343
|
+
firstLoggedDate: presentedNullString,
|
|
344
|
+
lastLoggedDate: presentedNullString,
|
|
345
|
+
totalReps: presentedNullNumber,
|
|
346
|
+
totalVolume: presentedNullNumber
|
|
347
|
+
});
|
|
348
|
+
const teamVolumeAthleteSchema = z.object({
|
|
349
|
+
athleteId: z.number(),
|
|
350
|
+
name: presentedNullString,
|
|
351
|
+
sessions: z.number(),
|
|
352
|
+
reps: z.number(),
|
|
353
|
+
volume: z.number(),
|
|
354
|
+
firstLoggedDate: presentedNullString,
|
|
355
|
+
lastLoggedDate: presentedNullString
|
|
356
|
+
});
|
|
357
|
+
z.object({
|
|
358
|
+
window: z.object({
|
|
359
|
+
start: z.string(),
|
|
360
|
+
end: z.string()
|
|
361
|
+
}),
|
|
362
|
+
athletes: z.array(teamVolumeAthleteSchema),
|
|
363
|
+
totals: z.object({
|
|
364
|
+
athletes: z.number(),
|
|
365
|
+
sessions: z.number(),
|
|
366
|
+
reps: z.number(),
|
|
367
|
+
volume: z.number()
|
|
368
|
+
})
|
|
369
|
+
});
|
|
370
|
+
z.array(rosterActivityRowSchema);
|
|
371
|
+
const presentedExerciseSessionSchema = z.object({
|
|
372
|
+
date: z.string(),
|
|
373
|
+
abr: presentedNullString,
|
|
374
|
+
estimated1RM: presentedNullNumber,
|
|
375
|
+
sets: z.array(z.object({
|
|
376
|
+
setNumber: z.number(),
|
|
377
|
+
value: presentedNullString
|
|
378
|
+
}))
|
|
379
|
+
});
|
|
380
|
+
const presentedExerciseHistorySchema = z.object({
|
|
381
|
+
liftPRs: z.array(z.object({
|
|
382
|
+
description: presentedNullString,
|
|
383
|
+
reps: presentedNullNumber,
|
|
384
|
+
weight: presentedNullNumber,
|
|
385
|
+
units: presentedNullString,
|
|
386
|
+
date: presentedNullString
|
|
387
|
+
})),
|
|
388
|
+
sessions: z.array(presentedExerciseSessionSchema)
|
|
389
|
+
});
|
|
390
|
+
/** Presented history first so the loose raw detail schema cannot swallow it. */
|
|
391
|
+
const exerciseHistoryOutputSchema = z.union([presentedExerciseHistorySchema, exerciseHistoryDetailSchema]);
|
|
392
|
+
//#endregion
|
|
393
|
+
//#region ../dto/src/exercise.ts
|
|
394
|
+
/**
|
|
395
|
+
* A row presented for display. The raw param-type codes are dropped and the fixed
|
|
396
|
+
* measurement units are surfaced positionally in `units`, ordered by entry slot
|
|
397
|
+
* (param 1, then param 2). Positional, not semantic: param 2 is not always the load
|
|
398
|
+
* — some exercises reverse the slots — so the units are not labelled by role.
|
|
399
|
+
*/
|
|
400
|
+
const exerciseViewSchema = z.object({
|
|
401
|
+
id: z.number(),
|
|
402
|
+
title: z.string(),
|
|
403
|
+
can_edit: z.number(),
|
|
404
|
+
user_id: z.number().nullable(),
|
|
405
|
+
use_count: z.number(),
|
|
406
|
+
units: z.array(z.string().nullable())
|
|
407
|
+
});
|
|
408
|
+
z.looseObject({
|
|
409
|
+
id: idSchema,
|
|
410
|
+
title: z.string(),
|
|
411
|
+
units: z.array(z.string().nullable())
|
|
412
|
+
});
|
|
413
|
+
z.object({
|
|
414
|
+
match: exerciseViewSchema.nullable(),
|
|
415
|
+
candidates: z.array(exerciseViewSchema)
|
|
416
|
+
});
|
|
254
417
|
z.looseObject({
|
|
255
418
|
title: z.string().min(1),
|
|
256
419
|
param_1_type: z.number().optional(),
|
|
@@ -305,6 +468,198 @@ z.object({
|
|
|
305
468
|
pub_timezone: z.string().optional()
|
|
306
469
|
}).refine((v) => v.pub_enabled !== void 0 || v.pub_days !== void 0 || v.pub_time !== void 0 || v.pub_timezone !== void 0, { message: "Provide at least one pub_* field" });
|
|
307
470
|
//#endregion
|
|
471
|
+
//#region ../dto/src/tool-output.ts
|
|
472
|
+
const nullableNumber = z.number().nullable();
|
|
473
|
+
const nullableString = z.string().nullable();
|
|
474
|
+
const truncationMarkerSchema = z.looseObject({
|
|
475
|
+
field: z.string().optional(),
|
|
476
|
+
returned: z.number().int().nonnegative().optional(),
|
|
477
|
+
total: z.number().int().nonnegative(),
|
|
478
|
+
omitted: z.number().int().nonnegative(),
|
|
479
|
+
hint: z.string()
|
|
480
|
+
});
|
|
481
|
+
/** Dedicated budget-fallback envelope. Never the natural tool shape. */
|
|
482
|
+
const truncatedOutputSchema = z.looseObject({
|
|
483
|
+
preview: z.string().optional(),
|
|
484
|
+
items: z.array(z.json()).optional(),
|
|
485
|
+
__truncated: truncationMarkerSchema
|
|
486
|
+
});
|
|
487
|
+
/**
|
|
488
|
+
* Add the shared size-budget fallback to a tool's natural result shape.
|
|
489
|
+
* Truncation is a dedicated `{ items|preview, __truncated }` envelope, so it cannot
|
|
490
|
+
* validate as the natural object and drop the marker.
|
|
491
|
+
*/
|
|
492
|
+
function toolOutputSchema(schema) {
|
|
493
|
+
return z.union([truncatedOutputSchema, schema]);
|
|
494
|
+
}
|
|
495
|
+
/** Explicit passthrough for tools whose live API payload is not yet contracted. */
|
|
496
|
+
const opaqueOutputSchema = toolOutputSchema(z.json());
|
|
497
|
+
const logSetTargetOutputSchema = z.object({
|
|
498
|
+
date: z.string(),
|
|
499
|
+
workoutTitle: z.string(),
|
|
500
|
+
program: nullableString,
|
|
501
|
+
programId: nullableNumber,
|
|
502
|
+
team: nullableString,
|
|
503
|
+
teamId: nullableNumber,
|
|
504
|
+
savedWorkoutSetId: z.number(),
|
|
505
|
+
setTitle: nullableString,
|
|
506
|
+
exercises: z.array(z.object({
|
|
507
|
+
savedWorkoutSetExerciseId: z.number(),
|
|
508
|
+
title: z.string(),
|
|
509
|
+
units: z.array(nullableString),
|
|
510
|
+
prescribed: z.array(z.string()),
|
|
511
|
+
performed: z.array(z.string())
|
|
512
|
+
}))
|
|
513
|
+
});
|
|
514
|
+
const logTargetsOutputSchema = z.union([z.array(logSetTargetOutputSchema), programWorkoutListSchema]);
|
|
515
|
+
z.object({
|
|
516
|
+
draft: z.literal(true),
|
|
517
|
+
note: z.string(),
|
|
518
|
+
would_POST: z.string(),
|
|
519
|
+
payload: z.json()
|
|
520
|
+
});
|
|
521
|
+
z.looseObject({
|
|
522
|
+
status: z.enum(["sent", "logged"]),
|
|
523
|
+
reference: z.string().optional(),
|
|
524
|
+
note: z.string()
|
|
525
|
+
});
|
|
526
|
+
z.looseObject({ removed: z.union([z.boolean(), z.number()]) });
|
|
527
|
+
const athleteSessionRemovedOutputSchema = z.object({
|
|
528
|
+
removed: z.literal(true),
|
|
529
|
+
programWorkoutId: z.number(),
|
|
530
|
+
date: z.string()
|
|
531
|
+
});
|
|
532
|
+
z.object({
|
|
533
|
+
invited: z.literal(true),
|
|
534
|
+
teamId: z.number(),
|
|
535
|
+
result: z.json().optional()
|
|
536
|
+
});
|
|
537
|
+
const setLogOutputSchema = z.object({
|
|
538
|
+
savedWorkoutSetId: z.number(),
|
|
539
|
+
exercisesLogged: z.number().int().nonnegative(),
|
|
540
|
+
setCompleted: z.boolean()
|
|
541
|
+
});
|
|
542
|
+
const setPrescriptionOutputSchema = z.object({
|
|
543
|
+
savedWorkoutSetId: z.number(),
|
|
544
|
+
exercisesPrescribed: z.number().int().nonnegative()
|
|
545
|
+
});
|
|
546
|
+
const exerciseSwapOutputSchema = z.object({
|
|
547
|
+
savedWorkoutSetExerciseId: z.number(),
|
|
548
|
+
athleteId: nullableNumber,
|
|
549
|
+
newExerciseId: z.number(),
|
|
550
|
+
newExerciseTitle: nullableString,
|
|
551
|
+
originalTeamExerciseId: nullableNumber
|
|
552
|
+
});
|
|
553
|
+
const sessionLogSetOutputSchema = z.object({
|
|
554
|
+
savedWorkoutSetId: z.number(),
|
|
555
|
+
exercisesLogged: z.number().int().nonnegative()
|
|
556
|
+
});
|
|
557
|
+
const sessionLogOutputSchema = z.object({
|
|
558
|
+
date: z.string(),
|
|
559
|
+
created: z.boolean(),
|
|
560
|
+
sets: z.array(sessionLogSetOutputSchema),
|
|
561
|
+
scheduledAlternatives: z.array(z.object({
|
|
562
|
+
exerciseId: z.number(),
|
|
563
|
+
title: z.string(),
|
|
564
|
+
program: nullableString,
|
|
565
|
+
workoutTitle: z.string(),
|
|
566
|
+
savedWorkoutSetId: z.number(),
|
|
567
|
+
savedWorkoutSetExerciseId: z.number()
|
|
568
|
+
})).optional()
|
|
569
|
+
});
|
|
570
|
+
const personalWorkoutCreatedOutputSchema = z.object({
|
|
571
|
+
programWorkoutId: z.number(),
|
|
572
|
+
workoutId: z.number(),
|
|
573
|
+
savedWorkoutId: z.number(),
|
|
574
|
+
groupId: z.number(),
|
|
575
|
+
date: z.string()
|
|
576
|
+
});
|
|
577
|
+
const workoutReadExerciseOutputSchema = z.object({
|
|
578
|
+
order: z.number(),
|
|
579
|
+
title: z.string(),
|
|
580
|
+
reps: z.array(z.string()),
|
|
581
|
+
primaryUnit: nullableString,
|
|
582
|
+
load: z.array(z.string()),
|
|
583
|
+
loadUnit: nullableString,
|
|
584
|
+
instruction: z.string()
|
|
585
|
+
});
|
|
586
|
+
const workoutReadBlockOutputSchema = z.object({
|
|
587
|
+
order: z.number(),
|
|
588
|
+
title: z.string(),
|
|
589
|
+
instruction: z.string(),
|
|
590
|
+
leaderboard: nullableString,
|
|
591
|
+
exercises: z.array(workoutReadExerciseOutputSchema)
|
|
592
|
+
});
|
|
593
|
+
const workoutReadOutputSchema = z.object({
|
|
594
|
+
pwId: z.number(),
|
|
595
|
+
date: z.string(),
|
|
596
|
+
published: z.json().optional(),
|
|
597
|
+
instruction: z.string(),
|
|
598
|
+
blocks: z.array(workoutReadBlockOutputSchema)
|
|
599
|
+
});
|
|
600
|
+
z.object({
|
|
601
|
+
pwId: z.number(),
|
|
602
|
+
workoutId: z.number(),
|
|
603
|
+
programId: z.number(),
|
|
604
|
+
published: z.literal(false),
|
|
605
|
+
advisories: z.object({
|
|
606
|
+
notes: z.array(z.string()),
|
|
607
|
+
warnings: z.array(z.string())
|
|
608
|
+
}),
|
|
609
|
+
readback: workoutReadOutputSchema.nullable(),
|
|
610
|
+
note: z.string()
|
|
611
|
+
});
|
|
612
|
+
z.object({
|
|
613
|
+
published: z.number(),
|
|
614
|
+
readback: workoutReadOutputSchema,
|
|
615
|
+
note: z.string()
|
|
616
|
+
});
|
|
617
|
+
z.object({
|
|
618
|
+
sent: z.literal(true),
|
|
619
|
+
comment: z.json()
|
|
620
|
+
});
|
|
621
|
+
z.object({
|
|
622
|
+
deleted: z.literal(true),
|
|
623
|
+
response: z.json().optional()
|
|
624
|
+
});
|
|
625
|
+
z.object({ deleted: z.number() });
|
|
626
|
+
z.object({ forgotten: z.number() });
|
|
627
|
+
z.looseObject({ id: z.union([z.number(), z.string()]) });
|
|
628
|
+
const mainLiftPrOutputSchema = z.object({
|
|
629
|
+
family: z.enum([
|
|
630
|
+
"cleanjerk",
|
|
631
|
+
"snatch",
|
|
632
|
+
"deadlift",
|
|
633
|
+
"squat",
|
|
634
|
+
"bench",
|
|
635
|
+
"overhead"
|
|
636
|
+
]),
|
|
637
|
+
label: z.string(),
|
|
638
|
+
exerciseId: nullableNumber,
|
|
639
|
+
title: nullableString,
|
|
640
|
+
weight: nullableNumber,
|
|
641
|
+
reps: nullableNumber,
|
|
642
|
+
units: nullableString,
|
|
643
|
+
date: nullableString
|
|
644
|
+
});
|
|
645
|
+
z.object({
|
|
646
|
+
athleteId: z.number(),
|
|
647
|
+
athleteName: nullableString,
|
|
648
|
+
prs: z.array(mainLiftPrOutputSchema)
|
|
649
|
+
});
|
|
650
|
+
z.object({
|
|
651
|
+
containerId: z.number(),
|
|
652
|
+
programId: z.number(),
|
|
653
|
+
title: z.string(),
|
|
654
|
+
kind: z.enum(["calendar", "fixed"]),
|
|
655
|
+
requestedName: z.string(),
|
|
656
|
+
nameApplied: z.boolean()
|
|
657
|
+
});
|
|
658
|
+
z.object({
|
|
659
|
+
programId: z.number(),
|
|
660
|
+
containerId: nullableNumber
|
|
661
|
+
});
|
|
662
|
+
//#endregion
|
|
308
663
|
//#region ../dto/src/workout.ts
|
|
309
664
|
/** A single exercise prescription inside a block. */
|
|
310
665
|
const exerciseSpecSchema = z.object({
|
|
@@ -369,6 +724,12 @@ function toId(value) {
|
|
|
369
724
|
}
|
|
370
725
|
const READ = {
|
|
371
726
|
readOnlyHint: true,
|
|
727
|
+
destructiveHint: false,
|
|
728
|
+
openWorldHint: true
|
|
729
|
+
};
|
|
730
|
+
const ADDITIVE = {
|
|
731
|
+
readOnlyHint: false,
|
|
732
|
+
destructiveHint: false,
|
|
372
733
|
openWorldHint: true
|
|
373
734
|
};
|
|
374
735
|
const DESTRUCTIVE = {
|
|
@@ -390,32 +751,36 @@ async function attempt(fn) {
|
|
|
390
751
|
}
|
|
391
752
|
/** Conservative per-result character cap, below the smallest host cap. */
|
|
392
753
|
const DEFAULT_RESULT_BUDGET = 6e4;
|
|
754
|
+
/** Smallest cap that can still carry a useful structured truncation envelope. */
|
|
755
|
+
const MIN_RESULT_BUDGET = 256;
|
|
393
756
|
/** Reserve for the `__truncated` marker so wrapping cannot push back over budget. */
|
|
394
757
|
const MARKER_RESERVE = 300;
|
|
395
758
|
const DEFAULT_ARRAY_HINT = "Result was truncated to fit the size budget. Narrow it with a filter/search argument or paginate to see the rest.";
|
|
396
759
|
const DEFAULT_OBJECT_HINT = "Result was truncated to fit the size budget. Request a more specific id or sub-resource.";
|
|
397
760
|
/**
|
|
398
761
|
* Clip an array to its first `keep` items and attach the `__truncated` marker describing what was
|
|
399
|
-
* dropped.
|
|
400
|
-
*
|
|
401
|
-
*
|
|
762
|
+
* dropped. Every budget fallback that trims a list uses this envelope (`{ items, __truncated }`),
|
|
763
|
+
* including in-place object-array truncation (the sliced array becomes `items` and `field` names
|
|
764
|
+
* the original key). `athlete_exercises` uses the same helper for a client-side catalog cap.
|
|
402
765
|
*/
|
|
403
|
-
function clipArray(items, keep, hint) {
|
|
766
|
+
function clipArray(items, keep, hint, field) {
|
|
767
|
+
const marker = {
|
|
768
|
+
returned: keep,
|
|
769
|
+
total: items.length,
|
|
770
|
+
omitted: items.length - keep,
|
|
771
|
+
hint: hint ?? DEFAULT_ARRAY_HINT
|
|
772
|
+
};
|
|
773
|
+
if (field !== void 0) marker.field = field;
|
|
404
774
|
return {
|
|
405
775
|
items: items.slice(0, keep),
|
|
406
|
-
__truncated:
|
|
407
|
-
returned: keep,
|
|
408
|
-
total: items.length,
|
|
409
|
-
omitted: items.length - keep,
|
|
410
|
-
hint: hint ?? DEFAULT_ARRAY_HINT
|
|
411
|
-
}
|
|
776
|
+
__truncated: marker
|
|
412
777
|
};
|
|
413
778
|
}
|
|
414
779
|
/** Active budget. Overridable via TH_MCP_RESULT_BUDGET on Node; the default on workerd. */
|
|
415
780
|
function resultBudget() {
|
|
416
781
|
const raw = (globalThis.process?.env)?.TH_MCP_RESULT_BUDGET;
|
|
417
782
|
const n = raw ? Number(raw) : NaN;
|
|
418
|
-
return Number.isFinite(n) && n
|
|
783
|
+
return Number.isFinite(n) && n >= MIN_RESULT_BUDGET ? n : DEFAULT_RESULT_BUDGET;
|
|
419
784
|
}
|
|
420
785
|
function isPlainObject(value) {
|
|
421
786
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -432,13 +797,6 @@ function largestPrefixCount(pieces, charBudget) {
|
|
|
432
797
|
}
|
|
433
798
|
return k;
|
|
434
799
|
}
|
|
435
|
-
/** Last resort: cap a string at the budget and label it as truncated, non-JSON output. */
|
|
436
|
-
function hardCap(text, budget, hint) {
|
|
437
|
-
if (text.length <= budget) return text;
|
|
438
|
-
const note = `\n\n[TRUNCATED: output exceeded ${budget} chars and is NOT valid JSON. ${hint ?? "Narrow the query (filter, paginate, or fetch a specific id)."}]`;
|
|
439
|
-
const keep = Math.max(0, budget - note.length);
|
|
440
|
-
return text.slice(0, keep) + note;
|
|
441
|
-
}
|
|
442
800
|
function largestArrayValuedKey(obj) {
|
|
443
801
|
let best = null;
|
|
444
802
|
let bestLen = -1;
|
|
@@ -452,56 +810,79 @@ function largestArrayValuedKey(obj) {
|
|
|
452
810
|
}
|
|
453
811
|
return best;
|
|
454
812
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
813
|
+
function jsonValue(data) {
|
|
814
|
+
return JSON.parse(JSON.stringify(data) ?? "null");
|
|
815
|
+
}
|
|
816
|
+
function previewEnvelope(source, budget, hint) {
|
|
817
|
+
const total = source.length;
|
|
818
|
+
const makeValue = (preview, markerHint) => ({
|
|
819
|
+
preview,
|
|
820
|
+
__truncated: {
|
|
821
|
+
total,
|
|
822
|
+
omitted: total - preview.length,
|
|
823
|
+
hint: markerHint
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
let markerHint = hint ?? "Narrow the query (filter, paginate, or fetch a specific id).";
|
|
827
|
+
while (JSON.stringify(makeValue("", markerHint)).length > budget && markerHint.length > 0) markerHint = markerHint.slice(0, -1);
|
|
828
|
+
if (JSON.stringify(makeValue("", markerHint)).length > budget) throw new RangeError("Result budget is too small for a structured truncation envelope.");
|
|
829
|
+
let keep = Math.max(0, budget - JSON.stringify(makeValue("", markerHint)).length);
|
|
830
|
+
let value = makeValue(source.slice(0, keep), markerHint);
|
|
831
|
+
while (JSON.stringify(value).length > budget && keep > 0) {
|
|
832
|
+
keep = Math.max(0, keep - (JSON.stringify(value).length - budget));
|
|
833
|
+
value = makeValue(source.slice(0, keep), markerHint);
|
|
834
|
+
}
|
|
835
|
+
return value;
|
|
836
|
+
}
|
|
837
|
+
function boundedResult(data, budget, hint) {
|
|
838
|
+
if (typeof data === "string" && data.length <= budget) return {
|
|
839
|
+
text: data,
|
|
840
|
+
value: data
|
|
841
|
+
};
|
|
842
|
+
const value = jsonValue(data);
|
|
843
|
+
const compact = JSON.stringify(value);
|
|
464
844
|
if (compact.length <= budget) {
|
|
465
|
-
const pretty = JSON.stringify(
|
|
466
|
-
return
|
|
845
|
+
const pretty = JSON.stringify(value, null, 2);
|
|
846
|
+
return {
|
|
847
|
+
text: pretty.length <= budget ? pretty : compact,
|
|
848
|
+
value
|
|
849
|
+
};
|
|
467
850
|
}
|
|
468
|
-
if (Array.isArray(
|
|
469
|
-
const
|
|
470
|
-
const
|
|
471
|
-
if (
|
|
472
|
-
|
|
473
|
-
|
|
851
|
+
if (Array.isArray(value)) {
|
|
852
|
+
const truncated = clipArray(value, largestPrefixCount(value.map((element) => JSON.stringify(element)), budget - MARKER_RESERVE), hint);
|
|
853
|
+
const text = JSON.stringify(truncated);
|
|
854
|
+
if (text.length <= budget) return {
|
|
855
|
+
text,
|
|
856
|
+
value: truncated
|
|
857
|
+
};
|
|
858
|
+
} else if (isPlainObject(value)) {
|
|
859
|
+
const key = largestArrayValuedKey(value);
|
|
474
860
|
if (key !== null) {
|
|
475
|
-
const
|
|
476
|
-
const
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
const k = largestPrefixCount(pieces, Math.max(0, budget - MARKER_RESERVE - restLen));
|
|
482
|
-
const clone = {
|
|
483
|
-
...data,
|
|
484
|
-
[key]: arr.slice(0, k),
|
|
485
|
-
__truncated: {
|
|
486
|
-
field: key,
|
|
487
|
-
returned: k,
|
|
488
|
-
total: arr.length,
|
|
489
|
-
omitted: arr.length - k,
|
|
490
|
-
hint: hint ?? DEFAULT_OBJECT_HINT
|
|
491
|
-
}
|
|
861
|
+
const array = value[key];
|
|
862
|
+
const truncated = clipArray(array, largestPrefixCount(array.map((element) => JSON.stringify(element)), budget - MARKER_RESERVE), hint ?? DEFAULT_OBJECT_HINT, key);
|
|
863
|
+
const text = JSON.stringify(truncated);
|
|
864
|
+
if (text.length <= budget) return {
|
|
865
|
+
text,
|
|
866
|
+
value: truncated
|
|
492
867
|
};
|
|
493
|
-
const out = JSON.stringify(clone);
|
|
494
|
-
if (out.length <= budget) return out;
|
|
495
868
|
}
|
|
496
869
|
}
|
|
497
|
-
|
|
870
|
+
const truncated = previewEnvelope(typeof data === "string" ? data : compact, budget, hint);
|
|
871
|
+
return {
|
|
872
|
+
text: JSON.stringify(truncated),
|
|
873
|
+
value: truncated
|
|
874
|
+
};
|
|
498
875
|
}
|
|
499
876
|
/** A successful tool result carrying JSON (or text) for the model, size-bounded. */
|
|
500
877
|
function jsonResult(data, opts) {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
878
|
+
const { text, value } = boundedResult(data, resultBudget(), opts?.hint);
|
|
879
|
+
return {
|
|
880
|
+
content: [{
|
|
881
|
+
type: "text",
|
|
882
|
+
text
|
|
883
|
+
}],
|
|
884
|
+
structuredContent: value
|
|
885
|
+
};
|
|
505
886
|
}
|
|
506
887
|
/** A tool-level error: returned in-band (isError) so the model can self-correct. */
|
|
507
888
|
function errorResult(message) {
|
|
@@ -1830,12 +2211,14 @@ function registerProfileTools(server, ctx, whoami, userId) {
|
|
|
1830
2211
|
title: "Who am I (athlete)",
|
|
1831
2212
|
description: "The logged-in account's identity (id, name, roles) from /user/simple.",
|
|
1832
2213
|
inputSchema: {},
|
|
2214
|
+
outputSchema: toolOutputSchema(userSimpleSchema),
|
|
1833
2215
|
annotations: READ
|
|
1834
2216
|
}, () => attempt(async () => jsonResult(await whoami())));
|
|
1835
2217
|
server.registerTool("athlete_profile", {
|
|
1836
2218
|
title: "Athlete profile + lifetime totals",
|
|
1837
2219
|
description: "Lifetime training totals in one call — all-time session count (summary.sessions_count), total reps and volume, first/last logged date — plus the profile (name, units, dob). Use this for any 'how many sessions all-time / total volume ever' question rather than summing athlete_workouts windows. Set useMetric for kg/metric totals.",
|
|
1838
2220
|
inputSchema: { useMetric: z.boolean().optional() },
|
|
2221
|
+
outputSchema: toolOutputSchema(athleteProfileOutputSchema),
|
|
1839
2222
|
annotations: READ
|
|
1840
2223
|
}, ({ useMetric }) => attempt(async () => {
|
|
1841
2224
|
const id = await userId();
|
|
@@ -1849,12 +2232,14 @@ function registerProfileTools(server, ctx, whoami, userId) {
|
|
|
1849
2232
|
title: "Athlete preferences",
|
|
1850
2233
|
description: "Notification and display preference flags for the athlete account.",
|
|
1851
2234
|
inputSchema: {},
|
|
2235
|
+
outputSchema: toolOutputSchema(athletePrefsSchema),
|
|
1852
2236
|
annotations: READ
|
|
1853
2237
|
}, () => attempt(async () => jsonResult(await fetchAthletePrefs(ctx.client))));
|
|
1854
2238
|
server.registerTool("athlete_working_maxes", {
|
|
1855
2239
|
title: "Working maxes",
|
|
1856
2240
|
description: "The athlete's working max per exercise (drives % prescriptions). An entry can carry a null value: the exercise has a working-max slot but no number has been set yet, which means there is effectively no working max for it.",
|
|
1857
2241
|
inputSchema: {},
|
|
2242
|
+
outputSchema: toolOutputSchema(athleteWorkingMaxListSchema),
|
|
1858
2243
|
annotations: READ
|
|
1859
2244
|
}, () => attempt(async () => jsonResult(await fetchWorkingMaxes(ctx.client))));
|
|
1860
2245
|
server.registerTool("athlete_leaderboard", {
|
|
@@ -1866,6 +2251,7 @@ function registerProfileTools(server, ctx, whoami, userId) {
|
|
|
1866
2251
|
pageSize: z.number().int().positive().max(200).optional(),
|
|
1867
2252
|
gender: z.number().int().optional()
|
|
1868
2253
|
},
|
|
2254
|
+
outputSchema: opaqueOutputSchema,
|
|
1869
2255
|
annotations: READ
|
|
1870
2256
|
}, ({ workoutId, page, pageSize, gender }) => attempt(async () => {
|
|
1871
2257
|
const opts = {};
|
|
@@ -1922,6 +2308,7 @@ function registerExerciseTools(server, ctx, userId) {
|
|
|
1922
2308
|
limit: z.number().int().positive().max(200).optional(),
|
|
1923
2309
|
summary: z.boolean().optional()
|
|
1924
2310
|
},
|
|
2311
|
+
outputSchema: toolOutputSchema(athleteWorkoutsOutputSchema),
|
|
1925
2312
|
annotations: READ
|
|
1926
2313
|
}, (args) => runAthleteWorkouts(ctx, args));
|
|
1927
2314
|
server.registerTool("athlete_exercises", {
|
|
@@ -1931,6 +2318,7 @@ function registerExerciseTools(server, ctx, userId) {
|
|
|
1931
2318
|
q: z.string().optional(),
|
|
1932
2319
|
limit: z.number().int().positive().optional()
|
|
1933
2320
|
},
|
|
2321
|
+
outputSchema: toolOutputSchema(athleteExerciseCatalogOutputSchema),
|
|
1934
2322
|
annotations: READ
|
|
1935
2323
|
}, (args) => runAthleteExercises(ctx, args));
|
|
1936
2324
|
server.registerTool("athlete_exercise_history", {
|
|
@@ -1942,6 +2330,7 @@ function registerExerciseTools(server, ctx, userId) {
|
|
|
1942
2330
|
since: dateString.optional(),
|
|
1943
2331
|
until: dateString.optional()
|
|
1944
2332
|
},
|
|
2333
|
+
outputSchema: toolOutputSchema(exerciseHistoryOutputSchema),
|
|
1945
2334
|
annotations: READ
|
|
1946
2335
|
}, ({ exerciseId, raw, since, until }) => attempt(async () => {
|
|
1947
2336
|
const detail = await fetchExerciseHistoryDetail(ctx.client, toId(exerciseId), await userId());
|
|
@@ -1952,6 +2341,7 @@ function registerExerciseTools(server, ctx, userId) {
|
|
|
1952
2341
|
title: "Exercise personal records",
|
|
1953
2342
|
description: "The all-time PR board for an exercise (reps/weight per rep-max, strength-standard filters). Get the exercise id from athlete_exercises. A lift is often logged under several name variants, each with its own board, so if a PR looks missing check the other variants. For a point-in-time snapshot use athlete_exercise_stats; for the dated session trend use athlete_exercise_history.",
|
|
1954
2343
|
inputSchema: { exerciseId: idParam },
|
|
2344
|
+
outputSchema: toolOutputSchema(personalRecordListSchema),
|
|
1955
2345
|
annotations: READ
|
|
1956
2346
|
}, ({ exerciseId }) => attempt(async () => jsonResult(await fetchPersonalRecords(ctx.client, toId(exerciseId)))));
|
|
1957
2347
|
server.registerTool("athlete_exercise_stats", {
|
|
@@ -1961,6 +2351,7 @@ function registerExerciseTools(server, ctx, userId) {
|
|
|
1961
2351
|
exerciseId: idParam,
|
|
1962
2352
|
date: dateString
|
|
1963
2353
|
},
|
|
2354
|
+
outputSchema: toolOutputSchema(exerciseStatsSchema),
|
|
1964
2355
|
annotations: READ
|
|
1965
2356
|
}, ({ exerciseId, date }) => attempt(async () => jsonResult(await fetchExerciseStats(ctx.client, toId(exerciseId), await userId(), date))));
|
|
1966
2357
|
}
|
|
@@ -1977,6 +2368,7 @@ function registerLogTargetsTool(server, ctx) {
|
|
|
1977
2368
|
teamId: idParam.optional(),
|
|
1978
2369
|
raw: z.boolean().optional()
|
|
1979
2370
|
},
|
|
2371
|
+
outputSchema: toolOutputSchema(logTargetsOutputSchema),
|
|
1980
2372
|
annotations: READ
|
|
1981
2373
|
}, ({ startDate, endDate, program, programId, teamId, raw }) => attempt(async () => {
|
|
1982
2374
|
const workouts = selectWorkoutsByProgram(await fetchAthleteWorkouts(ctx.client, startDate, endDate), definedProps({
|
|
@@ -1994,11 +2386,8 @@ function registerSessionTools(server, ctx) {
|
|
|
1994
2386
|
title: "Create personal workout session",
|
|
1995
2387
|
description: "Create a new personal workout session for a given YYYY-MM-DD date on the athlete's personal calendar. Returns programWorkoutId, workoutId (pass to athlete_session_add_exercises), savedWorkoutId, groupId, and date.",
|
|
1996
2388
|
inputSchema: { date: dateString },
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
destructiveHint: false,
|
|
2000
|
-
openWorldHint: true
|
|
2001
|
-
}
|
|
2389
|
+
outputSchema: toolOutputSchema(personalWorkoutCreatedOutputSchema),
|
|
2390
|
+
annotations: ADDITIVE
|
|
2002
2391
|
}, ({ date }) => attempt(async () => jsonResult(await createPersonalWorkout(ctx.client, date))));
|
|
2003
2392
|
server.registerTool("athlete_session_add_exercises", {
|
|
2004
2393
|
title: "Add exercises to a personal workout session",
|
|
@@ -2010,11 +2399,8 @@ function registerSessionTools(server, ctx) {
|
|
|
2010
2399
|
order: z.number().int().positive()
|
|
2011
2400
|
})).min(1)
|
|
2012
2401
|
},
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
destructiveHint: false,
|
|
2016
|
-
openWorldHint: true
|
|
2017
|
-
}
|
|
2402
|
+
outputSchema: opaqueOutputSchema,
|
|
2403
|
+
annotations: ADDITIVE
|
|
2018
2404
|
}, ({ workoutId, exercises }) => attempt(async () => {
|
|
2019
2405
|
const mapped = exercises.map((e) => ({
|
|
2020
2406
|
exerciseId: toId(e.exerciseId),
|
|
@@ -2029,6 +2415,7 @@ function registerSessionTools(server, ctx) {
|
|
|
2029
2415
|
...athleteSessionRemoveArgsSchema.shape,
|
|
2030
2416
|
confirm: z.boolean().optional()
|
|
2031
2417
|
},
|
|
2418
|
+
outputSchema: toolOutputSchema(athleteSessionRemovedOutputSchema),
|
|
2032
2419
|
annotations: DESTRUCTIVE
|
|
2033
2420
|
}, ({ programWorkoutId, date, confirm }, extra) => attempt(async () => {
|
|
2034
2421
|
const id = toId(programWorkoutId);
|
|
@@ -2071,6 +2458,7 @@ function registerLogTool(server, ctx) {
|
|
|
2071
2458
|
...logSessionArgsSchema.shape,
|
|
2072
2459
|
confirm: z.boolean().optional()
|
|
2073
2460
|
},
|
|
2461
|
+
outputSchema: toolOutputSchema(sessionLogOutputSchema),
|
|
2074
2462
|
annotations: DESTRUCTIVE
|
|
2075
2463
|
}, ({ date, exercises, confirm }, extra) => attempt(async () => {
|
|
2076
2464
|
const blocked = confirmGate(extra, `Log a session of ${exercises.length} exercise(s) on ${date}? This writes to your coach-visible training log.`, confirm);
|
|
@@ -2089,6 +2477,7 @@ function registerLogTool(server, ctx) {
|
|
|
2089
2477
|
...logSetArgsSchema.shape,
|
|
2090
2478
|
confirm: z.boolean().optional()
|
|
2091
2479
|
},
|
|
2480
|
+
outputSchema: toolOutputSchema(setLogOutputSchema),
|
|
2092
2481
|
annotations: DESTRUCTIVE
|
|
2093
2482
|
}, ({ date, savedWorkoutSetId, results, confirm }, extra) => attempt(async () => {
|
|
2094
2483
|
const blocked = confirmGate(extra, `Log results to saved workout set ${toId(savedWorkoutSetId)} on ${date}? This writes to your coach-visible training log.`, confirm);
|
|
@@ -2106,6 +2495,7 @@ function registerLogTool(server, ctx) {
|
|
|
2106
2495
|
...athletePrescribeSetArgsSchema.shape,
|
|
2107
2496
|
confirm: z.boolean().optional()
|
|
2108
2497
|
},
|
|
2498
|
+
outputSchema: toolOutputSchema(setPrescriptionOutputSchema),
|
|
2109
2499
|
annotations: DESTRUCTIVE
|
|
2110
2500
|
}, ({ date, savedWorkoutSetId, results, confirm }, extra) => attempt(async () => {
|
|
2111
2501
|
const id = toId(savedWorkoutSetId);
|
|
@@ -2132,6 +2522,7 @@ function registerSwapTool(server, ctx) {
|
|
|
2132
2522
|
...swapAthleteExerciseArgsSchema.shape,
|
|
2133
2523
|
confirm: z.boolean().optional()
|
|
2134
2524
|
},
|
|
2525
|
+
outputSchema: toolOutputSchema(exerciseSwapOutputSchema),
|
|
2135
2526
|
annotations: DESTRUCTIVE
|
|
2136
2527
|
}, ({ savedWorkoutSetExerciseId, exerciseId, confirm }, extra) => attempt(async () => {
|
|
2137
2528
|
const sweId = toId(savedWorkoutSetExerciseId);
|
|
@@ -2149,18 +2540,21 @@ function registerCatalogReads(server, ctx) {
|
|
|
2149
2540
|
title: "Circuit history",
|
|
2150
2541
|
description: "Named circuit history for the logged-in athlete (GET /v5/users/circuits/{recent|history}). kind defaults to recent. Empty when the athlete has no saved circuits. Distinct from circuit *blocks* in workout_build (type 1).",
|
|
2151
2542
|
inputSchema: { kind: z.enum(["recent", "history"]).optional() },
|
|
2543
|
+
outputSchema: opaqueOutputSchema,
|
|
2152
2544
|
annotations: READ
|
|
2153
2545
|
}, ({ kind }) => attempt(async () => jsonResult(await fetchAthleteCircuits(ctx.client, kind ?? "recent"))));
|
|
2154
2546
|
server.registerTool("athlete_programming_programs", {
|
|
2155
2547
|
title: "Subscribed programs",
|
|
2156
2548
|
description: "Programs the athlete is subscribed to (GET /1.0/athlete/programming/programs). Not the coach list_programs surface. Empty when the athlete has no subscriptions.",
|
|
2157
2549
|
inputSchema: {},
|
|
2550
|
+
outputSchema: opaqueOutputSchema,
|
|
2158
2551
|
annotations: READ
|
|
2159
2552
|
}, () => attempt(async () => jsonResult(await fetchAthleteProgrammingPrograms(ctx.client))));
|
|
2160
2553
|
server.registerTool("athlete_recent_exercises", {
|
|
2161
2554
|
title: "Recent exercises",
|
|
2162
2555
|
description: "Recently used exercises (GET /v5/users/exercises/recent). Distinct from athlete_exercises (full logged catalog) and athlete_exercise_history (one lift).",
|
|
2163
2556
|
inputSchema: {},
|
|
2557
|
+
outputSchema: opaqueOutputSchema,
|
|
2164
2558
|
annotations: READ
|
|
2165
2559
|
}, () => attempt(async () => jsonResult(await fetchRecentExercises(ctx.client))));
|
|
2166
2560
|
}
|
|
@@ -2194,7 +2588,7 @@ function registerAthleteTrainingTools(server, ctx) {
|
|
|
2194
2588
|
}
|
|
2195
2589
|
//#endregion
|
|
2196
2590
|
//#region package.json
|
|
2197
|
-
var version = "3.
|
|
2591
|
+
var version = "3.3.0";
|
|
2198
2592
|
//#endregion
|
|
2199
2593
|
//#region src/server.ts
|
|
2200
2594
|
function main() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trainheroic-unofficial/athlete-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -21,13 +21,13 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@modelcontextprotocol/server": "2.0.0",
|
|
23
23
|
"zod": "^4.4.3",
|
|
24
|
-
"@trainheroic-unofficial/core": "3.
|
|
25
|
-
"@trainheroic-unofficial/js": "3.
|
|
24
|
+
"@trainheroic-unofficial/core": "3.3.0",
|
|
25
|
+
"@trainheroic-unofficial/js": "3.3.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^26.2.0",
|
|
29
29
|
"tsdown": "^0.22.14",
|
|
30
|
-
"tsx": "^4.23.
|
|
30
|
+
"tsx": "^4.23.12",
|
|
31
31
|
"typescript": "^7.0.2",
|
|
32
32
|
"vitest": "^4.1.10"
|
|
33
33
|
},
|