hevy-mcp 1.17.2 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js DELETED
@@ -1,1458 +0,0 @@
1
- #!/usr/bin/env node
2
- // Generated with tsup
3
- // https://github.com/egoist/tsup
4
-
5
- // src/index.ts
6
- import dotenvx from "@dotenvx/dotenvx";
7
- import * as Sentry from "@sentry/node";
8
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
10
- import { z as z6 } from "zod";
11
-
12
- // src/tools/folders.ts
13
- import { z } from "zod";
14
-
15
- // src/utils/error-handler.ts
16
- function createErrorResponse(error, context) {
17
- const errorMessage = error instanceof Error ? error.message : String(error);
18
- const errorCode = error instanceof Error && "code" in error ? error.code : void 0;
19
- const errorType = determineErrorType(error, errorMessage);
20
- if (errorCode) {
21
- console.debug(`Error code: ${errorCode}`);
22
- }
23
- const contextPrefix = context ? `[${context}] ` : "";
24
- const formattedMessage = `${contextPrefix}Error: ${errorMessage}`;
25
- console.error(`${formattedMessage} (Type: ${errorType})`, error);
26
- return {
27
- content: [
28
- {
29
- type: "text",
30
- text: formattedMessage
31
- }
32
- ],
33
- isError: true
34
- };
35
- }
36
- function determineErrorType(error, message) {
37
- const messageLower = message.toLowerCase();
38
- const nameLower = error instanceof Error ? error.name.toLowerCase() : "";
39
- if (nameLower.includes("network") || messageLower.includes("network") || nameLower.includes("fetch") || messageLower.includes("fetch") || nameLower.includes("timeout") || messageLower.includes("timeout")) {
40
- return "NETWORK_ERROR" /* NETWORK_ERROR */;
41
- }
42
- if (nameLower.includes("validation") || messageLower.includes("validation") || messageLower.includes("invalid") || messageLower.includes("required")) {
43
- return "VALIDATION_ERROR" /* VALIDATION_ERROR */;
44
- }
45
- if (messageLower.includes("not found") || messageLower.includes("404") || messageLower.includes("does not exist")) {
46
- return "NOT_FOUND" /* NOT_FOUND */;
47
- }
48
- if (nameLower.includes("api") || messageLower.includes("api") || messageLower.includes("server error") || messageLower.includes("500")) {
49
- return "API_ERROR" /* API_ERROR */;
50
- }
51
- return "UNKNOWN_ERROR" /* UNKNOWN_ERROR */;
52
- }
53
- function withErrorHandling(fn, context) {
54
- return async (args) => {
55
- try {
56
- return await fn(args);
57
- } catch (error) {
58
- return createErrorResponse(error, context);
59
- }
60
- };
61
- }
62
-
63
- // src/utils/formatters.ts
64
- function formatWorkout(workout) {
65
- return {
66
- id: workout.id,
67
- title: workout.title,
68
- description: workout.description,
69
- startTime: workout.start_time,
70
- endTime: workout.end_time,
71
- createdAt: workout.created_at,
72
- updatedAt: workout.updated_at,
73
- duration: calculateDuration(workout.start_time, workout.end_time),
74
- exercises: workout.exercises?.map((exercise) => {
75
- return {
76
- index: exercise.index,
77
- name: exercise.title,
78
- exerciseTemplateId: exercise.exercise_template_id,
79
- notes: exercise.notes,
80
- supersetsId: exercise.supersets_id,
81
- sets: exercise.sets?.map((set) => ({
82
- index: set.index,
83
- type: set.type,
84
- weight: set.weight_kg,
85
- reps: set.reps,
86
- distance: set.distance_meters,
87
- duration: set.duration_seconds,
88
- rpe: set.rpe,
89
- customMetric: set.custom_metric
90
- }))
91
- };
92
- })
93
- };
94
- }
95
- function formatRoutine(routine) {
96
- return {
97
- id: routine.id,
98
- title: routine.title,
99
- folderId: routine.folder_id,
100
- createdAt: routine.created_at,
101
- updatedAt: routine.updated_at,
102
- exercises: routine.exercises?.map((exercise) => {
103
- return {
104
- name: exercise.title,
105
- index: exercise.index,
106
- exerciseTemplateId: exercise.exercise_template_id,
107
- notes: exercise.notes,
108
- supersetId: exercise.supersets_id,
109
- restSeconds: exercise.rest_seconds,
110
- sets: exercise.sets?.map((set) => ({
111
- index: set.index,
112
- type: set.type,
113
- weight: set.weight_kg,
114
- reps: set.reps,
115
- ...set.rep_range !== void 0 && { repRange: set.rep_range },
116
- distance: set.distance_meters,
117
- duration: set.duration_seconds,
118
- ...set.rpe !== void 0 && { rpe: set.rpe },
119
- customMetric: set.custom_metric
120
- }))
121
- };
122
- })
123
- };
124
- }
125
- function formatRoutineFolder(folder) {
126
- return {
127
- id: folder.id,
128
- title: folder.title,
129
- createdAt: folder.created_at,
130
- updatedAt: folder.updated_at
131
- };
132
- }
133
- function calculateDuration(startTime, endTime) {
134
- if (!startTime || !endTime) return "Unknown duration";
135
- try {
136
- const start = new Date(startTime);
137
- const end = new Date(endTime);
138
- if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) {
139
- return "Unknown duration";
140
- }
141
- const durationMs = end.getTime() - start.getTime();
142
- if (durationMs < 0) {
143
- return "Invalid duration (end time before start time)";
144
- }
145
- const hours = Math.floor(durationMs / (1e3 * 60 * 60));
146
- const minutes = Math.floor(durationMs % (1e3 * 60 * 60) / (1e3 * 60));
147
- const seconds = Math.floor(durationMs % (1e3 * 60) / 1e3);
148
- return `${hours}h ${minutes}m ${seconds}s`;
149
- } catch (error) {
150
- console.error("Error calculating duration:", error);
151
- return "Unknown duration";
152
- }
153
- }
154
- function formatExerciseTemplate(template) {
155
- return {
156
- id: template.id,
157
- title: template.title,
158
- type: template.type,
159
- primaryMuscleGroup: template.primary_muscle_group,
160
- secondaryMuscleGroups: template.secondary_muscle_groups,
161
- isCustom: template.is_custom
162
- };
163
- }
164
- function formatExerciseHistoryEntry(entry) {
165
- return {
166
- workoutId: entry.workout_id,
167
- workoutTitle: entry.workout_title,
168
- workoutStartTime: entry.workout_start_time,
169
- workoutEndTime: entry.workout_end_time,
170
- exerciseTemplateId: entry.exercise_template_id,
171
- weight: entry.weight_kg,
172
- reps: entry.reps,
173
- distance: entry.distance_meters,
174
- duration: entry.duration_seconds,
175
- rpe: entry.rpe,
176
- customMetric: entry.custom_metric,
177
- setType: entry.set_type
178
- };
179
- }
180
-
181
- // src/utils/response-formatter.ts
182
- function createJsonResponse(data, options = { pretty: true, indent: 2 }) {
183
- const jsonString = options.pretty ? JSON.stringify(data, null, options.indent) : JSON.stringify(data);
184
- return {
185
- content: [
186
- {
187
- type: "text",
188
- text: jsonString
189
- }
190
- ]
191
- };
192
- }
193
- function createEmptyResponse(message = "No data found") {
194
- return {
195
- content: [
196
- {
197
- type: "text",
198
- text: message
199
- }
200
- ]
201
- };
202
- }
203
-
204
- // src/tools/folders.ts
205
- function registerFolderTools(server, hevyClient) {
206
- const getRoutineFoldersSchema = {
207
- page: z.coerce.number().int().gte(1).default(1),
208
- pageSize: z.coerce.number().int().gte(1).lte(10).default(5)
209
- };
210
- server.tool(
211
- "get-routine-folders",
212
- "Get a paginated list of your routine folders, including both default and custom folders. Useful for organizing and browsing your workout routines.",
213
- getRoutineFoldersSchema,
214
- withErrorHandling(async (args) => {
215
- if (!hevyClient) {
216
- throw new Error(
217
- "API client not initialized. Please provide HEVY_API_KEY."
218
- );
219
- }
220
- const { page, pageSize } = args;
221
- const data = await hevyClient.getRoutineFolders({
222
- page,
223
- pageSize
224
- });
225
- const folders = data?.routine_folders?.map(
226
- (folder) => formatRoutineFolder(folder)
227
- ) || [];
228
- if (folders.length === 0) {
229
- return createEmptyResponse(
230
- "No routine folders found for the specified parameters"
231
- );
232
- }
233
- return createJsonResponse(folders);
234
- }, "get-routine-folders")
235
- );
236
- const getRoutineFolderSchema = {
237
- folderId: z.string().min(1)
238
- };
239
- server.tool(
240
- "get-routine-folder",
241
- "Get complete details of a specific routine folder by its ID, including name, creation date, and associated routines.",
242
- getRoutineFolderSchema,
243
- withErrorHandling(async (args) => {
244
- if (!hevyClient) {
245
- throw new Error(
246
- "API client not initialized. Please provide HEVY_API_KEY."
247
- );
248
- }
249
- const { folderId } = args;
250
- const data = await hevyClient.getRoutineFolder(folderId);
251
- if (!data) {
252
- return createEmptyResponse(
253
- `Routine folder with ID ${folderId} not found`
254
- );
255
- }
256
- const folder = formatRoutineFolder(data);
257
- return createJsonResponse(folder);
258
- }, "get-routine-folder")
259
- );
260
- const createRoutineFolderSchema = {
261
- name: z.string().min(1)
262
- };
263
- server.tool(
264
- "create-routine-folder",
265
- "Create a new routine folder in your Hevy account. Requires a name for the folder. Returns the full folder details including the new folder ID.",
266
- createRoutineFolderSchema,
267
- withErrorHandling(async (args) => {
268
- if (!hevyClient) {
269
- throw new Error(
270
- "API client not initialized. Please provide HEVY_API_KEY."
271
- );
272
- }
273
- const { name: name2 } = args;
274
- const data = await hevyClient.createRoutineFolder({
275
- routine_folder: {
276
- title: name2
277
- }
278
- });
279
- if (!data) {
280
- return createEmptyResponse(
281
- "Failed to create routine folder: Server returned no data"
282
- );
283
- }
284
- const folder = formatRoutineFolder(data);
285
- return createJsonResponse(folder, {
286
- pretty: true,
287
- indent: 2
288
- });
289
- }, "create-routine-folder")
290
- );
291
- }
292
-
293
- // src/tools/routines.ts
294
- import { z as z2 } from "zod";
295
- function registerRoutineTools(server, hevyClient) {
296
- const getRoutinesSchema = {
297
- page: z2.coerce.number().int().gte(1).default(1),
298
- pageSize: z2.coerce.number().int().gte(1).lte(10).default(5)
299
- };
300
- server.tool(
301
- "get-routines",
302
- "Get a paginated list of your workout routines, including custom and default routines. Useful for browsing or searching your available routines.",
303
- getRoutinesSchema,
304
- withErrorHandling(async (args) => {
305
- if (!hevyClient) {
306
- throw new Error(
307
- "API client not initialized. Please provide HEVY_API_KEY."
308
- );
309
- }
310
- const { page, pageSize } = args;
311
- const data = await hevyClient.getRoutines({
312
- page,
313
- pageSize
314
- });
315
- const routines = data?.routines?.map((routine) => formatRoutine(routine)) || [];
316
- if (routines.length === 0) {
317
- return createEmptyResponse(
318
- "No routines found for the specified parameters"
319
- );
320
- }
321
- return createJsonResponse(routines);
322
- }, "get-routines")
323
- );
324
- const getRoutineSchema = {
325
- routineId: z2.string().min(1)
326
- };
327
- server.tool(
328
- "get-routine",
329
- "Get a routine by its ID using the direct endpoint. Returns all details for the specified routine.",
330
- getRoutineSchema,
331
- withErrorHandling(async (args) => {
332
- if (!hevyClient) {
333
- throw new Error(
334
- "API client not initialized. Please provide HEVY_API_KEY."
335
- );
336
- }
337
- const { routineId } = args;
338
- const data = await hevyClient.getRoutineById(String(routineId));
339
- if (!data || !data.routine) {
340
- return createEmptyResponse(`Routine with ID ${routineId} not found`);
341
- }
342
- const routine = formatRoutine(data.routine);
343
- return createJsonResponse(routine);
344
- }, "get-routine")
345
- );
346
- const createRoutineSchema = {
347
- title: z2.string().min(1),
348
- folderId: z2.coerce.number().nullable().optional(),
349
- notes: z2.string().optional(),
350
- exercises: z2.array(
351
- z2.object({
352
- exerciseTemplateId: z2.string().min(1),
353
- supersetId: z2.coerce.number().nullable().optional(),
354
- restSeconds: z2.coerce.number().int().min(0).optional(),
355
- notes: z2.string().optional(),
356
- sets: z2.array(
357
- z2.object({
358
- type: z2.enum(["warmup", "normal", "failure", "dropset"]).default("normal"),
359
- weight: z2.coerce.number().optional(),
360
- weightKg: z2.coerce.number().optional(),
361
- reps: z2.coerce.number().int().optional(),
362
- distance: z2.coerce.number().int().optional(),
363
- distanceMeters: z2.coerce.number().int().optional(),
364
- duration: z2.coerce.number().int().optional(),
365
- durationSeconds: z2.coerce.number().int().optional(),
366
- customMetric: z2.coerce.number().optional(),
367
- repRange: z2.object({
368
- start: z2.coerce.number().int().optional(),
369
- end: z2.coerce.number().int().optional()
370
- }).optional()
371
- })
372
- )
373
- })
374
- )
375
- };
376
- server.tool(
377
- "create-routine",
378
- "Create a new workout routine in your Hevy account. Requires a title and at least one exercise with sets. Optionally assign to a folder. Returns the full routine details including the new routine ID.",
379
- createRoutineSchema,
380
- withErrorHandling(async (args) => {
381
- if (!hevyClient) {
382
- throw new Error(
383
- "API client not initialized. Please provide HEVY_API_KEY."
384
- );
385
- }
386
- const { title, folderId, notes, exercises } = args;
387
- const data = await hevyClient.createRoutine({
388
- routine: {
389
- title,
390
- folder_id: folderId ?? null,
391
- notes: notes ?? "",
392
- exercises: exercises.map(
393
- (exercise) => ({
394
- exercise_template_id: exercise.exerciseTemplateId,
395
- superset_id: exercise.supersetId ?? null,
396
- rest_seconds: exercise.restSeconds ?? null,
397
- notes: exercise.notes ?? null,
398
- sets: exercise.sets.map(
399
- (set) => ({
400
- type: set.type,
401
- weight_kg: set.weight ?? set.weightKg ?? null,
402
- reps: set.reps ?? null,
403
- distance_meters: set.distance ?? set.distanceMeters ?? null,
404
- duration_seconds: set.duration ?? set.durationSeconds ?? null,
405
- custom_metric: set.customMetric ?? null,
406
- rep_range: set.repRange ? {
407
- start: set.repRange.start ?? null,
408
- end: set.repRange.end ?? null
409
- } : null
410
- })
411
- )
412
- })
413
- )
414
- }
415
- });
416
- if (!data) {
417
- return createEmptyResponse(
418
- "Failed to create routine: Server returned no data"
419
- );
420
- }
421
- const routine = formatRoutine(data);
422
- return createJsonResponse(routine, {
423
- pretty: true,
424
- indent: 2
425
- });
426
- }, "create-routine")
427
- );
428
- const updateRoutineSchema = {
429
- routineId: z2.string().min(1),
430
- title: z2.string().min(1),
431
- notes: z2.string().optional(),
432
- exercises: z2.array(
433
- z2.object({
434
- exerciseTemplateId: z2.string().min(1),
435
- supersetId: z2.coerce.number().nullable().optional(),
436
- restSeconds: z2.coerce.number().int().min(0).optional(),
437
- notes: z2.string().optional(),
438
- sets: z2.array(
439
- z2.object({
440
- type: z2.enum(["warmup", "normal", "failure", "dropset"]).default("normal"),
441
- weight: z2.coerce.number().optional(),
442
- weightKg: z2.coerce.number().optional(),
443
- reps: z2.coerce.number().int().optional(),
444
- distance: z2.coerce.number().int().optional(),
445
- distanceMeters: z2.coerce.number().int().optional(),
446
- duration: z2.coerce.number().int().optional(),
447
- durationSeconds: z2.coerce.number().int().optional(),
448
- customMetric: z2.coerce.number().optional(),
449
- repRange: z2.object({
450
- start: z2.coerce.number().int().optional(),
451
- end: z2.coerce.number().int().optional()
452
- }).optional()
453
- })
454
- )
455
- })
456
- )
457
- };
458
- server.tool(
459
- "update-routine",
460
- "Update an existing routine by ID. You can modify the title, notes, and exercise configurations. Returns the updated routine with all changes applied.",
461
- updateRoutineSchema,
462
- withErrorHandling(async (args) => {
463
- if (!hevyClient) {
464
- throw new Error(
465
- "API client not initialized. Please provide HEVY_API_KEY."
466
- );
467
- }
468
- const { routineId, title, notes, exercises } = args;
469
- const data = await hevyClient.updateRoutine(routineId, {
470
- routine: {
471
- title,
472
- notes: notes ?? null,
473
- exercises: exercises.map(
474
- (exercise) => ({
475
- exercise_template_id: exercise.exerciseTemplateId,
476
- superset_id: exercise.supersetId ?? null,
477
- rest_seconds: exercise.restSeconds ?? null,
478
- notes: exercise.notes ?? null,
479
- sets: exercise.sets.map(
480
- (set) => ({
481
- type: set.type,
482
- weight_kg: set.weight ?? set.weightKg ?? null,
483
- reps: set.reps ?? null,
484
- distance_meters: set.distance ?? set.distanceMeters ?? null,
485
- duration_seconds: set.duration ?? set.durationSeconds ?? null,
486
- custom_metric: set.customMetric ?? null,
487
- rep_range: set.repRange ? {
488
- start: set.repRange.start ?? null,
489
- end: set.repRange.end ?? null
490
- } : null
491
- })
492
- )
493
- })
494
- )
495
- }
496
- });
497
- if (!data) {
498
- return createEmptyResponse(
499
- `Failed to update routine with ID ${routineId}`
500
- );
501
- }
502
- const routine = formatRoutine(data);
503
- return createJsonResponse(routine, {
504
- pretty: true,
505
- indent: 2
506
- });
507
- }, "update-routine")
508
- );
509
- }
510
-
511
- // src/tools/templates.ts
512
- import { z as z3 } from "zod";
513
- function registerTemplateTools(server, hevyClient) {
514
- const getExerciseTemplatesSchema = {
515
- page: z3.coerce.number().int().gte(1).default(1),
516
- pageSize: z3.coerce.number().int().gte(1).lte(100).default(5)
517
- };
518
- server.tool(
519
- "get-exercise-templates",
520
- "Get a paginated list of exercise templates (default and custom) with details like name, category, equipment, and muscle groups. Useful for browsing or searching available exercises.",
521
- getExerciseTemplatesSchema,
522
- withErrorHandling(async (args) => {
523
- if (!hevyClient) {
524
- throw new Error(
525
- "API client not initialized. Please provide HEVY_API_KEY."
526
- );
527
- }
528
- const { page, pageSize } = args;
529
- const data = await hevyClient.getExerciseTemplates({
530
- page,
531
- pageSize
532
- });
533
- const templates = data?.exercise_templates?.map(
534
- (template) => formatExerciseTemplate(template)
535
- ) || [];
536
- if (templates.length === 0) {
537
- return createEmptyResponse(
538
- "No exercise templates found for the specified parameters"
539
- );
540
- }
541
- return createJsonResponse(templates);
542
- }, "get-exercise-templates")
543
- );
544
- const getExerciseTemplateSchema = {
545
- exerciseTemplateId: z3.string().min(1)
546
- };
547
- server.tool(
548
- "get-exercise-template",
549
- "Get complete details of a specific exercise template by its ID, including name, category, equipment, muscle groups, and notes.",
550
- getExerciseTemplateSchema,
551
- withErrorHandling(async (args) => {
552
- if (!hevyClient) {
553
- throw new Error(
554
- "API client not initialized. Please provide HEVY_API_KEY."
555
- );
556
- }
557
- const { exerciseTemplateId } = args;
558
- const data = await hevyClient.getExerciseTemplate(exerciseTemplateId);
559
- if (!data) {
560
- return createEmptyResponse(
561
- `Exercise template with ID ${exerciseTemplateId} not found`
562
- );
563
- }
564
- const template = formatExerciseTemplate(data);
565
- return createJsonResponse(template);
566
- }, "get-exercise-template")
567
- );
568
- const getExerciseHistorySchema = {
569
- exerciseTemplateId: z3.string().min(1),
570
- startDate: z3.string().datetime({ offset: true }).describe("ISO 8601 start date for filtering history").optional(),
571
- endDate: z3.string().datetime({ offset: true }).describe("ISO 8601 end date for filtering history").optional()
572
- };
573
- server.tool(
574
- "get-exercise-history",
575
- "Get past sets for a specific exercise template, optionally filtered by start and end dates.",
576
- getExerciseHistorySchema,
577
- withErrorHandling(async (args) => {
578
- if (!hevyClient) {
579
- throw new Error(
580
- "API client not initialized. Please provide HEVY_API_KEY."
581
- );
582
- }
583
- const { exerciseTemplateId, startDate, endDate } = args;
584
- const data = await hevyClient.getExerciseHistory(exerciseTemplateId, {
585
- ...startDate ? { start_date: startDate } : {},
586
- ...endDate ? { end_date: endDate } : {}
587
- });
588
- const history = data?.exercise_history?.map(
589
- (entry) => formatExerciseHistoryEntry(entry)
590
- ) || [];
591
- if (history.length === 0) {
592
- return createEmptyResponse(
593
- `No exercise history found for template ${exerciseTemplateId}`
594
- );
595
- }
596
- return createJsonResponse(history);
597
- }, "get-exercise-history")
598
- );
599
- const createExerciseTemplateSchema = {
600
- title: z3.string().min(1),
601
- exerciseType: z3.enum([
602
- "weight_reps",
603
- "reps_only",
604
- "bodyweight_reps",
605
- "bodyweight_assisted_reps",
606
- "duration",
607
- "weight_duration",
608
- "distance_duration",
609
- "short_distance_weight"
610
- ]),
611
- equipmentCategory: z3.enum([
612
- "none",
613
- "barbell",
614
- "dumbbell",
615
- "kettlebell",
616
- "machine",
617
- "plate",
618
- "resistance_band",
619
- "suspension",
620
- "other"
621
- ]),
622
- muscleGroup: z3.enum([
623
- "abdominals",
624
- "shoulders",
625
- "biceps",
626
- "triceps",
627
- "forearms",
628
- "quadriceps",
629
- "hamstrings",
630
- "calves",
631
- "glutes",
632
- "abductors",
633
- "adductors",
634
- "lats",
635
- "upper_back",
636
- "traps",
637
- "lower_back",
638
- "chest",
639
- "cardio",
640
- "neck",
641
- "full_body",
642
- "other"
643
- ]),
644
- otherMuscles: z3.array(
645
- z3.enum([
646
- "abdominals",
647
- "shoulders",
648
- "biceps",
649
- "triceps",
650
- "forearms",
651
- "quadriceps",
652
- "hamstrings",
653
- "calves",
654
- "glutes",
655
- "abductors",
656
- "adductors",
657
- "lats",
658
- "upper_back",
659
- "traps",
660
- "lower_back",
661
- "chest",
662
- "cardio",
663
- "neck",
664
- "full_body",
665
- "other"
666
- ])
667
- ).default([])
668
- };
669
- server.tool(
670
- "create-exercise-template",
671
- "Create a custom exercise template with title, type, equipment, and muscle groups.",
672
- createExerciseTemplateSchema,
673
- withErrorHandling(async (args) => {
674
- if (!hevyClient) {
675
- throw new Error(
676
- "API client not initialized. Please provide HEVY_API_KEY."
677
- );
678
- }
679
- const {
680
- title,
681
- exerciseType,
682
- equipmentCategory,
683
- muscleGroup,
684
- otherMuscles
685
- } = args;
686
- const response = await hevyClient.createExerciseTemplate({
687
- exercise: {
688
- title,
689
- exercise_type: exerciseType,
690
- equipment_category: equipmentCategory,
691
- muscle_group: muscleGroup,
692
- other_muscles: otherMuscles
693
- }
694
- });
695
- return createJsonResponse({
696
- id: response?.id,
697
- message: "Exercise template created successfully"
698
- });
699
- }, "create-exercise-template")
700
- );
701
- }
702
-
703
- // src/tools/webhooks.ts
704
- import { z as z4 } from "zod";
705
- var webhookUrlSchema = z4.string().url().refine(
706
- (url) => {
707
- try {
708
- const parsed = new URL(url);
709
- return parsed.protocol === "https:" || parsed.protocol === "http:";
710
- } catch {
711
- return false;
712
- }
713
- },
714
- {
715
- message: "Webhook URL must be a valid HTTP or HTTPS URL"
716
- }
717
- ).refine(
718
- (url) => {
719
- try {
720
- const parsed = new URL(url);
721
- return parsed.hostname !== "localhost" && !parsed.hostname.startsWith("127.");
722
- } catch {
723
- return false;
724
- }
725
- },
726
- {
727
- message: "Webhook URL cannot be localhost or loopback address"
728
- }
729
- );
730
- function registerWebhookTools(server, hevyClient) {
731
- const getWebhookSubscriptionSchema = {};
732
- server.tool(
733
- "get-webhook-subscription",
734
- "Get the current webhook subscription for this account. Returns the webhook URL and auth token if a subscription exists.",
735
- getWebhookSubscriptionSchema,
736
- withErrorHandling(async (_args) => {
737
- if (!hevyClient) {
738
- throw new Error(
739
- "API client not initialized. Please provide HEVY_API_KEY."
740
- );
741
- }
742
- if (!hevyClient.getWebhookSubscription) {
743
- throw new Error(
744
- "Webhook subscription API not available. Please regenerate the client from the updated OpenAPI spec."
745
- );
746
- }
747
- const data = await hevyClient.getWebhookSubscription();
748
- if (!data) {
749
- return createEmptyResponse(
750
- "No webhook subscription found for this account"
751
- );
752
- }
753
- return createJsonResponse(data);
754
- }, "get-webhook-subscription")
755
- );
756
- const createWebhookSubscriptionSchema = {
757
- url: webhookUrlSchema.describe(
758
- "The webhook URL that will receive POST requests when workouts are created"
759
- ),
760
- authToken: z4.string().optional().describe(
761
- "Optional auth token that will be sent as Authorization header in webhook requests"
762
- )
763
- };
764
- server.tool(
765
- "create-webhook-subscription",
766
- "Create a new webhook subscription for this account. The webhook will receive POST requests when workouts are created. Your endpoint must respond with 200 OK within 5 seconds.",
767
- createWebhookSubscriptionSchema,
768
- withErrorHandling(async (args) => {
769
- if (!hevyClient) {
770
- throw new Error(
771
- "API client not initialized. Please provide HEVY_API_KEY."
772
- );
773
- }
774
- const { url, authToken } = args;
775
- if (!hevyClient.createWebhookSubscription) {
776
- throw new Error(
777
- "Webhook subscription API not available. Please regenerate the client from the updated OpenAPI spec."
778
- );
779
- }
780
- const data = await hevyClient.createWebhookSubscription({
781
- webhook: {
782
- url,
783
- authToken: authToken || null
784
- }
785
- });
786
- if (!data) {
787
- return createEmptyResponse(
788
- "Failed to create webhook subscription - please check your URL and try again"
789
- );
790
- }
791
- return createJsonResponse(data);
792
- }, "create-webhook-subscription")
793
- );
794
- const deleteWebhookSubscriptionSchema = {};
795
- server.tool(
796
- "delete-webhook-subscription",
797
- "Delete the current webhook subscription for this account. This will stop all webhook notifications.",
798
- deleteWebhookSubscriptionSchema,
799
- withErrorHandling(async (_args) => {
800
- if (!hevyClient) {
801
- throw new Error(
802
- "API client not initialized. Please provide HEVY_API_KEY."
803
- );
804
- }
805
- if (!hevyClient.deleteWebhookSubscription) {
806
- throw new Error(
807
- "Webhook subscription API not available. Please regenerate the client from the updated OpenAPI spec."
808
- );
809
- }
810
- const data = await hevyClient.deleteWebhookSubscription();
811
- if (!data) {
812
- return createEmptyResponse(
813
- "Failed to delete webhook subscription - no subscription may exist or there was a server error"
814
- );
815
- }
816
- return createJsonResponse(data);
817
- }, "delete-webhook-subscription")
818
- );
819
- }
820
-
821
- // src/tools/workouts.ts
822
- import { z as z5 } from "zod";
823
- function registerWorkoutTools(server, hevyClient) {
824
- const getWorkoutsSchema = {
825
- page: z5.coerce.number().gte(1).default(1),
826
- pageSize: z5.coerce.number().int().gte(1).lte(10).default(5)
827
- };
828
- server.tool(
829
- "get-workouts",
830
- "Get a paginated list of workouts. Returns workout details including title, description, start/end times, and exercises performed. Results are ordered from newest to oldest.",
831
- getWorkoutsSchema,
832
- withErrorHandling(async (args) => {
833
- if (!hevyClient) {
834
- throw new Error(
835
- "API client not initialized. Please provide HEVY_API_KEY."
836
- );
837
- }
838
- const { page, pageSize } = args;
839
- const data = await hevyClient.getWorkouts({
840
- page,
841
- pageSize
842
- });
843
- const workouts = data?.workouts?.map((workout) => formatWorkout(workout)) || [];
844
- if (workouts.length === 0) {
845
- return createEmptyResponse(
846
- "No workouts found for the specified parameters"
847
- );
848
- }
849
- return createJsonResponse(workouts);
850
- }, "get-workouts")
851
- );
852
- const getWorkoutSchema = {
853
- workoutId: z5.string().min(1)
854
- };
855
- server.tool(
856
- "get-workout",
857
- "Get complete details of a specific workout by ID. Returns all workout information including title, description, start/end times, and detailed exercise data.",
858
- getWorkoutSchema,
859
- withErrorHandling(async (args) => {
860
- if (!hevyClient) {
861
- throw new Error(
862
- "API client not initialized. Please provide HEVY_API_KEY."
863
- );
864
- }
865
- const { workoutId } = args;
866
- const data = await hevyClient.getWorkout(workoutId);
867
- if (!data) {
868
- return createEmptyResponse(`Workout with ID ${workoutId} not found`);
869
- }
870
- const workout = formatWorkout(data);
871
- return createJsonResponse(workout);
872
- }, "get-workout")
873
- );
874
- server.tool(
875
- "get-workout-count",
876
- "Get the total number of workouts on the account. Useful for pagination or statistics.",
877
- {},
878
- withErrorHandling(async () => {
879
- if (!hevyClient) {
880
- throw new Error(
881
- "API client not initialized. Please provide HEVY_API_KEY."
882
- );
883
- }
884
- const data = await hevyClient.getWorkoutCount();
885
- const count = data ? data.workoutCount || 0 : 0;
886
- return createJsonResponse({ count });
887
- }, "get-workout-count")
888
- );
889
- const getWorkoutEventsSchema = {
890
- page: z5.coerce.number().int().gte(1).default(1),
891
- pageSize: z5.coerce.number().int().gte(1).lte(10).default(5),
892
- since: z5.string().default("1970-01-01T00:00:00Z")
893
- };
894
- server.tool(
895
- "get-workout-events",
896
- "Retrieve a paged list of workout events (updates or deletes) since a given date. Events are ordered from newest to oldest. The intention is to allow clients to keep their local cache of workouts up to date without having to fetch the entire list of workouts.",
897
- getWorkoutEventsSchema,
898
- withErrorHandling(async (args) => {
899
- if (!hevyClient) {
900
- throw new Error(
901
- "API client not initialized. Please provide HEVY_API_KEY."
902
- );
903
- }
904
- const { page, pageSize, since } = args;
905
- const data = await hevyClient.getWorkoutEvents({
906
- page,
907
- pageSize,
908
- since
909
- });
910
- const events = data?.events || [];
911
- if (events.length === 0) {
912
- return createEmptyResponse(
913
- `No workout events found for the specified parameters since ${since}`
914
- );
915
- }
916
- return createJsonResponse(events);
917
- }, "get-workout-events")
918
- );
919
- const createWorkoutSchema = {
920
- title: z5.string().min(1),
921
- description: z5.string().optional().nullable(),
922
- startTime: z5.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/),
923
- endTime: z5.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/),
924
- routineId: z5.string().optional().nullable(),
925
- isPrivate: z5.boolean().default(false),
926
- exercises: z5.array(
927
- z5.object({
928
- exerciseTemplateId: z5.string().min(1),
929
- supersetId: z5.coerce.number().nullable().optional(),
930
- notes: z5.string().optional().nullable(),
931
- sets: z5.array(
932
- z5.object({
933
- type: z5.enum(["warmup", "normal", "failure", "dropset"]).default("normal"),
934
- weight: z5.coerce.number().optional().nullable(),
935
- weightKg: z5.coerce.number().optional().nullable(),
936
- reps: z5.coerce.number().int().optional().nullable(),
937
- distance: z5.coerce.number().int().optional().nullable(),
938
- distanceMeters: z5.coerce.number().int().optional().nullable(),
939
- duration: z5.coerce.number().int().optional().nullable(),
940
- durationSeconds: z5.coerce.number().int().optional().nullable(),
941
- rpe: z5.coerce.number().optional().nullable(),
942
- customMetric: z5.coerce.number().optional().nullable()
943
- })
944
- )
945
- })
946
- )
947
- };
948
- server.tool(
949
- "create-workout",
950
- "Create a new workout in your Hevy account. Requires title, start/end times, and at least one exercise with sets. Returns the complete workout details upon successful creation including the newly assigned workout ID.",
951
- createWorkoutSchema,
952
- withErrorHandling(async (args) => {
953
- if (!hevyClient) {
954
- throw new Error(
955
- "API client not initialized. Please provide HEVY_API_KEY."
956
- );
957
- }
958
- const { title, description, startTime, endTime, isPrivate, exercises } = args;
959
- const workoutPayload = {
960
- title,
961
- description: description || null,
962
- start_time: startTime,
963
- end_time: endTime,
964
- routine_id: args.routineId ?? null,
965
- is_private: isPrivate,
966
- exercises: exercises.map(
967
- (exercise) => ({
968
- exercise_template_id: exercise.exerciseTemplateId,
969
- superset_id: exercise.supersetId ?? null,
970
- notes: exercise.notes ?? null,
971
- sets: exercise.sets.map((set) => ({
972
- type: set.type,
973
- weight_kg: set.weight ?? set.weightKg ?? null,
974
- reps: set.reps ?? null,
975
- distance_meters: set.distance ?? set.distanceMeters ?? null,
976
- duration_seconds: set.duration ?? set.durationSeconds ?? null,
977
- rpe: set.rpe ?? null,
978
- custom_metric: set.customMetric ?? null
979
- }))
980
- })
981
- )
982
- };
983
- const requestBody = { workout: workoutPayload };
984
- const data = await hevyClient.createWorkout(requestBody);
985
- if (!data) {
986
- return createEmptyResponse(
987
- "Failed to create workout: Server returned no data"
988
- );
989
- }
990
- const workout = formatWorkout(data);
991
- return createJsonResponse(workout, {
992
- pretty: true,
993
- indent: 2
994
- });
995
- }, "create-workout")
996
- );
997
- const updateWorkoutSchema = {
998
- workoutId: z5.string().min(1),
999
- title: z5.string().min(1),
1000
- description: z5.string().optional().nullable(),
1001
- startTime: z5.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/),
1002
- endTime: z5.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/),
1003
- routineId: z5.string().optional().nullable(),
1004
- isPrivate: z5.boolean().default(false),
1005
- exercises: z5.array(
1006
- z5.object({
1007
- exerciseTemplateId: z5.string().min(1),
1008
- supersetId: z5.coerce.number().nullable().optional(),
1009
- notes: z5.string().optional().nullable(),
1010
- sets: z5.array(
1011
- z5.object({
1012
- type: z5.enum(["warmup", "normal", "failure", "dropset"]).default("normal"),
1013
- weight: z5.coerce.number().optional().nullable(),
1014
- weightKg: z5.coerce.number().optional().nullable(),
1015
- reps: z5.coerce.number().int().optional().nullable(),
1016
- distance: z5.coerce.number().int().optional().nullable(),
1017
- distanceMeters: z5.coerce.number().int().optional().nullable(),
1018
- duration: z5.coerce.number().int().optional().nullable(),
1019
- durationSeconds: z5.coerce.number().int().optional().nullable(),
1020
- rpe: z5.coerce.number().optional().nullable(),
1021
- customMetric: z5.coerce.number().optional().nullable()
1022
- })
1023
- )
1024
- })
1025
- )
1026
- };
1027
- server.tool(
1028
- "update-workout",
1029
- "Update an existing workout by ID. You can modify the title, description, start/end times, privacy setting, and exercise data. Returns the updated workout with all changes applied.",
1030
- updateWorkoutSchema,
1031
- withErrorHandling(async (args) => {
1032
- if (!hevyClient) {
1033
- throw new Error(
1034
- "API client not initialized. Please provide HEVY_API_KEY."
1035
- );
1036
- }
1037
- const {
1038
- workoutId,
1039
- title,
1040
- description,
1041
- startTime,
1042
- endTime,
1043
- routineId,
1044
- isPrivate,
1045
- exercises
1046
- } = args;
1047
- const workoutPayload = {
1048
- title,
1049
- description: description || null,
1050
- start_time: startTime,
1051
- end_time: endTime,
1052
- routine_id: routineId ?? null,
1053
- is_private: isPrivate,
1054
- exercises: exercises.map(
1055
- (exercise) => ({
1056
- exercise_template_id: exercise.exerciseTemplateId,
1057
- superset_id: exercise.supersetId ?? null,
1058
- notes: exercise.notes ?? null,
1059
- sets: exercise.sets.map((set) => ({
1060
- type: set.type,
1061
- weight_kg: set.weight ?? set.weightKg ?? null,
1062
- reps: set.reps ?? null,
1063
- distance_meters: set.distance ?? set.distanceMeters ?? null,
1064
- duration_seconds: set.duration ?? set.durationSeconds ?? null,
1065
- rpe: set.rpe ?? null,
1066
- custom_metric: set.customMetric ?? null
1067
- }))
1068
- })
1069
- )
1070
- };
1071
- const requestBody = { workout: workoutPayload };
1072
- const data = await hevyClient.updateWorkout(workoutId, requestBody);
1073
- if (!data) {
1074
- return createEmptyResponse(
1075
- `Failed to update workout with ID ${workoutId}`
1076
- );
1077
- }
1078
- const workout = formatWorkout(data);
1079
- return createJsonResponse(workout, {
1080
- pretty: true,
1081
- indent: 2
1082
- });
1083
- }, "update-workout-operation")
1084
- );
1085
- }
1086
-
1087
- // src/utils/config.ts
1088
- function parseConfig(argv, env) {
1089
- let apiKey = "";
1090
- const apiKeyArgPatterns = [
1091
- /^--hevy-api-key=(.+)$/i,
1092
- /^--hevyApiKey=(.+)$/i,
1093
- /^hevy-api-key=(.+)$/i
1094
- ];
1095
- for (const raw of argv) {
1096
- for (const pattern of apiKeyArgPatterns) {
1097
- const m = raw.match(pattern);
1098
- if (m) {
1099
- apiKey = m[1];
1100
- break;
1101
- }
1102
- }
1103
- if (apiKey) break;
1104
- }
1105
- if (!apiKey) {
1106
- apiKey = env.HEVY_API_KEY || "";
1107
- }
1108
- return {
1109
- apiKey
1110
- };
1111
- }
1112
- function assertApiKey(apiKey) {
1113
- if (!apiKey) {
1114
- console.error(
1115
- "Hevy API key is required. Provide it via the HEVY_API_KEY environment variable or the --hevy-api-key=YOUR_KEY command argument."
1116
- );
1117
- process.exit(1);
1118
- }
1119
- }
1120
-
1121
- // src/utils/hevyClientKubb.ts
1122
- import axios from "axios";
1123
-
1124
- // src/generated/client/api/getV1ExerciseHistoryExercisetemplateid.ts
1125
- import fetch from "@kubb/plugin-client/clients/axios";
1126
- function getGetV1ExerciseHistoryExercisetemplateidUrl(exerciseTemplateId) {
1127
- const res = { method: "GET", url: `/v1/exercise_history/${exerciseTemplateId}` };
1128
- return res;
1129
- }
1130
- async function getV1ExerciseHistoryExercisetemplateid(exerciseTemplateId, headers, params, config = {}) {
1131
- const { client: request = fetch, ...requestConfig } = config;
1132
- const res = await request({ method: "GET", url: getGetV1ExerciseHistoryExercisetemplateidUrl(exerciseTemplateId).url.toString(), params, ...requestConfig, headers: { ...headers, ...requestConfig.headers } });
1133
- return res.data;
1134
- }
1135
-
1136
- // src/generated/client/api/getV1ExerciseTemplates.ts
1137
- import fetch2 from "@kubb/plugin-client/clients/axios";
1138
- function getGetV1ExerciseTemplatesUrl() {
1139
- const res = { method: "GET", url: `/v1/exercise_templates` };
1140
- return res;
1141
- }
1142
- async function getV1ExerciseTemplates(headers, params, config = {}) {
1143
- const { client: request = fetch2, ...requestConfig } = config;
1144
- const res = await request({ method: "GET", url: getGetV1ExerciseTemplatesUrl().url.toString(), params, ...requestConfig, headers: { ...headers, ...requestConfig.headers } });
1145
- return res.data;
1146
- }
1147
-
1148
- // src/generated/client/api/getV1ExerciseTemplatesExercisetemplateid.ts
1149
- import fetch3 from "@kubb/plugin-client/clients/axios";
1150
- function getGetV1ExerciseTemplatesExercisetemplateidUrl(exerciseTemplateId) {
1151
- const res = { method: "GET", url: `/v1/exercise_templates/${exerciseTemplateId}` };
1152
- return res;
1153
- }
1154
- async function getV1ExerciseTemplatesExercisetemplateid(exerciseTemplateId, headers, config = {}) {
1155
- const { client: request = fetch3, ...requestConfig } = config;
1156
- const res = await request({ method: "GET", url: getGetV1ExerciseTemplatesExercisetemplateidUrl(exerciseTemplateId).url.toString(), ...requestConfig, headers: { ...headers, ...requestConfig.headers } });
1157
- return res.data;
1158
- }
1159
-
1160
- // src/generated/client/api/getV1RoutineFolders.ts
1161
- import fetch4 from "@kubb/plugin-client/clients/axios";
1162
- function getGetV1RoutineFoldersUrl() {
1163
- const res = { method: "GET", url: `/v1/routine_folders` };
1164
- return res;
1165
- }
1166
- async function getV1RoutineFolders(headers, params, config = {}) {
1167
- const { client: request = fetch4, ...requestConfig } = config;
1168
- const res = await request({ method: "GET", url: getGetV1RoutineFoldersUrl().url.toString(), params, ...requestConfig, headers: { ...headers, ...requestConfig.headers } });
1169
- return res.data;
1170
- }
1171
-
1172
- // src/generated/client/api/getV1RoutineFoldersFolderid.ts
1173
- import fetch5 from "@kubb/plugin-client/clients/axios";
1174
- function getGetV1RoutineFoldersFolderidUrl(folderId) {
1175
- const res = { method: "GET", url: `/v1/routine_folders/${folderId}` };
1176
- return res;
1177
- }
1178
- async function getV1RoutineFoldersFolderid(folderId, headers, config = {}) {
1179
- const { client: request = fetch5, ...requestConfig } = config;
1180
- const res = await request({ method: "GET", url: getGetV1RoutineFoldersFolderidUrl(folderId).url.toString(), ...requestConfig, headers: { ...headers, ...requestConfig.headers } });
1181
- return res.data;
1182
- }
1183
-
1184
- // src/generated/client/api/getV1Routines.ts
1185
- import fetch6 from "@kubb/plugin-client/clients/axios";
1186
- function getGetV1RoutinesUrl() {
1187
- const res = { method: "GET", url: `/v1/routines` };
1188
- return res;
1189
- }
1190
- async function getV1Routines(headers, params, config = {}) {
1191
- const { client: request = fetch6, ...requestConfig } = config;
1192
- const res = await request({ method: "GET", url: getGetV1RoutinesUrl().url.toString(), params, ...requestConfig, headers: { ...headers, ...requestConfig.headers } });
1193
- return res.data;
1194
- }
1195
-
1196
- // src/generated/client/api/getV1RoutinesRoutineid.ts
1197
- import fetch7 from "@kubb/plugin-client/clients/axios";
1198
- function getGetV1RoutinesRoutineidUrl(routineId) {
1199
- const res = { method: "GET", url: `/v1/routines/${routineId}` };
1200
- return res;
1201
- }
1202
- async function getV1RoutinesRoutineid(routineId, headers, config = {}) {
1203
- const { client: request = fetch7, ...requestConfig } = config;
1204
- const res = await request({ method: "GET", url: getGetV1RoutinesRoutineidUrl(routineId).url.toString(), ...requestConfig, headers: { ...headers, ...requestConfig.headers } });
1205
- return res.data;
1206
- }
1207
-
1208
- // src/generated/client/api/getV1Workouts.ts
1209
- import fetch8 from "@kubb/plugin-client/clients/axios";
1210
- function getGetV1WorkoutsUrl() {
1211
- const res = { method: "GET", url: `/v1/workouts` };
1212
- return res;
1213
- }
1214
- async function getV1Workouts(headers, params, config = {}) {
1215
- const { client: request = fetch8, ...requestConfig } = config;
1216
- const res = await request({ method: "GET", url: getGetV1WorkoutsUrl().url.toString(), params, ...requestConfig, headers: { ...headers, ...requestConfig.headers } });
1217
- return res.data;
1218
- }
1219
-
1220
- // src/generated/client/api/getV1WorkoutsCount.ts
1221
- import fetch9 from "@kubb/plugin-client/clients/axios";
1222
- function getGetV1WorkoutsCountUrl() {
1223
- const res = { method: "GET", url: `/v1/workouts/count` };
1224
- return res;
1225
- }
1226
- async function getV1WorkoutsCount(headers, config = {}) {
1227
- const { client: request = fetch9, ...requestConfig } = config;
1228
- const res = await request({ method: "GET", url: getGetV1WorkoutsCountUrl().url.toString(), ...requestConfig, headers: { ...headers, ...requestConfig.headers } });
1229
- return res.data;
1230
- }
1231
-
1232
- // src/generated/client/api/getV1WorkoutsEvents.ts
1233
- import fetch10 from "@kubb/plugin-client/clients/axios";
1234
- function getGetV1WorkoutsEventsUrl() {
1235
- const res = { method: "GET", url: `/v1/workouts/events` };
1236
- return res;
1237
- }
1238
- async function getV1WorkoutsEvents(headers, params, config = {}) {
1239
- const { client: request = fetch10, ...requestConfig } = config;
1240
- const res = await request({ method: "GET", url: getGetV1WorkoutsEventsUrl().url.toString(), params, ...requestConfig, headers: { ...headers, ...requestConfig.headers } });
1241
- return res.data;
1242
- }
1243
-
1244
- // src/generated/client/api/getV1WorkoutsWorkoutid.ts
1245
- import fetch11 from "@kubb/plugin-client/clients/axios";
1246
- function getGetV1WorkoutsWorkoutidUrl(workoutId) {
1247
- const res = { method: "GET", url: `/v1/workouts/${workoutId}` };
1248
- return res;
1249
- }
1250
- async function getV1WorkoutsWorkoutid(workoutId, headers, config = {}) {
1251
- const { client: request = fetch11, ...requestConfig } = config;
1252
- const res = await request({ method: "GET", url: getGetV1WorkoutsWorkoutidUrl(workoutId).url.toString(), ...requestConfig, headers: { ...headers, ...requestConfig.headers } });
1253
- return res.data;
1254
- }
1255
-
1256
- // src/generated/client/api/postV1ExerciseTemplates.ts
1257
- import fetch12 from "@kubb/plugin-client/clients/axios";
1258
- function getPostV1ExerciseTemplatesUrl() {
1259
- const res = { method: "POST", url: `/v1/exercise_templates` };
1260
- return res;
1261
- }
1262
- async function postV1ExerciseTemplates(headers, data, config = {}) {
1263
- const { client: request = fetch12, ...requestConfig } = config;
1264
- const requestData = data;
1265
- const res = await request({ method: "POST", url: getPostV1ExerciseTemplatesUrl().url.toString(), data: requestData, ...requestConfig, headers: { ...headers, ...requestConfig.headers } });
1266
- return res.data;
1267
- }
1268
-
1269
- // src/generated/client/api/postV1RoutineFolders.ts
1270
- import fetch13 from "@kubb/plugin-client/clients/axios";
1271
- function getPostV1RoutineFoldersUrl() {
1272
- const res = { method: "POST", url: `/v1/routine_folders` };
1273
- return res;
1274
- }
1275
- async function postV1RoutineFolders(headers, data, config = {}) {
1276
- const { client: request = fetch13, ...requestConfig } = config;
1277
- const requestData = data;
1278
- const res = await request({ method: "POST", url: getPostV1RoutineFoldersUrl().url.toString(), data: requestData, ...requestConfig, headers: { ...headers, ...requestConfig.headers } });
1279
- return res.data;
1280
- }
1281
-
1282
- // src/generated/client/api/postV1Routines.ts
1283
- import fetch14 from "@kubb/plugin-client/clients/axios";
1284
- function getPostV1RoutinesUrl() {
1285
- const res = { method: "POST", url: `/v1/routines` };
1286
- return res;
1287
- }
1288
- async function postV1Routines(headers, data, config = {}) {
1289
- const { client: request = fetch14, ...requestConfig } = config;
1290
- const requestData = data;
1291
- const res = await request({ method: "POST", url: getPostV1RoutinesUrl().url.toString(), data: requestData, ...requestConfig, headers: { ...headers, ...requestConfig.headers } });
1292
- return res.data;
1293
- }
1294
-
1295
- // src/generated/client/api/postV1Workouts.ts
1296
- import fetch15 from "@kubb/plugin-client/clients/axios";
1297
- function getPostV1WorkoutsUrl() {
1298
- const res = { method: "POST", url: `/v1/workouts` };
1299
- return res;
1300
- }
1301
- async function postV1Workouts(headers, data, config = {}) {
1302
- const { client: request = fetch15, ...requestConfig } = config;
1303
- const requestData = data;
1304
- const res = await request({ method: "POST", url: getPostV1WorkoutsUrl().url.toString(), data: requestData, ...requestConfig, headers: { ...headers, ...requestConfig.headers } });
1305
- return res.data;
1306
- }
1307
-
1308
- // src/generated/client/api/putV1RoutinesRoutineid.ts
1309
- import fetch16 from "@kubb/plugin-client/clients/axios";
1310
- function getPutV1RoutinesRoutineidUrl(routineId) {
1311
- const res = { method: "PUT", url: `/v1/routines/${routineId}` };
1312
- return res;
1313
- }
1314
- async function putV1RoutinesRoutineid(routineId, headers, data, config = {}) {
1315
- const { client: request = fetch16, ...requestConfig } = config;
1316
- const requestData = data;
1317
- const res = await request({ method: "PUT", url: getPutV1RoutinesRoutineidUrl(routineId).url.toString(), data: requestData, ...requestConfig, headers: { ...headers, ...requestConfig.headers } });
1318
- return res.data;
1319
- }
1320
-
1321
- // src/generated/client/api/putV1WorkoutsWorkoutid.ts
1322
- import fetch17 from "@kubb/plugin-client/clients/axios";
1323
- function getPutV1WorkoutsWorkoutidUrl(workoutId) {
1324
- const res = { method: "PUT", url: `/v1/workouts/${workoutId}` };
1325
- return res;
1326
- }
1327
- async function putV1WorkoutsWorkoutid(workoutId, headers, data, config = {}) {
1328
- const { client: request = fetch17, ...requestConfig } = config;
1329
- const requestData = data;
1330
- const res = await request({ method: "PUT", url: getPutV1WorkoutsWorkoutidUrl(workoutId).url.toString(), data: requestData, ...requestConfig, headers: { ...headers, ...requestConfig.headers } });
1331
- return res.data;
1332
- }
1333
-
1334
- // src/utils/hevyClientKubb.ts
1335
- function createClient(apiKey, baseUrl = "https://api.hevyapp.com") {
1336
- const axiosInstance = axios.create({
1337
- baseURL: baseUrl,
1338
- headers: {
1339
- "api-key": apiKey
1340
- }
1341
- });
1342
- const headers = {
1343
- "api-key": apiKey
1344
- };
1345
- const client = axiosInstance;
1346
- return {
1347
- // Workouts
1348
- getWorkouts: (params) => getV1Workouts(headers, params, { client }),
1349
- getWorkout: (workoutId) => getV1WorkoutsWorkoutid(workoutId, headers, { client }),
1350
- createWorkout: (data) => postV1Workouts(headers, data, { client }),
1351
- updateWorkout: (workoutId, data) => putV1WorkoutsWorkoutid(workoutId, headers, data, {
1352
- client
1353
- }),
1354
- getWorkoutCount: () => getV1WorkoutsCount(headers, { client }),
1355
- getWorkoutEvents: (params) => getV1WorkoutsEvents(headers, params, { client }),
1356
- // Routines
1357
- getRoutines: (params) => getV1Routines(headers, params, { client }),
1358
- getRoutineById: (routineId) => getV1RoutinesRoutineid(routineId, headers, { client }),
1359
- createRoutine: (data) => postV1Routines(headers, data, { client }),
1360
- updateRoutine: (routineId, data) => putV1RoutinesRoutineid(routineId, headers, data, {
1361
- client
1362
- }),
1363
- // Exercise Templates
1364
- getExerciseTemplates: (params) => getV1ExerciseTemplates(headers, params, { client }),
1365
- getExerciseTemplate: (templateId) => getV1ExerciseTemplatesExercisetemplateid(templateId, headers, {
1366
- client
1367
- }),
1368
- getExerciseHistory: (exerciseTemplateId, params) => getV1ExerciseHistoryExercisetemplateid(
1369
- exerciseTemplateId,
1370
- headers,
1371
- params,
1372
- { client }
1373
- ),
1374
- createExerciseTemplate: (data) => postV1ExerciseTemplates(headers, data, { client }),
1375
- // Routine Folders
1376
- getRoutineFolders: (params) => getV1RoutineFolders(headers, params, { client }),
1377
- createRoutineFolder: (data) => postV1RoutineFolders(headers, data, { client }),
1378
- getRoutineFolder: (folderId) => getV1RoutineFoldersFolderid(folderId, headers, {
1379
- client
1380
- }),
1381
- getWebhookSubscription: async () => {
1382
- throw new Error(
1383
- "Webhook subscription API not available. Please regenerate the client from the updated OpenAPI spec."
1384
- );
1385
- },
1386
- createWebhookSubscription: async (_data) => {
1387
- throw new Error(
1388
- "Webhook subscription API not available. Please regenerate the client from the updated OpenAPI spec."
1389
- );
1390
- },
1391
- deleteWebhookSubscription: async () => {
1392
- throw new Error(
1393
- "Webhook subscription API not available. Please regenerate the client from the updated OpenAPI spec."
1394
- );
1395
- }
1396
- };
1397
- }
1398
-
1399
- // src/utils/hevyClient.ts
1400
- function createClient2(apiKey, baseUrl) {
1401
- return createClient(apiKey, baseUrl);
1402
- }
1403
-
1404
- // src/index.ts
1405
- var isBuiltArtifact = true ? true : false;
1406
- if (isBuiltArtifact && false) {
1407
- throw new Error(
1408
- "Build-time variables __HEVY_MCP_NAME__ and __HEVY_MCP_VERSION__ must be defined."
1409
- );
1410
- }
1411
- var name = true ? "hevy-mcp" : "hevy-mcp";
1412
- var version = true ? "1.17.1" : "dev";
1413
- dotenvx.config({ quiet: true });
1414
- var sentryRelease = process.env.SENTRY_RELEASE ?? `${name}@${version}`;
1415
- var sentryConfig = {
1416
- dsn: "https://ce696d8333b507acbf5203eb877bce0f@o4508975499575296.ingest.de.sentry.io/4509049671647312",
1417
- release: sentryRelease,
1418
- // Tracing must be enabled for MCP monitoring to work
1419
- tracesSampleRate: 1,
1420
- sendDefaultPii: false
1421
- };
1422
- Sentry.init(sentryConfig);
1423
- var HEVY_API_BASEURL = "https://api.hevyapp.com";
1424
- var serverConfigSchema = z6.object({
1425
- apiKey: z6.string().min(1, "Hevy API key is required").describe("Your Hevy API key (available in the Hevy app settings).")
1426
- });
1427
- function buildServer(apiKey) {
1428
- const baseServer = new McpServer({
1429
- name,
1430
- version
1431
- });
1432
- const server = Sentry.wrapMcpServerWithSentry(baseServer);
1433
- const hevyClient = createClient2(apiKey, HEVY_API_BASEURL);
1434
- console.error("Hevy client initialized with API key");
1435
- registerWorkoutTools(server, hevyClient);
1436
- registerRoutineTools(server, hevyClient);
1437
- registerTemplateTools(server, hevyClient);
1438
- registerFolderTools(server, hevyClient);
1439
- registerWebhookTools(server, hevyClient);
1440
- return server;
1441
- }
1442
- async function runServer() {
1443
- const args = process.argv.slice(2);
1444
- const cfg = parseConfig(args, process.env);
1445
- const apiKey = cfg.apiKey;
1446
- assertApiKey(apiKey);
1447
- const server = buildServer(apiKey);
1448
- console.error("Starting MCP server in stdio mode");
1449
- const transport = new StdioServerTransport();
1450
- await server.connect(transport);
1451
- }
1452
-
1453
- // src/cli.ts
1454
- void runServer().catch((error) => {
1455
- console.error("Fatal error in main():", error);
1456
- process.exit(1);
1457
- });
1458
- //# sourceMappingURL=cli.js.map