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
|
|
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` | `
|
|
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-
|
|
12
|
-
import { a as createSafeErrorDiagnostic } from "./src-
|
|
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-
|
|
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-
|
|
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-
|
|
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] = "
|
|
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-
|
|
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.
|
|
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-
|
|
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()
|
|
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({
|
|
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.
|
|
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.
|
|
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.
|
|
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-
|
|
5309
|
+
//# sourceMappingURL=src-CrDSsc2t.mjs.map
|
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": "6.1.
|
|
10
|
+
"version": "6.1.5",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "hevy-mcp",
|
|
15
|
-
"version": "6.1.
|
|
15
|
+
"version": "6.1.5",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|