ClaudeCyclingMCP
Generates Garmin Connect workout payloads from a structured cycling session spec, including power targets, cadence, and step types, ready for upload via the Garmin MCP's upload_workout tool.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@ClaudeCyclingMCP Render this cycling workout spec to MyWhoosh .zwo and Garmin files"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
ClaudeCyclingMCP
Describe a structured cycling session once. Get a valid MyWhoosh .zwo and a valid Garmin
Connect workout out of it.
spec (JSON) ──┬── render_zwo → .zwo (MyWhoosh)
└── render_garmin → .json (Garmin upload_workout payload)An MCP server that turns one hand-readable workout spec into files both platforms accept, and then checks that what the platform stored is what you sent. It is pure and deterministic — no network, no auth, no credentials. Uploading is a separate, human-in-the-loop step that ships as skills.
Why this exists
Writing a structured bike session today means either clicking blocks around in a graphical editor — slow, imprecise — or hand-writing XML. And the two platforms want different formats, so the same session gets built twice.
Garmin's MCP has structured builders for running, strength and walk/run, but nothing for cycling:
a bike session there means raw JSON through upload_workout. MyWhoosh has no usable API at all.
Worse, both platforms fail quietly. A Garmin watt target sent with the target type the Garmin
MCP's own docs recommend uploads without error and is stored as a pace target. A repeat group
missing one numeric id silently gets the wrong repetition count. A .zwo stores power only as a
fraction of FTP, so a spec written against the wrong FTP produces a file that is still "correct"
— just scaled to a different athlete. None of these announce themselves.
So: one spec in, two files out, and a verification step that compares against what was sent rather than trusting a success response.
Related MCP server: intervals-mcp
Highlights
📝 One hand-readable spec — flat JSON with
steady,ramp,freeandrepeatblocks. Power is explicit:power_worpower_pct, never guessed from magnitude (spec format).📊 See the session before you build it —
describe_specprints a block table with computed watts, elapsed time, NP, IF, TSS and kJ.✅ Validation that catches the real mistakes — swapped watts/percentages, zero durations, flat ramps, nested repeats, and typo'd keys like
powr_wthat would otherwise pass in silence.🚴 Two renderers, one source —
.zwofor MyWhoosh and a Garminupload_workoutpayload, each pinned to a platform quirk that was expensive to learn (format notes).🔍 Verification, not optimism — compare a Garmin upload against what was actually stored, and a MyWhoosh import against the scraped builder header, including a pre-import snapshot that catches a silent no-op.
🔐 No credentials, ever — the server never uploads and holds no tokens. Writing a rendered file when you pass
out_pathis its only side effect.🧩 Bundled skills — Garmin upload-and-verify, and a browser-driven MyWhoosh import that reads MyWhoosh's own FTP before rendering.
Install
Requires Python 3.10+.
Claude Code — install the plugin; it registers the server and the skills in every session, from any directory:
/plugin marketplace add elias-ramzi/ClaudeCyclingMCP/plugin install claude-cycling-mcp@cycling-toolsAny MCP client — register the server; the bundled skills travel with it as MCP prompts:
{
"mcpServers": {
"claude-cycling": {
"command": "uvx",
"args": ["--from", "git+https://github.com/elias-ramzi/ClaudeCyclingMCP", "claude-cycling-mcp"]
}
}
}Claude Desktop — drag claude-cycling-mcp.mcpb from the
latest release onto the app.
Full details, including how to upload the skills to your Claude account so the model reaches for them on its own: Installing.
What you can do
Once connected, describe a session and ask Claude to build it. It drives these tools:
Write a spec —
spec_schemahands the model the schema and the authoring notes, so the JSON comes out valid the first time.Check it —
validate_specfor errors and warnings,describe_specfor the block-by-block table with NP/IF/TSS.Render —
render_zworeturns the XML plus the filename to upload it under (the filename becomes the MyWhoosh library name);render_garminreturns a payload ready for the Garmin MCP'supload_workout. Both take an optionalout_path.Verify before you upload —
check_garmin_payloadcompares the payload you composed against the digest the renderer issued, and returns a checklist for confirming by eye.Verify after —
verify_garmin_uploaddiffs what Garmin returns against what was sent;verify_mywhoosh_importdoes the same for the scraped MyWhoosh builder header.
One thing worth reading before your first workout: On FTP — the two platforms consume FTP at different times, and getting it wrong on the MyWhoosh side is silent.
Skills
Two bundled procedures in .claude/skills/, triggering on descriptions of a
session — "create", "add", "send", "put it on" — not only on "upload":
garmin-upload— renders, uploads via the Garmin MCP, then fetches the workout back and compares it against what was sent. Offers to schedule it.mywhoosh-upload— drives the MyWhoosh builder through Claude in Chrome, since there is no API. It reads MyWhoosh's FTP out of the builder before rendering, so the fractions are right by construction, and stops for explicit confirmation before the export — which spends a finite slot credit.
Each step states what it expects to see, so a run that breaks after a platform redesign reports which assumption failed instead of quietly producing nothing.
How a skill reaches your client, and what each needs to actually run: Skills.
Documentation
Spec format — every field, the block types, and how FTP is consumed by each platform.
Tools — the full tool reference, plus the
.zwoand Garmin format rules the renderers are pinned to.Garmin schema provenance — what the payload shape was derived against, the two silent-failure findings, and verification status.
Skills — what each bundled skill does, and the two ways one runs.
Testing — the offline gate and the live Garmin round-trip.
CLAUDE.md — architecture, and the platform quirks that are load-bearing.
CONTRIBUTING.md — how to propose a change, and the two project-specific rules.
Development
uv venv && uv sync --extra devruff check . && ruff format --check . && pytestThe offline suite is hermetic and needs no credentials. The live Garmin round-trip is deselected by default — see Testing.
License
MIT.
Available Tools
6 toolsdescribe_specA
Render a workout spec as a human-readable block table for sanity-checking.
Shows every block with its computed watts, duration and elapsed time, plus total duration, average and normalised power, estimated IF and TSS, and total work. Use this before rendering to confirm a session is what was intended — it is much easier to spot a wrong number here than in XML.
Inputs: spec (required). Returns: result
render_garminA
Render a workout spec to a Garmin Connect workout payload.
The returned "payload" is ready to hand to the Garmin MCP's upload_workout tool unchanged. Power targets are absolute watts via power.between (workoutTargetTypeId 6); percentages in the spec are resolved against the spec's FTP. Repeats become RepeatGroupDTO groups carrying a complete endCondition, including the numeric conditionTypeId that Garmin needs to avoid silently corrupting the repeat count.
This server does not upload. Use the bundled garmin-upload skill, which uploads and then verifies by fetching the workout back and comparing it against what was sent.
Pass out_path to also write the payload as JSON to disk.
Inputs: spec (required), out_path. Returns: result
render_zwoA
Render a workout spec to a MyWhoosh-compatible .zwo file.
Returns the XML and the filename to upload it under. That filename matters: MyWhoosh takes the workout's library name from the uploaded filename, not from the tag inside the file. Set the spec's "filename" field to control it.
Ramps are emitted as explicit and repeats are flattened into individual blocks, both so the result stays unambiguous and editable inside MyWhoosh's editor.
Pass out_path to also write the file to disk; the content is returned either way. Writing is the only filesystem access this server performs.
Inputs: spec (required), out_path. Returns: result
spec_schemaA
Return the workout spec's JSON schema plus the notes needed to author one.
Call this before writing a spec by hand if the format is not already known.
Inputs: no named parameters. Returns: result
validate_specA
Check a workout spec and report every problem found.
Catches the mistakes that actually bite: empty workouts, zero or negative durations, power that resolves outside a sane fraction of FTP (usually watts and percentages swapped), ramps whose endpoints are equal, both unit forms on one block, percentages with no FTP to resolve them against, and empty or nested repeats. Unknown keys are reported as warnings, which catches typos like "powr_w" that would otherwise be silently ignored.
Returns valid/errors/warnings, plus a summary of the workout when it is valid.
Inputs: spec (required). Returns: result
verify_garmin_uploadA
Check that Garmin stored the workout that was actually sent.
Pass the payload given to upload_workout, and the response from get_workout_by_id for the workout it created. Returns the list of differences; an empty list means Garmin kept what was sent.
This compares against the sent payload, not against an assumed read shape — the difference matters, because get_workout_by_id returns a lossy projection that will happily agree with a wrong-units payload. It also catches a repeat count silently corrupted by a missing conditionTypeId.
Two things it cannot prove: whether a power target was stored as watts or as %FTP (the curated read drops the targetValueUnit field that distinguishes them), and whether the workout displays correctly on a head unit. Confirm those by opening the workout in Garmin Connect once.
Pure comparison, no network access.
Inputs: fetched (required), payload (required). Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 6 tools
Each tool has a clearly distinct purpose: validation, human-readable description, ZWO rendering, Garmin rendering, upload verification, and schema retrieval. The two render tools differ by target format, and validate_spec vs describe_spec are complementary (errors vs computed values), leaving no ambiguity.
All tool names use lowercase with underscores and mostly follow a verb_noun pattern (validate_spec, describe_spec, render_zwo, render_garmin, verify_garmin_upload). The only exception is spec_schema, a noun phrase rather than an imperative verb, which is a minor deviation from the otherwise consistent pattern.
Six tools is well-scoped for a workout spec server. Each tool earns its place, covering the full authoring workflow without redundancy or bloat.
The surface covers the complete lifecycle: schema reference (spec_schema), validation (validate_spec), visualization (describe_spec), rendering to two target formats, and post-upload verification. There are no obvious gaps for the stated domain.
Maintenance
Related MCP Connectors
Remote MCP server for training, nutrition, wellness, and performance data with OAuth 2.0.
Adaptive running coach MCP server — training data, plans, and recovery for AI assistants.
Convert, validate, and inspect workout files (FIT, TCX, ZWO, Garmin Connect) for AI agents.
Fitness MCP for coaches and gyms: build workouts and read clients' training from live Superset data.
Related MCP Servers
- AlicenseBqualityDmaintenanceAn MCP server for interacting with the Wahoo Cloud API to manage workouts, routes, training plans, and power zones. It enables users to list, retrieve, and create fitness data through secure OAuth 2.0 authentication.97GPL 3.0
- AlicenseBqualityDmaintenanceLocal MCP server for analyzing Intervals.icu accounts, creating AI-assisted training plans, and applying managed drafts to the calendar after explicit confirmation.14AGPL 3.0
- AlicenseAqualityCmaintenanceMCP server for Intervals.icu that enables AI assistants to manage athletic training data, including activities, calendar events, wellness metrics, and workout libraries.19Apache 2.0
- FlicenseNot gradedqualityCmaintenanceMCP server for TrainingPeaks that reads workouts, fitness metrics (CTL/ATL/TSB), and health data, and creates/updates/deletes planned workouts. Enables AI-powered training plan management and automated adjustment based on performance, fatigue, and injury.-