@trainheroic-unofficial/athlete-mcp 3.5.2 → 3.6.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 +191 -16
  2. package/package.json +3 -3
package/dist/server.mjs CHANGED
@@ -1001,30 +1001,200 @@ function confirmGate(ctx, message, confirmArg) {
1001
1001
  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.";
1002
1002
  //#endregion
1003
1003
  //#region ../js/src/http-error.ts
1004
- /**
1005
- * A final non-2xx response from TrainHeroic. The error deliberately carries only request
1006
- * metadata that is safe to send to telemetry: never the path, query string, request body,
1007
- * response body, credentials, or session token.
1008
- */
1004
+ const MAX_REQUEST_KEYS = 50;
1005
+ const MAX_RESPONSE_DEPTH = 4;
1006
+ const MAX_RESPONSE_KEYS = 20;
1007
+ const MAX_RESPONSE_ITEMS = 10;
1008
+ const MAX_RESPONSE_NODES = 50;
1009
+ const RESPONSE_DIAGNOSTIC_KEYS = /* @__PURE__ */ new Set([
1010
+ "code",
1011
+ "detail",
1012
+ "details",
1013
+ "error",
1014
+ "error_code",
1015
+ "errors",
1016
+ "message",
1017
+ "reason",
1018
+ "status",
1019
+ "status_code",
1020
+ "success"
1021
+ ]);
1022
+ const RESPONSE_CONTAINER_KEYS = /* @__PURE__ */ new Set([
1023
+ "data",
1024
+ "received",
1025
+ "response",
1026
+ "result"
1027
+ ]);
1028
+ const RESPONSE_STATUS_KEYS = /* @__PURE__ */ new Set(["status", "status_code"]);
1029
+ const RESPONSE_BOOLEAN_KEYS = /* @__PURE__ */ new Set(["success"]);
1030
+ const PARAMETER_TYPES = /* @__PURE__ */ new Set([
1031
+ 0,
1032
+ 1,
1033
+ 2,
1034
+ 3,
1035
+ 4,
1036
+ 5,
1037
+ 6,
1038
+ 7,
1039
+ 10,
1040
+ 11,
1041
+ 12,
1042
+ 13,
1043
+ 14,
1044
+ 18
1045
+ ]);
1046
+ function safeFieldName(key) {
1047
+ if (key === "__proto__" || key === "constructor" || key === "prototype") return "[Redacted key]";
1048
+ if (!/^[A-Za-z_][A-Za-z0-9_.-]{0,79}$/.test(key)) return "[Redacted key]";
1049
+ return key;
1050
+ }
1051
+ function boundedEntries(object, limit) {
1052
+ const entries = [];
1053
+ for (const key of Object.keys(object)) {
1054
+ const descriptor = Object.getOwnPropertyDescriptor(object, key);
1055
+ entries.push([key, descriptor && "value" in descriptor ? descriptor.value : void 0]);
1056
+ if (entries.length >= limit) break;
1057
+ }
1058
+ return entries;
1059
+ }
1060
+ function safeRequestValue(key, value) {
1061
+ if (key === "is_circuit" && typeof value === "boolean") return value;
1062
+ if (["param_1_type", "param_2_type"].includes(key) && typeof value === "number" && PARAMETER_TYPES.has(value)) return value;
1063
+ }
1064
+ function requestBodySummary(body) {
1065
+ if (Array.isArray(body)) return {
1066
+ type: "array",
1067
+ length: body.length
1068
+ };
1069
+ if (body && typeof body === "object") {
1070
+ const entries = boundedEntries(body, MAX_REQUEST_KEYS).sort(([left], [right]) => left.localeCompare(right));
1071
+ const keys = entries.map(([key]) => safeFieldName(key));
1072
+ const values = {};
1073
+ for (const [key, value] of entries) {
1074
+ const safeValue = safeRequestValue(key, value);
1075
+ if (safeValue !== void 0) values[safeFieldName(key)] = safeValue;
1076
+ }
1077
+ return Object.keys(values).length > 0 ? {
1078
+ type: "object",
1079
+ keys,
1080
+ values
1081
+ } : {
1082
+ type: "object",
1083
+ keys
1084
+ };
1085
+ }
1086
+ const primitive = typeof body;
1087
+ return primitive === "boolean" || primitive === "number" || primitive === "string" ? { type: primitive } : { type: "other" };
1088
+ }
1089
+ function sanitizedDiagnosticValue(value, depth, budget, field) {
1090
+ if (budget.nodes <= 0) return "[Truncated]";
1091
+ budget.nodes -= 1;
1092
+ if (value === null) return null;
1093
+ if (typeof value === "boolean") return field && RESPONSE_BOOLEAN_KEYS.has(field) ? value : "[Redacted]";
1094
+ if (typeof value === "number") return field && RESPONSE_STATUS_KEYS.has(field) && Number.isInteger(value) && value >= 100 && value <= 599 ? value : "[Redacted]";
1095
+ if (typeof value === "string") return "[Redacted]";
1096
+ if (depth >= MAX_RESPONSE_DEPTH) return "[Truncated]";
1097
+ if (Array.isArray(value)) {
1098
+ const result = [];
1099
+ for (const item of value.slice(0, MAX_RESPONSE_ITEMS)) {
1100
+ if (budget.nodes <= 0) {
1101
+ result.push("[Truncated]");
1102
+ break;
1103
+ }
1104
+ result.push(sanitizedDiagnosticValue(item, depth + 1, budget));
1105
+ }
1106
+ return result;
1107
+ }
1108
+ if (value && typeof value === "object") {
1109
+ const result = {};
1110
+ for (const [key, item] of boundedEntries(value, MAX_RESPONSE_KEYS)) {
1111
+ if (budget.nodes <= 0) {
1112
+ result.truncated = "[Truncated]";
1113
+ break;
1114
+ }
1115
+ const safeKey = safeFieldName(key);
1116
+ const safeValue = safeRequestValue(key, item);
1117
+ if (RESPONSE_DIAGNOSTIC_KEYS.has(key)) result[safeKey] = sanitizedDiagnosticValue(item, depth + 1, budget, key);
1118
+ else if (RESPONSE_CONTAINER_KEYS.has(key) && item && typeof item === "object") result[safeKey] = sanitizedDiagnosticValue(item, depth + 1, budget);
1119
+ else if (safeValue !== void 0) {
1120
+ budget.nodes -= 1;
1121
+ result[safeKey] = safeValue;
1122
+ }
1123
+ }
1124
+ return result;
1125
+ }
1126
+ return `[${typeof value}]`;
1127
+ }
1128
+ function responseBodyDiagnostics(body, budget, depth = 0) {
1129
+ const activeBudget = budget ?? { nodes: MAX_RESPONSE_NODES };
1130
+ if (activeBudget.nodes <= 0 || depth >= MAX_RESPONSE_DEPTH) return "[Truncated]";
1131
+ if (!body || typeof body !== "object" || Array.isArray(body)) return sanitizedDiagnosticValue(body, depth, activeBudget);
1132
+ const object = body;
1133
+ const result = {};
1134
+ for (const [key, value] of boundedEntries(object, MAX_RESPONSE_KEYS)) {
1135
+ if (activeBudget.nodes <= 0) {
1136
+ result.truncated = "[Truncated]";
1137
+ break;
1138
+ }
1139
+ if (RESPONSE_DIAGNOSTIC_KEYS.has(key)) {
1140
+ result[safeFieldName(key)] = sanitizedDiagnosticValue(value, depth + 1, activeBudget, key);
1141
+ continue;
1142
+ }
1143
+ if (RESPONSE_CONTAINER_KEYS.has(key) && value && typeof value === "object") {
1144
+ activeBudget.nodes -= 1;
1145
+ const nested = responseBodyDiagnostics(value, activeBudget, depth + 1);
1146
+ if (nested === "[Truncated]" || nested && typeof nested === "object" && Object.keys(nested).length > 0) result[safeFieldName(key)] = nested;
1147
+ }
1148
+ }
1149
+ if (Object.keys(result).length > 0) return result;
1150
+ return { type: "object" };
1151
+ }
1152
+ function parseResponseText(text) {
1153
+ if (text.length === 0) return text;
1154
+ try {
1155
+ return JSON.parse(text);
1156
+ } catch {
1157
+ return text;
1158
+ }
1159
+ }
1160
+ function safeRequestBodySummary(body) {
1161
+ try {
1162
+ return requestBodySummary(body);
1163
+ } catch {
1164
+ return { type: "other" };
1165
+ }
1166
+ }
1167
+ function safeResponseBodyDiagnostics(body) {
1168
+ try {
1169
+ return responseBodyDiagnostics(body);
1170
+ } catch {
1171
+ return "[Unavailable]";
1172
+ }
1173
+ }
1174
+ /** A final non-2xx response plus bounded, telemetry-safe request and response diagnostics. */
1009
1175
  var TrainHeroicHttpError = class extends Error {
1010
1176
  name = "TrainHeroicHttpError";
1011
1177
  method;
1012
1178
  status;
1013
1179
  host;
1014
- constructor(method, url, status) {
1180
+ requestBody;
1181
+ responseBody;
1182
+ constructor(method, url, status, diagnostics = {}) {
1015
1183
  const parsed = new URL(url);
1016
1184
  const normalizedMethod = method.toUpperCase();
1017
1185
  super(`TrainHeroic ${normalizedMethod} request failed with HTTP ${status}`);
1018
1186
  this.method = normalizedMethod;
1019
1187
  this.status = status;
1020
1188
  this.host = parsed.host;
1189
+ this.requestBody = diagnostics.requestBody === void 0 ? void 0 : safeRequestBodySummary(diagnostics.requestBody);
1190
+ this.responseBody = diagnostics.responseBody === void 0 ? void 0 : safeResponseBodyDiagnostics(diagnostics.responseBody);
1021
1191
  }
1022
1192
  };
1023
1193
  /** Call observability hooks without allowing them to change SDK behavior. */
1024
- function notifyHttpError(handler, method, url, status) {
1194
+ function notifyHttpError(handler, method, url, status, diagnostics = {}) {
1025
1195
  if (!handler) return;
1026
1196
  try {
1027
- Promise.resolve(handler(new TrainHeroicHttpError(method, url, status))).catch(() => {});
1197
+ Promise.resolve(handler(new TrainHeroicHttpError(method, url, status, diagnostics))).catch(() => {});
1028
1198
  } catch {}
1029
1199
  }
1030
1200
  //#endregion
@@ -1148,14 +1318,19 @@ var TrainHeroicClient = class {
1148
1318
  session = await this.#ensureSession();
1149
1319
  res = await this.#send(method, url, session, options.body);
1150
1320
  }
1151
- if (!res.ok && !options.expectedStatuses?.includes(res.status)) notifyHttpError(this.#onHttpError, method, url, res.status);
1152
- const text = await res.text();
1153
- let data = text;
1154
- if (text.length > 0) try {
1155
- data = JSON.parse(text);
1156
- } catch {
1157
- data = text;
1321
+ const shouldReport = !res.ok && !options.expectedStatuses?.includes(res.status);
1322
+ let text;
1323
+ try {
1324
+ text = await res.text();
1325
+ } catch (error) {
1326
+ if (shouldReport) notifyHttpError(this.#onHttpError, method, url, res.status, { requestBody: options.body });
1327
+ throw error;
1158
1328
  }
1329
+ const data = parseResponseText(text);
1330
+ if (shouldReport) notifyHttpError(this.#onHttpError, method, url, res.status, {
1331
+ requestBody: options.body,
1332
+ responseBody: data
1333
+ });
1159
1334
  return {
1160
1335
  status: res.status,
1161
1336
  ok: res.ok,
@@ -2960,7 +3135,7 @@ function registerAthleteTrainingTools(server, ctx) {
2960
3135
  }
2961
3136
  //#endregion
2962
3137
  //#region package.json
2963
- var version = "3.5.2";
3138
+ var version = "3.6.0";
2964
3139
  //#endregion
2965
3140
  //#region src/server.ts
2966
3141
  function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trainheroic-unofficial/athlete-mcp",
3
- "version": "3.5.2",
3
+ "version": "3.6.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.5.2",
25
- "@trainheroic-unofficial/js": "3.5.2"
24
+ "@trainheroic-unofficial/core": "3.6.0",
25
+ "@trainheroic-unofficial/js": "3.6.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^26.3.0",