hevy-mcp 6.1.4 → 6.1.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/README.md CHANGED
@@ -320,30 +320,30 @@ These server-provided MCP prompts coordinate common multi-step workflows:
320
320
  and update tools are exposed with MCP mutation annotations so compatible clients
321
321
  can request confirmation.
322
322
 
323
- | Category | Tool | Description |
324
- | ------------------ | --------------------------- | --------------------------------------------------------------------------------- |
325
- | Training analysis | `get-training-summary` | Summarize 1-12 weeks of workout activity and body-measurement trends in one call. |
326
- | Workouts | `get-workouts` | List workouts from newest to oldest with exercise and timing details. |
327
- | Workouts | `get-workout` | Get complete details for one workout by ID. |
328
- | Workouts | `get-workout-events` | List workout update and delete events since a timestamp. |
329
- | Workouts | `create-workout` | Create a completed workout in Hevy. |
330
- | Workouts | `update-workout` | Patch workout metadata by ID; omitted fields and all exercises remain unchanged. |
331
- | Workouts | `replace-workout-exercises` | Replace all exercises and sets while preserving workout metadata. |
332
- | Routines | `search-routines` | Search routine titles and return compact metadata for discovery. |
333
- | Routines | `get-routines` | List custom and default workout routines. |
334
- | Routines | `get-routine` | Get one routine and its exercise configuration by ID. |
335
- | Routines | `create-routine` | Create a reusable workout routine. |
336
- | Routines | `update-routine` | Replace an existing routine's content. |
337
- | Routine folders | `get-routine-folder` | Get one routine folder's metadata by ID. |
338
- | Routine folders | `create-routine-folder` | Create a routine folder. |
339
- | Exercise templates | `get-exercise-template` | Get complete metadata for one exercise template by ID. |
340
- | Exercise templates | `search-exercise-templates` | Search the full exercise catalog by title substring. |
341
- | Exercise templates | `create-exercise-template` | Create a custom exercise template. |
342
- | Exercise history | `get-exercise-history` | Get past performed sets for one exercise template. |
343
- | Body measurements | `get-body-measurements` | List dated body measurements. |
344
- | Body measurements | `get-body-measurement` | Get the body measurement entry for one date. |
345
- | Body measurements | `create-body-measurement` | Create a dated body measurement. |
346
- | Body measurements | `update-body-measurement` | Update the body measurement for an existing date. |
323
+ | Category | Tool | Description |
324
+ | ------------------ | --------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
325
+ | Training analysis | `get-training-summary` | Summarize 1-12 weeks of workout activity and body-measurement trends in one call. |
326
+ | Workouts | `get-workouts` | List workouts from newest to oldest with exercise and timing details. |
327
+ | Workouts | `get-workout` | Get complete details for one workout by ID. |
328
+ | Workouts | `get-workout-events` | List workout update and delete events since a timestamp. |
329
+ | Workouts | `create-workout` | Create a completed workout in Hevy. |
330
+ | Workouts | `update-workout` | Patch workout metadata by ID; `is_private` is required, while other omitted fields and all exercises remain unchanged. |
331
+ | Workouts | `replace-workout-exercises` | Replace all exercises and sets; `is_private` is required and updated, while other workout metadata remains unchanged. |
332
+ | Routines | `search-routines` | Search routine titles and return compact metadata for discovery. |
333
+ | Routines | `get-routines` | List custom and default workout routines. |
334
+ | Routines | `get-routine` | Get one routine and its exercise configuration by ID. |
335
+ | Routines | `create-routine` | Create a reusable workout routine. |
336
+ | Routines | `update-routine` | Replace an existing routine's content. |
337
+ | Routine folders | `get-routine-folder` | Get one routine folder's metadata by ID. |
338
+ | Routine folders | `create-routine-folder` | Create a routine folder. |
339
+ | Exercise templates | `get-exercise-template` | Get complete metadata for one exercise template by ID. |
340
+ | Exercise templates | `search-exercise-templates` | Search the full exercise catalog by title substring. |
341
+ | Exercise templates | `create-exercise-template` | Create a custom exercise template. |
342
+ | Exercise history | `get-exercise-history` | Get past performed sets for one exercise template. |
343
+ | Body measurements | `get-body-measurements` | List dated body measurements. |
344
+ | Body measurements | `get-body-measurement` | Get the body measurement entry for one date. |
345
+ | Body measurements | `create-body-measurement` | Create a dated body measurement. |
346
+ | Body measurements | `update-body-measurement` | Update the body measurement for an existing date. |
347
347
 
348
348
  `create-routine` requires a top-level `routine` envelope with a required `exercises` array; fields use snake_case at every level:
349
349
 
@@ -452,7 +452,7 @@ self-hosted Streamable HTTP.
452
452
  | Setting | Default | Scope | Notes |
