mcp-fitbod
Provides tools for interacting with Strava, including in-app messages and workout synchronization.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-fitbodGenerate a full-body workout I can do with my home gym equipment"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 |
| routines, gyms, equipment, generator config, profile, workout history | 36 |
| body composition time series, strength scoring, streaks, percentiles | 11 |
| the workout generator, including an LLM backed variant | 4 |
| AI coaching sessions with persistent memories | 5 |
| public gym directory and Places search | 3 |
| AI chat backend | 1 |
| in-app messages, Strava, subscription state | 4 |
| 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=Trueand 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_routinevalidates 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 expiryBuild 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/rawDescribe your gym so tools can filter to performable exercises.
cp data/gym-profile.example.json data/gym-profile.json
$EDITOR data/gym-profile.jsonVerify, then run.
python -m pytest tests/ -q # offline: parsing and analysis
python smoke_test.py # live: every tool, writes are dry-run
python server.pyConfiguration
Register the server with any MCP client over stdio:
{
"mcpServers": {
"fitbod": {
"command": "python",
"args": ["/absolute/path/to/mcp-fitbod/server.py"]
}
}
}Variable | Purpose |
| Used by |
| Used by |
| 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 |
| Logged workouts, newest first |
| Full detail for one workout, every set expanded |
| Every set logged for one movement, with estimated 1RM |
| Working sets and tonnage per muscle group |
| Movements whose best estimated 1RM has not improved |
| Totals, per-muscle volume and stalled movements in one call |
| Total workout count via the server's own aggregation |
| Body metrics from the profile |
| Log a completed workout into history |
| 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 |
| Ask Fitbod's own engine to generate a session |
| Generate, then swap specified exercises out |
| Free-text question about a generated workout |
| 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 |
| Gyms on the account with equipment counts |
| Equipment currently attached to a gym |
| How your inventory maps onto Fitbod equipment ids |
| Create a gym configured from your inventory |
| Attach or detach one equipment type |
| Declare which specific weights exist for one type |
| Apply recorded dumbbell and plate increments |
| Delete a gym |
Tool | Description |
| Goal, split, experience, days per week, supersets, warmups, duration |
| Training blocks and their focus exercises |
| Per-exercise coefficients biasing selection |
Tool | Description |
| Create a complete routine in one call, with rollback |
| Read saved templates |
| Template lifecycle |
| Add a prescribed exercise |
| Create an exercise group container |
| Define a movement Fitbod does not model |
Tool | Description |
| Current composition with full history per metric |
| Lean mass and weight over time, with deltas |
| Per-muscle scoring against the population |
| Where a lift ranks against everyone else |
| Streaks, weekly counts, volume targets |
| The metric catalogue and its numeric ids |
| 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 blockCloudflare'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/"
doneAll 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 |
|
|
|
|
|
|
Coach sessions | Cannot be deleted ( |
| Blocked by a Cloudflare WAF rule ( |
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 defaultRequests 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 |
| The complete surface: 145 endpoint declarations across 12 interfaces, with 365 request and response models resolved transitively |
| Field-level schemas with exact wire names, types and required flags |
| 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.
Legal
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
This server cannot be installed
Maintenance
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
- AlicenseNot gradedqualityCmaintenanceEnables interaction with the Hevy fitness tracking platform through their API. Supports managing workouts, routines, exercise templates, and webhook subscriptions for comprehensive fitness data management.9ISC
- AlicenseBqualityDmaintenanceEnables 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.1616314MIT
- AlicenseAqualityDmaintenanceProvides read-only access to Nolio training data including planned workouts, completed sessions, metrics, records, and notes.83MIT
- AlicenseNot gradedqualityCmaintenanceEnables 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
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.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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