@trainheroic-unofficial/athlete-mcp 1.2.0 → 1.4.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.
Files changed (2) hide show
  1. package/dist/server.mjs +33 -7
  2. package/package.json +3 -3
package/dist/server.mjs CHANGED
@@ -453,7 +453,20 @@ async function confirmGate(server, requestId, message, confirmArg) {
453
453
  const SERVER_INSTRUCTIONS = "Speak to the user in plain, everyday language about their training. Describe what you are doing in the TrainHeroic app's own terms (for example, say you are creating a workout rather than naming a tool). Do not surface internal tool names (the snake_case identifiers such as athlete_session_create), raw parameter names, or numeric ids in your replies unless the user explicitly asks for them; they are implementation details. The tool descriptions cross-reference each other by name only so you can chain them correctly. Keep that wiring to yourself.";
454
454
  //#endregion
455
455
  //#region ../js/src/auth.ts
456
- const AUTH_URL = "https://apis.trainheroic.com/auth";
456
+ const DEFAULT_AUTH_URL = "https://apis.trainheroic.com/auth";
457
+ /**
458
+ * The login endpoint, allowing an env override so a test harness can authenticate against a local
459
+ * fake backend. Precedence: an explicit `TH_AUTH_URL`, else `${TH_APIS_BASE}/auth` (login lives on
460
+ * the apis host, so it follows that base), else the real endpoint. Read via `globalThis.process`
461
+ * (no `import process`) to keep this module workerd-safe, and per call so a child-env override wins.
462
+ */
463
+ function authUrl() {
464
+ const env = globalThis.process?.env;
465
+ if (env?.TH_AUTH_URL && env.TH_AUTH_URL.length > 0) return env.TH_AUTH_URL;
466
+ const apis = env?.TH_APIS_BASE;
467
+ if (apis && apis.length > 0) return `${apis.replace(/\/$/, "")}/auth`;
468
+ return DEFAULT_AUTH_URL;
469
+ }
457
470
  /**
458
471
  * Authenticate against TrainHeroic. Returns the session bundle, or null on bad
459
472
  * credentials. TrainHeroic returns only { id, scope, role, session_id } (verified in
@@ -461,7 +474,7 @@ const AUTH_URL = "https://apis.trainheroic.com/auth";
461
474
  * is sent as the `session-token` header and works against both API hosts.
462
475
  */
463
476
  async function loginTrainHeroic(email, password) {
464
- const res = await fetch(AUTH_URL, {
477
+ const res = await fetch(authUrl(), {
465
478
  method: "POST",
466
479
  headers: {
467
480
  "content-type": "application/x-www-form-urlencoded",
@@ -484,8 +497,19 @@ async function loginTrainHeroic(email, password) {
484
497
  }
485
498
  //#endregion
486
499
  //#region ../js/src/client.ts
487
- const COACH_BASE = "https://api.trainheroic.com";
488
- const APIS_BASE = "https://apis.trainheroic.com";
500
+ const DEFAULT_COACH_BASE = "https://api.trainheroic.com";
501
+ const DEFAULT_APIS_BASE = "https://apis.trainheroic.com";
502
+ /**
503
+ * Resolve an API host, allowing an env override. The override exists so a test harness can point
504
+ * the client at a local fake backend (and it doubles as a staging knob); production leaves these
505
+ * unset and gets the real hosts. Read through `globalThis.process?.env` — not an `import process`
506
+ * — so the runtime-agnostic `.` entry stays free of `node:*` and runs unchanged on workerd, and
507
+ * read per request (not at module load) so a value the harness sets in the child env always wins.
508
+ */
509
+ function envBase(key, fallback) {
510
+ const v = (globalThis.process?.env)?.[key];
511
+ return v && v.length > 0 ? v : fallback;
512
+ }
489
513
  var TrainHeroicAuthError = class extends Error {
490
514
  name = "TrainHeroicAuthError";
491
515
  };
@@ -524,7 +548,7 @@ var TrainHeroicClient = class {
524
548
  return this.#sessionId;
525
549
  }
526
550
  async request(method, path, options = {}) {
527
- const url = `${options.base === "apis" ? APIS_BASE : COACH_BASE}/${path.replace(/^\//, "")}`;
551
+ const url = `${options.base === "apis" ? envBase("TH_APIS_BASE", DEFAULT_APIS_BASE) : envBase("TH_COACH_BASE", DEFAULT_COACH_BASE)}/${path.replace(/^\//, "")}`;
528
552
  let session = await this.#ensureSession();
529
553
  let res = await this.#send(method, url, session, options.body);
530
554
  if (res.status === 401 || res.status === 403) {
@@ -1082,7 +1106,7 @@ async function writeSetResults(client, target, workouts, savedWorkoutSetId, resu
1082
1106
  throw new Error(`savedWorkoutSetExerciseId ${result.savedWorkoutSetExerciseId} not found in saved workout set ${savedWorkoutSetId}. Exercises in this set: ${valid.join(", ") || "none"}.`);
1083
1107
  }
1084
1108
  const workoutSetExerciseId = coerceInt(ex.workout_set_exercise_id);
1085
- if (!workoutSetExerciseId) throw new Error(`Could not resolve workout_set_exercise_id for exercise ${result.savedWorkoutSetExerciseId}.`);
1109
+ if (!workoutSetExerciseId) throw new Error(`savedWorkoutSetExercise ${result.savedWorkoutSetExerciseId} is missing its workout_set_exercise_id (the prescription-template pointer the write needs). This is the savedWorkoutSetExerciseId, not an exercise_id — re-read the ids from athlete_saved_workouts.`);
1086
1110
  const body = {
1087
1111
  ...buildExerciseSetPayload(result.savedWorkoutSetExerciseId, savedWorkoutSetId, workoutSetExerciseId, result.sets, mode),
1088
1112
  ...extra
@@ -1261,6 +1285,7 @@ function presentExerciseHistory(detail) {
1261
1285
  description: p.description ?? null,
1262
1286
  reps: p.reps ?? null,
1263
1287
  weight: p.weight ?? null,
1288
+ units: p.units ?? null,
1264
1289
  date: p.dateCompleted ?? null
1265
1290
  })),
1266
1291
  sessions: (detail.history ?? []).map((h) => ({
@@ -1295,6 +1320,7 @@ function historyInRange(presented, since, until) {
1295
1320
  sessions
1296
1321
  };
1297
1322
  }
1323
+ z.number().int().positive().max(36).optional();
1298
1324
  //#endregion
1299
1325
  //#region ../core/src/tools/athlete-training.ts
1300
1326
  /** Identity, profile, prefs, working maxes, leaderboard. */
@@ -1548,7 +1574,7 @@ function registerAthleteTrainingTools(server, ctx) {
1548
1574
  }
1549
1575
  //#endregion
1550
1576
  //#region package.json
1551
- var version = "1.2.0";
1577
+ var version = "1.4.0";
1552
1578
  //#endregion
1553
1579
  //#region src/server.ts
1554
1580
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trainheroic-unofficial/athlete-mcp",
3
- "version": "1.2.0",
3
+ "version": "1.4.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": "1.2.0",
25
- "@trainheroic-unofficial/js": "1.2.0"
24
+ "@trainheroic-unofficial/core": "1.4.0",
25
+ "@trainheroic-unofficial/js": "1.4.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^26.0.0",