Icuvisor.app - opensource local MCP server, shipped as a signed binary for Mac and Windows

icuvisor is an open-source, MIT-licensed MCP server that connects intervals.icu to Claude, ChatGPT, Pi, Cursor, and other MCP-compatible clients. The repo is hosted on Github, and end-user docs / installers live at https://icuvisor.app.

What’s different from other MCP servers

The goal is to make this usable by athletes who don’t want to install Python tooling, edit JSON config by hand, or hand their API key to a hosted service. Concretely:

  • Single signed Go binary. Install via .dmg / .msi / .deb / Homebrew / Scoop / Winget. No uv, no pip, no hatchling errors. The first-launch flow asks for an API key, detects your athlete ID and timezone, and writes the MCP config for your chosen client(s) automatically.

  • API key lives in the OS keychain (macOS Keychain, Windows Credential Manager, libsecret) — not in a .env file that ends up in dotfile backups or repos.

  • Terse-by-default responses. Every read tool returns the smallest useful payload; heavy fields (streams, raw samples) require an explicit include_full: true. Nulls are stripped (wellness rows with N/A device fields shrink dramatically). The target is fitting a full year of overview data inside a single free-tier Claude conversation without running out of context.

  • Scale labels embedded in the response itselffeel is 1–5, sleepQuality is 1–4, sleepScore is 0–100. These are in _meta.scales on every row, because some MCP clients don’t pass the tool description back to the model at inference time and the LLM would otherwise compare numbers on incompatible scales.

  • Sleep dual-scale handling. sleepQuality (athlete-entered 1–4) and sleepScore (device-imported 0–100) are surfaced as distinct fields with provenance, never collapsed into a single ambiguous “sleep rating.”

  • Wellness provenance + freshness. Bridged fields like readiness carry a _meta.provenance per field (polar | garmin | oura | …) and a _meta.stale: true flag when the bridge hasn’t refreshed in 24h (the Polar-bridge-only-refreshes-when-you-open-the-website problem).

  • Strava-imported activities are detected and returned as a structured unavailable: { reason: "strava_tos" } with a workaround, instead of empty N/A fields the model might hallucinate over.

  • Unit and timezone normalization. Distances render in the athlete’s preferred_units (miles or km) with the unit name in the field key, so no LLM unit drift. All times are in the athlete’s configured timezone.

  • Delete safety is not LLM-controllable. delete_event, delete_events_by_date_range, delete_workout, etc. are gated by an ICUVISOR_DELETE_MODE env var read at startup (none / safe / full). Forbidden tools aren’t registered with the MCP server at all — the model can’t see them, can’t be talked into them, and can’t invent a confirm: true flag. A per-call confirmation argument controlled by the model is not a credible safety guard.

  • Coach mode with per-athlete tool ACLs. Paste a coach-scoped API key, list athletes, and grant each athlete a specific tool subset (e.g. read-only for a prospective client, full read+write for an active one). The athlete_id argument selects who the call targets; it is never a credential. The coach key never leaves the local binary.

  • Workout DSL round-trip. intervals.icu emits structured workout_doc on reads but silently ignores it on writes — uploads need to be re-encoded into the description-string DSL. icuvisor owns that serializer so you pass structured steps and never see the asymmetry. Lossy fields are surfaced as a _meta.lossy_fields warning rather than dropped silently.

  • Free-text descriptions are written through verbatim. Athlete/coach notes in description are not silently normalized into structured blocks on edit.

  • MCP Resources for long-form schemas. The workout-syntax DSL, event categories, custom-item content schemas, and the live athlete profile ship as Resources rather than inline tool descriptions, so the per-session tool-description budget stays small.

  • Toolset tiers. Defaults to a curated core subset (around 17 tools) covering the daily-use path; the full surface is one env var (ICUVISOR_TOOLSET=full) away. Smaller catalog → fewer tool-selection mistakes on smaller models.

  • MCP Prompts (training analysis, recovery check, weekly planning, race-week taper, coach roster triage) for clients that surface prompts.

What it doesn’t do (yet)

  • No Strava / TrainingPeaks direct ingestion — connect your device directly to intervals.icu (or add another MCP server alongside).

  • No hosted relay yet. Everything runs on your machine; your API key and data never leave it.

  • The analyzer family (analyze_trend, compute_zone_time, etc.) is planned for v0.6 — for now the LLM does the math from the read tools.

Status

Beta. Check the roadmap and the PRD, which explains the design choices above in more detail.

I’d love feedback, especially on the installation process and documentation, tool catalog, the response shapes, and anything that feels wrong against how you actually use intervals.icu. Issues and PRs on Github welcome.

Also thanks to the intervals.icu community and other MCP connector authors; a lot of the design decisions here are direct responses to problems people raised on other posts and projects.

6 Likes

Hi Ricardo, thx for making such a tool. I tried to install this tool but there is no way to install it. I asked another AI tool what the problem is but still, I can’t install this.
I have no knowledge in coding, can you explain me how to install your tool?
TIA

Hi! What platform are you on? Windows? Mac? Which install method did you use? Any error message from the installer? What steps have you done when installing?

Hi Ricardo, just wanted to say this is an amazing tool. I had small Python tool that I used to parse my intervals.icu data, but this mcp server has basically replaced that.

1 Like

UPDATE

Get it working following this steps: Connect Claude Desktop – icuvisor — Talk to your intervals.icu data

Because it mentions Mac OS and gives MacOS paths, I tought it wouldn’t work on windows.

But the “claude_desktop_config.json” is in this location: %appdata%\claude

Ricardo, maybe you can update the documentation to include the correct info for windows users as well.

/UPDATE

Hi @Ricardo_N_Cabral

I tried Claude and ChatGPT, but no luck in connecting.

Any ideas please?

I did the setup and followed Connect Claude Desktop – icuvisor — Talk to your intervals.icu data

But that is apparently only for MacOS. I’m on windows 11

Then I tried to connect it to chatGPT.

fist I had to enable Dev tools.

Then I could create an app.

when I want to use the URL (http://127.0.0.1:8765/mcp), I get the message that the URL is unsafe (probably because it’s not HTTPS)

Thanks, you are right. The Claude Desktop manual fallback docs were too macOS-focused.

On Windows, Claude Desktop uses:

%APPDATA%\Claude\claude_desktop_config.json

and the usual icuvisor command path after install is:

C:\Users\<you>\AppData\Local\Programs\icuvisor\icuvisor.exe

So the manual Claude Desktop block should look like this, replacing , athlete ID, and timezone:

  {
    "mcpServers": {
      "icuvisor": {
        "command": "C:\\Users\\<you>\\AppData\\Local\\Programs\\icuvisor\\icuvisor.exe",
        "env": {
          "INTERVALS_ICU_ATHLETE_ID": "i12345",
          "ICUVISOR_TIMEZONE": "Europe/Brussels",
          "ICUVISOR_TRANSPORT": "stdio"
        }
      }
    }
  }

Do not put the API key in that JSON file. icuvisor setup stores it in Windows Credential Manager.

For ChatGPT: the http://127.0.0.1:8765/mcp error is expected if you are using a remote/custom connector UI. That UI needs a public HTTPS endpoint reachable from OpenAI, and it cannot call localhost on your PC. icuvisor’s local HTTP endpoint is intentionally loopback-only. Use a local MCP/stdio-capable client for now (Claude Desktop, ChatGPT app, Codex, Claude Code).