@trainheroic-unofficial/athlete-mcp 3.0.1 → 3.1.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.
- package/README.md +6 -3
- package/dist/server.mjs +58 -7
- package/package.json +3 -3
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,
|
|
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
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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
|
|
@@ -281,6 +287,17 @@ const programWorkoutResponseSchema = z.looseObject({
|
|
|
281
287
|
sets: z.record(z.string(), z.unknown()).optional()
|
|
282
288
|
});
|
|
283
289
|
z.looseObject({ programWorkouts: z.array(programWorkoutResponseSchema).optional() });
|
|
290
|
+
z.looseObject({
|
|
291
|
+
id: intLike,
|
|
292
|
+
title: z.string(),
|
|
293
|
+
programId: intLike,
|
|
294
|
+
group_program: intLike,
|
|
295
|
+
type: intLike,
|
|
296
|
+
program: z.looseObject({
|
|
297
|
+
id: intLike,
|
|
298
|
+
program_type: intLike
|
|
299
|
+
}).optional()
|
|
300
|
+
});
|
|
284
301
|
//#endregion
|
|
285
302
|
//#region ../dto/src/workout.ts
|
|
286
303
|
/** A single exercise prescription inside a block. */
|
|
@@ -693,7 +710,7 @@ var TrainHeroicClient = class {
|
|
|
693
710
|
session = await this.#ensureSession();
|
|
694
711
|
res = await this.#send(method, url, session, options.body);
|
|
695
712
|
}
|
|
696
|
-
if (!res.ok) notifyHttpError(this.#onHttpError, method, url, res.status);
|
|
713
|
+
if (!res.ok && !options.expectedStatuses?.includes(res.status)) notifyHttpError(this.#onHttpError, method, url, res.status);
|
|
697
714
|
const text = await res.text();
|
|
698
715
|
let data = text;
|
|
699
716
|
if (text.length > 0) try {
|
|
@@ -1427,6 +1444,21 @@ async function logAthleteSet(client, args) {
|
|
|
1427
1444
|
};
|
|
1428
1445
|
}
|
|
1429
1446
|
/**
|
|
1447
|
+
* Set the logged-in athlete's prescribed reps/weight for one saved workout set WITHOUT marking it
|
|
1448
|
+
* performed. This is the athlete-side equivalent of editing target values in the app: it writes
|
|
1449
|
+
* `param_N_data_M` with every made/completed flag cleared and skips the set-completion PUT.
|
|
1450
|
+
*
|
|
1451
|
+
* The write replaces the selected exercise's whole prescription, so callers must pass every set
|
|
1452
|
+
* they want to keep. An omitted param is cleared rather than preserved.
|
|
1453
|
+
*/
|
|
1454
|
+
async function prescribeAthleteSet(client, args) {
|
|
1455
|
+
const r = await writeSetResults(client, { role: "athlete" }, await fetchAthleteWorkouts(client, args.date, args.date), args.savedWorkoutSetId, args.results, "prescribe");
|
|
1456
|
+
return {
|
|
1457
|
+
savedWorkoutSetId: r.savedWorkoutSetId,
|
|
1458
|
+
exercisesPrescribed: r.exercisesWritten
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
/**
|
|
1430
1462
|
* Swap the exercise prescribed in one of an athlete's saved-workout slots for a different
|
|
1431
1463
|
* exercise — the API equivalent of the app's per-athlete "swap exercise":
|
|
1432
1464
|
* `PUT /v5/savedWorkoutSetExercises/{savedWorkoutSetExerciseId}?exerciseId={exerciseId}` with an
|
|
@@ -1462,9 +1494,10 @@ async function swapAthleteExercise(client, args) {
|
|
|
1462
1494
|
};
|
|
1463
1495
|
}
|
|
1464
1496
|
/**
|
|
1465
|
-
* Shared set-write behind {@link logAthleteSet}, {@link
|
|
1466
|
-
* {@link prescribeForAthlete}. `target` selects the surface: `athlete`
|
|
1467
|
-
* `coach` writes `/1.0/coach/...{athleteId}` and stamps `athleteId` into
|
|
1497
|
+
* Shared set-write behind {@link logAthleteSet}, {@link prescribeAthleteSet},
|
|
1498
|
+
* {@link logForAthlete}, and {@link prescribeForAthlete}. `target` selects the surface: `athlete`
|
|
1499
|
+
* writes `/1.0/athlete/...`; `coach` writes `/1.0/coach/...{athleteId}` and stamps `athleteId` into
|
|
1500
|
+
* each body.
|
|
1468
1501
|
*
|
|
1469
1502
|
* Step 1 PUTs each exercise's per-set values to its own endpoint (the only path that actually
|
|
1470
1503
|
* stores reps and weight). `mode` decides what those values mean: `"log"` records them as a
|
|
@@ -2047,6 +2080,24 @@ function registerLogTool(server, ctx) {
|
|
|
2047
2080
|
results: toSetResults(results)
|
|
2048
2081
|
}));
|
|
2049
2082
|
}));
|
|
2083
|
+
server.registerTool("athlete_prescribe_set", {
|
|
2084
|
+
title: "Set your prescribed reps/weight without logging",
|
|
2085
|
+
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).",
|
|
2086
|
+
inputSchema: {
|
|
2087
|
+
...athletePrescribeSetArgsSchema.shape,
|
|
2088
|
+
confirm: z.boolean().optional()
|
|
2089
|
+
},
|
|
2090
|
+
annotations: DESTRUCTIVE
|
|
2091
|
+
}, ({ date, savedWorkoutSetId, results, confirm }, extra) => attempt(async () => {
|
|
2092
|
+
const id = toId(savedWorkoutSetId);
|
|
2093
|
+
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);
|
|
2094
|
+
if (blocked) return blocked;
|
|
2095
|
+
return jsonResult(await prescribeAthleteSet(ctx.client, {
|
|
2096
|
+
date,
|
|
2097
|
+
savedWorkoutSetId: id,
|
|
2098
|
+
results: toSetResults(results)
|
|
2099
|
+
}));
|
|
2100
|
+
}));
|
|
2050
2101
|
}
|
|
2051
2102
|
/**
|
|
2052
2103
|
* The athlete's own per-exercise swap, in its own function so registerLogTool/registerSessionTools
|
|
@@ -2103,7 +2154,7 @@ function registerAthleteTrainingTools(server, ctx) {
|
|
|
2103
2154
|
}
|
|
2104
2155
|
//#endregion
|
|
2105
2156
|
//#region package.json
|
|
2106
|
-
var version = "3.
|
|
2157
|
+
var version = "3.1.1";
|
|
2107
2158
|
//#endregion
|
|
2108
2159
|
//#region src/server.ts
|
|
2109
2160
|
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.1.1",
|
|
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.
|
|
25
|
-
"@trainheroic-unofficial/js": "3.
|
|
24
|
+
"@trainheroic-unofficial/core": "3.1.1",
|
|
25
|
+
"@trainheroic-unofficial/js": "3.1.1"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^26.2.0",
|