Skip to main content
Glama

Contents

Overview  ·  Install  ·  Configuration  ·  Tools  ·  Authentication  ·  Notable findings  ·  Limitations  ·  Architecture  ·  Legal

Related MCP server: WHOOP MCP Server

Overview

mcp-fitbod puts a Fitbod account behind a single stdio MCP server, so any MCP client can work with your training data directly. It spans ten backend services rather than a single endpoint:

Service

What it holds

Tools

nautilus

routines, gyms, equipment, generator config, profile, workout history

36

metros

body composition time series, strength scoring, streaks, percentiles

11

pyserve

the workout generator, including an LLM backed variant

4

coach

AI coaching sessions with persistent memories

5

gympulse

public gym directory and Places search

3

prism

AI chat backend

1

blimp · partnerio · billing

in-app messages, Strava, subscription state

4

gate-keeper

login and per-service token minting

auth layer

The most useful piece is pyserve. Most integrations write routines alongside the app; this one calls the generator itself and gets back a complete session with exercise selection, sets, reps, loads and rest times, then filters the result against the equipment you actually own and requests replacements for anything you cannot perform.

Highlights

  • Every write tool defaults to dry_run=True and returns the exact payload it would send, without touching the account.

  • An offline corpus of roughly 1,400 exercises means catalogue lookups, gym filtering and muscle mapping cost no network calls.

  • build_routine validates every movement against your gym before writing, and deletes the routine on partial failure so nothing is left half-created.

  • Analysis (tonnage, per-muscle volume, estimated 1RM, stalled lifts) lives in pure functions with no network dependency, unit tested against fixtures.

  • Token minting, refresh on 401, rate limiting, pagination and transient challenge retries are all handled inside the client.

Install

Requires Python 3.10 or newer.

git clone https://github.com/Muno459/mcp-fitbod
cd mcp-fitbod
pip install -e ".[dev]"

Authenticate once. The refresh token lasts about a year, and only that token is persisted.

FITBOD_EMAIL='you@example.com' FITBOD_PASSWORD='...' python -m fitbod.auth

/users/login currently sits behind a Cloudflare WAF rule that blocks it outright (see Known limitations), so that command may fail even with correct credentials. Nothing else in the package needs it. Carry a token across from a machine that already has one instead:

python -m fitbod.auth --export            # on the machine that has a token
python -m fitbod.auth --import '<token>'  # on the new machine
python -m fitbod.auth --status            # confirm account and expiry

Build the offline exercise corpus from your own Fitbod install. Extract the APK, then point the builder at its res/raw directory.

python build_reference.py --apk-res /path/to/fitbod/res/raw

Describe your gym so tools can filter to performable exercises.

cp data/gym-profile.example.json data/gym-profile.json
$EDITOR data/gym-profile.json

Verify, then run.

python -m pytest tests/ -q     # offline: parsing and analysis
python smoke_test.py           # live: every tool, writes are dry-run
python server.py

Configuration

Register the server with any MCP client over stdio:

{
  "mcpServers": {
    "fitbod": {
      "command": "python",
      "args": ["/absolute/path/to/mcp-fitbod/server.py"]
    }
  }
}

Variable

Purpose

FITBOD_EMAIL

Used by python -m fitbod.auth only, never stored

FITBOD_PASSWORD

Used by python -m fitbod.auth only, never stored

FITBOD_REFRESH_TOKEN

Supply the refresh token directly instead of the credentials file

Credentials live in .fitbod-credentials.json, owner readable and gitignored. The file holds the refresh token and nothing else. Your password is never written to disk and never logged. To revoke access, change your password.

data/gym-profile.json maps the equipment you own onto Fitbod's 78 equipment types, including the exact dumbbell and plate increments available to you, so prescribed loads are ones you can actually assemble. Start from data/gym-profile.example.json and check your names resolve with preview_inventory_mapping.

Tools

97 tools across twelve modules. Every write tool takes dry_run.

Tool

Description

list_workouts

Logged workouts, newest first

get_workout

Full detail for one workout, every set expanded

exercise_history

Every set logged for one movement, with estimated 1RM

volume_by_muscle

