hevy-mcp 5.0.0 → 5.0.2

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 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.0" };
7
+ e.SENTRY_RELEASE = { id: "hevy-mcp@5.0.2" };
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-BUmpaxmH.mjs";
12
+ import { a as createSafeErrorDiagnostic, i as MissingHevyApiKeyError, n as runServer, o as flushTelemetry } from "./src-DBkGttYB.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.0" };
7
+ e.SENTRY_RELEASE = { id: "hevy-mcp@5.0.2" };
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-BUmpaxmH.mjs";
12
+ import { n as runServer, r as runStdioServer, t as createNodeMcpServer } from "./src-DBkGttYB.mjs";
13
13
  export { createNodeMcpServer, runServer, runStdioServer };
@@ -4,9 +4,9 @@
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.0" };
7
+ e.SENTRY_RELEASE = { id: "hevy-mcp@5.0.2" };
8
8
  var n = new e.Error().stack;
9
- n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "8ea46bdf-fa98-4e73-b4d5-a25044e016df", e._sentryDebugIdIdentifier = "sentry-dbid-8ea46bdf-fa98-4e73-b4d5-a25044e016df");
9
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "f7cbf1da-83d1-4da1-bb18-1c590afb9b11", e._sentryDebugIdIdentifier = "sentry-dbid-f7cbf1da-83d1-4da1-bb18-1c590afb9b11");
10
10
  } catch (e) {}
11
11
  })();
12
12
  import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
@@ -20,7 +20,7 @@ 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 { z } from "zod";
23
+ import { ZodError, z } from "zod";
24
24
  import { McpServer, deserializeMessage } from "@modelcontextprotocol/server";
25
25
  import { z as z$1 } from "zod/v4";
26
26
  import { isIP } from "node:net";
