@trainheroic-unofficial/athlete-mcp 2.0.1 → 2.0.2

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 +14 -3
  2. package/package.json +3 -3
package/dist/server.mjs CHANGED
@@ -310,13 +310,24 @@ const leaderboardSpecSchema = z.union([
310
310
  instruction: z.string().optional()
311
311
  })
312
312
  ]);
313
- /** A block (group of exercises); two exercises render as a superset. */
313
+ /**
314
+ * A block (group of exercises); two exercises render as a superset.
315
+ * Empty `exercises` is allowed only with a non-empty `instruction` — that is TrainHeroic's
316
+ * free-text Circuit / Conditioning block (type 1): title + instruction blurb, no linked lifts.
317
+ */
314
318
  const blockSpecSchema = z.object({
315
319
  title: z.string(),
316
320
  type: z.number().optional(),
317
321
  instruction: z.string().optional(),
318
322
  leaderboard: leaderboardSpecSchema.optional(),
319
323
  exercises: z.array(exerciseSpecSchema)
324
+ }).superRefine((block, ctx) => {
325
+ if (block.exercises.length > 0) return;
326
+ if ((block.instruction?.trim() ?? "") === "") ctx.addIssue({
327
+ code: "custom",
328
+ message: "A block with no exercises needs a non-empty instruction (text-only Circuit / Conditioning block). Otherwise provide at least one exercise.",
329
+ path: ["exercises"]
330
+ });
320
331
  });
321
332
  z.object({
322
333
  blocks: z.array(blockSpecSchema),
@@ -1817,7 +1828,7 @@ function registerProfileTools(server, ctx, whoami, userId) {
1817
1828
  return jsonResult(await fetchLeaderboard(ctx.client, toId(workoutId), opts));
1818
1829
  }));
1819
1830
  }
1820
- const ATHLETE_WORKOUTS_DESC = "Workouts in an inclusive YYYY-MM-DD window, flattened to blocks/exercises. Each exercise carries both its `prescribed` sets (what the program called for) and `performed` sets (what the athlete actually logged); each workout has a top-level `logged` flag. Use `performed`/`logged` to tell what was recorded or done. That is the reliable signal: a session can hold logged sets while the API's own completion flags stay 0, and an empty `performed` means nothing was logged for that exercise. For 'did I record anything / what did I do', set loggedOnly:true to return only sessions with logged sets (it keeps whole sessions that have any logged set, so individual exercises inside can still show empty `performed`; it also shrinks a large result); limit returns the most recent N workouts (newest first). For a high-level overview ('what's on my schedule this week', 'what have I been training lately') set summary:true to get one compact row per session (date, program, title, logged flag, and exerciseCount/performedCount — read performedCount against exerciseCount, e.g. 1 of 12 logged) instead of every set — a multi-program week of full detail is large, so prefer summary first, then re-query a single day without it to drill into that day's sets. For 'what's my next workout', query a forward window from today; if it comes back empty, the most recent session is likely yesterday's still-unlogged one, so widen the window backward a day or two. Both filters apply to the presented view, not raw. raw:true returns the untouched API objects. This is a date-windowed fetch, NOT an aggregate: for lifetime totals (all-time session count, total volume, first/last logged date) call athlete_profile instead of summing windows — a multi-year range here can time out. Narrow the window if the result is truncated.";
1831
+ const ATHLETE_WORKOUTS_DESC = "Workouts on the AUTHENTICATED user's own athlete calendar in an inclusive YYYY-MM-DD window, flattened to blocks/exercises. When a coach account calls this, it returns the coach's own training schedule — not a roster athlete's. To inspect a roster athlete use athlete_saved_workouts (or athlete_training for a month overview); to verify a coach-published team session from the calendar side use workout_read. Each exercise carries both its `prescribed` sets (what the program called for) and `performed` sets (what the athlete actually logged); each workout has a top-level `logged` flag. Use `performed`/`logged` to tell what was recorded or done. That is the reliable signal: a session can hold logged sets while the API's own completion flags stay 0, and an empty `performed` means nothing was logged for that exercise. For 'did I record anything / what did I do', set loggedOnly:true to return only sessions with logged sets (it keeps whole sessions that have any logged set, so individual exercises inside can still show empty `performed`; it also shrinks a large result); limit returns the most recent N workouts (newest first). For a high-level overview ('what's on my schedule this week', 'what have I been training lately') set summary:true to get one compact row per session (date, program, title, logged flag, and exerciseCount/performedCount — read performedCount against exerciseCount, e.g. 1 of 12 logged) instead of every set — a multi-program week of full detail is large, so prefer summary first, then re-query a single day without it to drill into that day's sets. For 'what's my next workout', query a forward window from today; if it comes back empty, the most recent session is likely yesterday's still-unlogged one, so widen the window backward a day or two. Both filters apply to the presented view, not raw. raw:true returns the untouched API objects. This is a date-windowed fetch, NOT an aggregate: for lifetime totals (all-time session count, total volume, first/last logged date) call athlete_profile instead of summing windows — a multi-year range here can time out. Narrow the window if the result is truncated.";
1821
1832
  const ATHLETE_LOG_TARGETS_DESC = "The savedWorkoutSetId + savedWorkoutSetExerciseId that athlete_log_set needs, read straight off your scheduled/logged workouts in an inclusive YYYY-MM-DD window — no raw needed. This is the self-service path for logging into a COACH-SCHEDULED workout: read the ids here, then pass them to athlete_log_set. The default view is COMPACT: one row per saved set, each carrying its program/programId, the savedWorkoutSetId, and every exercise's savedWorkoutSetExerciseId with prescribed/performed values. When several workouts fall on the same day (you're on more than one program), narrow to one with program (a case-insensitive title substring, e.g. 'bodybuilding' — no id lookup needed), or programId/teamId if you have the id. raw:true returns the untouched API objects, but that blob is large and can be truncated when several workouts share a date — prefer the program filter + the default view. For reading what you actually did (not the log ids), use athlete_workouts.";
