@trainheroic-unofficial/athlete-mcp 3.0.1 → 3.1.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 CHANGED
@@ -4,7 +4,7 @@ Local single-user [MCP](https://modelcontextprotocol.io) server for a TrainHeroi
4
4
 
5
5
  It reads two required environment variables, `TRAINHEROIC_EMAIL` and `TRAINHEROIC_PASSWORD` (your existing TrainHeroic login). These are real credentials in plaintext; the config below puts them in a file or shell history, so treat them as secrets.
6
6
 
7
- It exposes the logged-in athlete's own training: scheduled and completed workouts, per-exercise history, PRs (personal records), working maxes, and lifetime totals, plus one confirmation-gated write that logs a completed set. Coaching capabilities (rosters, teams, programs, messaging) are available through [`@trainheroic-unofficial/coach-mcp`](../coach-mcp). A hosted version that holds credentials server-side behind OAuth and gives a coach login both the athlete and coaching tools is described in the [root README](../../README.md).
7
+ It exposes the logged-in athlete's own training: scheduled and completed workouts, per-exercise history, PRs (personal records), working maxes, lifetime totals, and confirmation-gated training writes. Coaching capabilities (rosters, teams, programs, messaging) are available through [`@trainheroic-unofficial/coach-mcp`](../coach-mcp). A hosted version that holds credentials server-side behind OAuth and gives a coach login both the athlete and coaching tools is described in the [root README](../../README.md).
8
8
 
9
9
  ---
10
10
 
@@ -48,8 +48,8 @@ A coach account works here too: a TrainHeroic coach account also has its own ath
48
48
 
49
49
  ## Tools
50
50
 
51
- All read-only except `athlete_log_set`. The assistant fills in each tool's parameters (dates
52
- are `YYYY-MM-DD`); your MCP client shows the full schema for each.
51
+ The assistant fills in each tool's parameters (dates are `YYYY-MM-DD`); your MCP client shows the
52
+ full schema for each. Writes are confirmation-gated.
53
53
 
54
54
  - `athlete_whoami`: identity (id, name, roles)
55
55
  - `athlete_profile`: lifetime totals + profile
@@ -67,6 +67,9 @@ are `YYYY-MM-DD`); your MCP client shows the full schema for each.
67
67
  - `athlete_log_set`: logs completed set results to your training log. This is a real write
68
68
  that your coach can see. It confirms before running: the server asks the client to confirm,
69
69
  falling back to an explicit `confirm: true` argument when the client can't prompt.
70
+ - `athlete_prescribe_set`: sets your planned reps and/or weight without recording the set as
71
+ completed. It replaces the selected exercise's whole prescription, so every planned set and
72
+ value to keep must be included.
70
73
 
71
74
  ---
72
75
 
package/dist/server.mjs CHANGED
@@ -203,12 +203,18 @@ const logSetArgsSchema = z.object({
203
203
  results: loggedExerciseResultsSchema
204
204
  });
205
205
  logSetArgsSchema.extend({ athleteId: idArgSchema });
