Skip to main content
Glama
GymTimer MCP Server

GymTimer MCP Server

by elkno

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:

  1. Node.js 18 or newer. Check with node --version. If that command isn't found, install it from nodejs.org (or brew install node).

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

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

  1. Open the CloudKit Console and choose CloudKit Database.

  2. Select the container iCloud.com.elkno.gymtimer.gymtimer in the header, and set the environment selector next to it to Production. This matters: Production is where TestFlight and App Store installs sync, and it's the environment this server assumes by default. Creating the token while the console is on Development is the single easiest mistake to make here.

  3. In the left sidebar under Settings, click Tokens & Keys.

  4. Create a new API Token and give it a name (gymtimer is fine).

  5. 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 ckWebAuthToken out 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.

  6. 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 doctor

This 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 expired

The fix takes about a minute:

cd gymtimer-mcp-server
./refresh-tokens.sh

It 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 /mcp

  • Claude 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 development when your data is in production (or the reverse). npm run doctor calls 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

get_workout_history

Completed sessions with their exercises and sets, over the last N days

get_exercise_history

Every set you've logged for one exercise, over time

analyze_training_volume

Sets, reps and volume totalled by muscle group or exercise

get_recovery_status

Days since each muscle group was last trained

get_exercise_library

Your exercise library, optionally filtered

get_workout_templates

Your saved templates with their full ordered exercise lists

get_training_preferences

Your whole profile: training preferences, body stats, and food preferences

get_nutrition_plan

Your active nutrition plan - the week's days, meals and recipes

Writing back to the app

Tool

What it does

create_exercise

Adds an exercise (name, muscle groups, equipment, optional photo)

update_exercise

Changes an exercise's name, muscle groups, equipment or photo

delete_exercise

Removes an exercise from your library

save_workout_template

Creates a template with an ordered exercise list, target sets/reps/rest, tempo, intensity and supersets

update_workout_template

Edits a template in place, keeping its history links. Replaces the whole exercise list, so it reads first

update_workout_template_name

Renames a template without touching its exercises

delete_workout_template

Deletes a template (your past workouts stay)

save_periodization_plan

Creates a named training block with a date range, optionally linking templates to it

save_nutrition_plan

Creates the week's nutrition plan. Replaces your current active plan

update_nutrition_plan

Edits the active plan's name, notes or start date only

update_nutrition_day

Rewrites one day of the active plan

update_nutrition_meal

Rewrites a single meal - the "swap Tuesday's lunch" tool

delete_nutrition_plan

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

GYMTIMER_CK_ENVIRONMENT

production

Which CloudKit environment to read. Falls back to ~/.config/gymtimer/environment (written by setup.sh) before this default

GYMTIMER_CONTAINER_ID

iCloud.com.elkno.gymtimer.gymtimer

The CloudKit container to talk to

GYMTIMER_CONFIG_DIR

~/.config/gymtimer

Where tokens and your environment choice are stored

GYMTIMER_CK_API_TOKEN_PATH

<config dir>/ck-api-token

Path to the API token file

GYMTIMER_CK_WEB_AUTH_TOKEN_PATH

<config dir>/ck-web-auth-token

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 tools
analyze_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

    A3.9/5.0

    Scored across 21 tools

    Disambiguation4/5

    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.

    Naming Consistency4/5

    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.

    Tool Count3/5

    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.

    Completeness3/5

    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

    ActivityStale
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables 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 npm
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Connects AI assistants directly to Hevy workout data, enabling analysis of training volume, strength progression tracking, and web searches for fitness research.
      MIT