453
453
  | -------------------------------- | -------------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
454
454
  | `HEVY_API_KEY` | None; required | Local stdio or HTTP | Hevy API key from the Hevy app. Never pass it in a URL. |
455
- | `HEVY_MCP_API_TIMEOUT` | `30000` ms | Local stdio | Positive Hevy API timeout in milliseconds. Invalid values fall back to 30 seconds. |
455
+ | `HEVY_MCP_API_TIMEOUT` | `60000` ms | Local stdio | Positive Hevy API timeout in milliseconds. Invalid values fall back to 60 seconds. |
456
456
  | `HEVY_MCP_DEBUG` | Disabled | Local Node | Set to exactly `1` for privacy-bounded diagnostics on stderr. Stdout remains reserved for MCP JSON-RPC. |
457
457
  | `HEVY_MCP_HTTP_BEARER_TOKEN` | None | Non-loopback HTTP | Required when `--host` is not loopback; use a separate token, never the Hevy API key. |
458
458
  | `HEVY_MCP_HTTP_MAX_SESSIONS` | `100` | Local HTTP | Maximum established sessions (including sessions currently initializing); excess requests receive `429` and DELETE, disconnect, or idle eviction frees capacity. Invalid values use the default; capped at 10,000. |
package/dist/cli.mjs CHANGED
@@ -8,8 +8,8 @@
8
8
  n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "d805d34c-9cce-4622-8b67-728aa0cacb5d", e._sentryDebugIdIdentifier = "sentry-dbid-d805d34c-9cce-4622-8b67-728aa0cacb5d");
9
9
  } catch (e) {}
10
10
  })();
11
- import { i as flushTelemetry, r as MissingHevyApiKeyError, t as runServer } from "./runtime-CokZbz-j.mjs";
12
- import { a as createSafeErrorDiagnostic } from "./src-DCTiE-_b.mjs";
11
+ import { i as flushTelemetry, r as MissingHevyApiKeyError, t as runServer } from "./runtime-hmSQY75_.mjs";
12
+ import { a as createSafeErrorDiagnostic } from "./src-CrDSsc2t.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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // Generated with tsdown
3
3
  // https://tsdown.dev
4
- import { o as createHevyClient, t as createHevyMcpServer } from "./src-DCTiE-_b.mjs";
4
+ import { o as createHevyClient, t as createHevyMcpServer } from "./src-CrDSsc2t.mjs";
5
5
  import { z } from "zod";
6
6
  //#region src/index.ts
7
7
  const serverConfigSchema = z.object({ apiKey: z.string().min(1, "Hevy API key is required").describe("Your Hevy API key (available in the Hevy app settings).") });
@@ -32,7 +32,7 @@ function createNodeMcpServer({ apiKey }, _transport = "stdio", lifecycleSignal)
32
32
  * does not evaluate the runtime bootstrap; it is loaded only when invoked.
33
33
  */
34
34
  async function runStdioServer() {
35
- const { runStdioServer: run } = await import("./runtime-CokZbz-j.mjs").then((n) => n.n);
35
+ const { runStdioServer: run } = await import("./runtime-hmSQY75_.mjs").then((n) => n.n);
36
36
  return run();
37
37
  }
38
38
  /**
@@ -40,7 +40,7 @@ async function runStdioServer() {
40
40
  * does not evaluate the runtime bootstrap; it is loaded only when invoked.
41
41
  */
42
42
  async function runServer() {
43
- const { runServer: run } = await import("./runtime-CokZbz-j.mjs").then((n) => n.n);
43
+ const { runServer: run } = await import("./runtime-hmSQY75_.mjs").then((n) => n.n);
44
44
  return run();
45
45
  }
46
46
  //#endregion
@@ -5,10 +5,10 @@
5
5
  try {
6
6
  var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
7
7
  var n = new e.Error().stack;
8
- n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "baee8222-0612-498c-a1f8-cf149a59a78b", e._sentryDebugIdIdentifier = "sentry-dbid-baee8222-0612-498c-a1f8-cf149a59a78b");
8
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "7cb09c0b-f923-483d-acbb-7eeebfbb1dcd", e._sentryDebugIdIdentifier = "sentry-dbid-7cb09c0b-f923-483d-acbb-7eeebfbb1dcd");
9
9
  } catch (e) {}
10
10
  })();
11
- import { a as createSafeErrorDiagnostic, c as diagnosticEndpointIdentity, i as bucketCount, l as metricEndpointIdentity, n as createExecutionProjection, o as createHevyClient, r as mergeAbortSignals, s as SAFE_OBSERVATION_CODES, t as createHevyMcpServer, u as isHevyHttpError } from "./src-DCTiE-_b.mjs";
11
+ import { a as createSafeErrorDiagnostic, c as diagnosticEndpointIdentity, i as bucketCount, l as metricEndpointIdentity, n as createExecutionProjection, o as createHevyClient, r as mergeAbortSignals, s as SAFE_OBSERVATION_CODES, t as createHevyMcpServer, u as isHevyHttpError } from "./src-CrDSsc2t.mjs";
12
12
  import { ZodError, z } from "zod";
