hevy-mcp 1.23.23 → 1.24.0
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 +9 -7
- package/dist/cli.mjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{src-Cag-RZcj.mjs → src-BVYF4gvN.mjs} +147 -50
- package/dist/src-BVYF4gvN.mjs.map +1 -0
- package/package.json +27 -27
- package/dist/src-Cag-RZcj.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -167,13 +167,15 @@ Docker-based workflows are retired. The provided `Dockerfile` now exits with a m
|
|
|
167
167
|
|
|
168
168
|
## 🛠️ Available MCP Tools
|
|
169
169
|
|
|
170
|
-
| Category
|
|
171
|
-
|
|
|
172
|
-
| **Workouts**
|
|
173
|
-
| **Routines**
|
|
174
|
-
| **Templates**
|
|
175
|
-
| **Folders**
|
|
176
|
-
| **
|
|
170
|
+
| Category | Tools |
|
|
171
|
+
| :-------------------- | :--------------------------------------------------------------------------------------------------------------------------------- |
|
|
172
|
+
| **Workouts** | `get-workouts`, `get-workout`, `create-workout`, `update-workout`, `get-workout-count`, `get-workout-events` |
|
|
173
|
+
| **Routines** | `get-routines`, `get-routine`, `create-routine`, `update-routine` |
|
|
174
|
+
| **Templates** | `get-exercise-templates`, `get-exercise-template`, `search-exercise-templates`, `create-exercise-template`, `get-exercise-history` |
|
|
175
|
+
| **Folders** | `get-routine-folders`, `get-routine-folder`, `create-routine-folder` |
|
|
176
|
+
| **Body Measurements** | `get-body-measurements`, `get-body-measurement`, `create-body-measurement`, `update-body-measurement` |
|
|
177
|
+
| **User** | `get-user-info` |
|
|
178
|
+
| **Webhooks** | `get-webhook-subscription`, `create-webhook-subscription`, `delete-webhook-subscription` |
|
|
177
179
|
|
|
178
180
|
---
|
|
179
181
|
|
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: "1.23.
|
|
7
|
+
e.SENTRY_RELEASE = { id: "1.23.24" };
|
|
8
8
|
var n = new e.Error().stack;
|
|
9
9
|
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "03cf150f-6466-4ca3-9cf1-e423cc0e3cd2", e._sentryDebugIdIdentifier = "sentry-dbid-03cf150f-6466-4ca3-9cf1-e423cc0e3cd2");
|
|
10
10
|
} catch (e) {}
|
|
11
11
|
})();
|
|
12
|
-
import { r as runServer } from "./src-
|
|
12
|
+
import { r as runServer } from "./src-BVYF4gvN.mjs";
|
|
13
13
|
//#region src/cli.ts
|
|
14
14
|
runServer().catch((error) => {
|
|
15
15
|
console.error("Fatal error in main():", error);
|
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: "1.23.
|
|
7
|
+
e.SENTRY_RELEASE = { id: "1.23.24" };
|
|
8
8
|
var n = new e.Error().stack;
|
|
9
9
|
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "a23492d3-dc93-45b6-9c01-6c2fef1c843d", e._sentryDebugIdIdentifier = "sentry-dbid-a23492d3-dc93-45b6-9c01-6c2fef1c843d");
|
|
10
10
|
} catch (e) {}
|
|
11
11
|
})();
|
|
12
|
-
import { n as createServer, r as runServer, t as configSchema } from "./src-
|
|
12
|
+
import { n as createServer, r as runServer, t as configSchema } from "./src-BVYF4gvN.mjs";
|
|
13
13
|
export { configSchema, createServer as default, runServer };
|
|
@@ -4,12 +4,13 @@
|
|
|
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: "1.23.
|
|
7
|
+
e.SENTRY_RELEASE = { id: "1.23.24" };
|
|
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] = "257cac61-1bfb-4591-a911-cb5e1e8f1dbd", e._sentryDebugIdIdentifier = "sentry-dbid-257cac61-1bfb-4591-a911-cb5e1e8f1dbd");
|
|
10
10
|
} catch (e) {}
|
|
11
11
|
})();
|
|
12
12
|
import * as Sentry from "@sentry/node";
|
|
13
|
+
import { createHmac } from "node:crypto";
|
|
13
14
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
14
15
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15
16
|
import { z } from "zod";
|
|
@@ -294,8 +295,51 @@ function createEmptyResponse(message = "No data found") {
|
|
|
294
295
|
}] };
|
|
295
296
|
}
|
|
296
297
|
//#endregion
|
|
298
|
+
//#region src/utils/tool-annotations.ts
|
|
299
|
+
/** Read-only tools (get-*, search-*): no side effects. */
|
|
300
|
+
function readOnlyAnnotations(title) {
|
|
301
|
+
return {
|
|
302
|
+
title,
|
|
303
|
+
readOnlyHint: true,
|
|
304
|
+
openWorldHint: false
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
/** Create tools (create-*): additive writes, repeating creates duplicates. */
|
|
308
|
+
function createAnnotations(title) {
|
|
309
|
+
return {
|
|
310
|
+
title,
|
|
311
|
+
readOnlyHint: false,
|
|
312
|
+
destructiveHint: false,
|
|
313
|
+
idempotentHint: false,
|
|
314
|
+
openWorldHint: false
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Update tools (update-*): PUT-style overwrites that replace prior data,
|
|
319
|
+
* hence destructive; repeating the same call yields the same state.
|
|
320
|
+
*/
|
|
321
|
+
function updateAnnotations(title) {
|
|
322
|
+
return {
|
|
323
|
+
title,
|
|
324
|
+
readOnlyHint: false,
|
|
325
|
+
destructiveHint: true,
|
|
326
|
+
idempotentHint: true,
|
|
327
|
+
openWorldHint: false
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/** Delete tools (delete-*): destructive and idempotent. */
|
|
331
|
+
function destructiveAnnotations(title) {
|
|
332
|
+
return {
|
|
333
|
+
title,
|
|
334
|
+
readOnlyHint: false,
|
|
335
|
+
destructiveHint: true,
|
|
336
|
+
idempotentHint: true,
|
|
337
|
+
openWorldHint: false
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
//#endregion
|
|
297
341
|
//#region src/tools/body-measurements.ts
|
|
298
|
-
const zNullableNumber = z.number().nullable().optional();
|
|
342
|
+
const zNullableNumber = z.preprocess((value) => value === "" ? void 0 : value, z.coerce.number().nullable().optional());
|
|
299
343
|
const bodyMeasurementFieldsSchema = {
|
|
300
344
|
weightKg: zNullableNumber.describe("Body weight in kilograms"),
|
|
301
345
|
leanMassKg: zNullableNumber.describe("Lean body mass in kilograms"),
|
|
@@ -315,33 +359,39 @@ const bodyMeasurementFieldsSchema = {
|
|
|
315
359
|
leftCalf: zNullableNumber.describe("Left calf circumference in centimeters"),
|
|
316
360
|
rightCalf: zNullableNumber.describe("Right calf circumference in centimeters")
|
|
317
361
|
};
|
|
362
|
+
const MEASUREMENT_FIELD_TO_API_KEY = {
|
|
363
|
+
weightKg: "weight_kg",
|
|
364
|
+
leanMassKg: "lean_mass_kg",
|
|
365
|
+
fatPercent: "fat_percent",
|
|
366
|
+
neckCm: "neck_cm",
|
|
367
|
+
shoulderCm: "shoulder_cm",
|
|
368
|
+
chestCm: "chest_cm",
|
|
369
|
+
leftBicepCm: "left_bicep_cm",
|
|
370
|
+
rightBicepCm: "right_bicep_cm",
|
|
371
|
+
leftForearmCm: "left_forearm_cm",
|
|
372
|
+
rightForearmCm: "right_forearm_cm",
|
|
373
|
+
abdomen: "abdomen",
|
|
374
|
+
waist: "waist",
|
|
375
|
+
hips: "hips",
|
|
376
|
+
leftThigh: "left_thigh",
|
|
377
|
+
rightThigh: "right_thigh",
|
|
378
|
+
leftCalf: "left_calf",
|
|
379
|
+
rightCalf: "right_calf"
|
|
380
|
+
};
|
|
318
381
|
function buildMeasurementPayload(args) {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
chest_cm: args.chestCm ?? null,
|
|
326
|
-
left_bicep_cm: args.leftBicepCm ?? null,
|
|
327
|
-
right_bicep_cm: args.rightBicepCm ?? null,
|
|
328
|
-
left_forearm_cm: args.leftForearmCm ?? null,
|
|
329
|
-
right_forearm_cm: args.rightForearmCm ?? null,
|
|
330
|
-
abdomen: args.abdomen ?? null,
|
|
331
|
-
waist: args.waist ?? null,
|
|
332
|
-
hips: args.hips ?? null,
|
|
333
|
-
left_thigh: args.leftThigh ?? null,
|
|
334
|
-
right_thigh: args.rightThigh ?? null,
|
|
335
|
-
left_calf: args.leftCalf ?? null,
|
|
336
|
-
right_calf: args.rightCalf ?? null
|
|
337
|
-
};
|
|
382
|
+
const payload = {};
|
|
383
|
+
for (const [camelKey, apiKey] of Object.entries(MEASUREMENT_FIELD_TO_API_KEY)) {
|
|
384
|
+
const value = args[camelKey];
|
|
385
|
+
if (value != null) payload[apiKey] = value;
|
|
386
|
+
}
|
|
387
|
+
return payload;
|
|
338
388
|
}
|
|
339
389
|
function registerBodyMeasurementTools(server, hevyClient) {
|
|
340
390
|
const getBodyMeasurementsSchema = {
|
|
341
391
|
page: z.coerce.number().int().gte(1).default(1),
|
|
342
392
|
pageSize: z.coerce.number().int().gte(1).lte(10).default(10)
|
|
343
393
|
};
|
|
344
|
-
server.tool("get-body-measurements", "Get a paginated list of body measurements for the authenticated user. Returns measurements including weight, body fat, and various circumference measurements.", getBodyMeasurementsSchema, withErrorHandling(async (args) => {
|
|
394
|
+
server.tool("get-body-measurements", "Get a paginated list of body measurements for the authenticated user. Returns measurements including weight, body fat, and various circumference measurements.", getBodyMeasurementsSchema, readOnlyAnnotations("Get Body Measurements"), withErrorHandling(async (args) => {
|
|
345
395
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
346
396
|
const { page, pageSize } = args;
|
|
347
397
|
const measurements = (await hevyClient.getBodyMeasurements({
|
|
@@ -352,7 +402,7 @@ function registerBodyMeasurementTools(server, hevyClient) {
|
|
|
352
402
|
return createJsonResponse(measurements);
|
|
353
403
|
}, "get-body-measurements"));
|
|
354
404
|
const getBodyMeasurementSchema = { date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format").describe("The date of the body measurement (YYYY-MM-DD)") };
|
|
355
|
-
server.tool("get-body-measurement", "Get a single body measurement by date. Returns all measurement fields for the specified date.", getBodyMeasurementSchema, withErrorHandling(async (args) => {
|
|
405
|
+
server.tool("get-body-measurement", "Get a single body measurement by date. Returns all measurement fields for the specified date.", getBodyMeasurementSchema, readOnlyAnnotations("Get Body Measurement"), withErrorHandling(async (args) => {
|
|
356
406
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
357
407
|
const { date } = args;
|
|
358
408
|
const data = await hevyClient.getBodyMeasurement(date);
|
|
@@ -363,7 +413,7 @@ function registerBodyMeasurementTools(server, hevyClient) {
|
|
|
363
413
|
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format").describe("The date of the body measurement (YYYY-MM-DD). Must be unique — returns 409 if an entry already exists for this date."),
|
|
364
414
|
...bodyMeasurementFieldsSchema
|
|
365
415
|
};
|
|
366
|
-
server.tool("create-body-measurement", "Create a body measurement entry for a given date. All measurement fields are optional. Returns 409 if an entry already exists for that date — use update-body-measurement instead.", createBodyMeasurementSchema, withErrorHandling(async (args) => {
|
|
416
|
+
server.tool("create-body-measurement", "Create a body measurement entry for a given date. All measurement fields are optional; null values are treated as omitted, since the Hevy API does not support clearing individual fields. Returns 409 if an entry already exists for that date — use update-body-measurement instead.", createBodyMeasurementSchema, createAnnotations("Create Body Measurement"), withErrorHandling(async (args) => {
|
|
367
417
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
368
418
|
const { date, ...fields } = args;
|
|
369
419
|
await hevyClient.createBodyMeasurement({
|
|
@@ -376,10 +426,12 @@ function registerBodyMeasurementTools(server, hevyClient) {
|
|
|
376
426
|
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format").describe("The date of the body measurement to update (YYYY-MM-DD). Must already exist — returns 404 otherwise."),
|
|
377
427
|
...bodyMeasurementFieldsSchema
|
|
378
428
|
};
|
|
379
|
-
server.tool("update-body-measurement", "Update an existing body measurement entry for a given date.
|
|
429
|
+
server.tool("update-body-measurement", "Update an existing body measurement entry for a given date. Only the fields you provide are sent and updated; null values are treated as omitted, since the Hevy API does not support clearing individual fields. Requires at least one measurement field. Returns 404 if no entry exists for the date.", updateBodyMeasurementSchema, updateAnnotations("Update Body Measurement"), withErrorHandling(async (args) => {
|
|
380
430
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
381
431
|
const { date, ...fields } = args;
|
|
382
|
-
|
|
432
|
+
const payload = buildMeasurementPayload(fields);
|
|
433
|
+
if (Object.keys(payload).length === 0) throw new Error("No measurement fields provided. Include at least one numeric measurement field (e.g. weightKg) to update.");
|
|
434
|
+
await hevyClient.updateBodyMeasurement(date, payload);
|
|
383
435
|
return createTextResponse(`Body measurement for ${date} updated successfully.`);
|
|
384
436
|
}, "update-body-measurement"));
|
|
385
437
|
}
|
|
@@ -393,7 +445,7 @@ function registerFolderTools(server, hevyClient) {
|
|
|
393
445
|
page: z.coerce.number().int().gte(1).default(1),
|
|
394
446
|
pageSize: z.coerce.number().int().gte(1).lte(10).default(5)
|
|
395
447
|
};
|
|
396
|
-
server.tool("get-routine-folders", "Get a paginated list of your routine folders, including both default and custom folders. Useful for organizing and browsing your workout routines.", getRoutineFoldersSchema, withErrorHandling(async (args) => {
|
|
448
|
+
server.tool("get-routine-folders", "Get a paginated list of your routine folders, including both default and custom folders. Useful for organizing and browsing your workout routines.", getRoutineFoldersSchema, readOnlyAnnotations("Get Routine Folders"), withErrorHandling(async (args) => {
|
|
397
449
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
398
450
|
const { page, pageSize } = args;
|
|
399
451
|
const folders = (await hevyClient.getRoutineFolders({
|
|
@@ -404,7 +456,7 @@ function registerFolderTools(server, hevyClient) {
|
|
|
404
456
|
return createJsonResponse(folders);
|
|
405
457
|
}, "get-routine-folders"));
|
|
406
458
|
const getRoutineFolderSchema = { folderId: z.string().min(1) };
|
|
407
|
-
server.tool("get-routine-folder", "Get complete details of a specific routine folder by its ID, including name, creation date, and associated routines.", getRoutineFolderSchema, withErrorHandling(async (args) => {
|
|
459
|
+
server.tool("get-routine-folder", "Get complete details of a specific routine folder by its ID, including name, creation date, and associated routines.", getRoutineFolderSchema, readOnlyAnnotations("Get Routine Folder"), withErrorHandling(async (args) => {
|
|
408
460
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
409
461
|
const { folderId } = args;
|
|
410
462
|
const data = await hevyClient.getRoutineFolder(folderId);
|
|
@@ -412,7 +464,7 @@ function registerFolderTools(server, hevyClient) {
|
|
|
412
464
|
return createJsonResponse(formatRoutineFolder(data));
|
|
413
465
|
}, "get-routine-folder"));
|
|
414
466
|
const createRoutineFolderSchema = { name: z.string().min(1) };
|
|
415
|
-
server.tool("create-routine-folder", "Create a new routine folder in your Hevy account. Requires a name for the folder. Returns the full folder details including the new folder ID.", createRoutineFolderSchema, withErrorHandling(async (args) => {
|
|
467
|
+
server.tool("create-routine-folder", "Create a new routine folder in your Hevy account. Requires a name for the folder. Returns the full folder details including the new folder ID.", createRoutineFolderSchema, createAnnotations("Create Routine Folder"), withErrorHandling(async (args) => {
|
|
416
468
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
417
469
|
const { name } = args;
|
|
418
470
|
const data = await hevyClient.createRoutineFolder({ routine_folder: { title: name } });
|
|
@@ -493,7 +545,7 @@ function registerRoutineTools(server, hevyClient) {
|
|
|
493
545
|
page: z.coerce.number().int().gte(1).default(1),
|
|
494
546
|
pageSize: z.coerce.number().int().gte(1).lte(10).default(5)
|
|
495
547
|
};
|
|
496
|
-
server.tool("get-routines", "Get a paginated list of your workout routines, including custom and default routines. Useful for browsing or searching your available routines.", getRoutinesSchema, withErrorHandling(async (args) => {
|
|
548
|
+
server.tool("get-routines", "Get a paginated list of your workout routines, including custom and default routines. Useful for browsing or searching your available routines.", getRoutinesSchema, readOnlyAnnotations("Get Routines"), withErrorHandling(async (args) => {
|
|
497
549
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
498
550
|
const { page, pageSize } = args;
|
|
499
551
|
const routines = (await hevyClient.getRoutines({
|
|
@@ -504,7 +556,7 @@ function registerRoutineTools(server, hevyClient) {
|
|
|
504
556
|
return createJsonResponse(routines);
|
|
505
557
|
}, "get-routines"));
|
|
506
558
|
const getRoutineSchema = { routineId: z.string().min(1) };
|
|
507
|
-
server.tool("get-routine", "Get a routine by its ID using the direct endpoint. Returns all details for the specified routine.", getRoutineSchema, withErrorHandling(async (args) => {
|
|
559
|
+
server.tool("get-routine", "Get a routine by its ID using the direct endpoint. Returns all details for the specified routine.", getRoutineSchema, readOnlyAnnotations("Get Routine"), withErrorHandling(async (args) => {
|
|
508
560
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
509
561
|
const { routineId } = args;
|
|
510
562
|
const data = await hevyClient.getRoutineById(String(routineId));
|
|
@@ -539,7 +591,7 @@ function registerRoutineTools(server, hevyClient) {
|
|
|
539
591
|
}))
|
|
540
592
|
})))
|
|
541
593
|
};
|
|
542
|
-
server.tool("create-routine", "Create a new workout routine in your Hevy account. Requires a title and at least one exercise with sets. Optionally assign to a folder. Returns the full routine details including the new routine ID.", createRoutineSchema, withErrorHandling(async (args) => {
|
|
594
|
+
server.tool("create-routine", "Create a new workout routine in your Hevy account. Requires a title and at least one exercise with sets. Optionally assign to a folder. Returns the full routine details including the new routine ID.", createRoutineSchema, createAnnotations("Create Routine"), withErrorHandling(async (args) => {
|
|
543
595
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
544
596
|
const { title, folderId, notes, exercises } = args;
|
|
545
597
|
let usesRepRanges = false;
|
|
@@ -611,7 +663,7 @@ function registerRoutineTools(server, hevyClient) {
|
|
|
611
663
|
}))
|
|
612
664
|
})))
|
|
613
665
|
};
|
|
614
|
-
server.tool("update-routine", "Update an existing routine by ID. You can modify the title, notes, and exercise configurations. Returns the updated routine with all changes applied.", updateRoutineSchema, withErrorHandling(async (args) => {
|
|
666
|
+
server.tool("update-routine", "Update an existing routine by ID. You can modify the title, notes, and exercise configurations. Returns the updated routine with all changes applied.", updateRoutineSchema, updateAnnotations("Update Routine"), withErrorHandling(async (args) => {
|
|
615
667
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
616
668
|
const { routineId, title, notes, exercises } = args;
|
|
617
669
|
let usesRepRanges = false;
|
|
@@ -689,7 +741,7 @@ function registerTemplateTools(server, hevyClient) {
|
|
|
689
741
|
page: z.coerce.number().int().gte(1).default(1),
|
|
690
742
|
pageSize: z.coerce.number().int().gte(1).lte(100).default(5)
|
|
691
743
|
};
|
|
692
|
-
server.tool("get-exercise-templates", "Get a paginated list of exercise templates (default and custom) with details like name, category, equipment, and muscle groups. Useful for browsing or searching available exercises.", getExerciseTemplatesSchema, withErrorHandling(async (args) => {
|
|
744
|
+
server.tool("get-exercise-templates", "Get a paginated list of exercise templates (default and custom) with details like name, category, equipment, and muscle groups. Useful for browsing or searching available exercises.", getExerciseTemplatesSchema, readOnlyAnnotations("Get Exercise Templates"), withErrorHandling(async (args) => {
|
|
693
745
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
694
746
|
const { page, pageSize } = args;
|
|
695
747
|
const templates = (await hevyClient.getExerciseTemplates({
|
|
@@ -700,7 +752,7 @@ function registerTemplateTools(server, hevyClient) {
|
|
|
700
752
|
return createJsonResponse(templates);
|
|
701
753
|
}, "get-exercise-templates"));
|
|
702
754
|
const getExerciseTemplateSchema = { exerciseTemplateId: z.string().min(1) };
|
|
703
|
-
server.tool("get-exercise-template", "Get complete details of a specific exercise template by its ID, including name, category, equipment, muscle groups, and notes.", getExerciseTemplateSchema, withErrorHandling(async (args) => {
|
|
755
|
+
server.tool("get-exercise-template", "Get complete details of a specific exercise template by its ID, including name, category, equipment, muscle groups, and notes.", getExerciseTemplateSchema, readOnlyAnnotations("Get Exercise Template"), withErrorHandling(async (args) => {
|
|
704
756
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
705
757
|
const { exerciseTemplateId } = args;
|
|
706
758
|
const data = await hevyClient.getExerciseTemplate(exerciseTemplateId);
|
|
@@ -712,7 +764,7 @@ function registerTemplateTools(server, hevyClient) {
|
|
|
712
764
|
startDate: z.string().datetime({ offset: true }).describe("ISO 8601 start date for filtering history").optional(),
|
|
713
765
|
endDate: z.string().datetime({ offset: true }).describe("ISO 8601 end date for filtering history").optional()
|
|
714
766
|
};
|
|
715
|
-
server.tool("get-exercise-history", "Get past sets for a specific exercise template, optionally filtered by start and end dates.", getExerciseHistorySchema, withErrorHandling(async (args) => {
|
|
767
|
+
server.tool("get-exercise-history", "Get past sets for a specific exercise template, optionally filtered by start and end dates.", getExerciseHistorySchema, readOnlyAnnotations("Get Exercise History"), withErrorHandling(async (args) => {
|
|
716
768
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
717
769
|
const { exerciseTemplateId, startDate, endDate } = args;
|
|
718
770
|
const history = (await hevyClient.getExerciseHistory(exerciseTemplateId, {
|
|
@@ -748,7 +800,7 @@ function registerTemplateTools(server, hevyClient) {
|
|
|
748
800
|
muscleGroup: z.enum(MUSCLE_GROUPS),
|
|
749
801
|
otherMuscles: z.array(z.enum(MUSCLE_GROUPS)).default([])
|
|
750
802
|
};
|
|
751
|
-
server.tool("create-exercise-template", "Create a custom exercise template with title, type, equipment, and muscle groups.", createExerciseTemplateSchema, withErrorHandling(async (args) => {
|
|
803
|
+
server.tool("create-exercise-template", "Create a custom exercise template with title, type, equipment, and muscle groups.", createExerciseTemplateSchema, createAnnotations("Create Exercise Template"), withErrorHandling(async (args) => {
|
|
752
804
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
753
805
|
const { title, exerciseType, equipmentCategory, muscleGroup, otherMuscles } = args;
|
|
754
806
|
return createJsonResponse({
|
|
@@ -767,7 +819,7 @@ function registerTemplateTools(server, hevyClient) {
|
|
|
767
819
|
primaryMuscleGroup: z.enum(MUSCLE_GROUPS).optional().describe("Optional filter to restrict results to a specific primary muscle group"),
|
|
768
820
|
refresh: z.boolean().optional().default(false).describe("Set to true to bust the in-memory cache and re-fetch all templates from the API")
|
|
769
821
|
};
|
|
770
|
-
server.tool("search-exercise-templates", "Search exercise templates by name with optional muscle group filter. Fetches all templates from the Hevy API on first call and caches them in memory for subsequent searches. Use refresh:true to force a re-fetch.", searchExerciseTemplatesSchema, withErrorHandling(async (args) => {
|
|
822
|
+
server.tool("search-exercise-templates", "Search exercise templates by name with optional muscle group filter. Fetches all templates from the Hevy API on first call and caches them in memory for subsequent searches. Use refresh:true to force a re-fetch.", searchExerciseTemplatesSchema, readOnlyAnnotations("Search Exercise Templates"), withErrorHandling(async (args) => {
|
|
771
823
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
772
824
|
const { query, primaryMuscleGroup, refresh } = args;
|
|
773
825
|
if (exerciseTemplateCache === null || refresh) {
|
|
@@ -801,6 +853,16 @@ function registerTemplateTools(server, hevyClient) {
|
|
|
801
853
|
}, "search-exercise-templates"));
|
|
802
854
|
}
|
|
803
855
|
//#endregion
|
|
856
|
+
//#region src/tools/user.ts
|
|
857
|
+
function registerUserTools(server, hevyClient) {
|
|
858
|
+
server.tool("get-user-info", "Get the authenticated user's account info, including user ID, display name, and public profile URL. Useful for verifying which account the API key belongs to.", {}, readOnlyAnnotations("Get User Info"), withErrorHandling(async (_args) => {
|
|
859
|
+
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
860
|
+
const data = await hevyClient.getUserInfo();
|
|
861
|
+
if (!data?.data) return createEmptyResponse("No user info found for the authenticated user");
|
|
862
|
+
return createJsonResponse(data.data);
|
|
863
|
+
}, "get-user-info"));
|
|
864
|
+
}
|
|
865
|
+
//#endregion
|
|
804
866
|
//#region src/tools/webhooks.ts
|
|
805
867
|
const webhookUrlSchema = z.string().url().refine((url) => {
|
|
806
868
|
try {
|
|
@@ -818,7 +880,7 @@ const webhookUrlSchema = z.string().url().refine((url) => {
|
|
|
818
880
|
}
|
|
819
881
|
}, { message: "Webhook URL cannot be localhost or loopback address" });
|
|
820
882
|
function registerWebhookTools(server, hevyClient) {
|
|
821
|
-
server.tool("get-webhook-subscription", "Get the current webhook subscription for this account. Returns the webhook URL and auth token if a subscription exists.", {}, withErrorHandling(async (_args) => {
|
|
883
|
+
server.tool("get-webhook-subscription", "Get the current webhook subscription for this account. Returns the webhook URL and auth token if a subscription exists.", {}, readOnlyAnnotations("Get Webhook Subscription"), withErrorHandling(async (_args) => {
|
|
822
884
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
823
885
|
if (!hevyClient.getWebhookSubscription) throw new Error("Webhook subscription API not available. Please regenerate the client from the updated OpenAPI spec.");
|
|
824
886
|
const data = await hevyClient.getWebhookSubscription();
|
|
@@ -829,7 +891,7 @@ function registerWebhookTools(server, hevyClient) {
|
|
|
829
891
|
url: webhookUrlSchema.describe("The webhook URL that will receive POST requests when workouts are created"),
|
|
830
892
|
authToken: z.string().optional().describe("Optional auth token that will be sent as Authorization header in webhook requests")
|
|
831
893
|
};
|
|
832
|
-
server.tool("create-webhook-subscription", "Create a new webhook subscription for this account. The webhook will receive POST requests when workouts are created. Your endpoint must respond with 200 OK within 5 seconds.", createWebhookSubscriptionSchema, withErrorHandling(async (args) => {
|
|
894
|
+
server.tool("create-webhook-subscription", "Create a new webhook subscription for this account. The webhook will receive POST requests when workouts are created. Your endpoint must respond with 200 OK within 5 seconds.", createWebhookSubscriptionSchema, createAnnotations("Create Webhook Subscription"), withErrorHandling(async (args) => {
|
|
833
895
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
834
896
|
const { url, authToken } = args;
|
|
835
897
|
if (!hevyClient.createWebhookSubscription) throw new Error("Webhook subscription API not available. Please regenerate the client from the updated OpenAPI spec.");
|
|
@@ -840,7 +902,7 @@ function registerWebhookTools(server, hevyClient) {
|
|
|
840
902
|
if (!data) return createEmptyResponse("Failed to create webhook subscription - please check your URL and try again");
|
|
841
903
|
return createJsonResponse(data);
|
|
842
904
|
}, "create-webhook-subscription"));
|
|
843
|
-
server.tool("delete-webhook-subscription", "Delete the current webhook subscription for this account. This will stop all webhook notifications.", {}, withErrorHandling(async (_args) => {
|
|
905
|
+
server.tool("delete-webhook-subscription", "Delete the current webhook subscription for this account. This will stop all webhook notifications.", {}, destructiveAnnotations("Delete Webhook Subscription"), withErrorHandling(async (_args) => {
|
|
844
906
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
845
907
|
if (!hevyClient.deleteWebhookSubscription) throw new Error("Webhook subscription API not available. Please regenerate the client from the updated OpenAPI spec.");
|
|
846
908
|
const data = await hevyClient.deleteWebhookSubscription();
|
|
@@ -858,7 +920,7 @@ function registerWorkoutTools(server, hevyClient) {
|
|
|
858
920
|
page: z.coerce.number().gte(1).default(1),
|
|
859
921
|
pageSize: z.coerce.number().int().gte(1).lte(10).default(5)
|
|
860
922
|
};
|
|
861
|
-
server.tool("get-workouts", "Get a paginated list of workouts. Returns workout details including title, description, start/end times, and exercises performed. Results are ordered from newest to oldest.", getWorkoutsSchema, withErrorHandling(async (args) => {
|
|
923
|
+
server.tool("get-workouts", "Get a paginated list of workouts. Returns workout details including title, description, start/end times, and exercises performed. Results are ordered from newest to oldest.", getWorkoutsSchema, readOnlyAnnotations("Get Workouts"), withErrorHandling(async (args) => {
|
|
862
924
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
863
925
|
const { page, pageSize } = args;
|
|
864
926
|
const workouts = (await hevyClient.getWorkouts({
|
|
@@ -869,14 +931,14 @@ function registerWorkoutTools(server, hevyClient) {
|
|
|
869
931
|
return createJsonResponse(workouts);
|
|
870
932
|
}, "get-workouts"));
|
|
871
933
|
const getWorkoutSchema = { workoutId: z.string().min(1) };
|
|
872
|
-
server.tool("get-workout", "Get complete details of a specific workout by ID. Returns all workout information including title, description, start/end times, and detailed exercise data.", getWorkoutSchema, withErrorHandling(async (args) => {
|
|
934
|
+
server.tool("get-workout", "Get complete details of a specific workout by ID. Returns all workout information including title, description, start/end times, and detailed exercise data.", getWorkoutSchema, readOnlyAnnotations("Get Workout"), withErrorHandling(async (args) => {
|
|
873
935
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
874
936
|
const { workoutId } = args;
|
|
875
937
|
const data = await hevyClient.getWorkout(workoutId);
|
|
876
938
|
if (!data) return createEmptyResponse(`Workout with ID ${workoutId} not found`);
|
|
877
939
|
return createJsonResponse(formatWorkout(data));
|
|
878
940
|
}, "get-workout"));
|
|
879
|
-
server.tool("get-workout-count", "Get the total number of workouts on the account. Useful for pagination or statistics.", {}, withErrorHandling(async () => {
|
|
941
|
+
server.tool("get-workout-count", "Get the total number of workouts on the account. Useful for pagination or statistics.", {}, readOnlyAnnotations("Get Workout Count"), withErrorHandling(async () => {
|
|
880
942
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
881
943
|
return createJsonResponse({ count: (await hevyClient.getWorkoutCount())?.workout_count ?? 0 });
|
|
882
944
|
}, "get-workout-count"));
|
|
@@ -885,7 +947,7 @@ function registerWorkoutTools(server, hevyClient) {
|
|
|
885
947
|
pageSize: z.coerce.number().int().gte(1).lte(10).default(5),
|
|
886
948
|
since: z.string().default("1970-01-01T00:00:00Z")
|
|
887
949
|
};
|
|
888
|
-
server.tool("get-workout-events", "Retrieve a paged list of workout events (updates or deletes) since a given date. Events are ordered from newest to oldest. The intention is to allow clients to keep their local cache of workouts up to date without having to fetch the entire list of workouts.", getWorkoutEventsSchema, withErrorHandling(async (args) => {
|
|
950
|
+
server.tool("get-workout-events", "Retrieve a paged list of workout events (updates or deletes) since a given date. Events are ordered from newest to oldest. The intention is to allow clients to keep their local cache of workouts up to date without having to fetch the entire list of workouts.", getWorkoutEventsSchema, readOnlyAnnotations("Get Workout Events"), withErrorHandling(async (args) => {
|
|
889
951
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
890
952
|
const { page, pageSize, since } = args;
|
|
891
953
|
const events = (await hevyClient.getWorkoutEvents({
|
|
@@ -925,7 +987,7 @@ function registerWorkoutTools(server, hevyClient) {
|
|
|
925
987
|
}))
|
|
926
988
|
})))
|
|
927
989
|
};
|
|
928
|
-
server.tool("create-workout", "Create a new workout in your Hevy account. Requires title, start/end times, and at least one exercise with sets. Returns the complete workout details upon successful creation including the newly assigned workout ID.", createWorkoutSchema, withErrorHandling(async (args) => {
|
|
990
|
+
server.tool("create-workout", "Create a new workout in your Hevy account. Requires title, start/end times, and at least one exercise with sets. Returns the complete workout details upon successful creation including the newly assigned workout ID.", createWorkoutSchema, createAnnotations("Create Workout"), withErrorHandling(async (args) => {
|
|
929
991
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
930
992
|
const { title, description, startTime, endTime, isPrivate, exercises } = args;
|
|
931
993
|
const requestBody = { workout: {
|
|
@@ -986,7 +1048,7 @@ function registerWorkoutTools(server, hevyClient) {
|
|
|
986
1048
|
}))
|
|
987
1049
|
})))
|
|
988
1050
|
};
|
|
989
|
-
server.tool("update-workout", "Update an existing workout by ID. You can modify the title, description, start/end times, privacy setting, and exercise data. Returns the updated workout with all changes applied.", updateWorkoutSchema, withErrorHandling(async (args) => {
|
|
1051
|
+
server.tool("update-workout", "Update an existing workout by ID. You can modify the title, description, start/end times, privacy setting, and exercise data. Returns the updated workout with all changes applied.", updateWorkoutSchema, updateAnnotations("Update Workout"), withErrorHandling(async (args) => {
|
|
990
1052
|
if (!hevyClient) throw new Error("API client not initialized. Please provide HEVY_API_KEY.");
|
|
991
1053
|
const { workoutId, title, description, startTime, endTime, isPrivate, exercises } = args;
|
|
992
1054
|
const requestBody = { workout: {
|
|
@@ -1312,6 +1374,34 @@ async function getV1RoutinesRoutineid(routineId, headers, config = {}) {
|
|
|
1312
1374
|
})).data;
|
|
1313
1375
|
}
|
|
1314
1376
|
//#endregion
|
|
1377
|
+
//#region src/generated/client/api/getV1UserInfo.ts
|
|
1378
|
+
/**
|
|
1379
|
+
* Generated by Kubb (https://kubb.dev/).
|
|
1380
|
+
* Do not edit manually.
|
|
1381
|
+
*/
|
|
1382
|
+
function getGetV1UserInfoUrl() {
|
|
1383
|
+
return {
|
|
1384
|
+
method: "GET",
|
|
1385
|
+
url: `/v1/user/info`
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
/**
|
|
1389
|
+
* @summary Get user info
|
|
1390
|
+
* {@link /v1/user/info}
|
|
1391
|
+
*/
|
|
1392
|
+
async function getV1UserInfo(headers, config = {}) {
|
|
1393
|
+
const { client: request = fetch, ...requestConfig } = config;
|
|
1394
|
+
return (await request({
|
|
1395
|
+
method: "GET",
|
|
1396
|
+
url: getGetV1UserInfoUrl().url.toString(),
|
|
1397
|
+
...requestConfig,
|
|
1398
|
+
headers: {
|
|
1399
|
+
...headers,
|
|
1400
|
+
...requestConfig.headers
|
|
1401
|
+
}
|
|
1402
|
+
})).data;
|
|
1403
|
+
}
|
|
1404
|
+
//#endregion
|
|
1315
1405
|
//#region src/generated/client/api/getV1Workouts.ts
|
|
1316
1406
|
/**
|
|
1317
1407
|
* Generated by Kubb (https://kubb.dev/).
|
|
@@ -1707,6 +1797,7 @@ function createClient$1(apiKey, baseUrl = "https://api.hevyapp.com") {
|
|
|
1707
1797
|
getBodyMeasurement: (date) => wrapApi(getV1BodyMeasurementsDate)(date, headers, { client }),
|
|
1708
1798
|
createBodyMeasurement: (data) => wrapApi(postV1BodyMeasurements)(data, headers, { client }),
|
|
1709
1799
|
updateBodyMeasurement: (date, data) => wrapApi(putV1BodyMeasurementsDate)(date, data, headers, { client }),
|
|
1800
|
+
getUserInfo: () => wrapApi(getV1UserInfo)(headers, { client }),
|
|
1710
1801
|
getWebhookSubscription: async () => {
|
|
1711
1802
|
throw new Error("Webhook subscription API not available. Please regenerate the client from the updated OpenAPI spec.");
|
|
1712
1803
|
},
|
|
@@ -1726,7 +1817,7 @@ function createClient(apiKey, baseUrl) {
|
|
|
1726
1817
|
//#endregion
|
|
1727
1818
|
//#region src/index.ts
|
|
1728
1819
|
const name = "hevy-mcp";
|
|
1729
|
-
const version = "1.23.
|
|
1820
|
+
const version = "1.23.24";
|
|
1730
1821
|
const sentryConfig = {
|
|
1731
1822
|
dsn: "https://ce696d8333b507acbf5203eb877bce0f@o4508975499575296.ingest.de.sentry.io/4509049671647312",
|
|
1732
1823
|
release: process.env.SENTRY_RELEASE ?? `${name}@${version}`,
|
|
@@ -1735,9 +1826,14 @@ const sentryConfig = {
|
|
|
1735
1826
|
};
|
|
1736
1827
|
Sentry.init(sentryConfig);
|
|
1737
1828
|
const HEVY_API_BASEURL = "https://api.hevyapp.com";
|
|
1829
|
+
const SENTRY_USER_ID_CONTEXT = "hevy-mcp:sentry-user-id:v1";
|
|
1830
|
+
function fingerprintApiKey(apiKey) {
|
|
1831
|
+
return createHmac("sha256", apiKey).update(SENTRY_USER_ID_CONTEXT).digest("hex");
|
|
1832
|
+
}
|
|
1738
1833
|
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).") });
|
|
1739
1834
|
const configSchema = serverConfigSchema;
|
|
1740
1835
|
function buildServer(apiKey) {
|
|
1836
|
+
Sentry.setUser({ id: fingerprintApiKey(apiKey) });
|
|
1741
1837
|
const baseServer = new McpServer({
|
|
1742
1838
|
name,
|
|
1743
1839
|
version
|
|
@@ -1750,6 +1846,7 @@ function buildServer(apiKey) {
|
|
|
1750
1846
|
registerTemplateTools(server, hevyClient);
|
|
1751
1847
|
registerFolderTools(server, hevyClient);
|
|
1752
1848
|
registerBodyMeasurementTools(server, hevyClient);
|
|
1849
|
+
registerUserTools(server, hevyClient);
|
|
1753
1850
|
registerWebhookTools(server, hevyClient);
|
|
1754
1851
|
return server;
|
|
1755
1852
|
}
|
|
@@ -1768,4 +1865,4 @@ async function runServer() {
|
|
|
1768
1865
|
//#endregion
|
|
1769
1866
|
export { createServer as n, runServer as r, configSchema as t };
|
|
1770
1867
|
|
|
1771
|
-
//# sourceMappingURL=src-
|
|
1868
|
+
//# sourceMappingURL=src-BVYF4gvN.mjs.map
|