hevy-mcp 4.1.0 → 4.1.2
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 +48 -11
- package/dist/cli.mjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{src-wSWxe4LJ.mjs → src-Wa4VP4Na.mjs} +92 -80
- package/package.json +12 -12
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -414,17 +414,18 @@ self-hosted Streamable HTTP.
|
|
|
414
414
|
|
|
415
415
|
## Advanced configuration
|
|
416
416
|
|
|
417
|
-
| Setting | Default | Scope | Notes
|
|
418
|
-
| ---------------------------- | ------------------------------ | ----------------------------- |
|
|
419
|
-
| `HEVY_API_KEY` | None; required | Local stdio or HTTP | Hevy API key from the Hevy app. Never pass it in a URL.
|
|
420
|
-
| `HEVY_MCP_API_TIMEOUT` | `30000` ms | Local stdio | Positive Hevy API timeout in milliseconds. Invalid values fall back to 30 seconds.
|
|
421
|
-
| `HEVY_MCP_DEBUG` | Disabled | Local Node | Set to exactly `1` for privacy-bounded diagnostics on stderr. Stdout remains reserved for MCP JSON-RPC.
|
|
422
|
-
| `HEVY_MCP_HTTP_BEARER_TOKEN` | None | Non-loopback HTTP | Required when `--host` is not loopback; use a separate token, never the Hevy API key.
|
|
423
|
-
| `
|
|
424
|
-
| `
|
|
425
|
-
| `
|
|
426
|
-
|
|
|
427
|
-
| `-
|
|
417
|
+
| Setting | Default | Scope | Notes |
|
|
418
|
+
| ---------------------------- | ------------------------------ | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
419
|
+
| `HEVY_API_KEY` | None; required | Local stdio or HTTP | Hevy API key from the Hevy app. Never pass it in a URL. |
|
|
420
|
+
| `HEVY_MCP_API_TIMEOUT` | `30000` ms | Local stdio | Positive Hevy API timeout in milliseconds. Invalid values fall back to 30 seconds. |
|
|
421
|
+
| `HEVY_MCP_DEBUG` | Disabled | Local Node | Set to exactly `1` for privacy-bounded diagnostics on stderr. Stdout remains reserved for MCP JSON-RPC. |
|
|
422
|
+
| `HEVY_MCP_HTTP_BEARER_TOKEN` | None | Non-loopback HTTP | Required when `--host` is not loopback; use a separate token, never the Hevy API key. |
|
|
423
|
+
| `HEVY_MCP_TELEMETRY` | Enabled | Local Node | Set to exactly `0` before startup/import to disable Sentry errors/traces and OTLP traces/metrics. Takes precedence over `SENTRY_DSN` and packaged/runtime collector credentials. |
|
|
424
|
+
| `XDG_CACHE_HOME` | `~/.cache` | Local stdio | Changes the root for the npm update-check cache at `hevy-mcp/update-check.json`. |
|
|
425
|
+
| `SENTRY_DSN` | Packaged project DSN | Optional local Node telemetry | Sentry-only override for the destination. An empty value disables Sentry export. The Worker does not import Node telemetry. |
|
|
426
|
+
| `SENTRY_RELEASE` | `hevy-mcp@<installed-version>` | Optional local Node telemetry | Overrides the release label attached to local Sentry events and traces. |
|
|
427
|
+
| `-h`, `--help` | N/A | Local stdio CLI | Print supported options and exit. |
|
|
428
|
+
| `-v`, `--version` | N/A | Local stdio CLI | Print the installed version and exit. |
|
|
428
429
|
|
|
429
430
|
The local executable uses stdio by default. To opt into Streamable HTTP, run:
|
|
430
431
|
|
|
@@ -459,6 +460,42 @@ server-scoped in-memory catalog cache:
|
|
|
459
460
|
- Paginated `get-exercise-templates` calls always fetch their requested page.
|
|
460
461
|
- Each hosted Worker request gets a fresh cache, preventing cross-key sharing.
|
|
461
462
|
|
|
463
|
+
### Local Node telemetry and privacy
|
|
464
|
+
|
|
465
|
+
The local Node package enables project telemetry by default. It is local Node
|
|
466
|
+
behavior only; the Cloudflare Worker does not import Node telemetry. Set
|
|
467
|
+
`HEVY_MCP_TELEMETRY=0` before startup or import to disable all project
|
|
468
|
+
telemetry. Only the literal value `0` opts out: an unset value, an empty value,
|
|
469
|
+
`1`, `false`, and every other value remain enabled. The master setting takes
|
|
470
|
+
precedence over `SENTRY_DSN` and packaged or runtime `OTEL_COLLECTOR_TOKEN`
|
|
471
|
+
credentials, so the disabled path creates no telemetry exporters or periodic
|
|
472
|
+
metric readers and makes no telemetry network requests. `SENTRY_DSN` remains a
|
|
473
|
+
Sentry-only setting; when telemetry is enabled, an empty value disables only
|
|
474
|
+
Sentry export.
|
|
475
|
+
|
|
476
|
+
When enabled, errors and traces are sent to the packaged Sentry project at
|
|
477
|
+
<https://o4508975499575296.ingest.de.sentry.io/4509049671647312>. Traces and
|
|
478
|
+
metrics are sent to the collector at
|
|
479
|
+
<https://otel.chrisdoc.dev/v1/traces> and
|
|
480
|
+
<https://otel.chrisdoc.dev/v1/metrics>, which forward to Honeycomb. Metrics
|
|
481
|
+
export every 10 seconds.
|
|
482
|
+
|
|
483
|
+
The API key is never exported. Enabled telemetry derives a deterministic
|
|
484
|
+
ten-character HMAC-SHA-256 pseudonym from it solely for cross-span
|
|
485
|
+
correlation. The pseudonym is attached to spans only, never used as a metric
|
|
486
|
+
dimension, and is not intended for per-user behavior histories.
|
|
487
|
+
|
|
488
|
+
The privacy allowlist contains service/version/transport; fixed tool feature,
|
|
489
|
+
read/write kind, operation, and short-lived tool name; bounded
|
|
490
|
+
outcome/error/count/retry/duration/session/cache/workflow values; normalized API
|
|
491
|
+
method/endpoint/status; shape-only key names, presence, count, and boolean
|
|
492
|
+
fields; sanitized client/protocol tokens; and the span-only pseudonym. It
|
|
493
|
+
explicitly prohibits raw prompts, tool argument values, tool result content,
|
|
494
|
+
request bodies, API keys, raw identifiers/queries/exact dates,
|
|
495
|
+
workout/routine/folder/template/body-measurement content, names/titles/
|
|
496
|
+
descriptions/notes, measurement values, arbitrary client metadata, and
|
|
497
|
+
unnormalized endpoint paths.
|
|
498
|
+
|
|
462
499
|
## Security and mutations
|
|
463
500
|
|
|
464
501
|
- Keep `HEVY_API_KEY` out of source control, URLs, logs, and screenshots.
|
package/dist/cli.mjs
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
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@4.1.
|
|
7
|
+
e.SENTRY_RELEASE = { id: "hevy-mcp@4.1.2" };
|
|
8
8
|
var n = new e.Error().stack;
|
|
9
9
|
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "625c4c9c-e20d-40b3-96b1-4e799ef130f3", e._sentryDebugIdIdentifier = "sentry-dbid-625c4c9c-e20d-40b3-96b1-4e799ef130f3");
|
|
10
10
|
} catch (e) {}
|
|
11
11
|
})();
|
|
12
|
-
import { a as createSafeErrorDiagnostic, i as MissingHevyApiKeyError, n as runServer, o as flushTelemetry } from "./src-
|
|
12
|
+
import { a as createSafeErrorDiagnostic, i as MissingHevyApiKeyError, n as runServer, o as flushTelemetry } from "./src-Wa4VP4Na.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
|
@@ -4,10 +4,10 @@
|
|
|
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@4.1.
|
|
7
|
+
e.SENTRY_RELEASE = { id: "hevy-mcp@4.1.2" };
|
|
8
8
|
var n = new e.Error().stack;
|
|
9
9
|
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "e9496b31-626b-4ad1-9416-47ba4ffd2995", e._sentryDebugIdIdentifier = "sentry-dbid-e9496b31-626b-4ad1-9416-47ba4ffd2995");
|
|
10
10
|
} catch (e) {}
|
|
11
11
|
})();
|
|
12
|
-
import { n as runServer, r as runStdioServer, t as createNodeMcpServer } from "./src-
|
|
12
|
+
import { n as runServer, r as runStdioServer, t as createNodeMcpServer } from "./src-Wa4VP4Na.mjs";
|
|
13
13
|
export { createNodeMcpServer, runServer, runStdioServer };
|
|
@@ -4,11 +4,12 @@
|
|
|
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@4.1.
|
|
7
|
+
e.SENTRY_RELEASE = { id: "hevy-mcp@4.1.2" };
|
|
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] = "b064b429-35aa-4617-a2a9-779c7c2ed596", e._sentryDebugIdIdentifier = "sentry-dbid-b064b429-35aa-4617-a2a9-779c7c2ed596");
|
|
10
10
|
} catch (e) {}
|
|
11
11
|
})();
|
|
12
|
+
import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
|
|
12
13
|
import * as Sentry from "@sentry/node";
|
|
13
14
|
import { SentryPropagator, SentrySampler, SentrySpanProcessor } from "@sentry/opentelemetry";
|
|
14
15
|
import { SpanStatusCode, metrics, trace } from "@opentelemetry/api";
|
|
@@ -18,7 +19,6 @@ import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
|
18
19
|
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
19
20
|
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
|
20
21
|
import { MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
|
21
|
-
import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
|
|
22
22
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
23
23
|
import { z } from "zod";
|
|
24
24
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -81,26 +81,15 @@ function sanitizeSentryMcpSpan(span) {
|
|
|
81
81
|
* OTel Collector → Honeycomb: performance traces, metrics
|
|
82
82
|
*/
|
|
83
83
|
const name$1 = "hevy-mcp";
|
|
84
|
-
const version$1 = "4.1.
|
|
84
|
+
const version$1 = "4.1.2";
|
|
85
|
+
const telemetryEnabled = process.env.HEVY_MCP_TELEMETRY !== "0";
|
|
85
86
|
const collectorToken = "NH9vOela-HYreQxAbJa68cjEmORoEKM57EvneUDVcOo";
|
|
86
87
|
const COLLECTOR_ENDPOINT = "https://otel.chrisdoc.dev/v1";
|
|
87
|
-
const sentryRelease = process.env.SENTRY_RELEASE ?? `hevy-mcp@4.1.
|
|
88
|
+
const sentryRelease = process.env.SENTRY_RELEASE ?? `hevy-mcp@4.1.2`;
|
|
88
89
|
const resource = resourceFromAttributes({
|
|
89
90
|
"service.name": name$1,
|
|
90
91
|
"service.version": version$1
|
|
91
92
|
});
|
|
92
|
-
const rawDsn = process.env.SENTRY_DSN ?? "https://ce696d8333b507acbf5203eb877bce0f@o4508975499575296.ingest.de.sentry.io/4509049671647312";
|
|
93
|
-
const isValidDsn = typeof rawDsn === "string" && rawDsn.length > 0 && !rawDsn.startsWith("*");
|
|
94
|
-
const sentryClient = Sentry.init({
|
|
95
|
-
dsn: isValidDsn ? rawDsn : void 0,
|
|
96
|
-
beforeSendSpan: sanitizeSentryMcpSpan,
|
|
97
|
-
release: sentryRelease,
|
|
98
|
-
tracesSampleRate: 1,
|
|
99
|
-
sendDefaultPii: false,
|
|
100
|
-
skipOpenTelemetrySetup: true,
|
|
101
|
-
registerEsmLoaderHooks: false,
|
|
102
|
-
ignoreErrors: ["EPIPE", "broken pipe"]
|
|
103
|
-
});
|
|
104
93
|
let currentUserHash;
|
|
105
94
|
var UserHashSpanProcessor = class {
|
|
106
95
|
onStart(span) {
|
|
@@ -110,37 +99,53 @@ var UserHashSpanProcessor = class {
|
|
|
110
99
|
async forceFlush() {}
|
|
111
100
|
async shutdown() {}
|
|
112
101
|
};
|
|
113
|
-
|
|
114
|
-
spanProcessors.push(new BatchSpanProcessor(new OTLPTraceExporter({
|
|
115
|
-
url: `${COLLECTOR_ENDPOINT}/traces`,
|
|
116
|
-
headers: { Authorization: `Bearer ${collectorToken}` }
|
|
117
|
-
})));
|
|
118
|
-
const tracerProvider = new NodeTracerProvider({
|
|
119
|
-
resource,
|
|
120
|
-
sampler: sentryClient ? new SentrySampler(sentryClient) : void 0,
|
|
121
|
-
spanProcessors
|
|
122
|
-
});
|
|
123
|
-
tracerProvider.register({
|
|
124
|
-
propagator: new SentryPropagator(),
|
|
125
|
-
contextManager: new Sentry.SentryContextManager()
|
|
126
|
-
});
|
|
102
|
+
let tracerProvider;
|
|
127
103
|
let meterProvider;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
104
|
+
if (telemetryEnabled) {
|
|
105
|
+
const rawDsn = process.env.SENTRY_DSN ?? "https://ce696d8333b507acbf5203eb877bce0f@o4508975499575296.ingest.de.sentry.io/4509049671647312";
|
|
106
|
+
const isValidDsn = typeof rawDsn === "string" && rawDsn.length > 0 && !rawDsn.startsWith("*");
|
|
107
|
+
const sentryClient = Sentry.init({
|
|
108
|
+
dsn: isValidDsn ? rawDsn : void 0,
|
|
109
|
+
beforeSendSpan: sanitizeSentryMcpSpan,
|
|
110
|
+
release: sentryRelease,
|
|
111
|
+
tracesSampleRate: 1,
|
|
112
|
+
sendDefaultPii: false,
|
|
113
|
+
skipOpenTelemetrySetup: true,
|
|
114
|
+
registerEsmLoaderHooks: false,
|
|
115
|
+
ignoreErrors: ["EPIPE", "broken pipe"]
|
|
116
|
+
});
|
|
117
|
+
const spanProcessors = [new UserHashSpanProcessor(), new SentrySpanProcessor()];
|
|
118
|
+
spanProcessors.push(new BatchSpanProcessor(new OTLPTraceExporter({
|
|
119
|
+
url: `${COLLECTOR_ENDPOINT}/traces`,
|
|
120
|
+
headers: { Authorization: `Bearer ${collectorToken}` }
|
|
121
|
+
})));
|
|
122
|
+
tracerProvider = new NodeTracerProvider({
|
|
123
|
+
resource,
|
|
124
|
+
sampler: sentryClient ? new SentrySampler(sentryClient) : void 0,
|
|
125
|
+
spanProcessors
|
|
126
|
+
});
|
|
127
|
+
tracerProvider.register({
|
|
128
|
+
propagator: new SentryPropagator(),
|
|
129
|
+
contextManager: new Sentry.SentryContextManager()
|
|
130
|
+
});
|
|
131
|
+
meterProvider = new MeterProvider({
|
|
132
|
+
resource,
|
|
133
|
+
readers: [new PeriodicExportingMetricReader({
|
|
134
|
+
exporter: new OTLPMetricExporter({
|
|
135
|
+
url: `${COLLECTOR_ENDPOINT}/metrics`,
|
|
136
|
+
headers: { Authorization: `Bearer ${collectorToken}` }
|
|
137
|
+
}),
|
|
138
|
+
exportIntervalMillis: 1e4
|
|
139
|
+
})]
|
|
140
|
+
});
|
|
141
|
+
metrics.setGlobalMeterProvider(meterProvider);
|
|
142
|
+
trace.setGlobalTracerProvider(tracerProvider);
|
|
143
|
+
Sentry.validateOpenTelemetrySetup();
|
|
144
|
+
}
|
|
141
145
|
async function flushTelemetry(timeoutMs = 1e3) {
|
|
146
|
+
if (!telemetryEnabled) return;
|
|
142
147
|
const flushPromise = Promise.allSettled([
|
|
143
|
-
tracerProvider.forceFlush(),
|
|
148
|
+
...tracerProvider ? [tracerProvider.forceFlush()] : [],
|
|
144
149
|
...meterProvider ? [meterProvider.forceFlush()] : [],
|
|
145
150
|
Sentry.flush(timeoutMs)
|
|
146
151
|
]);
|
|
@@ -156,8 +161,12 @@ async function flushTelemetry(timeoutMs = 1e3) {
|
|
|
156
161
|
}
|
|
157
162
|
const tracer = trace.getTracer(name$1);
|
|
158
163
|
const meter = metrics.getMeter(name$1);
|
|
159
|
-
|
|
160
|
-
|
|
164
|
+
const SENTRY_USER_ID_CONTEXT = "hevy-mcp:sentry-user-id:v1";
|
|
165
|
+
function setTelemetryUser(apiKey) {
|
|
166
|
+
if (!telemetryEnabled) return;
|
|
167
|
+
const userHash = createHmac("sha256", apiKey).update(SENTRY_USER_ID_CONTEXT).digest("hex").slice(0, 10);
|
|
168
|
+
currentUserHash = userHash;
|
|
169
|
+
Sentry.setUser({ id: userHash });
|
|
161
170
|
}
|
|
162
171
|
//#endregion
|
|
163
172
|
//#region src/utils/metrics.ts
|
|
@@ -211,10 +220,15 @@ function coerceNullishNumberInput(value) {
|
|
|
211
220
|
}
|
|
212
221
|
const zNullableInt = z.preprocess(coerceNullishNumberInput, z.number().int().nullable().optional());
|
|
213
222
|
const zNullableNumber = z.preprocess((value) => value === "" ? void 0 : value, z.coerce.number().nullable().optional());
|
|
214
|
-
const
|
|
223
|
+
const repRangeShape = {
|
|
215
224
|
start: zNullableInt,
|
|
216
225
|
end: zNullableInt
|
|
217
|
-
}
|
|
226
|
+
};
|
|
227
|
+
function optionalRepRangeSchema(schema) {
|
|
228
|
+
return z.preprocess((value) => value === null ? void 0 : value, schema);
|
|
229
|
+
}
|
|
230
|
+
const zOptionalRepRange = optionalRepRangeSchema(z.object(repRangeShape).optional());
|
|
231
|
+
optionalRepRangeSchema(z.object(repRangeShape).strict().optional());
|
|
218
232
|
const setTypeEnum = z.enum([
|
|
219
233
|
"warmup",
|
|
220
234
|
"normal",
|
|
@@ -2586,7 +2600,7 @@ function registerHevyResources(server, runtime) {
|
|
|
2586
2600
|
//#endregion
|
|
2587
2601
|
//#region ../core/src/server-metadata.ts
|
|
2588
2602
|
const SERVER_NAME = "hevy-mcp";
|
|
2589
|
-
const SERVER_VERSION = "4.1.
|
|
2603
|
+
const SERVER_VERSION = "4.1.2";
|
|
2590
2604
|
const SERVER_INSTRUCTIONS = [
|
|
2591
2605
|
"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.",
|
|
2592
2606
|
"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.",
|
|
@@ -2731,6 +2745,14 @@ function paginationShape({ defaultPageSize, maxPageSize, integerPage = true }) {
|
|
|
2731
2745
|
};
|
|
2732
2746
|
}
|
|
2733
2747
|
const nonEmptyId = z.string().min(1);
|
|
2748
|
+
const exerciseTemplateInputShape = {
|
|
2749
|
+
title: z.string().min(1),
|
|
2750
|
+
exerciseType: exerciseTypeEnum,
|
|
2751
|
+
equipmentCategory: equipmentCategoryEnum,
|
|
2752
|
+
muscleGroup: muscleGroupEnum,
|
|
2753
|
+
otherMuscles: z.array(muscleGroupEnum).default([])
|
|
2754
|
+
};
|
|
2755
|
+
const routineFolderInputShape = { name: z.string().min(1) };
|
|
2734
2756
|
const CALENDAR_DATE_MESSAGE = "Date must be in YYYY-MM-DD format";
|
|
2735
2757
|
const calendarDate = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, CALENDAR_DATE_MESSAGE).refine((value) => {
|
|
2736
2758
|
const parsed = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
|
|
@@ -2931,6 +2953,18 @@ function buildMeasurementPayload(fields) {
|
|
|
2931
2953
|
}
|
|
2932
2954
|
return payload;
|
|
2933
2955
|
}
|
|
2956
|
+
function buildExerciseTemplateRequest(input) {
|
|
2957
|
+
return { exercise: {
|
|
2958
|
+
title: input.title,
|
|
2959
|
+
exercise_type: input.exerciseType,
|
|
2960
|
+
equipment_category: input.equipmentCategory,
|
|
2961
|
+
muscle_group: input.muscleGroup,
|
|
2962
|
+
other_muscles: input.otherMuscles
|
|
2963
|
+
} };
|
|
2964
|
+
}
|
|
2965
|
+
function buildRoutineFolderRequest(input) {
|
|
2966
|
+
return { routine_folder: { title: input.name } };
|
|
2967
|
+
}
|
|
2934
2968
|
//#endregion
|
|
2935
2969
|
//#region ../core/src/utils/hevy-error-policy.ts
|
|
2936
2970
|
const READ_RESOURCE_ENDPOINTS = /* @__PURE__ */ new Set([
|
|
@@ -3110,7 +3144,7 @@ const getRoutineFoldersSchema = paginationShape({
|
|
|
3110
3144
|
maxPageSize: 10
|
|
3111
3145
|
});
|
|
3112
3146
|
const getRoutineFolderSchema = { folderId: nonEmptyId };
|
|
3113
|
-
const createRoutineFolderSchema =
|
|
3147
|
+
const createRoutineFolderSchema = routineFolderInputShape;
|
|
3114
3148
|
const folderToolDefinitions = [
|
|
3115
3149
|
{
|
|
3116
3150
|
name: "get-routine-folders",
|
|
@@ -3208,8 +3242,7 @@ const folderToolDefinitions = [
|
|
|
3208
3242
|
kind: "write",
|
|
3209
3243
|
responseContract: createRoutineFolderResponse,
|
|
3210
3244
|
execute: async (runtime, args) => {
|
|
3211
|
-
|
|
3212
|
-
return runtime.getClient().createRoutineFolder({ routine_folder: { title: name } });
|
|
3245
|
+
return runtime.getClient().createRoutineFolder(buildRoutineFolderRequest(args));
|
|
3213
3246
|
}
|
|
3214
3247
|
}
|
|
3215
3248
|
];
|
|
@@ -3374,13 +3407,7 @@ const getExerciseHistorySchema = {
|
|
|
3374
3407
|
startDate: z.string().datetime({ offset: true }).describe("ISO 8601 start date for filtering history").optional(),
|
|
3375
3408
|
endDate: z.string().datetime({ offset: true }).describe("ISO 8601 end date for filtering history").optional()
|
|
3376
3409
|
};
|
|
3377
|
-
const createExerciseTemplateSchema =
|
|
3378
|
-
title: z.string().min(1),
|
|
3379
|
-
exerciseType: exerciseTypeEnum,
|
|
3380
|
-
equipmentCategory: equipmentCategoryEnum,
|
|
3381
|
-
muscleGroup: muscleGroupEnum,
|
|
3382
|
-
otherMuscles: z.array(muscleGroupEnum).default([])
|
|
3383
|
-
};
|
|
3410
|
+
const createExerciseTemplateSchema = exerciseTemplateInputShape;
|
|
3384
3411
|
const searchExerciseTemplatesSchema = {
|
|
3385
3412
|
query: z.string().min(1).describe("Case-insensitive substring to match against exercise template titles"),
|
|
3386
3413
|
primaryMuscleGroup: muscleGroupEnum.optional().describe("Optional filter to restrict results to a specific primary muscle group"),
|
|
@@ -3514,14 +3541,7 @@ const templateToolDefinitions = [
|
|
|
3514
3541
|
kind: "write",
|
|
3515
3542
|
responseContract: createExerciseTemplateResponse,
|
|
3516
3543
|
execute: async (runtime, args) => {
|
|
3517
|
-
|
|
3518
|
-
return await runtime.getClient().createExerciseTemplate({ exercise: {
|
|
3519
|
-
title,
|
|
3520
|
-
exercise_type: exerciseType,
|
|
3521
|
-
equipment_category: equipmentCategory,
|
|
3522
|
-
muscle_group: muscleGroup,
|
|
3523
|
-
other_muscles: otherMuscles
|
|
3524
|
-
} });
|
|
3544
|
+
return runtime.getClient().createExerciseTemplate(buildExerciseTemplateRequest(args));
|
|
3525
3545
|
}
|
|
3526
3546
|
},
|
|
3527
3547
|
{
|
|
@@ -5542,6 +5562,7 @@ const HELP_TEXT = [
|
|
|
5542
5562
|
" HEVY_API_KEY=<api-key> Hevy API key from Hevy app settings",
|
|
5543
5563
|
" HEVY_MCP_DEBUG=1 Enable verbose diagnostics on stderr",
|
|
5544
5564
|
" HEVY_MCP_HTTP_BEARER_TOKEN Protect non-loopback HTTP deployments",
|
|
5565
|
+
" HEVY_MCP_TELEMETRY=0 Disable all project telemetry",
|
|
5545
5566
|
"",
|
|
5546
5567
|
"Examples:",
|
|
5547
5568
|
" HEVY_API_KEY=your-key npx hevy-mcp",
|
|
@@ -5570,10 +5591,6 @@ const SAFE_NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
|
5570
5591
|
"ETIMEDOUT",
|
|
5571
5592
|
"HEVY_RETRY_EXHAUSTED"
|
|
5572
5593
|
]);
|
|
5573
|
-
const SENTRY_USER_ID_CONTEXT = "hevy-mcp:sentry-user-id:v1";
|
|
5574
|
-
function fingerprintApiKey(apiKey) {
|
|
5575
|
-
return createHmac("sha256", apiKey).update(SENTRY_USER_ID_CONTEXT).digest("hex").slice(0, 10);
|
|
5576
|
-
}
|
|
5577
5594
|
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).") });
|
|
5578
5595
|
function getHttpStatus(error) {
|
|
5579
5596
|
if (isHevyHttpError(error)) return error.status;
|
|
@@ -5638,9 +5655,7 @@ function buildServer(apiKey, transport = "stdio") {
|
|
|
5638
5655
|
}
|
|
5639
5656
|
async function createNodeMcpServer({ apiKey }, transport = "stdio") {
|
|
5640
5657
|
const { apiKey: validatedApiKey } = serverConfigSchema.parse({ apiKey });
|
|
5641
|
-
|
|
5642
|
-
setCurrentUserHash(userHash);
|
|
5643
|
-
Sentry.setUser({ id: userHash });
|
|
5658
|
+
setTelemetryUser(validatedApiKey);
|
|
5644
5659
|
await validateApiKey(validatedApiKey);
|
|
5645
5660
|
return buildServer(validatedApiKey, transport);
|
|
5646
5661
|
}
|
|
@@ -5656,11 +5671,7 @@ async function runStdioServer() {
|
|
|
5656
5671
|
}
|
|
5657
5672
|
serverStartups.add(1, { version });
|
|
5658
5673
|
const configuredApiKey = process.env.HEVY_API_KEY;
|
|
5659
|
-
|
|
5660
|
-
if (initialUserHash) {
|
|
5661
|
-
setCurrentUserHash(initialUserHash);
|
|
5662
|
-
Sentry.setUser({ id: initialUserHash });
|
|
5663
|
-
}
|
|
5674
|
+
if (configuredApiKey) setTelemetryUser(configuredApiKey);
|
|
5664
5675
|
let connectAttempted = false;
|
|
5665
5676
|
await tracer.startActiveSpan("mcp.server.run", { attributes: { "mcp.transport": "stdio" } }, async (span) => {
|
|
5666
5677
|
try {
|
|
@@ -5724,6 +5735,7 @@ async function runServer() {
|
|
|
5724
5735
|
try {
|
|
5725
5736
|
const cfg = parseConfig(process.env);
|
|
5726
5737
|
assertApiKey(cfg.apiKey);
|
|
5738
|
+
setTelemetryUser(cfg.apiKey);
|
|
5727
5739
|
await validateApiKey(cfg.apiKey);
|
|
5728
5740
|
const handle = await startStreamableHttpServer(options, cfg.apiKey, (params) => Promise.resolve(buildServer(params.apiKey, "http")));
|
|
5729
5741
|
listening = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hevy-mcp",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.2",
|
|
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": {
|
|
@@ -35,24 +35,24 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
38
|
-
"@opentelemetry/api": "^1.9.
|
|
39
|
-
"@opentelemetry/exporter-metrics-otlp-http": "^0.
|
|
40
|
-
"@opentelemetry/exporter-trace-otlp-http": "^0.
|
|
41
|
-
"@opentelemetry/resources": "^2.
|
|
42
|
-
"@opentelemetry/sdk-metrics": "^2.
|
|
43
|
-
"@opentelemetry/sdk-trace-base": "^2.
|
|
44
|
-
"@opentelemetry/sdk-trace-node": "^2.
|
|
45
|
-
"@sentry/node": "^10.
|
|
46
|
-
"@sentry/opentelemetry": "^10.
|
|
38
|
+
"@opentelemetry/api": "^1.9.1",
|
|
39
|
+
"@opentelemetry/exporter-metrics-otlp-http": "^0.221.0",
|
|
40
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.221.0",
|
|
41
|
+
"@opentelemetry/resources": "^2.10.0",
|
|
42
|
+
"@opentelemetry/sdk-metrics": "^2.10.0",
|
|
43
|
+
"@opentelemetry/sdk-trace-base": "^2.10.0",
|
|
44
|
+
"@opentelemetry/sdk-trace-node": "^2.10.0",
|
|
45
|
+
"@sentry/node": "^10.68.0",
|
|
46
|
+
"@sentry/opentelemetry": "^10.68.0",
|
|
47
47
|
"semver": "^7.8.5",
|
|
48
48
|
"zod": "^4.4.3"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@hevy-mcp/core": "*",
|
|
52
52
|
"@hevy-mcp/hevy-client": "*",
|
|
53
|
-
"@types/node": "^26.
|
|
53
|
+
"@types/node": "^26.1.1",
|
|
54
54
|
"cross-env": "^10.1.0",
|
|
55
|
-
"tsdown": "^0.22.
|
|
55
|
+
"tsdown": "^0.22.14",
|
|
56
56
|
"typescript": "^7.0.2"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
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": "4.1.
|
|
10
|
+
"version": "4.1.2",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "hevy-mcp",
|
|
15
|
-
"version": "4.1.
|
|
15
|
+
"version": "4.1.2",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|