@@ -80,11 +80,11 @@ function sanitizeSentryMcpSpan(span) {
80
80
  * OTel Collector → Honeycomb: performance traces, metrics
81
81
  */
82
82
  const name$1 = "hevy-mcp";
83
- const version$1 = "5.0.0";
83
+ const version$1 = "5.0.2";
84
84
  const telemetryEnabled = process.env.HEVY_MCP_TELEMETRY !== "0";
85
85
  const collectorToken = "NH9vOela-HYreQxAbJa68cjEmORoEKM57EvneUDVcOo";
86
86
  const COLLECTOR_ENDPOINT = "https://otel.chrisdoc.dev/v1";
87
- const sentryRelease = process.env.SENTRY_RELEASE ?? `hevy-mcp@5.0.0`;
87
+ const sentryRelease = process.env.SENTRY_RELEASE ?? `hevy-mcp@5.0.2`;
88
88
  const resource = resourceFromAttributes({
89
89
  "service.name": name$1,
90
90
  "service.version": version$1
@@ -985,6 +985,28 @@ function normalizeMaxGetRetries(value) {
985
985
  function defaultSleep(milliseconds) {
986
986
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
987
987
  }
988
+ function withTimeout(operation, timeoutMs, onTimeout) {
989
+ return new Promise((resolve, reject) => {
990
+ let settled = false;
991
+ const timer = setTimeout(() => {
992
+ if (settled) return;
993
+ settled = true;
994
+ onTimeout();
995
+ reject(new DOMException("Operation timed out", "AbortError"));
996
+ }, Math.max(0, timeoutMs));
997
+ operation.then((value) => {
998
+ if (settled) return;
999
+ settled = true;
1000
+ clearTimeout(timer);
1001
+ resolve(value);
1002
+ }, (error) => {
1003
+ if (settled) return;
1004
+ settled = true;
1005
+ clearTimeout(timer);
1006
+ reject(error);
1007
+ });
1008
+ });
1009
+ }
988
1010
  function getRequestContext(config) {
989
1011
  const method = (config.method ?? "GET").toUpperCase();
990
1012
  const rawEndpoint = (config.url ?? "").split("?")[0] ?? "";
@@ -1081,14 +1103,15 @@ function createNativeClient(apiKey, baseUrl, options) {
1081
1103
  try {
1082
1104
  const headers = new Headers({ "api-key": apiKey });
1083
1105
  if (normalized.data !== void 0 && !(normalized.data instanceof FormData)) headers.set("content-type", "application/json");
1084
- const response = await fetchImplementation(url, {
1106
+ const attemptDeadline = Date.now() + timeoutMs;
1107
+ const response = await withTimeout(fetchImplementation(url, {
1085
1108
  method,
1086
1109
  headers,
1087
1110
  redirect: "manual",
1088
1111
  body: normalized.data instanceof FormData ? normalized.data : normalized.data === void 0 ? void 0 : JSON.stringify(normalized.data),
1089
1112
  signal: controller.signal
1090
- });
1091
- const data = await parseResponseData(response);
1113
+ }), Math.max(0, attemptDeadline - Date.now()), () => controller.abort());
1114
+ const data = await withTimeout(parseResponseData(response), Math.max(0, attemptDeadline - Date.now()), () => controller.abort());
1092
1115
  if (!response.ok) throw new HevyHttpError(`Hevy API request failed (HTTP ${response.status})`, {
1093
1116
  status: response.status,
1094
1117
  statusText: response.statusText,
@@ -1691,7 +1714,7 @@ const routineSchema = z$1.object({
1691
1714
  exercises: z$1.optional(z$1.array(z$1.object({
1692
1715
  index: z$1.optional(z$1.number().describe("Index indicating the order of the exercise in the routine.")),
1693
1716
  title: z$1.optional(z$1.string().describe("Title of the exercise")),
1694
- rest_seconds: z$1.optional(z$1.string().describe("The rest time in seconds between sets of the exercise")),
1717
+ rest_seconds: z$1.optional(z$1.int().describe("The rest time in seconds between sets of the exercise")),
1695
1718
  notes: z$1.optional(z$1.string().describe("Routine notes on the exercise")),
1696
1719
  exercise_template_id: z$1.optional(z$1.string().describe("The id of the exercise template. This can be used to fetch the exercise template.")),
1697
1720
  supersets_id: z$1.number().describe("The id of the superset that the exercise belongs to. A value of null indicates the exercise is not part of a superset.").nullish(),
@@ -1934,7 +1957,7 @@ const formattedRoutineExerciseSchema = z.object({
1934
1957
  exercise_template_id: z.string().optional(),
1935
1958
  notes: z.string().optional(),
1936
1959
  supersets_id: optionalNumber,
1937
- rest_seconds: z.string().optional(),
1960
+ rest_seconds: z.number().int().optional(),
1938
1961
  sets: z.array(formattedRoutineSetSchema).optional()
1939
1962
  });
1940
1963
  const formattedRoutineSchema = z.object({
@@ -2621,7 +2644,7 @@ function registerHevyResources(server, runtime) {
2621
2644
  //#endregion
2622
2645
  //#region ../core/src/server-metadata.ts
2623
2646
  const SERVER_NAME = "hevy-mcp";
2624
- const SERVER_VERSION = "5.0.0";
2647
+ const SERVER_VERSION = "5.0.2";
2625
2648
  const SERVER_INSTRUCTIONS = [
2626
2649
  "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.",
2627
2650
  "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.",
@@ -5152,6 +5175,10 @@ const SAFE_MCP_METHODS = {
5152
5175
  "prompts/list": true
5153
5176
  };
5154
5177
  const REDACTED_CONTENT_MARKER = "[REDACTED]";
5178
+ const MAX_MALFORMED_LINES_PER_READ = 100;
5179
+ function isMalformedMessageError(error) {
5180
+ return error instanceof SyntaxError || error instanceof ZodError;
5181
+ }
5155
5182
  /**
5156
5183
  * Adapter boundary around MCP SDK stdio internals.
5157
5184
  *
@@ -5177,14 +5204,36 @@ function createSdkPrivateStdioAdapter(transport) {
5177
5204
  installReadMessageHook(onReadLine) {
5178
5205
  const readBuffer = mutableTransport._readBuffer;
5179
5206
  if (!readBuffer || typeof readBuffer.readMessage !== "function") return false;
5207
+ let deferredMessage = null;
5180
5208
  readBuffer.readMessage = () => {
5181
- const buffer = readBuffer._buffer;
5182
- if (!buffer) return null;
5183
- const index = buffer.indexOf("\n");
5184
- if (index === -1) return null;
5185
- const lineBuffer = buffer.subarray(0, index);
5186
- readBuffer._buffer = buffer.subarray(index + 1);
5187
- return onReadLine(lineBuffer.toString("utf8").replace(/\r$/, ""));
5209
+ if (deferredMessage) {
5210
+ const message = deferredMessage;
5211
+ deferredMessage = null;
5212
+ return message;
5213
+ }
5214
+ let skippedMalformedLines = 0;
5215
+ while (true) {
5216
+ const buffer = readBuffer._buffer;
5217
+ if (!buffer) return null;
5218
+ const index = buffer.indexOf("\n");
5219
+ if (index === -1) return null;
5220
+ const lineBuffer = buffer.subarray(0, index);
5221
+ readBuffer._buffer = buffer.subarray(index + 1);
5222
+ const line = lineBuffer.toString("utf8").replace(/\r$/, "");
5223
+ try {
5224
+ return onReadLine(line);
5225
+ } catch (error) {
5226
+ if (!isMalformedMessageError(error)) throw error;
5227
+ skippedMalformedLines += 1;
5228
+ if (skippedMalformedLines >= MAX_MALFORMED_LINES_PER_READ) {
5229
+ setImmediate(() => {
5230
+ const message = readBuffer.readMessage();
5231
+ if (message) deferredMessage = message;
5232
+ });
5233
+ return null;
5234
+ }
5235
+ }
5236
+ }
5188
5237
  };
5189
5238
  return true;
5190
5239
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hevy-mcp",
3
- "version": "5.0.0",
3
+ "version": "5.0.2",
4
4
  "private": false,
5
5
  "description": "A Model Context Protocol (MCP) server implementation that interfaces with the Hevy fitness tracking app and its API.",
6
6
  "repository": {
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.0",
10
+ "version": "5.0.2",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "hevy-mcp",
15
- "version": "5.0.0",
15
+ "version": "5.0.2",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  },