hevy-mcp 5.0.2 → 5.0.3

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/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.2" };
7
+ e.SENTRY_RELEASE = { id: "hevy-mcp@5.0.3" };
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-DBkGttYB.mjs";
12
+ import { a as createSafeErrorDiagnostic, i as MissingHevyApiKeyError, n as runServer, o as flushTelemetry } from "./src-BGQCPTet.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.2" };
7
+ e.SENTRY_RELEASE = { id: "hevy-mcp@5.0.3" };
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-DBkGttYB.mjs";
12
+ import { n as runServer, r as runStdioServer, t as createNodeMcpServer } from "./src-BGQCPTet.mjs";
13
13
  export { createNodeMcpServer, runServer, runStdioServer };
@@ -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@5.0.2" };
7
+ e.SENTRY_RELEASE = { id: "hevy-mcp@5.0.3" };
8
8
  var n = new e.Error().stack;
9
- n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "f7cbf1da-83d1-4da1-bb18-1c590afb9b11", e._sentryDebugIdIdentifier = "sentry-dbid-f7cbf1da-83d1-4da1-bb18-1c590afb9b11");
9
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "b75e442e-2372-4f08-83bb-f0c392f5101b", e._sentryDebugIdIdentifier = "sentry-dbid-b75e442e-2372-4f08-83bb-f0c392f5101b");
10
10
  } catch (e) {}
11
11
  })();
12
12
  import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
