hevy-mcp 3.2.1 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -367,9 +367,28 @@ The bearer value is your Hevy API key, not an OAuth token. The Worker validates
367
367
  the key with Hevy on each request, does not store it, and forwards it upstream
368
368
  only as Hevy's required `api-key` header.
369
369
 
370
- The endpoint does not expose legacy SSE or a `GET` event stream. Clients that
371
- require OAuth discovery, dynamic registration, token refresh, or legacy SSE are
372
- not compatible unless they can send the fixed custom header above.
370
+ ### OAuth for Claude.ai and other remote MCP clients
371
+
372
+ Workers deployed with an `OAUTH_KV` namespace binding (see
373
+ [CONTRIBUTING.md](./CONTRIBUTING.md)) additionally expose a full OAuth 2.1
374
+ layer for clients that cannot send a fixed header, such as Claude.ai custom
375
+ connectors:
376
+
377
+ - RFC 8414 / RFC 9728 discovery metadata under `/.well-known/`
378
+ - Dynamic client registration (`/register`) and PKCE token exchange (`/token`)
379
+ - An `/authorize` page where you paste your Hevy API key once; the key is
380
+ validated with Hevy and stored encrypted inside the OAuth grant
381
+
382
+ Add the Worker URL ending in `/mcp` as a Claude.ai custom connector and
383
+ complete the authorization flow in the browser. Direct
384
+ `Authorization: Bearer <hevy-api-key>` requests keep working unchanged — the
385
+ OAuth layer is purely additive — and rotating your Hevy API key invalidates
386
+ every OAuth grant created with it.
387
+
388
+ The endpoint does not expose legacy SSE or a `GET` event stream. Without the
389
+ opt-in OAuth layer, clients that require OAuth discovery, dynamic
390
+ registration, or token refresh are not compatible unless they can send the
391
+ fixed custom header above.
373
392
 
374
393
  ### Self-host the Worker
375
394
 
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@3.2.1" };
7
+ e.SENTRY_RELEASE = { id: "hevy-mcp@3.3.0" };
8
8
  var n = new e.Error().stack;
9
9
  n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "47a79c69-c982-44cf-abda-d010f962b955", e._sentryDebugIdIdentifier = "sentry-dbid-47a79c69-c982-44cf-abda-d010f962b955");
10
10
  } catch (e) {}
11
11
  })();
12
- import { i as createSafeErrorDiagnostic, r as runServer } from "./src-D1O3O9f2.mjs";
12
+ import { i as createSafeErrorDiagnostic, r as runServer } from "./src-B_mNqS1X.mjs";
13
13
  //#region src/cli.ts
14
14
  runServer().catch((error) => {
15
15
  console.error("Fatal error in main()", createSafeErrorDiagnostic(error));
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@3.2.1" };
7
+ e.SENTRY_RELEASE = { id: "hevy-mcp@3.3.0" };
8
8
  var n = new e.Error().stack;
9
9
  n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "5b077b94-9ef8-4f24-a0b0-8dcfbf491be1", e._sentryDebugIdIdentifier = "sentry-dbid-5b077b94-9ef8-4f24-a0b0-8dcfbf491be1");
10
10
  } catch (e) {}
11
11
  })();
12
- import { n as createServer, r as runServer, t as configSchema } from "./src-D1O3O9f2.mjs";
12
+ import { n as createServer, r as runServer, t as configSchema } from "./src-B_mNqS1X.mjs";
13
13
  export { configSchema, createServer, createServer as default, runServer };
@@ -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@3.2.1" };
7
+ e.SENTRY_RELEASE = { id: "hevy-mcp@3.3.0" };
8
8
  var n = new e.Error().stack;
9
- n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "8ce801d0-ad38-40ee-a7f3-dc829e212db8", e._sentryDebugIdIdentifier = "sentry-dbid-8ce801d0-ad38-40ee-a7f3-dc829e212db8");
9
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "d4c67aeb-89ed-4cf7-ae4a-09c19c187b3d", e._sentryDebugIdIdentifier = "sentry-dbid-d4c67aeb-89ed-4cf7-ae4a-09c19c187b3d");
10
10
  } catch (e) {}
11
11
  })();
12
12
  import * as Sentry from "@sentry/node";
@@ -40,10 +40,10 @@ import semver from "semver";
40
40
  * OTel Collector → Honeycomb: performance traces, metrics
41
41
  */
42
42
  const name$1 = "hevy-mcp";
43
- const version$1 = "3.2.1";
43
+ const version$1 = "3.3.0";
44
44
  const collectorToken = "NH9vOela-HYreQxAbJa68cjEmORoEKM57EvneUDVcOo";
45
45
  const COLLECTOR_ENDPOINT = "https://otel.chrisdoc.dev/v1";
