LeaderboardBack to leaderboard — loading return destination
Opening report…
Loading report overview
M
MCP server
server.example.com/mcp
Tools and resources for your MCP client, with a public quality report and detailed checks.
Protocol
MCP 2025-11-25
Server version
1.0.0
Website
server.example.com
Supports
Tools · Resources
Overall score
90 /100
How is this score calculated?
Server metadata
Loading server metadata
Reviewed90/100
Discoverability
Loading discoverability
Reviewed90/100
Protocol
Loading protocol
Reviewed90/100
Tools
Loading tools
Reviewed90/100
MCP Apps
Loading mcp apps
Reviewed90/100
Tool documentation quality
Loading tool documentation quality
Reviewed90/100
LeaderboardBack to leaderboard — loading return destination
A
athletedata
mcp.athletedata.health/mcp
Reads one athlete's training, recovery, sleep and nutrition data from Garmin, Oura, WHOOP, Hevy and 35 more fitness apps, merged into one deduplicated analytics layer: sessions with splits and streams, training load and fitness/fatigue/form, HRV and sleep trends, power and pace curves, race predictions and the training plan. With write access it also logs food and sends structured workouts to the athlete's watch, Zwift, Hevy, intervals.icu or TrainingPeaks.
Reads one athlete's training, recovery, sleep and nutrition data from Garmin, Oura, WHOOP, Hevy and 35 more fitness apps, merged into one deduplicated analytics layer: sessions with splits and streams, training load and fitness/fatigue/form, HRV and sleep trends, power and pace curves, race predictions and the training plan. With write access it also logs food and sends structured workouts to the athlete's watch, Zwift, Hevy, intervals.icu or TrainingPeaks.
This connection serves one athlete: Julian Flieller ([email protected]). Every tool reads only that athlete's account. Each athletedata connection in a client belongs to a different athlete, so numbers from two connections are two people's and are never merged; the connection to use is the one whose athlete the question is about.
Cross-source tools (get_analytics_summary, get_training_trends, get_pmc_status, get_daily_metrics, get_sleep, get_activity_detail, food_log_get) merge every connected app with duplicate sessions counted once, and answer load, fitness, sleep and trend questions. <provider>_* tools read one app's own records. Fitness / fatigue / form (CTL / ATL / TSB) come from get_pmc_status.
Every supported app's <provider>_* tools are listed whether or not it is connected; one for an app that is not connected returns the link to connect it. Connected when this conversation started: Hevy, Withings, WHOOP, Garmin, Apple Health, Wahoo and Google Calendar.
Zwift has no tools of its own; its sessions are in the cross-source tools and in terra_get_activities({provider}).
Planned workouts inside Zwift are not readable here, only completed sessions; a plan another tool returns is a different plan.
The athlete's athletedata coach plan is readable with get_planned_workouts.
Food from Apple Health is read with food_log_get; an entry flagged whole_day_total is a day sum, so its time and meal type are placeholders.
push_workout_to_watch checks the live connections on each call, so a target missing from its description can still be reachable (connected at setup: garmin, zwift). A watch that is full: list_workouts_on_watch, then delete_workout_from_watch for past ones, before pushing again.
athletedata_subscription_status returns the apps connected right now and the full notes for this connection (connection_notes).
Discoverability
1/6 passed1 of 6 passed17/100
Client listings and public setup information.
mcp.json or server-card.json
Either mcp.json or an MCP Server Card declaring this endpoint passes.
The MCP Server Card declares this server's exact endpoint.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Reports the athletedata subscription state (plan active or not, billing status and the next billing or end date), the apps connected to this account right now, how to reload a stale tool list, and connection_notes: which tool reads each connected app's data and what this connection can and cannot do. Call this if the athlete asks about their plan or billing, or if training-data tools seem to be missing from this connection.
The description explains the capability, when to use it, and what to expect.
Inputs100/100
This tool declares no inputs, so argument descriptions are not needed.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "athletedata_subscription_status",
"title": "athletedata Subscription Status",
"description": "Reports the athletedata subscription state (plan active or not, billing status and the next billing or end date), the apps connected to this account right now, how to reload a stale tool list, and connection_notes: which tool reads each connected app's data and what this connection can and cannot do. Call this if the athlete asks about their plan or billing, or if training-data tools seem to be missing from this connection.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {}
},
"annotations": {
"title": "athletedata Subscription Status",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description94/100
Get paginated list of completed Hevy strength workouts with full exercise breakdowns: every set's reps, weight, RPE, and rest. Use when reviewing recent strength training, computing volume per muscle group, or checking progressive overload (combine with hevy_get_exercise_progress for one-exercise trends). PAGING: Hevy caps pageSize at 10 on this endpoint and 400s above it, so a multi-week review is several calls - walk `page` 1,2,3... using `page_count`; a bigger page is not available. Example: hevy_get_workouts({ page: 2 }). DUMBBELL WEIGHT AMBIGUITY: Hevy stores the literal number the athlete typed and computes volume as weight_kg × reps with no automatic per-hand doubling. For a dumbbell exercise, weight_kg may be the weight of ONE dumbbell (per-hand) or the combined pair total - the convention depends on how that specific athlete logs and can differ between exercises. It is not reliably per-hand, and no fixed bilateral-vs-unilateral rule applies. When a dumbbell weight looks inconsistent with a known per-hand reference, the athlete's convention is unknown until they confirm it, and a conversion made on an assumed convention can double or halve the load. A load written to a plan session (weightKg) is always per dumbbell, whatever the logging habit. Reps are always the total reps performed - never doubled or halved.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
page
Page number (default 1)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
pageSize
Results per page (default 10, max 10 - Hevy 400s above that; values over 10 are clamped)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "hevy_get_workouts",
"title": "Hevy: Get Workouts",
"description": "Get paginated list of completed Hevy strength workouts with full exercise breakdowns: every set's reps, weight, RPE, and rest. Use when reviewing recent strength training, computing volume per muscle group, or checking progressive overload (combine with hevy_get_exercise_progress for one-exercise trends). PAGING: Hevy caps pageSize at 10 on this endpoint and 400s above it, so a multi-week review is several calls - walk `page` 1,2,3... using `page_count`; a bigger page is not available. Example: hevy_get_workouts({ page: 2 }). DUMBBELL WEIGHT AMBIGUITY: Hevy stores the literal number the athlete typed and computes volume as weight_kg × reps with no automatic per-hand doubling. For a dumbbell exercise, weight_kg may be the weight of ONE dumbbell (per-hand) or the combined pair total - the convention depends on how that specific athlete logs and can differ between exercises. It is not reliably per-hand, and no fixed bilateral-vs-unilateral rule applies. When a dumbbell weight looks inconsistent with a known per-hand reference, the athlete's convention is unknown until they confirm it, and a conversion made on an assumed convention can double or halve the load. A load written to a plan session (weightKg) is always per dumbbell, whatever the logging habit. Reps are always the total reps performed - never doubled or halved.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"page": {
"type": "number",
"description": "Page number (default 1)"
},
"pageSize": {
"type": "number",
"description": "Results per page (default 10, max 10 - Hevy 400s above that; values over 10 are clamped)"
}
}
},
"annotations": {
"title": "Hevy: Get Workouts",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description95/100
Get full detail for one Hevy workout by ID: every exercise, every set's weight/reps/RPE/rest, total duration, notes. Use when the athlete asks about a specific session you already located via hevy_get_workouts (e.g. "how was Tuesday's push day?"). Example: hevy_get_workout({ id: "abc-123" }). DUMBBELL WEIGHT AMBIGUITY: Hevy stores the literal number the athlete typed and computes volume as weight_kg × reps with no automatic per-hand doubling. For a dumbbell exercise, weight_kg may be the weight of ONE dumbbell (per-hand) or the combined pair total - the convention depends on how that specific athlete logs and can differ between exercises. It is not reliably per-hand, and no fixed bilateral-vs-unilateral rule applies. When a dumbbell weight looks inconsistent with a known per-hand reference, the athlete's convention is unknown until they confirm it, and a conversion made on an assumed convention can double or halve the load. A load written to a plan session (weightKg) is always per dumbbell, whatever the logging habit. Reps are always the total reps performed - never doubled or halved.
The description explains the capability, when to use it, and what to expect.
Inputs98/100
Input documentation gives an agent enough context to populate the arguments.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "hevy_get_workout",
"title": "Hevy: Get Workout",
"description": "Get full detail for one Hevy workout by ID: every exercise, every set's weight/reps/RPE/rest, total duration, notes. Use when the athlete asks about a specific session you already located via hevy_get_workouts (e.g. \"how was Tuesday's push day?\"). Example: hevy_get_workout({ id: \"abc-123\" }). DUMBBELL WEIGHT AMBIGUITY: Hevy stores the literal number the athlete typed and computes volume as weight_kg × reps with no automatic per-hand doubling. For a dumbbell exercise, weight_kg may be the weight of ONE dumbbell (per-hand) or the combined pair total - the convention depends on how that specific athlete logs and can differ between exercises. It is not reliably per-hand, and no fixed bilateral-vs-unilateral rule applies. When a dumbbell weight looks inconsistent with a known per-hand reference, the athlete's convention is unknown until they confirm it, and a conversion made on an assumed convention can double or halve the load. A load written to a plan session (weightKg) is always per dumbbell, whatever the logging habit. Reps are always the total reps performed - never doubled or halved.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"id"
],
"properties": {
"id": {
"type": "string",
"description": "The workout's `id` from hevy_get_workouts."
}
}
},
"annotations": {
"title": "Hevy: Get Workout",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description92/100
Get longitudinal progress for ONE exercise: every set the athlete has logged across all workouts, with weight/reps/estimated 1RM trend lines. Use when assessing whether progressive overload is working ("am I getting stronger on bench?") or to check the last working set before prescribing the next one. Need exercise_template_id from hevy_get_exercise_templates first. Example: hevy_get_exercise_progress({ exercise_template_id: "X4LK4N", pageSize: 20 }). DUMBBELL WEIGHT AMBIGUITY: Hevy stores the literal number the athlete typed and computes volume as weight_kg × reps with no automatic per-hand doubling. For a dumbbell exercise, weight_kg may be the weight of ONE dumbbell (per-hand) or the combined pair total - the convention depends on how that specific athlete logs and can differ between exercises. It is not reliably per-hand, and no fixed bilateral-vs-unilateral rule applies. When a dumbbell weight looks inconsistent with a known per-hand reference, the athlete's convention is unknown until they confirm it, and a conversion made on an assumed convention can double or halve the load. A load written to a plan session (weightKg) is always per dumbbell, whatever the logging habit. Reps are always the total reps performed - never doubled or halved.
The description explains the capability, when to use it, and what to expect.
Inputs95/100
Input documentation gives an agent enough context to populate the arguments.
exercise_template_id
Which Hevy exercise this is: its template id from hevy_get_exercise_templates, or as it appears on an exercise in hevy_get_workout / hevy_get_routine.
Clear enough to supply this argument.
page
Page number (default 1)
Clear enough to supply this argument.
pageSize
Results per page (default 10)
Clear enough to supply this argument.
start_date
Earliest date to include (ISO 8601, e.g. 2024-01-01). Omitted, no start bound is sent.
Clear enough to supply this argument.
end_date
Latest date to include (ISO 8601, e.g. 2024-12-31). Omitted, no end bound is sent.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "hevy_get_exercise_progress",
"title": "Hevy: Get Exercise Progress",
"description": "Get longitudinal progress for ONE exercise: every set the athlete has logged across all workouts, with weight/reps/estimated 1RM trend lines. Use when assessing whether progressive overload is working (\"am I getting stronger on bench?\") or to check the last working set before prescribing the next one. Need exercise_template_id from hevy_get_exercise_templates first. Example: hevy_get_exercise_progress({ exercise_template_id: \"X4LK4N\", pageSize: 20 }). DUMBBELL WEIGHT AMBIGUITY: Hevy stores the literal number the athlete typed and computes volume as weight_kg × reps with no automatic per-hand doubling. For a dumbbell exercise, weight_kg may be the weight of ONE dumbbell (per-hand) or the combined pair total - the convention depends on how that specific athlete logs and can differ between exercises. It is not reliably per-hand, and no fixed bilateral-vs-unilateral rule applies. When a dumbbell weight looks inconsistent with a known per-hand reference, the athlete's convention is unknown until they confirm it, and a conversion made on an assumed convention can double or halve the load. A load written to a plan session (weightKg) is always per dumbbell, whatever the logging habit. Reps are always the total reps performed - never doubled or halved.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"exercise_template_id"
],
"properties": {
"page": {
"type": "number",
"description": "Page number (default 1)"
},
"end_date": {
"type": "string",
"description": "Latest date to include (ISO 8601, e.g. 2024-12-31). Omitted, no end bound is sent."
},
"pageSize": {
"type": "number",
"description": "Results per page (default 10)"
},
"start_date": {
"type": "string",
"description": "Earliest date to include (ISO 8601, e.g. 2024-01-01). Omitted, no start bound is sent."
},
"exercise_template_id": {
"type": "string",
"description": "Which Hevy exercise this is: its template id from hevy_get_exercise_templates, or as it appears on an exercise in hevy_get_workout / hevy_get_routine."
}
}
},
"annotations": {
"title": "Hevy: Get Exercise Progress",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
The athlete's saved Hevy routines: templates for future sessions (completed sessions are workouts, a separate concept). Use to look up a routine id for hevy_update_routine, plan a progression or exercise swap, or audit which programs are active. PAGING: Hevy caps pageSize at 10 here; walk `page` up to `page_count`. Example: hevy_get_routines({ page: 2 }). Every routine on a page comes back with its id, title and folder; when a page is too large to send whole, later routines carry `exercises_omitted: true` (body not sent, not empty) and the payload leads with `routine_list_note`, and hevy_get_routine({ id }) returns the full body. This is Hevy's server-side state read live: a routine listed here that the athlete cannot see in the app is an app-refresh or folder question (`folder_id` null = outside every folder), not a failed write. Dumbbell `weight_kg` is the number the athlete typed, per hand or pair total depending on their own habit; reps are total reps.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
page
Page number (default 1)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
pageSize
Results per page (default 10, max 10 - Hevy 400s above that; values over 10 are clamped)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "hevy_get_routines",
"title": "Hevy: Get Routines",
"description": "The athlete's saved Hevy routines: templates for future sessions (completed sessions are workouts, a separate concept). Use to look up a routine id for hevy_update_routine, plan a progression or exercise swap, or audit which programs are active. PAGING: Hevy caps pageSize at 10 here; walk `page` up to `page_count`. Example: hevy_get_routines({ page: 2 }). Every routine on a page comes back with its id, title and folder; when a page is too large to send whole, later routines carry `exercises_omitted: true` (body not sent, not empty) and the payload leads with `routine_list_note`, and hevy_get_routine({ id }) returns the full body. This is Hevy's server-side state read live: a routine listed here that the athlete cannot see in the app is an app-refresh or folder question (`folder_id` null = outside every folder), not a failed write. Dumbbell `weight_kg` is the number the athlete typed, per hand or pair total depending on their own habit; reps are total reps.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"page": {
"type": "number",
"description": "Page number (default 1)"
},
"pageSize": {
"type": "number",
"description": "Results per page (default 10, max 10 - Hevy 400s above that; values over 10 are clamped)"
}
}
},
"annotations": {
"title": "Hevy: Get Routines",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
The athlete's Hevy exercise template library: exercise IDs, names, muscle groups and equipment. Use it to look up the exercise_template_id values needed to log workouts, create routines or query exercise progress. The library is large (often 400-500+ entries). The athlete's existing routines (hevy_get_routines) already carry the template IDs of the lifts they use, so this library is mainly needed for exercises not found there. pageSize defaults to 50 (max 100); 50 is the largest page that fits one response. Every id on a page is always returned: when a page is too large to serve whole, the later entries come back as id and title only, with "details_omitted": true, and the payload starts with template_list_note. Those ids are complete and usable; the muscle-group and equipment details for one of them come from hevy_get_exercise_template({ id }).
The description explains the capability, when to use it, and what to expect.
Inputs88/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
page
Page number (default 1)
Clear enough to supply this argument.
pageSize
Results per page (default 50, max 100). 50 returns the library in a few pages; 10 takes many more.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "hevy_get_exercise_templates",
"title": "Hevy: Get Exercise Templates",
"description": "The athlete's Hevy exercise template library: exercise IDs, names, muscle groups and equipment. Use it to look up the exercise_template_id values needed to log workouts, create routines or query exercise progress. The library is large (often 400-500+ entries). The athlete's existing routines (hevy_get_routines) already carry the template IDs of the lifts they use, so this library is mainly needed for exercises not found there. pageSize defaults to 50 (max 100); 50 is the largest page that fits one response. Every id on a page is always returned: when a page is too large to serve whole, the later entries come back as id and title only, with \"details_omitted\": true, and the payload starts with template_list_note. Those ids are complete and usable; the muscle-group and equipment details for one of them come from hevy_get_exercise_template({ id }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"page": {
"type": "number",
"description": "Page number (default 1)"
},
"pageSize": {
"type": "number",
"description": "Results per page (default 50, max 100). 50 returns the library in a few pages; 10 takes many more."
}
}
},
"annotations": {
"title": "Hevy: Get Exercise Templates",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description100/100
Get the lifetime count of Hevy workouts logged. Use when the athlete asks about total session count or you need to know if a paginated query (hevy_get_workouts) has more pages to fetch. Cheap call, no tokens of payload. Returns `workout_count`, the number of workouts on the athlete's Hevy log. Example: hevy_get_workout_count({}).
The description explains the capability, when to use it, and what to expect.
Inputs100/100
This tool declares no inputs, so argument descriptions are not needed.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "hevy_get_workout_count",
"title": "Hevy: Get Workout Count",
"description": "Get the lifetime count of Hevy workouts logged. Use when the athlete asks about total session count or you need to know if a paginated query (hevy_get_workouts) has more pages to fetch. Cheap call, no tokens of payload. Returns `workout_count`, the number of workouts on the athlete's Hevy log. Example: hevy_get_workout_count({}).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {}
},
"annotations": {
"title": "Hevy: Get Workout Count",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description100/100
Log a COMPLETED workout to Hevy. It is for sessions the athlete has already finished, with real start_time/end_time in the past. Hevy will treat it as a completed entry on the athlete's log. APP VISIBILITY: the echo this tool returns IS the confirmation Hevy stored the workout - the Hevy app caches its own log and may need a few minutes or a pull-to-refresh to show it. A workout missing from the app after this echo is app-cache lag (pull-to-refresh shows it), not a failed write; calling this tool again creates a duplicate. It is not for future / planned sessions — Hevy has no concept of a 'scheduled workout' and will show the entry as done. For future or template sessions use hevy_create_routine instead. If the athlete asks you to 'create' or 'add' upcoming sessions in Hevy, that's a routine, not a workout.
The description explains the capability, when to use it, and what to expect.
Inputs91/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
workout › title
The workout's name on the athlete's Hevy log.
Clear enough to supply this argument.
workout › start_time
ISO date string for workout start. Must be in the past — this represents a session that already happened. For future/planned sessions use hevy_create_routine.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
workout › end_time
ISO date string for workout end. Must be in the past.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
workout › is_private
Whether the workout is private (default true)
Clear enough to supply this argument.
workout › description
Free-text notes shown on the workout in Hevy; omit for none.
Which Hevy exercise this is: its template id from hevy_get_exercise_templates, or as it appears on an exercise in hevy_get_workout / hevy_get_routine.
Clear enough to supply this argument.
workout › exercises › items › superset_id
Superset / circuit group. Exercises that share the same number form one group in Hevy; null = a straight exercise. This is a FULL-REPLACEMENT PUT: re-send the group on every member from hevy_get_routine. A routine update that omits a group the stored routine has is refused; send null to unpair on purpose.
Clear enough to supply this argument.
workout › exercises › items › supersets_id
Alias for superset_id: hevy_get_routine returns the group as `supersets_id`, so a fetched exercise can be sent back as-is. superset_id wins if both are given.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
workout › exercises › items › notes
The exercise's notes in the Hevy app. Copy from hevy_get_workout or they are erased.
Clear enough to supply this argument.
workout › exercises › items › sets › items › type
Set kind in Hevy. Omitted, it is sent as 'normal'; warmup, dropset and failure change how Hevy counts the set.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Rate of perceived exertion the athlete logged on this set (Hevy accepts 6, 7, 7.5, 8, 8.5, 9, 9.5, 10). This is a FULL-REPLACEMENT write: copy each set's rpe from hevy_get_workout or it is erased.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Hevy's custom set metric (steps / floors). Copy it from hevy_get_workout when present; omit otherwise.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
allow_duplicate
Set true only when the athlete confirms they really did two overlapping/same-time sessions. Without it, a create whose time window overlaps an already-logged Hevy workout is refused (duplicate-log protection — Hevy v1 has no API delete, so a duplicate is athlete-visible until manually removed).
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "hevy_create_workout",
"title": "Hevy: Create Workout",
"description": "Log a COMPLETED workout to Hevy. It is for sessions the athlete has already finished, with real start_time/end_time in the past. Hevy will treat it as a completed entry on the athlete's log. APP VISIBILITY: the echo this tool returns IS the confirmation Hevy stored the workout - the Hevy app caches its own log and may need a few minutes or a pull-to-refresh to show it. A workout missing from the app after this echo is app-cache lag (pull-to-refresh shows it), not a failed write; calling this tool again creates a duplicate. It is not for future / planned sessions — Hevy has no concept of a 'scheduled workout' and will show the entry as done. For future or template sessions use hevy_create_routine instead. If the athlete asks you to 'create' or 'add' upcoming sessions in Hevy, that's a routine, not a workout.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"workout"
],
"properties": {
"workout": {
"type": "object",
"required": [
"title",
"start_time",
"end_time",
"exercises"
],
"properties": {
"title": {
"type": "string",
"description": "The workout's name on the athlete's Hevy log."
},
"end_time": {
"type": "string",
"description": "ISO date string for workout end. Must be in the past."
},
"exercises": {
"type": "array",
"items": {
"type": "object",
"required": [
"exercise_template_id",
"sets"
],
"properties": {
"sets": {
"type": "array",
"items": {
"type": "object",
"properties": {
"rpe": {
"anyOf": [
{
"anyOf": [
{
"type": "number",
"const": 6
},
{
"type": "number",
"const": 7
},
{
"type": "number",
"const": 7.5
},
{
"type": "number",
"const": 8
},
{
"type": "number",
"const": 8.5
},
{
"type": "number",
"const": 9
},
{
"type": "number",
"const": 9.5
},
{
"type": "number",
"const": 10
}
]
},
{
"type": "null"
}
],
"description": "Rate of perceived exertion the athlete logged on this set (Hevy accepts 6, 7, 7.5, 8, 8.5, 9, 9.5, 10). This is a FULL-REPLACEMENT write: copy each set's rpe from hevy_get_workout or it is erased."
},
"reps": {
"type": "number",
"description": "Reps performed on this set."
},
"type": {
"enum": [
"normal",
"warmup",
"dropset",
"drop",
"failure"
],
"type": "string",
"description": "Set kind in Hevy. Omitted, it is sent as 'normal'; warmup, dropset and failure change how Hevy counts the set."
},
"weight_kg": {
"type": "number",
"description": "Load lifted on this set, in kg; omit for bodyweight."
},
"custom_metric": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Hevy's custom set metric (steps / floors). Copy it from hevy_get_workout when present; omit otherwise."
},
"distance_meters": {
"type": "number",
"description": "Metres, for a distance set (a carry, a row)."
},
"duration_seconds": {
"type": "number",
"description": "Seconds, for a timed set (a hold or a cardio interval)."
}
}
}
},
"notes": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The exercise's notes in the Hevy app. Copy from hevy_get_workout or they are erased."
},
"superset_id": {
"anyOf": [
{
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
{
"type": "null"
}
],
"description": "Superset / circuit group. Exercises that share the same number form one group in Hevy; null = a straight exercise. This is a FULL-REPLACEMENT PUT: re-send the group on every member from hevy_get_routine. A routine update that omits a group the stored routine has is refused; send null to unpair on purpose."
},
"supersets_id": {
"anyOf": [
{
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
{
"type": "null"
}
],
"description": "Alias for superset_id: hevy_get_routine returns the group as `supersets_id`, so a fetched exercise can be sent back as-is. superset_id wins if both are given."
},
"exercise_template_id": {
"type": "string",
"description": "Which Hevy exercise this is: its template id from hevy_get_exercise_templates, or as it appears on an exercise in hevy_get_workout / hevy_get_routine."
}
}
}
},
"is_private": {
"type": "boolean",
"description": "Whether the workout is private (default true)"
},
"start_time": {
"type": "string",
"description": "ISO date string for workout start. Must be in the past — this represents a session that already happened. For future/planned sessions use hevy_create_routine."
},
"description": {
"type": "string",
"description": "Free-text notes shown on the workout in Hevy; omit for none."
}
},
"description": "Workout data"
},
"allow_duplicate": {
"type": "boolean",
"description": "Set true only when the athlete confirms they really did two overlapping/same-time sessions. Without it, a create whose time window overlaps an already-logged Hevy workout is refused (duplicate-log protection — Hevy v1 has no API delete, so a duplicate is athlete-visible until manually removed)."
}
}
},
"annotations": {
"title": "Hevy: Create Workout",
"readOnlyHint": false,
"openWorldHint": true,
"idempotentHint": false,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description100/100
Update an existing workout in Hevy. Use this to correct logged exercises, sets, weights, or reps after a workout has been saved. The workout body is a FULL REPLACEMENT — it replaces the whole stored workout, so it is hevy_get_workout's current object with the correction applied; a body rebuilt from memory silently overwrites real sets with wrong ones (the #1 Hevy trust failure). The result echoes what Hevy now stores, so it shows whether the stored workout matches what the athlete asked for. APP VISIBILITY: that echo IS the confirmation Hevy stored the change - the Hevy app caches its own copy and may need a few minutes or a pull-to-refresh to show it. A correction missing from the app after this echo is app-cache lag, not a failed write (hevy_get_workout re-reads the stored state); re-issuing the write changes nothing, and logging a second workout creates a duplicate.
The description explains the capability, when to use it, and what to expect.
Inputs98/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
id
The workout's `id` from hevy_get_workouts / hevy_get_workout.
Clear enough to supply this argument.
workout › title
The workout's name on the athlete's Hevy log.
Clear enough to supply this argument.
workout › start_time
When the session started, as an ISO 8601 date-time; copy it from hevy_get_workout unless correcting it.
Clear enough to supply this argument.
workout › end_time
When the session ended, as an ISO 8601 date-time; copy it from hevy_get_workout unless correcting it.
Clear enough to supply this argument.
workout › is_private
Whether the workout is private (default true)
Clear enough to supply this argument.
workout › description
The workout's notes in Hevy. Omitted, they are left out of this full-replacement write, so copy them from hevy_get_workout to keep them.
Which Hevy exercise this is: its template id from hevy_get_exercise_templates, or as it appears on an exercise in hevy_get_workout / hevy_get_routine.
Clear enough to supply this argument.
workout › exercises › items › superset_id
Superset / circuit group. Exercises that share the same number form one group in Hevy; null = a straight exercise. This is a FULL-REPLACEMENT PUT: re-send the group on every member from hevy_get_routine. A routine update that omits a group the stored routine has is refused; send null to unpair on purpose.
Clear enough to supply this argument.
workout › exercises › items › supersets_id
Alias for superset_id: hevy_get_routine returns the group as `supersets_id`, so a fetched exercise can be sent back as-is. superset_id wins if both are given.
Clear enough to supply this argument.
workout › exercises › items › notes
The exercise's notes in the Hevy app. Copy from hevy_get_workout or they are erased.
Clear enough to supply this argument.
workout › exercises › items › sets › items › type
Set kind in Hevy. Omitted, it is sent as 'normal'; warmup, dropset and failure change how Hevy counts the set.
Rate of perceived exertion the athlete logged on this set (Hevy accepts 6, 7, 7.5, 8, 8.5, 9, 9.5, 10). This is a FULL-REPLACEMENT write: copy each set's rpe from hevy_get_workout or it is erased.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "hevy_update_workout",
"title": "Hevy: Update Workout",
"description": "Update an existing workout in Hevy. Use this to correct logged exercises, sets, weights, or reps after a workout has been saved. The workout body is a FULL REPLACEMENT — it replaces the whole stored workout, so it is hevy_get_workout's current object with the correction applied; a body rebuilt from memory silently overwrites real sets with wrong ones (the #1 Hevy trust failure). The result echoes what Hevy now stores, so it shows whether the stored workout matches what the athlete asked for. APP VISIBILITY: that echo IS the confirmation Hevy stored the change - the Hevy app caches its own copy and may need a few minutes or a pull-to-refresh to show it. A correction missing from the app after this echo is app-cache lag, not a failed write (hevy_get_workout re-reads the stored state); re-issuing the write changes nothing, and logging a second workout creates a duplicate.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"id",
"workout"
],
"properties": {
"id": {
"type": "string",
"description": "The workout's `id` from hevy_get_workouts / hevy_get_workout."
},
"workout": {
"type": "object",
"required": [
"title",
"start_time",
"end_time",
"exercises"
],
"properties": {
"title": {
"type": "string",
"description": "The workout's name on the athlete's Hevy log."
},
"end_time": {
"type": "string",
"description": "When the session ended, as an ISO 8601 date-time; copy it from hevy_get_workout unless correcting it."
},
"exercises": {
"type": "array",
"items": {
"type": "object",
"required": [
"exercise_template_id",
"sets"
],
"properties": {
"sets": {
"type": "array",
"items": {
"type": "object",
"properties": {
"rpe": {
"anyOf": [
{
"anyOf": [
{
"type": "number",
"const": 6
},
{
"type": "number",
"const": 7
},
{
"type": "number",
"const": 7.5
},
{
"type": "number",
"const": 8
},
{
"type": "number",
"const": 8.5
},
{
"type": "number",
"const": 9
},
{
"type": "number",
"const": 9.5
},
{
"type": "number",
"const": 10
}
]
},
{
"type": "null"
}
],
"description": "Rate of perceived exertion the athlete logged on this set (Hevy accepts 6, 7, 7.5, 8, 8.5, 9, 9.5, 10). This is a FULL-REPLACEMENT write: copy each set's rpe from hevy_get_workout or it is erased."
},
"reps": {
"type": "number",
"description": "Reps performed on this set."
},
"type": {
"enum": [
"normal",
"warmup",
"dropset",
"drop",
"failure"
],
"type": "string",
"description": "Set kind in Hevy. Omitted, it is sent as 'normal'; warmup, dropset and failure change how Hevy counts the set."
},
"weight_kg": {
"type": "number",
"description": "Load lifted on this set, in kg; omit for bodyweight."
},
"custom_metric": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Hevy's custom set metric (steps / floors). Copy it from hevy_get_workout when present; omit otherwise."
},
"distance_meters": {
"type": "number",
"description": "Metres, for a distance set (a carry, a row)."
},
"duration_seconds": {
"type": "number",
"description": "Seconds, for a timed set (a hold or a cardio interval)."
}
}
}
},
"notes": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The exercise's notes in the Hevy app. Copy from hevy_get_workout or they are erased."
},
"superset_id": {
"anyOf": [
{
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
{
"type": "null"
}
],
"description": "Superset / circuit group. Exercises that share the same number form one group in Hevy; null = a straight exercise. This is a FULL-REPLACEMENT PUT: re-send the group on every member from hevy_get_routine. A routine update that omits a group the stored routine has is refused; send null to unpair on purpose."
},
"supersets_id": {
"anyOf": [
{
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
{
"type": "null"
}
],
"description": "Alias for superset_id: hevy_get_routine returns the group as `supersets_id`, so a fetched exercise can be sent back as-is. superset_id wins if both are given."
},
"exercise_template_id": {
"type": "string",
"description": "Which Hevy exercise this is: its template id from hevy_get_exercise_templates, or as it appears on an exercise in hevy_get_workout / hevy_get_routine."
}
}
}
},
"is_private": {
"type": "boolean",
"description": "Whether the workout is private (default true)"
},
"start_time": {
"type": "string",
"description": "When the session started, as an ISO 8601 date-time; copy it from hevy_get_workout unless correcting it."
},
"description": {
"type": "string",
"description": "The workout's notes in Hevy. Omitted, they are left out of this full-replacement write, so copy them from hevy_get_workout to keep them."
}
},
"description": "Updated workout data (full replacement)"
}
}
},
"annotations": {
"title": "Hevy: Update Workout",
"readOnlyHint": false,
"openWorldHint": true,
"idempotentHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Create a Hevy ROUTINE (template) the athlete starts manually when they're ready. Use this for upcoming / planned sessions, training-plan templates, or anything the athlete will execute later. Routines have NO timestamps and do not appear in the workout log until the athlete starts and saves them. This is the tool for requests to 'create / add / set up' future sessions in Hevy — including porting a training-plan session into Hevy; hevy_create_workout logs a completed session instead. WEIGHTS: weight_kg is the wire format; if the athlete reads weights in pounds, the conversion is lbs * 0.45359237 at full float precision — rounding to 2 decimals breaks the round-trip (25 lbs rounded to 11.34 kg displays back as 25.0 lbs, not 25). A tweak to an existing routine is hevy_update_routine on the existing id; a new routine created to 'replace' it leaves duplicate routines (which Hevy v1 cannot delete via API). APP VISIBILITY: the echo this tool returns IS the confirmation Hevy stored the routine - but the Hevy app caches its own routine list, so it can take a few minutes or a pull-to-refresh to appear, and with no folder_id this posts folder_id: null, so the routine lands OUTSIDE any folder the athlete keeps their other routines in (the folder's id from hevy_get_routine_folders places it; the echo's folder_id says where Hevy put it, and the result's _write_confirmation matches it). A routine the athlete cannot see after this echo is saved; app-cache lag and the folder (or its absence) explain it, not a failed write, re-creating it makes a duplicate, and it is not a support issue. REP RANGES: Hevy stores a native range per set, written as sets[].rep_range {start, end} (e.g. 6-10 for double progression), so send the range there and keep the exercise notes for cues.
The description explains the capability, when to use it, and what to expect.
Inputs96/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
routine › title
Routine name
Clear enough to supply this argument.
routine › folder_id
Folder ID to place routine in (null for no folder)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Which Hevy exercise this is: its template id from hevy_get_exercise_templates, or as it appears on an exercise in hevy_get_workout / hevy_get_routine.
Clear enough to supply this argument.
routine › exercises › items › rest_seconds
Rest timer in seconds AFTER each set of this exercise. This is Hevy's real per-exercise rest field (it lives on the exercise, NOT the set) — the countdown in the Hevy app fires from this field; rest times in notes/description don't drive the timer. In a superset, rest_seconds goes only on the LAST exercise of the group (0 or omit on the others).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
routine › exercises › items › notes
Per-exercise notes shown in the Hevy app (form cues, tempo, etc.).
Clear enough to supply this argument.
routine › exercises › items › superset_id
Superset / circuit group. Exercises that share the same number form one group in Hevy; null = a straight exercise. This is a FULL-REPLACEMENT PUT: re-send the group on every member from hevy_get_routine. A routine update that omits a group the stored routine has is refused; send null to unpair on purpose.
Clear enough to supply this argument.
routine › exercises › items › supersets_id
Alias for superset_id: hevy_get_routine returns the group as `supersets_id`, so a fetched exercise can be sent back as-is. superset_id wins if both are given.
Clear enough to supply this argument.
routine › exercises › items › sets › items › type
Set kind in Hevy. Omitted, it is sent as 'normal'; warmup, dropset and failure change how Hevy counts the set.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Hevy's custom set metric (steps / floors). Copy it from hevy_get_routine when present; omit otherwise.
Clear enough to supply this argument.
allow_duplicate
Set true only when the athlete explicitly wants a second routine with the same name. Without it, a create whose title matches an existing routine (case-insensitive) is refused — hevy_update_routine changes the existing one (Hevy v1 cannot delete routines via API, so a duplicate stays until manually removed).
Clear enough to supply this argument.
load_reason
Why a working load in this write differs from the athlete's strength progression model (a deload, an illness hold, the athlete asked for this load). Only first-party writes read it: without it, such a load - or more than one working load on one exercise under double progression - is refused with nothing written, and the refusal names the model's load. More reps on one set is not a reason; the model already counts logged sets.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "hevy_create_routine",
"title": "Hevy: Create Routine",
"description": "Create a Hevy ROUTINE (template) the athlete starts manually when they're ready. Use this for upcoming / planned sessions, training-plan templates, or anything the athlete will execute later. Routines have NO timestamps and do not appear in the workout log until the athlete starts and saves them. This is the tool for requests to 'create / add / set up' future sessions in Hevy — including porting a training-plan session into Hevy; hevy_create_workout logs a completed session instead. WEIGHTS: weight_kg is the wire format; if the athlete reads weights in pounds, the conversion is lbs * 0.45359237 at full float precision — rounding to 2 decimals breaks the round-trip (25 lbs rounded to 11.34 kg displays back as 25.0 lbs, not 25). A tweak to an existing routine is hevy_update_routine on the existing id; a new routine created to 'replace' it leaves duplicate routines (which Hevy v1 cannot delete via API). APP VISIBILITY: the echo this tool returns IS the confirmation Hevy stored the routine - but the Hevy app caches its own routine list, so it can take a few minutes or a pull-to-refresh to appear, and with no folder_id this posts folder_id: null, so the routine lands OUTSIDE any folder the athlete keeps their other routines in (the folder's id from hevy_get_routine_folders places it; the echo's folder_id says where Hevy put it, and the result's _write_confirmation matches it). A routine the athlete cannot see after this echo is saved; app-cache lag and the folder (or its absence) explain it, not a failed write, re-creating it makes a duplicate, and it is not a support issue. REP RANGES: Hevy stores a native range per set, written as sets[].rep_range {start, end} (e.g. 6-10 for double progression), so send the range there and keep the exercise notes for cues.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"routine"
],
"properties": {
"routine": {
"type": "object",
"required": [
"title",
"exercises"
],
"properties": {
"notes": {
"type": "string",
"description": "Optional routine notes/instructions"
},
"title": {
"type": "string",
"description": "Routine name"
},
"exercises": {
"type": "array",
"items": {
"type": "object",
"required": [
"exercise_template_id",
"sets"
],
"properties": {
"sets": {
"type": "array",
"items": {
"type": "object",
"properties": {
"reps": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Planned reps for this set (a single number; a range goes in rep_range)."
},
"type": {
"enum": [
"normal",
"warmup",
"dropset",
"drop",
"failure"
],
"type": "string",
"description": "Set kind in Hevy. Omitted, it is sent as 'normal'; warmup, dropset and failure change how Hevy counts the set."
},
"rep_range": {
"anyOf": [
{
"type": "object",
"properties": {
"end": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "High end of the rep range, e.g. 10"
},
"start": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Low end of the rep range, e.g. 6"
}
}
},
{
"type": "null"
}
],
"description": "Hevy's NATIVE rep range for this set (shown as e.g. 6-10 in the app). Use it instead of a single `reps` when the athlete progresses within a range (double progression); do not park the range in the exercise notes. This is a FULL-REPLACEMENT write: copy each set's rep_range from hevy_get_routine or it is erased."
},
"weight_kg": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Planned load for this set, in kg; omit for bodyweight."
},
"custom_metric": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Hevy's custom set metric (steps / floors). Copy it from hevy_get_routine when present; omit otherwise."
},
"distance_meters": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Metres, for a distance set (a carry, a row)."
},
"duration_seconds": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Seconds, for a timed set (a hold or a cardio interval)."
}
}
}
},
"notes": {
"type": "string",
"description": "Per-exercise notes shown in the Hevy app (form cues, tempo, etc.)."
},
"superset_id": {
"anyOf": [
{
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
{
"type": "null"
}
],
"description": "Superset / circuit group. Exercises that share the same number form one group in Hevy; null = a straight exercise. This is a FULL-REPLACEMENT PUT: re-send the group on every member from hevy_get_routine. A routine update that omits a group the stored routine has is refused; send null to unpair on purpose."
},
"rest_seconds": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Rest timer in seconds AFTER each set of this exercise. This is Hevy's real per-exercise rest field (it lives on the exercise, NOT the set) — the countdown in the Hevy app fires from this field; rest times in notes/description don't drive the timer. In a superset, rest_seconds goes only on the LAST exercise of the group (0 or omit on the others)."
},
"supersets_id": {
"anyOf": [
{
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
{
"type": "null"
}
],
"description": "Alias for superset_id: hevy_get_routine returns the group as `supersets_id`, so a fetched exercise can be sent back as-is. superset_id wins if both are given."
},
"exercise_template_id": {
"type": "string",
"description": "Which Hevy exercise this is: its template id from hevy_get_exercise_templates, or as it appears on an exercise in hevy_get_workout / hevy_get_routine."
}
}
}
},
"folder_id": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Folder ID to place routine in (null for no folder)"
}
},
"description": "Routine data"
},
"load_reason": {
"type": "string",
"description": "Why a working load in this write differs from the athlete's strength progression model (a deload, an illness hold, the athlete asked for this load). Only first-party writes read it: without it, such a load - or more than one working load on one exercise under double progression - is refused with nothing written, and the refusal names the model's load. More reps on one set is not a reason; the model already counts logged sets."
},
"allow_duplicate": {
"type": "boolean",
"description": "Set true only when the athlete explicitly wants a second routine with the same name. Without it, a create whose title matches an existing routine (case-insensitive) is refused — hevy_update_routine changes the existing one (Hevy v1 cannot delete routines via API, so a duplicate stays until manually removed)."
}
}
},
"annotations": {
"title": "Hevy: Create Routine",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": false,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Get one Hevy routine by ID: its actual exercise list with sets, reps, and weights. This is what a routine really contains, for prescribing modifications to a strength session (which lifts to skip for a joint issue, weight bumps, exercise swaps) and as the starting object for hevy_update_routine - the plan-session title ("Strength A Full Body") is freehand text that drifts from what the routine really contains, so the exercises here, not the label, are the routine. A hit here is Hevy's SERVER-SIDE state read live, so it settles "did that routine actually save?" on its own: if it comes back the write landed, and the athlete not seeing it in their app is a Hevy-app refresh lag or looking in the wrong place (this routine's folder_id says where it is; null means outside every folder), not a failed write. Example: hevy_get_routine({ id: "abc123" }) with the id from hevy_get_routines.
The description explains the capability, when to use it, and what to expect.
Inputs98/100
Input documentation gives an agent enough context to populate the arguments.
id
The Hevy routine to read: its `id` from hevy_get_routines.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "hevy_get_routine",
"title": "Hevy: Get Routine",
"description": "Get one Hevy routine by ID: its actual exercise list with sets, reps, and weights. This is what a routine really contains, for prescribing modifications to a strength session (which lifts to skip for a joint issue, weight bumps, exercise swaps) and as the starting object for hevy_update_routine - the plan-session title (\"Strength A Full Body\") is freehand text that drifts from what the routine really contains, so the exercises here, not the label, are the routine. A hit here is Hevy's SERVER-SIDE state read live, so it settles \"did that routine actually save?\" on its own: if it comes back the write landed, and the athlete not seeing it in their app is a Hevy-app refresh lag or looking in the wrong place (this routine's folder_id says where it is; null means outside every folder), not a failed write. Example: hevy_get_routine({ id: \"abc123\" }) with the id from hevy_get_routines.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"id"
],
"properties": {
"id": {
"type": "string",
"description": "The Hevy routine to read: its `id` from hevy_get_routines."
}
}
},
"annotations": {
"title": "Hevy: Get Routine",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
Update an existing training routine in Hevy (full-replacement PUT). Use for progressive overload - adjusting weights, reps, or exercises in an existing routine. Constraints: (1) Hevy v1 has NO delete-routine endpoint and rejects an empty exercises array - a routine cannot be deleted or emptied via this tool; retiring one is a swipe-delete in the Hevy app. (2) folder_id is read-only on update and the integration layer strips it - changing folders requires the athlete to move the routine in the app. (3) WEIGHTS: weight_kg is the wire format; if the athlete reads weights in pounds, the conversion is lbs * 0.45359237 at full float precision (rounding to 2 decimals breaks the round-trip - 25 lbs rounded to 11.34 kg displays back as 25.0 lbs not 25, which the athlete reads as the coach changing their weights). (4) FULL REPLACEMENT: the body replaces the whole routine, so it is hevy_get_routine's current object with the change applied; a body reconstructed from memory silently reverts edits the athlete made in the app. The echoed result shows whether the stored routine matches the intended change. (5) APP VISIBILITY: the echoed result IS confirmation Hevy stored the change; the Hevy app caches its own copy and may need a few minutes or a pull-to-refresh to show it. An edit missing from the app after this echo is app-cache lag, not a failed write; re-issuing the write changes nothing, and a replacement routine would be a duplicate. (6) REP RANGES: a set's native range is sets[].rep_range {start, end}; copy it from hevy_get_routine on every set. (7) A write that leaves off a superset group, rest timer or rep range the stored routine has on an exercise you keep is REFUSED with nothing written: copy them over, or remove one on purpose with superset_id: null / rest_seconds: 0 / rep_range: null.
The description explains the capability, when to use it, and what to expect.
Inputs98/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
id
Routine ID to update
Clear enough to supply this argument.
routine › title
Routine name
Clear enough to supply this argument.
routine › folder_id
(IGNORED on update — Hevy rejects folder_id changes; the integration strips this before sending.)
Which Hevy exercise this is: its template id from hevy_get_exercise_templates, or as it appears on an exercise in hevy_get_workout / hevy_get_routine.
Clear enough to supply this argument.
routine › exercises › items › rest_seconds
Rest timer in seconds AFTER each set of this exercise. This is Hevy's real per-exercise rest field (it lives on the exercise, NOT the set) — the countdown in the Hevy app fires from this field; rest times in notes/description don't drive the timer. In a superset, rest_seconds goes only on the LAST exercise of the group (0 or omit on the others). This is a FULL-REPLACEMENT PUT, so an exercise sent without rest_seconds has its existing timer cleared.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
routine › exercises › items › notes
Per-exercise notes shown in the Hevy app (form cues, tempo, etc.).
Clear enough to supply this argument.
routine › exercises › items › superset_id
Superset / circuit group. Exercises that share the same number form one group in Hevy; null = a straight exercise. This is a FULL-REPLACEMENT PUT: re-send the group on every member from hevy_get_routine. A routine update that omits a group the stored routine has is refused; send null to unpair on purpose.
Clear enough to supply this argument.
routine › exercises › items › supersets_id
Alias for superset_id: hevy_get_routine returns the group as `supersets_id`, so a fetched exercise can be sent back as-is. superset_id wins if both are given.
Clear enough to supply this argument.
routine › exercises › items › sets › items › type
Set kind in Hevy. Omitted, it is sent as 'normal'; warmup, dropset and failure change how Hevy counts the set.
Hevy's custom set metric (steps / floors). Copy it from hevy_get_routine when present; omit otherwise.
Clear enough to supply this argument.
keep_ramp_sets_as_written
Set true ONLY when the athlete asked, in this conversation, for fewer, none or different warm-up sets in this routine: it is stored exactly as written. Without it our coach's write gets the ramp sets their warm-up rule gives each loaded exercise. For a standing wish also record it with set_strength_progression.
Clear enough to supply this argument.
load_reason
Why a working load in this write differs from the athlete's strength progression model (a deload, an illness hold, the athlete asked for this load). Only first-party writes read it: without it, such a load - or more than one working load on one exercise under double progression - is refused with nothing written, and the refusal names the model's load. More reps on one set is not a reason; the model already counts logged sets.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "hevy_update_routine",
"title": "Hevy: Update Routine",
"description": "Update an existing training routine in Hevy (full-replacement PUT). Use for progressive overload - adjusting weights, reps, or exercises in an existing routine. Constraints: (1) Hevy v1 has NO delete-routine endpoint and rejects an empty exercises array - a routine cannot be deleted or emptied via this tool; retiring one is a swipe-delete in the Hevy app. (2) folder_id is read-only on update and the integration layer strips it - changing folders requires the athlete to move the routine in the app. (3) WEIGHTS: weight_kg is the wire format; if the athlete reads weights in pounds, the conversion is lbs * 0.45359237 at full float precision (rounding to 2 decimals breaks the round-trip - 25 lbs rounded to 11.34 kg displays back as 25.0 lbs not 25, which the athlete reads as the coach changing their weights). (4) FULL REPLACEMENT: the body replaces the whole routine, so it is hevy_get_routine's current object with the change applied; a body reconstructed from memory silently reverts edits the athlete made in the app. The echoed result shows whether the stored routine matches the intended change. (5) APP VISIBILITY: the echoed result IS confirmation Hevy stored the change; the Hevy app caches its own copy and may need a few minutes or a pull-to-refresh to show it. An edit missing from the app after this echo is app-cache lag, not a failed write; re-issuing the write changes nothing, and a replacement routine would be a duplicate. (6) REP RANGES: a set's native range is sets[].rep_range {start, end}; copy it from hevy_get_routine on every set. (7) A write that leaves off a superset group, rest timer or rep range the stored routine has on an exercise you keep is REFUSED with nothing written: copy them over, or remove one on purpose with superset_id: null / rest_seconds: 0 / rep_range: null.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"id",
"routine"
],
"properties": {
"id": {
"type": "string",
"description": "Routine ID to update"
},
"routine": {
"type": "object",
"required": [
"title",
"exercises"
],
"properties": {
"notes": {
"type": "string",
"description": "Optional routine notes/instructions"
},
"title": {
"type": "string",
"description": "Routine name"
},
"exercises": {
"type": "array",
"items": {
"type": "object",
"required": [
"exercise_template_id",
"sets"
],
"properties": {
"sets": {
"type": "array",
"items": {
"type": "object",
"properties": {
"reps": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Planned reps for this set (a single number; a range goes in rep_range)."
},
"type": {
"enum": [
"normal",
"warmup",
"dropset",
"drop",
"failure"
],
"type": "string",
"description": "Set kind in Hevy. Omitted, it is sent as 'normal'; warmup, dropset and failure change how Hevy counts the set."
},
"rep_range": {
"anyOf": [
{
"type": "object",
"properties": {
"end": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "High end of the rep range, e.g. 10"
},
"start": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Low end of the rep range, e.g. 6"
}
}
},
{
"type": "null"
}
],
"description": "Hevy's NATIVE rep range for this set (shown as e.g. 6-10 in the app). Use it instead of a single `reps` when the athlete progresses within a range (double progression); do not park the range in the exercise notes. This is a FULL-REPLACEMENT write: copy each set's rep_range from hevy_get_routine or it is erased."
},
"weight_kg": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Planned load for this set, in kg; omit for bodyweight."
},
"custom_metric": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Hevy's custom set metric (steps / floors). Copy it from hevy_get_routine when present; omit otherwise."
},
"distance_meters": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Metres, for a distance set (a carry, a row)."
},
"duration_seconds": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Seconds, for a timed set (a hold or a cardio interval)."
}
}
}
},
"notes": {
"type": "string",
"description": "Per-exercise notes shown in the Hevy app (form cues, tempo, etc.)."
},
"superset_id": {
"anyOf": [
{
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
{
"type": "null"
}
],
"description": "Superset / circuit group. Exercises that share the same number form one group in Hevy; null = a straight exercise. This is a FULL-REPLACEMENT PUT: re-send the group on every member from hevy_get_routine. A routine update that omits a group the stored routine has is refused; send null to unpair on purpose."
},
"rest_seconds": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Rest timer in seconds AFTER each set of this exercise. This is Hevy's real per-exercise rest field (it lives on the exercise, NOT the set) — the countdown in the Hevy app fires from this field; rest times in notes/description don't drive the timer. In a superset, rest_seconds goes only on the LAST exercise of the group (0 or omit on the others). This is a FULL-REPLACEMENT PUT, so an exercise sent without rest_seconds has its existing timer cleared."
},
"supersets_id": {
"anyOf": [
{
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
{
"type": "null"
}
],
"description": "Alias for superset_id: hevy_get_routine returns the group as `supersets_id`, so a fetched exercise can be sent back as-is. superset_id wins if both are given."
},
"exercise_template_id": {
"type": "string",
"description": "Which Hevy exercise this is: its template id from hevy_get_exercise_templates, or as it appears on an exercise in hevy_get_workout / hevy_get_routine."
}
}
}
},
"folder_id": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "(IGNORED on update — Hevy rejects folder_id changes; the integration strips this before sending.)"
}
},
"description": "Updated routine data (full replacement)"
},
"load_reason": {
"type": "string",
"description": "Why a working load in this write differs from the athlete's strength progression model (a deload, an illness hold, the athlete asked for this load). Only first-party writes read it: without it, such a load - or more than one working load on one exercise under double progression - is refused with nothing written, and the refusal names the model's load. More reps on one set is not a reason; the model already counts logged sets."
},
"keep_ramp_sets_as_written": {
"type": "boolean",
"description": "Set true ONLY when the athlete asked, in this conversation, for fewer, none or different warm-up sets in this routine: it is stored exactly as written. Without it our coach's write gets the ramp sets their warm-up rule gives each loaded exercise. For a standing wish also record it with set_strength_progression."
}
}
},
"annotations": {
"title": "Hevy: Update Routine",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Get workout update/delete events since a given timestamp. Useful for detecting when workouts were modified or deleted. Returns `page`, `page_count` and `events`: each event is either an updated workout, carrying the workout itself, or a deleted workout, carrying its id and when it was deleted.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
since
ISO 8601 timestamp to get events since (e.g. 2024-01-01T00:00:00Z)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
page
Page number (default 1)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
pageSize
Results per page (default 10, max 10 - Hevy 400s above that; values over 10 are clamped)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "hevy_get_workout_events",
"title": "Hevy: Get Workout Events",
"description": "Get workout update/delete events since a given timestamp. Useful for detecting when workouts were modified or deleted. Returns `page`, `page_count` and `events`: each event is either an updated workout, carrying the workout itself, or a deleted workout, carrying its id and when it was deleted.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"page": {
"type": "number",
"description": "Page number (default 1)"
},
"since": {
"type": "string",
"description": "ISO 8601 timestamp to get events since (e.g. 2024-01-01T00:00:00Z)"
},
"pageSize": {
"type": "number",
"description": "Results per page (default 10, max 10 - Hevy 400s above that; values over 10 are clamped)"
}
}
},
"annotations": {
"title": "Hevy: Get Workout Events",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description100/100
Create a custom exercise template in the user's Hevy library when no built-in template covers it (rare; check hevy_get_exercise_templates first). Use only when the athlete is logging an unusual variation (e.g. "Bulgarian split squat with deficit") that the standard library doesn't have. Returns the new template's id, which you can then use in hevy_create_routine / hevy_create_workout. Hevy v1 has NO delete-exercise-template endpoint, so a duplicate is permanent and athlete-visible - this tool refuses a title that already exists (built-in or custom) and hands you that template's id to reuse instead. A result with "created": true IS the confirmation the write landed; re-issuing the same create adds nothing and is refused as a duplicate. Newly created custom exercises may take a little while to appear in the athlete's Hevy app, and only surface there when they SEARCH the name (they are not pinned into "Popular Exercises") - those are the known reasons one is not visible yet; any other cause is unconfirmed.
The description explains the capability, when to use it, and what to expect.
Inputs79/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
exercise_template › title
Exercise name
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
exercise_template › exercise_type
Exercise type. Values seen in Hevy's library: weight_reps | reps_only | duration | bodyweight_weighted | bodyweight_assisted | distance_duration. Most strength moves are weight_reps; unweighted rep work is reps_only; timed holds/plyos are duration.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
exercise_template › equipment_category
Equipment category. Hevy accepts ONLY these values - use 'none' for bodyweight and 'resistance_band' for bands.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
exercise_template › muscle_group
Primary muscle group. Hevy accepts ONLY these values - there is no 'legs', 'core' or 'back'; use quadriceps/hamstrings/glutes/calves, abdominals, and lats/upper_back/lower_back.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
exercise_template › other_muscles
Secondary muscle groups, from the same list as muscle_group.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
allow_duplicate
Set true only when the athlete explicitly wants a second template with an existing name. Leave unset to get the duplicate refusal.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "hevy_create_exercise_template",
"title": "Hevy: Create Exercise Template",
"description": "Create a custom exercise template in the user's Hevy library when no built-in template covers it (rare; check hevy_get_exercise_templates first). Use only when the athlete is logging an unusual variation (e.g. \"Bulgarian split squat with deficit\") that the standard library doesn't have. Returns the new template's id, which you can then use in hevy_create_routine / hevy_create_workout. Hevy v1 has NO delete-exercise-template endpoint, so a duplicate is permanent and athlete-visible - this tool refuses a title that already exists (built-in or custom) and hands you that template's id to reuse instead. A result with \"created\": true IS the confirmation the write landed; re-issuing the same create adds nothing and is refused as a duplicate. Newly created custom exercises may take a little while to appear in the athlete's Hevy app, and only surface there when they SEARCH the name (they are not pinned into \"Popular Exercises\") - those are the known reasons one is not visible yet; any other cause is unconfirmed.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"exercise_template"
],
"properties": {
"allow_duplicate": {
"type": "boolean",
"description": "Set true only when the athlete explicitly wants a second template with an existing name. Leave unset to get the duplicate refusal."
},
"exercise_template": {
"type": "object",
"required": [
"title",
"exercise_type",
"equipment_category",
"muscle_group"
],
"properties": {
"title": {
"type": "string",
"description": "Exercise name"
},
"muscle_group": {
"enum": [
"abdominals",
"shoulders",
"biceps",
"triceps",
"forearms",
"quadriceps",
"hamstrings",
"calves",
"glutes",
"abductors",
"adductors",
"lats",
"upper_back",
"traps",
"lower_back",
"chest",
"cardio",
"neck",
"full_body",
"other"
],
"type": "string",
"description": "Primary muscle group. Hevy accepts ONLY these values - there is no 'legs', 'core' or 'back'; use quadriceps/hamstrings/glutes/calves, abdominals, and lats/upper_back/lower_back."
},
"exercise_type": {
"type": "string",
"description": "Exercise type. Values seen in Hevy's library: weight_reps | reps_only | duration | bodyweight_weighted | bodyweight_assisted | distance_duration. Most strength moves are weight_reps; unweighted rep work is reps_only; timed holds/plyos are duration."
},
"other_muscles": {
"type": "array",
"items": {
"enum": [
"abdominals",
"shoulders",
"biceps",
"triceps",
"forearms",
"quadriceps",
"hamstrings",
"calves",
"glutes",
"abductors",
"adductors",
"lats",
"upper_back",
"traps",
"lower_back",
"chest",
"cardio",
"neck",
"full_body",
"other"
],
"type": "string"
},
"description": "Secondary muscle groups, from the same list as muscle_group."
},
"equipment_category": {
"enum": [
"none",
"barbell",
"dumbbell",
"kettlebell",
"machine",
"plate",
"resistance_band",
"suspension",
"other"
],
"type": "string",
"description": "Equipment category. Hevy accepts ONLY these values - use 'none' for bodyweight and 'resistance_band' for bands."
}
},
"description": "Exercise template data"
}
}
},
"annotations": {
"title": "Hevy: Create Exercise Template",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": false,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description93/100
Get details of a specific exercise template including muscle groups and equipment. Returns Hevy's template record: its id, title, exercise type, muscle groups and `is_custom` (true for an exercise the athlete created).
The description explains the capability, when to use it, and what to expect.
Inputs99/100
Input documentation gives an agent enough context to populate the arguments.
exercise_template_id
The Hevy exercise template to read: an `exercise_template_id` from hevy_get_exercise_templates, or from an exercise in hevy_get_workouts / hevy_get_routines.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "hevy_get_exercise_template",
"title": "Hevy: Get Exercise Template",
"description": "Get details of a specific exercise template including muscle groups and equipment. Returns Hevy's template record: its id, title, exercise type, muscle groups and `is_custom` (true for an exercise the athlete created).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"exercise_template_id"
],
"properties": {
"exercise_template_id": {
"type": "string",
"description": "The Hevy exercise template to read: an `exercise_template_id` from hevy_get_exercise_templates, or from an exercise in hevy_get_workouts / hevy_get_routines."
}
}
},
"annotations": {
"title": "Hevy: Get Exercise Template",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description100/100
Get the athlete's Hevy routine folders (containers grouping routines, e.g. "Push/Pull/Legs", "Travel"). Use when planning where to place a new routine via hevy_create_routine, or auditing program structure. Returns `page`, `page_count` and `routine_folders`, each with the folder's `id` (the folder_id other Hevy tools take) and `title`. Cheap call. Example: hevy_get_routine_folders({}).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
page
Page number (default 1)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
pageSize
Results per page (default 10, max 10 - Hevy 400s above that; values over 10 are clamped)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "hevy_get_routine_folders",
"title": "Hevy: Get Routine Folders",
"description": "Get the athlete's Hevy routine folders (containers grouping routines, e.g. \"Push/Pull/Legs\", \"Travel\"). Use when planning where to place a new routine via hevy_create_routine, or auditing program structure. Returns `page`, `page_count` and `routine_folders`, each with the folder's `id` (the folder_id other Hevy tools take) and `title`. Cheap call. Example: hevy_get_routine_folders({}).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"page": {
"type": "number",
"description": "Page number (default 1)"
},
"pageSize": {
"type": "number",
"description": "Results per page (default 10, max 10 - Hevy 400s above that; values over 10 are clamped)"
}
}
},
"annotations": {
"title": "Hevy: Get Routine Folders",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description100/100
Create a new routine folder in Hevy, for when the athlete asks to group their routines or a structured program is being set up (e.g. a "Marathon Block" folder). A folder is an empty container: each routine in it is created separately with hevy_create_routine, passing this folder's folder_id, and needs its exercise template IDs and set details. A folder created before those are ready can be left with no routines, which shows the athlete an empty program; creating the folder and its routines together avoids that. Returns the created folder, including the `id` that hevy_create_routine takes as folder_id. Example: hevy_create_routine_folder({ routine_folder: { title: "5/3/1 Cycle" } }).
The description explains the capability, when to use it, and what to expect.
Inputs83/100
Input documentation gives an agent enough context to populate the arguments.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "hevy_create_routine_folder",
"title": "Hevy: Create Routine Folder",
"description": "Create a new routine folder in Hevy, for when the athlete asks to group their routines or a structured program is being set up (e.g. a \"Marathon Block\" folder). A folder is an empty container: each routine in it is created separately with hevy_create_routine, passing this folder's folder_id, and needs its exercise template IDs and set details. A folder created before those are ready can be left with no routines, which shows the athlete an empty program; creating the folder and its routines together avoids that. Returns the created folder, including the `id` that hevy_create_routine takes as folder_id. Example: hevy_create_routine_folder({ routine_folder: { title: \"5/3/1 Cycle\" } }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"routine_folder"
],
"properties": {
"routine_folder": {
"type": "object",
"required": [
"title"
],
"properties": {
"title": {
"type": "string",
"description": "Folder name"
}
},
"description": "Routine folder data"
}
}
},
"annotations": {
"title": "Hevy: Create Routine Folder",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": false,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Get details of one Hevy routine folder by ID: title, contained routine IDs. Use when you have a folder ID from hevy_get_routine_folders and need to enumerate the routines inside before adjusting one. Example: hevy_get_routine_folder({ folder_id: "folder_xyz" }).
The description explains the capability, when to use it, and what to expect.
Inputs99/100
Input documentation gives an agent enough context to populate the arguments.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "hevy_get_routine_folder",
"title": "Hevy: Get Routine Folder",
"description": "Get details of one Hevy routine folder by ID: title, contained routine IDs. Use when you have a folder ID from hevy_get_routine_folders and need to enumerate the routines inside before adjusting one. Example: hevy_get_routine_folder({ folder_id: \"folder_xyz\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"folder_id"
],
"properties": {
"folder_id": {
"type": "string",
"description": "The folder's `id` from hevy_get_routine_folders."
}
}
},
"annotations": {
"title": "Hevy: Get Routine Folder",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
The athlete's Hevy body measurements: tape circumferences (waist, hips, chest, neck, shoulder, left and right bicep, forearm, thigh, calf, abdomen) plus weight_kg, lean_mass_kg and fat_percent, one entry per date, most recent first. Every field is nullable; athletes log only the ones they track. Use it for waist, arm or leg measurements and circumference trends (e.g. "is my waist coming down?"), which the body-weight and body-composition tools (withings_get_measurements, apple_health_get_body_composition, garmin_get_body_composition) do not cover. Lengths are in cm and mass in kg. Hevy caps pageSize at 10; older entries are on later pages, and the last page is where page equals the page_count returned. Most athletes have ten or fewer measurements, so usually there is one page. Example: hevy_get_body_measurements({ pageSize: 10 }).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
page
Page number (default 1)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
pageSize
Results per page (default 10, max 10 — Hevy caps this)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Annotations100/100
All four behavior hints are declared.
{
"title": "Hevy: Get Body Measurements",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
Output schema0/100
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "hevy_get_body_measurements",
"title": "Hevy: Get Body Measurements",
"description": "The athlete's Hevy body measurements: tape circumferences (waist, hips, chest, neck, shoulder, left and right bicep, forearm, thigh, calf, abdomen) plus weight_kg, lean_mass_kg and fat_percent, one entry per date, most recent first. Every field is nullable; athletes log only the ones they track. Use it for waist, arm or leg measurements and circumference trends (e.g. \"is my waist coming down?\"), which the body-weight and body-composition tools (withings_get_measurements, apple_health_get_body_composition, garmin_get_body_composition) do not cover. Lengths are in cm and mass in kg. Hevy caps pageSize at 10; older entries are on later pages, and the last page is where page equals the page_count returned. Most athletes have ten or fewer measurements, so usually there is one page. Example: hevy_get_body_measurements({ pageSize: 10 }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"page": {
"type": "number",
"description": "Page number (default 1)"
},
"pageSize": {
"type": "number",
"description": "Results per page (default 10, max 10 — Hevy caps this)"
}
}
},
"annotations": {
"title": "Hevy: Get Body Measurements",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Get every measurement Withings holds for the athlete, from any Withings device - smart scale (weight, body fat, muscle/bone mass, body water, visceral fat, BMR), BPM Connect / BPM Core blood-pressure cuff (systolic, diastolic, pulse), and ScanWatch (SpO2, VO2max, vascular age, pulse wave velocity). Returns one entry per measurement group with the athlete's local timezone, so a cuff reading and a weigh-in arrive as separate entries. For blood pressure, get_blood_pressure is the primary tool: it reads the stored blood_pressure_readings table across every cuff the athlete owns (Withings, Garmin Index BPM, anything writing into Apple Health), computes the ACC/AHA category in code, separates device from manual readings, and carries the clinical rules that apply to the readings; for blood pressure this tool adds only the raw Withings measurement group alongside the weigh-in it was taken with. Neither is in daily_metrics, so get_daily_metrics will never show BP. Pass a startdate to pull history and trend it (Withings returns the full series, not just the latest). For a one-off recent weight, get_daily_metrics already has it - use this for ranges and for anything the scale does not measure. Example: withings_get_measurements({ startdate: "2026-01-01" }).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
startdate
ISO date string — get measurements after this date
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
enddate
ISO date string — get measurements before this date
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
meastypes
Comma-separated Withings measure-type ids. OMIT to get all of them (the default is "1,4,5,6,8,9,10,11,54,76,77,88,91,123,155,168,169,170,173,174,175,196,226"), which is usually what you want. Supported: 1=weight (kg), 4=height (m), 5=fat-free mass (kg), 6=body fat (%), 8=fat mass (kg), 9=diastolic blood pressure (mmHg), 10=systolic blood pressure (mmHg), 11=pulse (bpm), 54=SpO2 (%), 76=muscle mass (kg), 77=hydration (kg), 88=bone mass (kg), 91=pulse wave velocity (m/s), 123=VO2max, 155=vascular age, 168=extracellular water (kg), 169=intracellular water (kg), 170=visceral fat index, 173=fat-free mass, segmental, 174=fat mass, segmental, 175=muscle mass, segmental, 196=electrodermal activity, 226=basal metabolic rate (kcal).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "withings_get_measurements",
"title": "Withings: Get Measurements",
"description": "Get every measurement Withings holds for the athlete, from any Withings device - smart scale (weight, body fat, muscle/bone mass, body water, visceral fat, BMR), BPM Connect / BPM Core blood-pressure cuff (systolic, diastolic, pulse), and ScanWatch (SpO2, VO2max, vascular age, pulse wave velocity). Returns one entry per measurement group with the athlete's local timezone, so a cuff reading and a weigh-in arrive as separate entries. For blood pressure, get_blood_pressure is the primary tool: it reads the stored blood_pressure_readings table across every cuff the athlete owns (Withings, Garmin Index BPM, anything writing into Apple Health), computes the ACC/AHA category in code, separates device from manual readings, and carries the clinical rules that apply to the readings; for blood pressure this tool adds only the raw Withings measurement group alongside the weigh-in it was taken with. Neither is in daily_metrics, so get_daily_metrics will never show BP. Pass a startdate to pull history and trend it (Withings returns the full series, not just the latest). For a one-off recent weight, get_daily_metrics already has it - use this for ranges and for anything the scale does not measure. Example: withings_get_measurements({ startdate: \"2026-01-01\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"enddate": {
"type": "string",
"description": "ISO date string — get measurements before this date"
},
"meastypes": {
"type": "string",
"description": "Comma-separated Withings measure-type ids. OMIT to get all of them (the default is \"1,4,5,6,8,9,10,11,54,76,77,88,91,123,155,168,169,170,173,174,175,196,226\"), which is usually what you want. Supported: 1=weight (kg), 4=height (m), 5=fat-free mass (kg), 6=body fat (%), 8=fat mass (kg), 9=diastolic blood pressure (mmHg), 10=systolic blood pressure (mmHg), 11=pulse (bpm), 54=SpO2 (%), 76=muscle mass (kg), 77=hydration (kg), 88=bone mass (kg), 91=pulse wave velocity (m/s), 123=VO2max, 155=vascular age, 168=extracellular water (kg), 169=intracellular water (kg), 170=visceral fat index, 173=fat-free mass, segmental, 174=fat mass, segmental, 175=muscle mass, segmental, 196=electrodermal activity, 226=basal metabolic rate (kcal)."
},
"startdate": {
"type": "string",
"description": "ISO date string — get measurements after this date"
}
}
},
"annotations": {
"title": "Withings: Get Measurements",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description92/100
Get list of ECG recordings from Withings ScanWatch with classifications. Returns `ecg_recordings`, each with its `timestamp`, the `ecg` reading and its `classification` (normal_sinus_rhythm, negative, positive_afib, inconclusive, or unknown for a code Withings did not document), plus the `heart_rate` and `bloodpressure` Withings stored with the recording.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date/time as ISO string or YYYY-MM-DD
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date/time as ISO string or YYYY-MM-DD
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "withings_get_heart_list",
"title": "Withings: Get Heart List",
"description": "Get list of ECG recordings from Withings ScanWatch with classifications. Returns `ecg_recordings`, each with its `timestamp`, the `ecg` reading and its `classification` (normal_sinus_rhythm, negative, positive_afib, inconclusive, or unknown for a code Withings did not document), plus the `heart_rate` and `bloodpressure` Withings stored with the recording.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"start_date",
"end_date"
],
"properties": {
"end_date": {
"type": "string",
"description": "End date/time as ISO string or YYYY-MM-DD"
},
"start_date": {
"type": "string",
"description": "Start date/time as ISO string or YYYY-MM-DD"
}
}
},
"annotations": {
"title": "Withings: Get Heart List",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description96/100
Get the athlete's connected Withings devices: model (Body+, ScanWatch, Sleep Analyzer, etc.), battery level, last sync time. Use when troubleshooting why Withings data is missing ("my weigh-ins aren't syncing") or when deciding which Withings tools are actually viable for this athlete (no ScanWatch = no sleep_raw / heart_list). Example: withings_get_devices({}).
The description explains the capability, when to use it, and what to expect.
Inputs100/100
This tool declares no inputs, so argument descriptions are not needed.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "withings_get_devices",
"title": "Withings: Get Devices",
"description": "Get the athlete's connected Withings devices: model (Body+, ScanWatch, Sleep Analyzer, etc.), battery level, last sync time. Use when troubleshooting why Withings data is missing (\"my weigh-ins aren't syncing\") or when deciding which Withings tools are actually viable for this athlete (no ScanWatch = no sleep_raw / heart_list). Example: withings_get_devices({}).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {}
},
"annotations": {
"title": "Withings: Get Devices",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description96/100
Get Oura's per-day sleep SCORES (0-100 score, efficiency, timing, contributor breakdown). Use when the athlete asks about their Oura sleep score or you need the score's contributors. Example: oura_get_daily_sleep({ start_date: "2026-06-09", end_date: "2026-06-10" }). For stage-level detail (deep/REM/light minutes, overnight HR + HRV) use oura_get_sleep_periods - this tool only carries the score layer. A night is dated by its WAKE day; include yesterday AND today in the window to be safe. A window too long for daily rows in one result comes back as one row per calendar month (aggregate: "month", days_with_data and the means or counts) for the whole window plus recent_days, the newest records in full, with the dates they cover; aggregate: "month" asks for the monthly rows alone, aggregate: "day" for the newest daily rows that fit. NAMING A NIGHT: each record's date is the WAKE date - the morning the athlete got up - so the night it describes began the EVENING BEFORE it. Every record we can ground carries `night_of`, the evening that night actually began on, in athlete-local time with the clock they fell asleep at (`Thursday 2026-09-10 (asleep 21:32)`). `night_of` is what names the night; a night named by the weekday of the record's own date is the next day. `after midnight` on a `night_of` means they fell asleep past midnight, so the clock time sits on the wake date while the night is still the previous evening's (asleep 01:08 Saturday IS Friday night). A record with no `night_of` has no stored onset or no resolved timezone, so only its wake date is grounded: the night is the one before that date, and no weekday for it is known.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
aggregate
"month": per-calendar-month means instead of daily records; "day": the newest daily records that fit
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "oura_get_daily_sleep",
"title": "Oura: Get Daily Sleep",
"description": "Get Oura's per-day sleep SCORES (0-100 score, efficiency, timing, contributor breakdown). Use when the athlete asks about their Oura sleep score or you need the score's contributors. Example: oura_get_daily_sleep({ start_date: \"2026-06-09\", end_date: \"2026-06-10\" }). For stage-level detail (deep/REM/light minutes, overnight HR + HRV) use oura_get_sleep_periods - this tool only carries the score layer. A night is dated by its WAKE day; include yesterday AND today in the window to be safe. A window too long for daily rows in one result comes back as one row per calendar month (aggregate: \"month\", days_with_data and the means or counts) for the whole window plus recent_days, the newest records in full, with the dates they cover; aggregate: \"month\" asks for the monthly rows alone, aggregate: \"day\" for the newest daily rows that fit. NAMING A NIGHT: each record's date is the WAKE date - the morning the athlete got up - so the night it describes began the EVENING BEFORE it. Every record we can ground carries `night_of`, the evening that night actually began on, in athlete-local time with the clock they fell asleep at (`Thursday 2026-09-10 (asleep 21:32)`). `night_of` is what names the night; a night named by the weekday of the record's own date is the next day. `after midnight` on a `night_of` means they fell asleep past midnight, so the clock time sits on the wake date while the night is still the previous evening's (asleep 01:08 Saturday IS Friday night). A record with no `night_of` has no stored onset or no resolved timezone, so only its wake date is grounded: the night is the one before that date, and no weekday for it is known.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"aggregate": {
"enum": [
"month",
"day"
],
"type": "string",
"description": "\"month\": per-calendar-month means instead of daily records; \"day\": the newest daily records that fit"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Oura: Get Daily Sleep",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Get Oura's per-day readiness records: 0-100 score plus the contributors that matter for coaching (HRV balance, resting HR, body temperature deviation, recovery index, sleep balance). Use for any "how recovered am I" / "my Oura readiness" question and before prescribing today's intensity for an Oura athlete. Example: oura_get_daily_readiness({ start_date: "2026-06-09", end_date: "2026-06-10" }) - records label by wake day, so include yesterday + today. The 0-100 score is Oura's composite; the contributors read against the athlete's own baseline carry the underlying signal. A window too long for daily rows in one result comes back as one row per calendar month (aggregate: "month", days_with_data and the means or counts) for the whole window plus recent_days, the newest records in full, with the dates they cover; aggregate: "month" asks for the monthly rows alone, aggregate: "day" for the newest daily rows that fit.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
aggregate
"month": per-calendar-month means instead of daily records; "day": the newest daily records that fit
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "oura_get_daily_readiness",
"title": "Oura: Get Daily Readiness",
"description": "Get Oura's per-day readiness records: 0-100 score plus the contributors that matter for coaching (HRV balance, resting HR, body temperature deviation, recovery index, sleep balance). Use for any \"how recovered am I\" / \"my Oura readiness\" question and before prescribing today's intensity for an Oura athlete. Example: oura_get_daily_readiness({ start_date: \"2026-06-09\", end_date: \"2026-06-10\" }) - records label by wake day, so include yesterday + today. The 0-100 score is Oura's composite; the contributors read against the athlete's own baseline carry the underlying signal. A window too long for daily rows in one result comes back as one row per calendar month (aggregate: \"month\", days_with_data and the means or counts) for the whole window plus recent_days, the newest records in full, with the dates they cover; aggregate: \"month\" asks for the monthly rows alone, aggregate: \"day\" for the newest daily rows that fit.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"aggregate": {
"enum": [
"month",
"day"
],
"type": "string",
"description": "\"month\": per-calendar-month means instead of daily records; \"day\": the newest daily records that fit"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Oura: Get Daily Readiness",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description96/100
Get daily activity data from Oura Ring (steps, total_calories = resting + active, active_calories, activity score, movement breakdown). For a long span (months, "average per month") pass aggregate: "month" for one row per calendar month with days_with_data and the mean total_calories / active_calories / steps / score; a window too long for daily rows in one result gets those monthly rows automatically, plus recent_days, the newest days in full, with the dates they cover (aggregate: "day" keeps the newest daily rows that fit). Oura's per-minute `met` and 5-minute `class_5_min` series are left out; pass detail: "full" for the raw records with them (large: ~5.5k characters a day).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
aggregate
"month": per-calendar-month means instead of daily records; "day": the newest daily records that fit
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
detail
"full": Oura's raw daily records, per-minute `met` and `class_5_min` series included. Default "compact".
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "oura_get_daily_activity",
"title": "Oura: Get Daily Activity",
"description": "Get daily activity data from Oura Ring (steps, total_calories = resting + active, active_calories, activity score, movement breakdown). For a long span (months, \"average per month\") pass aggregate: \"month\" for one row per calendar month with days_with_data and the mean total_calories / active_calories / steps / score; a window too long for daily rows in one result gets those monthly rows automatically, plus recent_days, the newest days in full, with the dates they cover (aggregate: \"day\" keeps the newest daily rows that fit). Oura's per-minute `met` and 5-minute `class_5_min` series are left out; pass detail: \"full\" for the raw records with them (large: ~5.5k characters a day).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"detail": {
"enum": [
"compact",
"full"
],
"type": "string",
"description": "\"full\": Oura's raw daily records, per-minute `met` and `class_5_min` series included. Default \"compact\"."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"aggregate": {
"enum": [
"month",
"day"
],
"type": "string",
"description": "\"month\": per-calendar-month means instead of daily records; \"day\": the newest daily records that fit"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Oura: Get Daily Activity",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description89/100
Get continuous heart rate from Oura Ring for a window, as fixed-width buckets (mean / min / max bpm, sized so the window fits) plus a summary. Times are the athlete's own wall clock. start_date / end_date are a plain YYYY-MM-DD (the athlete's local day) OR an ISO timestamp for a sub-day window, e.g. the outing the athlete says ran 6:00 to 9:15: oura_get_heart_rate({ start_date: "2026-11-07T06:00:00", end_date: "2026-11-07T09:15:00" }) (no offset = the athlete's local time). Oura only records while the ring is worn and charged: an empty or gapped window means no samples, never a flat heart rate. One call reads up to 14 days; buckets are 1 minute for a window under ~3h and widen with the window.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start: YYYY-MM-DD (athlete's local day start) or an ISO timestamp (no offset = athlete's local time)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End: YYYY-MM-DD (athlete's local day end) or an ISO timestamp (no offset = athlete's local time)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "oura_get_heart_rate",
"title": "Oura: Get Heart Rate",
"description": "Get continuous heart rate from Oura Ring for a window, as fixed-width buckets (mean / min / max bpm, sized so the window fits) plus a summary. Times are the athlete's own wall clock. start_date / end_date are a plain YYYY-MM-DD (the athlete's local day) OR an ISO timestamp for a sub-day window, e.g. the outing the athlete says ran 6:00 to 9:15: oura_get_heart_rate({ start_date: \"2026-11-07T06:00:00\", end_date: \"2026-11-07T09:15:00\" }) (no offset = the athlete's local time). Oura only records while the ring is worn and charged: an empty or gapped window means no samples, never a flat heart rate. One call reads up to 14 days; buckets are 1 minute for a window under ~3h and widen with the window.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end_date": {
"type": "string",
"description": "End: YYYY-MM-DD (athlete's local day end) or an ISO timestamp (no offset = athlete's local time)"
},
"start_date": {
"type": "string",
"description": "Start: YYYY-MM-DD (athlete's local day start) or an ISO timestamp (no offset = athlete's local time)"
}
}
},
"annotations": {
"title": "Oura: Get Heart Rate",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Oura's stage-level sleep per period: deep / REM / light / awake durations, overnight average and lowest HR, overnight HRV, restlessness, bedtime and wake time; the raw layer behind the sleep score (the 0-100 score alone is oura_get_daily_sleep). Example: oura_get_sleep_periods({ start_date: "2026-06-09", end_date: "2026-06-10" }) (nights are labelled by wake day, so include yesterday and today). The 5-minute HR / HRV arrays and hypnograms are left out; detail: "full" returns Oura's raw periods with them (about 3.5k characters a period).
LONG WINDOWS: a window too long for daily rows in one result comes back as one row per calendar month (aggregate: "month", days_with_data and means) plus recent_days, the newest records in full; aggregate: "month" asks for the monthly rows alone, aggregate: "day" for daily rows even when they will be cut. Monthly means use each night's main sleep (long_sleep); naps count in other_periods.
NIGHTS: each record's date is the WAKE date, so the night began the evening before. `night_of` names that evening in athlete-local time with the clock they fell asleep at; `after midnight` on it means they fell asleep past midnight, still the previous evening's night. A record without `night_of` has no stored onset or timezone.
For a night's stages, bed and wake time merged across devices, get_sleep returns one compact row per night; this tool is Oura's own record.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
aggregate
"month": per-calendar-month means instead of daily records; "day": the newest daily records that fit
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
detail
"full": Oura's raw sleep periods, 5-minute HR / HRV and hypnogram series included. Default "compact".
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "oura_get_sleep_periods",
"title": "Oura: Get Sleep Periods",
"description": "Oura's stage-level sleep per period: deep / REM / light / awake durations, overnight average and lowest HR, overnight HRV, restlessness, bedtime and wake time; the raw layer behind the sleep score (the 0-100 score alone is oura_get_daily_sleep). Example: oura_get_sleep_periods({ start_date: \"2026-06-09\", end_date: \"2026-06-10\" }) (nights are labelled by wake day, so include yesterday and today). The 5-minute HR / HRV arrays and hypnograms are left out; detail: \"full\" returns Oura's raw periods with them (about 3.5k characters a period).\n\nLONG WINDOWS: a window too long for daily rows in one result comes back as one row per calendar month (aggregate: \"month\", days_with_data and means) plus recent_days, the newest records in full; aggregate: \"month\" asks for the monthly rows alone, aggregate: \"day\" for daily rows even when they will be cut. Monthly means use each night's main sleep (long_sleep); naps count in other_periods.\n\nNIGHTS: each record's date is the WAKE date, so the night began the evening before. `night_of` names that evening in athlete-local time with the clock they fell asleep at; `after midnight` on it means they fell asleep past midnight, still the previous evening's night. A record without `night_of` has no stored onset or timezone.\n\nFor a night's stages, bed and wake time merged across devices, get_sleep returns one compact row per night; this tool is Oura's own record.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"detail": {
"enum": [
"compact",
"full"
],
"type": "string",
"description": "\"full\": Oura's raw sleep periods, 5-minute HR / HRV and hypnogram series included. Default \"compact\"."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"aggregate": {
"enum": [
"month",
"day"
],
"type": "string",
"description": "\"month\": per-calendar-month means instead of daily records; \"day\": the newest daily records that fit"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Oura: Get Sleep Periods",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description94/100
Get Oura-tracked workouts (manually-logged AND auto-detected): sport, intensity, duration, calories, distance. Oura auto-detects some workouts from movement alone (walks, commutes, housework), so an auto-detected Oura workout with no session at the same time in another connected source may not be a training session. Use when reviewing all-day movement or when no other activity source is connected. Example: oura_get_workouts({ start_date: "2026-05-01", end_date: "2026-05-07" }). A window too long for daily rows in one result comes back as one row per calendar month (aggregate: "month", days_with_data and the means or counts) for the whole window plus recent_days, the newest records in full, with the dates they cover; aggregate: "month" asks for the monthly rows alone, aggregate: "day" for the newest daily rows that fit.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
aggregate
"month": per-calendar-month means instead of daily records; "day": the newest daily records that fit
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "oura_get_workouts",
"title": "Oura: Get Workouts",
"description": "Get Oura-tracked workouts (manually-logged AND auto-detected): sport, intensity, duration, calories, distance. Oura auto-detects some workouts from movement alone (walks, commutes, housework), so an auto-detected Oura workout with no session at the same time in another connected source may not be a training session. Use when reviewing all-day movement or when no other activity source is connected. Example: oura_get_workouts({ start_date: \"2026-05-01\", end_date: \"2026-05-07\" }). A window too long for daily rows in one result comes back as one row per calendar month (aggregate: \"month\", days_with_data and the means or counts) for the whole window plus recent_days, the newest records in full, with the dates they cover; aggregate: \"month\" asks for the monthly rows alone, aggregate: \"day\" for the newest daily rows that fit.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"aggregate": {
"enum": [
"month",
"day"
],
"type": "string",
"description": "\"month\": per-calendar-month means instead of daily records; \"day\": the newest daily records that fit"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Oura: Get Workouts",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
Get Oura's daily stress score: 0-100 stress index plus high-stress minutes per day. Use when assessing autonomic load (work stress, sleep deprivation, illness onset) on top of training load. High stress on a day with a planned hard session signals reduced capacity to absorb that session. Example: oura_get_daily_stress({ start_date: "2026-05-01", end_date: "2026-05-07" }). A window too long for daily rows in one result comes back as one row per calendar month (aggregate: "month", days_with_data and the means or counts) for the whole window plus recent_days, the newest records in full, with the dates they cover; aggregate: "month" asks for the monthly rows alone, aggregate: "day" for the newest daily rows that fit.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
aggregate
"month": per-calendar-month means instead of daily records; "day": the newest daily records that fit
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "oura_get_daily_stress",
"title": "Oura: Get Daily Stress",
"description": "Get Oura's daily stress score: 0-100 stress index plus high-stress minutes per day. Use when assessing autonomic load (work stress, sleep deprivation, illness onset) on top of training load. High stress on a day with a planned hard session signals reduced capacity to absorb that session. Example: oura_get_daily_stress({ start_date: \"2026-05-01\", end_date: \"2026-05-07\" }). A window too long for daily rows in one result comes back as one row per calendar month (aggregate: \"month\", days_with_data and the means or counts) for the whole window plus recent_days, the newest records in full, with the dates they cover; aggregate: \"month\" asks for the monthly rows alone, aggregate: \"day\" for the newest daily rows that fit.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"aggregate": {
"enum": [
"month",
"day"
],
"type": "string",
"description": "\"month\": per-calendar-month means instead of daily records; \"day\": the newest daily records that fit"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Oura: Get Daily Stress",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description93/100
Get daily resilience data from Oura Ring - daytime recovery, evening recovery, and stress response metrics. Shows how well the body handles and recovers from stress throughout the day. A window too long for daily rows in one result comes back as one row per calendar month (aggregate: "month", days_with_data and the means or counts) for the whole window plus recent_days, the newest records in full, with the dates they cover; aggregate: "month" asks for the monthly rows alone, aggregate: "day" for the newest daily rows that fit.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
aggregate
"month": per-calendar-month means instead of daily records; "day": the newest daily records that fit
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "oura_get_daily_resilience",
"title": "Oura: Get Daily Resilience",
"description": "Get daily resilience data from Oura Ring - daytime recovery, evening recovery, and stress response metrics. Shows how well the body handles and recovers from stress throughout the day. A window too long for daily rows in one result comes back as one row per calendar month (aggregate: \"month\", days_with_data and the means or counts) for the whole window plus recent_days, the newest records in full, with the dates they cover; aggregate: \"month\" asks for the monthly rows alone, aggregate: \"day\" for the newest daily rows that fit.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"aggregate": {
"enum": [
"month",
"day"
],
"type": "string",
"description": "\"month\": per-calendar-month means instead of daily records; \"day\": the newest daily records that fit"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Oura: Get Daily Resilience",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
Get personal info from Oura Ring - age, weight, height, biological sex. Useful for contextualizing metrics like HRV ranges and calorie estimates. Returns Oura's personal_info record as Oura sends it.
The description explains the capability, when to use it, and what to expect.
Inputs100/100
This tool declares no inputs, so argument descriptions are not needed.
No input fields.
Annotations100/100
All four behavior hints are declared.
{
"title": "Oura: Get Personal Info",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
Output schema0/100
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "oura_get_personal_info",
"title": "Oura: Get Personal Info",
"description": "Get personal info from Oura Ring - age, weight, height, biological sex. Useful for contextualizing metrics like HRV ranges and calorie estimates. Returns Oura's personal_info record as Oura sends it.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {}
},
"annotations": {
"title": "Oura: Get Personal Info",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description94/100
Get daily blood oxygen (SpO2) levels from Oura Ring. Useful for detecting recovery issues, altitude adaptation, or illness. A window too long for daily rows in one result comes back as one row per calendar month (aggregate: "month", days_with_data and the means or counts) for the whole window plus recent_days, the newest records in full, with the dates they cover; aggregate: "month" asks for the monthly rows alone, aggregate: "day" for the newest daily rows that fit.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
aggregate
"month": per-calendar-month means instead of daily records; "day": the newest daily records that fit
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "oura_get_daily_spo2",
"title": "Oura: Get Daily SpO2",
"description": "Get daily blood oxygen (SpO2) levels from Oura Ring. Useful for detecting recovery issues, altitude adaptation, or illness. A window too long for daily rows in one result comes back as one row per calendar month (aggregate: \"month\", days_with_data and the means or counts) for the whole window plus recent_days, the newest records in full, with the dates they cover; aggregate: \"month\" asks for the monthly rows alone, aggregate: \"day\" for the newest daily rows that fit.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"aggregate": {
"enum": [
"month",
"day"
],
"type": "string",
"description": "\"month\": per-calendar-month means instead of daily records; \"day\": the newest daily records that fit"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Oura: Get Daily SpO2",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description95/100
Get VO2 max (cardio capacity) estimates from Oura Ring. Tracks cardiovascular fitness trends over time. Returns one record per day carrying `vo2_max` (ml/kg/min). A window too long for daily rows in one result comes back as one row per calendar month (aggregate: "month", days_with_data and the means or counts) for the whole window plus recent_days, the newest records in full, with the dates they cover; aggregate: "month" asks for the monthly rows alone, aggregate: "day" for the newest daily rows that fit.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
aggregate
"month": per-calendar-month means instead of daily records; "day": the newest daily records that fit
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "oura_get_vo2_max",
"title": "Oura: Get VO2 Max",
"description": "Get VO2 max (cardio capacity) estimates from Oura Ring. Tracks cardiovascular fitness trends over time. Returns one record per day carrying `vo2_max` (ml/kg/min). A window too long for daily rows in one result comes back as one row per calendar month (aggregate: \"month\", days_with_data and the means or counts) for the whole window plus recent_days, the newest records in full, with the dates they cover; aggregate: \"month\" asks for the monthly rows alone, aggregate: \"day\" for the newest daily rows that fit.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"aggregate": {
"enum": [
"month",
"day"
],
"type": "string",
"description": "\"month\": per-calendar-month means instead of daily records; \"day\": the newest daily records that fit"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Oura: Get VO2 Max",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description90/100
Get daily cardiovascular age from Oura Ring. Based on arterial stiffness/pulse wave velocity - shows heart health relative to chronological age. A window too long for daily rows in one result comes back as one row per calendar month (aggregate: "month", days_with_data and the means or counts) for the whole window plus recent_days, the newest records in full, with the dates they cover; aggregate: "month" asks for the monthly rows alone, aggregate: "day" for the newest daily rows that fit.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
aggregate
"month": per-calendar-month means instead of daily records; "day": the newest daily records that fit
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "oura_get_cardiovascular_age",
"title": "Oura: Get Cardiovascular Age",
"description": "Get daily cardiovascular age from Oura Ring. Based on arterial stiffness/pulse wave velocity - shows heart health relative to chronological age. A window too long for daily rows in one result comes back as one row per calendar month (aggregate: \"month\", days_with_data and the means or counts) for the whole window plus recent_days, the newest records in full, with the dates they cover; aggregate: \"month\" asks for the monthly rows alone, aggregate: \"day\" for the newest daily rows that fit.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"aggregate": {
"enum": [
"month",
"day"
],
"type": "string",
"description": "\"month\": per-calendar-month means instead of daily records; \"day\": the newest daily records that fit"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Oura: Get Cardiovascular Age",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description91/100
Get meditation, breathing, and relaxation sessions from Oura Ring. The per-sample HR, HRV and motion series are left out; pass detail: "full" for Oura's raw sessions with them. A window too long for daily rows in one result comes back as one row per calendar month (aggregate: "month", days_with_data and the means or counts) for the whole window plus recent_days, the newest records in full, with the dates they cover; aggregate: "month" asks for the monthly rows alone, aggregate: "day" for the newest daily rows that fit.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
aggregate
"month": per-calendar-month means instead of daily records; "day": the newest daily records that fit
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
detail
"full": Oura's raw sessions, per-sample HR / HRV / motion series included. Default "compact".
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "oura_get_sessions",
"title": "Oura: Get Sessions",
"description": "Get meditation, breathing, and relaxation sessions from Oura Ring. The per-sample HR, HRV and motion series are left out; pass detail: \"full\" for Oura's raw sessions with them. A window too long for daily rows in one result comes back as one row per calendar month (aggregate: \"month\", days_with_data and the means or counts) for the whole window plus recent_days, the newest records in full, with the dates they cover; aggregate: \"month\" asks for the monthly rows alone, aggregate: \"day\" for the newest daily rows that fit.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"detail": {
"enum": [
"compact",
"full"
],
"type": "string",
"description": "\"full\": Oura's raw sessions, per-sample HR / HRV / motion series included. Default \"compact\"."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"aggregate": {
"enum": [
"month",
"day"
],
"type": "string",
"description": "\"month\": per-calendar-month means instead of daily records; \"day\": the newest daily records that fit"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Oura: Get Sessions",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description92/100
Get user-created tags from Oura Ring (e.g. stress, alcohol, caffeine, illness, menstruation). Provides context about lifestyle factors affecting recovery and performance. Returns Oura's tag records as sent: each tag's type (or the athlete's custom name), when it starts and ends, and any comment the athlete added.
The description explains the capability, when to use it, and what to expect.
Inputs67/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "oura_get_enhanced_tags",
"title": "Oura: Get Enhanced Tags",
"description": "Get user-created tags from Oura Ring (e.g. stress, alcohol, caffeine, illness, menstruation). Provides context about lifestyle factors affecting recovery and performance. Returns Oura's tag records as sent: each tag's type (or the athlete's custom name), when it starts and ends, and any comment the athlete added.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Oura: Get Enhanced Tags",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description79/100
Get Oura's recommended bedtime windows. Shows optimal sleep timing and whether the user is following recommendations. A window too long for daily rows in one result comes back as one row per calendar month (aggregate: "month", days_with_data and the means or counts) for the whole window plus recent_days, the newest records in full, with the dates they cover; aggregate: "month" asks for the monthly rows alone, aggregate: "day" for the newest daily rows that fit.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
aggregate
"month": per-calendar-month means instead of daily records; "day": the newest daily records that fit
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "oura_get_sleep_time",
"title": "Oura: Get Recommended Bedtime",
"description": "Get Oura's recommended bedtime windows. Shows optimal sleep timing and whether the user is following recommendations. A window too long for daily rows in one result comes back as one row per calendar month (aggregate: \"month\", days_with_data and the means or counts) for the whole window plus recent_days, the newest records in full, with the dates they cover; aggregate: \"month\" asks for the monthly rows alone, aggregate: \"day\" for the newest daily rows that fit.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"aggregate": {
"enum": [
"month",
"day"
],
"type": "string",
"description": "\"month\": per-calendar-month means instead of daily records; \"day\": the newest daily records that fit"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Oura: Get Recommended Bedtime",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description94/100
Get rest mode periods from Oura Ring - times when the user activated rest mode (e.g. sick days, recovery periods). Includes tagged reasons. Returns Oura's rest mode records as sent: each period's start and end, and the episodes with their tags.
The description explains the capability, when to use it, and what to expect.
Inputs74/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "oura_get_rest_mode_periods",
"title": "Oura: Get Rest Mode Periods",
"description": "Get rest mode periods from Oura Ring - times when the user activated rest mode (e.g. sick days, recovery periods). Includes tagged reasons. Returns Oura's rest mode records as sent: each period's start and end, and the episodes with their tags.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Oura: Get Rest Mode Periods",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description94/100
Get Oura Ring hardware info: model (Gen 2 / Gen 3 / Gen 4), color, size, firmware version, setup date. Rarely needed for coaching - use only when troubleshooting ("why isn't my SpO2 showing?" - Gen 3+ only) or answering hardware questions. Example: oura_get_ring_configuration({}).
The description explains the capability, when to use it, and what to expect.
Inputs100/100
This tool declares no inputs, so argument descriptions are not needed.
No input fields.
Annotations100/100
All four behavior hints are declared.
{
"title": "Oura: Get Ring Configuration",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
Output schema0/100
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "oura_get_ring_configuration",
"title": "Oura: Get Ring Configuration",
"description": "Get Oura Ring hardware info: model (Gen 2 / Gen 3 / Gen 4), color, size, firmware version, setup date. Rarely needed for coaching - use only when troubleshooting (\"why isn't my SpO2 showing?\" - Gen 3+ only) or answering hardware questions. Example: oura_get_ring_configuration({}).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {}
},
"annotations": {
"title": "Oura: Get Ring Configuration",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Get WHOOP per-night recovery: recovery_score (0-100), HRV (rMSSD ms), resting HR, SpO2, skin temp deviation. recovery_score is WHOOP's composite; the raw HRV/RHR compared against this athlete's own baselines carries the underlying signal. Use when prescribing today's session intensity or interpreting a hard-day signal. DATES: each record's `wake_date` is the athlete-local day they woke from the night it scored - the `date` get_daily_metrics files that night's HRV/RHR under, so join on it (WHOOP's own record carries no date); start_date/end_date select on it. FRESHNESS: if the latest record's wake_date predates the athlete's current local date, they have NO recovery for last night and that record is not last night's or current. Example: whoop_get_recovery({ start_date: "2026-05-01", end_date: "2026-05-07" }).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
First wake_date to return, YYYY-MM-DD (the athlete's local calendar day)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
Last wake_date to return, YYYY-MM-DD (the athlete's local calendar day)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "whoop_get_recovery",
"title": "WHOOP: Get Recovery",
"description": "Get WHOOP per-night recovery: recovery_score (0-100), HRV (rMSSD ms), resting HR, SpO2, skin temp deviation. recovery_score is WHOOP's composite; the raw HRV/RHR compared against this athlete's own baselines carries the underlying signal. Use when prescribing today's session intensity or interpreting a hard-day signal. DATES: each record's `wake_date` is the athlete-local day they woke from the night it scored - the `date` get_daily_metrics files that night's HRV/RHR under, so join on it (WHOOP's own record carries no date); start_date/end_date select on it. FRESHNESS: if the latest record's wake_date predates the athlete's current local date, they have NO recovery for last night and that record is not last night's or current. Example: whoop_get_recovery({ start_date: \"2026-05-01\", end_date: \"2026-05-07\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end_date": {
"type": "string",
"description": "Last wake_date to return, YYYY-MM-DD (the athlete's local calendar day)"
},
"start_date": {
"type": "string",
"description": "First wake_date to return, YYYY-MM-DD (the athlete's local calendar day)"
}
}
},
"annotations": {
"title": "WHOOP: Get Recovery",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
Get WHOOP day-level physiological cycles: day strain (0-21 logarithmic - NOT TSS, NOT additive across activities), day-level avg/max HR, kilojoules burned, and the cycle's step_count (the day's steps; absent on a strap that does not count them, and still accumulating while the cycle has no end). Use when reviewing daily systemic load or steps. DATES: a cycle STARTS at bedtime, so its `start` is the evening before for any night begun before midnight; each record's `wake_date` is the athlete-local day its opening sleep ended - the day the cycle covers and the `date` get_daily_metrics uses for that night, so join on it. start_date/end_date select on it. `wake_date` is null when WHOOP recorded no main sleep for the cycle (strap off overnight); a response carrying `wake_date_note` instead had its sleep read fail, and its dates are approximate. WHOOP strain is NOT comparable to training_load (TSS), is not TSS, and has no meaningful sum. Example: whoop_get_cycles({ start_date: "2026-05-01", end_date: "2026-05-07" }).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
First wake_date to return, YYYY-MM-DD (the athlete's local calendar day)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
Last wake_date to return, YYYY-MM-DD (the athlete's local calendar day)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "whoop_get_cycles",
"title": "WHOOP: Get Cycles",
"description": "Get WHOOP day-level physiological cycles: day strain (0-21 logarithmic - NOT TSS, NOT additive across activities), day-level avg/max HR, kilojoules burned, and the cycle's step_count (the day's steps; absent on a strap that does not count them, and still accumulating while the cycle has no end). Use when reviewing daily systemic load or steps. DATES: a cycle STARTS at bedtime, so its `start` is the evening before for any night begun before midnight; each record's `wake_date` is the athlete-local day its opening sleep ended - the day the cycle covers and the `date` get_daily_metrics uses for that night, so join on it. start_date/end_date select on it. `wake_date` is null when WHOOP recorded no main sleep for the cycle (strap off overnight); a response carrying `wake_date_note` instead had its sleep read fail, and its dates are approximate. WHOOP strain is NOT comparable to training_load (TSS), is not TSS, and has no meaningful sum. Example: whoop_get_cycles({ start_date: \"2026-05-01\", end_date: \"2026-05-07\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end_date": {
"type": "string",
"description": "Last wake_date to return, YYYY-MM-DD (the athlete's local calendar day)"
},
"start_date": {
"type": "string",
"description": "First wake_date to return, YYYY-MM-DD (the athlete's local calendar day)"
}
}
},
"annotations": {
"title": "WHOOP: Get Cycles",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
WHOOP nightly sleep: in-bed and asleep durations, stages (light / deep / REM / awake), sleep performance / efficiency / consistency scores, respiratory rate, and sleep need vs achieved. Use for sleep-debt reviews, stage quality, or illness signals (respiratory rate more than 1 bpm above baseline). `wake_date` is the athlete-local day the sleep ended (the date get_daily_metrics files the night under) and is what start_date / end_date select on; naps carry `nap: true`; `night_of` names the evening the night began. Newest first. A window too long to fit whole (over about two weeks) comes back as one compact row per night, in minutes, with the window's means on top; `detail: "full"` returns WHOOP's raw records instead, whole nights in pages, a page with more behind it carrying `next_cursor` (pass it back as `cursor` with the same dates). For trends, monthly averages or before/after comparisons, get_sleep_trend answers in one call; for a night's stages and bed / wake time merged across devices, get_sleep is one compact row per night. Example: whoop_get_sleep({ start_date: "2026-05-01", end_date: "2026-05-07" }).
The description explains the capability, when to use it, and what to expect.
Inputs81/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
First wake_date to return, YYYY-MM-DD (the athlete's local calendar day)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
Last wake_date to return, YYYY-MM-DD (the athlete's local calendar day)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
cursor
Continuation token: the `next_cursor` value from this tool's previous response, passed back unchanged with the same other arguments to get the next page. Omit it for the first page.
Clear enough to supply this argument.
detail
"full": WHOOP's raw sleep records, in pages, instead of the compact rows a long window gets. Default "compact".
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "whoop_get_sleep",
"title": "WHOOP: Get Sleep",
"description": "WHOOP nightly sleep: in-bed and asleep durations, stages (light / deep / REM / awake), sleep performance / efficiency / consistency scores, respiratory rate, and sleep need vs achieved. Use for sleep-debt reviews, stage quality, or illness signals (respiratory rate more than 1 bpm above baseline). `wake_date` is the athlete-local day the sleep ended (the date get_daily_metrics files the night under) and is what start_date / end_date select on; naps carry `nap: true`; `night_of` names the evening the night began. Newest first. A window too long to fit whole (over about two weeks) comes back as one compact row per night, in minutes, with the window's means on top; `detail: \"full\"` returns WHOOP's raw records instead, whole nights in pages, a page with more behind it carrying `next_cursor` (pass it back as `cursor` with the same dates). For trends, monthly averages or before/after comparisons, get_sleep_trend answers in one call; for a night's stages and bed / wake time merged across devices, get_sleep is one compact row per night. Example: whoop_get_sleep({ start_date: \"2026-05-01\", end_date: \"2026-05-07\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"cursor": {
"type": "string",
"description": "Continuation token: the `next_cursor` value from this tool's previous response, passed back unchanged with the same other arguments to get the next page. Omit it for the first page."
},
"detail": {
"enum": [
"compact",
"full"
],
"type": "string",
"description": "\"full\": WHOOP's raw sleep records, in pages, instead of the compact rows a long window gets. Default \"compact\"."
},
"end_date": {
"type": "string",
"description": "Last wake_date to return, YYYY-MM-DD (the athlete's local calendar day)"
},
"start_date": {
"type": "string",
"description": "First wake_date to return, YYYY-MM-DD (the athlete's local calendar day)"
}
}
},
"annotations": {
"title": "WHOOP: Get Sleep",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description95/100
WHOOP-tracked workouts in a date window: sport, duration, average/max HR, time-in-zone (Z1-Z5), strain (0-21), energy (kJ). WHOOP auto-detects some workouts from heart rate alone (commutes, walks), so a WHOOP workout with no session at the same time in another connected source may not be a training session. SENSOR COVERAGE: `score.percent_recorded` is a raw fraction of 1, not a percentage (0.99999 means essentially complete heart-rate coverage). `hr_coverage_percent` (0-100) and `hr_coverage_note` are the readable form; `hr_coverage_note` is present only when a session's HR coverage is actually low, and typical values are 89-100%. NO SCORE: a record can carry `score_state: "SCORED"` with no `score` object (e.g. sauna), meaning WHOOP measured no HR or strain at all; such records carry `no_score_note`, are not low-coverage, and have no effort data. Example: whoop_get_workouts({ start_date: "2026-05-01", end_date: "2026-05-07" }).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format (the athlete's local calendar day)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format (the athlete's local calendar day)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "whoop_get_workouts",
"title": "WHOOP: Get Workouts",
"description": "WHOOP-tracked workouts in a date window: sport, duration, average/max HR, time-in-zone (Z1-Z5), strain (0-21), energy (kJ). WHOOP auto-detects some workouts from heart rate alone (commutes, walks), so a WHOOP workout with no session at the same time in another connected source may not be a training session. SENSOR COVERAGE: `score.percent_recorded` is a raw fraction of 1, not a percentage (0.99999 means essentially complete heart-rate coverage). `hr_coverage_percent` (0-100) and `hr_coverage_note` are the readable form; `hr_coverage_note` is present only when a session's HR coverage is actually low, and typical values are 89-100%. NO SCORE: a record can carry `score_state: \"SCORED\"` with no `score` object (e.g. sauna), meaning WHOOP measured no HR or strain at all; such records carry `no_score_note`, are not low-coverage, and have no effort data. Example: whoop_get_workouts({ start_date: \"2026-05-01\", end_date: \"2026-05-07\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format (the athlete's local calendar day)"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format (the athlete's local calendar day)"
}
}
},
"annotations": {
"title": "WHOOP: Get Workouts",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
Get body measurements from WHOOP - height, weight, max heart rate. Useful for contextualizing training zones and calorie estimates. Returns `height_meter`, `weight_kilogram` and `max_heart_rate` (bpm) as stored on the athlete's WHOOP profile.
The description explains the capability, when to use it, and what to expect.
Inputs100/100
This tool declares no inputs, so argument descriptions are not needed.
No input fields.
Annotations100/100
All four behavior hints are declared.
{
"title": "WHOOP: Get Body Measurement",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
Output schema0/100
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "whoop_get_body_measurement",
"title": "WHOOP: Get Body Measurement",
"description": "Get body measurements from WHOOP - height, weight, max heart rate. Useful for contextualizing training zones and calorie estimates. Returns `height_meter`, `weight_kilogram` and `max_heart_rate` (bpm) as stored on the athlete's WHOOP profile.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {}
},
"annotations": {
"title": "WHOOP: Get Body Measurement",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Lists Garmin activity summaries (runs, rides, swims, strength, etc.) in a date window: sport, duration, distance, avg/max HR, pace and power averages. Garmin's copy only: one day across every device, deduped, is get_activity_detail({ date }), which is also the per-session read (laps, splits, zones). Example: garmin_get_activities({ start_date: "2026-06-03", end_date: "2026-06-10" }).
NOT ADDITIVE: the same session also appears in strava_get_activities and intervals_get_activities. get_analytics_summary gives deduped weekly distance, hours and load with no row cap.
DAY: `local_date` and `local_weekday` are the session's day in the athlete's timezone; `startTimeInSeconds` is a UTC epoch whose day can differ. FRESHNESS: data arrives by Garmin webhook, so a session finished minutes ago may be missing; a window widened by a day catches it. POWER: a `power` block appears only on sessions we hold watts for. CALORIES: `activeKilocalories` includes resting kcal.
RESPONSE: a bare array, or an object with notes ahead of `activities`:
- `truncated`: rows are newest-first and capped by `limit`, so the OLDEST were cut; totals, streaks and bests need a narrower window or get_analytics_summary.
- `kcal_note`: session kcal include resting kcal.
- `coverage_gap`: sessions held from another source that Garmin will never send.
- `history_limit`: Garmin backfills only about 30 days at connect, so an empty stretch before that is not untrained time; their Garmin data export recovers it (link included).
- `archive_note`: rows older than Garmin's 30-day cache come from athletedata's store (`source_detail: "normalized_store"`) without device-native fields.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format (user-local). For 'last N days' use today minus N-1.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format (user-local), inclusive
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "garmin_get_activities",
"title": "Garmin: Get Activities",
"description": "Lists Garmin activity summaries (runs, rides, swims, strength, etc.) in a date window: sport, duration, distance, avg/max HR, pace and power averages. Garmin's copy only: one day across every device, deduped, is get_activity_detail({ date }), which is also the per-session read (laps, splits, zones). Example: garmin_get_activities({ start_date: \"2026-06-03\", end_date: \"2026-06-10\" }).\n\nNOT ADDITIVE: the same session also appears in strava_get_activities and intervals_get_activities. get_analytics_summary gives deduped weekly distance, hours and load with no row cap.\n\nDAY: `local_date` and `local_weekday` are the session's day in the athlete's timezone; `startTimeInSeconds` is a UTC epoch whose day can differ. FRESHNESS: data arrives by Garmin webhook, so a session finished minutes ago may be missing; a window widened by a day catches it. POWER: a `power` block appears only on sessions we hold watts for. CALORIES: `activeKilocalories` includes resting kcal.\n\nRESPONSE: a bare array, or an object with notes ahead of `activities`:\n- `truncated`: rows are newest-first and capped by `limit`, so the OLDEST were cut; totals, streaks and bests need a narrower window or get_analytics_summary.\n- `kcal_note`: session kcal include resting kcal.\n- `coverage_gap`: sessions held from another source that Garmin will never send.\n- `history_limit`: Garmin backfills only about 30 days at connect, so an empty stretch before that is not untrained time; their Garmin data export recovers it (link included).\n- `archive_note`: rows older than Garmin's 30-day cache come from athletedata's store (`source_detail: \"normalized_store\"`) without device-native fields.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format (user-local), inclusive"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format (user-local). For 'last N days' use today minus N-1."
}
}
},
"annotations": {
"title": "Garmin: Get Activities",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
Get detailed Garmin activity by ID: summary stats, splits, HR zones, and the session's power. POWER: Garmin's own activity summary carries NO power field, so a session we hold watts for carries a `power` block instead (`avg_watts` / `normalized_watts` / `max_watts` / `meter`) - that block is the session's power, qualified by `power_note`. Its absence means we hold no power for that session, never that the ride had none recorded. CALORIES: `activeKilocalories` on a session is Garmin's activity calories (active PLUS resting kcal for that session), not the daily active figure - read `kcal_note` before quoting it or comparing it with garmin_get_daily_summaries. NOTE: Garmin's activity-summary push does NOT include Training Effect, anaerobic TE, Intensity Factor, TSS, or training/exercise load — those live in the .fit session message, so use garmin_get_activity_file for any of them. Use this tool when you have a specific activity id (e.g. from a webhook) and need splits/HR-zone depth beyond what get_activity_detail provides; the parameter is spelled `id`, NOT `activity_id`. For per-rep analysis use get_activity_detail. Example: garmin_get_activity({ id: "18923456789" }). Each row carries `local_date` (YYYY-MM-DD) + `local_weekday` in the athlete's own timezone - take the day from those, never from the UTC `startTimeInSeconds` epoch.
The description explains the capability, when to use it, and what to expect.
Inputs95/100
Input documentation gives an agent enough context to populate the arguments.
id
The `activityId` (or `summaryId`) of a row from garmin_get_activities.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "garmin_get_activity",
"title": "Garmin: Get Activity",
"description": "Get detailed Garmin activity by ID: summary stats, splits, HR zones, and the session's power. POWER: Garmin's own activity summary carries NO power field, so a session we hold watts for carries a `power` block instead (`avg_watts` / `normalized_watts` / `max_watts` / `meter`) - that block is the session's power, qualified by `power_note`. Its absence means we hold no power for that session, never that the ride had none recorded. CALORIES: `activeKilocalories` on a session is Garmin's activity calories (active PLUS resting kcal for that session), not the daily active figure - read `kcal_note` before quoting it or comparing it with garmin_get_daily_summaries. NOTE: Garmin's activity-summary push does NOT include Training Effect, anaerobic TE, Intensity Factor, TSS, or training/exercise load — those live in the .fit session message, so use garmin_get_activity_file for any of them. Use this tool when you have a specific activity id (e.g. from a webhook) and need splits/HR-zone depth beyond what get_activity_detail provides; the parameter is spelled `id`, NOT `activity_id`. For per-rep analysis use get_activity_detail. Example: garmin_get_activity({ id: \"18923456789\" }). Each row carries `local_date` (YYYY-MM-DD) + `local_weekday` in the athlete's own timezone - take the day from those, never from the UTC `startTimeInSeconds` epoch.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"id"
],
"properties": {
"id": {
"type": "string",
"description": "The `activityId` (or `summaryId`) of a row from garmin_get_activities."
}
}
},
"annotations": {
"title": "Garmin: Get Activity",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Garmin per-day rollups: steps, active and resting calories, intensity minutes, average stress, body battery charged and drained for the day (`bodyBatteryChargedValue` / `bodyBatteryDrainedValue`) and its levels (`bodyBatteryAtWakeTime`, `bodyBatteryLatestValue`; highest / lowest once the day is settled), resting HR, floors climbed. Use for all-day load and recovery outside workouts ("is today a rest day?"). The daily `activeKilocalories` counts only calories above resting, so it is not the sum of the sessions' own kcal. Garmin pushes running snapshots: each row is the fullest one we hold for that day, with `day_window_hours` and `is_partial_day`, and a partial row (a day still in progress) carries `partial_day_note` because its counters are totals so far. Rows older than Garmin's 30-day cache come from athletedata's stored record, marked `source_detail` and explained by `archive_note`; `truncated` appears when the window holds more rows than `limit`. Example: garmin_get_daily_summaries({ start_date: "2026-05-01", end_date: "2026-05-07" }).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "garmin_get_daily_summaries",
"title": "Garmin: Get Daily Summaries",
"description": "Garmin per-day rollups: steps, active and resting calories, intensity minutes, average stress, body battery charged and drained for the day (`bodyBatteryChargedValue` / `bodyBatteryDrainedValue`) and its levels (`bodyBatteryAtWakeTime`, `bodyBatteryLatestValue`; highest / lowest once the day is settled), resting HR, floors climbed. Use for all-day load and recovery outside workouts (\"is today a rest day?\"). The daily `activeKilocalories` counts only calories above resting, so it is not the sum of the sessions' own kcal. Garmin pushes running snapshots: each row is the fullest one we hold for that day, with `day_window_hours` and `is_partial_day`, and a partial row (a day still in progress) carries `partial_day_note` because its counters are totals so far. Rows older than Garmin's 30-day cache come from athletedata's stored record, marked `source_detail` and explained by `archive_note`; `truncated` appears when the window holds more rows than `limit`. Example: garmin_get_daily_summaries({ start_date: \"2026-05-01\", end_date: \"2026-05-07\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Garmin: Get Daily Summaries",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Garmin sleep records, one per night: total / deep / light / REM / awake duration, sleep score (0-100), `sleepScores.restlessness`, and the night's breathing rate and SpO2 as `sleepRespirationSummary` / `sleepSpo2Summary` ({avg, min, max, samples}) when the device measured them; a window too long to fit comes back as one compact row per night, in minutes, with the window's means on top. `detail: "full"` returns Garmin's raw records with the per-minute `timeOffsetSleepSpo2` / `timeOffsetSleepRespiration` series. `include_stage_timeline: true` adds each night's stage timeline (`sleepLevelsMap`, epoch seconds); it is left out by default. For "how did I sleep" or a night's stages, bed and wake time across devices, get_sleep answers in one compact row per night; this tool is Garmin's own raw record (the series, the stage timeline, restlessness) or a cross-check of another tracker. Durations are raw seconds (`*InSeconds`), each with a readable `*Display` sibling (naps carry raw seconds only). A Garmin sleep record has no HRV field; overnight HRV is garmin_get_hrv. DATES: a record's date is the WAKE date, and `night_of` names the evening the night began (athlete-local, with the clock they fell asleep at). `is_current_night` marks last night, and the newest record carries `staleness_note` when last night has not synced. Rows older than Garmin's 30-day cache come from athletedata's stored record, marked `source_detail` and explained by `archive_note`; `truncated` appears when the window holds more rows than `limit`. Example: garmin_get_sleep({ start_date: "2026-05-01", end_date: "2026-05-07" }).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
include_stage_timeline
Also return each night's stage timeline (`sleepLevelsMap`), about 1-2k characters a night. Default false.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
detail
"full": Garmin's raw records with the per-minute breathing / SpO2 series and no compaction. Default "compact".
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "garmin_get_sleep",
"title": "Garmin: Get Sleep",
"description": "Garmin sleep records, one per night: total / deep / light / REM / awake duration, sleep score (0-100), `sleepScores.restlessness`, and the night's breathing rate and SpO2 as `sleepRespirationSummary` / `sleepSpo2Summary` ({avg, min, max, samples}) when the device measured them; a window too long to fit comes back as one compact row per night, in minutes, with the window's means on top. `detail: \"full\"` returns Garmin's raw records with the per-minute `timeOffsetSleepSpo2` / `timeOffsetSleepRespiration` series. `include_stage_timeline: true` adds each night's stage timeline (`sleepLevelsMap`, epoch seconds); it is left out by default. For \"how did I sleep\" or a night's stages, bed and wake time across devices, get_sleep answers in one compact row per night; this tool is Garmin's own raw record (the series, the stage timeline, restlessness) or a cross-check of another tracker. Durations are raw seconds (`*InSeconds`), each with a readable `*Display` sibling (naps carry raw seconds only). A Garmin sleep record has no HRV field; overnight HRV is garmin_get_hrv. DATES: a record's date is the WAKE date, and `night_of` names the evening the night began (athlete-local, with the clock they fell asleep at). `is_current_night` marks last night, and the newest record carries `staleness_note` when last night has not synced. Rows older than Garmin's 30-day cache come from athletedata's stored record, marked `source_detail` and explained by `archive_note`; `truncated` appears when the window holds more rows than `limit`. Example: garmin_get_sleep({ start_date: \"2026-05-01\", end_date: \"2026-05-07\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit."
},
"detail": {
"enum": [
"compact",
"full"
],
"type": "string",
"description": "\"full\": Garmin's raw records with the per-minute breathing / SpO2 series and no compaction. Default \"compact\"."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
},
"include_stage_timeline": {
"type": "boolean",
"description": "Also return each night's stage timeline (`sleepLevelsMap`), about 1-2k characters a night. Default false."
}
}
},
"annotations": {
"title": "Garmin: Get Sleep",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description93/100
Get Garmin Index Scale body comp readings: weight (kg), body fat %, muscle mass (kg), bone mass, body water %, BMI. Holds data only for athletes who weigh in on a Garmin Index scale. Example: garmin_get_body_composition({ start_date: "2026-04-01", end_date: "2026-05-07" }). OLDER HISTORY: dates older than Garmin's 30-day raw webhook cache, and everything from a Garmin "Export Your Data" import, are served from athletedata's own durable record, marked `source_detail` (`durable_archive` = Garmin's complete record, same shape as newer rows; `normalized_store` = headline values rebuilt from stored data). When present, `archive_note` leads the result and explains the marker; those rows are not incomplete, unsynced or less certain than the others.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Annotations100/100
All four behavior hints are declared.
{
"title": "Garmin: Get Body Composition",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
Output schema0/100
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "garmin_get_body_composition",
"title": "Garmin: Get Body Composition",
"description": "Get Garmin Index Scale body comp readings: weight (kg), body fat %, muscle mass (kg), bone mass, body water %, BMI. Holds data only for athletes who weigh in on a Garmin Index scale. Example: garmin_get_body_composition({ start_date: \"2026-04-01\", end_date: \"2026-05-07\" }). OLDER HISTORY: dates older than Garmin's 30-day raw webhook cache, and everything from a Garmin \"Export Your Data\" import, are served from athletedata's own durable record, marked `source_detail` (`durable_archive` = Garmin's complete record, same shape as newer rows; `normalized_store` = headline values rebuilt from stored data). When present, `archive_note` leads the result and explains the marker; those rows are not incomplete, unsynced or less certain than the others.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Garmin: Get Body Composition",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Get Garmin all-day stress + BODY BATTERY, one row per calendar day: average / max stress score (0-100) and high/medium/low/rest minutes, plus `bodyBatteryHighestValue`, `bodyBatteryLowestValue`, `bodyBatteryLatestValue` and (when the athlete slept with the watch on) `bodyBatteryAtWakeTime` - the level they woke up on, which is the number athletes usually mean by "my body battery". garmin_get_daily_summaries carries the same levels beside the day's CHARGE and DRAIN. `bodyBatterySampleCount` says how many readings the day's levels were computed from - a settled day is roughly 480, so a low count means the watch was off the wrist for most of it and the high/low are not the day's true range. PARTIAL DAYS: `is_partial_day` / `partial_day_note` mean exactly what they do on garmin_get_daily_summaries - on a day still in progress the high and low are "so far today", while `bodyBatteryLatestValue` and `bodyBatteryAtWakeTime` are unaffected by it. Use when assessing autonomic load on top of training. Body battery dropping fast on a rest day = something else is taxing the athlete (sleep debt, illness, work). Example: garmin_get_stress({ start_date: "2026-05-01", end_date: "2026-05-07" }).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "garmin_get_stress",
"title": "Garmin: Get Stress",
"description": "Get Garmin all-day stress + BODY BATTERY, one row per calendar day: average / max stress score (0-100) and high/medium/low/rest minutes, plus `bodyBatteryHighestValue`, `bodyBatteryLowestValue`, `bodyBatteryLatestValue` and (when the athlete slept with the watch on) `bodyBatteryAtWakeTime` - the level they woke up on, which is the number athletes usually mean by \"my body battery\". garmin_get_daily_summaries carries the same levels beside the day's CHARGE and DRAIN. `bodyBatterySampleCount` says how many readings the day's levels were computed from - a settled day is roughly 480, so a low count means the watch was off the wrist for most of it and the high/low are not the day's true range. PARTIAL DAYS: `is_partial_day` / `partial_day_note` mean exactly what they do on garmin_get_daily_summaries - on a day still in progress the high and low are \"so far today\", while `bodyBatteryLatestValue` and `bodyBatteryAtWakeTime` are unaffected by it. Use when assessing autonomic load on top of training. Body battery dropping fast on a rest day = something else is taxing the athlete (sleep debt, illness, work). Example: garmin_get_stress({ start_date: \"2026-05-01\", end_date: \"2026-05-07\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Garmin: Get Stress",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description93/100
Get Garmin SpO2 readings: continuous and on-demand pulse oximetry (avg/min %). ALL-DAY summaries come only from the athlete's "Export Your Data" archive import; Garmin's live feed does not deliver them. When the window holds none, one row per night is derived from the sleep records instead, marked `source: "sleep_window"` (`sleepSpo2` {avg,min,max,samples}): that is the SLEEP window's SpO2, never an all-day figure. Use when investigating altitude exposure, sleep-disordered breathing signals, or unexplained recovery dips (SpO2 dropping below 90% during sleep is a flag). Example: garmin_get_pulse_ox({ start_date: "2026-05-01", end_date: "2026-05-07" }).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "garmin_get_pulse_ox",
"title": "Garmin: Get Pulse Ox",
"description": "Get Garmin SpO2 readings: continuous and on-demand pulse oximetry (avg/min %). ALL-DAY summaries come only from the athlete's \"Export Your Data\" archive import; Garmin's live feed does not deliver them. When the window holds none, one row per night is derived from the sleep records instead, marked `source: \"sleep_window\"` (`sleepSpo2` {avg,min,max,samples}): that is the SLEEP window's SpO2, never an all-day figure. Use when investigating altitude exposure, sleep-disordered breathing signals, or unexplained recovery dips (SpO2 dropping below 90% during sleep is a flag). Example: garmin_get_pulse_ox({ start_date: \"2026-05-01\", end_date: \"2026-05-07\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Garmin: Get Pulse Ox",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description93/100
Get Garmin daily HR series: resting HR, max HR, average HR over 24h. Use for RHR trend reviews (is RHR drifting up = stress/illness/overreaching, drifting down = adaptation). Pair with garmin_get_hrv for fuller autonomic picture. Example: garmin_get_heart_rate({ start_date: "2026-04-01", end_date: "2026-05-07" }). OLDER HISTORY: dates older than Garmin's 30-day raw webhook cache, and everything from a Garmin "Export Your Data" import, are served from athletedata's own durable record, marked `source_detail` (`durable_archive` = Garmin's complete record, same shape as newer rows; `normalized_store` = headline values rebuilt from stored data). When present, `archive_note` leads the result and explains the marker; those rows are not incomplete, unsynced or less certain than the others.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "garmin_get_heart_rate",
"title": "Garmin: Get Heart Rate",
"description": "Get Garmin daily HR series: resting HR, max HR, average HR over 24h. Use for RHR trend reviews (is RHR drifting up = stress/illness/overreaching, drifting down = adaptation). Pair with garmin_get_hrv for fuller autonomic picture. Example: garmin_get_heart_rate({ start_date: \"2026-04-01\", end_date: \"2026-05-07\" }). OLDER HISTORY: dates older than Garmin's 30-day raw webhook cache, and everything from a Garmin \"Export Your Data\" import, are served from athletedata's own durable record, marked `source_detail` (`durable_archive` = Garmin's complete record, same shape as newer rows; `normalized_store` = headline values rebuilt from stored data). When present, `archive_note` leads the result and explains the marker; those rows are not incomplete, unsynced or less certain than the others.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Garmin: Get Heart Rate",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description92/100
Get Garmin respiration data: average / lowest / highest breaths-per-minute over the day. ALL-DAY summaries come only from the athlete's "Export Your Data" archive import; Garmin's live feed does not deliver them. When the window holds none, one row per night is derived from the sleep records instead, marked `source: "sleep_window"` (`sleepRespiration` {avg,min,max,samples}): that is the SLEEP window's breathing rate, never an all-day or waking figure. Use when investigating illness onset (resp rate elevated >1-2 bpm above baseline is an early warning) or sleep quality. Example: garmin_get_respiration({ start_date: "2026-05-01", end_date: "2026-05-07" }).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "garmin_get_respiration",
"title": "Garmin: Get Respiration",
"description": "Get Garmin respiration data: average / lowest / highest breaths-per-minute over the day. ALL-DAY summaries come only from the athlete's \"Export Your Data\" archive import; Garmin's live feed does not deliver them. When the window holds none, one row per night is derived from the sleep records instead, marked `source: \"sleep_window\"` (`sleepRespiration` {avg,min,max,samples}): that is the SLEEP window's breathing rate, never an all-day or waking figure. Use when investigating illness onset (resp rate elevated >1-2 bpm above baseline is an early warning) or sleep quality. Example: garmin_get_respiration({ start_date: \"2026-05-01\", end_date: \"2026-05-07\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Garmin: Get Respiration",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description82/100
Get Garmin's computed user metrics: VO2 max (running and cycling separately), fitness age, lactate threshold HR/pace if estimated. Use when answering capability questions ("what's my current VO2 max?") or sanity-checking goal pacing - Garmin VO2 max trend is a useful long-term fitness indicator. Example: garmin_get_user_metrics({ start_date: "2026-04-01", end_date: "2026-05-07" }).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Annotations100/100
All four behavior hints are declared.
{
"title": "Garmin: Get User Metrics",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
Output schema0/100
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "garmin_get_user_metrics",
"title": "Garmin: Get User Metrics",
"description": "Get Garmin's computed user metrics: VO2 max (running and cycling separately), fitness age, lactate threshold HR/pace if estimated. Use when answering capability questions (\"what's my current VO2 max?\") or sanity-checking goal pacing - Garmin VO2 max trend is a useful long-term fitness indicator. Example: garmin_get_user_metrics({ start_date: \"2026-04-01\", end_date: \"2026-05-07\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Garmin: Get User Metrics",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Garmin overnight HRV, one record per night as Garmin's Health API sends it: `lastNightAvg` (the night's average in ms), `lastNight5MinHigh`, `hrvValuesSummary` ({avg, min, max, samples} of the 5-minute series; `detail: "full"` returns the `hrvValues` series itself) and the night's window. Garmin's HRV Status weekly average, status label (balanced / unbalanced / poor) and baseline range are computed inside Garmin Connect and are not in this feed. Instead each record carries `trailing_7night_avg_ms` (athletedata's mean of the nightly averages when at least 4 of the last 7 nights are known, explained by `trailing_7night_avg_note`), close to but not identical to Garmin's HRV Status figure. Dates are WAKE dates; `is_current_night` marks last night, and the newest record carries `staleness_note` when last night has not synced. Rows older than Garmin's 30-day cache come from athletedata's stored record, marked `source_detail` and explained by `archive_note`; `truncated` appears when the window holds more rows than `limit`. Example: garmin_get_hrv({ start_date: "2026-05-01", end_date: "2026-05-07" }).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
detail
"full": each night's 5-minute `hrvValues` series instead of its summary. Default "compact".
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "garmin_get_hrv",
"title": "Garmin: Get HRV",
"description": "Garmin overnight HRV, one record per night as Garmin's Health API sends it: `lastNightAvg` (the night's average in ms), `lastNight5MinHigh`, `hrvValuesSummary` ({avg, min, max, samples} of the 5-minute series; `detail: \"full\"` returns the `hrvValues` series itself) and the night's window. Garmin's HRV Status weekly average, status label (balanced / unbalanced / poor) and baseline range are computed inside Garmin Connect and are not in this feed. Instead each record carries `trailing_7night_avg_ms` (athletedata's mean of the nightly averages when at least 4 of the last 7 nights are known, explained by `trailing_7night_avg_note`), close to but not identical to Garmin's HRV Status figure. Dates are WAKE dates; `is_current_night` marks last night, and the newest record carries `staleness_note` when last night has not synced. Rows older than Garmin's 30-day cache come from athletedata's stored record, marked `source_detail` and explained by `archive_note`; `truncated` appears when the window holds more rows than `limit`. Example: garmin_get_hrv({ start_date: \"2026-05-01\", end_date: \"2026-05-07\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit."
},
"detail": {
"enum": [
"compact",
"full"
],
"type": "string",
"description": "\"full\": each night's 5-minute `hrvValues` series instead of its summary. Default \"compact\"."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Garmin: Get HRV",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Garmin activity-detail summaries for every activity in a date RANGE (it takes dates, not an activity id, and returns a list): distance, duration, HR, speed, elevation, calories, device name, with `local_date` / `local_weekday` in the athlete's timezone (`startTimeInSeconds` is UTC). Keep the window to 1-3 days. Per-second samples are stripped and laps are collapsed to a count, because this feed cannot tell auto-laps from interval laps: for reps, running dynamics or full-resolution data use garmin_get_activity_file. A session's `activeKilocalories` is its active plus resting kcal (`kcal_note`), not the daily active figure in garmin_get_daily_summaries. Power, when we hold it, is a `power` block (`avg_watts`, `normalized_watts`, `max_watts`, `meter`) with `power_note`; no block means we hold no power for that session. Results are newest-first and capped (default 100, `limit` max 500); `truncated` says how many records the window holds when older ones were cut. `coverage_gap` appears when we hold sessions from another source that Garmin lacks, and `history_limit` when the window reaches past the start of the athlete's Garmin record. Example: garmin_get_activity_details({ start_date: "2026-06-09", end_date: "2026-06-10" }).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format (user-local). Keep the window tight (1-3 days) - payloads are large.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format (user-local), inclusive
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "garmin_get_activity_details",
"title": "Garmin: Get Activity Details",
"description": "Garmin activity-detail summaries for every activity in a date RANGE (it takes dates, not an activity id, and returns a list): distance, duration, HR, speed, elevation, calories, device name, with `local_date` / `local_weekday` in the athlete's timezone (`startTimeInSeconds` is UTC). Keep the window to 1-3 days. Per-second samples are stripped and laps are collapsed to a count, because this feed cannot tell auto-laps from interval laps: for reps, running dynamics or full-resolution data use garmin_get_activity_file. A session's `activeKilocalories` is its active plus resting kcal (`kcal_note`), not the daily active figure in garmin_get_daily_summaries. Power, when we hold it, is a `power` block (`avg_watts`, `normalized_watts`, `max_watts`, `meter`) with `power_note`; no block means we hold no power for that session. Results are newest-first and capped (default 100, `limit` max 500); `truncated` says how many records the window holds when older ones were cut. `coverage_gap` appears when we hold sessions from another source that Garmin lacks, and `history_limit` when the window reaches past the start of the athlete's Garmin record. Example: garmin_get_activity_details({ start_date: \"2026-06-09\", end_date: \"2026-06-10\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format (user-local), inclusive"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format (user-local). Keep the window tight (1-3 days) - payloads are large."
}
}
},
"annotations": {
"title": "Garmin: Get Activity Details",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description100/100
Get Garmin skin temperature deviation (measured during sleep, in °C from baseline). Use when investigating illness onset (+0.3°C deviation often precedes symptoms by 24-48h), menstrual phase tracking, or unexplained recovery dips. Watch-only feature - not all Garmin devices report it. Returns Garmin's stored skin-temperature records newest first, or an object with `truncated` and `records` when the window holds more than `limit`. Example: garmin_get_skin_temps({ start_date: "2026-05-01", end_date: "2026-05-07" }).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "garmin_get_skin_temps",
"title": "Garmin: Get Skin Temps",
"description": "Get Garmin skin temperature deviation (measured during sleep, in °C from baseline). Use when investigating illness onset (+0.3°C deviation often precedes symptoms by 24-48h), menstrual phase tracking, or unexplained recovery dips. Watch-only feature - not all Garmin devices report it. Returns Garmin's stored skin-temperature records newest first, or an object with `truncated` and `records` when the window holds more than `limit`. Example: garmin_get_skin_temps({ start_date: \"2026-05-01\", end_date: \"2026-05-07\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Garmin: Get Skin Temps",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description96/100
Get Garmin health snapshot data (2-min on-demand readings: HR, HRV, stress, SpO2, respiration). Returns Garmin's stored snapshot records newest first, one per snapshot the athlete took, or an object with `truncated` and `records` when the window holds more than `limit`.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Annotations100/100
All four behavior hints are declared.
{
"title": "Garmin: Get Health Snapshot",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
Output schema0/100
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "garmin_get_health_snapshot",
"title": "Garmin: Get Health Snapshot",
"description": "Get Garmin health snapshot data (2-min on-demand readings: HR, HRV, stress, SpO2, respiration). Returns Garmin's stored snapshot records newest first, one per snapshot the athlete took, or an object with `truncated` and `records` when the window holds more than `limit`.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Garmin: Get Health Snapshot",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description93/100
Get Move IQ auto-detected activities from Garmin (walking, running, cycling detected automatically). Each row carries `local_date` (YYYY-MM-DD) + `local_weekday` in the athlete's own timezone - take the day from those, never from the UTC `startTimeInSeconds` epoch. Returns one row per detected activity (`activityType`, `durationInSeconds`, start time, local day), newest first, or an object with `truncated` and `records` when the window holds more than `limit`.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "garmin_get_move_iq",
"title": "Garmin: Get Move IQ",
"description": "Get Move IQ auto-detected activities from Garmin (walking, running, cycling detected automatically). Each row carries `local_date` (YYYY-MM-DD) + `local_weekday` in the athlete's own timezone - take the day from those, never from the UTC `startTimeInSeconds` epoch. Returns one row per detected activity (`activityType`, `durationInSeconds`, start time, local day), newest first, or an object with `truncated` and `records` when the window holds more than `limit`.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this silently loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Garmin: Get Move IQ",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
A second read, not the first: get_activity_detail is the read for any session, Garmin's included (laps, splits, zones, load, strength sets); come here only for what it lacks. Parsed .fit for one Garmin activity: `sessions`, `laps`, `lengths` (pool), per-sample `records`, `local_date`/`local_weekday`. Takes a Garmin ID or the matching Strava ID. Files land about 5 min after the activity; on "no file stored" the summary and lap tools are the fallback. A stored snapshot: later Garmin Connect edits never appear.
Only here: per-lap normalized power (`np`); Training Effect, IF, TSS and load in `sessions[0]`; running dynamics; per-lap Cycling Dynamics; `athleteRating` (RPE, feel); `deviceSettings` (watch HRmax, LTHR, threshold power); `ambientWeather`; `deviceTimeInZone` (the watch's own zone split as Garmin Connect shows it; get_activity_detail `hrZones` is athletedata's); `bestEfforts`. Run cadence is total steps/min; altitude in meters.
Laps: `lap_trigger` manual = a lap-button press (a real interval); distance/time = auto-lap. `laps.length` is not the rep count: `workoutSteps` merges auto-laps into reps (`lapStructureWarning`). Pool: each `lengths` line is one length; `rest` lines mark rep boundaries.
Strength: `strength` block (exercise, sets, reps, weight); names are wrist-motion guesses unless we pushed the workout; `loadDataAbsentNote` = weight unrecorded, not zero.
`records` span the whole activity or the `start_seconds`/`end_seconds` window; when `sample_seconds` (default 30) does not fit, the interval widens (`samplingNote`) instead of cutting the end. A narrow window with `sample_seconds: 1` gives 1 Hz. `channels` adds `hr_sources`, `gap`, `stamina`, `body_battery`, `performance_condition`, `step_speed_loss`.
`hr` is the watch's pick between wrist and strap; `hrSourceSummary` and `conflictAttribution` say which sensor departed when they disagree. Files ingested before 2026-08-25 lack HR-source, device-zone, weather and channel data.
The description explains the capability, when to use it, and what to expect.
Inputs95/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
id
Garmin activity ID (Strava ID also accepted; auto-resolves to the matching Garmin activity)
Clear enough to supply this argument.
sample_seconds
Downsample interval in seconds (default 30). Set to 1 for full 1 Hz resolution.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_seconds
Optional: only return records at/after this many elapsed seconds from activity start. Pair with end_seconds to pull a bounded high-resolution slice of a long activity without the from-the-start read truncating first.
Clear enough to supply this argument.
end_seconds
Optional: only return records at/before this many elapsed seconds from activity start.
Clear enough to supply this argument.
channels
Optional: append Garmin PROPRIETARY per-sample channels to each record line. `hr_sources` = the raw wrist and strap heart-rate channels (use when you need to know WHICH sensor a heart rate came from, or when hrSourceSummary reports a conflict window); `gap` = grade-adjusted speed (use on any hilly or rolling session - per-km net elevation cannot see repeated up-and-down inside one kilometre); `stamina` = stamina potential and actual; `body_battery`; `performance_condition`; `step_speed_loss`. When the file lacks a requested channel the response says so in `channelsNote` (a file with no `wrist`/`strap` tokens and no hrSourceSummary carries only the selected `hr` series - the sensor behind a dropout cannot be attributed from it). Opt-in because each added channel widens every line and therefore COARSENS the sampling interval the budget allows - ask only for what the question needs, and pair with start_seconds/end_seconds to keep full resolution on the segment you care about.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "garmin_get_activity_file",
"title": "Garmin: Get Activity File",
"description": "A second read, not the first: get_activity_detail is the read for any session, Garmin's included (laps, splits, zones, load, strength sets); come here only for what it lacks. Parsed .fit for one Garmin activity: `sessions`, `laps`, `lengths` (pool), per-sample `records`, `local_date`/`local_weekday`. Takes a Garmin ID or the matching Strava ID. Files land about 5 min after the activity; on \"no file stored\" the summary and lap tools are the fallback. A stored snapshot: later Garmin Connect edits never appear.\n\nOnly here: per-lap normalized power (`np`); Training Effect, IF, TSS and load in `sessions[0]`; running dynamics; per-lap Cycling Dynamics; `athleteRating` (RPE, feel); `deviceSettings` (watch HRmax, LTHR, threshold power); `ambientWeather`; `deviceTimeInZone` (the watch's own zone split as Garmin Connect shows it; get_activity_detail `hrZones` is athletedata's); `bestEfforts`. Run cadence is total steps/min; altitude in meters.\n\nLaps: `lap_trigger` manual = a lap-button press (a real interval); distance/time = auto-lap. `laps.length` is not the rep count: `workoutSteps` merges auto-laps into reps (`lapStructureWarning`). Pool: each `lengths` line is one length; `rest` lines mark rep boundaries.\n\nStrength: `strength` block (exercise, sets, reps, weight); names are wrist-motion guesses unless we pushed the workout; `loadDataAbsentNote` = weight unrecorded, not zero.\n\n`records` span the whole activity or the `start_seconds`/`end_seconds` window; when `sample_seconds` (default 30) does not fit, the interval widens (`samplingNote`) instead of cutting the end. A narrow window with `sample_seconds: 1` gives 1 Hz. `channels` adds `hr_sources`, `gap`, `stamina`, `body_battery`, `performance_condition`, `step_speed_loss`.\n\n`hr` is the watch's pick between wrist and strap; `hrSourceSummary` and `conflictAttribution` say which sensor departed when they disagree. Files ingested before 2026-08-25 lack HR-source, device-zone, weather and channel data.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"id"
],
"properties": {
"id": {
"type": "string",
"description": "Garmin activity ID (Strava ID also accepted; auto-resolves to the matching Garmin activity)"
},
"channels": {
"type": "array",
"items": {
"enum": [
"hr_sources",
"gap",
"stamina",
"body_battery",
"performance_condition",
"step_speed_loss"
],
"type": "string"
},
"description": "Optional: append Garmin PROPRIETARY per-sample channels to each record line. `hr_sources` = the raw wrist and strap heart-rate channels (use when you need to know WHICH sensor a heart rate came from, or when hrSourceSummary reports a conflict window); `gap` = grade-adjusted speed (use on any hilly or rolling session - per-km net elevation cannot see repeated up-and-down inside one kilometre); `stamina` = stamina potential and actual; `body_battery`; `performance_condition`; `step_speed_loss`. When the file lacks a requested channel the response says so in `channelsNote` (a file with no `wrist`/`strap` tokens and no hrSourceSummary carries only the selected `hr` series - the sensor behind a dropout cannot be attributed from it). Opt-in because each added channel widens every line and therefore COARSENS the sampling interval the budget allows - ask only for what the question needs, and pair with start_seconds/end_seconds to keep full resolution on the segment you care about."
},
"end_seconds": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"description": "Optional: only return records at/before this many elapsed seconds from activity start."
},
"start_seconds": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"description": "Optional: only return records at/after this many elapsed seconds from activity start. Pair with end_seconds to pull a bounded high-resolution slice of a long activity without the from-the-start read truncating first."
},
"sample_seconds": {
"type": "integer",
"maximum": 300,
"minimum": 1,
"description": "Downsample interval in seconds (default 30). Set to 1 for full 1 Hz resolution."
}
}
},
"annotations": {
"title": "Garmin: Get Activity File",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
List Garmin activities that have a parsed .fit file available. Returns activityId + start time + session summary for each. Each row carries `local_date` (YYYY-MM-DD) + `local_weekday` in the athlete's own timezone - take the day from those, never from the UTC `startTimeInSeconds` epoch. Use garmin_get_activity_file to fetch the full time-series for a specific activity. PAGES: newest first, up to `limit` (default 50) per call and fewer when the files' laps make the page large; when more files are held back the result is an object carrying `next_cursor`, which passed back as `cursor` with the same dates returns the next older files. MISSING SESSIONS: when this window contains a session we hold from another source and Garmin does not, the result is an object `{coverage_gap, activity_files}` instead of a bare array - a .fit only exists for a session a Garmin device recorded, so an absent file for a session named in `coverage_gap` is permanent, not a sync delay.
The description explains the capability, when to use it, and what to expect.
Inputs81/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max activities per page (default 50). More files than that come with `next_cursor`.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
cursor
Continuation token: the `next_cursor` value from this tool's previous response, passed back unchanged with the same other arguments to get the next page. Omit it for the first page.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "garmin_list_activity_files",
"title": "Garmin: List Activity Files",
"description": "List Garmin activities that have a parsed .fit file available. Returns activityId + start time + session summary for each. Each row carries `local_date` (YYYY-MM-DD) + `local_weekday` in the athlete's own timezone - take the day from those, never from the UTC `startTimeInSeconds` epoch. Use garmin_get_activity_file to fetch the full time-series for a specific activity. PAGES: newest first, up to `limit` (default 50) per call and fewer when the files' laps make the page large; when more files are held back the result is an object carrying `next_cursor`, which passed back as `cursor` with the same dates returns the next older files. MISSING SESSIONS: when this window contains a session we hold from another source and Garmin does not, the result is an object `{coverage_gap, activity_files}` instead of a bare array - a .fit only exists for a session a Garmin device recorded, so an absent file for a session named in `coverage_gap` is permanent, not a sync delay.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "integer",
"maximum": 100,
"minimum": 1,
"description": "Max activities per page (default 50). More files than that come with `next_cursor`."
},
"cursor": {
"type": "string",
"description": "Continuation token: the `next_cursor` value from this tool's previous response, passed back unchanged with the same other arguments to get the next page. Omit it for the first page."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Garmin: List Activity Files",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description93/100
Workouts recorded in Apple Health, pushed from the athlete's iPhone / Apple Watch: activity type, duration, distance, avg/max HR, energy. Newer app builds add `laps` (the watch's own work/recovery boundaries with each rep's duration, distance, pace and HR: the per-rep view of an interval session), and runs add per-km `splits` plus, on outdoor runs from a watch that records them, run dynamics (cadence, ground contact, stride length, vertical oscillation, running power), per run and per split / lap. Those fields exist on only part of the runs (newer builds, outdoor runs, capable watches), so an empty one means not recorded on that run. `splitsWithheld` and `lapDistancesWithheld` mark distances another app wrote as one block or double-counted; the run's distance is then in get_activity_detail. A workout another app (a bike computer, Strava) wrote into Health can carry elapsed time including stops; get_activity_detail has the session's moving time. For HR zones of a session use get_activity_detail, for its HR trace get_activity_stream, for sub-kilometre run detail apple_health_get_run_stream. Results are newest-first and capped: when older rows are cut the result is `{coverage, workouts}`, and `coverage` gives the athlete's true earliest date and total workout count.
The description explains the capability, when to use it, and what to expect.
Inputs63/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "apple_health_get_workouts",
"title": "Apple Health: Get Workouts",
"description": "Workouts recorded in Apple Health, pushed from the athlete's iPhone / Apple Watch: activity type, duration, distance, avg/max HR, energy. Newer app builds add `laps` (the watch's own work/recovery boundaries with each rep's duration, distance, pace and HR: the per-rep view of an interval session), and runs add per-km `splits` plus, on outdoor runs from a watch that records them, run dynamics (cadence, ground contact, stride length, vertical oscillation, running power), per run and per split / lap. Those fields exist on only part of the runs (newer builds, outdoor runs, capable watches), so an empty one means not recorded on that run. `splitsWithheld` and `lapDistancesWithheld` mark distances another app wrote as one block or double-counted; the run's distance is then in get_activity_detail. A workout another app (a bike computer, Strava) wrote into Health can carry elapsed time including stops; get_activity_detail has the session's moving time. For HR zones of a session use get_activity_detail, for its HR trace get_activity_stream, for sub-kilometre run detail apple_health_get_run_stream. Results are newest-first and capped: when older rows are cut the result is `{coverage, workouts}`, and `coverage` gives the athlete's true earliest date and total workout count.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Apple Health: Get Workouts",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Get the SUB-KILOMETRE trace of one Apple Health run: heart rate, cadence (spm), pace, ground contact time, stride length, vertical oscillation and running power, downsampled onto one shared time grid (~10-25s per bucket). It covers form drift WITHIN a session - 'did my cadence drop in the last 5k', 'when did ground contact start creeping up', 'how did power hold through the reps' - and for any request finer than the per-km `splits` on get_activity_detail. Returned as parallel arrays sharing the `t` axis (seconds since the run started): `t`, plus whichever of `hr`, `spm`, `paceSecPerKm`, `gctMs`, `strideM`, `voscCm`, `powerW` the watch recorded. Every array is the same length as `t`, so read them by index. A `null` inside an array means that bucket had no sample (a walk break, a signal gap) - it is NOT a zero and does not belong in an average over the array. A stream that is ABSENT from the response was never written by the watch at all, which is a different statement from a null: the metric wasn't recorded for that run, which is not evidence about the athlete's watch model or its sensors. Resolve the run with source_activity_id (from apple_health_get_workouts) or with date (YYYY-MM-DD, the run on that day). Only Apple Health RUNS from a recent app build carry this - an empty result means this run predates it, not that the athlete has no data. For the per-km view use get_activity_detail's `splits`; for per-REP structure use `laps`.
The description explains the capability, when to use it, and what to expect.
Inputs88/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
source_activity_id
The workout's Apple Health id, from apple_health_get_workouts
Clear enough to supply this argument.
date
YYYY-MM-DD - resolves the run recorded on that day instead
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Annotations100/100
All four behavior hints are declared.
{
"title": "Apple Health: Get Run Stream",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
Output schema0/100
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "apple_health_get_run_stream",
"title": "Apple Health: Get Run Stream",
"description": "Get the SUB-KILOMETRE trace of one Apple Health run: heart rate, cadence (spm), pace, ground contact time, stride length, vertical oscillation and running power, downsampled onto one shared time grid (~10-25s per bucket). It covers form drift WITHIN a session - 'did my cadence drop in the last 5k', 'when did ground contact start creeping up', 'how did power hold through the reps' - and for any request finer than the per-km `splits` on get_activity_detail. Returned as parallel arrays sharing the `t` axis (seconds since the run started): `t`, plus whichever of `hr`, `spm`, `paceSecPerKm`, `gctMs`, `strideM`, `voscCm`, `powerW` the watch recorded. Every array is the same length as `t`, so read them by index. A `null` inside an array means that bucket had no sample (a walk break, a signal gap) - it is NOT a zero and does not belong in an average over the array. A stream that is ABSENT from the response was never written by the watch at all, which is a different statement from a null: the metric wasn't recorded for that run, which is not evidence about the athlete's watch model or its sensors. Resolve the run with source_activity_id (from apple_health_get_workouts) or with date (YYYY-MM-DD, the run on that day). Only Apple Health RUNS from a recent app build carry this - an empty result means this run predates it, not that the athlete has no data. For the per-km view use get_activity_detail's `splits`; for per-REP structure use `laps`.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"date": {
"type": "string",
"description": "YYYY-MM-DD - resolves the run recorded on that day instead"
},
"source_activity_id": {
"type": "string",
"description": "The workout's Apple Health id, from apple_health_get_workouts"
}
}
},
"annotations": {
"title": "Apple Health: Get Run Stream",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description93/100
Get sleep records from Apple Health. Each record has both `time_in_bed_sec` (the full HKCategorySleepAnalysis window from getting in bed to getting out) and `asleep_sec` (actual time asleep, sum of core+deep+REM stages). `asleep_sec` is the sleep-duration figure — it matches what HRV4Training, intervals.icu, and Garmin/WHOOP/Oura call sleep duration. Time in bed is typically 1-2h longer. For "how did I sleep" / a night's stages, bed and wake time across devices, call get_sleep first: one compact merged row per night. Use this tool for the provider's own raw record.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format (matches wake-up day)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "apple_health_get_sleep",
"title": "Apple Health: Get Sleep",
"description": "Get sleep records from Apple Health. Each record has both `time_in_bed_sec` (the full HKCategorySleepAnalysis window from getting in bed to getting out) and `asleep_sec` (actual time asleep, sum of core+deep+REM stages). `asleep_sec` is the sleep-duration figure — it matches what HRV4Training, intervals.icu, and Garmin/WHOOP/Oura call sleep duration. Time in bed is typically 1-2h longer. For \"how did I sleep\" / a night's stages, bed and wake time across devices, call get_sleep first: one compact merged row per night. Use this tool for the provider's own raw record.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format (matches wake-up day)"
}
}
},
"annotations": {
"title": "Apple Health: Get Sleep",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description95/100
Get heart rate variability (HRV SDNN, milliseconds) from Apple Health. Apple Watch writes MANY HRV samples per day (often 10-100+), and raw mode returns at most 500 newest-first - so for an active user a raw pull only reaches back ~1-6 weeks even WITH a start_date, and CANNOT surface multi-month history (the newest 500 samples fill up long before the range start). For any trend, baseline, or period-vs-period comparison (e.g. 'HRV since March vs before'), pass aggregate:"daily" to get one row per day {date, avg, min, max, samples} across the ENTIRE requested window with no truncation. (get_daily_metrics also carries the full daily HRV history alongside RHR/sleep/readiness.) A raw pull that stops a few weeks back is the cap, NOT the limit of the stored data - aggregate:"daily" over the full range reaches the historic HRV.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
aggregate
Roll samples up to one row per calendar day (avg/min/max/samples). Use for ANY range beyond ~2 weeks or any trend/period comparison - raw mode silently truncates to the most recent ~500 samples.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Raw mode only: max samples returned, newest-first (default 100, max 500). Ignored when aggregate is set.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "apple_health_get_hrv",
"title": "Apple Health: Get HRV",
"description": "Get heart rate variability (HRV SDNN, milliseconds) from Apple Health. Apple Watch writes MANY HRV samples per day (often 10-100+), and raw mode returns at most 500 newest-first - so for an active user a raw pull only reaches back ~1-6 weeks even WITH a start_date, and CANNOT surface multi-month history (the newest 500 samples fill up long before the range start). For any trend, baseline, or period-vs-period comparison (e.g. 'HRV since March vs before'), pass aggregate:\"daily\" to get one row per day {date, avg, min, max, samples} across the ENTIRE requested window with no truncation. (get_daily_metrics also carries the full daily HRV history alongside RHR/sleep/readiness.) A raw pull that stops a few weeks back is the cap, NOT the limit of the stored data - aggregate:\"daily\" over the full range reaches the historic HRV.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "number",
"description": "Raw mode only: max samples returned, newest-first (default 100, max 500). Ignored when aggregate is set."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"aggregate": {
"enum": [
"daily"
],
"type": "string",
"description": "Roll samples up to one row per calendar day (avg/min/max/samples). Use for ANY range beyond ~2 weeks or any trend/period comparison - raw mode silently truncates to the most recent ~500 samples."
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Apple Health: Get HRV",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Get resting HR from Apple Health (bpm). Use when Apple Health is the primary source for RHR (athlete uses Apple Watch but no Garmin/WHOOP/Oura). The trend carries the signal, not single values - 5+ bpm above 14d baseline = stress / illness / poor sleep flag. Like HRV, Apple Watch writes many RHR samples per day and raw mode caps at 500 newest-first, so a raw pull only reaches back ~1-6 weeks for an active user. For multi-week trends or period comparisons pass aggregate:"daily" (one row/day {date, avg, min, max, samples} over the full window) or use get_daily_metrics. Example: apple_health_get_resting_hr({ start_date: "2026-01-01", end_date: "2026-06-20", aggregate: "daily" }).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
aggregate
Roll samples up to one row per calendar day (avg/min/max/samples). Use for any range beyond ~2 weeks or any trend/period comparison - raw mode silently truncates to the most recent ~500 samples.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Raw mode only: max samples returned, newest-first (default 100, max 500). Ignored when aggregate is set.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "apple_health_get_resting_hr",
"title": "Apple Health: Get Resting HR",
"description": "Get resting HR from Apple Health (bpm). Use when Apple Health is the primary source for RHR (athlete uses Apple Watch but no Garmin/WHOOP/Oura). The trend carries the signal, not single values - 5+ bpm above 14d baseline = stress / illness / poor sleep flag. Like HRV, Apple Watch writes many RHR samples per day and raw mode caps at 500 newest-first, so a raw pull only reaches back ~1-6 weeks for an active user. For multi-week trends or period comparisons pass aggregate:\"daily\" (one row/day {date, avg, min, max, samples} over the full window) or use get_daily_metrics. Example: apple_health_get_resting_hr({ start_date: \"2026-01-01\", end_date: \"2026-06-20\", aggregate: \"daily\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "number",
"description": "Raw mode only: max samples returned, newest-first (default 100, max 500). Ignored when aggregate is set."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"aggregate": {
"enum": [
"daily"
],
"type": "string",
"description": "Roll samples up to one row per calendar day (avg/min/max/samples). Use for any range beyond ~2 weeks or any trend/period comparison - raw mode silently truncates to the most recent ~500 samples."
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Apple Health: Get Resting HR",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description83/100
Get weight (kg) and body-fat (0-1 fraction) samples from Apple Health, returned as { weight: [...], bodyFat: [...] }.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Annotations100/100
All four behavior hints are declared.
{
"title": "Apple Health: Get Body Composition",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
Output schema0/100
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "apple_health_get_body_composition",
"title": "Apple Health: Get Body Composition",
"description": "Get weight (kg) and body-fat (0-1 fraction) samples from Apple Health, returned as { weight: [...], bodyFat: [...] }.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Apple Health: Get Body Composition",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description96/100
Get VO2max estimates from Apple Health (Apple Watch cardio fitness). Values in ml/kg/min. Returns one record per estimate (its date and `value`), newest first, or an object with `truncated` and `records` when the window holds more than `limit`.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "apple_health_get_vo2max",
"title": "Apple Health: Get Vo2max",
"description": "Get VO2max estimates from Apple Health (Apple Watch cardio fitness). Values in ml/kg/min. Returns one record per estimate (its date and `value`), newest first, or an object with `truncated` and `records` when the window holds more than `limit`.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "Max records to return (default 100, max 500). Rows come newest-first, so a window holding more than this loses its OLDEST records - the response says so in `truncated` when that happens. Prefer a narrower date window over a big limit."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Apple Health: Get Vo2max",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
The athlete's phone calendar events, synced by the athletedata app (Apple Calendar on iPhone, Samsung Calendar on Android). Use to check their schedule before placing sessions around busy or travel days. Only the next 14 days and the past 2 are synced: a wider range returns the same rows plus a Coverage note. Each event: title, calendar name, start / end and an all-day flag, plus (on recent app builds) `free` (marked available), `canceled`, and selfIsOrganizer / selfIsAttendee / selfResponse (a declined or non-participant event is not a firm commitment). TIMES: when an event has a timezone field, startIso / endIso are already the athlete's local wall clock with its own offset ("2026-09-14T10:25:00-04:00" is 10:25 for them), so converting again shifts it; with no timezone field the value is UTC ("...Z") and the local offset is unknown. All-day events use startDate / endDate. Results follow the athlete's calendar selection and say which calendars were filtered out; `all_calendars: true` returns every calendar on the phone, including other people's.
The description explains the capability, when to use it, and what to expect.
Inputs94/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
First day to include, YYYY-MM-DD, interpreted in the ATHLETE'S timezone (defaults to 2 days ago)
Clear enough to supply this argument.
end_date
Last day to include, YYYY-MM-DD, inclusive and interpreted in the ATHLETE'S timezone (defaults to the last synced day, 14 days out; nothing further ahead is synced from the phone)
Clear enough to supply this argument.
limit
Max events to return (default 100, max 500)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
all_calendars
true = ignore the athlete's calendar selection and return every synced calendar. Use only when they ask what else is on their phone (e.g. to pick which calendars are theirs).
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "apple_calendar_get_events",
"title": "Apple Calendar: Get Events",
"description": "The athlete's phone calendar events, synced by the athletedata app (Apple Calendar on iPhone, Samsung Calendar on Android). Use to check their schedule before placing sessions around busy or travel days. Only the next 14 days and the past 2 are synced: a wider range returns the same rows plus a Coverage note. Each event: title, calendar name, start / end and an all-day flag, plus (on recent app builds) `free` (marked available), `canceled`, and selfIsOrganizer / selfIsAttendee / selfResponse (a declined or non-participant event is not a firm commitment). TIMES: when an event has a timezone field, startIso / endIso are already the athlete's local wall clock with its own offset (\"2026-09-14T10:25:00-04:00\" is 10:25 for them), so converting again shifts it; with no timezone field the value is UTC (\"...Z\") and the local offset is unknown. All-day events use startDate / endDate. Results follow the athlete's calendar selection and say which calendars were filtered out; `all_calendars: true` returns every calendar on the phone, including other people's.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "number",
"description": "Max events to return (default 100, max 500)"
},
"end_date": {
"type": "string",
"description": "Last day to include, YYYY-MM-DD, inclusive and interpreted in the ATHLETE'S timezone (defaults to the last synced day, 14 days out; nothing further ahead is synced from the phone)"
},
"start_date": {
"type": "string",
"description": "First day to include, YYYY-MM-DD, interpreted in the ATHLETE'S timezone (defaults to 2 days ago)"
},
"all_calendars": {
"type": "boolean",
"description": "true = ignore the athlete's calendar selection and return every synced calendar. Use only when they ask what else is on their phone (e.g. to pick which calendars are theirs)."
}
}
},
"annotations": {
"title": "Apple Calendar: Get Events",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Get the athlete's events from their Outlook / Microsoft 365 calendars (read live: their default calendar plus the other calendars in their mailbox the coach reads, minus any they hid with set_calendar_selection; each event names its calendar when more than one is read). Use to check their schedule and avoid planning sessions during busy/travel blocks. Defaults to 2 days back through 21 days out. Each event has a title, start/end, an allDay flag, showAs (Outlook's free/busy status: free = an FYI that does not block training, tentative, busy, oof = out of office, workingElsewhere), canceled, selfIsOrganizer, attendeeCount, selfResponse (accepted / tentative / declined / needsAction), location and categories. TIMES: when an event carries a timezone field, its startIso/endIso are ALREADY the athlete's local time with their own UTC offset (e.g. "2026-09-14T10:25:00-04:00" means 10:25 for them) - quote that wall clock exactly and never shift it. Without a timezone field the value is plain UTC ("...Z") because we have not placed the athlete: ask rather than guess an offset. All-day events have no clock time; use startDate/endDate (YYYY-MM-DD, endDate exclusive). A declined or canceled event is NOT a commitment. Read-only: we cannot create or edit Outlook events, but their PLAN can still reach Outlook through the plan calendar feed.
The description explains the capability, when to use it, and what to expect.
Inputs92/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
First day to include, YYYY-MM-DD, interpreted in the ATHLETE'S timezone (defaults to 2 days ago)
Clear enough to supply this argument.
end_date
Last day to include, YYYY-MM-DD, inclusive and interpreted in the ATHLETE'S timezone (defaults to 21 days out)
Clear enough to supply this argument.
limit
Max events to return (default 100, max 500)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "outlook_calendar_get_events",
"title": "Outlook Calendar: Get Events",
"description": "Get the athlete's events from their Outlook / Microsoft 365 calendars (read live: their default calendar plus the other calendars in their mailbox the coach reads, minus any they hid with set_calendar_selection; each event names its calendar when more than one is read). Use to check their schedule and avoid planning sessions during busy/travel blocks. Defaults to 2 days back through 21 days out. Each event has a title, start/end, an allDay flag, showAs (Outlook's free/busy status: free = an FYI that does not block training, tentative, busy, oof = out of office, workingElsewhere), canceled, selfIsOrganizer, attendeeCount, selfResponse (accepted / tentative / declined / needsAction), location and categories. TIMES: when an event carries a timezone field, its startIso/endIso are ALREADY the athlete's local time with their own UTC offset (e.g. \"2026-09-14T10:25:00-04:00\" means 10:25 for them) - quote that wall clock exactly and never shift it. Without a timezone field the value is plain UTC (\"...Z\") because we have not placed the athlete: ask rather than guess an offset. All-day events have no clock time; use startDate/endDate (YYYY-MM-DD, endDate exclusive). A declined or canceled event is NOT a commitment. Read-only: we cannot create or edit Outlook events, but their PLAN can still reach Outlook through the plan calendar feed.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "number",
"description": "Max events to return (default 100, max 500)"
},
"end_date": {
"type": "string",
"description": "Last day to include, YYYY-MM-DD, inclusive and interpreted in the ATHLETE'S timezone (defaults to 21 days out)"
},
"start_date": {
"type": "string",
"description": "First day to include, YYYY-MM-DD, interpreted in the ATHLETE'S timezone (defaults to 2 days ago)"
}
}
},
"annotations": {
"title": "Outlook Calendar: Get Events",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Get exercise sessions from Fitbit / Pixel Watch via the Google Health API (type, duration, heart rate, calories). Results are bounded to the requested dates client-side; `coverage` shows how far the pull reached - `page_cap_reached` with an `oldest_date_returned` later than your start means this pull did not reach the whole window, NOT that the athlete has no data there. By default each session is one summarized row (splits/laps reduced to count and pace spread, pause events counted); pass detail: "full" for Google's raw data points. Example: {"start_date": "2026-08-01"}
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format (default: 7 days ago)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
detail
"compact" (default): one summarized row per session. "full": Google's raw data points with every stage segment / split / event - much larger, only when you need the timeline.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "google_health_get_exercises",
"title": "Google Health: Get Exercises",
"description": "Get exercise sessions from Fitbit / Pixel Watch via the Google Health API (type, duration, heart rate, calories). Results are bounded to the requested dates client-side; `coverage` shows how far the pull reached - `page_cap_reached` with an `oldest_date_returned` later than your start means this pull did not reach the whole window, NOT that the athlete has no data there. By default each session is one summarized row (splits/laps reduced to count and pace spread, pause events counted); pass detail: \"full\" for Google's raw data points. Example: {\"start_date\": \"2026-08-01\"}",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"detail": {
"enum": [
"compact",
"full"
],
"type": "string",
"description": "\"compact\" (default): one summarized row per session. \"full\": Google's raw data points with every stage segment / split / event - much larger, only when you need the timeline."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format (default: 7 days ago)"
}
}
},
"annotations": {
"title": "Google Health: Get Exercises",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Get sleep sessions (duration, stages) from Fitbit / Pixel Watch via the Google Health API. Results are filtered to the requested dates (by when each sleep ended) and bounded client-side: `coverage` shows how far the pull reached - `page_cap_reached` means this pull did not reach the whole window. Google reports no real time-to-fall-asleep (its minutesToFallAsleep is always 0 and is omitted here): never quote sleep latency from this source. For sleep history/trends prefer get_sleep_trend. By default each session is one row with minutes per stage summed from the stage segments (the same numbers get_daily_metrics stores); pass detail: "full" for Google's raw data points including every stage segment. Example: {"start_date": "2026-08-01"} For "how did I sleep" / a night's stages, bed and wake time across devices, call get_sleep first: one compact merged row per night. Use this tool for the provider's own raw record.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format (default: 7 days ago)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
detail
"compact" (default): one summarized row per session. "full": Google's raw data points with every stage segment / split / event - much larger, only when you need the timeline.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "google_health_get_sleep",
"title": "Google Health: Get Sleep",
"description": "Get sleep sessions (duration, stages) from Fitbit / Pixel Watch via the Google Health API. Results are filtered to the requested dates (by when each sleep ended) and bounded client-side: `coverage` shows how far the pull reached - `page_cap_reached` means this pull did not reach the whole window. Google reports no real time-to-fall-asleep (its minutesToFallAsleep is always 0 and is omitted here): never quote sleep latency from this source. For sleep history/trends prefer get_sleep_trend. By default each session is one row with minutes per stage summed from the stage segments (the same numbers get_daily_metrics stores); pass detail: \"full\" for Google's raw data points including every stage segment. Example: {\"start_date\": \"2026-08-01\"} For \"how did I sleep\" / a night's stages, bed and wake time across devices, call get_sleep first: one compact merged row per night. Use this tool for the provider's own raw record.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"detail": {
"enum": [
"compact",
"full"
],
"type": "string",
"description": "\"compact\" (default): one summarized row per session. \"full\": Google's raw data points with every stage segment / split / event - much larger, only when you need the timeline."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format (default: 7 days ago)"
}
}
},
"annotations": {
"title": "Google Health: Get Sleep",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Get nightly HRV (RMSSD, ms) and recent heart-rate samples from Fitbit / Pixel Watch via the Google Health API. `hrv` is one row per night (the same value stored as daily_metrics.hrv with hrv_source='google_health'), so it is directly usable as the nightly value. `heart_rate` is a SHORT RECENT WINDOW of raw samples (Google returns instant samples newest-first at one every 2-3 seconds) - window_start/window_end give its span, and its avg_bpm is not a daily or resting HR; resting HR lives in get_daily_metrics. Google applies no server-side date filter to these types, so a long range returns only the newest slice: check `coverage.page_cap_reached` and, for any multi-week HRV trend or baseline, use get_daily_metrics instead - it holds the full stored history. An empty `hrv` list means THIS PULL returned no samples in the window, NOT that the athlete has no HRV - get_daily_metrics holds their stored Fitbit HRV history. Example: {"start_date": "2026-08-01"}
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format (default: 7 days ago)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "google_health_get_vitals",
"title": "Google Health: Get Vitals",
"description": "Get nightly HRV (RMSSD, ms) and recent heart-rate samples from Fitbit / Pixel Watch via the Google Health API. `hrv` is one row per night (the same value stored as daily_metrics.hrv with hrv_source='google_health'), so it is directly usable as the nightly value. `heart_rate` is a SHORT RECENT WINDOW of raw samples (Google returns instant samples newest-first at one every 2-3 seconds) - window_start/window_end give its span, and its avg_bpm is not a daily or resting HR; resting HR lives in get_daily_metrics. Google applies no server-side date filter to these types, so a long range returns only the newest slice: check `coverage.page_cap_reached` and, for any multi-week HRV trend or baseline, use get_daily_metrics instead - it holds the full stored history. An empty `hrv` list means THIS PULL returned no samples in the window, NOT that the athlete has no HRV - get_daily_metrics holds their stored Fitbit HRV history. Example: {\"start_date\": \"2026-08-01\"}",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format (default: 7 days ago)"
}
}
},
"annotations": {
"title": "Google Health: Get Vitals",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description95/100
Get weight and body fat measurements from the Fitbit app / Google Health API. Results are bounded to the requested dates client-side; `coverage` shows how far the pull reached - `page_cap_reached` means this pull did not reach the whole window. Returns `weight` and `bodyFat`, each a list of Google Health data points, and `coverage` for each list. Example: {"start_date": "2026-08-01"}
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format (default: 7 days ago)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "google_health_get_body",
"title": "Google Health: Get Body",
"description": "Get weight and body fat measurements from the Fitbit app / Google Health API. Results are bounded to the requested dates client-side; `coverage` shows how far the pull reached - `page_cap_reached` means this pull did not reach the whole window. Returns `weight` and `bodyFat`, each a list of Google Health data points, and `coverage` for each list. Example: {\"start_date\": \"2026-08-01\"}",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format (default: 7 days ago)"
}
}
},
"annotations": {
"title": "Google Health: Get Body",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Get workouts from Wahoo (cycling, running - duration, power, HR from KICKR & ELEMNT devices). start_date/end_date really do filter by date, including years back - the whole archive is reachable. Returns `total` (all-time count) plus `matched` (how many fell in the requested window), so 'how many activities in 2023' is answerable. Metrics beyond duration need wahoo_get_workout_summary on a specific id. Wahoo's collection also holds PLANNED workouts sitting on the head unit; those come back flagged `scheduled_not_recorded: true` and are not sessions the athlete did. Example: wahoo_get_workouts({ start_date: "2023-01-01", end_date: "2023-12-31" }).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max workouts to return (default 50). Counts are exact regardless.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "wahoo_get_workouts",
"title": "Wahoo: Get Workouts",
"description": "Get workouts from Wahoo (cycling, running - duration, power, HR from KICKR & ELEMNT devices). start_date/end_date really do filter by date, including years back - the whole archive is reachable. Returns `total` (all-time count) plus `matched` (how many fell in the requested window), so 'how many activities in 2023' is answerable. Metrics beyond duration need wahoo_get_workout_summary on a specific id. Wahoo's collection also holds PLANNED workouts sitting on the head unit; those come back flagged `scheduled_not_recorded: true` and are not sessions the athlete did. Example: wahoo_get_workouts({ start_date: \"2023-01-01\", end_date: \"2023-12-31\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "number",
"description": "Max workouts to return (default 50). Counts are exact regardless."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Wahoo: Get Workouts",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description95/100
Get a single Wahoo workout: sport, duration, distance, route. For training-load metrics (TSS, NP, IF, power/HR averages) use wahoo_get_workout_summary instead - this endpoint returns the lighter shape. Example: wahoo_get_workout({ workout_id: "abc123" }).
The description explains the capability, when to use it, and what to expect.
Inputs96/100
Input documentation gives an agent enough context to populate the arguments.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description91/100
Aggregate metrics for one Wahoo workout (KICKR trainer or ELEMNT head unit): average and normalized power, intensity factor (IF), TSS, average/max heart rate, cadence, speed and work (kJ). Takes the workout id from wahoo_get_workouts. Suited to cycling load analysis of sessions recorded on Wahoo hardware. Example: wahoo_get_workout_summary({ workout_id: "abc123" }).
The description explains the capability, when to use it, and what to expect.
Inputs98/100
Input documentation gives an agent enough context to populate the arguments.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description100/100
Get the athlete's Wahoo power zones (Z1-Z7 Coggan-style, FTP-anchored). Use when prescribing cycling sessions targeted at the athlete's Wahoo-configured zones, or when their FTP differs across platforms (Wahoo vs Intervals.icu) and you need to know which one their head-unit is paced against. Returns Wahoo's power-zone records for the athlete as Wahoo sends them; an empty list means Wahoo holds none. Example: wahoo_get_power_zones({}).
The description explains the capability, when to use it, and what to expect.
Inputs100/100
This tool declares no inputs, so argument descriptions are not needed.
No input fields.
Annotations100/100
All four behavior hints are declared.
{
"title": "Wahoo: Get Power Zones",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
Output schema0/100
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "wahoo_get_power_zones",
"title": "Wahoo: Get Power Zones",
"description": "Get the athlete's Wahoo power zones (Z1-Z7 Coggan-style, FTP-anchored). Use when prescribing cycling sessions targeted at the athlete's Wahoo-configured zones, or when their FTP differs across platforms (Wahoo vs Intervals.icu) and you need to know which one their head-unit is paced against. Returns Wahoo's power-zone records for the athlete as Wahoo sends them; an empty list means Wahoo holds none. Example: wahoo_get_power_zones({}).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {}
},
"annotations": {
"title": "Wahoo: Get Power Zones",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
The athlete's Eight Sleep Pod nights, the Pod's own reading rather than the merged night: time asleep, deep / REM / light / awake minutes, time in bed, the Pod's 0-100 sleep score, plus overnight HRV (median RMSSD) and lowest sleeping heart rate measured by the mattress. Use for questions about their Eight Sleep, when a wearable's numbers are being questioned, or when the Pod is their pinned sleep / recovery source (the default after connecting one). `is_daily_winner` says whether the night is also the one on their dashboard and in get_daily_metrics. Mattress and wrist HRV are different measurements with separate baselines, so the two disagreeing is expected. Data arrives on a scheduled morning poll with no manual sync, so a missing last night has not landed yet. Dates are WAKE dates; `night_of` names the evening the night began. Defaults to the last 14 nights. Example: eight_sleep_get_sleep({ start_date: "2026-09-01", end_date: "2026-09-07" }).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date (wake date) in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date (wake date) in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "eight_sleep_get_sleep",
"title": "Eight Sleep: Get Sleep",
"description": "The athlete's Eight Sleep Pod nights, the Pod's own reading rather than the merged night: time asleep, deep / REM / light / awake minutes, time in bed, the Pod's 0-100 sleep score, plus overnight HRV (median RMSSD) and lowest sleeping heart rate measured by the mattress. Use for questions about their Eight Sleep, when a wearable's numbers are being questioned, or when the Pod is their pinned sleep / recovery source (the default after connecting one). `is_daily_winner` says whether the night is also the one on their dashboard and in get_daily_metrics. Mattress and wrist HRV are different measurements with separate baselines, so the two disagreeing is expected. Data arrives on a scheduled morning poll with no manual sync, so a missing last night has not landed yet. Dates are WAKE dates; `night_of` names the evening the night began. Defaults to the last 14 nights. Example: eight_sleep_get_sleep({ start_date: \"2026-09-01\", end_date: \"2026-09-07\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end_date": {
"type": "string",
"description": "End date (wake date) in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date (wake date) in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Eight Sleep: Get Sleep",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description100/100
Get the athlete's Renpho smart-scale weigh-ins: one row per time they stood on the scale, with weight (kg), BMI, body fat %, muscle %, body water %, bone %, protein %, subcutaneous fat %, visceral fat index, BMR (kcal/day), lean mass (kg) and metabolic age. Use this for any body-composition question - get_daily_metrics carries only weight and body fat, because those are the only two fields the daily table has. UNITS MATTER HERE: muscle, water, bone, protein and subcutaneous fat are PERCENTAGES of body mass, not kilograms, and `visceral_fat_index` is a unitless index (roughly 1-59), neither a percentage nor a mass. `standing_hr_bpm` is a pulse taken STANDING on the scale - it is NOT a resting heart rate and runs well above one, so it is not comparable with their resting-HR baseline or a wearable's readings. Each row carries `is_daily_winner`: true means this is also the weight on their dashboard; false means a Garmin / Withings / Apple Health scale outranked Renpho that day. A field that is null was not measured (the scale returns 0 for a failed impedance reading, and 0 is stored as null rather than as a measurement). Readings reach us only after the athlete opens the Renpho phone app near the scale, so a fresh weigh-in can be hours late and there is NO manual sync - a missing today has simply not landed yet. Example: renpho_get_body_composition({ start_date: "2026-09-01", end_date: "2026-09-14" }). Defaults to the last 30 days.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Annotations100/100
All four behavior hints are declared.
{
"title": "Renpho: Get Body Composition",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
Output schema0/100
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "renpho_get_body_composition",
"title": "Renpho: Get Body Composition",
"description": "Get the athlete's Renpho smart-scale weigh-ins: one row per time they stood on the scale, with weight (kg), BMI, body fat %, muscle %, body water %, bone %, protein %, subcutaneous fat %, visceral fat index, BMR (kcal/day), lean mass (kg) and metabolic age. Use this for any body-composition question - get_daily_metrics carries only weight and body fat, because those are the only two fields the daily table has. UNITS MATTER HERE: muscle, water, bone, protein and subcutaneous fat are PERCENTAGES of body mass, not kilograms, and `visceral_fat_index` is a unitless index (roughly 1-59), neither a percentage nor a mass. `standing_hr_bpm` is a pulse taken STANDING on the scale - it is NOT a resting heart rate and runs well above one, so it is not comparable with their resting-HR baseline or a wearable's readings. Each row carries `is_daily_winner`: true means this is also the weight on their dashboard; false means a Garmin / Withings / Apple Health scale outranked Renpho that day. A field that is null was not measured (the scale returns 0 for a failed impedance reading, and 0 is stored as null rather than as a measurement). Readings reach us only after the athlete opens the Renpho phone app near the scale, so a fresh weigh-in can be hours late and there is NO manual sync - a missing today has simply not landed yet. Example: renpho_get_body_composition({ start_date: \"2026-09-01\", end_date: \"2026-09-14\" }). Defaults to the last 30 days.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Renpho: Get Body Composition",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description90/100
Get erg results from the athlete's Concept2 Logbook (RowErg, BikeErg, SkiErg, plus on-water rowing they logged). Every result carries distance, time, split per 500m, stroke rate, drag factor and average heart rate; ErgData pieces also carry per-split detail. start_date/end_date filter server-side and reach the whole logbook, which is often the athlete's DEEPEST training record - people keep one Concept2 account across decades. Pace is split per 500m (the erg convention), not min/km. Example: concept2_get_results({ start_date: "2026-08-01", type: "rower" }).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
type
Filter by machine: rower, bike (BikeErg), skierg, dynamic, slides, water, snow, rollerski, paddle, multierg
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max results to return (default 50)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "concept2_get_results",
"title": "Concept2: Get Results",
"description": "Get erg results from the athlete's Concept2 Logbook (RowErg, BikeErg, SkiErg, plus on-water rowing they logged). Every result carries distance, time, split per 500m, stroke rate, drag factor and average heart rate; ErgData pieces also carry per-split detail. start_date/end_date filter server-side and reach the whole logbook, which is often the athlete's DEEPEST training record - people keep one Concept2 account across decades. Pace is split per 500m (the erg convention), not min/km. Example: concept2_get_results({ start_date: \"2026-08-01\", type: \"rower\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"type": {
"type": "string",
"description": "Filter by machine: rower, bike (BikeErg), skierg, dynamic, slides, water, snow, rollerski, paddle, multierg"
},
"limit": {
"type": "number",
"description": "Max results to return (default 50)"
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
}
}
},
"annotations": {
"title": "Concept2: Get Results",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
Get one Concept2 result in full by id, including its per-split breakdown when the piece was recorded through ErgData (distance, time, stroke rate and heart rate per split). Use this to see whether an interval session held pace or faded - the list tool's rows describe the piece as a whole. Example: concept2_get_result({ result_id: "339" }).
The description explains the capability, when to use it, and what to expect.
Inputs96/100
Input documentation gives an agent enough context to populate the arguments.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "concept2_get_result",
"title": "Concept2: Get Result",
"description": "Get one Concept2 result in full by id, including its per-split breakdown when the piece was recorded through ErgData (distance, time, stroke rate and heart rate per split). Use this to see whether an interval session held pace or faded - the list tool's rows describe the piece as a whole. Example: concept2_get_result({ result_id: \"339\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"result_id"
],
"properties": {
"result_id": {
"type": "string",
"description": "The result's `id` from concept2_get_results."
}
}
},
"annotations": {
"title": "Concept2: Get Result",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Get stroke-by-stroke data for one Concept2 result: per-stroke pace, power, stroke rate and heart rate through the piece. This is the finest-grained view of an erg session anywhere in the athlete's data - use it to analyse pacing within a rep, drive consistency, or a fade. Only pieces recorded through ErgData have it, so an EMPTY answer is normal and means the result came from the web form or a USB stick, NOT that anything failed. Example: concept2_get_strokes({ result_id: "339" }).
The description explains the capability, when to use it, and what to expect.
Inputs98/100
Input documentation gives an agent enough context to populate the arguments.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "concept2_get_strokes",
"title": "Concept2: Get Strokes",
"description": "Get stroke-by-stroke data for one Concept2 result: per-stroke pace, power, stroke rate and heart rate through the piece. This is the finest-grained view of an erg session anywhere in the athlete's data - use it to analyse pacing within a rep, drive consistency, or a fade. Only pieces recorded through ErgData have it, so an EMPTY answer is normal and means the result came from the web form or a USB stick, NOT that anything failed. Example: concept2_get_strokes({ result_id: \"339\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"result_id"
],
"properties": {
"result_id": {
"type": "string",
"description": "The result's `id` from concept2_get_results."
}
}
},
"annotations": {
"title": "Concept2: Get Strokes",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description96/100
Send ONE structured indoor ride to the athlete's MyWhoosh Custom Workouts library, ready to ride in the MyWhoosh app. Cycling only, power steps only: a MyWhoosh workout holds % FTP steps, so author every block with targetFtpPct (or targetPowerWatts, which is converted to % FTP against the athlete's FTP on file and refused when they have none). A heart-rate or pace target, or a ride with no structured blocks, is refused with the reason: do not convert it to power yourself, that is a different session. THE LIBRARY HAS NO DATES: it is a flat list of named cards, so this does not schedule the ride for a day, and the athlete starts it whenever they choose. MyWhoosh CANNOT delete or replace a card from here: sending the same ride again, or an edited version, adds another card, and the athlete removes extras in the MyWhoosh app. A byte-identical ride already uploaded for one of their plan sessions is reported as already in the library and nothing is sent. MyWhoosh has no read-back, so a success confirms the send, not that the card is showing: if the athlete cannot find it, say you cannot see their library. This tool only SENDS: it reads no MyWhoosh data, and finished MyWhoosh rides reach athletedata through Strava, intervals.icu or TrainingPeaks (never Garmin's own sync).
The description explains the capability, when to use it, and what to expect.
Inputs46/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
title
Workout name on the MyWhoosh card. Plain ASCII is kept as written; other characters are folded (× becomes x, accents dropped).
Clear enough to supply this argument.
description
Optional execution notes shown on the card. Defaults to the title (MyWhoosh requires a description).
Most specific Garmin category for the lift (warm-up sets keep the lift's own). Two exercises on one category read alike on the watch unless garminExercise names each.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description100/100
List all Google calendars the user has access to (primary, shared, subscribed). Use when the athlete has multiple calendars (work + personal) and you need a non-primary calendar_id for create/update/delete event tools, or for inspecting which calendars exist. Most coaching writes go to the primary calendar - skip this call if you only need primary. Returns Google's calendar list: `items`, each with the calendar's `id` (the calendar_id the event tools take), `summary` (its name), `timeZone`, `accessRole` and `primary`. Example: google_calendar_get_calendars({}).
The description explains the capability, when to use it, and what to expect.
Inputs100/100
This tool declares no inputs, so argument descriptions are not needed.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "google_calendar_get_calendars",
"title": "Google Calendar: Get Calendars",
"description": "List all Google calendars the user has access to (primary, shared, subscribed). Use when the athlete has multiple calendars (work + personal) and you need a non-primary calendar_id for create/update/delete event tools, or for inspecting which calendars exist. Most coaching writes go to the primary calendar - skip this call if you only need primary. Returns Google's calendar list: `items`, each with the calendar's `id` (the calendar_id the event tools take), `summary` (its name), `timeZone`, `accessRole` and `primary`. Example: google_calendar_get_calendars({}).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {}
},
"annotations": {
"title": "Google Calendar: Get Calendars",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Get events from Google Calendar. Use to check the user's schedule, find upcoming commitments, or search for specific events. Returns events sorted by start time. OMIT calendar_id to search every calendar the coach reads for this athlete: their primary calendar, the calendar athletedata writes their plan to, their other own calendars, and any calendar they named with set_calendar_selection (minus the ones they hid) - the response then carries calendarsSearched plus a calendarId/calendarName on every event. An event with planMirror: true is athletedata's OWN plan session mirrored onto their calendar - it is the plan you already know, never an athlete commitment, so never count it as one. Passing calendar_id searches ONLY that calendar, so a single-calendar read cannot show that an event is missing from 'their calendar'.
The description explains the capability, when to use it, and what to expect.
Inputs87/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
calendar_id
Calendar ID. Omit to search every calendar the coach reads for this athlete; pass one to restrict the search to it.
Clear enough to supply this argument.
time_min
Start of time range in ISO 8601 format (e.g. '2026-03-21T00:00:00Z')
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "google_calendar_get_events",
"title": "Google Calendar: Get Events",
"description": "Get events from Google Calendar. Use to check the user's schedule, find upcoming commitments, or search for specific events. Returns events sorted by start time. OMIT calendar_id to search every calendar the coach reads for this athlete: their primary calendar, the calendar athletedata writes their plan to, their other own calendars, and any calendar they named with set_calendar_selection (minus the ones they hid) - the response then carries calendarsSearched plus a calendarId/calendarName on every event. An event with planMirror: true is athletedata's OWN plan session mirrored onto their calendar - it is the plan you already know, never an athlete commitment, so never count it as one. Passing calendar_id searches ONLY that calendar, so a single-calendar read cannot show that an event is missing from 'their calendar'.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"q": {
"type": "string",
"description": "Free-text search terms to find events"
},
"time_max": {
"type": "string",
"description": "End of time range in ISO 8601 format"
},
"time_min": {
"type": "string",
"description": "Start of time range in ISO 8601 format (e.g. '2026-03-21T00:00:00Z')"
},
"calendar_id": {
"type": "string",
"description": "Calendar ID. Omit to search every calendar the coach reads for this athlete; pass one to restrict the search to it."
},
"max_results": {
"type": "number",
"description": "Max events to return (default 250)"
}
}
},
"annotations": {
"title": "Google Calendar: Get Events",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Get full detail for one Google Calendar event by ID: title, description, location, attendees, recurrence rule. Use when an event_id is already in hand (from google_calendar_get_events) and you need fields beyond the list view (e.g. recurrence) before updating or deleting. Example: google_calendar_get_event({ event_id: "abc123xyz" }).
The description explains the capability, when to use it, and what to expect.
Inputs98/100
Input documentation gives an agent enough context to populate the arguments.
calendar_id
Which calendar: an `id` from google_calendar_get_calendars. Omitted, the athlete's primary calendar.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "google_calendar_get_event",
"title": "Google Calendar: Get Event",
"description": "Get full detail for one Google Calendar event by ID: title, description, location, attendees, recurrence rule. Use when an event_id is already in hand (from google_calendar_get_events) and you need fields beyond the list view (e.g. recurrence) before updating or deleting. Example: google_calendar_get_event({ event_id: \"abc123xyz\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"event_id"
],
"properties": {
"event_id": {
"type": "string",
"description": "The event's `id` from google_calendar_get_events."
},
"calendar_id": {
"type": "string",
"description": "Which calendar: an `id` from google_calendar_get_calendars. Omitted, the athlete's primary calendar."
}
}
},
"annotations": {
"title": "Google Calendar: Get Event",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description79/100
Create a new event on Google Calendar. Use when the user wants to schedule a workout, race, or any calendar event. Supports timed events and all-day events.
The description explains the capability, when to use it, and what to expect.
Inputs96/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
calendar_id
Which calendar: an `id` from google_calendar_get_calendars. Omitted, the athlete's primary calendar.
Clear enough to supply this argument.
summary
Event title
Clear enough to supply this argument.
description
Notes shown in the body of the event.
Clear enough to supply this argument.
start_time
Start time in ISO 8601 format (e.g. '2026-03-22T09:00:00-04:00') or YYYY-MM-DD for all-day events
Clear enough to supply this argument.
end_time
End time in ISO 8601 format or YYYY-MM-DD for all-day events
Clear enough to supply this argument.
location
Where the event takes place, as free text shown on the event (e.g. a gym or a trailhead).
Clear enough to supply this argument.
color_id
Event color ID (1=Lavender, 2=Sage, 3=Grape, 4=Flamingo, 5=Banana, 6=Tangerine, 7=Peacock, 8=Graphite, 9=Blueberry, 10=Basil, 11=Tomato)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
transparency
Free/busy: 'opaque' marks the athlete BUSY (Google's default), 'transparent' shows the event but leaves them FREE. Use 'transparent' for all-day training blocks (an all-day event does not really take the whole day) and whenever the athlete asks not to be blocked; 'opaque' for a timed session they want protected.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "google_calendar_create_event",
"title": "Google Calendar: Create Event",
"description": "Create a new event on Google Calendar. Use when the user wants to schedule a workout, race, or any calendar event. Supports timed events and all-day events.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"summary",
"start_time",
"end_time"
],
"properties": {
"summary": {
"type": "string",
"description": "Event title"
},
"color_id": {
"type": "string",
"description": "Event color ID (1=Lavender, 2=Sage, 3=Grape, 4=Flamingo, 5=Banana, 6=Tangerine, 7=Peacock, 8=Graphite, 9=Blueberry, 10=Basil, 11=Tomato)"
},
"end_time": {
"type": "string",
"description": "End time in ISO 8601 format or YYYY-MM-DD for all-day events"
},
"location": {
"type": "string",
"description": "Where the event takes place, as free text shown on the event (e.g. a gym or a trailhead)."
},
"start_time": {
"type": "string",
"description": "Start time in ISO 8601 format (e.g. '2026-03-22T09:00:00-04:00') or YYYY-MM-DD for all-day events"
},
"calendar_id": {
"type": "string",
"description": "Which calendar: an `id` from google_calendar_get_calendars. Omitted, the athlete's primary calendar."
},
"description": {
"type": "string",
"description": "Notes shown in the body of the event."
},
"transparency": {
"enum": [
"opaque",
"transparent"
],
"type": "string",
"description": "Free/busy: 'opaque' marks the athlete BUSY (Google's default), 'transparent' shows the event but leaves them FREE. Use 'transparent' for all-day training blocks (an all-day event does not really take the whole day) and whenever the athlete asks not to be blocked; 'opaque' for a timed session they want protected."
}
}
},
"annotations": {
"title": "Google Calendar: Create Event",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": false,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description89/100
Update an existing Google Calendar event (change title, time, description, location). An event tagged planMirror is athletedata's own mirror of a plan session: do not hand-edit it (put a time or place in the session itself via the plan tools) - the change would stand as a calendar-side edit the plan does not know about. Converts between timed and all-day: pass BOTH start_time and end_time in the new form (all-day = YYYY-MM-DD with end_time the day AFTER the last day, timed = ISO timestamps).
The description explains the capability, when to use it, and what to expect.
Inputs92/100
Explain what happens when this optional value is omitted.
calendar_id
Which calendar: an `id` from google_calendar_get_calendars. Omitted, the athlete's primary calendar.
Clear enough to supply this argument.
event_id
The event to update: its `id` from google_calendar_get_events.
Clear enough to supply this argument.
summary
New event title
Explain what happens when this optional value is omitted.
description
New notes for the body of the event, replacing the old ones. Omitted, the notes stay as they are.
Clear enough to supply this argument.
start_time
New start time in ISO 8601 or YYYY-MM-DD
Clear enough to supply this argument.
end_time
New end time in ISO 8601 or YYYY-MM-DD
Explain what happens when this optional value is omitted.
location
New event location
Clear enough to supply this argument.
color_id
Event color ID (1=Lavender, 2=Sage, 3=Grape, 4=Flamingo, 5=Banana, 6=Tangerine, 7=Peacock, 8=Graphite, 9=Blueberry, 10=Basil, 11=Tomato)
Clear enough to supply this argument.
transparency
Free/busy: 'opaque' marks the athlete BUSY, 'transparent' leaves them FREE. Set 'transparent' when the athlete says a training event should not block their availability.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "google_calendar_update_event",
"title": "Google Calendar: Update Event",
"description": "Update an existing Google Calendar event (change title, time, description, location). An event tagged planMirror is athletedata's own mirror of a plan session: do not hand-edit it (put a time or place in the session itself via the plan tools) - the change would stand as a calendar-side edit the plan does not know about. Converts between timed and all-day: pass BOTH start_time and end_time in the new form (all-day = YYYY-MM-DD with end_time the day AFTER the last day, timed = ISO timestamps).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"event_id"
],
"properties": {
"summary": {
"type": "string",
"description": "New event title"
},
"color_id": {
"type": "string",
"description": "Event color ID (1=Lavender, 2=Sage, 3=Grape, 4=Flamingo, 5=Banana, 6=Tangerine, 7=Peacock, 8=Graphite, 9=Blueberry, 10=Basil, 11=Tomato)"
},
"end_time": {
"type": "string",
"description": "New end time in ISO 8601 or YYYY-MM-DD"
},
"event_id": {
"type": "string",
"description": "The event to update: its `id` from google_calendar_get_events."
},
"location": {
"type": "string",
"description": "New event location"
},
"start_time": {
"type": "string",
"description": "New start time in ISO 8601 or YYYY-MM-DD"
},
"calendar_id": {
"type": "string",
"description": "Which calendar: an `id` from google_calendar_get_calendars. Omitted, the athlete's primary calendar."
},
"description": {
"type": "string",
"description": "New notes for the body of the event, replacing the old ones. Omitted, the notes stay as they are."
},
"transparency": {
"enum": [
"opaque",
"transparent"
],
"type": "string",
"description": "Free/busy: 'opaque' marks the athlete BUSY, 'transparent' leaves them FREE. Set 'transparent' when the athlete says a training event should not block their availability."
}
}
},
"annotations": {
"title": "Google Calendar: Update Event",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description90/100
Delete a Google Calendar event by ID. Use when cleaning up stale calendar entries before creating replacements (deleting/updating a stale event before creating its replacement prevents duplicate workout invites). Non-coaching events are the athlete's own entries: deleting one removes it from their calendar, so it needs the athlete's go-ahead. Example: google_calendar_delete_event({ event_id: "abc123xyz" }). For more than a few events on one calendar use google_calendar_delete_events.
The description explains the capability, when to use it, and what to expect.
Inputs98/100
Input documentation gives an agent enough context to populate the arguments.
calendar_id
Which calendar: an `id` from google_calendar_get_calendars. Omitted, the athlete's primary calendar.
Clear enough to supply this argument.
event_id
The event to delete: its `id` from google_calendar_get_events.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "google_calendar_delete_event",
"title": "Google Calendar: Delete Event",
"description": "Delete a Google Calendar event by ID. Use when cleaning up stale calendar entries before creating replacements (deleting/updating a stale event before creating its replacement prevents duplicate workout invites). Non-coaching events are the athlete's own entries: deleting one removes it from their calendar, so it needs the athlete's go-ahead. Example: google_calendar_delete_event({ event_id: \"abc123xyz\" }). For more than a few events on one calendar use google_calendar_delete_events.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"event_id"
],
"properties": {
"event_id": {
"type": "string",
"description": "The event to delete: its `id` from google_calendar_get_events."
},
"calendar_id": {
"type": "string",
"description": "Which calendar: an `id` from google_calendar_get_calendars. Omitted, the athlete's primary calendar."
}
}
},
"annotations": {
"title": "Google Calendar: Delete Event",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Delete many events from ONE Google Calendar in one call, for when the athlete asks to clear a whole set (an imported plan, a calendar's worth of workouts) that google_calendar_delete_event would take one call per event to remove. Two steps: call WITHOUT confirm_count to see how many events match the window (nothing is deleted; a sample is returned), then, once the athlete has asked for exactly that, call again with the same arguments and confirm_count set to the matched number. At most 100 are deleted per call; the response carries remaining_matching from a re-read of the window, so a still-matching count means run it again. Events athletedata's own plan mirror wrote, events with other people on the guest list (deleting one cancels it for them) and occurrences of a recurring series are skipped and counted (skipped_plan_mirror, skipped_with_guests, skipped_recurring). Narrow with q (free text) or ical_uid_contains (the id suffix an import stamped on its events, e.g. '@piano-triathlon'). Every call returns `status` (preview, deleted, count_changed or window_too_large), `matched`, `deleted`, the skip counts, `remaining_matching` after a delete, and `next`, which says what the following call should be. Example: google_calendar_delete_events({ calendar_id: "[email protected]", time_min: "2026-11-01T00:00:00Z", time_max: "2027-05-01T00:00:00Z", ical_uid_contains: "@piano-triathlon" }).
The description explains the capability, when to use it, and what to expect.
Inputs97/100
Input documentation gives an agent enough context to populate the arguments.
calendar_id
The ONE calendar to clear (its id from google_calendar_get_calendars or an event's calendarId); 'primary' for their main calendar
Clear enough to supply this argument.
time_min
Start of the window in ISO 8601 (e.g. '2026-11-01T00:00:00Z')
Clear enough to supply this argument.
time_max
End of the window in ISO 8601
Clear enough to supply this argument.
q
Free-text filter, matched by Google against title, description and location
Clear enough to supply this argument.
ical_uid_contains
Keep only events whose iCalUID contains this text (imports stamp one suffix on every event)
Clear enough to supply this argument.
confirm_count
Omit to preview. Set to the `matched` (or `remaining_matching`) number of the preview to delete.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "google_calendar_delete_events",
"title": "Google Calendar: Delete Events",
"description": "Delete many events from ONE Google Calendar in one call, for when the athlete asks to clear a whole set (an imported plan, a calendar's worth of workouts) that google_calendar_delete_event would take one call per event to remove. Two steps: call WITHOUT confirm_count to see how many events match the window (nothing is deleted; a sample is returned), then, once the athlete has asked for exactly that, call again with the same arguments and confirm_count set to the matched number. At most 100 are deleted per call; the response carries remaining_matching from a re-read of the window, so a still-matching count means run it again. Events athletedata's own plan mirror wrote, events with other people on the guest list (deleting one cancels it for them) and occurrences of a recurring series are skipped and counted (skipped_plan_mirror, skipped_with_guests, skipped_recurring). Narrow with q (free text) or ical_uid_contains (the id suffix an import stamped on its events, e.g. '@piano-triathlon'). Every call returns `status` (preview, deleted, count_changed or window_too_large), `matched`, `deleted`, the skip counts, `remaining_matching` after a delete, and `next`, which says what the following call should be. Example: google_calendar_delete_events({ calendar_id: \"[email protected]\", time_min: \"2026-11-01T00:00:00Z\", time_max: \"2027-05-01T00:00:00Z\", ical_uid_contains: \"@piano-triathlon\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"calendar_id",
"time_min",
"time_max"
],
"properties": {
"q": {
"type": "string",
"description": "Free-text filter, matched by Google against title, description and location"
},
"time_max": {
"type": "string",
"description": "End of the window in ISO 8601"
},
"time_min": {
"type": "string",
"description": "Start of the window in ISO 8601 (e.g. '2026-11-01T00:00:00Z')"
},
"calendar_id": {
"type": "string",
"description": "The ONE calendar to clear (its id from google_calendar_get_calendars or an event's calendarId); 'primary' for their main calendar"
},
"confirm_count": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"description": "Omit to preview. Set to the `matched` (or `remaining_matching`) number of the preview to delete."
},
"ical_uid_contains": {
"type": "string",
"description": "Keep only events whose iCalUID contains this text (imports stamp one suffix on every event)"
}
}
},
"annotations": {
"title": "Google Calendar: Delete Events",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
List Intervals.icu activities in a date window: training load (TSS), power/HR averages, intensity, rep structure, and per-activity ids. Intervals.icu's copy only, for its per-session load (intervals.icu-primary athletes); one day across every device, deduped: get_activity_detail({ date }). Example: intervals_get_activities({ oldest: "2026-06-04", newest: "2026-06-10" }). Rows are CURATED summaries in athletedata's own units (pace as sec/km, run cadence as total spm, feel as 5=great..1=exhausted); the key names are internal field names, not athlete-facing terms. `rep_structure` is intervals.icu's own rep digest (e.g. "3x 10m 238w"); for the full per-rep/lap breakdown of one session take the id into intervals_get_activity (full provider payload) or intervals_get_interval_breakdown. Distance and load are not additive across providers' lists: one session appears in each, so a 38km week reads as 113km. Deduped totals: get_analytics_summary (weekly_volume.distance_display / by_sport_distance_display).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
oldest
Start date (YYYY-MM-DD). Defaults to 30 days ago
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
newest
End date (YYYY-MM-DD)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max number of activities to return
Explain what happens when this optional value is omitted.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "intervals_get_activities",
"title": "intervals.icu: Get Activities",
"description": "List Intervals.icu activities in a date window: training load (TSS), power/HR averages, intensity, rep structure, and per-activity ids. Intervals.icu's copy only, for its per-session load (intervals.icu-primary athletes); one day across every device, deduped: get_activity_detail({ date }). Example: intervals_get_activities({ oldest: \"2026-06-04\", newest: \"2026-06-10\" }). Rows are CURATED summaries in athletedata's own units (pace as sec/km, run cadence as total spm, feel as 5=great..1=exhausted); the key names are internal field names, not athlete-facing terms. `rep_structure` is intervals.icu's own rep digest (e.g. \"3x 10m 238w\"); for the full per-rep/lap breakdown of one session take the id into intervals_get_activity (full provider payload) or intervals_get_interval_breakdown. Distance and load are not additive across providers' lists: one session appears in each, so a 38km week reads as 113km. Deduped totals: get_analytics_summary (weekly_volume.distance_display / by_sport_distance_display).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "number",
"description": "Max number of activities to return"
},
"newest": {
"type": "string",
"description": "End date (YYYY-MM-DD)"
},
"oldest": {
"type": "string",
"description": "Start date (YYYY-MM-DD). Defaults to 30 days ago"
}
}
},
"annotations": {
"title": "intervals.icu: Get Activities",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description95/100
Get detailed information about a single Intervals.icu activity. For per-rep interval analysis (cadence/HR/power/pace per work + recovery segment) use intervals_get_interval_breakdown instead - it returns a compact rep table that fits the context budget, whereas this tool's full payload is large and gets truncated.
The description explains the capability, when to use it, and what to expect.
Inputs97/100
Input documentation gives an agent enough context to populate the arguments.
id
The activity's `id` from intervals_get_activities.
Clear enough to supply this argument.
intervals
Include detected intervals and analysis (default false). Prefer intervals_get_interval_breakdown for rep-by-rep detail.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "intervals_get_activity",
"title": "intervals.icu: Get Activity",
"description": "Get detailed information about a single Intervals.icu activity. For per-rep interval analysis (cadence/HR/power/pace per work + recovery segment) use intervals_get_interval_breakdown instead - it returns a compact rep table that fits the context budget, whereas this tool's full payload is large and gets truncated.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"id"
],
"properties": {
"id": {
"type": "string",
"description": "The activity's `id` from intervals_get_activities."
},
"intervals": {
"type": "boolean",
"description": "Include detected intervals and analysis (default false). Prefer intervals_get_interval_breakdown for rep-by-rep detail."
}
}
},
"annotations": {
"title": "intervals.icu: Get Activity",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Per-rep breakdown for an Intervals.icu activity (runs or rides). Returns (1) `intervals` - every detected WORK/RECOVERY segment in order with duration, distance, pace, grade-adjusted pace (gap), avg power, normalized power (np), avg/max HR, cadence, decoupling, and intensity (% of threshold); (2) `groups` - intervals.icu's collapsed view of repeated reps (`count` = how many reps in the group), the cleaner read of session structure; (3) `notes`. Use for 'how did I pace that', within-session cadence/HR drift, and rep-by-rep comparison. CADENCE: for runs it is steps/min (spm) - already doubled from intervals.icu's per-leg rpm, so it needs no further halving or doubling. The session-average cadence on the activity summary is diluted by recovery/walk segments, so the per-rep cadence here is the accurate figure, not the activity average. When intervals.icu's auto-detection fragmented the reps (`intervals` has many more rows than `groups` implies), the exact rep count is uncertain.
The description explains the capability, when to use it, and what to expect.
Inputs98/100
Input documentation gives an agent enough context to populate the arguments.
id
The activity's `id` from intervals_get_activities.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "intervals_get_interval_breakdown",
"title": "intervals.icu: Get Interval Breakdown",
"description": "Per-rep breakdown for an Intervals.icu activity (runs or rides). Returns (1) `intervals` - every detected WORK/RECOVERY segment in order with duration, distance, pace, grade-adjusted pace (gap), avg power, normalized power (np), avg/max HR, cadence, decoupling, and intensity (% of threshold); (2) `groups` - intervals.icu's collapsed view of repeated reps (`count` = how many reps in the group), the cleaner read of session structure; (3) `notes`. Use for 'how did I pace that', within-session cadence/HR drift, and rep-by-rep comparison. CADENCE: for runs it is steps/min (spm) - already doubled from intervals.icu's per-leg rpm, so it needs no further halving or doubling. The session-average cadence on the activity summary is diluted by recovery/walk segments, so the per-rep cadence here is the accurate figure, not the activity average. When intervals.icu's auto-detection fragmented the reps (`intervals` has many more rows than `groups` implies), the exact rep count is uncertain.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"id"
],
"properties": {
"id": {
"type": "string",
"description": "The activity's `id` from intervals_get_activities."
}
}
},
"annotations": {
"title": "intervals.icu: Get Interval Breakdown",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description90/100
Get per-day wellness rows from Intervals.icu: resting HR, HRV, sleep duration/quality, weight, plus athlete-entered fatigue/soreness/mood/stress. Use when the athlete logs wellness in intervals.icu or when cross-checking what intervals relayed. Example: intervals_get_wellness({ oldest: "2026-06-04", newest: "2026-06-10" }). Note: intervals wellness is often a RELAY of Garmin/Oura data and can lag or drift from the source - for provider-specific questions ("my Garmin HRV") call that provider's own tool instead.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
oldest
Start date (YYYY-MM-DD)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
newest
End date (YYYY-MM-DD)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "intervals_get_wellness",
"title": "intervals.icu: Get Wellness",
"description": "Get per-day wellness rows from Intervals.icu: resting HR, HRV, sleep duration/quality, weight, plus athlete-entered fatigue/soreness/mood/stress. Use when the athlete logs wellness in intervals.icu or when cross-checking what intervals relayed. Example: intervals_get_wellness({ oldest: \"2026-06-04\", newest: \"2026-06-10\" }). Note: intervals wellness is often a RELAY of Garmin/Oura data and can lag or drift from the source - for provider-specific questions (\"my Garmin HRV\") call that provider's own tool instead.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"newest": {
"type": "string",
"description": "End date (YYYY-MM-DD)"
},
"oldest": {
"type": "string",
"description": "Start date (YYYY-MM-DD)"
}
}
},
"annotations": {
"title": "intervals.icu: Get Wellness",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Get the athlete's Intervals.icu CALENDAR: planned/scheduled workouts, races, and notes. Use to see what's scheduled in intervals.icu - e.g. a coach-authored plan, or verifying a mirrored athletedata session landed. Example: intervals_get_events({ oldest: "2026-06-10", newest: "2026-06-24" }). This is the PLANNED side; completed activities live in intervals_get_activities. By default each event comes back compact: its fields, the description (very long ones capped with a marker), and `workout` (step_count, the thresholds its targets use, and one line per top-level step as intervals.icu parsed it, which is what the device runs and can differ from the description's text), with the raw workout_doc tree and the PMC/energy fields left out. A NOTE (a Rest day) carries `leftover_workout_steps` (a count): intervals.icu keeps the step tree of the workout it used to be, and it is not part of that day. Pass detail: "full" for the raw intervals.icu events (the tree stays, with the same marker).
The description explains the capability, when to use it, and what to expect.
Inputs81/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
oldest
Start date (YYYY-MM-DD)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
newest
End date (YYYY-MM-DD)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
category
Only events of this Intervals.icu calendar category, e.g. WORKOUT (planned sessions), NOTE, RACE_A (an A-priority race) or TARGET. Omitted, no category filter is applied.
Clear enough to supply this argument.
detail
"compact" (default): each event's fields plus a short `workout` summary of its structure. "full": the raw intervals.icu events including the whole workout_doc step tree; large, use only when you need a field compact leaves out.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "intervals_get_events",
"title": "intervals.icu: Get Events",
"description": "Get the athlete's Intervals.icu CALENDAR: planned/scheduled workouts, races, and notes. Use to see what's scheduled in intervals.icu - e.g. a coach-authored plan, or verifying a mirrored athletedata session landed. Example: intervals_get_events({ oldest: \"2026-06-10\", newest: \"2026-06-24\" }). This is the PLANNED side; completed activities live in intervals_get_activities. By default each event comes back compact: its fields, the description (very long ones capped with a marker), and `workout` (step_count, the thresholds its targets use, and one line per top-level step as intervals.icu parsed it, which is what the device runs and can differ from the description's text), with the raw workout_doc tree and the PMC/energy fields left out. A NOTE (a Rest day) carries `leftover_workout_steps` (a count): intervals.icu keeps the step tree of the workout it used to be, and it is not part of that day. Pass detail: \"full\" for the raw intervals.icu events (the tree stays, with the same marker).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"detail": {
"enum": [
"compact",
"full"
],
"type": "string",
"description": "\"compact\" (default): each event's fields plus a short `workout` summary of its structure. \"full\": the raw intervals.icu events including the whole workout_doc step tree; large, use only when you need a field compact leaves out."
},
"newest": {
"type": "string",
"description": "End date (YYYY-MM-DD)"
},
"oldest": {
"type": "string",
"description": "Start date (YYYY-MM-DD)"
},
"category": {
"type": "string",
"description": "Only events of this Intervals.icu calendar category, e.g. WORKOUT (planned sessions), NOTE, RACE_A (an A-priority race) or TARGET. Omitted, no category filter is applied."
}
}
},
"annotations": {
"title": "intervals.icu: Get Events",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description90/100
Get sport-specific settings from Intervals.icu (FTP, power zones, HR zones, training load settings). Returns Intervals.icu's settings record for that one sport type, as Intervals.icu sends it.
The description explains the capability, when to use it, and what to expect.
Inputs90/100
Input documentation gives an agent enough context to populate the arguments.
type
Sport type (e.g. 'Ride', 'Run', 'VirtualRide'). Default: 'Ride'
Clear enough to supply this argument.
Annotations100/100
All four behavior hints are declared.
{
"title": "intervals.icu: Get Sport Settings",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
Output schema0/100
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "intervals_get_sport_settings",
"title": "intervals.icu: Get Sport Settings",
"description": "Get sport-specific settings from Intervals.icu (FTP, power zones, HR zones, training load settings). Returns Intervals.icu's settings record for that one sport type, as Intervals.icu sends it.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"type": {
"type": "string",
"description": "Sport type (e.g. 'Ride', 'Run', 'VirtualRide'). Default: 'Ride'"
}
}
},
"annotations": {
"title": "intervals.icu: Get Sport Settings",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description89/100
Get the athlete's mean-max power curve from Intervals.icu: best watts achieved at each duration (1s, 5s, 30s, 1m, 5m, 20m, 60m, etc.) over the date window. Cycling-specific. Use when sizing intervals ("prescribe 5x4min, target 110% of CP") or assessing fitness changes (curve dropping = detraining, peaks at long durations rising = aerobic gain). For runners, use get_pace_curve instead. Example: intervals_get_power_curve({ type: "Ride", oldest: "2026-02-01", newest: "2026-05-07" }).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
type
Activity type (required). e.g. 'Ride', 'Run', 'Swim', 'VirtualRide'
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
oldest
Start date (YYYY-MM-DD)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
newest
End date (YYYY-MM-DD)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Annotations100/100
All four behavior hints are declared.
{
"title": "intervals.icu: Get Power Curve",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
Output schema0/100
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "intervals_get_power_curve",
"title": "intervals.icu: Get Power Curve",
"description": "Get the athlete's mean-max power curve from Intervals.icu: best watts achieved at each duration (1s, 5s, 30s, 1m, 5m, 20m, 60m, etc.) over the date window. Cycling-specific. Use when sizing intervals (\"prescribe 5x4min, target 110% of CP\") or assessing fitness changes (curve dropping = detraining, peaks at long durations rising = aerobic gain). For runners, use get_pace_curve instead. Example: intervals_get_power_curve({ type: \"Ride\", oldest: \"2026-02-01\", newest: \"2026-05-07\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "Activity type (required). e.g. 'Ride', 'Run', 'Swim', 'VirtualRide'"
},
"newest": {
"type": "string",
"description": "End date (YYYY-MM-DD)"
},
"oldest": {
"type": "string",
"description": "Start date (YYYY-MM-DD)"
}
}
},
"annotations": {
"title": "intervals.icu: Get Power Curve",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
List the athlete's Intervals.icu route library - saved GPX/route uploads and race courses, each with distance, tags and how many activities have been matched to it. This is a DIFFERENT thing from intervals_get_events (the calendar): a race event on the calendar is not itself a route, even when the athlete has attached one. Use this to find a route by name/tag before calling intervals_get_route to confirm its distance/shape. Rows carry no GPS path (Intervals.icu's list endpoint omits it) and every row carries `delete_note`: Intervals.icu's API has NO route-deletion endpoint, so a route can only be removed by the athlete in the Intervals.icu app; athletedata cannot remove it.
The description explains the capability, when to use it, and what to expect.
Inputs100/100
This tool declares no inputs, so argument descriptions are not needed.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "intervals_get_routes",
"title": "intervals.icu: Get Routes",
"description": "List the athlete's Intervals.icu route library - saved GPX/route uploads and race courses, each with distance, tags and how many activities have been matched to it. This is a DIFFERENT thing from intervals_get_events (the calendar): a race event on the calendar is not itself a route, even when the athlete has attached one. Use this to find a route by name/tag before calling intervals_get_route to confirm its distance/shape. Rows carry no GPS path (Intervals.icu's list endpoint omits it) and every row carries `delete_note`: Intervals.icu's API has NO route-deletion endpoint, so a route can only be removed by the athlete in the Intervals.icu app; athletedata cannot remove it.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {}
},
"annotations": {
"title": "intervals.icu: Get Routes",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description95/100
Get one Intervals.icu route's distance and shape (is it a loop or point-to-point). Use this whenever the athlete asks whether you can see a route/course they loaded into Intervals.icu. route_id comes from intervals_get_routes. LIMIT: Intervals.icu's route API never carries elevation (its GPS path is bare [lat, lng], verified against real routes, not just documented) - `elevation_available` is included but will read false for every real Intervals.icu route, and `elevation_note` then says so. This tool therefore cannot answer a climbs/gain/how-hilly question, because Intervals.icu doesn't expose that for a route; a GPX file of the course sent directly by the athlete DOES carry full elevation. A route can carry no GPS path at all (`path_available: false`), in which case nothing about it, including distance, is readable from the path. The path carries only coordinates: no street/town/trail names and no surface (road/gravel/trail). `delete_note` on the result states that Intervals.icu's API has no way to delete a route, so athletedata cannot remove it.
The description explains the capability, when to use it, and what to expect.
Inputs98/100
Input documentation gives an agent enough context to populate the arguments.
route_id
Intervals.icu route id (from intervals_get_routes).
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "intervals_get_route",
"title": "intervals.icu: Get Route",
"description": "Get one Intervals.icu route's distance and shape (is it a loop or point-to-point). Use this whenever the athlete asks whether you can see a route/course they loaded into Intervals.icu. route_id comes from intervals_get_routes. LIMIT: Intervals.icu's route API never carries elevation (its GPS path is bare [lat, lng], verified against real routes, not just documented) - `elevation_available` is included but will read false for every real Intervals.icu route, and `elevation_note` then says so. This tool therefore cannot answer a climbs/gain/how-hilly question, because Intervals.icu doesn't expose that for a route; a GPX file of the course sent directly by the athlete DOES carry full elevation. A route can carry no GPS path at all (`path_available: false`), in which case nothing about it, including distance, is readable from the path. The path carries only coordinates: no street/town/trail names and no surface (road/gravel/trail). `delete_note` on the result states that Intervals.icu's API has no way to delete a route, so athletedata cannot remove it.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"route_id"
],
"properties": {
"route_id": {
"type": "string",
"description": "Intervals.icu route id (from intervals_get_routes)."
}
}
},
"annotations": {
"title": "intervals.icu: Get Route",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Create or update a workout on the user's Intervals.icu calendar. Use to push structured workouts that sync to the athlete's watch / head unit via Intervals.icu's device integrations (Garmin, Wahoo, COROS, etc.). To update an existing event, pass session_id verbatim from intervals_get_events (numeric ids for events the user created in Intervals.icu, external_id strings for events previously created via this tool). Omit session_id to create new. workout_blocks (optional) get serialized to Intervals.icu's workout DSL inside the description so the workout appears structured on the watch. A description written directly in Intervals.icu's own workout DSL (targeted "- " step lines like "- 4m 264-299w") is passed through and parsed by Intervals.icu. For Run/Ride/Swim sessions whose prose contains distance reps ("3 x 40m", "800m jog"), workout_blocks is REQUIRED — Intervals.icu reads bare "Nm" as minutes, so prose-only pushes with distance reps are rejected (encode reps with distance_meters; untargeted reps are fine). Returns one line: `Created` or `Updated intervals.icu workout (id=...)` with the date, sport and title, followed by any notes on how the workout was written; a refused or failed write returns a line starting `Error:` instead.
The description explains the capability, when to use it, and what to expect.
Inputs78/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
session_id
Existing Intervals.icu event id (from intervals_get_events). Numeric for user-authored events, external_id string for events created via this tool. Omit to create new.
Clear enough to supply this argument.
date
Calendar day in YYYY-MM-DD.
Clear enough to supply this argument.
sport
Run | Ride | Swim | Strength | Yoga | Workout | Rest | Mobility. Maps to Intervals.icu event type.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
title
Workout title (event name).
Clear enough to supply this argument.
description
Free-text notes on the workout in Intervals.icu (intent, execution cues). When workout_blocks are given, their steps are written into this text in Intervals.icu's workout syntax.
Clear enough to supply this argument.
rationale
One-line reason for the edit. Prepended to description.
Clear enough to supply this argument.
duration_min
Target total duration in minutes.
Clear enough to supply this argument.
target_load
Planned training load (TSS) for the workout, shown on the Intervals.icu calendar. With workout_blocks, the load computed from the blocks can replace it; the result says when.
Most specific Garmin category for the lift (warm-up sets keep the lift's own). Two exercises on one category read alike on the watch unless garminExercise names each.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
override_constraint
Only for a write refused because it violates a standing athlete restriction: true once the athlete has confirmed in this conversation that they want this session anyway. The restriction itself stays on file.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description100/100
Delete a workout from the user's Intervals.icu calendar. session_id comes from intervals_get_events (numeric for user-authored events, external_id string for ones created via intervals_upsert_workout). The event is deleted in Intervals.icu itself and there is no undo: putting it back means creating it again with intervals_upsert_workout. Needs an Intervals.icu connection with calendar-write access; a missing scope or rejected key returns an `Error:` line and deletes nothing. Returns `Deleted intervals.icu workout (id=...)` on success.
The description explains the capability, when to use it, and what to expect.
Inputs89/100
Input documentation gives an agent enough context to populate the arguments.
session_id
Intervals.icu event id (from intervals_get_events).
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "intervals_delete_workout",
"title": "intervals.icu: Delete Workout",
"description": "Delete a workout from the user's Intervals.icu calendar. session_id comes from intervals_get_events (numeric for user-authored events, external_id string for ones created via intervals_upsert_workout). The event is deleted in Intervals.icu itself and there is no undo: putting it back means creating it again with intervals_upsert_workout. Needs an Intervals.icu connection with calendar-write access; a missing scope or rejected key returns an `Error:` line and deletes nothing. Returns `Deleted intervals.icu workout (id=...)` on success.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"session_id"
],
"properties": {
"session_id": {
"type": "string",
"description": "Intervals.icu event id (from intervals_get_events)."
}
}
},
"annotations": {
"title": "intervals.icu: Delete Workout",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description94/100
Reschedule an existing Intervals.icu workout to a different day WITHOUT changing the workout. The structured steps, title, duration, load, and tags are preserved exactly — only the date moves — so you can't accidentally rewrite the workout while just shifting the day. Use this (not intervals_upsert_workout) whenever the user wants to move/reschedule a session and is NOT changing its content. session_id comes from intervals_get_events (numeric for user-authored events, external_id string for ones created via intervals_upsert_workout).
The description explains the capability, when to use it, and what to expect.
Inputs88/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
session_id
Intervals.icu event id (from intervals_get_events). Numeric for user-authored events, external_id string for events created via intervals_upsert_workout.
Clear enough to supply this argument.
date
New calendar day for the workout, YYYY-MM-DD.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "intervals_move_workout",
"title": "intervals.icu: Move Workout",
"description": "Reschedule an existing Intervals.icu workout to a different day WITHOUT changing the workout. The structured steps, title, duration, load, and tags are preserved exactly — only the date moves — so you can't accidentally rewrite the workout while just shifting the day. Use this (not intervals_upsert_workout) whenever the user wants to move/reschedule a session and is NOT changing its content. session_id comes from intervals_get_events (numeric for user-authored events, external_id string for ones created via intervals_upsert_workout).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"session_id",
"date"
],
"properties": {
"date": {
"type": "string",
"description": "New calendar day for the workout, YYYY-MM-DD."
},
"session_id": {
"type": "string",
"description": "Intervals.icu event id (from intervals_get_events). Numeric for user-authored events, external_id string for events created via intervals_upsert_workout."
}
}
},
"annotations": {
"title": "intervals.icu: Move Workout",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description95/100
Add a NEW planned session to the athlete's TrainingPeaks calendar (their own calendar - this is their account). Use when they ask for a session to be put on a day: "add an easy 45min spin Saturday", "put a 4x8 threshold session on Tuesday". For a session that already exists use trainingpeaks_edit_workout (change it), trainingpeaks_move_workout (reschedule it) or trainingpeaks_delete_workout. Only today or later - this schedules PLANNED work, and a past-dated session would sit on their calendar as one they never did. Pass workout_blocks to have the intervals land as TrainingPeaks' own structured steps (the athlete's watch then gets the workout, not just a title); prose-only sessions are fine too. When the plan is authored by a human coach in TrainingPeaks, a session added here is written into that coach's plan and is visible to the coach, so it is for sessions the athlete explicitly asks for, not a whole week of them. NOTE: TrainingPeaks BASIC accounts cannot schedule more than a day ahead - TrainingPeaks answers 402 and this tool says so. That is TrainingPeaks' own paywall, not a sync failure, and retrying will not clear it.
The description explains the capability, when to use it, and what to expect.
Inputs77/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
date
Calendar day for the session, YYYY-MM-DD. Today or later.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
sport
Run | Ride | Swim | Strength | Walk | Rowing | Brick | Rest. Anything else lands as TrainingPeaks' Cross-Train, which is what its own UI steers people to.
Clear enough to supply this argument.
title
Session title - what the athlete sees on the calendar.
Clear enough to supply this argument.
description
Execution notes / intent. Required unless duration_min or workout_blocks is given, or the session lands as an empty calendar entry.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
rationale
One-line reason for the session; prepended to the description.
Clear enough to supply this argument.
duration_min
Target total duration in minutes. Derived from workout_blocks when omitted.
Clear enough to supply this argument.
target_load
Target TSS. Computed from workout_blocks when omitted (TrainingPeaks does not derive it from the structure itself).
Most specific Garmin category for the lift (warm-up sets keep the lift's own). Two exercises on one category read alike on the watch unless garminExercise names each.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "trainingpeaks_create_workout",
"title": "TrainingPeaks: Create Workout",
"description": "Add a NEW planned session to the athlete's TrainingPeaks calendar (their own calendar - this is their account). Use when they ask for a session to be put on a day: \"add an easy 45min spin Saturday\", \"put a 4x8 threshold session on Tuesday\". For a session that already exists use trainingpeaks_edit_workout (change it), trainingpeaks_move_workout (reschedule it) or trainingpeaks_delete_workout. Only today or later - this schedules PLANNED work, and a past-dated session would sit on their calendar as one they never did. Pass workout_blocks to have the intervals land as TrainingPeaks' own structured steps (the athlete's watch then gets the workout, not just a title); prose-only sessions are fine too. When the plan is authored by a human coach in TrainingPeaks, a session added here is written into that coach's plan and is visible to the coach, so it is for sessions the athlete explicitly asks for, not a whole week of them. NOTE: TrainingPeaks BASIC accounts cannot schedule more than a day ahead - TrainingPeaks answers 402 and this tool says so. That is TrainingPeaks' own paywall, not a sync failure, and retrying will not clear it.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"date",
"sport",
"title"
],
"properties": {
"date": {
"type": "string",
"description": "Calendar day for the session, YYYY-MM-DD. Today or later."
},
"sport": {
"type": "string",
"description": "Run | Ride | Swim | Strength | Walk | Rowing | Brick | Rest. Anything else lands as TrainingPeaks' Cross-Train, which is what its own UI steers people to."
},
"title": {
"type": "string",
"description": "Session title - what the athlete sees on the calendar."
},
"rationale": {
"type": "string",
"description": "One-line reason for the session; prepended to the description."
},
"description": {
"type": "string",
"description": "Execution notes / intent. Required unless duration_min or workout_blocks is given, or the session lands as an empty calendar entry."
},
"target_load": {
"type": "number",
"description": "Target TSS. Computed from workout_blocks when omitted (TrainingPeaks does not derive it from the structure itself)."
},
"duration_min": {
"type": "number",
"description": "Target total duration in minutes. Derived from workout_blocks when omitted."
},
"workout_blocks": {
"type": "object",
"required": [
"blocks"
],
"properties": {
"blocks": {
"type": "array",
"items": {
"anyOf": [
{
"type": "object",
"required": [
"kind",
"repeats",
"work",
"rest"
],
"properties": {
"kind": {
"enum": [
"interval"
],
"type": "string",
"description": "Repeat block."
},
"rest": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"enum": [
"warmup",
"cooldown",
"steady",
"rest"
],
"type": "string",
"description": "Step type."
},
"label": {
"type": "string",
"maxLength": 60,
"description": "Step name."
},
"endOnLap": {
"type": "boolean",
"description": "Ends on the lap button."
},
"targetRpe": {
"type": "array",
"items": {
"type": "number"
},
"maximum": 10,
"minimum": 1,
"maxItems": 2,
"minItems": 2,
"description": "RPE, 1-10."
},
"freeEffort": {
"type": "boolean",
"description": "Open step, no target."
},
"swimStroke": {
"enum": [
"freestyle",
"backstroke",
"breaststroke",
"butterfly",
"drill",
"mixed",
"im"
],
"type": "string",
"description": "Swim stroke."
},
"durationSec": {
"type": "integer",
"maximum": 86400,
"minimum": 1,
"description": "Seconds (10 min = 600). Required unless distanceMeters is set."
},
"targetHrBpm": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "Heart rate, bpm."
},
"targetFtpPct": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "% of FTP."
},
"targetHrZone": {
"type": "integer",
"maximum": 5,
"minimum": 1,
"description": "Heart-rate zone."
},
"swimEquipment": {
"enum": [
"none",
"fins",
"kickboard",
"paddles",
"pull_buoy",
"snorkel"
],
"type": "string",
"description": "Pool toy."
},
"targetCadence": {
"type": "integer",
"maximum": 200,
"minimum": 30,
"description": "Cadence, rpm or steps/min."
},
"distanceMeters": {
"type": "integer",
"maximum": 50000,
"minimum": 1,
"description": "Metres, the alternative to durationSec."
},
"targetHrPctMax": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "% of max HR."
},
"targetPowerWatts": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "Watts."
},
"targetPaceSecPerKm": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "Pace, sec/km."
}
},
"description": "The recovery leg of each repeat, usually { kind: 'rest', durationSec: N }."
},
"work": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"enum": [
"warmup",
"cooldown",
"steady",
"rest"
],
"type": "string",
"description": "Step type."
},
"label": {
"type": "string",
"maxLength": 60,
"description": "Step name on the watch; set it on every swim drill or kick rep."
},
"endOnLap": {
"type": "boolean",
"description": "Step ends on the lap button; durationSec stays as the shown estimate (only a rest step may omit it). The COROS/Suunto push lands it as a timed step."
},
"targetRpe": {
"type": "array",
"items": {
"type": "number"
},
"maximum": 10,
"minimum": 1,
"maxItems": 2,
"minItems": 2,
"description": "RPE [low, high]: the target for effort-based work with no pace or HR alert."
},
"freeEffort": {
"type": "boolean",
"description": "Open step with no target or alert, for a max-effort test or an athlete who asked for none; no power target."
},
"swimStroke": {
"enum": [
"freestyle",
"backstroke",
"breaststroke",
"butterfly",
"drill",
"mixed",
"im"
],
"type": "string",
"description": "Swim only. 'drill' on every drill or kick rep, else the stroke swum."
},
"durationSec": {
"type": "integer",
"maximum": 86400,
"minimum": 1,
"description": "Seconds (10 min = 600). Required unless distanceMeters is set."
},
"targetHrBpm": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "[low, high] bpm, at least 12 wide; a whole zone is targetHrZone."
},
"targetFtpPct": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "% of FTP, cycling."
},
"targetHrZone": {
"type": "integer",
"maximum": 5,
"minimum": 1,
"description": "Heart-rate zone."
},
"swimEquipment": {
"enum": [
"none",
"fins",
"kickboard",
"paddles",
"pull_buoy",
"snorkel"
],
"type": "string",
"description": "Swim only. Pool toy for this rep; omit if none."
},
"targetCadence": {
"type": "integer",
"maximum": 200,
"minimum": 30,
"description": "Cadence, rpm or steps/min."
},
"distanceMeters": {
"type": "integer",
"maximum": 50000,
"minimum": 1,
"description": "Metres, the alternative to durationSec."
},
"targetHrPctMax": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "% of max heart rate."
},
"targetPowerWatts": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "Power in watts."
},
"targetPaceSecPerKm": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "[low, high] sec/km, at least 15 wide."
}
},
"description": "The work leg of each repeat: a step with an extent and a target."
},
"label": {
"type": "string",
"maxLength": 60,
"description": "Optional set name; label the work leg to show it on each rep."
},
"repeats": {
"type": "integer",
"maximum": 100,
"minimum": 1,
"description": "Number of repeats."
}
},
"description": "Repeat block: N x (work + rest)."
},
{
"type": "object",
"required": [
"kind",
"exerciseCategory",
"sets"
],
"properties": {
"kind": {
"enum": [
"strength"
],
"type": "string",
"description": "One exercise."
},
"reps": {
"type": "integer",
"maximum": 100,
"minimum": 1,
"description": "Reps per set; omit for an AMRAP set or a timed hold."
},
"sets": {
"type": "integer",
"maximum": 20,
"minimum": 1,
"description": "Number of sets."
},
"perSide": {
"type": "boolean",
"description": "True when sets counts each side (3x10 per side = sets 3, perSide true)."
},
"restSec": {
"type": "integer",
"maximum": 1800,
"minimum": 0,
"description": "Fixed rest between sets in seconds; 0 or omitted means no rest step at all."
},
"isWarmup": {
"type": "boolean",
"description": "True on a warm-up or ramp set of the lift its category names."
},
"repRange": {
"type": "array",
"items": {
"type": "integer",
"maximum": 100,
"minimum": 1
},
"maxItems": 2,
"minItems": 2,
"description": "[low, high] range the lift progresses in; reps sits inside it."
},
"weightKg": {
"type": "number",
"maximum": 500,
"minimum": 0,
"description": "Start load in kg; for dumbbells ONE dumbbell. Omit only for bodyweight or when no load is known."
},
"durationSec": {
"type": "integer",
"maximum": 3600,
"minimum": 1,
"description": "A timed hold (plank) instead of reps."
},
"exerciseName": {
"type": "string",
"description": "The lift's name in the athlete's language; always set it."
},
"supersetGroup": {
"type": "integer",
"maximum": 20,
"minimum": 1,
"description": "The same integer on exercises done alternately (a superset or circuit); sets = rounds."
},
"weightKgRange": {
"type": "array",
"items": {
"type": "number",
"maximum": 500,
"minimum": 0
},
"maxItems": 2,
"minItems": 2,
"description": "[low, high] load range in kg; weightKg defaults to the low end."
},
"garminExercise": {
"type": "string",
"maxLength": 120,
"description": "Garmin's key for the exact lift (e.g. DUMBBELL_BENCH_PRESS); omit when unsure (it is picked at write time); \"NONE\" when Garmin has no key for it."
},
"exerciseCategory": {
"enum": [
"UNKNOWN",
"BENCH_PRESS",
"CALF_RAISE",
"CARDIO",
"CARRY",
"CHOP",
"CORE",
"CRUNCH",
"CURL",
"DEADLIFT",
"FLYE",
"HIP_RAISE",
"HIP_STABILITY",
"HIP_SWING",
"HYPEREXTENSION",
"LATERAL_RAISE",
"LEG_CURL",
"LEG_RAISE",
"LUNGE",
"OLYMPIC_LIFT",
"PLANK",
"PLYO",
"PULL_UP",
"PUSH_UP",
"ROW",
"SHOULDER_PRESS",
"SHOULDER_STABILITY",
"SHRUG",
"SIT_UP",
"SQUAT",
"TOTAL_BODY",
"TRICEPS_EXTENSION",
"WARM_UP"
],
"type": "string",
"description": "Most specific Garmin category for the lift (warm-up sets keep the lift's own). Two exercises on one category read alike on the watch unless garminExercise names each."
}
},
"description": "One exercise of a Strength session."
},
{
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"enum": [
"warmup",
"cooldown",
"steady",
"rest",
"ramp"
],
"type": "string",
"description": "Step type; 'ramp' = cycling power ramp."
},
"label": {
"type": "string",
"maxLength": 60,
"description": "Step name on the watch; set it on every swim drill or kick rep."
},
"endOnLap": {
"type": "boolean",
"description": "Step ends on the lap button; durationSec stays as the shown estimate (only a rest step may omit it). The COROS/Suunto push lands it as a timed step."
},
"targetRpe": {
"type": "array",
"items": {
"type": "number"
},
"maximum": 10,
"minimum": 1,
"maxItems": 2,
"minItems": 2,
"description": "RPE [low, high]: the target for effort-based work with no pace or HR alert."
},
"freeEffort": {
"type": "boolean",
"description": "Open step with no target or alert, for a max-effort test or an athlete who asked for none; no power target."
},
"swimStroke": {
"enum": [
"freestyle",
"backstroke",
"breaststroke",
"butterfly",
"drill",
"mixed",
"im"
],
"type": "string",
"description": "Swim only. 'drill' on every drill or kick rep, else the stroke swum."
},
"durationSec": {
"type": "integer",
"maximum": 86400,
"minimum": 1,
"description": "Seconds (10 min = 600). Required unless distanceMeters is set."
},
"targetHrBpm": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "[low, high] bpm, at least 12 wide; a whole zone is targetHrZone."
},
"targetFtpPct": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "% of FTP, cycling."
},
"targetHrZone": {
"type": "integer",
"maximum": 5,
"minimum": 1,
"description": "Heart-rate zone."
},
"swimEquipment": {
"enum": [
"none",
"fins",
"kickboard",
"paddles",
"pull_buoy",
"snorkel"
],
"type": "string",
"description": "Swim only. Pool toy for this rep; omit if none."
},
"targetCadence": {
"type": "integer",
"maximum": 200,
"minimum": 30,
"description": "Cadence, rpm or steps/min."
},
"distanceMeters": {
"type": "integer",
"maximum": 50000,
"minimum": 1,
"description": "Metres, the alternative to durationSec."
},
"targetHrPctMax": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "% of max heart rate."
},
"targetPowerWatts": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "Power in watts."
},
"targetPaceSecPerKm": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "[low, high] sec/km, at least 15 wide."
}
},
"description": "One step. Needs durationSec or distanceMeters, and one target unless it is a rest or an untargeted distance rep; kind 'ramp' is a cycling %FTP ramp (targetFtpPct [start, end]). Every target is a [low, high] band, never a point."
},
{
"type": "object",
"required": [
"kind",
"format",
"movements"
],
"properties": {
"kind": {
"enum": [
"metcon"
],
"type": "string",
"description": "Conditioning piece."
},
"label": {
"type": "string",
"maxLength": 60,
"description": "Optional name."
},
"capSec": {
"type": "integer",
"maximum": 14400,
"minimum": 1,
"description": "AMRAP: required, the clock in seconds. For Time: the cap. Not on an EMOM."
},
"format": {
"enum": [
"amrap",
"emom",
"for_time"
],
"type": "string",
"description": "AMRAP, EMOM or For Time."
},
"rounds": {
"type": "integer",
"maximum": 100,
"minimum": 1,
"description": "For Time only: rounds (default 1)."
},
"intervals": {
"type": "integer",
"maximum": 60,
"minimum": 1,
"description": "EMOM only, required: how many intervals."
},
"movements": {
"type": "array",
"items": {
"type": "object",
"required": [
"exerciseName"
],
"properties": {
"reps": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "Rep count."
},
"calories": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "Calories (erg)."
},
"weightKg": {
"type": "number",
"maximum": 500,
"minimum": 0,
"description": "Load in kg; omit for bodyweight."
},
"durationSec": {
"type": "integer",
"maximum": 3600,
"minimum": 1,
"description": "Seconds (hold or station)."
},
"exerciseName": {
"type": "string",
"maxLength": 60,
"description": "The movement's name."
},
"distanceMeters": {
"type": "number",
"maximum": 42195,
"minimum": 1,
"description": "Metres."
},
"garminExercise": {
"type": "string",
"maxLength": 120,
"description": "Optional Garmin key; set it when exerciseName is not English."
},
"exerciseCategory": {
"enum": [
"UNKNOWN",
"BENCH_PRESS",
"CALF_RAISE",
"CARDIO",
"CARRY",
"CHOP",
"CORE",
"CRUNCH",
"CURL",
"DEADLIFT",
"FLYE",
"HIP_RAISE",
"HIP_STABILITY",
"HIP_SWING",
"HYPEREXTENSION",
"LATERAL_RAISE",
"LEG_CURL",
"LEG_RAISE",
"LUNGE",
"OLYMPIC_LIFT",
"PLANK",
"PLYO",
"PULL_UP",
"PUSH_UP",
"ROW",
"SHOULDER_PRESS",
"SHOULDER_STABILITY",
"SHRUG",
"SIT_UP",
"SQUAT",
"TOTAL_BODY",
"TRICEPS_EXTENSION",
"WARM_UP"
],
"type": "string",
"description": "Optional Garmin category for a lift."
}
},
"description": "One movement, with ONE count: reps, durationSec, distanceMeters or calories (none on an EMOM = max effort for the interval)."
},
"maxItems": 15,
"minItems": 1
},
"emomPattern": {
"enum": [
"alternate",
"all"
],
"type": "string",
"description": "EMOM: 'alternate' (default) one movement per interval, 'all' the whole list each interval."
},
"intervalSec": {
"type": "integer",
"maximum": 600,
"minimum": 10,
"description": "EMOM only: seconds per interval (default 60)."
},
"restAfterSec": {
"type": "integer",
"maximum": 1800,
"minimum": 0,
"description": "Optional rest after the piece."
}
},
"description": "AMRAP / EMOM / For Time piece as ONE block: its clock plus the movement list, e.g. {kind:'metcon', format:'amrap', capSec:720, movements:[{exerciseName:'Burpee', reps:10}]}."
}
]
},
"minItems": 1
}
},
"description": "Structured intervals JSON: { blocks: [...] }. Leaf kinds: warmup / steady / cooldown / rest. Repeats: { kind: 'interval', repeats: 4, work: {...}, rest: {...} }. Targets as targetFtpPct / targetPaceSecPerKm / targetHrBpm / targetHrPctMax (or targetPowerWatts on a ride), each a single number or a [low,high] band; TrainingPeaks stores pace and HR as a percentage of the athlete's threshold, so absolute pace and bpm targets are converted against the threshold pace / threshold HR in the athlete's OWN TrainingPeaks zones (falling back to the one on file here), which is what keeps the steps reading as authored on TrainingPeaks and on the watch it syncs to; targetHrPctMax also needs their max HR. AUTHOR POOL SWIMS AND TRACK REPS IN METRES: put distanceMeters on the leaf instead of durationSec (rests stay in seconds, and the two mix freely in one session), and TrainingPeaks stores them as its own distance steps - a CSS set is 10x100m, not 10x1:50. A distance leaf may carry no target at all. A ramp, a strength block, a device HR zone (targetHrZone), or mixed target types have no TrainingPeaks equivalent - the session then still lands, as title + description, and the response says which."
}
}
},
"annotations": {
"title": "TrainingPeaks: Create Workout",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": false,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description95/100
Change an EXISTING session on the athlete's TrainingPeaks calendar (title, description, duration, target TSS, sport, or - on the athlete's own client - its structured steps via workout_blocks). session_id is the TrainingPeaks workout id - the `trainingpeaks_workout_id` field from trainingpeaks_get_workouts, which reads their actual calendar. To ADD a session that does not exist yet use trainingpeaks_create_workout; this tool only changes one that is already on the calendar. To reschedule without changing the workout use trainingpeaks_move_workout. NOTE: TrainingPeaks BASIC accounts cannot be written to more than a day ahead - TrainingPeaks answers 402 and the tool reports that the change needs TrainingPeaks Premium. That is TrainingPeaks' own paywall, not a sync failure, and retrying will not clear it.
The description explains the capability, when to use it, and what to expect.
Inputs81/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
session_id
TrainingPeaks workout id - the `trainingpeaks_workout_id` field from trainingpeaks_get_workouts (which reads their actual calendar), or from get_planned_workouts when TrainingPeaks is the athlete's plan source.
Clear enough to supply this argument.
date
YYYY-MM-DD of the session as it stands now. Optional; anchors the lookup, which otherwise searches 45 days either side of today.
Clear enough to supply this argument.
title
New name for the session on the TrainingPeaks calendar. Omitted, the name is unchanged.
Clear enough to supply this argument.
description
New execution notes. REPLACES the existing description.
Clear enough to supply this argument.
rationale
One-line reason for the edit; prepended to the description.
Clear enough to supply this argument.
duration_min
New target duration in minutes.
Clear enough to supply this argument.
target_load
New planned TSS for the session in TrainingPeaks. Omitted, the planned TSS is unchanged.
Clear enough to supply this argument.
sport
Run | Ride | Swim | Strength | Walk | Rowing | Brick. Only when changing discipline.
Most specific Garmin category for the lift (warm-up sets keep the lift's own). Two exercises on one category read alike on the watch unless garminExercise names each.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description94/100
Reschedule a TrainingPeaks session to a different day WITHOUT changing the workout. Title, description, structure and load are preserved exactly - only the date moves - so the workout can't be accidentally rewritten while just shifting the day. Use this (not trainingpeaks_edit_workout) whenever the session is moving but its content is not. Same TrainingPeaks Premium caveat as trainingpeaks_edit_workout.
The description explains the capability, when to use it, and what to expect.
Inputs88/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
session_id
TrainingPeaks workout id - the `trainingpeaks_workout_id` field from trainingpeaks_get_workouts (which reads their actual calendar), or from get_planned_workouts when TrainingPeaks is the athlete's plan source.
Clear enough to supply this argument.
date
New calendar day for the session, YYYY-MM-DD.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "trainingpeaks_move_workout",
"title": "TrainingPeaks: Move Workout",
"description": "Reschedule a TrainingPeaks session to a different day WITHOUT changing the workout. Title, description, structure and load are preserved exactly - only the date moves - so the workout can't be accidentally rewritten while just shifting the day. Use this (not trainingpeaks_edit_workout) whenever the session is moving but its content is not. Same TrainingPeaks Premium caveat as trainingpeaks_edit_workout.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"session_id",
"date"
],
"properties": {
"date": {
"type": "string",
"description": "New calendar day for the session, YYYY-MM-DD."
},
"session_id": {
"type": "string",
"description": "TrainingPeaks workout id - the `trainingpeaks_workout_id` field from trainingpeaks_get_workouts (which reads their actual calendar), or from get_planned_workouts when TrainingPeaks is the athlete's plan source."
}
}
},
"annotations": {
"title": "TrainingPeaks: Move Workout",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description92/100
Remove a session from the athlete's TrainingPeaks calendar. session_id is the `trainingpeaks_workout_id` field from trainingpeaks_get_workouts, which reads their actual calendar. On an athletedata-authored plan, a session WE pushed is removed with the athletedata coach's delete_plan_session tool instead - that takes the TrainingPeaks copy with it. A COMPLETED session is refused - that is training history, not a plan row. Same TrainingPeaks Premium caveat as trainingpeaks_edit_workout.
The description explains the capability, when to use it, and what to expect.
Inputs98/100
Input documentation gives an agent enough context to populate the arguments.
session_id
TrainingPeaks workout id - the `trainingpeaks_workout_id` field from trainingpeaks_get_workouts (which reads their actual calendar), or from get_planned_workouts when TrainingPeaks is the athlete's plan source.
Clear enough to supply this argument.
date
YYYY-MM-DD of the session. It anchors the lookup that checks the session is still planned, not completed; omitted, that lookup searches 45 days either side of today.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "trainingpeaks_delete_workout",
"title": "TrainingPeaks: Delete Workout",
"description": "Remove a session from the athlete's TrainingPeaks calendar. session_id is the `trainingpeaks_workout_id` field from trainingpeaks_get_workouts, which reads their actual calendar. On an athletedata-authored plan, a session WE pushed is removed with the athletedata coach's delete_plan_session tool instead - that takes the TrainingPeaks copy with it. A COMPLETED session is refused - that is training history, not a plan row. Same TrainingPeaks Premium caveat as trainingpeaks_edit_workout.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"session_id"
],
"properties": {
"date": {
"type": "string",
"description": "YYYY-MM-DD of the session. It anchors the lookup that checks the session is still planned, not completed; omitted, that lookup searches 45 days either side of today."
},
"session_id": {
"type": "string",
"description": "TrainingPeaks workout id - the `trainingpeaks_workout_id` field from trainingpeaks_get_workouts (which reads their actual calendar), or from get_planned_workouts when TrainingPeaks is the athlete's plan source."
}
}
},
"annotations": {
"title": "TrainingPeaks: Delete Workout",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Reads what is on the athlete's TrainingPeaks calendar for a date range: planned and completed sessions, their own and ones athletedata pushed, plus TrainingPeaks Strength Builder sessions (`calendar: "strength_builder"`, ids starting `tps:`, which no trainingpeaks_* write tool can change). Use for questions about their TrainingPeaks calendar: what is on it, a duplicate, or a mismatch with the plan (get_planned_workouts returns athletedata's own plan instead). Races and other events, notes and availability are not workouts and never appear here: trainingpeaks_get_calendar_items reads them. Each row: `trainingpeaks_workout_id` (the session_id trainingpeaks_edit_workout / trainingpeaks_move_workout / trainingpeaks_delete_workout take), `status` (planned | completed), title, the coach's written `description`, the athlete's own `athlete_comments` (read live), and `pushed_by_athletedata` with `plan_session_id`. `unmanaged_planned_days` lists days holding a planned workout athletedata did not push, and `note` names the removal route. There are no performance metrics and no decoded interval structure here, even on a completed row: a completed session's numbers come from terra_get_activities(provider: "trainingpeaks"), which `completed_metrics_note` repeats. A very wide window carries `omitted_workouts` / `omitted_note`; a failed Strength Builder read carries `strength_calendar_unavailable`. Read-only.
The description explains the capability, when to use it, and what to expect.
Inputs83/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
First day to read, YYYY-MM-DD. Defaults to a week before today.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
Last day to read, YYYY-MM-DD. Defaults to three weeks out.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
days
Alternative to end_date: read this many days FORWARD from today (the week behind today is included either way).
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "trainingpeaks_get_workouts",
"title": "TrainingPeaks: Get Workouts",
"description": "Reads what is on the athlete's TrainingPeaks calendar for a date range: planned and completed sessions, their own and ones athletedata pushed, plus TrainingPeaks Strength Builder sessions (`calendar: \"strength_builder\"`, ids starting `tps:`, which no trainingpeaks_* write tool can change). Use for questions about their TrainingPeaks calendar: what is on it, a duplicate, or a mismatch with the plan (get_planned_workouts returns athletedata's own plan instead). Races and other events, notes and availability are not workouts and never appear here: trainingpeaks_get_calendar_items reads them. Each row: `trainingpeaks_workout_id` (the session_id trainingpeaks_edit_workout / trainingpeaks_move_workout / trainingpeaks_delete_workout take), `status` (planned | completed), title, the coach's written `description`, the athlete's own `athlete_comments` (read live), and `pushed_by_athletedata` with `plan_session_id`. `unmanaged_planned_days` lists days holding a planned workout athletedata did not push, and `note` names the removal route. There are no performance metrics and no decoded interval structure here, even on a completed row: a completed session's numbers come from terra_get_activities(provider: \"trainingpeaks\"), which `completed_metrics_note` repeats. A very wide window carries `omitted_workouts` / `omitted_note`; a failed Strength Builder read carries `strength_calendar_unavailable`. Read-only.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"days": {
"type": "number",
"description": "Alternative to end_date: read this many days FORWARD from today (the week behind today is included either way)."
},
"end_date": {
"type": "string",
"description": "Last day to read, YYYY-MM-DD. Defaults to three weeks out."
},
"start_date": {
"type": "string",
"description": "First day to read, YYYY-MM-DD. Defaults to a week before today."
}
}
},
"annotations": {
"title": "TrainingPeaks: Get Workouts",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Read the thresholds in the athlete's own TrainingPeaks profile (Settings -> Zones): bike FTP, bike and run threshold heart rate, run threshold pace and run power threshold, swim threshold pace, plus the default set TrainingPeaks falls back to, next to what athletedata holds for the same anchors and where they differ. Use it when the athlete says their zones, FTP or threshold are in TrainingPeaks, or before writing watt, pace or heart-rate targets for a TrainingPeaks athlete with no threshold on file - instead of asking them to type numbers TrainingPeaks already holds. These are the numbers TrainingPeaks itself divides a structured step by. A READ: it saves nothing; when they say to use those numbers, save them with set_ftp / set_thresholds. Only the thresholds are read, not the zone boundaries. A null is not set in their profile - ask for it, do not guess it. Returns JSON: TrainingPeaks' values under `bike`, `run`, `swim` and `default_set`, athletedata's under `on_file_in_athletedata`, the mismatches in `differences`, and a `note`.
The description explains the capability, when to use it, and what to expect.
Inputs100/100
This tool declares no inputs, so argument descriptions are not needed.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "trainingpeaks_get_profile",
"title": "TrainingPeaks: Get Profile",
"description": "Read the thresholds in the athlete's own TrainingPeaks profile (Settings -> Zones): bike FTP, bike and run threshold heart rate, run threshold pace and run power threshold, swim threshold pace, plus the default set TrainingPeaks falls back to, next to what athletedata holds for the same anchors and where they differ. Use it when the athlete says their zones, FTP or threshold are in TrainingPeaks, or before writing watt, pace or heart-rate targets for a TrainingPeaks athlete with no threshold on file - instead of asking them to type numbers TrainingPeaks already holds. These are the numbers TrainingPeaks itself divides a structured step by. A READ: it saves nothing; when they say to use those numbers, save them with set_ftp / set_thresholds. Only the thresholds are read, not the zone boundaries. A null is not set in their profile - ask for it, do not guess it. Returns JSON: TrainingPeaks' values under `bike`, `run`, `swim` and `default_set`, athletedata's under `on_file_in_athletedata`, the mismatches in `differences`, and a `note`.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {}
},
"annotations": {
"title": "TrainingPeaks: Get Profile",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Read the NON-workout items on the athlete's TrainingPeaks calendar for a date range: calendar notes, events (races, with A/B/C priority) and availability (days marked unavailable, or limited to some sports). These are what the athlete and a TrainingPeaks coach use to tell each other about travel, races and life around training. Each note carries `note_id` and `written_by_athlete` (false = written by someone else on the account, usually their coach; those notes are read-only here). Each event carries `event_id`; each availability entry carries `availability_id`, `status` (unavailable | limited) and, when limited, `available_sports`. Those ids are what trainingpeaks_save_note / trainingpeaks_save_event / trainingpeaks_set_availability (to change one) and trainingpeaks_delete_calendar_item take. Workouts are not included; trainingpeaks_get_workouts reads those. A list that comes back null (with `unavailable_note`) was not readable this time and is unknown, not empty. This is a READ and changes nothing.
The description explains the capability, when to use it, and what to expect.
Inputs88/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
First day, YYYY-MM-DD. Defaults to a week before today.
Clear enough to supply this argument.
end_date
Last day, YYYY-MM-DD. Defaults to 180 days out. At most 365 days after start_date (a TrainingPeaks limit).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "trainingpeaks_get_calendar_items",
"title": "TrainingPeaks: Get Calendar Items",
"description": "Read the NON-workout items on the athlete's TrainingPeaks calendar for a date range: calendar notes, events (races, with A/B/C priority) and availability (days marked unavailable, or limited to some sports). These are what the athlete and a TrainingPeaks coach use to tell each other about travel, races and life around training. Each note carries `note_id` and `written_by_athlete` (false = written by someone else on the account, usually their coach; those notes are read-only here). Each event carries `event_id`; each availability entry carries `availability_id`, `status` (unavailable | limited) and, when limited, `available_sports`. Those ids are what trainingpeaks_save_note / trainingpeaks_save_event / trainingpeaks_set_availability (to change one) and trainingpeaks_delete_calendar_item take. Workouts are not included; trainingpeaks_get_workouts reads those. A list that comes back null (with `unavailable_note`) was not readable this time and is unknown, not empty. This is a READ and changes nothing.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end_date": {
"type": "string",
"description": "Last day, YYYY-MM-DD. Defaults to 180 days out. At most 365 days after start_date (a TrainingPeaks limit)."
},
"start_date": {
"type": "string",
"description": "First day, YYYY-MM-DD. Defaults to a week before today."
}
}
},
"annotations": {
"title": "TrainingPeaks: Get Calendar Items",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description92/100
Add a calendar NOTE to a day on the athlete's TrainingPeaks calendar, or change one they wrote. A note is a title (shown on the day) plus optional text, and it is how an athlete tells a TrainingPeaks coach something about a day: "travelling, hotel gym only", "slept 4h, legs heavy", "work dinner Thursday, can train early". Without note_id it adds a new note; with note_id (from trainingpeaks_get_calendar_items) it changes that note's title, text or date. Notes written by someone else on the account (written_by_athlete: false, usually the coach) are refused for edits, and a new note on the same day is the way to reply. Any day, past or future. Not a TrainingPeaks Premium feature: Basic accounts can write notes. Write it in the athlete's own voice, as a message to their coach, and only what the athlete asked to pass on.
The description explains the capability, when to use it, and what to expect.
Inputs94/100
Input documentation gives an agent enough context to populate the arguments.
note_id
Omit to add a new note. Pass an existing note_id to change that note.
Clear enough to supply this argument.
date
Calendar day, YYYY-MM-DD. Required for a new note; on an edit, moves the note to this day.
Clear enough to supply this argument.
title
Short title shown on the calendar day. Required for a new note.
Clear enough to supply this argument.
description
The note's text. On an edit, REPLACES the existing text.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "trainingpeaks_save_note",
"title": "TrainingPeaks: Save Note",
"description": "Add a calendar NOTE to a day on the athlete's TrainingPeaks calendar, or change one they wrote. A note is a title (shown on the day) plus optional text, and it is how an athlete tells a TrainingPeaks coach something about a day: \"travelling, hotel gym only\", \"slept 4h, legs heavy\", \"work dinner Thursday, can train early\". Without note_id it adds a new note; with note_id (from trainingpeaks_get_calendar_items) it changes that note's title, text or date. Notes written by someone else on the account (written_by_athlete: false, usually the coach) are refused for edits, and a new note on the same day is the way to reply. Any day, past or future. Not a TrainingPeaks Premium feature: Basic accounts can write notes. Write it in the athlete's own voice, as a message to their coach, and only what the athlete asked to pass on.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"date": {
"type": "string",
"description": "Calendar day, YYYY-MM-DD. Required for a new note; on an edit, moves the note to this day."
},
"title": {
"type": "string",
"description": "Short title shown on the calendar day. Required for a new note."
},
"note_id": {
"type": "string",
"description": "Omit to add a new note. Pass an existing note_id to change that note."
},
"description": {
"type": "string",
"description": "The note's text. On an edit, REPLACES the existing text."
}
}
},
"annotations": {
"title": "TrainingPeaks: Save Note",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": false,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description92/100
Add a race / EVENT to the athlete's TrainingPeaks calendar, or change an existing one. Without event_id it adds a new event; with event_id (from trainingpeaks_get_calendar_items) it changes only the fields passed. Priority follows TrainingPeaks' A/B/C convention: A = a goal race the season is built around, B = an important race trained through, C = a training race (the default). Changing an existing event's priority or date can change how a TrainingPeaks coach's annual plan treats it, so that is a change for the athlete to ask for explicitly. Not a TrainingPeaks Premium feature: Basic accounts can add events.
The description explains the capability, when to use it, and what to expect.
Inputs97/100
Input documentation gives an agent enough context to populate the arguments.
event_id
Omit to add a new event. Pass an existing event_id to change that event.
Clear enough to supply this argument.
current_date
On an edit, the event's date as trainingpeaks_get_calendar_items returned it (YYYY-MM-DD). Locates the event; without it the lookup covers a year back to two years out.
Clear enough to supply this argument.
name
Race name, e.g. "Cambridge Half". Required for a new event.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "trainingpeaks_save_event",
"title": "TrainingPeaks: Save Event",
"description": "Add a race / EVENT to the athlete's TrainingPeaks calendar, or change an existing one. Without event_id it adds a new event; with event_id (from trainingpeaks_get_calendar_items) it changes only the fields passed. Priority follows TrainingPeaks' A/B/C convention: A = a goal race the season is built around, B = an important race trained through, C = a training race (the default). Changing an existing event's priority or date can change how a TrainingPeaks coach's annual plan treats it, so that is a change for the athlete to ask for explicitly. Not a TrainingPeaks Premium feature: Basic accounts can add events.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"date": {
"type": "string",
"description": "Race day, YYYY-MM-DD. Required for a new event."
},
"name": {
"type": "string",
"description": "Race name, e.g. \"Cambridge Half\". Required for a new event."
},
"event_id": {
"type": "string",
"description": "Omit to add a new event. Pass an existing event_id to change that event."
},
"priority": {
"type": "string",
"description": "A | B | C. Defaults to C on a new event."
},
"ctl_target": {
"type": "number",
"description": "Target CTL (fitness) for race day, if the athlete has one."
},
"event_type": {
"type": "string",
"description": "TrainingPeaks event type, e.g. RunningRoad, RunningTrail, CyclingRoad, CyclingMountain, SwimOpenWater, MultisportTriathlon, MultisportDuathlon, RowingRegatta, SnowNordic, OtherOther (default)."
},
"description": {
"type": "string",
"description": "Free text: goals, logistics. On an edit, REPLACES the existing text."
},
"distance_km": {
"type": "number",
"description": "Race distance in kilometres (a half marathon is 21.0975)."
},
"current_date": {
"type": "string",
"description": "On an edit, the event's date as trainingpeaks_get_calendar_items returned it (YYYY-MM-DD). Locates the event; without it the lookup covers a year back to two years out."
}
}
},
"annotations": {
"title": "TrainingPeaks: Save Event",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": false,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description96/100
Mark days on the athlete's TrainingPeaks calendar as UNAVAILABLE (no training) or LIMITED (only some sports possible), or change an existing availability entry. This is TrainingPeaks' own availability feature, the one a TrainingPeaks coach plans around: "away Thursday to Sunday, can only run" is start_date..end_date, limited, available_sports ["Run"]. Without availability_id it adds a new entry; with one (from trainingpeaks_get_calendar_items) it changes that entry and keeps every field not passed. A single day is start_date alone. At most 92 days per entry. Not a TrainingPeaks Premium feature: Basic accounts can set availability. Workouts already planned on those days stay where they are; moving them is trainingpeaks_move_workout.
The description explains the capability, when to use it, and what to expect.
Inputs96/100
Input documentation gives an agent enough context to populate the arguments.
availability_id
Omit to add a new entry. Pass an existing availability_id to change it.
Clear enough to supply this argument.
current_date
On an edit, the entry's start_date as trainingpeaks_get_calendar_items returned it (YYYY-MM-DD). Locates the entry; without it the lookup covers a year back to two years out.
Clear enough to supply this argument.
start_date
First day affected, YYYY-MM-DD. Required for a new entry.
Clear enough to supply this argument.
end_date
Last day affected (inclusive), YYYY-MM-DD. Defaults to start_date.
Clear enough to supply this argument.
limited
false = unavailable, no training at all (the default). true = only the sports in available_sports are possible. Implied true when available_sports is passed.
Clear enough to supply this argument.
available_sports
For a limited entry, the sports still possible those days: Swim, Bike, Run, Brick, Crosstrain, MTB, Strength, XC-Ski, Rowing, Walk, Other.
Clear enough to supply this argument.
reason
Short category shown in TrainingPeaks, e.g. Travel, Work, Family, Illness, Injury, Vacation. Defaults to Other.
Clear enough to supply this argument.
description
Free text the coach sees, e.g. "New York, hotel gym only".
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "trainingpeaks_set_availability",
"title": "TrainingPeaks: Set Availability",
"description": "Mark days on the athlete's TrainingPeaks calendar as UNAVAILABLE (no training) or LIMITED (only some sports possible), or change an existing availability entry. This is TrainingPeaks' own availability feature, the one a TrainingPeaks coach plans around: \"away Thursday to Sunday, can only run\" is start_date..end_date, limited, available_sports [\"Run\"]. Without availability_id it adds a new entry; with one (from trainingpeaks_get_calendar_items) it changes that entry and keeps every field not passed. A single day is start_date alone. At most 92 days per entry. Not a TrainingPeaks Premium feature: Basic accounts can set availability. Workouts already planned on those days stay where they are; moving them is trainingpeaks_move_workout.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"reason": {
"type": "string",
"description": "Short category shown in TrainingPeaks, e.g. Travel, Work, Family, Illness, Injury, Vacation. Defaults to Other."
},
"limited": {
"type": "boolean",
"description": "false = unavailable, no training at all (the default). true = only the sports in available_sports are possible. Implied true when available_sports is passed."
},
"end_date": {
"type": "string",
"description": "Last day affected (inclusive), YYYY-MM-DD. Defaults to start_date."
},
"start_date": {
"type": "string",
"description": "First day affected, YYYY-MM-DD. Required for a new entry."
},
"description": {
"type": "string",
"description": "Free text the coach sees, e.g. \"New York, hotel gym only\"."
},
"current_date": {
"type": "string",
"description": "On an edit, the entry's start_date as trainingpeaks_get_calendar_items returned it (YYYY-MM-DD). Locates the entry; without it the lookup covers a year back to two years out."
},
"availability_id": {
"type": "string",
"description": "Omit to add a new entry. Pass an existing availability_id to change it."
},
"available_sports": {
"type": "array",
"items": {
"type": "string"
},
"description": "For a limited entry, the sports still possible those days: Swim, Bike, Run, Brick, Crosstrain, MTB, Strength, XC-Ski, Rowing, Walk, Other."
}
}
},
"annotations": {
"title": "TrainingPeaks: Set Availability",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": false,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
Remove a note, event or availability entry from the athlete's TrainingPeaks calendar. kind says which, id is its note_id / event_id / availability_id from trainingpeaks_get_calendar_items. A note written by someone else on the account (written_by_athlete: false, usually the coach) is refused. Deleting an event leaves the workouts on its days in place. Workouts themselves are removed with trainingpeaks_delete_workout.
The description explains the capability, when to use it, and what to expect.
Inputs88/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
kind
note | event | availability
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
id
The item's `note_id`, `event_id` or `availability_id`, from trainingpeaks_get_calendar_items.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "trainingpeaks_delete_calendar_item",
"title": "TrainingPeaks: Delete Calendar Item",
"description": "Remove a note, event or availability entry from the athlete's TrainingPeaks calendar. kind says which, id is its note_id / event_id / availability_id from trainingpeaks_get_calendar_items. A note written by someone else on the account (written_by_athlete: false, usually the coach) is refused. Deleting an event leaves the workouts on its days in place. Workouts themselves are removed with trainingpeaks_delete_workout.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"kind",
"id"
],
"properties": {
"id": {
"type": "string",
"description": "The item's `note_id`, `event_id` or `availability_id`, from trainingpeaks_get_calendar_items."
},
"kind": {
"enum": [
"note",
"event",
"availability"
],
"type": "string",
"description": "note | event | availability"
}
}
},
"annotations": {
"title": "TrainingPeaks: Delete Calendar Item",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Look up an athlete's OFFICIAL race result - finish time, leg/station/kilometre splits, and overall + age-group placement - from public results databases. COVERAGE: IRONMAN, HYROX, and the Berlin / Boston / Chicago / London / New York marathons. Nothing else has a results database behind it, so this tool cannot find any other road race, parkrun or local triathlon (Tokyo and Sydney included - they are Majors but their portals aren't reachable here). Use when the athlete references a result you can't see in their connected data: Strava/Garmin have the GPS file but NOT the official timing, splits, or placement. Give the athlete's full name, the event, and the year. Provider is auto-detected from the event name; set it explicitly if ambiguous. For HYROX, the format (singles, doubles, pro, relay, elite, adaptive, or pro / elite doubles) is auto-detected from the event name but can be forced with hyrox_division - doubles/relay results return the TEAM's combined station splits, and either partner's name finds the entry. For IRONMAN, world championships resolve from the host city alone ('Marbella', 'Taupo', 'Kona', 'Nice'); if an event still can't be resolved, pass ironman_results_url with either the ironman.com results link or a labs-v2.competitor.com/results/event/... link (both work). Marathon results carry every timing mat (5K, 10K, ... HALF ... Finish) with the CUMULATIVE time, the segment split and the pace for each, which is what answers 'where did I lose time' - the segments carry that, not the cumulative column. What this returns is OFFICIAL timing data from the race's own results database. Returns one entry per matching finisher.
The description explains the capability, when to use it, and what to expect.
Inputs97/100
Input documentation gives an agent enough context to populate the arguments.
athlete_name
Athlete's full name as it appears in the results (e.g. 'Sam Laidlow'). For HYROX doubles/relay, either partner's name works.
Clear enough to supply this argument.
event
Race/event name, e.g. 'IRONMAN World Championship Nice', 'IRONMAN 70.3 Marbella', 'HYROX Berlin' (host city for HYROX), 'Berlin Marathon', 'TCS New York City Marathon'.
Clear enough to supply this argument.
year
Calendar year the race took place (e.g. 2024).
Clear enough to supply this argument.
provider
Force the results database. Omit to auto-detect from the event name.
Clear enough to supply this argument.
gender
Narrows disambiguation (IRONMAN Men/Women editions, HYROX field). 'M'/'F' or 'male'/'female'.
Clear enough to supply this argument.
hyrox_division
HYROX format. Omitted, it is read from the event name (doubles or pairs, relay, pro, elite, adaptive, and pro or elite doubles), else singles. Set explicitly when the name doesn't say (e.g. the athlete only tells you they raced doubles).
Clear enough to supply this argument.
ironman_results_url
IRONMAN escape hatch: the exact ironman.com results URL or race slug when name resolution fails.
Clear enough to supply this argument.
hyrox_season
HYROX season override (e.g. 8 for the 2025/26 season) when the year->season guess misses.
Clear enough to supply this argument.
Annotations100/100
All four behavior hints are declared.
{
"title": "Look Up Official Race Result",
"readOnlyHint": true,
"openWorldHint": true,
"idempotentHint": true,
"destructiveHint": false
}
Output schema0/100
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "race_results_lookup",
"title": "Look Up Official Race Result",
"description": "Look up an athlete's OFFICIAL race result - finish time, leg/station/kilometre splits, and overall + age-group placement - from public results databases. COVERAGE: IRONMAN, HYROX, and the Berlin / Boston / Chicago / London / New York marathons. Nothing else has a results database behind it, so this tool cannot find any other road race, parkrun or local triathlon (Tokyo and Sydney included - they are Majors but their portals aren't reachable here). Use when the athlete references a result you can't see in their connected data: Strava/Garmin have the GPS file but NOT the official timing, splits, or placement. Give the athlete's full name, the event, and the year. Provider is auto-detected from the event name; set it explicitly if ambiguous. For HYROX, the format (singles, doubles, pro, relay, elite, adaptive, or pro / elite doubles) is auto-detected from the event name but can be forced with hyrox_division - doubles/relay results return the TEAM's combined station splits, and either partner's name finds the entry. For IRONMAN, world championships resolve from the host city alone ('Marbella', 'Taupo', 'Kona', 'Nice'); if an event still can't be resolved, pass ironman_results_url with either the ironman.com results link or a labs-v2.competitor.com/results/event/... link (both work). Marathon results carry every timing mat (5K, 10K, ... HALF ... Finish) with the CUMULATIVE time, the segment split and the pace for each, which is what answers 'where did I lose time' - the segments carry that, not the cumulative column. What this returns is OFFICIAL timing data from the race's own results database. Returns one entry per matching finisher.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"athlete_name",
"event",
"year"
],
"properties": {
"year": {
"type": "number",
"description": "Calendar year the race took place (e.g. 2024)."
},
"event": {
"type": "string",
"description": "Race/event name, e.g. 'IRONMAN World Championship Nice', 'IRONMAN 70.3 Marbella', 'HYROX Berlin' (host city for HYROX), 'Berlin Marathon', 'TCS New York City Marathon'."
},
"gender": {
"enum": [
"M",
"F",
"male",
"female",
"men",
"women"
],
"type": "string",
"description": "Narrows disambiguation (IRONMAN Men/Women editions, HYROX field). 'M'/'F' or 'male'/'female'."
},
"provider": {
"enum": [
"ironman",
"hyrox",
"marathon"
],
"type": "string",
"description": "Force the results database. Omit to auto-detect from the event name."
},
"athlete_name": {
"type": "string",
"description": "Athlete's full name as it appears in the results (e.g. 'Sam Laidlow'). For HYROX doubles/relay, either partner's name works."
},
"hyrox_season": {
"type": "number",
"description": "HYROX season override (e.g. 8 for the 2025/26 season) when the year->season guess misses."
},
"hyrox_division": {
"enum": [
"singles",
"doubles",
"pro",
"pro_doubles",
"relay",
"elite",
"elite_doubles",
"adaptive"
],
"type": "string",
"description": "HYROX format. Omitted, it is read from the event name (doubles or pairs, relay, pro, elite, adaptive, and pro or elite doubles), else singles. Set explicitly when the name doesn't say (e.g. the athlete only tells you they raced doubles)."
},
"ironman_results_url": {
"type": "string",
"description": "IRONMAN escape hatch: the exact ironman.com results URL or race slug when name resolution fails."
}
}
},
"annotations": {
"title": "Look Up Official Race Result",
"readOnlyHint": true,
"openWorldHint": true,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
Food log entries in a date range from every nutrition source: Apple Health (any food app writing to HealthKit), Cronometer, MyFitnessPal, Yazio, Lifesum, Lose It!, Fuelin, the athletedata app's own food logger (source athletedata; photo estimates have ids starting app:photo:) and manual entries. Nutrition has no per-provider tool, so all of it is read here. Returns individual entries plus daily macro totals.
TIMES: Yazio, Lifesum and Lose It! rows are meal slots whose logged_at is a placeholder (a Lifesum saved meal has null totals; a Lose It! slot has a null macro when a food lacks it). Fuelin rows are planned meal windows, plus a 'During <session>' row for fuel taken in a session. Apple Health rows are either PER-MEAL, with the real local eating time in logged_at, or WHOLE-DAY, flagged whole_day_total, whose logged_at and meal_type are placeholders; the flag is per entry. A manual whole_day_total day is complete. pre_logged entries are dated after the athlete's today: planned, not eaten.
PAGES: raw entries come in pages of whole days, newest first (at most `limit` entries, about 15,000 characters); `next_cursor`, passed back as `cursor` with the same other arguments, returns older days. `daily_totals` reaches up to a month further back than a page's entries. For a month or more, aggregate='weekly' returns per-week averages over the whole range.
DETAIL: compact by default (macros, foods with quantities and their macros; `id` only where food_log_update / food_log_delete apply); detail='full' returns every stored field.
The description explains the capability, when to use it, and what to expect.
Inputs84/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start date in YYYY-MM-DD format (defaults to today)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End date in YYYY-MM-DD format (defaults to today)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
meal_type
Filter by meal type
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max entries per page (default 250, max 1000); a page also ends early, on a whole day, once it reaches about 15,000 characters. Ignored when aggregate is set. Apple Health delivers one entry per eating occasion, so a day is several entries, not one. A range holding more comes with `next_cursor`.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
detail
'compact' (default): per entry the day, time, meal, source, macros and foods with quantities, without per-food micronutrient maps, null fields or bookkeeping ids. 'full': every stored field, including each food's full micronutrient map - much larger, so a page holds fewer days. Keep the same value when paging with cursor.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
timezone
IANA timezone for correct day boundaries (e.g. 'America/New_York'). Pass the user's timezone so date queries align with their local calendar.
Clear enough to supply this argument.
aggregate
'weekly' returns per-ISO-week averages (kcal/macros per logged day) across the whole range — use for multi-month nutrition analysis; raw entries are omitted.
Clear enough to supply this argument.
cursor
Continuation token: the `next_cursor` value from this tool's previous response, passed back unchanged with the same other arguments to get the next page. Omit it for the first page.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "food_log_get",
"title": "Get Food Log",
"description": "Food log entries in a date range from every nutrition source: Apple Health (any food app writing to HealthKit), Cronometer, MyFitnessPal, Yazio, Lifesum, Lose It!, Fuelin, the athletedata app's own food logger (source athletedata; photo estimates have ids starting app:photo:) and manual entries. Nutrition has no per-provider tool, so all of it is read here. Returns individual entries plus daily macro totals.\n\nTIMES: Yazio, Lifesum and Lose It! rows are meal slots whose logged_at is a placeholder (a Lifesum saved meal has null totals; a Lose It! slot has a null macro when a food lacks it). Fuelin rows are planned meal windows, plus a 'During <session>' row for fuel taken in a session. Apple Health rows are either PER-MEAL, with the real local eating time in logged_at, or WHOLE-DAY, flagged whole_day_total, whose logged_at and meal_type are placeholders; the flag is per entry. A manual whole_day_total day is complete. pre_logged entries are dated after the athlete's today: planned, not eaten.\n\nPAGES: raw entries come in pages of whole days, newest first (at most `limit` entries, about 15,000 characters); `next_cursor`, passed back as `cursor` with the same other arguments, returns older days. `daily_totals` reaches up to a month further back than a page's entries. For a month or more, aggregate='weekly' returns per-week averages over the whole range.\n\nDETAIL: compact by default (macros, foods with quantities and their macros; `id` only where food_log_update / food_log_delete apply); detail='full' returns every stored field.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"limit": {
"type": "number",
"description": "Max entries per page (default 250, max 1000); a page also ends early, on a whole day, once it reaches about 15,000 characters. Ignored when aggregate is set. Apple Health delivers one entry per eating occasion, so a day is several entries, not one. A range holding more comes with `next_cursor`."
},
"cursor": {
"type": "string",
"description": "Continuation token: the `next_cursor` value from this tool's previous response, passed back unchanged with the same other arguments to get the next page. Omit it for the first page."
},
"detail": {
"enum": [
"compact",
"full"
],
"type": "string",
"description": "'compact' (default): per entry the day, time, meal, source, macros and foods with quantities, without per-food micronutrient maps, null fields or bookkeeping ids. 'full': every stored field, including each food's full micronutrient map - much larger, so a page holds fewer days. Keep the same value when paging with cursor."
},
"end_date": {
"type": "string",
"description": "End date in YYYY-MM-DD format (defaults to today)"
},
"timezone": {
"type": "string",
"description": "IANA timezone for correct day boundaries (e.g. 'America/New_York'). Pass the user's timezone so date queries align with their local calendar."
},
"aggregate": {
"enum": [
"weekly"
],
"type": "string",
"description": "'weekly' returns per-ISO-week averages (kcal/macros per logged day) across the whole range — use for multi-month nutrition analysis; raw entries are omitted."
},
"meal_type": {
"enum": [
"breakfast",
"lunch",
"dinner",
"snack"
],
"type": "string",
"description": "Filter by meal type"
},
"start_date": {
"type": "string",
"description": "Start date in YYYY-MM-DD format (defaults to today)"
}
}
},
"annotations": {
"title": "Get Food Log",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description96/100
Log a food entry with estimated macronutrients. Use when the user describes what they ate (text or photo). Macros are estimates; approximate figures are accepted. STORED RECIPES: when the athlete refers to a recurring meal BY NAME (e.g. 'my usual Beyond Burger', 'the typical pasta I always make'), the stored figures for that item are in the athletedata coach's nutrition memory (read_memory_file('nutrition'), a coach tool), and reusing them rather than re-estimating keeps the same meal from drifting day to day. A stored recipe only fills in what the athlete has not said in this request: whatever they state now (an ingredient, a serving count, or that the meal is new/different) overrides it, and if their description departs from the stored one, the entry reflects what they actually said, not the stored total. A serving count or macro figure taken from the stored recipe rather than from the athlete has not been confirmed by them, and they only see it if it is stated. BARE NUMBERS ('no. 4', '#2', 'meal 3'): the athlete is pointing at an item in a list you have to go find (a menu photo, a numbered recipe), not naming a food - if more than one numbered list is in play, matching the wrong one is easy and the resulting macros will look just as confident as a right match. A wrong match is visible to the athlete immediately only when the matched food is named, not just the number; otherwise they have to catch it by doing the arithmetic themselves. Returns the saved row PLUS the athlete's authoritative day total for that day, recomputed after this entry - that total is the 'how much have I eaten today' figure, already including this meal.
The description explains the capability, when to use it, and what to expect.
Inputs97/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
meal_type
Type of meal
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
description
Human-readable description of the meal (e.g., '2 eggs, toast with butter, and coffee')
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
calories
Estimated total calories (kcal)
Clear enough to supply this argument.
protein_grams
Estimated protein in grams
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
carbs_grams
Estimated carbohydrates in grams
Clear enough to supply this argument.
fat_grams
Estimated total fat in grams
Clear enough to supply this argument.
saturated_fat_grams
Estimated saturated fat in grams (only if known, e.g. from a label or the athlete)
Clear enough to supply this argument.
unsaturated_fat_grams
Estimated unsaturated fat in grams (mono + poly). Tracked explicitly, NOT derived from total minus saturated - leave unset if unknown
Clear enough to supply this argument.
fiber_grams
Estimated fiber in grams
Clear enough to supply this argument.
sugar_grams
Estimated sugar in grams
Clear enough to supply this argument.
sodium_mg
Estimated sodium in mg
Clear enough to supply this argument.
water_ml
Water intake in ml (for drinks / hydration logged with the meal)
Clear enough to supply this argument.
food_items › items › name
Food item name with quantity (e.g., '2 scrambled eggs')
Clear enough to supply this argument.
food_items › items › grams
Estimated edible weight of this item in grams. REQUIRED when the meal came from a PHOTO. State the mass you actually assumed - it is the number most likely to be wrong, and recording it is what lets the athlete correct one figure instead of arguing with four macros. On a CORRECTION, re-send it for every item you are keeping: the array replaces the stored one wholesale.
Clear enough to supply this argument.
food_items › items › calories
This item's energy in kcal.
Clear enough to supply this argument.
food_items › items › protein
This item's protein in grams.
Clear enough to supply this argument.
food_items › items › carbs
This item's carbohydrates in grams.
Clear enough to supply this argument.
food_items › items › fat
This item's total fat in grams.
Clear enough to supply this argument.
food_items › items › athlete_confirmed
true only when the athlete gave THIS item's figures themselves this turn (read off a label or package, or stated the number). Stored as their confirmed food, so later logs of it reuse the figures instead of a fresh estimate. Never for your own estimate.
Clear enough to supply this argument.
food_items › items › basis
What THIS item's numbers rest on. label: its figures are off a label, package or menu (the athlete's, or a stored confirmed food). stated: the athlete gave the amount (grams, ml, or a count of a packaged unit of known size) and you supplied standard values for it. estimate: you assumed the portion from a description. photo: you sized the portion from a photo. A day whose calories are mostly estimate/photo items is treated as +/-30%, so only say label or stated when that is where the amount came from. On a CORRECTION, re-send it for every item you are keeping.
Clear enough to supply this argument.
logged_at
ISO datetime when the meal was eaten (defaults to now)
Clear enough to supply this argument.
whole_day_total
true when this ONE entry is the athlete's WHOLE DAY of eating, not one meal - e.g. a screenshot of their food app's day totals, or 'here's everything I ate yesterday' logged as a single entry. meal_type is then just a placeholder. Without it, a completed day holding one entry reads as a day where they logged one meal and stopped, and the day is reported as only partly logged when it is not.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "food_log_entry",
"title": "Log Food Entry",
"description": "Log a food entry with estimated macronutrients. Use when the user describes what they ate (text or photo). Macros are estimates; approximate figures are accepted. STORED RECIPES: when the athlete refers to a recurring meal BY NAME (e.g. 'my usual Beyond Burger', 'the typical pasta I always make'), the stored figures for that item are in the athletedata coach's nutrition memory (read_memory_file('nutrition'), a coach tool), and reusing them rather than re-estimating keeps the same meal from drifting day to day. A stored recipe only fills in what the athlete has not said in this request: whatever they state now (an ingredient, a serving count, or that the meal is new/different) overrides it, and if their description departs from the stored one, the entry reflects what they actually said, not the stored total. A serving count or macro figure taken from the stored recipe rather than from the athlete has not been confirmed by them, and they only see it if it is stated. BARE NUMBERS ('no. 4', '#2', 'meal 3'): the athlete is pointing at an item in a list you have to go find (a menu photo, a numbered recipe), not naming a food - if more than one numbered list is in play, matching the wrong one is easy and the resulting macros will look just as confident as a right match. A wrong match is visible to the athlete immediately only when the matched food is named, not just the number; otherwise they have to catch it by doing the arithmetic themselves. Returns the saved row PLUS the athlete's authoritative day total for that day, recomputed after this entry - that total is the 'how much have I eaten today' figure, already including this meal.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"meal_type",
"description"
],
"properties": {
"calories": {
"type": "number",
"description": "Estimated total calories (kcal)"
},
"water_ml": {
"type": "number",
"description": "Water intake in ml (for drinks / hydration logged with the meal)"
},
"fat_grams": {
"type": "number",
"description": "Estimated total fat in grams"
},
"logged_at": {
"type": "string",
"description": "ISO datetime when the meal was eaten (defaults to now)"
},
"meal_type": {
"enum": [
"breakfast",
"lunch",
"dinner",
"snack"
],
"type": "string",
"description": "Type of meal"
},
"sodium_mg": {
"type": "number",
"description": "Estimated sodium in mg"
},
"food_items": {
"type": "array",
"items": {
"type": "object",
"required": [
"name"
],
"properties": {
"fat": {
"type": "number",
"description": "This item's total fat in grams."
},
"name": {
"type": "string",
"description": "Food item name with quantity (e.g., '2 scrambled eggs')"
},
"basis": {
"enum": [
"label",
"stated",
"estimate",
"photo"
],
"type": "string",
"description": "What THIS item's numbers rest on. label: its figures are off a label, package or menu (the athlete's, or a stored confirmed food). stated: the athlete gave the amount (grams, ml, or a count of a packaged unit of known size) and you supplied standard values for it. estimate: you assumed the portion from a description. photo: you sized the portion from a photo. A day whose calories are mostly estimate/photo items is treated as +/-30%, so only say label or stated when that is where the amount came from. On a CORRECTION, re-send it for every item you are keeping."
},
"carbs": {
"type": "number",
"description": "This item's carbohydrates in grams."
},
"grams": {
"type": "number",
"description": "Estimated edible weight of this item in grams. REQUIRED when the meal came from a PHOTO. State the mass you actually assumed - it is the number most likely to be wrong, and recording it is what lets the athlete correct one figure instead of arguing with four macros. On a CORRECTION, re-send it for every item you are keeping: the array replaces the stored one wholesale."
},
"protein": {
"type": "number",
"description": "This item's protein in grams."
},
"calories": {
"type": "number",
"description": "This item's energy in kcal."
},
"athlete_confirmed": {
"type": "boolean",
"description": "true only when the athlete gave THIS item's figures themselves this turn (read off a label or package, or stated the number). Stored as their confirmed food, so later logs of it reuse the figures instead of a fresh estimate. Never for your own estimate."
}
}
},
"description": "Individual food items with per-item macro breakdown"
},
"carbs_grams": {
"type": "number",
"description": "Estimated carbohydrates in grams"
},
"description": {
"type": "string",
"description": "Human-readable description of the meal (e.g., '2 eggs, toast with butter, and coffee')"
},
"fiber_grams": {
"type": "number",
"description": "Estimated fiber in grams"
},
"sugar_grams": {
"type": "number",
"description": "Estimated sugar in grams"
},
"protein_grams": {
"type": "number",
"description": "Estimated protein in grams"
},
"whole_day_total": {
"type": "boolean",
"description": "true when this ONE entry is the athlete's WHOLE DAY of eating, not one meal - e.g. a screenshot of their food app's day totals, or 'here's everything I ate yesterday' logged as a single entry. meal_type is then just a placeholder. Without it, a completed day holding one entry reads as a day where they logged one meal and stopped, and the day is reported as only partly logged when it is not."
},
"saturated_fat_grams": {
"type": "number",
"description": "Estimated saturated fat in grams (only if known, e.g. from a label or the athlete)"
},
"unsaturated_fat_grams": {
"type": "number",
"description": "Estimated unsaturated fat in grams (mono + poly). Tracked explicitly, NOT derived from total minus saturated - leave unset if unknown"
}
}
},
"annotations": {
"title": "Log Food Entry",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": false,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description100/100
Correct an existing MANUAL food log entry (the ones logged via food_log_entry). Use when the athlete says a logged meal was wrong - wrong macros, wrong meal type, wrong day, or a typo. Pass the entry `id` (from food_log_get) plus only the fields to change. Cannot edit Cronometer / MyFitnessPal rows (those sync from Terra and must be fixed in the source app). Returns the corrected row PLUS that day's authoritative total recomputed after the correction, which supersedes any figure derived before it.
The description explains the capability, when to use it, and what to expect.
Inputs99/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
id
The nutrition_logs entry id to update (from food_log_get)
Clear enough to supply this argument.
date
Move the entry to this user-local day, YYYY-MM-DD
Clear enough to supply this argument.
meal_type
Corrected meal type
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
description
Corrected description of the meal
Clear enough to supply this argument.
calories
Corrected total calories (kcal); null to clear (refilled from food_items when every item carries that figure)
Clear enough to supply this argument.
protein_grams
Corrected protein in grams; null to clear (refilled from food_items when every item carries that figure)
Clear enough to supply this argument.
carbs_grams
Corrected carbohydrates in grams; null to clear (refilled from food_items when every item carries that figure)
Clear enough to supply this argument.
fat_grams
Corrected total fat in grams; null to clear (refilled from food_items when every item carries that figure)
Clear enough to supply this argument.
saturated_fat_grams
Corrected saturated fat in grams; null to clear
Clear enough to supply this argument.
unsaturated_fat_grams
Corrected unsaturated fat in grams (mono + poly, tracked explicitly); null to clear
Clear enough to supply this argument.
fiber_grams
Corrected fiber in grams; null to clear
Clear enough to supply this argument.
sugar_grams
Corrected sugar in grams; null to clear
Clear enough to supply this argument.
sodium_mg
Corrected sodium in mg; null to clear
Clear enough to supply this argument.
water_ml
Corrected water in ml; null to clear
Clear enough to supply this argument.
food_items › anyOf › 0 › items › name
Food item name with quantity (e.g., '2 scrambled eggs')
Clear enough to supply this argument.
food_items › anyOf › 0 › items › grams
Estimated edible weight of this item in grams. REQUIRED when the meal came from a PHOTO. State the mass you actually assumed - it is the number most likely to be wrong, and recording it is what lets the athlete correct one figure instead of arguing with four macros. On a CORRECTION, re-send it for every item you are keeping: the array replaces the stored one wholesale.
true only when the athlete gave THIS item's figures themselves this turn (read off a label or package, or stated the number). Stored as their confirmed food, so later logs of it reuse the figures instead of a fresh estimate. Never for your own estimate.
Clear enough to supply this argument.
food_items › anyOf › 0 › items › basis
What THIS item's numbers rest on. label: its figures are off a label, package or menu (the athlete's, or a stored confirmed food). stated: the athlete gave the amount (grams, ml, or a count of a packaged unit of known size) and you supplied standard values for it. estimate: you assumed the portion from a description. photo: you sized the portion from a photo. A day whose calories are mostly estimate/photo items is treated as +/-30%, so only say label or stated when that is where the amount came from. On a CORRECTION, re-send it for every item you are keeping.
Clear enough to supply this argument.
whole_day_total
true: this entry is the athlete's whole day of eating in one entry (see food_log_entry). false: it is one meal after all.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "food_log_update",
"title": "Update Food Log Entry",
"description": "Correct an existing MANUAL food log entry (the ones logged via food_log_entry). Use when the athlete says a logged meal was wrong - wrong macros, wrong meal type, wrong day, or a typo. Pass the entry `id` (from food_log_get) plus only the fields to change. Cannot edit Cronometer / MyFitnessPal rows (those sync from Terra and must be fixed in the source app). Returns the corrected row PLUS that day's authoritative total recomputed after the correction, which supersedes any figure derived before it.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"id"
],
"properties": {
"id": {
"type": "string",
"description": "The nutrition_logs entry id to update (from food_log_get)"
},
"date": {
"type": "string",
"description": "Move the entry to this user-local day, YYYY-MM-DD"
},
"calories": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Corrected total calories (kcal); null to clear (refilled from food_items when every item carries that figure)"
},
"water_ml": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Corrected water in ml; null to clear"
},
"fat_grams": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Corrected total fat in grams; null to clear (refilled from food_items when every item carries that figure)"
},
"meal_type": {
"enum": [
"breakfast",
"lunch",
"dinner",
"snack"
],
"type": "string",
"description": "Corrected meal type"
},
"sodium_mg": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Corrected sodium in mg; null to clear"
},
"food_items": {
"anyOf": [
{
"type": "array",
"items": {
"type": "object",
"required": [
"name"
],
"properties": {
"fat": {
"type": "number",
"description": "This item's total fat in grams."
},
"name": {
"type": "string",
"description": "Food item name with quantity (e.g., '2 scrambled eggs')"
},
"basis": {
"enum": [
"label",
"stated",
"estimate",
"photo"
],
"type": "string",
"description": "What THIS item's numbers rest on. label: its figures are off a label, package or menu (the athlete's, or a stored confirmed food). stated: the athlete gave the amount (grams, ml, or a count of a packaged unit of known size) and you supplied standard values for it. estimate: you assumed the portion from a description. photo: you sized the portion from a photo. A day whose calories are mostly estimate/photo items is treated as +/-30%, so only say label or stated when that is where the amount came from. On a CORRECTION, re-send it for every item you are keeping."
},
"carbs": {
"type": "number",
"description": "This item's carbohydrates in grams."
},
"grams": {
"type": "number",
"description": "Estimated edible weight of this item in grams. REQUIRED when the meal came from a PHOTO. State the mass you actually assumed - it is the number most likely to be wrong, and recording it is what lets the athlete correct one figure instead of arguing with four macros. On a CORRECTION, re-send it for every item you are keeping: the array replaces the stored one wholesale."
},
"protein": {
"type": "number",
"description": "This item's protein in grams."
},
"calories": {
"type": "number",
"description": "This item's energy in kcal."
},
"athlete_confirmed": {
"type": "boolean",
"description": "true only when the athlete gave THIS item's figures themselves this turn (read off a label or package, or stated the number). Stored as their confirmed food, so later logs of it reuse the figures instead of a fresh estimate. Never for your own estimate."
}
}
}
},
{
"type": "null"
}
],
"description": "Corrected per-item breakdown. Pass the FULL replacement array (it overwrites the existing items, not merges) so a wrong sub-item no longer needs a delete + recreate; null to clear. Re-send `grams` on every item you are keeping - a mass you do not restate is carried forward from the stored row only when the item's NAME is unchanged, and a renamed item starts with no mass."
},
"carbs_grams": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Corrected carbohydrates in grams; null to clear (refilled from food_items when every item carries that figure)"
},
"description": {
"type": "string",
"description": "Corrected description of the meal"
},
"fiber_grams": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Corrected fiber in grams; null to clear"
},
"sugar_grams": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Corrected sugar in grams; null to clear"
},
"protein_grams": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Corrected protein in grams; null to clear (refilled from food_items when every item carries that figure)"
},
"whole_day_total": {
"type": "boolean",
"description": "true: this entry is the athlete's whole day of eating in one entry (see food_log_entry). false: it is one meal after all."
},
"saturated_fat_grams": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Corrected saturated fat in grams; null to clear"
},
"unsaturated_fat_grams": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Corrected unsaturated fat in grams (mono + poly, tracked explicitly); null to clear"
}
}
},
"annotations": {
"title": "Update Food Log Entry",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description100/100
Delete a MANUAL food log entry (the ones logged via food_log_entry). Use when the athlete asks to remove a meal that was logged by mistake - e.g. a fueling question mistaken for an eaten meal, or a duplicate. Pass the entry `id` from food_log_get. Cannot delete Cronometer / MyFitnessPal rows (those sync from Terra and must be removed in the source app). Returns that day's authoritative total recomputed after the deletion, which supersedes subtracting the removed entry from an earlier figure.
The description explains the capability, when to use it, and what to expect.
Inputs97/100
Input documentation gives an agent enough context to populate the arguments.
id
The nutrition_logs entry id to delete (from food_log_get)
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "food_log_delete",
"title": "Delete Food Log Entry",
"description": "Delete a MANUAL food log entry (the ones logged via food_log_entry). Use when the athlete asks to remove a meal that was logged by mistake - e.g. a fueling question mistaken for an eaten meal, or a duplicate. Pass the entry `id` from food_log_get. Cannot delete Cronometer / MyFitnessPal rows (those sync from Terra and must be removed in the source app). Returns that day's authoritative total recomputed after the deletion, which supersedes subtracting the removed entry from an earlier figure.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"id"
],
"properties": {
"id": {
"type": "string",
"description": "The nutrition_logs entry id to delete (from food_log_get)"
}
}
},
"annotations": {
"title": "Delete Food Log Entry",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
Get the athlete's daily calorie and macro targets (protein, carbs, fat), with weekend overrides and any dated protocol (carb load, refeed), plus TODAY's resolved targets, what they have eaten so far today across every nutrition source, and what remains. Call before answering anything about targets, 'how much do I have left', or whether a day's intake is on track. Targets may have been set by the coach or by the athlete in the app (set_by).
The description explains the capability, when to use it, and what to expect.
Inputs100/100
This tool declares no inputs, so argument descriptions are not needed.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "nutrition_targets_get",
"title": "Get Nutrition Targets",
"description": "Get the athlete's daily calorie and macro targets (protein, carbs, fat), with weekend overrides and any dated protocol (carb load, refeed), plus TODAY's resolved targets, what they have eaten so far today across every nutrition source, and what remains. Call before answering anything about targets, 'how much do I have left', or whether a day's intake is on track. Targets may have been set by the coach or by the athlete in the app (set_by).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {}
},
"annotations": {
"title": "Get Nutrition Targets",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description95/100
Set or update the athlete's daily calorie/macro targets. Call when the athlete states or agrees to specific numbers. Every field is optional: a field you pass replaces its stored value, a field you omit keeps it, so a protein-only change keeps the calorie target. Pass weekend_* only when Sat AND Sun differ; pass day_targets when a single weekday differs (it would otherwise land on the other weekend day too). Pass schedule for targets that change by DATE (race-week carb load, refeed): it replaces the whole stored dated protocol, [] drops it. clear=true removes all targets. The targets show in the athlete's app Food tab and anchor the coach's fuelling advice.
The description explains the capability, when to use it, and what to expect.
Inputs73/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
kcal
Daily calorie target (weekday base when a weekend split exists).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
protein_g
Daily protein target in grams.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
carbs_g
Daily carbohydrate target in grams.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
fat_g
Daily fat target in grams.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
weekend_kcal
Sat/Sun calorie override.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
weekend_protein_g
Sat/Sun protein override in grams.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
weekend_carbs_g
Sat/Sun carbs override in grams.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
weekend_fat_g
Sat/Sun fat override in grams.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
clear_weekend
true drops the stored Sat/Sun override.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
day_targets › items › day
mon, tue, wed, thu, fri, sat or sun.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
day_targets › items › kcal
That weekday's calorie target in kcal.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
day_targets › items › protein_g
That weekday's protein target in grams.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
day_targets › items › carbs_g
That weekday's carbohydrate target in grams.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
day_targets › items › fat_g
That weekday's fat target in grams.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
day_targets › items › clear
true removes this weekday's override.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
schedule › items › date
YYYY-MM-DD, athlete-local.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
schedule › items › label
Short label, e.g. 'primer day'.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
schedule › items › kcal
That date's calorie target in kcal (lower bound when kcal_max is set).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
schedule › items › protein_g
That date's protein target in grams (lower bound when protein_g_max is set).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
schedule › items › carbs_g
Lower bound when a range.
Explain the meaning of this value, not just its name.
schedule › items › fat_g
That date's fat target in grams (lower bound when fat_g_max is set).
Explain the meaning of this value, not just its name.
schedule › items › kcal_max
Upper bound of a calorie range in kcal; omit for a single target.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
schedule › items › protein_g_max
Upper bound of a protein range in grams; omit for a single target.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
schedule › items › carbs_g_max
Upper bound of a carb range.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
schedule › items › fat_g_max
Upper bound of a fat range in grams; omit for a single target.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
note
Short context shown with the targets, e.g. 'cut until early Sep'.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
clear
true removes all targets; every other field is ignored.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "nutrition_targets_set",
"title": "Set Nutrition Targets",
"description": "Set or update the athlete's daily calorie/macro targets. Call when the athlete states or agrees to specific numbers. Every field is optional: a field you pass replaces its stored value, a field you omit keeps it, so a protein-only change keeps the calorie target. Pass weekend_* only when Sat AND Sun differ; pass day_targets when a single weekday differs (it would otherwise land on the other weekend day too). Pass schedule for targets that change by DATE (race-week carb load, refeed): it replaces the whole stored dated protocol, [] drops it. clear=true removes all targets. The targets show in the athlete's app Food tab and anchor the coach's fuelling advice.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"kcal": {
"type": "number",
"description": "Daily calorie target (weekday base when a weekend split exists)."
},
"note": {
"type": "string",
"description": "Short context shown with the targets, e.g. 'cut until early Sep'."
},
"clear": {
"type": "boolean",
"description": "true removes all targets; every other field is ignored."
},
"fat_g": {
"type": "number",
"description": "Daily fat target in grams."
},
"carbs_g": {
"type": "number",
"description": "Daily carbohydrate target in grams."
},
"schedule": {
"type": "array",
"items": {
"type": "object",
"required": [
"date"
],
"properties": {
"date": {
"type": "string",
"description": "YYYY-MM-DD, athlete-local."
},
"kcal": {
"type": "number",
"description": "That date's calorie target in kcal (lower bound when kcal_max is set)."
},
"fat_g": {
"type": "number",
"description": "That date's fat target in grams (lower bound when fat_g_max is set)."
},
"label": {
"type": "string",
"description": "Short label, e.g. 'primer day'."
},
"carbs_g": {
"type": "number",
"description": "Lower bound when a range."
},
"kcal_max": {
"type": "number",
"description": "Upper bound of a calorie range in kcal; omit for a single target."
},
"fat_g_max": {
"type": "number",
"description": "Upper bound of a fat range in grams; omit for a single target."
},
"protein_g": {
"type": "number",
"description": "That date's protein target in grams (lower bound when protein_g_max is set)."
},
"carbs_g_max": {
"type": "number",
"description": "Upper bound of a carb range."
},
"protein_g_max": {
"type": "number",
"description": "Upper bound of a protein range in grams; omit for a single target."
}
}
},
"description": "Date-keyed overrides for a multi-day protocol. Replaces the whole stored schedule; [] drops it. Max 60 entries."
},
"protein_g": {
"type": "number",
"description": "Daily protein target in grams."
},
"day_targets": {
"type": "array",
"items": {
"type": "object",
"required": [
"day"
],
"properties": {
"day": {
"type": "string",
"description": "mon, tue, wed, thu, fri, sat or sun."
},
"kcal": {
"type": "number",
"description": "That weekday's calorie target in kcal."
},
"clear": {
"type": "boolean",
"description": "true removes this weekday's override."
},
"fat_g": {
"type": "number",
"description": "That weekday's fat target in grams."
},
"carbs_g": {
"type": "number",
"description": "That weekday's carbohydrate target in grams."
},
"protein_g": {
"type": "number",
"description": "That weekday's protein target in grams."
}
}
},
"description": "Recurring single-weekday overrides. Each entry replaces that weekday's earlier override; other weekdays are kept; [] drops them all."
},
"weekend_kcal": {
"type": "number",
"description": "Sat/Sun calorie override."
},
"clear_weekend": {
"type": "boolean",
"description": "true drops the stored Sat/Sun override."
},
"weekend_fat_g": {
"type": "number",
"description": "Sat/Sun fat override in grams."
},
"weekend_carbs_g": {
"type": "number",
"description": "Sat/Sun carbs override in grams."
},
"weekend_protein_g": {
"type": "number",
"description": "Sat/Sun protein override in grams."
}
}
},
"annotations": {
"title": "Set Nutrition Targets",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
This week vs last week, across all connected platforms: sessions, minutes and distance by type with a week-over-week comparison, ride kinds, plus HRV/RHR and sleep trend lines. get_analytics_summary already carries the same deduped volume with load, fitness and wellness, so an overview needs ONE of the two: call this one only for week-over-week or ride kinds, never both for one answer. `week_over_week` is ALWAYS the last 7 days including today against the 7 days before them, whatever `days` you asked for - it does not stretch with the window, and it carries the two date ranges (`current_week` / `previous_week`) that show exactly which days each half covers. In a window longer than 14 days the older sessions are in `total_sessions` and in NEITHER week, so the two week counts never add up to the total. Everything else (total_sessions, total_duration_minutes, total_distance_display, sessions_by_type, distance_by_type_display, avg_hr) covers the whole `period`, a different scope from `week_over_week`. Distance is per sport in `distance_by_type_display`, paired with `sessions_by_type`; `total_distance_display` is absent when two or more sports carry distance, since a run km plus a ride km is not one number. SINGLE-SPORT QUESTIONS: pass `sport` (run/ride/swim/strength/walk/row) to narrow sessions, minutes, distance and the week-over-week halves to that sport alone; recovery and sleep stay whole-body. This tool carries no training load at all, so for one sport's ACWR / monotony / CTL call get_analytics_summary with the same sport. RIDE KINDS: `sessions_by_subtype` splits the ride count into road / indoor / gravel / mountain / track / commute / e_bike / unspecified / unknown (unknown = no stored source could say, so it may still be a commute), and `commute_and_e_bike_rides` lists which rides were commutes or e-bike rides, with the provider label and Garmin .fit sub_sport behind each.
The description explains the capability, when to use it, and what to expect.
Inputs88/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
days
Number of days to analyze (default 14, max 28). Widens `period` and the totals only - `week_over_week` stays the last 7 days vs the 7 before, and is omitted entirely below 14 days because the window cannot hold both weeks.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
sport
Narrow every training number in this response to ONE sport: run, ride, swim, strength, walk, row, other (common synonyms work - "running", "cycling", "bike", "hiking", "lifting"). Pass this whenever the athlete asked about a single sport ("what is my running ACWR", "is my cycling ramp too fast", "how many km did I run this week, not counting the hike") - ACWR, monotony, Foster strain, CTL/ATL/TSB and weekly volume are otherwise POOLED across every sport, so a week of holiday cycling reads as a running overload. Omit it for the athlete's total load. Recovery, sleep and wellness are whole-body and are never filtered.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_training_trends",
"title": "Get Training Trends",
"description": "This week vs last week, across all connected platforms: sessions, minutes and distance by type with a week-over-week comparison, ride kinds, plus HRV/RHR and sleep trend lines. get_analytics_summary already carries the same deduped volume with load, fitness and wellness, so an overview needs ONE of the two: call this one only for week-over-week or ride kinds, never both for one answer. `week_over_week` is ALWAYS the last 7 days including today against the 7 days before them, whatever `days` you asked for - it does not stretch with the window, and it carries the two date ranges (`current_week` / `previous_week`) that show exactly which days each half covers. In a window longer than 14 days the older sessions are in `total_sessions` and in NEITHER week, so the two week counts never add up to the total. Everything else (total_sessions, total_duration_minutes, total_distance_display, sessions_by_type, distance_by_type_display, avg_hr) covers the whole `period`, a different scope from `week_over_week`. Distance is per sport in `distance_by_type_display`, paired with `sessions_by_type`; `total_distance_display` is absent when two or more sports carry distance, since a run km plus a ride km is not one number. SINGLE-SPORT QUESTIONS: pass `sport` (run/ride/swim/strength/walk/row) to narrow sessions, minutes, distance and the week-over-week halves to that sport alone; recovery and sleep stay whole-body. This tool carries no training load at all, so for one sport's ACWR / monotony / CTL call get_analytics_summary with the same sport. RIDE KINDS: `sessions_by_subtype` splits the ride count into road / indoor / gravel / mountain / track / commute / e_bike / unspecified / unknown (unknown = no stored source could say, so it may still be a commute), and `commute_and_e_bike_rides` lists which rides were commutes or e-bike rides, with the provider label and Garmin .fit sub_sport behind each.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"days": {
"type": "number",
"description": "Number of days to analyze (default 14, max 28). Widens `period` and the totals only - `week_over_week` stays the last 7 days vs the 7 before, and is omitted entirely below 14 days because the window cannot hold both weeks."
},
"sport": {
"type": "string",
"description": "Narrow every training number in this response to ONE sport: run, ride, swim, strength, walk, row, other (common synonyms work - \"running\", \"cycling\", \"bike\", \"hiking\", \"lifting\"). Pass this whenever the athlete asked about a single sport (\"what is my running ACWR\", \"is my cycling ramp too fast\", \"how many km did I run this week, not counting the hike\") - ACWR, monotony, Foster strain, CTL/ATL/TSB and weekly volume are otherwise POOLED across every sport, so a week of holiday cycling reads as a running overload. Omit it for the athlete's total load. Recovery, sleep and wellness are whole-body and are never filtered."
}
}
},
"annotations": {
"title": "Get Training Trends",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description100/100
Aerobic efficiency (EF = output per heartbeat) as a WEEK-BY-WEEK time series across every connected source, deduped. Use for "is my aerobic efficiency improving?", "how is my power-to-HR ratio developing?", "did that training block work?", or any pace/power-vs-HR trend over weeks or months - it is the served, cross-source deduped answer, which NP/HR or pace/HR hand-rolled from activity lists is not. Returns per sport (ride, run): a weekly table (sessions, median/min/max EF, mean HR, mean power or pace, mean duration), an early-third vs late-third `trend` (improving / stable / declining / insufficient_data, with ef_change_pct and hr_shift_bpm), and the qualifying sessions themselves (date, source, duration, avg HR, power or pace, EF, temp_c, decoupling when a provider shipped it) for charting. Rides use NP/HR in W per bpm, runs use speed/HR in m/min per bpm - DIFFERENT units, not comparable across sports or with another platform's EF (see ef_definition). `trend.confounder_note` means the two ends of the window were ridden/run at different intensities, so the change is partly effort, not fitness, and only like-for-like sessions separate the two; `insufficient_data` means one end has fewer than 3 qualifying sessions and the weekly table is then NOT a trend. A session qualifies only with HR + power (rides) or HR + pace (runs) and >= 20 min; `excluded` counts say why others dropped, so a small sessions_used is usually a missing HR strap or power meter, not missing history. Default window is the last 12 weeks; pass weeks (4-52) and optionally end (YYYY-MM-DD) to look at a past block. Example: get_efficiency_trend({ sport: "ride", weeks: 12 }).
The description explains the capability, when to use it, and what to expect.
Inputs83/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
sport
Restrict to one sport. Default: both, each reported separately.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
weeks
Window length in weeks ending at `end` (default 12, min 4, max 52).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end
Window end YYYY-MM-DD (default today). Use to trend a past training block.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_efficiency_trend",
"title": "Get Efficiency Trend",
"description": "Aerobic efficiency (EF = output per heartbeat) as a WEEK-BY-WEEK time series across every connected source, deduped. Use for \"is my aerobic efficiency improving?\", \"how is my power-to-HR ratio developing?\", \"did that training block work?\", or any pace/power-vs-HR trend over weeks or months - it is the served, cross-source deduped answer, which NP/HR or pace/HR hand-rolled from activity lists is not. Returns per sport (ride, run): a weekly table (sessions, median/min/max EF, mean HR, mean power or pace, mean duration), an early-third vs late-third `trend` (improving / stable / declining / insufficient_data, with ef_change_pct and hr_shift_bpm), and the qualifying sessions themselves (date, source, duration, avg HR, power or pace, EF, temp_c, decoupling when a provider shipped it) for charting. Rides use NP/HR in W per bpm, runs use speed/HR in m/min per bpm - DIFFERENT units, not comparable across sports or with another platform's EF (see ef_definition). `trend.confounder_note` means the two ends of the window were ridden/run at different intensities, so the change is partly effort, not fitness, and only like-for-like sessions separate the two; `insufficient_data` means one end has fewer than 3 qualifying sessions and the weekly table is then NOT a trend. A session qualifies only with HR + power (rides) or HR + pace (runs) and >= 20 min; `excluded` counts say why others dropped, so a small sessions_used is usually a missing HR strap or power meter, not missing history. Default window is the last 12 weeks; pass weeks (4-52) and optionally end (YYYY-MM-DD) to look at a past block. Example: get_efficiency_trend({ sport: \"ride\", weeks: 12 }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end": {
"type": "string",
"description": "Window end YYYY-MM-DD (default today). Use to trend a past training block."
},
"sport": {
"enum": [
"ride",
"run"
],
"type": "string",
"description": "Restrict to one sport. Default: both, each reported separately."
},
"weeks": {
"type": "number",
"description": "Window length in weeks ending at `end` (default 12, min 4, max 52)."
}
}
},
"annotations": {
"title": "Get Efficiency Trend",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Sleep NIGHT BY NIGHT across every connected device - the first call for "how did I sleep", "last night's sleep", "my deep/REM this week", "what time did I go to bed", naps, or overnight SpO2 / HR / HRV, whichever device (Garmin, Oura, WHOOP, Apple Health, Fitbit/Pixel, Withings, Polar, COROS, Eight Sleep...) recorded the night. One compact row per night (WAKE date, newest first): `night_of`, `bedtime` / `wake_time` (athlete-local), `asleep` + `asleep_min`, `deep_min` / `light_min` / `rem_min` / `awake_min`, `in_bed_min`, `efficiency_pct`, `score` with `score_source` and the device's own `score_factors` ratings, `latency_min`, `awakenings`, `lowest_hr`, `avg_sleep_hr`, `sleep_hrv_ms`, `spo2_avg` / `spo2_min`, `respiratory_rate`, the day's `hrv_ms` / `resting_hr` with their sources, `sleep_debt_min`, `naps`, `sessions` for a night slept in several stretches, and `still_syncing` while a night is not final. The night is deduped across devices exactly as on get_daily_metrics and the dashboard; `source` names the device behind it, and `other_sources` gives each other device's own totals when several recorded the same night. A field that is absent was not recorded on that night. When the window should hold last night and does not, `staleness_note` says so and names the newest night on file. Default the last 7 nights; `days` or `start`/`end` up to 60 nights; `source` (e.g. garmin, oura, whoop) reads one device's own nights. Averages over weeks or months: get_sleep_trend. A comparison across many nights (HRV after late bedtimes vs the rest, sleep by weekday): analyze_pattern, which computes it over up to 730 days - never add up paged nights yourself. A provider's raw record (per-minute series, stage timeline, its own app fields): that provider's sleep tool. Example: get_sleep({ days: 7 }).
The description explains the capability, when to use it, and what to expect.
Inputs88/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
days
Nights ending at `end` (default 7, max 60).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start
First wake date YYYY-MM-DD (overrides days).
Clear enough to supply this argument.
end
Last wake date YYYY-MM-DD (default today, athlete-local).
Clear enough to supply this argument.
start_date
Same as `start`.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
Same as `end`.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
source
Read one device's own nights only, e.g. garmin, oura, whoop, apple_health, google_health. Default: the merged night.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_sleep",
"title": "Get Sleep",
"description": "Sleep NIGHT BY NIGHT across every connected device - the first call for \"how did I sleep\", \"last night's sleep\", \"my deep/REM this week\", \"what time did I go to bed\", naps, or overnight SpO2 / HR / HRV, whichever device (Garmin, Oura, WHOOP, Apple Health, Fitbit/Pixel, Withings, Polar, COROS, Eight Sleep...) recorded the night. One compact row per night (WAKE date, newest first): `night_of`, `bedtime` / `wake_time` (athlete-local), `asleep` + `asleep_min`, `deep_min` / `light_min` / `rem_min` / `awake_min`, `in_bed_min`, `efficiency_pct`, `score` with `score_source` and the device's own `score_factors` ratings, `latency_min`, `awakenings`, `lowest_hr`, `avg_sleep_hr`, `sleep_hrv_ms`, `spo2_avg` / `spo2_min`, `respiratory_rate`, the day's `hrv_ms` / `resting_hr` with their sources, `sleep_debt_min`, `naps`, `sessions` for a night slept in several stretches, and `still_syncing` while a night is not final. The night is deduped across devices exactly as on get_daily_metrics and the dashboard; `source` names the device behind it, and `other_sources` gives each other device's own totals when several recorded the same night. A field that is absent was not recorded on that night. When the window should hold last night and does not, `staleness_note` says so and names the newest night on file. Default the last 7 nights; `days` or `start`/`end` up to 60 nights; `source` (e.g. garmin, oura, whoop) reads one device's own nights. Averages over weeks or months: get_sleep_trend. A comparison across many nights (HRV after late bedtimes vs the rest, sleep by weekday): analyze_pattern, which computes it over up to 730 days - never add up paged nights yourself. A provider's raw record (per-minute series, stage timeline, its own app fields): that provider's sleep tool. Example: get_sleep({ days: 7 }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end": {
"type": "string",
"description": "Last wake date YYYY-MM-DD (default today, athlete-local)."
},
"days": {
"type": "number",
"description": "Nights ending at `end` (default 7, max 60)."
},
"start": {
"type": "string",
"description": "First wake date YYYY-MM-DD (overrides days)."
},
"source": {
"type": "string",
"description": "Read one device's own nights only, e.g. garmin, oura, whoop, apple_health, google_health. Default: the merged night."
},
"end_date": {
"type": "string",
"description": "Same as `end`."
},
"start_date": {
"type": "string",
"description": "Same as `start`."
}
}
},
"annotations": {
"title": "Get Sleep",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Sleep over a WINDOW, aggregated for you - the served answer for "did my sleep improve since X?", "how has my WHOOP/Oura/Garmin sleep trended over the summer?", "sleep before vs after I changed training or bedtime", or any average of sleep over weeks or months. Paging a provider's nightly list instead is costly: a raw night is ~1,000 chars (WHOOP) and the result cap is 16,000, so a month is cut to about 16 nights and a long pull to a fortnight. Reads the nights we STORE, per source, never blended (WHOOP, Oura and Garmin scores are different scales). Returns, per source: overall stats (nights, mean/median hours asleep, mean deep/REM/light/awake minutes, mean efficiency %, mean provider score) and a `buckets` table by week (or by month when the window is over 26 weeks). Pass `split_date` (YYYY-MM-DD, the first day of the "after" side) for a before/after: each side's stats plus a `change` block (asleep minutes, efficiency, score, deep, REM). READ BEFORE QUOTING: `change` is null when either side holds fewer than 14 nights - quote the two stretches and say the comparison is thin, never a delta; a side with no nights means the record does not reach that far (say where it starts), not that sleep was bad; `initial_import` means the history is still arriving, so the early end may be incomplete; `causation_note` applies - a before/after is an association, not proof the change caused it. Quote asleep time and stage minutes before any provider score. Default window is the last 84 days; max 1,100. Example: get_sleep_trend({ start: "2026-03-01", end: "2026-10-03", split_date: "2026-06-01", source: "whoop" }).
The description explains the capability, when to use it, and what to expect.
Inputs87/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start
Window start YYYY-MM-DD (default: 84 days before `end`).
Clear enough to supply this argument.
end
Window end YYYY-MM-DD (default today).
Clear enough to supply this argument.
start_date
Same as `start`.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
Same as `end`.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
days
Window length in days ending at `end`, when no start is given.
Clear enough to supply this argument.
weeks
Window length in weeks ending at `end`, when no start is given.
Clear enough to supply this argument.
split_date
First day of the "after" side, for a before/after comparison.
Clear enough to supply this argument.
source
Restrict to one source, e.g. whoop, oura, garmin, apple_health. Default: every source we hold, each reported separately.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_sleep_trend",
"title": "Get Sleep Trend",
"description": "Sleep over a WINDOW, aggregated for you - the served answer for \"did my sleep improve since X?\", \"how has my WHOOP/Oura/Garmin sleep trended over the summer?\", \"sleep before vs after I changed training or bedtime\", or any average of sleep over weeks or months. Paging a provider's nightly list instead is costly: a raw night is ~1,000 chars (WHOOP) and the result cap is 16,000, so a month is cut to about 16 nights and a long pull to a fortnight. Reads the nights we STORE, per source, never blended (WHOOP, Oura and Garmin scores are different scales). Returns, per source: overall stats (nights, mean/median hours asleep, mean deep/REM/light/awake minutes, mean efficiency %, mean provider score) and a `buckets` table by week (or by month when the window is over 26 weeks). Pass `split_date` (YYYY-MM-DD, the first day of the \"after\" side) for a before/after: each side's stats plus a `change` block (asleep minutes, efficiency, score, deep, REM). READ BEFORE QUOTING: `change` is null when either side holds fewer than 14 nights - quote the two stretches and say the comparison is thin, never a delta; a side with no nights means the record does not reach that far (say where it starts), not that sleep was bad; `initial_import` means the history is still arriving, so the early end may be incomplete; `causation_note` applies - a before/after is an association, not proof the change caused it. Quote asleep time and stage minutes before any provider score. Default window is the last 84 days; max 1,100. Example: get_sleep_trend({ start: \"2026-03-01\", end: \"2026-10-03\", split_date: \"2026-06-01\", source: \"whoop\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end": {
"type": "string",
"description": "Window end YYYY-MM-DD (default today)."
},
"days": {
"type": "number",
"description": "Window length in days ending at `end`, when no start is given."
},
"start": {
"type": "string",
"description": "Window start YYYY-MM-DD (default: 84 days before `end`)."
},
"weeks": {
"type": "number",
"description": "Window length in weeks ending at `end`, when no start is given."
},
"source": {
"type": "string",
"description": "Restrict to one source, e.g. whoop, oura, garmin, apple_health. Default: every source we hold, each reported separately."
},
"end_date": {
"type": "string",
"description": "Same as `end`."
},
"split_date": {
"type": "string",
"description": "First day of the \"after\" side, for a before/after comparison."
},
"start_date": {
"type": "string",
"description": "Same as `start`."
}
}
},
"annotations": {
"title": "Get Sleep Trend",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Which days in a window HOLD each kind of data, and whether a stretch with no activities was rest or a dark device. Use before calling a stretch a break, rest week or detraining, before "why is my HRV/sleep missing", and before reading a trend across a stretch with holes. Per metric (activities, sleep, hrv, resting_hr, readiness, weight): `days_present` / `days_absent` / `coverage_pct`, `first_date` / `last_date`, `sources` (days per provider) and `absent_ranges`. `no_activity_ranges` lists every stretch of at least `min_gap_days` days with no recorded activity, each with `days_device_reported` (days the wearable reported sleep, HRV, resting HR or readiness) and `coverage`: `observed` = worn every day and no activity recorded; `dark` = no wear data on any day, so we know NOTHING about those days and must not call them rest; `partial` = in between, say which days; `unknown` = the athlete switched off workouts from a source (`activity_sources_hidden`), so a worn day may still hold a session - never call it rest. Weight and steps do not count as wear (a scale or a phone reports while a watch is dead). `reaches_window_end` = the stretch may still be open and the newest day may not have synced. Default the last 28 days; `days` or `start`/`end` up to 366. Example: get_data_coverage({ days: 90 }).
The description explains the capability, when to use it, and what to expect.
Inputs79/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
days
Days ending at `end` (default 28, max 366).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start
First day YYYY-MM-DD (overrides days).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end
Last day YYYY-MM-DD (default today, athlete-local).
Clear enough to supply this argument.
start_date
Same as `start`.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
Same as `end`.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
min_gap_days
Shortest no-activity stretch to list (default 3).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_data_coverage",
"title": "Get Data Coverage",
"description": "Which days in a window HOLD each kind of data, and whether a stretch with no activities was rest or a dark device. Use before calling a stretch a break, rest week or detraining, before \"why is my HRV/sleep missing\", and before reading a trend across a stretch with holes. Per metric (activities, sleep, hrv, resting_hr, readiness, weight): `days_present` / `days_absent` / `coverage_pct`, `first_date` / `last_date`, `sources` (days per provider) and `absent_ranges`. `no_activity_ranges` lists every stretch of at least `min_gap_days` days with no recorded activity, each with `days_device_reported` (days the wearable reported sleep, HRV, resting HR or readiness) and `coverage`: `observed` = worn every day and no activity recorded; `dark` = no wear data on any day, so we know NOTHING about those days and must not call them rest; `partial` = in between, say which days; `unknown` = the athlete switched off workouts from a source (`activity_sources_hidden`), so a worn day may still hold a session - never call it rest. Weight and steps do not count as wear (a scale or a phone reports while a watch is dead). `reaches_window_end` = the stretch may still be open and the newest day may not have synced. Default the last 28 days; `days` or `start`/`end` up to 366. Example: get_data_coverage({ days: 90 }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end": {
"type": "string",
"description": "Last day YYYY-MM-DD (default today, athlete-local)."
},
"days": {
"type": "number",
"description": "Days ending at `end` (default 28, max 366)."
},
"start": {
"type": "string",
"description": "First day YYYY-MM-DD (overrides days)."
},
"end_date": {
"type": "string",
"description": "Same as `end`."
},
"start_date": {
"type": "string",
"description": "Same as `start`."
},
"min_gap_days": {
"type": "number",
"description": "Shortest no-activity stretch to list (default 3)."
}
}
},
"annotations": {
"title": "Get Data Coverage",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description100/100
How the athlete's endurance training time split across easy (Z1-Z2), moderate (Z3) and hard (Z4 and above), overall and week by week (Monday-anchored, matching the plan's weeks). Use for "am I training polarized?", "how much of my volume is easy?", "what was my split in that build?", or checking real training against a prescribed distribution (80/20, pyramidal). Default window 4 weeks; `weeks` (1-52) and `end` (YYYY-MM-DD) read a past block. Returns `overall` and `weekly`, each with easy_pct / moderate_pct / hard_pct (whole percents summing to 100), zone-attributed hours, and `sessions` / `eligible_sessions`. Counts ride / run / swim / row / ski sessions only, deduplicated across sources; walks, hikes and strength are excluded by design. `excluded.endurance_sessions_without_zone_data` counts sessions with no zone data, which are left out rather than scored as easy; `status: "no_data"` means nothing in the window was measurable. Summing get_activity_detail zone buckets per session does not reproduce this (no cross-source dedupe). The zones approximate a 3-zone model whose true boundaries are ventilatory thresholds. No verdict is included: the expected split depends on the athlete's methodology and phase.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
weeks
Window length in weeks ending at `end` (default 4, min 1, max 52).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end
Window end YYYY-MM-DD (default today). Use to read a past training block.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_intensity_distribution",
"title": "Get Intensity Distribution",
"description": "How the athlete's endurance training time split across easy (Z1-Z2), moderate (Z3) and hard (Z4 and above), overall and week by week (Monday-anchored, matching the plan's weeks). Use for \"am I training polarized?\", \"how much of my volume is easy?\", \"what was my split in that build?\", or checking real training against a prescribed distribution (80/20, pyramidal). Default window 4 weeks; `weeks` (1-52) and `end` (YYYY-MM-DD) read a past block. Returns `overall` and `weekly`, each with easy_pct / moderate_pct / hard_pct (whole percents summing to 100), zone-attributed hours, and `sessions` / `eligible_sessions`. Counts ride / run / swim / row / ski sessions only, deduplicated across sources; walks, hikes and strength are excluded by design. `excluded.endurance_sessions_without_zone_data` counts sessions with no zone data, which are left out rather than scored as easy; `status: \"no_data\"` means nothing in the window was measurable. Summing get_activity_detail zone buckets per session does not reproduce this (no cross-source dedupe). The zones approximate a 3-zone model whose true boundaries are ventilatory thresholds. No verdict is included: the expected split depends on the athlete's methodology and phase.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end": {
"type": "string",
"description": "Window end YYYY-MM-DD (default today). Use to read a past training block."
},
"weeks": {
"type": "number",
"description": "Window length in weeks ending at `end` (default 4, min 1, max 52)."
}
}
},
"annotations": {
"title": "Get Intensity Distribution",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Save a coaching insight, analysis result, or observation for future reference. Use this to avoid repeating analyses, track trends you've spotted, record milestones, or flag concerns. Returns the new note's `id` and `created: true`.
The description explains the capability, when to use it, and what to expect.
Inputs83/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
category
Type of note: trend_insight (pattern you spotted), milestone (PR, goal reached), concern (overtraining, injury risk, illness signal)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
content
The insight or analysis to save (be specific - include numbers, dates, and context)
Clear enough to supply this argument.
data_range
Date range this note covers, e.g. '2026-02-24 to 2026-03-02'
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Retrieve past coaching notes - your previous analyses, insights, and observations. Useful before a new analysis, to build on previous findings rather than repeat the work. Returns `notes`, newest first (each with `id`, `category`, `content`, `data_range`, `created_at`), and `count`.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
category
Filter by note type
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max notes to return (default 20, max 50)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
since_days
Only return notes from the last N days
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Remove coaching notes from coaching_note_get, for example superseded versions of a standing rule that were re-saved as v2..v6 and keep being read back next to the current one. Takes the note `id`s from coaching_note_get (up to 25). Only the athlete's own AI-coach notes can be removed; the notes stay stored and recoverable, they just stop being returned. Returns `deleted`, `deleted_ids` and `not_found` (ids that match no active note); `deleted: 0` means nothing was removed. The note holding the current version is created with coaching_note_create before the old ones are removed.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
ids
The coaching note ids to remove (from coaching_note_get)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "coaching_note_delete",
"title": "Delete Coaching Notes",
"description": "Remove coaching notes from coaching_note_get, for example superseded versions of a standing rule that were re-saved as v2..v6 and keep being read back next to the current one. Takes the note `id`s from coaching_note_get (up to 25). Only the athlete's own AI-coach notes can be removed; the notes stay stored and recoverable, they just stop being returned. Returns `deleted`, `deleted_ids` and `not_found` (ids that match no active note); `deleted: 0` means nothing was removed. The note holding the current version is created with coaching_note_create before the old ones are removed.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"ids"
],
"properties": {
"ids": {
"type": "array",
"items": {
"type": "string"
},
"maxItems": 25,
"minItems": 1,
"description": "The coaching note ids to remove (from coaching_note_get)"
}
}
},
"annotations": {
"title": "Delete Coaching Notes",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
List the documents in the athlete's Health Vault (lab results, blood panels, VO2max/lactate/spirometry test reports, DEXA scans, training plans, race reports, etc. - uploaded via chat or the dashboard). Returns metadata + a one-line title per file, NOT the content. Use read_athlete_file with a file_id to get the full extraction. RESULTS ARE NEWEST FIRST, a page at a time: a vault with more files than `limit` returns `{next_cursor, paging_note, files}`, and `next_cursor` passed back as `cursor` with the same filters returns the next older files; a bare array is the whole listing. `uploaded_at` is when the file ARRIVED, in the athlete's own wall clock to the minute (it says "UTC" when we have not resolved their timezone); `taken_at` is the date printed on the document itself. `original_name` is NOT an identifier - an automated uploader re-sends the same filename, so two rows can differ only by `uploaded_at` and `file_id`, so `uploaded_at` is what identifies which version was read.
The description explains the capability, when to use it, and what to expect.
Inputs80/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
shelf
Filter by shelf: "document" for real records (lab reports, tests, plans, medical records), "context" for chat items that are not documents (food photos, app screenshots). Omit for everything.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
since
Only files uploaded on/after this date (YYYY-MM-DD)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max files per page (default 25, max 50). More files than that come with `next_cursor`.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
cursor
Continuation token: the `next_cursor` value from this tool's previous response, passed back unchanged with the same other arguments to get the next page. Omit it for the first page.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "list_athlete_files",
"title": "List Athlete Files",
"description": "List the documents in the athlete's Health Vault (lab results, blood panels, VO2max/lactate/spirometry test reports, DEXA scans, training plans, race reports, etc. - uploaded via chat or the dashboard). Returns metadata + a one-line title per file, NOT the content. Use read_athlete_file with a file_id to get the full extraction. RESULTS ARE NEWEST FIRST, a page at a time: a vault with more files than `limit` returns `{next_cursor, paging_note, files}`, and `next_cursor` passed back as `cursor` with the same filters returns the next older files; a bare array is the whole listing. `uploaded_at` is when the file ARRIVED, in the athlete's own wall clock to the minute (it says \"UTC\" when we have not resolved their timezone); `taken_at` is the date printed on the document itself. `original_name` is NOT an identifier - an automated uploader re-sends the same filename, so two rows can differ only by `uploaded_at` and `file_id`, so `uploaded_at` is what identifies which version was read.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"kind": {
"type": "string",
"description": "Filter by kind: vo2max_test, lactate_test, spirometry, metabolic_test, blood_panel, lab_result, dexa_scan, body_composition, genetics, medical_report, medical_imaging, cardiac, physio_rehab, medication, dental, training_plan, race_report, biomechanics, nutrition, data_export, other"
},
"limit": {
"type": "number",
"description": "Max files per page (default 25, max 50). More files than that come with `next_cursor`."
},
"shelf": {
"enum": [
"document",
"context"
],
"type": "string",
"description": "Filter by shelf: \"document\" for real records (lab reports, tests, plans, medical records), \"context\" for chat items that are not documents (food photos, app screenshots). Omit for everything."
},
"since": {
"type": "string",
"description": "Only files uploaded on/after this date (YYYY-MM-DD)"
},
"cursor": {
"type": "string",
"description": "Continuation token: the `next_cursor` value from this tool's previous response, passed back unchanged with the same other arguments to get the next page. Omit it for the first page."
}
}
},
"annotations": {
"title": "List Athlete Files",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Return the EXTRACTED SUMMARY of a Health Vault document. This is a bounded model pass made over the file at upload time, NOT the file: the median vault document is ~185 KB of source behind ~1,000 characters of summary, and per-day rows, table structure and page-by-page detail are routinely not preserved (the payload states the compression ratio for the file you asked for). Use it for the values it does carry - bloodwork over time, lactate-test thresholds, the shape of a coach's plan - but this text alone is not evidence of what the document itself says, and something absent here is not necessarily absent from the document. The header names the original filename and when the file ARRIVED (athlete-local to the minute); filenames repeat, so the arrival time and the file_id are the only things that separate two uploads of the same name. For a text/CSV file, pass date_from / date_to (YYYY-MM-DD, on the file's date column) and/or contains to get that file's EXACT ROWS from the stored original instead of the summary - a logged session, a race, a week. Pass file_id from list_athlete_files.
The description explains the capability, when to use it, and what to expect.
Inputs94/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
file_id
The file id from list_athlete_files
Clear enough to supply this argument.
date_from
Text/CSV file only: YYYY-MM-DD inclusive lower bound on the file's date column. Returns exact rows, not the summary.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
date_to
Text/CSV file only: YYYY-MM-DD inclusive upper bound on the file's date column.
Clear enough to supply this argument.
contains
Text/CSV file only: case-insensitive text a row must contain (exercise name, session id).
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "read_athlete_file",
"title": "Read Athlete File",
"description": "Return the EXTRACTED SUMMARY of a Health Vault document. This is a bounded model pass made over the file at upload time, NOT the file: the median vault document is ~185 KB of source behind ~1,000 characters of summary, and per-day rows, table structure and page-by-page detail are routinely not preserved (the payload states the compression ratio for the file you asked for). Use it for the values it does carry - bloodwork over time, lactate-test thresholds, the shape of a coach's plan - but this text alone is not evidence of what the document itself says, and something absent here is not necessarily absent from the document. The header names the original filename and when the file ARRIVED (athlete-local to the minute); filenames repeat, so the arrival time and the file_id are the only things that separate two uploads of the same name. For a text/CSV file, pass date_from / date_to (YYYY-MM-DD, on the file's date column) and/or contains to get that file's EXACT ROWS from the stored original instead of the summary - a logged session, a race, a week. Pass file_id from list_athlete_files.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"file_id"
],
"properties": {
"date_to": {
"type": "string",
"description": "Text/CSV file only: YYYY-MM-DD inclusive upper bound on the file's date column."
},
"file_id": {
"type": "string",
"description": "The file id from list_athlete_files"
},
"contains": {
"type": "string",
"description": "Text/CSV file only: case-insensitive text a row must contain (exercise name, session id)."
},
"date_from": {
"type": "string",
"description": "Text/CSV file only: YYYY-MM-DD inclusive lower bound on the file's date column. Returns exact rows, not the summary."
}
}
},
"annotations": {
"title": "Read Athlete File",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
List the Google Sheets spreadsheets the user has made available to the coach. The user controls this whitelist from the dashboard - sheets not in the returned list cannot be read. Returns `files`, most recently modified first, each with the spreadsheet `id` (what the other google_sheets_* tools take), `name`, `modifiedTime`, `createdTime` and `webViewLink`.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
query
Search term to filter spreadsheets by name
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
max_results
Max spreadsheets to return (default 20)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "google_sheets_list_spreadsheets",
"title": "Google Sheets: List Spreadsheets",
"description": "List the Google Sheets spreadsheets the user has made available to the coach. The user controls this whitelist from the dashboard - sheets not in the returned list cannot be read. Returns `files`, most recently modified first, each with the spreadsheet `id` (what the other google_sheets_* tools take), `name`, `modifiedTime`, `createdTime` and `webViewLink`.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"query": {
"type": "string",
"description": "Search term to filter spreadsheets by name"
},
"max_results": {
"type": "number",
"description": "Max spreadsheets to return (default 20)"
}
}
},
"annotations": {
"title": "Google Sheets: List Spreadsheets",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
Read data from a Google Sheet. Supports A1 notation ranges (e.g. 'Sheet1!A1:D50', 'Blood Work!A:F'). Use to read food logs, lab results, blood work, or any tracked data. Returns Google's `range` and `values`: one array of formatted cell strings per row, with `trimmed: true` when max_rows cut rows off.
The description explains the capability, when to use it, and what to expect.
Inputs98/100
Input documentation gives an agent enough context to populate the arguments.
spreadsheet_id
The spreadsheet's `id` from google_sheets_list_spreadsheets (also the long id in the sheet's URL).
Clear enough to supply this argument.
range
A1 notation range (e.g. 'Sheet1!A1:D50', 'Food Log!A:F')
Clear enough to supply this argument.
max_rows
Keep only the first N rows of the range (useful for large sheets). Omitted, every row in the range is returned.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "google_sheets_read",
"title": "Google Sheets: Read",
"description": "Read data from a Google Sheet. Supports A1 notation ranges (e.g. 'Sheet1!A1:D50', 'Blood Work!A:F'). Use to read food logs, lab results, blood work, or any tracked data. Returns Google's `range` and `values`: one array of formatted cell strings per row, with `trimmed: true` when max_rows cut rows off.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"spreadsheet_id",
"range"
],
"properties": {
"range": {
"type": "string",
"description": "A1 notation range (e.g. 'Sheet1!A1:D50', 'Food Log!A:F')"
},
"max_rows": {
"type": "number",
"description": "Keep only the first N rows of the range (useful for large sheets). Omitted, every row in the range is returned."
},
"spreadsheet_id": {
"type": "string",
"description": "The spreadsheet's `id` from google_sheets_list_spreadsheets (also the long id in the sheet's URL)."
}
}
},
"annotations": {
"title": "Google Sheets: Read",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Read multiple ranges from a Google Sheet in one call. More efficient than multiple single reads. Returns `valueRanges`, one per requested range in order, each with its `range` and `values` (one array of formatted cell strings per row).
The description explains the capability, when to use it, and what to expect.
Inputs98/100
Input documentation gives an agent enough context to populate the arguments.
spreadsheet_id
The spreadsheet's `id` from google_sheets_list_spreadsheets (also the long id in the sheet's URL).
Clear enough to supply this argument.
ranges
Array of A1 notation ranges (e.g. ['Sheet1!A1:D10', 'Sheet2!A1:B5'])
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "google_sheets_read_multiple",
"title": "Google Sheets: Read Multiple Ranges",
"description": "Read multiple ranges from a Google Sheet in one call. More efficient than multiple single reads. Returns `valueRanges`, one per requested range in order, each with its `range` and `values` (one array of formatted cell strings per row).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"spreadsheet_id",
"ranges"
],
"properties": {
"ranges": {
"type": "array",
"items": {
"type": "string"
},
"description": "Array of A1 notation ranges (e.g. ['Sheet1!A1:D10', 'Sheet2!A1:B5'])"
},
"spreadsheet_id": {
"type": "string",
"description": "The spreadsheet's `id` from google_sheets_list_spreadsheets (also the long id in the sheet's URL)."
}
}
},
"annotations": {
"title": "Google Sheets: Read Multiple Ranges",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description96/100
The overview read: PMC (CTL/ATL/TSB), deduped weekly volume and load, wellness trends (HRV, RHR, sleep, weight), HR zones and peak HR in one call; get_training_trends repeats the volume and adds only week-over-week and ride kinds. A fast DB read; `data_freshness` shows its age, and past 24h the live provider tools hold newer data.
WINDOW: `days` (default 28) is a trailing window; `start`/`end` reach any date, and start = the 1st returns exactly that calendar month. `data_available_from` is the earliest stored training date. `_history_limit` marks a Garmin record starting at the connect date (Garmin backfills about 30 days), not untrained time; its upload link fills it. An oversized span is served as its most recent weeks with `_volume_coverage`; `range_totals` (hours, distance, load, sessions, per sport) always covers the whole window.
VOLUME: weekly_volume (distance_display, by_sport_distance_display, hours, load, sessions) is deduped across providers: the authoritative weekly distance, hours and load, with no row cap. Summing per-provider lists double-counts. Distance is never pooled across sports (by_sport_distance_display instead). Weeks are Monday-anchored; a row with `is_partial_week` (`days_covered`) is short by construction, not a decline.
WEIGHT: `wellness.latest_weight` is one latest reading, possibly weeks old (`latest_weight_date`). Source `whoop_profile` is a self-typed value, not a weigh-in.
PEAK HR: `hr_records.max_hr_observed` is the window's highest single-session max HR, deduped, with spikes over 65 bpm above the session average moved to `excluded_spikes`.
SPORT: without `sport`, volume, hr_zones, hr_records and load pool all sports. `sport` (run/ride/swim/strength/walk/row) filters them and adds `sport_load` (that sport's ACWR, monotony, strain and own CTL/ATL/TSB). pmc and wellness are never filtered.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
days
Trailing window size in days (default 28). Use for recent windows. For anything older than ~a year, pass start/end instead - days is a window back from today, not a way to reach old years.
Clear enough to supply this argument.
start
Start date YYYY-MM-DD (overrides days). No cap - pass a start years back (e.g. "2015-01-01") for season/multi-year/whole-history questions. See data_available_from in the response for how far back data exists.
Clear enough to supply this argument.
end
End date YYYY-MM-DD (default today).
Clear enough to supply this argument.
include
Which sections to include (default all). Pass "all", or omit, for everything.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
sport
Narrow every training number in this response to ONE sport: run, ride, swim, strength, walk, row, other (common synonyms work - "running", "cycling", "bike", "hiking", "lifting"). Pass this whenever the athlete asked about a single sport ("what is my running ACWR", "is my cycling ramp too fast", "how many km did I run this week, not counting the hike") - ACWR, monotony, Foster strain, CTL/ATL/TSB and weekly volume are otherwise POOLED across every sport, so a week of holiday cycling reads as a running overload. Omit it for the athlete's total load. Recovery, sleep and wellness are whole-body and are never filtered.
Resolve the mismatch between this description and its schema constraints.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_analytics_summary",
"title": "Get Analytics Summary",
"description": "The overview read: PMC (CTL/ATL/TSB), deduped weekly volume and load, wellness trends (HRV, RHR, sleep, weight), HR zones and peak HR in one call; get_training_trends repeats the volume and adds only week-over-week and ride kinds. A fast DB read; `data_freshness` shows its age, and past 24h the live provider tools hold newer data.\n\nWINDOW: `days` (default 28) is a trailing window; `start`/`end` reach any date, and start = the 1st returns exactly that calendar month. `data_available_from` is the earliest stored training date. `_history_limit` marks a Garmin record starting at the connect date (Garmin backfills about 30 days), not untrained time; its upload link fills it. An oversized span is served as its most recent weeks with `_volume_coverage`; `range_totals` (hours, distance, load, sessions, per sport) always covers the whole window.\n\nVOLUME: weekly_volume (distance_display, by_sport_distance_display, hours, load, sessions) is deduped across providers: the authoritative weekly distance, hours and load, with no row cap. Summing per-provider lists double-counts. Distance is never pooled across sports (by_sport_distance_display instead). Weeks are Monday-anchored; a row with `is_partial_week` (`days_covered`) is short by construction, not a decline.\n\nWEIGHT: `wellness.latest_weight` is one latest reading, possibly weeks old (`latest_weight_date`). Source `whoop_profile` is a self-typed value, not a weigh-in.\n\nPEAK HR: `hr_records.max_hr_observed` is the window's highest single-session max HR, deduped, with spikes over 65 bpm above the session average moved to `excluded_spikes`.\n\nSPORT: without `sport`, volume, hr_zones, hr_records and load pool all sports. `sport` (run/ride/swim/strength/walk/row) filters them and adds `sport_load` (that sport's ACWR, monotony, strain and own CTL/ATL/TSB). pmc and wellness are never filtered.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end": {
"type": "string",
"description": "End date YYYY-MM-DD (default today)."
},
"days": {
"type": "number",
"description": "Trailing window size in days (default 28). Use for recent windows. For anything older than ~a year, pass start/end instead - days is a window back from today, not a way to reach old years."
},
"sport": {
"type": "string",
"description": "Narrow every training number in this response to ONE sport: run, ride, swim, strength, walk, row, other (common synonyms work - \"running\", \"cycling\", \"bike\", \"hiking\", \"lifting\"). Pass this whenever the athlete asked about a single sport (\"what is my running ACWR\", \"is my cycling ramp too fast\", \"how many km did I run this week, not counting the hike\") - ACWR, monotony, Foster strain, CTL/ATL/TSB and weekly volume are otherwise POOLED across every sport, so a week of holiday cycling reads as a running overload. Omit it for the athlete's total load. Recovery, sleep and wellness are whole-body and are never filtered."
},
"start": {
"type": "string",
"description": "Start date YYYY-MM-DD (overrides days). No cap - pass a start years back (e.g. \"2015-01-01\") for season/multi-year/whole-history questions. See data_available_from in the response for how far back data exists."
},
"include": {
"type": "array",
"items": {
"enum": [
"pmc",
"volume",
"wellness",
"zones",
"all"
],
"type": "string"
},
"description": "Which sections to include (default all). Pass \"all\", or omit, for everything."
}
}
},
"annotations": {
"title": "Get Analytics Summary",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
Expand internal abbreviations and remove technical prefixes from the display title.
Description91/100
Get current PMC status - CTL (fitness), ATL (fatigue), TSB (form), trend, form interpretation. Fast DB query. MIXED BASIS: ctl/atl are TODAY's end-of-day values while tsb is START-OF-DAY form (yesterday's CTL - ATL, the TrainingPeaks / intervals.icu convention), so tsb is deliberately NOT ctl - atl. `tsb_basis` gives the dated CTL/ATL pair the form was computed from, and `_tsb_pairing_note` appears whenever the two bases actually disagree. CTL, ATL and TSB printed side by side without the start-of-day basis (or tsb_basis) read as an equation that visibly does not hold. Includes data_freshness; stale data is refreshed by syncing on the analytics dashboard. DAY IN PROGRESS: when `day_in_progress` is true the athlete has no recorded load today yet, so ctl/atl are a live reading that still counts today as a zero-load day (~2.4% of decay a later session would offset), and ramp_rate/fitness_trend are graded on the last COMPLETED day instead. The `_in_progress_note` that comes with it says the same: an unfinished day is low by construction, so today's number is not evidence that fitness is falling, that the athlete is behind, or that CTL was lost - exactly like `is_partial_week` on get_analytics_summary. `ctl_frame` is the athlete's own fitness range over the last year (low, high, the last day it was higher than today): CTL has no other scale, so answer "what does the number mean / out of what" from it.
The description explains the capability, when to use it, and what to expect.
Inputs100/100
This tool declares no inputs, so argument descriptions are not needed.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_pmc_status",
"title": "Get Fitness, Fatigue and Form",
"description": "Get current PMC status - CTL (fitness), ATL (fatigue), TSB (form), trend, form interpretation. Fast DB query. MIXED BASIS: ctl/atl are TODAY's end-of-day values while tsb is START-OF-DAY form (yesterday's CTL - ATL, the TrainingPeaks / intervals.icu convention), so tsb is deliberately NOT ctl - atl. `tsb_basis` gives the dated CTL/ATL pair the form was computed from, and `_tsb_pairing_note` appears whenever the two bases actually disagree. CTL, ATL and TSB printed side by side without the start-of-day basis (or tsb_basis) read as an equation that visibly does not hold. Includes data_freshness; stale data is refreshed by syncing on the analytics dashboard. DAY IN PROGRESS: when `day_in_progress` is true the athlete has no recorded load today yet, so ctl/atl are a live reading that still counts today as a zero-load day (~2.4% of decay a later session would offset), and ramp_rate/fitness_trend are graded on the last COMPLETED day instead. The `_in_progress_note` that comes with it says the same: an unfinished day is low by construction, so today's number is not evidence that fitness is falling, that the athlete is behind, or that CTL was lost - exactly like `is_partial_week` on get_analytics_summary. `ctl_frame` is the athlete's own fitness range over the last year (low, high, the last day it was higher than today): CTL has no other scale, so answer \"what does the number mean / out of what\" from it.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {}
},
"annotations": {
"title": "Get Fitness, Fatigue and Form",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description93/100
Daily rows (default 14 days; `start`/`end` uncapped): PMC (CTL/ATL/TSB), wellness (HRV, RHR, sleep, weight, steps), scores, load math, z-scores vs a 60-day baseline, anomalies. Today: get_readiness_today; long spans: get_analytics_summary; sleep stages, bed/wake and naps: get_sleep.
DATES: `date` is the WAKE date (`night_of` = the evening before). Rows merge all providers; a null field means no source wrote it that day.
PAGES: about 16-24 days a call, newest first; a longer window carries `window_coverage` and `next_cursor`, passed back as `cursor` (same other arguments) for the next older page. Totals, streaks and bests need every page.
SCORES: `readinessScore` is the provider's number (WHOOP Recovery %, Oura Readiness). `recoveryScore` and `readinessScoreV2` are athletedata's and will not match the wearable app. Both are clamped to 0-100: their lowest value is the clamp, not a measurement, and `score_floor_note` marks a window holding one. `readinessVerdict` bands readinessScoreV2 within-athlete (go_hard >=90, steady >=57, easy >=50, rest <50): an intensity band, not a cancellation; `rest` falls on about a quarter of days.
LOAD: `strainFoster` is a rolling 7-day Foster load, not WHOOP strain. acwr, monotony, strainFoster, dailyLoad and ctl/atl/tsb pool all sports; `load_math_caveat` flags when they misdescribe the athlete. `sport` (run/ride/swim/strength/walk/row) adds `sport_load_rows` and `sport_load`.
PROVENANCE: each metric has its own `*Source`; weightSource `whoop_profile` is self-typed, not a weigh-in.
STEPS: all-day count; today's is running, and Garmin and Oura store it after local midnight. Null = no count held. Empty by design for Suunto and Google Health. Apple Health steps merge all HealthKit sources (`steps_merge_note`).
ENERGY: `bmrKcal`, `activeKcal`, `burnKcal`: see `burn_note`. BASELINES: `baselineSpread` holds each z-score's {mean, sd, sampleSd, n}.
The description explains the capability, when to use it, and what to expect.
Inputs95/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
days
Days of history (default 14)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start
Start date YYYY-MM-DD (overrides days). No cap - pass a start months/a year back for long-term wellness/weight/HRV/RHR trend questions.
Clear enough to supply this argument.
end
End date YYYY-MM-DD (default today)
Clear enough to supply this argument.
sport
Narrow every training number in this response to ONE sport: run, ride, swim, strength, walk, row, other (common synonyms work - "running", "cycling", "bike", "hiking", "lifting"). Pass this whenever the athlete asked about a single sport ("what is my running ACWR", "is my cycling ramp too fast", "how many km did I run this week, not counting the hike") - ACWR, monotony, Foster strain, CTL/ATL/TSB and weekly volume are otherwise POOLED across every sport, so a week of holiday cycling reads as a running overload. Omit it for the athlete's total load. Recovery, sleep and wellness are whole-body and are never filtered.
Clear enough to supply this argument.
cursor
Continuation token: the `next_cursor` value from this tool's previous response, passed back unchanged with the same other arguments to get the next page. Omit it for the first page.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_daily_metrics",
"title": "Get Daily Metrics",
"description": "Daily rows (default 14 days; `start`/`end` uncapped): PMC (CTL/ATL/TSB), wellness (HRV, RHR, sleep, weight, steps), scores, load math, z-scores vs a 60-day baseline, anomalies. Today: get_readiness_today; long spans: get_analytics_summary; sleep stages, bed/wake and naps: get_sleep.\n\nDATES: `date` is the WAKE date (`night_of` = the evening before). Rows merge all providers; a null field means no source wrote it that day.\n\nPAGES: about 16-24 days a call, newest first; a longer window carries `window_coverage` and `next_cursor`, passed back as `cursor` (same other arguments) for the next older page. Totals, streaks and bests need every page.\n\nSCORES: `readinessScore` is the provider's number (WHOOP Recovery %, Oura Readiness). `recoveryScore` and `readinessScoreV2` are athletedata's and will not match the wearable app. Both are clamped to 0-100: their lowest value is the clamp, not a measurement, and `score_floor_note` marks a window holding one. `readinessVerdict` bands readinessScoreV2 within-athlete (go_hard >=90, steady >=57, easy >=50, rest <50): an intensity band, not a cancellation; `rest` falls on about a quarter of days.\n\nLOAD: `strainFoster` is a rolling 7-day Foster load, not WHOOP strain. acwr, monotony, strainFoster, dailyLoad and ctl/atl/tsb pool all sports; `load_math_caveat` flags when they misdescribe the athlete. `sport` (run/ride/swim/strength/walk/row) adds `sport_load_rows` and `sport_load`.\n\nPROVENANCE: each metric has its own `*Source`; weightSource `whoop_profile` is self-typed, not a weigh-in.\n\nSTEPS: all-day count; today's is running, and Garmin and Oura store it after local midnight. Null = no count held. Empty by design for Suunto and Google Health. Apple Health steps merge all HealthKit sources (`steps_merge_note`).\n\nENERGY: `bmrKcal`, `activeKcal`, `burnKcal`: see `burn_note`. BASELINES: `baselineSpread` holds each z-score's {mean, sd, sampleSd, n}.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end": {
"type": "string",
"description": "End date YYYY-MM-DD (default today)"
},
"days": {
"type": "number",
"description": "Days of history (default 14)"
},
"sport": {
"type": "string",
"description": "Narrow every training number in this response to ONE sport: run, ride, swim, strength, walk, row, other (common synonyms work - \"running\", \"cycling\", \"bike\", \"hiking\", \"lifting\"). Pass this whenever the athlete asked about a single sport (\"what is my running ACWR\", \"is my cycling ramp too fast\", \"how many km did I run this week, not counting the hike\") - ACWR, monotony, Foster strain, CTL/ATL/TSB and weekly volume are otherwise POOLED across every sport, so a week of holiday cycling reads as a running overload. Omit it for the athlete's total load. Recovery, sleep and wellness are whole-body and are never filtered."
},
"start": {
"type": "string",
"description": "Start date YYYY-MM-DD (overrides days). No cap - pass a start months/a year back for long-term wellness/weight/HRV/RHR trend questions."
},
"cursor": {
"type": "string",
"description": "Continuation token: the `next_cursor` value from this tool's previous response, passed back unchanged with the same other arguments to get the next page. Omit it for the first page."
}
}
},
"annotations": {
"title": "Get Daily Metrics",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Answers "should I train hard today?" from the latest computed day (the most recent row if today is not computed). readiness_score answers that question; recovery_score describes physiology alone. FLOOR: both scores are clamped to 0-100, so the lowest value is the clamp rather than a measurement, and `score_floor_note` marks it. Both are athletedata's own and differ from the WHOOP Recovery % or Oura Readiness shown in-app, which is `provider_readiness` (null for Garmin, which has no recovery score).
RETURNS: recovery_score (HRV z, RHR z, sleep, sleep debt) with recovery_components (signed points per signal); readiness_score (recovery plus TSB, ACWR, monotony); readiness_verdict, verdict_note and drivers; score_deductions; adjusted_zones (HR/power zones nudged up to 5% by HRV/RHR, for a day with no session target); todays_session_targets, which supersede adjusted_zones for a planned session.
VERDICT: bands readiness_score (go_hard >=90, steady >=57, easy >=50, rest <50), z-scored within-athlete over 60 days, so 65 is their normal. `rest` falls on about a quarter of days and calls for scaling a session rather than cancelling it. `verdict_provider_disagreement` appears when a restrictive verdict meets a good WHOOP/Oura score or good Garmin all-day signals.
FRESHNESS: `as_of` is the WAKE date of the latest row (`night_of` = the night before); an as_of before the athlete's local today means the device was off or unsynced.
BASELINE: `baseline_warmup` = that signal's device changed and its z-score is withheld while the baseline rebuilds, so a missing hrv/rhr component is not a decline. `context.hrv` (ms) and `context.resting_hr` (bpm) are that date's readings beside 60-day means and z-scores; `baseline_spread` holds each z's {mean, sd, sample_sd, n}.
PROVENANCE: `hrv_source` and `rhr_source` often name different devices; `wellness_source` is a row-level label, not any metric's source.
The description explains the capability, when to use it, and what to expect.
Inputs100/100
This tool declares no inputs, so argument descriptions are not needed.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_readiness_today",
"title": "Get Readiness Today",
"description": "Answers \"should I train hard today?\" from the latest computed day (the most recent row if today is not computed). readiness_score answers that question; recovery_score describes physiology alone. FLOOR: both scores are clamped to 0-100, so the lowest value is the clamp rather than a measurement, and `score_floor_note` marks it. Both are athletedata's own and differ from the WHOOP Recovery % or Oura Readiness shown in-app, which is `provider_readiness` (null for Garmin, which has no recovery score).\n\nRETURNS: recovery_score (HRV z, RHR z, sleep, sleep debt) with recovery_components (signed points per signal); readiness_score (recovery plus TSB, ACWR, monotony); readiness_verdict, verdict_note and drivers; score_deductions; adjusted_zones (HR/power zones nudged up to 5% by HRV/RHR, for a day with no session target); todays_session_targets, which supersede adjusted_zones for a planned session.\n\nVERDICT: bands readiness_score (go_hard >=90, steady >=57, easy >=50, rest <50), z-scored within-athlete over 60 days, so 65 is their normal. `rest` falls on about a quarter of days and calls for scaling a session rather than cancelling it. `verdict_provider_disagreement` appears when a restrictive verdict meets a good WHOOP/Oura score or good Garmin all-day signals.\n\nFRESHNESS: `as_of` is the WAKE date of the latest row (`night_of` = the night before); an as_of before the athlete's local today means the device was off or unsynced.\n\nBASELINE: `baseline_warmup` = that signal's device changed and its z-score is withheld while the baseline rebuilds, so a missing hrv/rhr component is not a decline. `context.hrv` (ms) and `context.resting_hr` (bpm) are that date's readings beside 60-day means and z-scores; `baseline_spread` holds each z's {mean, sd, sample_sd, n}.\n\nPROVENANCE: `hrv_source` and `rhr_source` often name different devices; `wellness_source` is a row-level label, not any metric's source.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {}
},
"annotations": {
"title": "Get Readiness Today",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description100/100
The athlete's blood-pressure history from a connected cuff (Withings, Garmin Index BPM, or any cuff writing to Apple Health / Health Connect, such as an Omron). Returns `readings` (measuredAt, systolic, diastolic, pulse, source, sourceType; one row per measurement even when several sources relayed it, the others in `alsoFrom`) and `by_day` (per-day min / max / mean and the day's worst category). Readings the athlete disowned are excluded. Blood pressure is measured several times a day and varies on purpose (at rest vs after a session), so the within-day range carries the signal and a daily mean supports no trend. `category` is the ACC/AHA 2017 band computed by athletedata (normal / elevated / stage 1 / stage 2 / crisis / low) and is authoritative: the thresholds are AND on the healthy side and OR on the raised side, so 118/86 is stage 1. Garmin Connect can show the ISH scale instead; `categoryNote` gives the ISH reading when the two differ. `sourceType` is 'device' or 'manual'; NULL (every Apple Health reading) means unknown. The data supports reporting numbers, changes against the athlete's own history and training context; it is not a diagnosis, a target or medication guidance, and readings at stage 2 or above are a question for the doctor managing them. Example: get_blood_pressure({ days: 90 }).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
days
Days of history (default 90)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max individual readings returned, newest first (default 200). Athletes measuring several times a day accumulate hundreds in a 90-day window.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_blood_pressure",
"title": "Get Blood Pressure",
"description": "The athlete's blood-pressure history from a connected cuff (Withings, Garmin Index BPM, or any cuff writing to Apple Health / Health Connect, such as an Omron). Returns `readings` (measuredAt, systolic, diastolic, pulse, source, sourceType; one row per measurement even when several sources relayed it, the others in `alsoFrom`) and `by_day` (per-day min / max / mean and the day's worst category). Readings the athlete disowned are excluded. Blood pressure is measured several times a day and varies on purpose (at rest vs after a session), so the within-day range carries the signal and a daily mean supports no trend. `category` is the ACC/AHA 2017 band computed by athletedata (normal / elevated / stage 1 / stage 2 / crisis / low) and is authoritative: the thresholds are AND on the healthy side and OR on the raised side, so 118/86 is stage 1. Garmin Connect can show the ISH scale instead; `categoryNote` gives the ISH reading when the two differ. `sourceType` is 'device' or 'manual'; NULL (every Apple Health reading) means unknown. The data supports reporting numbers, changes against the athlete's own history and training context; it is not a diagnosis, a target or medication guidance, and readings at stage 2 or above are a question for the doctor managing them. Example: get_blood_pressure({ days: 90 }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"days": {
"type": "number",
"description": "Days of history (default 90)"
},
"limit": {
"type": "number",
"description": "Max individual readings returned, newest first (default 200). Athletes measuring several times a day accumulate hundreds in a 90-day window."
}
}
},
"annotations": {
"title": "Get Blood Pressure",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description94/100
Training-LOAD anomaly flag (0-100), NOT a validated injury predictor. Adds points for ACWR out of the 0.8-1.3 band, CTL ramp, Foster monotony, HRV decline, and sleep (debt against need, or `sleep_drop_vs_baseline`: the last 7 nights' mean at least 10% under the athlete's own 28 nights before, one device; the larger of the two counts). ACWR's prognostic validity is contested (Impellizzeri 2020) and it inverts for sprint/jump/strength profiles, so: a lone ACWR/monotony reading with no independent corroborator (ramp/HRV/sleep) is auto-discounted and tagged `_unconfirmed`, and for neuromuscular-dominant load the ACWR/monotony cluster is withheld and tagged `_sport_unvalidated`. The `sport_caveat` field, when present, says why this athlete's load series does not support the reading - strength/power-dominant load, a series flattened by our per-session strength cap, or a POOLED MULTI-MODALITY series (a runner's holiday week of cycling and walking spikes ACWR while their running is flat), in which case the number is TOTAL load and says nothing about any one sport, so it does not support naming a sport or cutting one sport's volume. Returns the index, drivers, a disclaimer, and a 14-day trend. Use when the athlete mentions niggles or accumulating fatigue or asks 'am I overtraining?' - it is a starting point for inspecting the load picture, not a diagnosis.
The description explains the capability, when to use it, and what to expect.
Inputs100/100
This tool declares no inputs, so argument descriptions are not needed.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_load_flag",
"title": "Get Load Flag",
"description": "Training-LOAD anomaly flag (0-100), NOT a validated injury predictor. Adds points for ACWR out of the 0.8-1.3 band, CTL ramp, Foster monotony, HRV decline, and sleep (debt against need, or `sleep_drop_vs_baseline`: the last 7 nights' mean at least 10% under the athlete's own 28 nights before, one device; the larger of the two counts). ACWR's prognostic validity is contested (Impellizzeri 2020) and it inverts for sprint/jump/strength profiles, so: a lone ACWR/monotony reading with no independent corroborator (ramp/HRV/sleep) is auto-discounted and tagged `_unconfirmed`, and for neuromuscular-dominant load the ACWR/monotony cluster is withheld and tagged `_sport_unvalidated`. The `sport_caveat` field, when present, says why this athlete's load series does not support the reading - strength/power-dominant load, a series flattened by our per-session strength cap, or a POOLED MULTI-MODALITY series (a runner's holiday week of cycling and walking spikes ACWR while their running is flat), in which case the number is TOTAL load and says nothing about any one sport, so it does not support naming a sport or cutting one sport's volume. Returns the index, drivers, a disclaimer, and a 14-day trend. Use when the athlete mentions niggles or accumulating fatigue or asks 'am I overtraining?' - it is a starting point for inspecting the load picture, not a diagnosis.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {}
},
"annotations": {
"title": "Get Load Flag",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Each of the athlete's connected apps and devices (plus any removed or rejected in the last 30 days): `state` (live | auth_rejected | not_linked | removed), `since` (when that state began), `first_connected`, `last_arrival_at` (when athletedata last stored a record from it, by arrival time) with `last_arrival_days_ago`, `newest_record_date` (that newest record's own date, so an old reading delivered today reads as old), and `last_poll_at` where the app is polled. `last_arrival_at` null with `stores_records: true` means no record from it dated in the last 90 days; `stores_records: false` (calendars, Google Sheets, MyWhoosh) means it is read live or push-only and has no arrival to date. A long gap on a live connection is a date, not a diagnosis: the record does not say whether the device stopped recording or the app stopped sending. Use for 'is my X still connected', 'why is my reading missing', 'when did X drop'.
The description explains the capability, when to use it, and what to expect.
Inputs100/100
This tool declares no inputs, so argument descriptions are not needed.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_connection_status",
"title": "Get Connection Status",
"description": "Each of the athlete's connected apps and devices (plus any removed or rejected in the last 30 days): `state` (live | auth_rejected | not_linked | removed), `since` (when that state began), `first_connected`, `last_arrival_at` (when athletedata last stored a record from it, by arrival time) with `last_arrival_days_ago`, `newest_record_date` (that newest record's own date, so an old reading delivered today reads as old), and `last_poll_at` where the app is polled. `last_arrival_at` null with `stores_records: true` means no record from it dated in the last 90 days; `stores_records: false` (calendars, Google Sheets, MyWhoosh) means it is read live or push-only and has no arrival to date. A long gap on a live connection is a date, not a diagnosis: the record does not say whether the device stopped recording or the app stopped sending. Use for 'is my X still connected', 'why is my reading missing', 'when did X drop'.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {}
},
"annotations": {
"title": "Get Connection Status",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description89/100
Detected outliers in HRV, RHR, and sleep score over the last N days vs personal 60d baseline. Sorted by severity (|z-score|). Answers 'anything weird?' questions, e.g. at the start of a check-in.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
days
Lookback window (default 7, max 60)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_anomalies",
"title": "Get Anomalies",
"description": "Detected outliers in HRV, RHR, and sleep score over the last N days vs personal 60d baseline. Sorted by severity (|z-score|). Answers 'anything weird?' questions, e.g. at the start of a check-in.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"days": {
"type": "number",
"description": "Lookback window (default 7, max 60)"
}
}
},
"annotations": {
"title": "Get Anomalies",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Acute:chronic workload ratio (ACWR), Foster monotony, Foster strain, ramp rate, and sport load split (ride/run/strength/etc.) over the window. ACWR sweet spot is 0.8-1.3; monotony >2 is a warning sign. NOTE: ACWR is contested (Impellizzeri 2020), validated on ENDURANCE load and validated WITHIN a single modality. When either of those does not hold for this athlete - sprint/jump/strength-dominant load, or a pooled split where no one sport owns the series - the result carries an acwr_caveat naming the actual split, and a pooled number is not one sport's overload signal (a ride or a walk is not a run). Use when discussing training load distribution, taper, or whether the athlete is ramping safely. Returns `current` (acwr, acwr_zone, monotony, strain_foster, ramp_rate, sport_load_split), `daily_loads` (load and ACWR per day) and `data_confidence` (how much history backs each signal).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
days
Window in days (default 28, min 7, max 90)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_load_balance",
"title": "Get Load Balance",
"description": "Acute:chronic workload ratio (ACWR), Foster monotony, Foster strain, ramp rate, and sport load split (ride/run/strength/etc.) over the window. ACWR sweet spot is 0.8-1.3; monotony >2 is a warning sign. NOTE: ACWR is contested (Impellizzeri 2020), validated on ENDURANCE load and validated WITHIN a single modality. When either of those does not hold for this athlete - sprint/jump/strength-dominant load, or a pooled split where no one sport owns the series - the result carries an acwr_caveat naming the actual split, and a pooled number is not one sport's overload signal (a ride or a walk is not a run). Use when discussing training load distribution, taper, or whether the athlete is ramping safely. Returns `current` (acwr, acwr_zone, monotony, strain_foster, ramp_rate, sport_load_split), `daily_loads` (load and ACWR per day) and `data_confidence` (how much history backs each signal).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"days": {
"type": "number",
"description": "Window in days (default 28, min 7, max 90)"
}
}
},
"annotations": {
"title": "Get Load Balance",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Mean-max power curve: the best average power held for 1, 5, 10, 20, 30, 45, 60 and 90 minutes over `window_days` (30 | 90 | 365 | 9999 = all-time; default 90). Cycling by default; `sport: "run"` returns the separate running-power (Stryd) curve on its own scale, so ride FTP and run CP never read across. For run pace use get_pace_curve; for the peaks inside one session use get_activity_detail. Each point has a `basis`: `stream` is a true rolling best from the per-second stream (an effort inside a longer ride counts, and a warm-up cannot dilute it); `fallback` is a whole-session average standing in, a lower bound that does not establish FTP, threshold or a personal best. Points also carry `source_activity` (the session, its length, the athlete's RPE) and, when known, `effort` (`submaximal` = a lower bound for that duration, or `hard`); the response `note` describes the mix. Use for FTP / CP discussion, peak-fitness comparison and gaps at specific durations. Which sport a pasted table of watts belongs to is decided by matching its dates and values against each curve.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_power_curve",
"title": "Get Power Curve",
"description": "Mean-max power curve: the best average power held for 1, 5, 10, 20, 30, 45, 60 and 90 minutes over `window_days` (30 | 90 | 365 | 9999 = all-time; default 90). Cycling by default; `sport: \"run\"` returns the separate running-power (Stryd) curve on its own scale, so ride FTP and run CP never read across. For run pace use get_pace_curve; for the peaks inside one session use get_activity_detail. Each point has a `basis`: `stream` is a true rolling best from the per-second stream (an effort inside a longer ride counts, and a warm-up cannot dilute it); `fallback` is a whole-session average standing in, a lower bound that does not establish FTP, threshold or a personal best. Points also carry `source_activity` (the session, its length, the athlete's RPE) and, when known, `effort` (`submaximal` = a lower bound for that duration, or `hard`); the response `note` describes the mix. Use for FTP / CP discussion, peak-fitness comparison and gaps at specific durations. Which sport a pasted table of watts belongs to is decided by matching its dates and values against each curve.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"sport": {
"enum": [
"ride",
"run"
],
"type": "string",
"description": "ride (default) = cycling power; run = running power (Stryd / running-power watts). Separate curves."
},
"window_days": {
"type": "number",
"description": "Window: 30 | 90 | 365 | 9999 (all-time). Default 90."
}
}
},
"annotations": {
"title": "Get Power Curve",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description99/100
Mean-max pace curve for running: the best average pace (seconds per km, with a `paceDisplay` in both units) held for each duration. Use for threshold-pace discussion and finding training gaps; for the pace to expect in a race or a timed test use get_race_prediction. Each point has a `basis` (`stream` = a true rolling best from the per-second stream, so an effort inside a longer run counts; `fallback` = a whole-session average standing in, a lower bound), a `source_activity` (name, length, distance, the athlete's RPE) and, when known, `effort` (`submaximal` = a lower bound for that duration, or `hard`). The response `note` describes the mix. Conditional blocks explain themselves: `soft_threshold_warning` (the 60-min point is a session average, so the 20-min point is the threshold read), `grade_adjusted_curve` (only flat-equivalent bests could be derived for this athlete; they are not paces actually run and not a threshold basis), and `distance_records` (the fastest 1k to marathon stretches inside GPS runs we hold; these start at `first_effort_on` and are not lifetime PRs, and `coverage.warning` applies when `coverage.covers_run_history` is false).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_pace_curve",
"title": "Get Pace Curve",
"description": "Mean-max pace curve for running: the best average pace (seconds per km, with a `paceDisplay` in both units) held for each duration. Use for threshold-pace discussion and finding training gaps; for the pace to expect in a race or a timed test use get_race_prediction. Each point has a `basis` (`stream` = a true rolling best from the per-second stream, so an effort inside a longer run counts; `fallback` = a whole-session average standing in, a lower bound), a `source_activity` (name, length, distance, the athlete's RPE) and, when known, `effort` (`submaximal` = a lower bound for that duration, or `hard`). The response `note` describes the mix. Conditional blocks explain themselves: `soft_threshold_warning` (the 60-min point is a session average, so the 20-min point is the threshold read), `grade_adjusted_curve` (only flat-equivalent bests could be derived for this athlete; they are not paces actually run and not a threshold basis), and `distance_records` (the fastest 1k to marathon stretches inside GPS runs we hold; these start at `first_effort_on` and are not lifetime PRs, and `coverage.warning` applies when `coverage.covers_run_history` is false).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"window_days": {
"type": "number",
"description": "Window: 30 | 90 | 365 | 9999 (all-time). Default 90."
}
}
},
"annotations": {
"title": "Get Pace Curve",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Analytics for one session ("how was Tuesday's tempo?"). Pass `source_activity_id` or `date` (+`source`); a date returns the day's longest session, with the rest in `other_activities_on_date`. `no_data` + `source_connection` = provider disconnected; + `withheld_by_provider` = intervals.icu will never serve that Strava import; otherwise a sync delay.
Returns NP, HR/power zones (`hrZones` is athletedata's, used for grading; `watchHrZones` is what the athlete's Garmin shows), weather-adjusted values, `derived_metrics` (IF/VI/EF/decoupling), merged duplicates, and `training_load`, the TSS-equivalent behind CTL/ATL/TSB (Garmin Exercise Load, Strava Relative Effort and WHOOP strain are other scales).
`elevationGainM` null = unreported, not flat. `gapSecPerKm` is flat-equivalent pace. `avg_cadence` is total steps/min on runs, rpm on rides. `splits` are per km; "lap"/"activity" laps are device boundaries, "segment" laps are intervals.icu auto-detection that often misses reps (`lap_structure_warning`). `splitSegments` slices include warm-up; `stream_analysis.aerobic_drift` is the drift figure.
Strength: `total_volume` (kg), `total_sets`, `strength_exercises`; Garmin exercise names on a free session are wrist-detected and often wrong, weights are as recorded (`garmin_strength_accuracy_note`). Pool: `poolLengths` is one line per length. `treadmill_pace_warning`: Garmin wrist pace on a treadmill reads 20-40% off.
`mean_max_peaks`: the session's best 1-90 min power/pace/HR. `stream_analysis`: wind, climbs, critical_power, best_efforts, durability, aerobic_drift, planned_vs_done; absent = no stream held. Per-second trace: get_activity_stream; .fit extras: garmin_get_activity_file.
The description explains the capability, when to use it, and what to expect.
Inputs90/100
Input documentation gives an agent enough context to populate the arguments.
source_activity_id
Provider activity id (Strava/Garmin/Hevy/Intervals/etc.)
Clear enough to supply this argument.
source
Provider slug to disambiguate (strava|garmin|hevy|intervals|whoop|apple_health|...): lowercase with underscores, as in the `apple_health_workout:` label of a trigger line
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_activity_detail",
"title": "Get Activity Detail",
"description": "Analytics for one session (\"how was Tuesday's tempo?\"). Pass `source_activity_id` or `date` (+`source`); a date returns the day's longest session, with the rest in `other_activities_on_date`. `no_data` + `source_connection` = provider disconnected; + `withheld_by_provider` = intervals.icu will never serve that Strava import; otherwise a sync delay.\n\nReturns NP, HR/power zones (`hrZones` is athletedata's, used for grading; `watchHrZones` is what the athlete's Garmin shows), weather-adjusted values, `derived_metrics` (IF/VI/EF/decoupling), merged duplicates, and `training_load`, the TSS-equivalent behind CTL/ATL/TSB (Garmin Exercise Load, Strava Relative Effort and WHOOP strain are other scales).\n\n`elevationGainM` null = unreported, not flat. `gapSecPerKm` is flat-equivalent pace. `avg_cadence` is total steps/min on runs, rpm on rides. `splits` are per km; \"lap\"/\"activity\" laps are device boundaries, \"segment\" laps are intervals.icu auto-detection that often misses reps (`lap_structure_warning`). `splitSegments` slices include warm-up; `stream_analysis.aerobic_drift` is the drift figure.\n\nStrength: `total_volume` (kg), `total_sets`, `strength_exercises`; Garmin exercise names on a free session are wrist-detected and often wrong, weights are as recorded (`garmin_strength_accuracy_note`). Pool: `poolLengths` is one line per length. `treadmill_pace_warning`: Garmin wrist pace on a treadmill reads 20-40% off.\n\n`mean_max_peaks`: the session's best 1-90 min power/pace/HR. `stream_analysis`: wind, climbs, critical_power, best_efforts, durability, aerobic_drift, planned_vs_done; absent = no stream held. Per-second trace: get_activity_stream; .fit extras: garmin_get_activity_file.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"date": {
"type": "string",
"description": "YYYY-MM-DD if id is not known"
},
"source": {
"type": "string",
"description": "Provider slug to disambiguate (strava|garmin|hevy|intervals|whoop|apple_health|...): lowercase with underscores, as in the `apple_health_workout:` label of a trigger line"
},
"source_activity_id": {
"type": "string",
"description": "Provider activity id (Strava/Garmin/Hevy/Intervals/etc.)"
}
}
},
"annotations": {
"title": "Get Activity Detail",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Raw second-by-second trace of one session from athletedata's stream store: any provider we hold a recording from (Garmin, Strava, Wahoo, Zwift, COROS, Polar, Suunto, TrainingPeaks, intervals.icu, TrainerRoad, Rouvy, Hammerhead, Zepp), plus the heart-rate trace of Apple Health and Health Connect workouts. For the underlying signal or anything finer than get_activity_detail's laps and splits; get_activity_detail's `stream_analysis` already holds the authoritative climbs, wind, best efforts, W'balance and aerobic drift.
RESOLVING: `source_activity_id` (optionally with `source`) or `date` for the day's main session. The trace may come from another device's copy of the same session (`recorded_by`, `notes.provenance_note`).
CHANNELS: default watts, hr, cadence, velocity, altitude; `channels` adds distance, grade, temp, lrBalance, lat/lng (GPS roughly doubles the response). `channels_available` lists what this recording carries; `channels_absent` were not measured by it.
LINES: one per sample; `t=` is seconds from the first sample; units per `legend`. A missing token is a sample with no reading, not a zero.
RESOLUTION: long recordings are bucket-averaged on ingest (`stored_interval_seconds`). When a window does not fit, sampling widens (`notes.sampling_note`) so the stream always spans the whole window. `start_seconds` / `end_seconds` (plus `sample_seconds`) zoom into one segment.
STATUS: `no_stream` = no recording is held, permanently, not a sync delay; `stream_unavailable` = a transient storage read failure; `no_data` = the requested window or channels are empty in an existing recording.
The description explains the capability, when to use it, and what to expect.
Inputs96/100
Input documentation gives an agent enough context to populate the arguments.
source_activity_id
Provider activity id (from get_activity_detail, or any provider's activity list)
Clear enough to supply this argument.
source
Which provider's copy of the session to read (garmin|wahoo|zwift|coros|trainingpeaks|intervals|...), also used to resolve source_activity_id. Omitted, the session's main copy is read, switching to another stored copy when that one lacks a requested channel.
Clear enough to supply this argument.
date
YYYY-MM-DD if the id is not known - resolves the day's main session, exactly as get_activity_detail does
Clear enough to supply this argument.
channels
Comma-separated channels. Default: watts,hr,cadence,velocity,altitude. Also available: distance, grade, temp, lrBalance, and lat,lng for the GPS track (expensive).
Clear enough to supply this argument.
start_seconds
Window start, seconds elapsed from the beginning of the recording
Clear enough to supply this argument.
end_seconds
Window end, seconds elapsed from the beginning of the recording
Clear enough to supply this argument.
sample_seconds
Requested seconds between samples. Cannot go finer than stored_interval_seconds, and is widened automatically if the window would not fit.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_activity_stream",
"title": "Get Activity Stream",
"description": "Raw second-by-second trace of one session from athletedata's stream store: any provider we hold a recording from (Garmin, Strava, Wahoo, Zwift, COROS, Polar, Suunto, TrainingPeaks, intervals.icu, TrainerRoad, Rouvy, Hammerhead, Zepp), plus the heart-rate trace of Apple Health and Health Connect workouts. For the underlying signal or anything finer than get_activity_detail's laps and splits; get_activity_detail's `stream_analysis` already holds the authoritative climbs, wind, best efforts, W'balance and aerobic drift.\n\nRESOLVING: `source_activity_id` (optionally with `source`) or `date` for the day's main session. The trace may come from another device's copy of the same session (`recorded_by`, `notes.provenance_note`).\n\nCHANNELS: default watts, hr, cadence, velocity, altitude; `channels` adds distance, grade, temp, lrBalance, lat/lng (GPS roughly doubles the response). `channels_available` lists what this recording carries; `channels_absent` were not measured by it.\n\nLINES: one per sample; `t=` is seconds from the first sample; units per `legend`. A missing token is a sample with no reading, not a zero.\n\nRESOLUTION: long recordings are bucket-averaged on ingest (`stored_interval_seconds`). When a window does not fit, sampling widens (`notes.sampling_note`) so the stream always spans the whole window. `start_seconds` / `end_seconds` (plus `sample_seconds`) zoom into one segment.\n\nSTATUS: `no_stream` = no recording is held, permanently, not a sync delay; `stream_unavailable` = a transient storage read failure; `no_data` = the requested window or channels are empty in an existing recording.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"date": {
"type": "string",
"description": "YYYY-MM-DD if the id is not known - resolves the day's main session, exactly as get_activity_detail does"
},
"source": {
"type": "string",
"description": "Which provider's copy of the session to read (garmin|wahoo|zwift|coros|trainingpeaks|intervals|...), also used to resolve source_activity_id. Omitted, the session's main copy is read, switching to another stored copy when that one lacks a requested channel."
},
"channels": {
"type": "string",
"description": "Comma-separated channels. Default: watts,hr,cadence,velocity,altitude. Also available: distance, grade, temp, lrBalance, and lat,lng for the GPS track (expensive)."
},
"end_seconds": {
"type": "number",
"description": "Window end, seconds elapsed from the beginning of the recording"
},
"start_seconds": {
"type": "number",
"description": "Window start, seconds elapsed from the beginning of the recording"
},
"sample_seconds": {
"type": "number",
"description": "Requested seconds between samples. Cannot go finer than stored_interval_seconds, and is widened automatically if the window would not fit."
},
"source_activity_id": {
"type": "string",
"description": "Provider activity id (from get_activity_detail, or any provider's activity list)"
}
}
},
"annotations": {
"title": "Get Activity Stream",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description96/100
Show which activities were merged across providers (e.g. the same run captured by both Strava and Garmin). Returns canonical rows used in PMC/sport split/curves and the duplicate rows that were folded in. Use this when the user says 'I see only one run but I did two' or when explaining why aggregated metrics don't match a provider's raw count.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
days
Lookback window in days (default 14, max 90)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start
Start date YYYY-MM-DD (overrides days)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end
End date YYYY-MM-DD (default today)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_dedupe_clusters",
"title": "Get Merged Duplicate Activities",
"description": "Show which activities were merged across providers (e.g. the same run captured by both Strava and Garmin). Returns canonical rows used in PMC/sport split/curves and the duplicate rows that were folded in. Use this when the user says 'I see only one run but I did two' or when explaining why aggregated metrics don't match a provider's raw count.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end": {
"type": "string",
"description": "End date YYYY-MM-DD (default today)"
},
"days": {
"type": "number",
"description": "Lookback window in days (default 14, max 90)"
},
"start": {
"type": "string",
"description": "Start date YYYY-MM-DD (overrides days)"
}
}
},
"annotations": {
"title": "Get Merged Duplicate Activities",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Full performance analysis: FTP, Critical Power (CP), W' (anaerobic work capacity), threshold pace, race equivalents, power profile analysis (strengths/weaknesses across sprint-to-endurance spectrum rated as strong/moderate/weak with athlete archetype classification), Coggan 7-zone power zones with watt ranges, and prioritized training recommendations targeting identified limiters with specific session examples. Use when discussing fitness level, race targets, training zones, 'what should I work on?', or power profile. The `stored_*` fields carry the confirmed anchors: `ftp_watts` and `running.thresholdPaceSecPerKm` are the CURVE ESTIMATES, and either can be null while the athlete has a confirmed anchor on file, so a null estimate does not mean the anchor is missing - the confirmed value lives in `stored_ftp_watts` and in `running.storedThresholdPaceSecPerKm` / `storedSwimThresholdPaceSecPer100m` / `storedRunCpWatts`, which is what zones, TSS and every pushed workout target actually use. The same split holds for heart rate: `estimates.lthrBpm` is the raw curve row, and the LTHR zones and watch bands run on is in `running.lthr` (stored per sport, else our pooled estimate; read its note). Returns one object: `ftp_watts`, `critical_power_watts`, `w_prime_joules`, `running`, `power_profile`, `power_zones` and `training_recommendations`, with the basis or note for each estimate.
The description explains the capability, when to use it, and what to expect.
Inputs100/100
This tool declares no inputs, so argument descriptions are not needed.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_performance_estimates",
"title": "Get Performance Estimates",
"description": "Full performance analysis: FTP, Critical Power (CP), W' (anaerobic work capacity), threshold pace, race equivalents, power profile analysis (strengths/weaknesses across sprint-to-endurance spectrum rated as strong/moderate/weak with athlete archetype classification), Coggan 7-zone power zones with watt ranges, and prioritized training recommendations targeting identified limiters with specific session examples. Use when discussing fitness level, race targets, training zones, 'what should I work on?', or power profile. The `stored_*` fields carry the confirmed anchors: `ftp_watts` and `running.thresholdPaceSecPerKm` are the CURVE ESTIMATES, and either can be null while the athlete has a confirmed anchor on file, so a null estimate does not mean the anchor is missing - the confirmed value lives in `stored_ftp_watts` and in `running.storedThresholdPaceSecPerKm` / `storedSwimThresholdPaceSecPer100m` / `storedRunCpWatts`, which is what zones, TSS and every pushed workout target actually use. The same split holds for heart rate: `estimates.lthrBpm` is the raw curve row, and the LTHR zones and watch bands run on is in `running.lthr` (stored per sport, else our pooled estimate; read its note). Returns one object: `ftp_watts`, `critical_power_watts`, `w_prime_joules`, `running`, `power_profile`, `power_zones` and `training_recommendations`, with the basis or note for each estimate.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {}
},
"annotations": {
"title": "Get Performance Estimates",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
The athlete's race-time PREDICTION for a running distance, as a BAND with its anchor: low/high finish time, the pace band, what it rests on (a recent race at the distance, their stored threshold pace, or the curve estimate) and, for a marathon, whether the slow end was widened because their longest recent run is short. Use this - not get_performance_estimates' single equivalent, and never training pace x distance - whenever they ask what time they can run, whether a goal is realistic, or what to aim for. Quote the band and the anchor, never the centre alone. With no estimate on file it may still return `own_run`, a run of theirs that covered the distance: answer with that as its message says rather than declining. distance: "5k", "10k", "half", "marathon" or a number of km (e.g. 30).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
distance
The race distance: "5k", "10k", "half", "marathon", or km as a number (e.g. 30).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_race_prediction",
"title": "Get Race Prediction",
"description": "The athlete's race-time PREDICTION for a running distance, as a BAND with its anchor: low/high finish time, the pace band, what it rests on (a recent race at the distance, their stored threshold pace, or the curve estimate) and, for a marathon, whether the slow end was widened because their longest recent run is short. Use this - not get_performance_estimates' single equivalent, and never training pace x distance - whenever they ask what time they can run, whether a goal is realistic, or what to aim for. Quote the band and the anchor, never the centre alone. With no estimate on file it may still return `own_run`, a run of theirs that covered the distance: answer with that as its message says rather than declining. distance: \"5k\", \"10k\", \"half\", \"marathon\" or a number of km (e.g. 30).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"distance"
],
"properties": {
"distance": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
}
],
"description": "The race distance: \"5k\", \"10k\", \"half\", \"marathon\", or km as a number (e.g. 30)."
}
}
},
"annotations": {
"title": "Get Race Prediction",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Set the athlete's cycling FTP (functional threshold power, in watts) as a value THEY confirm. Use when the athlete tells you their real FTP - a Zwift / TrainerRoad ramp or 20-min test, a lab test, or a smart-trainer number - and our curve-derived estimate is wrong (common for athletes who only ever ride easy Zone 2, whose curve underestimates true threshold). This writes the load-bearing users.ftp_watts and marks it athlete-confirmed (source='user'), so power zones, Intensity Factor, TSS, and ERG/.zwo/Garmin workout targets all use it, and the nightly curve auto-estimate no longer overwrites it. Only for CYCLING FTP (bike watts) - a Stryd running-power / run-FTP number is on a different scale and does not belong here. Range 50-600W. Example: set_ftp({ ftp_watts: 227 }).
The description explains the capability, when to use it, and what to expect.
Inputs88/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
ftp_watts
Cycling FTP in watts (50-600). The athlete's own confirmed threshold power - e.g. their Zwift/TrainerRoad tested FTP.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
athlete_directed
True means the athlete explicitly stated this FTP value themselves (they typed the number, or a ramp/field/lab test they reported produced it); a value derived from curves or activity data is not athlete-directed. Required to lower an athlete-confirmed FTP already on file. Omitted counts as false.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "set_ftp",
"title": "Set FTP",
"description": "Set the athlete's cycling FTP (functional threshold power, in watts) as a value THEY confirm. Use when the athlete tells you their real FTP - a Zwift / TrainerRoad ramp or 20-min test, a lab test, or a smart-trainer number - and our curve-derived estimate is wrong (common for athletes who only ever ride easy Zone 2, whose curve underestimates true threshold). This writes the load-bearing users.ftp_watts and marks it athlete-confirmed (source='user'), so power zones, Intensity Factor, TSS, and ERG/.zwo/Garmin workout targets all use it, and the nightly curve auto-estimate no longer overwrites it. Only for CYCLING FTP (bike watts) - a Stryd running-power / run-FTP number is on a different scale and does not belong here. Range 50-600W. Example: set_ftp({ ftp_watts: 227 }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"ftp_watts"
],
"properties": {
"ftp_watts": {
"type": "number",
"description": "Cycling FTP in watts (50-600). The athlete's own confirmed threshold power - e.g. their Zwift/TrainerRoad tested FTP."
},
"athlete_directed": {
"type": "boolean",
"description": "True means the athlete explicitly stated this FTP value themselves (they typed the number, or a ramp/field/lab test they reported produced it); a value derived from curves or activity data is not athlete-directed. Required to lower an athlete-confirmed FTP already on file. Omitted counts as false."
}
}
},
"annotations": {
"title": "Set FTP",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description90/100
Set the athlete's own per-sport threshold anchors: run threshold pace, swim threshold pace (CSS), running LTHR, cycling LTHR, run critical power (running FTP) and tested max HR. Pace and heart-rate workout targets (TrainingPeaks structured steps, watch pushes, zones) are percentages of these, so a pace-targeted run cannot be written as structured steps until a run threshold pace is on file here or in TrainingPeaks. Every column is sport-specific: running LTHR typically sits 5-10 bpm above cycling LTHR, and run CP is a different scale from cycling FTP. Cycling FTP is set_ftp. Omit a field to leave it unchanged, pass null to clear it. Values are checked against the athlete's own recorded efforts; a value far slower than what they have already held is refused unless athlete_directed is true. Bounds: run pace 150-600 sec/km, swim CSS 50-360 sec/100m, running LTHR 100-220 bpm, cycling LTHR 80-220 bpm, run CP 50-700 W, max HR 120-230 bpm. Example: set_thresholds({ threshold_pace_sec_per_km: 270 }) for 4:30/km.
The description explains the capability, when to use it, and what to expect.
Inputs98/100
Input documentation gives an agent enough context to populate the arguments.
threshold_pace_sec_per_km
RUNNING threshold pace in seconds per km (270 = 4:30/km): roughly the pace held for an all-out hour, or the average of a solo 30-min time trial.
Clear enough to supply this argument.
swim_threshold_pace_sec_per_100m
SWIM threshold pace / Critical Swim Speed in seconds per 100m (90 = 1:30/100m). From a 400+200 test: (T400 - T200) / 2.
Clear enough to supply this argument.
lthr_bpm_run
RUNNING lactate threshold heart rate in bpm (average HR of the final 20 min of a 30-min run time trial).
Clear enough to supply this argument.
lthr_bpm
CYCLING lactate threshold heart rate in bpm. Running LTHR goes in lthr_bpm_run.
Clear enough to supply this argument.
run_cp_watts
Run critical power / running FTP in watts, from any run-power source (Stryd or other footpod, a Garmin/COROS watch's running power). Not cycling FTP. From a two-effort test (Stryd 9/3 or 3/12), by the two-parameter model on the two efforts' AVERAGE power: CP = (P_long × t_long − P_short × t_short) / (t_long − t_short), W' = (P_short − P_long) × t_short × t_long / (t_long − t_short). No valid CP when the shorter effort's average power is not above the longer one's (the test failed), or when the fit lands below half the longer effort's power (a bad power reading) - retest, never derive. A rep average is never the CP.
Clear enough to supply this argument.
max_hr
The athlete's tested or known maximum heart rate in bpm. Needed for %max-HR targets, and it lifts the plausibility ceiling for an LTHR above their recent training peak.
Clear enough to supply this argument.
athlete_directed
True when the athlete stated these values themselves or they come from a test the athlete reported. Leave unset for a value derived from activity data.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "set_thresholds",
"title": "Set Thresholds",
"description": "Set the athlete's own per-sport threshold anchors: run threshold pace, swim threshold pace (CSS), running LTHR, cycling LTHR, run critical power (running FTP) and tested max HR. Pace and heart-rate workout targets (TrainingPeaks structured steps, watch pushes, zones) are percentages of these, so a pace-targeted run cannot be written as structured steps until a run threshold pace is on file here or in TrainingPeaks. Every column is sport-specific: running LTHR typically sits 5-10 bpm above cycling LTHR, and run CP is a different scale from cycling FTP. Cycling FTP is set_ftp. Omit a field to leave it unchanged, pass null to clear it. Values are checked against the athlete's own recorded efforts; a value far slower than what they have already held is refused unless athlete_directed is true. Bounds: run pace 150-600 sec/km, swim CSS 50-360 sec/100m, running LTHR 100-220 bpm, cycling LTHR 80-220 bpm, run CP 50-700 W, max HR 120-230 bpm. Example: set_thresholds({ threshold_pace_sec_per_km: 270 }) for 4:30/km.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"max_hr": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "The athlete's tested or known maximum heart rate in bpm. Needed for %max-HR targets, and it lifts the plausibility ceiling for an LTHR above their recent training peak."
},
"lthr_bpm": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "CYCLING lactate threshold heart rate in bpm. Running LTHR goes in lthr_bpm_run."
},
"lthr_bpm_run": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "RUNNING lactate threshold heart rate in bpm (average HR of the final 20 min of a 30-min run time trial)."
},
"run_cp_watts": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Run critical power / running FTP in watts, from any run-power source (Stryd or other footpod, a Garmin/COROS watch's running power). Not cycling FTP. From a two-effort test (Stryd 9/3 or 3/12), by the two-parameter model on the two efforts' AVERAGE power: CP = (P_long × t_long − P_short × t_short) / (t_long − t_short), W' = (P_short − P_long) × t_short × t_long / (t_long − t_short). No valid CP when the shorter effort's average power is not above the longer one's (the test failed), or when the fit lands below half the longer effort's power (a bad power reading) - retest, never derive. A rep average is never the CP."
},
"athlete_directed": {
"type": "boolean",
"description": "True when the athlete stated these values themselves or they come from a test the athlete reported. Leave unset for a value derived from activity data."
},
"threshold_pace_sec_per_km": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "RUNNING threshold pace in seconds per km (270 = 4:30/km): roughly the pace held for an all-out hour, or the average of a solo 30-min time trial."
},
"swim_threshold_pace_sec_per_100m": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "SWIM threshold pace / Critical Swim Speed in seconds per 100m (90 = 1:30/100m). From a 400+200 test: (T400 - T200) / 2."
}
}
},
"annotations": {
"title": "Set Thresholds",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Sets the athlete's measurement preferences, saved on the account, so pushed workouts and coach output use their units. `system` ('metric' | 'imperial'): km, kg and °C or mi, lb and °F in workout descriptions and coach output; under imperial, runs push as min/mi. `pool_length` (e.g. '25yd', '25m', '33m', '50m'): the real swim pool, so the watch counts laps in that pool and swim text reads in yards and /100y or metres and /100m. A changed pool also re-sends the upcoming swims already on the watch in the same call; a pool change from a later date goes through set_watch_push_preferences(pool_length, pool_length_from). `weight_unit` ('kg' | 'lb' | 'follow_system'): the unit for weights alone (strength loads, calendar and watch text, body weight), so an athlete who runs in km and lifts in pounds is system 'metric' with weight_unit 'lb'. Every future push_workout_to_watch and all coach output use these settings; there is no per-workout unit argument. A US swimmer whose swims arrive in metres needs pool_length '25yd' (usually with system 'imperial'). At least one field is required. The on-watch pace alert is sent in SI and shown in the watch's own unit setting, and strength-set weights are always sent to Garmin in kg and shown in the athlete's Garmin Connect unit setting (8 kg shows as about 17.5 lb on imperial), so those two follow the Garmin settings, not this tool. Returns `ok`, the saved `unit_system`, `weight_unit` and `swim_pool`, `applied` (what changed, including upcoming workouts re-sent to the watch) and a `note`. Example: set_units({ system: 'imperial', pool_length: '25yd' }).
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
system
Display unit system. imperial → mi / lb / °F and runs push as min/mi; metric → km / kg / °C.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
pool_length
Real swim pool length, e.g. '25yd', '25m', '33m', '50m' (10-100 in the unit). Pass '' / 'clear' / 'metric' to clear it (swims fall back to metres).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
weight_unit
Unit for WEIGHTS only (strength loads on workout pictures, calendar and watch text, body weight), when it differs from `system`. weight_unit 'lb' with system 'metric' is the athlete who runs in km and lifts in pounds. 'follow_system' clears it.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "set_units",
"title": "Set Units",
"description": "Sets the athlete's measurement preferences, saved on the account, so pushed workouts and coach output use their units. `system` ('metric' | 'imperial'): km, kg and °C or mi, lb and °F in workout descriptions and coach output; under imperial, runs push as min/mi. `pool_length` (e.g. '25yd', '25m', '33m', '50m'): the real swim pool, so the watch counts laps in that pool and swim text reads in yards and /100y or metres and /100m. A changed pool also re-sends the upcoming swims already on the watch in the same call; a pool change from a later date goes through set_watch_push_preferences(pool_length, pool_length_from). `weight_unit` ('kg' | 'lb' | 'follow_system'): the unit for weights alone (strength loads, calendar and watch text, body weight), so an athlete who runs in km and lifts in pounds is system 'metric' with weight_unit 'lb'. Every future push_workout_to_watch and all coach output use these settings; there is no per-workout unit argument. A US swimmer whose swims arrive in metres needs pool_length '25yd' (usually with system 'imperial'). At least one field is required. The on-watch pace alert is sent in SI and shown in the watch's own unit setting, and strength-set weights are always sent to Garmin in kg and shown in the athlete's Garmin Connect unit setting (8 kg shows as about 17.5 lb on imperial), so those two follow the Garmin settings, not this tool. Returns `ok`, the saved `unit_system`, `weight_unit` and `swim_pool`, `applied` (what changed, including upcoming workouts re-sent to the watch) and a `note`. Example: set_units({ system: 'imperial', pool_length: '25yd' }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"system": {
"enum": [
"metric",
"imperial"
],
"type": "string",
"description": "Display unit system. imperial → mi / lb / °F and runs push as min/mi; metric → km / kg / °C."
},
"pool_length": {
"type": "string",
"description": "Real swim pool length, e.g. '25yd', '25m', '33m', '50m' (10-100 in the unit). Pass '' / 'clear' / 'metric' to clear it (swims fall back to metres)."
},
"weight_unit": {
"enum": [
"kg",
"lb",
"follow_system"
],
"type": "string",
"description": "Unit for WEIGHTS only (strength loads on workout pictures, calendar and watch text, body weight), when it differs from `system`. weight_unit 'lb' with system 'metric' is the athlete who runs in km and lifts in pounds. 'follow_system' clears it."
}
}
},
"annotations": {
"title": "Set Units",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description95/100
Mine the athlete's historical data for dose-response patterns: when stimulus X happened, how did response metric Y change vs their 14-day baseline over the next 1-2 days? Bins events by dose magnitude (low/med/high tertiles) and reports median response delta + sample size per bin. Use this to find an athlete's personal sweet spot and tolerance for a given training stimulus.
Stimulus types:
- high_intensity_session: single sessions with IF >= 0.85 (or top-quartile training load if no IF). Response window: day +1.
- long_endurance: sessions >= 90 min duration with IF < 0.80. Response window: day +1.
- strength: strength/weight training sessions. Response window: day +1.
- weekly_volume: total weekly training load (one event per week). Response window: days +1 and +2 after week end.
- back_to_back_hard: two consecutive days each with daily load >= 60 TSS. Response window: days +1 and +2 after the second day.
Response metrics (each requires data from a connected source):
- hrv (RMSSD, ms) - higher is better
- rhr (resting HR, bpm) - lower is better (sign auto-normalized)
- sleep_score (0-100) - higher is better
- readiness (Oura/WHOOP recovery score) - higher is better
- tsb (form) - higher is better
This is observational n=1 analysis: confounders are NOT controlled, so a pattern is a discussion starter, not a physiological law. Every bin carries its sample size, and a bin with n<5 is too thin to support a conclusion. status=insufficient_data means no result was computed; its reason field names what is missing.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
stimulus_type
Which training stimulus to analyze
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
response_metric
Which recovery/wellness metric to measure response in
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
lookback_days
Days of history to analyze (default 120, min 30, max 365). Longer = more events but older data may be less relevant.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "analyze_dose_response",
"title": "Analyze Dose-Response",
"description": "Mine the athlete's historical data for dose-response patterns: when stimulus X happened, how did response metric Y change vs their 14-day baseline over the next 1-2 days? Bins events by dose magnitude (low/med/high tertiles) and reports median response delta + sample size per bin. Use this to find an athlete's personal sweet spot and tolerance for a given training stimulus.\n\nStimulus types:\n- high_intensity_session: single sessions with IF >= 0.85 (or top-quartile training load if no IF). Response window: day +1.\n- long_endurance: sessions >= 90 min duration with IF < 0.80. Response window: day +1.\n- strength: strength/weight training sessions. Response window: day +1.\n- weekly_volume: total weekly training load (one event per week). Response window: days +1 and +2 after week end.\n- back_to_back_hard: two consecutive days each with daily load >= 60 TSS. Response window: days +1 and +2 after the second day.\n\nResponse metrics (each requires data from a connected source):\n- hrv (RMSSD, ms) - higher is better\n- rhr (resting HR, bpm) - lower is better (sign auto-normalized)\n- sleep_score (0-100) - higher is better\n- readiness (Oura/WHOOP recovery score) - higher is better\n- tsb (form) - higher is better\n\nThis is observational n=1 analysis: confounders are NOT controlled, so a pattern is a discussion starter, not a physiological law. Every bin carries its sample size, and a bin with n<5 is too thin to support a conclusion. status=insufficient_data means no result was computed; its reason field names what is missing.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"stimulus_type",
"response_metric"
],
"properties": {
"lookback_days": {
"type": "number",
"description": "Days of history to analyze (default 120, min 30, max 365). Longer = more events but older data may be less relevant."
},
"stimulus_type": {
"enum": [
"high_intensity_session",
"long_endurance",
"strength",
"weekly_volume",
"back_to_back_hard"
],
"type": "string",
"description": "Which training stimulus to analyze"
},
"response_metric": {
"enum": [
"hrv",
"rhr",
"sleep_score",
"readiness",
"tsb"
],
"type": "string",
"description": "Which recovery/wellness metric to measure response in"
}
}
},
"annotations": {
"title": "Analyze Dose-Response",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description92/100
Computes one comparison or relationship over the athlete's own history on the server and returns the numbers: group sizes, means, the cutoff used, n and r. Answers questions like "do I sleep worse after hard days", "HRV after a late bedtime", "does short sleep lower my HRV", "which weekday do I sleep worst" or "my rolling FTP over the year", which paging sleep or session rows by hand cannot (result caps cut them). Reads the same deduped rows as get_analytics_summary, over up to 730 days.
Views (view):
- compare: y on the days in group (read lag_days after them) vs every other day. group is a session filter {sport?, min_minutes?, max_minutes?}, {kind:"full_moon"} or {kind:"late_bedtime", minutes_late} (nights that began that many minutes after the athlete's median bedtime). Returns in_group / others {n, mean}, diff and diff_pct_of_others, plus the median and cutoff as clock times for a late-bedtime group. Example: {view:"compare", days:730, y:{field:"hrv"}, group:{kind:"late_bedtime", minutes_late:75}}.
- scatter: y against x, one point per day, y read lag_days after x (n, r, slope). Example: {view:"scatter", x:{field:"sleep_hours"}, y:{field:"hrv"}, lag_days:1}.
- weekday: y per weekday with n. weekly: y per week (weekly_agg sum|mean|max|min). trend: y over time (first, last, mean, min, max).
series_covers gives the dates the compared series has values on: one provider's history can start later than another's (Garmin HRV about a year back while its sleep reaches years), so a comparison covers only the days both exist. enough=false means a side is too thin to support a difference. Every result is an association within one athlete's days, not proof of cause.
The description explains the capability, when to use it, and what to expect.
Inputs82/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
view
Which analysis to run.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
days
Window length in days ending today (14-730, default 180).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
y › field
A daily field (hrv, resting_hr, sleep_hours, sleep_score, readiness, weight, body_fat, steps, daily_load, ctl, atl, tsb, bedtime), a per-session field (duration_min, distance, pace, avg_hr, avg_power, normalized_power, load, elevation, efficiency, climb_rate) or an estimate (ftp, critical_power, threshold_pace, lthr, vo2max).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
x › min_minutes
Per-session fields: only sessions at least this many minutes long.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
x › max_minutes
Per-session fields: only sessions at most this many minutes long.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
x › per_day
Per-session fields: how a day's sessions combine.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
lag_days
scatter / compare: read y this many days (0-7) after x or after the group day. Default 0; a night is filed under the morning it ended on, so HRV after a late bedtime is lag 0.
Clear enough to supply this argument.
weekly_agg
weekly only: how a week's values combine.
Clear enough to supply this argument.
group › kind
Which days form the group: days with a matching session (session, the default), full-moon days, or nights after a late bedtime.
Clear enough to supply this argument.
group › sport
session group: only sessions of this sport (run, ride, swim, strength, walk, row, other).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
group › min_minutes
session group: only sessions at least this many minutes long.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
group › max_minutes
session group: only sessions at most this many minutes long.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
group › minutes_late
late_bedtime: minutes after the athlete's own median bedtime (30-240, default 75).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "analyze_pattern",
"title": "Analyze Pattern",
"description": "Computes one comparison or relationship over the athlete's own history on the server and returns the numbers: group sizes, means, the cutoff used, n and r. Answers questions like \"do I sleep worse after hard days\", \"HRV after a late bedtime\", \"does short sleep lower my HRV\", \"which weekday do I sleep worst\" or \"my rolling FTP over the year\", which paging sleep or session rows by hand cannot (result caps cut them). Reads the same deduped rows as get_analytics_summary, over up to 730 days.\n\nViews (view):\n- compare: y on the days in group (read lag_days after them) vs every other day. group is a session filter {sport?, min_minutes?, max_minutes?}, {kind:\"full_moon\"} or {kind:\"late_bedtime\", minutes_late} (nights that began that many minutes after the athlete's median bedtime). Returns in_group / others {n, mean}, diff and diff_pct_of_others, plus the median and cutoff as clock times for a late-bedtime group. Example: {view:\"compare\", days:730, y:{field:\"hrv\"}, group:{kind:\"late_bedtime\", minutes_late:75}}.\n- scatter: y against x, one point per day, y read lag_days after x (n, r, slope). Example: {view:\"scatter\", x:{field:\"sleep_hours\"}, y:{field:\"hrv\"}, lag_days:1}.\n- weekday: y per weekday with n. weekly: y per week (weekly_agg sum|mean|max|min). trend: y over time (first, last, mean, min, max).\n\nseries_covers gives the dates the compared series has values on: one provider's history can start later than another's (Garmin HRV about a year back while its sleep reaches years), so a comparison covers only the days both exist. enough=false means a side is too thin to support a difference. Every result is an association within one athlete's days, not proof of cause.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"view",
"y"
],
"properties": {
"x": {
"type": "object",
"required": [
"field"
],
"properties": {
"field": {
"type": "string",
"description": "A daily field (hrv, resting_hr, sleep_hours, sleep_score, readiness, weight, body_fat, steps, daily_load, ctl, atl, tsb, bedtime), a per-session field (duration_min, distance, pace, avg_hr, avg_power, normalized_power, load, elevation, efficiency, climb_rate) or an estimate (ftp, critical_power, threshold_pace, lthr, vo2max)."
},
"sport": {
"type": "string",
"description": "Per-session fields: run, ride, swim, strength, walk, row, other."
},
"per_day": {
"enum": [
"mean",
"max",
"min",
"sum"
],
"type": "string",
"description": "Per-session fields: how a day's sessions combine."
},
"max_minutes": {
"type": "number",
"description": "Per-session fields: only sessions at most this many minutes long."
},
"min_minutes": {
"type": "number",
"description": "Per-session fields: only sessions at least this many minutes long."
}
},
"description": "scatter only: the other axis.",
"additionalProperties": false
},
"y": {
"type": "object",
"required": [
"field"
],
"properties": {
"field": {
"type": "string",
"description": "A daily field (hrv, resting_hr, sleep_hours, sleep_score, readiness, weight, body_fat, steps, daily_load, ctl, atl, tsb, bedtime), a per-session field (duration_min, distance, pace, avg_hr, avg_power, normalized_power, load, elevation, efficiency, climb_rate) or an estimate (ftp, critical_power, threshold_pace, lthr, vo2max)."
},
"sport": {
"type": "string",
"description": "Per-session fields: run, ride, swim, strength, walk, row, other."
},
"per_day": {
"enum": [
"mean",
"max",
"min",
"sum"
],
"type": "string",
"description": "Per-session fields: how a day's sessions combine."
},
"max_minutes": {
"type": "number",
"description": "Per-session fields: only sessions at most this many minutes long."
},
"min_minutes": {
"type": "number",
"description": "Per-session fields: only sessions at least this many minutes long."
}
},
"description": "The series compared or plotted.",
"additionalProperties": false
},
"days": {
"type": "number",
"description": "Window length in days ending today (14-730, default 180)."
},
"view": {
"enum": [
"trend",
"weekly",
"weekday",
"scatter",
"compare"
],
"type": "string",
"description": "Which analysis to run."
},
"group": {
"type": "object",
"properties": {
"kind": {
"enum": [
"session",
"full_moon",
"late_bedtime"
],
"type": "string",
"description": "Which days form the group: days with a matching session (session, the default), full-moon days, or nights after a late bedtime."
},
"sport": {
"type": "string",
"description": "session group: only sessions of this sport (run, ride, swim, strength, walk, row, other)."
},
"max_minutes": {
"type": "number",
"description": "session group: only sessions at most this many minutes long."
},
"min_minutes": {
"type": "number",
"description": "session group: only sessions at least this many minutes long."
},
"minutes_late": {
"type": "number",
"description": "late_bedtime: minutes after the athlete's own median bedtime (30-240, default 75)."
}
},
"description": "compare only: the days on one side.",
"additionalProperties": false
},
"lag_days": {
"type": "number",
"description": "scatter / compare: read y this many days (0-7) after x or after the group day. Default 0; a night is filed under the morning it ended on, so HRV after a late bedtime is lag 0."
},
"weekly_agg": {
"enum": [
"mean",
"max",
"min",
"sum"
],
"type": "string",
"description": "weekly only: how a week's values combine."
}
}
},
"annotations": {
"title": "Analyze Pattern",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description96/100
Reality-checks a numeric performance goal with a timeframe ("250W to 280W FTP in 8 weeks", "sub-3 marathon by October", "bench 100kg by summer") against the athlete's current fitness and training volume. Inputs: goal_metric, current_value, target_value, timeframe_weeks, and optional proposed_weekly_hours for the higher-volume scenario. Returns their PMC status, 28-day training trends, `adaptation_rate_reference` (typical realistic improvement rates for a trained non-elite athlete across FTP, run pace / VO2max, 5K, marathon, strength, body composition and volume ramps, with the safety floors and caveats), and the frame for two scenarios: (A) current weekly hours and (B) the proposed volume. This grounds the volume and fitness half of the answer from their real data. current_value is the caller's own string, echoed back unverified; `current_value_check`, when present, is the measured evidence that confirms or contradicts it.
The description explains the capability, when to use it, and what to expect.
Inputs97/100
Input documentation gives an agent enough context to populate the arguments.
goal_metric
What they want to improve, e.g. 'FTP', '5K time', 'marathon time', 'bench press 1RM', 'body weight'
Clear enough to supply this argument.
current_value
Current value with unit, e.g. '250W', '22:30', '3:45:00', '85kg'. NOT VERIFIED - echoed back exactly as written. A valid value is a number the athlete stated or one of their stored anchors; a shorter session's average pace is not a longer race's value (a 4km easy run at 8:44/km is not a 10K pace). With neither available, the current value is unknown.
Clear enough to supply this argument.
target_value
Target value with unit, e.g. '280W', '20:00', 'sub-3:30', '100kg'
Clear enough to supply this argument.
timeframe_weeks
Weeks until they want to hit the target
Clear enough to supply this argument.
proposed_weekly_hours
Optional alternative weekly hours for scenario B (if not provided, scenario B is current + ~30%)
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "reality_check_goal",
"title": "Reality-Check a Goal",
"description": "Reality-checks a numeric performance goal with a timeframe (\"250W to 280W FTP in 8 weeks\", \"sub-3 marathon by October\", \"bench 100kg by summer\") against the athlete's current fitness and training volume. Inputs: goal_metric, current_value, target_value, timeframe_weeks, and optional proposed_weekly_hours for the higher-volume scenario. Returns their PMC status, 28-day training trends, `adaptation_rate_reference` (typical realistic improvement rates for a trained non-elite athlete across FTP, run pace / VO2max, 5K, marathon, strength, body composition and volume ramps, with the safety floors and caveats), and the frame for two scenarios: (A) current weekly hours and (B) the proposed volume. This grounds the volume and fitness half of the answer from their real data. current_value is the caller's own string, echoed back unverified; `current_value_check`, when present, is the measured evidence that confirms or contradicts it.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"goal_metric",
"current_value",
"target_value",
"timeframe_weeks"
],
"properties": {
"goal_metric": {
"type": "string",
"description": "What they want to improve, e.g. 'FTP', '5K time', 'marathon time', 'bench press 1RM', 'body weight'"
},
"target_value": {
"type": "string",
"description": "Target value with unit, e.g. '280W', '20:00', 'sub-3:30', '100kg'"
},
"current_value": {
"type": "string",
"description": "Current value with unit, e.g. '250W', '22:30', '3:45:00', '85kg'. NOT VERIFIED - echoed back exactly as written. A valid value is a number the athlete stated or one of their stored anchors; a shorter session's average pace is not a longer race's value (a 4km easy run at 8:44/km is not a 10K pace). With neither available, the current value is unknown."
},
"timeframe_weeks": {
"type": "number",
"description": "Weeks until they want to hit the target"
},
"proposed_weekly_hours": {
"type": "number",
"description": "Optional alternative weekly hours for scenario B (if not provided, scenario B is current + ~30%)"
}
}
},
"annotations": {
"title": "Reality-Check a Goal",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description96/100
Current conditions plus hourly and daily weather forecast for one location. Use it for dressing for an upcoming workout, adjusting hydration or pacing for heat or cold, or planning around rain. Inputs: either latitude and longitude in decimal degrees, or location as a place name (e.g. 'Boulder, CO' or 'Munich, Germany'), which is geocoded. Any place name geocodes to a plausible forecast with no error, so a guessed place returns confident weather for the wrong city. The athlete's stored home city can differ from where they are now (for example while traveling), and when no location is known, the athlete is the only source for it. Units default to imperial (Fahrenheit, mph, inches); pass units: 'metric' for Celsius, km/h and mm. When available, air_quality carries the US AQI (current, plus each day's daylight maximum with its EPA category). Returns the resolved `location` and `coordinates`, the `units`, `current`, `daily` and (unless include_hourly is false) `hourly`, plus `air_quality` as above.
The description explains the capability, when to use it, and what to expect.
Inputs83/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
latitude
Latitude in decimal degrees
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
longitude
Longitude in decimal degrees
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
location
Place name to geocode (e.g. 'Boulder, CO' or 'Munich, Germany'). Used if latitude/longitude not provided.
Clear enough to supply this argument.
units
imperial (default) or metric
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
forecast_days
Days of forecast, 1-16 (default 3)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
include_hourly
Include hour-by-hour forecast (default true). Set false for token savings if only daily summary is needed.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_weather_forecast",
"title": "Get Weather Forecast",
"description": "Current conditions plus hourly and daily weather forecast for one location. Use it for dressing for an upcoming workout, adjusting hydration or pacing for heat or cold, or planning around rain. Inputs: either latitude and longitude in decimal degrees, or location as a place name (e.g. 'Boulder, CO' or 'Munich, Germany'), which is geocoded. Any place name geocodes to a plausible forecast with no error, so a guessed place returns confident weather for the wrong city. The athlete's stored home city can differ from where they are now (for example while traveling), and when no location is known, the athlete is the only source for it. Units default to imperial (Fahrenheit, mph, inches); pass units: 'metric' for Celsius, km/h and mm. When available, air_quality carries the US AQI (current, plus each day's daylight maximum with its EPA category). Returns the resolved `location` and `coordinates`, the `units`, `current`, `daily` and (unless include_hourly is false) `hourly`, plus `air_quality` as above.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"units": {
"enum": [
"imperial",
"metric"
],
"type": "string",
"description": "imperial (default) or metric"
},
"latitude": {
"type": "number",
"description": "Latitude in decimal degrees"
},
"location": {
"type": "string",
"description": "Place name to geocode (e.g. 'Boulder, CO' or 'Munich, Germany'). Used if latitude/longitude not provided."
},
"longitude": {
"type": "number",
"description": "Longitude in decimal degrees"
},
"forecast_days": {
"type": "number",
"description": "Days of forecast, 1-16 (default 3)"
},
"include_hourly": {
"type": "boolean",
"description": "Include hour-by-hour forecast (default true). Set false for token savings if only daily summary is needed."
}
}
},
"annotations": {
"title": "Get Weather Forecast",
"readOnlyHint": true,
"openWorldHint": true,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description88/100
Historical weather conditions for a date range. Use this to contextualize past workouts ('the reason that tempo run felt brutal: 88F + 70% RH') or analyze race-day conditions. Archive data lags ~2-5 days behind today, so for very recent days use get_weather_forecast instead. Pass either {latitude, longitude} or {location}. Any place name geocodes and returns a plausible-looking result, so a city inferred rather than read produces confident, wrong weather with no error; when no city is on record anywhere, only the athlete can supply it. A label from the athlete's saved start points (<start-points>) works as `location` and uses that saved place's own coordinates.
The description explains the capability, when to use it, and what to expect.
Inputs79/100
Clarify units, where to obtain this value, or conventions not already expressed in the schema.
latitude
Latitude in decimal degrees
Clarify units, where to obtain this value, or conventions not already expressed in the schema.
longitude
Longitude in decimal degrees
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
location
Place name to geocode if lat/lon not provided
Clear enough to supply this argument.
start_date
ISO date YYYY-MM-DD
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
ISO date YYYY-MM-DD
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
units
imperial (default) or metric
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_historical_weather",
"title": "Get Historical Weather",
"description": "Historical weather conditions for a date range. Use this to contextualize past workouts ('the reason that tempo run felt brutal: 88F + 70% RH') or analyze race-day conditions. Archive data lags ~2-5 days behind today, so for very recent days use get_weather_forecast instead. Pass either {latitude, longitude} or {location}. Any place name geocodes and returns a plausible-looking result, so a city inferred rather than read produces confident, wrong weather with no error; when no city is on record anywhere, only the athlete can supply it. A label from the athlete's saved start points (<start-points>) works as `location` and uses that saved place's own coordinates.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"start_date",
"end_date"
],
"properties": {
"units": {
"enum": [
"imperial",
"metric"
],
"type": "string",
"description": "imperial (default) or metric"
},
"end_date": {
"type": "string",
"description": "ISO date YYYY-MM-DD"
},
"latitude": {
"type": "number",
"description": "Latitude in decimal degrees"
},
"location": {
"type": "string",
"description": "Place name to geocode if lat/lon not provided"
},
"longitude": {
"type": "number",
"description": "Longitude in decimal degrees"
},
"start_date": {
"type": "string",
"description": "ISO date YYYY-MM-DD"
},
"include_hourly": {
"type": "boolean",
"description": "Include hourly data (more tokens). Default false."
}
}
},
"annotations": {
"title": "Get Historical Weather",
"readOnlyHint": true,
"openWorldHint": true,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description96/100
Resolve a place name (e.g. 'Boulder, CO') to latitude/longitude. Useful for caching the user's coordinates in their athlete profile so future weather lookups are faster and more precise. Returns the best match's `latitude`, `longitude`, `name`, region (`admin1`), `country`, `countryCode` and `timezone`, or a no-match message.
The description explains the capability, when to use it, and what to expect.
Inputs87/100
Input documentation gives an agent enough context to populate the arguments.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "geocode_location",
"title": "Geocode Location",
"description": "Resolve a place name (e.g. 'Boulder, CO') to latitude/longitude. Useful for caching the user's coordinates in their athlete profile so future weather lookups are faster and more precise. Returns the best match's `latitude`, `longitude`, `name`, region (`admin1`), `country`, `countryCode` and `timezone`, or a no-match message.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"location"
],
"properties": {
"location": {
"type": "string",
"description": "Place name to resolve"
}
}
},
"annotations": {
"title": "Geocode Location",
"readOnlyHint": true,
"openWorldHint": true,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description92/100
Activities from Zwift, Polar, COROS, Suunto, Zepp, Ultrahuman, Rouvy, TrainerRoad, Hammerhead Karoo, TrainingPeaks, Strong, Fitbod, Technogym or Frontier X, one `provider` per call (Garmin, Wahoo and Hevy have their own tools). Strong, Fitbod and Technogym gym sessions list here as a bare duration; their per-exercise sets, reps and kg are in get_activity_detail. Frontier X sessions carry `cardiac`, the ECG strap's own read (rhythm-classifier seconds as normal / AFib / other / noise, beat counts, breathing rate, in-session HRV, `vt` VT1/VT2 estimates from that session): a device classifier, not a diagnosis. WINDOW: `start_date` / `end_date` (YYYY-MM-DD, inclusive) reach any past window, as far back as we ingested (up to five years for TrainingPeaks); the trailing `days` window caps at 365. Rows are newest-first, so a truncated wide window drops the OLDEST sessions, which a narrower window returns. A session's note is in `notes`, with `notes_by` naming who wrote it (`athlete` = their own comment) and `notes_key` explaining the rest. Example: terra_get_activities({ provider: "trainingpeaks", start_date: "2025-02-01", end_date: "2025-02-28" }).
The description explains the capability, when to use it, and what to expect.
Inputs85/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
provider
Platform to read activities from: Zwift, Polar, COROS, Suunto, Zepp, Ultrahuman, Rouvy, TrainerRoad, Hammerhead Karoo, TrainingPeaks, Strong, Fitbod, Technogym or Frontier X (frontier_x)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start of the window (YYYY-MM-DD, inclusive). Takes precedence over `days` and is NOT capped at 365 days - this is how you reach history older than a year.
Clear enough to supply this argument.
end_date
End of the window (YYYY-MM-DD, inclusive). Defaults to today when only start_date is given.
Clear enough to supply this argument.
days
Days back from today (default 30, max 365). Inclusive of today. Ignored when start_date is given; with only end_date it is the span ending there.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max rows to return (default 50, max 200)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "terra_get_activities",
"title": "Terra: Get Activities",
"description": "Activities from Zwift, Polar, COROS, Suunto, Zepp, Ultrahuman, Rouvy, TrainerRoad, Hammerhead Karoo, TrainingPeaks, Strong, Fitbod, Technogym or Frontier X, one `provider` per call (Garmin, Wahoo and Hevy have their own tools). Strong, Fitbod and Technogym gym sessions list here as a bare duration; their per-exercise sets, reps and kg are in get_activity_detail. Frontier X sessions carry `cardiac`, the ECG strap's own read (rhythm-classifier seconds as normal / AFib / other / noise, beat counts, breathing rate, in-session HRV, `vt` VT1/VT2 estimates from that session): a device classifier, not a diagnosis. WINDOW: `start_date` / `end_date` (YYYY-MM-DD, inclusive) reach any past window, as far back as we ingested (up to five years for TrainingPeaks); the trailing `days` window caps at 365. Rows are newest-first, so a truncated wide window drops the OLDEST sessions, which a narrower window returns. A session's note is in `notes`, with `notes_by` naming who wrote it (`athlete` = their own comment) and `notes_key` explaining the rest. Example: terra_get_activities({ provider: \"trainingpeaks\", start_date: \"2025-02-01\", end_date: \"2025-02-28\" }).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"provider"
],
"properties": {
"days": {
"type": "number",
"description": "Days back from today (default 30, max 365). Inclusive of today. Ignored when start_date is given; with only end_date it is the span ending there."
},
"limit": {
"type": "number",
"description": "Max rows to return (default 50, max 200)"
},
"end_date": {
"type": "string",
"description": "End of the window (YYYY-MM-DD, inclusive). Defaults to today when only start_date is given."
},
"provider": {
"enum": [
"polar",
"coros",
"suunto",
"zepp",
"ultrahuman",
"zwift",
"rouvy",
"trainerroad",
"hammerhead",
"trainingpeaks",
"strong",
"fitbod",
"technogym",
"frontier_x"
],
"type": "string",
"description": "Platform to read activities from: Zwift, Polar, COROS, Suunto, Zepp, Ultrahuman, Rouvy, TrainerRoad, Hammerhead Karoo, TrainingPeaks, Strong, Fitbod, Technogym or Frontier X (frontier_x)"
},
"start_date": {
"type": "string",
"description": "Start of the window (YYYY-MM-DD, inclusive). Takes precedence over `days` and is NOT capped at 365 days - this is how you reach history older than a year."
}
}
},
"annotations": {
"title": "Terra: Get Activities",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description90/100
Snapshot of what a connected platform - Zwift, Polar, COROS, Suunto, Zepp, Ultrahuman, Rouvy, TrainerRoad, Hammerhead Karoo, TrainingPeaks, Cronometer, MyFitnessPal, Clue, or Flo (direct, read-only) - knows about the athlete: activities (if it tracks them), wellness scalars in daily_metrics, nutrition logs, cycle events (Clue / Flo: the derived cycles plus logged periods, flow, symptoms, mood, ovulation tests and BBT; Flo forecasts are never stored). Useful for a 'what does Zwift/Polar/Ultrahuman know about me?' overview. For nutrition queries (Cronometer / MyFitnessPal) prefer food_log_get - it aggregates daily macro totals across all sources.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
provider
Platform to query: Zwift, Polar, COROS, Suunto, Zepp, Ultrahuman, Rouvy, TrainerRoad, Hammerhead Karoo, TrainingPeaks, Cronometer, MyFitnessPal, Clue, or Flo
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
days
Days back from today (default 14, max 90)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Per-block / per-rep session analysis for a TrainingPeaks, Zwift, Polar, COROS, Suunto, Rouvy, TrainerRoad, Hammerhead, Zepp, or Ultrahuman RIDE - the Terra-bridged equivalent of strava_get_interval_breakdown / garmin_get_activity_file. This is the tool for 'break down the sets/blocks', 'per-block splits', 'how did I pace that', within-session power/HR drift, or block-1-vs-block-2 comparison on any of those providers. For TrainingPeaks athletes on the official bridge: TP/Terra routinely ship an EMPTY laps array and no per-km splits, so activity_metrics.laps being null does NOT mean the data is missing - the raw per-second POWER and HR samples are stored and this tool reconstructs the session from them. So for a TrainingPeaks/Zwift/etc. ride, missing laps or a zone-totals-only summary elsewhere does not mean per-second data is unavailable; this tool is where it is read. The TrainingPeaks COMMUNITY connector works too: it reads the per-second samples stored from TrainingPeaks' own workout detail, and returns the session's recorded laps alongside. Returns: `reps`/`recoveries` (heuristic rep breakdown with per-rep watts/HR + startSec/endSec) and `confidence`/`notes` WHEN discrete interval structure is present (e.g. a VO2 4x4); and ALWAYS a `signal` - a downsampled power/HR time-series (~150-200 points) covering the whole ride. For a STEADY endurance ride (e.g. a `2x25` Z1/Z2 block session) no discrete reps are detected (`detected:false`) and that is correct - each block's avg power/HR is readable directly off the `signal` trace over that block's time window. Pass `source_activity_id` (from terra_get_activities / the webhook) or a `date` (YYYY-MM-DD) to resolve the session.
The description explains the capability, when to use it, and what to expect.
Inputs92/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
provider
Platform the ride came from: TrainingPeaks, Zwift, Polar, COROS, Suunto, Zepp, Ultrahuman, Rouvy, TrainerRoad, or Hammerhead Karoo
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
source_activity_id
Provider activity id (the sourceActivityId from terra_get_activities, or the webhook summary_id)
Clear enough to supply this argument.
date
YYYY-MM-DD to look the ride up by day when the id isn't known (picks the longest activity that day)
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "terra_get_interval_breakdown",
"title": "Terra: Get Interval Breakdown",
"description": "Per-block / per-rep session analysis for a TrainingPeaks, Zwift, Polar, COROS, Suunto, Rouvy, TrainerRoad, Hammerhead, Zepp, or Ultrahuman RIDE - the Terra-bridged equivalent of strava_get_interval_breakdown / garmin_get_activity_file. This is the tool for 'break down the sets/blocks', 'per-block splits', 'how did I pace that', within-session power/HR drift, or block-1-vs-block-2 comparison on any of those providers. For TrainingPeaks athletes on the official bridge: TP/Terra routinely ship an EMPTY laps array and no per-km splits, so activity_metrics.laps being null does NOT mean the data is missing - the raw per-second POWER and HR samples are stored and this tool reconstructs the session from them. So for a TrainingPeaks/Zwift/etc. ride, missing laps or a zone-totals-only summary elsewhere does not mean per-second data is unavailable; this tool is where it is read. The TrainingPeaks COMMUNITY connector works too: it reads the per-second samples stored from TrainingPeaks' own workout detail, and returns the session's recorded laps alongside. Returns: `reps`/`recoveries` (heuristic rep breakdown with per-rep watts/HR + startSec/endSec) and `confidence`/`notes` WHEN discrete interval structure is present (e.g. a VO2 4x4); and ALWAYS a `signal` - a downsampled power/HR time-series (~150-200 points) covering the whole ride. For a STEADY endurance ride (e.g. a `2x25` Z1/Z2 block session) no discrete reps are detected (`detected:false`) and that is correct - each block's avg power/HR is readable directly off the `signal` trace over that block's time window. Pass `source_activity_id` (from terra_get_activities / the webhook) or a `date` (YYYY-MM-DD) to resolve the session.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"provider"
],
"properties": {
"date": {
"type": "string",
"description": "YYYY-MM-DD to look the ride up by day when the id isn't known (picks the longest activity that day)"
},
"provider": {
"enum": [
"polar",
"coros",
"suunto",
"zepp",
"ultrahuman",
"zwift",
"rouvy",
"trainerroad",
"hammerhead",
"trainingpeaks"
],
"type": "string",
"description": "Platform the ride came from: TrainingPeaks, Zwift, Polar, COROS, Suunto, Zepp, Ultrahuman, Rouvy, TrainerRoad, or Hammerhead Karoo"
},
"source_activity_id": {
"type": "string",
"description": "Provider activity id (the sourceActivityId from terra_get_activities, or the webhook summary_id)"
}
}
},
"annotations": {
"title": "Terra: Get Interval Breakdown",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
Completed Peloton classes (rides, runs, walks, rows, strength, yoga, stretching) with class title + instructor, output/power, cadence, heart rate and distance. Use for 'what Peloton classes have I done', 'who do I ride with', or 'how much output did I hold'. Strength classes also carry per-movement reps and weights - use peloton_get_workout for those. Peloton does NOT report sleep, recovery, an FTP we trust, or upcoming/scheduled classes. PAST WINDOWS: pass start_date/end_date (YYYY-MM-DD, inclusive) to read ANY window, e.g. peloton_get_workouts({ start_date: "2025-02-01", end_date: "2025-02-28" }). Those two have no 365-day ceiling and reach as far back as we ingested; the trailing `days` window DOES cap at 365, so a question about a specific past month or a year-before-last needs the dates, not `days`. Rows come back newest-first, so when a wide window is truncated the classes dropped are the OLDEST ones - a month-at-a-time read returns them, and truncation does not mean the older history is missing.
The description explains the capability, when to use it, and what to expect.
Inputs81/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
Start of the window (YYYY-MM-DD, inclusive). Takes precedence over `days` and is NOT capped at 365 days - this is how you reach history older than a year.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
End of the window (YYYY-MM-DD, inclusive). Defaults to today when only start_date is given.
Clear enough to supply this argument.
days
Days back from today (default 30, max 365). Inclusive of today. Ignored when start_date is given; with only end_date it is the span ending there.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
limit
Max rows to return (default 50, max 200)
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "peloton_get_workouts",
"title": "Peloton: Get Workouts",
"description": "Completed Peloton classes (rides, runs, walks, rows, strength, yoga, stretching) with class title + instructor, output/power, cadence, heart rate and distance. Use for 'what Peloton classes have I done', 'who do I ride with', or 'how much output did I hold'. Strength classes also carry per-movement reps and weights - use peloton_get_workout for those. Peloton does NOT report sleep, recovery, an FTP we trust, or upcoming/scheduled classes. PAST WINDOWS: pass start_date/end_date (YYYY-MM-DD, inclusive) to read ANY window, e.g. peloton_get_workouts({ start_date: \"2025-02-01\", end_date: \"2025-02-28\" }). Those two have no 365-day ceiling and reach as far back as we ingested; the trailing `days` window DOES cap at 365, so a question about a specific past month or a year-before-last needs the dates, not `days`. Rows come back newest-first, so when a wide window is truncated the classes dropped are the OLDEST ones - a month-at-a-time read returns them, and truncation does not mean the older history is missing.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"days": {
"type": "number",
"description": "Days back from today (default 30, max 365). Inclusive of today. Ignored when start_date is given; with only end_date it is the span ending there."
},
"limit": {
"type": "number",
"description": "Max rows to return (default 50, max 200)"
},
"end_date": {
"type": "string",
"description": "End of the window (YYYY-MM-DD, inclusive). Defaults to today when only start_date is given."
},
"start_date": {
"type": "string",
"description": "Start of the window (YYYY-MM-DD, inclusive). Takes precedence over `days` and is NOT capped at 365 days - this is how you reach history older than a year."
}
}
},
"annotations": {
"title": "Peloton: Get Workouts",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description97/100
Full detail for ONE Peloton class by its id (sourceActivityId from peloton_get_workouts). For strength classes this returns the per-movement breakdown - movement name, sets, reps and weight in kg - which is the detail no other connected source has for a Peloton class.
The description explains the capability, when to use it, and what to expect.
Inputs98/100
Input documentation gives an agent enough context to populate the arguments.
source_activity_id
The Peloton workout id, as returned by peloton_get_workouts
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "peloton_get_workout",
"title": "Peloton: Get Workout",
"description": "Full detail for ONE Peloton class by its id (sourceActivityId from peloton_get_workouts). For strength classes this returns the per-movement breakdown - movement name, sets, reps and weight in kg - which is the detail no other connected source has for a Peloton class.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"source_activity_id"
],
"properties": {
"source_activity_id": {
"type": "string",
"description": "The Peloton workout id, as returned by peloton_get_workouts"
}
}
},
"annotations": {
"title": "Peloton: Get Workout",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description98/100
The athlete's PLANNED / upcoming training sessions - their forward training plan. Use this for questions like 'what's on my plan this week?', 'what's my next workout?', or 'what does my coach have scheduled?'. For TrainingPeaks athletes this returns the live TrainingPeaks plan (planned, not yet completed sessions). NOTE: this is the FUTURE plan - for COMPLETED workouts use the per-provider activity tools (strava_get_activities, garmin_get_activities, terra_get_activities, ...) or the cross-source get_analytics_summary. Each session has date, sport, title, description, target duration/load, status (planned/completed/skipped/moved; completed rows carry a linkedActivityId once the actual workout is matched, and `execution`: DONE when that workout was the prescription, PARTIAL / OVER / HARDER / EASIER with `execution_detail` saying what was run when it was not, UNKNOWN when it could not be read - completed alone does not mean the session was done as written), structured intervals in workoutBlocks when the source expressed them, and ids. `trainingpeaks_workout_id` is the id to pass to trainingpeaks_edit_workout / trainingpeaks_move_workout / trainingpeaks_delete_workout, and it is filled ONLY for a TrainingPeaks-owned session mirrored in from the writeable community connector. A null there does NOT mean the session cannot be changed and does NOT mean the athlete is on the read-only bridge: on an athletedata-authored plan every row is null here by construction, because these are OUR sessions - they are changed with the athletedata coach's upsert_plan_session / delete_plan_session tools (the TrainingPeaks copy follows), and trainingpeaks_get_workouts reads what is actually sitting on their TrainingPeaks calendar, including the ids for anything on it. `session_id` is the generic identifier for the row and is NOT a TrainingPeaks id - it is not a valid id for any trainingpeaks_* tool.
The description explains the capability, when to use it, and what to expect.
Inputs75/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
start_date
ISO date YYYY-MM-DD, inclusive. Default: today (user-local).
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
end_date
ISO date YYYY-MM-DD, inclusive. Default: today + 14 days. Window is clamped to [today-30d, today+120d].
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "get_planned_workouts",
"title": "Get Planned Workouts",
"description": "The athlete's PLANNED / upcoming training sessions - their forward training plan. Use this for questions like 'what's on my plan this week?', 'what's my next workout?', or 'what does my coach have scheduled?'. For TrainingPeaks athletes this returns the live TrainingPeaks plan (planned, not yet completed sessions). NOTE: this is the FUTURE plan - for COMPLETED workouts use the per-provider activity tools (strava_get_activities, garmin_get_activities, terra_get_activities, ...) or the cross-source get_analytics_summary. Each session has date, sport, title, description, target duration/load, status (planned/completed/skipped/moved; completed rows carry a linkedActivityId once the actual workout is matched, and `execution`: DONE when that workout was the prescription, PARTIAL / OVER / HARDER / EASIER with `execution_detail` saying what was run when it was not, UNKNOWN when it could not be read - completed alone does not mean the session was done as written), structured intervals in workoutBlocks when the source expressed them, and ids. `trainingpeaks_workout_id` is the id to pass to trainingpeaks_edit_workout / trainingpeaks_move_workout / trainingpeaks_delete_workout, and it is filled ONLY for a TrainingPeaks-owned session mirrored in from the writeable community connector. A null there does NOT mean the session cannot be changed and does NOT mean the athlete is on the read-only bridge: on an athletedata-authored plan every row is null here by construction, because these are OUR sessions - they are changed with the athletedata coach's upsert_plan_session / delete_plan_session tools (the TrainingPeaks copy follows), and trainingpeaks_get_workouts reads what is actually sitting on their TrainingPeaks calendar, including the ids for anything on it. `session_id` is the generic identifier for the row and is NOT a TrainingPeaks id - it is not a valid id for any trainingpeaks_* tool.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"end_date": {
"type": "string",
"description": "ISO date YYYY-MM-DD, inclusive. Default: today + 14 days. Window is clamped to [today-30d, today+120d]."
},
"start_date": {
"type": "string",
"description": "ISO date YYYY-MM-DD, inclusive. Default: today (user-local)."
}
}
},
"annotations": {
"title": "Get Planned Workouts",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description92/100
Push a structured workout to the athlete's watch for one day: whichever of garmin, coros, suunto, apple_watch, zwift the athlete has connected (`provider` picks one; a call with none connected says how to connect one). It appears as a scheduled structured workout started with one tap. Garmin and COROS deliver within a minute: Run / Ride / VirtualRide / Swim / Strength, and Garmin also Yoga / Pilates / Mobility (Mobility lands as Yoga). apple_watch takes Run / Ride only, lands the next time the athlete opens the athletedata iOS app, and shows pace as a speed band. Walk / Hike / Ski / Paddle go to a native Garmin as a sport-agnostic workout with a heart-rate target only. Rest and ball sports cannot be pushed (not in Garmin's workout sport list), and relabelling the sport does not change that. Cycling power targets resolve to watts from the athlete's FTP; running power (Stryd) uses targetPowerWatts.
Each call adds a copy (no dedup). The response carries a "workout_id": update_workout_on_watch with that id changes the workout (an ad-hoc push cannot be found by date), and delete_workout_from_watch removes it. Most old pushes leave the watch after their date (list_workouts_on_watch shows when). A full watch is cleared with list_workouts_on_watch and delete_workout_from_watch; another push only adds to the count.
zwift is not a watch: it schedules the workout on the athlete's Zwift home screen as "ATHLETEDATA: <title>", visible on its own day only (a past date is refused). A Ride / VirtualRide needs targetFtpPct (% FTP) on every non-rest step; watts convert when an FTP is on file, and heart-rate, pace or RPE ride steps are refused. A Run takes pace, heart-rate or RPE steps, each run as a free step with the target on screen and each with durationSec or distanceMeters. Zwift is used only with provider:"zwift" or when it is the only target.
Describe the returned information so the agent knows whether it will answer the request.
Inputs87/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
date
Day the workout should appear on the watch, YYYY-MM-DD.
Clear enough to supply this argument.
sport
Run | Ride | VirtualRide | Swim | Strength | Yoga | Pilates | Mobility. Maps to the device activity type. Yoga / Pilates / Mobility are Garmin-only (Mobility lands as a Yoga workout) and use the same kind:"strength" exercise blocks as Strength. Walk / Hike / Ski / Kayak / Canoe / SUP go to a native Garmin as a sport-agnostic workout (pass the real sport, never relabel it). Rest and ball sports are not pushable (not in Garmin's workout Sport enum). zwift takes Ride / VirtualRide / Run.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
title
Workout name shown on the watch.
Clear enough to supply this argument.
description
Optional execution notes / intent shown with the workout.
Clear enough to supply this argument.
target_load
Planned training load (TSS) for this workout, stored with it as metadata. Omitted, the workout carries no planned load.
Clear enough to supply this argument.
provider
Which connected push target to use (garmin | coros | suunto | apple_watch | zwift). Omit to use the most-recently-connected watch; when apple_watch is the ONLY target it's used automatically. Pass "apple_watch" explicitly to reach the Apple Watch alongside a connected Garmin/COROS.
Most specific Garmin category for the lift (warm-up sets keep the lift's own). Two exercises on one category read alike on the watch unless garminExercise names each.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
Set true ONLY when the athlete explicitly asked for this workout IN ADDITION to the session their athletedata coach's plan already put on the watch that day. Without it, a push onto a day that already holds a planned session of the same sport on the same target is refused, because it would put two workouts on the watch; changing or replacing a plan session goes through the athlete's coach chat.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "push_workout_to_watch",
"title": "Push Workout to Watch",
"description": "Push a structured workout to the athlete's watch for one day: whichever of garmin, coros, suunto, apple_watch, zwift the athlete has connected (`provider` picks one; a call with none connected says how to connect one). It appears as a scheduled structured workout started with one tap. Garmin and COROS deliver within a minute: Run / Ride / VirtualRide / Swim / Strength, and Garmin also Yoga / Pilates / Mobility (Mobility lands as Yoga). apple_watch takes Run / Ride only, lands the next time the athlete opens the athletedata iOS app, and shows pace as a speed band. Walk / Hike / Ski / Paddle go to a native Garmin as a sport-agnostic workout with a heart-rate target only. Rest and ball sports cannot be pushed (not in Garmin's workout sport list), and relabelling the sport does not change that. Cycling power targets resolve to watts from the athlete's FTP; running power (Stryd) uses targetPowerWatts.\n\nEach call adds a copy (no dedup). The response carries a \"workout_id\": update_workout_on_watch with that id changes the workout (an ad-hoc push cannot be found by date), and delete_workout_from_watch removes it. Most old pushes leave the watch after their date (list_workouts_on_watch shows when). A full watch is cleared with list_workouts_on_watch and delete_workout_from_watch; another push only adds to the count.\n\nzwift is not a watch: it schedules the workout on the athlete's Zwift home screen as \"ATHLETEDATA: <title>\", visible on its own day only (a past date is refused). A Ride / VirtualRide needs targetFtpPct (% FTP) on every non-rest step; watts convert when an FTP is on file, and heart-rate, pace or RPE ride steps are refused. A Run takes pace, heart-rate or RPE steps, each run as a free step with the target on screen and each with durationSec or distanceMeters. Zwift is used only with provider:\"zwift\" or when it is the only target.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"date",
"sport",
"title",
"workout_blocks"
],
"properties": {
"date": {
"type": "string",
"description": "Day the workout should appear on the watch, YYYY-MM-DD."
},
"sport": {
"type": "string",
"description": "Run | Ride | VirtualRide | Swim | Strength | Yoga | Pilates | Mobility. Maps to the device activity type. Yoga / Pilates / Mobility are Garmin-only (Mobility lands as a Yoga workout) and use the same kind:\"strength\" exercise blocks as Strength. Walk / Hike / Ski / Kayak / Canoe / SUP go to a native Garmin as a sport-agnostic workout (pass the real sport, never relabel it). Rest and ball sports are not pushable (not in Garmin's workout Sport enum). zwift takes Ride / VirtualRide / Run."
},
"title": {
"type": "string",
"description": "Workout name shown on the watch."
},
"provider": {
"type": "string",
"description": "Which connected push target to use (garmin | coros | suunto | apple_watch | zwift). Omit to use the most-recently-connected watch; when apple_watch is the ONLY target it's used automatically. Pass \"apple_watch\" explicitly to reach the Apple Watch alongside a connected Garmin/COROS."
},
"description": {
"type": "string",
"description": "Optional execution notes / intent shown with the workout."
},
"target_load": {
"type": "number",
"description": "Planned training load (TSS) for this workout, stored with it as metadata. Omitted, the workout carries no planned load."
},
"workout_blocks": {
"type": "object",
"required": [
"blocks"
],
"properties": {
"blocks": {
"type": "array",
"items": {
"anyOf": [
{
"type": "object",
"required": [
"kind",
"repeats",
"work",
"rest"
],
"properties": {
"kind": {
"enum": [
"interval"
],
"type": "string",
"description": "Repeat block."
},
"rest": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"enum": [
"warmup",
"cooldown",
"steady",
"rest"
],
"type": "string",
"description": "Step type."
},
"label": {
"type": "string",
"maxLength": 60,
"description": "Step name."
},
"endOnLap": {
"type": "boolean",
"description": "Ends on the lap button."
},
"targetRpe": {
"type": "array",
"items": {
"type": "number"
},
"maximum": 10,
"minimum": 1,
"maxItems": 2,
"minItems": 2,
"description": "RPE, 1-10."
},
"freeEffort": {
"type": "boolean",
"description": "Open step, no target."
},
"swimStroke": {
"enum": [
"freestyle",
"backstroke",
"breaststroke",
"butterfly",
"drill",
"mixed",
"im"
],
"type": "string",
"description": "Swim stroke."
},
"durationSec": {
"type": "integer",
"maximum": 86400,
"minimum": 1,
"description": "Seconds (10 min = 600). Required unless distanceMeters is set."
},
"targetHrBpm": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "Heart rate, bpm."
},
"targetFtpPct": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "% of FTP."
},
"targetHrZone": {
"type": "integer",
"maximum": 5,
"minimum": 1,
"description": "Heart-rate zone."
},
"swimEquipment": {
"enum": [
"none",
"fins",
"kickboard",
"paddles",
"pull_buoy",
"snorkel"
],
"type": "string",
"description": "Pool toy."
},
"targetCadence": {
"type": "integer",
"maximum": 200,
"minimum": 30,
"description": "Cadence, rpm or steps/min."
},
"distanceMeters": {
"type": "integer",
"maximum": 50000,
"minimum": 1,
"description": "Metres, the alternative to durationSec."
},
"targetHrPctMax": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "% of max HR."
},
"targetPowerWatts": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "Watts."
},
"targetPaceSecPerKm": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "Pace, sec/km."
}
},
"description": "The recovery leg of each repeat, usually { kind: 'rest', durationSec: N }."
},
"work": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"enum": [
"warmup",
"cooldown",
"steady",
"rest"
],
"type": "string",
"description": "Step type."
},
"label": {
"type": "string",
"maxLength": 60,
"description": "Step name on the watch; set it on every swim drill or kick rep."
},
"endOnLap": {
"type": "boolean",
"description": "Step ends on the lap button; durationSec stays as the shown estimate (only a rest step may omit it). The COROS/Suunto push lands it as a timed step."
},
"targetRpe": {
"type": "array",
"items": {
"type": "number"
},
"maximum": 10,
"minimum": 1,
"maxItems": 2,
"minItems": 2,
"description": "RPE [low, high]: the target for effort-based work with no pace or HR alert."
},
"freeEffort": {
"type": "boolean",
"description": "Open step with no target or alert, for a max-effort test or an athlete who asked for none; no power target."
},
"swimStroke": {
"enum": [
"freestyle",
"backstroke",
"breaststroke",
"butterfly",
"drill",
"mixed",
"im"
],
"type": "string",
"description": "Swim only. 'drill' on every drill or kick rep, else the stroke swum."
},
"durationSec": {
"type": "integer",
"maximum": 86400,
"minimum": 1,
"description": "Seconds (10 min = 600). Required unless distanceMeters is set."
},
"targetHrBpm": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "[low, high] bpm, at least 12 wide; a whole zone is targetHrZone."
},
"targetFtpPct": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "% of FTP, cycling."
},
"targetHrZone": {
"type": "integer",
"maximum": 5,
"minimum": 1,
"description": "Heart-rate zone."
},
"swimEquipment": {
"enum": [
"none",
"fins",
"kickboard",
"paddles",
"pull_buoy",
"snorkel"
],
"type": "string",
"description": "Swim only. Pool toy for this rep; omit if none."
},
"targetCadence": {
"type": "integer",
"maximum": 200,
"minimum": 30,
"description": "Cadence, rpm or steps/min."
},
"distanceMeters": {
"type": "integer",
"maximum": 50000,
"minimum": 1,
"description": "Metres, the alternative to durationSec."
},
"targetHrPctMax": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "% of max heart rate."
},
"targetPowerWatts": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "Power in watts."
},
"targetPaceSecPerKm": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "[low, high] sec/km, at least 15 wide."
}
},
"description": "The work leg of each repeat: a step with an extent and a target."
},
"label": {
"type": "string",
"maxLength": 60,
"description": "Optional set name; label the work leg to show it on each rep."
},
"repeats": {
"type": "integer",
"maximum": 100,
"minimum": 1,
"description": "Number of repeats."
}
},
"description": "Repeat block: N x (work + rest)."
},
{
"type": "object",
"required": [
"kind",
"exerciseCategory",
"sets"
],
"properties": {
"kind": {
"enum": [
"strength"
],
"type": "string",
"description": "One exercise."
},
"reps": {
"type": "integer",
"maximum": 100,
"minimum": 1,
"description": "Reps per set; omit for an AMRAP set or a timed hold."
},
"sets": {
"type": "integer",
"maximum": 20,
"minimum": 1,
"description": "Number of sets."
},
"perSide": {
"type": "boolean",
"description": "True when sets counts each side (3x10 per side = sets 3, perSide true)."
},
"restSec": {
"type": "integer",
"maximum": 1800,
"minimum": 0,
"description": "Fixed rest between sets in seconds; 0 or omitted means no rest step at all."
},
"isWarmup": {
"type": "boolean",
"description": "True on a warm-up or ramp set of the lift its category names."
},
"repRange": {
"type": "array",
"items": {
"type": "integer",
"maximum": 100,
"minimum": 1
},
"maxItems": 2,
"minItems": 2,
"description": "[low, high] range the lift progresses in; reps sits inside it."
},
"weightKg": {
"type": "number",
"maximum": 500,
"minimum": 0,
"description": "Start load in kg; for dumbbells ONE dumbbell. Omit only for bodyweight or when no load is known."
},
"durationSec": {
"type": "integer",
"maximum": 3600,
"minimum": 1,
"description": "A timed hold (plank) instead of reps."
},
"exerciseName": {
"type": "string",
"description": "The lift's name in the athlete's language; always set it."
},
"supersetGroup": {
"type": "integer",
"maximum": 20,
"minimum": 1,
"description": "The same integer on exercises done alternately (a superset or circuit); sets = rounds."
},
"weightKgRange": {
"type": "array",
"items": {
"type": "number",
"maximum": 500,
"minimum": 0
},
"maxItems": 2,
"minItems": 2,
"description": "[low, high] load range in kg; weightKg defaults to the low end."
},
"garminExercise": {
"type": "string",
"maxLength": 120,
"description": "Garmin's key for the exact lift (e.g. DUMBBELL_BENCH_PRESS); omit when unsure (it is picked at write time); \"NONE\" when Garmin has no key for it."
},
"exerciseCategory": {
"enum": [
"UNKNOWN",
"BENCH_PRESS",
"CALF_RAISE",
"CARDIO",
"CARRY",
"CHOP",
"CORE",
"CRUNCH",
"CURL",
"DEADLIFT",
"FLYE",
"HIP_RAISE",
"HIP_STABILITY",
"HIP_SWING",
"HYPEREXTENSION",
"LATERAL_RAISE",
"LEG_CURL",
"LEG_RAISE",
"LUNGE",
"OLYMPIC_LIFT",
"PLANK",
"PLYO",
"PULL_UP",
"PUSH_UP",
"ROW",
"SHOULDER_PRESS",
"SHOULDER_STABILITY",
"SHRUG",
"SIT_UP",
"SQUAT",
"TOTAL_BODY",
"TRICEPS_EXTENSION",
"WARM_UP"
],
"type": "string",
"description": "Most specific Garmin category for the lift (warm-up sets keep the lift's own). Two exercises on one category read alike on the watch unless garminExercise names each."
}
},
"description": "One exercise of a Strength session."
},
{
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"enum": [
"warmup",
"cooldown",
"steady",
"rest",
"ramp"
],
"type": "string",
"description": "Step type; 'ramp' = cycling power ramp."
},
"label": {
"type": "string",
"maxLength": 60,
"description": "Step name on the watch; set it on every swim drill or kick rep."
},
"endOnLap": {
"type": "boolean",
"description": "Step ends on the lap button; durationSec stays as the shown estimate (only a rest step may omit it). The COROS/Suunto push lands it as a timed step."
},
"targetRpe": {
"type": "array",
"items": {
"type": "number"
},
"maximum": 10,
"minimum": 1,
"maxItems": 2,
"minItems": 2,
"description": "RPE [low, high]: the target for effort-based work with no pace or HR alert."
},
"freeEffort": {
"type": "boolean",
"description": "Open step with no target or alert, for a max-effort test or an athlete who asked for none; no power target."
},
"swimStroke": {
"enum": [
"freestyle",
"backstroke",
"breaststroke",
"butterfly",
"drill",
"mixed",
"im"
],
"type": "string",
"description": "Swim only. 'drill' on every drill or kick rep, else the stroke swum."
},
"durationSec": {
"type": "integer",
"maximum": 86400,
"minimum": 1,
"description": "Seconds (10 min = 600). Required unless distanceMeters is set."
},
"targetHrBpm": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "[low, high] bpm, at least 12 wide; a whole zone is targetHrZone."
},
"targetFtpPct": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "% of FTP, cycling."
},
"targetHrZone": {
"type": "integer",
"maximum": 5,
"minimum": 1,
"description": "Heart-rate zone."
},
"swimEquipment": {
"enum": [
"none",
"fins",
"kickboard",
"paddles",
"pull_buoy",
"snorkel"
],
"type": "string",
"description": "Swim only. Pool toy for this rep; omit if none."
},
"targetCadence": {
"type": "integer",
"maximum": 200,
"minimum": 30,
"description": "Cadence, rpm or steps/min."
},
"distanceMeters": {
"type": "integer",
"maximum": 50000,
"minimum": 1,
"description": "Metres, the alternative to durationSec."
},
"targetHrPctMax": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "% of max heart rate."
},
"targetPowerWatts": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "Power in watts."
},
"targetPaceSecPerKm": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "[low, high] sec/km, at least 15 wide."
}
},
"description": "One step. Needs durationSec or distanceMeters, and one target unless it is a rest or an untargeted distance rep; kind 'ramp' is a cycling %FTP ramp (targetFtpPct [start, end]). Every target is a [low, high] band, never a point."
},
{
"type": "object",
"required": [
"kind",
"format",
"movements"
],
"properties": {
"kind": {
"enum": [
"metcon"
],
"type": "string",
"description": "Conditioning piece."
},
"label": {
"type": "string",
"maxLength": 60,
"description": "Optional name."
},
"capSec": {
"type": "integer",
"maximum": 14400,
"minimum": 1,
"description": "AMRAP: required, the clock in seconds. For Time: the cap. Not on an EMOM."
},
"format": {
"enum": [
"amrap",
"emom",
"for_time"
],
"type": "string",
"description": "AMRAP, EMOM or For Time."
},
"rounds": {
"type": "integer",
"maximum": 100,
"minimum": 1,
"description": "For Time only: rounds (default 1)."
},
"intervals": {
"type": "integer",
"maximum": 60,
"minimum": 1,
"description": "EMOM only, required: how many intervals."
},
"movements": {
"type": "array",
"items": {
"type": "object",
"required": [
"exerciseName"
],
"properties": {
"reps": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "Rep count."
},
"calories": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "Calories (erg)."
},
"weightKg": {
"type": "number",
"maximum": 500,
"minimum": 0,
"description": "Load in kg; omit for bodyweight."
},
"durationSec": {
"type": "integer",
"maximum": 3600,
"minimum": 1,
"description": "Seconds (hold or station)."
},
"exerciseName": {
"type": "string",
"maxLength": 60,
"description": "The movement's name."
},
"distanceMeters": {
"type": "number",
"maximum": 42195,
"minimum": 1,
"description": "Metres."
},
"garminExercise": {
"type": "string",
"maxLength": 120,
"description": "Optional Garmin key; set it when exerciseName is not English."
},
"exerciseCategory": {
"enum": [
"UNKNOWN",
"BENCH_PRESS",
"CALF_RAISE",
"CARDIO",
"CARRY",
"CHOP",
"CORE",
"CRUNCH",
"CURL",
"DEADLIFT",
"FLYE",
"HIP_RAISE",
"HIP_STABILITY",
"HIP_SWING",
"HYPEREXTENSION",
"LATERAL_RAISE",
"LEG_CURL",
"LEG_RAISE",
"LUNGE",
"OLYMPIC_LIFT",
"PLANK",
"PLYO",
"PULL_UP",
"PUSH_UP",
"ROW",
"SHOULDER_PRESS",
"SHOULDER_STABILITY",
"SHRUG",
"SIT_UP",
"SQUAT",
"TOTAL_BODY",
"TRICEPS_EXTENSION",
"WARM_UP"
],
"type": "string",
"description": "Optional Garmin category for a lift."
}
},
"description": "One movement, with ONE count: reps, durationSec, distanceMeters or calories (none on an EMOM = max effort for the interval)."
},
"maxItems": 15,
"minItems": 1
},
"emomPattern": {
"enum": [
"alternate",
"all"
],
"type": "string",
"description": "EMOM: 'alternate' (default) one movement per interval, 'all' the whole list each interval."
},
"intervalSec": {
"type": "integer",
"maximum": 600,
"minimum": 10,
"description": "EMOM only: seconds per interval (default 60)."
},
"restAfterSec": {
"type": "integer",
"maximum": 1800,
"minimum": 0,
"description": "Optional rest after the piece."
}
},
"description": "AMRAP / EMOM / For Time piece as ONE block: its clock plus the movement list, e.g. {kind:'metcon', format:'amrap', capSec:720, movements:[{exerciseName:'Burpee', reps:10}]}."
}
]
},
"minItems": 1
}
},
"description": "Structured intervals JSON: { \"blocks\": [ ... ] }. Each cardio leaf block has kind (warmup/steady/cooldown/rest), durationSec OR distanceMeters, and a target. Range targets accept a single number OR a [low,high] band (a scalar is coerced to [n,n]): targetFtpPct for cycling, targetPowerWatts absolute watts for running-power/Stryd or cycling-by-watts, targetPaceSecPerKm for running, targetHrBpm, or targetHrPctMax for HR; targetHrZone is an integer 1-5. TARGET KEYS SIT DIRECTLY ON THE LEAF BLOCK, never nested under a \"target\" object - a minimal run with a pace band is { \"blocks\": [ { \"kind\":\"warmup\", \"durationSec\":600, \"targetPaceSecPerKm\":[360,390] }, { \"kind\":\"steady\", \"distanceMeters\":6000, \"targetPaceSecPerKm\":[320,330] }, { \"kind\":\"cooldown\", \"durationSec\":600, \"targetPaceSecPerKm\":[360,390] } ] }. A SCALAR IS A FIXED TARGET, NOT A BAND: targetPaceSecPerKm:325 puts a single 5:25/km alert on the watch, so an athlete who asked for 5:20-5:30 needs [320,330]. The zone/%max forms resolve to the athlete's own watch zones on push. Interval blocks have { kind:\"interval\", repeats, work:{...}, rest:{...} }. TARGETS ARE OPTIONAL ON A DISTANCE BLOCK: a block with distanceMeters (and a kind:\"rest\" block) needs NO target at all, and one authored without a target pushes fine — it lands as an OPEN step that counts down the distance and buzzes at nothing. No target is required there, and a deliberately wide \"non-alerting\" band is not the same as none: a 100-185bpm band is still a live alert on the watch. Only a TIME-based non-rest block needs either a target or free_effort:true. SWIMMING: a pool swim is the common case for this — when the athlete wants plain distances and rest periods, author every leaf with distanceMeters and NO targetPaceSecPerKm / targetHrZone / targetHrBpm, and the watch shows lengths only, with no pace or HR alert (verified on Garmin 2026-08-21). Add a pace target only if they asked to swim to a pace. distanceMeters is the pool distance in METRES. If the athlete swims in a YARDS pool, the value is yards multiplied by 0.9144, passed exact rather than floored/truncated (100y=91.44, 200y=182.88, 400y=365.76, 500y=457.2, 1000y=914.4). Passing the fractional value is fine; the server rounds it to the nearest metre (365.76 -> 366), and 366m renders back as 400yd on the watch. Truncating to 365m instead renders as 399yd — one short. STRENGTH sessions (sport:\"Strength\") do NOT use cardio leaf blocks — they use one strength block per exercise: { kind:\"strength\", exerciseCategory (e.g. SQUAT/DEADLIFT/BENCH_PRESS/PLANK/LUNGE/ROW/PULL_UP), exerciseName?, sets, reps OR durationSec (timed holds), weightKg?, restSec? }. Strength blocks take no targetHrZone/targetHrBpm. MOBILITY / YOGA / PILATES sessions (Garmin only) use these same strength blocks, one per movement: durationSec for a hold, reps for a counted drill, no weightKg; stretches go under exerciseCategory WARM_UP (e.g. exerciseName \"Pigeon Pose Stretch\"), core drills under CORE / HIP_STABILITY / PLANK. Do not mix strength and cardio blocks in one session. Same schema as the coach's upsert_plan_session."
},
"alongside_plan_session": {
"type": "boolean",
"description": "Set true ONLY when the athlete explicitly asked for this workout IN ADDITION to the session their athletedata coach's plan already put on the watch that day. Without it, a push onto a day that already holds a planned session of the same sport on the same target is refused, because it would put two workouts on the watch; changing or replacing a plan session goes through the athlete's coach chat."
}
}
},
"annotations": {
"title": "Push Workout to Watch",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": false,
"destructiveHint": false
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description100/100
Change a workout that is already on the athlete's watch, instead of adding a second one next to it. Connected push target(s): whichever of garmin, coros, suunto, apple_watch, zwift the athlete has connected (a call with none connected says how to connect one). This is the tool for any edit to an already-pushed workout - a different set of intervals, a corrected weight, a new title, a moved date. Delete-and-re-push is not a safe edit: push_workout_to_watch has no dedup, so that sequence puts two copies on the athlete's calendar whenever the delete does not land, and on Terra/COROS a refused re-push after a successful delete leaves them with nothing. Pass "workout_id" - the id the previous push (or update) returned - plus the workout as you want it to be NOW: this is a REPLACE, not a patch, so send every field and every block, exactly as for a create. Anything you omit is gone from the new version. On Garmin this is a true in-place update through Garmin's own Training API: one workout object, one calendar entry, nothing to clean up afterwards. On COROS/Terra the provider has no update, so we create the new copy first and only then retire the old one - the response says whether the old copy actually went ("prior_copy_retired": false means both copies are on the athlete's calendar). apple_watch replaces the queued workout. The response carries a NEW "workout_id"; the one passed in is now stale, so later changes need the new one. If the athlete deleted the workout on the device in the meantime this lands as a fresh push rather than failing. To ADD a workout that does not exist yet use push_workout_to_watch; to REMOVE one entirely use delete_workout_from_watch. On zwift there is no update either: the new version is scheduled first and the old one deleted after, and "prior_copy_retired" says whether it went (false means the athlete may see both that day). A zwift workout_id looks like "zwift:<id>".
The description explains the capability, when to use it, and what to expect.
Inputs78/100
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
workout_id
The "workout_id" returned by the push (or the previous update) of the workout you are changing. Required - this is what makes it an edit rather than a second workout. A Garmin id looks like "garmin:1698579352:1778067503"; an apple_watch id is the "queue_id" the push returned.
Clear enough to supply this argument.
date
Day the workout should appear on the watch, YYYY-MM-DD. Pass a DIFFERENT date to move the session - the calendar entry is re-scheduled, the workout is not duplicated.
Clear enough to supply this argument.
sport
Run | Ride | VirtualRide | Swim | Strength | Yoga | Pilates | Mobility. Maps to the device activity type. Yoga / Pilates / Mobility are Garmin-only (Mobility lands as a Yoga workout) and use the same kind:"strength" exercise blocks as Strength. Rest is not pushable. zwift takes Ride / VirtualRide / Run.
Clear enough to supply this argument.
title
Workout name shown on the watch.
Clear enough to supply this argument.
description
Optional execution notes / intent shown with the workout.
Clear enough to supply this argument.
target_load
Planned training load (TSS) for this workout, stored with it as metadata. Omitted, the workout carries no planned load.
Clear enough to supply this argument.
provider
Which connected push target the workout lives on (garmin | coros | suunto | apple_watch | zwift). Omit to use the same default push_workout_to_watch uses. It must match the target the workout was pushed to - a Garmin id sent at COROS (or the reverse) is refused rather than guessed at.
Most specific Garmin category for the lift (warm-up sets keep the lift's own). Two exercises on one category read alike on the watch unless garminExercise names each.
Remove validation rules already expressed in this field’s schema; describe its meaning and usage instead.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "update_workout_on_watch",
"title": "Update Workout on Watch",
"description": "Change a workout that is already on the athlete's watch, instead of adding a second one next to it. Connected push target(s): whichever of garmin, coros, suunto, apple_watch, zwift the athlete has connected (a call with none connected says how to connect one). This is the tool for any edit to an already-pushed workout - a different set of intervals, a corrected weight, a new title, a moved date. Delete-and-re-push is not a safe edit: push_workout_to_watch has no dedup, so that sequence puts two copies on the athlete's calendar whenever the delete does not land, and on Terra/COROS a refused re-push after a successful delete leaves them with nothing. Pass \"workout_id\" - the id the previous push (or update) returned - plus the workout as you want it to be NOW: this is a REPLACE, not a patch, so send every field and every block, exactly as for a create. Anything you omit is gone from the new version. On Garmin this is a true in-place update through Garmin's own Training API: one workout object, one calendar entry, nothing to clean up afterwards. On COROS/Terra the provider has no update, so we create the new copy first and only then retire the old one - the response says whether the old copy actually went (\"prior_copy_retired\": false means both copies are on the athlete's calendar). apple_watch replaces the queued workout. The response carries a NEW \"workout_id\"; the one passed in is now stale, so later changes need the new one. If the athlete deleted the workout on the device in the meantime this lands as a fresh push rather than failing. To ADD a workout that does not exist yet use push_workout_to_watch; to REMOVE one entirely use delete_workout_from_watch. On zwift there is no update either: the new version is scheduled first and the old one deleted after, and \"prior_copy_retired\" says whether it went (false means the athlete may see both that day). A zwift workout_id looks like \"zwift:<id>\".",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"workout_id",
"date",
"sport",
"title",
"workout_blocks"
],
"properties": {
"date": {
"type": "string",
"description": "Day the workout should appear on the watch, YYYY-MM-DD. Pass a DIFFERENT date to move the session - the calendar entry is re-scheduled, the workout is not duplicated."
},
"sport": {
"type": "string",
"description": "Run | Ride | VirtualRide | Swim | Strength | Yoga | Pilates | Mobility. Maps to the device activity type. Yoga / Pilates / Mobility are Garmin-only (Mobility lands as a Yoga workout) and use the same kind:\"strength\" exercise blocks as Strength. Rest is not pushable. zwift takes Ride / VirtualRide / Run."
},
"title": {
"type": "string",
"description": "Workout name shown on the watch."
},
"provider": {
"type": "string",
"description": "Which connected push target the workout lives on (garmin | coros | suunto | apple_watch | zwift). Omit to use the same default push_workout_to_watch uses. It must match the target the workout was pushed to - a Garmin id sent at COROS (or the reverse) is refused rather than guessed at."
},
"workout_id": {
"type": "string",
"description": "The \"workout_id\" returned by the push (or the previous update) of the workout you are changing. Required - this is what makes it an edit rather than a second workout. A Garmin id looks like \"garmin:1698579352:1778067503\"; an apple_watch id is the \"queue_id\" the push returned."
},
"description": {
"type": "string",
"description": "Optional execution notes / intent shown with the workout."
},
"target_load": {
"type": "number",
"description": "Planned training load (TSS) for this workout, stored with it as metadata. Omitted, the workout carries no planned load."
},
"workout_blocks": {
"type": "object",
"required": [
"blocks"
],
"properties": {
"blocks": {
"type": "array",
"items": {
"anyOf": [
{
"type": "object",
"required": [
"kind",
"repeats",
"work",
"rest"
],
"properties": {
"kind": {
"enum": [
"interval"
],
"type": "string",
"description": "Repeat block."
},
"rest": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"enum": [
"warmup",
"cooldown",
"steady",
"rest"
],
"type": "string",
"description": "Step type."
},
"label": {
"type": "string",
"maxLength": 60,
"description": "Step name."
},
"endOnLap": {
"type": "boolean",
"description": "Ends on the lap button."
},
"targetRpe": {
"type": "array",
"items": {
"type": "number"
},
"maximum": 10,
"minimum": 1,
"maxItems": 2,
"minItems": 2,
"description": "RPE, 1-10."
},
"freeEffort": {
"type": "boolean",
"description": "Open step, no target."
},
"swimStroke": {
"enum": [
"freestyle",
"backstroke",
"breaststroke",
"butterfly",
"drill",
"mixed",
"im"
],
"type": "string",
"description": "Swim stroke."
},
"durationSec": {
"type": "integer",
"maximum": 86400,
"minimum": 1,
"description": "Seconds (10 min = 600). Required unless distanceMeters is set."
},
"targetHrBpm": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "Heart rate, bpm."
},
"targetFtpPct": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "% of FTP."
},
"targetHrZone": {
"type": "integer",
"maximum": 5,
"minimum": 1,
"description": "Heart-rate zone."
},
"swimEquipment": {
"enum": [
"none",
"fins",
"kickboard",
"paddles",
"pull_buoy",
"snorkel"
],
"type": "string",
"description": "Pool toy."
},
"targetCadence": {
"type": "integer",
"maximum": 200,
"minimum": 30,
"description": "Cadence, rpm or steps/min."
},
"distanceMeters": {
"type": "integer",
"maximum": 50000,
"minimum": 1,
"description": "Metres, the alternative to durationSec."
},
"targetHrPctMax": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "% of max HR."
},
"targetPowerWatts": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "Watts."
},
"targetPaceSecPerKm": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "Pace, sec/km."
}
},
"description": "The recovery leg of each repeat, usually { kind: 'rest', durationSec: N }."
},
"work": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"enum": [
"warmup",
"cooldown",
"steady",
"rest"
],
"type": "string",
"description": "Step type."
},
"label": {
"type": "string",
"maxLength": 60,
"description": "Step name on the watch; set it on every swim drill or kick rep."
},
"endOnLap": {
"type": "boolean",
"description": "Step ends on the lap button; durationSec stays as the shown estimate (only a rest step may omit it). The COROS/Suunto push lands it as a timed step."
},
"targetRpe": {
"type": "array",
"items": {
"type": "number"
},
"maximum": 10,
"minimum": 1,
"maxItems": 2,
"minItems": 2,
"description": "RPE [low, high]: the target for effort-based work with no pace or HR alert."
},
"freeEffort": {
"type": "boolean",
"description": "Open step with no target or alert, for a max-effort test or an athlete who asked for none; no power target."
},
"swimStroke": {
"enum": [
"freestyle",
"backstroke",
"breaststroke",
"butterfly",
"drill",
"mixed",
"im"
],
"type": "string",
"description": "Swim only. 'drill' on every drill or kick rep, else the stroke swum."
},
"durationSec": {
"type": "integer",
"maximum": 86400,
"minimum": 1,
"description": "Seconds (10 min = 600). Required unless distanceMeters is set."
},
"targetHrBpm": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "[low, high] bpm, at least 12 wide; a whole zone is targetHrZone."
},
"targetFtpPct": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "% of FTP, cycling."
},
"targetHrZone": {
"type": "integer",
"maximum": 5,
"minimum": 1,
"description": "Heart-rate zone."
},
"swimEquipment": {
"enum": [
"none",
"fins",
"kickboard",
"paddles",
"pull_buoy",
"snorkel"
],
"type": "string",
"description": "Swim only. Pool toy for this rep; omit if none."
},
"targetCadence": {
"type": "integer",
"maximum": 200,
"minimum": 30,
"description": "Cadence, rpm or steps/min."
},
"distanceMeters": {
"type": "integer",
"maximum": 50000,
"minimum": 1,
"description": "Metres, the alternative to durationSec."
},
"targetHrPctMax": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "% of max heart rate."
},
"targetPowerWatts": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "Power in watts."
},
"targetPaceSecPerKm": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "[low, high] sec/km, at least 15 wide."
}
},
"description": "The work leg of each repeat: a step with an extent and a target."
},
"label": {
"type": "string",
"maxLength": 60,
"description": "Optional set name; label the work leg to show it on each rep."
},
"repeats": {
"type": "integer",
"maximum": 100,
"minimum": 1,
"description": "Number of repeats."
}
},
"description": "Repeat block: N x (work + rest)."
},
{
"type": "object",
"required": [
"kind",
"exerciseCategory",
"sets"
],
"properties": {
"kind": {
"enum": [
"strength"
],
"type": "string",
"description": "One exercise."
},
"reps": {
"type": "integer",
"maximum": 100,
"minimum": 1,
"description": "Reps per set; omit for an AMRAP set or a timed hold."
},
"sets": {
"type": "integer",
"maximum": 20,
"minimum": 1,
"description": "Number of sets."
},
"perSide": {
"type": "boolean",
"description": "True when sets counts each side (3x10 per side = sets 3, perSide true)."
},
"restSec": {
"type": "integer",
"maximum": 1800,
"minimum": 0,
"description": "Fixed rest between sets in seconds; 0 or omitted means no rest step at all."
},
"isWarmup": {
"type": "boolean",
"description": "True on a warm-up or ramp set of the lift its category names."
},
"repRange": {
"type": "array",
"items": {
"type": "integer",
"maximum": 100,
"minimum": 1
},
"maxItems": 2,
"minItems": 2,
"description": "[low, high] range the lift progresses in; reps sits inside it."
},
"weightKg": {
"type": "number",
"maximum": 500,
"minimum": 0,
"description": "Start load in kg; for dumbbells ONE dumbbell. Omit only for bodyweight or when no load is known."
},
"durationSec": {
"type": "integer",
"maximum": 3600,
"minimum": 1,
"description": "A timed hold (plank) instead of reps."
},
"exerciseName": {
"type": "string",
"description": "The lift's name in the athlete's language; always set it."
},
"supersetGroup": {
"type": "integer",
"maximum": 20,
"minimum": 1,
"description": "The same integer on exercises done alternately (a superset or circuit); sets = rounds."
},
"weightKgRange": {
"type": "array",
"items": {
"type": "number",
"maximum": 500,
"minimum": 0
},
"maxItems": 2,
"minItems": 2,
"description": "[low, high] load range in kg; weightKg defaults to the low end."
},
"garminExercise": {
"type": "string",
"maxLength": 120,
"description": "Garmin's key for the exact lift (e.g. DUMBBELL_BENCH_PRESS); omit when unsure (it is picked at write time); \"NONE\" when Garmin has no key for it."
},
"exerciseCategory": {
"enum": [
"UNKNOWN",
"BENCH_PRESS",
"CALF_RAISE",
"CARDIO",
"CARRY",
"CHOP",
"CORE",
"CRUNCH",
"CURL",
"DEADLIFT",
"FLYE",
"HIP_RAISE",
"HIP_STABILITY",
"HIP_SWING",
"HYPEREXTENSION",
"LATERAL_RAISE",
"LEG_CURL",
"LEG_RAISE",
"LUNGE",
"OLYMPIC_LIFT",
"PLANK",
"PLYO",
"PULL_UP",
"PUSH_UP",
"ROW",
"SHOULDER_PRESS",
"SHOULDER_STABILITY",
"SHRUG",
"SIT_UP",
"SQUAT",
"TOTAL_BODY",
"TRICEPS_EXTENSION",
"WARM_UP"
],
"type": "string",
"description": "Most specific Garmin category for the lift (warm-up sets keep the lift's own). Two exercises on one category read alike on the watch unless garminExercise names each."
}
},
"description": "One exercise of a Strength session."
},
{
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"enum": [
"warmup",
"cooldown",
"steady",
"rest",
"ramp"
],
"type": "string",
"description": "Step type; 'ramp' = cycling power ramp."
},
"label": {
"type": "string",
"maxLength": 60,
"description": "Step name on the watch; set it on every swim drill or kick rep."
},
"endOnLap": {
"type": "boolean",
"description": "Step ends on the lap button; durationSec stays as the shown estimate (only a rest step may omit it). The COROS/Suunto push lands it as a timed step."
},
"targetRpe": {
"type": "array",
"items": {
"type": "number"
},
"maximum": 10,
"minimum": 1,
"maxItems": 2,
"minItems": 2,
"description": "RPE [low, high]: the target for effort-based work with no pace or HR alert."
},
"freeEffort": {
"type": "boolean",
"description": "Open step with no target or alert, for a max-effort test or an athlete who asked for none; no power target."
},
"swimStroke": {
"enum": [
"freestyle",
"backstroke",
"breaststroke",
"butterfly",
"drill",
"mixed",
"im"
],
"type": "string",
"description": "Swim only. 'drill' on every drill or kick rep, else the stroke swum."
},
"durationSec": {
"type": "integer",
"maximum": 86400,
"minimum": 1,
"description": "Seconds (10 min = 600). Required unless distanceMeters is set."
},
"targetHrBpm": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "[low, high] bpm, at least 12 wide; a whole zone is targetHrZone."
},
"targetFtpPct": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "% of FTP, cycling."
},
"targetHrZone": {
"type": "integer",
"maximum": 5,
"minimum": 1,
"description": "Heart-rate zone."
},
"swimEquipment": {
"enum": [
"none",
"fins",
"kickboard",
"paddles",
"pull_buoy",
"snorkel"
],
"type": "string",
"description": "Swim only. Pool toy for this rep; omit if none."
},
"targetCadence": {
"type": "integer",
"maximum": 200,
"minimum": 30,
"description": "Cadence, rpm or steps/min."
},
"distanceMeters": {
"type": "integer",
"maximum": 50000,
"minimum": 1,
"description": "Metres, the alternative to durationSec."
},
"targetHrPctMax": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "% of max heart rate."
},
"targetPowerWatts": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "Power in watts."
},
"targetPaceSecPerKm": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "[low, high] sec/km, at least 15 wide."
}
},
"description": "One step. Needs durationSec or distanceMeters, and one target unless it is a rest or an untargeted distance rep; kind 'ramp' is a cycling %FTP ramp (targetFtpPct [start, end]). Every target is a [low, high] band, never a point."
},
{
"type": "object",
"required": [
"kind",
"format",
"movements"
],
"properties": {
"kind": {
"enum": [
"metcon"
],
"type": "string",
"description": "Conditioning piece."
},
"label": {
"type": "string",
"maxLength": 60,
"description": "Optional name."
},
"capSec": {
"type": "integer",
"maximum": 14400,
"minimum": 1,
"description": "AMRAP: required, the clock in seconds. For Time: the cap. Not on an EMOM."
},
"format": {
"enum": [
"amrap",
"emom",
"for_time"
],
"type": "string",
"description": "AMRAP, EMOM or For Time."
},
"rounds": {
"type": "integer",
"maximum": 100,
"minimum": 1,
"description": "For Time only: rounds (default 1)."
},
"intervals": {
"type": "integer",
"maximum": 60,
"minimum": 1,
"description": "EMOM only, required: how many intervals."
},
"movements": {
"type": "array",
"items": {
"type": "object",
"required": [
"exerciseName"
],
"properties": {
"reps": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "Rep count."
},
"calories": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "Calories (erg)."
},
"weightKg": {
"type": "number",
"maximum": 500,
"minimum": 0,
"description": "Load in kg; omit for bodyweight."
},
"durationSec": {
"type": "integer",
"maximum": 3600,
"minimum": 1,
"description": "Seconds (hold or station)."
},
"exerciseName": {
"type": "string",
"maxLength": 60,
"description": "The movement's name."
},
"distanceMeters": {
"type": "number",
"maximum": 42195,
"minimum": 1,
"description": "Metres."
},
"garminExercise": {
"type": "string",
"maxLength": 120,
"description": "Optional Garmin key; set it when exerciseName is not English."
},
"exerciseCategory": {
"enum": [
"UNKNOWN",
"BENCH_PRESS",
"CALF_RAISE",
"CARDIO",
"CARRY",
"CHOP",
"CORE",
"CRUNCH",
"CURL",
"DEADLIFT",
"FLYE",
"HIP_RAISE",
"HIP_STABILITY",
"HIP_SWING",
"HYPEREXTENSION",
"LATERAL_RAISE",
"LEG_CURL",
"LEG_RAISE",
"LUNGE",
"OLYMPIC_LIFT",
"PLANK",
"PLYO",
"PULL_UP",
"PUSH_UP",
"ROW",
"SHOULDER_PRESS",
"SHOULDER_STABILITY",
"SHRUG",
"SIT_UP",
"SQUAT",
"TOTAL_BODY",
"TRICEPS_EXTENSION",
"WARM_UP"
],
"type": "string",
"description": "Optional Garmin category for a lift."
}
},
"description": "One movement, with ONE count: reps, durationSec, distanceMeters or calories (none on an EMOM = max effort for the interval)."
},
"maxItems": 15,
"minItems": 1
},
"emomPattern": {
"enum": [
"alternate",
"all"
],
"type": "string",
"description": "EMOM: 'alternate' (default) one movement per interval, 'all' the whole list each interval."
},
"intervalSec": {
"type": "integer",
"maximum": 600,
"minimum": 10,
"description": "EMOM only: seconds per interval (default 60)."
},
"restAfterSec": {
"type": "integer",
"maximum": 1800,
"minimum": 0,
"description": "Optional rest after the piece."
}
},
"description": "AMRAP / EMOM / For Time piece as ONE block: its clock plus the movement list, e.g. {kind:'metcon', format:'amrap', capSec:720, movements:[{exerciseName:'Burpee', reps:10}]}."
}
]
},
"minItems": 1
}
},
"description": "Structured intervals JSON: { \"blocks\": [ ... ] }. Each cardio leaf block has kind (warmup/steady/cooldown/rest), durationSec OR distanceMeters, and a target. Range targets accept a single number OR a [low,high] band (a scalar is coerced to [n,n]): targetFtpPct for cycling, targetPowerWatts absolute watts for running-power/Stryd or cycling-by-watts, targetPaceSecPerKm for running, targetHrBpm, or targetHrPctMax for HR; targetHrZone is an integer 1-5. TARGET KEYS SIT DIRECTLY ON THE LEAF BLOCK, never nested under a \"target\" object - a minimal run with a pace band is { \"blocks\": [ { \"kind\":\"warmup\", \"durationSec\":600, \"targetPaceSecPerKm\":[360,390] }, { \"kind\":\"steady\", \"distanceMeters\":6000, \"targetPaceSecPerKm\":[320,330] }, { \"kind\":\"cooldown\", \"durationSec\":600, \"targetPaceSecPerKm\":[360,390] } ] }. A SCALAR IS A FIXED TARGET, NOT A BAND: targetPaceSecPerKm:325 puts a single 5:25/km alert on the watch, so an athlete who asked for 5:20-5:30 needs [320,330]. The zone/%max forms resolve to the athlete's own watch zones on push. Interval blocks have { kind:\"interval\", repeats, work:{...}, rest:{...} }. TARGETS ARE OPTIONAL ON A DISTANCE BLOCK: a block with distanceMeters (and a kind:\"rest\" block) needs NO target at all, and one authored without a target pushes fine — it lands as an OPEN step that counts down the distance and buzzes at nothing. No target is required there, and a deliberately wide \"non-alerting\" band is not the same as none: a 100-185bpm band is still a live alert on the watch. Only a TIME-based non-rest block needs either a target or free_effort:true. SWIMMING: a pool swim is the common case for this — when the athlete wants plain distances and rest periods, author every leaf with distanceMeters and NO targetPaceSecPerKm / targetHrZone / targetHrBpm, and the watch shows lengths only, with no pace or HR alert (verified on Garmin 2026-08-21). Add a pace target only if they asked to swim to a pace. distanceMeters is the pool distance in METRES. If the athlete swims in a YARDS pool, the value is yards multiplied by 0.9144, passed exact rather than floored/truncated (100y=91.44, 200y=182.88, 400y=365.76, 500y=457.2, 1000y=914.4). Passing the fractional value is fine; the server rounds it to the nearest metre (365.76 -> 366), and 366m renders back as 400yd on the watch. Truncating to 365m instead renders as 399yd — one short. STRENGTH sessions (sport:\"Strength\") do NOT use cardio leaf blocks — they use one strength block per exercise: { kind:\"strength\", exerciseCategory (e.g. SQUAT/DEADLIFT/BENCH_PRESS/PLANK/LUNGE/ROW/PULL_UP), exerciseName?, sets, reps OR durationSec (timed holds), weightKg?, restSec? }. Strength blocks take no targetHrZone/targetHrBpm. MOBILITY / YOGA / PILATES sessions (Garmin only) use these same strength blocks, one per movement: durationSec for a hold, reps for a counted drill, no weightKg; stretches go under exerciseCategory WARM_UP (e.g. exerciseName \"Pigeon Pose Stretch\"), core drills under CORE / HIP_STABILITY / PLANK. Do not mix strength and cardio blocks in one session. Same schema as the coach's upsert_plan_session."
}
}
},
"annotations": {
"title": "Update Workout on Watch",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": false,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description95/100
Remove a previously-pushed workout from the athlete's watch - the delete counterpart to push_workout_to_watch. Connected push target(s): whichever of garmin, coros, suunto, apple_watch, zwift the athlete has connected (a call with none connected says how to connect one). Use this when the workout should stop existing. Changing one (different intervals, corrected weight, new title, moved date) is update_workout_on_watch - deleting and re-pushing risks two copies on the calendar if the delete does not land, and on COROS/Terra a refused re-push after a successful delete leaves the athlete with nothing. With "workout_id" (the id push_workout_to_watch returned) this deletes exactly that workout, no search. Without "workout_id" this falls back to a date search: on Garmin that covers workouts pushed from the athlete's PLAN plus one-off pushes sent since October 2026 (Garmin's API has no list-by-date endpoint, so the ids come from our own records - an older one-off push needs its workout_id); on COROS/Terra it reads the day's planned workouts back from the provider. apple_watch: cancels the queued workout; it disappears from the watch on the iOS app's next sync (only ad-hoc pushed workouts can be canceled this way - plan sessions leave the watch by editing the plan). Pass "title" to narrow a date search to matching workouts (case-insensitive substring); omit it to clear every pushed workout on that date. A delete has no undo, so the date (and title, if the athlete named one) decides exactly what is removed. zwift: a "zwift:<id>" workout_id deletes that Zwift schedule, so the ride leaves the athlete's Zwift home screen. Without workout_id the date search covers rides scheduled from the athlete's PLAN only; a one-off Zwift push without its id cannot be removed from here, but it drops off the home screen by itself at the end of its day.
The description explains the capability, when to use it, and what to expect.
Inputs98/100
Input documentation gives an agent enough context to populate the arguments.
date
Day whose pushed workout(s) to remove, YYYY-MM-DD. Still required for the response and the athlete-facing message even when workout_id is given.
Clear enough to supply this argument.
workout_id
The "workout_id" push_workout_to_watch returned for this workout. When given, it deletes exactly that workout with no search, and it is the only thing that can remove an ad-hoc push (those are not plan sessions, so the date search cannot see them).
Clear enough to supply this argument.
title
Only delete workouts whose name contains this (case-insensitive). Ignored when workout_id is given. Omit to delete every pushed workout on that date.
Clear enough to supply this argument.
provider
Which connected push target to use (garmin | coros | suunto | apple_watch | zwift). Omit to use the most-recently-connected watch; when apple_watch is the ONLY target it's used automatically.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "delete_workout_from_watch",
"title": "Delete Workout from Watch",
"description": "Remove a previously-pushed workout from the athlete's watch - the delete counterpart to push_workout_to_watch. Connected push target(s): whichever of garmin, coros, suunto, apple_watch, zwift the athlete has connected (a call with none connected says how to connect one). Use this when the workout should stop existing. Changing one (different intervals, corrected weight, new title, moved date) is update_workout_on_watch - deleting and re-pushing risks two copies on the calendar if the delete does not land, and on COROS/Terra a refused re-push after a successful delete leaves the athlete with nothing. With \"workout_id\" (the id push_workout_to_watch returned) this deletes exactly that workout, no search. Without \"workout_id\" this falls back to a date search: on Garmin that covers workouts pushed from the athlete's PLAN plus one-off pushes sent since October 2026 (Garmin's API has no list-by-date endpoint, so the ids come from our own records - an older one-off push needs its workout_id); on COROS/Terra it reads the day's planned workouts back from the provider. apple_watch: cancels the queued workout; it disappears from the watch on the iOS app's next sync (only ad-hoc pushed workouts can be canceled this way - plan sessions leave the watch by editing the plan). Pass \"title\" to narrow a date search to matching workouts (case-insensitive substring); omit it to clear every pushed workout on that date. A delete has no undo, so the date (and title, if the athlete named one) decides exactly what is removed. zwift: a \"zwift:<id>\" workout_id deletes that Zwift schedule, so the ride leaves the athlete's Zwift home screen. Without workout_id the date search covers rides scheduled from the athlete's PLAN only; a one-off Zwift push without its id cannot be removed from here, but it drops off the home screen by itself at the end of its day.",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"date"
],
"properties": {
"date": {
"type": "string",
"description": "Day whose pushed workout(s) to remove, YYYY-MM-DD. Still required for the response and the athlete-facing message even when workout_id is given."
},
"title": {
"type": "string",
"description": "Only delete workouts whose name contains this (case-insensitive). Ignored when workout_id is given. Omit to delete every pushed workout on that date."
},
"provider": {
"type": "string",
"description": "Which connected push target to use (garmin | coros | suunto | apple_watch | zwift). Omit to use the most-recently-connected watch; when apple_watch is the ONLY target it's used automatically."
},
"workout_id": {
"type": "string",
"description": "The \"workout_id\" push_workout_to_watch returned for this workout. When given, it deletes exactly that workout with no search, and it is the only thing that can remove an ad-hoc push (those are not plan sessions, so the date search cannot see them)."
}
}
},
"annotations": {
"title": "Delete Workout from Watch",
"readOnlyHint": false,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": true
}
}
The title clearly communicates the tool’s action and subject. Keep it concise.
Description92/100
List the workouts athletedata pushed to the athlete's watch that are still there as far as we know - plan sessions and one-off push_workout_to_watch pushes - each with its "workout_id" (pass it to delete_workout_from_watch), date, title, whether its day is over, whether the athlete trained it, and when the automatic cleanup removes it. Call this when the athlete says the watch is full ("maximum number of workouts" / "workout limit exceeded" on Garmin), when Suunto stopped syncing new workouts, when they ask what we sent, or before pushing several workouts at once; then delete the ones whose day is over (stale: true) and push. Covers garmin, coros and suunto. It lists only workouts WE pushed, never ones built in Garmin Connect or another app, and it cannot see the device itself. COROS / Suunto one-off pushes are not in our records: delete_workout_from_watch with a date reads those back from the provider. apple_watch and zwift never accumulate old workouts (the Apple Watch list is rewritten with today and the days ahead on every sync; a Zwift workout shows on its own day only).
The description explains the capability, when to use it, and what to expect.
Inputs97/100
Input documentation gives an agent enough context to populate the arguments.
provider
Only this target (garmin | coros | suunto). Omit for every connected one.
Clear enough to supply this argument.
only_past
true = only workouts whose day is over (what you clear from a full watch). Default false.
No output schema is declared. Add one for structured results so clients can interpret returned fields.
Not declared
Original tool metadata
{
"name": "list_workouts_on_watch",
"title": "List Workouts on Watch",
"description": "List the workouts athletedata pushed to the athlete's watch that are still there as far as we know - plan sessions and one-off push_workout_to_watch pushes - each with its \"workout_id\" (pass it to delete_workout_from_watch), date, title, whether its day is over, whether the athlete trained it, and when the automatic cleanup removes it. Call this when the athlete says the watch is full (\"maximum number of workouts\" / \"workout limit exceeded\" on Garmin), when Suunto stopped syncing new workouts, when they ask what we sent, or before pushing several workouts at once; then delete the ones whose day is over (stale: true) and push. Covers garmin, coros and suunto. It lists only workouts WE pushed, never ones built in Garmin Connect or another app, and it cannot see the device itself. COROS / Suunto one-off pushes are not in our records: delete_workout_from_watch with a date reads those back from the provider. apple_watch and zwift never accumulate old workouts (the Apple Watch list is rewritten with today and the days ahead on every sync; a Zwift workout shows on its own day only).",
"inputSchema": {
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"provider": {
"type": "string",
"description": "Only this target (garmin | coros | suunto). Omit for every connected one."
},
"only_past": {
"type": "boolean",
"description": "true = only workouts whose day is over (what you clear from a full watch). Default false."
}
}
},
"annotations": {
"title": "List Workouts on Watch",
"readOnlyHint": true,
"openWorldHint": false,
"idempotentHint": true,
"destructiveHint": false
}
}
Loading score analytics…
Put your MCP to the test
A few tasks worth trying with this server, chosen from its reviewed tools. These have not been run.
01Get SleepSleep NIGHT BY NIGHT across every connected device - the first call for "how did I sleep", "last night's sleep", "my deep/REM this week", "what time did I go…
02Get Weather ForecastCurrent conditions plus hourly and daily weather forecast for one location.
03WHOOP: Get WorkoutsWHOOP-tracked workouts in a date window: sport, duration, average/max HR, time-in-zone (Z1-Z5), strain (0-21), energy (kJ).
04Garmin: Get Daily SummariesGarmin per-day rollups: steps, active and resting calories, intensity minutes, average stress, body battery charged and drained for the day (`bodyBatteryChar…
05Garmin: Get StressGet Garmin all-day stress + BODY BATTERY, one row per calendar day: average / max stress score (0-100) and high/medium/low/rest minutes, plus `bodyBatteryHig…
Want to run these tasks against your MCP server with us?
athletedata setup, tools, quality score, publishing readiness, and alternatives.
How do I add athletedata to Claude, ChatGPT, or Cursor?
Add athletedata to Claude, ChatGPT, or Cursor by connecting its remote MCP URL in each client's MCP settings. In Claude, add a custom connector. In ChatGPT, add an MCP app. In Cursor, add an MCP server under Settings. The MCP URL is https://mcp.athletedata.health/mcp.
Is athletedata MCP ready for ChatGPT and Claude?
As of November 9, 2026, athletedata scores 100/100 on Manufact's MCP publishing checklist. The scan meets ChatGPT publishing checks and meets Claude publishing checks. Passing these checks does not guarantee marketplace approval.
How clear is athletedata's MCP tool documentation?
As of November 9, 2026, athletedata scores 84/100 in Manufact's review of tool titles, descriptions, input and output schemas, and behavior annotations. The review covers 173 of 173 discovered tools. The quality score measures documentation clarity; runtime behavior and marketplace readiness require separate checks.
What are the best alternatives to athletedata?
As of November 10, 2026, alternatives to athletedata in data-analytics on the Manufact MCP leaderboard include Neon, ClickHouse Cloud MCP Server, Ploti, ClickHouse Cloud MCP Server, and ClickHouse Cloud MCP Server.
How can athletedata improve its MCP documentation?
mywhoosh_push_workout is the lowest-scoring reviewed tool at 74/100. No output schema is declared. Add one for structured results so clients can interpret returned fields. Open its review for feedback and a copyable fix prompt, then rerun the documentation review after updating your tool metadata.
Not your MCP?
Review any public or OAuth MCP server for protocol compatibility, tool documentation, and ChatGPT and Claude publishing readiness. No account required.