{"service":"MyoAmigo MCP tools","endpoint":"https://api-staging.myoamigo.com/mcp","transport":"JSON-RPC 2.0 over HTTP POST","protocolVersion":"2025-06-18","authentication":{"oauth":"https://api-staging.myoamigo.com/.well-known/oauth-protected-resource/mcp","apiKeyFallback":"Authorization: Bearer myo_sk_… — issue one at https://api-staging.myoamigo.com/connect"},"docs":"https://api-staging.myoamigo.com/connect","tools":[{"name":"log_set","title":"Log set","annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"description":"Log one completed set onto the user's synced workout (the app sees it). The `exercise` must EXACTLY match a catalog exercise name/id or one of the user's custom exercises — call get_user_context first if unsure. Nothing is created for an unknown exercise. For a normal set pass `reps` (with `weight`). For a TIMED HOLD (plank, wall sit, dead hang) pass `durationSeconds` and omit `reps` (weight 0 unless it's a weighted hold). For a LOADED CARRY (farmers walk, yoke, sled push) pass `distanceMeters` (optionally with `durationSeconds`) and omit `reps`. Weight defaults to lb — pass `weightUnit: \"kg\"` to log a kilogram set (it round-trips losslessly to the app). By default the set lands on the live/most-recent session; to FIX UP A PAST WORKOUT (e.g. a movement you forgot to log earlier) pass `workoutId` from list_workouts to attach it to that exact session, or pass `date` (ISO-8601) to place it on that day. To avoid double-logging on a retry, pass a stable `idempotencyKey` and REUSE the same value if you call again for the same set.","inputSchema":{"type":"object","properties":{"exercise":{"type":"string","description":"Exact exercise name or id, e.g. 'Barbell Deadlift' or 'Barbell_Deadlift'"},"weight":{"type":"number","minimum":0,"description":"Weight (≥ 0; use 0 for bodyweight movements and unweighted holds). Unit is lb unless weightUnit says otherwise."},"weightUnit":{"type":"string","enum":["lb","kg"],"description":"Unit the `weight` is given in (optional, default 'lb'). The app stores and displays the set in this exact unit."},"reps":{"type":"integer","minimum":1,"description":"Reps completed (≥ 1). Omit for a timed hold or loaded carry — pass durationSeconds and/or distanceMeters instead."},"durationSeconds":{"type":"number","exclusiveMinimum":0,"description":"Hold duration in seconds for a timed isometric exercise (plank/wall sit/dead hang), or the time a carry took. Provide this and/or distanceMeters, OR reps."},"distanceMeters":{"type":"number","exclusiveMinimum":0,"description":"Distance in meters for a loaded carry (farmers walk, yoke walk, sled push). Pair with `weight`; omit reps."},"rir":{"type":"integer","minimum":0,"description":"Reps in reserve (optional, ≥ 0)"},"isWarmup":{"type":"boolean","description":"Whether this was a warmup set (optional, default false)"},"workoutId":{"type":"string","description":"Attach this set to an existing session (its id from list_workouts) instead of the live/most-recent one — use it to add a forgotten movement to a past, already-finished workout."},"date":{"type":"string","description":"ISO-8601 instant the set was performed (optional). Without workoutId, also places the set on that calendar day's session (creating one if needed) so a backfilled set reads on the correct date instead of today."},"idempotencyKey":{"type":"string","description":"Optional stable key identifying this logical set; reuse the exact same value when retrying so a duplicate call doesn't log a second set."}},"required":["exercise","weight"]}},{"name":"get_history","title":"Get exercise history","annotations":{"readOnlyHint":true,"openWorldHint":false},"description":"Recent logged sets for an exercise, most recent first. Unlike the write tools (log_set), read tools resolve the `exercise` LENIENTLY — a close/colloquial name or a partial match still works (it reports how it interpreted an inexact match). Optionally bound the window with `since`/`before` (ISO-8601; inclusive-start, exclusive-end). A timed hold renders as its duration (e.g. '45s hold').","inputSchema":{"type":"object","properties":{"exercise":{"type":"string","description":"Exercise name or id (matched leniently)"},"limit":{"type":"integer","minimum":1,"maximum":100,"description":"Max rows (default 10, max 100)"},"since":{"type":"string","description":"Only sets performed at/after this ISO-8601 instant (inclusive)."},"before":{"type":"string","description":"Only sets performed strictly before this ISO-8601 instant (exclusive)."}},"required":["exercise"]}},{"name":"get_pr","title":"Get personal record","annotations":{"readOnlyHint":true,"openWorldHint":false},"description":"Personal record for an exercise: heaviest working set and best Epley e1RM, plus the longest single timed hold (seconds, for planks/wall sits/dead hangs). A load-less hold-only exercise reports just the hold. Like get_history, the `exercise` is resolved LENIENTLY (a close/colloquial name works), unlike the write tools which require an exact match. Mixed-unit logs are compared correctly (a 100 kg set outranks a 200 lb one).","inputSchema":{"type":"object","properties":{"exercise":{"type":"string","description":"Exercise name or id (matched leniently)"}},"required":["exercise"]}},{"name":"get_user_context","title":"Get user context","annotations":{"readOnlyHint":true,"openWorldHint":false},"description":"A dense snapshot of the user so you can act correctly: recent workouts, the user's exercise vocabulary (the exact ids/names valid for log_set), and per-exercise estimated 1RM, recent working ranges, and PRs. Call this before logging if you're unsure of the exact exercise identifier.","inputSchema":{"type":"object","properties":{}}},{"name":"search_exercises","title":"Search exercise catalog","annotations":{"readOnlyHint":true,"openWorldHint":false},"description":"Browse the built-in exercise catalog (891 movements) to find the EXACT id/name to pass to create_routine / log_set. Fuzzy `query` matches name, alternate names (aliases like 'skull crusher' → 'Lying Dumbbell Tricep Extension'), equipment, and muscles, tolerating typos and word order. Filter by target `muscle` (e.g. 'chest', 'quadriceps') and/or `equipment` (e.g. 'barbell', 'dumbbell', 'cable'). Use this whenever you want to program a movement the user hasn't logged before — get_user_context only covers what they've already done. Returns id | name | equipment | primary muscles (+ aka aliases when present).","inputSchema":{"type":"object","properties":{"query":{"type":"string","description":"Name substring, e.g. 'press' or 'romanian deadlift'"},"muscle":{"type":"string","description":"Filter by a primary muscle, e.g. 'chest', 'hamstrings', 'lats'"},"equipment":{"type":"string","description":"Filter by equipment, e.g. 'barbell', 'dumbbell', 'cable', 'body only'"},"limit":{"type":"integer","description":"Max results (default 20, max 100)"}}}},{"name":"create_exercise","title":"Create a custom exercise","annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"description":"Create a CUSTOM exercise when the movement you want isn't in the built-in catalog. ALWAYS call search_exercises first — if it's already in the catalog, use that instead (this tool rejects a name that matches a catalog exercise, and a name matching one of the user's existing customs). Only `name` is required; everything else is optional with sensible defaults (level=beginner, category=strength, trackingType=weightReps). Once created it syncs to the app and is immediately usable by log_set / create_routine (pass its name or id exactly). For non-weight×reps movements set `trackingType`: reps (bodyweight), duration (timed hold), distanceDuration (cardio), or weightedCarry (loaded carry — weight over distance). To avoid creating a duplicate on a retry, pass a stable `idempotencyKey` and REUSE the same value if you call again for the same exercise.","inputSchema":{"type":"object","properties":{"name":{"type":"string","description":"Display name, e.g. 'Spoto Press', 'Copenhagen Plank'"},"idempotencyKey":{"type":"string","description":"Optional stable key identifying this logical exercise; reuse the exact same value when retrying so a duplicate call doesn't create a second copy."},"primaryMuscles":{"type":"array","items":{"type":"string"},"description":"Primary muscles worked, e.g. ['chest','triceps'] (optional)"},"secondaryMuscles":{"type":"array","items":{"type":"string"},"description":"Secondary muscles, e.g. ['shoulders'] (optional)"},"equipment":{"type":"string","description":"Equipment, e.g. 'barbell', 'dumbbell', 'cable', 'body only' (optional)"},"force":{"type":"string","description":"'push', 'pull', or 'static' (optional)"},"mechanic":{"type":"string","description":"'compound' or 'isolation' (optional)"},"level":{"type":"string","description":"'beginner', 'intermediate', or 'expert' (optional, default 'beginner')"},"category":{"type":"string","description":"e.g. 'strength', 'cardio', 'stretching' (optional, default 'strength')"},"instructions":{"type":"array","items":{"type":"string"},"description":"Step-by-step instructions (optional)"},"trackingType":{"type":"string","enum":["weightReps","reps","duration","distanceDuration","weightedCarry"],"description":"How the movement is tracked (optional, default 'weightReps')"}},"required":["name"]}},{"name":"delete_exercise","title":"Delete a custom exercise","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"description":"Delete one of the user's CUSTOM exercises (soft delete, syncs to the app/watch). Only user-created customs can be deleted — a built-in catalog exercise is refused (it's regenerated identically on every device). Past workouts that logged the custom are untouched (their sets keep resolving by id); only the custom definition goes. Pass the custom's id (a `custom-…` id from get_user_context / search_exercises). Irreversible from this surface — there is no undelete tool.","inputSchema":{"type":"object","properties":{"exerciseId":{"type":"string","description":"The custom exercise's id (a 'custom-…' id from get_user_context or search_exercises)"}},"required":["exerciseId"]}},{"name":"list_workouts","title":"List workouts","annotations":{"readOnlyHint":true,"openWorldHint":false},"description":"List the user's COMPLETED workouts (most recent first) with full per-exercise set detail (weight × reps or timed hold, RIR, warmups). Use this to analyze training history before programming. Defaults to completed sessions only; pass includeInProgress to also see a session still being logged. Optionally bound the window with `since`/`before` (ISO-8601; inclusive-start, exclusive-end).","inputSchema":{"type":"object","properties":{"limit":{"type":"integer","minimum":1,"maximum":100,"description":"Max workouts (default 10, max 100)"},"includeInProgress":{"type":"boolean","description":"Include the in-progress session too (default false)"},"since":{"type":"string","description":"Only workouts dated at/after this ISO-8601 instant (inclusive)."},"before":{"type":"string","description":"Only workouts dated strictly before this ISO-8601 instant (exclusive)."}}}},{"name":"get_export","title":"Export all data","annotations":{"readOnlyHint":true,"openWorldHint":false},"description":"Bulk-export the user's full synced history as wire-contract JSON — EVERY category that syncs (workouts + exercises + sets, routines + items, weekly plans + days, body metrics, cardio, custom exercises, gyms, settings), so nothing is left behind. Cursor-paginated: call with no cursor for the first page, then pass back the returned `cursor` while `hasMore` is true. The machine-readable records are in structuredContent.changes (keyed by category); the text is a per-category count summary. The anti-lock-in path for quantified-self users.","inputSchema":{"type":"object","properties":{"cursor":{"type":"string","description":"Opaque pagination cursor from a prior get_export response; omit for the first page."},"limit":{"type":"integer","minimum":1,"maximum":1000,"description":"Max records per page (default 500, max 1000)."}}}},{"name":"finish_workout","title":"Finish workout","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"description":"Mark a workout session as COMPLETED (stamps its finishedAt). Use this after logging the final set of a session you built with log_set, so it counts as a completed workout in the app's history and stats. Get the session id from a log_set response (the `session …` it returns) or from list_workouts({ includeInProgress: true }). Idempotent: finishing an already-finished session is a no-op.","inputSchema":{"type":"object","properties":{"sessionId":{"type":"string","description":"The workout session id (from a log_set response or list_workouts)"}},"required":["sessionId"]}},{"name":"delete_workout","title":"Delete workout","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"description":"Delete a workout session and everything in it — its exercises and sets (soft delete, same cascade as deleting a workout in the app). History, PRs, and stats drop the sets immediately, and the deletion syncs to the app/watch. Use to remove a mis-logged or test session. Get the id from list_workouts({ includeInProgress: true }) or a log_set response. Irreversible from this surface — there is no undelete tool.","inputSchema":{"type":"object","properties":{"sessionId":{"type":"string","description":"The workout session id (from list_workouts or a log_set response)"}},"required":["sessionId"]}},{"name":"update_workout","title":"Edit a workout","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"description":"Edit a workout session's own fields — its `date`, `notes`, `rating` (1 = thumbs-up), and/or `gymId` — WITHOUT touching its exercises or sets. Provide at least one field; omitted fields keep their current value (pass `notes`/`rating`/`gymId` as null to clear them). Works on a completed or in-progress session; the edit keeps the session's identity and syncs to the app/watch. Get the `sessionId` from list_workouts (\"[id: <id>]\") or a log_set response. To change the SETS in a workout use update_set / log_set / delete_set; to move a set to a different day use its `date` here or re-log it.","inputSchema":{"type":"object","properties":{"sessionId":{"type":"string","description":"The workout session id (from list_workouts or a log_set response)"},"date":{"type":"string","description":"New ISO-8601 date/time for the session (optional)."},"notes":{"type":["string","null"],"description":"New session notes, or null to clear (optional)."},"rating":{"type":["integer","null"],"description":"Lifter's verdict: 1 = thumbs-up, or null to clear (optional)."},"gymId":{"type":["string","null"],"description":"The Gym.id this session was performed at, or null to clear (optional)."}},"required":["sessionId"]}},{"name":"update_set","title":"Edit a logged set","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"description":"Correct a previously logged set — change its weight, reps, RIR, warmup flag, unit, and/or hold duration. Provide at least one field; omitted fields keep their current value (pass `rir: null` to clear an RIR). `weight` is interpreted in the set's OWN entered unit unless you also pass `weightUnit` to re-unit it. To fix a mis-logged timed hold pass `durationSeconds` (or `durationSeconds: null` to turn it back into a normal weight×reps set). The set keeps its identity, exercise, and place in the session; the edit syncs to the app/watch and re-counts in history/PRs/stats. Get the `setId` from list_workouts (each set renders as \"…×… [set <id>]\") or from a log_set response. To change which exercise a set belongs to, delete it and log_set under the right exercise instead.","inputSchema":{"type":"object","properties":{"setId":{"type":"string","description":"The set id (from list_workouts — shown as \"[set <id>]\" — or a log_set response)"},"weight":{"type":"number","minimum":0,"description":"New weight in the set's entered unit (≥ 0; pass weightUnit to change the unit). Optional."},"weightUnit":{"type":"string","enum":["lb","kg"],"description":"Re-unit the set to lb or kg (optional; default keeps the set's current unit)."},"reps":{"type":"integer","minimum":1,"description":"New rep count (≥ 1; optional)"},"rir":{"type":["integer","null"],"minimum":0,"description":"New reps-in-reserve (≥ 0), or null to clear it (optional)"},"isWarmup":{"type":"boolean","description":"New warmup flag (optional)"},"durationSeconds":{"type":["number","null"],"exclusiveMinimum":0,"description":"New timed-hold duration in seconds, or null to clear it (turns it back into a weight×reps set). Optional."},"distanceMeters":{"type":["number","null"],"exclusiveMinimum":0,"description":"New carry distance in meters, or null to clear it. Optional."}},"required":["setId"]}},{"name":"reorder_exercises","title":"Reorder exercises in a workout","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false},"description":"Reorder the exercises within an IN-PROGRESS workout. Pass the session's WorkoutExercise ids in the new order — they're rewritten in place (each set keeps its identity; nothing is deleted or recreated) and the new order syncs to the app/watch. In-progress sessions ONLY: a finished workout is rejected. Call list_workouts({ includeInProgress: true }) to get the in-progress session id and its exercises' current order — each exercise renders its handle as \"[ex <id>]\". `exerciseIds` must be an EXACT reordering of the session's current exercises (same ids, same count, none added or omitted). If the new order splits a superset apart (its members are no longer adjacent), that superset is automatically broken (its grouping cleared) and the response says which exercises were affected.","inputSchema":{"type":"object","properties":{"sessionId":{"type":"string","description":"The in-progress workout session id (from list_workouts({ includeInProgress: true }))"},"exerciseIds":{"type":"array","minItems":1,"items":{"type":"string"},"description":"The session's WorkoutExercise ids (the \"[ex <id>]\" handles from list_workouts) in the desired order — an exact reordering of the current set."}},"required":["sessionId","exerciseIds"]}},{"name":"delete_set","title":"Delete a logged set","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"description":"Delete a single mis-logged set (soft delete, same as removing one set in the app). History, PRs, and stats drop it immediately and the deletion syncs to the app/watch; the rest of the workout is untouched. Get the `setId` from list_workouts (each set renders as \"…×… [set <id>]\") or a log_set response. To remove an entire session, use delete_workout instead. Irreversible from this surface — there is no undelete tool.","inputSchema":{"type":"object","properties":{"setId":{"type":"string","description":"The set id (from list_workouts — shown as \"[set <id>]\" — or a log_set response)"}},"required":["setId"]}},{"name":"log_cardio","title":"Log cardio activity","annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"description":"Log a cardio session (run/ride/swim/…) onto the user's synced model (the app sees it). `activityType` is a name (Cycling, Elliptical, Hiking, Rowing, Running, Stair Climbing, Swimming, Walking, HIIT, Other) or a raw HKWorkoutActivityType integer. Distance is in METERS and duration in SECONDS (canonical; the app converts to mi/km for display) — both optional, but provide at least one. To avoid double-logging on a retry, pass a stable `idempotencyKey` and REUSE it if you call again for the same activity.","inputSchema":{"type":"object","properties":{"activityType":{"type":"string","description":"Activity type name (e.g. 'running', 'cycling') or a raw HK integer"},"distanceMeters":{"type":"number","exclusiveMinimum":0,"description":"Distance in meters (optional, > 0)"},"durationSeconds":{"type":"number","exclusiveMinimum":0,"description":"Duration in seconds (optional, > 0)"},"notes":{"type":"string","description":"Optional notes"},"date":{"type":"string","description":"ISO-8601 date/time of the activity (optional, defaults to now)"},"idempotencyKey":{"type":"string","description":"Stable key; reuse on retry so a duplicate call doesn't log twice."}},"required":["activityType"]}},{"name":"get_cardio","title":"List cardio activities","annotations":{"readOnlyHint":true,"openWorldHint":false},"description":"List ALL the user's cardio, newest first — both standalone cardio activities (runs/rides/swims…) AND cardio logged INSIDE a strength workout (a distance/duration exercise like a treadmill or rower block). Each row: date | type | distance · time · pace (distance rounded to miles, time as H:MM:SS, pace /mi); in-workout entries are tagged with their parent workout id. The structuredContent mirror carries canonical meters/seconds plus `source` ('activity' | 'inWorkout') and `workoutId`.","inputSchema":{"type":"object","properties":{"limit":{"type":"integer","description":"Max rows (default 30, max 500)"}}}},{"name":"get_body_metrics","title":"Get body metrics","annotations":{"readOnlyHint":true,"openWorldHint":false},"description":"Read the user's logged body metrics, newest first — the coaching context the workout tools can't see (relative strength, cut/bulk progress). Covers bodyweight, body-fat % (logged in the app or imported from Apple Health, type 'bodyFatPercent', unit '%'), and custom tape measurements. Optionally filter by `type` (e.g. 'bodyweight', 'bodyFatPercent', or a measurement name). Returns date | type | value unit.","inputSchema":{"type":"object","properties":{"type":{"type":"string","description":"Filter to one metric type, e.g. 'bodyweight' (optional — omit for all)"},"limit":{"type":"integer","description":"Max rows, newest first (default 30, max 500)"}}}},{"name":"log_body_metric","title":"Log body metric","annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"description":"Log a body metric onto the user's synced model (the app sees it): `type` 'bodyweight' (unit lb or kg), 'bodyFatPercent' (unit '%', matching the app/Apple Health), or 'measurement' (a tape measurement, unit in or cm, with a `name` for the site — e.g. 'Waist', 'Chest', 'Hips', 'Left Bicep'; reuse the user's existing names from get_body_metrics so trends stay on one chart). The value is stored verbatim in its entered unit. The coach and strength standards prefer a logged bodyweight, so log one when the user tells you their weight. To avoid double-logging on a retry, pass a stable `idempotencyKey` and REUSE it if you call again for the same sample.","inputSchema":{"type":"object","properties":{"type":{"type":"string","enum":["bodyweight","bodyFatPercent","measurement"],"description":"'bodyweight', 'bodyFatPercent', or 'measurement'"},"value":{"type":"number","exclusiveMinimum":0,"description":"The reading, in `unit` (stored verbatim)"},"unit":{"type":"string","enum":["lb","kg","%","in","cm"],"description":"lb/kg for bodyweight; '%' for bodyFatPercent; in/cm for measurements"},"name":{"type":"string","description":"Measurement site (required for type 'measurement', e.g. 'Waist'); omit for bodyweight/bodyFatPercent"},"date":{"type":"string","description":"ISO-8601 date/time of the sample (optional, defaults to now)"},"idempotencyKey":{"type":"string","description":"Stable key; reuse on retry so a duplicate call doesn't log twice."}},"required":["type","value","unit"]}},{"name":"delete_body_metric","title":"Delete a body metric","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"description":"Delete a single mis-logged body metric (soft delete, same as removing one in the app) — a bodyweight, body-fat, or measurement reading. Trends drop it immediately and the deletion syncs to the app/watch. Get the `metricId` (the `[id: <id>]`) from get_body_metrics. Irreversible from this surface — there is no undelete tool.","inputSchema":{"type":"object","properties":{"metricId":{"type":"string","description":"The body-metric id (the \"[id: <id>]\" from get_body_metrics)"}},"required":["metricId"]}},{"name":"delete_cardio","title":"Delete a cardio activity","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"description":"Delete a single mis-logged standalone cardio activity (a run/ride/swim you logged with log_cardio; soft delete, same as removing it in the app). It drops from get_cardio immediately and the deletion syncs to the app/watch. Get the `cardioId` (the `[id: <id>]`) from get_cardio — only standalone activities (source 'activity') are deletable here; to remove cardio logged INSIDE a strength workout, use delete_set / delete_workout on that session instead. Irreversible from this surface — there is no undelete tool.","inputSchema":{"type":"object","properties":{"cardioId":{"type":"string","description":"The cardio activity id (the \"[id: <id>]\" from get_cardio, source 'activity')"}},"required":["cardioId"]}},{"name":"get_stats","title":"Get training stats","annotations":{"readOnlyHint":true,"openWorldHint":false},"description":"Aggregate training stats across all the user's logged working sets: per-exercise estimated 1RM / heaviest / total volume, total volume by muscle, and a weekly volume trend (all weights normalized to lb). The dense analytics view for spotting progress, imbalances, and fatigue before programming.","inputSchema":{"type":"object","properties":{}}},{"name":"list_routines","title":"List routines","annotations":{"readOnlyHint":true,"openWorldHint":false},"description":"List the user's training routines (programs) with each routine's id, name, and ordered exercises (exact exercise id/name + target sets, rep range, and rest). Call before update_routine to get the routine id and the current item list.","inputSchema":{"type":"object","properties":{}}},{"name":"create_routine","title":"Create routine","annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"description":"Author a NEW training routine (program) for the user — the agent's primary write capability. Each exercise must EXACTLY match a catalog name/id or one of the user's customs (call get_user_context if unsure); an unknown exercise rejects the whole call (nothing is created). Per-exercise prescription is optional and defaults to 3 sets × 8–12 reps, 90s rest. The routine syncs to the app and watch, ready to start. To avoid duplicate routines on a retry, pass a stable `idempotencyKey` and REUSE the same value if you call again for the same routine.","inputSchema":{"type":"object","properties":{"name":{"type":"string","maxLength":200,"description":"Routine name, e.g. 'Push Day A' (max 200 chars)"},"notes":{"type":"string","description":"Optional coaching notes for the routine"},"idempotencyKey":{"type":"string","description":"Optional stable key identifying this logical routine; reuse the exact same value when retrying so a duplicate call doesn't create a second copy."},"exercises":{"type":"array","description":"Ordered exercises in the routine (at least one).","items":{"type":"object","properties":{"exercise":{"type":"string","description":"Exact exercise name or id, e.g. 'Barbell Bench Press'"},"targetSets":{"type":"integer","minimum":1,"description":"Working sets (default 3)"},"repLow":{"type":"integer","minimum":1,"description":"Low end of the rep range (default 8; ≤ repHigh)"},"repHigh":{"type":"integer","minimum":1,"description":"High end of the rep range (default 12; ≥ repLow)"},"restSeconds":{"type":"integer","minimum":0,"description":"Rest between sets in seconds (default 90)"},"notes":{"type":"string","description":"Optional per-exercise coaching note (e.g. 'last set to failure', 'tempo 3-1-3', 'RPE 8')"}},"required":["exercise"]}}},"required":["name","exercises"]}},{"name":"update_routine","title":"Update routine","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"description":"Revise an existing routine (get its id from list_routines). Optionally rename (`name`) and/or re-note (`notes`). If `exercises` is given it REPLACES the routine's full exercise list (old items removed, new ordered list written) — use it to reprogram a block. Same exact-match rule as create_routine: an unknown exercise rejects the whole call.","inputSchema":{"type":"object","properties":{"routineId":{"type":"string","description":"The routine's id (from list_routines)"},"name":{"type":"string","maxLength":200,"description":"New name (optional, max 200 chars)"},"notes":{"type":"string","description":"New notes (optional)"},"exercises":{"type":"array","description":"Optional FULL replacement of the routine's ordered exercises.","items":{"type":"object","properties":{"exercise":{"type":"string","description":"Exact exercise name or id"},"targetSets":{"type":"integer","minimum":1,"description":"Working sets (default 3)"},"repLow":{"type":"integer","minimum":1,"description":"Low end of the rep range (default 8; ≤ repHigh)"},"repHigh":{"type":"integer","minimum":1,"description":"High end of the rep range (default 12; ≥ repLow)"},"restSeconds":{"type":"integer","minimum":0,"description":"Rest between sets in seconds (default 90)"},"notes":{"type":"string","description":"Optional per-exercise coaching note (e.g. 'last set to failure', 'tempo 3-1-3', 'RPE 8')"}},"required":["exercise"]}}},"required":["routineId"]}},{"name":"delete_routine","title":"Delete routine","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"description":"Delete a routine and its exercise list (soft delete, same as deleting a routine in the app — past workouts logged from it are untouched). Get the id from list_routines. If a weekly plan still schedules the routine, the plan keeps the slot and the response names the affected plans so you can revise them with update_plan. Irreversible from this surface — there is no undelete tool.","inputSchema":{"type":"object","properties":{"routineId":{"type":"string","description":"The routine's id (from list_routines)"}},"required":["routineId"]}},{"name":"list_plans","title":"List weekly plans","annotations":{"readOnlyHint":true,"openWorldHint":false},"description":"List the user's weekly plans (which routine runs on which weekday). At most one plan is ACTIVE — it steers the app's Train tab, widget, and nudges. Each plan shows its id, weekday→routine schedule, missed-day policy, and — when it was produced by the app's plan generator — the generating request (goal, days/week, experience, equipment). Call before update_plan/delete_plan to get ids.","inputSchema":{"type":"object","properties":{}}},{"name":"create_plan","title":"Create weekly plan","annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"description":"Put a weekly schedule on the user's calendar: map routines onto weekdays (1 = Monday … 7 = Sunday). Each routine must be the user's own — pass a routine id or exact name from list_routines (create routines first with create_routine if needed). Activates by default, replacing whatever plan was steering the app (pass activate: false to save without scheduling). Syncs to the app and watch; missed days default to catching up. `name` is capped at 200 characters and `notes` at 2000. To avoid duplicate plans on a retry, pass a stable `idempotencyKey` and REUSE the same value if you call again for the same plan.","inputSchema":{"type":"object","properties":{"name":{"type":"string","maxLength":200,"description":"Plan name, e.g. 'Push/Pull/Legs' (max 200 chars)"},"days":{"type":"array","description":"The weekday→routine slots (at least one). Unlisted weekdays are rest days.","items":{"type":"object","properties":{"weekday":{"type":"integer","minimum":1,"maximum":7,"description":"ISO weekday: 1 = Monday … 7 = Sunday"},"routine":{"type":"string","description":"Routine id or exact name (from list_routines)"}},"required":["weekday","routine"]}},"missedPolicy":{"type":"string","enum":["catchUp","skip"],"description":"What a missed day becomes: 'catchUp' (default — it leads the week until done) or 'skip' (strict calendar)."},"activate":{"type":"boolean","description":"Make this the active plan (default true; deactivates any other)."},"notes":{"type":"string","maxLength":2000,"description":"Optional coaching notes for the plan (max 2000 chars)"},"idempotencyKey":{"type":"string","description":"Optional stable key identifying this logical plan; reuse the exact same value when retrying so a duplicate call doesn't create a second copy."}},"required":["name","days"]}},{"name":"update_plan","title":"Update weekly plan","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"description":"Revise a weekly plan (get its id from list_plans). Optionally rename, re-note, change the missed-day policy, REPLACE the full weekday→routine schedule (`days` swaps the whole week), and/or flip `isActive` (activating deactivates every other plan; deactivating leaves the app on rotation inference). `name` is capped at 200 characters and `notes` at 2000.","inputSchema":{"type":"object","properties":{"planId":{"type":"string","description":"The plan's id (from list_plans)"},"name":{"type":"string","maxLength":200,"description":"New name (optional, max 200 chars)"},"notes":{"type":"string","maxLength":2000,"description":"New notes (optional, max 2000 chars)"},"missedPolicy":{"type":"string","enum":["catchUp","skip"],"description":"New missed-day policy (optional)"},"days":{"type":"array","description":"Optional FULL replacement of the weekday→routine slots.","items":{"type":"object","properties":{"weekday":{"type":"integer","minimum":1,"maximum":7,"description":"ISO weekday: 1 = Monday … 7 = Sunday"},"routine":{"type":"string","description":"Routine id or exact name (from list_routines)"}},"required":["weekday","routine"]}},"isActive":{"type":"boolean","description":"Activate (true) or deactivate (false) this plan (optional)"}},"required":["planId"]}},{"name":"delete_plan","title":"Delete weekly plan","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"description":"Delete a weekly plan (soft delete — the routines it scheduled stay in the user's list, only the weekly schedule goes). Get the id from list_plans.","inputSchema":{"type":"object","properties":{"planId":{"type":"string","description":"The plan's id (from list_plans)"}},"required":["planId"]}},{"name":"generate_plan","title":"Generate a training plan","annotations":{"readOnlyHint":true,"openWorldHint":false},"description":"PROPOSE a multi-day training plan for the user with the app's own plan generator — the same engine onboarding uses. Read-only: it returns a suggested week (day names, exercise picks, sets × rep range, rest) tailored to `goal` and `daysPerWeek`, ranking the catalog against the user's own history (so familiar lifts and sensible progressions surface), and persists NOTHING. For lifts the user has never logged it includes a provenance-labelled starting weight band (in lb, like the rest of the surface; the structuredContent mirror carries canonical kg). Use it to draft a program to discuss; to actually schedule it, persist the routines with create_routine and the weekly schedule with create_plan.","inputSchema":{"type":"object","properties":{"goal":{"type":"string","enum":["strength","hypertrophy","general"],"description":"Training goal (drives set/rep/rest scheme)."},"daysPerWeek":{"type":"integer","minimum":1,"maximum":6,"description":"Training days per week (1–6); sets the split."},"equipment":{"type":"array","items":{"type":"string"},"description":"Lowercased equipment the user can access, e.g. ['barbell','dumbbell','cable']. Omit/empty = unconstrained ('body only' is always allowed)."},"experience":{"type":"string","enum":["beginner","intermediate","advanced"],"description":"Training experience (optional, defaults to beginner)."},"avoidExerciseIds":{"type":"array","items":{"type":"string"},"description":"Exercise ids to exclude (optional), e.g. from search_exercises."},"avoidMuscles":{"type":"array","items":{"type":"string"},"description":"Primary-muscle strings to avoid for injury (optional), e.g. ['lower back']."},"preferFullBody":{"type":"boolean","description":"Force Full Body A/B/C at 1–3 days/week instead of the default split (optional)."}},"required":["goal","daysPerWeek"]}},{"name":"get_strength_standards","title":"Get strength standards","annotations":{"readOnlyHint":true,"openWorldHint":false},"description":"Rate the user's big barbell lifts (squat / bench / deadlift / overhead press) against sex-specific population strength standards: bodyweight-relative ratio, percentile (1–99), and a Beginner→Elite level — the same assessment the app's Progress screen shows. Needs a logged bodyweight (see get_body_metrics). Sex isn't synced, so pass `sex` for accuracy; it defaults to male with a note. Only clean competition-style barbell variants count (an incline bench / RDL won't).","inputSchema":{"type":"object","properties":{"sex":{"type":"string","enum":["male","female"],"description":"Biological sex for the standard (defaults to male if omitted)."}}}},{"name":"get_muscle_balance","title":"Get muscle balance","annotations":{"readOnlyHint":true,"openWorldHint":false},"description":"Per-muscle weekly working-set volume vs evidence-based hypertrophy target bands (à la Renaissance Periodization MEV–MAV), plus days since each muscle was last trained — for ALL muscle groups, so neglected ones surface too. Each muscle is labeled untrained / under / optimal / high. The coaching view for spotting imbalances and under/over-training before programming.","inputSchema":{"type":"object","properties":{}},"_meta":{"ui":{"resourceUri":"ui://myoamigo/muscle-balance.html"},"ui/resourceUri":"ui://myoamigo/muscle-balance.html"}},{"name":"get_training_consistency","title":"Get training consistency","annotations":{"readOnlyHint":true,"openWorldHint":false},"description":"Training cadence at a glance: sessions this week and this month, the current consecutive-week streak, average sessions/week, and the last 8 weeks' session counts. Use to coach consistency ('you've trained 4 weeks straight' vs 'let's rebuild the habit').","inputSchema":{"type":"object","properties":{}},"_meta":{"ui":{"resourceUri":"ui://myoamigo/training-consistency.html"},"ui/resourceUri":"ui://myoamigo/training-consistency.html"}},{"name":"get_progress","title":"Get progress","annotations":{"readOnlyHint":true,"openWorldHint":false},"description":"Progress over time, computed from history: recent PRs (estimated-1RM, top-weight, rep-at-a-held-load, and timed-hold records — a rep PR means double progression is working), stalls (lifts with NO qualifying PR of any kind in several sessions, classified regressing/holding/rebuilding — 'rebuilding' means e1RM is climbing back after a deliberate load drop, not a plateau), related-lift context (a close variant PRing or trending up while a lift is flagged), and a per-exercise trend snapshot (current e1RM, all-time best, direction over the stated span, recent e1RM series). The signal for spotting what's moving and what's stuck.","inputSchema":{"type":"object","properties":{}},"_meta":{"ui":{"resourceUri":"ui://myoamigo/progress.html"},"ui/resourceUri":"ui://myoamigo/progress.html"}},{"name":"get_week_review","title":"Get week review","annotations":{"readOnlyHint":true,"openWorldHint":false},"description":"The coach's week-in-review digest for one training week (Monday–Sunday): sessions / working sets / tonnage with the prior-week trend, adherence to the active weekly plan (which scheduled days slipped), PR events that landed in the week, per-muscle weekly working sets vs the evidence bands, and 1–3 dry, evidence-cited focus pointers for next week. Honest by design — it reports what the log shows and never cheers. `week`: 'last' (default — the most recently completed week, the natural Monday read) or 'current' (the in-flight week; open days are not counted as missed and a clean read is explicitly provisional).","inputSchema":{"type":"object","properties":{"week":{"type":"string","enum":["last","current"],"description":"'last' (default) = the most recently completed Mon–Sun week; 'current' = the week in progress."}}},"_meta":{"ui":{"resourceUri":"ui://myoamigo/week-review.html"},"ui/resourceUri":"ui://myoamigo/week-review.html"}},{"name":"assess_session","title":"Assess session","annotations":{"readOnlyHint":true,"openWorldHint":false},"description":"An honest, evidence-based verdict on a finished session — measured against the user's OWN history (their bests, their last session, their personal volume norm). Returns a tier (breakthrough / strong / solid / light / empty), a headline + subtext, and the evidence behind it. Deliberately not generic praise: a PR is celebrated, a light/sandbagged day gets a candid nudge. Defaults to the most recent finished session; pass `sessionId` (from list_workouts) for a specific one.","inputSchema":{"type":"object","properties":{"sessionId":{"type":"string","description":"A specific session id (from list_workouts). Omit for the most recent finished session."}}}},{"name":"estimate_starting_weight","title":"Estimate starting weight","annotations":{"readOnlyHint":true,"openWorldHint":false},"description":"Estimate a sensible STARTING working weight + rep range for an exercise the user has NEVER logged, by transferring strength from the most similar lift they HAVE logged. Returns the band (lb), an estimated 1RM, the donor lift it's based on, and a confidence — an estimate with provenance, deliberately conservative (warm up toward it, never auto-load). For a lift the user already trains, use get_history / get_pr instead. `exercise` must EXACTLY match a catalog name/id or one of the user's customs.","inputSchema":{"type":"object","properties":{"exercise":{"type":"string","description":"Exact exercise name or id of the un-logged target, e.g. 'Incline Dumbbell Press'"}},"required":["exercise"]}},{"name":"set_profile","title":"Set profile","annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"description":"Set the user's profile inputs for strength standards — biological `sex` ('male'/'female') and/or `bodyweightLb` — on their synced cloud profile. Use this when standards can't be computed because sex or bodyweight is missing (e.g. get_strength_standards reports no bodyweight), or to correct them. IMPORTANT: this writes only to the cloud profile used here for analytics. It does NOT write to Apple Health, and it does not change what the iOS app displays; the app pushes its own sex from the device and a logged bodyweight syncs separately, either of which can later supersede what you set. Prefer values the user explicitly states.","inputSchema":{"type":"object","properties":{"sex":{"type":"string","enum":["male","female"],"description":"Biological sex for sex-specific standards."},"bodyweightLb":{"type":"number","exclusiveMinimum":0,"description":"Bodyweight in lb (> 0). Stored in kg on the profile."}}}},{"name":"submit_feedback","title":"Submit feedback","annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"description":"Send feedback, a bug report, a feature request, or a support question to the MyoAmigo team on the user's behalf. Use this only when the user explicitly asks to send feedback or report a problem — relay their words; do not invent feedback. The message is saved and the team is notified by email. Returns a reference id.","inputSchema":{"type":"object","properties":{"message":{"type":"string","description":"The feedback / report / question, in the user's words (required, ≤ 5000 chars)."},"category":{"type":"string","enum":["bug","feature","question","other"],"description":"What kind of feedback this is (default 'other')."},"subject":{"type":"string","description":"Optional short subject line (≤ 200 chars)."},"contactEmail":{"type":"string","description":"Optional reply-to address if the user wants a response somewhere other than their account email."}},"required":["message"]}}]}