hevy-mcp 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![MseeP.ai Security Assessment Badge](https://mseep.net/pr/chrisdoc-hevy-mcp-badge.png)](https://mseep.ai/app/chrisdoc-hevy-mcp)
2
+
1
3
  # hevy-mcp: Model Context Protocol Server for Hevy Fitness API
2
4
 
3
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
package/dist/index.js CHANGED
@@ -72,13 +72,24 @@ function formatRoutineFolder(folder) {
72
72
  }
73
73
  function calculateDuration(startTime, endTime) {
74
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`;
75
+ try {
76
+ const start = new Date(startTime);
77
+ const end = new Date(endTime);
78
+ if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) {
79
+ return "Unknown duration";
80
+ }
81
+ const durationMs = end.getTime() - start.getTime();
82
+ if (durationMs < 0) {
83
+ return "Invalid duration (end time before start time)";
84
+ }
85
+ const hours = Math.floor(durationMs / (1e3 * 60 * 60));
86
+ const minutes = Math.floor(durationMs % (1e3 * 60 * 60) / (1e3 * 60));
87
+ const seconds = Math.floor(durationMs % (1e3 * 60) / 1e3);
88
+ return `${hours}h ${minutes}m ${seconds}s`;
89
+ } catch (error) {
90
+ console.error("Error calculating duration:", error);
91
+ return "Unknown duration";
92
+ }
82
93
  }
83
94
  function formatExerciseTemplate(template) {
84
95
  return {
@@ -569,6 +580,61 @@ function registerTemplateTools(server2, hevyClient2) {
569
580
 
570
581
  // src/tools/workouts.ts
571
582
  import { z as z4 } from "zod";
583
+
584
+ // src/utils/error-handler.ts
585
+ function createErrorResponse(error, context) {
586
+ const errorMessage = error instanceof Error ? error.message : String(error);
587
+ const errorCode = error instanceof Error && "code" in error ? error.code : void 0;
588
+ if (errorCode) {
589
+ console.debug(`Error code: ${errorCode}`);
590
+ }
591
+ const contextPrefix = context ? `[${context}] ` : "";
592
+ const formattedMessage = `${contextPrefix}Error: ${errorMessage}`;
593
+ console.error(formattedMessage, error);
594
+ return {
595
+ content: [
596
+ {
597
+ type: "text",
598
+ text: formattedMessage
599
+ }
600
+ ],
601
+ isError: true
602
+ };
603
+ }
604
+ function withErrorHandling(fn, context) {
605
+ return async (...args) => {
606
+ try {
607
+ return await fn(...args);
608
+ } catch (error) {
609
+ return createErrorResponse(error, context);
610
+ }
611
+ };
612
+ }
613
+
614
+ // src/utils/response-formatter.ts
615
+ function createJsonResponse(data, options = { pretty: true, indent: 2 }) {
616
+ const jsonString = options.pretty ? JSON.stringify(data, null, options.indent) : JSON.stringify(data);
617
+ return {
618
+ content: [
619
+ {
620
+ type: "text",
621
+ text: jsonString
622
+ }
623
+ ]
624
+ };
625
+ }
626
+ function createEmptyResponse(message = "No data found") {
627
+ return {
628
+ content: [
629
+ {
630
+ type: "text",
631
+ text: message
632
+ }
633
+ ]
634
+ };
635
+ }
636
+
637
+ // src/tools/workouts.ts
572
638
  function registerWorkoutTools(server2, hevyClient2) {
573
639
  server2.tool(
574
640
  "get-workouts",
@@ -577,36 +643,19 @@ function registerWorkoutTools(server2, hevyClient2) {
577
643
  page: z4.coerce.number().gte(1).default(1),
578
644
  pageSize: z4.coerce.number().int().gte(1).lte(10).default(5)
579
645
  },
580
- async ({ page, pageSize }, extra) => {
581
- try {
582
- const data = await hevyClient2.v1.workouts.get({
583
- queryParameters: {
584
- page,
585
- pageSize
586
- }
587
- });
588
- const workouts = data?.workouts?.map((workout) => formatWorkout(workout)) || [];
589
- return {
590
- content: [
591
- {
592
- type: "text",
593
- text: JSON.stringify(workouts, null, 2)
594
- }
595
- ]
596
- };
597
- } catch (error) {
598
- console.error("Error fetching workouts:", error);
599
- return {
600
- content: [
601
- {
602
- type: "text",
603
- text: `Error fetching workouts: ${error instanceof Error ? error.message : String(error)}`
604
- }
605
- ],
606
- isError: true
607
- };
646
+ withErrorHandling(async ({ page, pageSize }) => {
647
+ const data = await hevyClient2.v1.workouts.get({
648
+ queryParameters: {
649
+ page,
650
+ pageSize
651
+ }
652
+ });
653
+ const workouts = data?.workouts?.map((workout) => formatWorkout(workout)) || [];
654
+ if (workouts.length === 0) {
655
+ return createEmptyResponse("No workouts found for the specified parameters");
608
656
  }
609
- }
657
+ return createJsonResponse(workouts);
658
+ }, "get-workouts")
610
659
  );
611
660
  server2.tool(
612
661
  "get-workout",
@@ -614,70 +663,24 @@ function registerWorkoutTools(server2, hevyClient2) {
614
663
  {
615
664
  workoutId: z4.string().min(1)
616
665
  },
617
- async ({ workoutId }, extra) => {
618
- try {
619
- const data = await hevyClient2.v1.workouts.byWorkoutId(workoutId).get();
620
- if (!data) {
621
- return {
622
- content: [
623
- {
624
- type: "text",
625
- text: `Workout with ID ${workoutId} not found`
626
- }
627
- ]
628
- };
629
- }
630
- const workout = formatWorkout(data);
631
- return {
632
- content: [
633
- {
634
- type: "text",
635
- text: JSON.stringify(workout, null, 2)
636
- }
637
- ]
638
- };
639
- } catch (error) {
640
- console.error(`Error fetching workout ${workoutId}:`, error);
641
- return {
642
- content: [
643
- {
644
- type: "text",
645
- text: `Error fetching workout: ${error instanceof Error ? error.message : String(error)}`
646
- }
647
- ],
648
- isError: true
649
- };
666
+ withErrorHandling(async ({ workoutId }) => {
667
+ const data = await hevyClient2.v1.workouts.byWorkoutId(workoutId).get();
668
+ if (!data) {
669
+ return createEmptyResponse(`Workout with ID ${workoutId} not found`);
650
670
  }
651
- }
671
+ const workout = formatWorkout(data);
672
+ return createJsonResponse(workout);
673
+ }, "get-workout")
652
674
  );
653
675
  server2.tool(
654
676
  "get-workout-count",
655
677
  "Get the total number of workouts on the account. Useful for pagination or statistics.",
656
678
  {},
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.workoutCount || 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
- }
680
- }
679
+ withErrorHandling(async () => {
680
+ const data = await hevyClient2.v1.workouts.count.get();
681
+ const count = data ? data.workoutCount || 0 : 0;
682
+ return createJsonResponse({ count });
683
+ }, "get-workout-count")
681
684
  );
682
685
  server2.tool(
683
686
  "get-workout-events",
@@ -687,36 +690,20 @@ function registerWorkoutTools(server2, hevyClient2) {
687
690
  pageSize: z4.coerce.number().int().gte(1).lte(10).default(5),
688
691
  since: z4.string().default("1970-01-01T00:00:00Z")
689
692
  },
690
- async ({ page, pageSize, since }, extra) => {
691
- try {
692
- const data = await hevyClient2.v1.workouts.events.get({
693
- queryParameters: {
694
- page,
695
- pageSize,
696
- since
697
- }
698
- });
699
- return {
700
- content: [
701
- {
702
- type: "text",
703
- text: JSON.stringify(data?.events || [], null, 2)
704
- }
705
- ]
706
- };
707
- } catch (error) {
708
- console.error("Error fetching workout events:", error);
709
- return {
710
- content: [
711
- {
712
- type: "text",
713
- text: `Error fetching workout events: ${error instanceof Error ? error.message : String(error)}`
714
- }
715
- ],
716
- isError: true
717
- };
693
+ withErrorHandling(async ({ page, pageSize, since }) => {
694
+ const data = await hevyClient2.v1.workouts.events.get({
695
+ queryParameters: {
696
+ page,
697
+ pageSize,
698
+ since
699
+ }
700
+ });
701
+ const events = data?.events || [];
702
+ if (events.length === 0) {
703
+ return createEmptyResponse(`No workout events found for the specified parameters since ${since}`);
718
704
  }
719
- }
705
+ return createJsonResponse(events);
706
+ }, "get-workout-events")
720
707
  );
721
708
  server2.tool(
722
709
  "create-workout",
@@ -746,8 +733,15 @@ function registerWorkoutTools(server2, hevyClient2) {
746
733
  })
747
734
  )
748
735
  },
749
- async ({ title, description, startTime, endTime, isPrivate, exercises }, extra) => {
750
- try {
736
+ withErrorHandling(
737
+ async ({
738
+ title,
739
+ description,
740
+ startTime,
741
+ endTime,
742
+ isPrivate,
743
+ exercises
744
+ }) => {
751
745
  const requestBody = {
752
746
  workout: {
753
747
  title,
@@ -773,38 +767,16 @@ function registerWorkoutTools(server2, hevyClient2) {
773
767
  };
774
768
  const data = await hevyClient2.v1.workouts.post(requestBody);
775
769
  if (!data) {
776
- return {
777
- content: [
778
- {
779
- type: "text",
780
- text: "Failed to create workout"
781
- }
782
- ]
783
- };
770
+ return createEmptyResponse("Failed to create workout: Server returned no data");
784
771
  }
785
772
  const workout = formatWorkout(data);
786
- return {
787
- content: [
788
- {
789
- type: "text",
790
- text: `Workout created successfully:
791
- ${JSON.stringify(workout, null, 2)}`
792
- }
793
- ]
794
- };
795
- } catch (error) {
796
- console.error("Error creating workout:", error);
797
- return {
798
- content: [
799
- {
800
- type: "text",
801
- text: `Error creating workout: ${error instanceof Error ? error.message : String(error)}`
802
- }
803
- ],
804
- isError: true
805
- };
806
- }
807
- }
773
+ return createJsonResponse(workout, {
774
+ pretty: true,
775
+ indent: 2
776
+ });
777
+ },
778
+ "create-workout"
779
+ )
808
780
  );
809
781
  server2.tool(
810
782
  "update-workout",
@@ -835,16 +807,16 @@ ${JSON.stringify(workout, null, 2)}`
835
807
  })
836
808
  )
837
809
  },
838
- async ({
839
- workoutId,
840
- title,
841
- description,
842
- startTime,
843
- endTime,
844
- isPrivate,
845
- exercises
846
- }, extra) => {
847
- try {
810
+ withErrorHandling(
811
+ async ({
812
+ workoutId,
813
+ title,
814
+ description,
815
+ startTime,
816
+ endTime,
817
+ isPrivate,
818
+ exercises
819
+ }) => {
848
820
  const requestBody = {
849
821
  workout: {
850
822
  title,
@@ -870,38 +842,18 @@ ${JSON.stringify(workout, null, 2)}`
870
842
  };
871
843
  const data = await hevyClient2.v1.workouts.byWorkoutId(workoutId).put(requestBody);
872
844
  if (!data) {
873
- return {
874
- content: [
875
- {
876
- type: "text",
877
- text: `Failed to update workout with ID ${workoutId}`
878
- }
879
- ]
880
- };
845
+ return createEmptyResponse(
846
+ `Failed to update workout with ID ${workoutId}`
847
+ );
881
848
  }
882
849
  const workout = formatWorkout(data);
883
- return {
884
- content: [
885
- {
886
- type: "text",
887
- text: `Workout updated successfully:
888
- ${JSON.stringify(workout, null, 2)}`
889
- }
890
- ]
891
- };
892
- } catch (error) {
893
- console.error(`Error updating workout ${workoutId}:`, error);
894
- return {
895
- content: [
896
- {
897
- type: "text",
898
- text: `Error updating workout: ${error instanceof Error ? error.message : String(error)}`
899
- }
900
- ],
901
- isError: true
902
- };
903
- }
904
- }
850
+ return createJsonResponse(workout, {
851
+ pretty: true,
852
+ indent: 2
853
+ });
854
+ },
855
+ "update-workout-operation"
856
+ )
905
857
  );
906
858
  }
907
859
 
@@ -2136,7 +2088,7 @@ function createClient(apiKey2, baseUrl) {
2136
2088
 
2137
2089
  // package.json
2138
2090
  var name = "hevy-mcp";
2139
- var version = "1.2.2";
2091
+ var version = "1.4.0";
2140
2092
 
2141
2093
  // src/index.ts
2142
2094
  var HEVY_API_BASEURL = "https://api.hevyapp.com";