hevy-mcp 1.28.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -7
- package/dist/cli.mjs +2 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{src-J8OsyJcw.mjs → src-c3mB-R0m.mjs} +771 -125
- package/dist/src-c3mB-R0m.mjs.map +1 -0
- package/package.json +12 -4
- package/server.json +29 -0
- package/dist/src-J8OsyJcw.mjs.map +0 -1
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
(function() {
|
|
5
5
|
try {
|
|
6
6
|
var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
|
|
7
|
-
e.SENTRY_RELEASE = { id: "hevy-mcp@
|
|
7
|
+
e.SENTRY_RELEASE = { id: "hevy-mcp@2.0.0" };
|
|
8
8
|
var n = new e.Error().stack;
|
|
9
|
-
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "
|
|
9
|
+
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "17d5086b-9850-447d-9372-27bb2ce5dcc5", e._sentryDebugIdIdentifier = "sentry-dbid-17d5086b-9850-447d-9372-27bb2ce5dcc5");
|
|
10
10
|
} catch (e) {}
|
|
11
11
|
})();
|
|
12
12
|
import * as Sentry from "@sentry/node";
|
|
@@ -18,13 +18,17 @@ import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
|
18
18
|
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
19
19
|
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
|
20
20
|
import { MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
|
21
|
-
import { createHmac } from "node:crypto";
|
|
21
|
+
import { createHmac, randomUUID } from "node:crypto";
|
|
22
22
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
23
23
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
24
24
|
import { z } from "zod";
|
|
25
25
|
import axios, { isAxiosError } from "axios";
|
|
26
26
|
import { z as z$1 } from "zod/v4";
|
|
27
27
|
import { deserializeMessage } from "@modelcontextprotocol/sdk/shared/stdio.js";
|
|
28
|
+
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
29
|
+
import { homedir } from "node:os";
|
|
30
|
+
import { dirname, join } from "node:path";
|
|
31
|
+
import semver from "semver";
|
|
28
32
|
//#region src/utils/telemetry.ts
|
|
29
33
|
/**
|
|
30
34
|
* Centralized telemetry initialization.
|
|
@@ -37,10 +41,10 @@ import { deserializeMessage } from "@modelcontextprotocol/sdk/shared/stdio.js";
|
|
|
37
41
|
* OTel Collector → Honeycomb: performance traces, metrics
|
|
38
42
|
*/
|
|
39
43
|
const name$1 = "hevy-mcp";
|
|
40
|
-
const version$1 = "
|
|
44
|
+
const version$1 = "2.0.0";
|
|
41
45
|
const collectorToken = "NH9vOela-HYreQxAbJa68cjEmORoEKM57EvneUDVcOo";
|
|
42
46
|
const COLLECTOR_ENDPOINT = "https://otel.chrisdoc.dev/v1";
|
|
43
|
-
const sentryRelease = process.env.SENTRY_RELEASE ?? `hevy-mcp@
|
|
47
|
+
const sentryRelease = process.env.SENTRY_RELEASE ?? `hevy-mcp@2.0.0`;
|
|
44
48
|
const resource = resourceFromAttributes({
|
|
45
49
|
"service.name": name$1,
|
|
46
50
|
"service.version": version$1
|
|
@@ -693,6 +697,93 @@ function withErrorHandling(fn, context) {
|
|
|
693
697
|
};
|
|
694
698
|
}
|
|
695
699
|
//#endregion
|
|
700
|
+
//#region src/utils/debug.ts
|
|
701
|
+
const DEBUG_PREFIX = "[hevy-mcp:debug] ";
|
|
702
|
+
const MAX_DEBUG_RECORD_LENGTH = 8192;
|
|
703
|
+
const MAX_REDACTION_DEPTH = 4;
|
|
704
|
+
const MAX_OBJECT_KEYS = 20;
|
|
705
|
+
function isDebugEnabled(env = process.env) {
|
|
706
|
+
return env.HEVY_MCP_DEBUG === "1";
|
|
707
|
+
}
|
|
708
|
+
function redactValue(value, depth, seen) {
|
|
709
|
+
if (value === null) return "[null]";
|
|
710
|
+
if (typeof value !== "object") return `[${typeof value}]`;
|
|
711
|
+
if (seen.has(value)) return "[circular]";
|
|
712
|
+
if (Array.isArray(value)) {
|
|
713
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
|
|
714
|
+
const length = lengthDescriptor && "value" in lengthDescriptor && typeof lengthDescriptor.value === "number" ? lengthDescriptor.value : 0;
|
|
715
|
+
if (depth >= MAX_REDACTION_DEPTH) return {
|
|
716
|
+
type: "array",
|
|
717
|
+
length,
|
|
718
|
+
items: "[max-depth]"
|
|
719
|
+
};
|
|
720
|
+
seen.add(value);
|
|
721
|
+
try {
|
|
722
|
+
const itemCount = Math.min(length, MAX_OBJECT_KEYS);
|
|
723
|
+
const items = {};
|
|
724
|
+
for (let index = 0; index < itemCount; index += 1) {
|
|
725
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
726
|
+
items[`item-${index + 1}`] = descriptor && "value" in descriptor ? redactValue(descriptor.value, depth + 1, seen) : "[empty-or-accessor]";
|
|
727
|
+
}
|
|
728
|
+
return {
|
|
729
|
+
type: "array",
|
|
730
|
+
length,
|
|
731
|
+
items,
|
|
732
|
+
...length > MAX_OBJECT_KEYS ? { truncatedItems: length - MAX_OBJECT_KEYS } : {}
|
|
733
|
+
};
|
|
734
|
+
} finally {
|
|
735
|
+
seen.delete(value);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
const keys = Object.keys(value);
|
|
739
|
+
if (depth >= MAX_REDACTION_DEPTH) return {
|
|
740
|
+
type: "object",
|
|
741
|
+
fieldCount: keys.length,
|
|
742
|
+
fields: "[max-depth]"
|
|
743
|
+
};
|
|
744
|
+
seen.add(value);
|
|
745
|
+
try {
|
|
746
|
+
const fields = {};
|
|
747
|
+
for (const [index, rawKey] of keys.slice(0, MAX_OBJECT_KEYS).entries()) {
|
|
748
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, rawKey);
|
|
749
|
+
fields[`field-${index + 1}`] = descriptor && "value" in descriptor ? redactValue(descriptor.value, depth + 1, seen) : "[accessor]";
|
|
750
|
+
}
|
|
751
|
+
return {
|
|
752
|
+
type: "object",
|
|
753
|
+
fieldCount: keys.length,
|
|
754
|
+
fields,
|
|
755
|
+
...keys.length > MAX_OBJECT_KEYS ? { truncatedFields: keys.length - MAX_OBJECT_KEYS } : {}
|
|
756
|
+
};
|
|
757
|
+
} finally {
|
|
758
|
+
seen.delete(value);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
/** Redact every input scalar while preserving bounded argument structure. */
|
|
762
|
+
function redactToolArgs(args) {
|
|
763
|
+
try {
|
|
764
|
+
return redactValue(args, 0, /* @__PURE__ */ new WeakSet());
|
|
765
|
+
} catch {
|
|
766
|
+
return "[unavailable]";
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Write a single bounded structured debug record to stderr without throwing.
|
|
771
|
+
*/
|
|
772
|
+
function debugLog(event, data) {
|
|
773
|
+
if (!isDebugEnabled()) return;
|
|
774
|
+
try {
|
|
775
|
+
const record = JSON.stringify({
|
|
776
|
+
event,
|
|
777
|
+
...data
|
|
778
|
+
});
|
|
779
|
+
const output = record.length <= MAX_DEBUG_RECORD_LENGTH ? record : JSON.stringify({
|
|
780
|
+
event,
|
|
781
|
+
truncated: true
|
|
782
|
+
});
|
|
783
|
+
process.stderr.write(`${DEBUG_PREFIX}${output}\n`);
|
|
784
|
+
} catch {}
|
|
785
|
+
}
|
|
786
|
+
//#endregion
|
|
696
787
|
//#region src/utils/telemetry-wrapper.ts
|
|
697
788
|
/** Whitelist of safe argument keys that can be logged without exposing PII. */
|
|
698
789
|
const ARGUMENT_WHITELIST = /* @__PURE__ */ new Set([
|
|
@@ -729,6 +820,10 @@ function extractSafeArgs(args) {
|
|
|
729
820
|
function withTelemetry(fn, context) {
|
|
730
821
|
return async (rawArgs) => {
|
|
731
822
|
const args = rawArgs ?? {};
|
|
823
|
+
if (isDebugEnabled()) debugLog("tool_invocation", {
|
|
824
|
+
tool: context,
|
|
825
|
+
params: redactToolArgs(args)
|
|
826
|
+
});
|
|
732
827
|
const argumentKeyCount = Object.keys(args).length;
|
|
733
828
|
const startTime = Date.now();
|
|
734
829
|
let isError = false;
|
|
@@ -786,62 +881,6 @@ function withObservability(fn, context) {
|
|
|
786
881
|
return withErrorHandling(withTelemetry(fn, context), context);
|
|
787
882
|
}
|
|
788
883
|
//#endregion
|
|
789
|
-
//#region src/generated/client/schemas/deletedWorkoutSchema.ts
|
|
790
|
-
/**
|
|
791
|
-
* Generated by Kubb (https://kubb.dev/).
|
|
792
|
-
* Do not edit manually.
|
|
793
|
-
*/
|
|
794
|
-
const deletedWorkoutSchema = z$1.object({
|
|
795
|
-
type: z$1.string().describe("Indicates the type of the event (deleted)"),
|
|
796
|
-
id: z$1.string().describe("The unique identifier of the deleted workout"),
|
|
797
|
-
deleted_at: z$1.optional(z$1.string().describe("A date string indicating when the workout was deleted"))
|
|
798
|
-
});
|
|
799
|
-
//#endregion
|
|
800
|
-
//#region src/generated/client/schemas/workoutSchema.ts
|
|
801
|
-
/**
|
|
802
|
-
* Generated by Kubb (https://kubb.dev/).
|
|
803
|
-
* Do not edit manually.
|
|
804
|
-
*/
|
|
805
|
-
const workoutSchema = z$1.object({
|
|
806
|
-
id: z$1.optional(z$1.string().describe("The workout ID.")),
|
|
807
|
-
title: z$1.optional(z$1.string().describe("The workout title.")),
|
|
808
|
-
routine_id: z$1.optional(z$1.string().describe("The ID of the routine that this workout belongs to.")),
|
|
809
|
-
description: z$1.optional(z$1.string().describe("The workout description.")),
|
|
810
|
-
start_time: z$1.optional(z$1.string().describe("ISO 8601 timestamp of when the workout was recorded to have started.")),
|
|
811
|
-
end_time: z$1.optional(z$1.string().describe("ISO 8601 timestamp of when the workout was recorded to have ended.")),
|
|
812
|
-
updated_at: z$1.optional(z$1.string().describe("ISO 8601 timestamp of when the workout was last updated.")),
|
|
813
|
-
created_at: z$1.optional(z$1.string().describe("ISO 8601 timestamp of when the workout was created.")),
|
|
814
|
-
exercises: z$1.optional(z$1.array(z$1.object({
|
|
815
|
-
index: z$1.optional(z$1.number().describe("Index indicating the order of the exercise in the workout.")),
|
|
816
|
-
title: z$1.optional(z$1.string().describe("Title of the exercise")),
|
|
817
|
-
notes: z$1.optional(z$1.string().describe("Notes on the exercise")),
|
|
818
|
-
exercise_template_id: z$1.optional(z$1.string().describe("The id of the exercise template. This can be used to fetch the exercise template.")),
|
|
819
|
-
supersets_id: z$1.number().describe("The id of the superset that the exercise belongs to. A value of null indicates the exercise is not part of a superset.").nullish(),
|
|
820
|
-
sets: z$1.optional(z$1.array(z$1.object({
|
|
821
|
-
index: z$1.optional(z$1.number().describe("Index indicating the order of the set in the workout.")),
|
|
822
|
-
type: z$1.optional(z$1.string().describe("The type of set. This can be one of 'normal', 'warmup', 'dropset', 'failure'")),
|
|
823
|
-
weight_kg: z$1.number().describe("Weight lifted in kilograms.").nullish(),
|
|
824
|
-
reps: z$1.number().describe("Number of reps logged for the set").nullish(),
|
|
825
|
-
distance_meters: z$1.number().describe("Number of meters logged for the set").nullish(),
|
|
826
|
-
duration_seconds: z$1.number().describe("Number of seconds logged for the set").nullish(),
|
|
827
|
-
rpe: z$1.number().describe("RPE (Relative perceived exertion) value logged for the set").nullish(),
|
|
828
|
-
custom_metric: z$1.number().describe("Custom metric logged for the set (Currently only used to log floors or steps for stair machine exercises)").nullish()
|
|
829
|
-
})))
|
|
830
|
-
})))
|
|
831
|
-
});
|
|
832
|
-
//#endregion
|
|
833
|
-
//#region src/generated/client/schemas/updatedWorkoutSchema.ts
|
|
834
|
-
/**
|
|
835
|
-
* Generated by Kubb (https://kubb.dev/).
|
|
836
|
-
* Do not edit manually.
|
|
837
|
-
*/
|
|
838
|
-
const updatedWorkoutSchema = z$1.object({
|
|
839
|
-
type: z$1.string().describe("Indicates the type of the event (updated)"),
|
|
840
|
-
get workout() {
|
|
841
|
-
return workoutSchema;
|
|
842
|
-
}
|
|
843
|
-
});
|
|
844
|
-
//#endregion
|
|
845
884
|
//#region src/generated/client/schemas/userInfoSchema.ts
|
|
846
885
|
/**
|
|
847
886
|
* Generated by Kubb (https://kubb.dev/).
|
|
@@ -966,7 +1005,16 @@ const formattedBodyMeasurementSchema = z.object({
|
|
|
966
1005
|
const workoutsOutputSchema = { workouts: z.array(formattedWorkoutSchema) };
|
|
967
1006
|
const workoutOutputSchema = { workout: formattedWorkoutSchema.nullable() };
|
|
968
1007
|
const workoutCountOutputSchema = { count: z.number().int() };
|
|
969
|
-
const
|
|
1008
|
+
const formattedUpdatedWorkoutSchema = z.object({
|
|
1009
|
+
type: z.literal("updated"),
|
|
1010
|
+
workout: formattedWorkoutSchema
|
|
1011
|
+
});
|
|
1012
|
+
const formattedDeletedWorkoutSchema = z.object({
|
|
1013
|
+
type: z.literal("deleted"),
|
|
1014
|
+
id: z.string(),
|
|
1015
|
+
deletedAt: z.string().optional()
|
|
1016
|
+
});
|
|
1017
|
+
const workoutEventsOutputSchema = { events: z.array(z.union([formattedUpdatedWorkoutSchema, formattedDeletedWorkoutSchema])) };
|
|
970
1018
|
const routinesOutputSchema = { routines: z.array(formattedRoutineSchema) };
|
|
971
1019
|
const routineOutputSchema = { routine: formattedRoutineSchema.nullable() };
|
|
972
1020
|
const exerciseTemplatesOutputSchema = { exerciseTemplates: z.array(formattedExerciseTemplateSchema) };
|
|
@@ -1074,6 +1122,16 @@ function updateAnnotations(title) {
|
|
|
1074
1122
|
};
|
|
1075
1123
|
}
|
|
1076
1124
|
//#endregion
|
|
1125
|
+
//#region src/utils/tool-descriptions.ts
|
|
1126
|
+
function describeTool({ summary, aliases, useCase, importantNotes }) {
|
|
1127
|
+
return [
|
|
1128
|
+
summary,
|
|
1129
|
+
`Aliases: ${aliases.join(", ")}.`,
|
|
1130
|
+
`<use_case>${useCase}</use_case>`,
|
|
1131
|
+
`<important_notes>${importantNotes}</important_notes>`
|
|
1132
|
+
].join(" ");
|
|
1133
|
+
}
|
|
1134
|
+
//#endregion
|
|
1077
1135
|
//#region src/utils/schemas.ts
|
|
1078
1136
|
function coerceNullishNumberInput(value) {
|
|
1079
1137
|
if (value === null || value === void 0) return value;
|
|
@@ -1196,7 +1254,16 @@ function registerBodyMeasurementTools(server, hevyClient) {
|
|
|
1196
1254
|
pageSize: z.coerce.number().int().gte(1).lte(10).default(10)
|
|
1197
1255
|
};
|
|
1198
1256
|
server.registerTool("get-body-measurements", {
|
|
1199
|
-
description:
|
|
1257
|
+
description: describeTool({
|
|
1258
|
+
summary: "Read-only. Lists dated body measurements for the account.",
|
|
1259
|
+
aliases: [
|
|
1260
|
+
"body stats history",
|
|
1261
|
+
"list weigh-ins",
|
|
1262
|
+
"measurement log"
|
|
1263
|
+
],
|
|
1264
|
+
useCase: "Use to browse measurement history; use get-body-measurement for one exact date.",
|
|
1265
|
+
importantNotes: "Results are paginated; page starts at 1 and pageSize is limited to 10."
|
|
1266
|
+
}),
|
|
1200
1267
|
inputSchema: getBodyMeasurementsSchema,
|
|
1201
1268
|
outputSchema: bodyMeasurementsOutputSchema,
|
|
1202
1269
|
annotations: readOnlyAnnotations("Get Body Measurements")
|
|
@@ -1212,7 +1279,16 @@ function registerBodyMeasurementTools(server, hevyClient) {
|
|
|
1212
1279
|
}, "get-body-measurements"));
|
|
1213
1280
|
const getBodyMeasurementSchema = { date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format").describe("The date of the body measurement (YYYY-MM-DD)") };
|
|
1214
1281
|
server.registerTool("get-body-measurement", {
|
|
1215
|
-
description:
|
|
1282
|
+
description: describeTool({
|
|
1283
|
+
summary: "Read-only. Retrieves the body measurement entry for one date.",
|
|
1284
|
+
aliases: [
|
|
1285
|
+
"get weigh-in",
|
|
1286
|
+
"show body stats",
|
|
1287
|
+
"measurement by date"
|
|
1288
|
+
],
|
|
1289
|
+
useCase: "Use when the exact measurement date is known; use get-body-measurements to browse dates.",
|
|
1290
|
+
importantNotes: "date must use YYYY-MM-DD; at most one entry exists per date."
|
|
1291
|
+
}),
|
|
1216
1292
|
inputSchema: getBodyMeasurementSchema,
|
|
1217
1293
|
outputSchema: bodyMeasurementOutputSchema,
|
|
1218
1294
|
annotations: readOnlyAnnotations("Get Body Measurement")
|
|
@@ -1228,7 +1304,16 @@ function registerBodyMeasurementTools(server, hevyClient) {
|
|
|
1228
1304
|
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format").describe("The date of the body measurement (YYYY-MM-DD). Must be unique — returns 409 if an entry already exists for this date."),
|
|
1229
1305
|
...bodyMeasurementFieldsSchema
|
|
1230
1306
|
};
|
|
1231
|
-
server.tool("create-body-measurement",
|
|
1307
|
+
server.tool("create-body-measurement", describeTool({
|
|
1308
|
+
summary: "Writes to the Hevy account by creating a dated body measurement.",
|
|
1309
|
+
aliases: [
|
|
1310
|
+
"log weigh-in",
|
|
1311
|
+
"add body stats",
|
|
1312
|
+
"record measurements"
|
|
1313
|
+
],
|
|
1314
|
+
useCase: "Use for a date without an entry; use update-body-measurement when that date already exists.",
|
|
1315
|
+
importantNotes: "date must use YYYY-MM-DD and be unique. Null fields are omitted and cannot clear values; an existing date returns 409."
|
|
1316
|
+
}), createBodyMeasurementSchema, createAnnotations("Create Body Measurement"), withObservability(async (args) => {
|
|
1232
1317
|
const client = requireClient(hevyClient);
|
|
1233
1318
|
const { date, ...fields } = args;
|
|
1234
1319
|
await client.createBodyMeasurement({
|
|
@@ -1241,7 +1326,16 @@ function registerBodyMeasurementTools(server, hevyClient) {
|
|
|
1241
1326
|
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format").describe("The date of the body measurement to update (YYYY-MM-DD). Must already exist — returns 404 otherwise."),
|
|
1242
1327
|
...bodyMeasurementFieldsSchema
|
|
1243
1328
|
};
|
|
1244
|
-
server.tool("update-body-measurement",
|
|
1329
|
+
server.tool("update-body-measurement", describeTool({
|
|
1330
|
+
summary: "Mutates the Hevy account by updating a body measurement for a date.",
|
|
1331
|
+
aliases: [
|
|
1332
|
+
"edit weigh-in",
|
|
1333
|
+
"correct body stats",
|
|
1334
|
+
"change measurements"
|
|
1335
|
+
],
|
|
1336
|
+
useCase: "Use to change fields on an existing date; use create-body-measurement for a new date.",
|
|
1337
|
+
importantNotes: "date must use YYYY-MM-DD and already exist. Provide at least one numeric field; nulls are omitted and cannot clear stored values."
|
|
1338
|
+
}), updateBodyMeasurementSchema, updateAnnotations("Update Body Measurement"), withObservability(async (args) => {
|
|
1245
1339
|
const client = requireClient(hevyClient);
|
|
1246
1340
|
const { date, ...fields } = args;
|
|
1247
1341
|
const payload = buildMeasurementPayload(fields);
|
|
@@ -1261,7 +1355,16 @@ function registerFolderTools(server, hevyClient) {
|
|
|
1261
1355
|
pageSize: z.coerce.number().int().gte(1).lte(10).default(5)
|
|
1262
1356
|
};
|
|
1263
1357
|
server.registerTool("get-routine-folders", {
|
|
1264
|
-
description:
|
|
1358
|
+
description: describeTool({
|
|
1359
|
+
summary: "Read-only. Lists default and custom routine folders.",
|
|
1360
|
+
aliases: [
|
|
1361
|
+
"list folders",
|
|
1362
|
+
"browse routine groups",
|
|
1363
|
+
"show plan folders"
|
|
1364
|
+
],
|
|
1365
|
+
useCase: "Use to browse folder organization or discover folder IDs for routine creation.",
|
|
1366
|
+
importantNotes: "Results are paginated; page starts at 1 and pageSize is limited to 10."
|
|
1367
|
+
}),
|
|
1265
1368
|
inputSchema: getRoutineFoldersSchema,
|
|
1266
1369
|
outputSchema: routineFoldersOutputSchema,
|
|
1267
1370
|
annotations: readOnlyAnnotations("Get Routine Folders")
|
|
@@ -1277,7 +1380,16 @@ function registerFolderTools(server, hevyClient) {
|
|
|
1277
1380
|
}, "get-routine-folders"));
|
|
1278
1381
|
const getRoutineFolderSchema = { folderId: z.string().min(1) };
|
|
1279
1382
|
server.registerTool("get-routine-folder", {
|
|
1280
|
-
description:
|
|
1383
|
+
description: describeTool({
|
|
1384
|
+
summary: "Read-only. Retrieves one routine folder's metadata by ID.",
|
|
1385
|
+
aliases: [
|
|
1386
|
+
"show folder",
|
|
1387
|
+
"folder details",
|
|
1388
|
+
"routine folder metadata"
|
|
1389
|
+
],
|
|
1390
|
+
useCase: "Use for one known folder; use get-routine-folders to browse or discover folder IDs.",
|
|
1391
|
+
importantNotes: "Requires a folderId from get-routine-folders or a prior create response."
|
|
1392
|
+
}),
|
|
1281
1393
|
inputSchema: getRoutineFolderSchema,
|
|
1282
1394
|
outputSchema: routineFolderOutputSchema,
|
|
1283
1395
|
annotations: readOnlyAnnotations("Get Routine Folder")
|
|
@@ -1290,7 +1402,16 @@ function registerFolderTools(server, hevyClient) {
|
|
|
1290
1402
|
return createStructuredJsonResponse(folder, { routineFolder: folder });
|
|
1291
1403
|
}, "get-routine-folder"));
|
|
1292
1404
|
const createRoutineFolderSchema = { name: z.string().min(1) };
|
|
1293
|
-
server.tool("create-routine-folder",
|
|
1405
|
+
server.tool("create-routine-folder", describeTool({
|
|
1406
|
+
summary: "Writes to the Hevy account by creating a new routine folder.",
|
|
1407
|
+
aliases: [
|
|
1408
|
+
"add folder",
|
|
1409
|
+
"organize routines",
|
|
1410
|
+
"create plan group"
|
|
1411
|
+
],
|
|
1412
|
+
useCase: "Use to create an organizational destination before assigning new routines to a folderId.",
|
|
1413
|
+
importantNotes: "Requires a non-empty name. Retrying or reusing a name can create duplicate folders."
|
|
1414
|
+
}), createRoutineFolderSchema, createAnnotations("Create Routine Folder"), withObservability(async (args) => {
|
|
1294
1415
|
const client = requireClient(hevyClient);
|
|
1295
1416
|
const { name } = args;
|
|
1296
1417
|
const data = await client.createRoutineFolder({ routine_folder: { title: name } });
|
|
@@ -1355,7 +1476,16 @@ function registerRoutineTools(server, hevyClient) {
|
|
|
1355
1476
|
pageSize: z.coerce.number().int().gte(1).lte(10).default(5)
|
|
1356
1477
|
};
|
|
1357
1478
|
server.registerTool("get-routines", {
|
|
1358
|
-
description:
|
|
1479
|
+
description: describeTool({
|
|
1480
|
+
summary: "Read-only. Lists custom and default workout routines.",
|
|
1481
|
+
aliases: [
|
|
1482
|
+
"list routines",
|
|
1483
|
+
"show workout plans",
|
|
1484
|
+
"browse saved routines"
|
|
1485
|
+
],
|
|
1486
|
+
useCase: "Use to browse routines or discover a routine ID; use get-routine for one known routine.",
|
|
1487
|
+
importantNotes: "Results are paginated; page starts at 1 and pageSize is limited to 10."
|
|
1488
|
+
}),
|
|
1359
1489
|
inputSchema: getRoutinesSchema,
|
|
1360
1490
|
outputSchema: routinesOutputSchema,
|
|
1361
1491
|
annotations: readOnlyAnnotations("Get Routines")
|
|
@@ -1371,7 +1501,16 @@ function registerRoutineTools(server, hevyClient) {
|
|
|
1371
1501
|
}, "get-routines"));
|
|
1372
1502
|
const getRoutineSchema = { routineId: z.string().min(1) };
|
|
1373
1503
|
server.registerTool("get-routine", {
|
|
1374
|
-
description:
|
|
1504
|
+
description: describeTool({
|
|
1505
|
+
summary: "Read-only. Retrieves one routine and its exercise configuration by ID.",
|
|
1506
|
+
aliases: [
|
|
1507
|
+
"show routine",
|
|
1508
|
+
"fetch workout plan",
|
|
1509
|
+
"routine details"
|
|
1510
|
+
],
|
|
1511
|
+
useCase: "Use when the routineId is known; use get-routines to browse or discover IDs.",
|
|
1512
|
+
importantNotes: "Requires a routineId from get-routines or a prior create response."
|
|
1513
|
+
}),
|
|
1375
1514
|
inputSchema: getRoutineSchema,
|
|
1376
1515
|
outputSchema: routineOutputSchema,
|
|
1377
1516
|
annotations: readOnlyAnnotations("Get Routine")
|
|
@@ -1406,7 +1545,16 @@ function registerRoutineTools(server, hevyClient) {
|
|
|
1406
1545
|
}))
|
|
1407
1546
|
})))
|
|
1408
1547
|
};
|
|
1409
|
-
server.tool("create-routine",
|
|
1548
|
+
server.tool("create-routine", describeTool({
|
|
1549
|
+
summary: "Writes to the Hevy account by creating a new workout routine.",
|
|
1550
|
+
aliases: [
|
|
1551
|
+
"add routine",
|
|
1552
|
+
"build workout plan",
|
|
1553
|
+
"save training template"
|
|
1554
|
+
],
|
|
1555
|
+
useCase: "Use to create a reusable plan; use create-workout to log a completed session.",
|
|
1556
|
+
importantNotes: "Requires exercise template IDs; folderId is optional. Retrying can create duplicates, and non-fixed rep ranges may not display in Hevy apps."
|
|
1557
|
+
}), createRoutineSchema, createAnnotations("Create Routine"), withObservability(async (args) => {
|
|
1410
1558
|
const client = requireClient(hevyClient);
|
|
1411
1559
|
const { title, folderId, notes, exercises } = args;
|
|
1412
1560
|
let usesRepRanges = false;
|
|
@@ -1473,7 +1621,16 @@ function registerRoutineTools(server, hevyClient) {
|
|
|
1473
1621
|
}))
|
|
1474
1622
|
})))
|
|
1475
1623
|
};
|
|
1476
|
-
server.tool("update-routine",
|
|
1624
|
+
server.tool("update-routine", describeTool({
|
|
1625
|
+
summary: "Mutates the Hevy account by replacing an existing routine's content.",
|
|
1626
|
+
aliases: [
|
|
1627
|
+
"edit routine",
|
|
1628
|
+
"revise workout plan",
|
|
1629
|
+
"replace routine exercises"
|
|
1630
|
+
],
|
|
1631
|
+
useCase: "Use to change a known routine; use create-routine for a separate new plan.",
|
|
1632
|
+
importantNotes: "Requires routineId and the complete title and exercises payload; omitted exercises are removed. Non-fixed rep ranges may not display in Hevy apps."
|
|
1633
|
+
}), updateRoutineSchema, updateAnnotations("Update Routine"), withObservability(async (args) => {
|
|
1477
1634
|
const client = requireClient(hevyClient);
|
|
1478
1635
|
const { routineId, title, notes, exercises } = args;
|
|
1479
1636
|
let usesRepRanges = false;
|
|
@@ -1529,7 +1686,16 @@ function registerTemplateTools(server, hevyClient, options = {}) {
|
|
|
1529
1686
|
pageSize: z.coerce.number().int().gte(1).lte(100).default(5)
|
|
1530
1687
|
};
|
|
1531
1688
|
server.registerTool("get-exercise-templates", {
|
|
1532
|
-
description:
|
|
1689
|
+
description: describeTool({
|
|
1690
|
+
summary: "Read-only. Lists default and custom exercise templates with equipment and muscle metadata.",
|
|
1691
|
+
aliases: [
|
|
1692
|
+
"browse exercises",
|
|
1693
|
+
"list exercise catalog",
|
|
1694
|
+
"show movements"
|
|
1695
|
+
],
|
|
1696
|
+
useCase: "Use for page-by-page catalog browsing; use search-exercise-templates for a name lookup across the full catalog.",
|
|
1697
|
+
importantNotes: "Results are paginated; page starts at 1 and pageSize is limited to 100."
|
|
1698
|
+
}),
|
|
1533
1699
|
inputSchema: getExerciseTemplatesSchema,
|
|
1534
1700
|
outputSchema: exerciseTemplatesOutputSchema,
|
|
1535
1701
|
annotations: readOnlyAnnotations("Get Exercise Templates")
|
|
@@ -1545,7 +1711,16 @@ function registerTemplateTools(server, hevyClient, options = {}) {
|
|
|
1545
1711
|
}, "get-exercise-templates"));
|
|
1546
1712
|
const getExerciseTemplateSchema = { exerciseTemplateId: z.string().min(1) };
|
|
1547
1713
|
server.registerTool("get-exercise-template", {
|
|
1548
|
-
description:
|
|
1714
|
+
description: describeTool({
|
|
1715
|
+
summary: "Read-only. Retrieves complete metadata for one exercise template by ID.",
|
|
1716
|
+
aliases: [
|
|
1717
|
+
"show exercise details",
|
|
1718
|
+
"fetch movement",
|
|
1719
|
+
"exercise template info"
|
|
1720
|
+
],
|
|
1721
|
+
useCase: "Use after locating an exact template; use search-exercise-templates when only a name is known.",
|
|
1722
|
+
importantNotes: "Requires an exerciseTemplateId from a template list, search, routine, or workout."
|
|
1723
|
+
}),
|
|
1549
1724
|
inputSchema: getExerciseTemplateSchema,
|
|
1550
1725
|
outputSchema: exerciseTemplateOutputSchema,
|
|
1551
1726
|
annotations: readOnlyAnnotations("Get Exercise Template")
|
|
@@ -1563,7 +1738,16 @@ function registerTemplateTools(server, hevyClient, options = {}) {
|
|
|
1563
1738
|
endDate: z.string().datetime({ offset: true }).describe("ISO 8601 end date for filtering history").optional()
|
|
1564
1739
|
};
|
|
1565
1740
|
server.registerTool("get-exercise-history", {
|
|
1566
|
-
description:
|
|
1741
|
+
description: describeTool({
|
|
1742
|
+
summary: "Read-only. Retrieves past performed sets for one exercise template.",
|
|
1743
|
+
aliases: [
|
|
1744
|
+
"exercise progress",
|
|
1745
|
+
"past sets",
|
|
1746
|
+
"movement history"
|
|
1747
|
+
],
|
|
1748
|
+
useCase: "Use to analyze performance for one movement; use get-workouts for complete sessions.",
|
|
1749
|
+
importantNotes: "Requires an exerciseTemplateId. Optional startDate and endDate must be ISO 8601 datetimes with an offset."
|
|
1750
|
+
}),
|
|
1567
1751
|
inputSchema: getExerciseHistorySchema,
|
|
1568
1752
|
outputSchema: exerciseHistoryOutputSchema,
|
|
1569
1753
|
annotations: readOnlyAnnotations("Get Exercise History")
|
|
@@ -1584,7 +1768,16 @@ function registerTemplateTools(server, hevyClient, options = {}) {
|
|
|
1584
1768
|
muscleGroup: muscleGroupEnum,
|
|
1585
1769
|
otherMuscles: z.array(muscleGroupEnum).default([])
|
|
1586
1770
|
};
|
|
1587
|
-
server.tool("create-exercise-template",
|
|
1771
|
+
server.tool("create-exercise-template", describeTool({
|
|
1772
|
+
summary: "Writes to the Hevy account by creating a custom exercise template.",
|
|
1773
|
+
aliases: [
|
|
1774
|
+
"add custom exercise",
|
|
1775
|
+
"create movement",
|
|
1776
|
+
"define exercise"
|
|
1777
|
+
],
|
|
1778
|
+
useCase: "Use only when the needed movement is absent; search-exercise-templates should check existing templates first.",
|
|
1779
|
+
importantNotes: "Requires title, exercise type, equipment category, and primary muscle group. Retrying or reusing a title can create duplicates."
|
|
1780
|
+
}), createExerciseTemplateSchema, createAnnotations("Create Exercise Template"), withObservability(async (args) => {
|
|
1588
1781
|
const client = requireClient(hevyClient);
|
|
1589
1782
|
const { title, exerciseType, equipmentCategory, muscleGroup, otherMuscles } = args;
|
|
1590
1783
|
return createJsonResponse({
|
|
@@ -1604,7 +1797,16 @@ function registerTemplateTools(server, hevyClient, options = {}) {
|
|
|
1604
1797
|
refresh: z.boolean().optional().default(false).describe("Set to true to invalidate the catalog cache and re-fetch all templates from the API")
|
|
1605
1798
|
};
|
|
1606
1799
|
server.registerTool("search-exercise-templates", {
|
|
1607
|
-
description:
|
|
1800
|
+
description: describeTool({
|
|
1801
|
+
summary: "Read-only for the Hevy account. Searches the full exercise template catalog by title substring.",
|
|
1802
|
+
aliases: [
|
|
1803
|
+
"find exercise",
|
|
1804
|
+
"look up movement",
|
|
1805
|
+
"search exercise IDs"
|
|
1806
|
+
],
|
|
1807
|
+
useCase: "Use when a name or partial name is known, especially to discover IDs for workouts and routines; use get-exercise-templates for page browsing.",
|
|
1808
|
+
importantNotes: "Matching is case-insensitive. The catalog is cached locally for 5 minutes; refresh:true re-fetches all pages and changes only local cache state."
|
|
1809
|
+
}),
|
|
1608
1810
|
inputSchema: searchExerciseTemplatesSchema,
|
|
1609
1811
|
outputSchema: exerciseTemplatesOutputSchema,
|
|
1610
1812
|
annotations: readOnlyAnnotations("Search Exercise Templates")
|
|
@@ -1641,7 +1843,16 @@ function registerTemplateTools(server, hevyClient, options = {}) {
|
|
|
1641
1843
|
//#region src/tools/user.ts
|
|
1642
1844
|
function registerUserTools(server, hevyClient) {
|
|
1643
1845
|
server.registerTool("get-user-info", {
|
|
1644
|
-
description:
|
|
1846
|
+
description: describeTool({
|
|
1847
|
+
summary: "Read-only. Returns the authenticated account's user ID, display name, and public profile URL.",
|
|
1848
|
+
aliases: [
|
|
1849
|
+
"who am I",
|
|
1850
|
+
"account info",
|
|
1851
|
+
"verify Hevy user"
|
|
1852
|
+
],
|
|
1853
|
+
useCase: "Use to confirm which Hevy account is connected before reading or writing account data.",
|
|
1854
|
+
importantNotes: "Accepts no inputs and reports only the account associated with the configured credentials."
|
|
1855
|
+
}),
|
|
1645
1856
|
inputSchema: {},
|
|
1646
1857
|
outputSchema: userOutputSchema,
|
|
1647
1858
|
annotations: readOnlyAnnotations("Get User Info")
|
|
@@ -1653,6 +1864,18 @@ function registerUserTools(server, hevyClient) {
|
|
|
1653
1864
|
}
|
|
1654
1865
|
//#endregion
|
|
1655
1866
|
//#region src/tools/workouts.ts
|
|
1867
|
+
function formatWorkoutEvent(event) {
|
|
1868
|
+
if (event.type === "updated" && "workout" in event) return {
|
|
1869
|
+
type: "updated",
|
|
1870
|
+
workout: formatWorkout(event.workout)
|
|
1871
|
+
};
|
|
1872
|
+
if (event.type === "deleted" && "id" in event) return {
|
|
1873
|
+
type: "deleted",
|
|
1874
|
+
id: event.id,
|
|
1875
|
+
deletedAt: event.deleted_at
|
|
1876
|
+
};
|
|
1877
|
+
throw new Error(`Unsupported workout event type: ${event.type}`);
|
|
1878
|
+
}
|
|
1656
1879
|
/**
|
|
1657
1880
|
* Register all workout-related tools with the MCP server
|
|
1658
1881
|
*/
|
|
@@ -1662,7 +1885,16 @@ function registerWorkoutTools(server, hevyClient) {
|
|
|
1662
1885
|
pageSize: z.coerce.number().int().gte(1).lte(10).default(5)
|
|
1663
1886
|
};
|
|
1664
1887
|
server.registerTool("get-workouts", {
|
|
1665
|
-
description:
|
|
1888
|
+
description: describeTool({
|
|
1889
|
+
summary: "Read-only. Lists workouts from newest to oldest with exercise and timing details.",
|
|
1890
|
+
aliases: [
|
|
1891
|
+
"list workout history",
|
|
1892
|
+
"show recent workouts",
|
|
1893
|
+
"browse logs"
|
|
1894
|
+
],
|
|
1895
|
+
useCase: "Use to browse or page through workout history; use get-workout when a workout ID is already known.",
|
|
1896
|
+
importantNotes: "Results are paginated; page starts at 1 and pageSize is limited to 10."
|
|
1897
|
+
}),
|
|
1666
1898
|
inputSchema: getWorkoutsSchema,
|
|
1667
1899
|
outputSchema: workoutsOutputSchema,
|
|
1668
1900
|
annotations: readOnlyAnnotations("Get Workouts")
|
|
@@ -1678,7 +1910,16 @@ function registerWorkoutTools(server, hevyClient) {
|
|
|
1678
1910
|
}, "get-workouts"));
|
|
1679
1911
|
const getWorkoutSchema = { workoutId: z.string().min(1) };
|
|
1680
1912
|
server.registerTool("get-workout", {
|
|
1681
|
-
description:
|
|
1913
|
+
description: describeTool({
|
|
1914
|
+
summary: "Read-only. Retrieves complete details for one workout by its ID.",
|
|
1915
|
+
aliases: [
|
|
1916
|
+
"show workout",
|
|
1917
|
+
"fetch workout details",
|
|
1918
|
+
"open workout log"
|
|
1919
|
+
],
|
|
1920
|
+
useCase: "Use after get-workouts identifies the exact workout; do not use for browsing multiple workouts.",
|
|
1921
|
+
importantNotes: "Requires a workoutId discovered from a workout list, event, or prior create response."
|
|
1922
|
+
}),
|
|
1682
1923
|
inputSchema: getWorkoutSchema,
|
|
1683
1924
|
outputSchema: workoutOutputSchema,
|
|
1684
1925
|
annotations: readOnlyAnnotations("Get Workout")
|
|
@@ -1691,7 +1932,16 @@ function registerWorkoutTools(server, hevyClient) {
|
|
|
1691
1932
|
return createStructuredJsonResponse(workout, { workout });
|
|
1692
1933
|
}, "get-workout"));
|
|
1693
1934
|
server.registerTool("get-workout-count", {
|
|
1694
|
-
description:
|
|
1935
|
+
description: describeTool({
|
|
1936
|
+
summary: "Read-only. Returns the total workout count for the account.",
|
|
1937
|
+
aliases: [
|
|
1938
|
+
"count workouts",
|
|
1939
|
+
"how many workouts",
|
|
1940
|
+
"workout total"
|
|
1941
|
+
],
|
|
1942
|
+
useCase: "Use for totals, statistics, or estimating pages; use get-workouts for actual workout records.",
|
|
1943
|
+
importantNotes: "Returns only a count and accepts no paging or date filters."
|
|
1944
|
+
}),
|
|
1695
1945
|
inputSchema: {},
|
|
1696
1946
|
outputSchema: workoutCountOutputSchema,
|
|
1697
1947
|
annotations: readOnlyAnnotations("Get Workout Count")
|
|
@@ -1705,7 +1955,16 @@ function registerWorkoutTools(server, hevyClient) {
|
|
|
1705
1955
|
since: z.string().default("1970-01-01T00:00:00Z")
|
|
1706
1956
|
};
|
|
1707
1957
|
server.registerTool("get-workout-events", {
|
|
1708
|
-
description:
|
|
1958
|
+
description: describeTool({
|
|
1959
|
+
summary: "Read-only. Lists workout update and delete events since a timestamp, newest first.",
|
|
1960
|
+
aliases: [
|
|
1961
|
+
"sync workout changes",
|
|
1962
|
+
"workout change feed",
|
|
1963
|
+
"deleted workouts"
|
|
1964
|
+
],
|
|
1965
|
+
useCase: "Use to incrementally synchronize a local workout cache; use get-workouts for the current workout list.",
|
|
1966
|
+
importantNotes: "since must be a timestamp string; events are paginated with pageSize at most 10, and the default since value reads from 1970."
|
|
1967
|
+
}),
|
|
1709
1968
|
inputSchema: getWorkoutEventsSchema,
|
|
1710
1969
|
outputSchema: workoutEventsOutputSchema,
|
|
1711
1970
|
annotations: readOnlyAnnotations("Get Workout Events")
|
|
@@ -1716,7 +1975,7 @@ function registerWorkoutTools(server, hevyClient) {
|
|
|
1716
1975
|
page,
|
|
1717
1976
|
pageSize,
|
|
1718
1977
|
since
|
|
1719
|
-
}))?.events || [];
|
|
1978
|
+
}))?.events?.map(formatWorkoutEvent) || [];
|
|
1720
1979
|
if (events.length === 0) return createStructuredEmptyResponse(`No workout events found for the specified parameters since ${since}`, { events: [] });
|
|
1721
1980
|
return createStructuredJsonResponse(events, { events });
|
|
1722
1981
|
}, "get-workout-events"));
|
|
@@ -1744,7 +2003,16 @@ function registerWorkoutTools(server, hevyClient) {
|
|
|
1744
2003
|
}))
|
|
1745
2004
|
})))
|
|
1746
2005
|
};
|
|
1747
|
-
server.tool("create-workout",
|
|
2006
|
+
server.tool("create-workout", describeTool({
|
|
2007
|
+
summary: "Writes to the Hevy account by creating a new workout.",
|
|
2008
|
+
aliases: [
|
|
2009
|
+
"log workout",
|
|
2010
|
+
"add workout",
|
|
2011
|
+
"record training session"
|
|
2012
|
+
],
|
|
2013
|
+
useCase: "Use to add a completed workout; use update-workout only when modifying an existing workout ID.",
|
|
2014
|
+
importantNotes: "Requires UTC startTime/endTime in YYYY-MM-DDTHH:mm:ssZ form and exercise template IDs. Retrying can create duplicates."
|
|
2015
|
+
}), createWorkoutSchema, createAnnotations("Create Workout"), withObservability(async (args) => {
|
|
1748
2016
|
const client = requireClient(hevyClient);
|
|
1749
2017
|
const { title, description, startTime, endTime, isPrivate, exercises } = args;
|
|
1750
2018
|
const requestBody = { workout: {
|
|
@@ -1800,7 +2068,16 @@ function registerWorkoutTools(server, hevyClient) {
|
|
|
1800
2068
|
}))
|
|
1801
2069
|
})))
|
|
1802
2070
|
};
|
|
1803
|
-
server.tool("update-workout",
|
|
2071
|
+
server.tool("update-workout", describeTool({
|
|
2072
|
+
summary: "Mutates the Hevy account by replacing an existing workout.",
|
|
2073
|
+
aliases: [
|
|
2074
|
+
"edit workout",
|
|
2075
|
+
"correct workout log",
|
|
2076
|
+
"replace workout details"
|
|
2077
|
+
],
|
|
2078
|
+
useCase: "Use to revise a known workout; use create-workout for a new training session.",
|
|
2079
|
+
importantNotes: "Requires workoutId plus the complete title, times, privacy, exercises, and sets payload; omitted optional values may be cleared or defaulted."
|
|
2080
|
+
}), updateWorkoutSchema, updateAnnotations("Update Workout"), withObservability(async (args) => {
|
|
1804
2081
|
const client = requireClient(hevyClient);
|
|
1805
2082
|
const { workoutId, title, description, startTime, endTime, isPrivate, exercises } = args;
|
|
1806
2083
|
const requestBody = { workout: {
|
|
@@ -1878,6 +2155,81 @@ function assertApiKey(apiKey) {
|
|
|
1878
2155
|
}
|
|
1879
2156
|
}
|
|
1880
2157
|
//#endregion
|
|
2158
|
+
//#region src/utils/graceful-shutdown.ts
|
|
2159
|
+
const shutdownSignals = ["SIGINT", "SIGTERM"];
|
|
2160
|
+
const FORCED_EXIT_TIMEOUT_MS = 5e3;
|
|
2161
|
+
function flushStdout(stdout = process.stdout) {
|
|
2162
|
+
return new Promise((resolve, reject) => {
|
|
2163
|
+
try {
|
|
2164
|
+
stdout.write("", (error) => {
|
|
2165
|
+
if (error) {
|
|
2166
|
+
reject(error);
|
|
2167
|
+
return;
|
|
2168
|
+
}
|
|
2169
|
+
resolve();
|
|
2170
|
+
});
|
|
2171
|
+
} catch (error) {
|
|
2172
|
+
reject(error);
|
|
2173
|
+
}
|
|
2174
|
+
});
|
|
2175
|
+
}
|
|
2176
|
+
function installGracefulShutdown({ target, process: processLike = process, logError = console.error, flush = flushStdout, forcedExitTimeoutMs = FORCED_EXIT_TIMEOUT_MS, scheduleForcedExit = setTimeout }) {
|
|
2177
|
+
let listenersInstalled = true;
|
|
2178
|
+
let shutdownSettled = false;
|
|
2179
|
+
let shutdownPromise;
|
|
2180
|
+
const cleanup = () => {
|
|
2181
|
+
if (!listenersInstalled || shutdownPromise && !shutdownSettled) return;
|
|
2182
|
+
listenersInstalled = false;
|
|
2183
|
+
for (const signal of shutdownSignals) {
|
|
2184
|
+
const listener = signalListeners.get(signal);
|
|
2185
|
+
if (listener) processLike.removeListener(signal, listener);
|
|
2186
|
+
}
|
|
2187
|
+
};
|
|
2188
|
+
const handleSignal = (signal) => {
|
|
2189
|
+
if (shutdownPromise) return;
|
|
2190
|
+
if (processLike.exitCode == null) processLike.exitCode = 0;
|
|
2191
|
+
scheduleForcedExit(() => {
|
|
2192
|
+
processLike.exit(processLike.exitCode ?? 0);
|
|
2193
|
+
}, forcedExitTimeoutMs).unref();
|
|
2194
|
+
shutdownPromise = (async () => {
|
|
2195
|
+
logError(`Shutting down gracefully after ${signal}`);
|
|
2196
|
+
let shutdownFailed = false;
|
|
2197
|
+
let shutdownError;
|
|
2198
|
+
try {
|
|
2199
|
+
await target.close();
|
|
2200
|
+
} catch (error) {
|
|
2201
|
+
shutdownFailed = true;
|
|
2202
|
+
shutdownError = error;
|
|
2203
|
+
}
|
|
2204
|
+
try {
|
|
2205
|
+
await flush();
|
|
2206
|
+
} catch (error) {
|
|
2207
|
+
if (!shutdownFailed) shutdownError = error;
|
|
2208
|
+
shutdownFailed = true;
|
|
2209
|
+
}
|
|
2210
|
+
try {
|
|
2211
|
+
if (shutdownFailed) {
|
|
2212
|
+
logError(`Graceful shutdown failed: ${shutdownError instanceof Error ? shutdownError.message : "Unknown shutdown error"}`);
|
|
2213
|
+
processLike.exitCode = 1;
|
|
2214
|
+
return;
|
|
2215
|
+
}
|
|
2216
|
+
} finally {
|
|
2217
|
+
shutdownSettled = true;
|
|
2218
|
+
cleanup();
|
|
2219
|
+
}
|
|
2220
|
+
})();
|
|
2221
|
+
};
|
|
2222
|
+
const signalListeners = new Map(shutdownSignals.map((signal) => [signal, () => handleSignal(signal)]));
|
|
2223
|
+
for (const signal of shutdownSignals) {
|
|
2224
|
+
const listener = signalListeners.get(signal);
|
|
2225
|
+
if (listener) processLike.on(signal, listener);
|
|
2226
|
+
}
|
|
2227
|
+
return {
|
|
2228
|
+
cleanup,
|
|
2229
|
+
getShutdownPromise: () => shutdownPromise
|
|
2230
|
+
};
|
|
2231
|
+
}
|
|
2232
|
+
//#endregion
|
|
1881
2233
|
//#region src/generated/.kubb/fetch.ts
|
|
1882
2234
|
let _config = {
|
|
1883
2235
|
baseURL: typeof AXIOS_BASE !== "undefined" ? AXIOS_BASE : void 0,
|
|
@@ -1901,13 +2253,13 @@ const mergeConfig = (...configs) => {
|
|
|
1901
2253
|
}, {});
|
|
1902
2254
|
};
|
|
1903
2255
|
const axiosInstance = axios.create(getConfig());
|
|
1904
|
-
const fetch = async (config) => {
|
|
2256
|
+
const fetch$1 = async (config) => {
|
|
1905
2257
|
return axiosInstance.request(mergeConfig(getConfig(), config)).catch((e) => {
|
|
1906
2258
|
throw e;
|
|
1907
2259
|
});
|
|
1908
2260
|
};
|
|
1909
|
-
fetch.getConfig = getConfig;
|
|
1910
|
-
fetch.setConfig = setConfig;
|
|
2261
|
+
fetch$1.getConfig = getConfig;
|
|
2262
|
+
fetch$1.setConfig = setConfig;
|
|
1911
2263
|
//#endregion
|
|
1912
2264
|
//#region src/generated/client/api/getV1BodyMeasurements.ts
|
|
1913
2265
|
function getGetV1BodyMeasurementsUrl() {
|
|
@@ -1921,7 +2273,7 @@ function getGetV1BodyMeasurementsUrl() {
|
|
|
1921
2273
|
* {@link /v1/body_measurements}
|
|
1922
2274
|
*/
|
|
1923
2275
|
async function getV1BodyMeasurements(headers, params, config = {}) {
|
|
1924
|
-
const { client: request = fetch, ...requestConfig } = config;
|
|
2276
|
+
const { client: request = fetch$1, ...requestConfig } = config;
|
|
1925
2277
|
return (await request({
|
|
1926
2278
|
method: "GET",
|
|
1927
2279
|
url: getGetV1BodyMeasurementsUrl().url.toString(),
|
|
@@ -1946,7 +2298,7 @@ function getGetV1BodyMeasurementsDateUrl(date) {
|
|
|
1946
2298
|
* {@link /v1/body_measurements/:date}
|
|
1947
2299
|
*/
|
|
1948
2300
|
async function getV1BodyMeasurementsDate(date, headers, config = {}) {
|
|
1949
|
-
const { client: request = fetch, ...requestConfig } = config;
|
|
2301
|
+
const { client: request = fetch$1, ...requestConfig } = config;
|
|
1950
2302
|
return (await request({
|
|
1951
2303
|
method: "GET",
|
|
1952
2304
|
url: getGetV1BodyMeasurementsDateUrl(date).url.toString(),
|
|
@@ -1970,7 +2322,7 @@ function getGetV1ExerciseHistoryExercisetemplateidUrl(exerciseTemplateId) {
|
|
|
1970
2322
|
* {@link /v1/exercise_history/:exerciseTemplateId}
|
|
1971
2323
|
*/
|
|
1972
2324
|
async function getV1ExerciseHistoryExercisetemplateid(exerciseTemplateId, headers, params, config = {}) {
|
|
1973
|
-
const { client: request = fetch, ...requestConfig } = config;
|
|
2325
|
+
const { client: request = fetch$1, ...requestConfig } = config;
|
|
1974
2326
|
return (await request({
|
|
1975
2327
|
method: "GET",
|
|
1976
2328
|
url: getGetV1ExerciseHistoryExercisetemplateidUrl(exerciseTemplateId).url.toString(),
|
|
@@ -1995,7 +2347,7 @@ function getGetV1ExerciseTemplatesUrl() {
|
|
|
1995
2347
|
* {@link /v1/exercise_templates}
|
|
1996
2348
|
*/
|
|
1997
2349
|
async function getV1ExerciseTemplates(headers, params, config = {}) {
|
|
1998
|
-
const { client: request = fetch, ...requestConfig } = config;
|
|
2350
|
+
const { client: request = fetch$1, ...requestConfig } = config;
|
|
1999
2351
|
return (await request({
|
|
2000
2352
|
method: "GET",
|
|
2001
2353
|
url: getGetV1ExerciseTemplatesUrl().url.toString(),
|
|
@@ -2020,7 +2372,7 @@ function getGetV1ExerciseTemplatesExercisetemplateidUrl(exerciseTemplateId) {
|
|
|
2020
2372
|
* {@link /v1/exercise_templates/:exerciseTemplateId}
|
|
2021
2373
|
*/
|
|
2022
2374
|
async function getV1ExerciseTemplatesExercisetemplateid(exerciseTemplateId, headers, config = {}) {
|
|
2023
|
-
const { client: request = fetch, ...requestConfig } = config;
|
|
2375
|
+
const { client: request = fetch$1, ...requestConfig } = config;
|
|
2024
2376
|
return (await request({
|
|
2025
2377
|
method: "GET",
|
|
2026
2378
|
url: getGetV1ExerciseTemplatesExercisetemplateidUrl(exerciseTemplateId).url.toString(),
|
|
@@ -2044,7 +2396,7 @@ function getGetV1RoutineFoldersUrl() {
|
|
|
2044
2396
|
* {@link /v1/routine_folders}
|
|
2045
2397
|
*/
|
|
2046
2398
|
async function getV1RoutineFolders(headers, params, config = {}) {
|
|
2047
|
-
const { client: request = fetch, ...requestConfig } = config;
|
|
2399
|
+
const { client: request = fetch$1, ...requestConfig } = config;
|
|
2048
2400
|
return (await request({
|
|
2049
2401
|
method: "GET",
|
|
2050
2402
|
url: getGetV1RoutineFoldersUrl().url.toString(),
|
|
@@ -2069,7 +2421,7 @@ function getGetV1RoutineFoldersFolderidUrl(folderId) {
|
|
|
2069
2421
|
* {@link /v1/routine_folders/:folderId}
|
|
2070
2422
|
*/
|
|
2071
2423
|
async function getV1RoutineFoldersFolderid(folderId, headers, config = {}) {
|
|
2072
|
-
const { client: request = fetch, ...requestConfig } = config;
|
|
2424
|
+
const { client: request = fetch$1, ...requestConfig } = config;
|
|
2073
2425
|
return (await request({
|
|
2074
2426
|
method: "GET",
|
|
2075
2427
|
url: getGetV1RoutineFoldersFolderidUrl(folderId).url.toString(),
|
|
@@ -2093,7 +2445,7 @@ function getGetV1RoutinesUrl() {
|
|
|
2093
2445
|
* {@link /v1/routines}
|
|
2094
2446
|
*/
|
|
2095
2447
|
async function getV1Routines(headers, params, config = {}) {
|
|
2096
|
-
const { client: request = fetch, ...requestConfig } = config;
|
|
2448
|
+
const { client: request = fetch$1, ...requestConfig } = config;
|
|
2097
2449
|
return (await request({
|
|
2098
2450
|
method: "GET",
|
|
2099
2451
|
url: getGetV1RoutinesUrl().url.toString(),
|
|
@@ -2118,7 +2470,7 @@ function getGetV1RoutinesRoutineidUrl(routineId) {
|
|
|
2118
2470
|
* {@link /v1/routines/:routineId}
|
|
2119
2471
|
*/
|
|
2120
2472
|
async function getV1RoutinesRoutineid(routineId, headers, config = {}) {
|
|
2121
|
-
const { client: request = fetch, ...requestConfig } = config;
|
|
2473
|
+
const { client: request = fetch$1, ...requestConfig } = config;
|
|
2122
2474
|
return (await request({
|
|
2123
2475
|
method: "GET",
|
|
2124
2476
|
url: getGetV1RoutinesRoutineidUrl(routineId).url.toString(),
|
|
@@ -2142,7 +2494,7 @@ function getGetV1UserInfoUrl() {
|
|
|
2142
2494
|
* {@link /v1/user/info}
|
|
2143
2495
|
*/
|
|
2144
2496
|
async function getV1UserInfo(headers, config = {}) {
|
|
2145
|
-
const { client: request = fetch, ...requestConfig } = config;
|
|
2497
|
+
const { client: request = fetch$1, ...requestConfig } = config;
|
|
2146
2498
|
return (await request({
|
|
2147
2499
|
method: "GET",
|
|
2148
2500
|
url: getGetV1UserInfoUrl().url.toString(),
|
|
@@ -2166,7 +2518,7 @@ function getGetV1WorkoutsUrl() {
|
|
|
2166
2518
|
* {@link /v1/workouts}
|
|
2167
2519
|
*/
|
|
2168
2520
|
async function getV1Workouts(headers, params, config = {}) {
|
|
2169
|
-
const { client: request = fetch, ...requestConfig } = config;
|
|
2521
|
+
const { client: request = fetch$1, ...requestConfig } = config;
|
|
2170
2522
|
return (await request({
|
|
2171
2523
|
method: "GET",
|
|
2172
2524
|
url: getGetV1WorkoutsUrl().url.toString(),
|
|
@@ -2191,7 +2543,7 @@ function getGetV1WorkoutsCountUrl() {
|
|
|
2191
2543
|
* {@link /v1/workouts/count}
|
|
2192
2544
|
*/
|
|
2193
2545
|
async function getV1WorkoutsCount(headers, config = {}) {
|
|
2194
|
-
const { client: request = fetch, ...requestConfig } = config;
|
|
2546
|
+
const { client: request = fetch$1, ...requestConfig } = config;
|
|
2195
2547
|
return (await request({
|
|
2196
2548
|
method: "GET",
|
|
2197
2549
|
url: getGetV1WorkoutsCountUrl().url.toString(),
|
|
@@ -2216,7 +2568,7 @@ function getGetV1WorkoutsEventsUrl() {
|
|
|
2216
2568
|
* {@link /v1/workouts/events}
|
|
2217
2569
|
*/
|
|
2218
2570
|
async function getV1WorkoutsEvents(headers, params, config = {}) {
|
|
2219
|
-
const { client: request = fetch, ...requestConfig } = config;
|
|
2571
|
+
const { client: request = fetch$1, ...requestConfig } = config;
|
|
2220
2572
|
return (await request({
|
|
2221
2573
|
method: "GET",
|
|
2222
2574
|
url: getGetV1WorkoutsEventsUrl().url.toString(),
|
|
@@ -2241,7 +2593,7 @@ function getGetV1WorkoutsWorkoutidUrl(workoutId) {
|
|
|
2241
2593
|
* {@link /v1/workouts/:workoutId}
|
|
2242
2594
|
*/
|
|
2243
2595
|
async function getV1WorkoutsWorkoutid(workoutId, headers, config = {}) {
|
|
2244
|
-
const { client: request = fetch, ...requestConfig } = config;
|
|
2596
|
+
const { client: request = fetch$1, ...requestConfig } = config;
|
|
2245
2597
|
return (await request({
|
|
2246
2598
|
method: "GET",
|
|
2247
2599
|
url: getGetV1WorkoutsWorkoutidUrl(workoutId).url.toString(),
|
|
@@ -2265,7 +2617,7 @@ function getPostV1BodyMeasurementsUrl() {
|
|
|
2265
2617
|
* {@link /v1/body_measurements}
|
|
2266
2618
|
*/
|
|
2267
2619
|
async function postV1BodyMeasurements(data, headers, config = {}) {
|
|
2268
|
-
const { client: request = fetch, ...requestConfig } = config;
|
|
2620
|
+
const { client: request = fetch$1, ...requestConfig } = config;
|
|
2269
2621
|
const requestData = data;
|
|
2270
2622
|
return (await request({
|
|
2271
2623
|
method: "POST",
|
|
@@ -2291,7 +2643,7 @@ function getPostV1ExerciseTemplatesUrl() {
|
|
|
2291
2643
|
* {@link /v1/exercise_templates}
|
|
2292
2644
|
*/
|
|
2293
2645
|
async function postV1ExerciseTemplates(data, headers, config = {}) {
|
|
2294
|
-
const { client: request = fetch, ...requestConfig } = config;
|
|
2646
|
+
const { client: request = fetch$1, ...requestConfig } = config;
|
|
2295
2647
|
const requestData = data;
|
|
2296
2648
|
return (await request({
|
|
2297
2649
|
method: "POST",
|
|
@@ -2317,7 +2669,7 @@ function getPostV1RoutineFoldersUrl() {
|
|
|
2317
2669
|
* {@link /v1/routine_folders}
|
|
2318
2670
|
*/
|
|
2319
2671
|
async function postV1RoutineFolders(data, headers, config = {}) {
|
|
2320
|
-
const { client: request = fetch, ...requestConfig } = config;
|
|
2672
|
+
const { client: request = fetch$1, ...requestConfig } = config;
|
|
2321
2673
|
const requestData = data;
|
|
2322
2674
|
return (await request({
|
|
2323
2675
|
method: "POST",
|
|
@@ -2343,7 +2695,7 @@ function getPostV1RoutinesUrl() {
|
|
|
2343
2695
|
* {@link /v1/routines}
|
|
2344
2696
|
*/
|
|
2345
2697
|
async function postV1Routines(data, headers, config = {}) {
|
|
2346
|
-
const { client: request = fetch, ...requestConfig } = config;
|
|
2698
|
+
const { client: request = fetch$1, ...requestConfig } = config;
|
|
2347
2699
|
const requestData = data;
|
|
2348
2700
|
return (await request({
|
|
2349
2701
|
method: "POST",
|
|
@@ -2369,7 +2721,7 @@ function getPostV1WorkoutsUrl() {
|
|
|
2369
2721
|
* {@link /v1/workouts}
|
|
2370
2722
|
*/
|
|
2371
2723
|
async function postV1Workouts(data, headers, config = {}) {
|
|
2372
|
-
const { client: request = fetch, ...requestConfig } = config;
|
|
2724
|
+
const { client: request = fetch$1, ...requestConfig } = config;
|
|
2373
2725
|
const requestData = data;
|
|
2374
2726
|
return (await request({
|
|
2375
2727
|
method: "POST",
|
|
@@ -2395,7 +2747,7 @@ function getPutV1BodyMeasurementsDateUrl(date) {
|
|
|
2395
2747
|
* {@link /v1/body_measurements/:date}
|
|
2396
2748
|
*/
|
|
2397
2749
|
async function putV1BodyMeasurementsDate(date, data, headers, config = {}) {
|
|
2398
|
-
const { client: request = fetch, ...requestConfig } = config;
|
|
2750
|
+
const { client: request = fetch$1, ...requestConfig } = config;
|
|
2399
2751
|
const requestData = data;
|
|
2400
2752
|
return (await request({
|
|
2401
2753
|
method: "PUT",
|
|
@@ -2421,7 +2773,7 @@ function getPutV1RoutinesRoutineidUrl(routineId) {
|
|
|
2421
2773
|
* {@link /v1/routines/:routineId}
|
|
2422
2774
|
*/
|
|
2423
2775
|
async function putV1RoutinesRoutineid(routineId, data, headers, config = {}) {
|
|
2424
|
-
const { client: request = fetch, ...requestConfig } = config;
|
|
2776
|
+
const { client: request = fetch$1, ...requestConfig } = config;
|
|
2425
2777
|
const requestData = data;
|
|
2426
2778
|
return (await request({
|
|
2427
2779
|
method: "PUT",
|
|
@@ -2447,7 +2799,7 @@ function getPutV1WorkoutsWorkoutidUrl(workoutId) {
|
|
|
2447
2799
|
* {@link /v1/workouts/:workoutId}
|
|
2448
2800
|
*/
|
|
2449
2801
|
async function putV1WorkoutsWorkoutid(workoutId, data, headers, config = {}) {
|
|
2450
|
-
const { client: request = fetch, ...requestConfig } = config;
|
|
2802
|
+
const { client: request = fetch$1, ...requestConfig } = config;
|
|
2451
2803
|
const requestData = data;
|
|
2452
2804
|
return (await request({
|
|
2453
2805
|
method: "PUT",
|
|
@@ -2519,6 +2871,15 @@ function getApiTimeoutMs() {
|
|
|
2519
2871
|
if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_API_TIMEOUT_MS;
|
|
2520
2872
|
return Math.trunc(parsed);
|
|
2521
2873
|
}
|
|
2874
|
+
function normalizeMaxGetRetries(value) {
|
|
2875
|
+
if (value === void 0 || !Number.isFinite(value) || value < 0) return 3;
|
|
2876
|
+
return Math.floor(value);
|
|
2877
|
+
}
|
|
2878
|
+
function normalizeTimeoutMs(value) {
|
|
2879
|
+
if (value === void 0 || !Number.isFinite(value) || value <= 0) return getApiTimeoutMs();
|
|
2880
|
+
const normalizedValue = Math.floor(value);
|
|
2881
|
+
return normalizedValue > 0 ? normalizedValue : getApiTimeoutMs();
|
|
2882
|
+
}
|
|
2522
2883
|
function sleep(ms) {
|
|
2523
2884
|
return new Promise((resolve) => {
|
|
2524
2885
|
setTimeout(resolve, ms);
|
|
@@ -2606,7 +2967,7 @@ function markRetryExhausted(error, retryCount) {
|
|
|
2606
2967
|
if (retryError.code) retryError.hevyRetryOriginalCode = retryError.code;
|
|
2607
2968
|
retryError.code = HEVY_RETRY_EXHAUSTED_ERROR_CODE;
|
|
2608
2969
|
}
|
|
2609
|
-
async function requestWithRetries(axiosInstance, config, logger) {
|
|
2970
|
+
async function requestWithRetries(axiosInstance, config, maxGetRetries, logger) {
|
|
2610
2971
|
let retryCount = 0;
|
|
2611
2972
|
while (true) try {
|
|
2612
2973
|
return await axiosInstance.request(config);
|
|
@@ -2628,7 +2989,7 @@ async function requestWithRetries(axiosInstance, config, logger) {
|
|
|
2628
2989
|
});
|
|
2629
2990
|
throw error;
|
|
2630
2991
|
}
|
|
2631
|
-
if (retryCount >=
|
|
2992
|
+
if (retryCount >= maxGetRetries) {
|
|
2632
2993
|
emitClientLog(logger, {
|
|
2633
2994
|
level: status === 429 ? "warning" : "error",
|
|
2634
2995
|
logger: "hevy-api",
|
|
@@ -2636,7 +2997,7 @@ async function requestWithRetries(axiosInstance, config, logger) {
|
|
|
2636
2997
|
message: "Hevy API request failed after retries",
|
|
2637
2998
|
status,
|
|
2638
2999
|
attempt: retryCount + 1,
|
|
2639
|
-
maxAttempts:
|
|
3000
|
+
maxAttempts: maxGetRetries + 1,
|
|
2640
3001
|
method,
|
|
2641
3002
|
endpoint
|
|
2642
3003
|
}
|
|
@@ -2654,7 +3015,7 @@ async function requestWithRetries(axiosInstance, config, logger) {
|
|
|
2654
3015
|
message: status === 429 ? "Hevy API rate limit; retrying request" : "Retrying Hevy API request",
|
|
2655
3016
|
status,
|
|
2656
3017
|
attempt: retryCount + 1,
|
|
2657
|
-
maxAttempts:
|
|
3018
|
+
maxAttempts: maxGetRetries + 1,
|
|
2658
3019
|
delayMs: retryDelayMs,
|
|
2659
3020
|
...status === 429 ? { retryAfterMs: retryAfterMs ?? null } : {},
|
|
2660
3021
|
method,
|
|
@@ -2664,10 +3025,10 @@ async function requestWithRetries(axiosInstance, config, logger) {
|
|
|
2664
3025
|
await sleep(retryDelayMs);
|
|
2665
3026
|
}
|
|
2666
3027
|
}
|
|
2667
|
-
function createResilientClient(axiosInstance, logger) {
|
|
3028
|
+
function createResilientClient(axiosInstance, maxGetRetries, logger) {
|
|
2668
3029
|
let clientConfig = { baseURL: axiosInstance.defaults.baseURL };
|
|
2669
3030
|
const resilientClient = (async (config) => {
|
|
2670
|
-
return requestWithRetries(axiosInstance, config, logger);
|
|
3031
|
+
return requestWithRetries(axiosInstance, config, maxGetRetries, logger);
|
|
2671
3032
|
});
|
|
2672
3033
|
resilientClient.getConfig = () => ({ ...clientConfig });
|
|
2673
3034
|
resilientClient.setConfig = (config) => {
|
|
@@ -2723,9 +3084,11 @@ function finalizeRequestTrace(opts) {
|
|
|
2723
3084
|
}
|
|
2724
3085
|
function createClient$1(apiKey, baseUrl = "https://api.hevyapp.com", options = {}) {
|
|
2725
3086
|
const { logger } = options;
|
|
3087
|
+
const maxGetRetries = normalizeMaxGetRetries(options.maxGetRetries);
|
|
3088
|
+
const timeoutMs = normalizeTimeoutMs(options.timeoutMs);
|
|
2726
3089
|
const axiosInstance = axios.create({
|
|
2727
3090
|
baseURL: baseUrl,
|
|
2728
|
-
timeout:
|
|
3091
|
+
timeout: timeoutMs,
|
|
2729
3092
|
headers: { "api-key": apiKey }
|
|
2730
3093
|
});
|
|
2731
3094
|
axiosInstance.interceptors.request.use((config) => {
|
|
@@ -2733,11 +3096,13 @@ function createClient$1(apiKey, baseUrl = "https://api.hevyapp.com", options = {
|
|
|
2733
3096
|
const method = (config.method ?? "get").toUpperCase();
|
|
2734
3097
|
const url = config.url ?? "";
|
|
2735
3098
|
const endpoint = url.split("?")[0] ?? url;
|
|
3099
|
+
const userId = getCurrentUserId();
|
|
2736
3100
|
tracedConfig._span = tracer.startSpan(`hevy.api.${method}`, { attributes: {
|
|
2737
3101
|
"http.method": method,
|
|
2738
3102
|
"http.url": url,
|
|
2739
3103
|
"http.base_url": config.baseURL ?? "",
|
|
2740
|
-
"hevy.api.endpoint": endpoint
|
|
3104
|
+
"hevy.api.endpoint": endpoint,
|
|
3105
|
+
...userId ? { "user.id": userId } : {}
|
|
2741
3106
|
} });
|
|
2742
3107
|
tracedConfig._startTime = Date.now();
|
|
2743
3108
|
return config;
|
|
@@ -2749,6 +3114,13 @@ function createClient$1(apiKey, baseUrl = "https://api.hevyapp.com", options = {
|
|
|
2749
3114
|
url: response.config.url,
|
|
2750
3115
|
_startTime: tracedConfig._startTime
|
|
2751
3116
|
});
|
|
3117
|
+
const requestContext = getRequestContext(response.config);
|
|
3118
|
+
debugLog("api_response", {
|
|
3119
|
+
method: requestContext.method,
|
|
3120
|
+
endpoint: requestContext.endpoint,
|
|
3121
|
+
durationMs,
|
|
3122
|
+
status: response.status
|
|
3123
|
+
});
|
|
2752
3124
|
finalizeRequestTrace({
|
|
2753
3125
|
span: tracedConfig._span,
|
|
2754
3126
|
statusCode: response.status,
|
|
@@ -2764,6 +3136,13 @@ function createClient$1(apiKey, baseUrl = "https://api.hevyapp.com", options = {
|
|
|
2764
3136
|
url: error.config?.url,
|
|
2765
3137
|
_startTime: tracedConfig._startTime
|
|
2766
3138
|
});
|
|
3139
|
+
const requestContext = getRequestContext(error.config ?? {});
|
|
3140
|
+
debugLog("api_response", {
|
|
3141
|
+
method: requestContext.method,
|
|
3142
|
+
endpoint: requestContext.endpoint,
|
|
3143
|
+
durationMs,
|
|
3144
|
+
status: error.response?.status ?? null
|
|
3145
|
+
});
|
|
2767
3146
|
finalizeRequestTrace({
|
|
2768
3147
|
span: tracedConfig._span,
|
|
2769
3148
|
statusCode: error.response?.status ?? 0,
|
|
@@ -2775,7 +3154,7 @@ function createClient$1(apiKey, baseUrl = "https://api.hevyapp.com", options = {
|
|
|
2775
3154
|
throw error;
|
|
2776
3155
|
});
|
|
2777
3156
|
const headers = { "api-key": apiKey };
|
|
2778
|
-
const client = createResilientClient(axiosInstance, logger);
|
|
3157
|
+
const client = createResilientClient(axiosInstance, maxGetRetries, logger);
|
|
2779
3158
|
return {
|
|
2780
3159
|
getWorkouts: (params) => wrapApi(getV1Workouts)(headers, params, { client }),
|
|
2781
3160
|
getWorkout: (workoutId) => wrapApi(getV1WorkoutsWorkoutid)(workoutId, headers, { client }),
|
|
@@ -2832,6 +3211,9 @@ function createMcpClientLogger(server) {
|
|
|
2832
3211
|
//#endregion
|
|
2833
3212
|
//#region src/utils/stdio-observability.ts
|
|
2834
3213
|
const UTF8_BOM = "";
|
|
3214
|
+
/** Maximum escaped characters included in a malformed stdin shape preview. */
|
|
3215
|
+
const STDIN_PARSE_SHAPE_PREVIEW_MAX_LENGTH = 200;
|
|
3216
|
+
const REDACTED_CONTENT_MARKER = "[REDACTED]";
|
|
2835
3217
|
/**
|
|
2836
3218
|
* Adapter boundary around MCP SDK stdio internals.
|
|
2837
3219
|
*
|
|
@@ -2886,6 +3268,62 @@ function getFailureLocation(failurePosition, lineHadLeadingBom) {
|
|
|
2886
3268
|
if (failurePosition !== null) return "line_body";
|
|
2887
3269
|
return "unknown";
|
|
2888
3270
|
}
|
|
3271
|
+
function createStructuralShapePreview(line) {
|
|
3272
|
+
let shapePreview = "";
|
|
3273
|
+
let inContentRun = false;
|
|
3274
|
+
let inWhitespaceRun = false;
|
|
3275
|
+
const append = (token) => {
|
|
3276
|
+
if (shapePreview.length + token.length > STDIN_PARSE_SHAPE_PREVIEW_MAX_LENGTH) return false;
|
|
3277
|
+
shapePreview += token;
|
|
3278
|
+
return true;
|
|
3279
|
+
};
|
|
3280
|
+
for (const character of line) {
|
|
3281
|
+
if ("{}[]:,\"".includes(character)) {
|
|
3282
|
+
inContentRun = false;
|
|
3283
|
+
inWhitespaceRun = false;
|
|
3284
|
+
if (!append(character === "\"" ? "\\u0022" : character)) return {
|
|
3285
|
+
shapePreview,
|
|
3286
|
+
truncated: true
|
|
3287
|
+
};
|
|
3288
|
+
continue;
|
|
3289
|
+
}
|
|
3290
|
+
if (/\s/u.test(character)) {
|
|
3291
|
+
inContentRun = false;
|
|
3292
|
+
if (!inWhitespaceRun) {
|
|
3293
|
+
if (!append("\\s")) return {
|
|
3294
|
+
shapePreview,
|
|
3295
|
+
truncated: true
|
|
3296
|
+
};
|
|
3297
|
+
inWhitespaceRun = true;
|
|
3298
|
+
}
|
|
3299
|
+
continue;
|
|
3300
|
+
}
|
|
3301
|
+
inWhitespaceRun = false;
|
|
3302
|
+
if (!inContentRun) {
|
|
3303
|
+
if (!append(REDACTED_CONTENT_MARKER)) return {
|
|
3304
|
+
shapePreview,
|
|
3305
|
+
truncated: true
|
|
3306
|
+
};
|
|
3307
|
+
inContentRun = true;
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
return {
|
|
3311
|
+
shapePreview,
|
|
3312
|
+
truncated: false
|
|
3313
|
+
};
|
|
3314
|
+
}
|
|
3315
|
+
function getSafeErrorKind(error) {
|
|
3316
|
+
if (error instanceof SyntaxError) return "SyntaxError";
|
|
3317
|
+
if (error instanceof Error) return "Error";
|
|
3318
|
+
return "UnknownError";
|
|
3319
|
+
}
|
|
3320
|
+
function reportStdinParseFailure(error, line, lineByteLength, failureLocation, failurePosition) {
|
|
3321
|
+
try {
|
|
3322
|
+
const errorKind = getSafeErrorKind(error);
|
|
3323
|
+
const { shapePreview, truncated } = createStructuralShapePreview(line);
|
|
3324
|
+
console.error(`Failed to parse MCP stdin message: error_kind=${errorKind} line_bytes=${lineByteLength} failure_location=${failureLocation} failure_position=${failurePosition === null ? "unknown" : failurePosition} shape_preview="${shapePreview}" shape_preview_redacted=true shape_preview_truncated=${truncated}`);
|
|
3325
|
+
} catch {}
|
|
3326
|
+
}
|
|
2889
3327
|
function deserializeMessageWithObservability(line, chunkSnapshot) {
|
|
2890
3328
|
const lineHadLeadingBom = line.startsWith(UTF8_BOM);
|
|
2891
3329
|
const normalizedLine = lineHadLeadingBom ? line.slice(1) : line;
|
|
@@ -2929,6 +3367,7 @@ function deserializeMessageWithObservability(line, chunkSnapshot) {
|
|
|
2929
3367
|
});
|
|
2930
3368
|
Sentry.captureException(error);
|
|
2931
3369
|
});
|
|
3370
|
+
reportStdinParseFailure(error, line, lineByteLength, failureLocation, failurePosition);
|
|
2932
3371
|
throw error;
|
|
2933
3372
|
} finally {
|
|
2934
3373
|
span.end();
|
|
@@ -2951,9 +3390,164 @@ function createInstrumentedStdioTransport(transport) {
|
|
|
2951
3390
|
return transport;
|
|
2952
3391
|
}
|
|
2953
3392
|
//#endregion
|
|
3393
|
+
//#region src/utils/version-check.ts
|
|
3394
|
+
const CACHE_TTL_MS = 1440 * 60 * 1e3;
|
|
3395
|
+
const REQUEST_TIMEOUT_MS = 3e3;
|
|
3396
|
+
const REGISTRY_URL = "https://registry.npmjs.org";
|
|
3397
|
+
const defaultDependencies = {
|
|
3398
|
+
fetch,
|
|
3399
|
+
readFile,
|
|
3400
|
+
mkdir,
|
|
3401
|
+
writeFile,
|
|
3402
|
+
rename,
|
|
3403
|
+
unlink,
|
|
3404
|
+
homedir,
|
|
3405
|
+
randomUUID,
|
|
3406
|
+
pid: process.pid,
|
|
3407
|
+
now: Date.now,
|
|
3408
|
+
env: process.env,
|
|
3409
|
+
writeStderr: (message) => process.stderr.write(message)
|
|
3410
|
+
};
|
|
3411
|
+
const defaultSchedulerDependencies = {
|
|
3412
|
+
setImmediate,
|
|
3413
|
+
checkForUpdate
|
|
3414
|
+
};
|
|
3415
|
+
function getCachePath(dependencies) {
|
|
3416
|
+
return join(dependencies.env.XDG_CACHE_HOME || join(dependencies.homedir(), ".cache"), "hevy-mcp", "update-check.json");
|
|
3417
|
+
}
|
|
3418
|
+
function parseCacheEntry(value, now) {
|
|
3419
|
+
try {
|
|
3420
|
+
const parsed = JSON.parse(value);
|
|
3421
|
+
if (typeof parsed !== "object" || parsed === null || !("checkedAt" in parsed) || !("latestVersion" in parsed)) return;
|
|
3422
|
+
const { checkedAt, latestVersion } = parsed;
|
|
3423
|
+
if (typeof checkedAt !== "number" || !Number.isFinite(checkedAt) || typeof latestVersion !== "string" || !semver.valid(latestVersion)) return;
|
|
3424
|
+
const age = now - checkedAt;
|
|
3425
|
+
if (age < 0 || age >= CACHE_TTL_MS) return;
|
|
3426
|
+
return {
|
|
3427
|
+
checkedAt,
|
|
3428
|
+
latestVersion
|
|
3429
|
+
};
|
|
3430
|
+
} catch {
|
|
3431
|
+
return;
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3434
|
+
function parseLatestVersion(value) {
|
|
3435
|
+
if (typeof value !== "object" || value === null || !("dist-tags" in value)) return;
|
|
3436
|
+
const distTags = value["dist-tags"];
|
|
3437
|
+
if (typeof distTags !== "object" || distTags === null || !("latest" in distTags) || typeof distTags.latest !== "string" || !semver.valid(distTags.latest)) return;
|
|
3438
|
+
return distTags.latest;
|
|
3439
|
+
}
|
|
3440
|
+
function shouldNotifyAboutUpdate(currentVersion, latestVersion) {
|
|
3441
|
+
const current = semver.parse(currentVersion);
|
|
3442
|
+
const latest = semver.parse(latestVersion);
|
|
3443
|
+
if (!current || !latest || latest.prerelease.length > 0 || !semver.gt(latest, current)) return false;
|
|
3444
|
+
if (latest.major > current.major) return true;
|
|
3445
|
+
return latest.major === current.major && latest.minor - current.minor > 2;
|
|
3446
|
+
}
|
|
3447
|
+
async function readFreshCachedVersion(cachePath, dependencies) {
|
|
3448
|
+
try {
|
|
3449
|
+
return parseCacheEntry(await dependencies.readFile(cachePath, "utf8"), dependencies.now())?.latestVersion;
|
|
3450
|
+
} catch {
|
|
3451
|
+
return;
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3454
|
+
async function fetchLatestVersion(packageName, dependencies) {
|
|
3455
|
+
try {
|
|
3456
|
+
const response = await dependencies.fetch(`${REGISTRY_URL}/${encodeURIComponent(packageName)}`, {
|
|
3457
|
+
headers: { Accept: "application/vnd.npm.install-v1+json" },
|
|
3458
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
3459
|
+
});
|
|
3460
|
+
if (!response.ok) return;
|
|
3461
|
+
return parseLatestVersion(await response.json());
|
|
3462
|
+
} catch {
|
|
3463
|
+
return;
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
async function writeCache(cachePath, entry, dependencies) {
|
|
3467
|
+
const temporaryPath = `${cachePath}.${dependencies.pid}.${dependencies.randomUUID()}.tmp`;
|
|
3468
|
+
const payload = `${JSON.stringify(entry)}\n`;
|
|
3469
|
+
try {
|
|
3470
|
+
await dependencies.mkdir(dirname(cachePath), { recursive: true });
|
|
3471
|
+
await dependencies.writeFile(temporaryPath, payload, "utf8");
|
|
3472
|
+
} catch {
|
|
3473
|
+
try {
|
|
3474
|
+
await dependencies.unlink(temporaryPath);
|
|
3475
|
+
} catch {}
|
|
3476
|
+
return;
|
|
3477
|
+
}
|
|
3478
|
+
try {
|
|
3479
|
+
await dependencies.rename(temporaryPath, cachePath);
|
|
3480
|
+
return;
|
|
3481
|
+
} catch {
|
|
3482
|
+
try {
|
|
3483
|
+
await dependencies.writeFile(cachePath, payload, "utf8");
|
|
3484
|
+
} catch {}
|
|
3485
|
+
}
|
|
3486
|
+
try {
|
|
3487
|
+
await dependencies.unlink(temporaryPath);
|
|
3488
|
+
} catch {}
|
|
3489
|
+
}
|
|
3490
|
+
async function checkForUpdate(options, overrides = {}) {
|
|
3491
|
+
try {
|
|
3492
|
+
if (!semver.valid(options.currentVersion)) return;
|
|
3493
|
+
const dependencies = {
|
|
3494
|
+
...defaultDependencies,
|
|
3495
|
+
...overrides
|
|
3496
|
+
};
|
|
3497
|
+
const cachePath = getCachePath(dependencies);
|
|
3498
|
+
let latestVersion = await readFreshCachedVersion(cachePath, dependencies);
|
|
3499
|
+
if (!latestVersion) {
|
|
3500
|
+
latestVersion = await fetchLatestVersion(options.packageName, dependencies);
|
|
3501
|
+
if (!latestVersion) return;
|
|
3502
|
+
await writeCache(cachePath, {
|
|
3503
|
+
checkedAt: dependencies.now(),
|
|
3504
|
+
latestVersion
|
|
3505
|
+
}, dependencies);
|
|
3506
|
+
}
|
|
3507
|
+
if (shouldNotifyAboutUpdate(options.currentVersion, latestVersion)) dependencies.writeStderr(`Update available for ${options.packageName}: current ${options.currentVersion}, latest ${latestVersion}. Notices are shown for newer major versions or when more than two minor versions behind. Update using your preferred installation method (for example, npm install -g ${options.packageName}@latest).\n`);
|
|
3508
|
+
} catch {}
|
|
3509
|
+
}
|
|
3510
|
+
function scheduleUpdateCheck(options, overrides = {}) {
|
|
3511
|
+
const dependencies = {
|
|
3512
|
+
...defaultSchedulerDependencies,
|
|
3513
|
+
...overrides
|
|
3514
|
+
};
|
|
3515
|
+
dependencies.setImmediate(() => {
|
|
3516
|
+
dependencies.checkForUpdate(options).catch(() => void 0);
|
|
3517
|
+
}).unref();
|
|
3518
|
+
}
|
|
3519
|
+
//#endregion
|
|
2954
3520
|
//#region src/index.ts
|
|
2955
3521
|
const name = name$1;
|
|
2956
3522
|
const version = version$1;
|
|
3523
|
+
const SERVER_INSTRUCTIONS = [
|
|
3524
|
+
[
|
|
3525
|
+
"Hevy MCP connects clients to the authenticated user's Hevy",
|
|
3526
|
+
"workout-tracking data, including workouts, routines, exercise templates,",
|
|
3527
|
+
"routine folders, body measurements, and profile information.",
|
|
3528
|
+
"HEVY_API_KEY must contain a valid Hevy API key."
|
|
3529
|
+
].join(" "),
|
|
3530
|
+
[
|
|
3531
|
+
"Safety: all get-* and search-* tools are read-only. create-* and",
|
|
3532
|
+
"update-* tools mutate Hevy data. Creates are additive and",
|
|
3533
|
+
"non-idempotent, so repeating one can create duplicates. Updates can",
|
|
3534
|
+
"overwrite existing data. Delete operations are not available."
|
|
3535
|
+
].join(" "),
|
|
3536
|
+
[
|
|
3537
|
+
"Workflow: search exercise templates first, then use the returned",
|
|
3538
|
+
"template IDs when creating workouts or routines. To create a completed",
|
|
3539
|
+
"workout from a routine, fetch the routine as a plan, then obtain the",
|
|
3540
|
+
"actual completed sets and end time from the user; never invent completion",
|
|
3541
|
+
"data. Use the built-in workflow prompts when they match the task."
|
|
3542
|
+
].join(" "),
|
|
3543
|
+
["Pagination: start at page 1 and fetch only the pages needed. Most list", "tools allow pageSize up to 10; get-exercise-templates allows up to 100."].join(" "),
|
|
3544
|
+
[
|
|
3545
|
+
"Rate limits and retries: minimize repeated calls. If Hevy returns HTTP",
|
|
3546
|
+
"429, follow its retry guidance. Transient read requests retry",
|
|
3547
|
+
"automatically, but write requests do not; confirm uncertain write",
|
|
3548
|
+
"outcomes before trying again."
|
|
3549
|
+
].join(" ")
|
|
3550
|
+
].join("\n\n");
|
|
2957
3551
|
const HELP_TEXT = [
|
|
2958
3552
|
"Usage:",
|
|
2959
3553
|
" hevy-mcp [options]",
|
|
@@ -2965,6 +3559,7 @@ const HELP_TEXT = [
|
|
|
2965
3559
|
"",
|
|
2966
3560
|
"Environment:",
|
|
2967
3561
|
" HEVY_API_KEY=<api-key> Hevy API key from Hevy app settings",
|
|
3562
|
+
" HEVY_MCP_DEBUG=1 Enable verbose diagnostics on stderr",
|
|
2968
3563
|
"",
|
|
2969
3564
|
"Examples:",
|
|
2970
3565
|
" HEVY_API_KEY=your-key npx hevy-mcp",
|
|
@@ -2979,6 +3574,21 @@ function getCliAction(args) {
|
|
|
2979
3574
|
return "start";
|
|
2980
3575
|
}
|
|
2981
3576
|
const HEVY_API_BASEURL = "https://api.hevyapp.com";
|
|
3577
|
+
const STARTUP_PROBE_TIMEOUT_MS = 5e3;
|
|
3578
|
+
const INVALID_API_KEY_MESSAGE = "HEVY_API_KEY is invalid or expired. Please check your API key in the Hevy app under Settings > API Key.";
|
|
3579
|
+
const API_KEY_VALIDATION_WARNING = "Warning: HEVY_API_KEY could not be validated during startup. Startup will continue; check your network connection and Hevy API availability.";
|
|
3580
|
+
const SAFE_NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
3581
|
+
"EAI_AGAIN",
|
|
3582
|
+
"ECONNABORTED",
|
|
3583
|
+
"ECONNREFUSED",
|
|
3584
|
+
"ECONNRESET",
|
|
3585
|
+
"ENETUNREACH",
|
|
3586
|
+
"ENOTFOUND",
|
|
3587
|
+
"ERR_NETWORK",
|
|
3588
|
+
"ERR_SOCKET_TIMEOUT",
|
|
3589
|
+
"ETIMEDOUT",
|
|
3590
|
+
"HEVY_RETRY_EXHAUSTED"
|
|
3591
|
+
]);
|
|
2982
3592
|
const SENTRY_USER_ID_CONTEXT = "hevy-mcp:sentry-user-id:v1";
|
|
2983
3593
|
function fingerprintApiKey(apiKey) {
|
|
2984
3594
|
return createHmac("sha256", apiKey).update(SENTRY_USER_ID_CONTEXT).digest("hex").slice(0, 10);
|
|
@@ -3007,6 +3617,33 @@ function createToolCountingServer(server) {
|
|
|
3007
3617
|
getCount: () => count
|
|
3008
3618
|
};
|
|
3009
3619
|
}
|
|
3620
|
+
function getHttpStatus(error) {
|
|
3621
|
+
if (!error || typeof error !== "object" || !("response" in error)) return;
|
|
3622
|
+
const response = error.response;
|
|
3623
|
+
if (!response || typeof response !== "object" || !("status" in response)) return;
|
|
3624
|
+
return typeof response.status === "number" && Number.isInteger(response.status) && response.status >= 100 && response.status <= 599 ? response.status : void 0;
|
|
3625
|
+
}
|
|
3626
|
+
function getSafeValidationDiagnostic(error) {
|
|
3627
|
+
const status = getHttpStatus(error);
|
|
3628
|
+
if (status !== void 0) return `HTTP ${status}`;
|
|
3629
|
+
if (!error || typeof error !== "object" || !("code" in error)) return;
|
|
3630
|
+
const code = error.code;
|
|
3631
|
+
return typeof code === "string" && SAFE_NETWORK_ERROR_CODES.has(code) ? code : void 0;
|
|
3632
|
+
}
|
|
3633
|
+
async function validateApiKey(apiKey) {
|
|
3634
|
+
const startupProbeClient = createClient(apiKey, HEVY_API_BASEURL, {
|
|
3635
|
+
maxGetRetries: 0,
|
|
3636
|
+
timeoutMs: STARTUP_PROBE_TIMEOUT_MS
|
|
3637
|
+
});
|
|
3638
|
+
try {
|
|
3639
|
+
await startupProbeClient.getUserInfo();
|
|
3640
|
+
} catch (error) {
|
|
3641
|
+
const status = getHttpStatus(error);
|
|
3642
|
+
if (status === 401 || status === 403) throw new Error(INVALID_API_KEY_MESSAGE);
|
|
3643
|
+
const diagnostic = getSafeValidationDiagnostic(error);
|
|
3644
|
+
console.error(diagnostic ? `${API_KEY_VALIDATION_WARNING} Diagnostic: ${diagnostic}.` : API_KEY_VALIDATION_WARNING);
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3010
3647
|
function buildServer(apiKey) {
|
|
3011
3648
|
const userId = fingerprintApiKey(apiKey);
|
|
3012
3649
|
return tracer.startActiveSpan("mcp.server.build", { attributes: {
|
|
@@ -3021,7 +3658,10 @@ function buildServer(apiKey) {
|
|
|
3021
3658
|
const baseServer = new McpServer({
|
|
3022
3659
|
name,
|
|
3023
3660
|
version
|
|
3024
|
-
}, {
|
|
3661
|
+
}, {
|
|
3662
|
+
capabilities: { logging: {} },
|
|
3663
|
+
instructions: SERVER_INSTRUCTIONS
|
|
3664
|
+
});
|
|
3025
3665
|
const server = Sentry.wrapMcpServerWithSentry(baseServer);
|
|
3026
3666
|
const clientLogger = createMcpClientLogger(server);
|
|
3027
3667
|
const hevyClient = tracer.startActiveSpan("mcp.hevy-client.initialize", (childSpan) => {
|
|
@@ -3064,15 +3704,16 @@ function buildServer(apiKey) {
|
|
|
3064
3704
|
}
|
|
3065
3705
|
});
|
|
3066
3706
|
}
|
|
3067
|
-
function createServer({ config }) {
|
|
3707
|
+
async function createServer({ config }) {
|
|
3068
3708
|
const { apiKey } = serverConfigSchema.parse(config);
|
|
3709
|
+
await validateApiKey(apiKey);
|
|
3069
3710
|
return buildServer(apiKey);
|
|
3070
3711
|
}
|
|
3071
3712
|
async function runServer() {
|
|
3072
3713
|
const args = process.argv.slice(2);
|
|
3073
3714
|
const cliAction = getCliAction(args);
|
|
3074
3715
|
if (cliAction === "version") {
|
|
3075
|
-
console.
|
|
3716
|
+
console.error(`${name} v${version}`);
|
|
3076
3717
|
return;
|
|
3077
3718
|
}
|
|
3078
3719
|
if (cliAction === "help") {
|
|
@@ -3084,7 +3725,7 @@ async function runServer() {
|
|
|
3084
3725
|
try {
|
|
3085
3726
|
const apiKey = parseConfig(args, process.env).apiKey;
|
|
3086
3727
|
assertApiKey(apiKey);
|
|
3087
|
-
const server =
|
|
3728
|
+
const server = await createServer({ config: { apiKey } });
|
|
3088
3729
|
console.error("Starting MCP server in stdio mode");
|
|
3089
3730
|
const transport = createInstrumentedStdioTransport(new StdioServerTransport());
|
|
3090
3731
|
await tracer.startActiveSpan("mcp.server.connect", { attributes: { "mcp.transport": "stdio" } }, async (connectSpan) => {
|
|
@@ -3098,6 +3739,11 @@ async function runServer() {
|
|
|
3098
3739
|
connectSpan.end();
|
|
3099
3740
|
}
|
|
3100
3741
|
});
|
|
3742
|
+
scheduleUpdateCheck({
|
|
3743
|
+
packageName: name$1,
|
|
3744
|
+
currentVersion: version$1
|
|
3745
|
+
});
|
|
3746
|
+
installGracefulShutdown({ target: server });
|
|
3101
3747
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
3102
3748
|
} catch (e) {
|
|
3103
3749
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
@@ -3110,4 +3756,4 @@ async function runServer() {
|
|
|
3110
3756
|
//#endregion
|
|
3111
3757
|
export { createServer as n, runServer as r, configSchema as t };
|
|
3112
3758
|
|
|
3113
|
-
//# sourceMappingURL=src-
|
|
3759
|
+
//# sourceMappingURL=src-c3mB-R0m.mjs.map
|