Skip to main content

coros-training-mcp

Running-first MCP server for COROS. Author, edit, and schedule running workouts — pace-based intervals, distance targets, repeat groups, clone-and-swap edits — plus sleep, HRV, training load, and activity exports.

Runs locally as a stdio subprocess of your AI assistant. No API key required, no public endpoint. Credentials live in your OS keyring; traffic is outbound only, directly to COROS.

Landing page & screenshots: https://dholliday3.github.io/coros-training-mcp/

Install

uv tool install coros-training-mcp
coros-mcp setup

(pipx install coros-training-mcp works too.) The wizard asks for your COROS email, password, and region, verifies them against the API, stores them in your system keyring, detects which AI assistants you have installed (Claude Code, Claude Desktop, Codex CLI, Cursor), writes the MCP entry for each one you pick (preserving any existing entries), and runs a smoke test.

Requirements: Python ≥ 3.11 (uv tool install fetches it automatically), a COROS Training Hub account, macOS / Linux / Windows.

Lifecycle:

coros-mcp setup --reconfigure   # change credentials or add more assistants
coros-mcp uninstall             # remove from assistants, optionally clear keyring
coros-mcp auth-status           # check stored tokens
uv tool upgrade coros-training-mcp

What you can ask

Running workouts (the focus):

  • "Create a 5×1km threshold workout at 4:05–4:15/km with 2-minute jog recovery"
  • "Change my Tuesday VO2 workout to 6 reps instead of 5"
  • "Build a 90-minute long run with 4×8min at marathon pace in the middle"
  • "Move Thursday's tempo run to Friday"
  • "Replace my scheduled Sunday long run with a 16km progression at easy→steady"

Recovery & training data:

  • "How much deep sleep and REM did I get last week?"
  • "What was my HRV trend over the last 4 weeks?"
  • "Show me resting HR and training load for last week"

Activities, schedule, and other sports:

  • "List my rides from last month"
  • "Export my Saturday long run to GPX"
  • "What's on my training calendar next week?"
  • "Create a 90-minute sweet spot workout"
  • "Create a 20-minute strength circuit with squats, lunges, and planks"

Tools

Tool Description
create_run_workout Create a run with pace/HR/distance targets, repeat groups
update_run_workout Clone-and-edit a running workout using run-specific step patches
get_run_workout_schema Shared run-step schema used by create + update
create_bike_workout Create a cycling workout with power/HR/time/distance targets, repeat groups
update_bike_workout Clone-and-edit a cycling workout using bike-specific step patches
get_bike_workout_schema Shared bike-step schema used by create + update
create_strength_workout Build a strength circuit from the COROS exercise catalog
get_strength_workout_schema Strength-step schema (reps, time, rest, exercise swap)
update_workout Generic clone-and-patch primitive (strength & cycling)
create_workout Generic time-and-power builder (legacy cycling path — prefer create_bike_workout)
list_workouts / get_workout / delete_workout Manage the library
list_scheduled_workouts / schedule_workout Calendar read & add
move_scheduled_workout Move a scheduled entry to another day
replace_scheduled_workout Swap a scheduled entry for a different workout
remove_scheduled_workout Remove a scheduled entry
get_daily_metrics HRV, resting HR, training load, VO₂max, stamina (n weeks)
get_sleep_data Deep / light / REM / awake minutes, sleep HR (n weeks)
list_activities / get_activity_detail Completed activity listing + detail
export_activity_file Download a completed activity as GPX / FIT / TCX / KML / CSV
list_exercises Browse the COROS exercise catalog
get_workout_builder_catalog Live-extracted enum registry for workout authoring
authenticate_coros / check_coros_auth Explicit login & status (usually automatic)

Workout taxonomy

Three distinct objects that are easy to confuse:

  • Library workout — reusable program in your account. Queried by list_workouts.
  • Scheduled entry — calendar occurrence of a workout on a specific day. Queried by list_scheduled_workouts. Has different IDs than its source workout.
  • Plan container — higher-level training plan that owns scheduled entries.

