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 +22 -3
- package/dist/cli.mjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{src-D1O3O9f2.mjs → src-B_mNqS1X.mjs} +29 -22
- package/dist/src-B_mNqS1X.mjs.map +1 -0
- package/package.json +2 -1
- package/server.json +2 -2
- package/dist/src-D1O3O9f2.mjs.map +0 -1
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
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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.
|
|
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-
|
|
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.
|
|
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-
|
|
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.
|
|
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] = "
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
94
|
-
|
|
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.
|
|
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-
|
|
4340
|
+
//# sourceMappingURL=src-B_mNqS1X.mjs.map
|