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 +14 -0
- package/README.md +93 -580
- package/dist/cli.mjs +1 -1
- package/dist/{config-CzIupjFC.mjs → config-D1ZvWKRJ.mjs} +96 -157
- package/dist/index.mjs +3 -3
- package/dist/{runtime-cyL53BFT.mjs → runtime-BNYpQWRr.mjs} +12 -7
- package/package.json +3 -3
- package/server.json +2 -2
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
|
-
#
|
|
1
|
+
# hevy-mcp
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8
|
-
[](https://www.npmjs.com/package/hevy-mcp)
|
|
9
|
-
[](https://github.com/chrisdoc/hevy-mcp/actions/workflows/build-and-test.yml)
|
|
10
|
-
[](https://codecov.io/gh/chrisdoc/hevy-mcp)
|
|
11
|
-
[](https://github.com/chrisdoc/hevy-mcp/stargazers)
|
|
12
|
-
[](#hosted-cloudflare-endpoint)
|
|
13
|
-
[](./LICENSE)
|
|
12
|
+
## Run locally with stdio
|
|
14
13
|
|
|
15
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
[](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
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
220
|
-
embedded server:
|
|
50
|
+
## Hosted alternative
|
|
221
51
|
|
|
222
|
-
|
|
223
|
-
import { runStdioServer } from "hevy-mcp";
|
|
52
|
+
No local process is needed when your client supports remote Streamable HTTP:
|
|
224
53
|
|
|
225
|
-
|
|
54
|
+
```text
|
|
55
|
+
https://mcp.hevy-mcp.dev/mcp
|
|
226
56
|
```
|
|
227
57
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
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
|
-
|
|
277
|
-
<summary><strong>Use Docker instead</strong></summary>
|
|
74
|
+
## Local Streamable HTTP
|
|
278
75
|
|
|
279
|
-
|
|
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
|
-
|
|
80
|
+
npx hevy-mcp --transport http --host 127.0.0.1 --port 3000
|
|
285
81
|
```
|
|
286
82
|
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
309
|
-
|
|
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
|
-
|
|
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
|
-
|
|
99
|
+
The package's public ESM entry point supports caller-owned server construction:
|
|
321
100
|
|
|
322
|
-
|
|
101
|
+
```javascript
|
|
102
|
+
import { createNodeMcpServer } from "hevy-mcp";
|
|
103
|
+
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
323
104
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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
|
-
|
|
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
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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
|
-
|
|
535
|
-
|
|
536
|
-
|
|
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
|
-
|
|
540
|
-
|
|
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-
|
|
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:
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
...
|
|
2388
|
-
|
|
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:
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
},
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
},
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
},
|
|
2471
|
-
|
|
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.
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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] = "
|
|
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-
|
|
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 (!
|
|
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.
|
|
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-
|
|
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.
|
|
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.
|
|
48
|
-
"@hevy-mcp/hevy-client": "0.2.
|
|
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.
|
|
10
|
+
"version": "6.1.22",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "hevy-mcp",
|
|
15
|
-
"version": "6.1.
|
|
15
|
+
"version": "6.1.22",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|