1822
1833
  const ATHLETE_EXERCISE_HISTORY_DESC = "Per-exercise PRs and the dated session time-series (sets performed, estimated 1RM). The returned `sessions` run all-time, newest first; pass since/until (YYYY-MM-DD, inclusive) to keep only sessions in that window — use it for 'last 3 months' / 'this year' questions so the result stays small. estimated1RM is a formula off the session's best set and reads high when a session mixes a heavy single with high-rep backdown work, so treat it as approximate, not a logged single. `liftPRs` are always all-time and ignore since/until, but each carries the date it was set, so filter those dates yourself to answer 'PRs set this year'. Set raw:true for the untouched API object. Get the exercise id from athlete_exercises.";
1823
1834
  const ATHLETE_EXERCISES_DESC = "The exercises the athlete has logged (id + title + positional units). Call with no arguments to get the athlete's complete catalog, which often runs to several hundred entries; leave limit off when you want the whole list, since a no-q call returns everything by default. Pass q to free-text search by name (returns the top ranked matches, capped by limit, default 20). Use a returned id with athlete_exercise_history, athlete_personal_records, or athlete_exercise_stats (all of which require an exercise id). If a no-q result comes back wrapped with a __truncated marker, you passed a limit smaller than the catalog and are seeing a partial list; re-call without limit for all of it. A common name returns several variants (e.g. plain 'Bench Press' id 1162 vs 'BARBELL BENCH PRESS'); each variant keeps its own separate history and PR board, so prefer the plain canonical entry, and if a PR or trend looks incomplete, the work may be split across variants, so check the others.";
@@ -2097,7 +2108,7 @@ function registerAthleteTrainingTools(server, ctx) {
2097
2108
  }
2098
2109
  //#endregion
2099
2110
  //#region package.json
2100
- var version = "2.0.1";
2111
+ var version = "2.0.2";
2101
2112
  //#endregion
2102
2113
  //#region src/server.ts
2103
2114
  function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trainheroic-unofficial/athlete-mcp",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
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": "2.0.1",
25
- "@trainheroic-unofficial/js": "2.0.1"
24
+ "@trainheroic-unofficial/core": "2.0.2",
25
+ "@trainheroic-unofficial/js": "2.0.2"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^26.1.2",