Skip to main content
Glama
ClaudeCyclingMCP

ClaudeCyclingMCP

by elias-ramzi

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, free and repeat blocks. Power is explicit: power_w or power_pct, never guessed from magnitude (spec format).

  • 📊 See the session before you build it — describe_spec prints 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_w that would otherwise pass in silence.

  • 🚴 Two renderers, one source — .zwo for MyWhoosh and a Garmin upload_workout payload, 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_path is 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-tools

Any 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_schema hands the model the schema and the authoring notes, so the JSON comes out valid the first time.

  • Check it — validate_spec for errors and warnings, describe_spec for the block-by-block table with NP/IF/TSS.

  • Render — render_zwo returns the XML plus the filename to upload it under (the filename becomes the MyWhoosh library name); render_garmin returns a payload ready for the Garmin MCP's upload_workout. Both take an optional out_path.

  • Verify before you upload — check_garmin_payload compares the payload you composed against the digest the renderer issued, and returns a checklist for confirming by eye.

  • Verify after — verify_garmin_upload diffs what Garmin returns against what was sent; verify_mywhoosh_import does 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 .zwo and 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.

  • docs/versioning.md · CHANGELOG.md · SECURITY.md

Development

uv venv && uv sync --extra dev
ruff check . && ruff format --check . && pytest

The offline suite is hermetic and needs no credentials. The live Garmin round-trip is deselected by default — see Testing.

License

MIT.

Available Tools

6 tools
describe_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

    A4.5/5.0

    Scored across 6 tools

    Disambiguation5/5

    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.

    Naming Consistency4/5

    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.

    Tool Count5/5

    Six tools is well-scoped for a workout spec server. Each tool earns its place, covering the full authoring workflow without redundancy or bloat.

    Completeness5/5

    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

    ActivityInactive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      B
      quality
      D
      maintenance
      An 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.
      9
      7
      GPL 3.0
    • A
      license
      B
      quality
      D
      maintenance
      Local MCP server for analyzing Intervals.icu accounts, creating AI-assisted training plans, and applying managed drafts to the calendar after explicit confirmation.
      14
      AGPL 3.0
    • A
      license
      A
      quality
      C
      maintenance
      MCP server for Intervals.icu that enables AI assistants to manage athletic training data, including activities, calendar events, wellness metrics, and workout libraries.
      19
      Apache 2.0
    • F
      license
      Not graded
      quality
      C
      maintenance
      MCP 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.
      -