Full detail: docs/workout-taxonomy.md. Enum-extraction mechanics: docs/enum-extraction.md.

Export (GPX / FIT / TCX / KML / CSV) applies to completed activities only — structured library workouts use a separate share flow in the COROS app. The MCP writes to COROS server state; device sync is still handled by the app/watch.

Privacy & data handling

  • Credentials: system keyring (macOS Keychain / Windows Credential Manager / freedesktop Secret Service). If the keyring is unavailable (headless Linux, some VMs), the wizard falls back to an AES-encrypted file at ~/.coros-mcp/credentials.enc and tells you.
  • Assistant config entries: live in each assistant's own config file. Only a coros entry is added or replaced; other MCP entries are never touched.
  • Network: outbound TLS only, directly to teamapi.coros.com / teameuapi.coros.com / apieu.coros.com. No telemetry, no analytics, no third-party services.
  • Binary: coros-mcp lives in the uv tool / pipx isolated venv at an absolute path that MCP clients pin to.

Tool reference

Auth — authenticate_coros, authenticate_coros_mobile, check_coros_auth

You normally don't call these directly — credentials from the keyring or .env are picked up automatically. They exist for explicit login/reauth ({ "email", "password", "region" }) and status checks (check_coros_auth returns authenticated, expires_in_hours, mobile_authenticated, mobile_token_status). authenticate_coros_mobile is useful for restoring sleep-data access without redoing web auth.

get_daily_metrics

Fetch daily metrics for a configurable number of weeks (default: 4).

{ "weeks": 4 }

Returns: records (list), count, date_range. Each record includes:

Field Source Description
date Date (YYYYMMDD)
avg_sleep_hrv dayDetail Nightly HRV (RMSSD ms)
baseline dayDetail HRV rolling baseline
rhr dayDetail Resting heart rate (bpm)
training_load dayDetail Daily training load
training_load_ratio dayDetail Acute/chronic training load ratio
tired_rate dayDetail Fatigue rate
ati / cti dayDetail Acute / chronic training index
distance / duration dayDetail Distance (m) / duration (s)
vo2max analyse (merge) VO2 Max (last ~28 days)
lthr analyse (merge) Lactate threshold heart rate (bpm)
ltsp analyse (merge) Lactate threshold pace (s/km)
stamina_level analyse (merge) Base fitness level
stamina_level_7d analyse (merge) 7-day fitness trend

get_sleep_data

Fetch nightly sleep stage data for a configurable number of weeks (default: 4).

{ "weeks": 4 }

Returns: records, count, date_range. Each record includes date, total_duration_minutes, phases.{deep,light,rem,awake,nap}_minutes, avg_hr, min_hr, max_hr, quality_score.

