MCP server for accessing Hevy fitness app data, including exercise catalog, training progress analysis, routine management, and exercise alternatives
Strong tool definitions with excellent descriptions (avg 250+ chars) and clear naming. All 5 tools follow verb_noun pattern (get_*, hevy_ping). Input schemas present with type declarations and parameter descriptions. Output schemas inferred from return types but not explicitly documented in code. Error handling guidance embedded in descriptions (e.g., 'Call get_exercise_catalog first when exact title not known'). Main gaps: no explicit output schema documentation, no enum constraints for categorical inputs, no pagination support despite potential for large result sets.
Finds substitutes for an exercise when its equipment is unavailable - a machine is taken, a rack is busy, the gym does not have it. Use get_routines_for_exercise to identify today's routine unless the user is training outside saved routines or today's routine id is already known. Confirm the routine with the user even if only one matches, unless they have already identified it. Pass the confirmed routine's id as routineId to exclude all its exercises, including those not yet performed. If no routine matches or the user is training outside saved routines, omit routineId and explain that today's plan was not taken into account. Returns exercises that work the same muscle group with DIFFERENT equipment, ordered so the ones the user already trains come first; sessions says how many times each has been logged, and 0 means the user has never done it. The match field says how the list was found: Primary means same primary muscle group, Secondary means the muscle is only worked indirectly and the substitute is weaker, None means nothing suitable exists - say so rather than inventing a replacement. This searches the user's Hevy catalog, not the equipment their gym actually has, so present the results as options to pick from.
Lists the exercises the user has actually performed and logged in Hevy, most frequently trained first, with how many sessions each appears in and when it was last performed. Call this before get_exercise_progress whenever the exact Hevy title of an exercise is not already known - that tool matches titles exactly and fails on a guess. Also useful on its own: "what do I actually train", "what have I not done in a while", "how often do I squat". An empty result means the user has never logged such an exercise, not that the call failed.
Computes a strength trend for one exercise from the user's Hevy training history. Returns the slope of estimated 1RM over time in kg per month, fitted by linear regression across every session containing that exercise, plus the first and last estimated 1RM and how many sessions went into the fit. Two slopes come back: kgPerMonth is fitted over the whole recorded history, while recentKgPerMonth covers only the last three months, with recentSessions saying how many sessions fall in that window. Compare them - a clearly positive kgPerMonth together with a near-zero or negative recentKgPerMonth means the lift has stalled lately even though the long-term trend is up. recentKgPerMonth is null when the window holds fewer than two sessions, which is not an error. These two windows are the only periods measured. Do not describe how the trend behaved at any other point in time - no such breakdown is computed, and asserting one would be a guess. Per session, estimated 1RM is the best working set by the Epley formula; warmup sets and sets with no recorded weight are excluded. Use this for questions about progress, plateaus or stalling - "am I getting stronger at X", "have I stalled on Y", "am I progressing on Z (Weighted)". Prefer it over reading raw workouts: the arithmetic is done here, over the full history, not estimated from a sample. Note that for bodyweight exercises Hevy records only the added weight, so the numbers are relative to the user's bodyweight, not absolute loads. Fails when the exercise name is not in the user's Hevy catalog, or when fewer than two sessions have a recorded weight. If the exact Hevy title is not known, or this call fails with an unknown exercise, call get_exercise_catalog first to find the real title, then retry.
Output schemas not explicitly documented in code. Return types are inferred from method signatures (e.g., AlternativeMatch, ExerciseSummary) but no JSON Schema definitions visible. LLMs cannot reliably plan downstream operations without knowing response field names and types.
No pagination support. get_exercise_catalog and get_exercise_alternatives could return large result sets (entire exercise library, all alternatives). No limit, offset, or cursor parameters. Large responses risk context window exhaustion.
Parameter 'query' in get_exercise_catalog lacks type constraint. Accepts free-form string with no enum, regex pattern, or length limit. Description says 'fragment' but does not specify max length or character restrictions.
Inferred effective spec: <=2025-11-25.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | D | 57 | <=2025-11-25 | v2 |
Lists saved Hevy routines containing an exercise, including each routine's id, title and all its exercises. Use this to identify today's routine when finding substitutes. Ask which routine the user is doing today, even if only one matches, unless the user has already identified or confirmed today's routine in the conversation. An empty list means no saved routine contains this exercise. If no routine matches or the user is training outside saved routines, proceed without routine exclusions and explain that today's plan was not taken into account.
Returns a fixed number. Health check to verify the MCP server is alive.
Error responses rely on McpException with text messages. No structured error classification (retryable vs user-fixable vs fatal). LLMs cannot programmatically determine recovery strategy.
hevy_ping description is minimal (11 words). While functional, it does not explain when to call it or what constitutes a failure. Baseline for A+ tools is 50-200 chars; this is ~50 chars.