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 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 | Tools |
171
- | :------------ | :----------------------------------------------------------------------------------------------------------- |
172
- | **Workouts** | `get-workouts`, `get-workout`, `create-workout`, `update-workout`, `get-workout-count`, `get-workout-events` |
173
- | **Routines** | `get-routines`, `get-routine-by-id`, `create-routine`, `update-routine` |
174
- | **Templates** | `get-exercise-templates`, `get-exercise-template`, `search-exercise-templates` |
175
- | **Folders** | `get-routine-folders`, `get-routine-folder`, `create-routine-folder` |
176
- | **Webhooks** | `get-webhook-subscription`, `create-webhook-subscription`, `delete-webhook-subscription` |
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.22" };
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-Cag-RZcj.mjs";
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.22" };
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-Cag-RZcj.mjs";
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.22" };
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] = "25b3bb9e-9300-46a5-a29a-f2bf244304ef", e._sentryDebugIdIdentifier = "sentry-dbid-25b3bb9e-9300-46a5-a29a-f2bf244304ef");
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
- return {
320
- weight_kg: args.weightKg ?? null,
321
- lean_mass_kg: args.leanMassKg ?? null,
322
- fat_percent: args.fatPercent ?? null,
323
- neck_cm: args.neckCm ?? null,
324
- shoulder_cm: args.shoulderCm ?? null,
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. All fields are overwritten omitted fields are set to null. Returns 404 if no entry exists for the date.", updateBodyMeasurementSchema, withErrorHandling(async (args) => {
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
- await hevyClient.updateBodyMeasurement(date, buildMeasurementPayload(fields));
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.22";
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-Cag-RZcj.mjs.map
1868
+ //# sourceMappingURL=src-BVYF4gvN.mjs.map