Sleep data is fetched from the COROS mobile API (apieu.coros.com), which uses a separate token from the Training Hub web API. coros-mcp auth obtains both, but doing so logs you out of the COROS mobile app on your phone. Use coros-mcp auth-web (or let the wizard's default skip-mobile choice stand) — the mobile token is then fetched lazily on the first sleep-data request and refreshed automatically.

list_activities

{ "start_day": "20260101", "end_day": "20260305", "page": 1, "size": 30 }

Returns: activities, total_count, page. Each activity includes activity_id, name, sport_type, sport_name, start_time, end_time, duration_seconds, distance_meters, avg_hr, max_hr, calories, training_load, avg_power, normalized_power, elevation_gain.

get_activity_detail

{ "activity_id": "469901014965714948", "sport_type": 200 }

Full activity data including laps, HR zones, power zones, and sport-specific metrics. Large time-series arrays (graphList, frequencyList, gpsLightDuration) are stripped to keep the response manageable.

export_activity_file

{
  "activity_id": "469901014965714948",
  "sport_type": 100,
  "file_type": "gpx",
  "output_path": "/tmp/morning-run.gpx"
}

file_type: gpx, fit, tcx, kml, or csv. Returns activity_id, sport_type, file_type, file_url, output_path, downloaded.

list_workouts

{}

Returns workouts, count. Each workout includes id, name, sport_type, sport_name, estimated_time_seconds, exercise_count, exercises (steps with name, duration_seconds, power_low_w, power_high_w).

create_run_workout

Run-native step kinds (warmup, training, rest, cooldown), distance / time / open targets, optional pace / HR intensity ranges, nested repeat groups.

{
  "name": "Tuesday Threshold",
  "steps": [
    {"kind": "warmup",   "name": "Warm-up",  "target_type": "distance", "target_distance_meters": 2000},
    {"repeat": 5, "name": "Main Set", "steps": [
      {"kind": "training", "name": "Rep",      "target_type": "distance", "target_distance_meters": 1000,
       "intensity_type": 3, "intensity_value": 245, "intensity_value_extend": 255, "intensity_display_unit": 2},
      {"kind": "rest",     "name": "Recovery", "target_type": "time",     "target_duration_seconds": 120}
    ]},
    {"kind": "cooldown", "name": "Cool-down", "target_type": "distance", "target_distance_meters": 1500}
  ]
}

Pace targets use intensity_type: 3 with intensity_value / intensity_value_extend as seconds-per-km and intensity_display_unit: 2. Friendly pace strings like "4:05-4:15/km" or "5:30/mi" are also accepted on any run step as a "pace" field. For the full field vocabulary (HR zones, percent-of-LT, named intensity presets) call get_run_workout_schema or see run_workout_schema.py.

Open steps. "target_type": "open" is the Training Hub "Open" target (COROS targetType=1, manualEnd): the step has no target and runs until the athlete presses the lap button. Omit target_duration_seconds / target_distance_meters — they're ignored. This is how you author an open warm-up or cool-down so the athlete, not the watch, decides the length:

{
  "name": "Sub-T 3×6min",
  "steps": [
    {"kind": "warmup", "name": "WU open", "target_type": "open"},
    {"repeat": 3, "steps": [
      {"kind": "training", "name": "Sub-T 6min", "target_type": "time", "target_duration_seconds": 360,
       "intensity_label": "Heart Rate", "intensity_value": 168, "intensity_value_extend": 175},
      {"kind": "rest", "name": "Jog", "target_type": "time", "target_duration_seconds": 120}
    ]},
    {"kind": "cooldown", "name": "CD open", "target_type": "open"}
  ]
}

Open steps contribute nothing to the workout's estimated time/distance — only the targeted steps do. "manual" and "lap" are accepted aliases. Works the same on create_bike_workout, and on the update_* patch path (switching a step to open clears its stale target).

Returns: workout_id, sport_type, estimated_time_seconds, estimated_distance_meters, steps_count, message.

update_run_workout

Clone-and-edit an existing running workout. Select each step by step_name, step_id, or step_index and patch it with any run-step field used by create_run_workout. Original is preserved; a new workout ID is returned. If the original was scheduled, use replace_scheduled_workout (or schedule_workout) to swap the calendar entry.

{
  "workout_id": "476023839273435149",
  "name": "Tuesday Threshold (6×1km)",
  "step_updates": [
    {"step_name": "Main Set", "repeat": 6},
    {"step_name": "Rep", "target_distance_meters": 1000, "intensity_value": 240, "intensity_value_extend": 250}
  ]
}

Returns: new_workout_id, original_workout_id, name, steps_count, message.

get_run_workout_schema

Returns the shared schema used by create_run_workout and update_run_workout: allowed step kinds, target types, intensity presets pulled from the live Training Hub builder, and required vs. optional fields. Call this before authoring to avoid guessing.

create_bike_workout

Bike-native step kinds (warmup, training, rest, cooldown), time / distance / open targets, optional power / HR / cadence / FTP-percent intensity, nested repeat groups. Sets sportType=2 so warm-up / training / rest / cool-down segments render correctly on the watch (unlike create_workout, which builds flat training blocks).

{
  "name": "3×15 Sweetspot",
  "steps": [
    {"kind": "warmup",   "name": "Warm-up",  "target_type": "time", "target_duration_seconds": 600, "power": "120-160W"},
    {"repeat": 3, "name": "Main Set", "steps": [
      {"kind": "training", "name": "Sweetspot", "target_type": "time", "target_duration_seconds": 900, "power": "230-260W"},
      {"kind": "rest",     "name": "Recovery",  "target_type": "time", "target_duration_seconds": 180}
    ]},
    {"kind": "cooldown", "name": "Cool-down", "target_type": "time", "target_duration_seconds": 600}
  ]
}

Power targets use intensity_type: 6 with intensity_value / intensity_value_extend in watts. Friendly strings like "220W" or "220-260W" are also accepted on any bike step as a "power" field. Raw intensity fields work for HR, cadence, FTP-percent, and speed targets. For the full field vocabulary call get_bike_workout_schema or see bike_workout_schema.py.

Returns: workout_id, sport_type, sport_name, estimated_time_seconds, estimated_distance_meters, steps_count, message.

update_bike_workout

Clone-and-edit an existing cycling workout. Select each step by step_name, step_id, or step_index and patch it with any bike-step field used by create_bike_workout. Original is preserved; a new workout ID is returned. If the original was scheduled, use replace_scheduled_workout (or schedule_workout) to swap the calendar entry.

{
  "workout_id": "476023839273435149",
  "name": "4×15 Sweetspot",
  "step_updates": [
    {"step_name": "Sweetspot", "target_duration_seconds": 900, "power": "240-270W"}
  ]
}

get_bike_workout_schema

Returns the shared schema used by create_bike_workout and update_bike_workout. Intensity label presets (Power, %FTP, Heart Rate, Cadence, Speed, etc.) are pulled from the checked-in live Training Hub builder catalog. If the catalog hasn't yet been recaptured to include bike correlations, the preset list will be empty and you should pass the raw intensity_* fields or use the power ergonomic directly.

create_workout

Generic time-and-power builder (legacy cycling path). For cycling, prefer create_bike_workout — it understands segment kinds (warm-up / training / rest / cool-down) and renders them correctly on the watch. For running, use create_run_workout.

{
  "name": "3×10min Sweet Spot",
  "sport_type": 2,
  "steps": [
    {"name": "Warmup",   "duration_minutes": 10, "power_low_w": 150, "power_high_w": 200},
    {"repeat": 3, "steps": [
      {"name": "Sweet Spot", "duration_minutes": 10, "power_low_w": 265, "power_high_w": 285},
      {"name": "Recovery",   "duration_minutes":  3, "power_low_w": 150, "power_high_w": 175}
    ]},
    {"name": "Cooldown", "duration_minutes": 11, "power_low_w": 150, "power_high_w": 200}
  ]
}

sport_type: 2 = Indoor Cycling (default), 200 = Road Bike.

update_workout

Lower-level clone-and-patch primitive used by update_run_workout and for strength edits. Patch steps/exercises by step_name, step_id, step_index, or origin_id (strength exercise swap). Supports rest_seconds, target_type (time|reps|distance), and any field the create side accepts.

delete_workout

{ "workout_id": "476023839273435149" }

list_planned_activities / list_scheduled_workouts

{ "start_day": "20260309", "end_day": "20260316" }

Returns scheduled entries for the window, including library-sourced and plan-embedded programs. Use list_scheduled_workouts for the canonical MCP-facing shape.

schedule_workout

{ "workout_id": "1234567890", "happen_day": "20260312", "sort_no": 1 }

move_scheduled_workout

Move a scheduled entry to a new day without losing the underlying workout. Handles both library-sourced entries and plan-embedded programs (which have no library counterpart).

{ "plan_id": "987654321", "id_in_plan": "1234567890", "new_happen_day": "20260314" }

replace_scheduled_workout

Swap a scheduled entry for a different workout (typically a freshly updated clone) in-place. Preserves the calendar slot and sort order.

{ "plan_id": "987654321", "id_in_plan": "1234567890", "replacement_workout_id": "476023839273435149" }

remove_scheduled_workout

{ "plan_id": "987654321", "id_in_plan": "1234567890", "plan_program_id": "1234567890" }

If plan_program_id is missing from list_planned_activities, reuse id_in_plan.

create_strength_workout

Structured strength workout with repeated sets. Exercises come from the COROS catalog (list_exercises).

{
  "name": "Leg Circuit",
  "sets": 3,
  "exercises": [
    {"origin_id": "54",  "name": "T1061", "overview": "sid_strength_squats", "target_type": 3, "target_value": 12, "rest_seconds": 45},
    {"origin_id": "130", "name": "T1176", "overview": "sid_strength_plank",  "target_type": 2, "target_value": 60, "rest_seconds": 30}
  ]
}

target_type: 2 = time in seconds, 3 = reps.

list_exercises

{ "sport_type": 4 }

sport_type=4 is strength. Returns exercises, count, sport_type.


Manual setup (advanced)

If you're not using one of the auto-detected assistants, install the server and point any MCP client at it:

uv tool install coros-training-mcp
coros-mcp auth          # interactive login, stores tokens in keyring
which coros-mcp         # absolute path for the config below
{ "mcpServers": { "coros": { "command": "/absolute/path/to/coros-mcp", "args": ["serve"] } } }

Developer setup

git clone https://github.com/dholliday3/coros-training-mcp.git
cd coros-training-mcp
python3 -m venv .venv && source .venv/bin/activate
pip install -e .[dev]
pytest

CLI reference

coros-mcp setup                 # first-time interactive setup
coros-mcp setup --reconfigure   # re-run wizard
coros-mcp uninstall             # remove from assistants
coros-mcp serve                 # start the MCP server (what MCP clients run)
coros-mcp auth                  # re-authenticate (web + mobile)
coros-mcp auth-web              # web token only (sleep data lazy-loads)
coros-mcp auth-mobile           # mobile token only
coros-mcp auth-status           # check stored tokens
coros-mcp auth-clear            # remove stored tokens

Built on top of cygnusb/coros-mcp, kept as an upstream reference.

Disclaimer

Uses the unofficial COROS Training Hub API. The API may change at any time without notice. Not affiliated with or endorsed by COROS. Use at your own risk.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

coros_training_mcp-0.3.1.tar.gz (71.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

coros_training_mcp-0.3.1-py3-none-any.whl (80.9 kB view details)

Uploaded Python 3

File details

Details for the file coros_training_mcp-0.3.1.tar.gz.

File metadata

  • Download URL: coros_training_mcp-0.3.1.tar.gz
  • Upload date:
  • Size: 71.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for coros_training_mcp-0.3.1.tar.gz
Algorithm Hash digest
SHA256 5e084726d2e6b9e674299347fbe52ed3bf70d49efcf67cb26cbf308a00d74e99
MD5 cc67bd738616e75da96c94cc8a998960
BLAKE2b-256 c6fba24e2805f991826807e21477227d8ef3e387f4c2a23dbf3f22ca688b4fde

See more details on using hashes here.

Provenance

The following attestation bundles were made for coros_training_mcp-0.3.1.tar.gz:

Publisher: release.yml on dholliday3/coros-training-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file coros_training_mcp-0.3.1-py3-none-any.whl.

File metadata

File hashes

Hashes for coros_training_mcp-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8d14bac010342bf2242c84f1e0716cd1955c5f356c4986bc9be07f302ef4f53b
MD5 90da27b74c86a897501d59338ec8501c
BLAKE2b-256 204918b10485599b2901cb4e1d4176a31ec7d38038dc4ffbfd7a16ae6fb7c048

See more details on using hashes here.

Provenance

The following attestation bundles were made for coros_training_mcp-0.3.1-py3-none-any.whl:

Publisher: release.yml on dholliday3/coros-training-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.3.1 This release

2 files

0.3.0

2 files

0.2.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page