Working sets and tonnage per muscle group

stalled_lifts

Movements whose best estimated 1RM has not improved

training_summary

Totals, per-muscle volume and stalled movements in one call

workout_totals

Total workout count via the server's own aggregation

body_metrics

Body metrics from the profile

log_workout

Log a completed workout into history

update_logged_workout

Edit a workout already in history

Estimated 1RM uses Epley. Warmup sets are excluded from working-set counts and tonnage. An empty window returns an explanatory note rather than a bare zero.

Tool

Description

generate_workout

Ask Fitbod's own engine to generate a session

replace_exercises

Generate, then swap specified exercises out

workout_insights

Free-text question about a generated workout

generator_reference

The generator surface and its known constraints

Three engines are selectable: foundational_model, algo_direct_client and algo_llamabod. Results are post-filtered against your gym, and anything impossible is auto-swapped through exercise_replacement.

Tool

Description

list_gyms

Gyms on the account with equipment counts

get_gym_equipment

Equipment currently attached to a gym

preview_inventory_mapping

How your inventory maps onto Fitbod equipment ids

create_gym_from_inventory

Create a gym configured from your inventory

add_gym_equipment · remove_gym_equipment

Attach or detach one equipment type

set_available_weights

Declare which specific weights exist for one type

apply_inventory_weights

Apply recorded dumbbell and plate increments

delete_gym

Delete a gym

Tool

Description

get_workout_config · update_workout_config

Goal, split, experience, days per week, supersets, warmups, duration

get_blocks · create_block

Training blocks and their focus exercises

set_exercise_priority · list_exercise_priorities

Per-exercise coefficients biasing selection

Tool

Description

build_routine

Create a complete routine in one call, with rollback

list_routines · get_routine

Read saved templates

create_routine · update_routine · delete_routine

Template lifecycle

add_set_to_routine

Add a prescribed exercise

create_superset

Create an exercise group container

create_custom_exercise

Define a movement Fitbod does not model

Tool

Description

body_composition

Current composition with full history per metric

lean_mass_trend

Lean mass and weight over time, with deltas

muscle_strength · muscle_strength_detail

Per-muscle scoring against the population

population_percentiles

Where a lift ranks against everyone else

training_streak · set_goal_progress

Streaks, weekly counts, volume targets

list_metric_types · metric_details

The metric catalogue and its numeric ids

record_body_metric · delete_body_metric

Write and remove readings

A full time series is available for weight, lean mass, fat mass, body fat, BMI, BMR and seven circumferences, sourced from Apple Health or Health Connect.

Catalogue: find_exercises exercise_detail search_exercises_live exercise_details_live list_equipment muscle_groups warm_start_lookup onboarding_one_rep_maxes seed_one_rep_max api_surface gym_profile

Profile: get_profile update_profile add_injury list_injuries delete_injury rate_exercise list_exercise_ratings registered_devices app_config selected_cardio add_selected_cardio remove_selected_cardio selected_resistance_bands

Coach and chat: coach_ask coach_sessions coach_new_session coach_memories coach_session_stats chat

Analytics: exercise_analytics exercise_benchmarks workout_achievements

Other services: service_health subscription_status list_blimps dismiss_blimp strava_connection list_public_gyms public_gym_equipment search_places

Nutrition: nutrition_targets todays_targets recalculate_targets cut_progress

Escape hatches: raw_get and metros_raw_get, both read-only with path validation.

Authentication

Three steps, with every microservice minting its own short-lived token from one long-lived refresh token:

POST gate-keeper.fitbod.me/users/login   {"user":{"email","password"}}
  -> 201, refresh JWT in the Authorization RESPONSE header, exp about 1 year

POST <service>.fitbod.me/access_token    {"refresh_token":"<raw jwt>"}
  -> 201 {"access_token": ...}, aud=<service>.prod.fitbod.me, about 24h

Authorization: Bearer <access_token>  ->  https://<service>.fitbod.me/...

The client caches access tokens for 23 hours and re-mints transparently on a 401. Note the per-service inconsistency: nautilus requires refresh_token in the body, while prism, blimp and metros also accept it as a header.