13
13
  import { deserializeMessage } from "@modelcontextprotocol/server";
14
14
  import * as crypto from "node:crypto";
@@ -394,7 +394,7 @@ function parseBuildString(value, fallback) {
394
394
  return z.string().parse(value ?? fallback);
395
395
  }
396
396
  const name$1 = parseBuildString(readBuildGlobal(() => "hevy-mcp"), "hevy-mcp");
397
- const version$1 = parseBuildString(readBuildGlobal(() => "6.1.4"), "dev");
397
+ const version$1 = parseBuildString(readBuildGlobal(() => "6.1.5"), "dev");
398
398
  const telemetryEnabled = process.env.HEVY_MCP_TELEMETRY !== "0";
399
399
  const collectorToken = z.string().safeParse(readBuildGlobal(() => "NH9vOela-HYreQxAbJa68cjEmORoEKM57EvneUDVcOo")).data ?? process.env.OTEL_COLLECTOR_TOKEN ?? "";
400
400
  const COLLECTOR_ENDPOINT = "https://otel.chrisdoc.dev/v1";
@@ -2699,4 +2699,4 @@ async function runServer() {
2699
2699
  //#endregion
2700
2700
  export { flushTelemetry as i, runtime_exports as n, MissingHevyApiKeyError as r, runServer as t };
2701
2701
 
2702
- //# sourceMappingURL=runtime-CokZbz-j.mjs.map
2702
+ //# sourceMappingURL=runtime-hmSQY75_.mjs.map
@@ -2041,6 +2041,16 @@ function createHevyClient({ apiKey, baseUrl, ...options }) {
2041
2041
  return createClient(apiKey, baseUrl, options);
2042
2042
  }
2043
2043
  //#endregion
2044
+ //#region ../core/src/utils/safe-user-error.ts
2045
+ const WORKOUT_METADATA_PRIVACY_REQUIRED_ERROR = "is_private is required when updating workout metadata. The Hevy API does not return the current privacy setting on GET, so it must be explicitly provided on PUT. Set to true to make the workout private, or false to make it public.";
2046
+ var SafeUserError = class extends Error {
2047
+ safeForUser = true;
2048
+ constructor(message) {
2049
+ super(message);
2050
+ this.name = "SafeUserError";
2051
+ }
2052
+ };
2053
+ //#endregion
2044
2054
  //#region ../core/src/utils/error-policy.ts
2045
2055
  const ABORT_TIMEOUT_METADATA = {
2046
2056
  AbortError: {
@@ -2180,6 +2190,7 @@ function getRetryExhaustedMessage(error) {
2180
2190
  if (attemptCount) return `Unable to complete the request after ${attemptCount} attempts to the Hevy API due to transient failures. Please try again shortly.`;
2181
2191
  return "Unable to complete the request after multiple attempts to the Hevy API due to transient failures. Please try again shortly.";
2182
2192
  }
2193
+ const MAX_SAFE_USER_ERROR_LENGTH = 512;
2183
2194
  /** Classify an error using bounded status, names, and supplied text. */
2184
2195
  function determineErrorType(error, message) {
2185
2196
  if (isRetryExhausted(error)) return "NETWORK_ERROR";
@@ -2305,6 +2316,7 @@ function resolveErrorPolicy(error, defaultMessage, notInitializedMessage) {
2305
2316
  if (isNotInitialized) message = notInitializedMessage ?? defaultMessage;
2306
2317
  else if (isRetryExhausted(error)) message = getRetryExhaustedMessage(error);
2307
2318
  else if (diagnostic.status === 429) message = getRateLimitMessage(error);
2319
+ else if (diagnostic.status === void 0 && error instanceof SafeUserError && error.message && error.message !== notInitializedMessage) message = error.message.slice(0, MAX_SAFE_USER_ERROR_LENGTH);
2308
2320
  return {
2309
2321
  type: determineErrorType(error, message),
2310
2322
  message,
@@ -3368,7 +3380,7 @@ const workoutMetadataPatchSchema = z.strictObject({
3368
3380
  description: z.string().nullable().optional(),
3369
3381
  start_time: utcSecondTimestamp.optional(),
3370
3382
  end_time: utcSecondTimestamp.optional(),
3371
- is_private: z.boolean().optional()
3383
+ is_private: z.boolean()
3372
3384
  }).refine((patch) => Object.values(patch).some((value) => value !== void 0), "Include at least one workout metadata field").meta({ minProperties: 1 });
3373
3385
  const updateWorkoutInputFields = z.strictObject({
3374
3386
  workout_id: nonEmptyId,
@@ -3376,7 +3388,10 @@ const updateWorkoutInputFields = z.strictObject({
3376
3388
  }).shape;
3377
3389
  const replaceWorkoutExercisesInputFields = z.strictObject({
3378
3390
  workout_id: nonEmptyId,
3379
- workout: z.strictObject({ exercises: workoutExercisesSchema })
3391
+ workout: z.strictObject({
3392
+ is_private: z.boolean(),
3393
+ exercises: workoutExercisesSchema
3394
+ })
3380
3395
  }).shape;
3381
3396
  const routineSetFields = {
3382
3397
  type: setTypeEnum,
@@ -3575,6 +3590,7 @@ function preserveWorkoutExercises(current) {
3575
3590
  })) ?? [];
3576
3591
  }
3577
3592
  function buildWorkoutUpdatePayload(current, patch, replacementExercises) {
3593
+ if (replacementExercises === void 0 && patch.is_private === void 0) throw new SafeUserError(WORKOUT_METADATA_PRIVACY_REQUIRED_ERROR);
3578
3594
  const payload = {
3579
3595
  ...workoutUpdateMetadataSchema.parse({
3580
3596
  title: patch.title !== void 0 ? patch.title : current.title,
@@ -4282,7 +4298,7 @@ const hevyToolDefinitions = [
4282
4298
  name: "update-workout",
4283
4299
  feature: "workouts",
4284
4300
  operation: "update",
4285
- description: "Mutates workout metadata by ID. Omitted fields and all exercises remain unchanged.",
4301
+ description: "Mutates workout metadata by ID. is_private must be supplied explicitly because the Hevy API requires it on PUT; omitted fields and all exercises otherwise remain unchanged.",
4286
4302
  inputSchema: updateWorkoutSchema,
4287
4303
  annotations: updateAnnotations("Update Workout"),
4288
4304
  kind: "write",
@@ -4300,14 +4316,14 @@ const hevyToolDefinitions = [
4300
4316
  name: "replace-workout-exercises",
4301
4317
  feature: "workouts",
4302
4318
  operation: "update",
4303
- description: "Mutates a workout by replacing all exercises and sets. Workout metadata remains unchanged.",
4319
+ description: "Mutates a workout by replacing all exercises and sets. is_private must be supplied explicitly and is updated with the request; other workout metadata remains unchanged.",
4304
4320
  inputSchema: replaceWorkoutExercisesSchema,
4305
4321
  annotations: updateAnnotations("Replace Workout Exercises"),
4306
4322
  kind: "write",
4307
4323
  responseContract: updateWorkoutResponse,
4308
4324
  execute: async (runtime, args) => {
4309
4325
  const client = runtime.getClient();
4310
- const payload = buildWorkoutUpdatePayload(await client.getWorkout(args.workout_id), {}, args.workout.exercises);
4326
+ const payload = buildWorkoutUpdatePayload(await client.getWorkout(args.workout_id), { is_private: args.workout.is_private }, args.workout.exercises);
4311
4327
  return {
4312
4328
  workout: await client.updateWorkout(args.workout_id, { workout: payload }),
4313
4329
  workout_id: args.workout_id
@@ -4706,7 +4722,7 @@ function readBuildGlobal(value) {
4706
4722
  }
4707
4723
  }
4708
4724
  const buildName = readBuildGlobal(() => "hevy-mcp");
4709
- const buildVersion = readBuildGlobal(() => "6.1.4");
4725
+ const buildVersion = readBuildGlobal(() => "6.1.5");
4710
4726
  const SERVER_NAME = readBuildString(buildName, "hevy-mcp");
4711
4727
  const SERVER_VERSION = readBuildString(buildVersion, "dev");
4712
4728
  const SERVER_INSTRUCTIONS = [
@@ -5290,4 +5306,4 @@ function createHevyMcpServer(options) {
5290
5306
  //#endregion
5291
5307
  export { createSafeErrorDiagnostic as a, diagnosticEndpointIdentity as c, bucketCount as i, metricEndpointIdentity as l, createExecutionProjection as n, createHevyClient as o, mergeAbortSignals as r, SAFE_OBSERVATION_CODES as s, createHevyMcpServer as t, isHevyHttpError as u };
5292
5308
 
5293
- //# sourceMappingURL=src-DCTiE-_b.mjs.map
5309
+ //# sourceMappingURL=src-CrDSsc2t.mjs.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hevy-mcp",
3
- "version": "6.1.4",
3
+ "version": "6.1.5",
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": "6.1.4",
10
+ "version": "6.1.5",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "hevy-mcp",
15
- "version": "6.1.4",
15
+ "version": "6.1.5",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  },