@trainheroic-unofficial/athlete-mcp 0.4.0 → 0.4.1

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.
Files changed (2) hide show
  1. package/dist/server.mjs +75 -0
  2. package/package.json +3 -3
package/dist/server.mjs CHANGED
@@ -848,6 +848,45 @@ async function logAthleteSet(client, args) {
848
848
  exercisesLogged
849
849
  };
850
850
  }
851
+ /**
852
+ * POST /v5/programWorkouts/personal — create a personal workout session for a given date.
853
+ * Returns the key ids: workoutId (needed for addExercisesToWorkout), programWorkoutId,
854
+ * savedWorkoutId, and groupId.
855
+ */
856
+ async function createPersonalWorkout(client, date) {
857
+ const res = await client.request("POST", "/v5/programWorkouts/personal", { body: { date } });
858
+ if (!res.ok) throw new Error(`Create personal workout failed (HTTP ${res.status}).`);
859
+ if (!isRecord(res.data)) throw new Error("Unexpected response from /v5/programWorkouts/personal.");
860
+ const pw = isRecord(res.data.programWorkout) ? res.data.programWorkout : null;
861
+ const sw = isRecord(res.data.savedWorkout) ? res.data.savedWorkout : null;
862
+ if (!pw || !sw) throw new Error("Missing programWorkout or savedWorkout in response.");
863
+ const programWorkoutId = coerceInt(pw.id);
864
+ const workoutId = coerceInt(pw.workoutId);
865
+ const savedWorkoutId = coerceInt(sw.id);
866
+ const groupId = coerceInt(sw.group_id);
867
+ if (!programWorkoutId || !workoutId || !savedWorkoutId || !groupId) throw new Error("Could not parse required ids from personal workout response.");
868
+ return {
869
+ programWorkoutId,
870
+ workoutId,
871
+ savedWorkoutId,
872
+ groupId,
873
+ date: typeof pw.date === "string" ? pw.date : date
874
+ };
875
+ }
876
+ /**
877
+ * PUT /v5/personalCalendar/workouts/{workoutId}/addExercises — add exercises to a personal
878
+ * workout. Returns saved workout set objects: each top-level `id` is a savedWorkoutSetId
879
+ * and `savedWorkoutSetExercises[].id` is a savedWorkoutSetExerciseId, both needed by
880
+ * logAthleteSet.
881
+ */
882
+ async function addExercisesToWorkout(client, workoutId, exercises) {
883
+ const res = await client.request("PUT", `/v5/personalCalendar/workouts/${workoutId}/addExercises`, { body: {
884
+ exercises,
885
+ circuits: []
886
+ } });
887
+ if (!res.ok) throw new Error(`Add exercises to workout failed (HTTP ${res.status}).`);
888
+ return res.data;
889
+ }
851
890
  /** Flatten `/v5/exercises/{id}/history` into PRs + a session time-series. */
852
891
  function presentExerciseHistory(detail) {
853
892
  return {
@@ -980,6 +1019,41 @@ function registerExerciseTools(server, ctx, userId) {
980
1019
  annotations: READ
981
1020
  }, ({ exerciseId, date }) => attempt(async () => jsonResult(await fetchExerciseStats(ctx.client, toId(exerciseId), await userId(), date))));
982
1021
  }
1022
+ /** Create a personal workout session and add exercises to it. */
1023
+ function registerSessionTools(server, ctx) {
1024
+ server.registerTool("athlete_session_create", {
1025
+ title: "Create personal workout session",
1026
+ 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.",
1027
+ inputSchema: { date: dateString },
1028
+ annotations: {
1029
+ readOnlyHint: false,
1030
+ destructiveHint: false,
1031
+ openWorldHint: true
1032
+ }
1033
+ }, ({ date }) => attempt(async () => jsonResult(await createPersonalWorkout(ctx.client, date))));
1034
+ server.registerTool("athlete_session_add_exercises", {
1035
+ title: "Add exercises to a personal workout session",
1036
+ description: "Add one or more exercises to a personal workout session. Get workoutId from athlete_session_create. Each item needs an exerciseId (from athlete_exercises) and a 1-based order. Returns saved workout set objects: each top-level id is a savedWorkoutSetId and savedWorkoutSetExercises[].id is a savedWorkoutSetExerciseId — both needed for athlete_log_set.",
1037
+ inputSchema: {
1038
+ workoutId: idParam,
1039
+ exercises: z.array(z.object({
1040
+ exerciseId: idParam,
1041
+ order: z.number().int().positive()
1042
+ })).min(1)
1043
+ },
1044
+ annotations: {
1045
+ readOnlyHint: false,
1046
+ destructiveHint: false,
1047
+ openWorldHint: true
1048
+ }
1049
+ }, ({ workoutId, exercises }) => attempt(async () => {
1050
+ const mapped = exercises.map((e) => ({
1051
+ exerciseId: toId(e.exerciseId),
1052
+ order: e.order
1053
+ }));
1054
+ return jsonResult(await addExercisesToWorkout(ctx.client, toId(workoutId), mapped), { hint: "Each top-level id is a savedWorkoutSetId; savedWorkoutSetExercises[].id is the savedWorkoutSetExerciseId for athlete_log_set." });
1055
+ }));
1056
+ }
983
1057
  /** The gated set-logging write. */
984
1058
  function registerLogTool(server, ctx) {
985
1059
  server.registerTool("athlete_log_set", {
@@ -1030,6 +1104,7 @@ function registerAthleteTrainingTools(server, ctx) {
1030
1104
  };
1031
1105
  registerProfileTools(server, ctx, whoami, userId);
1032
1106
  registerExerciseTools(server, ctx, userId);
1107
+ registerSessionTools(server, ctx);
1033
1108
  registerLogTool(server, ctx);
1034
1109
  }
1035
1110
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trainheroic-unofficial/athlete-mcp",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
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.4.0",
25
- "@trainheroic-unofficial/js": "0.4.0"
24
+ "@trainheroic-unofficial/js": "0.4.1",
25
+ "@trainheroic-unofficial/core": "0.4.1"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^26.0.0",