MCP App Store
by KeenEthics
Leo Training icon

Leo Training

By Leo Training

Get New App Alerts

Overview

Leo is a running coach you talk to. Connect your Strava or Suunto account once, and Leo builds a periodized training plan around your goal race, then adapts it week after week based on your completed workouts and how you feel — no extra sensors needed. Ask Leo to review your last run, adjust this week's sessions around your schedule, estimate your race time, or explain the reasoning behind a workout. Every recommendation is grounded in peer-reviewed sports science, and Leo remembers your training history, so its coaching stays consistent over time. Requires a Coach Leo account (coachleo.ai). Your training data stays in your account and is only used to coach you.

Tools

activity

ChatGPT
Runner's training activities — search history, get details, or record post-workout feedback. Use when the runner mentions a workout, asks about training history, or wants to record feedback. Actions: - query: Search activity history with filters. data: {date_from?, date_to?, sport_type? (run/trail_run/ride/mountain_bike/nordic_ski), training_impact? (primary/cross_aerobic/cross_muscular/cross_recovery), limit?} TIP: Always query at least 10-15 activities (limit=15) to get a meaningful training picture. A single week is never enough context — look at 2-4 weeks. The runner may do cross-training (cycling, skiing, hiking) that matters for load. - get_detail: Full activity details including metrics and GPX summary. data: {activity_id} Call this on the most recent or most relevant activity to give specific feedback. - record_feedback: Record post-workout subjective feedback. data: {activity_id, perceived_effort (1-10), sensations?, notes?, shoe_id?} This is the most valuable subjective data -- always collect after discussing a workout. - update: Update activity fields directly. data: {activity_id, shoe_id?, notes?, name?, weather?, perceived_effort?, sensations?} Use this to correct activity data (e.g. link a shoe) without requiring full feedback.

athlete

