hevy-mcp 5.0.5 → 5.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -423,7 +423,7 @@ self-hosted Streamable HTTP.
423
423
  | `HEVY_MCP_HTTP_BEARER_TOKEN` | None | Non-loopback HTTP | Required when `--host` is not loopback; use a separate token, never the Hevy API key. |
424
424
  | `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. |
425
425
  | `XDG_CACHE_HOME` | `~/.cache` | Local stdio | Changes the root for the npm update-check cache at `hevy-mcp/update-check.json`. |
426
- | `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_DSN` | Packaged GlitchTip project DSN | Optional local Node telemetry | Sentry-compatible override for the destination. An empty value disables Sentry export. The Worker does not import Node telemetry. |
427
427
  | `SENTRY_RELEASE` | `hevy-mcp@<installed-version>` | Optional local Node telemetry | Overrides the release label attached to local Sentry events and traces. |
428
428
  | `-h`, `--help` | N/A | Local stdio CLI | Print supported options and exit. |
429
429
  | `-v`, `--version` | N/A | Local stdio CLI | Print the installed version and exit. |
@@ -474,9 +474,9 @@ metric readers and makes no telemetry network requests. `SENTRY_DSN` remains a
474
474
  Sentry-only setting; when telemetry is enabled, an empty value disables only
475
475
  Sentry export.
476
476
 
477
- When enabled, errors and traces are sent to the packaged Sentry project at
478
- <https://o4508975499575296.ingest.de.sentry.io/4509049671647312>. Traces and
479
- metrics are sent to the collector at
477
+ When enabled, errors are sent to the self-hosted GlitchTip project at
478
+ <https://glitchtip.chrisdoc.dev>; Sentry performance tracing is disabled.
479
+ Traces and metrics continue to be sent to the collector at
480
480
  <https://otel.chrisdoc.dev/v1/traces> and
481
481
  <https://otel.chrisdoc.dev/v1/metrics>, which forward to Honeycomb. Metrics
482
482
  export every 10 seconds.
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@5.0.5" };
7
+ e.SENTRY_RELEASE = { id: "hevy-mcp@5.0.6" };
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-TBD9ENR3.mjs";
12
+ import { a as createSafeErrorDiagnostic, i as MissingHevyApiKeyError, n as runServer, o as flushTelemetry } from "./src-CT0BzqOm.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@5.0.5" };
7
+ e.SENTRY_RELEASE = { id: "hevy-mcp@5.0.6" };
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-TBD9ENR3.mjs";
12
+ import { n as runServer, r as runStdioServer, t as createNodeMcpServer } from "./src-CT0BzqOm.mjs";
13
13
  export { createNodeMcpServer, runServer, runStdioServer };
@@ -4,19 +4,18 @@
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@5.0.5" };
7
+ e.SENTRY_RELEASE = { id: "hevy-mcp@5.0.6" };
8
8
  var n = new e.Error().stack;
9
- n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "f5270278-73ea-40fd-92e2-2ae5127e6780", e._sentryDebugIdIdentifier = "sentry-dbid-f5270278-73ea-40fd-92e2-2ae5127e6780");
9
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "8b83767c-bc1c-4e67-8601-3cf4da13a8f9", e._sentryDebugIdIdentifier = "sentry-dbid-8b83767c-bc1c-4e67-8601-3cf4da13a8f9");
10
10
  } catch (e) {}
11
11
  })();
12
12
  import { createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
13
13
  import * as Sentry from "@sentry/node";
14
- import { SentryPropagator, SentrySampler, SentrySpanProcessor } from "@sentry/opentelemetry";
15
14
  import { SpanStatusCode, context, metrics, trace } from "@opentelemetry/api";
16
15
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
17
16
  import { AggregationTemporalityPreference, OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
18
17
  import { resourceFromAttributes } from "@opentelemetry/resources";
19
- import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
18
+ import { AlwaysOnSampler, BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
20
19
  import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
21
20
  import { MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
22
21
  import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
@@ -31,52 +30,15 @@ import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
31
30
  import { homedir } from "node:os";
32
31
  import { dirname, join } from "node:path";
33
32
  import semver from "semver";
34
- //#region src/utils/sentry-privacy.ts
35
- const SENTRY_PROHIBITED_MCP_ATTRIBUTES = /* @__PURE__ */ new Set([
36
- "mcp.request.id",
37
- "mcp.session.id",
38
- "mcp.cancelled.request_id",
39
- "mcp.cancelled.reason",
40
- "mcp.progress.token",
41
- "mcp.progress.message",
42
- "mcp.resource.uri",
43
- "mcp.method.name",
44
- "mcp.resource.protocol",
45
- "mcp.prompt.name",
46
- "mcp.protocol.version",
47
- "mcp.logging.message",
48
- "mcp.prompt.result.description",
49
- "mcp.request.argument",
50
- "mcp.logging.logger",
51
- "mcp.logging.level",
52
- "mcp.client.name",
53
- "mcp.client.title",
54
- "mcp.client.version"
55
- ]);
56
- /** Remove MCP correlation and unbounded client fields before Sentry export. */
57
- function sanitizeSentryMcpSpan(span) {
58
- if (!span.data) return span;
59
- let sanitizedData;
60
- for (const key of SENTRY_PROHIBITED_MCP_ATTRIBUTES) {
61
- if (!(key in span.data)) continue;
62
- sanitizedData ??= { ...span.data };
63
- delete sanitizedData[key];
64
- }
65
- return sanitizedData ? {
66
- ...span,
67
- data: sanitizedData
68
- } : span;
69
- }
70
- //#endregion
71
33
  //#region src/utils/telemetry.ts
72
34
  /**
73
35
  * Centralized telemetry initialization.
74
36
  *
75
37
  * This module MUST be imported before any other application code.
76
- * It sets up OpenTelemetry with dual export: Sentry (error events +
77
- * traces) and an OTel Collector (traces + metrics to Honeycomb).
38
+ * It sets up independent telemetry paths: Sentry error events and an OTel
39
+ * Collector (traces + metrics to Honeycomb).
78
40
  *
79
- * Sentry SDK: error events, performance traces, release tracking
41
+ * Sentry SDK: error monitoring, release tracking
80
42
  * OTel Collector → Honeycomb: performance traces, metrics
81
43
  */
82
44
  const PROCESS_FAILURE_TAXONOMY = {
@@ -169,11 +131,12 @@ function installProcessExceptionTracking(processLike = process) {
169
131
  };
170
132
  }
171
133
  const name$1 = "hevy-mcp";
172
- const version$1 = "5.0.5";
134
+ const version$1 = "5.0.6";
173
135
  const telemetryEnabled = process.env.HEVY_MCP_TELEMETRY !== "0";
174
136
  const collectorToken = "NH9vOela-HYreQxAbJa68cjEmORoEKM57EvneUDVcOo";
175
137
  const COLLECTOR_ENDPOINT = "https://otel.chrisdoc.dev/v1";
176
138
  const sentryRelease = process.env.SENTRY_RELEASE ?? `${name$1}@${version$1}`;
139
+ const DEFAULT_SENTRY_DSN = "https://7c08d2c880ff4560a333dff4833594cd@glitchtip.chrisdoc.dev/1";
177
140
  function createServiceInstanceId(generate = randomUUID) {
178
141
  try {
179
142
  const generated = generate();
@@ -200,32 +163,28 @@ var UserHashSpanProcessor = class {
200
163
  let tracerProvider;
201
164
  let meterProvider;
202
165
  if (telemetryEnabled) {
203
- const rawDsn = process.env.SENTRY_DSN ?? "https://ce696d8333b507acbf5203eb877bce0f@o4508975499575296.ingest.de.sentry.io/4509049671647312";
166
+ const rawDsn = process.env.SENTRY_DSN ?? DEFAULT_SENTRY_DSN;
204
167
  const isValidDsn = typeof rawDsn === "string" && rawDsn.length > 0 && !rawDsn.startsWith("*");
205
- const sentryClient = Sentry.init({
168
+ Sentry.init({
206
169
  dsn: isValidDsn ? rawDsn : void 0,
207
- beforeSendSpan: sanitizeSentryMcpSpan,
208
170
  release: sentryRelease,
209
- tracesSampleRate: 1,
171
+ tracesSampleRate: 0,
210
172
  sendDefaultPii: false,
211
173
  skipOpenTelemetrySetup: true,
212
174
  registerEsmLoaderHooks: false,
213
175
  ignoreErrors: ["EPIPE", "broken pipe"]
214
176
  });
215
- const spanProcessors = [new UserHashSpanProcessor(), new SentrySpanProcessor()];
177
+ const spanProcessors = [new UserHashSpanProcessor()];
216
178
  spanProcessors.push(new BatchSpanProcessor(new OTLPTraceExporter({
217
179
  url: `${COLLECTOR_ENDPOINT}/traces`,
218
180
  headers: { Authorization: `Bearer ${collectorToken}` }
219
181
  })));
220
182
  tracerProvider = new NodeTracerProvider({
221
183
  resource,
222
- sampler: sentryClient ? new SentrySampler(sentryClient) : void 0,
184
+ sampler: new AlwaysOnSampler(),
223
185
  spanProcessors
224
186
  });
225
- tracerProvider.register({
226
- propagator: new SentryPropagator(),
227
- contextManager: new Sentry.SentryContextManager()
228
- });
187
+ tracerProvider.register();
229
188
  meterProvider = new MeterProvider({
230
189
  resource,
231
190
  readers: [new PeriodicExportingMetricReader({
@@ -234,12 +193,11 @@ if (telemetryEnabled) {
234
193
  headers: { Authorization: `Bearer ${collectorToken}` },
235
194
  temporalityPreference: AggregationTemporalityPreference.DELTA
236
195
  }),
237
- exportIntervalMillis: 1e4
196
+ exportIntervalMillis: 3e4
238
197
  })]
239
198
  });
240
199
  metrics.setGlobalMeterProvider(meterProvider);
241
200
  trace.setGlobalTracerProvider(tracerProvider);
242
- Sentry.validateOpenTelemetrySetup();
243
201
  }
244
202
  async function flushTelemetry(timeoutMs = 1e3) {
245
203
  if (!telemetryEnabled) return;
@@ -1060,7 +1018,7 @@ function isHevyHttpError(error) {
1060
1018
  const DEFAULT_API_TIMEOUT_MS = 3e4;
1061
1019
  const RETRY_BACKOFF_MAX_MS = 5e3;
1062
1020
  const RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([408, 429]);
1063
- const SAFE_STATIC_ENDPOINTS = /* @__PURE__ */ new Set([
1021
+ const SAFE_STATIC_ENDPOINTS$1 = /* @__PURE__ */ new Set([
1064
1022
  "/v1/body_measurements",
1065
1023
  "/v1/exercise_templates",
1066
1024
  "/v1/routine_folders",
@@ -1099,7 +1057,7 @@ const SAFE_OBSERVATION_CODES = /* @__PURE__ */ new Set([
1099
1057
  HEVY_REQUEST_ABORTED_ERROR_CODE,
1100
1058
  HEVY_RETRY_EXHAUSTED_ERROR_CODE
1101
1059
  ]);
1102
- const SAFE_DYNAMIC_ENDPOINTS = [
1060
+ const SAFE_DYNAMIC_ENDPOINTS$1 = [
1103
1061
  ["/v1/body_measurements/", "/v1/body_measurements/:date"],
1104
1062
  ["/v1/exercise_history/", "/v1/exercise_history/:exerciseTemplateId"],
1105
1063
  ["/v1/exercise_templates/", "/v1/exercise_templates/:exerciseTemplateId"],
@@ -1142,8 +1100,8 @@ function getRequestContext(config) {
1142
1100
  const method = (config.method ?? "GET").toUpperCase();
1143
1101
  const rawEndpoint = (config.url ?? "").split("?")[0] ?? "";
1144
1102
  let endpoint = "unknown";
1145
- if (SAFE_STATIC_ENDPOINTS.has(rawEndpoint)) endpoint = rawEndpoint;
1146
- else endpoint = SAFE_DYNAMIC_ENDPOINTS.find(([prefix]) => rawEndpoint.startsWith(prefix))?.[1] ?? "unknown";
1103
+ if (SAFE_STATIC_ENDPOINTS$1.has(rawEndpoint)) endpoint = rawEndpoint;
1104
+ else endpoint = SAFE_DYNAMIC_ENDPOINTS$1.find(([prefix]) => rawEndpoint.startsWith(prefix))?.[1] ?? "unknown";
1147
1105
  const page = config.params !== null && typeof config.params === "object" && "page" in config.params && typeof config.params.page === "number" ? config.params.page : void 0;
1148
1106
  return {
1149
1107
  method,
@@ -2844,7 +2802,7 @@ function registerHevyResources(server, runtime) {
2844
2802
  //#endregion
2845
2803
  //#region ../core/src/server-metadata.ts
2846
2804
  const SERVER_NAME = "hevy-mcp";
2847
- const SERVER_VERSION = "5.0.5";
2805
+ const SERVER_VERSION = "5.0.6";
2848
2806
  const SERVER_INSTRUCTIONS = [
2849
2807
  "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.",
2850
2808
  "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.",
@@ -5195,6 +5153,29 @@ function debugLog(event, data) {
5195
5153
  }
5196
5154
  //#endregion
5197
5155
  //#region src/utils/hevy-client-observability.ts
5156
+ const SAFE_STATIC_ENDPOINTS = /* @__PURE__ */ new Set([
5157
+ "/v1/body_measurements",
5158
+ "/v1/exercise_templates",
5159
+ "/v1/routine_folders",
5160
+ "/v1/routines",
5161
+ "/v1/user/info",
5162
+ "/v1/workouts",
5163
+ "/v1/workouts/count",
5164
+ "/v1/workouts/events"
5165
+ ]);
5166
+ const SAFE_DYNAMIC_ENDPOINTS = [
5167
+ ["/v1/body_measurements/", "/v1/body_measurements/:date"],
5168
+ ["/v1/exercise_history/", "/v1/exercise_history/:exerciseTemplateId"],
5169
+ ["/v1/exercise_templates/", "/v1/exercise_templates/:exerciseTemplateId"],
5170
+ ["/v1/routine_folders/", "/v1/routine_folders/:folderId"],
5171
+ ["/v1/routines/", "/v1/routines/:routineId"],
5172
+ ["/v1/workouts/", "/v1/workouts/:workoutId"]
5173
+ ];
5174
+ function normalizeHevyMetricEndpoint(endpoint) {
5175
+ const path = endpoint.split("?")[0] ?? "";
5176
+ if (SAFE_STATIC_ENDPOINTS.has(path)) return path;
5177
+ return SAFE_DYNAMIC_ENDPOINTS.find(([prefix]) => path.startsWith(prefix))?.[1] ?? "unknown";
5178
+ }
5198
5179
  function safeErrorAttributes(observation) {
5199
5180
  if (!observation.error) return {};
5200
5181
  const code = observation.error.code;
@@ -5248,9 +5229,10 @@ function createNodeHevyClientOptions() {
5248
5229
  onRequestComplete(observation) {
5249
5230
  const retryCountBucket = bucketCount(observation.retryCount);
5250
5231
  const errorAttributes = safeErrorAttributes(observation);
5232
+ const metricEndpoint = normalizeHevyMetricEndpoint(observation.endpoint);
5251
5233
  apiCalls.add(1, {
5252
5234
  method: observation.method,
5253
- endpoint: observation.endpoint,
5235
+ endpoint: metricEndpoint,
5254
5236
  status_code: observation.status,
5255
5237
  retry_count_bucket: retryCountBucket,
5256
5238
  outcome: observation.outcome,
@@ -5260,7 +5242,7 @@ function createNodeHevyClientOptions() {
5260
5242
  });
5261
5243
  apiDuration.record(observation.durationMs, {
5262
5244
  method: observation.method,
5263
- endpoint: observation.endpoint,
5245
+ endpoint: metricEndpoint,
5264
5246
  retry_count_bucket: retryCountBucket,
5265
5247
  outcome: observation.outcome,
5266
5248
  transport: getCurrentMcpTransport(),
@@ -5370,19 +5352,6 @@ function setResultAttributes(span, completion) {
5370
5352
  for (const [resource, pageCount] of Object.entries(summary.workflow.pagination)) if (WORKFLOW_PAGINATION_RESOURCES.has(resource) && Number.isSafeInteger(pageCount) && pageCount >= 0) span.setAttribute(`workflow.pagination.${resource}.pages`, pageCount);
5371
5353
  }
5372
5354
  }
5373
- function resultMetricAttributes(completion) {
5374
- const result = completion.result;
5375
- if (!result) return {};
5376
- const attributes = {
5377
- "mcp.tool.result.has_structured_content": result.hasStructuredContent,
5378
- "mcp.tool.result.content_count_bucket": result.contentCountBucket
5379
- };
5380
- const summary = result.summary;
5381
- if (summary?.itemCountBucket) attributes["mcp.tool.result.item_count_bucket"] = summary.itemCountBucket;
5382
- if (summary?.exerciseCountBucket) attributes["mcp.tool.result.exercise_count_bucket"] = summary.exerciseCountBucket;
5383
- if (summary?.setCountBucket) attributes["mcp.tool.result.set_count_bucket"] = summary.setCountBucket;
5384
- return attributes;
5385
- }
5386
5355
  function setSafeErrorAttributes(span, invocation, completion) {
5387
5356
  const diagnostic = completion.error;
5388
5357
  const errorType = completion.errorType ?? "UNKNOWN_ERROR";
@@ -5504,7 +5473,6 @@ function createNodeToolObserver() {
5504
5473
  }
5505
5474
  bestEffort(() => toolDuration.record(durationMs, {
5506
5475
  ...metrics,
5507
- ...resultMetricAttributes(nextCompletion),
5508
5476
  is_error: String(isError),
5509
5477
  outcome: nextCompletion.outcome
5510
5478
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hevy-mcp",
3
- "version": "5.0.5",
3
+ "version": "5.0.6",
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": {
@@ -43,7 +43,7 @@
43
43
  "@opentelemetry/sdk-metrics": "^2.10.0",
44
44
  "@opentelemetry/sdk-trace-base": "^2.10.0",
45
45
  "@opentelemetry/sdk-trace-node": "^2.10.0",
46
- "@sentry/node": "^10.68.0",
46
+ "@sentry/node": "^10.69.0",
47
47
  "@sentry/opentelemetry": "^10.68.0",
48
48
  "semver": "^7.8.5",
49
49
  "zod": "^4.4.3"
@@ -51,7 +51,7 @@
51
51
  "devDependencies": {
52
52
  "@hevy-mcp/core": "*",
53
53
  "@hevy-mcp/hevy-client": "*",
54
- "@types/node": "^26.1.1",
54
+ "@types/node": "^26.1.2",
55
55
  "cross-env": "^10.1.0",
56
56
  "tsdown": "^0.22.14",
57
57
  "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": "5.0.5",
10
+ "version": "5.0.6",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "hevy-mcp",
15
- "version": "5.0.5",
15
+ "version": "5.0.6",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  },