apple-workouts-mcp
It is an MCP server for building, previewing, publishing, and tracking structured Apple Watch workouts and multi-week training plans, with read-only Strava and race-catalogue context.
Read recent Strava activities and assess training/fitness gaps toward a goal time.
Preview workout or strength specs as exact WorkoutKit-shaped JSON without publishing.
Create and schedule structured runs with warmups, intervals, recoveries, cooldowns, HR-zone or pace targets, indoor/outdoor location, date and optional time.
Create and schedule guided strength/core workouts with sets, reps or timed holds, rest, and optional load.
Publish custom multi-week plans, marathon blocks scaled to current fitness, and high-school track blocks for 800/1600/3200/5K, with dry-run and replace options.
Inspect the workout feed and sync status, including whether HoodaRun reports workouts as on the Watch.
Remove one workout by id, or clear a date range with a dry-run-first workflow.
Browse or randomly pick marathons from a catalogue by weeks-out window.
The README also documents web dashboard, Siri/App Intents, meal/grocery, Texas race, and calendar features beyond the provided MCP schema. The server does not directly write to the Watch; a paired iPhone app using WorkoutKit must sync the feed.
Builds structured runs, strength sessions, and multi-week training plans, then publishes and schedules them on Apple Watch through WorkoutKit and the HoodaRun companion app.
Uses iCloud Drive as the default workout feed location and private CloudKit records for web-saved plans, enabling sync of planned workouts into HoodaRun.
Reads recent Strava activities and training history to assess fitness and estimate the gap to a goal time, using the user's own Strava API credentials.
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., "@apple-workouts-mcp Put a 3x2mi tempo at 6:20 on Tuesday and a 12-miler on Sunday"
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.
apple-workouts-mcp
An MCP server that lets Claude, OpenAI Agents, ChatGPT (where custom MCP is available), or any MCP client build structured runs, strength sessions and multi-week training plans and put them on your Apple Watch — the Apple counterpart to mcp-garmin.
"Put a 3×2mi tempo at 6:15–6:25 on Tuesday, Strength B on Thursday,
and a 12-miler with the last 3 at half-marathon pace on Sunday."
→ built, published, scheduled on the Watch by HoodaRun.HoodaRun Web — browser athlete studio
HoodaRun now has a responsive web dashboard in the same MCP server, accessible from a Mac, PC, iPad or phone. No JavaScript build pipeline or third-party frontend service is required. The browser app can preview and publish structured running sessions and multiweek plans, strength workouts, plant-based meal/grocery lists, a Texas race calendar, and inspect the real workout feed and iPhone sync status.
pip install -e ".[dev]"
TRANSPORT=streamable-http HOST=127.0.0.1 PORT=8000 apple-workouts-mcp
# Open http://127.0.0.1:8000/appFor remote hosting, use HTTPS, set MCP_AUTH_TOKEN and persistent
WORKOUT_FEED_DIR storage. The website's Connect server field accepts
that same token without saving it in browser storage. The public page has
no workout data; every browser API action is authenticated and protected.
Plans always require preview and a separate deliberate publish action.
See Website setup and deployment for the routes, screens, and security limitations.
Important: a website cannot call WorkoutKit or HealthKit on your Apple Watch. HoodaRun on your paired iPhone remains necessary to turn the shared JSON feed into scheduled Apple Watch workouts. Publishing to the feed is not the same as confirming a workout on the Watch.
Related MCP server: Garmin Claude MCP
Siri AI / Apple Intelligence / Shortcuts
HoodaRun also exposes typed Siri AI / Apple Shortcuts actions through Apple's App Intents, with a safe private bridge to the same workout feed used by Claude and OpenAI MCP tools. Siri is not a native generic MCP client: App Intents are the supported Apple integration path.
Features include next-workout lookup (local iCloud or CloudKit), requesting an iPhone-to-Watch sync, previewing and confirming new easy runs or interval sessions (including standing vs jogging rest), querying Texas races and getting simple plant-based meal ideas.
Add ios/HoodaRun/HoodaRunSiriIntents.swift to the HoodaRun iPhone app
target. For actions that reach the Python server, configure HTTPS plus
MCP_AUTH_TOKEN under HoodaRun's existing Remote Server settings.
Voice-created workouts require an explicit confirmation before they are
published. The new POST /api/siri endpoint is protected by the same
bearer-token middleware as MCP and the feed.
Read the Siri AI integration guide for setup, supported phrases, and the iOS 27/watchOS 27 hardware testing limitations.
How it reaches the Watch
Apple has no cloud API that writes to a Watch. Structured workouts get there through WorkoutKit, which only runs on the iPhone. So the work is split:
AI MCP client ──MCP──▶ apple-workouts-mcp ──writes──▶ workouts.json
│ (iCloud Drive, or GET /feed.json)
▼
HoodaRun (iPhone) ── WorkoutKit ──▶ Apple Watch
│
status.json ◀──writes─────┘ (what actually landed)The server compiles each workout into JSON that maps 1:1 onto WorkoutKit (
CustomWorkout→ warmup /IntervalBlocks / cooldown,HeartRateZoneAlert,SpeedRangeAlert, time / distance / open goals).HoodaRun reads the feed, schedules new or changed workouts with
WorkoutScheduler, removes ones that left the feed, and writesstatus.jsonback — solist_scheduledcan tell you what's really on your wrist.Completed runs reach Strava through Strava's own Apple Health sync; the server reads them back (read-only, your own Strava API app) for fitness assessment.
Nothing third-party sits in the middle: iCloud Drive for local use, or your own HTTP host.
Quick start (Mac + iCloud Drive)
git clone https://github.com/yashhooda1/mcp-apple-workouts
cd mcp-apple-workouts
pip install -e ".[dev]"
pytest -q # offline: compiles workouts + feed round-tripsAdd to Claude Desktop's MCP config (see examples/claude_desktop_config.json):
{ "mcpServers": { "apple-workouts": { "command": "apple-workouts-mcp" } } }On a Mac the feed defaults to iCloud Drive › HoodaRun › MCP
(WORKOUT_FEED_DIR overrides). In HoodaRun: Settings › Claude plans ›
Choose feed folder… and pick that folder once. HoodaRun syncs on launch and
from Sync now.
OpenAI / ChatGPT + advanced runner tools
The server speaks standard MCP, so it works with more than Claude. This
branch adds OpenAI Agents SDK (local stdio) and OpenAI Responses API
(remote HTTP) examples. ChatGPT custom MCP connections depend on your
workspace and supported authentication or a Secure MCP Tunnel; a local
stdio server cannot be plugged straight into ChatGPT on the web.
See OpenAI/ChatGPT setup and security for the working example commands, authentication caveats, and watchOS limitations.
Additional planning tools cover an adult sub-5 mile, sub-17 5K, sub-1:20 half and the existing sub-3 marathon generator. Road plans use your current mileage and optional recent time-trial benchmark rather than assuming a stretch goal already equals current fitness.
create_road_race_plan(distance="5k", goal_time="16:59",
current_weekly_miles=35, benchmark_time="18:15",
benchmark_distance="5k", weeks=16, dry_run=True)
build_meal_grocery_plan(diet="lacto_vegetarian", training_load="high")
list_texas_races(area="Austin", distance="5K")The meal planner returns seven quick-cook menus, grocery categories and plant-based B12/iron guidance. The race calendar uses published 2026–27 organizer dates and URLs for Austin, Dallas/Fort Worth and Houston; it does not pretend to fetch live registration inventory or infer future annual dates.
Optional: Strava history
Create an API app at https://www.strava.com/settings/api (callback domain
localhost) and setSTRAVA_CLIENT_ID/STRAVA_CLIENT_SECRET.Run
apple-workouts-strava-authonce. The rotating refresh token is saved toSTRAVA_TOKENS(default~/.hooda-workouts/strava_tokens.json, mode 600).
Without Strava, pass current_weekly_miles to the plan tools.
Remote (claude.ai custom connector)
Deploy with the Dockerfile / railway.toml, attach a volume at /data, set
MCP_AUTH_TOKEN (openssl rand -hex 32). The same bearer token gates all three
routes:
Route | Who | |
| Claude | MCP streamable HTTP |
| HoodaRun | the workout feed |
| HoodaRun | what's scheduled on the Watch |
| any client with the token | the feed as an iCalendar |
In HoodaRun use Remote server (HTTP) with your https:// host and token
(stored in the Keychain).
Claude and ChatGPT connectors (OAuth). Set PUBLIC_BASE_URL to the
server's https origin and the server also becomes its own OAuth 2.1
authorization server (discovery, dynamic client registration, PKCE S256,
rotating refresh tokens). Add https://<host>/mcp as a custom connector in
Claude or ChatGPT and sign in with your server password
(OAUTH_OWNER_PASSWORD, default MCP_AUTH_TOKEN). The static token keeps
working for HoodaRun and Claude Desktop.
Apple Calendar. Set CALENDAR_TOKEN and subscribe to
webcal://<host>/calendar/<CALENDAR_TOKEN>.ics, or turn on Add to Apple
Calendar in HoodaRun to get editable events each sync. Pick one. See
Calendar and connectors.
Tools
Tool | Access | Description |
| read | Recent Strava activities |
| read | Summarise recent training; size the gap to a goal time |
| read | Feed workouts, each flagged |
| read | Last HoodaRun sync: scheduled, pending, errors |
| read | Apple Calendar subscription URL and setup |
| read | The exact WorkoutKit-shaped JSON for a spec |
| read | International marathon catalogue |
| write | Structured run on a date (optional time, indoor/outdoor) |
| write | Guided strength/core session on a date |
| write | Remove one workout by id |
| write | Remove a date range (dry-run first) |
| plan | Publish a plan you wrote — runs and lifts, by date |
| plan | Any marathon, including sub-3, scaled to available fitness |
| plan | High-school 800 / 1600 / 3200 / 5K blocks |
Every generated plan schedules two strength/core sessions a week and returns per-week nutrition, hydration and recovery guidance.
Workout spec
Same schema as mcp-garmin, so prompts and plans port unchanged:
{"name": "4x1mi threshold", "location": "outdoor", "steps": [
{"kind": "warmup", "duration": "15:00", "target": "hr:2"},
{"repeat": 4, "steps": [
{"kind": "run", "duration": "1mi", "target": "pace:6:35-6:55"},
{"kind": "recovery", "duration": "2:30", "target": "hr:2"}]},
{"kind": "cooldown", "duration": "10:00", "target": "hr:2"}]}Durations:
"15:00","90s","1mi","400m","1km","open"(lap button)Targets:
"hr:2"→HeartRateZoneAlert,"pace:M:SS-M:SS"(per mile) →SpeedRangeAlert, or noneStrength:
{"name": …, "exercises": [{"category": "PLANK", "exercise": "SIDE_PLANK", "sets": 3, "hold": "30s", "rest_s": 30}]}— rep sets are open steps you end with the Action button; holds and rest are timed
Things WorkoutKit decides, not us
Scheduled-workout cap. WorkoutKit only holds
WorkoutScheduler.maxAllowedScheduledWorkoutCountworkouts at once (shared with anything HoodaRun schedules itself). HoodaRun fills free slots with the soonest sessions and reports the rest aspending; each sync rolls forward, so open the app every few days during a long block.One warmup and one cooldown. A leading
warmupand trailingcooldownbecome WorkoutKit's; everything else becomes interval blocks, in order.Strength is scheduled as Traditional Strength Training, falling back to Functional Strength if the Watch doesn't support it as a custom workout.
Building on the web (yashhooda.ai + CloudKit)
The HoodaRun section on yashhooda.ai is a workout builder that saves to the
visitor's own private CloudKit database after they sign in with their Apple
ID (CloudKit JS). HoodaRun reads those records alongside the MCP feed, schedules
them with the same code path, and writes scheduledRev back so the site shows
On Watch. Web workouts use the identical hooda.workouts.custom/v1 JSON.
One-time setup in CloudKit Console:
Container
iCloud.ai.yashhooda.HoodaRun(or changecontainerIDinCloudKitPlanStore.swiftandCK.containerin the site section to match HoodaRun's real container). Add it under HoodaRun › Signing & Capabilities › iCloud › CloudKit.Record type
PlannedWorkoutwith String fieldsdate,time,name,sport,workoutJSON,rev,source,scheduledRev, and Date/TimesyncedAt.Indexes:
dateQueryable + Sortable,recordNameQueryable.Deploy the schema to Production.
API Access › new web API token, sign-in callback/allowed origin
https://yashhooda.ai. Paste it intoCK.apiTokenin the site section; until then the builder runs in preview mode (build + copy JSON, no sign-in).
Apple Watch Ultra 4 / watchOS 27 notes
WorkoutKit schedules via the iPhone companion, not from a Python process running on the Watch. The JSON schema is watch-model neutral. Ultra 4/watchOS 27 are target devices, not hardware verified in this repository; a paired iPhone, signed HoodaRun app and actual device tests are required.
The local server never directly reads Apple Health or touches a watch without the app syncing. Published feed items may remain pending due to WorkoutKit's schedule cap. See platform details.
HoodaRun integration
Add the three files in ios/HoodaRun/ to the HoodaRun target (iOS 18+ /
watchOS 11+, WorkoutKit, CloudKit):
MCPPlanSync.swift— feed models, JSON →CustomWorkout, the sync actorCloudKitPlanStore.swift— reads/marks workouts saved from yashhooda.aiMCPSyncSettingsView.swift— source picker + Sync now
Then two insertions in the app:
// In HoodaRun's settings list:
NavigationLink("Claude plans") { MCPSyncSettingsView() }
// In the App struct, sync whenever HoodaRun comes to the foreground:
@Environment(\.scenePhase) private var scenePhase
// ...on the root WindowGroup content:
.onChange(of: scenePhase) { _, phase in
if phase == .active { Task { try? await MCPPlanSync.shared.sync() } }
}The sync only ever removes workouts it scheduled itself (tracked by id), so workouts made in HoodaRun's own builder are never touched.
License
MIT
Available Tools
14 toolsassess_fitnessB
Read-only: summarise recent training (from Strava), and size the gap to a marathon goal.
With goal_time ('3:15:00') and race_date ('2027-04-19') also returns what
the goal asks for, what a safe ramp reaches, and — when those disagree — the
goal this runway does support.
Inputs: goal_time, race_date, weeks_of_history, current_weekly_miles.
clear_scheduledA
Remove every feed workout in [start_date, end_date] inclusive.
Destructive. Defaults to dry_run=True — call again with dry_run=False to commit. Only touches workouts this server published; workouts made in HoodaRun's own builder are never removed.
Inputs: end_date (required), start_date (required), dry_run.
create_hs_track_planB
Build a high-school track block: 800 / 1600 / 3200 / 5k.
goal_time defaults to the barrier — sub-2:00 800, sub-4:30 1600, sub-10:00
3200, sub-15:00 5K. With current_pr the report sizes the gap honestly.
training_age ('new' | 'developing' | 'experienced') sets a hard mileage
ceiling; Sunday is always rest; no doubles; nutrition guidance is
adequacy-only. Take it to the athlete's school coach before running it.
dry_run=True validates without publishing.
Inputs: time, event, weeks, dry_run, +7 more.
create_marathon_planA
Build a marathon block for ANY race and ANY goal, scaled to current fitness.
Reads recent Strava history (unless use_strava_history=False or
current_weekly_miles is supplied), compares it to what the goal asks for,
and ramps from where the runner actually is — 8%/week ceiling,
3-up-1-down, 3-week taper, ending on race day. If the goal doesn't fit the
runway, the plan targets the goal that does and says so; force_goal=True
overrides. Two strength sessions a week; weekly fuelling/recovery guidance
in the report. dry_run=True validates without publishing.
Inputs: goal_time (required), race_date (required), race_name (required), time, +8 more.
create_strength_workoutB
Create a guided strength/core workout and schedule it for a date.
Each exercise takes reps (an open set you end with the Action button) or
hold ('45s', timed), plus sets and rest_s. Each set shows on the Watch by
name, e.g. 'Romanian Deadlift · 8 reps', with a timed rest between sets.
Inputs: spec (required), schedule_date (required), time.
create_training_planA
Publish a multi-week plan you wrote yourself.
items is a list of {"date": "YYYY-MM-DD", "spec": } — a spec with exercises is strength, otherwise running.
Re-pushing the same plan name updates it in place, and (replace=True) drops
sessions from the earlier push that are no longer in the plan.
dry_run=True validates and reports without publishing.
Inputs: name (required), items (required), time, dry_run, +1 more.
create_workoutA
Create a structured running workout and schedule it for a date.
schedule_date is 'YYYY-MM-DD'; time is optional 24h 'HH:MM'. The same name on the same date overwrites instead of duplicating. Targets: 'hr:2' (HR zone) or 'pace:6:35-6:55' (min/mi window); durations '15:00', '1mi', '400m', 'open'. Set spec.location='indoor' for treadmill sessions.
Inputs: spec (required), schedule_date (required), time.
list_activitiesB
Recent activities from Strava (read-only): id, name, type, start, distance, HR.
Inputs: days, limit.
list_marathonsC
Marathons in the catalogue whose next running falls inside the window. Dates come from each race's scheduling rule — confirm with the organiser.
Inputs: max_weeks_out, min_weeks_out.
list_scheduledB
Workouts in the feed (optionally within [start_date, end_date]), each marked with whether HoodaRun has confirmed it's scheduled on the Watch.
Inputs: end_date, start_date.
pick_random_marathonC
Pick a marathon at random from the catalogue — the 'surprise me' path.
Inputs: seed, max_weeks_out, min_weeks_out.
preview_workoutA
Compile a workout or strength spec and return the exact WorkoutKit-shaped JSON HoodaRun would receive, without publishing anything.
Inputs: spec (required).
sync_statusA
What HoodaRun last reported: when it synced, what's on the Watch, what's waiting for a free slot (WorkoutKit caps scheduled workouts), and errors.
Inputs: no named parameters.
unschedule_workoutA
Remove one workout from the feed by id; HoodaRun removes it from the Watch at its next sync.
Inputs: workout_id (required).
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 14 tools
Most tools have clearly distinct purposes: feed reads (list_scheduled, sync_status), feed mutations (unschedule_workout, clear_scheduled), and creation tools are well separated. The only real overlap is among the plan builders (create_training_plan vs create_marathon_plan vs create_hs_track_plan) — an agent could plausibly reach for the generic plan publisher when a marathon or track block is wanted, though the descriptions do clarify.
Almost all names are snake_case verb_noun (create_workout, list_scheduled, preview_workout, clear_scheduled). Minor deviations: sync_status is noun-only and pick_random_marathon is verb_adj_noun, but overall the pattern is predictable.
14 tools sit comfortably in the ideal band for a workout scheduling/generation server. Each tool covers a genuine capability — read feed, mutate feed, preview, sync, plan generation, catalogue — with no filler.
Lifecycle coverage is strong: create, preview/validate, list, unschedule single, clear range, plus sync status and plan publishing. Gaps are minor — no explicit get-by-id or update-existing-workout tool, though same-name/date overwrite and unschedule-then-recreate provide workarounds.
Maintenance
Related MCP Connectors
Your AI writes training plans that arrive as structured workouts on iPhone and Apple Watch.
tri-train is a training platform for triathletes that syncs activities from Strava and Garmin and exposes them to Claude through a remote MCP server. Ask Claude how your last block went, why your run pace dropped, or whether you are fresh enough for Sunday's race, and it answers from your real data: activities with laps and grade-adjusted pace, weekly load and the performance management chart (CTL/ATL/TSB), sleep, HRV and resting heart rate, and your race calendar with predictions. Claude can also act: create and adjust planned workouts with structured intervals, push them to your Garmin watch, add races and write notes. Every change is journaled and can be undone. Read and write access are separate OAuth scopes, and the server enforces them on every call. Built and hosted in Switzerland by a triathlete; data stays on our own servers (Swiss revDSG / GDPR).
Connect Claude to your Intervals.icu watch data for fitness, workout review, and plan writing.
Workout journal Claude and ChatGPT write to: log workouts, get a program, see your progress.
Related MCP Servers
- AlicenseNot gradedqualityNot gradedmaintenanceLets Claude read a runner's recent activities and wellness signals — sleep, HRV, resting heart rate — and then design, schedule, edit, or delete structured workouts. Planned sessions are written to an intervals.icu calendar that syncs through Garmin Connect onto the user's watch, without exposing Garmin credentials.MIT
- AlicenseNot gradedqualityCmaintenanceLets Claude read recent Garmin training activities and wellness signals such as sleep, HRV, and resting heart rate, then design and push structured workouts onto the user's calendar for syncing to their watch. Workouts can also be listed, edited, and deleted through natural language.MIT
- AlicenseNot gradedqualityNot gradedmaintenanceLets Claude read recent training history and wellness signals, then design structured workouts and write, edit, or remove them on an intervals.icu calendar so they sync through to a Garmin watch. It brokers all of this through intervals.icu's documented API, so no Garmin credentials are ever shared.MIT
- AlicenseNot gradedqualityNot gradedmaintenanceEnables Claude to read recent activities and wellness data, then design, create, update, and delete structured workouts that sync through intervals.icu to Garmin Connect and the user's watch.MIT