hevy-mcp 1.25.17 → 1.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -9
- package/dist/cli.mjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{src-1TMPprRG.mjs → src-Di7keTQA.mjs} +301 -91
- package/dist/src-Di7keTQA.mjs.map +1 -0
- package/package.json +10 -2
- package/dist/src-1TMPprRG.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -205,6 +205,15 @@ Docker-based workflows are retired. The provided `Dockerfile` now exits with a m
|
|
|
205
205
|
|
|
206
206
|
For a detailed senior engineer guide, please refer to [AGENTS.md](./AGENTS.md).
|
|
207
207
|
|
|
208
|
+
### Pull Request Checks
|
|
209
|
+
|
|
210
|
+
- **Conventional Commits**: CI lints commit messages on pull requests, so use
|
|
211
|
+
prefixes such as `feat:`, `fix:`, `docs:`, `ci:`, `chore:`, `refactor:`,
|
|
212
|
+
`test:`, or `style:`.
|
|
213
|
+
- **Changesets**: Contributor pull requests targeting `main` must include a
|
|
214
|
+
changeset. Dependabot PRs and automated `changeset-release/main` release PRs
|
|
215
|
+
are handled by automation and skip this check.
|
|
216
|
+
|
|
208
217
|
### API Client Generation
|
|
209
218
|
|
|
210
219
|
The API client is automatically generated from the OpenAPI spec using [Kubb](https://kubb.dev/):
|
|
@@ -215,24 +224,40 @@ npm run build:client
|
|
|
215
224
|
|
|
216
225
|
### Versioning & Releases
|
|
217
226
|
|
|
218
|
-
This project uses [Changesets](https://github.com/changesets/changesets) to
|
|
219
|
-
|
|
220
|
-
|
|
227
|
+
This project uses [Changesets](https://github.com/changesets/changesets) to
|
|
228
|
+
manage versioning, changelogs, releases, and pull request validation.
|
|
229
|
+
|
|
230
|
+
1. **Routine Release Cadence**: Merge the automated
|
|
231
|
+
`changeset-release/main` (**"Version Packages"**) Pull Request on a regular
|
|
232
|
+
cadence (weekly is a good default) instead of ad-hoc frequent merges.
|
|
233
|
+
2. **Urgent Release Exception**: Security fixes and high-impact,
|
|
234
|
+
user-facing bug fixes can be released immediately outside the routine
|
|
235
|
+
cadence.
|
|
236
|
+
3. **Use Bump Changesets Only for User-Facing Runtime Changes**: If your
|
|
237
|
+
change is user-facing/runtime-visible, run:
|
|
221
238
|
```bash
|
|
222
239
|
npx changeset
|
|
223
240
|
```
|
|
224
|
-
Follow the prompts to
|
|
225
|
-
|
|
241
|
+
Follow the prompts to choose `patch`, `minor`, or `major`, then write a
|
|
242
|
+
short summary. This creates a markdown file under `.changeset/`.
|
|
243
|
+
4. **Use Empty Changesets for Internal-Only Work**: Docs, CI, test-only,
|
|
244
|
+
refactor, and chore changes should use an empty changeset:
|
|
226
245
|
```bash
|
|
227
246
|
npx changeset --empty
|
|
228
247
|
```
|
|
229
|
-
|
|
248
|
+
5. **Validate Before Opening a PR**: Contributor pull requests targeting
|
|
249
|
+
`main` are checked for a changeset in CI. Dependabot PRs and automated
|
|
250
|
+
`changeset-release/main` release PRs are handled separately. You can run the
|
|
251
|
+
same validation locally with:
|
|
230
252
|
```bash
|
|
231
253
|
npm run check:changeset
|
|
232
254
|
```
|
|
233
|
-
|
|
234
|
-
- Pushing changesets to `main` triggers a GitHub Action that automatically
|
|
235
|
-
|
|
255
|
+
6. **Automated Releases**:
|
|
256
|
+
- Pushing changesets to `main` triggers a GitHub Action that automatically
|
|
257
|
+
creates or updates a **"Version Packages"** Pull Request.
|
|
258
|
+
- When this Pull Request is merged, the package is automatically built,
|
|
259
|
+
published to npm (via OIDC Trusted Publishing), and a GitHub Release is
|
|
260
|
+
created.
|
|
236
261
|
|
|
237
262
|
---
|
|
238
263
|
|
package/dist/cli.mjs
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
(function() {
|
|
5
5
|
try {
|
|
6
6
|
var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
|
|
7
|
-
e.SENTRY_RELEASE = { id: "hevy-mcp@1.
|
|
7
|
+
e.SENTRY_RELEASE = { id: "hevy-mcp@1.26.0" };
|
|
8
8
|
var n = new e.Error().stack;
|
|
9
9
|
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "03cf150f-6466-4ca3-9cf1-e423cc0e3cd2", e._sentryDebugIdIdentifier = "sentry-dbid-03cf150f-6466-4ca3-9cf1-e423cc0e3cd2");
|
|
10
10
|
} catch (e) {}
|
|
11
11
|
})();
|
|
12
|
-
import { r as runServer } from "./src-
|
|
12
|
+
import { r as runServer } from "./src-Di7keTQA.mjs";
|
|
13
13
|
//#region src/cli.ts
|
|
14
14
|
runServer().catch((error) => {
|
|
15
15
|
console.error("Fatal error in main():", error);
|
package/dist/index.mjs
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
(function() {
|
|
5
5
|
try {
|
|
6
6
|
var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
|
|
7
|
-
e.SENTRY_RELEASE = { id: "hevy-mcp@1.
|
|
7
|
+
e.SENTRY_RELEASE = { id: "hevy-mcp@1.26.0" };
|
|
8
8
|
var n = new e.Error().stack;
|
|
9
9
|
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "5b077b94-9ef8-4f24-a0b0-8dcfbf491be1", e._sentryDebugIdIdentifier = "sentry-dbid-5b077b94-9ef8-4f24-a0b0-8dcfbf491be1");
|
|
10
10
|
} catch (e) {}
|
|
11
11
|
})();
|
|
12
|
-
import { n as createServer, r as runServer, t as configSchema } from "./src-
|
|
12
|
+
import { n as createServer, r as runServer, t as configSchema } from "./src-Di7keTQA.mjs";
|
|
13
13
|
export { configSchema, createServer, createServer as default, runServer };
|
|
@@ -4,18 +4,125 @@
|
|
|
4
4
|
(function() {
|
|
5
5
|
try {
|
|
6
6
|
var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
|
|
7
|
-
e.SENTRY_RELEASE = { id: "hevy-mcp@1.
|
|
7
|
+
e.SENTRY_RELEASE = { id: "hevy-mcp@1.26.0" };
|
|
8
8
|
var n = new e.Error().stack;
|
|
9
|
-
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "
|
|
9
|
+
n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "b6fef1cd-aaf1-497b-af9e-7d50fa405061", e._sentryDebugIdIdentifier = "sentry-dbid-b6fef1cd-aaf1-497b-af9e-7d50fa405061");
|
|
10
10
|
} catch (e) {}
|
|
11
11
|
})();
|
|
12
12
|
import * as Sentry from "@sentry/node";
|
|
13
|
+
import { SentryPropagator, SentrySampler, SentrySpanProcessor } from "@sentry/opentelemetry";
|
|
14
|
+
import { SpanStatusCode, metrics, trace } from "@opentelemetry/api";
|
|
15
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
16
|
+
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
|
|
17
|
+
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
18
|
+
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
19
|
+
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
|
20
|
+
import { MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
|
13
21
|
import { createHmac } from "node:crypto";
|
|
14
22
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
15
23
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
16
24
|
import { z } from "zod";
|
|
17
25
|
import axios, { isAxiosError } from "axios";
|
|
18
26
|
import { deserializeMessage } from "@modelcontextprotocol/sdk/shared/stdio.js";
|
|
27
|
+
//#region src/utils/telemetry.ts
|
|
28
|
+
/**
|
|
29
|
+
* Centralized telemetry initialization.
|
|
30
|
+
*
|
|
31
|
+
* This module MUST be imported before any other application code.
|
|
32
|
+
* It sets up OpenTelemetry with dual export: Sentry (error events +
|
|
33
|
+
* traces) and an OTel Collector (traces + metrics to Honeycomb).
|
|
34
|
+
*
|
|
35
|
+
* Sentry SDK: error events, performance traces, release tracking
|
|
36
|
+
* OTel Collector → Honeycomb: performance traces, metrics
|
|
37
|
+
*/
|
|
38
|
+
const name$1 = "hevy-mcp";
|
|
39
|
+
const version$1 = "1.26.0";
|
|
40
|
+
const collectorToken = process.env.OTEL_COLLECTOR_TOKEN ?? "";
|
|
41
|
+
const COLLECTOR_ENDPOINT = "https://otel.chrisdoc.dev/v1";
|
|
42
|
+
const sentryRelease = process.env.SENTRY_RELEASE ?? `hevy-mcp@1.26.0`;
|
|
43
|
+
const resource = resourceFromAttributes({
|
|
44
|
+
"service.name": name$1,
|
|
45
|
+
"service.version": version$1
|
|
46
|
+
});
|
|
47
|
+
const rawDsn = process.env.SENTRY_DSN ?? "https://ce696d8333b507acbf5203eb877bce0f@o4508975499575296.ingest.de.sentry.io/4509049671647312";
|
|
48
|
+
const isValidDsn = typeof rawDsn === "string" && rawDsn.length > 0 && !rawDsn.startsWith("*");
|
|
49
|
+
const sentryClient = Sentry.init({
|
|
50
|
+
dsn: isValidDsn ? rawDsn : void 0,
|
|
51
|
+
release: sentryRelease,
|
|
52
|
+
tracesSampleRate: 1,
|
|
53
|
+
sendDefaultPii: false,
|
|
54
|
+
skipOpenTelemetrySetup: true,
|
|
55
|
+
registerEsmLoaderHooks: false,
|
|
56
|
+
ignoreErrors: ["EPIPE", "broken pipe"]
|
|
57
|
+
});
|
|
58
|
+
const spanProcessors = [new SentrySpanProcessor()];
|
|
59
|
+
if (collectorToken) spanProcessors.push(new BatchSpanProcessor(new OTLPTraceExporter({
|
|
60
|
+
url: `${COLLECTOR_ENDPOINT}/traces`,
|
|
61
|
+
headers: { Authorization: `Bearer ${collectorToken}` }
|
|
62
|
+
})));
|
|
63
|
+
const tracerProvider = new NodeTracerProvider({
|
|
64
|
+
resource,
|
|
65
|
+
sampler: sentryClient ? new SentrySampler(sentryClient) : void 0,
|
|
66
|
+
spanProcessors
|
|
67
|
+
});
|
|
68
|
+
tracerProvider.register({
|
|
69
|
+
propagator: new SentryPropagator(),
|
|
70
|
+
contextManager: new Sentry.SentryContextManager()
|
|
71
|
+
});
|
|
72
|
+
if (collectorToken) {
|
|
73
|
+
const meterProvider = new MeterProvider({
|
|
74
|
+
resource,
|
|
75
|
+
readers: [new PeriodicExportingMetricReader({
|
|
76
|
+
exporter: new OTLPMetricExporter({
|
|
77
|
+
url: `${COLLECTOR_ENDPOINT}/metrics`,
|
|
78
|
+
headers: { Authorization: `Bearer ${collectorToken}` }
|
|
79
|
+
}),
|
|
80
|
+
exportIntervalMillis: 1e4
|
|
81
|
+
})]
|
|
82
|
+
});
|
|
83
|
+
metrics.setGlobalMeterProvider(meterProvider);
|
|
84
|
+
}
|
|
85
|
+
trace.setGlobalTracerProvider(tracerProvider);
|
|
86
|
+
Sentry.validateOpenTelemetrySetup();
|
|
87
|
+
const tracer = trace.getTracer(name$1);
|
|
88
|
+
const meter = metrics.getMeter(name$1);
|
|
89
|
+
let currentUserId;
|
|
90
|
+
function setCurrentUserId(id) {
|
|
91
|
+
currentUserId = id;
|
|
92
|
+
}
|
|
93
|
+
function getCurrentUserId() {
|
|
94
|
+
return currentUserId;
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/utils/metrics.ts
|
|
98
|
+
/**
|
|
99
|
+
* Metric instruments for Honeycomb.
|
|
100
|
+
*
|
|
101
|
+
* These are created once from the shared meter provider and exported
|
|
102
|
+
* for use throughout the codebase. All instruments are sent to Honeycomb
|
|
103
|
+
* via the OTLP metrics exporter configured in telemetry.ts.
|
|
104
|
+
*/
|
|
105
|
+
/** Total MCP tool invocations, grouped by tool name. */
|
|
106
|
+
const toolInvocations = meter.createCounter("mcp.tool.invocations", { description: "Total MCP tool invocations" });
|
|
107
|
+
/** Total MCP tool errors, grouped by tool name and error type. */
|
|
108
|
+
const toolErrors = meter.createCounter("mcp.tool.errors", { description: "Total MCP tool errors" });
|
|
109
|
+
/** MCP tool execution duration in milliseconds. */
|
|
110
|
+
const toolDuration = meter.createHistogram("mcp.tool.duration_ms", {
|
|
111
|
+
description: "MCP tool execution duration in milliseconds",
|
|
112
|
+
unit: "ms"
|
|
113
|
+
});
|
|
114
|
+
/** Total Hevy API calls, grouped by method, endpoint, and status code. */
|
|
115
|
+
const apiCalls = meter.createCounter("hevy.api.calls", { description: "Total Hevy API calls" });
|
|
116
|
+
/** Hevy API response time in milliseconds. */
|
|
117
|
+
const apiDuration = meter.createHistogram("hevy.api.duration_ms", {
|
|
118
|
+
description: "Hevy API response time in milliseconds",
|
|
119
|
+
unit: "ms"
|
|
120
|
+
});
|
|
121
|
+
/** Total stdio JSON parse errors, grouped by failure location. */
|
|
122
|
+
const stdioParseErrors = meter.createCounter("mcp.stdio.parse_errors", { description: "Total stdio JSON parse errors" });
|
|
123
|
+
/** Total server startup count, grouped by version. */
|
|
124
|
+
const serverStartups = meter.createCounter("mcp.server.startups", { description: "Total server startup count" });
|
|
125
|
+
//#endregion
|
|
19
126
|
//#region src/utils/error-handler.ts
|
|
20
127
|
/**
|
|
21
128
|
* Centralized error handling utility for MCP tools
|
|
@@ -77,23 +184,26 @@ function determineErrorType(error, message) {
|
|
|
77
184
|
function withErrorHandling(fn, context) {
|
|
78
185
|
return async (args) => {
|
|
79
186
|
const argumentKeyCount = Object.keys(args).length;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}, async (span) => {
|
|
187
|
+
const startTime = Date.now();
|
|
188
|
+
toolInvocations.add(1, { tool_name: context });
|
|
189
|
+
return tracer.startActiveSpan(`mcp.tool.${context}`, { attributes: {
|
|
190
|
+
"mcp.tool.name": context,
|
|
191
|
+
"mcp.tool.args.key_count": argumentKeyCount,
|
|
192
|
+
...getCurrentUserId() ? { "user.id": getCurrentUserId() } : {}
|
|
193
|
+
} }, async (span) => {
|
|
88
194
|
try {
|
|
89
195
|
const result = await fn(args);
|
|
90
|
-
span.setStatus({ code:
|
|
196
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
91
197
|
span.setAttribute("mcp.tool.result.is_error", Boolean(result.isError));
|
|
92
198
|
return result;
|
|
93
199
|
} catch (error) {
|
|
94
|
-
span.setStatus({
|
|
95
|
-
|
|
96
|
-
|
|
200
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
201
|
+
span.recordException(error);
|
|
202
|
+
const errorType = determineErrorType(error, error instanceof Error ? error.message : String(error));
|
|
203
|
+
span.setAttribute("error.type", errorType);
|
|
204
|
+
toolErrors.add(1, {
|
|
205
|
+
tool_name: context,
|
|
206
|
+
error_type: errorType
|
|
97
207
|
});
|
|
98
208
|
Sentry.withScope((scope) => {
|
|
99
209
|
scope.setTag("mcp.tool.context", context);
|
|
@@ -104,6 +214,9 @@ function withErrorHandling(fn, context) {
|
|
|
104
214
|
Sentry.captureException(error);
|
|
105
215
|
});
|
|
106
216
|
return createErrorResponse(error, context);
|
|
217
|
+
} finally {
|
|
218
|
+
toolDuration.record(Date.now() - startTime, { tool_name: context });
|
|
219
|
+
span.end();
|
|
107
220
|
}
|
|
108
221
|
});
|
|
109
222
|
};
|
|
@@ -1681,11 +1794,98 @@ async function putV1WorkoutsWorkoutid(workoutId, data, headers, config = {}) {
|
|
|
1681
1794
|
function wrapApi(fn) {
|
|
1682
1795
|
return fn;
|
|
1683
1796
|
}
|
|
1797
|
+
/**
|
|
1798
|
+
* Extract common request metadata from an Axios config.
|
|
1799
|
+
* Shared between the success and error response interceptors to avoid
|
|
1800
|
+
* duplicating the method/url/endpoint/duration extraction logic.
|
|
1801
|
+
*/
|
|
1802
|
+
function extractRequestMeta(config) {
|
|
1803
|
+
const method = (config.method ?? "get").toUpperCase();
|
|
1804
|
+
const url = config.url ?? "";
|
|
1805
|
+
const endpoint = url.split("?")[0] ?? url;
|
|
1806
|
+
const now = Date.now();
|
|
1807
|
+
return {
|
|
1808
|
+
method,
|
|
1809
|
+
url,
|
|
1810
|
+
endpoint,
|
|
1811
|
+
durationMs: now - (config._startTime ?? now)
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1814
|
+
/**
|
|
1815
|
+
* Finalize a traced span and record metrics for a completed HTTP request.
|
|
1816
|
+
*/
|
|
1817
|
+
function finalizeRequestTrace(opts) {
|
|
1818
|
+
const { span, statusCode, durationMs, method, endpoint, error } = opts;
|
|
1819
|
+
if (span) {
|
|
1820
|
+
span.setAttribute("http.status_code", statusCode);
|
|
1821
|
+
span.setAttribute("http.response.duration_ms", durationMs);
|
|
1822
|
+
if (error) {
|
|
1823
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
1824
|
+
span.recordException(error);
|
|
1825
|
+
} else span.setStatus({ code: SpanStatusCode.OK });
|
|
1826
|
+
span.end();
|
|
1827
|
+
}
|
|
1828
|
+
apiCalls.add(1, {
|
|
1829
|
+
method,
|
|
1830
|
+
endpoint,
|
|
1831
|
+
status_code: statusCode
|
|
1832
|
+
});
|
|
1833
|
+
apiDuration.record(durationMs, {
|
|
1834
|
+
method,
|
|
1835
|
+
endpoint
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1684
1838
|
function createClient$1(apiKey, baseUrl = "https://api.hevyapp.com") {
|
|
1685
1839
|
const axiosInstance = axios.create({
|
|
1686
1840
|
baseURL: baseUrl,
|
|
1687
1841
|
headers: { "api-key": apiKey }
|
|
1688
1842
|
});
|
|
1843
|
+
axiosInstance.interceptors.request.use((config) => {
|
|
1844
|
+
const tracedConfig = config;
|
|
1845
|
+
const method = (config.method ?? "get").toUpperCase();
|
|
1846
|
+
const url = config.url ?? "";
|
|
1847
|
+
const endpoint = url.split("?")[0] ?? url;
|
|
1848
|
+
tracedConfig._span = tracer.startSpan(`hevy.api.${method}`, { attributes: {
|
|
1849
|
+
"http.method": method,
|
|
1850
|
+
"http.url": url,
|
|
1851
|
+
"http.base_url": config.baseURL ?? "",
|
|
1852
|
+
"hevy.api.endpoint": endpoint
|
|
1853
|
+
} });
|
|
1854
|
+
tracedConfig._startTime = Date.now();
|
|
1855
|
+
return config;
|
|
1856
|
+
});
|
|
1857
|
+
axiosInstance.interceptors.response.use((response) => {
|
|
1858
|
+
const tracedConfig = response.config;
|
|
1859
|
+
const { method, endpoint, durationMs } = extractRequestMeta({
|
|
1860
|
+
method: response.config.method,
|
|
1861
|
+
url: response.config.url,
|
|
1862
|
+
_startTime: tracedConfig._startTime
|
|
1863
|
+
});
|
|
1864
|
+
finalizeRequestTrace({
|
|
1865
|
+
span: tracedConfig._span,
|
|
1866
|
+
statusCode: response.status,
|
|
1867
|
+
durationMs,
|
|
1868
|
+
method,
|
|
1869
|
+
endpoint
|
|
1870
|
+
});
|
|
1871
|
+
return response;
|
|
1872
|
+
}, (error) => {
|
|
1873
|
+
const tracedConfig = error.config ?? {};
|
|
1874
|
+
const { method, endpoint, durationMs } = extractRequestMeta({
|
|
1875
|
+
method: error.config?.method,
|
|
1876
|
+
url: error.config?.url,
|
|
1877
|
+
_startTime: tracedConfig._startTime
|
|
1878
|
+
});
|
|
1879
|
+
finalizeRequestTrace({
|
|
1880
|
+
span: tracedConfig._span,
|
|
1881
|
+
statusCode: error.response?.status ?? 0,
|
|
1882
|
+
durationMs,
|
|
1883
|
+
method,
|
|
1884
|
+
endpoint,
|
|
1885
|
+
error
|
|
1886
|
+
});
|
|
1887
|
+
throw error;
|
|
1888
|
+
});
|
|
1689
1889
|
const headers = { "api-key": apiKey };
|
|
1690
1890
|
const client = axiosInstance;
|
|
1691
1891
|
return {
|
|
@@ -1741,32 +1941,28 @@ function deserializeMessageWithObservability(line, chunkSnapshot) {
|
|
|
1741
1941
|
const lineHadLeadingBom = line.startsWith(UTF8_BOM);
|
|
1742
1942
|
const normalizedLine = lineHadLeadingBom ? line.slice(1) : line;
|
|
1743
1943
|
const lineByteLength = Buffer.byteLength(line);
|
|
1744
|
-
return
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
"mcp.stdio.parse.chunk.last_byte_length": chunkSnapshot.lastChunkByteLength,
|
|
1754
|
-
"mcp.stdio.parse.chunk.last_had_utf8_bom": chunkSnapshot.lastChunkStartsWithUtf8Bom
|
|
1755
|
-
}
|
|
1756
|
-
}, (span) => {
|
|
1944
|
+
return tracer.startActiveSpan("mcp.stdio.deserialize", { attributes: {
|
|
1945
|
+
"mcp.transport": "stdio",
|
|
1946
|
+
"mcp.stdio.parse.line.char_length": line.length,
|
|
1947
|
+
"mcp.stdio.parse.line.byte_length": lineByteLength,
|
|
1948
|
+
"mcp.stdio.parse.line.had_leading_bom": lineHadLeadingBom,
|
|
1949
|
+
"mcp.stdio.parse.line.bom_stripped": lineHadLeadingBom,
|
|
1950
|
+
"mcp.stdio.parse.chunk.last_byte_length": chunkSnapshot.lastChunkByteLength,
|
|
1951
|
+
"mcp.stdio.parse.chunk.last_had_utf8_bom": chunkSnapshot.lastChunkStartsWithUtf8Bom
|
|
1952
|
+
} }, (span) => {
|
|
1757
1953
|
try {
|
|
1758
1954
|
const message = deserializeMessage(normalizedLine);
|
|
1759
|
-
span.setStatus({ code:
|
|
1955
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
1956
|
+
if (message && typeof message === "object" && "method" in message) span.setAttribute("mcp.method", String(message.method));
|
|
1760
1957
|
return message;
|
|
1761
1958
|
} catch (error) {
|
|
1762
1959
|
const failurePosition = parseFailurePosition(error);
|
|
1763
1960
|
const failureLocation = getFailureLocation(failurePosition, lineHadLeadingBom);
|
|
1764
|
-
span.setStatus({
|
|
1765
|
-
|
|
1766
|
-
message: "invalid_json"
|
|
1767
|
-
});
|
|
1961
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
1962
|
+
span.recordException(error);
|
|
1768
1963
|
span.setAttribute("mcp.stdio.parse.failure.location", failureLocation);
|
|
1769
1964
|
if (failurePosition !== null) span.setAttribute("mcp.stdio.parse.failure.position", failurePosition);
|
|
1965
|
+
stdioParseErrors.add(1, { failure_location: failureLocation });
|
|
1770
1966
|
Sentry.withScope((scope) => {
|
|
1771
1967
|
scope.setTag("mcp.transport", "stdio");
|
|
1772
1968
|
scope.setTag("mcp.stdio.parse.failure.location", failureLocation);
|
|
@@ -1785,6 +1981,8 @@ function deserializeMessageWithObservability(line, chunkSnapshot) {
|
|
|
1785
1981
|
Sentry.captureException(error);
|
|
1786
1982
|
});
|
|
1787
1983
|
throw error;
|
|
1984
|
+
} finally {
|
|
1985
|
+
span.end();
|
|
1788
1986
|
}
|
|
1789
1987
|
});
|
|
1790
1988
|
}
|
|
@@ -1817,15 +2015,8 @@ function createInstrumentedStdioTransport(transport) {
|
|
|
1817
2015
|
}
|
|
1818
2016
|
//#endregion
|
|
1819
2017
|
//#region src/index.ts
|
|
1820
|
-
const name =
|
|
1821
|
-
const version =
|
|
1822
|
-
const sentryConfig = {
|
|
1823
|
-
dsn: "https://ce696d8333b507acbf5203eb877bce0f@o4508975499575296.ingest.de.sentry.io/4509049671647312",
|
|
1824
|
-
release: process.env.SENTRY_RELEASE ?? `${name}@${version}`,
|
|
1825
|
-
tracesSampleRate: 1,
|
|
1826
|
-
sendDefaultPii: false
|
|
1827
|
-
};
|
|
1828
|
-
Sentry.init(sentryConfig);
|
|
2018
|
+
const name = name$1;
|
|
2019
|
+
const version = version$1;
|
|
1829
2020
|
const HEVY_API_BASEURL = "https://api.hevyapp.com";
|
|
1830
2021
|
const SENTRY_USER_ID_CONTEXT = "hevy-mcp:sentry-user-id:v1";
|
|
1831
2022
|
function fingerprintApiKey(apiKey) {
|
|
@@ -1834,39 +2025,49 @@ function fingerprintApiKey(apiKey) {
|
|
|
1834
2025
|
const serverConfigSchema = z.object({ apiKey: z.string().min(1, "Hevy API key is required").describe("Your Hevy API key (available in the Hevy app settings).") });
|
|
1835
2026
|
const configSchema = serverConfigSchema;
|
|
1836
2027
|
function buildServer(apiKey) {
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
2028
|
+
const userId = fingerprintApiKey(apiKey);
|
|
2029
|
+
return tracer.startActiveSpan("mcp.server.build", { attributes: {
|
|
2030
|
+
"mcp.server.name": name,
|
|
2031
|
+
"mcp.server.version": version,
|
|
2032
|
+
"mcp.transport": "stdio",
|
|
2033
|
+
"user.id": userId
|
|
2034
|
+
} }, (span) => {
|
|
2035
|
+
try {
|
|
2036
|
+
Sentry.setUser({ id: userId });
|
|
2037
|
+
setCurrentUserId(userId);
|
|
2038
|
+
const baseServer = new McpServer({
|
|
2039
|
+
name,
|
|
2040
|
+
version
|
|
2041
|
+
});
|
|
2042
|
+
const server = Sentry.wrapMcpServerWithSentry(baseServer);
|
|
2043
|
+
const hevyClient = tracer.startActiveSpan("mcp.hevy-client.initialize", (childSpan) => {
|
|
2044
|
+
try {
|
|
2045
|
+
return createClient(apiKey, HEVY_API_BASEURL);
|
|
2046
|
+
} finally {
|
|
2047
|
+
childSpan.end();
|
|
2048
|
+
}
|
|
2049
|
+
});
|
|
2050
|
+
console.error("Hevy client initialized with API key");
|
|
2051
|
+
tracer.startActiveSpan("mcp.tools.register", { attributes: { "mcp.tools.count": 6 } }, (toolsSpan) => {
|
|
2052
|
+
try {
|
|
2053
|
+
registerWorkoutTools(server, hevyClient);
|
|
2054
|
+
registerRoutineTools(server, hevyClient);
|
|
2055
|
+
registerTemplateTools(server, hevyClient);
|
|
2056
|
+
registerFolderTools(server, hevyClient);
|
|
2057
|
+
registerBodyMeasurementTools(server, hevyClient);
|
|
2058
|
+
registerUserTools(server, hevyClient);
|
|
2059
|
+
} finally {
|
|
2060
|
+
toolsSpan.end();
|
|
2061
|
+
}
|
|
2062
|
+
});
|
|
2063
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
2064
|
+
return server;
|
|
2065
|
+
} catch (e) {
|
|
2066
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
2067
|
+
throw e;
|
|
2068
|
+
} finally {
|
|
2069
|
+
span.end();
|
|
1844
2070
|
}
|
|
1845
|
-
}, () => {
|
|
1846
|
-
Sentry.setUser({ id: fingerprintApiKey(apiKey) });
|
|
1847
|
-
const baseServer = new McpServer({
|
|
1848
|
-
name,
|
|
1849
|
-
version
|
|
1850
|
-
});
|
|
1851
|
-
const server = Sentry.wrapMcpServerWithSentry(baseServer);
|
|
1852
|
-
const hevyClient = Sentry.startSpan({
|
|
1853
|
-
name: "mcp.hevy-client.initialize",
|
|
1854
|
-
op: "mcp.lifecycle.client.init"
|
|
1855
|
-
}, () => createClient(apiKey, HEVY_API_BASEURL));
|
|
1856
|
-
console.error("Hevy client initialized with API key");
|
|
1857
|
-
Sentry.startSpan({
|
|
1858
|
-
name: "mcp.tools.register",
|
|
1859
|
-
op: "mcp.lifecycle.tools.register",
|
|
1860
|
-
attributes: { "mcp.tools.count": 6 }
|
|
1861
|
-
}, () => {
|
|
1862
|
-
registerWorkoutTools(server, hevyClient);
|
|
1863
|
-
registerRoutineTools(server, hevyClient);
|
|
1864
|
-
registerTemplateTools(server, hevyClient);
|
|
1865
|
-
registerFolderTools(server, hevyClient);
|
|
1866
|
-
registerBodyMeasurementTools(server, hevyClient);
|
|
1867
|
-
registerUserTools(server, hevyClient);
|
|
1868
|
-
});
|
|
1869
|
-
return server;
|
|
1870
2071
|
});
|
|
1871
2072
|
}
|
|
1872
2073
|
function createServer({ config }) {
|
|
@@ -1874,26 +2075,35 @@ function createServer({ config }) {
|
|
|
1874
2075
|
return buildServer(apiKey);
|
|
1875
2076
|
}
|
|
1876
2077
|
async function runServer() {
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
2078
|
+
serverStartups.add(1, { version });
|
|
2079
|
+
await tracer.startActiveSpan("mcp.server.run", { attributes: { "mcp.transport": "stdio" } }, async (span) => {
|
|
2080
|
+
try {
|
|
2081
|
+
const apiKey = parseConfig(process.argv.slice(2), process.env).apiKey;
|
|
2082
|
+
assertApiKey(apiKey);
|
|
2083
|
+
const server = buildServer(apiKey);
|
|
2084
|
+
console.error("Starting MCP server in stdio mode");
|
|
2085
|
+
const transport = createInstrumentedStdioTransport(new StdioServerTransport());
|
|
2086
|
+
await tracer.startActiveSpan("mcp.server.connect", { attributes: { "mcp.transport": "stdio" } }, async (connectSpan) => {
|
|
2087
|
+
try {
|
|
2088
|
+
await server.connect(transport);
|
|
2089
|
+
connectSpan.setStatus({ code: SpanStatusCode.OK });
|
|
2090
|
+
} catch (e) {
|
|
2091
|
+
connectSpan.setStatus({ code: SpanStatusCode.ERROR });
|
|
2092
|
+
throw e;
|
|
2093
|
+
} finally {
|
|
2094
|
+
connectSpan.end();
|
|
2095
|
+
}
|
|
2096
|
+
});
|
|
2097
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
2098
|
+
} catch (e) {
|
|
2099
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
2100
|
+
throw e;
|
|
2101
|
+
} finally {
|
|
2102
|
+
span.end();
|
|
2103
|
+
}
|
|
1894
2104
|
});
|
|
1895
2105
|
}
|
|
1896
2106
|
//#endregion
|
|
1897
2107
|
export { createServer as n, runServer as r, configSchema as t };
|
|
1898
2108
|
|
|
1899
|
-
//# sourceMappingURL=src-
|
|
2109
|
+
//# sourceMappingURL=src-Di7keTQA.mjs.map
|