hevy-mcp 5.0.3 → 5.0.4
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-CJRjsomO.mjs} +578 -119
- 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.4" };
|
|
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-CJRjsomO.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.4" };
|
|
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-CJRjsomO.mjs";
|
|
13
13
|
export { createNodeMcpServer, runServer, runStdioServer };
|
|
@@ -4,29 +4,29 @@
|
|
|
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.4" };
|
|
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] = "72e7b08a-987c-4dba-aabd-6437da54ce2f", e._sentryDebugIdIdentifier = "sentry-dbid-72e7b08a-987c-4dba-aabd-6437da54ce2f");
|
|
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";
|
|
21
21
|
import { MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
|
22
22
|
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
23
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
23
24
|
import { ZodError, z } from "zod";
|
|
24
25
|
import { McpServer, deserializeMessage } from "@modelcontextprotocol/server";
|
|
25
26
|
import { z as z$1 } from "zod/v4";
|
|
26
27
|
import { isIP } from "node:net";
|
|
27
28
|
import { createServer } from "node:http";
|
|
28
29
|
import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
|
|
29
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
30
30
|
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
31
31
|
import { homedir } from "node:os";
|
|
32
32
|
import { dirname, join } from "node:path";
|
|
@@ -79,16 +79,55 @@ 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 SAFE_EXCEPTION_TYPES = /* @__PURE__ */ new Set([
|
|
83
|
+
"AggregateError",
|
|
84
|
+
"DOMException",
|
|
85
|
+
"Error",
|
|
86
|
+
"EvalError",
|
|
87
|
+
"HevyHttpError",
|
|
88
|
+
"RangeError",
|
|
89
|
+
"ReferenceError",
|
|
90
|
+
"SyntaxError",
|
|
91
|
+
"TypeError",
|
|
92
|
+
"URIError",
|
|
93
|
+
"ProtocolError",
|
|
94
|
+
"ZodError"
|
|
95
|
+
]);
|
|
96
|
+
const SAFE_EXCEPTION_CODES = /* @__PURE__ */ new Set([
|
|
97
|
+
"EAI_AGAIN",
|
|
98
|
+
"ECONNABORTED",
|
|
99
|
+
"ECONNREFUSED",
|
|
100
|
+
"ECONNRESET",
|
|
101
|
+
"ENETUNREACH",
|
|
102
|
+
"ENOTFOUND",
|
|
103
|
+
"ERR_NETWORK",
|
|
104
|
+
"ERR_SOCKET_TIMEOUT",
|
|
105
|
+
"ETIMEDOUT",
|
|
106
|
+
"HEVY_INVALID_ENDPOINT",
|
|
107
|
+
"HEVY_REQUEST_ABORTED",
|
|
108
|
+
"HEVY_RETRY_EXHAUSTED"
|
|
109
|
+
]);
|
|
82
110
|
function normalizeTelemetryError(error) {
|
|
83
|
-
const
|
|
84
|
-
return
|
|
111
|
+
const candidate = error instanceof Error && typeof error.name === "string" ? error.name : void 0;
|
|
112
|
+
return {
|
|
113
|
+
name: candidate && SAFE_EXCEPTION_TYPES.has(candidate) ? candidate : "UnknownError",
|
|
114
|
+
...error instanceof Error && error.stack ? { stack: error.stack } : {}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function getSafeExceptionCode(error) {
|
|
118
|
+
if (!error || typeof error !== "object" || !("code" in error)) return void 0;
|
|
119
|
+
const code = error.code;
|
|
120
|
+
return typeof code === "string" && SAFE_EXCEPTION_CODES.has(code) ? code : void 0;
|
|
85
121
|
}
|
|
86
122
|
function recordTelemetryException(error, attributes, span) {
|
|
87
123
|
if (!telemetryEnabled) return;
|
|
88
124
|
try {
|
|
89
125
|
const target = span ?? trace.getActiveSpan();
|
|
90
126
|
if (!target) return;
|
|
91
|
-
|
|
127
|
+
const normalized = normalizeTelemetryError(error);
|
|
128
|
+
target.recordException(normalized);
|
|
129
|
+
target.setAttribute("exception.type", normalized.name);
|
|
130
|
+
target.setAttribute("error.category", normalized.name);
|
|
92
131
|
if (attributes) target.setAttributes(attributes);
|
|
93
132
|
target.setStatus({ code: SpanStatusCode.ERROR });
|
|
94
133
|
} catch {}
|
|
@@ -97,9 +136,18 @@ function installProcessExceptionTracking(processLike = process) {
|
|
|
97
136
|
if (!telemetryEnabled) return () => {};
|
|
98
137
|
const recordProcessException = (source, error) => {
|
|
99
138
|
try {
|
|
100
|
-
tracer.startActiveSpan(`mcp.process.${source}`, (span) => {
|
|
101
|
-
|
|
102
|
-
|
|
139
|
+
tracer.startActiveSpan(`mcp.process.${source}`, { attributes: { "mcp.span.category": "process" } }, (span) => {
|
|
140
|
+
try {
|
|
141
|
+
const normalized = normalizeTelemetryError(error);
|
|
142
|
+
const code = getSafeExceptionCode(error);
|
|
143
|
+
recordTelemetryException(error, {
|
|
144
|
+
"exception.source": source,
|
|
145
|
+
"error.category": normalized.name,
|
|
146
|
+
...code ? { "error.code": code } : {}
|
|
147
|
+
}, span);
|
|
148
|
+
} finally {
|
|
149
|
+
span.end();
|
|
150
|
+
}
|
|
103
151
|
});
|
|
104
152
|
} catch {}
|
|
105
153
|
};
|
|
@@ -116,14 +164,24 @@ function installProcessExceptionTracking(processLike = process) {
|
|
|
116
164
|
};
|
|
117
165
|
}
|
|
118
166
|
const name$1 = "hevy-mcp";
|
|
119
|
-
const version$1 = "5.0.
|
|
167
|
+
const version$1 = "5.0.4";
|
|
120
168
|
const telemetryEnabled = process.env.HEVY_MCP_TELEMETRY !== "0";
|
|
121
169
|
const collectorToken = "NH9vOela-HYreQxAbJa68cjEmORoEKM57EvneUDVcOo";
|
|
122
170
|
const COLLECTOR_ENDPOINT = "https://otel.chrisdoc.dev/v1";
|
|
123
|
-
const sentryRelease = process.env.SENTRY_RELEASE ??
|
|
171
|
+
const sentryRelease = process.env.SENTRY_RELEASE ?? `${name$1}@${version$1}`;
|
|
172
|
+
function createServiceInstanceId(generate = randomUUID) {
|
|
173
|
+
try {
|
|
174
|
+
const generated = generate();
|
|
175
|
+
if (generated.length > 0 && generated.length <= 128) return generated;
|
|
176
|
+
} catch {}
|
|
177
|
+
return randomBytes(16).toString("hex");
|
|
178
|
+
}
|
|
124
179
|
const resource = resourceFromAttributes({
|
|
125
180
|
"service.name": name$1,
|
|
126
|
-
"service.version": version$1
|
|
181
|
+
"service.version": version$1,
|
|
182
|
+
"service.instance.id": createServiceInstanceId(),
|
|
183
|
+
"process.runtime.name": process.release.name,
|
|
184
|
+
"process.runtime.version": process.version
|
|
127
185
|
});
|
|
128
186
|
let currentUserHash;
|
|
129
187
|
var UserHashSpanProcessor = class {
|
|
@@ -168,7 +226,8 @@ if (telemetryEnabled) {
|
|
|
168
226
|
readers: [new PeriodicExportingMetricReader({
|
|
169
227
|
exporter: new OTLPMetricExporter({
|
|
170
228
|
url: `${COLLECTOR_ENDPOINT}/metrics`,
|
|
171
|
-
headers: { Authorization: `Bearer ${collectorToken}` }
|
|
229
|
+
headers: { Authorization: `Bearer ${collectorToken}` },
|
|
230
|
+
temporalityPreference: AggregationTemporalityPreference.DELTA
|
|
172
231
|
}),
|
|
173
232
|
exportIntervalMillis: 1e4
|
|
174
233
|
})]
|
|
@@ -196,6 +255,8 @@ async function flushTelemetry(timeoutMs = 1e3) {
|
|
|
196
255
|
}
|
|
197
256
|
const tracer = trace.getTracer(name$1);
|
|
198
257
|
const meter = metrics.getMeter(name$1);
|
|
258
|
+
const serviceName = name$1;
|
|
259
|
+
const serviceVersion = version$1;
|
|
199
260
|
const SENTRY_USER_ID_CONTEXT = "hevy-mcp:sentry-user-id:v1";
|
|
200
261
|
function setTelemetryUser(apiKey) {
|
|
201
262
|
if (!telemetryEnabled) return;
|
|
@@ -1004,6 +1065,35 @@ const SAFE_STATIC_ENDPOINTS = /* @__PURE__ */ new Set([
|
|
|
1004
1065
|
"/v1/workouts/count",
|
|
1005
1066
|
"/v1/workouts/events"
|
|
1006
1067
|
]);
|
|
1068
|
+
const EXPECTED_READ_404_ENDPOINTS = /* @__PURE__ */ new Set([
|
|
1069
|
+
"/v1/body_measurements/:date",
|
|
1070
|
+
"/v1/exercise_history/:exerciseTemplateId",
|
|
1071
|
+
"/v1/exercise_templates/:exerciseTemplateId",
|
|
1072
|
+
"/v1/routine_folders/:folderId",
|
|
1073
|
+
"/v1/routines/:routineId",
|
|
1074
|
+
"/v1/workouts/:workoutId"
|
|
1075
|
+
]);
|
|
1076
|
+
const EXPECTED_LIST_404_ENDPOINTS = /* @__PURE__ */ new Set([
|
|
1077
|
+
"/v1/body_measurements",
|
|
1078
|
+
"/v1/exercise_templates",
|
|
1079
|
+
"/v1/routine_folders",
|
|
1080
|
+
"/v1/routines",
|
|
1081
|
+
"/v1/workouts",
|
|
1082
|
+
"/v1/workouts/events"
|
|
1083
|
+
]);
|
|
1084
|
+
const SAFE_OBSERVATION_CODES = /* @__PURE__ */ new Set([
|
|
1085
|
+
"EAI_AGAIN",
|
|
1086
|
+
"ECONNABORTED",
|
|
1087
|
+
"ECONNREFUSED",
|
|
1088
|
+
"ECONNRESET",
|
|
1089
|
+
"ENETUNREACH",
|
|
1090
|
+
"ENOTFOUND",
|
|
1091
|
+
"ERR_NETWORK",
|
|
1092
|
+
"ERR_SOCKET_TIMEOUT",
|
|
1093
|
+
"ETIMEDOUT",
|
|
1094
|
+
HEVY_REQUEST_ABORTED_ERROR_CODE,
|
|
1095
|
+
HEVY_RETRY_EXHAUSTED_ERROR_CODE
|
|
1096
|
+
]);
|
|
1007
1097
|
const SAFE_DYNAMIC_ENDPOINTS = [
|
|
1008
1098
|
["/v1/body_measurements/", "/v1/body_measurements/:date"],
|
|
1009
1099
|
["/v1/exercise_history/", "/v1/exercise_history/:exerciseTemplateId"],
|
|
@@ -1049,9 +1139,11 @@ function getRequestContext(config) {
|
|
|
1049
1139
|
let endpoint = "unknown";
|
|
1050
1140
|
if (SAFE_STATIC_ENDPOINTS.has(rawEndpoint)) endpoint = rawEndpoint;
|
|
1051
1141
|
else endpoint = SAFE_DYNAMIC_ENDPOINTS.find(([prefix]) => rawEndpoint.startsWith(prefix))?.[1] ?? "unknown";
|
|
1142
|
+
const page = config.params !== null && typeof config.params === "object" && "page" in config.params && typeof config.params.page === "number" ? config.params.page : void 0;
|
|
1052
1143
|
return {
|
|
1053
1144
|
method,
|
|
1054
|
-
endpoint
|
|
1145
|
+
endpoint,
|
|
1146
|
+
page
|
|
1055
1147
|
};
|
|
1056
1148
|
}
|
|
1057
1149
|
function emitClientLog(logger, message) {
|
|
@@ -1061,11 +1153,49 @@ function emitClientLog(logger, message) {
|
|
|
1061
1153
|
console.error("Failed to emit structured Hevy API log");
|
|
1062
1154
|
}
|
|
1063
1155
|
}
|
|
1156
|
+
function emitRequestStart(observer, observation) {
|
|
1157
|
+
try {
|
|
1158
|
+
return observer?.(observation) ?? void 0;
|
|
1159
|
+
} catch {
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
function finishRequestObservation(scope, observation) {
|
|
1164
|
+
try {
|
|
1165
|
+
scope?.finish(observation);
|
|
1166
|
+
} catch {}
|
|
1167
|
+
}
|
|
1064
1168
|
function emitRequestObservation(observer, observation) {
|
|
1065
1169
|
try {
|
|
1066
1170
|
observer?.(observation);
|
|
1067
1171
|
} catch {}
|
|
1068
1172
|
}
|
|
1173
|
+
function runRequestObservation(scope, operation) {
|
|
1174
|
+
if (!scope?.run) return operation();
|
|
1175
|
+
let started = false;
|
|
1176
|
+
const trackedOperation = () => {
|
|
1177
|
+
started = true;
|
|
1178
|
+
return operation();
|
|
1179
|
+
};
|
|
1180
|
+
try {
|
|
1181
|
+
return scope.run(trackedOperation);
|
|
1182
|
+
} catch (error) {
|
|
1183
|
+
if (started) throw error;
|
|
1184
|
+
return operation();
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
function emitRetryWait(observer, observation) {
|
|
1188
|
+
try {
|
|
1189
|
+
return observer?.(observation) ?? void 0;
|
|
1190
|
+
} catch {
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
function finishRetryWait(scope) {
|
|
1195
|
+
try {
|
|
1196
|
+
scope?.finish();
|
|
1197
|
+
} catch {}
|
|
1198
|
+
}
|
|
1069
1199
|
function parseRetryAfterMs(value) {
|
|
1070
1200
|
if (!value) return void 0;
|
|
1071
1201
|
const seconds = Number(value);
|
|
@@ -1122,7 +1252,7 @@ function createNativeClient(apiKey, baseUrl, options) {
|
|
|
1122
1252
|
...clientConfig,
|
|
1123
1253
|
...config
|
|
1124
1254
|
};
|
|
1125
|
-
const { method, endpoint } = getRequestContext(normalized);
|
|
1255
|
+
const { method, endpoint, page } = getRequestContext(normalized);
|
|
1126
1256
|
const url = buildUrl(baseUrl, normalized);
|
|
1127
1257
|
let retryCount = 0;
|
|
1128
1258
|
while (true) {
|
|
@@ -1132,43 +1262,53 @@ function createNativeClient(apiKey, baseUrl, options) {
|
|
|
1132
1262
|
code: HEVY_REQUEST_ABORTED_ERROR_CODE
|
|
1133
1263
|
});
|
|
1134
1264
|
const startedAt = Date.now();
|
|
1265
|
+
const observationScope = emitRequestStart(options.onRequestStart, {
|
|
1266
|
+
method,
|
|
1267
|
+
endpoint,
|
|
1268
|
+
retryCount
|
|
1269
|
+
});
|
|
1135
1270
|
const controller = new AbortController();
|
|
1136
1271
|
const abortFromCaller = () => controller.abort();
|
|
1137
1272
|
normalized.signal?.addEventListener("abort", abortFromCaller, { once: true });
|
|
1138
1273
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
1139
1274
|
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
|
-
|
|
1275
|
+
return await runRequestObservation(observationScope, async () => {
|
|
1276
|
+
const headers = new Headers({ "api-key": apiKey });
|
|
1277
|
+
if (normalized.data !== void 0 && !(normalized.data instanceof FormData)) headers.set("content-type", "application/json");
|
|
1278
|
+
const attemptDeadline = Date.now() + timeoutMs;
|
|
1279
|
+
const response = await withTimeout(fetchImplementation(url, {
|
|
1280
|
+
method,
|
|
1281
|
+
headers,
|
|
1282
|
+
redirect: "manual",
|
|
1283
|
+
body: normalized.data instanceof FormData ? normalized.data : normalized.data === void 0 ? void 0 : JSON.stringify(normalized.data),
|
|
1284
|
+
signal: controller.signal
|
|
1285
|
+
}), Math.max(0, attemptDeadline - Date.now()), () => controller.abort());
|
|
1286
|
+
const data = await withTimeout(parseResponseData(response), Math.max(0, attemptDeadline - Date.now()), () => controller.abort());
|
|
1287
|
+
if (!response.ok) throw new HevyHttpError(`Hevy API request failed (HTTP ${response.status})`, {
|
|
1288
|
+
status: response.status,
|
|
1289
|
+
statusText: response.statusText,
|
|
1290
|
+
data,
|
|
1291
|
+
headers: response.headers,
|
|
1292
|
+
method,
|
|
1293
|
+
endpoint
|
|
1294
|
+
});
|
|
1295
|
+
const observation = {
|
|
1296
|
+
method,
|
|
1297
|
+
endpoint,
|
|
1298
|
+
status: response.status,
|
|
1299
|
+
durationMs: Date.now() - startedAt,
|
|
1300
|
+
retryCount,
|
|
1301
|
+
outcome: "success"
|
|
1302
|
+
};
|
|
1303
|
+
finishRequestObservation(observationScope, observation);
|
|
1304
|
+
emitRequestObservation(options.onRequestComplete, observation);
|
|
1305
|
+
return {
|
|
1306
|
+
data,
|
|
1307
|
+
status: response.status,
|
|
1308
|
+
statusText: response.statusText,
|
|
1309
|
+
headers: response.headers
|
|
1310
|
+
};
|
|
1165
1311
|
});
|
|
1166
|
-
return {
|
|
1167
|
-
data,
|
|
1168
|
-
status: response.status,
|
|
1169
|
-
statusText: response.statusText,
|
|
1170
|
-
headers: response.headers
|
|
1171
|
-
};
|
|
1172
1312
|
} catch (cause) {
|
|
1173
1313
|
const error = isHevyHttpError(cause) ? cause : new HevyHttpError(normalized.signal?.aborted ? "Hevy API request was canceled" : "Hevy API network request failed", {
|
|
1174
1314
|
method,
|
|
@@ -1176,24 +1316,31 @@ function createNativeClient(apiKey, baseUrl, options) {
|
|
|
1176
1316
|
code: normalized.signal?.aborted ? HEVY_REQUEST_ABORTED_ERROR_CODE : getNetworkCode(cause),
|
|
1177
1317
|
cause
|
|
1178
1318
|
});
|
|
1319
|
+
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
1320
|
const canRetry = method === "GET" && isRetryable(error);
|
|
1180
|
-
|
|
1321
|
+
const retryExhausted = canRetry && retryCount >= maxGetRetries;
|
|
1322
|
+
if (retryExhausted) {
|
|
1181
1323
|
error.hevyRetryExhausted = true;
|
|
1182
1324
|
error.hevyRetryCount = retryCount;
|
|
1183
1325
|
error.code = HEVY_RETRY_EXHAUSTED_ERROR_CODE;
|
|
1184
1326
|
}
|
|
1185
|
-
|
|
1327
|
+
const observation = {
|
|
1186
1328
|
method,
|
|
1187
1329
|
endpoint,
|
|
1188
1330
|
status: error.status ?? 0,
|
|
1189
1331
|
durationMs: Date.now() - startedAt,
|
|
1190
1332
|
retryCount,
|
|
1333
|
+
outcome: expectedReason ? "expected" : canRetry && !retryExhausted ? "retryable_failure" : "terminal_failure",
|
|
1334
|
+
...expectedReason ? { expectedReason } : {},
|
|
1191
1335
|
error: {
|
|
1192
1336
|
status: error.status,
|
|
1193
|
-
code: error.code
|
|
1337
|
+
code: typeof error.code === "string" && SAFE_OBSERVATION_CODES.has(error.code) ? error.code : void 0,
|
|
1338
|
+
category: error.status === void 0 ? "NetworkError" : "HevyHttpError"
|
|
1194
1339
|
}
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1340
|
+
};
|
|
1341
|
+
finishRequestObservation(observationScope, observation);
|
|
1342
|
+
emitRequestObservation(options.onRequestComplete, observation);
|
|
1343
|
+
if (!expectedReason && !canRetry) {
|
|
1197
1344
|
emitClientLog(options.onLog, {
|
|
1198
1345
|
level: "error",
|
|
1199
1346
|
logger: "hevy-api",
|
|
@@ -1206,7 +1353,7 @@ function createNativeClient(apiKey, baseUrl, options) {
|
|
|
1206
1353
|
});
|
|
1207
1354
|
throw error;
|
|
1208
1355
|
}
|
|
1209
|
-
if (error.hevyRetryExhausted) throw error;
|
|
1356
|
+
if (expectedReason || error.hevyRetryExhausted) throw error;
|
|
1210
1357
|
retryCount += 1;
|
|
1211
1358
|
const delayMs = getRetryDelayMs(error, retryCount);
|
|
1212
1359
|
emitClientLog(options.onLog, {
|
|
@@ -1222,7 +1369,19 @@ function createNativeClient(apiKey, baseUrl, options) {
|
|
|
1222
1369
|
endpoint
|
|
1223
1370
|
}
|
|
1224
1371
|
});
|
|
1225
|
-
await
|
|
1372
|
+
await runRequestObservation(observationScope, async () => {
|
|
1373
|
+
const retryWaitScope = emitRetryWait(options.onRetryWait, {
|
|
1374
|
+
method,
|
|
1375
|
+
endpoint,
|
|
1376
|
+
retryCount,
|
|
1377
|
+
delayMs
|
|
1378
|
+
});
|
|
1379
|
+
try {
|
|
1380
|
+
await sleep(delayMs);
|
|
1381
|
+
} finally {
|
|
1382
|
+
finishRetryWait(retryWaitScope);
|
|
1383
|
+
}
|
|
1384
|
+
});
|
|
1226
1385
|
} finally {
|
|
1227
1386
|
clearTimeout(timeout);
|
|
1228
1387
|
normalized.signal?.removeEventListener("abort", abortFromCaller);
|
|
@@ -2680,7 +2839,7 @@ function registerHevyResources(server, runtime) {
|
|
|
2680
2839
|
//#endregion
|
|
2681
2840
|
//#region ../core/src/server-metadata.ts
|
|
2682
2841
|
const SERVER_NAME = "hevy-mcp";
|
|
2683
|
-
const SERVER_VERSION = "5.0.
|
|
2842
|
+
const SERVER_VERSION = "5.0.4";
|
|
2684
2843
|
const SERVER_INSTRUCTIONS = [
|
|
2685
2844
|
"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
2845
|
"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 +4237,7 @@ function createErrorResponse(error, context) {
|
|
|
4078
4237
|
axios: axiosErrorContext ?? void 0
|
|
4079
4238
|
};
|
|
4080
4239
|
const formattedMessage = `${context ? `[${context}] ` : ""}Error: ${policy.message}`;
|
|
4081
|
-
console.error(createMcpToolFailureEvent(context || "unknown", policy.type, diagnostic));
|
|
4240
|
+
console.error(JSON.stringify(createMcpToolFailureEvent(context || "unknown", policy.type, diagnostic)));
|
|
4082
4241
|
return {
|
|
4083
4242
|
content: [{
|
|
4084
4243
|
type: "text",
|
|
@@ -4240,39 +4399,69 @@ var AsyncTtlCache = class {
|
|
|
4240
4399
|
ttlMs;
|
|
4241
4400
|
maxSize;
|
|
4242
4401
|
now;
|
|
4402
|
+
observer;
|
|
4243
4403
|
entries = /* @__PURE__ */ new Map();
|
|
4244
4404
|
inFlight = /* @__PURE__ */ new Map();
|
|
4245
4405
|
requestCounter = 0;
|
|
4246
4406
|
constructor(options, now = Date.now) {
|
|
4247
|
-
const { ttlMs, maxSize } = options;
|
|
4407
|
+
const { ttlMs, maxSize, observer } = options;
|
|
4248
4408
|
if (ttlMs <= 0) throw new Error("Cache ttlMs must be greater than 0.");
|
|
4249
4409
|
if (maxSize <= 0) throw new Error("Cache maxSize must be greater than 0.");
|
|
4250
4410
|
this.ttlMs = ttlMs;
|
|
4251
4411
|
this.maxSize = maxSize;
|
|
4252
4412
|
this.now = now;
|
|
4413
|
+
this.observer = observer;
|
|
4414
|
+
}
|
|
4415
|
+
startObservation(state) {
|
|
4416
|
+
try {
|
|
4417
|
+
return this.observer?.start({ state }) ?? void 0;
|
|
4418
|
+
} catch {
|
|
4419
|
+
return;
|
|
4420
|
+
}
|
|
4421
|
+
}
|
|
4422
|
+
finishObservation(scope, metadata) {
|
|
4423
|
+
try {
|
|
4424
|
+
scope?.finish(metadata);
|
|
4425
|
+
} catch {}
|
|
4253
4426
|
}
|
|
4254
4427
|
async getOrFetch(key, fetcher, options = {}) {
|
|
4255
|
-
const { refresh = false } = options;
|
|
4428
|
+
const { refresh = false, getObservationMetadata } = options;
|
|
4429
|
+
let observationState = refresh ? "refresh" : "miss";
|
|
4256
4430
|
if (refresh) this.invalidate(key);
|
|
4257
4431
|
else {
|
|
4258
4432
|
const cachedEntry = this.entries.get(key);
|
|
4259
4433
|
if (cachedEntry !== void 0) {
|
|
4260
4434
|
if (cachedEntry.expiresAt > this.now()) {
|
|
4261
4435
|
this.markAsRecentlyUsed(key, cachedEntry);
|
|
4436
|
+
const observationScope = this.startObservation("hit");
|
|
4437
|
+
this.finishObservation(observationScope);
|
|
4262
4438
|
return cachedEntry.value;
|
|
4263
4439
|
}
|
|
4264
4440
|
this.entries.delete(key);
|
|
4441
|
+
observationState = "expired";
|
|
4265
4442
|
}
|
|
4266
4443
|
const inFlightEntry = this.inFlight.get(key);
|
|
4267
|
-
if (inFlightEntry !== void 0)
|
|
4444
|
+
if (inFlightEntry !== void 0) {
|
|
4445
|
+
const observationScope = this.startObservation("inflight_wait");
|
|
4446
|
+
inFlightEntry.promise.then(() => this.finishObservation(observationScope), () => this.finishObservation(observationScope));
|
|
4447
|
+
return inFlightEntry.promise;
|
|
4448
|
+
}
|
|
4268
4449
|
}
|
|
4269
4450
|
const requestId = ++this.requestCounter;
|
|
4451
|
+
const observationScope = this.startObservation(observationState);
|
|
4270
4452
|
const request = (async () => {
|
|
4271
4453
|
try {
|
|
4272
4454
|
const value = await fetcher();
|
|
4273
4455
|
if (this.isCurrentRequest(key, requestId)) this.setValue(key, value);
|
|
4274
4456
|
return value;
|
|
4275
4457
|
} finally {
|
|
4458
|
+
let metadata;
|
|
4459
|
+
try {
|
|
4460
|
+
metadata = getObservationMetadata?.();
|
|
4461
|
+
} catch {
|
|
4462
|
+
metadata = void 0;
|
|
4463
|
+
}
|
|
4464
|
+
this.finishObservation(observationScope, metadata);
|
|
4276
4465
|
if (this.inFlight.get(key)?.requestId === requestId) this.inFlight.delete(key);
|
|
4277
4466
|
}
|
|
4278
4467
|
})();
|
|
@@ -4321,17 +4510,20 @@ var AsyncTtlCache = class {
|
|
|
4321
4510
|
const EXERCISE_TEMPLATE_CATALOG_CACHE_KEY = "exercise-template-catalog";
|
|
4322
4511
|
const EXERCISE_TEMPLATE_CATALOG_CACHE_TTL_MS = 300 * 1e3;
|
|
4323
4512
|
const EXERCISE_TEMPLATE_CATALOG_CACHE_MAX_SIZE = 1;
|
|
4324
|
-
|
|
4325
|
-
function createExerciseTemplateCatalog(hevyClient) {
|
|
4513
|
+
function createExerciseTemplateCatalog(hevyClient, cacheObserver) {
|
|
4326
4514
|
const cache = new AsyncTtlCache({
|
|
4327
4515
|
ttlMs: EXERCISE_TEMPLATE_CATALOG_CACHE_TTL_MS,
|
|
4328
|
-
maxSize: EXERCISE_TEMPLATE_CATALOG_CACHE_MAX_SIZE
|
|
4516
|
+
maxSize: EXERCISE_TEMPLATE_CATALOG_CACHE_MAX_SIZE,
|
|
4517
|
+
observer: cacheObserver
|
|
4329
4518
|
});
|
|
4330
4519
|
return {
|
|
4331
4520
|
get(options = {}) {
|
|
4332
4521
|
const reason = options.refresh ? "explicit-refresh" : cache.size === 0 ? "initial-load" : "ttl-expired";
|
|
4522
|
+
let observationMetadata;
|
|
4523
|
+
let pagesLoaded = 0;
|
|
4333
4524
|
return cache.getOrFetch(EXERCISE_TEMPLATE_CATALOG_CACHE_KEY, async () => {
|
|
4334
4525
|
const catalog = await fetchAllPages(async (page, pageSize) => {
|
|
4526
|
+
pagesLoaded = page;
|
|
4335
4527
|
const data = await hevyClient.getExerciseTemplates({
|
|
4336
4528
|
page,
|
|
4337
4529
|
pageSize
|
|
@@ -4341,9 +4533,17 @@ function createExerciseTemplateCatalog(hevyClient) {
|
|
|
4341
4533
|
pageCount: data?.page_count
|
|
4342
4534
|
};
|
|
4343
4535
|
}, 100);
|
|
4536
|
+
observationMetadata = {
|
|
4537
|
+
refreshReason: reason,
|
|
4538
|
+
pageCountBucket: bucketCount(pagesLoaded),
|
|
4539
|
+
itemCountBucket: bucketCount(catalog.length)
|
|
4540
|
+
};
|
|
4344
4541
|
options.onRefreshed?.(catalog, reason);
|
|
4345
4542
|
return catalog;
|
|
4346
|
-
},
|
|
4543
|
+
}, {
|
|
4544
|
+
refresh: options.refresh,
|
|
4545
|
+
getObservationMetadata: () => observationMetadata
|
|
4546
|
+
});
|
|
4347
4547
|
},
|
|
4348
4548
|
reset() {
|
|
4349
4549
|
cache.clear();
|
|
@@ -4405,7 +4605,7 @@ function createHevyMcpServer(options) {
|
|
|
4405
4605
|
const client = options.createClient({ onLog: (event) => mcpLogger(event) });
|
|
4406
4606
|
const runtime = createToolRuntime({
|
|
4407
4607
|
client,
|
|
4408
|
-
catalog: createExerciseTemplateCatalog(client),
|
|
4608
|
+
catalog: createExerciseTemplateCatalog(client, options.cacheObserver),
|
|
4409
4609
|
logger: mcpLogger,
|
|
4410
4610
|
observer: options.observer
|
|
4411
4611
|
});
|
|
@@ -4530,13 +4730,15 @@ function extractMcpClientMetadata(message) {
|
|
|
4530
4730
|
protocolVersion: normalizeMetadata(params.protocolVersion)
|
|
4531
4731
|
};
|
|
4532
4732
|
}
|
|
4533
|
-
function createMcpSessionContext(message, transport = "stdio") {
|
|
4733
|
+
function createMcpSessionContext(message, transport = "stdio", options = {}) {
|
|
4734
|
+
const normalizedOptions = typeof options === "function" ? { generateTelemetrySessionId: options } : options;
|
|
4534
4735
|
return {
|
|
4535
4736
|
metadata: extractMcpClientMetadata(message),
|
|
4536
|
-
startedAt: Date.now(),
|
|
4737
|
+
startedAt: (normalizedOptions.now ?? Date.now)(),
|
|
4537
4738
|
toolCalls: 0,
|
|
4538
4739
|
hadToolFailure: false,
|
|
4539
|
-
transport
|
|
4740
|
+
transport,
|
|
4741
|
+
telemetrySessionId: normalizedOptions.telemetrySessionId ?? (normalizedOptions.generateTelemetrySessionId ?? randomUUID)()
|
|
4540
4742
|
};
|
|
4541
4743
|
}
|
|
4542
4744
|
function runWithMcpSessionContext(context, callback) {
|
|
@@ -4545,6 +4747,9 @@ function runWithMcpSessionContext(context, callback) {
|
|
|
4545
4747
|
function getSession() {
|
|
4546
4748
|
return contextStorage.getStore() ?? activeStdioSession;
|
|
4547
4749
|
}
|
|
4750
|
+
function getCurrentMcpSessionId() {
|
|
4751
|
+
return getSession()?.telemetrySessionId;
|
|
4752
|
+
}
|
|
4548
4753
|
function metadataAttributes(metadata, transport) {
|
|
4549
4754
|
return {
|
|
4550
4755
|
client_name: metadata.name,
|
|
@@ -4985,6 +5190,38 @@ function debugLog(event, data) {
|
|
|
4985
5190
|
}
|
|
4986
5191
|
//#endregion
|
|
4987
5192
|
//#region src/utils/hevy-client-observability.ts
|
|
5193
|
+
function safeErrorAttributes(observation) {
|
|
5194
|
+
if (!observation.error) return {};
|
|
5195
|
+
const code = observation.error.code;
|
|
5196
|
+
return {
|
|
5197
|
+
error_category: observation.error.category ?? "HevyHttpError",
|
|
5198
|
+
...code && SAFE_OBSERVATION_CODES.has(code) ? { error_code: code } : {}
|
|
5199
|
+
};
|
|
5200
|
+
}
|
|
5201
|
+
function startApiSpan(start) {
|
|
5202
|
+
const sessionId = getCurrentMcpSessionId();
|
|
5203
|
+
return tracer.startSpan(`hevy.api.${start.method}`, { attributes: {
|
|
5204
|
+
"mcp.span.category": "api",
|
|
5205
|
+
"http.request.method": start.method,
|
|
5206
|
+
"hevy.api.endpoint": start.endpoint,
|
|
5207
|
+
"hevy.api.retry_count_bucket": bucketCount(start.retryCount),
|
|
5208
|
+
"mcp.transport": getCurrentMcpTransport(),
|
|
5209
|
+
...sessionId ? { "mcp.session.id": sessionId } : {}
|
|
5210
|
+
} });
|
|
5211
|
+
}
|
|
5212
|
+
function finishApiSpan(span, observation) {
|
|
5213
|
+
const errorAttributes = safeErrorAttributes(observation);
|
|
5214
|
+
if (observation.status > 0) span.setAttribute("http.response.status_code", observation.status);
|
|
5215
|
+
span.setAttribute("hevy.api.outcome", observation.outcome);
|
|
5216
|
+
if (observation.expectedReason) span.setAttribute("hevy.api.expected_reason", observation.expectedReason);
|
|
5217
|
+
for (const [key, value] of Object.entries(errorAttributes)) span.setAttribute(key, value);
|
|
5218
|
+
span.setStatus({ code: observation.outcome === "success" || observation.outcome === "expected" ? SpanStatusCode.OK : SpanStatusCode.ERROR });
|
|
5219
|
+
if (observation.outcome !== "success" && observation.outcome !== "expected") span.addEvent("hevy.api.failure", {
|
|
5220
|
+
"error.category": errorAttributes.error_category ?? "HevyHttpError",
|
|
5221
|
+
...errorAttributes.error_code ? { "error.code": errorAttributes.error_code } : {}
|
|
5222
|
+
});
|
|
5223
|
+
span.end();
|
|
5224
|
+
}
|
|
4988
5225
|
/** Node-only adapter; the Worker graph never imports telemetry or metrics. */
|
|
4989
5226
|
function createNodeHevyClientOptions() {
|
|
4990
5227
|
const rawTimeout = process.env.HEVY_MCP_API_TIMEOUT;
|
|
@@ -4992,37 +5229,38 @@ function createNodeHevyClientOptions() {
|
|
|
4992
5229
|
const timeoutMs = Number.isFinite(parsedTimeout) && parsedTimeout > 0 ? Math.floor(parsedTimeout) : void 0;
|
|
4993
5230
|
return {
|
|
4994
5231
|
...timeoutMs ? { timeoutMs } : {},
|
|
5232
|
+
onRequestStart(start) {
|
|
5233
|
+
const span = startApiSpan(start);
|
|
5234
|
+
return {
|
|
5235
|
+
run(operation) {
|
|
5236
|
+
return context.with(trace.setSpan(context.active(), span), operation);
|
|
5237
|
+
},
|
|
5238
|
+
finish(observation) {
|
|
5239
|
+
finishApiSpan(span, observation);
|
|
5240
|
+
}
|
|
5241
|
+
};
|
|
5242
|
+
},
|
|
4995
5243
|
onRequestComplete(observation) {
|
|
4996
5244
|
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();
|
|
5245
|
+
const errorAttributes = safeErrorAttributes(observation);
|
|
5014
5246
|
apiCalls.add(1, {
|
|
5015
5247
|
method: observation.method,
|
|
5016
5248
|
endpoint: observation.endpoint,
|
|
5017
5249
|
status_code: observation.status,
|
|
5018
5250
|
retry_count_bucket: retryCountBucket,
|
|
5019
|
-
|
|
5251
|
+
outcome: observation.outcome,
|
|
5252
|
+
transport: getCurrentMcpTransport(),
|
|
5253
|
+
...observation.expectedReason ? { expected_reason: observation.expectedReason } : {},
|
|
5254
|
+
...errorAttributes
|
|
5020
5255
|
});
|
|
5021
5256
|
apiDuration.record(observation.durationMs, {
|
|
5022
5257
|
method: observation.method,
|
|
5023
5258
|
endpoint: observation.endpoint,
|
|
5024
5259
|
retry_count_bucket: retryCountBucket,
|
|
5025
|
-
|
|
5260
|
+
outcome: observation.outcome,
|
|
5261
|
+
transport: getCurrentMcpTransport(),
|
|
5262
|
+
...observation.expectedReason ? { expected_reason: observation.expectedReason } : {},
|
|
5263
|
+
...errorAttributes
|
|
5026
5264
|
});
|
|
5027
5265
|
debugLog("api_response", {
|
|
5028
5266
|
method: observation.method,
|
|
@@ -5030,13 +5268,43 @@ function createNodeHevyClientOptions() {
|
|
|
5030
5268
|
durationMs: observation.durationMs,
|
|
5031
5269
|
status: observation.status || null,
|
|
5032
5270
|
retryCountBucket,
|
|
5033
|
-
|
|
5271
|
+
outcome: observation.outcome,
|
|
5272
|
+
...errorAttributes
|
|
5034
5273
|
});
|
|
5274
|
+
},
|
|
5275
|
+
onRetryWait(observation) {
|
|
5276
|
+
const sessionId = getCurrentMcpSessionId();
|
|
5277
|
+
const span = tracer.startSpan("hevy.api.retry_wait", { attributes: {
|
|
5278
|
+
"mcp.span.category": "api",
|
|
5279
|
+
"http.request.method": observation.method,
|
|
5280
|
+
"hevy.api.retry_count_bucket": bucketCount(observation.retryCount),
|
|
5281
|
+
"hevy.api.retry_wait_ms": Math.max(0, Math.min(5e3, observation.delayMs)),
|
|
5282
|
+
...sessionId ? { "mcp.session.id": sessionId } : {}
|
|
5283
|
+
} });
|
|
5284
|
+
return { finish: () => span.end() };
|
|
5035
5285
|
}
|
|
5036
5286
|
};
|
|
5037
5287
|
}
|
|
5288
|
+
function createNodeCacheObserver() {
|
|
5289
|
+
return { start(observation) {
|
|
5290
|
+
const sessionId = getCurrentMcpSessionId();
|
|
5291
|
+
const span = tracer.startSpan(`hevy.cache.${observation.state}`, { attributes: {
|
|
5292
|
+
"mcp.span.category": "cache",
|
|
5293
|
+
"hevy.cache.state": observation.state,
|
|
5294
|
+
"mcp.transport": getCurrentMcpTransport(),
|
|
5295
|
+
...sessionId ? { "mcp.session.id": sessionId } : {}
|
|
5296
|
+
} });
|
|
5297
|
+
return { finish(metadata) {
|
|
5298
|
+
if (metadata?.refreshReason) span.setAttribute("hevy.cache.refresh_reason", metadata.refreshReason);
|
|
5299
|
+
if (metadata?.pageCountBucket) span.setAttribute("hevy.cache.page_count_bucket", metadata.pageCountBucket);
|
|
5300
|
+
if (metadata?.itemCountBucket) span.setAttribute("hevy.cache.item_count_bucket", metadata.itemCountBucket);
|
|
5301
|
+
span.end();
|
|
5302
|
+
} };
|
|
5303
|
+
} };
|
|
5304
|
+
}
|
|
5038
5305
|
//#endregion
|
|
5039
5306
|
//#region src/utils/tool-observer.ts
|
|
5307
|
+
const DISCOVERY_TOOL_NAMES = /* @__PURE__ */ new Set(["search-routines"]);
|
|
5040
5308
|
const WORKFLOW_PAGINATION_RESOURCES = /* @__PURE__ */ new Set([
|
|
5041
5309
|
"workouts",
|
|
5042
5310
|
"bodyMeasurements",
|
|
@@ -5059,8 +5327,11 @@ function metricAttributes(invocation, clientAttributes) {
|
|
|
5059
5327
|
}
|
|
5060
5328
|
function createAttributes(invocation) {
|
|
5061
5329
|
const clientMetadata = getCurrentMcpClientMetadata();
|
|
5330
|
+
const isPrompt = invocation.kind === "prompt";
|
|
5331
|
+
const sessionId = getCurrentMcpSessionId();
|
|
5062
5332
|
const attributes = {
|
|
5063
|
-
|
|
5333
|
+
"mcp.span.category": DISCOVERY_TOOL_NAMES.has(invocation.name) ? "discovery" : "tool",
|
|
5334
|
+
[isPrompt ? "mcp.prompt.name" : "mcp.tool.name"]: invocation.name,
|
|
5064
5335
|
"mcp.operation.kind": invocation.kind ?? "tool",
|
|
5065
5336
|
...taxonomyAttributes(invocation),
|
|
5066
5337
|
"mcp.client.name": clientMetadata.name,
|
|
@@ -5068,7 +5339,8 @@ function createAttributes(invocation) {
|
|
|
5068
5339
|
"mcp.protocol.version": clientMetadata.protocolVersion,
|
|
5069
5340
|
"mcp.transport": getCurrentMcpTransport(),
|
|
5070
5341
|
"mcp.tool.args.key_count_bucket": invocation.argumentKeyCountBucket ?? "unknown",
|
|
5071
|
-
"mcp.tool.args.keys": invocation.argumentKeys?.join(",") ?? ""
|
|
5342
|
+
"mcp.tool.args.keys": invocation.argumentKeys?.join(",") ?? "",
|
|
5343
|
+
...sessionId ? { "mcp.session.id": sessionId } : {}
|
|
5072
5344
|
};
|
|
5073
5345
|
for (const key of Object.keys(invocation.argumentPresence ?? {})) attributes[`mcp.tool.args.${key}.present`] = true;
|
|
5074
5346
|
for (const [key, bucket] of Object.entries(invocation.numericArgumentBuckets ?? {})) if (bucket !== void 0) attributes[`mcp.tool.args.${key}.bucket`] = bucket;
|
|
@@ -5106,14 +5378,16 @@ function resultMetricAttributes(completion) {
|
|
|
5106
5378
|
if (summary?.setCountBucket) attributes["mcp.tool.result.set_count_bucket"] = summary.setCountBucket;
|
|
5107
5379
|
return attributes;
|
|
5108
5380
|
}
|
|
5109
|
-
function setSafeErrorAttributes(span, completion) {
|
|
5381
|
+
function setSafeErrorAttributes(span, invocation, completion) {
|
|
5110
5382
|
const diagnostic = completion.error;
|
|
5111
5383
|
const errorType = completion.errorType ?? "UNKNOWN_ERROR";
|
|
5112
5384
|
if (diagnostic) span.addEvent("mcp.tool.failure", {
|
|
5385
|
+
"mcp.tool.name": invocation.name,
|
|
5386
|
+
"error.type": errorType,
|
|
5113
5387
|
"error.category": diagnostic.category,
|
|
5114
5388
|
...diagnostic.code ? { "error.code": diagnostic.code } : {},
|
|
5115
|
-
...diagnostic.status !== void 0 ? { "http.status_code": diagnostic.status } : {},
|
|
5116
|
-
...diagnostic.method ? { "http.method": diagnostic.method } : {},
|
|
5389
|
+
...diagnostic.status !== void 0 ? { "http.response.status_code": diagnostic.status } : {},
|
|
5390
|
+
...diagnostic.method ? { "http.request.method": diagnostic.method } : {},
|
|
5117
5391
|
...diagnostic.endpoint ? { "hevy.api.endpoint": diagnostic.endpoint } : {}
|
|
5118
5392
|
});
|
|
5119
5393
|
span.setAttribute("error.type", errorType);
|
|
@@ -5130,8 +5404,8 @@ function captureSafeToolFailure(invocation, completion) {
|
|
|
5130
5404
|
scope.setTag("error.type", completion.errorType ?? "UNKNOWN_ERROR");
|
|
5131
5405
|
scope.setTag("error.category", category);
|
|
5132
5406
|
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);
|
|
5407
|
+
if (diagnostic?.status !== void 0) scope.setTag("http.response.status_code", String(diagnostic.status));
|
|
5408
|
+
if (diagnostic?.method) scope.setTag("http.request.method", diagnostic.method);
|
|
5135
5409
|
if (diagnostic?.endpoint) scope.setTag("hevy.api.endpoint", diagnostic.endpoint);
|
|
5136
5410
|
scope.setContext(invocation.kind === "prompt" ? "mcpPrompt" : "mcpTool", {
|
|
5137
5411
|
context: invocation.name,
|
|
@@ -5185,26 +5459,35 @@ function createNodeToolObserver() {
|
|
|
5185
5459
|
activeSpan.setAttribute("mcp.tool.outcome", nextCompletion.outcome);
|
|
5186
5460
|
setResultAttributes(activeSpan, nextCompletion);
|
|
5187
5461
|
if (nextCompletion.outcome === "thrown_error") {
|
|
5188
|
-
errorType = setSafeErrorAttributes(activeSpan, nextCompletion);
|
|
5462
|
+
errorType = setSafeErrorAttributes(activeSpan, invocation, nextCompletion);
|
|
5189
5463
|
bestEffort(() => recordTelemetryException(new Error(nextCompletion.error?.category ?? errorType), {
|
|
5190
5464
|
[invocation.kind === "prompt" ? "mcp.prompt.name" : "mcp.tool.name"]: invocation.name,
|
|
5191
5465
|
"error.type": errorType ?? "UNKNOWN_ERROR",
|
|
5192
5466
|
"error.category": nextCompletion.error?.category ?? "UnknownError",
|
|
5193
5467
|
...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 } : {},
|
|
5468
|
+
...nextCompletion.error?.status !== void 0 ? { "http.response.status_code": nextCompletion.error.status } : {},
|
|
5469
|
+
...nextCompletion.error?.method ? { "http.request.method": nextCompletion.error.method } : {},
|
|
5470
|
+
...nextCompletion.error?.endpoint ? { "hevy.api.endpoint": nextCompletion.error.endpoint } : {}
|
|
5471
|
+
}, activeSpan));
|
|
5472
|
+
}
|
|
5473
|
+
if (nextCompletion.outcome === "returned_error") {
|
|
5474
|
+
activeSpan.addEvent("mcp.tool.failure", {
|
|
5475
|
+
"mcp.tool.name": invocation.name,
|
|
5476
|
+
"error.type": "UNKNOWN_ERROR",
|
|
5477
|
+
"error.category": "McpToolReturnedError",
|
|
5478
|
+
"error.code": "MCP_TOOL_RETURNED_ERROR"
|
|
5479
|
+
});
|
|
5480
|
+
activeSpan.setAttribute("error.type", "UNKNOWN_ERROR");
|
|
5481
|
+
bestEffort(() => recordTelemetryException(new Error(nextCompletion.error?.category ?? "UnknownError"), {
|
|
5482
|
+
[invocation.kind === "prompt" ? "mcp.prompt.name" : "mcp.tool.name"]: invocation.name,
|
|
5483
|
+
"error.type": nextCompletion.errorType ?? "UNKNOWN_ERROR",
|
|
5484
|
+
"error.category": nextCompletion.error?.category ?? "UnknownError",
|
|
5485
|
+
...nextCompletion.error?.code ? { "error.code": nextCompletion.error.code } : {},
|
|
5486
|
+
...nextCompletion.error?.status !== void 0 ? { "http.response.status_code": nextCompletion.error.status } : {},
|
|
5487
|
+
...nextCompletion.error?.method ? { "http.request.method": nextCompletion.error.method } : {},
|
|
5196
5488
|
...nextCompletion.error?.endpoint ? { "hevy.api.endpoint": nextCompletion.error.endpoint } : {}
|
|
5197
5489
|
}, activeSpan));
|
|
5198
5490
|
}
|
|
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
5491
|
}
|
|
5209
5492
|
} catch {} finally {
|
|
5210
5493
|
if (nextCompletion.outcome === "thrown_error") {
|
|
@@ -5385,14 +5668,17 @@ function deserializeMessageWithObservability(line, chunkSnapshot) {
|
|
|
5385
5668
|
const lineHadLeadingBom = line.startsWith(UTF8_BOM);
|
|
5386
5669
|
const normalizedLine = lineHadLeadingBom ? line.slice(1) : line;
|
|
5387
5670
|
const lineByteLength = Buffer.byteLength(line);
|
|
5671
|
+
const sessionId = getCurrentMcpSessionId();
|
|
5388
5672
|
return tracer.startActiveSpan("mcp.stdio.deserialize", { attributes: {
|
|
5673
|
+
"mcp.span.category": "protocol",
|
|
5389
5674
|
"mcp.transport": "stdio",
|
|
5390
5675
|
"mcp.stdio.parse.line.char_length": line.length,
|
|
5391
5676
|
"mcp.stdio.parse.line.byte_length": lineByteLength,
|
|
5392
5677
|
"mcp.stdio.parse.line.had_leading_bom": lineHadLeadingBom,
|
|
5393
5678
|
"mcp.stdio.parse.line.bom_stripped": lineHadLeadingBom,
|
|
5394
5679
|
"mcp.stdio.parse.chunk.last_byte_length": chunkSnapshot.lastChunkByteLength,
|
|
5395
|
-
"mcp.stdio.parse.chunk.last_had_utf8_bom": chunkSnapshot.lastChunkStartsWithUtf8Bom
|
|
5680
|
+
"mcp.stdio.parse.chunk.last_had_utf8_bom": chunkSnapshot.lastChunkStartsWithUtf8Bom,
|
|
5681
|
+
...sessionId ? { "mcp.session.id": sessionId } : {}
|
|
5396
5682
|
} }, (span) => {
|
|
5397
5683
|
try {
|
|
5398
5684
|
const message = deserializeMessage(normalizedLine);
|
|
@@ -5402,6 +5688,8 @@ function deserializeMessageWithObservability(line, chunkSnapshot) {
|
|
|
5402
5688
|
if (typeof method === "string" && SAFE_MCP_METHODS[method] === true) span.setAttribute("mcp.method", method);
|
|
5403
5689
|
if (method === "initialize") {
|
|
5404
5690
|
const client = recordMcpSessionStart(message);
|
|
5691
|
+
const sessionId = getCurrentMcpSessionId();
|
|
5692
|
+
if (sessionId) span.setAttribute("mcp.session.id", sessionId);
|
|
5405
5693
|
span.setAttribute("mcp.client.name", client.name);
|
|
5406
5694
|
span.setAttribute("mcp.client.version", client.version);
|
|
5407
5695
|
span.setAttribute("mcp.protocol.version", client.protocolVersion);
|
|
@@ -5568,8 +5856,8 @@ function scheduleUpdateCheck(options, overrides = {}) {
|
|
|
5568
5856
|
}
|
|
5569
5857
|
//#endregion
|
|
5570
5858
|
//#region src/index.ts
|
|
5571
|
-
const name =
|
|
5572
|
-
const version =
|
|
5859
|
+
const name = serviceName;
|
|
5860
|
+
const version = serviceVersion;
|
|
5573
5861
|
const HELP_TEXT = [
|
|
5574
5862
|
"Usage:",
|
|
5575
5863
|
" hevy-mcp [options]",
|
|
@@ -5629,6 +5917,159 @@ function getSafeValidationDiagnostic(error) {
|
|
|
5629
5917
|
const code = error.code;
|
|
5630
5918
|
return typeof code === "string" && SAFE_NETWORK_ERROR_CODES.has(code) ? code : void 0;
|
|
5631
5919
|
}
|
|
5920
|
+
function recordLifecycleFailure(span, error, phase) {
|
|
5921
|
+
const diagnostic = createSafeErrorDiagnostic(error);
|
|
5922
|
+
const attributes = {
|
|
5923
|
+
"mcp.failure.phase": phase,
|
|
5924
|
+
"error.type": diagnostic.category,
|
|
5925
|
+
"error.category": diagnostic.category,
|
|
5926
|
+
...diagnostic.code ? { "error.code": diagnostic.code } : {},
|
|
5927
|
+
...diagnostic.status !== void 0 ? { "http.response.status_code": diagnostic.status } : {},
|
|
5928
|
+
...diagnostic.method ? { "http.request.method": diagnostic.method } : {},
|
|
5929
|
+
...diagnostic.endpoint ? { "hevy.api.endpoint": diagnostic.endpoint } : {}
|
|
5930
|
+
};
|
|
5931
|
+
span.addEvent("mcp.lifecycle.failure", {
|
|
5932
|
+
"error.type": diagnostic.category,
|
|
5933
|
+
"error.category": diagnostic.category,
|
|
5934
|
+
...diagnostic.code ? { "error.code": diagnostic.code } : {}
|
|
5935
|
+
});
|
|
5936
|
+
recordTelemetryException(error, attributes, span);
|
|
5937
|
+
}
|
|
5938
|
+
const sdkToolNameStorage = new AsyncLocalStorage();
|
|
5939
|
+
const SAFE_TOOL_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
|
|
5940
|
+
function getSdkToolName(request) {
|
|
5941
|
+
if (!request || typeof request !== "object") return "unknown";
|
|
5942
|
+
const candidate = request.params?.name;
|
|
5943
|
+
return typeof candidate === "string" && SAFE_TOOL_NAME_PATTERN.test(candidate) ? candidate : "unknown";
|
|
5944
|
+
}
|
|
5945
|
+
function markSdkToolFailure(span, error, toolName = sdkToolNameStorage.getStore() ?? "unknown", errorTypeOverride) {
|
|
5946
|
+
const diagnostic = createSafeErrorDiagnostic(error);
|
|
5947
|
+
const errorType = errorTypeOverride ?? diagnostic.category;
|
|
5948
|
+
span.addEvent("mcp.tool.failure", {
|
|
5949
|
+
"mcp.tool.name": toolName,
|
|
5950
|
+
"error.type": errorType,
|
|
5951
|
+
"error.category": diagnostic.category,
|
|
5952
|
+
...diagnostic.code ? { "error.code": diagnostic.code } : {},
|
|
5953
|
+
...diagnostic.status !== void 0 ? { "http.response.status_code": diagnostic.status } : {},
|
|
5954
|
+
...diagnostic.method ? { "http.request.method": diagnostic.method } : {},
|
|
5955
|
+
...diagnostic.endpoint ? { "hevy.api.endpoint": diagnostic.endpoint } : {}
|
|
5956
|
+
});
|
|
5957
|
+
span.setAttribute("error.type", errorType);
|
|
5958
|
+
recordTelemetryException(error, {
|
|
5959
|
+
"mcp.failure.phase": "sdk",
|
|
5960
|
+
"error.type": errorType,
|
|
5961
|
+
"error.category": diagnostic.category,
|
|
5962
|
+
...diagnostic.code ? { "error.code": diagnostic.code } : {},
|
|
5963
|
+
...diagnostic.status !== void 0 ? { "http.response.status_code": diagnostic.status } : {}
|
|
5964
|
+
}, span);
|
|
5965
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
5966
|
+
}
|
|
5967
|
+
function installSdkErrorTracking(server) {
|
|
5968
|
+
const protocol = server.server;
|
|
5969
|
+
if (!protocol) return;
|
|
5970
|
+
const previous = protocol.onerror;
|
|
5971
|
+
protocol.onerror = (error) => {
|
|
5972
|
+
const diagnostic = createSafeErrorDiagnostic(error);
|
|
5973
|
+
const attributes = {
|
|
5974
|
+
"mcp.failure.phase": "sdk",
|
|
5975
|
+
"error.category": diagnostic.category,
|
|
5976
|
+
...diagnostic.code ? { "error.code": diagnostic.code } : {},
|
|
5977
|
+
...diagnostic.status !== void 0 ? { "http.response.status_code": diagnostic.status } : {}
|
|
5978
|
+
};
|
|
5979
|
+
const activeSpan = trace.getActiveSpan();
|
|
5980
|
+
const sessionId = getCurrentMcpSessionId();
|
|
5981
|
+
if (activeSpan) {
|
|
5982
|
+
activeSpan.addEvent("mcp.tool.failure", {
|
|
5983
|
+
"mcp.tool.name": sdkToolNameStorage.getStore() ?? "unknown",
|
|
5984
|
+
"error.type": "UNKNOWN_ERROR",
|
|
5985
|
+
"error.category": diagnostic.category,
|
|
5986
|
+
...diagnostic.code ? { "error.code": diagnostic.code } : {}
|
|
5987
|
+
});
|
|
5988
|
+
recordTelemetryException(error, attributes, activeSpan);
|
|
5989
|
+
} else tracer.startActiveSpan("mcp.sdk.failure", { attributes: { ...sessionId ? { "mcp.session.id": sessionId } : {} } }, (span) => {
|
|
5990
|
+
span.addEvent("mcp.tool.failure", {
|
|
5991
|
+
"mcp.tool.name": "unknown",
|
|
5992
|
+
"error.type": "UNKNOWN_ERROR",
|
|
5993
|
+
"error.category": diagnostic.category,
|
|
5994
|
+
...diagnostic.code ? { "error.code": diagnostic.code } : {}
|
|
5995
|
+
});
|
|
5996
|
+
recordTelemetryException(error, attributes, span);
|
|
5997
|
+
span.end();
|
|
5998
|
+
});
|
|
5999
|
+
try {
|
|
6000
|
+
previous?.(error);
|
|
6001
|
+
} catch {}
|
|
6002
|
+
};
|
|
6003
|
+
const toolErrorHost = server;
|
|
6004
|
+
const previousCreateToolError = toolErrorHost.createToolError;
|
|
6005
|
+
if (previousCreateToolError) {
|
|
6006
|
+
const createToolError = previousCreateToolError.bind(server);
|
|
6007
|
+
toolErrorHost.createToolError = (message) => {
|
|
6008
|
+
const result = createToolError(message);
|
|
6009
|
+
const activeSpan = trace.getActiveSpan();
|
|
6010
|
+
if (activeSpan) markSdkToolFailure(activeSpan, /* @__PURE__ */ new Error("MCP tool validation failed"), void 0, "VALIDATION_ERROR");
|
|
6011
|
+
return result;
|
|
6012
|
+
};
|
|
6013
|
+
}
|
|
6014
|
+
const handlers = protocol._requestHandlers;
|
|
6015
|
+
if (!(handlers instanceof Map)) return;
|
|
6016
|
+
const toolHandler = handlers.get("tools/call");
|
|
6017
|
+
if (toolHandler) handlers.set("tools/call", (request, extra) => {
|
|
6018
|
+
const sessionId = getCurrentMcpSessionId();
|
|
6019
|
+
const toolName = getSdkToolName(request);
|
|
6020
|
+
return sdkToolNameStorage.run(toolName, () => tracer.startActiveSpan("mcp.sdk.tools.call", { attributes: {
|
|
6021
|
+
"mcp.span.category": "protocol",
|
|
6022
|
+
"mcp.operation.kind": "tool",
|
|
6023
|
+
"mcp.tool.name": toolName,
|
|
6024
|
+
...sessionId ? { "mcp.session.id": sessionId } : {}
|
|
6025
|
+
} }, async (span) => {
|
|
6026
|
+
try {
|
|
6027
|
+
const result = await toolHandler(request, extra);
|
|
6028
|
+
if (result?.isError === true) {
|
|
6029
|
+
span.setAttribute("mcp.tool.outcome", "returned_error");
|
|
6030
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
6031
|
+
} else span.setStatus({ code: SpanStatusCode.OK });
|
|
6032
|
+
return result;
|
|
6033
|
+
} catch (error) {
|
|
6034
|
+
markSdkToolFailure(span, error, toolName);
|
|
6035
|
+
throw error;
|
|
6036
|
+
} finally {
|
|
6037
|
+
span.end();
|
|
6038
|
+
}
|
|
6039
|
+
}));
|
|
6040
|
+
});
|
|
6041
|
+
const discoveryHandler = handlers.get("server/discover");
|
|
6042
|
+
if (discoveryHandler) handlers.set("server/discover", (request, extra) => {
|
|
6043
|
+
const sessionId = getCurrentMcpSessionId();
|
|
6044
|
+
return tracer.startActiveSpan("mcp.server.discover", { attributes: {
|
|
6045
|
+
"mcp.span.category": "discovery",
|
|
6046
|
+
"mcp.transport": getCurrentMcpTransport(),
|
|
6047
|
+
...sessionId ? { "mcp.session.id": sessionId } : {}
|
|
6048
|
+
} }, async (span) => {
|
|
6049
|
+
try {
|
|
6050
|
+
const result = await discoveryHandler(request, extra);
|
|
6051
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
6052
|
+
return result;
|
|
6053
|
+
} catch (error) {
|
|
6054
|
+
const diagnostic = createSafeErrorDiagnostic(error);
|
|
6055
|
+
span.addEvent("mcp.discovery.failure", {
|
|
6056
|
+
"error.type": diagnostic.category,
|
|
6057
|
+
"error.category": diagnostic.category,
|
|
6058
|
+
...diagnostic.code ? { "error.code": diagnostic.code } : {}
|
|
6059
|
+
});
|
|
6060
|
+
recordTelemetryException(error, {
|
|
6061
|
+
"mcp.failure.phase": "discovery",
|
|
6062
|
+
"error.type": diagnostic.category,
|
|
6063
|
+
"error.category": diagnostic.category,
|
|
6064
|
+
...diagnostic.code ? { "error.code": diagnostic.code } : {}
|
|
6065
|
+
}, span);
|
|
6066
|
+
throw error;
|
|
6067
|
+
} finally {
|
|
6068
|
+
span.end();
|
|
6069
|
+
}
|
|
6070
|
+
});
|
|
6071
|
+
});
|
|
6072
|
+
}
|
|
5632
6073
|
async function validateApiKey(apiKey) {
|
|
5633
6074
|
const startupProbeClient = createHevyClient({
|
|
5634
6075
|
apiKey,
|
|
@@ -5647,6 +6088,7 @@ async function validateApiKey(apiKey) {
|
|
|
5647
6088
|
}
|
|
5648
6089
|
function buildServer(apiKey, transport = "stdio") {
|
|
5649
6090
|
return tracer.startActiveSpan("mcp.server.build", { attributes: {
|
|
6091
|
+
"mcp.span.category": "startup",
|
|
5650
6092
|
"mcp.server.name": name,
|
|
5651
6093
|
"mcp.server.version": version,
|
|
5652
6094
|
"mcp.transport": transport
|
|
@@ -5663,12 +6105,15 @@ function buildServer(apiKey, transport = "stdio") {
|
|
|
5663
6105
|
recordOutputs: false
|
|
5664
6106
|
}),
|
|
5665
6107
|
onToolsRegistered: (count) => span.setAttribute("mcp.tools.count", count),
|
|
5666
|
-
observer: createNodeToolObserver()
|
|
6108
|
+
observer: createNodeToolObserver(),
|
|
6109
|
+
cacheObserver: createNodeCacheObserver()
|
|
5667
6110
|
});
|
|
6111
|
+
installSdkErrorTracking(server);
|
|
5668
6112
|
console.error("Hevy client initialized with API key");
|
|
5669
6113
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
5670
6114
|
return server;
|
|
5671
6115
|
} catch (e) {
|
|
6116
|
+
recordLifecycleFailure(span, e, "build");
|
|
5672
6117
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
5673
6118
|
throw e;
|
|
5674
6119
|
} finally {
|
|
@@ -5697,7 +6142,11 @@ async function runStdioServer() {
|
|
|
5697
6142
|
const configuredApiKey = process.env.HEVY_API_KEY;
|
|
5698
6143
|
if (configuredApiKey) setTelemetryUser(configuredApiKey);
|
|
5699
6144
|
let connectAttempted = false;
|
|
5700
|
-
|
|
6145
|
+
let connectSucceeded = false;
|
|
6146
|
+
await tracer.startActiveSpan("mcp.server.run", { attributes: {
|
|
6147
|
+
"mcp.span.category": "startup",
|
|
6148
|
+
"mcp.transport": "stdio"
|
|
6149
|
+
} }, async (span) => {
|
|
5701
6150
|
try {
|
|
5702
6151
|
const apiKey = parseConfig(process.env).apiKey;
|
|
5703
6152
|
assertApiKey(apiKey);
|
|
@@ -5705,11 +6154,16 @@ async function runStdioServer() {
|
|
|
5705
6154
|
console.error("Starting MCP server in stdio mode");
|
|
5706
6155
|
const transport = createInstrumentedStdioTransport(new StdioServerTransport());
|
|
5707
6156
|
connectAttempted = true;
|
|
5708
|
-
await tracer.startActiveSpan("mcp.server.connect", { attributes: {
|
|
6157
|
+
await tracer.startActiveSpan("mcp.server.connect", { attributes: {
|
|
6158
|
+
"mcp.span.category": "session",
|
|
6159
|
+
"mcp.transport": "stdio"
|
|
6160
|
+
} }, async (connectSpan) => {
|
|
5709
6161
|
try {
|
|
5710
6162
|
await server.connect(transport);
|
|
6163
|
+
connectSucceeded = true;
|
|
5711
6164
|
connectSpan.setStatus({ code: SpanStatusCode.OK });
|
|
5712
6165
|
} catch (e) {
|
|
6166
|
+
recordLifecycleFailure(connectSpan, e, "connect");
|
|
5713
6167
|
connectSpan.setStatus({ code: SpanStatusCode.ERROR });
|
|
5714
6168
|
throw e;
|
|
5715
6169
|
} finally {
|
|
@@ -5717,8 +6171,8 @@ async function runStdioServer() {
|
|
|
5717
6171
|
}
|
|
5718
6172
|
});
|
|
5719
6173
|
scheduleUpdateCheck({
|
|
5720
|
-
packageName:
|
|
5721
|
-
currentVersion:
|
|
6174
|
+
packageName: serviceName,
|
|
6175
|
+
currentVersion: serviceVersion
|
|
5722
6176
|
});
|
|
5723
6177
|
installGracefulShutdown({
|
|
5724
6178
|
target: server,
|
|
@@ -5730,6 +6184,7 @@ async function runStdioServer() {
|
|
|
5730
6184
|
});
|
|
5731
6185
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
5732
6186
|
} catch (e) {
|
|
6187
|
+
if (!connectAttempted || connectSucceeded) recordLifecycleFailure(span, e, "run");
|
|
5733
6188
|
recordMcpSessionTermination(connectAttempted ? "connect_failure" : "startup_failure");
|
|
5734
6189
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
5735
6190
|
cleanupProcessExceptionTracking();
|
|
@@ -5756,7 +6211,10 @@ async function runServer() {
|
|
|
5756
6211
|
return;
|
|
5757
6212
|
}
|
|
5758
6213
|
const cleanupProcessExceptionTracking = installProcessExceptionTracking();
|
|
5759
|
-
await tracer.startActiveSpan("mcp.server.run", { attributes: {
|
|
6214
|
+
await tracer.startActiveSpan("mcp.server.run", { attributes: {
|
|
6215
|
+
"mcp.span.category": "startup",
|
|
6216
|
+
"mcp.transport": "http"
|
|
6217
|
+
} }, async (span) => {
|
|
5760
6218
|
let listening = false;
|
|
5761
6219
|
serverStartups.add(1, { version });
|
|
5762
6220
|
try {
|
|
@@ -5768,8 +6226,8 @@ async function runServer() {
|
|
|
5768
6226
|
listening = true;
|
|
5769
6227
|
console.error(`Starting MCP server in HTTP mode at ${options.host}:${options.port}/mcp`);
|
|
5770
6228
|
scheduleUpdateCheck({
|
|
5771
|
-
packageName:
|
|
5772
|
-
currentVersion:
|
|
6229
|
+
packageName: serviceName,
|
|
6230
|
+
currentVersion: serviceVersion
|
|
5773
6231
|
});
|
|
5774
6232
|
installGracefulShutdown({
|
|
5775
6233
|
target: handle,
|
|
@@ -5780,6 +6238,7 @@ async function runServer() {
|
|
|
5780
6238
|
});
|
|
5781
6239
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
5782
6240
|
} catch (error) {
|
|
6241
|
+
recordLifecycleFailure(span, error, "run");
|
|
5783
6242
|
recordMcpSessionTermination(listening ? "unknown" : "startup_failure");
|
|
5784
6243
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
5785
6244
|
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.4",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "hevy-mcp",
|
|
15
|
-
"version": "5.0.
|
|
15
|
+
"version": "5.0.4",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|