206
- z.object({
206
+ /**
207
+ * Args for changing the logged-in athlete's prescribed targets without marking the set performed.
208
+ * The write replaces the exercise's whole prescription, so its sets are positional and sequential
209
+ * by definition: it deliberately omits the log path's `slot` field because a sparse prescription
210
+ * has no meaning here.
211
+ */
212
+ const athletePrescribeSetArgsSchema = z.object({
207
213
  date: dateString,
208
214
  savedWorkoutSetId: idArgSchema,
209
- athleteId: idArgSchema,
210
215
  results: prescribedExerciseResultsSchema
211
216
  });
217
+ athletePrescribeSetArgsSchema.extend({ athleteId: idArgSchema });
212
218
  /**
213
219
  * Args for the coach per-athlete exercise swap: replace the exercise prescribed in one of a
214
220
  * roster athlete's saved-workout slots with a different exercise, the API equivalent of the
@@ -1427,6 +1433,21 @@ async function logAthleteSet(client, args) {
1427
1433
  };
1428
1434
  }
1429
1435
  /**
1436
+ * Set the logged-in athlete's prescribed reps/weight for one saved workout set WITHOUT marking it
1437
+ * performed. This is the athlete-side equivalent of editing target values in the app: it writes
1438
+ * `param_N_data_M` with every made/completed flag cleared and skips the set-completion PUT.
1439
+ *
1440
+ * The write replaces the selected exercise's whole prescription, so callers must pass every set
1441
+ * they want to keep. An omitted param is cleared rather than preserved.
1442
+ */
1443
+ async function prescribeAthleteSet(client, args) {
1444
+ const r = await writeSetResults(client, { role: "athlete" }, await fetchAthleteWorkouts(client, args.date, args.date), args.savedWorkoutSetId, args.results, "prescribe");
1445
+ return {
1446
+ savedWorkoutSetId: r.savedWorkoutSetId,
1447
+ exercisesPrescribed: r.exercisesWritten
1448
+ };
1449
+ }
1450
+ /**
1430
1451
  * Swap the exercise prescribed in one of an athlete's saved-workout slots for a different
1431
1452
  * exercise — the API equivalent of the app's per-athlete "swap exercise":
1432
1453
  * `PUT /v5/savedWorkoutSetExercises/{savedWorkoutSetExerciseId}?exerciseId={exerciseId}` with an
@@ -1462,9 +1483,10 @@ async function swapAthleteExercise(client, args) {
1462
1483
  };
1463
1484
  }
1464
1485
  /**
1465
- * Shared set-write behind {@link logAthleteSet}, {@link logForAthlete}, and
1466
- * {@link prescribeForAthlete}. `target` selects the surface: `athlete` writes `/1.0/athlete/...`;
1467
- * `coach` writes `/1.0/coach/...{athleteId}` and stamps `athleteId` into each body.
1486
+ * Shared set-write behind {@link logAthleteSet}, {@link prescribeAthleteSet},
1487
+ * {@link logForAthlete}, and {@link prescribeForAthlete}. `target` selects the surface: `athlete`
1488
+ * writes `/1.0/athlete/...`; `coach` writes `/1.0/coach/...{athleteId}` and stamps `athleteId` into
1489
+ * each body.
1468
1490
  *
1469
1491
  * Step 1 PUTs each exercise's per-set values to its own endpoint (the only path that actually
1470
1492
  * stores reps and weight). `mode` decides what those values mean: `"log"` records them as a
@@ -2047,6 +2069,24 @@ function registerLogTool(server, ctx) {
2047
2069
  results: toSetResults(results)
2048
2070
  }));
2049
2071
  }));
2072
+ server.registerTool("athlete_prescribe_set", {
2073
+ title: "Set your prescribed reps/weight without logging",
2074
+ description: "Athlete-facing write: set the planned reps and/or weight for one of your scheduled workout sets WITHOUT recording it as performed. Use this when you know the target load before training but have not completed the set yet. The set stays open and nothing is added to exercise history. param1 is reps and param2 is weight; pass one sets[] entry per planned set. This write REPLACES the selected exercise's whole prescription, so include every planned set and every value you want to keep; an omitted param is cleared. Get savedWorkoutSetId + savedWorkoutSetExerciseId from athlete_log_targets. To record actual completed results, use athlete_log_set instead. Requires confirmation (elicitation or confirm:true).",
2075
+ inputSchema: {
2076
+ ...athletePrescribeSetArgsSchema.shape,
2077
+ confirm: z.boolean().optional()
2078
+ },
2079
+ annotations: DESTRUCTIVE
2080
+ }, ({ date, savedWorkoutSetId, results, confirm }, extra) => attempt(async () => {
2081
+ const id = toId(savedWorkoutSetId);
2082
+ const blocked = confirmGate(extra, `Set prescribed values on your saved workout set ${id} on ${date}? This changes your planned reps/weight but does not log the set as completed.`, confirm);
2083
+ if (blocked) return blocked;
2084
+ return jsonResult(await prescribeAthleteSet(ctx.client, {
2085
+ date,
2086
+ savedWorkoutSetId: id,
2087
+ results: toSetResults(results)
2088
+ }));
2089
+ }));
2050
2090
  }
2051
2091
  /**
2052
2092
  * The athlete's own per-exercise swap, in its own function so registerLogTool/registerSessionTools
@@ -2103,7 +2143,7 @@ function registerAthleteTrainingTools(server, ctx) {
2103
2143
  }
2104
2144
  //#endregion
2105
2145
  //#region package.json
2106
- var version = "3.0.1";
2146
+ var version = "3.1.0";
2107
2147
  //#endregion
2108
2148
  //#region src/server.ts
2109
2149
  function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trainheroic-unofficial/athlete-mcp",
3
- "version": "3.0.1",
3
+ "version": "3.1.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,8 +21,8 @@
21
21
  "dependencies": {
22
22
  "@modelcontextprotocol/server": "2.0.0",
23
23
  "zod": "^4.4.3",
24
- "@trainheroic-unofficial/core": "3.0.1",
25
- "@trainheroic-unofficial/js": "3.0.1"
24
+ "@trainheroic-unofficial/core": "3.1.0",
25
+ "@trainheroic-unofficial/js": "3.1.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^26.2.0",