hevy-mcp 6.1.3 → 6.1.4
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 +7 -4
- package/dist/cli.mjs +2 -2
- package/dist/index.mjs +3 -3
- package/dist/{runtime-BefToK0H.mjs → runtime-CokZbz-j.mjs} +27 -7
- package/dist/{src-Cb_gblD1.mjs → src-DCTiE-_b.mjs} +189 -143
- package/package.json +2 -2
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -67,8 +67,8 @@ Try asking:
|
|
|
67
67
|
|
|
68
68
|
### 1. Get your Hevy API key
|
|
69
69
|
|
|
70
|
-
Create an API key in Hevy
|
|
71
|
-
requires a Hevy PRO subscription.
|
|
70
|
+
Create an API key in [Hevy's API settings](https://www.hevyapp.com/api), then
|
|
71
|
+
keep it somewhere secure. API access currently requires a Hevy PRO subscription.
|
|
72
72
|
|
|
73
73
|
### 2. Connect `hevy-mcp` to your client
|
|
74
74
|
|
|
@@ -557,8 +557,11 @@ metadata, and unnormalized endpoint paths remain prohibited.
|
|
|
557
557
|
`npx -y hevy-mcp --version` in a terminal.
|
|
558
558
|
- **Codex cannot see the server:** run `codex mcp list`, then start a new Codex
|
|
559
559
|
session after confirming the `hevy` entry exists.
|
|
560
|
-
- **
|
|
561
|
-
|
|
560
|
+
- **Hevy API returns 401:** the key is invalid, expired, revoked, or
|
|
561
|
+
misconfigured. Verify or create an active key at
|
|
562
|
+
[Hevy's API settings](https://www.hevyapp.com/api), then restart the client.
|
|
563
|
+
- **Hosted authentication fails:** confirm the key belongs to a Hevy PRO
|
|
564
|
+
account and is sent as `Authorization: Bearer <HEVY_API_KEY>`.
|
|
562
565
|
- **Local authentication fails:** confirm the key is active and available to the
|
|
563
566
|
MCP child process as `HEVY_API_KEY`.
|
|
564
567
|
- **Need diagnostics:** set `HEVY_MCP_DEBUG=1`. Diagnostic output goes to stderr
|
package/dist/cli.mjs
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "d805d34c-9cce-4622-8b67-728aa0cacb5d", e._sentryDebugIdIdentifier = "sentry-dbid-d805d34c-9cce-4622-8b67-728aa0cacb5d");
|
|
9
9
|
} catch (e) {}
|
|
10
10
|
})();
|
|
11
|
-
import { i as flushTelemetry, r as MissingHevyApiKeyError, t as runServer } from "./runtime-
|
|
12
|
-
import { a as createSafeErrorDiagnostic } from "./src-
|
|
11
|
+
import { i as flushTelemetry, r as MissingHevyApiKeyError, t as runServer } from "./runtime-CokZbz-j.mjs";
|
|
12
|
+
import { a as createSafeErrorDiagnostic } from "./src-DCTiE-_b.mjs";
|
|
13
13
|
//#region src/cli.ts
|
|
14
14
|
runServer().catch(async (error) => {
|
|
15
15
|
if (error instanceof MissingHevyApiKeyError) console.error(error.message);
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Generated with tsdown
|
|
3
3
|
// https://tsdown.dev
|
|
4
|
-
import { o as createHevyClient, t as createHevyMcpServer } from "./src-
|
|
4
|
+
import { o as createHevyClient, t as createHevyMcpServer } from "./src-DCTiE-_b.mjs";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
//#region src/index.ts
|
|
7
7
|
const serverConfigSchema = z.object({ apiKey: z.string().min(1, "Hevy API key is required").describe("Your Hevy API key (available in the Hevy app settings).") });
|
|
@@ -32,7 +32,7 @@ function createNodeMcpServer({ apiKey }, _transport = "stdio", lifecycleSignal)
|
|
|
32
32
|
* does not evaluate the runtime bootstrap; it is loaded only when invoked.
|
|
33
33
|
*/
|
|
34
34
|
async function runStdioServer() {
|
|
35
|
-
const { runStdioServer: run } = await import("./runtime-
|
|
35
|
+
const { runStdioServer: run } = await import("./runtime-CokZbz-j.mjs").then((n) => n.n);
|
|
36
36
|
return run();
|
|
37
37
|
}
|
|
38
38
|
/**
|
|
@@ -40,7 +40,7 @@ async function runStdioServer() {
|
|
|
40
40
|
* does not evaluate the runtime bootstrap; it is loaded only when invoked.
|
|
41
41
|
*/
|
|
42
42
|
async function runServer() {
|
|
43
|
-
const { runServer: run } = await import("./runtime-
|
|
43
|
+
const { runServer: run } = await import("./runtime-CokZbz-j.mjs").then((n) => n.n);
|
|
44
44
|
return run();
|
|
45
45
|
}
|
|
46
46
|
//#endregion
|
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
try {
|
|
6
6
|
var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
|
|
7
7
|
var n = new e.Error().stack;
|
|
8
|
-
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "
|
|
8
|
+
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "baee8222-0612-498c-a1f8-cf149a59a78b", e._sentryDebugIdIdentifier = "sentry-dbid-baee8222-0612-498c-a1f8-cf149a59a78b");
|
|
9
9
|
} catch (e) {}
|
|
10
10
|
})();
|
|
11
|
-
import { a as createSafeErrorDiagnostic, c as diagnosticEndpointIdentity, i as bucketCount, l as metricEndpointIdentity, n as createExecutionProjection, o as createHevyClient, r as mergeAbortSignals, s as SAFE_OBSERVATION_CODES, t as createHevyMcpServer, u as isHevyHttpError } from "./src-
|
|
11
|
+
import { a as createSafeErrorDiagnostic, c as diagnosticEndpointIdentity, i as bucketCount, l as metricEndpointIdentity, n as createExecutionProjection, o as createHevyClient, r as mergeAbortSignals, s as SAFE_OBSERVATION_CODES, t as createHevyMcpServer, u as isHevyHttpError } from "./src-DCTiE-_b.mjs";
|
|
12
12
|
import { ZodError, z } from "zod";
|
|
13
13
|
import { deserializeMessage } from "@modelcontextprotocol/server";
|
|
14
|
+
import * as crypto from "node:crypto";
|
|
14
15
|
import { createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
15
16
|
import * as Sentry from "@sentry/node";
|
|
16
17
|
import { SpanStatusCode, context, metrics, trace } from "@opentelemetry/api";
|
|
@@ -393,18 +394,24 @@ function parseBuildString(value, fallback) {
|
|
|
393
394
|
return z.string().parse(value ?? fallback);
|
|
394
395
|
}
|
|
395
396
|
const name$1 = parseBuildString(readBuildGlobal(() => "hevy-mcp"), "hevy-mcp");
|
|
396
|
-
const version$1 = parseBuildString(readBuildGlobal(() => "6.1.
|
|
397
|
+
const version$1 = parseBuildString(readBuildGlobal(() => "6.1.4"), "dev");
|
|
397
398
|
const telemetryEnabled = process.env.HEVY_MCP_TELEMETRY !== "0";
|
|
398
399
|
const collectorToken = z.string().safeParse(readBuildGlobal(() => "NH9vOela-HYreQxAbJa68cjEmORoEKM57EvneUDVcOo")).data ?? process.env.OTEL_COLLECTOR_TOKEN ?? "";
|
|
399
400
|
const COLLECTOR_ENDPOINT = "https://otel.chrisdoc.dev/v1";
|
|
400
401
|
const DEFAULT_SENTRY_DSN = "https://ce696d8333b507acbf5203eb877bce0f@o4508975499575296.ingest.de.sentry.io/4509049671647312";
|
|
401
402
|
const sentryRelease = process.env.SENTRY_RELEASE ?? `${name$1}@${version$1}`;
|
|
403
|
+
/** Hex-encode bytes without relying on Buffer typings that vary across @types/node releases. */
|
|
404
|
+
function toHex$1(bytes) {
|
|
405
|
+
let hex = "";
|
|
406
|
+
for (const byte of bytes) hex += byte.toString(16).padStart(2, "0");
|
|
407
|
+
return hex;
|
|
408
|
+
}
|
|
402
409
|
function createServiceInstanceId(generate = randomUUID) {
|
|
403
410
|
try {
|
|
404
411
|
const generated = generate();
|
|
405
412
|
if (generated.length > 0 && generated.length <= 128) return generated;
|
|
406
413
|
} catch {}
|
|
407
|
-
return randomBytes(16)
|
|
414
|
+
return toHex$1(randomBytes(16));
|
|
408
415
|
}
|
|
409
416
|
const resource = resourceFromAttributes({
|
|
410
417
|
"service.name": name$1,
|
|
@@ -609,6 +616,19 @@ const UNKNOWN_METADATA = "unknown";
|
|
|
609
616
|
const MAX_METADATA_LENGTH = 64;
|
|
610
617
|
const SAFE_METADATA_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._+:/@-]{0,63}$/u;
|
|
611
618
|
const contextStorage = new AsyncLocalStorage();
|
|
619
|
+
/** Hex-encode bytes without relying on Buffer typings that vary across @types/node releases. */
|
|
620
|
+
function toHex(bytes) {
|
|
621
|
+
let hex = "";
|
|
622
|
+
for (const byte of bytes) hex += byte.toString(16).padStart(2, "0");
|
|
623
|
+
return hex;
|
|
624
|
+
}
|
|
625
|
+
function generateFallbackTelemetrySessionId() {
|
|
626
|
+
return `${Date.now().toString(36)}-${toHex(crypto.randomBytes(16))}`;
|
|
627
|
+
}
|
|
628
|
+
const parsedRandomUUID = z.function().output(z.string()).safeParse(crypto.randomUUID);
|
|
629
|
+
function generateTelemetrySessionId() {
|
|
630
|
+
return parsedRandomUUID.success ? parsedRandomUUID.data() : generateFallbackTelemetrySessionId();
|
|
631
|
+
}
|
|
612
632
|
let activeStdioSession;
|
|
613
633
|
function isObject$3(value) {
|
|
614
634
|
return z.object({}).passthrough().safeParse(value).success;
|
|
@@ -655,7 +675,7 @@ function createMcpSessionContext(message, transport = "stdio", options = {}) {
|
|
|
655
675
|
toolCalls: 0,
|
|
656
676
|
hadToolFailure: false,
|
|
657
677
|
transport,
|
|
658
|
-
telemetrySessionId: normalizedOptions.telemetrySessionId ?? (normalizedOptions.generateTelemetrySessionId ??
|
|
678
|
+
telemetrySessionId: normalizedOptions.telemetrySessionId ?? (normalizedOptions.generateTelemetrySessionId ?? generateTelemetrySessionId)()
|
|
659
679
|
};
|
|
660
680
|
}
|
|
661
681
|
function runWithMcpSessionContext(context, callback) {
|
|
@@ -958,7 +978,7 @@ async function startStreamableHttpServer(options, apiKey, createMcpServer, confi
|
|
|
958
978
|
const wildcard = options.host === "0.0.0.0" || options.host === "::" || options.host === "[::]";
|
|
959
979
|
const loopback = isLoopbackHost(options.host);
|
|
960
980
|
const bearerToken = process.env[HTTP_BEARER_TOKEN];
|
|
961
|
-
if (!loopback && !bearerToken) throw new Error(`Non-loopback HTTP mode requires ${HTTP_BEARER_TOKEN} to protect the shared Hevy account.`);
|
|
981
|
+
if (!loopback && !bearerToken) throw new Error(`Non-loopback HTTP mode requires ${HTTP_BEARER_TOKEN} to protect the shared Hevy account. Set ${HTTP_BEARER_TOKEN} to a secure random value, then retry.`);
|
|
962
982
|
const sessions = /* @__PURE__ */ new Map();
|
|
963
983
|
const pendingSessions = /* @__PURE__ */ new Set();
|
|
964
984
|
const initializationControllers = /* @__PURE__ */ new Set();
|
|
@@ -2679,4 +2699,4 @@ async function runServer() {
|
|
|
2679
2699
|
//#endregion
|
|
2680
2700
|
export { flushTelemetry as i, runtime_exports as n, MissingHevyApiKeyError as r, runServer as t };
|
|
2681
2701
|
|
|
2682
|
-
//# sourceMappingURL=runtime-
|
|
2702
|
+
//# sourceMappingURL=runtime-CokZbz-j.mjs.map
|
|
@@ -4,6 +4,100 @@
|
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { z as z$1 } from "zod/v4";
|
|
6
6
|
import { McpServer } from "@modelcontextprotocol/server";
|
|
7
|
+
//#region ../core/src/utils/type-predicates.ts
|
|
8
|
+
const stringSchema$2 = z.string();
|
|
9
|
+
const numberSchema$1 = z.number();
|
|
10
|
+
const booleanSchema = z.boolean();
|
|
11
|
+
const objectSchema$2 = z.object({}).passthrough();
|
|
12
|
+
const functionSchema = z.function();
|
|
13
|
+
function isString$2(value) {
|
|
14
|
+
return stringSchema$2.safeParse(value).success;
|
|
15
|
+
}
|
|
16
|
+
function isFiniteNumber(value) {
|
|
17
|
+
return numberSchema$1.safeParse(value).success && Number.isFinite(value);
|
|
18
|
+
}
|
|
19
|
+
function isBoolean(value) {
|
|
20
|
+
return booleanSchema.safeParse(value).success;
|
|
21
|
+
}
|
|
22
|
+
function isObject$2(value) {
|
|
23
|
+
return value !== null && objectSchema$2.safeParse(value).success;
|
|
24
|
+
}
|
|
25
|
+
function isFunction(value) {
|
|
26
|
+
return functionSchema.safeParse(value).success;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region ../core/src/utils/compact-json-schema.ts
|
|
30
|
+
function isRecord(value) {
|
|
31
|
+
return isObject$2(value) && !Array.isArray(value);
|
|
32
|
+
}
|
|
33
|
+
function isNullSchema(value) {
|
|
34
|
+
return isRecord(value) && value.type === "null" && Object.keys(value).length === 1;
|
|
35
|
+
}
|
|
36
|
+
let conversionCount = 0;
|
|
37
|
+
function compactNullableSchema(value) {
|
|
38
|
+
if (Array.isArray(value)) return value.map(compactNullableSchema);
|
|
39
|
+
if (!isRecord(value)) return value;
|
|
40
|
+
const anyOf = value.anyOf;
|
|
41
|
+
if (Array.isArray(anyOf) && anyOf.length === 2) {
|
|
42
|
+
const nullableBranch = anyOf.find(isNullSchema);
|
|
43
|
+
const valueBranch = anyOf.find((branch) => !isNullSchema(branch));
|
|
44
|
+
if (nullableBranch !== void 0 && isRecord(valueBranch) && isString$2(valueBranch.type)) {
|
|
45
|
+
const { anyOf: _anyOf, ...metadata } = value;
|
|
46
|
+
return compactNullableSchema({
|
|
47
|
+
...valueBranch,
|
|
48
|
+
...metadata,
|
|
49
|
+
type: [valueBranch.type, "null"]
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return Object.fromEntries(Object.entries(value).map(([key, nested]) => [key, compactNullableSchema(nested)]));
|
|
54
|
+
}
|
|
55
|
+
function omitOutputObjectRestrictions(value) {
|
|
56
|
+
if (Array.isArray(value)) return value.map(omitOutputObjectRestrictions);
|
|
57
|
+
if (!isRecord(value)) return value;
|
|
58
|
+
return Object.fromEntries(Object.entries(value).filter(([key, nested]) => key !== "additionalProperties" || nested !== false).map(([key, nested]) => [key, omitOutputObjectRestrictions(nested)]));
|
|
59
|
+
}
|
|
60
|
+
function convertSchema(schema, io) {
|
|
61
|
+
conversionCount += 1;
|
|
62
|
+
const converted = z.toJSONSchema(schema, {
|
|
63
|
+
target: "draft-2020-12",
|
|
64
|
+
io
|
|
65
|
+
});
|
|
66
|
+
if (!isRecord(converted)) throw new Error("Zod emitted a non-object root schema");
|
|
67
|
+
const compacted = compactNullableSchema(converted);
|
|
68
|
+
if (!isRecord(compacted)) throw new Error("Nullable schema compaction changed the root schema");
|
|
69
|
+
const output = io === "output" ? omitOutputObjectRestrictions(compacted) : compacted;
|
|
70
|
+
if (!isRecord(output)) throw new Error("Output schema compaction changed the root schema");
|
|
71
|
+
delete output.$schema;
|
|
72
|
+
return output;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Keep Zod validation while advertising a compact JSON Schema.
|
|
76
|
+
*
|
|
77
|
+
* Zod emits nullable values as `anyOf`; draft 2020-12 also permits a type
|
|
78
|
+
* array. The MCP wire envelope does not need a repeated `$schema` marker, and
|
|
79
|
+
* structured output is validated by the original Zod schema before sending.
|
|
80
|
+
*/
|
|
81
|
+
function compactJsonSchema(schema) {
|
|
82
|
+
let inputSchema;
|
|
83
|
+
let outputSchema;
|
|
84
|
+
Object.defineProperty(schema["~standard"], "jsonSchema", {
|
|
85
|
+
configurable: true,
|
|
86
|
+
value: {
|
|
87
|
+
input: () => {
|
|
88
|
+
inputSchema ??= convertSchema(schema, "input");
|
|
89
|
+
return inputSchema;
|
|
90
|
+
},
|
|
91
|
+
output: () => {
|
|
92
|
+
outputSchema ??= convertSchema(schema, "output");
|
|
93
|
+
return outputSchema;
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
writable: true
|
|
97
|
+
});
|
|
98
|
+
return schema;
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
7
101
|
//#region ../hevy-client/src/generated/client/schemas/userInfoSchema.ts
|
|
8
102
|
/**
|
|
9
103
|
* Generated by Kubb (https://kubb.dev/).
|
|
@@ -292,13 +386,13 @@ z$1.any();
|
|
|
292
386
|
z$1.lazy(() => getV1WorkoutsWorkoutid200Schema);
|
|
293
387
|
//#endregion
|
|
294
388
|
//#region ../hevy-client/src/response-error.ts
|
|
295
|
-
const objectSchema$
|
|
296
|
-
const stringSchema$
|
|
297
|
-
function isObject$
|
|
298
|
-
return objectSchema$
|
|
389
|
+
const objectSchema$1 = z.object({}).passthrough();
|
|
390
|
+
const stringSchema$1 = z.string();
|
|
391
|
+
function isObject$1(value) {
|
|
392
|
+
return objectSchema$1.safeParse(value).success;
|
|
299
393
|
}
|
|
300
|
-
function isString$
|
|
301
|
-
return stringSchema$
|
|
394
|
+
function isString$1(value) {
|
|
395
|
+
return stringSchema$1.safeParse(value).success;
|
|
302
396
|
}
|
|
303
397
|
const MAX_RESPONSE_ERROR_INPUT_LENGTH = 4096;
|
|
304
398
|
const MAX_RESPONSE_ERROR_LENGTH = 256;
|
|
@@ -316,7 +410,7 @@ const HTTP_URL = /\bhttps?:\/\/[^\s<>"']+/gi;
|
|
|
316
410
|
const EMAIL = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
|
|
317
411
|
const UUID = /\b[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b/gi;
|
|
318
412
|
function isResponseErrorBody(value) {
|
|
319
|
-
return isObject$
|
|
413
|
+
return isObject$1(value);
|
|
320
414
|
}
|
|
321
415
|
function ownValue(value, key) {
|
|
322
416
|
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
@@ -326,12 +420,12 @@ function responseErrorText(value) {
|
|
|
326
420
|
if (!isResponseErrorBody(value)) return void 0;
|
|
327
421
|
for (const key of RESPONSE_ERROR_KEYS) {
|
|
328
422
|
const candidate = ownValue(value, key);
|
|
329
|
-
if (isString$
|
|
423
|
+
if (isString$1(candidate)) return candidate;
|
|
330
424
|
if (!isResponseErrorBody(candidate)) continue;
|
|
331
425
|
const nestedMessage = ownValue(candidate, "message");
|
|
332
|
-
if (isString$
|
|
426
|
+
if (isString$1(nestedMessage)) return nestedMessage;
|
|
333
427
|
const nestedDetail = ownValue(candidate, "detail");
|
|
334
|
-
if (isString$
|
|
428
|
+
if (isString$1(nestedDetail)) return nestedDetail;
|
|
335
429
|
}
|
|
336
430
|
}
|
|
337
431
|
function removeControlCharacters(value) {
|
|
@@ -1280,13 +1374,13 @@ function isAbortLike(error) {
|
|
|
1280
1374
|
}
|
|
1281
1375
|
//#endregion
|
|
1282
1376
|
//#region ../hevy-client/src/hevy-client-kubb.ts
|
|
1283
|
-
const objectSchema
|
|
1284
|
-
const numberSchema
|
|
1285
|
-
const stringSchema
|
|
1286
|
-
const isObject
|
|
1287
|
-
const isNumber = (value) => numberSchema
|
|
1288
|
-
const isString
|
|
1289
|
-
const DEFAULT_API_TIMEOUT_MS =
|
|
1377
|
+
const objectSchema = z.object({}).passthrough();
|
|
1378
|
+
const numberSchema = z.number();
|
|
1379
|
+
const stringSchema = z.string();
|
|
1380
|
+
const isObject = (value) => objectSchema.safeParse(value).success;
|
|
1381
|
+
const isNumber = (value) => numberSchema.safeParse(value).success;
|
|
1382
|
+
const isString = (value) => stringSchema.safeParse(value).success;
|
|
1383
|
+
const DEFAULT_API_TIMEOUT_MS = 6e4;
|
|
1290
1384
|
const RETRY_BACKOFF_MAX_MS = 5e3;
|
|
1291
1385
|
const SAFE_OBSERVATION_CODES = /* @__PURE__ */ new Set([
|
|
1292
1386
|
"EAI_AGAIN",
|
|
@@ -1373,7 +1467,7 @@ function getRequestContext(config) {
|
|
|
1373
1467
|
return {
|
|
1374
1468
|
method: (config.method ?? "GET").toUpperCase(),
|
|
1375
1469
|
endpoint: canonicalEndpointIdentity(config.url ?? ""),
|
|
1376
|
-
page: config.params !== null && isObject
|
|
1470
|
+
page: config.params !== null && isObject(config.params) && "page" in config.params && isNumber(config.params.page) ? config.params.page : void 0
|
|
1377
1471
|
};
|
|
1378
1472
|
}
|
|
1379
1473
|
function emitClientLog(logger, message) {
|
|
@@ -1447,7 +1541,7 @@ function buildUrl(baseUrl, config) {
|
|
|
1447
1541
|
code: "HEVY_INVALID_ENDPOINT"
|
|
1448
1542
|
});
|
|
1449
1543
|
const url = new URL(config.url, baseUrl);
|
|
1450
|
-
if (config.params && isObject
|
|
1544
|
+
if (config.params && isObject(config.params)) {
|
|
1451
1545
|
for (const [key, value] of Object.entries(config.params)) if (value !== void 0) url.searchParams.append(key, value === null ? "null" : String(value));
|
|
1452
1546
|
}
|
|
1453
1547
|
return url;
|
|
@@ -1691,7 +1785,7 @@ function createFailureObservation(options, failure, error, commitState, safeToRe
|
|
|
1691
1785
|
safeToRetry: safeToRetry && !retryExhausted,
|
|
1692
1786
|
error: {
|
|
1693
1787
|
status: error.status,
|
|
1694
|
-
code: isString
|
|
1788
|
+
code: isString(error.code) && SAFE_OBSERVATION_CODES.has(error.code) ? error.code : void 0,
|
|
1695
1789
|
category: error.status === void 0 ? "NetworkError" : "HevyHttpError"
|
|
1696
1790
|
}
|
|
1697
1791
|
};
|
|
@@ -1922,7 +2016,10 @@ function createClient(apiKey, baseUrl = "https://api.hevyapp.com", options = {})
|
|
|
1922
2016
|
getWorkoutEvents: (params, options) => getV1WorkoutsEvents(headers, params, requestOptions(options, client)),
|
|
1923
2017
|
getRoutines: (params, options) => getV1Routines(headers, params, requestOptions(options, client)),
|
|
1924
2018
|
getRoutineById: (routineId, options) => getV1RoutinesRoutineid(routineId, headers, requestOptions(options, client)),
|
|
1925
|
-
createRoutine: (data, options) =>
|
|
2019
|
+
createRoutine: async (data, options) => {
|
|
2020
|
+
const response = await postV1Routines(data, headers, requestOptions(options, client));
|
|
2021
|
+
return Object.keys(response).length === 0 ? void 0 : response;
|
|
2022
|
+
},
|
|
1926
2023
|
updateRoutine: (routineId, data, options) => putV1RoutinesRoutineid(routineId, data, headers, requestOptions(options, client)),
|
|
1927
2024
|
getExerciseTemplates: (params, options) => getV1ExerciseTemplates(headers, params, requestOptions(options, client)),
|
|
1928
2025
|
getExerciseTemplate: (templateId, options) => getV1ExerciseTemplatesExercisetemplateid(templateId, headers, requestOptions(options, client)),
|
|
@@ -1944,28 +2041,6 @@ function createHevyClient({ apiKey, baseUrl, ...options }) {
|
|
|
1944
2041
|
return createClient(apiKey, baseUrl, options);
|
|
1945
2042
|
}
|
|
1946
2043
|
//#endregion
|
|
1947
|
-
//#region ../core/src/utils/type-predicates.ts
|
|
1948
|
-
const stringSchema = z.string();
|
|
1949
|
-
const numberSchema = z.number();
|
|
1950
|
-
const booleanSchema = z.boolean();
|
|
1951
|
-
const objectSchema = z.object({}).passthrough();
|
|
1952
|
-
const functionSchema = z.function();
|
|
1953
|
-
function isString(value) {
|
|
1954
|
-
return stringSchema.safeParse(value).success;
|
|
1955
|
-
}
|
|
1956
|
-
function isFiniteNumber(value) {
|
|
1957
|
-
return numberSchema.safeParse(value).success && Number.isFinite(value);
|
|
1958
|
-
}
|
|
1959
|
-
function isBoolean(value) {
|
|
1960
|
-
return booleanSchema.safeParse(value).success;
|
|
1961
|
-
}
|
|
1962
|
-
function isObject(value) {
|
|
1963
|
-
return value !== null && objectSchema.safeParse(value).success;
|
|
1964
|
-
}
|
|
1965
|
-
function isFunction(value) {
|
|
1966
|
-
return functionSchema.safeParse(value).success;
|
|
1967
|
-
}
|
|
1968
|
-
//#endregion
|
|
1969
2044
|
//#region ../core/src/utils/error-policy.ts
|
|
1970
2045
|
const ABORT_TIMEOUT_METADATA = {
|
|
1971
2046
|
AbortError: {
|
|
@@ -2025,7 +2100,7 @@ const SAFE_SOURCE_SUFFIXES = [
|
|
|
2025
2100
|
const PROJECT_PATH_MARKER = "/hevy-mcp/";
|
|
2026
2101
|
const MAX_STACK_POSITION = 1e6;
|
|
2027
2102
|
function normalizeHeaderValue(value) {
|
|
2028
|
-
if (isString(value)) {
|
|
2103
|
+
if (isString$2(value)) {
|
|
2029
2104
|
const trimmed = value.trim();
|
|
2030
2105
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
2031
2106
|
}
|
|
@@ -2034,7 +2109,7 @@ function normalizeHeaderValue(value) {
|
|
|
2034
2109
|
}
|
|
2035
2110
|
function getHeaderValue(headers, key) {
|
|
2036
2111
|
try {
|
|
2037
|
-
if (!isObject(headers)) return void 0;
|
|
2112
|
+
if (!isObject$2(headers)) return void 0;
|
|
2038
2113
|
if ("get" in headers && isFunction(headers.get)) return normalizeHeaderValue(headers.get(key));
|
|
2039
2114
|
const headerRecord = headers;
|
|
2040
2115
|
return normalizeHeaderValue(headerRecord[key] ?? headerRecord[key.toLowerCase()] ?? headerRecord[key.toUpperCase()]);
|
|
@@ -2054,7 +2129,7 @@ function extractErrorStatus(error) {
|
|
|
2054
2129
|
/** Return whether the client exhausted its bounded transient retry policy. */
|
|
2055
2130
|
function isRetryExhausted(error) {
|
|
2056
2131
|
try {
|
|
2057
|
-
return !!error && isObject(error) && error.hevyRetryExhausted === true;
|
|
2132
|
+
return !!error && isObject$2(error) && error.hevyRetryExhausted === true;
|
|
2058
2133
|
} catch {
|
|
2059
2134
|
return false;
|
|
2060
2135
|
}
|
|
@@ -2075,11 +2150,12 @@ function getRetryAfterSeconds(error, now = Date.now()) {
|
|
|
2075
2150
|
}
|
|
2076
2151
|
}
|
|
2077
2152
|
/** Map bounded Hevy HTTP statuses to stable user-facing messages. */
|
|
2078
|
-
function getStatusErrorMessage(status) {
|
|
2153
|
+
function getStatusErrorMessage(status, method, endpoint) {
|
|
2154
|
+
if (status === 404 && method?.toUpperCase() === "PUT" && endpoint === "/v1/routines/:routineId") return "The requested routine was not found in Hevy. It may have been deleted or the routine ID is incorrect.";
|
|
2155
|
+
if (status === 400 || status === 422) return "The request failed Hevy validation. Check the field values and try again.";
|
|
2079
2156
|
if (status === 401 || status === 403) return "The Hevy API key is invalid or has expired. Check HEVY_API_KEY.";
|
|
2080
2157
|
if (status === 404) return "The requested resource was not found in Hevy.";
|
|
2081
|
-
if (status === 409) return "A conflict occurred
|
|
2082
|
-
if (status === 422) return "The request failed Hevy validation. Check the field values and try again.";
|
|
2158
|
+
if (status === 409) return "A conflict occurred because the resource already exists or conflicts with the current server state. Check whether it already exists and use the update tool when appropriate.";
|
|
2083
2159
|
if (status === 429) return "Rate limited by Hevy. Please wait and retry.";
|
|
2084
2160
|
if (status !== void 0 && status >= 500 && status <= 599) return "Hevy API experienced an error. Please retry later.";
|
|
2085
2161
|
return null;
|
|
@@ -2096,7 +2172,7 @@ function getRateLimitMessage(error) {
|
|
|
2096
2172
|
function getRetryExhaustedMessage(error) {
|
|
2097
2173
|
let retryCount;
|
|
2098
2174
|
try {
|
|
2099
|
-
retryCount = isObject(error) ? error.hevyRetryCount : void 0;
|
|
2175
|
+
retryCount = isObject$2(error) ? error.hevyRetryCount : void 0;
|
|
2100
2176
|
} catch {
|
|
2101
2177
|
retryCount = void 0;
|
|
2102
2178
|
}
|
|
@@ -2142,9 +2218,9 @@ function classifyError(error) {
|
|
|
2142
2218
|
function getSafeCode(error) {
|
|
2143
2219
|
const abortTimeout = getAbortTimeoutErrorMetadata(error);
|
|
2144
2220
|
if (abortTimeout) return abortTimeout.code;
|
|
2145
|
-
if (!isObject(error) || !("code" in error)) return;
|
|
2221
|
+
if (!isObject$2(error) || !("code" in error)) return;
|
|
2146
2222
|
const code = error.code;
|
|
2147
|
-
return isString(code) && SAFE_ERROR_CODES.has(code) ? code : void 0;
|
|
2223
|
+
return isString$2(code) && SAFE_ERROR_CODES.has(code) ? code : void 0;
|
|
2148
2224
|
}
|
|
2149
2225
|
function getSafeMethod(error) {
|
|
2150
2226
|
if (!isHevyHttpError(error)) return void 0;
|
|
@@ -2174,7 +2250,7 @@ function getExecutionFields(error) {
|
|
|
2174
2250
|
return fields;
|
|
2175
2251
|
}
|
|
2176
2252
|
function parseSafeStackFrames(error) {
|
|
2177
|
-
if (!(error instanceof Error) || !isString(error.stack)) return;
|
|
2253
|
+
if (!(error instanceof Error) || !isString$2(error.stack)) return;
|
|
2178
2254
|
const frames = [];
|
|
2179
2255
|
for (const frameLine of error.stack.split(/\r?\n/).slice(1)) {
|
|
2180
2256
|
const match = /^\s{4}at (?:[^()\r\n]+ \()?([^()\s\r\n]+):(\d+):(\d+)\)?$/.exec(frameLine);
|
|
@@ -2219,7 +2295,7 @@ function createSafeErrorDiagnostic(error) {
|
|
|
2219
2295
|
/** Resolve all bounded policy outputs used by MCP, telemetry, and reporting adapters. */
|
|
2220
2296
|
function resolveErrorPolicy(error, defaultMessage, notInitializedMessage) {
|
|
2221
2297
|
const diagnostic = createSafeErrorDiagnostic(error);
|
|
2222
|
-
let message = getStatusErrorMessage(diagnostic.status) ?? (diagnostic.status !== void 0 ? `Hevy API request failed (HTTP ${diagnostic.status}).` : defaultMessage);
|
|
2298
|
+
let message = getStatusErrorMessage(diagnostic.status, diagnostic.method, diagnostic.endpoint) ?? (diagnostic.status !== void 0 ? `Hevy API request failed (HTTP ${diagnostic.status}).` : defaultMessage);
|
|
2223
2299
|
let isNotInitialized = false;
|
|
2224
2300
|
try {
|
|
2225
2301
|
isNotInitialized = Boolean(notInitializedMessage) && error instanceof Error && error.message === notInitializedMessage;
|
|
@@ -2352,6 +2428,13 @@ const formattedRoutineSchema = z.object({
|
|
|
2352
2428
|
updated_at: z.string().optional(),
|
|
2353
2429
|
exercises: z.array(formattedRoutineExerciseSchema).optional()
|
|
2354
2430
|
});
|
|
2431
|
+
const createRoutineOutputSchema = {
|
|
2432
|
+
created: z.literal(true),
|
|
2433
|
+
commit_state: z.literal("confirmed"),
|
|
2434
|
+
routine: formattedRoutineSchema.nullable(),
|
|
2435
|
+
routine_id: z.string().nullable(),
|
|
2436
|
+
uses_rep_ranges: z.boolean()
|
|
2437
|
+
};
|
|
2355
2438
|
const formattedRoutineFolderSchema = z.object({
|
|
2356
2439
|
id: z.number().optional(),
|
|
2357
2440
|
title: z.string().optional(),
|
|
@@ -2970,10 +3053,19 @@ const compactRoutinesResponse = defineStructuredResponseContract({
|
|
|
2970
3053
|
const createWorkoutResponse = defineJsonResponseContract((workout) => workout ? { json: projectWorkout(workout) } : { text: "Failed to create workout: Server returned no data" }, (workout) => workoutResultTelemetry(workout));
|
|
2971
3054
|
const updateWorkoutResponse = defineJsonResponseContract((data) => data.workout ? { json: projectWorkout(data.workout) } : { text: `Failed to update workout with ID ${data.workout_id}` }, (data) => workoutResultTelemetry(data.workout));
|
|
2972
3055
|
const repRangeDisplayWarningText = "Note: Hevy's public API stores rep ranges (rep_range), but the Hevy apps may not display them because they rely on an internal-only exercise field (input_modifier). See https://github.com/chrisdoc/hevy-mcp/issues/261 for details/workarounds.";
|
|
2973
|
-
const createRoutineResponse =
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
3056
|
+
const createRoutineResponse = defineStructuredResponseContract({
|
|
3057
|
+
outputSchema: createRoutineOutputSchema,
|
|
3058
|
+
normalize: (data) => ({
|
|
3059
|
+
created: true,
|
|
3060
|
+
commit_state: "confirmed",
|
|
3061
|
+
routine: data.routine ? projectRoutine(data.routine) : null,
|
|
3062
|
+
routine_id: data.routine?.id ?? null,
|
|
3063
|
+
uses_rep_ranges: data.usesRepRanges
|
|
3064
|
+
}),
|
|
3065
|
+
legacyJson: (output) => output,
|
|
3066
|
+
additionalText: (_data, output) => output.uses_rep_ranges ? [repRangeDisplayWarningText] : [],
|
|
3067
|
+
telemetry: (data) => routineResultTelemetry(data.routine)
|
|
3068
|
+
});
|
|
2977
3069
|
const updateRoutineResponse = defineJsonResponseContract((data) => data.routine ? {
|
|
2978
3070
|
json: projectRoutine(data.routine),
|
|
2979
3071
|
additionalText: data.usesRepRanges ? [repRangeDisplayWarningText] : []
|
|
@@ -2986,76 +3078,6 @@ const createRoutineFolderResponse = defineJsonResponseContract((folder) => folde
|
|
|
2986
3078
|
const createBodyMeasurementResponse = defineJsonResponseContract((date) => ({ text: `Body measurement for ${date} created successfully.` }), () => ({ itemCountBucket: "1" }));
|
|
2987
3079
|
const updateBodyMeasurementResponse = defineJsonResponseContract((date) => ({ text: `Body measurement for ${date} updated successfully.` }), () => ({ itemCountBucket: "1" }));
|
|
2988
3080
|
//#endregion
|
|
2989
|
-
//#region ../core/src/utils/compact-json-schema.ts
|
|
2990
|
-
function isRecord(value) {
|
|
2991
|
-
return isObject(value) && !Array.isArray(value);
|
|
2992
|
-
}
|
|
2993
|
-
function isNullSchema(value) {
|
|
2994
|
-
return isRecord(value) && value.type === "null" && Object.keys(value).length === 1;
|
|
2995
|
-
}
|
|
2996
|
-
function compactNullableSchema(value) {
|
|
2997
|
-
if (Array.isArray(value)) return value.map(compactNullableSchema);
|
|
2998
|
-
if (!isRecord(value)) return value;
|
|
2999
|
-
const anyOf = value.anyOf;
|
|
3000
|
-
if (Array.isArray(anyOf) && anyOf.length === 2) {
|
|
3001
|
-
const nullableBranch = anyOf.find(isNullSchema);
|
|
3002
|
-
const valueBranch = anyOf.find((branch) => !isNullSchema(branch));
|
|
3003
|
-
if (nullableBranch !== void 0 && isRecord(valueBranch) && isString(valueBranch.type)) {
|
|
3004
|
-
const { anyOf: _anyOf, ...metadata } = value;
|
|
3005
|
-
return compactNullableSchema({
|
|
3006
|
-
...valueBranch,
|
|
3007
|
-
...metadata,
|
|
3008
|
-
type: [valueBranch.type, "null"]
|
|
3009
|
-
});
|
|
3010
|
-
}
|
|
3011
|
-
}
|
|
3012
|
-
return Object.fromEntries(Object.entries(value).map(([key, nested]) => [key, compactNullableSchema(nested)]));
|
|
3013
|
-
}
|
|
3014
|
-
function omitOutputObjectRestrictions(value) {
|
|
3015
|
-
if (Array.isArray(value)) return value.map(omitOutputObjectRestrictions);
|
|
3016
|
-
if (!isRecord(value)) return value;
|
|
3017
|
-
return Object.fromEntries(Object.entries(value).filter(([key, nested]) => key !== "additionalProperties" || nested !== false).map(([key, nested]) => [key, omitOutputObjectRestrictions(nested)]));
|
|
3018
|
-
}
|
|
3019
|
-
function convertSchema(schema, io) {
|
|
3020
|
-
const converted = z.toJSONSchema(schema, {
|
|
3021
|
-
target: "draft-2020-12",
|
|
3022
|
-
io
|
|
3023
|
-
});
|
|
3024
|
-
if (!isRecord(converted)) throw new Error("Zod emitted a non-object root schema");
|
|
3025
|
-
const compacted = compactNullableSchema(converted);
|
|
3026
|
-
if (!isRecord(compacted)) throw new Error("Nullable schema compaction changed the root schema");
|
|
3027
|
-
const output = io === "output" ? omitOutputObjectRestrictions(compacted) : compacted;
|
|
3028
|
-
if (!isRecord(output)) throw new Error("Output schema compaction changed the root schema");
|
|
3029
|
-
delete output.$schema;
|
|
3030
|
-
return output;
|
|
3031
|
-
}
|
|
3032
|
-
/**
|
|
3033
|
-
* Keep Zod validation while advertising a compact JSON Schema.
|
|
3034
|
-
*
|
|
3035
|
-
* Zod emits nullable values as `anyOf`; draft 2020-12 also permits a type
|
|
3036
|
-
* array. The MCP wire envelope does not need a repeated `$schema` marker, and
|
|
3037
|
-
* structured output is validated by the original Zod schema before sending.
|
|
3038
|
-
*/
|
|
3039
|
-
function compactJsonSchema(schema) {
|
|
3040
|
-
let inputSchema;
|
|
3041
|
-
let outputSchema;
|
|
3042
|
-
Object.defineProperty(schema["~standard"], "jsonSchema", {
|
|
3043
|
-
configurable: true,
|
|
3044
|
-
value: {
|
|
3045
|
-
input: () => {
|
|
3046
|
-
inputSchema ??= convertSchema(schema, "input");
|
|
3047
|
-
return inputSchema;
|
|
3048
|
-
},
|
|
3049
|
-
output: () => {
|
|
3050
|
-
outputSchema ??= convertSchema(schema, "output");
|
|
3051
|
-
return outputSchema;
|
|
3052
|
-
}
|
|
3053
|
-
},
|
|
3054
|
-
writable: true
|
|
3055
|
-
});
|
|
3056
|
-
return schema;
|
|
3057
|
-
}
|
|
3058
|
-
//#endregion
|
|
3059
3081
|
//#region ../core/src/utils/tool-helpers.ts
|
|
3060
3082
|
/**
|
|
3061
3083
|
* Type-safe utilities for MCP tool handlers
|
|
@@ -3100,21 +3122,43 @@ function createTypedToolHandler(schema, handler) {
|
|
|
3100
3122
|
}
|
|
3101
3123
|
//#endregion
|
|
3102
3124
|
//#region ../core/src/tools/define-tool.ts
|
|
3125
|
+
/**
|
|
3126
|
+
* One-time-per-isolate registration metadata for each tool definition.
|
|
3127
|
+
*
|
|
3128
|
+
* Building a tool's compact JSON Schema (and the SDK's eager wire conversion
|
|
3129
|
+
* of the `compactJsonSchema`-wrapped schema on registration) is the dominant
|
|
3130
|
+
* CPU cost when constructing an MCP server. Tool definitions are module-level
|
|
3131
|
+
* constants, so this metadata can be computed once and shared by every server
|
|
3132
|
+
* a single isolate builds. The SDK's `registerTool` reads the schema strictly
|
|
3133
|
+
* through the Standard Schema interface and never mutates it, so sharing the
|
|
3134
|
+
* memoized config across concurrent servers is safe.
|
|
3135
|
+
*
|
|
3136
|
+
* Keep the memo lazy: CLI and test processes that never build a Hevy server
|
|
3137
|
+
* pay nothing. Isolate-based edge runtimes that want the one-time conversion
|
|
3138
|
+
* moved into module-scope (outside request CPU) call `preloadHevyToolSchemas`.
|
|
3139
|
+
*/
|
|
3140
|
+
const registeredToolConfigCache = /* @__PURE__ */ new WeakMap();
|
|
3141
|
+
function getRegisteredToolConfig(definition) {
|
|
3142
|
+
const cached = registeredToolConfigCache.get(definition);
|
|
3143
|
+
if (cached) return cached;
|
|
3144
|
+
const config = {
|
|
3145
|
+
description: definition.description,
|
|
3146
|
+
inputSchema: compactJsonSchema(z.strictObject(definition.inputSchema)),
|
|
3147
|
+
annotations: definition.annotations
|
|
3148
|
+
};
|
|
3149
|
+
if (definition.outputSchema) config.outputSchema = compactJsonSchema(z.object(definition.outputSchema));
|
|
3150
|
+
registeredToolConfigCache.set(definition, config);
|
|
3151
|
+
return config;
|
|
3152
|
+
}
|
|
3103
3153
|
function registerToolDefinition(server, runtime, definition) {
|
|
3104
3154
|
const directHandler = createTypedToolHandler(definition.inputSchema, async (args, requestContext) => respond(definition.responseContract, await definition.execute(requestContext ? runtime.forExecution(requestContext) : runtime, args)));
|
|
3105
|
-
const
|
|
3155
|
+
const handler = runtime.createHandler(directHandler, definition.name, {
|
|
3106
3156
|
feature: definition.feature,
|
|
3107
3157
|
kind: definition.kind,
|
|
3108
3158
|
operation: definition.operation
|
|
3109
3159
|
});
|
|
3110
|
-
const
|
|
3111
|
-
|
|
3112
|
-
description: definition.description,
|
|
3113
|
-
inputSchema,
|
|
3114
|
-
annotations: definition.annotations
|
|
3115
|
-
};
|
|
3116
|
-
if (definition.kind === "read") config.outputSchema = compactJsonSchema(z.object(definition.outputSchema));
|
|
3117
|
-
server.registerTool(definition.name, config, (args, context) => callback(z.strictObject(definition.inputSchema).parse(args), context ? {
|
|
3160
|
+
const config = getRegisteredToolConfig(definition);
|
|
3161
|
+
server.registerTool(definition.name, config, (args, context) => handler(z.strictObject(definition.inputSchema).parse(args), context ? {
|
|
3118
3162
|
signal: context.mcpReq.signal,
|
|
3119
3163
|
requestId: String(context.mcpReq.id)
|
|
3120
3164
|
} : void 0));
|
|
@@ -3165,7 +3209,7 @@ function updateAnnotations(title) {
|
|
|
3165
3209
|
* @returns The parsed array if val is a valid JSON string, otherwise returns val unchanged
|
|
3166
3210
|
*/
|
|
3167
3211
|
function parseJsonArray(val) {
|
|
3168
|
-
if (isString(val)) try {
|
|
3212
|
+
if (isString$2(val)) try {
|
|
3169
3213
|
return JSON.parse(val);
|
|
3170
3214
|
} catch {
|
|
3171
3215
|
return val;
|
|
@@ -3176,7 +3220,7 @@ function parseJsonArray(val) {
|
|
|
3176
3220
|
//#region ../core/src/utils/schemas.ts
|
|
3177
3221
|
function coerceNullishNumberInput(value) {
|
|
3178
3222
|
if (value === null || value === void 0) return value;
|
|
3179
|
-
if (!isString(value)) return value;
|
|
3223
|
+
if (!isString$2(value)) return value;
|
|
3180
3224
|
const trimmed = value.trim();
|
|
3181
3225
|
if (trimmed === "") return;
|
|
3182
3226
|
const lowered = trimmed.toLowerCase();
|
|
@@ -3488,7 +3532,7 @@ const FETCHED_ISO_TIMESTAMP = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(
|
|
|
3488
3532
|
* supplied values remain strict.
|
|
3489
3533
|
*/
|
|
3490
3534
|
function normalizeFetchedWorkoutTimestamp(value) {
|
|
3491
|
-
if (!isString(value)) return value;
|
|
3535
|
+
if (!isString$2(value)) return value;
|
|
3492
3536
|
const match = FETCHED_ISO_TIMESTAMP.exec(value);
|
|
3493
3537
|
if (!match) return value;
|
|
3494
3538
|
const [, year, month, day, hour, minute, second, offset] = match;
|
|
@@ -3772,6 +3816,7 @@ const routineToolDefinitions = [
|
|
|
3772
3816
|
description: "Writes a reusable routine; use create-workout for completed sessions. Retries can create duplicates.",
|
|
3773
3817
|
inputSchema: createRoutineInputFields,
|
|
3774
3818
|
kind: "write",
|
|
3819
|
+
outputSchema: createRoutineOutputSchema,
|
|
3775
3820
|
annotations: createAnnotations("Create Routine"),
|
|
3776
3821
|
responseContract: createRoutineResponse,
|
|
3777
3822
|
execute: async (runtime, args) => {
|
|
@@ -4403,7 +4448,7 @@ function withPromptObservation(name, observer, handler) {
|
|
|
4403
4448
|
let scope;
|
|
4404
4449
|
try {
|
|
4405
4450
|
const argumentKeys = Object.keys(args).filter((key) => key === "routine_id");
|
|
4406
|
-
const routineId = "routine_id" in args && isString(args.routine_id) ? args.routine_id : void 0;
|
|
4451
|
+
const routineId = "routine_id" in args && isString$2(args.routine_id) ? args.routine_id : void 0;
|
|
4407
4452
|
scope = memoizeObservationScope(observer?.start({
|
|
4408
4453
|
name,
|
|
4409
4454
|
kind: "prompt",
|
|
@@ -4661,13 +4706,14 @@ function readBuildGlobal(value) {
|
|
|
4661
4706
|
}
|
|
4662
4707
|
}
|
|
4663
4708
|
const buildName = readBuildGlobal(() => "hevy-mcp");
|
|
4664
|
-
const buildVersion = readBuildGlobal(() => "6.1.
|
|
4709
|
+
const buildVersion = readBuildGlobal(() => "6.1.4");
|
|
4665
4710
|
const SERVER_NAME = readBuildString(buildName, "hevy-mcp");
|
|
4666
4711
|
const SERVER_VERSION = readBuildString(buildVersion, "dev");
|
|
4667
4712
|
const SERVER_INSTRUCTIONS = [
|
|
4668
4713
|
"Hevy MCP connects clients to the authenticated user's Hevy workout-tracking data, including workouts, routines, exercise templates, routine folders, body measurements, and profile information. HEVY_API_KEY must contain a valid Hevy API key for local stdio use.",
|
|
4669
4714
|
"Safety: all get-* and search-* tools are read-only. create-* and update-* tools mutate Hevy data. Creates are additive and non-idempotent, so repeating one can create duplicates. Updates can overwrite existing data. Delete operations are not available.",
|
|
4670
4715
|
"Workflow: search exercise templates first, then use the returned template IDs when creating workouts or routines. To create a completed workout from a routine, fetch the routine as a plan, then obtain the actual completed sets and end time from the user; never invent completion data.",
|
|
4716
|
+
"Tool protocol: always call the exact tool name and pass inputs in the tools/call arguments object. Never append parameters to a tool name (for example, call get-workout with {\"workout_id\": \"<id>\"}, not get-workout-workoutId=<id>). The canonical discovery tools are get-workout, get-exercise-template, and get-routine-folder; get-workouts, search-exercise-templates, and the hevy://exercise-templates and hevy://routine-folders resources discover records. get-user-info is not an MCP tool; use the hevy://user resource.",
|
|
4671
4717
|
"Resources: use hevy://user, hevy://workout-count, hevy://exercise-templates, and hevy://routine-folders for complete cached datasets that do not require parameters. Use search-exercise-templates when you need title or muscle-group filtering.",
|
|
4672
4718
|
"Pagination: start at page 1 and fetch only the pages needed. List tools that remain available use bounded page sizes.",
|
|
4673
4719
|
"Rate limits and retries: minimize repeated calls. If Hevy returns HTTP 429, follow its retry guidance. Transient read requests retry automatically, but write requests do not; confirm uncertain write outcomes before trying again."
|
|
@@ -5244,4 +5290,4 @@ function createHevyMcpServer(options) {
|
|
|
5244
5290
|
//#endregion
|
|
5245
5291
|
export { createSafeErrorDiagnostic as a, diagnosticEndpointIdentity as c, bucketCount as i, metricEndpointIdentity as l, createExecutionProjection as n, createHevyClient as o, mergeAbortSignals as r, SAFE_OBSERVATION_CODES as s, createHevyMcpServer as t, isHevyHttpError as u };
|
|
5246
5292
|
|
|
5247
|
-
//# sourceMappingURL=src-
|
|
5293
|
+
//# sourceMappingURL=src-DCTiE-_b.mjs.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hevy-mcp",
|
|
3
|
-
"version": "6.1.
|
|
3
|
+
"version": "6.1.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A Model Context Protocol (MCP) server implementation that interfaces with the Hevy fitness tracking app and its API.",
|
|
6
6
|
"repository": {
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@hevy-mcp/core": "*",
|
|
52
52
|
"@hevy-mcp/hevy-client": "*",
|
|
53
|
-
"@types/node": "^26.
|
|
53
|
+
"@types/node": "^26.2.0",
|
|
54
54
|
"cross-env": "^10.1.0",
|
|
55
55
|
"tsdown": "^0.22.14",
|
|
56
56
|
"typescript": "^7.0.2"
|
package/server.json
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"url": "https://github.com/chrisdoc/hevy-mcp",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "6.1.
|
|
10
|
+
"version": "6.1.4",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "hevy-mcp",
|
|
15
|
-
"version": "6.1.
|
|
15
|
+
"version": "6.1.4",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|