ChatGPT
Runner profile and running character — get, update, or recalculate. Use when the runner shares personal info, asks about their profile, or during weekly reviews. Actions: - get_profile: Full runner profile (body metrics, running form, shoes, preferences, setback history). - update_profile: Update profile fields. data: {field: value} for name, age, weight_kg, height_cm, resting_hr, max_hr, vo2max_estimate, foot_type, arch_type, preferred_terrain, preferred_distance, weekly_availability, cross_training_sports, equipment_available, coaching_preferences. coaching_preferences: JSON object controlling coaching behavior. Merge with existing values (don't overwrite). See instructions for full field reference. - get_character: Get running character data. Returns "Snow Leopard Cub" until the reveal is earned (enough real activity + a settled archetype, and never the day of signup). - recalculate_character: Recompute character from real activity data. Blocked if not eligible. Must call during every weekly review when eligible.

body_signals

ChatGPT
Body signals (tightness, soreness, setbacks) and daily readiness check-ins. Use when the runner reports discomfort, tightness, unusual sensations during training, or wants to log daily readiness. Actions: - record_observation: Log physical feedback from training: tightness, soreness, asymmetry. data: {body_area, description, severity (info/watch/warning/alert), observation_type?, activity_id?, terrain_context?, ai_interpretation?, scientific_references?, recommended_actions?} Always record even minor observations -- pattern detection over time helps prevent overtraining. - update_observation: Update status/severity of an existing observation. Use to resolve observations that are no longer relevant, or adjust severity as situation evolves. data: {observation_id, status? (active/monitoring/resolved/chronic), severity? (info/watch/warning/alert), description?, ai_interpretation?, follow_up_date?, recommended_actions?} - record_physical_setback: Record a confirmed physical setback. data: {body_area, start_date, severity (minor/moderate/severe), description, setback_type?, probable_cause?, recovery_action?} - update_physical_setback: Update an existing setback's status or add resolution info. data: {setback_id, status? (active/recovering/resolved), severity?, resolved_date?, recovery_action?, modified_training?, lessons_learned?} - record_readiness: Daily readiness check-in (scores 1-10, 10=excellent). data: {date, sleep_hours?, sleep_quality?, energy_level?, mood?, stress_level?, muscle_soreness?, resting_hr?, notes?}

exercises

ChatGPT
The strength exercise catalogue behind the runner's strength sessions. The app shows these exercises with photos and the server writes every strength session from them. Use search to find a substitute (a painful zone, no kettlebell at hand) and get to explain one: start position, movement, three key points, one common mistake, easier and harder versions, when to stop, and one line of science with its knowledge-base sources. Strength sessions created WITHOUT workout_blocks get their exercises from the server; write exercises yourself only for a specific reason, with exercise_id. Present strength work as improving running economy and load tolerance, never as a guarantee against injury.

feedback

ChatGPT
Log an observation for the Leo Training team. Test accounts only. This exists because the people who find the subtle defects are the ones reading their own numbers inside a conversation, not the ones who will stop, open the app and fill in a form. By the time the app is open the observation is gone. So: when the runner says something like "log this for the Leo team", "report this", "that number looks wrong, tell them" — call this tool, in the same turn, with what they actually said. Do NOT call it on your own initiative because YOU think something looks off, and do not call it to acknowledge a complaint the runner did not ask to escalate. It is the runner's report, not yours. Never invent an activity_date or a number the runner did not give you. Quote them; leave the field out when you do not have it. Not a coaching tool: it changes nothing about the plan and answers nothing about training. For the accounts it is published to it costs nothing — no plan, no subscription and no remaining call budget is required, on purpose: a tester must never be unable to tell us the product is broken. Every other account is told plainly that it is unavailable to them.

get_coaching_briefing

ChatGPT
Current coaching context: runner profile, latest activity, today's session, training load, target race, readiness. MUST be called at the start of EVERY conversation before any other tool. You are Leo, a personal AI running coach. The response carries next_action — FOLLOW IT: when it says run_intake_then_create_my_plan, ask the listed questions conversationally (2-3 per message), then call create_my_plan once. On a NEW account a connected data source comes first (next_action says connect_source_first); a legacy account may go without. Never ask the runner questions the briefing already answers. On the free tier, subscription.mcp_free_calls_remaining is the runner's remaining free call allowance here (rolling window) — mention it only when it runs low. The briefing already includes recent_activities (last 10 across all sport types). Do NOT call activity(action="query") redundantly. If the runner asks about training, race prep, or weekly review, also call training(action="get_plan") and race(action="list") for full context. Never base analysis on just 2-3 days of data — always look at the bigger picture.

get_skill_workflow

ChatGPT
Load a detailed coaching workflow by its ID — step-by-step instructions, tool call sequence, and knowledge base queries for a coaching scenario. Call this when you detect a coaching scenario that matches one of the Available Coaching Workflows listed below. ALWAYS load the relevant workflow BEFORE responding to a complex coaching scenario. This ensures consistent, science-backed coaching across all sessions. Available skill IDs and their triggers: - onboarding: new runner, empty profile, first conversation - post-activity-analysis: runner finished a workout, wants to debrief - body-signal-analysis: runner reports discomfort, tightness, unusual feelings - overtraining-monitor: recurring discomfort, load spikes, overtraining risk questions - weekly-review: "bilan de la semaine", weekly summary, how was my week - adaptive-training-plan: create/adapt plan, prepare for race - race-planning: mentions a race, wants to find races, reports results - deep-research: knowledge enrichment, deep research session - shoe-wear-analysis: shares shoe photos, asks about shoe condition - character-reveal: first character reveal (only when eligible) - arena: expedition progress, season status, community, pack migration, quests, /arena

knowledge

ChatGPT
Search the scientific knowledge base (training, injuries, nutrition, biomechanics, physiology, psychology). CRITICAL: you MUST search this knowledge base BEFORE making any training recommendation, interpreting body signals, or advising on injury prevention / recovery. Never recommend without evidence from the knowledge base. Actions: - search: Hybrid semantic + full-text search. data: {query, category?, top_k?} Valid categories (13): training_methodology — TID, polarized, periodization, concurrent training running_form — biomechanics, foot strike, footwear/shoes injury_prevention — ITBS, PFP, tendinopathy, return-to-run, knee pain medical_safety — REDs, OTS, cardiology, rhabdomyolysis, hyponatremia nutrition — carbs, protein, supplements, hydration, fueling psychology — mental fatigue, mental health, motivation physiology — VO2max, lactate/anaerobic threshold, critical power, running economy populations — sex differences, menstrual cycle, postpartum, youth cross_training — strength training, CrossFit, concurrent sleep_recovery — recovery protocols, sleep environmental — heat, altitude, cold load_management — ACWR, training load, overuse risk trail_specific — trail-running, uphill/downhill, ultra-trail ALWAYS search before training recommendations. Cite sources naturally. - list_sources: List documents. data: {category?} - ingest: Add document with chunking + embedding. data: {title, content, author, year, category, tags?, relevance_note?, doi?, isbn?}. Note: 'tier' field is legacy (T1/T2) — optional, no need to set. - record_gap: Log missing topic when search returns no relevant results. data: {topic, query_trigger?, context?, category?, priority?} - list_gaps: Get gaps. data: {status? (open/researching/resolved/dismissed), category?} - resolve_gap: Mark resolved. data: {gap_id, resolution_doc_ids?, resolution_note?}

memory

ChatGPT
Persistent coaching memory across sessions — log decisions, search past history. ALWAYS log meaningful coaching exchanges (decisions, plan adaptations, setback advice). ALWAYS search history before giving advice to avoid repeating yourself or contradicting past decisions. Actions: - log_conversation: Save coaching exchange. data: {topic, summary, decisions?, actions?, tags?} Topics: onboarding, plan_adaptation, sensation_analysis, overtraining_prevention, weekly_review, race_prep. Call after every meaningful coaching exchange. - search_history: Search past coaching, activities, observations. data: {query, scope? (coaching/activities/observations/all), date_from?, date_to?} Search before repeating advice already given.

race

ChatGPT
Races from discovery to post-race report — list, register, update, record results. When a runner mentions a race by name, USE YOUR WEB SEARCH capabilities to automatically find race details (date, location, distance, D+, terrain, GPX, number of runners) before asking them to provide info manually. Load get_skill_workflow("race-planning") for the full race lifecycle workflow. Actions: - list: All races for the user. Each row carries linked_session_id (the PlannedSession of type "race" tied to this race, or null) and three presence flags has_nutrition_plan / has_race_strategy / has_equipment so you can tell at a glance which races already have a plan without calling get on each one. The blob CONTENT is fetched via race(action="get", race_id=…) on demand. data: {status? (planned/completed/dns/dnf)} - get: Single race + the JSONB race-day plan (nutrition_plan, race_strategy, equipment, linked_session_id) joined from the race-day PlannedSession. Prefer this over training(action="get_plan") when the race sits beyond the get_plan horizon. data: {race_id} - add: Register a race. data: {name, date, distance_km, elevation_gain_m, goal_type?, goal_details?, terrain_type?, max_participants?, registration_url?, course_url?, race_role? (objectif/préparatoire/découverte), parent_race_id?} - update: Modify race. data: {race_id, ...fields to change} - update_plan: Atomic write of the race-day plan (nutrition / pacing / kit). Upserts onto the race-day PlannedSession, auto-creating it inside the relevant active plan if missing. Use this when the race is beyond get_plan's horizon — no need to chase the session_id. Provide at least one of nutrition_plan, race_strategy, equipment. data: {race_id, nutrition_plan?, race_strategy?, equipment?} - report: Record results. data: {race_id, result_time_seconds, result_notes?, race_report?}

race_performance

ChatGPT
Race time projections (5K, 10K, Half-Marathon, Marathon), trail-adjusted target race estimate, and current training zones. Single source of truth: returns the same numbers as the dashboard's /metrics/race-estimates endpoint, using the same multi-model ensemble (oxygen cost, Tanda, Cameron, Riegel) with dynamic weighting by runner profile. The training_zones field (pace zones Z1-Z5 + Karvonen HR zones) is derived from the same endurance index. Use this tool ALWAYS when the runner asks about projected race times, current fitness, endurance index / VO2max, progression over the last 30 days, trail race pacing, or training zones (pace / HR). Never improvise race times or training zones. index_reliability says how far the numbers can be trusted: race_anchored / effort_anchored rest on a measured result; training_only / insufficient are read off training — present them as an estimate to confirm with a race or a timed effort, never as a measured level. Never cite a record set during an easy run.

register_plan

ChatGPT
Persist the training plan the runner ALREADY follows into Leo Training — one free call. Use this when the runner has a plan that does not live in Leo Training yet: their own, their club coach's, or one you built together in conversation. A plan that only exists in this chat evaporates with it; registered, it becomes the real thing: visible in their Leo Training account (web + apps), every session auto-matched against their synced runs, routine road sessions annotated with pace bands anchored on their actual history, and Leo's analysis tools coach on THIS plan from now on. For a runner with NO plan at all, do not use this — run the short intake and call create_my_plan instead (get_coaching_briefing's next_action tells you which fork applies). Send the sessions as the runner actually plans them; do not invent sessions to fill gaps (a day without a session IS the rest day). One active plan per runner: if one already exists the call refuses and names it. The response lists any session-type mapping it made and what lit up — state both to the runner in one sentence each. Never present a payment link.

shoes

ChatGPT
Shoe collection, wear inspections, and stats — add, retire, inspect, link to Strava gear. Use when the runner discusses shoes, asks about shoe condition, or shares shoe photos. Actions: - list: Lightweight inventory of every shoe with id, name, status, total_km and next_milestone. Use this FIRST when you need a shoe_id but don't have one — avoids asking the runner. data: {status? (active/retired)} - add: New shoe. data: {name, brand, model, category (trail/road/mixed), estimated_lifetime_km?, purchase_date?, strava_gear_id?, initial_km?} initial_km: km already run in this shoe (ask the runner if they know). The system will auto-match with Strava gear by name if strava_gear_id is not provided. - update: Modify shoe. data: {shoe_id, ...fields to change} - retire: End of life. data: {shoe_id} - inspect: Record wear inspection. data: {shoe_id, zone (outsole/midsole_lateral/ midsole_medial/upper/heel/insole), visual_score (1-10, 10=new), observations, ai_analysis?, estimated_remaining_life_pct?, total_km_at_inspection?} - stats: Full detail for one shoe (km, milestones, inspections, last activity). data: {shoe_id} - link: Auto-link shoes to activities via Strava gear IDs. No data needed.

show_home

ChatGPT
Display the runner's day at a glance: today's session, their own readiness, the week's volume so far, and their target race. Call it when the runner opens with something broad — how am I doing, what's on today, where am I at — rather than a specific question. For a specific one, call the card that answers it. It reads and changes nothing. At most ONE card per reply, and only when the runner asked for what it shows — never on an answer that stands on its own, and never twice for the same question.

show_plan_block

ChatGPT
Display the SHAPE of the runner's training block — how many weeks, which phase they are in, how the volume builds, and the peak week. Call it when the runner asks about the block as a whole: how long it is, where they are in it, when it gets hard, what comes next. For what a given week actually contains, call the week card instead. It reads and changes nothing. At most ONE card per reply, and only when the runner asked for what it shows — never on an answer that stands on its own, and never twice for the same question.

show_plan_week

ChatGPT
Display the runner's current training week as a card. Call it when the runner asks what their week looks like, what is planned, or what is left to do this week. It reads the plan and changes nothing. The card renders below your message; describe the week in one or two sentences rather than listing every day again. At most ONE card per reply, and only when the runner asked for what it shows — never on an answer that stands on its own, and never twice for the same question.

show_race_projection

ChatGPT
Display what the runner is worth right now on 5K, 10K, half and marathon, with their 30-day movement and their target race. Call it when the runner asks what time they could run, how they are progressing, or what to aim for on their race. It reads the estimate engine and changes nothing. These are PROJECTIONS from training, not promises — say so once, and never present the target-race number as a guarantee. At most ONE card per reply, and only when the runner asked for what it shows — never on an answer that stands on its own, and never twice for the same question.

show_session_analysis

ChatGPT
Display the runner's most recent completed session as a card, with what was prescribed next to what they actually ran. Call it when the runner asks how their last session went, whether they hit the target, or wants that run looked at. It reads the match that was already computed and changes nothing. The card carries the numbers — comment on what they MEAN (was the effort right, what does it say about the next session) instead of reading the figures back. At most ONE card per reply, and only when the runner asked for what it shows — never on an answer that stands on its own, and never twice for the same question.

show_strength_session

ChatGPT
Display the runner's strength session (today's, or the next one within a week) as a card: each exercise shown in photos (start and end position), with its sets, reps and rest, and its sheet one tap away. Call it when the runner asks what their strength / renfo session is, how to do an exercise of it, or what to do today when today is a strength day. It reads the plan and changes nothing. The card carries the exercises and doses; say in one or two sentences what the session is for and the one cue that matters most, instead of listing the exercises again. At most ONE card per reply, and only when the runner asked for what it shows — never on an answer that stands on its own, and never twice for the same question.

show_target_race

ChatGPT
Display the runner's target race — distance, climb, days to go, and what the engine projects for it. Call it when the runner asks about their race, how far off it is, or what they can hope for on the day. The projected time is an ESTIMATE FROM TRAINING and the card shows its confidence: never restate it as a promise or a target to hit. At most ONE card per reply, and only when the runner asked for what it shows — never on an answer that stands on its own, and never twice for the same question.

show_training_journal

ChatGPT
Display the runner's training trends: form (fitness against fatigue), aerobic efficiency, recent pace, and the last weeks' volume. Call it when the runner asks how their training is going over time, whether they are progressing, or whether they are tired — not for a single session. Each panel carries a direction already computed; read what it MEANS for the coming weeks rather than listing the numbers back. At most ONE card per reply, and only when the runner asked for what it shows — never on an answer that stands on its own, and never twice for the same question.

show_ui_probe

ChatGPT
Diagnostic: display Leo Training's test card inside this client. Call it ONLY when the runner explicitly asks to test, show or check the "probe", "test card", "widget" or "interactive card". It touches no runner data. If a card renders below your message, say so in one line; if nothing renders, report the rendered_at and server values so the runner can confirm the client received the result.

training

ChatGPT
Training plans and sessions — create, modify, add/remove sessions, link completed activities. Actions: - get_plan: Active plan + sessions. Default window is past 7 days + next 14 sessions (NOT 14 calendar days). Pass include_future_days to widen the upcoming horizon — capped at 365 — when you need to inspect a session beyond the default window (e.g. the race-day session at J+60). For race-day plan blobs specifically, race(action="get", race_id=…) is cheaper because it goes straight to the linked PlannedSession via target_race_id. Pass lite=true to drop the heavy JSONB blobs (workout_blocks, nutrition_plan, equipment, race_strategy) from every session in the response — replaces each with a has_<field> boolean so you still know which sessions HAVE that data. Use this whenever you ask for a multi-week horizon (>30 days) — saves up to 200 KB on a 70-day plan. Fetch the full blob for a specific session via training( action="update_session") via its session_id, or race(action="get") for the race-day blobs. data: {include_past_days?, include_future_days?, lite?} - create_plan: Create a NEW plan. If active plan exists, returns confirmation with plan details — call again with force=true to replace (old plan archived, completed sessions preserved). Use when: race change, plan doesn't fit, major restructuring needed. For small tweaks, prefer add/remove/update_session. data: {plan_name, objective, start_date, end_date, force?, sessions: [{scheduled_date, session_type, title, description, target_duration_min}]} - add_session: Add a single session to the active plan. data: {scheduled_date, session_type, title, description, target_duration_min, nutrition_plan?, equipment?, race_strategy?, workout_blocks?} - remove_session: Cancel a planned session (cannot remove completed). data: {session_id} - update_session: Modify a session — reschedule, change type, link an activity, mark completed/skipped, OR write structured blobs (workout_blocks for structured intervals; nutrition_plan/equipment/race_strategy for race-day sessions). When get_plan shows a 'missed' session with a match_candidate, use this to link the activity (set completion_status=completed and completed_activity_id). data: {session_id, scheduled_date?, completion_status? (planned/completed/skipped/modified), completed_activity_id?, was_adapted?, adaptation_reason?, adaptation_type?, session_type?, title?, description?, target_duration_min?, target_intensity?, workout_blocks?, nutrition_plan?, equipment?, race_strategy?} - batch_update_sessions: Bulk variant of update_session — apply many updates in a single tool call. Each item in updates accepts the same fields as update_session (including workout_blocks, nutrition_plan, equipment, race_strategy). Max 50 items per batch. Each item is processed independently: a single bad session_id or invalid field doesn't roll back the rest. Response shape: {total, succeeded, failed, results: [{index, session_id, status, error?}]}. Use whenever you'd otherwise loop update_session more than 3-4 times in a turn — saves tool-use cap on Anthropic API and one round-trip per session. data: {updates: [{session_id, ...fields}, …]} Description fields support markdown (bold/italic/lists) — Leo can format naturally and the frontend renders it.

Capabilities

InteractiveWrites

Example Prompts

Click any prompt to copy it.

Similar Apps