hevy-mcp 6.1.21 → 6.1.22

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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # hevy-mcp
2
2
 
3
+ ## 6.1.22
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1241](https://github.com/chrisdoc/hevy-mcp/pull/1241) [`cabfd9e`](https://github.com/chrisdoc/hevy-mcp/commit/cabfd9e75c89f3d905dfdd48589c1fd2195567e6) Thanks [@chrisdoc](https://github.com/chrisdoc)! - Replace the duplicated npm README with a concise standalone package guide linking maintained documentation. Correct authentication, cache, and privacy guidance, and improve source-backed documentation navigation and ownership.
8
+
9
+ - [`1f9f918`](https://github.com/chrisdoc/hevy-mcp/commit/1f9f9189883c4f00a752eb27c9f87ee5eefe368c) Thanks [@charliecreates[bot]](https://github.com/charliecreates%5Bbot%5D)! - Remove obsolete MCP SDK v1.29.0 compatibility note from README.
10
+
11
+ - [#1240](https://github.com/chrisdoc/hevy-mcp/pull/1240) [`8d43dab`](https://github.com/chrisdoc/hevy-mcp/commit/8d43dab9f7a9467cfe1766d727466af7eac0a1e8) Thanks [@chrisdoc](https://github.com/chrisdoc)! - Disable project telemetry by default for unbuilt Node source runs unless HEVY_MCP_TELEMETRY=1 is explicitly set. Preserve published-build opt-out behavior and label enabled source-run Sentry events as development, with SENTRY_ENVIRONMENT available as an override.
12
+
13
+ - [#1237](https://github.com/chrisdoc/hevy-mcp/pull/1237) [`5b60d44`](https://github.com/chrisdoc/hevy-mcp/commit/5b60d449863ae84bbe282a72e48cd9e977c34799) Thanks [@chrisdoc](https://github.com/chrisdoc)! - Remove `.agents/daemons/` daemon definitions: the Charlie agent that ran them is no longer active, so the daemon specs (docs-drift-maintainer, pr-check-repair, pr-merge-conflict-repair, pr-metadata, triage-sentry-issues) are dead configuration.
14
+
15
+ - [#1239](https://github.com/chrisdoc/hevy-mcp/pull/1239) [`ca7a5d7`](https://github.com/chrisdoc/hevy-mcp/commit/ca7a5d71c0e41867df4656c86307ab457b23f500) Thanks [@chrisdoc](https://github.com/chrisdoc)! - Consolidate native client request setup and test scaffolding while preserving public signatures, request controls, and test scenarios. Reduce test import overhead and CPU contention without narrowing validation lanes.
16
+
3
17
  ## 6.1.21
4
18
 
5
19
  ### Patch Changes
package/README.md CHANGED
@@ -1,151 +1,23 @@
1
- # Hevy MCP Server
1
+ # hevy-mcp
2
2
 
3
- <div align="center">
3
+ Connect Claude, Cursor, Codex, or another [MCP](https://modelcontextprotocol.io/)
4
+ client to your [Hevy](https://www.hevyapp.com/) workouts, routines, exercises,
5
+ and body measurements. Read and analyze data, or create and update records
6
+ with client-side confirmation. A Hevy API key (Hevy PRO) is required.
4
7
 
5
- **Talk to your Hevy workout data from Claude, Cursor, Codex, and other MCP clients.**
8
+ [Full guide](https://github.com/chrisdoc/hevy-mcp/blob/main/README.md) ·
9
+ [Tools and prompts](https://github.com/chrisdoc/hevy-mcp/blob/main/README.md#tools) ·
10
+ [Documentation](https://github.com/chrisdoc/hevy-mcp/blob/main/docs/index.md)
6
11
 
7
- [![npm version](https://img.shields.io/npm/v/hevy-mcp.svg)](https://www.npmjs.com/package/hevy-mcp)
8
- [![npm downloads](https://img.shields.io/npm/dm/hevy-mcp.svg)](https://www.npmjs.com/package/hevy-mcp)
9
- [![Build and Test](https://github.com/chrisdoc/hevy-mcp/actions/workflows/build-and-test.yml/badge.svg)](https://github.com/chrisdoc/hevy-mcp/actions/workflows/build-and-test.yml)
10
- [![Codecov](https://codecov.io/gh/chrisdoc/hevy-mcp/branch/main/graph/badge.svg)](https://codecov.io/gh/chrisdoc/hevy-mcp)
11
- [![GitHub stars](https://img.shields.io/github/stars/chrisdoc/hevy-mcp?style=flat)](https://github.com/chrisdoc/hevy-mcp/stargazers)
12
- [![Hosted on Cloudflare](https://img.shields.io/badge/Hosted_on-Cloudflare-F38020?logo=cloudflare&logoColor=white)](#hosted-cloudflare-endpoint)
13
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)
12
+ ## Run locally with stdio
14
13
 
15
- [Connect to the hosted MCP](#connect-to-the-hosted-endpoint) · [Watch the 18-second demo](https://raw.githubusercontent.com/chrisdoc/hevy-mcp/main/docs/assets/hevy-mcp-demo.mp4) · [Explore all 23 tools](#tools)
14
+ The published package currently requires **Node.js 20 or newer**; the
15
+ [`engines` field](https://github.com/chrisdoc/hevy-mcp/blob/main/packages/node/package.json)
16
+ is the compatibility source of truth.
16
17
 
17
- </div>
18
-
19
- `hevy-mcp` is an open-source [Model Context Protocol (MCP)](https://modelcontextprotocol.io/)
20
- server for the [Hevy](https://www.hevyapp.com/) fitness and workout tracking
21
- app. It lets AI assistants read, analyze, create, and update your Hevy workouts,
22
- routines, exercise templates, and body measurements through authenticated Hevy
23
- API requests.
24
-
25
- > A Hevy API key, available with **Hevy PRO**, is required.
26
-
27
- ## See it in action
28
-
29
- [![Hevy MCP demo showing an AI assistant analyzing six weeks of Hevy training data](https://raw.githubusercontent.com/chrisdoc/hevy-mcp/main/docs/assets/hevy-mcp-demo.gif)](https://raw.githubusercontent.com/chrisdoc/hevy-mcp/main/docs/assets/hevy-mcp-demo.mp4)
30
-
31
- <p align="center"><sub>Click the preview to play the full-quality 18-second demo.</sub></p>
32
-
33
- In the demo, the assistant retrieves real Hevy data and answers a multi-part
34
- training question with evidence from the user's workout history.
35
-
36
- ## What can you do with it?
37
-
38
- - **Analyze training progress:** summarize 1-12 weeks of workouts and body
39
- measurements in one tool call.
40
- - **Ask questions in plain language:** find recent sessions, frequently trained
41
- exercises, consistency gaps, routine details, or exercise history.
42
- - **Plan and log training:** create or update workouts, routines, routine folders,
43
- custom exercises, and body measurements.
44
- - **Search without huge responses:** discover routines and exercise templates with
45
- compact, AI-friendly results.
46
- - **Connect from your preferred MCP client:** use the hosted Streamable HTTP
47
- endpoint or run locally with Codex, Claude Desktop, Cursor, and other clients.
48
- - **Start without installing anything:** connect directly to the production
49
- Cloudflare Worker—no Node.js, package download, or Docker container required.
50
- - **Keep local control when you want it:** run the same server with `npx`, `bunx`,
51
- or the official Docker image.
52
-
53
- Try asking:
54
-
55
- > Analyze my training over the last six weeks. Show workouts per week, my most
56
- > frequently trained exercises, any obvious gaps or inconsistencies, and cite the
57
- > workout evidence you used.
58
-
59
- > Find my push-day routine and show its exercises and sets.
60
-
61
- > Compare my recent body measurements with my training consistency.
62
-
63
- > Create a completed workout from my saved routine. Ask me for any missing set
64
- > results before writing it to Hevy.
65
-
66
- ## Quick start
67
-
68
- ### 1. Get your Hevy API key
69
-
70
- Create an API key in [Hevy's API settings](https://hevy.com/settings?developer), then
71
- keep it somewhere secure. API access currently requires a Hevy PRO subscription.
72
-
73
- ### 2. Connect `hevy-mcp` to your client
74
-
75
- The hosted Cloudflare endpoint is the fastest way to start. It runs remotely,
76
- so your client does not need Node.js, Bun, Docker, or a local server process.
77
-
78
- #### Connect to the hosted endpoint
79
-
80
- Production URL:
81
-
82
- ```text
83
- https://mcp.hevy-mcp.dev/mcp
84
- ```
85
-
86
- The endpoint uses Streamable HTTP. Send your Hevy API key as a bearer token on
87
- every MCP request. The Worker also exposes unauthenticated `GET /health` as a
88
- liveness probe. It returns only `{"status":"ok"}` and does not validate a Hevy
89
- account. Browser CORS is limited to configured allowed origins.
90
-
91
- ##### Codex
92
-
93
- Codex CLI, the Codex desktop app, and the IDE extension share the same MCP
94
- configuration. Make your Hevy API key available in the environment that starts
95
- Codex, then add the hosted server:
96
-
97
- ```bash
98
- export HEVY_API_KEY=your-hevy-api-key
99
- codex mcp add hevy \
100
- --url https://mcp.hevy-mcp.dev/mcp \
101
- --bearer-token-env-var HEVY_API_KEY
102
- ```
103
-
104
- Codex stores the environment variable name, not the key itself, in its MCP
105
- configuration. Restart Codex or begin a new session, then run `codex mcp list`
106
- to verify the server is configured.
107
-
108
- ##### Other Streamable HTTP clients
109
-
110
- Clients that accept a remote MCP URL and fixed headers commonly use this shape:
111
-
112
- ```json
113
- {
114
- "mcpServers": {
115
- "hevy": {
116
- "url": "https://mcp.hevy-mcp.dev/mcp",
117
- "headers": {
118
- "Authorization": "Bearer your-hevy-api-key"
119
- }
120
- }
121
- }
122
- }
123
- ```
124
-
125
- Exact configuration keys vary by client. The hosted server requires support for
126
- Streamable HTTP and a fixed `Authorization` header.
127
-
128
- > [!IMPORTANT]
129
- > Treat the bearer value like a password. The Worker validates it with Hevy for
130
- > each request, does not store it, and forwards it to Hevy only as the required
131
- > `api-key` header.
132
-
133
- #### Run locally instead
134
-
135
- Choose local stdio if you prefer to run the server on your own machine or your
136
- client cannot attach a fixed authorization header to remote MCP requests.
137
-
138
- ##### Codex
139
-
140
- ```bash
141
- codex mcp add hevy \
142
- --env HEVY_API_KEY=your-hevy-api-key \
143
- -- npx -y hevy-mcp
144
- ```
145
-
146
- ##### Claude Desktop or Cursor
147
-
148
- Add this `mcpServers` entry to your client configuration:
18
+ Get an active key from [Hevy API settings](https://hevy.com/settings?developer).
19
+ Configure your MCP client to spawn the server and provide the key through its
20
+ child-process environment:
149
21
 
150
22
  ```json
151
23
  {
@@ -161,473 +33,114 @@ Add this `mcpServers` entry to your client configuration:
161
33
  }
162
34
  ```
163
35
 
164
- Common local configuration locations:
165
-
166
- - **Claude Desktop on macOS:**
167
- `~/Library/Application Support/Claude/claude_desktop_config.json`
168
- - **Claude Desktop on Windows:**
169
- `%APPDATA%\Claude\claude_desktop_config.json`
170
- - **Cursor:** `~/.cursor/mcp.json`
171
-
172
- Restart or reconnect the client after saving the file.
173
-
174
- ##### Any stdio MCP client
175
-
176
- Configure your client to launch this command with `HEVY_API_KEY` in the child
177
- process environment:
178
-
179
- ```bash
180
- npx -y hevy-mcp
181
- ```
182
-
183
- `npx` requires Node.js 20 or newer. Restart or reconnect your client after
184
- saving its configuration.
185
-
186
- ### Programmatic API
187
-
188
- The package exposes two named functions for applications that embed the MCP
189
- server:
190
-
191
- ```ts
192
- import { createNodeMcpServer, runStdioServer } from "hevy-mcp";
193
-
194
- const server = await createNodeMcpServer({ apiKey: process.env.HEVY_API_KEY! });
195
- // Connect `server` to the transport owned by your application.
196
- ```
36
+ The equivalent launch command is `npx -y hevy-mcp` with `HEVY_API_KEY` in the
37
+ child-process environment.
197
38
 
198
- Embedding applications can opt into the privacy-safe `feedback` tool by
199
- supplying their own `AgentFeedbackRecorder`. The recorder owns telemetry setup
200
- and should return `{ accepted: true }` only after it has accepted the bounded,
201
- sanitized message; omit it when feedback should remain unavailable:
202
-
203
- ```ts
204
- const server = await createNodeMcpServer({
205
- apiKey: process.env.HEVY_API_KEY!,
206
- feedbackRecorder: {
207
- record: (message) => {
208
- // Send `message` through the embedding application's approved pipeline.
209
- return { accepted: true };
210
- },
211
- },
212
- });
213
- ```
39
+ Stdio is the default transport. Stdout is reserved for MCP JSON-RPC; diagnostics
40
+ go to stderr. Set `HEVY_MCP_DEBUG=1` for privacy-bounded debugging. Keep real
41
+ keys out of source control, URLs, logs, and screenshots, and protect any client
42
+ configuration containing a key.
214
43
 
215
- `createNodeMcpServer` remains side-effect-free: it does not initialize project
216
- telemetry or create a recorder automatically. Without `feedbackRecorder`, the
217
- tool returns `telemetry_unavailable`.
44
+ See the maintained [local client examples](https://github.com/chrisdoc/hevy-mcp/blob/main/README.md#run-locally-instead)
45
+ for client-specific configuration and Docker usage. Reconnect your client after
46
+ changing its configuration. Try asking: “Summarize my training over the last
47
+ four weeks.” Review inputs before approving mutation tools; uncertain create
48
+ outcomes must not be blindly retried.
218
49
 
219
- For the CLI-owned stdio process, use the executable instead of creating an
220
- embedded server:
50
+ ## Hosted alternative
221
51
 
222
- ```ts
223
- import { runStdioServer } from "hevy-mcp";
52
+ No local process is needed when your client supports remote Streamable HTTP:
224
53
 
225
- await runStdioServer();
54
+ ```text
55
+ https://mcp.hevy-mcp.dev/mcp
226
56
  ```
227
57
 
228
- The runtime-owned runners resolve after startup while the transport remains
229
- active. They return a lifecycle handle with an idempotent `close()` method for
230
- embedding callers that need to initiate shutdown directly.
231
-
232
- `createNodeMcpServer` is the side-effect-free embedding entry: it validates the
233
- supplied key locally, but never reads environment variables, probes Hevy,
234
- connects a transport, initializes telemetry, or installs process lifecycle
235
- handlers. The CLI-only `runStdioServer` function owns those concerns.
236
-
237
- ## Runtime model
238
-
239
- The Node adapter owns the process Scope. Its scoped lifecycle acquires
240
- telemetry, signal handlers, and the selected transport, then finalizes them on
241
- shutdown. Core owns a nested server Scope for the MCP runtime and shared
242
- exercise-template cache. Each tool or resource invocation gets a request Scope
243
- whose deadline and MCP request signal interrupt the underlying Effect request.
244
-
245
- These internals do not replace the Promise embedding API. `createNodeMcpServer`
246
- and `runStdioServer` remain Promise entry points; Effect-typed variants, where
247
- provided, are additive. Importing `hevy-mcp` remains side-effect-free.
248
-
249
- Retry, timeout, and interruption decisions for Hevy requests are Effect-owned
250
- in the shared client. The hosted Worker is not Effect-wide: its OAuth,
251
- bindings, and request handling remain Promise-based. Tool contracts continue
252
- to use Zod, CLI/environment parsing remains throwing parser code, and Kubb
253
- generated internals are not part of the public package API.
58
+ Direct authentication sends `Authorization: Bearer <HEVY_API_KEY>` on each MCP
59
+ request. The Worker caches successful key validation for 15 minutes under a
60
+ hashed key; the direct path does not persist the raw key. It forwards the key
61
+ to Hevy only as the upstream `api-key` header.
254
62
 
255
- <details>
256
- <summary><strong>Use bunx instead</strong></summary>
257
-
258
- Requires [Bun](https://bun.sh/):
259
-
260
- ```json
261
- {
262
- "mcpServers": {
263
- "hevy": {
264
- "command": "bunx",
265
- "args": ["hevy-mcp@latest"],
266
- "env": {
267
- "HEVY_API_KEY": "your-hevy-api-key"
268
- }
269
- }
270
- }
271
- }
272
- ```
63
+ OAuth is a distinct, additive authentication path for clients that cannot send
64
+ a fixed header. When the Worker has an `OAUTH_KV` binding, its authorization
65
+ flow validates a Hevy key and stores it encrypted in an OAuth grant; clients
66
+ then send OAuth access tokens, not the Hevy key. Revoking or rotating the Hevy
67
+ key prevents subsequent upstream access but does not itself delete grants or
68
+ immediately clear cached validation.
273
69
 
274
- </details>
70
+ See [hosted client configuration](https://github.com/chrisdoc/hevy-mcp/blob/main/README.md#connect-to-the-hosted-endpoint)
71
+ and [OAuth configuration](https://github.com/chrisdoc/hevy-mcp/blob/main/README.md#oauth-for-claudeai-and-other-remote-mcp-clients).
72
+ The Worker is stateless and does not expose legacy SSE or a `GET /mcp` stream.
275
73
 
276
- <details>
277
- <summary><strong>Use Docker instead</strong></summary>
74
+ ## Local Streamable HTTP
278
75
 
279
- Official images support `linux/amd64` and `linux/arm64`. Keep stdin open with
280
- `-i` because the container runs the stdio MCP server:
76
+ Supply the Hevy key in the server process environment:
281
77
 
282
78
  ```bash
283
79
  export HEVY_API_KEY=your-hevy-api-key
284
- docker run -i --rm -e HEVY_API_KEY ghcr.io/chrisdoc/hevy-mcp:latest
80
+ npx hevy-mcp --transport http --host 127.0.0.1 --port 3000
285
81
  ```
286
82
 
287
- For an MCP client, store the key in a protected environment file and configure
288
- the client to launch Docker:
83
+ Clients connect to `http://127.0.0.1:3000/mcp`. Each initialized session owns its
84
+ own MCP server and catalog cache. `GET /health` is an unauthenticated liveness
85
+ probe, not a test of Hevy availability.
289
86
 
290
- ```json
291
- {
292
- "mcpServers": {
293
- "hevy": {
294
- "command": "docker",
295
- "args": [
296
- "run",
297
- "-i",
298
- "--rm",
299
- "--env-file",
300
- "/absolute/path/to/hevy-mcp.env",
301
- "ghcr.io/chrisdoc/hevy-mcp:latest"
302
- ]
303
- }
304
- }
305
- }
306
- ```
87
+ Non-loopback binds require `HEVY_MCP_HTTP_BEARER_TOKEN`, a **separate** endpoint
88
+ protection token that must not be the Hevy API key. Clients send this separate
89
+ token in their bearer header. Loopback binds do not enforce bearer
90
+ authentication. Publish Docker ports deliberately and do not expose an
91
+ unprotected shared account.
307
92
 
308
- Pin an exact image tag such as `ghcr.io/chrisdoc/hevy-mcp:X.Y.Z` when you need
309
- reproducible upgrades.
93
+ See [advanced configuration](https://github.com/chrisdoc/hevy-mcp/blob/main/README.md#advanced-configuration)
94
+ for HTTP limits and Docker commands, or [deployment modes](https://github.com/chrisdoc/hevy-mcp/blob/main/docs/deployment-modes.md)
95
+ for transport and authentication differences.
310
96
 
311
- </details>
312
-
313
- You can also add the npm server to supported clients with
314
- [`add-mcp`](https://github.com/neon-solutions/add-mcp):
315
-
316
- ```bash
317
- npx add-mcp hevy-mcp --env "HEVY_API_KEY=your-hevy-api-key"
318
- ```
97
+ ## Embed in a Node application
319
98
 
320
- ### 3. Ask your first question
99
+ The package's public ESM entry point supports caller-owned server construction:
321
100
 
322
- Try one of these after restarting or reconnecting your MCP client:
101
+ ```javascript
102
+ import { createNodeMcpServer } from "hevy-mcp";
103
+ import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
323
104
 
324
- - “Give me a training summary for the last four weeks.”
325
- - “What routines do I have saved on Hevy?”
326
- - “Show my three most recent workouts.”
327
- - “Find exercise templates containing squat.”
328
- - “Which Hevy account is connected?”
329
-
330
- Your assistant should ask for approval before mutation tools when the client
331
- supports tool confirmations.
332
-
333
- ## How it works
334
-
335
- ```text
336
- Hosted: Your AI assistant → Streamable HTTP → Cloudflare Worker → Hevy API
337
- Local: Your AI assistant → MCP over stdio → local hevy-mcp → Hevy API
105
+ const server = await createNodeMcpServer({ apiKey: process.env.HEVY_API_KEY });
106
+ await server.connect(new StdioServerTransport());
107
+ // Call await server.close() when your application shuts down.
338
108
  ```
339
109
 
340
- The hosted endpoint creates a fresh MCP server and Hevy client for each request.
341
- It validates the supplied key with Hevy, keeps no shared user session, and does
342
- not persist the key. The local server follows the same tool contract but runs on
343
- your machine and receives the key through its child-process environment.
344
-
345
- In either mode, read tools retrieve data; mutation tools create or replace data
346
- only when your assistant calls them.
347
-
348
- ## Guided prompts
349
-
350
- These server-provided MCP prompts coordinate common multi-step workflows:
351
-
352
- | Prompt | Arguments | Workflow |
353
- | ----------------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
354
- | `analyze-workout-progress` | Optional `weeks` from 1-12; default `4` | Calls `get-training-summary`, then analyzes workout activity and body-measurement trends from the returned evidence. |
355
- | `create-workout-from-routine` | Required `routine_id` and UTC `start_time` | Loads a routine, collects actual completed-set data and an end time, then creates a workout without inventing results. |
356
-
357
- > [!NOTE]
358
- > With MCP SDK v1.29.0, clients invoking `analyze-workout-progress` with its
359
- > default value must send `arguments: {}`. Omitting the entire `arguments`
360
- > object is rejected by that SDK version before the default is applied.
361
-
362
- ## Tools
363
-
364
- `hevy-mcp` registers 23 tools. Read-only tools are safe for exploration; create
365
- and update tools are exposed with MCP mutation annotations so compatible clients
366
- can request confirmation.
367
-
368
- | Category | Tool | Description |
369
- | ------------------ | --------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
370
- | Training analysis | `get-training-summary` | Summarize 1-12 weeks of workout activity and body-measurement trends in one call. |
371
- | Workouts | `get-workouts` | List workouts in Hevy API order, not by start time, with exercise and timing details. |
372
- | Workouts | `get-workout` | Get complete details for one workout by ID. |
373
- | Workouts | `get-workout-events` | List workout update and delete events since a timestamp. |
374
- | Workouts | `create-workout` | Create a completed workout in Hevy. |
375
- | Workouts | `update-workout` | Patch workout metadata by ID; `is_private` is required, while other omitted fields and all exercises remain unchanged. |
376
- | Workouts | `replace-workout-exercises` | Replace all exercises and sets; `is_private` is required and updated, while other workout metadata remains unchanged. |
377
- | Routines | `search-routines` | Search routine titles and return compact metadata for discovery. |
378
- | Routines | `get-routines` | List custom and default workout routines. |
379
- | Routines | `get-routine` | Get one routine and its exercise configuration by ID. |
380
- | Routines | `create-routine` | Create a reusable workout routine. |
381
- | Routines | `update-routine` | Replace an existing routine's content. |
382
- | Routine folders | `get-routine-folder` | Get one routine folder's metadata by ID. |
383
- | Routine folders | `create-routine-folder` | Create a routine folder. |
384
- | Exercise templates | `get-exercise-template` | Get complete metadata for one exercise template by ID. |
385
- | Exercise templates | `search-exercise-templates` | Search the full exercise catalog by title substring. |
386
- | Exercise templates | `create-exercise-template` | Create a custom exercise template. |
387
- | Exercise history | `get-exercise-history` | Get past performed sets for one exercise template. |
388
- | Body measurements | `get-body-measurements` | List dated body measurements. |
389
- | Body measurements | `get-body-measurement` | Get the body measurement entry for one date. |
390
- | Body measurements | `create-body-measurement` | Create a dated body measurement. |
391
- | Body measurements | `update-body-measurement` | Update the body measurement for an existing date. |
392
- | Diagnostics | `feedback` | Report a concise, privacy-safe description of an issue or unexpected behavior. |
393
-
394
- `create-routine` and `update-routine` require a top-level `routine` envelope with a non-empty `exercises` array; each exercise must contain at least one set, and fields use snake_case at every level:
110
+ Declare the MCP SDK as a direct dependency of an application importing its
111
+ transport. `createNodeMcpServer` requires an explicit non-empty key and returns
112
+ an unconnected server. It does not probe Hevy, read environment configuration,
113
+ initialize telemetry, or install process handlers. Your application owns the
114
+ transport and shutdown. Optional `maxGetRetries` and `feedbackRecorder`
115
+ configuration is documented in the [public API source](https://github.com/chrisdoc/hevy-mcp/blob/main/packages/node/src/index.ts).
395
116
 
396
- ```json
397
- {
398
- "routine": {
399
- "title": "Full Body A",
400
- "folder_id": 123,
401
- "notes": "First four exercises are the minimum viable workout",
402
- "exercises": [
403
- {
404
- "exercise_template_id": "30E293E3",
405
- "superset_id": null,
406
- "rest_seconds": 120,
407
- "notes": "Controlled active ROM",
408
- "sets": [
409
- {
410
- "type": "normal",
411
- "rep_range": {
412
- "start": 6,
413
- "end": 10
414
- }
415
- }
416
- ]
417
- }
418
- ]
419
- }
420
- }
421
- ```
422
-
423
- The Hevy API currently exposes no delete endpoints for workouts, routines,
424
- routine folders, exercise templates, or body measurements, so there are no
425
- corresponding delete tools.
426
-
427
- ### Resources
428
-
429
- | Name | URI | Description |
430
- | -------------------- | --------------------------- | -------------------------------------------- |
431
- | `user-profile` | `hevy://user` | Authenticated Hevy user profile. |
432
- | `workout-count` | `hevy://workout-count` | Total number of workouts in the account. |
433
- | `exercise-templates` | `hevy://exercise-templates` | Full formatted exercise template catalog. |
434
- | `routine-folders` | `hevy://routine-folders` | Full formatted list of Hevy routine folders. |
117
+ To opt into the executable's process lifecycle instead:
435
118
 
436
- ## Hosted Cloudflare endpoint
437
-
438
- The production MCP server is live at:
439
-
440
- ```text
441
- https://mcp.hevy-mcp.dev/mcp
442
- ```
443
-
444
- It is the quickest way to use `hevy-mcp`: there is nothing to install or keep
445
- running locally, and it exposes the same 23 tools as the npm package and Docker
446
- image.
447
-
448
- The Cloudflare Worker uses stateless **Streamable HTTP** at `POST /mcp`.
449
- Clients must send their Hevy API key as a fixed authorization header:
119
+ ```javascript
120
+ import { runStdioServer } from "hevy-mcp";
450
121
 
451
- ```json
452
- {
453
- "mcpServers": {
454
- "hevy": {
455
- "url": "https://mcp.hevy-mcp.dev/mcp",
456
- "headers": {
457
- "Authorization": "Bearer your-hevy-api-key"
458
- }
459
- }
460
- }
461
- }
122
+ await runStdioServer(); // Reads HEVY_API_KEY from the process environment.
462
123
  ```
463
124
 
464
- The bearer value is your Hevy API key, not an OAuth token. The Worker validates
465
- the key with Hevy on each request, does not store it, and forwards it upstream
466
- only as Hevy's required `api-key` header.
467
-
468
- ### OAuth for Claude.ai and other remote MCP clients
469
-
470
- Workers deployed with an `OAUTH_KV` namespace binding (see
471
- [CONTRIBUTING.md](./CONTRIBUTING.md)) additionally expose a full OAuth 2.1
472
- layer for clients that cannot send a fixed header, such as Claude.ai custom
473
- connectors:
474
-
475
- - RFC 8414 / RFC 9728 discovery metadata under `/.well-known/`
476
- - Dynamic client registration (`/register`) and PKCE token exchange (`/token`)
477
- - An `/authorize` page where you paste your Hevy API key once; the key is
478
- validated with Hevy and stored encrypted inside the OAuth grant
479
-
480
- Add the Worker URL ending in `/mcp` as a Claude.ai custom connector and
481
- complete the authorization flow in the browser. Direct
482
- `Authorization: Bearer <hevy-api-key>` requests keep working unchanged — the
483
- OAuth layer is purely additive — and rotating your Hevy API key invalidates
484
- every OAuth grant created with it.
485
-
486
- The endpoint does not expose legacy SSE or a `GET` event stream. Without the
487
- opt-in OAuth layer, clients that require OAuth discovery, dynamic
488
- registration, or token refresh are not compatible unless they can send the
489
- fixed custom header above.
490
-
491
- ### Self-host the Worker
492
-
493
- See [CONTRIBUTING.md](./CONTRIBUTING.md) to deploy the Cloudflare Worker for
494
- self-hosted Streamable HTTP.
495
-
496
- ## Advanced configuration
497
-
498
- | Setting | Default | Scope | Notes |
499
- | -------------------------------- | -------------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
500
- | `HEVY_API_KEY` | None; required | Local stdio or HTTP | Hevy API key from the Hevy app. Never pass it in a URL. |
501
- | `HEVY_MCP_API_TIMEOUT` | `60000` ms | Local stdio | Positive Hevy API timeout in milliseconds. Invalid values fall back to 60 seconds. |
502
- | `HEVY_MCP_DEBUG` | Disabled | Local Node | Set to exactly `1` for privacy-bounded diagnostics on stderr. Stdout remains reserved for MCP JSON-RPC. |
503
- | `HEVY_MCP_HTTP_BEARER_TOKEN` | None | Non-loopback HTTP | Required when `--host` is not loopback; use a separate token, never the Hevy API key. |
504
- | `HEVY_MCP_HTTP_MAX_SESSIONS` | `100` | Local HTTP | Maximum established sessions (including sessions currently initializing); excess requests receive `429` and DELETE, disconnect, or idle eviction frees capacity. Invalid values use the default; capped at 10,000. |
505
- | `HEVY_MCP_HTTP_MAX_INITIALIZING` | `10` | Local HTTP | Maximum concurrent session initializations; excess requests receive `503` and are not queued. Invalid values use the default; capped at 1,000. |
506
- | `HEVY_MCP_HTTP_IDLE_TIMEOUT_MS` | `1800000` ms | Local HTTP | Idle established sessions are evicted after 30 minutes; each session request resets the timer. Invalid values use the default; capped at 24 hours. |
507
- | `HEVY_MCP_HTTP_BODY_TIMEOUT_MS` | `30000` ms | Local HTTP | Application deadline for reading a request body. Stalled bodies receive `408`; invalid values use the default; capped at 5 minutes. |
508
- | `HEVY_MCP_TELEMETRY` | Enabled | Local Node | Set to exactly `0` before launching Node; the scoped lifecycle Layer reads it. Imports stay side-effect-free. |
509
- | `HEVY_MCP_TELEMETRY_DIAGNOSTICS` | Enabled | Local Node | Set to exactly `0` to keep structural telemetry while suppressing exception messages and stacks. |
510
- | `XDG_CACHE_HOME` | `~/.cache` | Local stdio | Changes the root for the npm update-check cache at `hevy-mcp/update-check.json`. |
511
- | `SENTRY_DSN` | Packaged Sentry SaaS project DSN | Optional local Node telemetry | Sentry project DSN override. An empty value disables Sentry export. The Worker does not import Node telemetry. |
512
- | `SENTRY_RELEASE` | `hevy-mcp@<installed-version>` | Optional local Node telemetry | Overrides the release label attached to local Sentry error events. |
513
- | `-h`, `--help` | N/A | Local stdio CLI | Print supported options and exit. |
514
- | `-v`, `--version` | N/A | Local stdio CLI | Print the installed version and exit. |
515
-
516
- The local executable uses stdio by default. To opt into Streamable HTTP, run:
125
+ Imports alone remain side-effect-free; the runtime helper acquires startup,
126
+ telemetry, and signal-handling resources when called. Use the executable for
127
+ HTTP transport selection.
517
128
 
518
- ```bash
519
- HEVY_API_KEY=your-hevy-api-key npx hevy-mcp --transport http --host 127.0.0.1 --port 3000
520
- ```
129
+ ## Telemetry and privacy
521
130
 
522
- The MCP endpoint is `http://127.0.0.1:3000/mcp`. The unauthenticated
523
- `GET /health` endpoint at `http://127.0.0.1:3000/health` is a liveness probe.
524
- It returns `{"status":"ok"}` while the server accepts requests. It returns
525
- `503` during shutdown. The probe does not validate Hevy availability. The Docker
526
- image uses stdio by default, so it has no HTTP health probe in that mode. Pass
527
- `--transport http` to expose one. For a specific bind host, HTTP mode validates
528
- the Host header and configured port to protect against DNS rebinding. Loopback
529
- is the default. Wildcard binds (`0.0.0.0` or `::`) accept any hostname so they
530
- can be used behind Docker port mappings or a reverse proxy; they require `HEVY_MCP_HTTP_BEARER_TOKEN` and rely on that separate
531
- authentication token. Do not expose an unprotected shared Hevy account to the
532
- public internet. For Docker HTTP mode, publish the port explicitly:
131
+ Published Node builds enable project telemetry by default. Unbuilt source runs
132
+ disable it unless `HEVY_MCP_TELEMETRY=1` is set explicitly. Set exactly
133
+ `HEVY_MCP_TELEMETRY=0` **before launch** to disable it in either mode. Imports
134
+ remain side-effect-free. Enabled source runs default to the Sentry environment
135
+ `development`; published builds use `production`. Set `SENTRY_ENVIRONMENT` to
136
+ override that label. Raw API keys and workout content are not exported; ordinary
137
+ tool telemetry can include a deterministic HMAC pseudonym derived from the key.
138
+ `feedback` uses a separate unobserved path.
533
139
 
534
- ```bash
535
- docker run --rm -p 3000:3000 -e HEVY_API_KEY -e HEVY_MCP_HTTP_BEARER_TOKEN \
536
- ghcr.io/chrisdoc/hevy-mcp:latest --transport http --host 0.0.0.0 --port 3000
537
- ```
140
+ See [telemetry and privacy](https://github.com/chrisdoc/hevy-mcp/blob/main/README.md#local-node-telemetry-and-privacy)
141
+ for diagnostic-detail controls and collector behavior, and
142
+ [troubleshooting](https://github.com/chrisdoc/hevy-mcp/blob/main/README.md#troubleshooting)
143
+ for connection and authentication issues.
538
144
 
539
- Wildcard binds are allowed only with the separate bearer token; publish the
540
- container port deliberately and keep that token private.
541
-
542
- ### Cache behavior
543
-
544
- `search-exercise-templates` and `hevy://exercise-templates` share a
545
- server-scoped in-memory catalog cache:
546
-
547
- - Entries live for five minutes, and the cache holds at most one catalog.
548
- - Concurrent catalog requests share an in-flight fetch when possible.
549
- - `search-exercise-templates` accepts `refresh: true` to invalidate the cache.
550
- - Each hosted Worker request gets a fresh cache, preventing cross-key sharing.
551
-
552
- ### Local Node telemetry and privacy
553
-
554
- The local Node package enables project telemetry by default. It is local Node
555
- behavior only; the Cloudflare Worker does not import Node telemetry. Set
556
- `HEVY_MCP_TELEMETRY=0` before launching the Node process to disable all project
557
- telemetry. The scoped Node lifecycle Layer reads this process-launch setting;
558
- importing the package remains side-effect-free. Only the literal value `0` opts
559
- out: an unset value, an empty value, `1`, `false`, and every other value remain
560
- enabled. The master setting takes precedence over `SENTRY_DSN` and packaged or
561
- runtime `OTEL_COLLECTOR_TOKEN` credentials, so the disabled path creates no
562
- telemetry exporters or periodic metric readers and makes no telemetry network
563
- requests. `SENTRY_DSN` remains a Sentry-only setting; when telemetry is enabled,
564
- an empty value disables only Sentry export.
565
-
566
- When enabled, actionable errors are sent to the Sentry project configured by
567
- `SENTRY_DSN`; Sentry performance tracing is disabled. Exception messages and
568
- stacks are bounded and scrubbed before export. Set
569
- `HEVY_MCP_TELEMETRY_DIAGNOSTICS=0` to keep structural traces and metrics while
570
- suppressing those details. Traces and metrics continue to be sent to the
571
- collector at
572
- <https://otel.chrisdoc.dev/v1/traces> and
573
- <https://otel.chrisdoc.dev/v1/metrics>, which forward to Honeycomb. Metrics
574
- export every 30 seconds.
575
-
576
- The API key is never exported and is not used to derive a user identity. A
577
- per-failure diagnostic ID and OTel trace ID may be attached to actionable
578
- errors for support correlation. Structured telemetry contains only bounded
579
- service, release, transport, tool, outcome, error, count, retry, duration,
580
- session, cache, workflow, API method, normalized endpoint, and status fields.
581
-
582
- Exception messages and stacks are treated as diagnostic details: they are
583
- length-limited, scrubbed for credentials, URLs, and local home paths, and
584
- removed entirely when `HEVY_MCP_TELEMETRY_DIAGNOSTICS=0`. Prompts, tool
585
- arguments, tool results, request bodies, API keys, raw identifiers/queries,
586
- exact dates, workout/routine/folder/template/body-measurement content,
587
- names/titles/descriptions/notes, measurement values, arbitrary client
588
- metadata, and unnormalized endpoint paths remain prohibited.
589
-
590
- ## Security and mutations
591
-
592
- - Keep `HEVY_API_KEY` out of source control, URLs, logs, and screenshots.
593
- - Local clients provide the key through the child process environment.
594
- - Hosted clients send the key only in the `Authorization: Bearer` header. The
595
- Worker validates each key with Hevy, does not store it, and sends it upstream
596
- only as Hevy's `api-key` header.
597
- - Browser requests must come from an exact allowlisted origin. The default
598
- allowlist includes Claude.ai, ChatGPT, VS Code for the Web, and github.dev;
599
- self-hosted deployments can override it with `MCP_ALLOWED_ORIGINS`.
600
- - Create operations can produce duplicates when retried. Update operations
601
- replace existing records. Review tool inputs and use client confirmations.
602
-
603
- ## Troubleshooting
604
-
605
- - **The server does not appear:** restart or reconnect your MCP client after
606
- changing its configuration.
607
- - **`npx` fails:** confirm that Node.js 20 or newer is installed, then run
608
- `npx -y hevy-mcp --version` in a terminal.
609
- - **Codex cannot see the server:** run `codex mcp list`, then start a new Codex
610
- session after confirming the `hevy` entry exists.
611
- - **Hevy API returns 401:** the key is invalid, expired, revoked, or
612
- misconfigured. Verify or create an active key at
613
- [Hevy's API settings](https://hevy.com/settings?developer), then restart the client.
614
- - **Hosted authentication fails:** confirm the key belongs to a Hevy PRO
615
- account and is sent as `Authorization: Bearer <HEVY_API_KEY>`.
616
- - **Local authentication fails:** confirm the key is active and available to the
617
- MCP child process as `HEVY_API_KEY`.
618
- - **Need diagnostics:** set `HEVY_MCP_DEBUG=1`. Diagnostic output goes to stderr
619
- and does not interfere with MCP messages on stdout.
620
-
621
- If you find a bug or have a feature request, [open an issue](https://github.com/chrisdoc/hevy-mcp/issues).
622
-
623
- ## Contributing
624
-
625
- Contributions are welcome. Developer setup, testing lanes, generated-client
626
- workflows, Cloudflare Worker deployment, and pull request rules are documented
627
- in [CONTRIBUTING.md](./CONTRIBUTING.md).
628
-
629
- ## License and acknowledgements
630
-
631
- - **License:** [MIT](./LICENSE)
632
- - **Credits:** [Model Context Protocol](https://github.com/modelcontextprotocol)
633
- and [Hevy Fitness](https://www.hevyapp.com/)
145
+ [MIT license](https://github.com/chrisdoc/hevy-mcp/blob/main/LICENSE) ·
146
+ [Report an issue](https://github.com/chrisdoc/hevy-mcp/issues)
package/dist/cli.mjs CHANGED
@@ -8,7 +8,7 @@
8
8
  n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "d637b259-c547-4286-9669-7b858825b504", e._sentryDebugIdIdentifier = "sentry-dbid-d637b259-c547-4286-9669-7b858825b504");
9
9
  } catch (e) {}
10
10
  })();
11
- import { i as handleFatalStartupError, r as getSafeStartupMessage, t as runServer } from "./runtime-cyL53BFT.mjs";
11
+ import { i as handleFatalStartupError, r as getSafeStartupMessage, t as runServer } from "./runtime-BNYpQWRr.mjs";
12
12
  //#region src/cli.ts
13
13
  runServer().catch(async (error) => {
14
14
  await handleFatalStartupError(error);
@@ -2350,166 +2350,105 @@ function createNativeClient(apiKey, baseUrl, options) {
2350
2350
  function createClient(apiKey, baseUrl = "https://api.hevyapp.com", options = {}) {
2351
2351
  const headers = { "api-key": apiKey };
2352
2352
  const client = createNativeClient(apiKey, baseUrl, options);
2353
+ const generatedClient = client;
2354
+ const configure = (options) => ({
2355
+ headers,
2356
+ ...requestOptions(options, client),
2357
+ client: generatedClient
2358
+ });
2359
+ const request = async (options, operation) => (await operation(configure(options))).data;
2353
2360
  return {
2354
2361
  client: {
2355
- getWorkouts: async (params, options) => {
2356
- return (await getV1Workouts({
2357
- headers,
2358
- query: params,
2359
- ...requestOptions(options, client)
2360
- })).data;
2361
- },
2362
- getWorkout: async (workoutId, options) => {
2363
- return (await getV1WorkoutsWorkoutid({
2364
- headers,
2365
- path: { workoutId },
2366
- ...requestOptions(options, client)
2367
- })).data;
2368
- },
2369
- createWorkout: async (data, options) => {
2370
- return (await postV1Workouts({
2371
- headers,
2372
- body: data,
2373
- ...requestOptions(options, client)
2374
- })).data;
2375
- },
2376
- updateWorkout: async (workoutId, data, options) => {
2377
- return (await putV1WorkoutsWorkoutid({
2378
- headers,
2379
- path: { workoutId },
2380
- body: data,
2381
- ...requestOptions(options, client)
2382
- })).data;
2383
- },
2384
- getWorkoutCount: async (options) => {
2385
- return (await getV1WorkoutsCount({
2386
- headers,
2387
- ...requestOptions(options, client)
2388
- })).data;
2389
- },
2390
- getWorkoutEvents: async (params, options) => {
2391
- return (await getV1WorkoutsEvents({
2392
- headers,
2393
- query: params,
2394
- ...requestOptions(options, client)
2395
- })).data;
2396
- },
2397
- getRoutines: async (params, options) => {
2398
- return (await getV1Routines({
2399
- headers,
2400
- query: params,
2401
- ...requestOptions(options, client)
2402
- })).data;
2403
- },
2404
- getRoutineById: async (routineId, options) => {
2405
- return (await getV1RoutinesRoutineid({
2406
- headers,
2407
- path: { routineId },
2408
- ...requestOptions(options, client)
2409
- })).data;
2410
- },
2411
- createRoutine: async (data, options) => {
2412
- const response = (await postV1Routines({
2413
- headers,
2414
- body: data,
2415
- ...requestOptions(options, client)
2416
- })).data;
2362
+ getWorkouts: (query, options) => request(options, (config) => getV1Workouts({
2363
+ ...config,
2364
+ query
2365
+ })),
2366
+ getWorkout: (workoutId, options) => request(options, (config) => getV1WorkoutsWorkoutid({
2367
+ ...config,
2368
+ path: { workoutId }
2369
+ })),
2370
+ createWorkout: (body, options) => request(options, (config) => postV1Workouts({
2371
+ ...config,
2372
+ body
2373
+ })),
2374
+ updateWorkout: (workoutId, body, options) => request(options, (config) => putV1WorkoutsWorkoutid({
2375
+ ...config,
2376
+ path: { workoutId },
2377
+ body
2378
+ })),
2379
+ getWorkoutCount: (options) => request(options, (config) => getV1WorkoutsCount(config)),
2380
+ getWorkoutEvents: (query, options) => request(options, (config) => getV1WorkoutsEvents({
2381
+ ...config,
2382
+ query
2383
+ })),
2384
+ getRoutines: (query, options) => request(options, (config) => getV1Routines({
2385
+ ...config,
2386
+ query
2387
+ })),
2388
+ getRoutineById: (routineId, options) => request(options, (config) => getV1RoutinesRoutineid({
2389
+ ...config,
2390
+ path: { routineId }
2391
+ })),
2392
+ createRoutine: async (body, options) => {
2393
+ const response = await request(options, (config) => postV1Routines({
2394
+ ...config,
2395
+ body
2396
+ }));
2417
2397
  if ("routine" in response) return response.routine;
2418
2398
  return Object.keys(response).length === 0 ? void 0 : response;
2419
2399
  },
2420
- updateRoutine: async (routineId, data, options) => {
2421
- return (await putV1RoutinesRoutineid({
2422
- headers,
2423
- path: { routineId },
2424
- body: data,
2425
- ...requestOptions(options, client)
2426
- })).data;
2427
- },
2428
- getExerciseTemplates: async (params, options) => {
2429
- return (await getV1ExerciseTemplates({
2430
- headers,
2431
- query: params,
2432
- ...requestOptions(options, client)
2433
- })).data;
2434
- },
2435
- getExerciseTemplate: async (templateId, options) => {
2436
- return (await getV1ExerciseTemplatesExercisetemplateid({
2437
- headers,
2438
- path: { exerciseTemplateId: templateId },
2439
- ...requestOptions(options, client)
2440
- })).data;
2441
- },
2442
- getExerciseHistory: async (exerciseTemplateId, params, options) => {
2443
- return (await getV1ExerciseHistoryExercisetemplateid({
2444
- headers,
2445
- path: { exerciseTemplateId },
2446
- query: params,
2447
- ...requestOptions(options, client)
2448
- })).data;
2449
- },
2450
- createExerciseTemplate: async (data, options) => {
2451
- return (await postV1ExerciseTemplates({
2452
- headers,
2453
- body: data,
2454
- ...requestOptions(options, client)
2455
- })).data;
2456
- },
2457
- getRoutineFolders: async (params, options) => {
2458
- return (await getV1RoutineFolders({
2459
- headers,
2460
- query: params,
2461
- ...requestOptions(options, client)
2462
- })).data;
2463
- },
2464
- createRoutineFolder: async (data, options) => {
2465
- return (await postV1RoutineFolders({
2466
- headers,
2467
- body: data,
2468
- ...requestOptions(options, client)
2469
- })).data;
2470
- },
2471
- getRoutineFolder: async (folderId, options) => {
2472
- return (await getV1RoutineFoldersFolderid({
2473
- headers,
2474
- path: { folderId },
2475
- ...requestOptions(options, client)
2476
- })).data;
2477
- },
2478
- getBodyMeasurements: async (params, options) => {
2479
- return (await getV1BodyMeasurements({
2480
- headers,
2481
- query: params,
2482
- ...requestOptions(options, client)
2483
- })).data;
2484
- },
2485
- getBodyMeasurement: async (date, options) => {
2486
- return (await getV1BodyMeasurementsDate({
2487
- headers,
2488
- path: { date },
2489
- ...requestOptions(options, client)
2490
- })).data;
2491
- },
2492
- createBodyMeasurement: async (data, options) => {
2493
- return (await postV1BodyMeasurements({
2494
- headers,
2495
- body: data,
2496
- ...requestOptions(options, client)
2497
- })).data;
2498
- },
2499
- updateBodyMeasurement: async (date, data, options) => {
2500
- return (await putV1BodyMeasurementsDate({
2501
- headers,
2502
- path: { date },
2503
- body: data,
2504
- ...requestOptions(options, client)
2505
- })).data;
2506
- },
2507
- getUserInfo: async (options) => {
2508
- return (await getV1UserInfo({
2509
- headers,
2510
- ...requestOptions(options, client)
2511
- })).data;
2512
- }
2400
+ updateRoutine: (routineId, body, options) => request(options, (config) => putV1RoutinesRoutineid({
2401
+ ...config,
2402
+ path: { routineId },
2403
+ body
2404
+ })),
2405
+ getExerciseTemplates: (query, options) => request(options, (config) => getV1ExerciseTemplates({
2406
+ ...config,
2407
+ query
2408
+ })),
2409
+ getExerciseTemplate: (exerciseTemplateId, options) => request(options, (config) => getV1ExerciseTemplatesExercisetemplateid({
2410
+ ...config,
2411
+ path: { exerciseTemplateId }
2412
+ })),
2413
+ getExerciseHistory: (exerciseTemplateId, query, options) => request(options, (config) => getV1ExerciseHistoryExercisetemplateid({
2414
+ ...config,
2415
+ path: { exerciseTemplateId },
2416
+ query
2417
+ })),
2418
+ createExerciseTemplate: (body, options) => request(options, (config) => postV1ExerciseTemplates({
2419
+ ...config,
2420
+ body
2421
+ })),
2422
+ getRoutineFolders: (query, options) => request(options, (config) => getV1RoutineFolders({
2423
+ ...config,
2424
+ query
2425
+ })),
2426
+ createRoutineFolder: (body, options) => request(options, (config) => postV1RoutineFolders({
2427
+ ...config,
2428
+ body
2429
+ })),
2430
+ getRoutineFolder: (folderId, options) => request(options, (config) => getV1RoutineFoldersFolderid({
2431
+ ...config,
2432
+ path: { folderId }
2433
+ })),
2434
+ getBodyMeasurements: (query, options) => request(options, (config) => getV1BodyMeasurements({
2435
+ ...config,
2436
+ query
2437
+ })),
2438
+ getBodyMeasurement: (date, options) => request(options, (config) => getV1BodyMeasurementsDate({
2439
+ ...config,
2440
+ path: { date }
2441
+ })),
2442
+ createBodyMeasurement: (body, options) => request(options, (config) => postV1BodyMeasurements({
2443
+ ...config,
2444
+ body
2445
+ })),
2446
+ updateBodyMeasurement: (date, body, options) => request(options, (config) => putV1BodyMeasurementsDate({
2447
+ ...config,
2448
+ path: { date },
2449
+ body
2450
+ })),
2451
+ getUserInfo: (options) => request(options, (config) => getV1UserInfo(config))
2513
2452
  },
2514
2453
  requestEffect: client.requestEffect
2515
2454
  };
@@ -6993,7 +6932,7 @@ function readBuildGlobal(value) {
6993
6932
  }
6994
6933
  }
6995
6934
  const buildName = readBuildGlobal(() => "hevy-mcp");
6996
- const buildVersion = readBuildGlobal(() => "6.1.21");
6935
+ const buildVersion = readBuildGlobal(() => "6.1.22");
6997
6936
  const SERVER_NAME = readBuildString(buildName, "hevy-mcp");
6998
6937
  const SERVER_VERSION = readBuildString(buildVersion, "dev");
6999
6938
  const SERVER_INSTRUCTIONS = [
@@ -7576,4 +7515,4 @@ function assertApiKey(apiKey) {
7576
7515
  //#endregion
7577
7516
  export { isHevyHttpError as _, SAFE_USER_HASH_PATTERN as a, mergeAbortSignals as c, createSafeErrorDiagnostic as d, bucketCount as f, metricEndpointIdentity as g, diagnosticEndpointIdentity as h, createHevyMcpServer as i, sanitizeDiagnosticText as l, SAFE_OBSERVATION_CODES as m, assertApiKey as n, USER_HASH_CONTEXT as o, createHevyClient as p, parseConfig as r, createExecutionProjection as s, MissingHevyApiKeyError as t, FEEDBACK_MAX_MESSAGE_LENGTH as u };
7578
7517
 
7579
- //# sourceMappingURL=config-CzIupjFC.mjs.map
7518
+ //# sourceMappingURL=config-D1ZvWKRJ.mjs.map
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // Generated with tsdown
3
3
  // https://tsdown.dev
4
- import { i as createHevyMcpServer, n as assertApiKey, p as createHevyClient } from "./config-CzIupjFC.mjs";
4
+ import { i as createHevyMcpServer, n as assertApiKey, p as createHevyClient } from "./config-D1ZvWKRJ.mjs";
5
5
  //#region src/index.ts
6
6
  async function createNodeMcpServer({ apiKey, maxGetRetries, feedbackRecorder }, _transport = "stdio", lifecycleSignal) {
7
7
  assertApiKey(apiKey);
@@ -20,7 +20,7 @@ async function createNodeMcpServer({ apiKey, maxGetRetries, feedbackRecorder },
20
20
  * does not evaluate the runtime bootstrap; it is loaded only when invoked.
21
21
  */
22
22
  async function runStdioServer() {
23
- const { runStdioServer: run } = await import("./runtime-cyL53BFT.mjs").then((n) => n.n);
23
+ const { runStdioServer: run } = await import("./runtime-BNYpQWRr.mjs").then((n) => n.n);
24
24
  return run();
25
25
  }
26
26
  /**
@@ -28,7 +28,7 @@ async function runStdioServer() {
28
28
  * does not evaluate the runtime bootstrap; it is loaded only when invoked.
29
29
  */
30
30
  async function runServer() {
31
- const { runServer: run } = await import("./runtime-cyL53BFT.mjs").then((n) => n.n);
31
+ const { runServer: run } = await import("./runtime-BNYpQWRr.mjs").then((n) => n.n);
32
32
  return run();
33
33
  }
34
34
  //#endregion
@@ -5,10 +5,10 @@
5
5
  try {
6
6
  var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
7
7
  var n = new e.Error().stack;
8
- n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "9b776cde-abdc-4f0e-b489-fbab5663883d", e._sentryDebugIdIdentifier = "sentry-dbid-9b776cde-abdc-4f0e-b489-fbab5663883d");
8
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "5e5a5e87-68cd-4783-9030-b22022f32bf5", e._sentryDebugIdIdentifier = "sentry-dbid-5e5a5e87-68cd-4783-9030-b22022f32bf5");
9
9
  } catch (e) {}
10
10
  })();
11
- import { _ as isHevyHttpError, a as SAFE_USER_HASH_PATTERN, c as mergeAbortSignals, d as createSafeErrorDiagnostic, f as bucketCount, g as metricEndpointIdentity, h as diagnosticEndpointIdentity, i as createHevyMcpServer, l as sanitizeDiagnosticText$1, m as SAFE_OBSERVATION_CODES, n as assertApiKey, o as USER_HASH_CONTEXT, p as createHevyClient, r as parseConfig, s as createExecutionProjection, t as MissingHevyApiKeyError, u as FEEDBACK_MAX_MESSAGE_LENGTH } from "./config-CzIupjFC.mjs";
11
+ import { _ as isHevyHttpError, a as SAFE_USER_HASH_PATTERN, c as mergeAbortSignals, d as createSafeErrorDiagnostic, f as bucketCount, g as metricEndpointIdentity, h as diagnosticEndpointIdentity, i as createHevyMcpServer, l as sanitizeDiagnosticText$1, m as SAFE_OBSERVATION_CODES, n as assertApiKey, o as USER_HASH_CONTEXT, p as createHevyClient, r as parseConfig, s as createExecutionProjection, t as MissingHevyApiKeyError, u as FEEDBACK_MAX_MESSAGE_LENGTH } from "./config-D1ZvWKRJ.mjs";
12
12
  import { ZodError, z } from "zod";
13
13
  import { Data, Duration, Effect, Exit, Fiber, Layer, Scope, Semaphore } from "effect";
14
14
  import { deserializeMessage } from "@modelcontextprotocol/server";
@@ -45,6 +45,11 @@ var __exportAll = (all, no_symbols) => {
45
45
  return target;
46
46
  };
47
47
  //#endregion
48
+ //#region src/utils/telemetry-config.ts
49
+ const setting = process.env.HEVY_MCP_TELEMETRY;
50
+ const telemetryEnabled = setting === "1" || setting !== "0";
51
+ const sentryEnvironment = process.env.SENTRY_ENVIRONMENT ?? "production";
52
+ //#endregion
48
53
  //#region src/utils/failure-reporter.ts
49
54
  const MAX_ATTRIBUTE_LENGTH = 256;
50
55
  const MAX_MESSAGE_LENGTH = 2048;
@@ -58,7 +63,6 @@ const SAFE_TAG_PREFIXES = [
58
63
  "otel.",
59
64
  "service."
60
65
  ];
61
- const TELEMETRY_ENABLED = process.env.HEVY_MCP_TELEMETRY !== "0";
62
66
  const DIAGNOSTIC_DETAILS_ENABLED = process.env.HEVY_MCP_TELEMETRY_DIAGNOSTICS !== "0";
63
67
  function isObject$4(value) {
64
68
  return z.object({}).passthrough().safeParse(value).success;
@@ -270,7 +274,7 @@ function hasReportedError(error) {
270
274
  return false;
271
275
  }
272
276
  function captureFailure(error, context) {
273
- if (!TELEMETRY_ENABLED) return void 0;
277
+ if (!telemetryEnabled) return void 0;
274
278
  const span = context.span ?? trace.getActiveSpan() ?? void 0;
275
279
  const record = normalizeFailure(error, context);
276
280
  const traceContext = getTraceContext(span);
@@ -385,8 +389,7 @@ function parseBuildString(value, fallback) {
385
389
  return z.string().parse(value ?? fallback);
386
390
  }
387
391
  const name$1 = parseBuildString(readBuildGlobal(() => "hevy-mcp"), "hevy-mcp");
388
- const version$1 = parseBuildString(readBuildGlobal(() => "6.1.21"), "dev");
389
- const telemetryEnabled = process.env.HEVY_MCP_TELEMETRY !== "0";
392
+ const version$1 = parseBuildString(readBuildGlobal(() => "6.1.22"), "dev");
390
393
  const collectorToken = z.string().safeParse(readBuildGlobal(() => "NH9vOela-HYreQxAbJa68cjEmORoEKM57EvneUDVcOo")).data ?? process.env.OTEL_COLLECTOR_TOKEN ?? "";
391
394
  const COLLECTOR_ENDPOINT = "https://otel.chrisdoc.dev/v1";
392
395
  const DEFAULT_SENTRY_DSN = "https://ce696d8333b507acbf5203eb877bce0f@o4508975499575296.ingest.de.sentry.io/4509049671647312";
@@ -423,6 +426,7 @@ function initializeTelemetry() {
423
426
  Sentry.init({
424
427
  dsn: isValidDsn ? rawDsn : void 0,
425
428
  release: sentryRelease,
429
+ environment: sentryEnvironment,
426
430
  tracesSampleRate: 0,
427
431
  sendClientReports: false,
428
432
  dataCollection: { userInfo: false },
@@ -2807,6 +2811,7 @@ const HELP_TEXT = [
2807
2811
  " HEVY_MCP_DEBUG=1 Enable verbose diagnostics on stderr",
2808
2812
  " HEVY_MCP_HTTP_BEARER_TOKEN Protect non-loopback HTTP deployments",
2809
2813
  " HEVY_MCP_TELEMETRY=0 Disable all project telemetry",
2814
+ " HEVY_MCP_TELEMETRY=1 Enable telemetry for unbuilt source runs",
2810
2815
  " HEVY_MCP_TELEMETRY_DIAGNOSTICS=0 Suppress exception details",
2811
2816
  "",
2812
2817
  "Examples:",
@@ -3008,4 +3013,4 @@ async function runServer() {
3008
3013
  //#endregion
3009
3014
  export { handleFatalStartupError as i, runtime_exports as n, getSafeStartupMessage as r, runServer as t };
3010
3015
 
3011
- //# sourceMappingURL=runtime-cyL53BFT.mjs.map
3016
+ //# sourceMappingURL=runtime-BNYpQWRr.mjs.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hevy-mcp",
3
- "version": "6.1.21",
3
+ "version": "6.1.22",
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": {
@@ -44,8 +44,8 @@
44
44
  "zod": "^4.6.5"
45
45
  },
46
46
  "devDependencies": {
47
- "@hevy-mcp/core": "0.2.19",
48
- "@hevy-mcp/hevy-client": "0.2.13",
47
+ "@hevy-mcp/core": "0.2.20",
48
+ "@hevy-mcp/hevy-client": "0.2.14",
49
49
  "@sentry/core": "^11.4.0",
50
50
  "@types/node": "^26.6.4",
51
51
  "cross-env": "^10.1.0",
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": "6.1.21",
10
+ "version": "6.1.22",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "hevy-mcp",
15
- "version": "6.1.21",
15
+ "version": "6.1.22",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  },