46
- const sentryRelease = process.env.SENTRY_RELEASE ?? `hevy-mcp@3.2.1`;
46
+ const sentryRelease = process.env.SENTRY_RELEASE ?? `hevy-mcp@3.3.0`;
47
47
  const resource = resourceFromAttributes({
48
48
  "service.name": name$1,
49
49
  "service.version": version$1
@@ -59,7 +59,16 @@ const sentryClient = Sentry.init({
59
59
  registerEsmLoaderHooks: false,
60
60
  ignoreErrors: ["EPIPE", "broken pipe"]
61
61
  });
62
- const spanProcessors = [new SentrySpanProcessor()];
62
+ let currentUserHash;
63
+ var UserHashSpanProcessor = class {
64
+ onStart(span) {
65
+ if (currentUserHash) span.setAttribute("user.hash", currentUserHash);
66
+ }
67
+ onEnd(_span) {}
68
+ async forceFlush() {}
69
+ async shutdown() {}
70
+ };
71
+ const spanProcessors = [new UserHashSpanProcessor(), new SentrySpanProcessor()];
63
72
  spanProcessors.push(new BatchSpanProcessor(new OTLPTraceExporter({
64
73
  url: `${COLLECTOR_ENDPOINT}/traces`,
65
74
  headers: { Authorization: `Bearer ${collectorToken}` }
@@ -90,12 +99,8 @@ trace.setGlobalTracerProvider(tracerProvider);
90
99
  Sentry.validateOpenTelemetrySetup();
91
100
  const tracer = trace.getTracer(name$1);
92
101
  const meter = metrics.getMeter(name$1);
93
- let currentUserId;
94
- function setCurrentUserId(id) {
95
- currentUserId = id;
96
- }
97
- function getCurrentUserId() {
98
- return currentUserId;
102
+ function setCurrentUserHash(hash) {
103
+ currentUserHash = hash;
99
104
  }
100
105
  //#endregion
101
106
  //#region src/utils/metrics.ts
@@ -1121,7 +1126,7 @@ function registerHevyResources(server, runtime) {
1121
1126
  //#endregion
1122
1127
  //#region src/server-metadata.ts
1123
1128
  const SERVER_NAME = "hevy-mcp";
1124
- const SERVER_VERSION = "3.2.1";
1129
+ const SERVER_VERSION = "3.3.0";
1125
1130
  const SERVER_INSTRUCTIONS = [
1126
1131
  "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.",
1127
1132
  "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.",
@@ -3692,8 +3697,7 @@ function createNodeHevyClientOptions() {
3692
3697
  const span = tracer.startSpan(`hevy.api.${observation.method}`, { attributes: {
3693
3698
  "http.method": observation.method,
3694
3699
  "http.status_code": observation.status,
3695
- "hevy.api.endpoint": observation.endpoint,
3696
- ...getCurrentUserId() ? { "user.id": getCurrentUserId() } : {}
3700
+ "hevy.api.endpoint": observation.endpoint
3697
3701
  } });
3698
3702
  span.setStatus({ code: observation.error ? SpanStatusCode.ERROR : SpanStatusCode.OK });
3699
3703
  if (observation.error) {
@@ -3789,7 +3793,6 @@ function withTelemetry(fn, context) {
3789
3793
  const startTime = Date.now();
3790
3794
  let isError = false;
3791
3795
  toolInvocations.add(1, { tool_name: context });
3792
- const userId = getCurrentUserId();
3793
3796
  const safeArgs = extractSafeArgs(args);
3794
3797
  const whitelistedKeys = Object.keys(safeArgs).map((key) => key.replace("mcp.tool.args.", ""));
3795
3798
  return tracer.startActiveSpan(`mcp.tool.${context}`, { attributes: {
@@ -3797,7 +3800,6 @@ function withTelemetry(fn, context) {
3797
3800
  "workflow.name": context,
3798
3801
  "mcp.tool.args.key_count": argumentKeyCount,
3799
3802
  "mcp.tool.args.keys": whitelistedKeys.join(","),
3800
- ...userId ? { "user.id": userId } : {},
3801
3803
  ...safeArgs
3802
3804
  } }, async (span) => {
3803
3805
  try {
@@ -4251,16 +4253,12 @@ async function validateApiKey(apiKey) {
4251
4253
  }
4252
4254
  }
4253
4255
  function buildServer(apiKey) {
4254
- const userId = fingerprintApiKey(apiKey);
4255
4256
  return tracer.startActiveSpan("mcp.server.build", { attributes: {
4256
4257
  "mcp.server.name": name,
4257
4258
  "mcp.server.version": version,
4258
- "mcp.transport": "stdio",
4259
- "user.id": userId
4259
+ "mcp.transport": "stdio"
4260
4260
  } }, (span) => {
4261
4261
  try {
4262
- Sentry.setUser({ id: userId });
4263
- setCurrentUserId(userId);
4264
4262
  const server = createSharedMcpServer({
4265
4263
  apiKey,
4266
4264
  clientOptions: createNodeHevyClientOptions(),
@@ -4281,6 +4279,9 @@ function buildServer(apiKey) {
4281
4279
  }
4282
4280
  async function createServer({ config }) {
4283
4281
  const { apiKey } = serverConfigSchema.parse(config);
4282
+ const userHash = fingerprintApiKey(apiKey);
4283
+ setCurrentUserHash(userHash);
4284
+ Sentry.setUser({ id: userHash });
4284
4285
  await validateApiKey(apiKey);
4285
4286
  return buildServer(apiKey);
4286
4287
  }
@@ -4295,6 +4296,12 @@ async function runServer() {
4295
4296
  return;
4296
4297
  }
4297
4298
  serverStartups.add(1, { version });
4299
+ const configuredApiKey = process.env.HEVY_API_KEY;
4300
+ const initialUserHash = configuredApiKey ? fingerprintApiKey(configuredApiKey) : void 0;
4301
+ if (initialUserHash) {
4302
+ setCurrentUserHash(initialUserHash);
4303
+ Sentry.setUser({ id: initialUserHash });
4304
+ }
4298
4305
  await tracer.startActiveSpan("mcp.server.run", { attributes: { "mcp.transport": "stdio" } }, async (span) => {
4299
4306
  try {
4300
4307
  const apiKey = parseConfig(process.env).apiKey;
@@ -4330,4 +4337,4 @@ async function runServer() {
4330
4337
  //#endregion
4331
4338
  export { createSafeErrorDiagnostic as i, createServer as n, runServer as r, configSchema as t };
4332
4339
 
4333
- //# sourceMappingURL=src-D1O3O9f2.mjs.map
4340
+ //# sourceMappingURL=src-B_mNqS1X.mjs.map