GymTimer MCP Server
Provides access to a user's GymTimer workout data stored in their private iCloud (CloudKit) storage, allowing AI assistants to read workout history, exercise libraries, templates, and nutrition plans, as well as write exercises and workout templates back to the GymTimer app.
Click on "Deploy 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., "@GymTimer MCP Server Show my last 7 days of training."
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.
GymTimer MCP Server
Connect your real workout data to an AI assistant. This lets you ask Cursor, Claude, or any other AI app questions about your actual training - and have it write plans back into the GymTimer app on your iPhone and Apple Watch.
Things you can say once it's running:
"Show my last 7 days of training."
"Which muscle groups have I been neglecting?"
"Build me a push day based on my history and preferences, then save it as a template."
"Generate a week of meals for my calorie and protein targets, with Spanish recipes."
PROMPTS.md has longer, ready-to-paste prompts for generating a full training week and a full nutrition week.
It works with whichever AI model you already use - the server only supplies your data, and all the thinking happens in the model you've chosen.
First, what is an MCP server?
MCP (Model Context Protocol) is a standard way to give an AI assistant access to tools. An MCP "server" is just a small program that runs on your own computer and offers a list of things the assistant can do - here, 21 tools like "read my workout history" and "save a workout template".
You don't run it yourself or leave it running. Your AI app starts it in the background whenever it needs it. Nothing is hosted anywhere, and your data never passes through anyone else's server.
So the install below is really just two things: put this program on your computer, then tell your AI app where to find it.
Related MCP server: hevy-mcp
Before you start
You need three things:
Node.js 18 or newer. Check with
node --version. If that command isn't found, install it from nodejs.org (orbrew install node).The GymTimer app, on an iPhone or Apple Watch signed into iCloud, with at least one workout logged. Your data lives in your own private iCloud (CloudKit) storage; this server reads it from there.
A CloudKit API token, which you get from whoever owns the GymTimer app.
That third one confuses people, so to be clear: the API token only identifies the app to Apple. It is not a password and it does not grant access to anyone's workouts, so the owner can safely give the same token to everybody. What scopes things to you is a separate Apple ID sign-in during setup. If you don't have a token yet, ask the app owner before continuing.
Creating the API token (app owner only)
Skip this if someone gave you a token - it only needs doing once per container, by whoever owns the GymTimer CloudKit container.
Open the CloudKit Console and choose CloudKit Database.
Select the container
iCloud.com.elkno.gymtimer.gymtimerin the header, and set the environment selector next to it toProduction. This matters:Productionis where TestFlight and App Store installs sync, and it's the environment this server assumes by default. Creating the token while the console is onDevelopmentis the single easiest mistake to make here.In the left sidebar under Settings, click Tokens & Keys.
Create a new API Token and give it a name (
gymtimeris fine).Configure it as follows - the callback setting is not optional:
Setting
Value
Why
Sign in Callback
URL Redirect, with any https URL (e.g.
https://example.com)Required. The sign-in flow works by reading
ckWebAuthTokenout of the URL Apple redirects to. With Post Message selected instead, there's no redirect to copy and setup can't complete. The URL doesn't need to exist - landing on a 404 is expected and fine.Allowed Origins
Any Domain
Sign-in happens in a normal browser, not from a hosted web page.
Discoverability
Leave unchecked
Not needed; it only controls whether users are discoverable to others by email.
Copy the API Access token value - a long hex string - and share it with whoever needs it.
Two tokens in that console are not what you want: the User Token and the
Management Token. Neither works with this REST flow. The one you need is
under API Access, and ./setup.sh will reject anything that isn't a hex string
of the right shape.
If your users will be on Production, also confirm the schema has been deployed
there (Deploy Schema Changes: Development to Production), or their writes
will fail on record types that only exist in development. See
docs/MAINTAINERS.md.
Install (once per computer)
git clone https://github.com/elkno/gymtimer-mcp-server.git
cd gymtimer-mcp-server
./setup.sh./setup.sh walks through six steps and asks you three questions:
It asks | What to answer |
Paste the CloudKit API token | The token from the app owner. Nothing appears as you type - that's intentional, just paste and press Enter. |
production or development? | production if you installed GymTimer from TestFlight or the App Store (almost everyone). development only if you build the app from Xcode yourself. |
Sign in with your Apple ID | It prints a URL. Open it, sign in with the same Apple ID as your iPhone and Watch, then copy the entire URL you land on afterwards and paste it back. That page may show an error or a blank page - it doesn't matter, the address bar is what's needed. |
Along the way it installs dependencies, builds the server, registers it with
Cursor, and finishes with a health check. Your answers are saved under
~/.config/gymtimer/, so you never repeat this.
Using something other than Cursor? See CLIENT-SETUP.md - it has copy-paste configuration for Claude Code, Claude Desktop, VS Code, Windsurf and Zed. For Claude Code it's a single command.
Check that it worked
npm run doctorThis checks everything in order - Node version, build, both tokens, which CloudKit environment you're pointed at - and then actually calls CloudKit and reads one record back. Every failure comes with the exact command that fixes it, so start here whenever something seems off:
GymTimer MCP server - health check
Runtime
ok Node.js v22.11.0
Build
ok dist/index.js exists - /Users/you/gymtimer-mcp-server/dist/index.js
Tokens
ok API token - 64 chars
ok Web auth token - 1091 chars
Environment
ok CloudKit environment: production - from ~/.config/gymtimer/environment
ok Container: iCloud.com.elkno.gymtimer.gymtimer
CloudKit connection
ok Signed in to CloudKit
ok Read your GymTimer data - found exercises in the "production" environment
All checks passed. Ask your assistant: "show my last 7 days of training".Then restart your AI app and ask it "show my last 7 days of training". If it answers with real workouts, you're done.
When it stops working: refreshing your token
This is the one bit of routine maintenance, and it will happen every few weeks.
You have two tokens, and only one of them expires:
Token | What it does | Expires? |
API token | Identifies the GymTimer app to Apple | No, effectively permanent |
Web auth token | Proves you are you, from your Apple ID sign-in | Yes, every few weeks |
When the web auth token expires, tools stop working and your assistant shows an error containing:
CloudKit ... authentication failed - your web auth token has likely expiredThe fix takes about a minute:
cd gymtimer-mcp-server
./refresh-tokens.shIt reopens the same Apple ID sign-in from installation - paste the URL back the same way. Then restart your AI app so it reconnects:
Cursor - restart it, or click the refresh icon in Settings -> MCP
Claude Code - run
/mcpClaude Desktop - fully quit and reopen (closing the window isn't enough)
Confirm with npm run doctor.
Two notes worth knowing before you go hunting for other causes:
You almost never need
./refresh-tokens.sh --api-token. That replaces the app token and is only for when the owner issues a new one.A wrong environment looks exactly like an expired token. If refreshing didn't help, you may be pointed at
developmentwhen your data is inproduction(or the reverse).npm run doctorcalls this out specifically - it will tell you it connected fine but found no exercises there.
What you can ask for
You don't have to memorise any of this - just ask for what you want in plain language and the assistant picks the tools. PROMPTS.md has worked-out prompts if you'd rather copy something that already does the job.
Reading your data
Tool | What it returns |
| Completed sessions with their exercises and sets, over the last N days |
| Every set you've logged for one exercise, over time |
| Sets, reps and volume totalled by muscle group or exercise |
| Days since each muscle group was last trained |
| Your exercise library, optionally filtered |
| Your saved templates with their full ordered exercise lists |
| Your whole profile: training preferences, body stats, and food preferences |
| Your active nutrition plan - the week's days, meals and recipes |
Writing back to the app
Tool | What it does |
| Adds an exercise (name, muscle groups, equipment, optional photo) |
| Changes an exercise's name, muscle groups, equipment or photo |
| Removes an exercise from your library |
| Creates a template with an ordered exercise list, target sets/reps/rest, tempo, intensity and supersets |
| Edits a template in place, keeping its history links. Replaces the whole exercise list, so it reads first |
| Renames a template without touching its exercises |
| Deletes a template (your past workouts stay) |
| Creates a named training block with a date range, optionally linking templates to it |
| Creates the week's nutrition plan. Replaces your current active plan |
| Edits the active plan's name, notes or start date only |
| Rewrites one day of the active plan |
| Rewrites a single meal - the "swap Tuesday's lunch" tool |
| Deletes the active nutrition plan |
One limitation to be aware of: a few single-choice fields (an exercise's
category and movementPattern, a cycle's phase) are deliberately never
written by these tools, so anything created through MCP has no value for them.
Reads return null in that case rather than guessing. Even where a value does
exist it's usually just the app's default, so don't rely on these two fields to
tell a push from a hinge - the exercise name and its muscle groups are the
trustworthy signal. docs/ARCHITECTURE.md explains why.
Settings
Everything has a working default; you only need these if you want to change something. They're set in your AI app's config (see CLIENT-SETUP.md) or as ordinary environment variables.
Variable | Default | What it's for |
|
| Which CloudKit environment to read. Falls back to |
|
| The CloudKit container to talk to |
|
| Where tokens and your environment choice are stored |
|
| Path to the API token file |
|
| Path to your web auth token file |
Is my data private?
Yes. The API token identifies the app and grants no access to workout data by itself. Every read and write is additionally scoped by your web auth token, from your own Apple ID sign-in, so you only ever see your own private CloudKit data - exactly like the GymTimer app does.
Sharing the API token with someone else doesn't expose your data to them, or
theirs to you. Each person signs in with their own Apple ID. Both tokens are
stored as files readable only by your user account (mode 600), and requests
go directly from your machine to Apple.
Troubleshooting
"Authentication failed" / "token has likely expired" - your web auth token
expired. Run ./refresh-tokens.sh, then restart your AI app. See
the section above.
The server shows as failed, or no tools appear - run npm run doctor
first, it catches most causes. If it passes, the problem is in your AI app's
config rather than the server, so check
CLIENT-SETUP.md's checklist.
The usual culprits are a missing npm run build, a relative path where an
absolute one is required, or an app that needs a full restart.
It connects but finds no data - you're almost certainly pointed at the
wrong CloudKit environment. production is for TestFlight and App Store
installs; development only holds data from builds run in Xcode.
npm run doctor tells you which one you're on and where that setting came from.
A field or record type seems to be missing - CloudKit only adds an optional field to its schema once some device has actually saved a value for it, so a brand-new field won't appear in results until you've set it at least once in the app. If a whole record type is missing, the schema may not be deployed to your environment yet - see docs/MAINTAINERS.md.
Duplicate exercises in get_exercise_library - if the app's exercise
seeder ran on two devices before they synced with each other, you can end up
with duplicates. Harmless for reading; tidy them up in the iPhone app.
More documentation
CLIENT-SETUP.md - adding the server to Cursor, Claude Code, Claude Desktop, VS Code, Windsurf and Zed
PROMPTS.md - ready-to-paste prompts for weekly training and nutrition plans
docs/ARCHITECTURE.md - how it talks to CloudKit, and how SwiftData's encoded fields are decoded
docs/MAINTAINERS.md - for the app owner: issuing API tokens and deploying schema changes
License
MIT - see LICENSE.
Available Tools
21 toolsanalyze_training_volumeAnalyze Training VolumeA
Aggregates sets/reps/volume (reps x weight) over the last N days, grouped either by muscle group or by exercise. Use this to spot over/under-trained muscle groups before proposing a workout.
Inputs: days, groupBy.
create_exerciseCreate ExerciseA
Adds a new exercise to the user's library, optionally with a photo. Note: the new exercise's category/movement pattern aren't set by this tool (a known limitation) - it'll show up on the user's devices with default values (compound/push) until edited from the app.
Inputs: name (required), equipment (required), muscleGroups (required), photoPath.
delete_exerciseDelete ExerciseA
Deletes an exercise from the user's library (fuzzy-matched by name, or exact by id).
Inputs: exercise (required).
delete_nutrition_planDelete Nutrition PlanA
Deletes the active nutrition plan and all its days/meals.
Inputs: no named parameters.
delete_workout_templateDelete Workout TemplateA
Deletes a workout template and its exercise slots (fuzzy-matched by name, or exact by id). Does not delete workout history/sessions that already used it.
Inputs: template (required).
get_exercise_historyGet Exercise HistoryA
Returns every logged set for one specific exercise over time (most recent first), so you can see progression (reps/weight trend) for that movement.
Inputs: exerciseName (required), limit.
get_exercise_libraryGet Exercise LibraryA
Returns the user's exercise library (name, muscle groups, equipment, category, movement pattern, whether it has a custom photo), optionally filtered. Use this to know what exercises/equipment are available when proposing a workout. Note: category and movementPattern are null when the exercise has no stored value, and in practice are unreliable even when set (this app's exercises are largely left at the defaults "compound"/"push", so a hinge or isometric movement may still report "push"). Judge movement patterns from the exercise name and muscleGroups instead of trusting these two fields.
Inputs: equipment, muscleGroup.
get_nutrition_planGet Nutrition PlanA
Returns the single active nutrition plan - the week's days and meals with full recipe details (ingredients, instructions, calories/macros) - or null if none has been generated yet.
Inputs: no named parameters.
get_recovery_statusGet Recovery StatusA
Returns days since each muscle group was last trained, plus sets trained this week, so you can judge whether a muscle group is ready to train again.
Inputs: muscleGroup.
get_training_preferencesGet Training PreferencesA
Returns the user's full profile: training preferences (goal, experience level, available equipment, days per week, session length, injuries/limitations) PLUS body/personal info (height, weight, age, biological sex, body type, fat distribution, activity level) and food preferences (dietary restrictions/allergies, cuisine/food notes, nutrition goal, daily calorie/macro targets). Always check this before generating a workout AND before generating or adapting a nutrition plan (see save_nutrition_plan).
Inputs: no named parameters.
get_workout_historyGet Workout HistoryA
Returns completed workout sessions (with every exercise and set) from the last N days, newest first. Use this to see what the user actually did recently. Each session includes difficultyRating (1-5, how hard the user said the workout felt right after finishing it, 5 being extremely hard) when available - use it alongside volume/reps trends to judge whether intensity or volume should change.
Inputs: days.
get_workout_templatesGet Workout TemplatesA
Returns the user's saved workout templates (training session blueprints) with their full ordered exercise lists - target sets/reps/rest plus tempo, intensity, suggested weight and superset grouping for every slot. Each slot comes back in exactly the shape save_workout_template/update_workout_template accept, so you can read a template, change part of it, and write it back without losing anything. Use this before update_workout_template - that tool replaces the whole exercise list, so you need the current one first.
Inputs: template.
save_nutrition_planSave Nutrition PlanA
Creates the week's nutrition plan. IMPORTANT: there is always exactly one active plan - calling this REPLACES whatever plan currently exists (deleting it and its days/meals) rather than creating a second one. Use this to generate/regenerate the whole week; for smaller tweaks to specific meals or days, prefer update_nutrition_meal / update_nutrition_day / update_nutrition_plan instead so the rest of the week isn't thrown away. Before calling this, check get_training_preferences (body stats + food preferences/restrictions) and ideally get_workout_history/get_recovery_status so the plan reflects the user's actual training and goals.
Inputs: days (required), name (required), notes, startDate, +1 more.
save_periodization_planSave Periodization PlanA
Creates a periodization cycle (a named training block, e.g. a hypertrophy or strength block with a start/end date) and optionally links existing workout templates to it. Note: the cycle's training phase isn't set by this tool (a known limitation) - edit that from the app.
Inputs: name (required), notes, endDate, startDate, +1 more.
save_workout_templateSave Workout TemplateA
Creates a NEW workout template (a reusable training session blueprint) with an ordered list of exercises, each with target sets/reps/rest and optional tempo/cadence, intensity (RIR/RPE), suggested working weight, and superset grouping. Exercises must already exist in the library (see get_exercise_library, or create_exercise first). To change an existing template instead, use update_workout_template - creating a duplicate and deleting the old one loses its link to past workouts.
Inputs: name (required), exercises (required), estimatedDurationMinutes.
update_exerciseUpdate ExerciseA
Updates an existing exercise's name, muscle groups, equipment, and/or photo (fuzzy-matched by name, or exact by id). Category and movement pattern can't be changed by this tool (a known limitation) - edit those from the app.
Inputs: exercise (required), name, equipment, photoPath, +2 more.
update_nutrition_dayUpdate Nutrition DayA
Adapts one day of the active nutrition plan: rename/re-note it, and/or replace its whole meal list - without touching other days. Use update_nutrition_meal instead for a single-meal tweak (e.g. just swapping one recipe).
Inputs: day (required), meals, notes, title.
update_nutrition_mealUpdate Nutrition MealA
Adapts a single meal's recipe, ingredients, instructions, macros, and/or time within the active nutrition plan, without regenerating the rest of the day or week. The main tool for requests like "swap Tuesday's lunch for something else" or "make Wednesday's dinner higher protein".
Inputs: day (required), meal (required), name, time, +7 more.
update_nutrition_planUpdate Nutrition PlanA
Adapts plan-level metadata (name, notes, start date, and/or the generation prompt) on the active nutrition plan in place, without touching its days or meals.
Inputs: name, notes, startDate, aiGenerationPrompt.
update_workout_templateUpdate Workout TemplateA
Edits an existing workout template in place - its name, estimated duration, and/or its exercises - keeping the template itself, so its link to past workouts in history (and to any periodization cycle) survives. This is the tool for requests like "swap the first three exercises of my chest day for cable variations" or "add a set to every exercise". IMPORTANT: exercises REPLACES the entire exercise list, so call get_workout_templates first and send the full list back with your changes applied; omit exercises to change only the name/duration. Prefer this over delete_workout_template + save_workout_template.
Inputs: template (required), name, exercises, estimatedDurationMinutes.
update_workout_template_nameUpdate Workout Template NameA
Renames an existing workout template in place (fuzzy-matched by name, or exact by id). Doesn't touch its exercises.
Inputs: newName (required), template (required).
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 21 tools
Tools are mostly distinct by resource and action: templates, exercises, nutrition, history, and preferences are clearly separated. The only real overlap is update_workout_template vs update_workout_template_name, where the latter is a narrower subset, but the descriptions make the intended use clear.
Tool names follow a consistent snake_case verb_noun pattern overall (get_, create_, update_, delete_). The main deviations are the use of 'save' for some creation/replacement tools and the more granular update_workout_template_name, but these are minor and still readable.
With 21 tools, the server is on the heavy side and sits in the borderline range. The count is defensible given the breadth of workout, nutrition, and analytics features, but a few tools could be consolidated.
Exercise and workout template CRUD are well covered, and nutrition has full plan-level lifecycle coverage. However, periodization has only save_periodization_plan with no way to read, update, or delete cycles, and there is no workout logging tool, which are notable gaps in the domain surface.
Maintenance
Related MCP Connectors
Your strength-training data for any AI assistant: workouts, progress, muscle volume, routines.
Your Apple Health data, food diary and lifts, for ChatGPT, Claude and Gemini. Approved on iPhone.
Your Garmin, Oura, WHOOP, Hevy and 35 more fitness apps, queryable from your own AI.
Log workouts and meals by telling your AI. 873 exercises, muscle diagrams, food lookup.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables AI assistants to access and analyze your Hevy workout data, including workout history, exercise progress, personal records, and routines.472 npm3MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to read, analyze, create, and update Hevy workout data, including workouts, routines, exercises, and body measurements, through the Model Context Protocol.3,965 npmMIT
- AlicenseNot gradedqualityCmaintenanceEnables AI assistants to design strength training workouts, push them to Garmin Connect and your watch, and read back completed sessions and exercise history.MIT
- AlicenseNot gradedqualityCmaintenanceConnects AI assistants directly to Hevy workout data, enabling analysis of training volume, strength progression tracking, and web searches for fitness research.MIT