The transport matters and the headers do not, which is the opposite of the usual advice. Do not port this to requests, httpx or curl_cffi, and do not reach for browser impersonation when something returns a 403. See Notable findings.

Notable findings

Behaviour that costs real time to work out, documented so it does not have to be rediscovered.

Two serialisation regimes coexist. JSON:API attributes are snake_case, but embedded documents use the source property name verbatim unless an explicit annotation overrides it. Inside individual_sets, isWarmup and restTime stay camelCase while _id, _created_at and is_amrap carry overrides. Confusing the two silently drops every field.

Some mappings are unguessable. workoutConfigId serialises as default_workout_config_id, circuitsEnabled as supersets_enabled, and algorithmCoefficient as algorithm_coeffecient, misspelled on the wire.

Logged sets do not use the resource id. They reference exercises by exercise_external_resource_id, which is a different number. Leg Press is id=291 but external_resource_id=218.

POST and PUT disagree about ids. POST workout_data rejects any id (data.attributes.id should be type integer_id), while PUT workout_data/{id} requires data.id to match the path.

Relationships versus attributes. gym_id and equipment_id are readable but not writable. Creating a gym_equipment row needs JSON:API relationships, not attributes.

Enum values are fixed and order matters.

FitnessGoal          0 GENERAL_FITNESS  1 STRENGTH_TRAINING  2 MUSCLE_TONE
                     3 BODYBUILDING     4 POWERLIFTING       5 OLYMPICWEIGHTLIFTING
MuscleSplit          0 FRESH_MUSCLE_GROUPS  1 UPPER_LOWER  2 FULL_BODY  3 PPL
ExperienceLevel      0 BEGINNER  1 INTERMEDIATE  2 EXPERT
WorkoutVariability   1 MORE_CONSISTENCY  3 SUGGESTED  5 MORE_VARIABILITY
StrengthAggregate    push, pull, lower   (there is no upper and no core)
ExerciseRating       like, dislike, exclude   (lowercase; uppercase returns 500)

Generator quirks. algo_versions is required but every field inside may be null. An empty user_equipment or muscle_usages_from_client returns a 500. exercise_replacement needs the workout echoed back as current_workout, and every id in exercise_ids_to_replace must appear in it. The generator does not strictly honour user_equipment, so always post-filter.

Browser impersonation makes things worse, not better. Measured head to head against one endpoint with the same bearer token, varying only the client:

urllib (stdlib)                      4/4 pass
urllib + Chrome client-hint headers  4/4 pass
curl_cffi, no impersonation          0/4  challenge
curl_cffi, chrome impersonation      0/4  WAF block

Cloudflare's rule keys on the TLS and HTTP/2 fingerprint, and blocks the curl family. The stdlib fingerprint passes. Adding or removing Chrome client-hint headers changes nothing at all, and the app's own fitbod-android/<version> agent is blocked the same as any other. This is why the client deliberately stays on urllib.

Bursts get challenged. A 403 with an HTML body is transient and clears in roughly 10 to 30 seconds. The client retries it rather than surfacing it as an auth error, which is what it resembles at first glance.

A sustained IP block looks identical but is not. Cloudflare will also block an address outright, and that one does not clear on retry. The two are easy to confuse because both are 403 with an HTML body. Distinguish them by checking a second host: a burst challenge is per-endpoint, while an IP block returns 403 for every service and every path, including a plain GET / on gate-keeper.

for h in gate-keeper nautilus metros pyserve; do
  curl -s -o /dev/null -w "$h %{http_code}\n" "https://$h.fitbod.me/"
done

All 403 means the address itself is blocked, which is a different problem from the endpoint rule above and has a different fix: no client change helps, so change egress. Measured: an address returning 403 on all four hosts logged in successfully on the first attempt through a VPN, unchanged client. Login is annual, so one request through a tunnel is enough - the resulting refresh token works fine from the normal connection afterwards, and --export / --import move it if the tunnel is somewhere inconvenient.

Durations are seconds. duration on a logged workout is not minutes, which is easy to miss until a 65 minute session reads as 3908.

Known limitations

Confirmed against a subscribed account with every payload shape tried:

Endpoint

Behaviour

POST set_breakdown_templates

403 on every shape while GET works. Literal per-set weight and reps cannot be prescribed. Steer load through theoretical_max instead.

POST circuit_templates

403, same pattern. Supersets cannot be created through the API.

rpe on POST workout_data

400 for every value including null. Readable, never writable.

Coach sessions

Cannot be deleted (405), so tests never create them.

POST users/login

Blocked by a Cloudflare WAF rule (Sorry, you have been blocked) on every transport, user agent and backoff tried. The address is not banned: the same client mints access tokens and calls every other endpoint normally. Use --export and --import to move an existing refresh token instead.

There is no nutrition API at all. The nutrition tools store targets locally and derive them from live body composition.

Architecture

server.py                thin entrypoint, registers tool modules
fitbod/
  client.py              auth chain, rate limiting, retry, pagination
  auth.py                python -m fitbod.auth
  schemas.py             dataclass parsers for both serialisation regimes
  analysis.py            volume, stalled lifts, 1RM. Pure, network free
  reference.py           offline corpus, dual id index, gym filtering
  tools/                 read, catalogue, gym, config, program, profile,
                         metrics, generate, services, coach, extras, nutrition
tests/                   offline tests over recorded fixtures
docs/
  API-MAP.md             145 endpoints, 365 request and response models
  SCHEMAS.md             field level schemas with exact wire names
build_reference.py       builds the offline corpus from an extracted APK
smoke_test.py            exercises all 97 tools live, writes dry-run by default

Requests are rate limited client-side to 6 per second with a burst of 10. That is a politeness budget rather than a discovered ceiling: 50 concurrent requests sustained about 32 per second with no 429s and no rate limit headers on any response. There is headroom if you need throughput.

The exercise corpus is keyed on a dual index, because logged sets and catalogue entries use different id spaces. reference.by_id() and reference.by_external_id() both exist for that reason.

Documentation

File

Contents

docs/API-MAP.md

The complete surface: 145 endpoint declarations across 12 interfaces, with 365 request and response models resolved transitively

docs/SCHEMAS.md

Field-level schemas with exact wire names, types and required flags

docs/GAPS.md

What remains unresolved, and why

The exercise catalogue

build_reference.py builds data/exercise-reference.json from the res/raw directory of an extracted Fitbod APK: roughly 1,400 exercises with written instructions, 78 equipment types, muscle group mappings and equipment weight tables. That content belongs to Fitbod, so it is not distributed here. Build it from your own install.

This uses an undocumented API and is very likely a breach of Fitbod's terms of service, even though it only ever touches your own account and does not circumvent payment. It exists for personal interoperability with your own training data. Use it on your own account, at your own risk, and do not point it at anyone else's.

The hexagon mark in the banner is an original drawing inspired by Fitbod's visual identity, not their trademark, and this project is not affiliated with, endorsed by, or connected to Fitbod in any way.

License

MIT

A
license - permissive license
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables interaction with the Hevy fitness tracking platform through their API. Supports managing workouts, routines, exercise templates, and webhook subscriptions for comprehensive fitness data management.
    9
    ISC
  • A
    license
    B
    quality
    D
    maintenance
    Enables access to WHOOP fitness and health data through all WHOOP v2 API endpoints. Supports OAuth 2.0 authentication and provides comprehensive access to user profiles, physiological cycles, recovery metrics, sleep analysis, and workout data.
    16
    163
    14
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables triathlon coaches and athletes to interact with Garmin Connect, including retrieving health/activity data, building and uploading structured workouts (cycling, running, swimming, brick), and accessing coaching analytics like readiness, load, and performance trends.
    MIT

View all related MCP servers

Related MCP Connectors

  • List, fetch, create, edit (replace), delete and schedule structured workouts on Garmin Connect (runn

  • Create Hevy routines and analyze your training from chat. Unofficial; BYO Hevy PRO API key.

  • Manage clients, plans, sessions, habits, and billing on Trainzilla via one-click OAuth.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Muno459/mcp-fitbod'

If you have feedback or need assistance with the MCP directory API, please join our Discord server