I wanted to give Codex on my Mac one simple job: inspect my recent rides and plan a sensible workout for tomorrow. Not a suggestion in a chat that I would have to rebuild by hand. I wanted the finished workout in the Workout app on my Apple Watch.
The data lives in HealthKit on the iPhone. Codex runs on the Mac. Ask My Health connects the two through a local MCP server.
From prompt to Apple Watch workout
The flow looks like this:
- Ask My Health starts an MCP endpoint on the iPhone.
- Codex connects and reads the descriptions of the available HealthKit and WorkoutKit tools.
- Codex queries the selected training period.
- Codex reports measured, derived and missing values separately.
- Codex builds a workout with warm-up, work intervals, recovery steps and targets, then asks Ask My Health to validate the plan.
- Only after I confirm it does Ask My Health schedule the workout through WorkoutKit.
It then appears in the Workout app on Apple Watch.
The detour through the data is intentional. I did not want an AI coach that invents an arbitrary interval session from a short prompt. Before building a plan, Codex should show the recent sessions, flag gaps in the data and state its assumptions.
Volume is not training load
For cycling, “three rides, 180 kilometers” is not much of an analysis. I need several layers:
- Volume: duration, distance and frequency describe how much training happened.
- External load: power, pace or elevation describe the mechanical work.
- Internal load: heart rate and derived measures such as TRIMP describe the physiological response.
- Recovery context: HRV and resting heart rate can add context when measured consistently under comparable conditions.
These values are not interchangeable. Two 90-minute rides can have completely different power profiles. The same power can also mean something different depending on the heart-rate response. Research likewise shows that relationships between internal and external load measures depend on the metric and training mode.
Ask My Health therefore reports these layers separately. There is no single score hiding the differences. And if power, heart rate or cadence is unavailable, that is not a zero. The data is missing.
Missing data is a finding
I get picky about TSS. A power-based calculation requires duration, Normalized Power, Intensity Factor and FTP. The formula documented by TrainingPeaks is:
TSS = (seconds × NP × IF) / (FTP × 3600) × 100
Without the power series, or with stale FTP, I do not want a TSS
value. A number with two decimal places would only be precisely
formatted nonsense. training_compute_load_metrics returns
the missing inputs instead.
The same applies to HRV. A single value does not decide whether tomorrow should be hard or easy. Deviations from a personal trend under comparable measurement conditions are more interesting. Even the research has not settled on the best baseline and averaging methods.
I use HRV and resting heart rate as context, not as a switch. Fatigue, illness symptoms and pain are not fully captured in HealthKit anyway.
Querying HealthKit through MCP
Codex on the Mac cannot query HealthKit on the iPhone directly. Ask My Health exposes selected operations as MCP tools:
healthkit_get_authorization_status
healthkit_request_read_authorization
healthkit_query_workouts
training_compute_load_metrics
When Codex connects, it sees the tool descriptions and JSON schemas. Each request names an operation and its arguments, such as workout type and date range. Ask My Health checks the token, runs the corresponding HealthKit query on the iPhone and returns the result as an MCP response.
That is all the access Codex gets. HealthKit remains the permission boundary. If a data category is unavailable or not permitted on the iPhone, the MCP server cannot return it.
Connecting it to Codex
Ask My Health exposes a Streamable HTTP endpoint on the local
network. Its configuration lives globally in
~/.codex/config.toml or in a project-scoped
.codex/config.toml:
[mcp_servers.ask_my_health]
url = "http://<iphone-local-ip>:8787/mcp"
bearer_token_env_var = "ASK_MY_HEALTH_TOKEN"
default_tools_approval_mode = "prompt"Streamable HTTP, bearer tokens and
default_tools_approval_mode are covered in the Codex MCP
documentation. With prompt, Codex asks before each tool
call by default.
The TOML file contains only the environment variable name. The Codex process reads the token from its environment. The endpoint uses HTTP, not HTTPS. The token authenticates the client but does not encrypt the transport. I therefore use the server only on a trusted local network and rotate the token after demos.
The prompt as an analysis protocol
My prompt reads more like an analysis request than a coaching question:
Use only the ask_my_health MCP server.
1. Check the HealthKit authorization state.
2. Query only cycling workouts from the last 14 days.
3. Compare the most recent 7 days with the previous 7 days.
4. List duration, distance, power, heart rate and cadence for each
session where available.
5. Separate measured, derived and missing values.
6. Compute load metrics only when all required inputs exist and
name the threshold values used.
7. Propose exactly one structured workout for tomorrow and explain
it from the available data.
8. Validate the plan with WorkoutKit.
Show the data, assumptions and complete plan before any write.
Do not call workoutkit_schedule_workout until I explicitly approve it.
Do not invent missing values or provide medical advice.
The two seven-day blocks are only a quick comparison of volume and intensity. For longer-term training decisions, I would include wider date ranges, training phases and personal threshold values.
Turning the proposal into a workout
The analysis uses read-only HealthKit tools. Creating the workout uses a separate set of WorkoutKit tools:
workoutkit_get_capabilities
workoutkit_get_authorization_status
workoutkit_validate_workout_plan
workoutkit_schedule_workout
Codex first sends the warm-up, work intervals, recovery steps and
targets to workoutkit_validate_workout_plan. The tool
checks whether the proposal can be represented as a WorkoutKit plan.
Nothing is scheduled yet.
workoutkit_schedule_workout can run only after I
explicitly approve it. The flow therefore has two stops: Codex asks
before the tool call, and Ask My Health shows the prepared plan again on
the iPhone. Only then does the plan enter Apple's system flow and
continue to Apple Watch.
Local does not mean offline
Ask My Health does not need its own cloud backend for this connection. “Local MCP server” only describes where the endpoint runs.
Tool results become part of the current Codex task. How they are stored and retained depends on the Codex surface, account and settings in use. I therefore query only the sport, date range and fields I need for the plan.
That is the interesting part for me: Codex can plan a real Apple Watch workout while keeping the underlying data visible. If power, FTP or HRV is missing, the proposal has to say so. I can then accept, change or reject the workout.