@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.
- package/dist/server.mjs +191 -16
- 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
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
25
|
-
"@trainheroic-unofficial/js": "3.
|
|
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",
|