@trainheroic-unofficial/athlete-mcp 2.0.2 → 2.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.
Files changed (2) hide show
  1. package/dist/server.mjs +61 -19
  2. package/package.json +5 -5
package/dist/server.mjs CHANGED
@@ -545,6 +545,34 @@ function confirmGate(ctx, message, confirmArg) {
545
545
  */
546
546
  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.";
547
547
  //#endregion
548
+ //#region ../js/src/http-error.ts
549
+ /**
550
+ * A final non-2xx response from TrainHeroic. The error deliberately carries only request
551
+ * metadata that is safe to send to telemetry: never the path, query string, request body,
552
+ * response body, credentials, or session token.
553
+ */
554
+ var TrainHeroicHttpError = class extends Error {
555
+ name = "TrainHeroicHttpError";
556
+ method;
557
+ status;
558
+ host;
559
+ constructor(method, url, status) {
560
+ const parsed = new URL(url);
561
+ const normalizedMethod = method.toUpperCase();
562
+ super(`TrainHeroic ${normalizedMethod} request failed with HTTP ${status}`);
563
+ this.method = normalizedMethod;
564
+ this.status = status;
565
+ this.host = parsed.host;
566
+ }
567
+ };
568
+ /** Call observability hooks without allowing them to change SDK behavior. */
569
+ function notifyHttpError(handler, method, url, status) {
570
+ if (!handler) return;
571
+ try {
572
+ Promise.resolve(handler(new TrainHeroicHttpError(method, url, status))).catch(() => {});
573
+ } catch {}
574
+ }
575
+ //#endregion
548
576
  //#region ../js/src/auth.ts
549
577
  const DEFAULT_AUTH_URL = "https://apis.trainheroic.com/auth";
550
578
  /**
@@ -566,8 +594,9 @@ function authUrl() {
566
594
  * the Phase 0 spike: no refresh_token, no api_token, no TTL). The 48-char session_id
567
595
  * is sent as the `session-token` header and works against both API hosts.
568
596
  */
569
- async function loginTrainHeroic(email, password) {
570
- const res = await fetch(authUrl(), {
597
+ async function loginTrainHeroic(email, password, options = {}) {
598
+ const url = authUrl();
599
+ const res = await fetch(url, {
571
600
  method: "POST",
572
601
  headers: {
573
602
  "content-type": "application/x-www-form-urlencoded",
@@ -578,7 +607,10 @@ async function loginTrainHeroic(email, password) {
578
607
  password
579
608
  }).toString()
580
609
  });
581
- if (!res.ok) return null;
610
+ if (!res.ok) {
611
+ notifyHttpError(options.onHttpError, "POST", url, res.status);
612
+ return null;
613
+ }
582
614
  const data = await res.json().catch(() => null);
583
615
  if (!data || typeof data.id !== "number" || !data.session_id) return null;
584
616
  return {
@@ -621,6 +653,7 @@ var TrainHeroicClient = class {
621
653
  #email;
622
654
  #password;
623
655
  #onSession;
656
+ #onHttpError;
624
657
  #sessionId;
625
658
  #loginInFlight = null;
626
659
  constructor(email, password, sessionId = null, options = {}) {
@@ -628,6 +661,7 @@ var TrainHeroicClient = class {
628
661
  this.#password = password;
629
662
  this.#sessionId = sessionId;
630
663
  this.#onSession = options.onSession;
664
+ this.#onHttpError = options.onHttpError;
631
665
  }
632
666
  get sessionId() {
633
667
  return this.#sessionId;
@@ -642,7 +676,7 @@ var TrainHeroicClient = class {
642
676
  }
643
677
  }
644
678
  async #login() {
645
- const session = await loginTrainHeroic(this.#email, this.#password);
679
+ const session = await loginTrainHeroic(this.#email, this.#password, this.#onHttpError ? { onHttpError: this.#onHttpError } : {});
646
680
  if (!session) throw new TrainHeroicAuthError("TrainHeroic login failed");
647
681
  this.#sessionId = session.sessionId;
648
682
  try {
@@ -659,6 +693,7 @@ var TrainHeroicClient = class {
659
693
  session = await this.#ensureSession();
660
694
  res = await this.#send(method, url, session, options.body);
661
695
  }
696
+ if (!res.ok) notifyHttpError(this.#onHttpError, method, url, res.status);
662
697
  const text = await res.text();
663
698
  let data = text;
664
699
  if (text.length > 0) try {
@@ -787,7 +822,7 @@ async function getJson(client, path, label) {
787
822
  if (!res.ok) throw new Error(`${label} failed (HTTP ${res.status}).`);
788
823
  return res.data;
789
824
  }
790
- async function getArray(client, path, label) {
825
+ async function getArray$1(client, path, label) {
791
826
  const res = await client.request("GET", path);
792
827
  if (!res.ok || !Array.isArray(res.data)) throw new Error(`${label} failed (HTTP ${res.status}).`);
793
828
  return res.data;
@@ -803,10 +838,10 @@ function fetchAthletePrefs(client) {
803
838
  return getJson(client, "/1.0/athlete/prefs", "athlete prefs");
804
839
  }
805
840
  function fetchWorkingMaxes(client) {
806
- return getArray(client, "/2.0/athlete/workingMax", "athlete working maxes");
841
+ return getArray$1(client, "/2.0/athlete/workingMax", "athlete working maxes");
807
842
  }
808
843
  function fetchExerciseHistoryList(client) {
809
- return getArray(client, "/v5/users/exercises/history", "athlete exercise history list");
844
+ return getArray$1(client, "/v5/users/exercises/history", "athlete exercise history list");
810
845
  }
811
846
  /** Free-text search over the athlete's logged exercises (FTS replacement via rankSearch). */
812
847
  async function searchExerciseHistory(client, query, limit = 20) {
@@ -816,7 +851,7 @@ function fetchExerciseHistoryDetail(client, exerciseId, userId) {
816
851
  return getJson(client, `/v5/exercises/${exerciseId}/history?userId=${userId}`, "athlete exercise history");
817
852
  }
818
853
  function fetchPersonalRecords(client, exerciseId) {
819
- return getArray(client, `/v5/exercises/${exerciseId}/personalRecords`, "athlete personal records");
854
+ return getArray$1(client, `/v5/exercises/${exerciseId}/personalRecords`, "athlete personal records");
820
855
  }
821
856
  /** Last performance + PR for an exercise. `date` (YYYY-MM-DD) is required by the API. */
822
857
  function fetchExerciseStats(client, exerciseId, userId, date) {
@@ -824,16 +859,7 @@ function fetchExerciseStats(client, exerciseId, userId, date) {
824
859
  }
825
860
  /** Scheduled + completed workouts in an inclusive YYYY-MM-DD window. */
826
861
  function fetchAthleteWorkouts(client, startDate, endDate) {
827
- return getArray(client, `/3.0/athlete/programworkout/range?startDate=${startDate}&endDate=${endDate}`, "athlete workouts");
828
- }
829
- /**
830
- * A coach's view of a roster athlete's scheduled + completed workouts in an inclusive
831
- * YYYY-MM-DD window (`/3.0/coach/athlete/programworkout/range/{athleteId}`). Returns the same
832
- * `ProgramWorkout[]` shape as `fetchAthleteWorkouts`, so the same presenters and
833
- * `findSavedWorkoutSet` apply — it just reads another athlete's data through the coach surface.
834
- */
835
- function fetchCoachAthleteWorkouts(client, athleteId, startDate, endDate) {
836
- return getArray(client, `/3.0/coach/athlete/programworkout/range/${athleteId}?startDate=${startDate}&endDate=${endDate}`, "coach athlete workouts");
862
+ return getArray$1(client, `/3.0/athlete/programworkout/range?startDate=${startDate}&endDate=${endDate}`, "athlete workouts");
837
863
  }
838
864
  function fetchLeaderboard(client, workoutId, opts = {}) {
839
865
  const qs = new URLSearchParams();
@@ -1186,6 +1212,22 @@ function presentExerciseHistory(detail) {
1186
1212
  };
1187
1213
  }
1188
1214
  //#endregion
1215
+ //#region ../js/src/coach-athlete-calendar.ts
1216
+ async function getArray(client, path, label) {
1217
+ const res = await client.request("GET", path);
1218
+ if (!res.ok || !Array.isArray(res.data)) throw new Error(`${label} failed (HTTP ${res.status}).`);
1219
+ return res.data;
1220
+ }
1221
+ /**
1222
+ * A coach's view of a roster athlete's scheduled + completed workouts in an inclusive
1223
+ * YYYY-MM-DD window (`/3.0/coach/athlete/programworkout/range/{athleteId}`). Returns the same
1224
+ * `ProgramWorkout[]` shape as `fetchAthleteWorkouts`, so the same presenters and
1225
+ * `findSavedWorkoutSet` apply — it just reads another athlete's data through the coach surface.
1226
+ */
1227
+ function fetchCoachAthleteWorkouts(client, athleteId, startDate, endDate) {
1228
+ return getArray(client, `/3.0/coach/athlete/programworkout/range/${athleteId}?startDate=${startDate}&endDate=${endDate}`, "coach athlete workouts");
1229
+ }
1230
+ //#endregion
1189
1231
  //#region ../js/src/exercise-set-payload.ts
1190
1232
  function slotData(exercise, key) {
1191
1233
  const value = exercise?.[key];
@@ -2108,7 +2150,7 @@ function registerAthleteTrainingTools(server, ctx) {
2108
2150
  }
2109
2151
  //#endregion
2110
2152
  //#region package.json
2111
- var version = "2.0.2";
2153
+ var version = "2.1.1";
2112
2154
  //#endregion
2113
2155
  //#region src/server.ts
2114
2156
  function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trainheroic-unofficial/athlete-mcp",
3
- "version": "2.0.2",
3
+ "version": "2.1.1",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,13 +21,13 @@
21
21
  "dependencies": {
22
22
  "@modelcontextprotocol/server": "2.0.0",
23
23
  "zod": "^4.4.3",
24
- "@trainheroic-unofficial/core": "2.0.2",
25
- "@trainheroic-unofficial/js": "2.0.2"
24
+ "@trainheroic-unofficial/js": "2.1.1",
25
+ "@trainheroic-unofficial/core": "2.1.1"
26
26
  },
27
27
  "devDependencies": {
28
- "@types/node": "^26.1.2",
28
+ "@types/node": "^26.2.0",
29
29
  "tsdown": "^0.22.14",
30
- "tsx": "^4.23.4",
30
+ "tsx": "^4.23.11",
31
31
  "typescript": "^7.0.2",
32
32
  "vitest": "^4.1.10"
33
33
  },