@trainheroic-unofficial/athlete-mcp 0.6.4 → 1.0.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 +55 -13
- package/package.json +3 -3
package/dist/server.mjs
CHANGED
|
@@ -167,6 +167,7 @@ const logSetArgsSchema = z.object({
|
|
|
167
167
|
})).min(1)
|
|
168
168
|
})).min(1)
|
|
169
169
|
});
|
|
170
|
+
logSetArgsSchema.extend({ athleteId: idArgSchema });
|
|
170
171
|
z.looseObject({
|
|
171
172
|
title: z.string().min(1),
|
|
172
173
|
param_1_type: z.number().optional(),
|
|
@@ -896,6 +897,7 @@ function buildExerciseLogPayload(savedWorkoutSetExerciseId, savedWorkoutSetId, w
|
|
|
896
897
|
* record so callers can build the set-completion PUT body via `buildSetCompletePayload`.
|
|
897
898
|
*/
|
|
898
899
|
function findSavedWorkoutSet(workouts, savedWorkoutSetId) {
|
|
900
|
+
const available = [];
|
|
899
901
|
for (const pw of workouts) {
|
|
900
902
|
const rec = pw;
|
|
901
903
|
const ssw = isRecord(rec.summarizedSavedWorkout) ? rec.summarizedSavedWorkout : {};
|
|
@@ -904,18 +906,29 @@ function findSavedWorkoutSet(workouts, savedWorkoutSetId) {
|
|
|
904
906
|
const sets = Array.isArray(sw.workoutSets) ? sw.workoutSets : [];
|
|
905
907
|
for (const s of sets) {
|
|
906
908
|
if (!isRecord(s)) continue;
|
|
909
|
+
const exercises = Array.isArray(s.workoutSetExercises) ? s.workoutSetExercises.filter(isRecord) : [];
|
|
907
910
|
if (coerceInt(s.id) === savedWorkoutSetId) {
|
|
908
911
|
const savedWorkoutId = coerceInt(sw.id);
|
|
909
912
|
if (!savedWorkoutId) continue;
|
|
910
913
|
return {
|
|
911
914
|
savedWorkoutId,
|
|
912
|
-
exercises
|
|
915
|
+
exercises,
|
|
913
916
|
rawSet: s
|
|
914
917
|
};
|
|
915
918
|
}
|
|
919
|
+
const setId = coerceInt(s.id);
|
|
920
|
+
if (setId !== null) {
|
|
921
|
+
const exLabels = exercises.map((ex) => {
|
|
922
|
+
const exId = coerceInt(ex.id);
|
|
923
|
+
const title = typeof ex.exercise_title === "string" && ex.exercise_title || typeof ex.title === "string" && ex.title || "exercise";
|
|
924
|
+
return exId === null ? null : `${exId} (${title})`;
|
|
925
|
+
}).filter((x) => x !== null);
|
|
926
|
+
available.push(`set ${setId} → exercise ids: ${exLabels.join(", ") || "none"}`);
|
|
927
|
+
}
|
|
916
928
|
}
|
|
917
929
|
}
|
|
918
|
-
|
|
930
|
+
const hint = available.length > 0 ? ` Saved workout sets present on this date: ${available.join("; ")}. Pass a "set N" id to --set (the savedWorkoutSetId), and the matching "exercise id" as savedWorkoutSetExerciseId in the results.` : " No saved workout is scheduled on this date — log against a date that has a workout (find it via athlete-workouts / coach athlete-workouts).";
|
|
931
|
+
throw new Error(`Saved workout set ${savedWorkoutSetId} not found on this date.${hint}`);
|
|
919
932
|
}
|
|
920
933
|
/**
|
|
921
934
|
* Build the body for `PUT /1.0/athlete/savedworkoutset/{id}` that marks the set completed.
|
|
@@ -972,23 +985,52 @@ function buildSetCompletePayload(rawSet, exerciseIds, complete) {
|
|
|
972
985
|
* by the respective PUT bodies.
|
|
973
986
|
*/
|
|
974
987
|
async function logAthleteSet(client, args) {
|
|
975
|
-
|
|
988
|
+
return writeSetResults(client, { role: "athlete" }, await fetchAthleteWorkouts(client, args.date, args.date), args.savedWorkoutSetId, args.results);
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Shared two-step set-log write behind {@link logAthleteSet} and {@link logForAthlete}.
|
|
992
|
+
* `target` selects the surface: `athlete` writes `/1.0/athlete/...`; `coach` writes
|
|
993
|
+
* `/1.0/coach/...{athleteId}` and stamps `athleteId` into each body. Step 1 PUTs each
|
|
994
|
+
* exercise's entered data to its own endpoint (the only path that actually stores reps and
|
|
995
|
+
* weight). Step 2 marks the set completed; that body needs the app's camelCase in-memory
|
|
996
|
+
* shape and the full list of savedWorkoutSetExercise IDs in the set (not only the logged ones).
|
|
997
|
+
*/
|
|
998
|
+
async function writeSetResults(client, target, workouts, savedWorkoutSetId, results) {
|
|
999
|
+
const { exercises, rawSet } = findSavedWorkoutSet(workouts, savedWorkoutSetId);
|
|
1000
|
+
const suffix = target.role === "coach" ? `/${target.athleteId}` : "";
|
|
1001
|
+
const extra = target.role === "coach" ? { athleteId: target.athleteId } : {};
|
|
976
1002
|
let exercisesLogged = 0;
|
|
977
|
-
for (const result of
|
|
1003
|
+
for (const result of results) {
|
|
978
1004
|
const ex = exercises.find((e) => coerceInt(e.id) === result.savedWorkoutSetExerciseId);
|
|
979
|
-
if (!ex)
|
|
1005
|
+
if (!ex) {
|
|
1006
|
+
const valid = exercises.map((e) => {
|
|
1007
|
+
const id = coerceInt(e.id);
|
|
1008
|
+
const title = typeof e.exercise_title === "string" && e.exercise_title || typeof e.title === "string" && e.title || "exercise";
|
|
1009
|
+
return id === null ? null : `${id} (${title})`;
|
|
1010
|
+
}).filter((x) => x !== null);
|
|
1011
|
+
throw new Error(`savedWorkoutSetExerciseId ${result.savedWorkoutSetExerciseId} not found in saved workout set ${savedWorkoutSetId}. Exercises in this set: ${valid.join(", ") || "none"}.`);
|
|
1012
|
+
}
|
|
980
1013
|
const workoutSetExerciseId = coerceInt(ex.workout_set_exercise_id);
|
|
981
1014
|
if (!workoutSetExerciseId) throw new Error(`Could not resolve workout_set_exercise_id for exercise ${result.savedWorkoutSetExerciseId}.`);
|
|
982
|
-
const body =
|
|
983
|
-
|
|
984
|
-
|
|
1015
|
+
const body = {
|
|
1016
|
+
...buildExerciseLogPayload(result.savedWorkoutSetExerciseId, savedWorkoutSetId, workoutSetExerciseId, result.sets),
|
|
1017
|
+
...extra
|
|
1018
|
+
};
|
|
1019
|
+
const res = await client.request("PUT", `/1.0/${target.role}/savedworkoutsetexercise/${result.savedWorkoutSetExerciseId}${suffix}`, { body });
|
|
1020
|
+
if (!res.ok) {
|
|
1021
|
+
const readOnly = target.role === "coach" && (res.status === 401 || res.status === 403) ? ` Athlete ${target.athleteId} appears to be read-only for results — TrainHeroic's seeded demo/sample athletes return ${res.status} here; results only persist for real (invited) athletes.` : "";
|
|
1022
|
+
throw new Error(`Failed to log exercise ${result.savedWorkoutSetExerciseId} (HTTP ${res.status}).${readOnly}`);
|
|
1023
|
+
}
|
|
985
1024
|
exercisesLogged += 1;
|
|
986
1025
|
}
|
|
987
|
-
const setBody =
|
|
988
|
-
|
|
989
|
-
|
|
1026
|
+
const setBody = {
|
|
1027
|
+
...buildSetCompletePayload(rawSet, exercises.map((e) => coerceInt(e.id)).filter((n) => n !== null), true),
|
|
1028
|
+
...extra
|
|
1029
|
+
};
|
|
1030
|
+
const setRes = await client.request("PUT", `/1.0/${target.role}/savedworkoutset/${savedWorkoutSetId}${suffix}`, { body: setBody });
|
|
1031
|
+
if (!setRes.ok) throw new Error(`Failed to mark workout set ${savedWorkoutSetId} completed (HTTP ${setRes.status}).`);
|
|
990
1032
|
return {
|
|
991
|
-
savedWorkoutSetId
|
|
1033
|
+
savedWorkoutSetId,
|
|
992
1034
|
exercisesLogged
|
|
993
1035
|
};
|
|
994
1036
|
}
|
|
@@ -1293,7 +1335,7 @@ function registerAthleteTrainingTools(server, ctx) {
|
|
|
1293
1335
|
}
|
|
1294
1336
|
//#endregion
|
|
1295
1337
|
//#region package.json
|
|
1296
|
-
var version = "0.
|
|
1338
|
+
var version = "1.0.0";
|
|
1297
1339
|
//#endregion
|
|
1298
1340
|
//#region src/server.ts
|
|
1299
1341
|
async function main() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trainheroic-unofficial/athlete-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
23
23
|
"zod": "^4.4.3",
|
|
24
|
-
"@trainheroic-unofficial/core": "0.
|
|
25
|
-
"@trainheroic-unofficial/js": "0.
|
|
24
|
+
"@trainheroic-unofficial/core": "1.0.0",
|
|
25
|
+
"@trainheroic-unofficial/js": "1.0.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^26.0.0",
|