Skip to content

feat: add body measurements MCP tools - #306

Merged
chrisdoc merged 2 commits into
mainfrom
feat/body-measurements-tool
Apr 23, 2026
Merged

feat: add body measurements MCP tools#306
chrisdoc merged 2 commits into
mainfrom
feat/body-measurements-tool

Conversation

@chrisdoc

@chrisdoc chrisdoc commented Apr 23, 2026

Copy link
Copy Markdown
Owner

Add four new MCP tools for managing body measurements via the Hevy API.

New Tools

  • get-body-measurements — Paginated list of body measurements
  • get-body-measurement — Single measurement by date (YYYY-MM-DD)
  • create-body-measurement — Create entry for a date (409 if already exists)
  • update-body-measurement — Overwrite entry for a date (404 if not found)

Changes

  • src/tools/body-measurements.ts — Tool implementations with Zod schemas and type-safe handlers
  • src/tools/body-measurements.test.ts — 7 unit tests covering all tools
  • src/utils/hevyClientKubb.ts — Added body measurement client methods
  • src/utils/formatters.ts — Added formatBodyMeasurement formatter
  • src/index.ts — Registered registerBodyMeasurementTools

✨ PR Description

Purpose: Implement body measurements MCP tools to enable creating, updating, and retrieving user body measurements via the Hevy API.

Main changes:

  • Added registerBodyMeasurementTools with four tools: get-body-measurements, get-body-measurement, create-body-measurement, update-body-measurement
  • Implemented formatBodyMeasurement formatter converting snake_case API fields to camelCase representation
  • Extended HevyClient with body measurement methods wrapping API endpoints and query parameter types

Generated 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

@coderabbitai

coderabbitai Bot commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@chrisdoc has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 20 minutes and 3 seconds before requesting another review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 809ba371-f23c-4e83-8294-86c9adccae89

📥 Commits

Reviewing files that changed from the base of the PR and between 2c0ab9d and a7f1a31.

📒 Files selected for processing (5)
  • src/index.ts
  • src/tools/body-measurements.test.ts
  • src/tools/body-measurements.ts
  • src/utils/formatters.ts
  • src/utils/hevyClientKubb.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/body-measurements-tool

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sentry

sentry Bot commented Apr 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 69.73684% with 23 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.34%. Comparing base (0bfad15) to head (a7f1a31).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/utils/formatters.ts 5.55% 0 Missing and 17 partials ⚠️
src/utils/hevyClientKubb.ts 0.00% 4 Missing ⚠️
src/tools/body-measurements.ts 96.22% 0 Missing and 2 partials ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@gitstream-cm gitstream-cm Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✨ 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-apps

greptile-apps Bot commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds four MCP tools (get-body-measurements, get-body-measurement, create-body-measurement, update-body-measurement) that proxy the Hevy body measurements API, following the same patterns used by the existing workout, routine, and template tools. All findings are P2 style/quality suggestions.

Confidence Score: 5/5

Safe 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

Filename Overview
src/tools/body-measurements.ts New file implementing 4 MCP tools for body measurements; minor issues with z.coerce number coercion, date regex not validating calendar validity, and pagination metadata not included in list response
src/tools/body-measurements.test.ts 7 unit tests covering all 4 tools including null client, empty results, and payload mapping; good coverage of happy paths and edge cases
src/utils/formatters.ts Adds formatBodyMeasurement and FormattedBodyMeasurement; interface unnecessarily widens field types to number
src/utils/hevyClientKubb.ts Adds 4 body measurement client methods following the existing wrapApi pattern with correct parameter ordering
src/index.ts Registers registerBodyMeasurementTools in buildServer, consistent with other tool registrations

Sequence Diagram

sequenceDiagram
    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.
Loading

Reviews (1): Last reviewed commit: "feat: add body measurements MCP tools" | Re-trigger Greptile

Comment thread src/tools/body-measurements.ts Outdated
typeof import("../utils/hevyClientKubb.js").createClient
>;

const zNullableNumber = z.coerce.number().nullable().optional();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Suggested change
const zNullableNumber = z.coerce.number().nullable().optional();
const zNullableNumber = z.number().nullable().optional();

Comment thread src/utils/formatters.ts
Comment on lines +298 to +317
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;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Comment on lines +144 to +147
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)"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Comment on lines +127 to +138
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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/index.ts
registerRoutineTools(server, hevyClient);
registerTemplateTools(server, hevyClient);
registerFolderTools(server, hevyClient);
registerBodyMeasurementTools(server, hevyClient);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Comment on lines +115 to +118
if (!hevyClient) {
throw new Error(
"API client not initialized. Please provide HEVY_API_KEY.",
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The check for hevyClient initialization is repeated in every tool handler. Consider abstracting this into a helper function or incorporating it into the withErrorHandling wrapper to reduce boilerplate and improve maintainability.

- 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
@chrisdoc
chrisdoc merged commit 78b3a5b into main Apr 23, 2026
18 checks passed
github-actions Bot pushed a commit that referenced this pull request Apr 23, 2026
# [1.23.0](v1.22.0...v1.23.0) (2026-04-23)

### Features

* add body measurements MCP tools ([#306](#306)) ([78b3a5b](78b3a5b))
aaearon added a commit to aaearon/hevy-mcp that referenced this pull request May 9, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant