hevy-mcp 4.1.2 → 5.0.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.
@@ -4,9 +4,9 @@
4
4
  (function() {
5
5
  try {
6
6
  var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
7
- e.SENTRY_RELEASE = { id: "hevy-mcp@4.1.2" };
7
+ e.SENTRY_RELEASE = { id: "hevy-mcp@5.0.0" };
8
8
  var n = new e.Error().stack;
9
- n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "b064b429-35aa-4617-a2a9-779c7c2ed596", e._sentryDebugIdIdentifier = "sentry-dbid-b064b429-35aa-4617-a2a9-779c7c2ed596");
9
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "8ea46bdf-fa98-4e73-b4d5-a25044e016df", e._sentryDebugIdIdentifier = "sentry-dbid-8ea46bdf-fa98-4e73-b4d5-a25044e016df");
10
10
  } catch (e) {}
11
11
  })();
12
12
  import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
@@ -19,15 +19,14 @@ import { resourceFromAttributes } from "@opentelemetry/resources";
19
19
  import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
20
20
  import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
21
21
  import { MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
22
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
22
+ import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
23
23
  import { z } from "zod";
24
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
24
+ import { McpServer, deserializeMessage } from "@modelcontextprotocol/server";
25
25
  import { z as z$1 } from "zod/v4";
26
26
  import { isIP } from "node:net";
27
27
  import { createServer } from "node:http";
28
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
28
+ import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
29
29
  import { AsyncLocalStorage } from "node:async_hooks";
30
- import { deserializeMessage } from "@modelcontextprotocol/sdk/shared/stdio.js";
31
30
  import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
32
31
  import { homedir } from "node:os";
33
32
  import { dirname, join } from "node:path";
@@ -81,11 +80,11 @@ function sanitizeSentryMcpSpan(span) {
81
80
  * OTel Collector → Honeycomb: performance traces, metrics
82
81
  */
83
82
  const name$1 = "hevy-mcp";
84
- const version$1 = "4.1.2";
83
+ const version$1 = "5.0.0";
85
84
  const telemetryEnabled = process.env.HEVY_MCP_TELEMETRY !== "0";
86
85
  const collectorToken = "NH9vOela-HYreQxAbJa68cjEmORoEKM57EvneUDVcOo";
87
86
  const COLLECTOR_ENDPOINT = "https://otel.chrisdoc.dev/v1";
88
- const sentryRelease = process.env.SENTRY_RELEASE ?? `hevy-mcp@4.1.2`;
87
+ const sentryRelease = process.env.SENTRY_RELEASE ?? `hevy-mcp@5.0.0`;
89
88
  const resource = resourceFromAttributes({
90
89
  "service.name": name$1,
91
90
  "service.version": version$1
@@ -227,8 +226,8 @@ const repRangeShape = {
227
226
  function optionalRepRangeSchema(schema) {
228
227
  return z.preprocess((value) => value === null ? void 0 : value, schema);
229
228
  }
230
- const zOptionalRepRange = optionalRepRangeSchema(z.object(repRangeShape).optional());
231
- optionalRepRangeSchema(z.object(repRangeShape).strict().optional());
229
+ optionalRepRangeSchema(z.object(repRangeShape).optional());
230
+ const zStrictOptionalRepRange = optionalRepRangeSchema(z.object(repRangeShape).strict().optional());
232
231
  const setTypeEnum = z.enum([
233
232
  "warmup",
234
233
  "normal",
@@ -1469,8 +1468,8 @@ function withPromptObservation(name, observer, handler) {
1469
1468
  scope = memoizeObservationScope(observer?.start({
1470
1469
  name,
1471
1470
  kind: "prompt",
1472
- argumentKeys: Object.keys(args).filter((key) => key === "routineId"),
1473
- argumentPresence: args.routineId ? { routineId: true } : {},
1471
+ argumentKeys: Object.keys(args).filter((key) => key === "routine_id"),
1472
+ argumentPresence: args.routine_id ? { routine_id: true } : {},
1474
1473
  argumentKeyCountBucket: bucketCount(Object.keys(args).length)
1475
1474
  }));
1476
1475
  } catch {
@@ -1507,7 +1506,7 @@ function registerWorkoutPrompts(server, observer) {
1507
1506
  server.registerPrompt("analyze-workout-progress", {
1508
1507
  title: "Analyze Workout Progress",
1509
1508
  description: "Analyze recent workout and body-measurement trends.",
1510
- argsSchema: { weeks: z.coerce.number().int().min(1).max(12).default(4).optional().describe("Number of recent weeks to analyze (1-12).") }
1509
+ argsSchema: z.object({ weeks: z.coerce.number().int().min(1).max(12).default(4).optional().describe("Number of recent weeks to analyze (1-12).") })
1511
1510
  }, withPromptObservation("analyze-workout-progress", observer, ({ weeks = 4 }) => ({ messages: [{
1512
1511
  role: "user",
1513
1512
  content: {
@@ -1524,23 +1523,23 @@ function registerWorkoutPrompts(server, observer) {
1524
1523
  server.registerPrompt("create-workout-from-routine", {
1525
1524
  title: "Create Workout From Routine",
1526
1525
  description: "Create a completed workout from an existing routine.",
1527
- argsSchema: {
1528
- routineId: z.string().min(1).optional().describe("Routine ID to use as a guide."),
1529
- startTime: utcSecondTimestamp.optional().describe("Workout start time in UTC as YYYY-MM-DDTHH:mm:ssZ.")
1530
- }
1531
- }, withPromptObservation("create-workout-from-routine", observer, ({ routineId, startTime }) => ({ messages: [{
1526
+ argsSchema: z.object({
1527
+ routine_id: z.string().min(1).optional().describe("Routine ID to use as a guide."),
1528
+ start_time: utcSecondTimestamp.optional().describe("Workout start time in UTC as YYYY-MM-DDTHH:mm:ssZ.")
1529
+ })
1530
+ }, withPromptObservation("create-workout-from-routine", observer, ({ routine_id, start_time }) => ({ messages: [{
1532
1531
  role: "user",
1533
1532
  content: {
1534
1533
  type: "text",
1535
- text: routineId && startTime ? [
1536
- `Create a workout from routine ${routineId}, starting at ${startTime}.`,
1537
- "First call get-routine with the routineId and map supported plan fields: routine title to workout title, plus each exerciseTemplateId, supersetId, exercise notes, and set type.",
1538
- "Do not copy routine-only restSeconds or repRange fields into create-workout.",
1539
- "Before calling create-workout, confirm or collect the user's actual completed set data for every set, including applicable weight, reps, distance, duration, RPE, or custom metric values.",
1540
- "Also collect the required endTime in strict UTC YYYY-MM-DDTHH:mm:ssZ format and confirm any other missing required workout fields.",
1541
- "Never invent completion data. If the actual results or endTime are unavailable, ask the user for them instead of creating the workout.",
1534
+ text: routine_id && start_time ? [
1535
+ `Create a workout from routine ${routine_id}, starting at ${start_time}.`,
1536
+ ["First call get-routine with the routine_id and map supported plan fields:", "routine title to workout title, plus each exercise_template_id, superset_id, exercise notes, and set type."].join(" "),
1537
+ ["Do not copy routine-only rest_seconds or rep_range fields into", "create-workout."].join(" "),
1538
+ ["Before calling create-workout, confirm or collect the user's actual completed set data for every set,", "including applicable weight_kg, reps, distance_meters, duration_seconds, rpe, or custom_metric values."].join(" "),
1539
+ ["Also collect the required end_time in strict UTC YYYY-MM-DDTHH:mm:ssZ format and confirm", "any other missing required workout fields."].join(" "),
1540
+ ["Never invent completion data. If the actual results or end_time are unavailable, ask the user for them", "instead of creating the workout."].join(" "),
1542
1541
  "Once confirmed, call create-workout with only fields supported by that tool."
1543
- ].join("\n") : "Provide a routineId and startTime to generate the full prompt."
1542
+ ].join("\n") : "Provide a routine_id and start_time to generate the full prompt."
1544
1543
  }
1545
1544
  }] })));
1546
1545
  }
@@ -1846,7 +1845,7 @@ const getV1WorkoutsWorkoutid200Schema = z$1.lazy(() => workoutSchema);
1846
1845
  z$1.any();
1847
1846
  z$1.lazy(() => getV1WorkoutsWorkoutid200Schema);
1848
1847
  //#endregion
1849
- //#region ../core/src/utils/response-formatter.ts
1848
+ //#region ../core/src/utils/response-contracts.ts
1850
1849
  function jsonText(data) {
1851
1850
  return JSON.stringify(data, null, 2) ?? "null";
1852
1851
  }
@@ -1884,146 +1883,150 @@ function defineJsonResponseContract(present, telemetry) {
1884
1883
  function respond(contract, data) {
1885
1884
  return contract.render(data);
1886
1885
  }
1887
- const optionalNullableNumber = z.number().nullable().optional();
1886
+ const optionalNumber = z.number().optional();
1888
1887
  const formattedWorkoutSetSchema = z.object({
1889
1888
  index: z.number().optional(),
1890
1889
  type: z.string().optional(),
1891
- weight: optionalNullableNumber,
1892
- reps: optionalNullableNumber,
1893
- distance: optionalNullableNumber,
1894
- duration: optionalNullableNumber,
1895
- rpe: optionalNullableNumber,
1896
- customMetric: optionalNullableNumber
1890
+ weight_kg: optionalNumber,
1891
+ reps: optionalNumber,
1892
+ distance_meters: optionalNumber,
1893
+ duration_seconds: optionalNumber,
1894
+ rpe: optionalNumber,
1895
+ custom_metric: optionalNumber
1897
1896
  });
1898
1897
  const formattedWorkoutExerciseSchema = z.object({
1899
1898
  index: z.number().optional(),
1900
- name: z.string().optional(),
1901
- exerciseTemplateId: z.string().optional(),
1902
- notes: z.string().nullable().optional(),
1903
- supersetsId: optionalNullableNumber,
1899
+ title: z.string().optional(),
1900
+ exercise_template_id: z.string().optional(),
1901
+ notes: z.string().optional(),
1902
+ supersets_id: optionalNumber,
1904
1903
  sets: z.array(formattedWorkoutSetSchema).optional()
1905
1904
  });
1906
1905
  const formattedWorkoutSchema = z.object({
1907
1906
  id: z.string().optional(),
1907
+ routine_id: z.string().optional(),
1908
1908
  title: z.string().optional(),
1909
- description: z.string().nullable().optional(),
1910
- startTime: z.union([z.string(), z.number()]).optional(),
1911
- endTime: z.union([z.string(), z.number()]).optional(),
1912
- createdAt: z.string().optional(),
1913
- updatedAt: z.string().optional(),
1909
+ description: z.string().optional(),
1910
+ start_time: z.string().optional(),
1911
+ end_time: z.string().optional(),
1912
+ created_at: z.string().optional(),
1913
+ updated_at: z.string().optional(),
1914
1914
  duration: z.string(),
1915
1915
  exercises: z.array(formattedWorkoutExerciseSchema).optional()
1916
1916
  });
1917
1917
  const formattedRoutineSetSchema = z.object({
1918
1918
  index: z.number().optional(),
1919
1919
  type: z.string().optional(),
1920
- weight: optionalNullableNumber,
1921
- reps: optionalNullableNumber,
1922
- distance: optionalNullableNumber,
1923
- duration: optionalNullableNumber,
1924
- customMetric: optionalNullableNumber,
1925
- repRange: z.object({
1926
- start: z.number().nullable().optional(),
1927
- end: z.number().nullable().optional()
1928
- }).nullable().optional(),
1929
- rpe: optionalNullableNumber
1920
+ weight_kg: optionalNumber,
1921
+ reps: optionalNumber,
1922
+ distance_meters: optionalNumber,
1923
+ duration_seconds: optionalNumber,
1924
+ custom_metric: optionalNumber,
1925
+ rep_range: z.object({
1926
+ start: z.number().optional(),
1927
+ end: z.number().optional()
1928
+ }).optional(),
1929
+ rpe: optionalNumber
1930
1930
  });
1931
1931
  const formattedRoutineExerciseSchema = z.object({
1932
- name: z.string().optional(),
1932
+ title: z.string().optional(),
1933
1933
  index: z.number().optional(),
1934
- exerciseTemplateId: z.string().optional(),
1935
- notes: z.string().nullable().optional(),
1936
- supersetId: optionalNullableNumber,
1937
- restSeconds: z.union([z.string(), z.number()]).nullable().optional(),
1934
+ exercise_template_id: z.string().optional(),
1935
+ notes: z.string().optional(),
1936
+ supersets_id: optionalNumber,
1937
+ rest_seconds: z.string().optional(),
1938
1938
  sets: z.array(formattedRoutineSetSchema).optional()
1939
1939
  });
1940
1940
  const formattedRoutineSchema = z.object({
1941
1941
  id: z.string().optional(),
1942
1942
  title: z.string().optional(),
1943
- folderId: z.number().nullable().optional(),
1944
- createdAt: z.string().optional(),
1945
- updatedAt: z.string().optional(),
1943
+ folder_id: z.number().optional(),
1944
+ created_at: z.string().optional(),
1945
+ updated_at: z.string().optional(),
1946
1946
  exercises: z.array(formattedRoutineExerciseSchema).optional()
1947
1947
  });
1948
1948
  const formattedRoutineFolderSchema = z.object({
1949
1949
  id: z.number().optional(),
1950
1950
  title: z.string().optional(),
1951
- createdAt: z.string().optional(),
1952
- updatedAt: z.string().optional()
1951
+ created_at: z.string().optional(),
1952
+ updated_at: z.string().optional()
1953
1953
  });
1954
1954
  const formattedExerciseTemplateSchema = z.object({
1955
1955
  id: z.string().optional(),
1956
1956
  title: z.string().optional(),
1957
1957
  type: z.string().optional(),
1958
- primaryMuscleGroup: z.string().optional(),
1959
- secondaryMuscleGroups: z.array(z.string()).optional(),
1960
- isCustom: z.boolean().optional()
1958
+ primary_muscle_group: z.string().optional(),
1959
+ secondary_muscle_groups: z.array(z.string()).optional(),
1960
+ is_custom: z.boolean().optional()
1961
1961
  });
1962
1962
  const formattedExerciseHistoryEntrySchema = z.object({
1963
- workoutId: z.string().optional(),
1964
- workoutTitle: z.string().optional(),
1965
- workoutStartTime: z.string().optional(),
1966
- workoutEndTime: z.string().optional(),
1967
- exerciseTemplateId: z.string().optional(),
1968
- weight: optionalNullableNumber,
1969
- reps: optionalNullableNumber,
1970
- distance: optionalNullableNumber,
1971
- duration: optionalNullableNumber,
1972
- rpe: optionalNullableNumber,
1973
- customMetric: optionalNullableNumber,
1974
- setType: z.string().optional()
1963
+ workout_id: z.string().optional(),
1964
+ workout_title: z.string().optional(),
1965
+ workout_start_time: z.string().optional(),
1966
+ workout_end_time: z.string().optional(),
1967
+ exercise_template_id: z.string().optional(),
1968
+ weight_kg: optionalNumber,
1969
+ reps: optionalNumber,
1970
+ distance_meters: optionalNumber,
1971
+ duration_seconds: optionalNumber,
1972
+ rpe: optionalNumber,
1973
+ custom_metric: optionalNumber,
1974
+ set_type: z.string().optional()
1975
1975
  });
1976
1976
  const formattedBodyMeasurementSchema = z.object({
1977
1977
  date: z.string(),
1978
- weightKg: z.number().nullable(),
1979
- leanMassKg: z.number().nullable(),
1980
- fatPercent: z.number().nullable(),
1981
- neckCm: z.number().nullable(),
1982
- shoulderCm: z.number().nullable(),
1983
- chestCm: z.number().nullable(),
1984
- leftBicepCm: z.number().nullable(),
1985
- rightBicepCm: z.number().nullable(),
1986
- leftForearmCm: z.number().nullable(),
1987
- rightForearmCm: z.number().nullable(),
1988
- abdomen: z.number().nullable(),
1989
- waist: z.number().nullable(),
1990
- hips: z.number().nullable(),
1991
- leftThigh: z.number().nullable(),
1992
- rightThigh: z.number().nullable(),
1993
- leftCalf: z.number().nullable(),
1994
- rightCalf: z.number().nullable()
1978
+ weight_kg: optionalNumber,
1979
+ lean_mass_kg: optionalNumber,
1980
+ fat_percent: optionalNumber,
1981
+ neck_cm: optionalNumber,
1982
+ shoulder_cm: optionalNumber,
1983
+ chest_cm: optionalNumber,
1984
+ left_bicep_cm: optionalNumber,
1985
+ right_bicep_cm: optionalNumber,
1986
+ left_forearm_cm: optionalNumber,
1987
+ right_forearm_cm: optionalNumber,
1988
+ abdomen: optionalNumber,
1989
+ waist: optionalNumber,
1990
+ hips: optionalNumber,
1991
+ left_thigh_cm: optionalNumber,
1992
+ right_thigh_cm: optionalNumber,
1993
+ left_calf_cm: optionalNumber,
1994
+ right_calf_cm: optionalNumber
1995
1995
  });
1996
- const workflowTelemetrySchema = z.object({
1997
- name: z.string(),
1998
- pagination: z.record(z.string(), z.number().int().nonnegative()),
1999
- cacheStatus: z.enum([
2000
- "hit",
2001
- "miss",
2002
- "not-used"
2003
- ]),
2004
- itemsScanned: z.number().int().nonnegative()
1996
+ const scanSchema = z.object({
1997
+ pages: z.record(z.string(), z.number().int().nonnegative()),
1998
+ items: z.number().int().nonnegative()
2005
1999
  });
2006
2000
  const trainingSummarySessionSchema = z.object({
2007
2001
  id: z.string().optional(),
2008
2002
  title: z.string().optional(),
2009
- startTime: z.string().optional(),
2010
- endTime: z.string().optional(),
2011
- durationSeconds: z.number().int().nonnegative().nullable(),
2012
- exerciseCount: z.number().int().nonnegative(),
2013
- setCount: z.number().int().nonnegative()
2003
+ start_time: z.string().optional(),
2004
+ end_time: z.string().optional(),
2005
+ duration_seconds: z.number().int().nonnegative().optional(),
2006
+ exercise_count: z.number().int().nonnegative(),
2007
+ set_count: z.number().int().nonnegative()
2014
2008
  });
2015
2009
  const compactRoutineSchema = z.object({
2016
2010
  id: z.string().optional(),
2017
2011
  title: z.string().optional(),
2018
- folderId: z.number().nullable(),
2019
- updatedAt: z.string().optional(),
2020
- exerciseCount: z.number().int().nonnegative(),
2021
- setCount: z.number().int().nonnegative()
2012
+ folder_id: z.number().optional(),
2013
+ updated_at: z.string().optional(),
2014
+ exercise_count: z.number().int().nonnegative(),
2015
+ set_count: z.number().int().nonnegative()
2016
+ });
2017
+ const formattedWorkoutSummarySchema = z.object({
2018
+ id: z.string().optional(),
2019
+ title: z.string().optional(),
2020
+ start_time: z.string().optional(),
2021
+ end_time: z.string().optional(),
2022
+ duration: z.string(),
2023
+ exercise_count: z.number().int().nonnegative(),
2024
+ set_count: z.number().int().nonnegative()
2022
2025
  });
2023
2026
  const paginationOutputSchema = {
2024
2027
  page: z.number().int().positive(),
2025
- pageCount: z.number().int().nonnegative().optional(),
2026
- hasNextPage: z.boolean().optional()
2028
+ page_count: z.number().int().nonnegative().optional(),
2029
+ has_next_page: z.boolean().optional()
2027
2030
  };
2028
2031
  function normalizePaginatedInput(data) {
2029
2032
  if (data === void 0) return {
@@ -2037,11 +2040,11 @@ function normalizePaginatedInput(data) {
2037
2040
  };
2038
2041
  }
2039
2042
  const workoutsOutputSchema = {
2040
- workouts: z.array(formattedWorkoutSchema),
2043
+ workouts: z.array(formattedWorkoutSummarySchema),
2041
2044
  ...paginationOutputSchema
2042
2045
  };
2043
2046
  const workoutOutputSchema = { workout: formattedWorkoutSchema.nullable() };
2044
- const workoutCountOutputSchema = { count: z.number().int() };
2047
+ const workoutCountOutputSchema = { workout_count: z.number().int() };
2045
2048
  const formattedUpdatedWorkoutSchema = z.object({
2046
2049
  type: z.literal("updated"),
2047
2050
  workout: formattedWorkoutSchema
@@ -2049,70 +2052,70 @@ const formattedUpdatedWorkoutSchema = z.object({
2049
2052
  const formattedDeletedWorkoutSchema = z.object({
2050
2053
  type: z.literal("deleted"),
2051
2054
  id: z.string(),
2052
- deletedAt: z.string().optional()
2055
+ deleted_at: z.string().optional()
2053
2056
  });
2054
2057
  const workoutEventsOutputSchema = {
2055
2058
  events: z.array(z.union([formattedUpdatedWorkoutSchema, formattedDeletedWorkoutSchema])),
2056
2059
  ...paginationOutputSchema
2057
2060
  };
2058
2061
  const routinesOutputSchema = {
2059
- routines: z.array(formattedRoutineSchema),
2062
+ routines: z.array(compactRoutineSchema),
2060
2063
  ...paginationOutputSchema
2061
2064
  };
2062
2065
  const routineOutputSchema = { routine: formattedRoutineSchema.nullable() };
2063
2066
  const exerciseTemplatesOutputSchema = {
2064
- exerciseTemplates: z.array(formattedExerciseTemplateSchema),
2067
+ exercise_templates: z.array(formattedExerciseTemplateSchema),
2065
2068
  ...paginationOutputSchema
2066
2069
  };
2067
- const exerciseTemplateSearchOutputSchema = { exerciseTemplates: z.array(formattedExerciseTemplateSchema) };
2068
- const exerciseTemplateOutputSchema = { exerciseTemplate: formattedExerciseTemplateSchema.nullable() };
2069
- const exerciseHistoryOutputSchema = { exerciseHistory: z.array(formattedExerciseHistoryEntrySchema) };
2070
+ const exerciseTemplateSearchOutputSchema = { exercise_templates: z.array(formattedExerciseTemplateSchema) };
2071
+ const exerciseTemplateOutputSchema = { exercise_template: formattedExerciseTemplateSchema.nullable() };
2072
+ const exerciseHistoryOutputSchema = { exercise_history: z.array(formattedExerciseHistoryEntrySchema) };
2070
2073
  const routineFoldersOutputSchema = {
2071
- routineFolders: z.array(formattedRoutineFolderSchema),
2074
+ routine_folders: z.array(formattedRoutineFolderSchema),
2072
2075
  ...paginationOutputSchema
2073
2076
  };
2074
- const routineFolderOutputSchema = { routineFolder: formattedRoutineFolderSchema.nullable() };
2077
+ const routineFolderOutputSchema = { routine_folder: formattedRoutineFolderSchema.nullable() };
2075
2078
  const bodyMeasurementsOutputSchema = {
2076
- bodyMeasurements: z.array(formattedBodyMeasurementSchema),
2079
+ body_measurements: z.array(formattedBodyMeasurementSchema),
2077
2080
  ...paginationOutputSchema
2078
2081
  };
2079
- const bodyMeasurementOutputSchema = { bodyMeasurement: formattedBodyMeasurementSchema.nullable() };
2082
+ const bodyMeasurementOutputSchema = { body_measurement: formattedBodyMeasurementSchema.nullable() };
2080
2083
  const userOutputSchema = { user: userInfoSchema.nullable() };
2081
2084
  const trainingSummaryOutputSchema = {
2082
2085
  period: z.object({
2083
- startDate: z.string(),
2084
- endDate: z.string(),
2086
+ start_date: z.string(),
2087
+ end_date: z.string(),
2085
2088
  weeks: z.number().int().positive()
2086
2089
  }),
2087
2090
  workouts: z.object({
2088
2091
  count: z.number().int().nonnegative(),
2089
- totalDurationSeconds: z.number().int().nonnegative(),
2090
- exerciseCount: z.number().int().nonnegative(),
2091
- setCount: z.number().int().nonnegative(),
2092
- uniqueExerciseTemplateIds: z.array(z.string()),
2092
+ total_duration_seconds: z.number().int().nonnegative(),
2093
+ exercise_count: z.number().int().nonnegative(),
2094
+ set_count: z.number().int().nonnegative(),
2095
+ unique_exercise_template_ids: z.array(z.string()),
2093
2096
  sessions: z.array(trainingSummarySessionSchema)
2094
2097
  }),
2095
- bodyMeasurements: z.object({
2098
+ body_measurements: z.object({
2096
2099
  count: z.number().int().nonnegative(),
2097
2100
  latest: z.object({
2098
2101
  date: z.string(),
2099
- weightKg: z.number().nullable(),
2100
- leanMassKg: z.number().nullable(),
2101
- fatPercent: z.number().nullable()
2102
- }).nullable(),
2102
+ weight_kg: optionalNumber,
2103
+ lean_mass_kg: optionalNumber,
2104
+ fat_percent: optionalNumber
2105
+ }).optional(),
2103
2106
  earliest: z.object({
2104
2107
  date: z.string(),
2105
- weightKg: z.number().nullable(),
2106
- leanMassKg: z.number().nullable(),
2107
- fatPercent: z.number().nullable()
2108
- }).nullable(),
2109
- weightChangeKg: z.number().nullable()
2108
+ weight_kg: optionalNumber,
2109
+ lean_mass_kg: optionalNumber,
2110
+ fat_percent: optionalNumber
2111
+ }).optional(),
2112
+ weight_change_kg: optionalNumber
2110
2113
  }),
2111
- workflow: workflowTelemetrySchema
2114
+ scan: scanSchema
2112
2115
  };
2113
2116
  const compactRoutinesOutputSchema = {
2114
2117
  routines: z.array(compactRoutineSchema),
2115
- workflow: workflowTelemetrySchema
2118
+ scan: scanSchema
2116
2119
  };
2117
2120
  function getSupersetId(exercise) {
2118
2121
  if (exercise.superset_id !== void 0) return exercise.superset_id;
@@ -2120,99 +2123,104 @@ function getSupersetId(exercise) {
2120
2123
  return null;
2121
2124
  }
2122
2125
  /**
2123
- * Format a workout object for consistent presentation
2124
- *
2125
- * @param workout - The workout object from the API
2126
- * @returns A formatted workout object with standardized properties
2126
+ * Project a workout into the sparse snake_case MCP contract.
2127
2127
  */
2128
- function formatWorkout(workout) {
2128
+ function projectWorkout(workout) {
2129
2129
  return {
2130
- id: workout.id,
2131
- title: workout.title,
2132
- description: workout.description,
2133
- startTime: workout.start_time,
2134
- endTime: workout.end_time,
2135
- createdAt: workout.created_at,
2136
- updatedAt: workout.updated_at,
2130
+ ...workout.id == null ? {} : { id: workout.id },
2131
+ ...workout.routine_id == null ? {} : { routine_id: workout.routine_id },
2132
+ ...workout.title == null ? {} : { title: workout.title },
2133
+ ...workout.description == null ? {} : { description: workout.description },
2134
+ ...workout.start_time == null ? {} : { start_time: workout.start_time },
2135
+ ...workout.end_time == null ? {} : { end_time: workout.end_time },
2136
+ ...workout.created_at == null ? {} : { created_at: workout.created_at },
2137
+ ...workout.updated_at == null ? {} : { updated_at: workout.updated_at },
2137
2138
  duration: calculateDuration(workout.start_time, workout.end_time),
2138
- exercises: workout.exercises?.map((exercise) => {
2139
- return {
2140
- index: exercise.index,
2141
- name: exercise.title,
2142
- exerciseTemplateId: exercise.exercise_template_id,
2143
- notes: exercise.notes,
2144
- supersetsId: getSupersetId(exercise),
2145
- sets: exercise.sets?.map((set) => ({
2146
- index: set.index,
2147
- type: set.type,
2148
- weight: set.weight_kg,
2149
- reps: set.reps,
2150
- distance: set.distance_meters,
2151
- duration: set.duration_seconds,
2152
- rpe: set.rpe,
2153
- customMetric: set.custom_metric
2154
- }))
2155
- };
2156
- })
2139
+ ...workout.exercises ? { exercises: workout.exercises.map((exercise) => ({
2140
+ ...exercise.index === void 0 ? {} : { index: exercise.index },
2141
+ ...exercise.title == null ? {} : { title: exercise.title },
2142
+ ...exercise.exercise_template_id == null ? {} : { exercise_template_id: exercise.exercise_template_id },
2143
+ ...exercise.notes == null ? {} : { notes: exercise.notes },
2144
+ ...getSupersetId(exercise) == null ? {} : { supersets_id: getSupersetId(exercise) ?? void 0 },
2145
+ ...exercise.sets ? { sets: exercise.sets.map((set) => ({
2146
+ ...set.index === void 0 ? {} : { index: set.index },
2147
+ ...set.type == null ? {} : { type: set.type },
2148
+ ...set.weight_kg == null ? {} : { weight_kg: set.weight_kg },
2149
+ ...set.reps == null ? {} : { reps: set.reps },
2150
+ ...set.distance_meters == null ? {} : { distance_meters: set.distance_meters },
2151
+ ...set.duration_seconds == null ? {} : { duration_seconds: set.duration_seconds },
2152
+ ...set.rpe == null ? {} : { rpe: set.rpe },
2153
+ ...set.custom_metric == null ? {} : { custom_metric: set.custom_metric }
2154
+ })) } : {}
2155
+ })) } : {}
2157
2156
  };
2158
2157
  }
2159
- /**
2160
- * Format a routine object for consistent presentation
2161
- *
2162
- * @param routine - The routine object from the API
2163
- * @returns A formatted routine object with standardized properties
2164
- */
2165
- function formatRoutine(routine) {
2158
+ function summarizeWorkout(workout) {
2159
+ const exercises = workout.exercises ?? [];
2166
2160
  return {
2167
- id: routine.id,
2168
- title: routine.title,
2169
- folderId: routine.folder_id,
2170
- createdAt: routine.created_at,
2171
- updatedAt: routine.updated_at,
2172
- exercises: routine.exercises?.map((exercise) => {
2173
- return {
2174
- name: exercise.title,
2175
- index: exercise.index,
2176
- exerciseTemplateId: exercise.exercise_template_id,
2177
- notes: exercise.notes,
2178
- supersetId: getSupersetId(exercise),
2179
- restSeconds: exercise.rest_seconds,
2180
- sets: exercise.sets?.map((set) => ({
2181
- index: set.index,
2182
- type: set.type,
2183
- weight: set.weight_kg,
2184
- reps: set.reps,
2185
- ...set.rep_range !== void 0 && { repRange: set.rep_range },
2186
- distance: set.distance_meters,
2187
- duration: set.duration_seconds,
2188
- ...set.rpe !== void 0 && { rpe: set.rpe },
2189
- customMetric: set.custom_metric
2190
- }))
2191
- };
2192
- })
2161
+ ...workout.id == null ? {} : { id: workout.id },
2162
+ ...workout.title == null ? {} : { title: workout.title },
2163
+ ...workout.start_time == null ? {} : { start_time: workout.start_time },
2164
+ ...workout.end_time == null ? {} : { end_time: workout.end_time },
2165
+ duration: calculateDuration(workout.start_time, workout.end_time),
2166
+ exercise_count: exercises.length,
2167
+ set_count: exercises.reduce((total, exercise) => total + (exercise.sets?.length ?? 0), 0)
2193
2168
  };
2194
2169
  }
2195
- /**
2196
- * Format a routine folder object for consistent presentation
2197
- *
2198
- * @param folder - The routine folder object from the API
2199
- * @returns A formatted routine folder object with standardized properties
2200
- */
2201
- function formatRoutineFolder(folder) {
2170
+ function projectRoutine(routine) {
2171
+ return {
2172
+ ...routine.id == null ? {} : { id: routine.id },
2173
+ ...routine.title == null ? {} : { title: routine.title },
2174
+ ...routine.folder_id == null ? {} : { folder_id: routine.folder_id },
2175
+ ...routine.created_at == null ? {} : { created_at: routine.created_at },
2176
+ ...routine.updated_at == null ? {} : { updated_at: routine.updated_at },
2177
+ ...routine.exercises ? { exercises: routine.exercises.map((exercise) => ({
2178
+ ...exercise.title == null ? {} : { title: exercise.title },
2179
+ ...exercise.index === void 0 ? {} : { index: exercise.index },
2180
+ ...exercise.exercise_template_id == null ? {} : { exercise_template_id: exercise.exercise_template_id },
2181
+ ...exercise.notes == null ? {} : { notes: exercise.notes },
2182
+ ...getSupersetId(exercise) == null ? {} : { supersets_id: getSupersetId(exercise) ?? void 0 },
2183
+ ...exercise.rest_seconds == null ? {} : { rest_seconds: exercise.rest_seconds },
2184
+ ...exercise.sets ? { sets: exercise.sets.map((set) => {
2185
+ const repRange = set.rep_range;
2186
+ const normalizedRepRange = repRange && (repRange.start != null || repRange.end != null) ? {
2187
+ ...repRange.start == null ? {} : { start: repRange.start },
2188
+ ...repRange.end == null ? {} : { end: repRange.end }
2189
+ } : void 0;
2190
+ return {
2191
+ ...set.index === void 0 ? {} : { index: set.index },
2192
+ ...set.type == null ? {} : { type: set.type },
2193
+ ...set.weight_kg == null ? {} : { weight_kg: set.weight_kg },
2194
+ ...set.reps == null ? {} : { reps: set.reps },
2195
+ ...normalizedRepRange ? { rep_range: normalizedRepRange } : {},
2196
+ ...set.distance_meters == null ? {} : { distance_meters: set.distance_meters },
2197
+ ...set.duration_seconds == null ? {} : { duration_seconds: set.duration_seconds },
2198
+ ...set.rpe == null ? {} : { rpe: set.rpe },
2199
+ ...set.custom_metric == null ? {} : { custom_metric: set.custom_metric }
2200
+ };
2201
+ }) } : {}
2202
+ })) } : {}
2203
+ };
2204
+ }
2205
+ function summarizeRoutine(routine) {
2206
+ const exercises = routine.exercises ?? [];
2207
+ return {
2208
+ ...routine.id == null ? {} : { id: routine.id },
2209
+ ...routine.title == null ? {} : { title: routine.title },
2210
+ ...routine.folder_id == null ? {} : { folder_id: routine.folder_id },
2211
+ ...routine.updated_at == null ? { updated_at: void 0 } : { updated_at: routine.updated_at },
2212
+ exercise_count: exercises.length,
2213
+ set_count: exercises.reduce((total, exercise) => total + (exercise.sets?.length ?? 0), 0)
2214
+ };
2215
+ }
2216
+ function projectRoutineFolder(folder) {
2202
2217
  return {
2203
2218
  id: folder.id,
2204
2219
  title: folder.title,
2205
- createdAt: folder.created_at,
2206
- updatedAt: folder.updated_at
2220
+ created_at: folder.created_at,
2221
+ updated_at: folder.updated_at
2207
2222
  };
2208
2223
  }
2209
- /**
2210
- * Calculate duration between two ISO timestamp strings
2211
- *
2212
- * @param startTime - The start time as ISO string or timestamp
2213
- * @param endTime - The end time as ISO string or timestamp
2214
- * @returns A formatted duration string (e.g. "1h 30m 45s") or "Unknown duration" if inputs are invalid
2215
- */
2216
2224
  function calculateDuration(startTime, endTime) {
2217
2225
  if (!startTime || !endTime) return "Unknown duration";
2218
2226
  try {
@@ -2227,69 +2235,53 @@ function calculateDuration(startTime, endTime) {
2227
2235
  return "Unknown duration";
2228
2236
  }
2229
2237
  }
2230
- /**
2231
- * Format an exercise template object for consistent presentation
2232
- *
2233
- * @param template - The exercise template object from the API
2234
- * @returns A formatted exercise template object with standardized properties
2235
- */
2236
- function formatExerciseTemplate(template) {
2237
- return {
2238
- id: template.id,
2239
- title: template.title,
2240
- type: template.type,
2241
- primaryMuscleGroup: template.primary_muscle_group,
2242
- secondaryMuscleGroups: template.secondary_muscle_groups,
2243
- isCustom: template.is_custom
2244
- };
2245
- }
2246
- function formatExerciseHistoryEntry(entry) {
2238
+ function normalizeExerciseHistoryEntry(entry) {
2247
2239
  return {
2248
- workoutId: entry.workout_id,
2249
- workoutTitle: entry.workout_title,
2250
- workoutStartTime: entry.workout_start_time,
2251
- workoutEndTime: entry.workout_end_time,
2252
- exerciseTemplateId: entry.exercise_template_id,
2253
- weight: entry.weight_kg,
2254
- reps: entry.reps,
2255
- distance: entry.distance_meters,
2256
- duration: entry.duration_seconds,
2257
- rpe: entry.rpe,
2258
- customMetric: entry.custom_metric,
2259
- setType: entry.set_type
2240
+ ...entry.workout_id == null ? {} : { workout_id: entry.workout_id },
2241
+ ...entry.workout_title == null ? {} : { workout_title: entry.workout_title },
2242
+ ...entry.workout_start_time == null ? {} : { workout_start_time: entry.workout_start_time },
2243
+ ...entry.workout_end_time == null ? {} : { workout_end_time: entry.workout_end_time },
2244
+ ...entry.exercise_template_id == null ? {} : { exercise_template_id: entry.exercise_template_id },
2245
+ ...entry.weight_kg == null ? {} : { weight_kg: entry.weight_kg },
2246
+ ...entry.reps == null ? {} : { reps: entry.reps },
2247
+ ...entry.distance_meters == null ? {} : { distance_meters: entry.distance_meters },
2248
+ ...entry.duration_seconds == null ? {} : { duration_seconds: entry.duration_seconds },
2249
+ ...entry.rpe == null ? {} : { rpe: entry.rpe },
2250
+ ...entry.custom_metric == null ? {} : { custom_metric: entry.custom_metric },
2251
+ ...entry.set_type == null ? {} : { set_type: entry.set_type }
2260
2252
  };
2261
2253
  }
2262
- function formatBodyMeasurement(measurement) {
2254
+ function normalizeBodyMeasurement(measurement) {
2263
2255
  return {
2264
2256
  date: measurement.date,
2265
- weightKg: measurement.weight_kg ?? null,
2266
- leanMassKg: measurement.lean_mass_kg ?? null,
2267
- fatPercent: measurement.fat_percent ?? null,
2268
- neckCm: measurement.neck_cm ?? null,
2269
- shoulderCm: measurement.shoulder_cm ?? null,
2270
- chestCm: measurement.chest_cm ?? null,
2271
- leftBicepCm: measurement.left_bicep_cm ?? null,
2272
- rightBicepCm: measurement.right_bicep_cm ?? null,
2273
- leftForearmCm: measurement.left_forearm_cm ?? null,
2274
- rightForearmCm: measurement.right_forearm_cm ?? null,
2275
- abdomen: measurement.abdomen ?? null,
2276
- waist: measurement.waist ?? null,
2277
- hips: measurement.hips ?? null,
2278
- leftThigh: measurement.left_thigh ?? null,
2279
- rightThigh: measurement.right_thigh ?? null,
2280
- leftCalf: measurement.left_calf ?? null,
2281
- rightCalf: measurement.right_calf ?? null
2257
+ ...measurement.weight_kg == null ? {} : { weight_kg: measurement.weight_kg },
2258
+ ...measurement.lean_mass_kg == null ? {} : { lean_mass_kg: measurement.lean_mass_kg },
2259
+ ...measurement.fat_percent == null ? {} : { fat_percent: measurement.fat_percent },
2260
+ ...measurement.neck_cm == null ? {} : { neck_cm: measurement.neck_cm },
2261
+ ...measurement.shoulder_cm == null ? {} : { shoulder_cm: measurement.shoulder_cm },
2262
+ ...measurement.chest_cm == null ? {} : { chest_cm: measurement.chest_cm },
2263
+ ...measurement.left_bicep_cm == null ? {} : { left_bicep_cm: measurement.left_bicep_cm },
2264
+ ...measurement.right_bicep_cm == null ? {} : { right_bicep_cm: measurement.right_bicep_cm },
2265
+ ...measurement.left_forearm_cm == null ? {} : { left_forearm_cm: measurement.left_forearm_cm },
2266
+ ...measurement.right_forearm_cm == null ? {} : { right_forearm_cm: measurement.right_forearm_cm },
2267
+ ...measurement.abdomen == null ? {} : { abdomen: measurement.abdomen },
2268
+ ...measurement.waist == null ? {} : { waist: measurement.waist },
2269
+ ...measurement.hips == null ? {} : { hips: measurement.hips },
2270
+ ...measurement.left_thigh == null ? {} : { left_thigh_cm: measurement.left_thigh },
2271
+ ...measurement.right_thigh == null ? {} : { right_thigh_cm: measurement.right_thigh },
2272
+ ...measurement.left_calf == null ? {} : { left_calf_cm: measurement.left_calf },
2273
+ ...measurement.right_calf == null ? {} : { right_calf_cm: measurement.right_calf }
2282
2274
  };
2283
2275
  }
2284
- function formatWorkoutEvent(event) {
2276
+ function projectWorkoutEvent(event) {
2285
2277
  if (event.type === "updated" && "workout" in event) return {
2286
2278
  type: "updated",
2287
- workout: formatWorkout(event.workout)
2279
+ workout: projectWorkout(event.workout)
2288
2280
  };
2289
2281
  if (event.type === "deleted" && "id" in event) return {
2290
2282
  type: "deleted",
2291
2283
  id: event.id,
2292
- deletedAt: event.deleted_at
2284
+ deleted_at: event.deleted_at
2293
2285
  };
2294
2286
  throw new Error(`Unsupported workout event type: ${event.type}`);
2295
2287
  }
@@ -2333,10 +2325,10 @@ const workoutsResponse = defineStructuredResponseContract({
2333
2325
  normalize: (input) => {
2334
2326
  const data = normalizePaginatedInput(input);
2335
2327
  return {
2336
- workouts: data.items.map(formatWorkout),
2328
+ workouts: data.items.map(summarizeWorkout),
2337
2329
  page: data.page,
2338
- pageCount: data.pageCount,
2339
- hasNextPage: data.pageCount === void 0 ? void 0 : data.page < data.pageCount
2330
+ page_count: data.pageCount,
2331
+ has_next_page: data.pageCount === void 0 ? void 0 : data.page < data.pageCount
2340
2332
  };
2341
2333
  },
2342
2334
  legacyJson: ({ workouts }) => workouts,
@@ -2348,9 +2340,9 @@ const workoutsResponse = defineStructuredResponseContract({
2348
2340
  });
2349
2341
  const workoutResponse = defineStructuredResponseContract({
2350
2342
  outputSchema: workoutOutputSchema,
2351
- normalize: (data) => ({ workout: data.workout ? formatWorkout(data.workout) : null }),
2343
+ normalize: (data) => ({ workout: data.workout ? projectWorkout(data.workout) : null }),
2352
2344
  legacyJson: ({ workout }) => workout,
2353
- text: ({ workoutId }, { workout }) => workout === null ? `Workout with ID ${workoutId} not found` : void 0,
2345
+ text: ({ workout_id }, { workout }) => workout === null ? `Workout with ID ${workout_id} not found` : void 0,
2354
2346
  telemetry: ({ workout, expected404Outcome }) => ({
2355
2347
  ...workoutResultTelemetry(workout),
2356
2348
  expected404Outcome
@@ -2358,17 +2350,17 @@ const workoutResponse = defineStructuredResponseContract({
2358
2350
  });
2359
2351
  const workoutCountResponse = defineStructuredResponseContract({
2360
2352
  outputSchema: workoutCountOutputSchema,
2361
- normalize: (count) => ({ count }),
2353
+ normalize: (count) => ({ workout_count: count }),
2362
2354
  legacyJson: (output) => output,
2363
2355
  telemetry: (count) => ({ itemCountBucket: bucketCount(count) })
2364
2356
  });
2365
2357
  const workoutEventsResponse = defineStructuredResponseContract({
2366
2358
  outputSchema: workoutEventsOutputSchema,
2367
2359
  normalize: (data) => ({
2368
- events: data.events?.map(formatWorkoutEvent) ?? [],
2360
+ events: data.events?.map(projectWorkoutEvent) ?? [],
2369
2361
  page: data.page,
2370
- pageCount: data.pageCount,
2371
- hasNextPage: data.pageCount === void 0 ? void 0 : data.page < data.pageCount
2362
+ page_count: data.pageCount,
2363
+ has_next_page: data.pageCount === void 0 ? void 0 : data.page < data.pageCount
2372
2364
  }),
2373
2365
  legacyJson: ({ events }) => events,
2374
2366
  text: ({ since }, { events }) => events.length === 0 ? `No workout events found for the specified parameters since ${since}` : void 0,
@@ -2382,10 +2374,10 @@ const routinesResponse = defineStructuredResponseContract({
2382
2374
  normalize: (input) => {
2383
2375
  const data = normalizePaginatedInput(input);
2384
2376
  return {
2385
- routines: data.items.map(formatRoutine),
2377
+ routines: data.items.map(summarizeRoutine),
2386
2378
  page: data.page,
2387
- pageCount: data.pageCount,
2388
- hasNextPage: data.pageCount === void 0 ? void 0 : data.page < data.pageCount
2379
+ page_count: data.pageCount,
2380
+ has_next_page: data.pageCount === void 0 ? void 0 : data.page < data.pageCount
2389
2381
  };
2390
2382
  },
2391
2383
  legacyJson: ({ routines }) => routines,
@@ -2397,9 +2389,9 @@ const routinesResponse = defineStructuredResponseContract({
2397
2389
  });
2398
2390
  const routineResponse = defineStructuredResponseContract({
2399
2391
  outputSchema: routineOutputSchema,
2400
- normalize: (data) => ({ routine: data.routine ? formatRoutine(data.routine) : null }),
2392
+ normalize: (data) => ({ routine: data.routine ? projectRoutine(data.routine) : null }),
2401
2393
  legacyJson: ({ routine }) => routine,
2402
- text: ({ routineId }, { routine }) => routine === null ? `Routine with ID ${routineId} not found` : void 0,
2394
+ text: ({ routine_id }, { routine }) => routine === null ? `Routine with ID ${routine_id} not found` : void 0,
2403
2395
  telemetry: ({ routine, expected404Outcome }) => ({
2404
2396
  ...routineResultTelemetry(routine),
2405
2397
  expected404Outcome
@@ -2410,14 +2402,14 @@ const exerciseTemplatesResponse = defineStructuredResponseContract({
2410
2402
  normalize: (input) => {
2411
2403
  const data = normalizePaginatedInput(input);
2412
2404
  return {
2413
- exerciseTemplates: data.items.map(formatExerciseTemplate),
2405
+ exercise_templates: data.items,
2414
2406
  page: data.page,
2415
- pageCount: data.pageCount,
2416
- hasNextPage: data.pageCount === void 0 ? void 0 : data.page < data.pageCount
2407
+ page_count: data.pageCount,
2408
+ has_next_page: data.pageCount === void 0 ? void 0 : data.page < data.pageCount
2417
2409
  };
2418
2410
  },
2419
- legacyJson: ({ exerciseTemplates }) => exerciseTemplates,
2420
- text: (_data, { exerciseTemplates }) => exerciseTemplates.length === 0 ? "No exercise templates found for the specified parameters" : void 0,
2411
+ legacyJson: ({ exercise_templates }) => exercise_templates,
2412
+ text: (_data, { exercise_templates }) => exercise_templates.length === 0 ? "No exercise templates found for the specified parameters" : void 0,
2421
2413
  telemetry: (templates) => ({
2422
2414
  itemCountBucket: bucketCount(normalizePaginatedInput(templates).items.length),
2423
2415
  expected404Outcome: normalizePaginatedInput(templates).expected404Outcome
@@ -2425,26 +2417,26 @@ const exerciseTemplatesResponse = defineStructuredResponseContract({
2425
2417
  });
2426
2418
  const exerciseTemplateResponse = defineStructuredResponseContract({
2427
2419
  outputSchema: exerciseTemplateOutputSchema,
2428
- normalize: (data) => ({ exerciseTemplate: data.exerciseTemplate ? formatExerciseTemplate(data.exerciseTemplate) : null }),
2429
- legacyJson: ({ exerciseTemplate }) => exerciseTemplate,
2430
- text: ({ exerciseTemplateId }, { exerciseTemplate }) => exerciseTemplate === null ? `Exercise template with ID ${exerciseTemplateId} not found` : void 0,
2431
- telemetry: ({ exerciseTemplate, expected404Outcome }) => ({
2432
- itemCountBucket: bucketCount(exerciseTemplate ? 1 : 0),
2420
+ normalize: (data) => ({ exercise_template: data.exercise_template ?? null }),
2421
+ legacyJson: ({ exercise_template }) => exercise_template,
2422
+ text: ({ exercise_template_id }, { exercise_template }) => exercise_template === null ? `Exercise template with ID ${exercise_template_id} not found` : void 0,
2423
+ telemetry: ({ exercise_template, expected404Outcome }) => ({
2424
+ itemCountBucket: bucketCount(exercise_template ? 1 : 0),
2433
2425
  expected404Outcome
2434
2426
  })
2435
2427
  });
2436
2428
  const exerciseHistoryResponse = defineStructuredResponseContract({
2437
2429
  outputSchema: exerciseHistoryOutputSchema,
2438
- normalize: (data) => ({ exerciseHistory: data.history?.map(formatExerciseHistoryEntry) ?? [] }),
2439
- legacyJson: ({ exerciseHistory }) => exerciseHistory,
2440
- text: ({ exerciseTemplateId }, { exerciseHistory }) => exerciseHistory.length === 0 ? `No exercise history found for template ${exerciseTemplateId}` : void 0,
2441
- telemetry: (data) => ({ itemCountBucket: bucketCount(data.history?.length ?? 0) })
2430
+ normalize: (data) => ({ exercise_history: data.exercise_history?.map(normalizeExerciseHistoryEntry) ?? [] }),
2431
+ legacyJson: ({ exercise_history }) => exercise_history,
2432
+ text: ({ exercise_template_id }, { exercise_history }) => exercise_history.length === 0 ? `No exercise history found for template ${exercise_template_id}` : void 0,
2433
+ telemetry: (data) => ({ itemCountBucket: bucketCount(data.exercise_history?.length ?? 0) })
2442
2434
  });
2443
2435
  const searchExerciseTemplatesResponse = defineStructuredResponseContract({
2444
2436
  outputSchema: exerciseTemplateSearchOutputSchema,
2445
- normalize: (data) => ({ exerciseTemplates: data.results.map(formatExerciseTemplate) }),
2446
- legacyJson: ({ exerciseTemplates }) => exerciseTemplates,
2447
- text: ({ query, primaryMuscleGroup }, { exerciseTemplates }) => exerciseTemplates.length === 0 ? `No exercise templates found matching "${query}"${primaryMuscleGroup ? ` with primary muscle group "${primaryMuscleGroup}"` : ""}` : void 0,
2437
+ normalize: (data) => ({ exercise_templates: data.results }),
2438
+ legacyJson: ({ exercise_templates }) => exercise_templates,
2439
+ text: ({ query, primary_muscle_group }, { exercise_templates }) => exercise_templates.length === 0 ? `No exercise templates found matching "${query}"${primary_muscle_group ? ` with primary muscle group "${primary_muscle_group}"` : ""}` : void 0,
2448
2440
  telemetry: (data) => ({ itemCountBucket: bucketCount(data.results.length) })
2449
2441
  });
2450
2442
  const routineFoldersResponse = defineStructuredResponseContract({
@@ -2452,14 +2444,14 @@ const routineFoldersResponse = defineStructuredResponseContract({
2452
2444
  normalize: (input) => {
2453
2445
  const data = normalizePaginatedInput(input);
2454
2446
  return {
2455
- routineFolders: data.items.map(formatRoutineFolder),
2447
+ routine_folders: data.items.map(projectRoutineFolder),
2456
2448
  page: data.page,
2457
- pageCount: data.pageCount,
2458
- hasNextPage: data.pageCount === void 0 ? void 0 : data.page < data.pageCount
2449
+ page_count: data.pageCount,
2450
+ has_next_page: data.pageCount === void 0 ? void 0 : data.page < data.pageCount
2459
2451
  };
2460
2452
  },
2461
- legacyJson: ({ routineFolders }) => routineFolders,
2462
- text: (_data, { routineFolders }) => routineFolders.length === 0 ? "No routine folders found for the specified parameters" : void 0,
2453
+ legacyJson: ({ routine_folders }) => routine_folders,
2454
+ text: (_data, { routine_folders }) => routine_folders.length === 0 ? "No routine folders found for the specified parameters" : void 0,
2463
2455
  telemetry: (folders) => ({
2464
2456
  itemCountBucket: bucketCount(normalizePaginatedInput(folders).items.length),
2465
2457
  expected404Outcome: normalizePaginatedInput(folders).expected404Outcome
@@ -2467,11 +2459,11 @@ const routineFoldersResponse = defineStructuredResponseContract({
2467
2459
  });
2468
2460
  const routineFolderResponse = defineStructuredResponseContract({
2469
2461
  outputSchema: routineFolderOutputSchema,
2470
- normalize: (data) => ({ routineFolder: data.routineFolder ? formatRoutineFolder(data.routineFolder) : null }),
2471
- legacyJson: ({ routineFolder }) => routineFolder,
2472
- text: ({ folderId }, { routineFolder }) => routineFolder === null ? `Routine folder with ID ${folderId} not found` : void 0,
2473
- telemetry: ({ routineFolder, expected404Outcome }) => ({
2474
- itemCountBucket: bucketCount(routineFolder ? 1 : 0),
2462
+ normalize: (data) => ({ routine_folder: data.routine_folder ? projectRoutineFolder(data.routine_folder) : null }),
2463
+ legacyJson: ({ routine_folder }) => routine_folder,
2464
+ text: ({ folder_id }, { routine_folder }) => routine_folder === null ? `Routine folder with ID ${folder_id} not found` : void 0,
2465
+ telemetry: ({ routine_folder, expected404Outcome }) => ({
2466
+ itemCountBucket: bucketCount(routine_folder ? 1 : 0),
2475
2467
  expected404Outcome
2476
2468
  })
2477
2469
  });
@@ -2480,14 +2472,14 @@ const bodyMeasurementsResponse = defineStructuredResponseContract({
2480
2472
  normalize: (input) => {
2481
2473
  const data = normalizePaginatedInput(input);
2482
2474
  return {
2483
- bodyMeasurements: data.items.map(formatBodyMeasurement),
2475
+ body_measurements: data.items.map(normalizeBodyMeasurement),
2484
2476
  page: data.page,
2485
- pageCount: data.pageCount,
2486
- hasNextPage: data.pageCount === void 0 ? void 0 : data.page < data.pageCount
2477
+ page_count: data.pageCount,
2478
+ has_next_page: data.pageCount === void 0 ? void 0 : data.page < data.pageCount
2487
2479
  };
2488
2480
  },
2489
- legacyJson: ({ bodyMeasurements }) => bodyMeasurements,
2490
- text: (_data, { bodyMeasurements }) => bodyMeasurements.length === 0 ? "No body measurements found for the specified parameters" : void 0,
2481
+ legacyJson: ({ body_measurements }) => body_measurements,
2482
+ text: (_data, { body_measurements }) => body_measurements.length === 0 ? "No body measurements found for the specified parameters" : void 0,
2491
2483
  telemetry: (measurements) => ({
2492
2484
  itemCountBucket: bucketCount(normalizePaginatedInput(measurements).items.length),
2493
2485
  expected404Outcome: normalizePaginatedInput(measurements).expected404Outcome
@@ -2495,11 +2487,11 @@ const bodyMeasurementsResponse = defineStructuredResponseContract({
2495
2487
  });
2496
2488
  const bodyMeasurementResponse = defineStructuredResponseContract({
2497
2489
  outputSchema: bodyMeasurementOutputSchema,
2498
- normalize: (data) => ({ bodyMeasurement: data.bodyMeasurement ? formatBodyMeasurement(data.bodyMeasurement) : null }),
2499
- legacyJson: ({ bodyMeasurement }) => bodyMeasurement,
2500
- text: ({ date }, { bodyMeasurement }) => bodyMeasurement === null ? `No body measurement found for date ${date}` : void 0,
2501
- telemetry: ({ bodyMeasurement, expected404Outcome }) => ({
2502
- itemCountBucket: bucketCount(bodyMeasurement ? 1 : 0),
2490
+ normalize: (data) => ({ body_measurement: data.body_measurement ? normalizeBodyMeasurement(data.body_measurement) : null }),
2491
+ legacyJson: ({ body_measurement }) => body_measurement,
2492
+ text: ({ date }, { body_measurement }) => body_measurement === null ? `No body measurement found for date ${date}` : void 0,
2493
+ telemetry: ({ body_measurement, expected404Outcome }) => ({
2494
+ itemCountBucket: bucketCount(body_measurement ? 1 : 0),
2503
2495
  expected404Outcome
2504
2496
  })
2505
2497
  });
@@ -2512,17 +2504,46 @@ const userResponse = defineStructuredResponseContract({
2512
2504
  });
2513
2505
  const trainingSummaryResponse = defineStructuredResponseContract({
2514
2506
  outputSchema: trainingSummaryOutputSchema,
2515
- normalize: (data) => data,
2507
+ normalize: (data) => {
2508
+ const { workflow, ...result } = data;
2509
+ const { latest, earliest, weight_change_kg, ...body_measurements } = result.body_measurements;
2510
+ return {
2511
+ ...result,
2512
+ body_measurements: {
2513
+ ...body_measurements,
2514
+ ...latest ? { latest } : {},
2515
+ ...earliest ? { earliest } : {},
2516
+ ...weight_change_kg == null ? {} : { weight_change_kg }
2517
+ },
2518
+ scan: {
2519
+ pages: workflow.pagination,
2520
+ items: workflow.itemsScanned
2521
+ }
2522
+ };
2523
+ },
2516
2524
  legacyJson: (output) => output,
2517
- text: (data) => data.workouts.count === 0 && data.bodyMeasurements.count === 0 ? "No workouts or body measurements found for the specified period" : void 0,
2525
+ text: (data) => data.workouts.count === 0 && data.body_measurements.count === 0 ? "No workouts or body measurements found for the specified period" : void 0,
2518
2526
  telemetry: (data) => ({
2519
- itemCountBucket: bucketCount(data.workouts.count + data.bodyMeasurements.count),
2527
+ itemCountBucket: bucketCount(data.workouts.count + data.body_measurements.count),
2520
2528
  workflow: workflowResultTelemetry(data.workflow)
2521
2529
  })
2522
2530
  });
2523
2531
  const compactRoutinesResponse = defineStructuredResponseContract({
2524
2532
  outputSchema: compactRoutinesOutputSchema,
2525
- normalize: (data) => data,
2533
+ normalize: (data) => {
2534
+ const { workflow, ...result } = data;
2535
+ return {
2536
+ ...result,
2537
+ routines: result.routines.map((routine) => ({
2538
+ ...routine,
2539
+ ...routine.folder_id == null ? { folder_id: void 0 } : {}
2540
+ })),
2541
+ scan: {
2542
+ pages: workflow.pagination,
2543
+ items: workflow.itemsScanned
2544
+ }
2545
+ };
2546
+ },
2526
2547
  legacyJson: ({ routines }) => routines,
2527
2548
  text: (_data, { routines }) => routines.length === 0 ? "No routines found matching the query" : void 0,
2528
2549
  telemetry: (data) => ({
@@ -2530,22 +2551,22 @@ const compactRoutinesResponse = defineStructuredResponseContract({
2530
2551
  workflow: workflowResultTelemetry(data.workflow)
2531
2552
  })
2532
2553
  });
2533
- const createWorkoutResponse = defineJsonResponseContract((workout) => workout ? { json: formatWorkout(workout) } : { text: "Failed to create workout: Server returned no data" }, (workout) => workoutResultTelemetry(workout));
2534
- const updateWorkoutResponse = defineJsonResponseContract((data) => data.workout ? { json: formatWorkout(data.workout) } : { text: `Failed to update workout with ID ${data.workoutId}` }, (data) => workoutResultTelemetry(data.workout));
2554
+ const createWorkoutResponse = defineJsonResponseContract((workout) => workout ? { json: projectWorkout(workout) } : { text: "Failed to create workout: Server returned no data" }, (workout) => workoutResultTelemetry(workout));
2555
+ const updateWorkoutResponse = defineJsonResponseContract((data) => data.workout ? { json: projectWorkout(data.workout) } : { text: `Failed to update workout with ID ${data.workout_id}` }, (data) => workoutResultTelemetry(data.workout));
2535
2556
  const repRangeDisplayWarningText = "Note: Hevy's public API stores rep ranges (rep_range), but the Hevy apps may not display them because they rely on an internal-only exercise field (input_modifier). See https://github.com/chrisdoc/hevy-mcp/issues/261 for details/workarounds.";
2536
2557
  const createRoutineResponse = defineJsonResponseContract((data) => data.routine ? {
2537
- json: formatRoutine(data.routine),
2558
+ json: projectRoutine(data.routine),
2538
2559
  additionalText: data.usesRepRanges ? [repRangeDisplayWarningText] : []
2539
2560
  } : { text: "Failed to create routine: Server returned no data" }, (data) => routineResultTelemetry(data.routine));
2540
2561
  const updateRoutineResponse = defineJsonResponseContract((data) => data.routine ? {
2541
- json: formatRoutine(data.routine),
2562
+ json: projectRoutine(data.routine),
2542
2563
  additionalText: data.usesRepRanges ? [repRangeDisplayWarningText] : []
2543
- } : { text: `Failed to update routine with ID ${data.routineId}` }, (data) => routineResultTelemetry(data.routine));
2564
+ } : { text: `Failed to update routine with ID ${data.routine_id}` }, (data) => routineResultTelemetry(data.routine));
2544
2565
  const createExerciseTemplateResponse = defineJsonResponseContract((response) => ({ json: {
2545
2566
  id: response?.id,
2546
2567
  message: "Exercise template created successfully"
2547
2568
  } }));
2548
- const createRoutineFolderResponse = defineJsonResponseContract((folder) => folder ? { json: formatRoutineFolder(folder) } : { text: "Failed to create routine folder: Server returned no data" }, (folder) => ({ itemCountBucket: bucketCount(folder ? 1 : 0) }));
2569
+ const createRoutineFolderResponse = defineJsonResponseContract((folder) => folder ? { json: projectRoutineFolder(folder) } : { text: "Failed to create routine folder: Server returned no data" }, (folder) => ({ itemCountBucket: bucketCount(folder ? 1 : 0) }));
2549
2570
  const createBodyMeasurementResponse = defineJsonResponseContract((date) => ({ text: `Body measurement for ${date} created successfully.` }), () => ({ itemCountBucket: "1" }));
2550
2571
  const updateBodyMeasurementResponse = defineJsonResponseContract((date) => ({ text: `Body measurement for ${date} updated successfully.` }), () => ({ itemCountBucket: "1" }));
2551
2572
  //#endregion
@@ -2582,33 +2603,103 @@ function registerHevyResources(server, runtime) {
2582
2603
  description: "Total number of workouts in the Hevy account",
2583
2604
  mimeType: JSON_MIME_TYPE
2584
2605
  }, async (uri) => {
2585
- return createJsonResourceResult(uri, { count: (await runtime.getClient().getWorkoutCount())?.workout_count ?? 0 });
2606
+ return createJsonResourceResult(uri, { workout_count: (await runtime.getClient().getWorkoutCount())?.workout_count ?? 0 });
2586
2607
  });
2587
2608
  server.registerResource("exercise-templates", "hevy://exercise-templates", {
2588
2609
  description: "Full formatted Hevy exercise template catalog",
2589
2610
  mimeType: JSON_MIME_TYPE
2590
2611
  }, async (uri) => {
2591
- return createJsonResourceResult(uri, (await runtime.catalog.get()).map(formatExerciseTemplate));
2612
+ return createJsonResourceResult(uri, await runtime.catalog.get());
2592
2613
  });
2593
2614
  server.registerResource("routine-folders", "hevy://routine-folders", {
2594
2615
  description: "Full formatted list of Hevy routine folders",
2595
2616
  mimeType: JSON_MIME_TYPE
2596
2617
  }, async (uri) => {
2597
- return createJsonResourceResult(uri, (await fetchAllRoutineFolders(runtime)).map(formatRoutineFolder));
2618
+ return createJsonResourceResult(uri, (await fetchAllRoutineFolders(runtime)).map(projectRoutineFolder));
2598
2619
  });
2599
2620
  }
2600
2621
  //#endregion
2601
2622
  //#region ../core/src/server-metadata.ts
2602
2623
  const SERVER_NAME = "hevy-mcp";
2603
- const SERVER_VERSION = "4.1.2";
2624
+ const SERVER_VERSION = "5.0.0";
2604
2625
  const SERVER_INSTRUCTIONS = [
2605
2626
  "Hevy MCP connects clients to the authenticated user's Hevy workout-tracking data, including workouts, routines, exercise templates, routine folders, body measurements, and profile information. HEVY_API_KEY must contain a valid Hevy API key for local stdio use.",
2606
2627
  "Safety: all get-* and search-* tools are read-only. create-* and update-* tools mutate Hevy data. Creates are additive and non-idempotent, so repeating one can create duplicates. Updates can overwrite existing data. Delete operations are not available.",
2607
2628
  "Workflow: search exercise templates first, then use the returned template IDs when creating workouts or routines. To create a completed workout from a routine, fetch the routine as a plan, then obtain the actual completed sets and end time from the user; never invent completion data.",
2608
- "Pagination: start at page 1 and fetch only the pages needed. Most list tools allow pageSize up to 10; get-exercise-templates allows up to 100.",
2629
+ "Pagination: start at page 1 and fetch only the pages needed. Most list tools allow page_size up to 10; get-exercise-templates allows page_size up to 100.",
2609
2630
  "Rate limits and retries: minimize repeated calls. If Hevy returns HTTP 429, follow its retry guidance. Transient read requests retry automatically, but write requests do not; confirm uncertain write outcomes before trying again."
2610
2631
  ].join("\n\n");
2611
2632
  //#endregion
2633
+ //#region ../core/src/utils/compact-json-schema.ts
2634
+ function isRecord$1(value) {
2635
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2636
+ }
2637
+ function isNullSchema(value) {
2638
+ return isRecord$1(value) && value.type === "null" && Object.keys(value).length === 1;
2639
+ }
2640
+ function compactNullableSchema(value) {
2641
+ if (Array.isArray(value)) return value.map(compactNullableSchema);
2642
+ if (!isRecord$1(value)) return value;
2643
+ const anyOf = value.anyOf;
2644
+ if (Array.isArray(anyOf) && anyOf.length === 2) {
2645
+ const nullableBranch = anyOf.find(isNullSchema);
2646
+ const valueBranch = anyOf.find((branch) => !isNullSchema(branch));
2647
+ if (nullableBranch !== void 0 && isRecord$1(valueBranch) && typeof valueBranch.type === "string") {
2648
+ const { anyOf: _anyOf, ...metadata } = value;
2649
+ return compactNullableSchema({
2650
+ ...valueBranch,
2651
+ ...metadata,
2652
+ type: [valueBranch.type, "null"]
2653
+ });
2654
+ }
2655
+ }
2656
+ return Object.fromEntries(Object.entries(value).map(([key, nested]) => [key, compactNullableSchema(nested)]));
2657
+ }
2658
+ function omitOutputObjectRestrictions(value) {
2659
+ if (Array.isArray(value)) return value.map(omitOutputObjectRestrictions);
2660
+ if (!isRecord$1(value)) return value;
2661
+ return Object.fromEntries(Object.entries(value).filter(([key, nested]) => key !== "additionalProperties" || nested !== false).map(([key, nested]) => [key, omitOutputObjectRestrictions(nested)]));
2662
+ }
2663
+ function convertSchema(schema, io) {
2664
+ const converted = z.toJSONSchema(schema, {
2665
+ target: "draft-2020-12",
2666
+ io
2667
+ });
2668
+ if (!isRecord$1(converted)) throw new Error("Zod emitted a non-object root schema");
2669
+ const compacted = compactNullableSchema(converted);
2670
+ if (!isRecord$1(compacted)) throw new Error("Nullable schema compaction changed the root schema");
2671
+ const output = io === "output" ? omitOutputObjectRestrictions(compacted) : compacted;
2672
+ if (!isRecord$1(output)) throw new Error("Output schema compaction changed the root schema");
2673
+ delete output.$schema;
2674
+ return output;
2675
+ }
2676
+ /**
2677
+ * Keep Zod validation while advertising a compact JSON Schema.
2678
+ *
2679
+ * Zod emits nullable values as `anyOf`; draft 2020-12 also permits a type
2680
+ * array. The MCP wire envelope does not need a repeated `$schema` marker, and
2681
+ * structured output is validated by the original Zod schema before sending.
2682
+ */
2683
+ function compactJsonSchema(schema) {
2684
+ let inputSchema;
2685
+ let outputSchema;
2686
+ Object.defineProperty(schema["~standard"], "jsonSchema", {
2687
+ configurable: true,
2688
+ value: {
2689
+ input: () => {
2690
+ inputSchema ??= convertSchema(schema, "input");
2691
+ return inputSchema;
2692
+ },
2693
+ output: () => {
2694
+ outputSchema ??= convertSchema(schema, "output");
2695
+ return outputSchema;
2696
+ }
2697
+ },
2698
+ writable: true
2699
+ });
2700
+ return schema;
2701
+ }
2702
+ //#endregion
2612
2703
  //#region ../core/src/utils/tool-helpers.ts
2613
2704
  /**
2614
2705
  * Type-safe utilities for MCP tool handlers
@@ -2646,7 +2737,7 @@ function requireClient(client) {
2646
2737
  * ```
2647
2738
  */
2648
2739
  function createTypedToolHandler(schema, handler) {
2649
- const zodSchema = z.object(schema);
2740
+ const zodSchema = z.strictObject(schema);
2650
2741
  return async (args) => {
2651
2742
  return handler(zodSchema.parse(args));
2652
2743
  };
@@ -2660,16 +2751,14 @@ function registerToolDefinition(server, runtime, definition) {
2660
2751
  kind: definition.kind,
2661
2752
  operation: definition.operation
2662
2753
  });
2663
- if (definition.kind === "read") {
2664
- server.registerTool(definition.name, {
2665
- description: definition.description,
2666
- inputSchema: definition.inputSchema,
2667
- outputSchema: definition.outputSchema,
2668
- annotations: definition.annotations
2669
- }, callback);
2670
- return;
2671
- }
2672
- server.tool(definition.name, definition.description, definition.inputSchema, definition.annotations, callback);
2754
+ const inputSchema = compactJsonSchema(z.strictObject(definition.inputSchema));
2755
+ const config = {
2756
+ description: definition.description,
2757
+ inputSchema,
2758
+ annotations: definition.annotations,
2759
+ ...definition.kind === "read" ? { outputSchema: compactJsonSchema(z.object(definition.outputSchema)) } : {}
2760
+ };
2761
+ server.registerTool(definition.name, config, callback);
2673
2762
  }
2674
2763
  //#endregion
2675
2764
  //#region ../core/src/utils/tool-annotations.ts
@@ -2705,16 +2794,6 @@ function updateAnnotations(title) {
2705
2794
  };
2706
2795
  }
2707
2796
  //#endregion
2708
- //#region ../core/src/utils/tool-descriptions.ts
2709
- function describeTool({ summary, aliases, useCase, importantNotes }) {
2710
- return [
2711
- summary,
2712
- `Aliases: ${aliases.join(", ")}.`,
2713
- `<use_case>${useCase}</use_case>`,
2714
- `<important_notes>${importantNotes}</important_notes>`
2715
- ].join(" ");
2716
- }
2717
- //#endregion
2718
2797
  //#region ../core/src/utils/json-parser.ts
2719
2798
  /**
2720
2799
  * Preprocessor to handle MCP clients that send JSON-stringified arrays
@@ -2736,143 +2815,156 @@ function parseJsonArray(val) {
2736
2815
  }
2737
2816
  //#endregion
2738
2817
  //#region ../core/src/tools/input-schemas.ts
2739
- /** Build the page and pageSize fields shared by paginated tools. */
2818
+ /** Build the page and page_size fields shared by paginated tools. */
2740
2819
  function paginationShape({ defaultPageSize, maxPageSize, integerPage = true }) {
2741
2820
  const pageNumber = z.coerce.number().gte(1);
2742
2821
  return {
2743
2822
  page: integerPage ? pageNumber.int() : pageNumber,
2744
- pageSize: z.coerce.number().int().gte(1).lte(maxPageSize).default(defaultPageSize)
2823
+ page_size: z.coerce.number().int().gte(1).lte(maxPageSize).default(defaultPageSize)
2745
2824
  };
2746
2825
  }
2747
2826
  const nonEmptyId = z.string().min(1);
2748
- const exerciseTemplateInputShape = {
2827
+ const exerciseTemplatePayloadShape = {
2749
2828
  title: z.string().min(1),
2750
- exerciseType: exerciseTypeEnum,
2751
- equipmentCategory: equipmentCategoryEnum,
2752
- muscleGroup: muscleGroupEnum,
2753
- otherMuscles: z.array(muscleGroupEnum).default([])
2829
+ exercise_type: exerciseTypeEnum,
2830
+ equipment_category: equipmentCategoryEnum,
2831
+ muscle_group: muscleGroupEnum,
2832
+ other_muscles: z.array(muscleGroupEnum).default([])
2754
2833
  };
2755
- const routineFolderInputShape = { name: z.string().min(1) };
2834
+ const exerciseTemplateInputShape = z.strictObject({ exercise: z.strictObject(exerciseTemplatePayloadShape) }).shape;
2835
+ const routineFolderPayloadShape = { title: z.string().min(1) };
2836
+ const routineFolderInputShape = z.strictObject({ routine_folder: z.strictObject(routineFolderPayloadShape) }).shape;
2756
2837
  const CALENDAR_DATE_MESSAGE = "Date must be in YYYY-MM-DD format";
2757
2838
  const calendarDate = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, CALENDAR_DATE_MESSAGE).refine((value) => {
2758
2839
  const parsed = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
2759
2840
  return !Number.isNaN(parsed.getTime()) && parsed.toISOString().startsWith(value);
2760
2841
  }, CALENDAR_DATE_MESSAGE);
2842
+ const rpeEnum = z.union([
2843
+ z.literal(6),
2844
+ z.literal(7),
2845
+ z.literal(7.5),
2846
+ z.literal(8),
2847
+ z.literal(8.5),
2848
+ z.literal(9),
2849
+ z.literal(9.5),
2850
+ z.literal(10)
2851
+ ]);
2761
2852
  const workoutSetShape = {
2762
2853
  type: setTypeEnum,
2763
- weight: z.coerce.number().optional().nullable(),
2764
- weightKg: z.coerce.number().optional().nullable(),
2854
+ weight_kg: z.coerce.number().optional().nullable(),
2765
2855
  reps: z.coerce.number().int().optional().nullable(),
2766
- distance: z.coerce.number().int().optional().nullable(),
2767
- distanceMeters: z.coerce.number().int().optional().nullable(),
2768
- duration: z.coerce.number().int().optional().nullable(),
2769
- durationSeconds: z.coerce.number().int().optional().nullable(),
2770
- rpe: z.coerce.number().optional().nullable(),
2771
- customMetric: z.coerce.number().optional().nullable()
2856
+ distance_meters: z.coerce.number().int().optional().nullable(),
2857
+ duration_seconds: z.coerce.number().int().optional().nullable(),
2858
+ rpe: rpeEnum.optional().nullable(),
2859
+ custom_metric: z.coerce.number().optional().nullable()
2772
2860
  };
2861
+ const workoutSetSchema = z.strictObject(workoutSetShape);
2773
2862
  const workoutExerciseShape = {
2774
- exerciseTemplateId: nonEmptyId,
2775
- supersetId: z.coerce.number().nullable().optional(),
2863
+ exercise_template_id: nonEmptyId,
2864
+ superset_id: z.coerce.number().nullable().optional(),
2776
2865
  notes: z.string().optional().nullable(),
2777
- sets: z.array(z.object(workoutSetShape))
2866
+ sets: z.array(workoutSetSchema)
2778
2867
  };
2779
- const workoutPayloadShape = {
2868
+ const workoutExerciseSchema = z.strictObject(workoutExerciseShape);
2869
+ const workoutExercisesSchema = z.preprocess(parseJsonArray, z.array(workoutExerciseSchema));
2870
+ const replaceWorkoutPayloadShape = {
2780
2871
  title: z.string().min(1),
2781
2872
  description: z.string().optional().nullable(),
2782
- startTime: utcSecondTimestamp,
2783
- endTime: utcSecondTimestamp,
2784
- isPrivate: z.boolean().default(false),
2785
- exercises: z.preprocess(parseJsonArray, z.array(z.object(workoutExerciseShape)))
2873
+ start_time: utcSecondTimestamp,
2874
+ end_time: utcSecondTimestamp,
2875
+ is_private: z.boolean().default(false),
2876
+ exercises: workoutExercisesSchema
2786
2877
  };
2878
+ const replaceWorkoutPayloadSchema = z.strictObject(replaceWorkoutPayloadShape);
2879
+ const workoutInputShape = z.strictObject({ workout: replaceWorkoutPayloadSchema }).shape;
2880
+ z.strictObject({
2881
+ workout_id: nonEmptyId,
2882
+ workout: replaceWorkoutPayloadSchema
2883
+ }).shape;
2884
+ const workoutMetadataPatchSchema = z.strictObject({
2885
+ title: z.string().min(1).optional(),
2886
+ description: z.string().nullable().optional(),
2887
+ start_time: utcSecondTimestamp.optional(),
2888
+ end_time: utcSecondTimestamp.optional(),
2889
+ is_private: z.boolean().optional()
2890
+ }).refine((patch) => Object.values(patch).some((value) => value !== void 0), "Include at least one workout metadata field").meta({ minProperties: 1 });
2891
+ const updateWorkoutInputShape = z.strictObject({
2892
+ workout_id: nonEmptyId,
2893
+ workout: workoutMetadataPatchSchema
2894
+ }).shape;
2895
+ const replaceWorkoutExercisesInputShape = z.strictObject({
2896
+ workout_id: nonEmptyId,
2897
+ workout: z.strictObject({ exercises: workoutExercisesSchema })
2898
+ }).shape;
2787
2899
  const routineSetShape = {
2788
2900
  type: setTypeEnum,
2789
- weight: z.coerce.number().optional(),
2790
- weightKg: z.coerce.number().optional(),
2901
+ weight_kg: z.coerce.number().optional(),
2791
2902
  reps: zNullableInt,
2792
- distance: z.coerce.number().int().optional(),
2793
- distanceMeters: z.coerce.number().int().optional(),
2794
- duration: z.coerce.number().int().optional(),
2795
- durationSeconds: z.coerce.number().int().optional(),
2796
- customMetric: z.coerce.number().optional(),
2797
- repRange: zOptionalRepRange
2903
+ distance_meters: z.coerce.number().int().optional(),
2904
+ duration_seconds: z.coerce.number().int().optional(),
2905
+ rep_range: zStrictOptionalRepRange,
2906
+ custom_metric: z.coerce.number().optional()
2798
2907
  };
2908
+ const routineSetSchema = z.strictObject(routineSetShape);
2799
2909
  const routineExerciseShape = {
2800
- exerciseTemplateId: nonEmptyId,
2801
- supersetId: z.coerce.number().nullable().optional(),
2802
- restSeconds: z.coerce.number().int().min(0).optional(),
2910
+ exercise_template_id: nonEmptyId,
2911
+ superset_id: z.coerce.number().nullable().optional(),
2912
+ rest_seconds: z.coerce.number().int().min(0).optional(),
2803
2913
  notes: z.string().optional(),
2804
- sets: z.array(z.object(routineSetShape))
2914
+ sets: z.array(routineSetSchema)
2805
2915
  };
2916
+ const routineExerciseSchema = z.strictObject(routineExerciseShape);
2917
+ const routineExercisesSchema = z.preprocess(parseJsonArray, z.array(routineExerciseSchema));
2806
2918
  const routinePayloadShape = {
2807
2919
  title: z.string().min(1),
2808
- folderId: z.coerce.number().nullable().optional(),
2920
+ folder_id: z.coerce.number().nullable().optional(),
2921
+ notes: z.string().optional(),
2922
+ exercises: routineExercisesSchema
2923
+ };
2924
+ const routinePayloadSchema = z.strictObject(routinePayloadShape);
2925
+ const createRoutineInputShape = z.strictObject({ routine: routinePayloadSchema }).shape;
2926
+ const routineUpdatePayloadShape = {
2927
+ title: z.string().min(1),
2809
2928
  notes: z.string().optional(),
2810
- exercises: z.preprocess(parseJsonArray, z.array(z.object(routineExerciseShape)))
2929
+ exercises: routineExercisesSchema
2811
2930
  };
2931
+ const routineUpdatePayloadSchema = z.strictObject(routineUpdatePayloadShape);
2932
+ const updateRoutineInputShape = z.strictObject({
2933
+ routine_id: nonEmptyId,
2934
+ routine: routineUpdatePayloadSchema
2935
+ }).shape;
2812
2936
  const bodyMeasurementFieldsSchema = {
2813
- weightKg: zNullableNumber.describe("Body weight in kilograms"),
2814
- leanMassKg: zNullableNumber.describe("Lean body mass in kilograms"),
2815
- fatPercent: zNullableNumber.describe("Body fat percentage"),
2816
- neckCm: zNullableNumber.describe("Neck circumference in centimeters"),
2817
- shoulderCm: zNullableNumber.describe("Shoulder circumference in centimeters"),
2818
- chestCm: zNullableNumber.describe("Chest circumference in centimeters"),
2819
- leftBicepCm: zNullableNumber.describe("Left bicep circumference in centimeters"),
2820
- rightBicepCm: zNullableNumber.describe("Right bicep circumference in centimeters"),
2821
- leftForearmCm: zNullableNumber.describe("Left forearm circumference in centimeters"),
2822
- rightForearmCm: zNullableNumber.describe("Right forearm circumference in centimeters"),
2823
- abdomen: zNullableNumber.describe("Abdomen circumference in centimeters"),
2824
- waist: zNullableNumber.describe("Waist circumference in centimeters"),
2825
- hips: zNullableNumber.describe("Hips circumference in centimeters"),
2826
- leftThigh: zNullableNumber.describe("Left thigh circumference in centimeters"),
2827
- rightThigh: zNullableNumber.describe("Right thigh circumference in centimeters"),
2828
- leftCalf: zNullableNumber.describe("Left calf circumference in centimeters"),
2829
- rightCalf: zNullableNumber.describe("Right calf circumference in centimeters")
2830
- };
2831
- const measurementFieldToApiKey = {
2832
- weightKg: "weight_kg",
2833
- leanMassKg: "lean_mass_kg",
2834
- fatPercent: "fat_percent",
2835
- neckCm: "neck_cm",
2836
- shoulderCm: "shoulder_cm",
2837
- chestCm: "chest_cm",
2838
- leftBicepCm: "left_bicep_cm",
2839
- rightBicepCm: "right_bicep_cm",
2840
- leftForearmCm: "left_forearm_cm",
2841
- rightForearmCm: "right_forearm_cm",
2842
- abdomen: "abdomen",
2843
- waist: "waist",
2844
- hips: "hips",
2845
- leftThigh: "left_thigh",
2846
- rightThigh: "right_thigh",
2847
- leftCalf: "left_calf",
2848
- rightCalf: "right_calf"
2937
+ weight_kg: zNullableNumber,
2938
+ lean_mass_kg: zNullableNumber,
2939
+ fat_percent: zNullableNumber,
2940
+ neck_cm: zNullableNumber,
2941
+ shoulder_cm: zNullableNumber,
2942
+ chest_cm: zNullableNumber,
2943
+ left_bicep_cm: zNullableNumber,
2944
+ right_bicep_cm: zNullableNumber,
2945
+ left_forearm_cm: zNullableNumber,
2946
+ right_forearm_cm: zNullableNumber,
2947
+ abdomen: zNullableNumber.describe("Circumference in centimeters."),
2948
+ waist: zNullableNumber.describe("Circumference in centimeters."),
2949
+ hips: zNullableNumber.describe("Circumference in centimeters."),
2950
+ left_thigh: zNullableNumber,
2951
+ right_thigh: zNullableNumber,
2952
+ left_calf: zNullableNumber,
2953
+ right_calf: zNullableNumber
2849
2954
  };
2955
+ z.strictObject(bodyMeasurementFieldsSchema);
2956
+ const createBodyMeasurementInputSchema = z.strictObject({
2957
+ date: calendarDate,
2958
+ ...bodyMeasurementFieldsSchema
2959
+ });
2960
+ const updateBodyMeasurementInputSchema = z.strictObject({
2961
+ date: calendarDate,
2962
+ ...bodyMeasurementFieldsSchema
2963
+ });
2964
+ const createBodyMeasurementInputShape = createBodyMeasurementInputSchema.shape;
2965
+ const updateBodyMeasurementInputShape = updateBodyMeasurementInputSchema.shape;
2850
2966
  //#endregion
2851
- //#region ../core/src/tools/payload-mappers.ts
2852
- /** Map the public camelCase workout input to the API's snake_case payload. */
2853
- function buildWorkoutPayload(input) {
2854
- return {
2855
- title: input.title,
2856
- description: input.description ?? null,
2857
- start_time: input.startTime,
2858
- end_time: input.endTime,
2859
- is_private: input.isPrivate,
2860
- exercises: input.exercises.map((exercise) => ({
2861
- exercise_template_id: exercise.exerciseTemplateId,
2862
- superset_id: exercise.supersetId ?? null,
2863
- notes: exercise.notes ?? null,
2864
- sets: exercise.sets.map((set) => ({
2865
- type: set.type,
2866
- weight_kg: set.weight ?? set.weightKg ?? null,
2867
- reps: set.reps ?? null,
2868
- distance_meters: set.distance ?? set.distanceMeters ?? null,
2869
- duration_seconds: set.duration ?? set.durationSeconds ?? null,
2870
- rpe: set.rpe ?? null,
2871
- custom_metric: set.customMetric ?? null
2872
- }))
2873
- }))
2874
- };
2875
- }
2967
+ //#region ../core/src/tools/mutation-semantics.ts
2876
2968
  function buildRepRange(repRange) {
2877
2969
  if (!repRange) return null;
2878
2970
  const start = repRange.start ?? void 0;
@@ -2892,14 +2984,14 @@ function getFixedRepsFromRepRange(repRange) {
2892
2984
  }
2893
2985
  function buildRoutineSets(sets, mode) {
2894
2986
  return sets.map((set) => {
2895
- const repRange = buildRepRange(set.repRange);
2987
+ const repRange = buildRepRange(set.rep_range);
2896
2988
  const reps = typeof set.reps === "number" ? set.reps : getFixedRepsFromRepRange(repRange);
2897
2989
  const common = {
2898
- weight_kg: set.weight ?? set.weightKg ?? null,
2990
+ weight_kg: set.weight_kg ?? null,
2899
2991
  reps: reps ?? null,
2900
- distance_meters: set.distance ?? set.distanceMeters ?? null,
2901
- duration_seconds: set.duration ?? set.durationSeconds ?? null,
2902
- custom_metric: set.customMetric ?? null
2992
+ distance_meters: set.distance_meters ?? null,
2993
+ duration_seconds: set.duration_seconds ?? null,
2994
+ custom_metric: set.custom_metric ?? null
2903
2995
  };
2904
2996
  if (mode === "create") return {
2905
2997
  ...common,
@@ -2919,9 +3011,9 @@ function buildRoutinePayload(input, mode) {
2919
3011
  const sets = buildRoutineSets(exercise.sets, mode);
2920
3012
  if (sets.some((set) => set.rep_range != null && getFixedRepsFromRepRange(set.rep_range) === null)) usesRepRanges = true;
2921
3013
  return {
2922
- exercise_template_id: exercise.exerciseTemplateId,
2923
- superset_id: exercise.supersetId ?? null,
2924
- rest_seconds: exercise.restSeconds ?? null,
3014
+ exercise_template_id: exercise.exercise_template_id,
3015
+ superset_id: exercise.superset_id ?? null,
3016
+ rest_seconds: exercise.rest_seconds ?? null,
2925
3017
  notes: exercise.notes ?? null,
2926
3018
  sets
2927
3019
  };
@@ -2929,7 +3021,7 @@ function buildRoutinePayload(input, mode) {
2929
3021
  if (mode === "create") return {
2930
3022
  payload: {
2931
3023
  title: input.title,
2932
- folder_id: input.folderId ?? null,
3024
+ folder_id: input.folder_id ?? null,
2933
3025
  notes: input.notes ?? "",
2934
3026
  exercises
2935
3027
  },
@@ -2944,27 +3036,71 @@ function buildRoutinePayload(input, mode) {
2944
3036
  usesRepRanges
2945
3037
  };
2946
3038
  }
3039
+ const workoutUpdateMetadataSchema = z.object({
3040
+ title: z.string().min(1),
3041
+ start_time: utcSecondTimestamp,
3042
+ end_time: utcSecondTimestamp
3043
+ });
3044
+ /**
3045
+ * Map fetched API exercises to the update shape without validating legacy data.
3046
+ * Caller-supplied replacement exercises are still validated by their input schema.
3047
+ */
3048
+ function preserveWorkoutExercises(current) {
3049
+ return current.exercises?.map((exercise) => ({
3050
+ exercise_template_id: exercise.exercise_template_id,
3051
+ superset_id: exercise.supersets_id ?? null,
3052
+ notes: exercise.notes ?? null,
3053
+ sets: exercise.sets?.map((set) => ({
3054
+ ...set.type === void 0 ? {} : { type: set.type },
3055
+ weight_kg: set.weight_kg ?? null,
3056
+ reps: set.reps ?? null,
3057
+ distance_meters: set.distance_meters ?? null,
3058
+ duration_seconds: set.duration_seconds ?? null,
3059
+ rpe: set.rpe,
3060
+ custom_metric: set.custom_metric ?? null
3061
+ })) ?? []
3062
+ })) ?? [];
3063
+ }
3064
+ function buildWorkoutUpdatePayload(current, patch, replacementExercises) {
3065
+ return {
3066
+ ...workoutUpdateMetadataSchema.parse({
3067
+ title: patch.title !== void 0 ? patch.title : current.title,
3068
+ start_time: patch.start_time !== void 0 ? patch.start_time : current.start_time,
3069
+ end_time: patch.end_time !== void 0 ? patch.end_time : current.end_time
3070
+ }),
3071
+ description: patch.description !== void 0 ? patch.description : current.description ?? null,
3072
+ exercises: replacementExercises === void 0 ? preserveWorkoutExercises(current) : replacementExercises,
3073
+ ...patch.is_private !== void 0 ? { is_private: patch.is_private } : {}
3074
+ };
3075
+ }
3076
+ const measurementKeys = [
3077
+ "weight_kg",
3078
+ "lean_mass_kg",
3079
+ "fat_percent",
3080
+ "neck_cm",
3081
+ "shoulder_cm",
3082
+ "chest_cm",
3083
+ "left_bicep_cm",
3084
+ "right_bicep_cm",
3085
+ "left_forearm_cm",
3086
+ "right_forearm_cm",
3087
+ "abdomen",
3088
+ "waist",
3089
+ "hips",
3090
+ "left_thigh",
3091
+ "right_thigh",
3092
+ "left_calf",
3093
+ "right_calf"
3094
+ ];
2947
3095
  /** Omit nullish measurement values because the API rejects explicit nulls. */
2948
3096
  function buildMeasurementPayload(fields) {
2949
3097
  const payload = {};
2950
- for (const [camelKey, apiKey] of Object.entries(measurementFieldToApiKey)) {
2951
- const value = fields[camelKey];
2952
- if (value != null) payload[apiKey] = value;
3098
+ for (const key of measurementKeys) {
3099
+ const value = fields[key];
3100
+ if (value != null) payload[key] = value;
2953
3101
  }
2954
3102
  return payload;
2955
3103
  }
2956
- function buildExerciseTemplateRequest(input) {
2957
- return { exercise: {
2958
- title: input.title,
2959
- exercise_type: input.exerciseType,
2960
- equipment_category: input.equipmentCategory,
2961
- muscle_group: input.muscleGroup,
2962
- other_muscles: input.otherMuscles
2963
- } };
2964
- }
2965
- function buildRoutineFolderRequest(input) {
2966
- return { routine_folder: { title: input.name } };
2967
- }
2968
3104
  //#endregion
2969
3105
  //#region ../core/src/utils/hevy-error-policy.ts
2970
3106
  const READ_RESOURCE_ENDPOINTS = /* @__PURE__ */ new Set([
@@ -2996,41 +3132,26 @@ const getBodyMeasurementsSchema = { ...paginationShape({
2996
3132
  defaultPageSize: 10,
2997
3133
  maxPageSize: 10
2998
3134
  }) };
2999
- const getBodyMeasurementSchema = { date: calendarDate.describe("The date of the body measurement (YYYY-MM-DD)") };
3000
- const createBodyMeasurementSchema = {
3001
- date: calendarDate.describe("The date of the body measurement (YYYY-MM-DD). Must be unique — returns 409 if an entry already exists for this date."),
3002
- ...bodyMeasurementFieldsSchema
3003
- };
3004
- const updateBodyMeasurementSchema = {
3005
- date: calendarDate.describe("The date of the body measurement to update (YYYY-MM-DD). Must already exist — returns 404 otherwise."),
3006
- ...bodyMeasurementFieldsSchema
3007
- };
3135
+ const getBodyMeasurementSchema = { date: calendarDate };
3136
+ const createBodyMeasurementSchema = createBodyMeasurementInputShape;
3137
+ const updateBodyMeasurementSchema = updateBodyMeasurementInputShape;
3008
3138
  const bodyMeasurementToolDefinitions = [
3009
3139
  {
3010
3140
  name: "get-body-measurements",
3011
3141
  feature: "measurements",
3012
3142
  operation: "list",
3013
- description: describeTool({
3014
- summary: "Read-only. Lists dated body measurements for the account.",
3015
- aliases: [
3016
- "body stats history",
3017
- "list weigh-ins",
3018
- "measurement log"
3019
- ],
3020
- useCase: "Use to browse measurement history; use get-body-measurement for one exact date.",
3021
- importantNotes: "Results are paginated; page starts at 1 and pageSize is limited to 10."
3022
- }),
3143
+ description: "Read-only. Lists dated body measurements; results are paginated. Use get-body-measurement for one date.",
3023
3144
  inputSchema: getBodyMeasurementsSchema,
3024
3145
  outputSchema: bodyMeasurementsResponse.outputSchema,
3025
3146
  annotations: readOnlyAnnotations("Get Body Measurements"),
3026
3147
  kind: "read",
3027
3148
  responseContract: bodyMeasurementsResponse,
3028
3149
  execute: async (runtime, args) => {
3029
- const { page, pageSize } = args;
3150
+ const { page, page_size } = args;
3030
3151
  try {
3031
3152
  const data = await runtime.getClient().getBodyMeasurements({
3032
3153
  page,
3033
- pageSize
3154
+ pageSize: page_size
3034
3155
  });
3035
3156
  return {
3036
3157
  items: data?.body_measurements ?? [],
@@ -3051,16 +3172,7 @@ const bodyMeasurementToolDefinitions = [
3051
3172
  name: "get-body-measurement",
3052
3173
  feature: "measurements",
3053
3174
  operation: "get",
3054
- description: describeTool({
3055
- summary: "Read-only. Retrieves the body measurement entry for one date.",
3056
- aliases: [
3057
- "get weigh-in",
3058
- "show body stats",
3059
- "measurement by date"
3060
- ],
3061
- useCase: "Use when the exact measurement date is known; use get-body-measurements to browse dates.",
3062
- importantNotes: "date must use YYYY-MM-DD; at most one entry exists per date."
3063
- }),
3175
+ description: "Read-only. Gets the body measurement for one YYYY-MM-DD date. Use get-body-measurements to browse dates.",
3064
3176
  inputSchema: getBodyMeasurementSchema,
3065
3177
  outputSchema: bodyMeasurementResponse.outputSchema,
3066
3178
  annotations: readOnlyAnnotations("Get Body Measurement"),
@@ -3070,12 +3182,12 @@ const bodyMeasurementToolDefinitions = [
3070
3182
  const { date } = args;
3071
3183
  try {
3072
3184
  return {
3073
- bodyMeasurement: await runtime.getClient().getBodyMeasurement(date),
3185
+ body_measurement: await runtime.getClient().getBodyMeasurement(date),
3074
3186
  date
3075
3187
  };
3076
3188
  } catch (error) {
3077
3189
  if (isExpectedReadNotFound(error)) return {
3078
- bodyMeasurement: null,
3190
+ body_measurement: null,
3079
3191
  date,
3080
3192
  expected404Outcome: "not_found"
3081
3193
  };
@@ -3087,16 +3199,7 @@ const bodyMeasurementToolDefinitions = [
3087
3199
  name: "create-body-measurement",
3088
3200
  feature: "measurements",
3089
3201
  operation: "create",
3090
- description: describeTool({
3091
- summary: "Writes to the Hevy account by creating a dated body measurement.",
3092
- aliases: [
3093
- "log weigh-in",
3094
- "add body stats",
3095
- "record measurements"
3096
- ],
3097
- useCase: "Use for a date without an entry; use update-body-measurement when that date already exists.",
3098
- importantNotes: "date must use YYYY-MM-DD and be unique. Null fields are omitted and cannot clear values; an existing date returns 409."
3099
- }),
3202
+ description: "Writes a body measurement for a new YYYY-MM-DD date. Existing dates conflict; retries are not idempotent.",
3100
3203
  inputSchema: createBodyMeasurementSchema,
3101
3204
  annotations: createAnnotations("Create Body Measurement"),
3102
3205
  kind: "write",
@@ -3114,16 +3217,7 @@ const bodyMeasurementToolDefinitions = [
3114
3217
  name: "update-body-measurement",
3115
3218
  feature: "measurements",
3116
3219
  operation: "update",
3117
- description: describeTool({
3118
- summary: "Mutates the Hevy account by updating a body measurement for a date.",
3119
- aliases: [
3120
- "edit weigh-in",
3121
- "correct body stats",
3122
- "change measurements"
3123
- ],
3124
- useCase: "Use to change fields on an existing date; use create-body-measurement for a new date.",
3125
- importantNotes: "date must use YYYY-MM-DD and already exist. Provide at least one numeric field; nulls are omitted and cannot clear stored values."
3126
- }),
3220
+ description: "Mutates numeric fields on an existing YYYY-MM-DD measurement. Omitted fields remain unchanged; values cannot be cleared.",
3127
3221
  inputSchema: updateBodyMeasurementSchema,
3128
3222
  annotations: updateAnnotations("Update Body Measurement"),
3129
3223
  kind: "write",
@@ -3131,7 +3225,7 @@ const bodyMeasurementToolDefinitions = [
3131
3225
  execute: async (runtime, args) => {
3132
3226
  const { date, ...fields } = args;
3133
3227
  const payload = buildMeasurementPayload(fields);
3134
- 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.");
3228
+ if (Object.keys(payload).length === 0) throw new Error("No measurement fields provided. Include at least one numeric measurement field (e.g. weight_kg) to update.");
3135
3229
  await runtime.getClient().updateBodyMeasurement(date, payload);
3136
3230
  return date;
3137
3231
  }
@@ -3143,34 +3237,25 @@ const getRoutineFoldersSchema = paginationShape({
3143
3237
  defaultPageSize: 5,
3144
3238
  maxPageSize: 10
3145
3239
  });
3146
- const getRoutineFolderSchema = { folderId: nonEmptyId };
3240
+ const getRoutineFolderSchema = { folder_id: nonEmptyId };
3147
3241
  const createRoutineFolderSchema = routineFolderInputShape;
3148
3242
  const folderToolDefinitions = [
3149
3243
  {
3150
3244
  name: "get-routine-folders",
3151
3245
  feature: "folders",
3152
3246
  operation: "list",
3153
- description: describeTool({
3154
- summary: "Read-only. Lists default and custom routine folders.",
3155
- aliases: [
3156
- "list folders",
3157
- "browse routine groups",
3158
- "show plan folders"
3159
- ],
3160
- useCase: "Use to browse folder organization or discover folder IDs for routine creation.",
3161
- importantNotes: "Results are paginated; page starts at 1 and pageSize is limited to 10."
3162
- }),
3247
+ description: "Read-only. Lists routine folders and IDs; results are paginated.",
3163
3248
  inputSchema: getRoutineFoldersSchema,
3164
3249
  outputSchema: routineFoldersResponse.outputSchema,
3165
3250
  annotations: readOnlyAnnotations("Get Routine Folders"),
3166
3251
  kind: "read",
3167
3252
  responseContract: routineFoldersResponse,
3168
3253
  execute: async (runtime, args) => {
3169
- const { page, pageSize } = args;
3254
+ const { page, page_size } = args;
3170
3255
  try {
3171
3256
  const data = await runtime.getClient().getRoutineFolders({
3172
3257
  page,
3173
- pageSize
3258
+ pageSize: page_size
3174
3259
  });
3175
3260
  return {
3176
3261
  items: data?.routine_folders ?? [],
@@ -3191,32 +3276,23 @@ const folderToolDefinitions = [
3191
3276
  name: "get-routine-folder",
3192
3277
  feature: "folders",
3193
3278
  operation: "get",
3194
- description: describeTool({
3195
- summary: "Read-only. Retrieves one routine folder's metadata by ID.",
3196
- aliases: [
3197
- "show folder",
3198
- "folder details",
3199
- "routine folder metadata"
3200
- ],
3201
- useCase: "Use for one known folder; use get-routine-folders to browse or discover folder IDs.",
3202
- importantNotes: "Requires a folderId from get-routine-folders or a prior create response."
3203
- }),
3279
+ description: "Read-only. Gets one routine folder by folder_id. Use get-routine-folders to discover IDs.",
3204
3280
  inputSchema: getRoutineFolderSchema,
3205
3281
  outputSchema: routineFolderResponse.outputSchema,
3206
3282
  annotations: readOnlyAnnotations("Get Routine Folder"),
3207
3283
  kind: "read",
3208
3284
  responseContract: routineFolderResponse,
3209
3285
  execute: async (runtime, args) => {
3210
- const { folderId } = args;
3286
+ const { folder_id } = args;
3211
3287
  try {
3212
3288
  return {
3213
- routineFolder: await runtime.getClient().getRoutineFolder(folderId),
3214
- folderId
3289
+ routine_folder: await runtime.getClient().getRoutineFolder(folder_id),
3290
+ folder_id
3215
3291
  };
3216
3292
  } catch (error) {
3217
3293
  if (isExpectedReadNotFound(error)) return {
3218
- routineFolder: null,
3219
- folderId,
3294
+ routine_folder: null,
3295
+ folder_id,
3220
3296
  expected404Outcome: "not_found"
3221
3297
  };
3222
3298
  throw error;
@@ -3227,169 +3303,109 @@ const folderToolDefinitions = [
3227
3303
  name: "create-routine-folder",
3228
3304
  feature: "folders",
3229
3305
  operation: "create",
3230
- description: describeTool({
3231
- summary: "Writes to the Hevy account by creating a new routine folder.",
3232
- aliases: [
3233
- "add folder",
3234
- "organize routines",
3235
- "create plan group"
3236
- ],
3237
- useCase: "Use to create an organizational destination before assigning new routines to a folderId.",
3238
- importantNotes: "Requires a non-empty name. Retrying or reusing a name can create duplicate folders."
3239
- }),
3306
+ description: "Writes a routine folder. Retries or reused titles can create duplicates.",
3240
3307
  inputSchema: createRoutineFolderSchema,
3241
3308
  annotations: createAnnotations("Create Routine Folder"),
3242
3309
  kind: "write",
3243
3310
  responseContract: createRoutineFolderResponse,
3244
3311
  execute: async (runtime, args) => {
3245
- return runtime.getClient().createRoutineFolder(buildRoutineFolderRequest(args));
3312
+ return runtime.getClient().createRoutineFolder(args);
3246
3313
  }
3247
3314
  }
3248
3315
  ];
3249
- //#endregion
3250
- //#region ../core/src/tools/routines.ts
3251
- const getRoutinesSchema = paginationShape({
3252
- defaultPageSize: 5,
3253
- maxPageSize: 10
3254
- });
3255
- const getRoutinesDefinition = {
3256
- name: "get-routines",
3257
- feature: "routines",
3258
- operation: "list",
3259
- description: describeTool({
3260
- summary: "Read-only. Lists custom and default workout routines.",
3261
- aliases: [
3262
- "list routines",
3263
- "show workout plans",
3264
- "browse saved routines"
3265
- ],
3266
- useCase: "Use to browse routines or discover a routine ID; use get-routine for one known routine.",
3267
- importantNotes: "Results are paginated; page starts at 1 and pageSize is limited to 10."
3268
- }),
3269
- inputSchema: getRoutinesSchema,
3270
- kind: "read",
3271
- outputSchema: routinesResponse.outputSchema,
3272
- annotations: readOnlyAnnotations("Get Routines"),
3273
- responseContract: routinesResponse,
3274
- execute: async (runtime, { page, pageSize }) => {
3275
- try {
3276
- const data = await runtime.getClient().getRoutines({
3277
- page,
3278
- pageSize
3279
- });
3280
- return {
3281
- items: data?.routines ?? [],
3282
- page,
3283
- pageCount: data?.page_count
3284
- };
3285
- } catch (error) {
3286
- if (isExpectedListPageNotFound(error, page)) return {
3287
- items: [],
3288
- page,
3289
- expected404Outcome: "end_of_list"
3290
- };
3291
- throw error;
3316
+ const routineToolDefinitions = [
3317
+ {
3318
+ name: "get-routines",
3319
+ feature: "routines",
3320
+ operation: "list",
3321
+ description: "Read-only. Lists compact routine summaries. Use get-routine for exercises and sets; results are paginated.",
3322
+ inputSchema: paginationShape({
3323
+ defaultPageSize: 5,
3324
+ maxPageSize: 10
3325
+ }),
3326
+ kind: "read",
3327
+ outputSchema: routinesResponse.outputSchema,
3328
+ annotations: readOnlyAnnotations("Get Routines"),
3329
+ responseContract: routinesResponse,
3330
+ execute: async (runtime, { page, page_size }) => {
3331
+ try {
3332
+ const data = await runtime.getClient().getRoutines({
3333
+ page,
3334
+ pageSize: page_size
3335
+ });
3336
+ return {
3337
+ items: data?.routines ?? [],
3338
+ page,
3339
+ pageCount: data?.page_count
3340
+ };
3341
+ } catch (error) {
3342
+ if (isExpectedListPageNotFound(error, page)) return {
3343
+ items: [],
3344
+ page,
3345
+ expected404Outcome: "end_of_list"
3346
+ };
3347
+ throw error;
3348
+ }
3292
3349
  }
3293
- }
3294
- };
3295
- const getRoutineSchema = { routineId: nonEmptyId };
3296
- const getRoutineDefinition = {
3297
- name: "get-routine",
3298
- feature: "routines",
3299
- operation: "get",
3300
- description: describeTool({
3301
- summary: "Read-only. Retrieves one routine and its exercise configuration by ID.",
3302
- aliases: [
3303
- "show routine",
3304
- "fetch workout plan",
3305
- "routine details"
3306
- ],
3307
- useCase: "Use when the routineId is known; use get-routines to browse or discover IDs.",
3308
- importantNotes: "Requires a routineId from get-routines or a prior create response."
3309
- }),
3310
- inputSchema: getRoutineSchema,
3311
- kind: "read",
3312
- outputSchema: routineResponse.outputSchema,
3313
- annotations: readOnlyAnnotations("Get Routine"),
3314
- responseContract: routineResponse,
3315
- execute: async (runtime, { routineId }) => {
3316
- try {
3350
+ },
3351
+ {
3352
+ name: "get-routine",
3353
+ feature: "routines",
3354
+ operation: "get",
3355
+ description: "Read-only. Gets one routine with exercises and sets by routine_id. Use search-routines to discover IDs.",
3356
+ inputSchema: { routine_id: nonEmptyId },
3357
+ kind: "read",
3358
+ outputSchema: routineResponse.outputSchema,
3359
+ annotations: readOnlyAnnotations("Get Routine"),
3360
+ responseContract: routineResponse,
3361
+ execute: async (runtime, { routine_id }) => {
3362
+ try {
3363
+ return {
3364
+ routine: (await runtime.getClient().getRoutineById(String(routine_id)))?.routine,
3365
+ routine_id
3366
+ };
3367
+ } catch (error) {
3368
+ if (isExpectedReadNotFound(error)) return {
3369
+ routine: null,
3370
+ routine_id,
3371
+ expected404Outcome: "not_found"
3372
+ };
3373
+ throw error;
3374
+ }
3375
+ }
3376
+ },
3377
+ {
3378
+ name: "create-routine",
3379
+ feature: "routines",
3380
+ operation: "create",
3381
+ description: "Writes a reusable routine; use create-workout for completed sessions. Retries can create duplicates.",
3382
+ inputSchema: createRoutineInputShape,
3383
+ kind: "write",
3384
+ annotations: createAnnotations("Create Routine"),
3385
+ responseContract: createRoutineResponse,
3386
+ execute: async (runtime, args) => {
3387
+ const { payload, usesRepRanges } = buildRoutinePayload(args.routine, "create");
3317
3388
  return {
3318
- routine: (await runtime.getClient().getRoutineById(String(routineId)))?.routine,
3319
- routineId
3320
- };
3321
- } catch (error) {
3322
- if (isExpectedReadNotFound(error)) return {
3323
- routine: null,
3324
- routineId,
3325
- expected404Outcome: "not_found"
3389
+ routine: await runtime.getClient().createRoutine({ routine: payload }),
3390
+ usesRepRanges
3326
3391
  };
3327
- throw error;
3328
3392
  }
3329
- }
3330
- };
3331
- const routineExercisesSchema = z.preprocess(parseJsonArray, z.array(z.object(routineExerciseShape)));
3332
- const createRoutineSchema = routinePayloadShape;
3333
- const createRoutineDefinition = {
3334
- name: "create-routine",
3335
- feature: "routines",
3336
- operation: "create",
3337
- description: describeTool({
3338
- summary: "Writes to the Hevy account by creating a new workout routine.",
3339
- aliases: [
3340
- "add routine",
3341
- "build workout plan",
3342
- "save training template"
3343
- ],
3344
- useCase: "Use to create a reusable plan; use create-workout to log a completed session.",
3345
- importantNotes: "Requires exercise template IDs; folderId is optional. Retrying can create duplicates, and non-fixed rep ranges may not display in Hevy apps."
3346
- }),
3347
- inputSchema: createRoutineSchema,
3348
- kind: "write",
3349
- annotations: createAnnotations("Create Routine"),
3350
- responseContract: createRoutineResponse,
3351
- execute: async (runtime, args) => {
3352
- const { payload, usesRepRanges } = buildRoutinePayload(args, "create");
3353
- return {
3354
- routine: await runtime.getClient().createRoutine({ routine: payload }),
3355
- usesRepRanges
3356
- };
3357
- }
3358
- };
3359
- const updateRoutineSchema = {
3360
- routineId: nonEmptyId,
3361
- title: z.string().min(1),
3362
- notes: z.string().optional(),
3363
- exercises: routineExercisesSchema
3364
- };
3365
- const routineToolDefinitions = [
3366
- getRoutinesDefinition,
3367
- getRoutineDefinition,
3368
- createRoutineDefinition,
3393
+ },
3369
3394
  {
3370
3395
  name: "update-routine",
3371
3396
  feature: "routines",
3372
3397
  operation: "update",
3373
- description: describeTool({
3374
- summary: "Mutates the Hevy account by replacing an existing routine's content.",
3375
- aliases: [
3376
- "edit routine",
3377
- "revise workout plan",
3378
- "replace routine exercises"
3379
- ],
3380
- useCase: "Use to change a known routine; use create-routine for a separate new plan.",
3381
- importantNotes: "Requires routineId and the complete title and exercises payload; omitted exercises are removed. Non-fixed rep ranges may not display in Hevy apps."
3382
- }),
3383
- inputSchema: updateRoutineSchema,
3398
+ description: "Mutates a routine by replacing its title and exercises. Omitted exercises are removed.",
3399
+ inputSchema: updateRoutineInputShape,
3384
3400
  kind: "write",
3385
3401
  annotations: updateAnnotations("Update Routine"),
3386
3402
  responseContract: updateRoutineResponse,
3387
3403
  execute: async (runtime, args) => {
3388
- const { routineId } = args;
3389
- const { payload, usesRepRanges } = buildRoutinePayload(args, "update");
3404
+ const { routine_id } = args;
3405
+ const { payload, usesRepRanges } = buildRoutinePayload(args.routine, "update");
3390
3406
  return {
3391
- routine: await runtime.getClient().updateRoutine(routineId, { routine: payload }),
3392
- routineId,
3407
+ routine: await runtime.getClient().updateRoutine(routine_id, { routine: payload }),
3408
+ routine_id,
3393
3409
  usesRepRanges
3394
3410
  };
3395
3411
  }
@@ -3401,17 +3417,19 @@ const getExerciseTemplatesSchema = paginationShape({
3401
3417
  defaultPageSize: 5,
3402
3418
  maxPageSize: 100
3403
3419
  });
3404
- const getExerciseTemplateSchema = { exerciseTemplateId: nonEmptyId };
3420
+ const getExerciseTemplateSchema = { exercise_template_id: nonEmptyId };
3421
+ const isoDateTimeBase = z.iso.datetime({ offset: true });
3422
+ const isoDateTimeWithOffset = z.string().refine((value) => isoDateTimeBase.safeParse(value).success, "Must be an ISO 8601 timestamp with an offset").meta({ format: "date-time" });
3405
3423
  const getExerciseHistorySchema = {
3406
- exerciseTemplateId: nonEmptyId,
3407
- startDate: z.string().datetime({ offset: true }).describe("ISO 8601 start date for filtering history").optional(),
3408
- endDate: z.string().datetime({ offset: true }).describe("ISO 8601 end date for filtering history").optional()
3424
+ exercise_template_id: nonEmptyId,
3425
+ start_date: isoDateTimeWithOffset.optional(),
3426
+ end_date: isoDateTimeWithOffset.optional()
3409
3427
  };
3410
3428
  const createExerciseTemplateSchema = exerciseTemplateInputShape;
3411
3429
  const searchExerciseTemplatesSchema = {
3412
- query: z.string().min(1).describe("Case-insensitive substring to match against exercise template titles"),
3413
- primaryMuscleGroup: muscleGroupEnum.optional().describe("Optional filter to restrict results to a specific primary muscle group"),
3414
- refresh: z.boolean().optional().default(false).describe("Set to true to invalidate the catalog cache and re-fetch all templates from the API")
3430
+ query: z.string().min(1),
3431
+ primary_muscle_group: muscleGroupEnum.optional(),
3432
+ refresh: z.boolean().optional().default(false)
3415
3433
  };
3416
3434
  /** Ordered exercise-template tools for composition by the shared server. */
3417
3435
  const templateToolDefinitions = [
@@ -3419,27 +3437,18 @@ const templateToolDefinitions = [
3419
3437
  name: "get-exercise-templates",
3420
3438
  feature: "templates",
3421
3439
  operation: "list",
3422
- description: describeTool({
3423
- summary: "Read-only. Lists default and custom exercise templates with equipment and muscle metadata.",
3424
- aliases: [
3425
- "browse exercises",
3426
- "list exercise catalog",
3427
- "show movements"
3428
- ],
3429
- useCase: "Use for page-by-page catalog browsing; use search-exercise-templates for a name lookup across the full catalog.",
3430
- importantNotes: "Results are paginated; page starts at 1 and pageSize is limited to 100."
3431
- }),
3440
+ description: "Read-only. Pages through exercise templates. Use search-exercise-templates when a title is known.",
3432
3441
  inputSchema: getExerciseTemplatesSchema,
3433
3442
  outputSchema: exerciseTemplatesResponse.outputSchema,
3434
3443
  annotations: readOnlyAnnotations("Get Exercise Templates"),
3435
3444
  kind: "read",
3436
3445
  responseContract: exerciseTemplatesResponse,
3437
3446
  execute: async (runtime, args) => {
3438
- const { page, pageSize } = args;
3447
+ const { page, page_size } = args;
3439
3448
  try {
3440
3449
  const data = await runtime.getClient().getExerciseTemplates({
3441
3450
  page,
3442
- pageSize
3451
+ pageSize: page_size
3443
3452
  });
3444
3453
  return {
3445
3454
  items: data?.exercise_templates ?? [],
@@ -3460,32 +3469,23 @@ const templateToolDefinitions = [
3460
3469
  name: "get-exercise-template",
3461
3470
  feature: "templates",
3462
3471
  operation: "get",
3463
- description: describeTool({
3464
- summary: "Read-only. Retrieves complete metadata for one exercise template by ID.",
3465
- aliases: [
3466
- "show exercise details",
3467
- "fetch movement",
3468
- "exercise template info"
3469
- ],
3470
- useCase: "Use after locating an exact template; use search-exercise-templates when only a name is known.",
3471
- importantNotes: "Requires an exerciseTemplateId from a template list, search, routine, or workout."
3472
- }),
3472
+ description: "Read-only. Gets one exercise template by exercise_template_id. Use search-exercise-templates to discover IDs.",
3473
3473
  inputSchema: getExerciseTemplateSchema,
3474
3474
  outputSchema: exerciseTemplateResponse.outputSchema,
3475
3475
  annotations: readOnlyAnnotations("Get Exercise Template"),
3476
3476
  kind: "read",
3477
3477
  responseContract: exerciseTemplateResponse,
3478
3478
  execute: async (runtime, args) => {
3479
- const { exerciseTemplateId } = args;
3479
+ const { exercise_template_id } = args;
3480
3480
  try {
3481
3481
  return {
3482
- exerciseTemplate: await runtime.getClient().getExerciseTemplate(exerciseTemplateId),
3483
- exerciseTemplateId
3482
+ exercise_template: await runtime.getClient().getExerciseTemplate(exercise_template_id),
3483
+ exercise_template_id
3484
3484
  };
3485
3485
  } catch (error) {
3486
3486
  if (isExpectedReadNotFound(error)) return {
3487
- exerciseTemplate: null,
3488
- exerciseTemplateId,
3487
+ exercise_template: null,
3488
+ exercise_template_id,
3489
3489
  expected404Outcome: "not_found"
3490
3490
  };
3491
3491
  throw error;
@@ -3496,29 +3496,20 @@ const templateToolDefinitions = [
3496
3496
  name: "get-exercise-history",
3497
3497
  feature: "templates",
3498
3498
  operation: "get",
3499
- description: describeTool({
3500
- summary: "Read-only. Retrieves past performed sets for one exercise template.",
3501
- aliases: [
3502
- "exercise progress",
3503
- "past sets",
3504
- "movement history"
3505
- ],
3506
- useCase: "Use to analyze performance for one movement; use get-workouts for complete sessions.",
3507
- importantNotes: "Requires an exerciseTemplateId. Optional startDate and endDate must be ISO 8601 datetimes with an offset."
3508
- }),
3499
+ description: "Read-only. Returns performed sets for one exercise-template ID, optionally bounded by ISO 8601 timestamps.",
3509
3500
  inputSchema: getExerciseHistorySchema,
3510
3501
  outputSchema: exerciseHistoryResponse.outputSchema,
3511
3502
  annotations: readOnlyAnnotations("Get Exercise History"),
3512
3503
  kind: "read",
3513
3504
  responseContract: exerciseHistoryResponse,
3514
3505
  execute: async (runtime, args) => {
3515
- const { exerciseTemplateId, startDate, endDate } = args;
3506
+ const { exercise_template_id, start_date, end_date } = args;
3516
3507
  return {
3517
- history: (await runtime.getClient().getExerciseHistory(exerciseTemplateId, {
3518
- ...startDate ? { start_date: startDate } : {},
3519
- ...endDate ? { end_date: endDate } : {}
3508
+ exercise_history: (await runtime.getClient().getExerciseHistory(exercise_template_id, {
3509
+ ...start_date ? { start_date } : {},
3510
+ ...end_date ? { end_date } : {}
3520
3511
  }))?.exercise_history,
3521
- exerciseTemplateId
3512
+ exercise_template_id
3522
3513
  };
3523
3514
  }
3524
3515
  },
@@ -3526,38 +3517,20 @@ const templateToolDefinitions = [
3526
3517
  name: "create-exercise-template",
3527
3518
  feature: "templates",
3528
3519
  operation: "create",
3529
- description: describeTool({
3530
- summary: "Writes to the Hevy account by creating a custom exercise template.",
3531
- aliases: [
3532
- "add custom exercise",
3533
- "create movement",
3534
- "define exercise"
3535
- ],
3536
- useCase: "Use only when the needed movement is absent; search-exercise-templates should check existing templates first.",
3537
- importantNotes: "Requires title, exercise type, equipment category, and primary muscle group. Retrying or reusing a title can create duplicates."
3538
- }),
3520
+ description: "Writes a custom exercise template. Search first; retries or reused titles can create duplicates.",
3539
3521
  inputSchema: createExerciseTemplateSchema,
3540
3522
  annotations: createAnnotations("Create Exercise Template"),
3541
3523
  kind: "write",
3542
3524
  responseContract: createExerciseTemplateResponse,
3543
3525
  execute: async (runtime, args) => {
3544
- return runtime.getClient().createExerciseTemplate(buildExerciseTemplateRequest(args));
3526
+ return runtime.getClient().createExerciseTemplate(args);
3545
3527
  }
3546
3528
  },
3547
3529
  {
3548
3530
  name: "search-exercise-templates",
3549
3531
  feature: "templates",
3550
3532
  operation: "search",
3551
- description: describeTool({
3552
- summary: "Read-only for the Hevy account. Searches the full exercise template catalog by title substring.",
3553
- aliases: [
3554
- "find exercise",
3555
- "look up movement",
3556
- "search exercise IDs"
3557
- ],
3558
- useCase: "Use when a name or partial name is known, especially to discover IDs for workouts and routines; use get-exercise-templates for page browsing.",
3559
- importantNotes: "Matching is case-insensitive. The catalog is cached locally for 5 minutes; refresh:true re-fetches all pages and changes only local cache state."
3560
- }),
3533
+ description: "Read-only. Searches template titles case-insensitively and returns IDs. refresh reloads the five-minute catalog cache.",
3561
3534
  inputSchema: searchExerciseTemplatesSchema,
3562
3535
  outputSchema: searchExerciseTemplatesResponse.outputSchema,
3563
3536
  annotations: readOnlyAnnotations("Search Exercise Templates"),
@@ -3565,7 +3538,7 @@ const templateToolDefinitions = [
3565
3538
  responseContract: searchExerciseTemplatesResponse,
3566
3539
  execute: async (runtime, args) => {
3567
3540
  runtime.getClient();
3568
- const { query, primaryMuscleGroup, refresh } = args;
3541
+ const { query, primary_muscle_group, refresh } = args;
3569
3542
  const templates = await runtime.catalog.get({
3570
3543
  refresh,
3571
3544
  onRefreshed: (refreshedCatalog, reason) => {
@@ -3586,11 +3559,11 @@ const templateToolDefinitions = [
3586
3559
  });
3587
3560
  const queryLower = query.toLowerCase();
3588
3561
  let results = templates.filter((t) => (t.title ?? "").toLowerCase().includes(queryLower));
3589
- if (primaryMuscleGroup !== void 0) results = results.filter((t) => t.primary_muscle_group === primaryMuscleGroup);
3562
+ if (primary_muscle_group !== void 0) results = results.filter((t) => t.primary_muscle_group === primary_muscle_group);
3590
3563
  return {
3591
3564
  results,
3592
3565
  query,
3593
- primaryMuscleGroup
3566
+ primary_muscle_group
3594
3567
  };
3595
3568
  }
3596
3569
  }
@@ -3599,16 +3572,7 @@ const userToolDefinitions = [{
3599
3572
  name: "get-user-info",
3600
3573
  feature: "profile",
3601
3574
  operation: "get",
3602
- description: describeTool({
3603
- summary: "Read-only. Returns the authenticated account's user ID, display name, and public profile URL.",
3604
- aliases: [
3605
- "who am I",
3606
- "account info",
3607
- "verify Hevy user"
3608
- ],
3609
- useCase: "Use to confirm which Hevy account is connected before reading or writing account data.",
3610
- importantNotes: "Accepts no inputs and reports only the account associated with the configured credentials."
3611
- }),
3575
+ description: "Read-only. Returns the authenticated Hevy user ID, display name, and public profile URL.",
3612
3576
  inputSchema: {},
3613
3577
  kind: "read",
3614
3578
  outputSchema: userResponse.outputSchema,
@@ -3621,8 +3585,8 @@ const userToolDefinitions = [{
3621
3585
  //#endregion
3622
3586
  //#region ../core/src/tools/routine-discovery.ts
3623
3587
  const routineDiscoverySchema = {
3624
- query: z.string().min(1).optional().describe("Optional case-insensitive substring to match routine titles."),
3625
- limit: z.coerce.number().int().min(1).max(100).default(20).describe("Maximum compact routines to return (1-100).")
3588
+ query: z.string().min(1).optional(),
3589
+ limit: z.coerce.number().int().min(1).max(100).default(20)
3626
3590
  };
3627
3591
  async function discoverRoutines(runtime, { query, limit }) {
3628
3592
  const normalizedQuery = query?.toLocaleLowerCase();
@@ -3649,14 +3613,7 @@ async function discoverRoutines(runtime, { query, limit }) {
3649
3613
  page += 1;
3650
3614
  }
3651
3615
  return {
3652
- routines: routines.slice(0, limit).map((routine) => ({
3653
- ...routine.id ? { id: routine.id } : {},
3654
- ...routine.title ? { title: routine.title } : {},
3655
- folderId: routine.folder_id ?? null,
3656
- ...routine.updated_at ? { updatedAt: routine.updated_at } : {},
3657
- exerciseCount: routine.exercises?.length ?? 0,
3658
- setCount: routine.exercises?.reduce((total, exercise) => total + (exercise.sets?.length ?? 0), 0) ?? 0
3659
- })),
3616
+ routines: routines.slice(0, limit).map(summarizeRoutine),
3660
3617
  workflow: {
3661
3618
  name: "routine-discovery",
3662
3619
  pagination: { routines: pages },
@@ -3669,16 +3626,7 @@ const routineDiscoveryToolDefinitions = [{
3669
3626
  name: "search-routines",
3670
3627
  feature: "workflows",
3671
3628
  operation: "search",
3672
- description: describeTool({
3673
- summary: "Read-only. Discovers routines by title and returns compact metadata without full set payloads.",
3674
- aliases: [
3675
- "find routine",
3676
- "browse routine names",
3677
- "compact routine list"
3678
- ],
3679
- useCase: "Use to find a routine ID or shortlist plans before calling get-routine for full exercise details.",
3680
- importantNotes: "Search scans routine pages at pageSize 10 and returns only IDs, titles, folder metadata, and exercise/set counts."
3681
- }),
3629
+ description: "Read-only. Searches routine titles and returns compact IDs and counts. Use get-routine for full exercises and sets.",
3682
3630
  inputSchema: routineDiscoverySchema,
3683
3631
  outputSchema: compactRoutinesResponse.outputSchema,
3684
3632
  annotations: readOnlyAnnotations("Search Routines"),
@@ -3688,7 +3636,7 @@ const routineDiscoveryToolDefinitions = [{
3688
3636
  }];
3689
3637
  //#endregion
3690
3638
  //#region ../core/src/tools/workflows.ts
3691
- const trainingSummarySchema = { weeks: z.coerce.number().int().min(1).max(12).default(4).describe("Number of recent weeks to summarize (1-12).") };
3639
+ const trainingSummarySchema = { weeks: z.coerce.number().int().min(1).max(12).default(4) };
3692
3640
  const MILLISECONDS_PER_DAY = 1440 * 60 * 1e3;
3693
3641
  function parseUtcDate(value) {
3694
3642
  const normalized = value.length === 10 ? `${value}T00:00:00.000Z` : value;
@@ -3742,28 +3690,29 @@ function getPeriod(weeks) {
3742
3690
  };
3743
3691
  }
3744
3692
  function durationSeconds(workout) {
3745
- if (!workout.start_time || !workout.end_time) return null;
3693
+ if (!workout.start_time || !workout.end_time) return void 0;
3746
3694
  const duration = new Date(workout.end_time).getTime() - new Date(workout.start_time).getTime();
3747
- return Number.isFinite(duration) && duration >= 0 ? Math.floor(duration / 1e3) : null;
3695
+ return Number.isFinite(duration) && duration >= 0 ? Math.floor(duration / 1e3) : void 0;
3748
3696
  }
3749
3697
  function compactSession(workout) {
3750
3698
  const exercises = workout.exercises ?? [];
3699
+ const elapsedSeconds = durationSeconds(workout);
3751
3700
  return {
3752
3701
  ...workout.id ? { id: workout.id } : {},
3753
3702
  ...workout.title ? { title: workout.title } : {},
3754
- ...workout.start_time ? { startTime: workout.start_time } : {},
3755
- ...workout.end_time ? { endTime: workout.end_time } : {},
3756
- durationSeconds: durationSeconds(workout),
3757
- exerciseCount: exercises.length,
3758
- setCount: exercises.reduce((total, exercise) => total + (exercise.sets?.length ?? 0), 0)
3703
+ ...workout.start_time ? { start_time: workout.start_time } : {},
3704
+ ...workout.end_time ? { end_time: workout.end_time } : {},
3705
+ ...elapsedSeconds === void 0 ? {} : { duration_seconds: elapsedSeconds },
3706
+ exercise_count: exercises.length,
3707
+ set_count: exercises.reduce((total, exercise) => total + (exercise.sets?.length ?? 0), 0)
3759
3708
  };
3760
3709
  }
3761
3710
  function compactMeasurement(measurement) {
3762
3711
  return {
3763
3712
  date: measurement.date,
3764
- weightKg: measurement.weight_kg ?? null,
3765
- leanMassKg: measurement.lean_mass_kg ?? null,
3766
- fatPercent: measurement.fat_percent ?? null
3713
+ ...measurement.weight_kg == null ? {} : { weight_kg: measurement.weight_kg },
3714
+ ...measurement.lean_mass_kg == null ? {} : { lean_mass_kg: measurement.lean_mass_kg },
3715
+ ...measurement.fat_percent == null ? {} : { fat_percent: measurement.fat_percent }
3767
3716
  };
3768
3717
  }
3769
3718
  async function getTrainingSummary(runtime, weeks) {
@@ -3791,37 +3740,38 @@ async function getTrainingSummary(runtime, weeks) {
3791
3740
  }, pageSize, period.startDate, period.endDate, (measurement) => measurement.date)]);
3792
3741
  const workouts = workoutPages.items;
3793
3742
  const sessions = workouts.map(compactSession);
3794
- const uniqueExerciseTemplateIds = [...new Set(workouts.flatMap((workout) => (workout.exercises ?? []).map((exercise) => exercise.exercise_template_id).filter((id) => Boolean(id))))];
3743
+ const unique_exercise_template_ids = [...new Set(workouts.flatMap((workout) => (workout.exercises ?? []).map((exercise) => exercise.exercise_template_id).filter((id) => Boolean(id))))];
3795
3744
  const measurements = [...measurementPages.items].sort((a, b) => a.date.localeCompare(b.date));
3796
3745
  const earliestMeasurement = measurements[0];
3797
3746
  const latestMeasurement = measurements.at(-1);
3798
- const earliest = earliestMeasurement ? compactMeasurement(earliestMeasurement) : null;
3799
- const latest = latestMeasurement ? compactMeasurement(latestMeasurement) : null;
3800
- const weightChangeKg = latest?.weightKg !== null && latest?.weightKg !== void 0 && earliest?.weightKg !== null && earliest?.weightKg !== void 0 ? latest.weightKg - earliest.weightKg : null;
3747
+ const earliest = earliestMeasurement ? compactMeasurement(earliestMeasurement) : void 0;
3748
+ const latest = latestMeasurement ? compactMeasurement(latestMeasurement) : void 0;
3749
+ const weight_change_kg = latest?.weight_kg !== void 0 && earliest?.weight_kg !== void 0 ? latest.weight_kg - earliest.weight_kg : void 0;
3801
3750
  return {
3802
3751
  period: {
3803
- ...period,
3752
+ start_date: period.startDate,
3753
+ end_date: period.endDate,
3804
3754
  weeks
3805
3755
  },
3806
3756
  workouts: {
3807
3757
  count: workouts.length,
3808
- totalDurationSeconds: sessions.reduce((total, session) => total + (session.durationSeconds ?? 0), 0),
3809
- exerciseCount: sessions.reduce((total, session) => total + session.exerciseCount, 0),
3810
- setCount: sessions.reduce((total, session) => total + session.setCount, 0),
3811
- uniqueExerciseTemplateIds,
3758
+ total_duration_seconds: sessions.reduce((total, session) => total + (session.duration_seconds ?? 0), 0),
3759
+ exercise_count: sessions.reduce((total, session) => total + session.exercise_count, 0),
3760
+ set_count: sessions.reduce((total, session) => total + session.set_count, 0),
3761
+ unique_exercise_template_ids,
3812
3762
  sessions
3813
3763
  },
3814
- bodyMeasurements: {
3764
+ body_measurements: {
3815
3765
  count: measurements.length,
3816
- latest,
3817
- earliest,
3818
- weightChangeKg
3766
+ ...latest ? { latest } : {},
3767
+ ...earliest ? { earliest } : {},
3768
+ ...weight_change_kg === void 0 ? {} : { weight_change_kg }
3819
3769
  },
3820
3770
  workflow: {
3821
3771
  name: "training-summary",
3822
3772
  pagination: {
3823
3773
  workouts: workoutPages.pages,
3824
- bodyMeasurements: measurementPages.pages
3774
+ body_measurements: measurementPages.pages
3825
3775
  },
3826
3776
  cacheStatus: "not-used",
3827
3777
  itemsScanned: workoutPages.itemsScanned + measurementPages.itemsScanned
@@ -3832,16 +3782,7 @@ const workflowToolDefinitions = [{
3832
3782
  name: "get-training-summary",
3833
3783
  feature: "workflows",
3834
3784
  operation: "get",
3835
- description: describeTool({
3836
- summary: "Read-only. Summarizes recent workout activity and body-measurement trends in one call.",
3837
- aliases: [
3838
- "training progress",
3839
- "progress summary",
3840
- "recent training overview"
3841
- ],
3842
- useCase: "Use for a bounded progress review instead of separately counting and paging through workouts and body measurements.",
3843
- importantNotes: "The summary covers the most recent 1-12 weeks, returns compact session evidence, and reports the pages and items scanned."
3844
- }),
3785
+ description: "Read-only. Summarizes workouts and body-measurement trends for the last 1–12 weeks, including compact session and scan evidence.",
3845
3786
  inputSchema: trainingSummarySchema,
3846
3787
  outputSchema: trainingSummaryResponse.outputSchema,
3847
3788
  annotations: readOnlyAnnotations("Get Training Summary"),
@@ -3856,7 +3797,7 @@ const getWorkoutsSchema = paginationShape({
3856
3797
  maxPageSize: 10,
3857
3798
  integerPage: false
3858
3799
  });
3859
- const getWorkoutSchema = { workoutId: nonEmptyId };
3800
+ const getWorkoutSchema = { workout_id: nonEmptyId };
3860
3801
  const getWorkoutEventsSchema = {
3861
3802
  ...paginationShape({
3862
3803
  defaultPageSize: 5,
@@ -3864,11 +3805,9 @@ const getWorkoutEventsSchema = {
3864
3805
  }),
3865
3806
  since: z.string().default("1970-01-01T00:00:00Z")
3866
3807
  };
3867
- const createWorkoutSchema = workoutPayloadShape;
3868
- const updateWorkoutSchema = {
3869
- workoutId: nonEmptyId,
3870
- ...workoutPayloadShape
3871
- };
3808
+ const createWorkoutSchema = workoutInputShape;
3809
+ const updateWorkoutSchema = updateWorkoutInputShape;
3810
+ const replaceWorkoutExercisesSchema = replaceWorkoutExercisesInputShape;
3872
3811
  //#endregion
3873
3812
  //#region ../core/src/tools/register.ts
3874
3813
  const hevyToolDefinitions = [
@@ -3877,16 +3816,7 @@ const hevyToolDefinitions = [
3877
3816
  name: "get-workouts",
3878
3817
  feature: "workouts",
3879
3818
  operation: "list",
3880
- description: describeTool({
3881
- summary: "Read-only. Lists workouts from newest to oldest with exercise and timing details.",
3882
- aliases: [
3883
- "list workout history",
3884
- "show recent workouts",
3885
- "browse logs"
3886
- ],
3887
- useCase: "Use to browse or page through workout history; use get-workout when a workout ID is already known.",
3888
- importantNotes: "Results are paginated; page starts at 1 and pageSize is limited to 10."
3889
- }),
3819
+ description: "Read-only. Lists compact workout summaries newest first. Use get-workout for exercises and sets; results are paginated.",
3890
3820
  inputSchema: getWorkoutsSchema,
3891
3821
  outputSchema: workoutsResponse.outputSchema,
3892
3822
  annotations: readOnlyAnnotations("Get Workouts"),
@@ -3896,7 +3826,7 @@ const hevyToolDefinitions = [
3896
3826
  try {
3897
3827
  const data = await runtime.getClient().getWorkouts({
3898
3828
  page: args.page,
3899
- pageSize: args.pageSize
3829
+ pageSize: args.page_size
3900
3830
  });
3901
3831
  return {
3902
3832
  items: data?.workouts ?? [],
@@ -3917,16 +3847,7 @@ const hevyToolDefinitions = [
3917
3847
  name: "get-workout",
3918
3848
  feature: "workouts",
3919
3849
  operation: "get",
3920
- description: describeTool({
3921
- summary: "Read-only. Retrieves complete details for one workout by its ID.",
3922
- aliases: [
3923
- "show workout",
3924
- "fetch workout details",
3925
- "open workout log"
3926
- ],
3927
- useCase: "Use after get-workouts identifies the exact workout; do not use for browsing multiple workouts.",
3928
- importantNotes: "Requires a workoutId discovered from a workout list, event, or prior create response."
3929
- }),
3850
+ description: "Read-only. Gets one workout with exercises and sets by workout_id. Use get-workouts to discover IDs.",
3930
3851
  inputSchema: getWorkoutSchema,
3931
3852
  outputSchema: workoutResponse.outputSchema,
3932
3853
  annotations: readOnlyAnnotations("Get Workout"),
@@ -3935,13 +3856,13 @@ const hevyToolDefinitions = [
3935
3856
  execute: async (runtime, args) => {
3936
3857
  try {
3937
3858
  return {
3938
- workout: await runtime.getClient().getWorkout(args.workoutId),
3939
- workoutId: args.workoutId
3859
+ workout: await runtime.getClient().getWorkout(args.workout_id),
3860
+ workout_id: args.workout_id
3940
3861
  };
3941
3862
  } catch (error) {
3942
3863
  if (isExpectedReadNotFound(error)) return {
3943
3864
  workout: null,
3944
- workoutId: args.workoutId,
3865
+ workout_id: args.workout_id,
3945
3866
  expected404Outcome: "not_found"
3946
3867
  };
3947
3868
  throw error;
@@ -3952,16 +3873,7 @@ const hevyToolDefinitions = [
3952
3873
  name: "get-workout-count",
3953
3874
  feature: "workouts",
3954
3875
  operation: "count",
3955
- description: describeTool({
3956
- summary: "Read-only. Returns the total workout count for the account.",
3957
- aliases: [
3958
- "count workouts",
3959
- "how many workouts",
3960
- "workout total"
3961
- ],
3962
- useCase: "Use for totals, statistics, or estimating pages; use get-workouts for actual workout records.",
3963
- importantNotes: "Returns only a count and accepts no paging or date filters."
3964
- }),
3876
+ description: "Read-only. Returns the total workout count; it does not return records or accept date filters.",
3965
3877
  inputSchema: {},
3966
3878
  outputSchema: workoutCountResponse.outputSchema,
3967
3879
  annotations: readOnlyAnnotations("Get Workout Count"),
@@ -3975,16 +3887,7 @@ const hevyToolDefinitions = [
3975
3887
  name: "get-workout-events",
3976
3888
  feature: "workouts",
3977
3889
  operation: "sync",
3978
- description: describeTool({
3979
- summary: "Read-only. Lists workout update and delete events since a timestamp, newest first.",
3980
- aliases: [
3981
- "sync workout changes",
3982
- "workout change feed",
3983
- "deleted workouts"
3984
- ],
3985
- useCase: "Use to incrementally synchronize a local workout cache; use get-workouts for the current workout list.",
3986
- importantNotes: "since must be a timestamp string; events are paginated with pageSize at most 10, and the default since value reads from 1970."
3987
- }),
3890
+ description: "Read-only. Lists workout update and deletion events since a timestamp for incremental sync; results are paginated.",
3988
3891
  inputSchema: getWorkoutEventsSchema,
3989
3892
  outputSchema: workoutEventsResponse.outputSchema,
3990
3893
  annotations: readOnlyAnnotations("Get Workout Events"),
@@ -3994,7 +3897,7 @@ const hevyToolDefinitions = [
3994
3897
  try {
3995
3898
  const data = await runtime.getClient().getWorkoutEvents({
3996
3899
  page: args.page,
3997
- pageSize: args.pageSize,
3900
+ pageSize: args.page_size,
3998
3901
  since: args.since
3999
3902
  });
4000
3903
  return {
@@ -4018,46 +3921,48 @@ const hevyToolDefinitions = [
4018
3921
  name: "create-workout",
4019
3922
  feature: "workouts",
4020
3923
  operation: "create",
4021
- description: describeTool({
4022
- summary: "Writes to the Hevy account by creating a new workout.",
4023
- aliases: [
4024
- "log workout",
4025
- "add workout",
4026
- "record training session"
4027
- ],
4028
- useCase: "Use to add a completed workout; use update-workout only when modifying an existing workout ID.",
4029
- importantNotes: "Requires UTC startTime/endTime in YYYY-MM-DDTHH:mm:ssZ form and exercise template IDs. Retrying can create duplicates."
4030
- }),
3924
+ description: "Writes a completed workout. Requires exercise-template IDs and UTC times. Retries can create duplicates.",
4031
3925
  inputSchema: createWorkoutSchema,
4032
3926
  annotations: createAnnotations("Create Workout"),
4033
3927
  kind: "write",
4034
3928
  responseContract: createWorkoutResponse,
4035
3929
  execute: async (runtime, args) => {
4036
- return await runtime.getClient().createWorkout({ workout: buildWorkoutPayload(args) });
3930
+ return await runtime.getClient().createWorkout({ workout: args.workout });
4037
3931
  }
4038
3932
  },
4039
3933
  {
4040
3934
  name: "update-workout",
4041
3935
  feature: "workouts",
4042
3936
  operation: "update",
4043
- description: describeTool({
4044
- summary: "Mutates the Hevy account by replacing an existing workout.",
4045
- aliases: [
4046
- "edit workout",
4047
- "correct workout log",
4048
- "replace workout details"
4049
- ],
4050
- useCase: "Use to revise a known workout; use create-workout for a new training session.",
4051
- importantNotes: "Requires workoutId plus the complete title, times, privacy, exercises, and sets payload; omitted optional values may be cleared or defaulted."
4052
- }),
3937
+ description: "Mutates workout metadata by ID. Omitted fields and all exercises remain unchanged.",
4053
3938
  inputSchema: updateWorkoutSchema,
4054
3939
  annotations: updateAnnotations("Update Workout"),
4055
3940
  kind: "write",
4056
3941
  responseContract: updateWorkoutResponse,
4057
3942
  execute: async (runtime, args) => {
3943
+ const client = runtime.getClient();
3944
+ const payload = buildWorkoutUpdatePayload(await client.getWorkout(args.workout_id), args.workout);
3945
+ return {
3946
+ workout: await client.updateWorkout(args.workout_id, { workout: payload }),
3947
+ workout_id: args.workout_id
3948
+ };
3949
+ }
3950
+ },
3951
+ {
3952
+ name: "replace-workout-exercises",
3953
+ feature: "workouts",
3954
+ operation: "update",
3955
+ description: "Mutates a workout by replacing all exercises and sets. Workout metadata remains unchanged.",
3956
+ inputSchema: replaceWorkoutExercisesSchema,
3957
+ annotations: updateAnnotations("Replace Workout Exercises"),
3958
+ kind: "write",
3959
+ responseContract: updateWorkoutResponse,
3960
+ execute: async (runtime, args) => {
3961
+ const client = runtime.getClient();
3962
+ const payload = buildWorkoutUpdatePayload(await client.getWorkout(args.workout_id), {}, args.workout.exercises);
4058
3963
  return {
4059
- workout: await runtime.getClient().updateWorkout(args.workoutId, { workout: buildWorkoutPayload(args) }),
4060
- workoutId: args.workoutId
3964
+ workout: await client.updateWorkout(args.workout_id, { workout: payload }),
3965
+ workout_id: args.workout_id
4061
3966
  };
4062
3967
  }
4063
3968
  }
@@ -4136,55 +4041,59 @@ function withErrorHandling(fn, context, onError) {
4136
4041
  }
4137
4042
  //#endregion
4138
4043
  //#region ../core/src/tools/tool-runtime.ts
4139
- const STRUCTURAL_ARGUMENT_KEYS = [
4140
- "page",
4141
- "pageSize",
4142
- "since",
4143
- "workoutId",
4144
- "routineId",
4145
- "folderId",
4146
- "exerciseTemplateId",
4147
- "date",
4148
- "startDate",
4149
- "endDate",
4150
- "updatedSince",
4151
- "includeCustom",
4152
- "limit",
4153
- "offset",
4154
- "refresh",
4155
- "query",
4156
- "primaryMuscleGroup"
4157
- ];
4158
- const PRESENCE_ARGUMENT_KEYS = /* @__PURE__ */ new Set([
4159
- "since",
4160
- "workoutId",
4161
- "routineId",
4162
- "folderId",
4163
- "exerciseTemplateId",
4164
- "date",
4165
- "startDate",
4166
- "endDate",
4167
- "updatedSince",
4168
- "query",
4169
- "primaryMuscleGroup"
4170
- ]);
4171
- const NUMERIC_ARGUMENT_KEYS = /* @__PURE__ */ new Set([
4172
- "page",
4173
- "pageSize",
4174
- "limit",
4175
- "offset"
4176
- ]);
4177
- const BOOLEAN_ARGUMENT_KEYS = /* @__PURE__ */ new Set(["includeCustom", "refresh"]);
4044
+ const STRUCTURAL_ARGUMENT_KEYS = {
4045
+ page: true,
4046
+ page_size: true,
4047
+ since: true,
4048
+ workout_id: true,
4049
+ routine_id: true,
4050
+ folder_id: true,
4051
+ exercise_template_id: true,
4052
+ date: true,
4053
+ start_date: true,
4054
+ end_date: true,
4055
+ updated_since: true,
4056
+ include_custom: true,
4057
+ limit: true,
4058
+ offset: true,
4059
+ refresh: true,
4060
+ query: true,
4061
+ primary_muscle_group: true
4062
+ };
4063
+ const PRESENCE_ARGUMENT_KEYS = {
4064
+ since: true,
4065
+ workout_id: true,
4066
+ routine_id: true,
4067
+ folder_id: true,
4068
+ exercise_template_id: true,
4069
+ date: true,
4070
+ start_date: true,
4071
+ end_date: true,
4072
+ updated_since: true,
4073
+ query: true,
4074
+ primary_muscle_group: true
4075
+ };
4076
+ const NUMERIC_ARGUMENT_KEYS = {
4077
+ page: true,
4078
+ page_size: true,
4079
+ limit: true,
4080
+ offset: true
4081
+ };
4082
+ const BOOLEAN_ARGUMENT_KEYS = {
4083
+ include_custom: true,
4084
+ refresh: true
4085
+ };
4086
+ const structuralArgumentKeys = Object.keys(STRUCTURAL_ARGUMENT_KEYS);
4178
4087
  function createSafeInvocation(name, args, taxonomy) {
4179
- const argumentKeys = STRUCTURAL_ARGUMENT_KEYS.filter((key) => key in args);
4088
+ const argumentKeys = structuralArgumentKeys.filter((key) => key in args);
4180
4089
  const argumentPresence = {};
4181
4090
  const numericArgumentBuckets = {};
4182
4091
  const booleanArguments = {};
4183
4092
  for (const key of argumentKeys) {
4184
4093
  const value = args[key];
4185
- if (PRESENCE_ARGUMENT_KEYS.has(key) && value !== null && value !== void 0) argumentPresence[key] = true;
4186
- if (NUMERIC_ARGUMENT_KEYS.has(key) && typeof value === "number") numericArgumentBuckets[key] = bucketCount(value);
4187
- if (BOOLEAN_ARGUMENT_KEYS.has(key) && typeof value === "boolean") booleanArguments[key] = value;
4094
+ if (key in PRESENCE_ARGUMENT_KEYS && value !== null && value !== void 0) argumentPresence[key] = true;
4095
+ if (key in NUMERIC_ARGUMENT_KEYS && typeof value === "number") numericArgumentBuckets[key] = bucketCount(value);
4096
+ if (key in BOOLEAN_ARGUMENT_KEYS && typeof value === "boolean") booleanArguments[key] = value;
4188
4097
  }
4189
4098
  return {
4190
4099
  name,
@@ -4399,18 +4308,13 @@ function createCountingServer(server) {
4399
4308
  let count = 0;
4400
4309
  return {
4401
4310
  server: new Proxy(server, { get(target, property, receiver) {
4402
- if (property === "tool") return (...args) => {
4403
- const result = target.tool(...args);
4404
- count += 1;
4405
- return result;
4406
- };
4407
4311
  if (property === "registerTool") {
4408
- const registerTool = (name, config, callback) => {
4409
- const result = target.registerTool(name, config, callback);
4312
+ const registerTool = target.registerTool.bind(target);
4313
+ return (...args) => {
4314
+ const result = Reflect.apply(registerTool, target, args);
4410
4315
  count += 1;
4411
4316
  return result;
4412
4317
  };
4413
- return registerTool;
4414
4318
  }
4415
4319
  return Reflect.get(target, property, receiver);
4416
4320
  } }),
@@ -4812,7 +4716,7 @@ async function startStreamableHttpServer(options, apiKey, createMcpServer) {
4812
4716
  let session;
4813
4717
  let mcpServer;
4814
4718
  let connected = false;
4815
- const transport = new StreamableHTTPServerTransport({
4719
+ const transport = new NodeStreamableHTTPServerTransport({
4816
4720
  sessionIdGenerator: randomUUID,
4817
4721
  onsessioninitialized: (id) => {
4818
4722
  if (session) sessions.set(id, session);