Model Context Protocol server for Coros with first-class running workout authoring, scheduling, and editing on top of training data, sleep, HRV, and activity exports.
Project description
coros-training-mcp
A Model Context Protocol (MCP) server for COROS with first-class running workout authoring and editing — pace-based intervals, distance targets, repeat groups, and clone-and-swap edits of scheduled runs — on top of the standard COROS training data, scheduling, strength, and activity export tools.
This project is built on top of cygnusb/coros-mcp and keeps that repository as an upstream reference.
Why this fork
Upstream is a solid read-oriented COROS MCP, but its authoring story is shaped around time-and-power workouts (cycling, strength). This fork is the one to pick when an agent needs to build or edit running workouts end-to-end:
- Sport-specific run tools —
create_run_workoutandupdate_run_workoutspeak distance/pace/HR targets natively, not just duration/power. - Shared run-step schema —
get_run_workout_schemaexposes the exact contract so agents don't guess which fields are valid on create vs. update. - Clone-and-swap edits — change a scheduled run's distance, pace band, or rep count without destroying the calendar entry.
- Live builder catalog — enum values (step kinds, target types, intensity types) are extracted from the Training Hub builder itself, not hand-maintained.
- Workout taxonomy docs — explicit disambiguation of library workouts, scheduled entries, and plan containers, so agents stop confusing
list_workoutswith the calendar. - Broader automated test suite covering the running-edit paths.
No API key required. This server authenticates directly with your Coros Training Hub credentials. Your token is stored securely in your system keyring (or an encrypted local file as fallback), never transmitted anywhere except to Coros.
For the distinction between library workouts, scheduled calendar entries, and plan containers, see docs/workout-taxonomy.md.
What You Can Do
Ask your AI assistant questions like:
Running workouts (the fork's 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 pace"
Training data & recovery:
- "How much deep sleep and REM did I get last week?"
- "What was my HRV trend over the last 4 weeks?"
- "Show me my resting heart rate and training load for last week"
- "How many steps did I average per day this month?"
Activities, calendar, and other sports:
- "List my rides from last month"
- "Show me the details of my last long ride"
- "Create a 90-minute sweet spot workout for me"
- "What's on my training calendar next week?"
- "Schedule my VO2 workout for Thursday"
- "Create a 20-minute strength circuit with squats, lunges, and planks"
Features
| Tool | Description |
|---|---|
authenticate_coros |
Log in with email and password — token stored securely in keyring |
authenticate_coros_mobile |
Log in to the mobile API only (useful for sleep data troubleshooting) |
check_coros_auth |
Check whether a valid auth token is present |
get_daily_metrics |
Fetch daily metrics (HRV, resting HR, training load, VO2max, stamina, and more) for n weeks (default: 4) |
get_sleep_data |
Fetch nightly sleep stages (deep, light, REM, awake) and sleep HR for n weeks (default: 4) |
list_activities |
List activities for a date range with summary metrics |
get_activity_detail |
Fetch full detail for a single activity (laps, HR zones, power zones) |
export_activity_file |
Export a completed activity file in GPX, FIT, TCX, KML, or CSV and save it locally |
list_workouts |
List all saved structured workout programs |
get_workout_builder_catalog |
Return the checked-in enum registry and live builder catalog for workout construction |
get_run_workout_schema |
Return the shared run-step schema used by both create_run_workout and update_run_workout |
create_run_workout |
Create a running workout with explicit run-step kinds and run targets |
update_run_workout |
Clone and edit a running workout using run-specific step updates |
create_workout |
Create a new structured workout with named steps and power targets |
delete_workout |
Delete a workout program from the library |
list_planned_activities |
List planned workouts from the Coros training calendar |
schedule_workout |
Schedule an existing workout on a calendar day |
remove_scheduled_workout |
Remove a scheduled workout from the calendar |
create_strength_workout |
Create a structured strength workout with sets, reps, or timed exercises |
list_exercises |
Browse the Coros exercise catalogue, especially for strength workouts |
Workout Taxonomy
Three distinct objects, often confused:
- library workout — reusable program in your account. Queried by
list_workouts. - scheduled entry — a calendar occurrence of a library workout on a specific day. Queried by
list_planned_activities. May have different IDs than its source workout. - plan container — the higher-level COROS schedule/plan that owns scheduled entries.
GPX/FIT/TCX/KML export applies to completed activities only — structured library workouts use a separate share flow in the app. The MCP writes to COROS server state; device sync is still handled by the COROS app/watch.
See docs/workout-taxonomy.md for full detail, and docs/enum-extraction.md for how enum values are discovered from the live Training Hub builder.
Setup
Two commands — install and run the wizard:
uv tool install coros-training-mcp
coros-mcp setup
(pipx install coros-training-mcp also works if you don't have uv.)
The wizard asks for email, password, and region, verifies the credentials against the COROS API, stores them in your system keyring, detects which AI assistants you have installed (Claude Code, Claude Desktop, Codex CLI, Cursor), lets you pick which to install into, writes the MCP config for each picked one (preserving any other MCP entries you already have), and runs a post-install smoke test to confirm the server boots.
Lifecycle
coros-mcp setup # first-time setup
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
To upgrade: uv tool upgrade coros-training-mcp (or pipx upgrade coros-training-mcp).
Requirements
- Python ≥ 3.11 (fetched automatically by
uv tool install; already present on most systems otherwise) - A COROS Training Hub account
- One of: Claude Code, Claude Desktop, Codex CLI, or Cursor (others can still use the server manually — see "Manual setup" below)
What gets stored and where
- 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 encrypted local file at
~/.coros-mcp/credentials.encand tells you. - Assistant config entries: live in each assistant's own config file — we only ever add/replace a
corosentry; we never touch other MCP entries. - The
coros-mcpbinary itself: in theuv tool/pipxisolated venv, at an absolute path that MCP clients pin to.
Manual setup (advanced)
If you're on a system none of the supported assistants run on, or you want full control, you can still run the server directly:
uv tool install coros-training-mcp
coros-mcp auth # one-time interactive login, stores tokens in keyring
Then point any MCP client at the binary:
{ "mcpServers": { "coros": { "command": "/absolute/path/to/coros-mcp", "args": ["serve"] } } }
Find the absolute path with which coros-mcp.
Developer setup
For hacking on the code:
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 # low-level: re-authenticate (web + mobile)
coros-mcp auth-web # low-level: web token only (sleep data lazy-loads)
coros-mcp auth-mobile # low-level: mobile token only
coros-mcp auth-status # check stored tokens
coros-mcp auth-clear # remove stored tokens
The mobile login logs you out of the COROS mobile app on your phone. Use
auth-web(or let the wizard's default skip-mobile choice stand) to avoid this — the mobile token is fetched lazily on the first sleep-data request.
Tool Reference
Auth — authenticate_coros, authenticate_coros_mobile, check_coros_auth
You normally don't call these directly — credentials from Keychain 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 (list), count, date_range
Each record includes:
| Field | Description |
|---|---|
date |
Date (YYYYMMDD) — the morning after the sleep |
total_duration_minutes |
Total sleep in minutes |
phases.deep_minutes |
Deep sleep |
phases.light_minutes |
Light sleep |
phases.rem_minutes |
REM sleep |
phases.awake_minutes |
Time awake during the night |
phases.nap_minutes |
Daytime nap time (null if none) |
avg_hr |
Average heart rate during sleep |
min_hr |
Minimum heart rate during sleep |
max_hr |
Maximum heart rate during sleep |
quality_score |
Sleep quality score (null if not computed) |
Note: 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 authobtains both tokens, but doing so logs you out of the Coros mobile app. Usecoros-mcp auth-webto skip mobile login — the mobile token is then obtained automatically on the first sleep data request. The token expires after ~1 hour but refreshes automatically on subsequent requests.
list_activities
List activities for a date range.
{ "start_day": "20260101", "end_day": "20260305", "page": 1, "size": 30 }
Returns: activities (list), 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
Fetch full detail for a single activity. Requires the sport_type from list_activities.
{ "activity_id": "469901014965714948", "sport_type": 200 }
Returns full activity data including laps, HR zones, power zones, and all sport-specific metrics.
Note: Large time-series arrays (
graphList,frequencyList,gpsLightDuration) are stripped from the response to keep it manageable.
export_activity_file
Export a completed activity file and save it locally.
{
"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
List all saved structured workout programs.
{}
Returns: workouts (list), count
Each workout includes: id, name, sport_type, sport_name, estimated_time_seconds, exercise_count, exercises (list of steps with name, duration_seconds, power_low_w, power_high_w)
create_run_workout
Create a running workout with run-native step kinds (warmup, training, rest, cooldown), distance or time targets, and optional pace / HR intensity ranges. Supports repeat groups for intervals.
5×1km threshold with jog recovery:
{
"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. For the full field vocabulary (HR zones, percent-of-LT, named intensity presets), call get_run_workout_schema or see run_workout_schema.py.
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 to change by step_name, step_id, or step_index, and patch it with any run-step field used by create_run_workout. The original workout is preserved; a new workout ID is returned. If the original was scheduled, re-schedule the replacement with schedule_workout.
{
"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
Return the shared run-step schema used by both create_run_workout and update_run_workout, including 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 which fields are valid.
{}
create_workout
Generic time-and-power workout builder (primarily cycling). For running, prefer create_run_workout. Supports plain steps and nested repeat groups for intervals.
{
"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. Returns: workout_id, name, total_minutes, steps_count, message.
delete_workout
Delete a workout program from the Coros account.
{ "workout_id": "476023839273435149" }
The workout_id comes from list_workouts.
Returns: deleted, workout_id, message
list_planned_activities
List planned activities from the Coros training calendar.
{ "start_day": "20260309", "end_day": "20260316" }
Returns: activities (list), count, date_range
schedule_workout
Add an existing workout from your library to the Coros training calendar.
{ "workout_id": "1234567890", "happen_day": "20260312", "sort_no": 1 }
Returns: scheduled, workout_id, happen_day
remove_scheduled_workout
Remove a scheduled workout from the Coros training calendar.
{
"plan_id": "987654321",
"id_in_plan": "1234567890",
"plan_program_id": "1234567890"
}
plan_id, id_in_plan, and plan_program_id come from list_planned_activities. If plan_program_id is missing, you can usually reuse id_in_plan.
Returns: removed, plan_id, id_in_plan
create_strength_workout
Create a structured strength workout with repeated sets. Exercises must come from the Coros exercise catalogue.
{
"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
Returns: workout_id, name, sets, exercise_count
list_exercises
List the Coros exercise catalogue for a sport type. Default sport_type=4 returns strength exercises.
{ "sport_type": 4 }
Returns: exercises (list), count, sport_type
Disclaimer
This project uses the unofficial COROS Training Hub API. The API may change at any time without notice. Use at your own risk.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file coros_training_mcp-0.2.0.tar.gz.
File metadata
- Download URL: coros_training_mcp-0.2.0.tar.gz
- Upload date:
- Size: 65.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9976cd4f7a82726641afc69ea94e79e91a2303ce210a48a614d715561bc25cbb
|
|
| MD5 |
b5e2ac548be3d9c73fac25097fa0ff25
|
|
| BLAKE2b-256 |
21c86c2baff4976751333c2d3edf175eee51a8d4769d4bf9f6c30878fe63d720
|
Provenance
The following attestation bundles were made for coros_training_mcp-0.2.0.tar.gz:
Publisher:
release.yml on dholliday3/coros-training-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
coros_training_mcp-0.2.0.tar.gz -
Subject digest:
9976cd4f7a82726641afc69ea94e79e91a2303ce210a48a614d715561bc25cbb - Sigstore transparency entry: 1346958908
- Sigstore integration time:
-
Permalink:
dholliday3/coros-training-mcp@316c7e2ab02525976dd3553907af53e9af7c5e18 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/dholliday3
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@316c7e2ab02525976dd3553907af53e9af7c5e18 -
Trigger Event:
push
-
Statement type:
File details
Details for the file coros_training_mcp-0.2.0-py3-none-any.whl.
File metadata
- Download URL: coros_training_mcp-0.2.0-py3-none-any.whl
- Upload date:
- Size: 74.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5ced51eaa41a2048ae120efa582c4e112b95df2db414b7809411f4248c033f7a
|
|
| MD5 |
7cf28bb40b130aa15d845a8ea19b33d9
|
|
| BLAKE2b-256 |
85b872d19d4694e5bb64a39ece5024468fe9f0a0b4cb72f7b53f03526592fb2d
|
Provenance
The following attestation bundles were made for coros_training_mcp-0.2.0-py3-none-any.whl:
Publisher:
release.yml on dholliday3/coros-training-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
coros_training_mcp-0.2.0-py3-none-any.whl -
Subject digest:
5ced51eaa41a2048ae120efa582c4e112b95df2db414b7809411f4248c033f7a - Sigstore transparency entry: 1346958993
- Sigstore integration time:
-
Permalink:
dholliday3/coros-training-mcp@316c7e2ab02525976dd3553907af53e9af7c5e18 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/dholliday3
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@316c7e2ab02525976dd3553907af53e9af7c5e18 -
Trigger Event:
push
-
Statement type: