Skip to main content
Glama
README.md
# technogym-mcp

An [MCP](https://modelcontextprotocol.io) server that gives AI assistants read access to your
Technogym **mywellness** training history: workout sessions, per-exercise data with per-set
reps / weight / rest, per-second exercise analytics (machine channels + heart rate) and
whole-session heart-rate traces.

It runs as a persistent local HTTP server that starts with Windows, so every MCP client on the
machine (Claude Desktop, Claude Code, Cursor, ...) can connect to the same endpoint.

> **How it works / caveat.** Technogym's official API is partner-only (B2B). This server
> speaks the same JSON API the official end-user web app (endusernext.mywellness.com) uses,
> logging in with your normal email + password and identifying itself with that app's
> client headers. No HTML scraping is involved, but it will still break if Technogym changes
> the login flow or the client-trust rules. The old `mywellness.com/cloud` portal that
> earlier tools (e.g. [lcanis/gymexport](https://github.com/lcanis/gymexport)) scraped was
> retired in 2026 and now redirects to technogym.com.

## Tools

| Tool | What it returns |
|---|---|
| `get_account_info` | Logs in and returns user id, name, culture, facilities and current Movergy score. Use to verify credentials. |
| `list_workout_sessions(from_date?, to_date?, only_workouts=true)` | Sessions in a range (default: last 30 days), newest first: `session_id`, `date`, time, name, duration, MOVEs, calories, exercise counts. |
| `get_workout_session(session_id, date?)` | Every exercise with machine, status, duration, calories, MOVEs, muscles, performed and prescribed sets (reps, kg, rest), plus session efficacy and muscle scores. |
| `get_recent_workouts(days=14)` | Lists and fully loads all sessions from the last N days. |
| `get_exercise_analytics(analytics_id, sample_every_seconds=1)` | Per-second machine channels, HR samples, HR zones and summary metrics (total weight, est. 1RM, METs...) for one exercise. |
| `get_session_heart_rate(session_id, date?, sample_every_seconds=10)` | Timestamped HR trace, HR zones and avg/min/max HR for a whole session. |

A session is addressed by `session_id` **and** its `date` (the API partitions by day). Pass the
date whenever you have it; if omitted, the last year of history is scanned to find it.

`GET /health` on the same port reports liveness and whether a session is authenticated.

## Setup

Requires [uv](https://docs.astral.sh/uv/) (it manages Python for you).

```powershell
git clone https://github.com/ErikAnkerKilbergSkallevold/technogym_mcp
cd technogym_mcp
copy .env.example .env      # then fill in MYWELLNESS_EMAIL / MYWELLNESS_PASSWORD
uv sync
```

### Run once (foreground)

```powershell
uv run technogym-mcp                   # HTTP on http://127.0.0.1:8765/mcp
uv run technogym-mcp --transport stdio # for clients that spawn the server themselves
```

### Start with Windows (always available)

```powershell
powershell -ExecutionPolicy Bypass -File scripts\install-startup.ps1
```

This registers a Scheduled Task named **Technogym MCP Server** that starts silently at logon
(via `uvw.exe`, no console window), restarts on failure, and never times out. Logs go to
`logs\technogym-mcp.log`. Remove it with `scripts\uninstall-startup.ps1`. After editing
`.env`, restart it: `Restart-ScheduledTask "Technogym MCP Server"`.

### Expose it publicly (Cloudflare Tunnel)

To reach the server from other machines or from a hosted Claude, tunnel it out through Cloudflare
instead of opening a port. One-time prerequisites:

```powershell
winget install Cloudflare.cloudflared
cloudflared tunnel login            # browser: pick your zone
```

Set `TECHNOGYM_MCP_PUBLIC_HOST=technogym.example.com` in `.env`, restart the server task, then:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\install-tunnel.ps1 -Hostname technogym.example.com
```

This creates a named tunnel, routes the DNS record, and installs `cloudflared` as a Windows
service (it will ask for elevation once). The MCP endpoint is then `https://technogym.example.com/mcp`.

Alternatively, create the tunnel in the Zero Trust dashboard (Networks → Tunnels → Create,
choose Windows, run the provided `cloudflared service install <token>` command) and add a public
hostname there pointing at `http://localhost:<port>`. That also installs an auto-starting Windows
service; skip the script in that case, since it would create a second tunnel.

**The MCP endpoint has no authentication of its own.** Pick one of two ways to protect it:

* **Cloudflare Access** (best when your clients can complete a browser login, e.g. Claude Code
  with `claude mcp add --transport http`): add an Access application for the hostname
  (Zero Trust → Access → Applications → Self-hosted) with a policy that allows only your email.
* **Unguessable path** (needed for clients that cannot send auth headers or do a Cloudflare
  login, e.g. **claude.ai custom connectors**): set `TECHNOGYM_MCP_PATH=/<32 random hex>/mcp`
  in `.env`, restart the server task, and treat the full URL as a secret. Requests to any other
  path get 404. Optionally still put Access on `/health` and `/`.

### Adding to claude.ai

1. Make sure the tunnel is up and `https://<host>/health` answers.
2. In claude.ai go to **Settings → Connectors → Add custom connector**.
3. Name it `Technogym`, paste the full endpoint URL (`https://<host><TECHNOGYM_MCP_PATH>`),
   leave OAuth fields empty, and click **Add**.
4. In a chat, open the tools menu (the sliders icon) and enable the Technogym connector.

## Connecting a client

**Claude Code**

```powershell
claude mcp add -s user --transport http technogym http://127.0.0.1:8765/mcp
```

**Claude Desktop** (`claude_desktop_config.json`), either pointing at the running server:

```json
{
  "mcpServers": {
    "technogym": { "type": "http", "url": "http://127.0.0.1:8765/mcp" }
  }
}
```

or spawning it over stdio:

```json
{
  "mcpServers": {
    "technogym": {
      "command": "uv",
      "args": ["run", "--directory", "C:\\path\\to\\technogym_mcp", "technogym-mcp", "--transport", "stdio"]
    }
  }
}
```

## Configuration (`.env`)

| Variable | Default | Purpose |
|---|---|---|
| `MYWELLNESS_EMAIL` | | Your mywellness login |
| `MYWELLNESS_PASSWORD` | | Your mywellness password |
| `TECHNOGYM_MCP_HOST` | `127.0.0.1` | Bind address. Keep on loopback; there is no auth on the MCP endpoint. |
| `TECHNOGYM_MCP_PORT` | `8765` | Port |
| `TECHNOGYM_MCP_PUBLIC_HOST` | | Public hostname(s) accepted in the Host header, e.g. `technogym.example.com`, when exposed through a tunnel. The SDK's DNS-rebinding protection rejects unknown hosts with HTTP 421. |
| `TECHNOGYM_MCP_PATH` | `/mcp` | URL path of the MCP endpoint. Use an unguessable path when the endpoint must be reachable without auth headers (see claude.ai below). |
| `TECHNOGYM_MCP_LOG_LEVEL` | `INFO` | Logging level |

## API notes (for maintainers)

* Login: `POST https://core.mywellness.com/v2/enduser/authentication/login` with
  `{username, password, keepMeLoggedIn}` and headers `X-MWAPPS-APPID: EC1D38D7-D359-48D0-A60C-D8C0B8FB9DF9`,
  `X-MWAPPS-CLIENT: enduserweb`. Returns `token`, `userContext.id`, `facilities[].url`.
* Data: `POST https://services.mywellness.com/{facilityUrl}/Training/User/{userId}/<Action>`
  with `Authorization: Bearer <token>` and the same X-MWAPPS headers. Actions used:
  `ActivityHistory`, `GetPerformedWorkoutSessionByIdCr`, `GetHrSession`, `MyMovergy`;
  plus `Training/CardioLog/{analyticsId}/Details`. Dates are `yyyyMMdd` partition keys.
* Localised strings (exercise names, units) come back in the account's culture.

## Development

```powershell
uv run pytest
uv run ruff check .
```

## License

MIT

TDQS

A4.4/5.0

Scored across 6 tools

Disambiguation4/5

Each tool targets a distinct data view: account, session list, session detail, recent workouts, per-exercise analytics, and heart-rate trace. get_recent_workouts overlaps with list_workout_sessions plus get_workout_session, but it is clearly framed as a convenience wrapper, so the ambiguity is minimal.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern using get_ or list_ prefixes: get_account_info, list_workout_sessions, get_workout_session, get_recent_workouts, get_exercise_analytics, get_session_heart_rate. There is no mixing of styles or vague verbs.

Tool Count5/5

Six tools is well-scoped for a read-only fitness/wellness data server. Each tool serves a clear purpose and the count is neither bloated nor too thin.

Completeness5/5

The tool surface covers the full read-only workflow: account verification, session listing, session detail, exercise-level analytics, heart-rate traces, and a convenience bulk loader. The data flow from list_workout_sessions to get_workout_session to get_exercise_analytics is complete with no obvious dead ends.

Maintenance

ActivityMaintained
ResponsivenessNo issues