@@ -79,12 +79,48 @@ function sanitizeSentryMcpSpan(span) {
79
79
  * Sentry SDK: error events, performance traces, release tracking
80
80
  * OTel Collector → Honeycomb: performance traces, metrics
81
81
  */
82
+ function normalizeTelemetryError(error) {
83
+ const name = error instanceof Error && error.name ? error.name : "Exception";
84
+ return new Error(name);
85
+ }
86
+ function recordTelemetryException(error, attributes, span) {
87
+ if (!telemetryEnabled) return;
88
+ try {
89
+ const target = span ?? trace.getActiveSpan();
90
+ if (!target) return;
91
+ target.recordException(normalizeTelemetryError(error));
92
+ if (attributes) target.setAttributes(attributes);
93
+ target.setStatus({ code: SpanStatusCode.ERROR });
94
+ } catch {}
95
+ }
96
+ function installProcessExceptionTracking(processLike = process) {
97
+ if (!telemetryEnabled) return () => {};
98
+ const recordProcessException = (source, error) => {
99
+ try {
100
+ tracer.startActiveSpan(`mcp.process.${source}`, (span) => {
101
+ recordTelemetryException(error, { "exception.source": source }, span);
102
+ span.end();
103
+ });
104
+ } catch {}
105
+ };
106
+ const uncaughtException = (error) => recordProcessException("uncaughtException", error);
107
+ const unhandledRejection = (error) => recordProcessException("unhandledRejection", error);
108
+ processLike.on("uncaughtExceptionMonitor", uncaughtException);
109
+ processLike.on("unhandledRejection", unhandledRejection);
110
+ let cleaned = false;
111
+ return () => {
112
+ if (cleaned) return;
113
+ cleaned = true;
114
+ processLike.removeListener("uncaughtExceptionMonitor", uncaughtException);
115
+ processLike.removeListener("unhandledRejection", unhandledRejection);
116
+ };
117
+ }
82
118
  const name$1 = "hevy-mcp";
83
- const version$1 = "5.0.2";
119
+ const version$1 = "5.0.3";
84
120
  const telemetryEnabled = process.env.HEVY_MCP_TELEMETRY !== "0";
85
121
  const collectorToken = "NH9vOela-HYreQxAbJa68cjEmORoEKM57EvneUDVcOo";
86
122
  const COLLECTOR_ENDPOINT = "https://otel.chrisdoc.dev/v1";
87
- const sentryRelease = process.env.SENTRY_RELEASE ?? `hevy-mcp@5.0.2`;
123
+ const sentryRelease = process.env.SENTRY_RELEASE ?? `hevy-mcp@5.0.3`;
88
124
  const resource = resourceFromAttributes({
89
125
  "service.name": name$1,
90
126
  "service.version": version$1
@@ -2644,7 +2680,7 @@ function registerHevyResources(server, runtime) {
2644
2680
  //#endregion
2645
2681
  //#region ../core/src/server-metadata.ts
2646
2682
  const SERVER_NAME = "hevy-mcp";
2647
- const SERVER_VERSION = "5.0.2";
2683
+ const SERVER_VERSION = "5.0.3";
2648
2684
  const SERVER_INSTRUCTIONS = [
2649
2685
  "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.",
2650
2686
  "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.",
@@ -4007,6 +4043,18 @@ function registerHevyTools(server, runtime) {
4007
4043
  /**
4008
4044
  * Centralized error handling utility for MCP tools
4009
4045
  */
4046
+ function createMcpToolFailureEvent(toolName, errorType, diagnostic) {
4047
+ return {
4048
+ event: "mcp.tool.failure",
4049
+ "mcp.tool.name": toolName,
4050
+ "error.type": errorType,
4051
+ "error.category": diagnostic.category,
4052
+ ...diagnostic.code ? { "error.code": diagnostic.code } : {},
4053
+ ...diagnostic.status !== void 0 ? { "http.status_code": diagnostic.status } : {},
4054
+ ...diagnostic.method ? { "http.method": diagnostic.method } : {},
4055
+ ...diagnostic.endpoint ? { "hevy.api.endpoint": diagnostic.endpoint } : {}
4056
+ };
4057
+ }
4010
4058
  /**
4011
4059
  * Create a standardized error response for MCP tools
4012
4060
  *
@@ -4030,7 +4078,7 @@ function createErrorResponse(error, context) {
4030
4078
  axios: axiosErrorContext ?? void 0
4031
4079
  };
4032
4080
  const formattedMessage = `${context ? `[${context}] ` : ""}Error: ${policy.message}`;
4033
- console.error("MCP tool failure", diagnostic);
4081
+ console.error(createMcpToolFailureEvent(context || "unknown", policy.type, diagnostic));
4034
4082
  return {
4035
4083
  content: [{
4036
4084
  type: "text",
@@ -5077,11 +5125,13 @@ function captureSafeToolFailure(invocation, completion) {
5077
5125
  try {
5078
5126
  Sentry.withScope((scope) => {
5079
5127
  const isPrompt = invocation.kind === "prompt";
5128
+ scope.setTag("mcp.tool.name", invocation.name);
5080
5129
  if (isPrompt) scope.setTag("mcp.prompt.name", invocation.name);
5081
- else scope.setTag("mcp.tool.context", invocation.name);
5130
+ scope.setTag("error.type", completion.errorType ?? "UNKNOWN_ERROR");
5082
5131
  scope.setTag("error.category", category);
5083
5132
  if (diagnostic?.code) scope.setTag("error.code", diagnostic.code);
5084
5133
  if (diagnostic?.status !== void 0) scope.setTag("http.status_code", String(diagnostic.status));
5134
+ if (diagnostic?.method) scope.setTag("http.method", diagnostic.method);
5085
5135
  if (diagnostic?.endpoint) scope.setTag("hevy.api.endpoint", diagnostic.endpoint);
5086
5136
  scope.setContext(invocation.kind === "prompt" ? "mcpPrompt" : "mcpTool", {
5087
5137
  context: invocation.name,
@@ -5134,7 +5184,27 @@ function createNodeToolObserver() {
5134
5184
  activeSpan.setStatus({ code: nextCompletion.outcome === "success" && nextCompletion.result?.isError !== true ? SpanStatusCode.OK : SpanStatusCode.ERROR });
5135
5185
  activeSpan.setAttribute("mcp.tool.outcome", nextCompletion.outcome);
5136
5186
  setResultAttributes(activeSpan, nextCompletion);
5137
- if (nextCompletion.outcome === "thrown_error") errorType = setSafeErrorAttributes(activeSpan, nextCompletion);
5187
+ if (nextCompletion.outcome === "thrown_error") {
5188
+ errorType = setSafeErrorAttributes(activeSpan, nextCompletion);
5189
+ bestEffort(() => recordTelemetryException(new Error(nextCompletion.error?.category ?? errorType), {
5190
+ [invocation.kind === "prompt" ? "mcp.prompt.name" : "mcp.tool.name"]: invocation.name,
5191
+ "error.type": errorType ?? "UNKNOWN_ERROR",
5192
+ "error.category": nextCompletion.error?.category ?? "UnknownError",
5193
+ ...nextCompletion.error?.code ? { "error.code": nextCompletion.error.code } : {},
5194
+ ...nextCompletion.error?.status !== void 0 ? { "http.status_code": nextCompletion.error.status } : {},
5195
+ ...nextCompletion.error?.method ? { "http.method": nextCompletion.error.method } : {},
5196
+ ...nextCompletion.error?.endpoint ? { "hevy.api.endpoint": nextCompletion.error.endpoint } : {}
5197
+ }, activeSpan));
5198
+ }
5199
+ if (nextCompletion.outcome === "returned_error") bestEffort(() => recordTelemetryException(new Error(nextCompletion.error?.category ?? "UnknownError"), {
5200
+ [invocation.kind === "prompt" ? "mcp.prompt.name" : "mcp.tool.name"]: invocation.name,
5201
+ "error.type": nextCompletion.errorType ?? "UNKNOWN_ERROR",
5202
+ "error.category": nextCompletion.error?.category ?? "UnknownError",
5203
+ ...nextCompletion.error?.code ? { "error.code": nextCompletion.error.code } : {},
5204
+ ...nextCompletion.error?.status !== void 0 ? { "http.status_code": nextCompletion.error.status } : {},
5205
+ ...nextCompletion.error?.method ? { "http.method": nextCompletion.error.method } : {},
5206
+ ...nextCompletion.error?.endpoint ? { "hevy.api.endpoint": nextCompletion.error.endpoint } : {}
5207
+ }, activeSpan));
5138
5208
  }
5139
5209
  } catch {} finally {
5140
5210
  if (nextCompletion.outcome === "thrown_error") {
@@ -5622,6 +5692,7 @@ async function runStdioServer() {
5622
5692
  console.log(HELP_TEXT);
5623
5693
  return;
5624
5694
  }
5695
+ const cleanupProcessExceptionTracking = installProcessExceptionTracking();
5625
5696
  serverStartups.add(1, { version });
5626
5697
  const configuredApiKey = process.env.HEVY_API_KEY;
5627
5698
  if (configuredApiKey) setTelemetryUser(configuredApiKey);
@@ -5653,6 +5724,7 @@ async function runStdioServer() {
5653
5724
  target: server,
5654
5725
  onComplete: async (succeeded) => {
5655
5726
  recordMcpSessionTermination(resolveSessionTerminationCategory(succeeded));
5727
+ cleanupProcessExceptionTracking();
5656
5728
  await flushTelemetry();
5657
5729
  }
5658
5730
  });
@@ -5660,6 +5732,7 @@ async function runStdioServer() {
5660
5732
  } catch (e) {
5661
5733
  recordMcpSessionTermination(connectAttempted ? "connect_failure" : "startup_failure");
5662
5734
  span.setStatus({ code: SpanStatusCode.ERROR });
5735
+ cleanupProcessExceptionTracking();
5663
5736
  throw e;
5664
5737
  } finally {
5665
5738
  span.end();
@@ -5682,6 +5755,7 @@ async function runServer() {
5682
5755
  await runStdioServer();
5683
5756
  return;
5684
5757
  }
5758
+ const cleanupProcessExceptionTracking = installProcessExceptionTracking();
5685
5759
  await tracer.startActiveSpan("mcp.server.run", { attributes: { "mcp.transport": "http" } }, async (span) => {
5686
5760
  let listening = false;
5687
5761
  serverStartups.add(1, { version });
@@ -5700,6 +5774,7 @@ async function runServer() {
5700
5774
  installGracefulShutdown({
5701
5775
  target: handle,
5702
5776
  onComplete: async () => {
5777
+ cleanupProcessExceptionTracking();
5703
5778
  await flushTelemetry();
5704
5779
  }
5705
5780
  });
@@ -5707,6 +5782,7 @@ async function runServer() {
5707
5782
  } catch (error) {
5708
5783
  recordMcpSessionTermination(listening ? "unknown" : "startup_failure");
5709
5784
  span.setStatus({ code: SpanStatusCode.ERROR });
5785
+ cleanupProcessExceptionTracking();
5710
5786
  throw error;
5711
5787
  } finally {
5712
5788
  span.end();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hevy-mcp",
3
- "version": "5.0.2",
3
+ "version": "5.0.3",
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": {
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.2",
10
+ "version": "5.0.3",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "hevy-mcp",
15
- "version": "5.0.2",
15
+ "version": "5.0.3",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  },