feat: add body measurements MCP tools - #306
Conversation
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 20 minutes and 3 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #306 +/- ##
==========================================
+ Coverage 69.29% 69.34% +0.05%
==========================================
Files 14 15 +1
Lines 508 584 +76
Branches 157 197 +40
==========================================
+ Hits 352 405 +53
- Misses 97 101 +4
- Partials 59 78 +19 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
✨ PR Review
LGTM
Generated by LinearB AI and added by gitStream.
AI-generated content may contain inaccuracies. Please verify before using.
💡 Tip: You can customize your AI Review using Guidelines Learn how
Greptile SummaryThis PR adds four MCP tools ( Confidence Score: 5/5Safe to merge; all findings are non-blocking P2 style/quality suggestions. The implementation is well-structured, follows existing codebase patterns precisely, has good test coverage for all four tools, and the Kubb client wrappers use correct parameter ordering. All flagged issues (coerce behaviour, type widening, date regex, dropped pagination metadata) are quality improvements rather than correctness bugs. src/tools/body-measurements.ts — z.coerce number coercion edge case and missing pagination metadata in list response Important Files Changed
Sequence DiagramsequenceDiagram
participant LLM as MCP Client (LLM)
participant Server as MCP Server
participant Client as hevyClientKubb
participant API as Hevy API
LLM->>Server: get-body-measurements(page, pageSize)
Server->>Client: getBodyMeasurements({page, pageSize})
Client->>API: GET /v1/body-measurements
API-->>Client: {page, page_count, body_measurements[]}
Client-->>Server: GetV1BodyMeasurements200
Server-->>LLM: JSON array of FormattedBodyMeasurement
LLM->>Server: get-body-measurement(date)
Server->>Client: getBodyMeasurement(date)
Client->>API: GET /v1/body-measurements/{date}
API-->>Client: BodyMeasurement | 404
Client-->>Server: GetV1BodyMeasurementsDate200
Server-->>LLM: JSON FormattedBodyMeasurement
LLM->>Server: create-body-measurement(date, ...fields)
Server->>Client: createBodyMeasurement({date, ...payload})
Client->>API: POST /v1/body-measurements
API-->>Client: 200 | 409 Conflict
Client-->>Server: void
Server-->>LLM: Body measurement for {date} created successfully.
LLM->>Server: update-body-measurement(date, ...fields)
Server->>Client: updateBodyMeasurement(date, payload)
Client->>API: PUT /v1/body-measurements/{date}
API-->>Client: 200 | 404 Not Found
Client-->>Server: void
Server-->>LLM: Body measurement for {date} updated successfully.
Reviews (1): Last reviewed commit: "feat: add body measurements MCP tools" | Re-trigger Greptile |
| typeof import("../utils/hevyClientKubb.js").createClient | ||
| >; | ||
|
|
||
| const zNullableNumber = z.coerce.number().nullable().optional(); |
There was a problem hiding this comment.
z.coerce.number() turns empty string into 0
z.coerce.number() uses Number("") which evaluates to 0, not null. If an LLM or client passes "" for any measurement field (e.g., weightKg: ""), it will silently be stored as 0 rather than null. Prefer z.number() without coercion, or pre-process/strip empty strings, to ensure callers can't accidentally overwrite a measurement with zero.
| const zNullableNumber = z.coerce.number().nullable().optional(); | |
| const zNullableNumber = z.number().nullable().optional(); |
| export interface FormattedBodyMeasurement { | ||
| date: string; | ||
| weightKg: number | undefined | null; | ||
| leanMassKg: number | undefined | null; | ||
| fatPercent: number | undefined | null; | ||
| neckCm: number | undefined | null; | ||
| shoulderCm: number | undefined | null; | ||
| chestCm: number | undefined | null; | ||
| leftBicepCm: number | undefined | null; | ||
| rightBicepCm: number | undefined | null; | ||
| leftForearmCm: number | undefined | null; | ||
| rightForearmCm: number | undefined | null; | ||
| abdomen: number | undefined | null; | ||
| waist: number | undefined | null; | ||
| hips: number | undefined | null; | ||
| leftThigh: number | undefined | null; | ||
| rightThigh: number | undefined | null; | ||
| leftCalf: number | undefined | null; | ||
| rightCalf: number | undefined | null; | ||
| } |
There was a problem hiding this comment.
number | undefined | null union is unnecessarily wide
The source BodyMeasurement type declares all optional fields as number | null. The FormattedBodyMeasurement interface widens them to number | undefined | null, adding undefined unnecessarily. Callers of formatBodyMeasurement now need to handle a three-way union for every field. Using number | null throughout keeps the contract cleaner and consistent with the source type.
| date: z | ||
| .string() | ||
| .regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format") | ||
| .describe("The date of the body measurement (YYYY-MM-DD)"), |
There was a problem hiding this comment.
Regex validates format but not calendar validity
The pattern /^\d{4}-\d{2}-\d{2}$/ accepts strings like 2025-13-45 or 2025-02-30, which are syntactically valid but semantically invalid dates. The API will likely reject them with a 400, but the error message won't be as clear as a Zod validation failure. Consider adding a .refine check or using z.coerce.date() with a transform back to a string. The same pattern appears on lines 180 and 216–217 as well.
| const measurements = | ||
| data?.body_measurements?.map((measurement: BodyMeasurement) => | ||
| formatBodyMeasurement(measurement), | ||
| ) || []; | ||
|
|
||
| if (measurements.length === 0) { | ||
| return createEmptyResponse( | ||
| "No body measurements found for the specified parameters", | ||
| ); | ||
| } | ||
|
|
||
| return createJsonResponse(measurements); |
There was a problem hiding this comment.
Pagination metadata (
page, page_count) is dropped from the response
GetV1BodyMeasurements200 includes page and page_count fields alongside body_measurements, but only the measurements array is returned to the caller. Without page_count, a client cannot determine whether there are additional pages to fetch. Consider including the pagination fields in the JSON response, consistent with how templates.ts exposes page_count for its own paginated fetches.
There was a problem hiding this comment.
Code Review
This pull request introduces body measurement tools to the Hevy MCP server, enabling users to retrieve, create, and update measurement data through new tool registrations, unit tests, and API client extensions. The implementation is generally sound, but feedback points out a critical runtime issue where the incorrect client version is imported in the main entry point. Additionally, there is an opportunity to improve maintainability by abstracting redundant API client initialization checks across the new tool handlers.
| registerRoutineTools(server, hevyClient); | ||
| registerTemplateTools(server, hevyClient); | ||
| registerFolderTools(server, hevyClient); | ||
| registerBodyMeasurementTools(server, hevyClient); |
There was a problem hiding this comment.
The hevyClient passed here is imported from ./utils/hevyClient.js (line 51), but the new body measurement methods were added to ./utils/hevyClientKubb.ts. You should update the import in src/index.ts to use the Kubb client, otherwise these tools will fail at runtime as the methods will be missing from the client instance.
| if (!hevyClient) { | ||
| throw new Error( | ||
| "API client not initialized. Please provide HEVY_API_KEY.", | ||
| ); |
- Replace z.coerce.number() with z.number() to prevent empty string → 0 - Tighten FormattedBodyMeasurement types from number | undefined | null to number | null - Coalesce undefined to null in formatBodyMeasurement
# [1.23.0](v1.22.0...v1.23.0) (2026-04-23) ### Features * add body measurements MCP tools ([#306](#306)) ([78b3a5b](78b3a5b))
Brings in body-measurements MCP tools (chrisdoc#306) and regenerated Kubb client (chrisdoc#305) from upstream; preserves fork-only Streamable HTTP and OAuth 2.1 transports.
Add four new MCP tools for managing body measurements via the Hevy API.
New Tools
Changes
src/tools/body-measurements.ts— Tool implementations with Zod schemas and type-safe handlerssrc/tools/body-measurements.test.ts— 7 unit tests covering all toolssrc/utils/hevyClientKubb.ts— Added body measurement client methodssrc/utils/formatters.ts— AddedformatBodyMeasurementformattersrc/index.ts— RegisteredregisterBodyMeasurementTools✨ PR Description
Purpose: Implement body measurements MCP tools to enable creating, updating, and retrieving user body measurements via the Hevy API.
Main changes:
registerBodyMeasurementToolswith four tools: get-body-measurements, get-body-measurement, create-body-measurement, update-body-measurementformatBodyMeasurementformatter converting snake_case API fields to camelCase representationGenerated by LinearB AI and added by gitStream.
AI-generated content may contain inaccuracies. Please verify before using.
💡 Tip: You can customize your AI Description using Guidelines Learn how