hevy-mcp 5.0.3 → 5.0.5
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/cli.mjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{src-BGQCPTet.mjs → src-TBD9ENR3.mjs} +640 -118
- package/package.json +1 -1
- package/server.json +2 -2
package/dist/cli.mjs
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
(function() {
|
|
5
5
|
try {
|
|
6
6
|
var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
|
|
7
|
-
e.SENTRY_RELEASE = { id: "hevy-mcp@5.0.
|
|
7
|
+
e.SENTRY_RELEASE = { id: "hevy-mcp@5.0.5" };
|
|
8
8
|
var n = new e.Error().stack;
|
|
9
9
|
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "625c4c9c-e20d-40b3-96b1-4e799ef130f3", e._sentryDebugIdIdentifier = "sentry-dbid-625c4c9c-e20d-40b3-96b1-4e799ef130f3");
|
|
10
10
|
} catch (e) {}
|
|
11
11
|
})();
|
|
12
|
-
import { a as createSafeErrorDiagnostic, i as MissingHevyApiKeyError, n as runServer, o as flushTelemetry } from "./src-
|
|
12
|
+
import { a as createSafeErrorDiagnostic, i as MissingHevyApiKeyError, n as runServer, o as flushTelemetry } from "./src-TBD9ENR3.mjs";
|
|
13
13
|
//#region src/cli.ts
|
|
14
14
|
runServer().catch(async (error) => {
|
|
15
15
|
if (error instanceof MissingHevyApiKeyError) console.error(error.message);
|
package/dist/index.mjs
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
(function() {
|
|
5
5
|
try {
|
|
6
6
|
var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
|
|
7
|
-
e.SENTRY_RELEASE = { id: "hevy-mcp@5.0.
|
|
7
|
+
e.SENTRY_RELEASE = { id: "hevy-mcp@5.0.5" };
|
|
8
8
|
var n = new e.Error().stack;
|
|
9
9
|
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "e9496b31-626b-4ad1-9416-47ba4ffd2995", e._sentryDebugIdIdentifier = "sentry-dbid-e9496b31-626b-4ad1-9416-47ba4ffd2995");
|
|
10
10
|
} catch (e) {}
|
|
11
11
|
})();
|
|
12
|
-
import { n as runServer, r as runStdioServer, t as createNodeMcpServer } from "./src-
|
|
12
|
+
import { n as runServer, r as runStdioServer, t as createNodeMcpServer } from "./src-TBD9ENR3.mjs";
|
|
13
13
|
export { createNodeMcpServer, runServer, runStdioServer };
|
|
@@ -4,17 +4,17 @@
|
|
|
4
4
|
(function() {
|
|
5
5
|
try {
|
|
6
6
|
var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
|
|
7
|
-
e.SENTRY_RELEASE = { id: "hevy-mcp@5.0.
|
|
7
|
+
e.SENTRY_RELEASE = { id: "hevy-mcp@5.0.5" };
|
|
8
8
|
var n = new e.Error().stack;
|
|
9
|
-
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "
|
|
9
|
+
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "f5270278-73ea-40fd-92e2-2ae5127e6780", e._sentryDebugIdIdentifier = "sentry-dbid-f5270278-73ea-40fd-92e2-2ae5127e6780");
|
|
10
10
|
} catch (e) {}
|
|
11
11
|
})();
|
|
12
|
-
import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
|
|
12
|
+
import { createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
13
13
|
import * as Sentry from "@sentry/node";
|
|
14
14
|
import { SentryPropagator, SentrySampler, SentrySpanProcessor } from "@sentry/opentelemetry";
|
|
15
|
-
import { SpanStatusCode, metrics, trace } from "@opentelemetry/api";
|
|
15
|
+
import { SpanStatusCode, context, metrics, trace } from "@opentelemetry/api";
|
|
16
16
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
17
|
-
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
|
|
17
|
+
import { AggregationTemporalityPreference, OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
|
|
18
18
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
19
19
|
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
20
20
|
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
|
@@ -79,16 +79,59 @@ function sanitizeSentryMcpSpan(span) {
|
|
|
79
79
|
* Sentry SDK: error events, performance traces, release tracking
|
|
80
80
|
* OTel Collector → Honeycomb: performance traces, metrics
|
|
81
81
|
*/
|
|
82
|
+
const PROCESS_FAILURE_TAXONOMY = {
|
|
83
|
+
uncaughtException: "uncaught_exception",
|
|
84
|
+
unhandledRejection: "unhandled_rejection"
|
|
85
|
+
};
|
|
86
|
+
const SAFE_EXCEPTION_TYPES = /* @__PURE__ */ new Set([
|
|
87
|
+
"AggregateError",
|
|
88
|
+
"DOMException",
|
|
89
|
+
"Error",
|
|
90
|
+
"EvalError",
|
|
91
|
+
"HevyHttpError",
|
|
92
|
+
"RangeError",
|
|
93
|
+
"ReferenceError",
|
|
94
|
+
"SyntaxError",
|
|
95
|
+
"TypeError",
|
|
96
|
+
"URIError",
|
|
97
|
+
"ProtocolError",
|
|
98
|
+
"ZodError"
|
|
99
|
+
]);
|
|
100
|
+
const SAFE_EXCEPTION_CODES = /* @__PURE__ */ new Set([
|
|
101
|
+
"EAI_AGAIN",
|
|
102
|
+
"ECONNABORTED",
|
|
103
|
+
"ECONNREFUSED",
|
|
104
|
+
"ECONNRESET",
|
|
105
|
+
"ENETUNREACH",
|
|
106
|
+
"ENOTFOUND",
|
|
107
|
+
"ERR_NETWORK",
|
|
108
|
+
"ERR_SOCKET_TIMEOUT",
|
|
109
|
+
"ETIMEDOUT",
|
|
110
|
+
"HEVY_INVALID_ENDPOINT",
|
|
111
|
+
"HEVY_REQUEST_ABORTED",
|
|
112
|
+
"HEVY_RETRY_EXHAUSTED"
|
|
113
|
+
]);
|
|
82
114
|
function normalizeTelemetryError(error) {
|
|
83
|
-
const
|
|
84
|
-
return
|
|
115
|
+
const candidate = error instanceof Error && typeof error.name === "string" ? error.name : void 0;
|
|
116
|
+
return { name: candidate && SAFE_EXCEPTION_TYPES.has(candidate) ? candidate : "UnknownError" };
|
|
117
|
+
}
|
|
118
|
+
function getSafeExceptionCode(error) {
|
|
119
|
+
if (!error || typeof error !== "object" || !("code" in error)) return void 0;
|
|
120
|
+
const code = error.code;
|
|
121
|
+
return typeof code === "string" && SAFE_EXCEPTION_CODES.has(code) ? code : void 0;
|
|
85
122
|
}
|
|
86
123
|
function recordTelemetryException(error, attributes, span) {
|
|
87
124
|
if (!telemetryEnabled) return;
|
|
88
125
|
try {
|
|
89
126
|
const target = span ?? trace.getActiveSpan();
|
|
90
127
|
if (!target) return;
|
|
91
|
-
|
|
128
|
+
const normalized = normalizeTelemetryError(error);
|
|
129
|
+
target.addEvent("exception", {
|
|
130
|
+
...attributes,
|
|
131
|
+
"exception.type": normalized.name
|
|
132
|
+
});
|
|
133
|
+
target.setAttribute("exception.type", normalized.name);
|
|
134
|
+
target.setAttribute("error.category", normalized.name);
|
|
92
135
|
if (attributes) target.setAttributes(attributes);
|
|
93
136
|
target.setStatus({ code: SpanStatusCode.ERROR });
|
|
94
137
|
} catch {}
|
|
@@ -97,9 +140,19 @@ function installProcessExceptionTracking(processLike = process) {
|
|
|
97
140
|
if (!telemetryEnabled) return () => {};
|
|
98
141
|
const recordProcessException = (source, error) => {
|
|
99
142
|
try {
|
|
100
|
-
tracer.startActiveSpan(`mcp.process.${source}`, (span) => {
|
|
101
|
-
|
|
102
|
-
|
|
143
|
+
tracer.startActiveSpan(`mcp.process.${source}`, { attributes: { "mcp.span.category": "process" } }, (span) => {
|
|
144
|
+
try {
|
|
145
|
+
const code = getSafeExceptionCode(error);
|
|
146
|
+
recordTelemetryException(error, {
|
|
147
|
+
"exception.source": source,
|
|
148
|
+
"mcp.failure.phase": PROCESS_FAILURE_TAXONOMY[source],
|
|
149
|
+
"error.type": "MCP_PROCESS_EXCEPTION",
|
|
150
|
+
"error.category": "McpProcessFailure",
|
|
151
|
+
...code ? { "error.code": code } : {}
|
|
152
|
+
}, span);
|
|
153
|
+
} finally {
|
|
154
|
+
span.end();
|
|
155
|
+
}
|
|
103
156
|
});
|
|
104
157
|
} catch {}
|
|
105
158
|
};
|
|
@@ -116,14 +169,24 @@ function installProcessExceptionTracking(processLike = process) {
|
|
|
116
169
|
};
|
|
117
170
|
}
|
|
118
171
|
const name$1 = "hevy-mcp";
|
|
119
|
-
const version$1 = "5.0.
|
|
172
|
+
const version$1 = "5.0.5";
|
|
120
173
|
const telemetryEnabled = process.env.HEVY_MCP_TELEMETRY !== "0";
|
|
121
174
|
const collectorToken = "NH9vOela-HYreQxAbJa68cjEmORoEKM57EvneUDVcOo";
|
|
122
175
|
const COLLECTOR_ENDPOINT = "https://otel.chrisdoc.dev/v1";
|
|
123
|
-
const sentryRelease = process.env.SENTRY_RELEASE ??
|
|
176
|
+
const sentryRelease = process.env.SENTRY_RELEASE ?? `${name$1}@${version$1}`;
|
|
177
|
+
function createServiceInstanceId(generate = randomUUID) {
|
|
178
|
+
try {
|
|
179
|
+
const generated = generate();
|
|
180
|
+
if (generated.length > 0 && generated.length <= 128) return generated;
|
|
181
|
+
} catch {}
|
|
182
|
+
return randomBytes(16).toString("hex");
|
|
183
|
+
}
|
|
124
184
|
const resource = resourceFromAttributes({
|
|
125
185
|
"service.name": name$1,
|
|
126
|
-
"service.version": version$1
|
|
186
|
+
"service.version": version$1,
|
|
187
|
+
"service.instance.id": createServiceInstanceId(),
|
|
188
|
+
"process.runtime.name": process.release.name,
|
|
189
|
+
"process.runtime.version": process.version
|
|
127
190
|
});
|
|
128
191
|
let currentUserHash;
|
|
129
192
|
var UserHashSpanProcessor = class {
|
|
@@ -168,7 +231,8 @@ if (telemetryEnabled) {
|
|
|
168
231
|
readers: [new PeriodicExportingMetricReader({
|
|
169
232
|
exporter: new OTLPMetricExporter({
|
|
170
233
|
url: `${COLLECTOR_ENDPOINT}/metrics`,
|
|
171
|
-
headers: { Authorization: `Bearer ${collectorToken}` }
|
|
234
|
+
headers: { Authorization: `Bearer ${collectorToken}` },
|
|
235
|
+
temporalityPreference: AggregationTemporalityPreference.DELTA
|
|
172
236
|
}),
|
|
173
237
|
exportIntervalMillis: 1e4
|
|
174
238
|
})]
|
|
@@ -196,6 +260,8 @@ async function flushTelemetry(timeoutMs = 1e3) {
|
|
|
196
260
|
}
|
|
197
261
|
const tracer = trace.getTracer(name$1);
|
|
198
262
|
const meter = metrics.getMeter(name$1);
|
|
263
|
+
const serviceName = name$1;
|
|
264
|
+
const serviceVersion = version$1;
|
|
199
265
|
const SENTRY_USER_ID_CONTEXT = "hevy-mcp:sentry-user-id:v1";
|
|
200
266
|
function setTelemetryUser(apiKey) {
|
|
201
267
|
if (!telemetryEnabled) return;
|
|
@@ -1004,6 +1070,35 @@ const SAFE_STATIC_ENDPOINTS = /* @__PURE__ */ new Set([
|
|
|
1004
1070
|
"/v1/workouts/count",
|
|
1005
1071
|
"/v1/workouts/events"
|
|
1006
1072
|
]);
|
|
1073
|
+
const EXPECTED_READ_404_ENDPOINTS = /* @__PURE__ */ new Set([
|
|
1074
|
+
"/v1/body_measurements/:date",
|
|
1075
|
+
"/v1/exercise_history/:exerciseTemplateId",
|
|
1076
|
+
"/v1/exercise_templates/:exerciseTemplateId",
|
|
1077
|
+
"/v1/routine_folders/:folderId",
|
|
1078
|
+
"/v1/routines/:routineId",
|
|
1079
|
+
"/v1/workouts/:workoutId"
|
|
1080
|
+
]);
|
|
1081
|
+
const EXPECTED_LIST_404_ENDPOINTS = /* @__PURE__ */ new Set([
|
|
1082
|
+
"/v1/body_measurements",
|
|
1083
|
+
"/v1/exercise_templates",
|
|
1084
|
+
"/v1/routine_folders",
|
|
1085
|
+
"/v1/routines",
|
|
1086
|
+
"/v1/workouts",
|
|
1087
|
+
"/v1/workouts/events"
|
|
1088
|
+
]);
|
|
1089
|
+
const SAFE_OBSERVATION_CODES = /* @__PURE__ */ new Set([
|
|
1090
|
+
"EAI_AGAIN",
|
|
1091
|
+
"ECONNABORTED",
|
|
1092
|
+
"ECONNREFUSED",
|
|
1093
|
+
"ECONNRESET",
|
|
1094
|
+
"ENETUNREACH",
|
|
1095
|
+
"ENOTFOUND",
|
|
1096
|
+
"ERR_NETWORK",
|
|
1097
|
+
"ERR_SOCKET_TIMEOUT",
|
|
1098
|
+
"ETIMEDOUT",
|
|
1099
|
+
HEVY_REQUEST_ABORTED_ERROR_CODE,
|
|
1100
|
+
HEVY_RETRY_EXHAUSTED_ERROR_CODE
|
|
1101
|
+
]);
|
|
1007
1102
|
const SAFE_DYNAMIC_ENDPOINTS = [
|
|
1008
1103
|
["/v1/body_measurements/", "/v1/body_measurements/:date"],
|
|
1009
1104
|
["/v1/exercise_history/", "/v1/exercise_history/:exerciseTemplateId"],
|
|
@@ -1049,9 +1144,11 @@ function getRequestContext(config) {
|
|
|
1049
1144
|
let endpoint = "unknown";
|
|
1050
1145
|
if (SAFE_STATIC_ENDPOINTS.has(rawEndpoint)) endpoint = rawEndpoint;
|
|
1051
1146
|
else endpoint = SAFE_DYNAMIC_ENDPOINTS.find(([prefix]) => rawEndpoint.startsWith(prefix))?.[1] ?? "unknown";
|
|
1147
|
+
const page = config.params !== null && typeof config.params === "object" && "page" in config.params && typeof config.params.page === "number" ? config.params.page : void 0;
|
|
1052
1148
|
return {
|
|
1053
1149
|
method,
|
|
1054
|
-
endpoint
|
|
1150
|
+
endpoint,
|
|
1151
|
+
page
|
|
1055
1152
|
};
|
|
1056
1153
|
}
|
|
1057
1154
|
function emitClientLog(logger, message) {
|
|
@@ -1061,11 +1158,49 @@ function emitClientLog(logger, message) {
|
|
|
1061
1158
|
console.error("Failed to emit structured Hevy API log");
|
|
1062
1159
|
}
|
|
1063
1160
|
}
|
|
1161
|
+
function emitRequestStart(observer, observation) {
|
|
1162
|
+
try {
|
|
1163
|
+
return observer?.(observation) ?? void 0;
|
|
1164
|
+
} catch {
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
function finishRequestObservation(scope, observation) {
|
|
1169
|
+
try {
|
|
1170
|
+
scope?.finish(observation);
|
|
1171
|
+
} catch {}
|
|
1172
|
+
}
|
|
1064
1173
|
function emitRequestObservation(observer, observation) {
|
|
1065
1174
|
try {
|
|
1066
1175
|
observer?.(observation);
|
|
1067
1176
|
} catch {}
|
|
1068
1177
|
}
|
|
1178
|
+
function runRequestObservation(scope, operation) {
|
|
1179
|
+
if (!scope?.run) return operation();
|
|
1180
|
+
let started = false;
|
|
1181
|
+
const trackedOperation = () => {
|
|
1182
|
+
started = true;
|
|
1183
|
+
return operation();
|
|
1184
|
+
};
|
|
1185
|
+
try {
|
|
1186
|
+
return scope.run(trackedOperation);
|
|
1187
|
+
} catch (error) {
|
|
1188
|
+
if (started) throw error;
|
|
1189
|
+
return operation();
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
function emitRetryWait(observer, observation) {
|
|
1193
|
+
try {
|
|
1194
|
+
return observer?.(observation) ?? void 0;
|
|
1195
|
+
} catch {
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
function finishRetryWait(scope) {
|
|
1200
|
+
try {
|
|
1201
|
+
scope?.finish();
|
|
1202
|
+
} catch {}
|
|
1203
|
+
}
|
|
1069
1204
|
function parseRetryAfterMs(value) {
|
|
1070
1205
|
if (!value) return void 0;
|
|
1071
1206
|
const seconds = Number(value);
|
|
@@ -1122,7 +1257,7 @@ function createNativeClient(apiKey, baseUrl, options) {
|
|
|
1122
1257
|
...clientConfig,
|
|
1123
1258
|
...config
|
|
1124
1259
|
};
|
|
1125
|
-
const { method, endpoint } = getRequestContext(normalized);
|
|
1260
|
+
const { method, endpoint, page } = getRequestContext(normalized);
|
|
1126
1261
|
const url = buildUrl(baseUrl, normalized);
|
|
1127
1262
|
let retryCount = 0;
|
|
1128
1263
|
while (true) {
|
|
@@ -1132,43 +1267,53 @@ function createNativeClient(apiKey, baseUrl, options) {
|
|
|
1132
1267
|
code: HEVY_REQUEST_ABORTED_ERROR_CODE
|
|
1133
1268
|
});
|
|
1134
1269
|
const startedAt = Date.now();
|
|
1270
|
+
const observationScope = emitRequestStart(options.onRequestStart, {
|
|
1271
|
+
method,
|
|
1272
|
+
endpoint,
|
|
1273
|
+
retryCount
|
|
1274
|
+
});
|
|
1135
1275
|
const controller = new AbortController();
|
|
1136
1276
|
const abortFromCaller = () => controller.abort();
|
|
1137
1277
|
normalized.signal?.addEventListener("abort", abortFromCaller, { once: true });
|
|
1138
1278
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
1139
1279
|
try {
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1280
|
+
return await runRequestObservation(observationScope, async () => {
|
|
1281
|
+
const headers = new Headers({ "api-key": apiKey });
|
|
1282
|
+
if (normalized.data !== void 0 && !(normalized.data instanceof FormData)) headers.set("content-type", "application/json");
|
|
1283
|
+
const attemptDeadline = Date.now() + timeoutMs;
|
|
1284
|
+
const response = await withTimeout(fetchImplementation(url, {
|
|
1285
|
+
method,
|
|
1286
|
+
headers,
|
|
1287
|
+
redirect: "manual",
|
|
1288
|
+
body: normalized.data instanceof FormData ? normalized.data : normalized.data === void 0 ? void 0 : JSON.stringify(normalized.data),
|
|
1289
|
+
signal: controller.signal
|
|
1290
|
+
}), Math.max(0, attemptDeadline - Date.now()), () => controller.abort());
|
|
1291
|
+
const data = await withTimeout(parseResponseData(response), Math.max(0, attemptDeadline - Date.now()), () => controller.abort());
|
|
1292
|
+
if (!response.ok) throw new HevyHttpError(`Hevy API request failed (HTTP ${response.status})`, {
|
|
1293
|
+
status: response.status,
|
|
1294
|
+
statusText: response.statusText,
|
|
1295
|
+
data,
|
|
1296
|
+
headers: response.headers,
|
|
1297
|
+
method,
|
|
1298
|
+
endpoint
|
|
1299
|
+
});
|
|
1300
|
+
const observation = {
|
|
1301
|
+
method,
|
|
1302
|
+
endpoint,
|
|
1303
|
+
status: response.status,
|
|
1304
|
+
durationMs: Date.now() - startedAt,
|
|
1305
|
+
retryCount,
|
|
1306
|
+
outcome: "success"
|
|
1307
|
+
};
|
|
1308
|
+
finishRequestObservation(observationScope, observation);
|
|
1309
|
+
emitRequestObservation(options.onRequestComplete, observation);
|
|
1310
|
+
return {
|
|
1311
|
+
data,
|
|
1312
|
+
status: response.status,
|
|
1313
|
+
statusText: response.statusText,
|
|
1314
|
+
headers: response.headers
|
|
1315
|
+
};
|
|
1165
1316
|
});
|
|
1166
|
-
return {
|
|
1167
|
-
data,
|
|
1168
|
-
status: response.status,
|
|
1169
|
-
statusText: response.statusText,
|
|
1170
|
-
headers: response.headers
|
|
1171
|
-
};
|
|
1172
1317
|
} catch (cause) {
|
|
1173
1318
|
const error = isHevyHttpError(cause) ? cause : new HevyHttpError(normalized.signal?.aborted ? "Hevy API request was canceled" : "Hevy API network request failed", {
|
|
1174
1319
|
method,
|
|
@@ -1176,24 +1321,31 @@ function createNativeClient(apiKey, baseUrl, options) {
|
|
|
1176
1321
|
code: normalized.signal?.aborted ? HEVY_REQUEST_ABORTED_ERROR_CODE : getNetworkCode(cause),
|
|
1177
1322
|
cause
|
|
1178
1323
|
});
|
|
1324
|
+
const expectedReason = error.status === 404 && method === "GET" && EXPECTED_READ_404_ENDPOINTS.has(endpoint) ? "not_found" : error.status === 404 && method === "GET" && page !== void 0 && page > 1 && EXPECTED_LIST_404_ENDPOINTS.has(endpoint) ? "end_of_list" : void 0;
|
|
1179
1325
|
const canRetry = method === "GET" && isRetryable(error);
|
|
1180
|
-
|
|
1326
|
+
const retryExhausted = canRetry && retryCount >= maxGetRetries;
|
|
1327
|
+
if (retryExhausted) {
|
|
1181
1328
|
error.hevyRetryExhausted = true;
|
|
1182
1329
|
error.hevyRetryCount = retryCount;
|
|
1183
1330
|
error.code = HEVY_RETRY_EXHAUSTED_ERROR_CODE;
|
|
1184
1331
|
}
|
|
1185
|
-
|
|
1332
|
+
const observation = {
|
|
1186
1333
|
method,
|
|
1187
1334
|
endpoint,
|
|
1188
1335
|
status: error.status ?? 0,
|
|
1189
1336
|
durationMs: Date.now() - startedAt,
|
|
1190
1337
|
retryCount,
|
|
1338
|
+
outcome: expectedReason ? "expected" : canRetry && !retryExhausted ? "retryable_failure" : "terminal_failure",
|
|
1339
|
+
...expectedReason ? { expectedReason } : {},
|
|
1191
1340
|
error: {
|
|
1192
1341
|
status: error.status,
|
|
1193
|
-
code: error.code
|
|
1342
|
+
code: typeof error.code === "string" && SAFE_OBSERVATION_CODES.has(error.code) ? error.code : void 0,
|
|
1343
|
+
category: error.status === void 0 ? "NetworkError" : "HevyHttpError"
|
|
1194
1344
|
}
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1345
|
+
};
|
|
1346
|
+
finishRequestObservation(observationScope, observation);
|
|
1347
|
+
emitRequestObservation(options.onRequestComplete, observation);
|
|
1348
|
+
if (!expectedReason && !canRetry) {
|
|
1197
1349
|
emitClientLog(options.onLog, {
|
|
1198
1350
|
level: "error",
|
|
1199
1351
|
logger: "hevy-api",
|
|
@@ -1206,7 +1358,7 @@ function createNativeClient(apiKey, baseUrl, options) {
|
|
|
1206
1358
|
});
|
|
1207
1359
|
throw error;
|
|
1208
1360
|
}
|
|
1209
|
-
if (error.hevyRetryExhausted) throw error;
|
|
1361
|
+
if (expectedReason || error.hevyRetryExhausted) throw error;
|
|
1210
1362
|
retryCount += 1;
|
|
1211
1363
|
const delayMs = getRetryDelayMs(error, retryCount);
|
|
1212
1364
|
emitClientLog(options.onLog, {
|
|
@@ -1222,7 +1374,19 @@ function createNativeClient(apiKey, baseUrl, options) {
|
|
|
1222
1374
|
endpoint
|
|
1223
1375
|
}
|
|
1224
1376
|
});
|
|
1225
|
-
await
|
|
1377
|
+
await runRequestObservation(observationScope, async () => {
|
|
1378
|
+
const retryWaitScope = emitRetryWait(options.onRetryWait, {
|
|
1379
|
+
method,
|
|
1380
|
+
endpoint,
|
|
1381
|
+
retryCount,
|
|
1382
|
+
delayMs
|
|
1383
|
+
});
|
|
1384
|
+
try {
|
|
1385
|
+
await sleep(delayMs);
|
|
1386
|
+
} finally {
|
|
1387
|
+
finishRetryWait(retryWaitScope);
|
|
1388
|
+
}
|
|
1389
|
+
});
|
|
1226
1390
|
} finally {
|
|
1227
1391
|
clearTimeout(timeout);
|
|
1228
1392
|
normalized.signal?.removeEventListener("abort", abortFromCaller);
|
|
@@ -2680,7 +2844,7 @@ function registerHevyResources(server, runtime) {
|
|
|
2680
2844
|
//#endregion
|
|
2681
2845
|
//#region ../core/src/server-metadata.ts
|
|
2682
2846
|
const SERVER_NAME = "hevy-mcp";
|
|
2683
|
-
const SERVER_VERSION = "5.0.
|
|
2847
|
+
const SERVER_VERSION = "5.0.5";
|
|
2684
2848
|
const SERVER_INSTRUCTIONS = [
|
|
2685
2849
|
"Hevy MCP connects clients to the authenticated user's Hevy workout-tracking data, including workouts, routines, exercise templates, routine folders, body measurements, and profile information. HEVY_API_KEY must contain a valid Hevy API key for local stdio use.",
|
|
2686
2850
|
"Safety: all get-* and search-* tools are read-only. create-* and update-* tools mutate Hevy data. Creates are additive and non-idempotent, so repeating one can create duplicates. Updates can overwrite existing data. Delete operations are not available.",
|
|
@@ -4078,7 +4242,7 @@ function createErrorResponse(error, context) {
|
|
|
4078
4242
|
axios: axiosErrorContext ?? void 0
|
|
4079
4243
|
};
|
|
4080
4244
|
const formattedMessage = `${context ? `[${context}] ` : ""}Error: ${policy.message}`;
|
|
4081
|
-
console.error(createMcpToolFailureEvent(context || "unknown", policy.type, diagnostic));
|
|
4245
|
+
console.error(JSON.stringify(createMcpToolFailureEvent(context || "unknown", policy.type, diagnostic)));
|
|
4082
4246
|
return {
|
|
4083
4247
|
content: [{
|
|
4084
4248
|
type: "text",
|
|
@@ -4240,39 +4404,69 @@ var AsyncTtlCache = class {
|
|
|
4240
4404
|
ttlMs;
|
|
4241
4405
|
maxSize;
|
|
4242
4406
|
now;
|
|
4407
|
+
observer;
|
|
4243
4408
|
entries = /* @__PURE__ */ new Map();
|
|
4244
4409
|
inFlight = /* @__PURE__ */ new Map();
|
|
4245
4410
|
requestCounter = 0;
|
|
4246
4411
|
constructor(options, now = Date.now) {
|
|
4247
|
-
const { ttlMs, maxSize } = options;
|
|
4412
|
+
const { ttlMs, maxSize, observer } = options;
|
|
4248
4413
|
if (ttlMs <= 0) throw new Error("Cache ttlMs must be greater than 0.");
|
|
4249
4414
|
if (maxSize <= 0) throw new Error("Cache maxSize must be greater than 0.");
|
|
4250
4415
|
this.ttlMs = ttlMs;
|
|
4251
4416
|
this.maxSize = maxSize;
|
|
4252
4417
|
this.now = now;
|
|
4418
|
+
this.observer = observer;
|
|
4419
|
+
}
|
|
4420
|
+
startObservation(state) {
|
|
4421
|
+
try {
|
|
4422
|
+
return this.observer?.start({ state }) ?? void 0;
|
|
4423
|
+
} catch {
|
|
4424
|
+
return;
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
finishObservation(scope, metadata) {
|
|
4428
|
+
try {
|
|
4429
|
+
scope?.finish(metadata);
|
|
4430
|
+
} catch {}
|
|
4253
4431
|
}
|
|
4254
4432
|
async getOrFetch(key, fetcher, options = {}) {
|
|
4255
|
-
const { refresh = false } = options;
|
|
4433
|
+
const { refresh = false, getObservationMetadata } = options;
|
|
4434
|
+
let observationState = refresh ? "refresh" : "miss";
|
|
4256
4435
|
if (refresh) this.invalidate(key);
|
|
4257
4436
|
else {
|
|
4258
4437
|
const cachedEntry = this.entries.get(key);
|
|
4259
4438
|
if (cachedEntry !== void 0) {
|
|
4260
4439
|
if (cachedEntry.expiresAt > this.now()) {
|
|
4261
4440
|
this.markAsRecentlyUsed(key, cachedEntry);
|
|
4441
|
+
const observationScope = this.startObservation("hit");
|
|
4442
|
+
this.finishObservation(observationScope);
|
|
4262
4443
|
return cachedEntry.value;
|
|
4263
4444
|
}
|
|
4264
4445
|
this.entries.delete(key);
|
|
4446
|
+
observationState = "expired";
|
|
4265
4447
|
}
|
|
4266
4448
|
const inFlightEntry = this.inFlight.get(key);
|
|
4267
|
-
if (inFlightEntry !== void 0)
|
|
4449
|
+
if (inFlightEntry !== void 0) {
|
|
4450
|
+
const observationScope = this.startObservation("inflight_wait");
|
|
4451
|
+
inFlightEntry.promise.then(() => this.finishObservation(observationScope), () => this.finishObservation(observationScope));
|
|
4452
|
+
return inFlightEntry.promise;
|
|
4453
|
+
}
|
|
4268
4454
|
}
|
|
4269
4455
|
const requestId = ++this.requestCounter;
|
|
4456
|
+
const observationScope = this.startObservation(observationState);
|
|
4270
4457
|
const request = (async () => {
|
|
4271
4458
|
try {
|
|
4272
4459
|
const value = await fetcher();
|
|
4273
4460
|
if (this.isCurrentRequest(key, requestId)) this.setValue(key, value);
|
|
4274
4461
|
return value;
|
|
4275
4462
|
} finally {
|
|
4463
|
+
let metadata;
|
|
4464
|
+
try {
|
|
4465
|
+
metadata = getObservationMetadata?.();
|
|
4466
|
+
} catch {
|
|
4467
|
+
metadata = void 0;
|
|
4468
|
+
}
|
|
4469
|
+
this.finishObservation(observationScope, metadata);
|
|
4276
4470
|
if (this.inFlight.get(key)?.requestId === requestId) this.inFlight.delete(key);
|
|
4277
4471
|
}
|
|
4278
4472
|
})();
|
|
@@ -4321,17 +4515,20 @@ var AsyncTtlCache = class {
|
|
|
4321
4515
|
const EXERCISE_TEMPLATE_CATALOG_CACHE_KEY = "exercise-template-catalog";
|
|
4322
4516
|
const EXERCISE_TEMPLATE_CATALOG_CACHE_TTL_MS = 300 * 1e3;
|
|
4323
4517
|
const EXERCISE_TEMPLATE_CATALOG_CACHE_MAX_SIZE = 1;
|
|
4324
|
-
|
|
4325
|
-
function createExerciseTemplateCatalog(hevyClient) {
|
|
4518
|
+
function createExerciseTemplateCatalog(hevyClient, cacheObserver) {
|
|
4326
4519
|
const cache = new AsyncTtlCache({
|
|
4327
4520
|
ttlMs: EXERCISE_TEMPLATE_CATALOG_CACHE_TTL_MS,
|
|
4328
|
-
maxSize: EXERCISE_TEMPLATE_CATALOG_CACHE_MAX_SIZE
|
|
4521
|
+
maxSize: EXERCISE_TEMPLATE_CATALOG_CACHE_MAX_SIZE,
|
|
4522
|
+
observer: cacheObserver
|
|
4329
4523
|
});
|
|
4330
4524
|
return {
|
|
4331
4525
|
get(options = {}) {
|
|
4332
4526
|
const reason = options.refresh ? "explicit-refresh" : cache.size === 0 ? "initial-load" : "ttl-expired";
|
|
4527
|
+
let observationMetadata;
|
|
4528
|
+
let pagesLoaded = 0;
|
|
4333
4529
|
return cache.getOrFetch(EXERCISE_TEMPLATE_CATALOG_CACHE_KEY, async () => {
|
|
4334
4530
|
const catalog = await fetchAllPages(async (page, pageSize) => {
|
|
4531
|
+
pagesLoaded = page;
|
|
4335
4532
|
const data = await hevyClient.getExerciseTemplates({
|
|
4336
4533
|
page,
|
|
4337
4534
|
pageSize
|
|
@@ -4341,9 +4538,17 @@ function createExerciseTemplateCatalog(hevyClient) {
|
|
|
4341
4538
|
pageCount: data?.page_count
|
|
4342
4539
|
};
|
|
4343
4540
|
}, 100);
|
|
4541
|
+
observationMetadata = {
|
|
4542
|
+
refreshReason: reason,
|
|
4543
|
+
pageCountBucket: bucketCount(pagesLoaded),
|
|
4544
|
+
itemCountBucket: bucketCount(catalog.length)
|
|
4545
|
+
};
|
|
4344
4546
|
options.onRefreshed?.(catalog, reason);
|
|
4345
4547
|
return catalog;
|
|
4346
|
-
},
|
|
4548
|
+
}, {
|
|
4549
|
+
refresh: options.refresh,
|
|
4550
|
+
getObservationMetadata: () => observationMetadata
|
|
4551
|
+
});
|
|
4347
4552
|
},
|
|
4348
4553
|
reset() {
|
|
4349
4554
|
cache.clear();
|
|
@@ -4405,7 +4610,7 @@ function createHevyMcpServer(options) {
|
|
|
4405
4610
|
const client = options.createClient({ onLog: (event) => mcpLogger(event) });
|
|
4406
4611
|
const runtime = createToolRuntime({
|
|
4407
4612
|
client,
|
|
4408
|
-
catalog: createExerciseTemplateCatalog(client),
|
|
4613
|
+
catalog: createExerciseTemplateCatalog(client, options.cacheObserver),
|
|
4409
4614
|
logger: mcpLogger,
|
|
4410
4615
|
observer: options.observer
|
|
4411
4616
|
});
|
|
@@ -4530,13 +4735,15 @@ function extractMcpClientMetadata(message) {
|
|
|
4530
4735
|
protocolVersion: normalizeMetadata(params.protocolVersion)
|
|
4531
4736
|
};
|
|
4532
4737
|
}
|
|
4533
|
-
function createMcpSessionContext(message, transport = "stdio") {
|
|
4738
|
+
function createMcpSessionContext(message, transport = "stdio", options = {}) {
|
|
4739
|
+
const normalizedOptions = typeof options === "function" ? { generateTelemetrySessionId: options } : options;
|
|
4534
4740
|
return {
|
|
4535
4741
|
metadata: extractMcpClientMetadata(message),
|
|
4536
|
-
startedAt: Date.now(),
|
|
4742
|
+
startedAt: (normalizedOptions.now ?? Date.now)(),
|
|
4537
4743
|
toolCalls: 0,
|
|
4538
4744
|
hadToolFailure: false,
|
|
4539
|
-
transport
|
|
4745
|
+
transport,
|
|
4746
|
+
telemetrySessionId: normalizedOptions.telemetrySessionId ?? (normalizedOptions.generateTelemetrySessionId ?? randomUUID)()
|
|
4540
4747
|
};
|
|
4541
4748
|
}
|
|
4542
4749
|
function runWithMcpSessionContext(context, callback) {
|
|
@@ -4545,6 +4752,9 @@ function runWithMcpSessionContext(context, callback) {
|
|
|
4545
4752
|
function getSession() {
|
|
4546
4753
|
return contextStorage.getStore() ?? activeStdioSession;
|
|
4547
4754
|
}
|
|
4755
|
+
function getCurrentMcpSessionId() {
|
|
4756
|
+
return getSession()?.telemetrySessionId;
|
|
4757
|
+
}
|
|
4548
4758
|
function metadataAttributes(metadata, transport) {
|
|
4549
4759
|
return {
|
|
4550
4760
|
client_name: metadata.name,
|
|
@@ -4985,6 +5195,38 @@ function debugLog(event, data) {
|
|
|
4985
5195
|
}
|
|
4986
5196
|
//#endregion
|
|
4987
5197
|
//#region src/utils/hevy-client-observability.ts
|
|
5198
|
+
function safeErrorAttributes(observation) {
|
|
5199
|
+
if (!observation.error) return {};
|
|
5200
|
+
const code = observation.error.code;
|
|
5201
|
+
return {
|
|
5202
|
+
error_category: observation.error.category ?? "HevyHttpError",
|
|
5203
|
+
...code && SAFE_OBSERVATION_CODES.has(code) ? { error_code: code } : {}
|
|
5204
|
+
};
|
|
5205
|
+
}
|
|
5206
|
+
function startApiSpan(start) {
|
|
5207
|
+
const sessionId = getCurrentMcpSessionId();
|
|
5208
|
+
return tracer.startSpan(`hevy.api.${start.method}`, { attributes: {
|
|
5209
|
+
"mcp.span.category": "api",
|
|
5210
|
+
"http.request.method": start.method,
|
|
5211
|
+
"hevy.api.endpoint": start.endpoint,
|
|
5212
|
+
"hevy.api.retry_count_bucket": bucketCount(start.retryCount),
|
|
5213
|
+
"mcp.transport": getCurrentMcpTransport(),
|
|
5214
|
+
...sessionId ? { "mcp.session.id": sessionId } : {}
|
|
5215
|
+
} });
|
|
5216
|
+
}
|
|
5217
|
+
function finishApiSpan(span, observation) {
|
|
5218
|
+
const errorAttributes = safeErrorAttributes(observation);
|
|
5219
|
+
if (observation.status > 0) span.setAttribute("http.response.status_code", observation.status);
|
|
5220
|
+
span.setAttribute("hevy.api.outcome", observation.outcome);
|
|
5221
|
+
if (observation.expectedReason) span.setAttribute("hevy.api.expected_reason", observation.expectedReason);
|
|
5222
|
+
for (const [key, value] of Object.entries(errorAttributes)) span.setAttribute(key, value);
|
|
5223
|
+
span.setStatus({ code: observation.outcome === "success" || observation.outcome === "expected" ? SpanStatusCode.OK : SpanStatusCode.ERROR });
|
|
5224
|
+
if (observation.outcome !== "success" && observation.outcome !== "expected") span.addEvent("hevy.api.failure", {
|
|
5225
|
+
"error.category": errorAttributes.error_category ?? "HevyHttpError",
|
|
5226
|
+
...errorAttributes.error_code ? { "error.code": errorAttributes.error_code } : {}
|
|
5227
|
+
});
|
|
5228
|
+
span.end();
|
|
5229
|
+
}
|
|
4988
5230
|
/** Node-only adapter; the Worker graph never imports telemetry or metrics. */
|
|
4989
5231
|
function createNodeHevyClientOptions() {
|
|
4990
5232
|
const rawTimeout = process.env.HEVY_MCP_API_TIMEOUT;
|
|
@@ -4992,37 +5234,38 @@ function createNodeHevyClientOptions() {
|
|
|
4992
5234
|
const timeoutMs = Number.isFinite(parsedTimeout) && parsedTimeout > 0 ? Math.floor(parsedTimeout) : void 0;
|
|
4993
5235
|
return {
|
|
4994
5236
|
...timeoutMs ? { timeoutMs } : {},
|
|
5237
|
+
onRequestStart(start) {
|
|
5238
|
+
const span = startApiSpan(start);
|
|
5239
|
+
return {
|
|
5240
|
+
run(operation) {
|
|
5241
|
+
return context.with(trace.setSpan(context.active(), span), operation);
|
|
5242
|
+
},
|
|
5243
|
+
finish(observation) {
|
|
5244
|
+
finishApiSpan(span, observation);
|
|
5245
|
+
}
|
|
5246
|
+
};
|
|
5247
|
+
},
|
|
4995
5248
|
onRequestComplete(observation) {
|
|
4996
5249
|
const retryCountBucket = bucketCount(observation.retryCount);
|
|
4997
|
-
const
|
|
4998
|
-
const safeErrorAttributes = diagnostic ? {
|
|
4999
|
-
error_category: diagnostic.category,
|
|
5000
|
-
...diagnostic.code ? { error_code: diagnostic.code } : {}
|
|
5001
|
-
} : {};
|
|
5002
|
-
const span = tracer.startSpan(`hevy.api.${observation.method}`, { attributes: {
|
|
5003
|
-
"http.method": observation.method,
|
|
5004
|
-
"http.status_code": observation.status,
|
|
5005
|
-
"hevy.api.retry_count_bucket": retryCountBucket,
|
|
5006
|
-
"hevy.api.endpoint": observation.endpoint
|
|
5007
|
-
} });
|
|
5008
|
-
span.setStatus({ code: observation.error ? SpanStatusCode.ERROR : SpanStatusCode.OK });
|
|
5009
|
-
if (diagnostic) span.addEvent("hevy.api.failure", {
|
|
5010
|
-
"error.category": diagnostic.category,
|
|
5011
|
-
...diagnostic.code ? { "error.code": diagnostic.code } : {}
|
|
5012
|
-
});
|
|
5013
|
-
span.end();
|
|
5250
|
+
const errorAttributes = safeErrorAttributes(observation);
|
|
5014
5251
|
apiCalls.add(1, {
|
|
5015
5252
|
method: observation.method,
|
|
5016
5253
|
endpoint: observation.endpoint,
|
|
5017
5254
|
status_code: observation.status,
|
|
5018
5255
|
retry_count_bucket: retryCountBucket,
|
|
5019
|
-
|
|
5256
|
+
outcome: observation.outcome,
|
|
5257
|
+
transport: getCurrentMcpTransport(),
|
|
5258
|
+
...observation.expectedReason ? { expected_reason: observation.expectedReason } : {},
|
|
5259
|
+
...errorAttributes
|
|
5020
5260
|
});
|
|
5021
5261
|
apiDuration.record(observation.durationMs, {
|
|
5022
5262
|
method: observation.method,
|
|
5023
5263
|
endpoint: observation.endpoint,
|
|
5024
5264
|
retry_count_bucket: retryCountBucket,
|
|
5025
|
-
|
|
5265
|
+
outcome: observation.outcome,
|
|
5266
|
+
transport: getCurrentMcpTransport(),
|
|
5267
|
+
...observation.expectedReason ? { expected_reason: observation.expectedReason } : {},
|
|
5268
|
+
...errorAttributes
|
|
5026
5269
|
});
|
|
5027
5270
|
debugLog("api_response", {
|
|
5028
5271
|
method: observation.method,
|
|
@@ -5030,13 +5273,43 @@ function createNodeHevyClientOptions() {
|
|
|
5030
5273
|
durationMs: observation.durationMs,
|
|
5031
5274
|
status: observation.status || null,
|
|
5032
5275
|
retryCountBucket,
|
|
5033
|
-
|
|
5276
|
+
outcome: observation.outcome,
|
|
5277
|
+
...errorAttributes
|
|
5034
5278
|
});
|
|
5279
|
+
},
|
|
5280
|
+
onRetryWait(observation) {
|
|
5281
|
+
const sessionId = getCurrentMcpSessionId();
|
|
5282
|
+
const span = tracer.startSpan("hevy.api.retry_wait", { attributes: {
|
|
5283
|
+
"mcp.span.category": "api",
|
|
5284
|
+
"http.request.method": observation.method,
|
|
5285
|
+
"hevy.api.retry_count_bucket": bucketCount(observation.retryCount),
|
|
5286
|
+
"hevy.api.retry_wait_ms": Math.max(0, Math.min(5e3, observation.delayMs)),
|
|
5287
|
+
...sessionId ? { "mcp.session.id": sessionId } : {}
|
|
5288
|
+
} });
|
|
5289
|
+
return { finish: () => span.end() };
|
|
5035
5290
|
}
|
|
5036
5291
|
};
|
|
5037
5292
|
}
|
|
5293
|
+
function createNodeCacheObserver() {
|
|
5294
|
+
return { start(observation) {
|
|
5295
|
+
const sessionId = getCurrentMcpSessionId();
|
|
5296
|
+
const span = tracer.startSpan(`hevy.cache.${observation.state}`, { attributes: {
|
|
5297
|
+
"mcp.span.category": "cache",
|
|
5298
|
+
"hevy.cache.state": observation.state,
|
|
5299
|
+
"mcp.transport": getCurrentMcpTransport(),
|
|
5300
|
+
...sessionId ? { "mcp.session.id": sessionId } : {}
|
|
5301
|
+
} });
|
|
5302
|
+
return { finish(metadata) {
|
|
5303
|
+
if (metadata?.refreshReason) span.setAttribute("hevy.cache.refresh_reason", metadata.refreshReason);
|
|
5304
|
+
if (metadata?.pageCountBucket) span.setAttribute("hevy.cache.page_count_bucket", metadata.pageCountBucket);
|
|
5305
|
+
if (metadata?.itemCountBucket) span.setAttribute("hevy.cache.item_count_bucket", metadata.itemCountBucket);
|
|
5306
|
+
span.end();
|
|
5307
|
+
} };
|
|
5308
|
+
} };
|
|
5309
|
+
}
|
|
5038
5310
|
//#endregion
|
|
5039
5311
|
//#region src/utils/tool-observer.ts
|
|
5312
|
+
const DISCOVERY_TOOL_NAMES = /* @__PURE__ */ new Set(["search-routines"]);
|
|
5040
5313
|
const WORKFLOW_PAGINATION_RESOURCES = /* @__PURE__ */ new Set([
|
|
5041
5314
|
"workouts",
|
|
5042
5315
|
"bodyMeasurements",
|
|
@@ -5059,8 +5332,11 @@ function metricAttributes(invocation, clientAttributes) {
|
|
|
5059
5332
|
}
|
|
5060
5333
|
function createAttributes(invocation) {
|
|
5061
5334
|
const clientMetadata = getCurrentMcpClientMetadata();
|
|
5335
|
+
const isPrompt = invocation.kind === "prompt";
|
|
5336
|
+
const sessionId = getCurrentMcpSessionId();
|
|
5062
5337
|
const attributes = {
|
|
5063
|
-
|
|
5338
|
+
"mcp.span.category": DISCOVERY_TOOL_NAMES.has(invocation.name) ? "discovery" : "tool",
|
|
5339
|
+
[isPrompt ? "mcp.prompt.name" : "mcp.tool.name"]: invocation.name,
|
|
5064
5340
|
"mcp.operation.kind": invocation.kind ?? "tool",
|
|
5065
5341
|
...taxonomyAttributes(invocation),
|
|
5066
5342
|
"mcp.client.name": clientMetadata.name,
|
|
@@ -5068,7 +5344,8 @@ function createAttributes(invocation) {
|
|
|
5068
5344
|
"mcp.protocol.version": clientMetadata.protocolVersion,
|
|
5069
5345
|
"mcp.transport": getCurrentMcpTransport(),
|
|
5070
5346
|
"mcp.tool.args.key_count_bucket": invocation.argumentKeyCountBucket ?? "unknown",
|
|
5071
|
-
"mcp.tool.args.keys": invocation.argumentKeys?.join(",") ?? ""
|
|
5347
|
+
"mcp.tool.args.keys": invocation.argumentKeys?.join(",") ?? "",
|
|
5348
|
+
...sessionId ? { "mcp.session.id": sessionId } : {}
|
|
5072
5349
|
};
|
|
5073
5350
|
for (const key of Object.keys(invocation.argumentPresence ?? {})) attributes[`mcp.tool.args.${key}.present`] = true;
|
|
5074
5351
|
for (const [key, bucket] of Object.entries(invocation.numericArgumentBuckets ?? {})) if (bucket !== void 0) attributes[`mcp.tool.args.${key}.bucket`] = bucket;
|
|
@@ -5106,14 +5383,16 @@ function resultMetricAttributes(completion) {
|
|
|
5106
5383
|
if (summary?.setCountBucket) attributes["mcp.tool.result.set_count_bucket"] = summary.setCountBucket;
|
|
5107
5384
|
return attributes;
|
|
5108
5385
|
}
|
|
5109
|
-
function setSafeErrorAttributes(span, completion) {
|
|
5386
|
+
function setSafeErrorAttributes(span, invocation, completion) {
|
|
5110
5387
|
const diagnostic = completion.error;
|
|
5111
5388
|
const errorType = completion.errorType ?? "UNKNOWN_ERROR";
|
|
5112
5389
|
if (diagnostic) span.addEvent("mcp.tool.failure", {
|
|
5390
|
+
"mcp.tool.name": invocation.name,
|
|
5391
|
+
"error.type": errorType,
|
|
5113
5392
|
"error.category": diagnostic.category,
|
|
5114
5393
|
...diagnostic.code ? { "error.code": diagnostic.code } : {},
|
|
5115
|
-
...diagnostic.status !== void 0 ? { "http.status_code": diagnostic.status } : {},
|
|
5116
|
-
...diagnostic.method ? { "http.method": diagnostic.method } : {},
|
|
5394
|
+
...diagnostic.status !== void 0 ? { "http.response.status_code": diagnostic.status } : {},
|
|
5395
|
+
...diagnostic.method ? { "http.request.method": diagnostic.method } : {},
|
|
5117
5396
|
...diagnostic.endpoint ? { "hevy.api.endpoint": diagnostic.endpoint } : {}
|
|
5118
5397
|
});
|
|
5119
5398
|
span.setAttribute("error.type", errorType);
|
|
@@ -5130,8 +5409,8 @@ function captureSafeToolFailure(invocation, completion) {
|
|
|
5130
5409
|
scope.setTag("error.type", completion.errorType ?? "UNKNOWN_ERROR");
|
|
5131
5410
|
scope.setTag("error.category", category);
|
|
5132
5411
|
if (diagnostic?.code) scope.setTag("error.code", diagnostic.code);
|
|
5133
|
-
if (diagnostic?.status !== void 0) scope.setTag("http.status_code", String(diagnostic.status));
|
|
5134
|
-
if (diagnostic?.method) scope.setTag("http.method", diagnostic.method);
|
|
5412
|
+
if (diagnostic?.status !== void 0) scope.setTag("http.response.status_code", String(diagnostic.status));
|
|
5413
|
+
if (diagnostic?.method) scope.setTag("http.request.method", diagnostic.method);
|
|
5135
5414
|
if (diagnostic?.endpoint) scope.setTag("hevy.api.endpoint", diagnostic.endpoint);
|
|
5136
5415
|
scope.setContext(invocation.kind === "prompt" ? "mcpPrompt" : "mcpTool", {
|
|
5137
5416
|
context: invocation.name,
|
|
@@ -5185,26 +5464,35 @@ function createNodeToolObserver() {
|
|
|
5185
5464
|
activeSpan.setAttribute("mcp.tool.outcome", nextCompletion.outcome);
|
|
5186
5465
|
setResultAttributes(activeSpan, nextCompletion);
|
|
5187
5466
|
if (nextCompletion.outcome === "thrown_error") {
|
|
5188
|
-
errorType = setSafeErrorAttributes(activeSpan, nextCompletion);
|
|
5467
|
+
errorType = setSafeErrorAttributes(activeSpan, invocation, nextCompletion);
|
|
5189
5468
|
bestEffort(() => recordTelemetryException(new Error(nextCompletion.error?.category ?? errorType), {
|
|
5190
5469
|
[invocation.kind === "prompt" ? "mcp.prompt.name" : "mcp.tool.name"]: invocation.name,
|
|
5191
5470
|
"error.type": errorType ?? "UNKNOWN_ERROR",
|
|
5192
5471
|
"error.category": nextCompletion.error?.category ?? "UnknownError",
|
|
5193
5472
|
...nextCompletion.error?.code ? { "error.code": nextCompletion.error.code } : {},
|
|
5194
|
-
...nextCompletion.error?.status !== void 0 ? { "http.status_code": nextCompletion.error.status } : {},
|
|
5195
|
-
...nextCompletion.error?.method ? { "http.method": nextCompletion.error.method } : {},
|
|
5473
|
+
...nextCompletion.error?.status !== void 0 ? { "http.response.status_code": nextCompletion.error.status } : {},
|
|
5474
|
+
...nextCompletion.error?.method ? { "http.request.method": nextCompletion.error.method } : {},
|
|
5475
|
+
...nextCompletion.error?.endpoint ? { "hevy.api.endpoint": nextCompletion.error.endpoint } : {}
|
|
5476
|
+
}, activeSpan));
|
|
5477
|
+
}
|
|
5478
|
+
if (nextCompletion.outcome === "returned_error") {
|
|
5479
|
+
activeSpan.addEvent("mcp.tool.failure", {
|
|
5480
|
+
"mcp.tool.name": invocation.name,
|
|
5481
|
+
"error.type": "UNKNOWN_ERROR",
|
|
5482
|
+
"error.category": "McpToolReturnedError",
|
|
5483
|
+
"error.code": "MCP_TOOL_RETURNED_ERROR"
|
|
5484
|
+
});
|
|
5485
|
+
activeSpan.setAttribute("error.type", "UNKNOWN_ERROR");
|
|
5486
|
+
bestEffort(() => recordTelemetryException(new Error(nextCompletion.error?.category ?? "UnknownError"), {
|
|
5487
|
+
[invocation.kind === "prompt" ? "mcp.prompt.name" : "mcp.tool.name"]: invocation.name,
|
|
5488
|
+
"error.type": nextCompletion.errorType ?? "UNKNOWN_ERROR",
|
|
5489
|
+
"error.category": nextCompletion.error?.category ?? "UnknownError",
|
|
5490
|
+
...nextCompletion.error?.code ? { "error.code": nextCompletion.error.code } : {},
|
|
5491
|
+
...nextCompletion.error?.status !== void 0 ? { "http.response.status_code": nextCompletion.error.status } : {},
|
|
5492
|
+
...nextCompletion.error?.method ? { "http.request.method": nextCompletion.error.method } : {},
|
|
5196
5493
|
...nextCompletion.error?.endpoint ? { "hevy.api.endpoint": nextCompletion.error.endpoint } : {}
|
|
5197
5494
|
}, activeSpan));
|
|
5198
5495
|
}
|
|
5199
|
-
if (nextCompletion.outcome === "returned_error") bestEffort(() => recordTelemetryException(new Error(nextCompletion.error?.category ?? "UnknownError"), {
|
|
5200
|
-
[invocation.kind === "prompt" ? "mcp.prompt.name" : "mcp.tool.name"]: invocation.name,
|
|
5201
|
-
"error.type": nextCompletion.errorType ?? "UNKNOWN_ERROR",
|
|
5202
|
-
"error.category": nextCompletion.error?.category ?? "UnknownError",
|
|
5203
|
-
...nextCompletion.error?.code ? { "error.code": nextCompletion.error.code } : {},
|
|
5204
|
-
...nextCompletion.error?.status !== void 0 ? { "http.status_code": nextCompletion.error.status } : {},
|
|
5205
|
-
...nextCompletion.error?.method ? { "http.method": nextCompletion.error.method } : {},
|
|
5206
|
-
...nextCompletion.error?.endpoint ? { "hevy.api.endpoint": nextCompletion.error.endpoint } : {}
|
|
5207
|
-
}, activeSpan));
|
|
5208
5496
|
}
|
|
5209
5497
|
} catch {} finally {
|
|
5210
5498
|
if (nextCompletion.outcome === "thrown_error") {
|
|
@@ -5385,14 +5673,17 @@ function deserializeMessageWithObservability(line, chunkSnapshot) {
|
|
|
5385
5673
|
const lineHadLeadingBom = line.startsWith(UTF8_BOM);
|
|
5386
5674
|
const normalizedLine = lineHadLeadingBom ? line.slice(1) : line;
|
|
5387
5675
|
const lineByteLength = Buffer.byteLength(line);
|
|
5676
|
+
const sessionId = getCurrentMcpSessionId();
|
|
5388
5677
|
return tracer.startActiveSpan("mcp.stdio.deserialize", { attributes: {
|
|
5678
|
+
"mcp.span.category": "protocol",
|
|
5389
5679
|
"mcp.transport": "stdio",
|
|
5390
5680
|
"mcp.stdio.parse.line.char_length": line.length,
|
|
5391
5681
|
"mcp.stdio.parse.line.byte_length": lineByteLength,
|
|
5392
5682
|
"mcp.stdio.parse.line.had_leading_bom": lineHadLeadingBom,
|
|
5393
5683
|
"mcp.stdio.parse.line.bom_stripped": lineHadLeadingBom,
|
|
5394
5684
|
"mcp.stdio.parse.chunk.last_byte_length": chunkSnapshot.lastChunkByteLength,
|
|
5395
|
-
"mcp.stdio.parse.chunk.last_had_utf8_bom": chunkSnapshot.lastChunkStartsWithUtf8Bom
|
|
5685
|
+
"mcp.stdio.parse.chunk.last_had_utf8_bom": chunkSnapshot.lastChunkStartsWithUtf8Bom,
|
|
5686
|
+
...sessionId ? { "mcp.session.id": sessionId } : {}
|
|
5396
5687
|
} }, (span) => {
|
|
5397
5688
|
try {
|
|
5398
5689
|
const message = deserializeMessage(normalizedLine);
|
|
@@ -5402,6 +5693,8 @@ function deserializeMessageWithObservability(line, chunkSnapshot) {
|
|
|
5402
5693
|
if (typeof method === "string" && SAFE_MCP_METHODS[method] === true) span.setAttribute("mcp.method", method);
|
|
5403
5694
|
if (method === "initialize") {
|
|
5404
5695
|
const client = recordMcpSessionStart(message);
|
|
5696
|
+
const sessionId = getCurrentMcpSessionId();
|
|
5697
|
+
if (sessionId) span.setAttribute("mcp.session.id", sessionId);
|
|
5405
5698
|
span.setAttribute("mcp.client.name", client.name);
|
|
5406
5699
|
span.setAttribute("mcp.client.version", client.version);
|
|
5407
5700
|
span.setAttribute("mcp.protocol.version", client.protocolVersion);
|
|
@@ -5440,6 +5733,177 @@ function createInstrumentedStdioTransport(transport) {
|
|
|
5440
5733
|
return transport;
|
|
5441
5734
|
}
|
|
5442
5735
|
//#endregion
|
|
5736
|
+
//#region src/utils/sdk-observability.ts
|
|
5737
|
+
const sdkToolNameStorage = new AsyncLocalStorage();
|
|
5738
|
+
const SAFE_TOOL_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
|
|
5739
|
+
const SDK_FAILURE_TAXONOMY = {
|
|
5740
|
+
protocol: {
|
|
5741
|
+
errorType: "UNKNOWN_ERROR",
|
|
5742
|
+
errorCategory: "McpSdkProtocolFailure"
|
|
5743
|
+
},
|
|
5744
|
+
tool_call: {
|
|
5745
|
+
errorType: "UNKNOWN_ERROR",
|
|
5746
|
+
errorCategory: "McpSdkToolCallFailure"
|
|
5747
|
+
},
|
|
5748
|
+
validation: {
|
|
5749
|
+
errorType: "VALIDATION_ERROR",
|
|
5750
|
+
errorCategory: "McpSdkValidationFailure"
|
|
5751
|
+
}
|
|
5752
|
+
};
|
|
5753
|
+
function createFailureAttributes(error, phase, errorType, errorCategory) {
|
|
5754
|
+
const diagnostic = createSafeErrorDiagnostic(error);
|
|
5755
|
+
return {
|
|
5756
|
+
"mcp.failure.phase": phase,
|
|
5757
|
+
"error.type": errorType,
|
|
5758
|
+
"error.category": errorCategory,
|
|
5759
|
+
...diagnostic.code ? { "error.code": diagnostic.code } : {},
|
|
5760
|
+
...diagnostic.status !== void 0 ? { "http.response.status_code": diagnostic.status } : {},
|
|
5761
|
+
...diagnostic.method ? { "http.request.method": diagnostic.method } : {},
|
|
5762
|
+
...diagnostic.endpoint ? { "hevy.api.endpoint": diagnostic.endpoint } : {}
|
|
5763
|
+
};
|
|
5764
|
+
}
|
|
5765
|
+
function enrichActiveSdkSpan(attributes) {
|
|
5766
|
+
try {
|
|
5767
|
+
trace.getActiveSpan()?.setAttributes(attributes);
|
|
5768
|
+
} catch {}
|
|
5769
|
+
}
|
|
5770
|
+
function getSdkToolName(request) {
|
|
5771
|
+
if (!request || typeof request !== "object") return "unknown";
|
|
5772
|
+
const candidate = request.params?.name;
|
|
5773
|
+
return typeof candidate === "string" && SAFE_TOOL_NAME_PATTERN.test(candidate) ? candidate : "unknown";
|
|
5774
|
+
}
|
|
5775
|
+
function markSdkToolFailure(span, error, toolName = sdkToolNameStorage.getStore() ?? "unknown", kind = "tool_call") {
|
|
5776
|
+
const taxonomy = SDK_FAILURE_TAXONOMY[kind];
|
|
5777
|
+
const attributes = createFailureAttributes(error, "sdk", taxonomy.errorType, taxonomy.errorCategory);
|
|
5778
|
+
span.addEvent("mcp.tool.failure", {
|
|
5779
|
+
"mcp.tool.name": toolName,
|
|
5780
|
+
...attributes
|
|
5781
|
+
});
|
|
5782
|
+
span.setAttribute("error.type", taxonomy.errorType);
|
|
5783
|
+
recordTelemetryException(error, attributes, span);
|
|
5784
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
5785
|
+
}
|
|
5786
|
+
function installProtocolErrorTracking(server, transport) {
|
|
5787
|
+
const protocol = server.server;
|
|
5788
|
+
if (!protocol) return;
|
|
5789
|
+
const previous = protocol.onerror;
|
|
5790
|
+
protocol.onerror = (error) => {
|
|
5791
|
+
const taxonomy = SDK_FAILURE_TAXONOMY.protocol;
|
|
5792
|
+
const attributes = createFailureAttributes(error, "sdk", taxonomy.errorType, taxonomy.errorCategory);
|
|
5793
|
+
const activeSpan = trace.getActiveSpan();
|
|
5794
|
+
const sessionId = getCurrentMcpSessionId();
|
|
5795
|
+
if (activeSpan) {
|
|
5796
|
+
activeSpan.addEvent("mcp.tool.failure", {
|
|
5797
|
+
"mcp.tool.name": sdkToolNameStorage.getStore() ?? "unknown",
|
|
5798
|
+
...attributes
|
|
5799
|
+
});
|
|
5800
|
+
recordTelemetryException(error, attributes, activeSpan);
|
|
5801
|
+
} else tracer.startActiveSpan("mcp.sdk.failure", { attributes: {
|
|
5802
|
+
"mcp.span.category": "protocol",
|
|
5803
|
+
"mcp.transport": transport,
|
|
5804
|
+
...sessionId ? { "mcp.session.id": sessionId } : {}
|
|
5805
|
+
} }, (span) => {
|
|
5806
|
+
span.addEvent("mcp.tool.failure", {
|
|
5807
|
+
"mcp.tool.name": "unknown",
|
|
5808
|
+
...attributes
|
|
5809
|
+
});
|
|
5810
|
+
recordTelemetryException(error, attributes, span);
|
|
5811
|
+
span.end();
|
|
5812
|
+
});
|
|
5813
|
+
try {
|
|
5814
|
+
previous?.(error);
|
|
5815
|
+
} catch {}
|
|
5816
|
+
};
|
|
5817
|
+
}
|
|
5818
|
+
function installValidationErrorTracking(server) {
|
|
5819
|
+
const toolErrorHost = server;
|
|
5820
|
+
const previousCreateToolError = toolErrorHost.createToolError;
|
|
5821
|
+
if (!previousCreateToolError) return;
|
|
5822
|
+
const createToolError = previousCreateToolError.bind(server);
|
|
5823
|
+
toolErrorHost.createToolError = (message) => {
|
|
5824
|
+
const result = createToolError(message);
|
|
5825
|
+
const activeSpan = trace.getActiveSpan();
|
|
5826
|
+
if (activeSpan) markSdkToolFailure(activeSpan, /* @__PURE__ */ new Error("MCP tool validation failed"), void 0, "validation");
|
|
5827
|
+
return result;
|
|
5828
|
+
};
|
|
5829
|
+
}
|
|
5830
|
+
function installInitializeEnrichment(handlers, transport) {
|
|
5831
|
+
const initializeHandler = handlers.get("initialize");
|
|
5832
|
+
if (!initializeHandler) return;
|
|
5833
|
+
handlers.set("initialize", (request, extra) => {
|
|
5834
|
+
enrichActiveSdkSpan({
|
|
5835
|
+
"mcp.span.category": "protocol",
|
|
5836
|
+
"mcp.transport": transport
|
|
5837
|
+
});
|
|
5838
|
+
return initializeHandler(request, extra);
|
|
5839
|
+
});
|
|
5840
|
+
}
|
|
5841
|
+
function installToolCallTracking(handlers, transport) {
|
|
5842
|
+
const toolHandler = handlers.get("tools/call");
|
|
5843
|
+
if (!toolHandler) return;
|
|
5844
|
+
handlers.set("tools/call", (request, extra) => {
|
|
5845
|
+
const sessionId = getCurrentMcpSessionId();
|
|
5846
|
+
const toolName = getSdkToolName(request);
|
|
5847
|
+
const attributes = {
|
|
5848
|
+
"mcp.span.category": "protocol",
|
|
5849
|
+
"mcp.transport": transport,
|
|
5850
|
+
"mcp.operation.kind": "tool",
|
|
5851
|
+
"mcp.tool.name": toolName,
|
|
5852
|
+
...sessionId ? { "mcp.session.id": sessionId } : {}
|
|
5853
|
+
};
|
|
5854
|
+
enrichActiveSdkSpan(attributes);
|
|
5855
|
+
return sdkToolNameStorage.run(toolName, () => tracer.startActiveSpan("mcp.sdk.tools.call", { attributes }, async (span) => {
|
|
5856
|
+
try {
|
|
5857
|
+
const result = await toolHandler(request, extra);
|
|
5858
|
+
if (result?.isError === true) {
|
|
5859
|
+
span.setAttribute("mcp.tool.outcome", "returned_error");
|
|
5860
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
5861
|
+
} else span.setStatus({ code: SpanStatusCode.OK });
|
|
5862
|
+
return result;
|
|
5863
|
+
} catch (error) {
|
|
5864
|
+
markSdkToolFailure(span, error, toolName);
|
|
5865
|
+
throw error;
|
|
5866
|
+
} finally {
|
|
5867
|
+
span.end();
|
|
5868
|
+
}
|
|
5869
|
+
}));
|
|
5870
|
+
});
|
|
5871
|
+
}
|
|
5872
|
+
function installDiscoveryTracking(handlers) {
|
|
5873
|
+
const discoveryHandler = handlers.get("server/discover");
|
|
5874
|
+
if (!discoveryHandler) return;
|
|
5875
|
+
handlers.set("server/discover", (request, extra) => {
|
|
5876
|
+
const sessionId = getCurrentMcpSessionId();
|
|
5877
|
+
return tracer.startActiveSpan("mcp.server.discover", { attributes: {
|
|
5878
|
+
"mcp.span.category": "discovery",
|
|
5879
|
+
"mcp.transport": getCurrentMcpTransport(),
|
|
5880
|
+
...sessionId ? { "mcp.session.id": sessionId } : {}
|
|
5881
|
+
} }, async (span) => {
|
|
5882
|
+
try {
|
|
5883
|
+
const result = await discoveryHandler(request, extra);
|
|
5884
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
5885
|
+
return result;
|
|
5886
|
+
} catch (error) {
|
|
5887
|
+
const attributes = createFailureAttributes(error, "discovery", "UNKNOWN_ERROR", "McpServerDiscoveryFailure");
|
|
5888
|
+
span.addEvent("mcp.discovery.failure", attributes);
|
|
5889
|
+
recordTelemetryException(error, attributes, span);
|
|
5890
|
+
throw error;
|
|
5891
|
+
} finally {
|
|
5892
|
+
span.end();
|
|
5893
|
+
}
|
|
5894
|
+
});
|
|
5895
|
+
});
|
|
5896
|
+
}
|
|
5897
|
+
function installSdkErrorTracking(server, transport) {
|
|
5898
|
+
installProtocolErrorTracking(server, transport);
|
|
5899
|
+
installValidationErrorTracking(server);
|
|
5900
|
+
const handlers = server.server?._requestHandlers;
|
|
5901
|
+
if (!(handlers instanceof Map)) return;
|
|
5902
|
+
installInitializeEnrichment(handlers, transport);
|
|
5903
|
+
installToolCallTracking(handlers, transport);
|
|
5904
|
+
installDiscoveryTracking(handlers);
|
|
5905
|
+
}
|
|
5906
|
+
//#endregion
|
|
5443
5907
|
//#region src/utils/version-check.ts
|
|
5444
5908
|
const CACHE_TTL_MS = 1440 * 60 * 1e3;
|
|
5445
5909
|
const REQUEST_TIMEOUT_MS = 3e3;
|
|
@@ -5568,8 +6032,8 @@ function scheduleUpdateCheck(options, overrides = {}) {
|
|
|
5568
6032
|
}
|
|
5569
6033
|
//#endregion
|
|
5570
6034
|
//#region src/index.ts
|
|
5571
|
-
const name =
|
|
5572
|
-
const version =
|
|
6035
|
+
const name = serviceName;
|
|
6036
|
+
const version = serviceVersion;
|
|
5573
6037
|
const HELP_TEXT = [
|
|
5574
6038
|
"Usage:",
|
|
5575
6039
|
" hevy-mcp [options]",
|
|
@@ -5629,6 +6093,46 @@ function getSafeValidationDiagnostic(error) {
|
|
|
5629
6093
|
const code = error.code;
|
|
5630
6094
|
return typeof code === "string" && SAFE_NETWORK_ERROR_CODES.has(code) ? code : void 0;
|
|
5631
6095
|
}
|
|
6096
|
+
const LIFECYCLE_FAILURE_TAXONOMY = {
|
|
6097
|
+
config: {
|
|
6098
|
+
errorType: "MCP_SERVER_CONFIG_ERROR",
|
|
6099
|
+
errorCategory: "McpServerConfigFailure"
|
|
6100
|
+
},
|
|
6101
|
+
api_key_validation: {
|
|
6102
|
+
errorType: "MCP_API_KEY_VALIDATION_ERROR",
|
|
6103
|
+
errorCategory: "McpApiKeyValidationFailure"
|
|
6104
|
+
},
|
|
6105
|
+
build: {
|
|
6106
|
+
errorType: "MCP_SERVER_BUILD_ERROR",
|
|
6107
|
+
errorCategory: "McpServerBuildFailure"
|
|
6108
|
+
},
|
|
6109
|
+
connect: {
|
|
6110
|
+
errorType: "MCP_TRANSPORT_CONNECT_ERROR",
|
|
6111
|
+
errorCategory: "McpTransportConnectFailure"
|
|
6112
|
+
},
|
|
6113
|
+
run: {
|
|
6114
|
+
errorType: "MCP_SERVER_RUN_ERROR",
|
|
6115
|
+
errorCategory: "McpServerRunFailure"
|
|
6116
|
+
}
|
|
6117
|
+
};
|
|
6118
|
+
function createLifecycleFailureAttributes(error, phase) {
|
|
6119
|
+
const diagnostic = createSafeErrorDiagnostic(error);
|
|
6120
|
+
const taxonomy = LIFECYCLE_FAILURE_TAXONOMY[phase];
|
|
6121
|
+
return {
|
|
6122
|
+
"mcp.failure.phase": phase,
|
|
6123
|
+
"error.type": taxonomy.errorType,
|
|
6124
|
+
"error.category": taxonomy.errorCategory,
|
|
6125
|
+
...diagnostic.code ? { "error.code": diagnostic.code } : {},
|
|
6126
|
+
...diagnostic.status !== void 0 ? { "http.response.status_code": diagnostic.status } : {},
|
|
6127
|
+
...diagnostic.method ? { "http.request.method": diagnostic.method } : {},
|
|
6128
|
+
...diagnostic.endpoint ? { "hevy.api.endpoint": diagnostic.endpoint } : {}
|
|
6129
|
+
};
|
|
6130
|
+
}
|
|
6131
|
+
function recordLifecycleFailure(span, error, phase) {
|
|
6132
|
+
const attributes = createLifecycleFailureAttributes(error, phase);
|
|
6133
|
+
span.addEvent("mcp.lifecycle.failure", attributes);
|
|
6134
|
+
recordTelemetryException(error, attributes, span);
|
|
6135
|
+
}
|
|
5632
6136
|
async function validateApiKey(apiKey) {
|
|
5633
6137
|
const startupProbeClient = createHevyClient({
|
|
5634
6138
|
apiKey,
|
|
@@ -5647,6 +6151,7 @@ async function validateApiKey(apiKey) {
|
|
|
5647
6151
|
}
|
|
5648
6152
|
function buildServer(apiKey, transport = "stdio") {
|
|
5649
6153
|
return tracer.startActiveSpan("mcp.server.build", { attributes: {
|
|
6154
|
+
"mcp.span.category": "startup",
|
|
5650
6155
|
"mcp.server.name": name,
|
|
5651
6156
|
"mcp.server.version": version,
|
|
5652
6157
|
"mcp.transport": transport
|
|
@@ -5663,12 +6168,15 @@ function buildServer(apiKey, transport = "stdio") {
|
|
|
5663
6168
|
recordOutputs: false
|
|
5664
6169
|
}),
|
|
5665
6170
|
onToolsRegistered: (count) => span.setAttribute("mcp.tools.count", count),
|
|
5666
|
-
observer: createNodeToolObserver()
|
|
6171
|
+
observer: createNodeToolObserver(),
|
|
6172
|
+
cacheObserver: createNodeCacheObserver()
|
|
5667
6173
|
});
|
|
6174
|
+
installSdkErrorTracking(server, transport);
|
|
5668
6175
|
console.error("Hevy client initialized with API key");
|
|
5669
6176
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
5670
6177
|
return server;
|
|
5671
6178
|
} catch (e) {
|
|
6179
|
+
recordLifecycleFailure(span, e, "build");
|
|
5672
6180
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
5673
6181
|
throw e;
|
|
5674
6182
|
} finally {
|
|
@@ -5697,7 +6205,11 @@ async function runStdioServer() {
|
|
|
5697
6205
|
const configuredApiKey = process.env.HEVY_API_KEY;
|
|
5698
6206
|
if (configuredApiKey) setTelemetryUser(configuredApiKey);
|
|
5699
6207
|
let connectAttempted = false;
|
|
5700
|
-
|
|
6208
|
+
let connectSucceeded = false;
|
|
6209
|
+
await tracer.startActiveSpan("mcp.server.run", { attributes: {
|
|
6210
|
+
"mcp.span.category": "startup",
|
|
6211
|
+
"mcp.transport": "stdio"
|
|
6212
|
+
} }, async (span) => {
|
|
5701
6213
|
try {
|
|
5702
6214
|
const apiKey = parseConfig(process.env).apiKey;
|
|
5703
6215
|
assertApiKey(apiKey);
|
|
@@ -5705,11 +6217,16 @@ async function runStdioServer() {
|
|
|
5705
6217
|
console.error("Starting MCP server in stdio mode");
|
|
5706
6218
|
const transport = createInstrumentedStdioTransport(new StdioServerTransport());
|
|
5707
6219
|
connectAttempted = true;
|
|
5708
|
-
await tracer.startActiveSpan("mcp.server.connect", { attributes: {
|
|
6220
|
+
await tracer.startActiveSpan("mcp.server.connect", { attributes: {
|
|
6221
|
+
"mcp.span.category": "session",
|
|
6222
|
+
"mcp.transport": "stdio"
|
|
6223
|
+
} }, async (connectSpan) => {
|
|
5709
6224
|
try {
|
|
5710
6225
|
await server.connect(transport);
|
|
6226
|
+
connectSucceeded = true;
|
|
5711
6227
|
connectSpan.setStatus({ code: SpanStatusCode.OK });
|
|
5712
6228
|
} catch (e) {
|
|
6229
|
+
recordLifecycleFailure(connectSpan, e, "connect");
|
|
5713
6230
|
connectSpan.setStatus({ code: SpanStatusCode.ERROR });
|
|
5714
6231
|
throw e;
|
|
5715
6232
|
} finally {
|
|
@@ -5717,8 +6234,8 @@ async function runStdioServer() {
|
|
|
5717
6234
|
}
|
|
5718
6235
|
});
|
|
5719
6236
|
scheduleUpdateCheck({
|
|
5720
|
-
packageName:
|
|
5721
|
-
currentVersion:
|
|
6237
|
+
packageName: serviceName,
|
|
6238
|
+
currentVersion: serviceVersion
|
|
5722
6239
|
});
|
|
5723
6240
|
installGracefulShutdown({
|
|
5724
6241
|
target: server,
|
|
@@ -5730,6 +6247,7 @@ async function runStdioServer() {
|
|
|
5730
6247
|
});
|
|
5731
6248
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
5732
6249
|
} catch (e) {
|
|
6250
|
+
if (!connectAttempted || connectSucceeded) recordLifecycleFailure(span, e, "run");
|
|
5733
6251
|
recordMcpSessionTermination(connectAttempted ? "connect_failure" : "startup_failure");
|
|
5734
6252
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
5735
6253
|
cleanupProcessExceptionTracking();
|
|
@@ -5756,7 +6274,10 @@ async function runServer() {
|
|
|
5756
6274
|
return;
|
|
5757
6275
|
}
|
|
5758
6276
|
const cleanupProcessExceptionTracking = installProcessExceptionTracking();
|
|
5759
|
-
await tracer.startActiveSpan("mcp.server.run", { attributes: {
|
|
6277
|
+
await tracer.startActiveSpan("mcp.server.run", { attributes: {
|
|
6278
|
+
"mcp.span.category": "startup",
|
|
6279
|
+
"mcp.transport": "http"
|
|
6280
|
+
} }, async (span) => {
|
|
5760
6281
|
let listening = false;
|
|
5761
6282
|
serverStartups.add(1, { version });
|
|
5762
6283
|
try {
|
|
@@ -5768,8 +6289,8 @@ async function runServer() {
|
|
|
5768
6289
|
listening = true;
|
|
5769
6290
|
console.error(`Starting MCP server in HTTP mode at ${options.host}:${options.port}/mcp`);
|
|
5770
6291
|
scheduleUpdateCheck({
|
|
5771
|
-
packageName:
|
|
5772
|
-
currentVersion:
|
|
6292
|
+
packageName: serviceName,
|
|
6293
|
+
currentVersion: serviceVersion
|
|
5773
6294
|
});
|
|
5774
6295
|
installGracefulShutdown({
|
|
5775
6296
|
target: handle,
|
|
@@ -5780,6 +6301,7 @@ async function runServer() {
|
|
|
5780
6301
|
});
|
|
5781
6302
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
5782
6303
|
} catch (error) {
|
|
6304
|
+
recordLifecycleFailure(span, error, "run");
|
|
5783
6305
|
recordMcpSessionTermination(listening ? "unknown" : "startup_failure");
|
|
5784
6306
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
5785
6307
|
cleanupProcessExceptionTracking();
|
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"url": "https://github.com/chrisdoc/hevy-mcp",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "5.0.
|
|
10
|
+
"version": "5.0.5",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "hevy-mcp",
|
|
15
|
-
"version": "5.0.
|
|
15
|
+
"version": "5.0.5",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|