hevy-mcp 1.1.0 → 1.2.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/index.js +207 -164
- package/dist/index.js.map +1 -1
- package/package.json +21 -4
package/dist/index.js
CHANGED
|
@@ -9,12 +9,96 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
9
9
|
|
|
10
10
|
// src/tools/folders.ts
|
|
11
11
|
import { z } from "zod";
|
|
12
|
+
|
|
13
|
+
// src/utils/formatters.ts
|
|
14
|
+
function formatWorkout(workout) {
|
|
15
|
+
return {
|
|
16
|
+
id: workout.id,
|
|
17
|
+
date: workout.createdAt,
|
|
18
|
+
name: workout.title,
|
|
19
|
+
description: workout.description,
|
|
20
|
+
duration: calculateDuration(workout.startTime || "", workout.endTime || ""),
|
|
21
|
+
exercises: workout.exercises?.map((exercise) => {
|
|
22
|
+
return {
|
|
23
|
+
name: exercise.title,
|
|
24
|
+
notes: exercise.notes,
|
|
25
|
+
sets: exercise.sets?.map((set) => ({
|
|
26
|
+
type: set.type,
|
|
27
|
+
weight: set.weightKg,
|
|
28
|
+
reps: set.reps,
|
|
29
|
+
distance: set.distanceMeters,
|
|
30
|
+
duration: set.durationSeconds,
|
|
31
|
+
rpe: set.rpe,
|
|
32
|
+
customMetric: set.customMetric
|
|
33
|
+
}))
|
|
34
|
+
};
|
|
35
|
+
})
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function formatRoutine(routine) {
|
|
39
|
+
return {
|
|
40
|
+
id: routine.id,
|
|
41
|
+
title: routine.title,
|
|
42
|
+
folderId: routine.folderId,
|
|
43
|
+
createdAt: routine.createdAt,
|
|
44
|
+
updatedAt: routine.updatedAt,
|
|
45
|
+
exercises: routine.exercises?.map((exercise) => {
|
|
46
|
+
return {
|
|
47
|
+
name: exercise.title,
|
|
48
|
+
index: exercise.index,
|
|
49
|
+
exerciseTemplateId: exercise.exerciseTemplateId,
|
|
50
|
+
notes: exercise.notes,
|
|
51
|
+
supersetId: exercise.supersetsId,
|
|
52
|
+
sets: exercise.sets?.map((set) => ({
|
|
53
|
+
index: set.index,
|
|
54
|
+
type: set.type,
|
|
55
|
+
weight: set.weightKg,
|
|
56
|
+
reps: set.reps,
|
|
57
|
+
distance: set.distanceMeters,
|
|
58
|
+
duration: set.durationSeconds,
|
|
59
|
+
customMetric: set.customMetric
|
|
60
|
+
}))
|
|
61
|
+
};
|
|
62
|
+
})
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function formatRoutineFolder(folder) {
|
|
66
|
+
return {
|
|
67
|
+
id: folder.id,
|
|
68
|
+
title: folder.title,
|
|
69
|
+
createdAt: folder.createdAt,
|
|
70
|
+
updatedAt: folder.updatedAt
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function calculateDuration(startTime, endTime) {
|
|
74
|
+
if (!startTime || !endTime) return "Unknown duration";
|
|
75
|
+
const start = new Date(startTime);
|
|
76
|
+
const end = new Date(endTime);
|
|
77
|
+
const durationMs = end.getTime() - start.getTime();
|
|
78
|
+
const hours = Math.floor(durationMs / (1e3 * 60 * 60));
|
|
79
|
+
const minutes = Math.floor(durationMs % (1e3 * 60 * 60) / (1e3 * 60));
|
|
80
|
+
const seconds = Math.floor(durationMs % (1e3 * 60) / 1e3);
|
|
81
|
+
return `${hours}h ${minutes}m ${seconds}s`;
|
|
82
|
+
}
|
|
83
|
+
function formatExerciseTemplate(template) {
|
|
84
|
+
return {
|
|
85
|
+
id: template.id,
|
|
86
|
+
title: template.title,
|
|
87
|
+
type: template.type,
|
|
88
|
+
primaryMuscleGroup: template.primaryMuscleGroup,
|
|
89
|
+
secondaryMuscleGroups: template.secondaryMuscleGroups,
|
|
90
|
+
isCustom: template.isCustom
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/tools/folders.ts
|
|
12
95
|
function registerFolderTools(server2, hevyClient2) {
|
|
13
96
|
server2.tool(
|
|
14
97
|
"get-routine-folders",
|
|
98
|
+
"Get a paginated list of routine folders available on the account. Returns folder details including ID, title, index (order position), and creation/update timestamps. Useful for organizing routines into categories.",
|
|
15
99
|
{
|
|
16
|
-
page: z.number().int().gte(1).default(1),
|
|
17
|
-
pageSize: z.number().int().gte(1).lte(10).default(5)
|
|
100
|
+
page: z.coerce.number().int().gte(1).default(1),
|
|
101
|
+
pageSize: z.coerce.number().int().gte(1).lte(10).default(5)
|
|
18
102
|
},
|
|
19
103
|
async ({ page, pageSize }) => {
|
|
20
104
|
try {
|
|
@@ -41,15 +125,17 @@ function registerFolderTools(server2, hevyClient2) {
|
|
|
41
125
|
type: "text",
|
|
42
126
|
text: `Error fetching routine folders: ${error instanceof Error ? error.message : String(error)}`
|
|
43
127
|
}
|
|
44
|
-
]
|
|
128
|
+
],
|
|
129
|
+
isError: true
|
|
45
130
|
};
|
|
46
131
|
}
|
|
47
132
|
}
|
|
48
133
|
);
|
|
49
134
|
server2.tool(
|
|
50
135
|
"get-routine-folder",
|
|
136
|
+
"Get complete details of a specific routine folder by ID. Returns all folder information including title, index (order position), and creation/update timestamps.",
|
|
51
137
|
{
|
|
52
|
-
folderId: z.number().int()
|
|
138
|
+
folderId: z.coerce.number().int()
|
|
53
139
|
},
|
|
54
140
|
async ({ folderId }) => {
|
|
55
141
|
try {
|
|
@@ -81,13 +167,15 @@ function registerFolderTools(server2, hevyClient2) {
|
|
|
81
167
|
type: "text",
|
|
82
168
|
text: `Error fetching routine folder: ${error instanceof Error ? error.message : String(error)}`
|
|
83
169
|
}
|
|
84
|
-
]
|
|
170
|
+
],
|
|
171
|
+
isError: true
|
|
85
172
|
};
|
|
86
173
|
}
|
|
87
174
|
}
|
|
88
175
|
);
|
|
89
176
|
server2.tool(
|
|
90
177
|
"create-routine-folder",
|
|
178
|
+
"Create a new routine folder in your Hevy account. The folder will be created at index 0, and all other folders will have their indexes incremented. Returns the complete folder details upon successful creation including the newly assigned folder ID.",
|
|
91
179
|
{
|
|
92
180
|
title: z.string().min(1)
|
|
93
181
|
},
|
|
@@ -126,30 +214,23 @@ ${JSON.stringify(folder, null, 2)}`
|
|
|
126
214
|
type: "text",
|
|
127
215
|
text: `Error creating routine folder: ${error instanceof Error ? error.message : String(error)}`
|
|
128
216
|
}
|
|
129
|
-
]
|
|
217
|
+
],
|
|
218
|
+
isError: true
|
|
130
219
|
};
|
|
131
220
|
}
|
|
132
221
|
}
|
|
133
222
|
);
|
|
134
223
|
}
|
|
135
|
-
function formatRoutineFolder(folder) {
|
|
136
|
-
return {
|
|
137
|
-
id: folder.id,
|
|
138
|
-
title: folder.title,
|
|
139
|
-
index: folder.index,
|
|
140
|
-
createdAt: folder.createdAt,
|
|
141
|
-
updatedAt: folder.updatedAt
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
224
|
|
|
145
225
|
// src/tools/routines.ts
|
|
146
226
|
import { z as z2 } from "zod";
|
|
147
227
|
function registerRoutineTools(server2, hevyClient2) {
|
|
148
228
|
server2.tool(
|
|
149
229
|
"get-routines",
|
|
230
|
+
"Get a paginated list of routines. Returns routine details including title, creation date, folder assignment, and exercise configurations. Results include both default and custom routines.",
|
|
150
231
|
{
|
|
151
|
-
page: z2.number().int().gte(1).default(1),
|
|
152
|
-
pageSize: z2.number().int().gte(1).lte(10).default(5)
|
|
232
|
+
page: z2.coerce.number().int().gte(1).default(1),
|
|
233
|
+
pageSize: z2.coerce.number().int().gte(1).lte(10).default(5)
|
|
153
234
|
},
|
|
154
235
|
async ({ page, pageSize }) => {
|
|
155
236
|
try {
|
|
@@ -176,22 +257,26 @@ function registerRoutineTools(server2, hevyClient2) {
|
|
|
176
257
|
type: "text",
|
|
177
258
|
text: `Error fetching routines: ${error instanceof Error ? error.message : String(error)}`
|
|
178
259
|
}
|
|
179
|
-
]
|
|
260
|
+
],
|
|
261
|
+
isError: true
|
|
180
262
|
};
|
|
181
263
|
}
|
|
182
264
|
}
|
|
183
265
|
);
|
|
184
266
|
server2.tool(
|
|
185
267
|
"get-routine",
|
|
268
|
+
"Get complete details of a specific routine by ID. Returns all routine information including title, notes, assigned folder, and detailed exercise data with set configurations.",
|
|
186
269
|
{
|
|
187
270
|
routineId: z2.string().min(1)
|
|
188
271
|
},
|
|
189
272
|
async ({ routineId }) => {
|
|
190
273
|
try {
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
274
|
+
const data = await hevyClient2.v1.routines.byRoutineId(routineId).put({
|
|
275
|
+
routine: {
|
|
276
|
+
title: ""
|
|
277
|
+
// We're providing a minimal body as required by the API
|
|
278
|
+
}
|
|
279
|
+
});
|
|
195
280
|
if (!data) {
|
|
196
281
|
return {
|
|
197
282
|
content: [
|
|
@@ -219,31 +304,33 @@ function registerRoutineTools(server2, hevyClient2) {
|
|
|
219
304
|
type: "text",
|
|
220
305
|
text: `Error fetching routine: ${error instanceof Error ? error.message : String(error)}`
|
|
221
306
|
}
|
|
222
|
-
]
|
|
307
|
+
],
|
|
308
|
+
isError: true
|
|
223
309
|
};
|
|
224
310
|
}
|
|
225
311
|
}
|
|
226
312
|
);
|
|
227
313
|
server2.tool(
|
|
228
314
|
"create-routine",
|
|
315
|
+
"Create a new workout routine in your Hevy account. Requires title and at least one exercise with sets. Optionally assign to a specific folder. Returns the complete routine details upon successful creation including the newly assigned routine ID.",
|
|
229
316
|
{
|
|
230
317
|
title: z2.string().min(1),
|
|
231
|
-
folderId: z2.number().nullable().optional(),
|
|
318
|
+
folderId: z2.coerce.number().nullable().optional(),
|
|
232
319
|
notes: z2.string().optional(),
|
|
233
320
|
exercises: z2.array(
|
|
234
321
|
z2.object({
|
|
235
322
|
exerciseTemplateId: z2.string().min(1),
|
|
236
|
-
supersetId: z2.number().nullable().optional(),
|
|
237
|
-
restSeconds: z2.number().int().min(0).optional(),
|
|
323
|
+
supersetId: z2.coerce.number().nullable().optional(),
|
|
324
|
+
restSeconds: z2.coerce.number().int().min(0).optional(),
|
|
238
325
|
notes: z2.string().optional(),
|
|
239
326
|
sets: z2.array(
|
|
240
327
|
z2.object({
|
|
241
328
|
type: z2.enum(["warmup", "normal", "failure", "dropset"]).default("normal"),
|
|
242
|
-
weightKg: z2.number().optional(),
|
|
243
|
-
reps: z2.number().int().optional(),
|
|
244
|
-
distanceMeters: z2.number().int().optional(),
|
|
245
|
-
durationSeconds: z2.number().int().optional(),
|
|
246
|
-
customMetric: z2.number().optional()
|
|
329
|
+
weightKg: z2.coerce.number().optional(),
|
|
330
|
+
reps: z2.coerce.number().int().optional(),
|
|
331
|
+
distanceMeters: z2.coerce.number().int().optional(),
|
|
332
|
+
durationSeconds: z2.coerce.number().int().optional(),
|
|
333
|
+
customMetric: z2.coerce.number().optional()
|
|
247
334
|
})
|
|
248
335
|
)
|
|
249
336
|
})
|
|
@@ -300,13 +387,15 @@ ${JSON.stringify(routine, null, 2)}`
|
|
|
300
387
|
type: "text",
|
|
301
388
|
text: `Error creating routine: ${error instanceof Error ? error.message : String(error)}`
|
|
302
389
|
}
|
|
303
|
-
]
|
|
390
|
+
],
|
|
391
|
+
isError: true
|
|
304
392
|
};
|
|
305
393
|
}
|
|
306
394
|
}
|
|
307
395
|
);
|
|
308
396
|
server2.tool(
|
|
309
397
|
"update-routine",
|
|
398
|
+
"Update an existing workout routine by ID. You can modify the title, notes, and exercise data. Returns the updated routine with all changes applied. Note that you cannot change the folder assignment through this method.",
|
|
310
399
|
{
|
|
311
400
|
routineId: z2.string().min(1),
|
|
312
401
|
title: z2.string().min(1),
|
|
@@ -314,17 +403,17 @@ ${JSON.stringify(routine, null, 2)}`
|
|
|
314
403
|
exercises: z2.array(
|
|
315
404
|
z2.object({
|
|
316
405
|
exerciseTemplateId: z2.string().min(1),
|
|
317
|
-
supersetId: z2.number().nullable().optional(),
|
|
318
|
-
restSeconds: z2.number().int().min(0).optional(),
|
|
406
|
+
supersetId: z2.coerce.number().nullable().optional(),
|
|
407
|
+
restSeconds: z2.coerce.number().int().min(0).optional(),
|
|
319
408
|
notes: z2.string().optional(),
|
|
320
409
|
sets: z2.array(
|
|
321
410
|
z2.object({
|
|
322
411
|
type: z2.enum(["warmup", "normal", "failure", "dropset"]).default("normal"),
|
|
323
|
-
weightKg: z2.number().optional(),
|
|
324
|
-
reps: z2.number().int().optional(),
|
|
325
|
-
distanceMeters: z2.number().int().optional(),
|
|
326
|
-
durationSeconds: z2.number().int().optional(),
|
|
327
|
-
customMetric: z2.number().optional()
|
|
412
|
+
weightKg: z2.coerce.number().optional(),
|
|
413
|
+
reps: z2.coerce.number().int().optional(),
|
|
414
|
+
distanceMeters: z2.coerce.number().int().optional(),
|
|
415
|
+
durationSeconds: z2.coerce.number().int().optional(),
|
|
416
|
+
customMetric: z2.coerce.number().optional()
|
|
328
417
|
})
|
|
329
418
|
)
|
|
330
419
|
})
|
|
@@ -380,48 +469,23 @@ ${JSON.stringify(routine, null, 2)}`
|
|
|
380
469
|
type: "text",
|
|
381
470
|
text: `Error updating routine: ${error instanceof Error ? error.message : String(error)}`
|
|
382
471
|
}
|
|
383
|
-
]
|
|
472
|
+
],
|
|
473
|
+
isError: true
|
|
384
474
|
};
|
|
385
475
|
}
|
|
386
476
|
}
|
|
387
477
|
);
|
|
388
478
|
}
|
|
389
|
-
function formatRoutine(routine) {
|
|
390
|
-
return {
|
|
391
|
-
id: routine.id,
|
|
392
|
-
title: routine.title,
|
|
393
|
-
folderId: routine.folderId,
|
|
394
|
-
createdAt: routine.createdAt,
|
|
395
|
-
updatedAt: routine.updatedAt,
|
|
396
|
-
exercises: routine.exercises?.map((exercise) => {
|
|
397
|
-
return {
|
|
398
|
-
name: exercise.title,
|
|
399
|
-
index: exercise.index,
|
|
400
|
-
exerciseTemplateId: exercise.exerciseTemplateId,
|
|
401
|
-
notes: exercise.notes,
|
|
402
|
-
supersetId: exercise.supersetsId,
|
|
403
|
-
sets: exercise.sets?.map((set) => ({
|
|
404
|
-
index: set.index,
|
|
405
|
-
type: set.type,
|
|
406
|
-
weight: set.weightKg,
|
|
407
|
-
reps: set.reps,
|
|
408
|
-
distance: set.distanceMeters,
|
|
409
|
-
duration: set.durationSeconds,
|
|
410
|
-
customMetric: set.customMetric
|
|
411
|
-
}))
|
|
412
|
-
};
|
|
413
|
-
})
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
479
|
|
|
417
480
|
// src/tools/templates.ts
|
|
418
481
|
import { z as z3 } from "zod";
|
|
419
482
|
function registerTemplateTools(server2, hevyClient2) {
|
|
420
483
|
server2.tool(
|
|
421
484
|
"get-exercise-templates",
|
|
485
|
+
"Get a paginated list of exercise templates available on the account. Returns both default and custom exercise templates with details including title, type, primary muscle group, and secondary muscle groups. Supports up to 100 templates per page.",
|
|
422
486
|
{
|
|
423
|
-
page: z3.number().int().gte(1).default(1),
|
|
424
|
-
pageSize: z3.number().int().gte(1).lte(100).default(20)
|
|
487
|
+
page: z3.coerce.number().int().gte(1).default(1),
|
|
488
|
+
pageSize: z3.coerce.number().int().gte(1).lte(100).default(20)
|
|
425
489
|
},
|
|
426
490
|
async ({ page, pageSize }) => {
|
|
427
491
|
try {
|
|
@@ -450,13 +514,15 @@ function registerTemplateTools(server2, hevyClient2) {
|
|
|
450
514
|
type: "text",
|
|
451
515
|
text: `Error fetching exercise templates: ${error instanceof Error ? error.message : String(error)}`
|
|
452
516
|
}
|
|
453
|
-
]
|
|
517
|
+
],
|
|
518
|
+
isError: true
|
|
454
519
|
};
|
|
455
520
|
}
|
|
456
521
|
}
|
|
457
522
|
);
|
|
458
523
|
server2.tool(
|
|
459
524
|
"get-exercise-template",
|
|
525
|
+
"Get complete details of a specific exercise template by ID. Returns all template information including title, type, primary muscle group, secondary muscle groups, and whether it's a custom exercise.",
|
|
460
526
|
{
|
|
461
527
|
exerciseTemplateId: z3.string().min(1)
|
|
462
528
|
},
|
|
@@ -493,31 +559,23 @@ function registerTemplateTools(server2, hevyClient2) {
|
|
|
493
559
|
type: "text",
|
|
494
560
|
text: `Error fetching exercise template: ${error instanceof Error ? error.message : String(error)}`
|
|
495
561
|
}
|
|
496
|
-
]
|
|
562
|
+
],
|
|
563
|
+
isError: true
|
|
497
564
|
};
|
|
498
565
|
}
|
|
499
566
|
}
|
|
500
567
|
);
|
|
501
568
|
}
|
|
502
|
-
function formatExerciseTemplate(template) {
|
|
503
|
-
return {
|
|
504
|
-
id: template.id,
|
|
505
|
-
title: template.title,
|
|
506
|
-
type: template.type,
|
|
507
|
-
primaryMuscleGroup: template.primaryMuscleGroup,
|
|
508
|
-
secondaryMuscleGroups: template.secondaryMuscleGroups,
|
|
509
|
-
isCustom: template.isCustom
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
569
|
|
|
513
570
|
// src/tools/workouts.ts
|
|
514
571
|
import { z as z4 } from "zod";
|
|
515
572
|
function registerWorkoutTools(server2, hevyClient2) {
|
|
516
573
|
server2.tool(
|
|
517
574
|
"get-workouts",
|
|
575
|
+
"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.",
|
|
518
576
|
{
|
|
519
|
-
page: z4.number().
|
|
520
|
-
pageSize: z4.number().int().gte(1).lte(10).default(5)
|
|
577
|
+
page: z4.coerce.number().gte(1).default(1),
|
|
578
|
+
pageSize: z4.coerce.number().int().gte(1).lte(10).default(5)
|
|
521
579
|
},
|
|
522
580
|
async ({ page, pageSize }, extra) => {
|
|
523
581
|
try {
|
|
@@ -544,13 +602,15 @@ function registerWorkoutTools(server2, hevyClient2) {
|
|
|
544
602
|
type: "text",
|
|
545
603
|
text: `Error fetching workouts: ${error instanceof Error ? error.message : String(error)}`
|
|
546
604
|
}
|
|
547
|
-
]
|
|
605
|
+
],
|
|
606
|
+
isError: true
|
|
548
607
|
};
|
|
549
608
|
}
|
|
550
609
|
}
|
|
551
610
|
);
|
|
552
611
|
server2.tool(
|
|
553
612
|
"get-workout",
|
|
613
|
+
"Get complete details of a specific workout by ID. Returns all workout information including title, description, start/end times, and detailed exercise data.",
|
|
554
614
|
{
|
|
555
615
|
workoutId: z4.string().min(1)
|
|
556
616
|
},
|
|
@@ -584,39 +644,47 @@ function registerWorkoutTools(server2, hevyClient2) {
|
|
|
584
644
|
type: "text",
|
|
585
645
|
text: `Error fetching workout: ${error instanceof Error ? error.message : String(error)}`
|
|
586
646
|
}
|
|
587
|
-
]
|
|
647
|
+
],
|
|
648
|
+
isError: true
|
|
588
649
|
};
|
|
589
650
|
}
|
|
590
651
|
}
|
|
591
652
|
);
|
|
592
|
-
server2.tool(
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
653
|
+
server2.tool(
|
|
654
|
+
"get-workout-count",
|
|
655
|
+
"Get the total number of workouts on the account. Useful for pagination or statistics.",
|
|
656
|
+
{},
|
|
657
|
+
async (_, extra) => {
|
|
658
|
+
try {
|
|
659
|
+
const data = await hevyClient2.v1.workouts.count.get();
|
|
660
|
+
return {
|
|
661
|
+
content: [
|
|
662
|
+
{
|
|
663
|
+
type: "text",
|
|
664
|
+
text: `Total workouts: ${data ? data.count || 0 : 0}`
|
|
665
|
+
}
|
|
666
|
+
]
|
|
667
|
+
};
|
|
668
|
+
} catch (error) {
|
|
669
|
+
console.error("Error fetching workout count:", error);
|
|
670
|
+
return {
|
|
671
|
+
content: [
|
|
672
|
+
{
|
|
673
|
+
type: "text",
|
|
674
|
+
text: `Error fetching workout count: ${error instanceof Error ? error.message : String(error)}`
|
|
675
|
+
}
|
|
676
|
+
],
|
|
677
|
+
isError: true
|
|
678
|
+
};
|
|
679
|
+
}
|
|
613
680
|
}
|
|
614
|
-
|
|
681
|
+
);
|
|
615
682
|
server2.tool(
|
|
616
683
|
"get-workout-events",
|
|
684
|
+
"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.",
|
|
617
685
|
{
|
|
618
|
-
page: z4.number().int().gte(1).default(1),
|
|
619
|
-
pageSize: z4.number().int().gte(1).lte(10).default(5),
|
|
686
|
+
page: z4.coerce.number().int().gte(1).default(1),
|
|
687
|
+
pageSize: z4.coerce.number().int().gte(1).lte(10).default(5),
|
|
620
688
|
since: z4.string().default("1970-01-01T00:00:00Z")
|
|
621
689
|
},
|
|
622
690
|
async ({ page, pageSize, since }, extra) => {
|
|
@@ -644,13 +712,15 @@ function registerWorkoutTools(server2, hevyClient2) {
|
|
|
644
712
|
type: "text",
|
|
645
713
|
text: `Error fetching workout events: ${error instanceof Error ? error.message : String(error)}`
|
|
646
714
|
}
|
|
647
|
-
]
|
|
715
|
+
],
|
|
716
|
+
isError: true
|
|
648
717
|
};
|
|
649
718
|
}
|
|
650
719
|
}
|
|
651
720
|
);
|
|
652
721
|
server2.tool(
|
|
653
722
|
"create-workout",
|
|
723
|
+
"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.",
|
|
654
724
|
{
|
|
655
725
|
title: z4.string().min(1),
|
|
656
726
|
description: z4.string().optional().nullable(),
|
|
@@ -660,17 +730,17 @@ function registerWorkoutTools(server2, hevyClient2) {
|
|
|
660
730
|
exercises: z4.array(
|
|
661
731
|
z4.object({
|
|
662
732
|
exerciseTemplateId: z4.string().min(1),
|
|
663
|
-
supersetId: z4.number().nullable().optional(),
|
|
733
|
+
supersetId: z4.coerce.number().nullable().optional(),
|
|
664
734
|
notes: z4.string().optional().nullable(),
|
|
665
735
|
sets: z4.array(
|
|
666
736
|
z4.object({
|
|
667
737
|
type: z4.enum(["warmup", "normal", "failure", "dropset"]).default("normal"),
|
|
668
|
-
weightKg: z4.number().optional().nullable(),
|
|
669
|
-
reps: z4.number().int().optional().nullable(),
|
|
670
|
-
distanceMeters: z4.number().int().optional().nullable(),
|
|
671
|
-
durationSeconds: z4.number().int().optional().nullable(),
|
|
672
|
-
rpe: z4.number().optional().nullable(),
|
|
673
|
-
customMetric: z4.number().optional().nullable()
|
|
738
|
+
weightKg: z4.coerce.number().optional().nullable(),
|
|
739
|
+
reps: z4.coerce.number().int().optional().nullable(),
|
|
740
|
+
distanceMeters: z4.coerce.number().int().optional().nullable(),
|
|
741
|
+
durationSeconds: z4.coerce.number().int().optional().nullable(),
|
|
742
|
+
rpe: z4.coerce.number().optional().nullable(),
|
|
743
|
+
customMetric: z4.coerce.number().optional().nullable()
|
|
674
744
|
})
|
|
675
745
|
)
|
|
676
746
|
})
|
|
@@ -730,13 +800,15 @@ ${JSON.stringify(workout, null, 2)}`
|
|
|
730
800
|
type: "text",
|
|
731
801
|
text: `Error creating workout: ${error instanceof Error ? error.message : String(error)}`
|
|
732
802
|
}
|
|
733
|
-
]
|
|
803
|
+
],
|
|
804
|
+
isError: true
|
|
734
805
|
};
|
|
735
806
|
}
|
|
736
807
|
}
|
|
737
808
|
);
|
|
738
809
|
server2.tool(
|
|
739
810
|
"update-workout",
|
|
811
|
+
"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.",
|
|
740
812
|
{
|
|
741
813
|
workoutId: z4.string().min(1),
|
|
742
814
|
title: z4.string().min(1),
|
|
@@ -747,17 +819,17 @@ ${JSON.stringify(workout, null, 2)}`
|
|
|
747
819
|
exercises: z4.array(
|
|
748
820
|
z4.object({
|
|
749
821
|
exerciseTemplateId: z4.string().min(1),
|
|
750
|
-
supersetId: z4.number().nullable().optional(),
|
|
822
|
+
supersetId: z4.coerce.number().nullable().optional(),
|
|
751
823
|
notes: z4.string().optional().nullable(),
|
|
752
824
|
sets: z4.array(
|
|
753
825
|
z4.object({
|
|
754
826
|
type: z4.enum(["warmup", "normal", "failure", "dropset"]).default("normal"),
|
|
755
|
-
weightKg: z4.number().optional().nullable(),
|
|
756
|
-
reps: z4.number().int().optional().nullable(),
|
|
757
|
-
distanceMeters: z4.number().int().optional().nullable(),
|
|
758
|
-
durationSeconds: z4.number().int().optional().nullable(),
|
|
759
|
-
rpe: z4.number().optional().nullable(),
|
|
760
|
-
customMetric: z4.number().optional().nullable()
|
|
827
|
+
weightKg: z4.coerce.number().optional().nullable(),
|
|
828
|
+
reps: z4.coerce.number().int().optional().nullable(),
|
|
829
|
+
distanceMeters: z4.coerce.number().int().optional().nullable(),
|
|
830
|
+
durationSeconds: z4.coerce.number().int().optional().nullable(),
|
|
831
|
+
rpe: z4.coerce.number().optional().nullable(),
|
|
832
|
+
customMetric: z4.coerce.number().optional().nullable()
|
|
761
833
|
})
|
|
762
834
|
)
|
|
763
835
|
})
|
|
@@ -825,47 +897,13 @@ ${JSON.stringify(workout, null, 2)}`
|
|
|
825
897
|
type: "text",
|
|
826
898
|
text: `Error updating workout: ${error instanceof Error ? error.message : String(error)}`
|
|
827
899
|
}
|
|
828
|
-
]
|
|
900
|
+
],
|
|
901
|
+
isError: true
|
|
829
902
|
};
|
|
830
903
|
}
|
|
831
904
|
}
|
|
832
905
|
);
|
|
833
906
|
}
|
|
834
|
-
function formatWorkout(workout) {
|
|
835
|
-
return {
|
|
836
|
-
id: workout.id,
|
|
837
|
-
date: workout.createdAt,
|
|
838
|
-
name: workout.title,
|
|
839
|
-
description: workout.description,
|
|
840
|
-
duration: calculateDuration(workout.startTime || "", workout.endTime || ""),
|
|
841
|
-
exercises: workout.exercises?.map((exercise) => {
|
|
842
|
-
return {
|
|
843
|
-
name: exercise.title,
|
|
844
|
-
notes: exercise.notes,
|
|
845
|
-
sets: exercise.sets?.map((set) => ({
|
|
846
|
-
type: set.type,
|
|
847
|
-
weight: set.weightKg,
|
|
848
|
-
reps: set.reps,
|
|
849
|
-
distance: set.distanceMeters,
|
|
850
|
-
duration: set.durationSeconds,
|
|
851
|
-
rpe: set.rpe,
|
|
852
|
-
customMetric: set.customMetric
|
|
853
|
-
}))
|
|
854
|
-
};
|
|
855
|
-
})
|
|
856
|
-
};
|
|
857
|
-
}
|
|
858
|
-
function calculateDuration(startTime, endTime) {
|
|
859
|
-
try {
|
|
860
|
-
if (!startTime || !endTime) return "Unknown duration";
|
|
861
|
-
const start = typeof startTime === "number" ? startTime : new Date(startTime).getTime();
|
|
862
|
-
const end = typeof endTime === "number" ? endTime : new Date(endTime).getTime();
|
|
863
|
-
const durationMinutes = Math.round((end - start) / 6e4);
|
|
864
|
-
return `${durationMinutes} minutes`;
|
|
865
|
-
} catch (error) {
|
|
866
|
-
return "Unknown duration";
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
907
|
|
|
870
908
|
// src/utils/hevyClient.ts
|
|
871
909
|
import {
|
|
@@ -2086,17 +2124,22 @@ var HevyClientNavigationMetadata = {
|
|
|
2086
2124
|
|
|
2087
2125
|
// src/utils/hevyClient.ts
|
|
2088
2126
|
function createClient(apiKey2, baseUrl) {
|
|
2089
|
-
const authProvider = new ApiKeyAuthenticationProvider(
|
|
2127
|
+
const authProvider = new ApiKeyAuthenticationProvider(
|
|
2128
|
+
apiKey2,
|
|
2129
|
+
"api-key",
|
|
2130
|
+
ApiKeyLocation.Header
|
|
2131
|
+
);
|
|
2090
2132
|
const adapter = new FetchRequestAdapter(authProvider);
|
|
2091
2133
|
adapter.baseUrl = baseUrl;
|
|
2092
2134
|
return createHevyClient(adapter);
|
|
2093
2135
|
}
|
|
2094
2136
|
|
|
2095
2137
|
// src/index.ts
|
|
2138
|
+
import Package from "paketo";
|
|
2096
2139
|
var HEVY_API_BASEURL = "https://api.hevyapp.com";
|
|
2097
2140
|
var server = new McpServer({
|
|
2098
|
-
name:
|
|
2099
|
-
version:
|
|
2141
|
+
name: Package.name,
|
|
2142
|
+
version: Package.version
|
|
2100
2143
|
});
|
|
2101
2144
|
if (!process.env.HEVY_API_KEY) {
|
|
2102
2145
|
console.error("HEVY_API_KEY environment variable is not set");
|