Skip to content

Latest commit

 

History

History
 
 

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

openGym MCP server

A Model Context Protocol bridge that lets an external LLM application (Claude Desktop, Cursor, Cline, Continue, etc.) read your openGym profile — routines, workouts, body-weight log, estimated 1RMs, and muscle balance — directly from your self-hosted ./data directory.

It is read-only, runs locally as a stdio process spawned by the LLM client, adds no new container, and requires no extra authentication. The LLM never sees passkeys, VAPID keys, or session secrets — it can only read the same state-<uid>.json files the openGym api already writes.

The numbers it answers with are computed by the same pure functions the React UI uses (frontend/src/lib/*.js) — estimate1RM, loadOfWorkouts, effectiveRoutine, etc. — so a "what's my bench 1RM?" answer matches the Stats screen exactly.

Phase 1 of a multi-phase plan. Read-only today; long-lived token auth + write tools are planned but not shipped yet. See Roadmap below.

Quick start

1. Install

cd mcp
npm install

2. Point it at your data

The MCP server reads the same ./data directory docker compose up creates. Pick the profile to answer for — its user id is in ./data/db.json under users[].id:

# single-user instance (the common self-hosted case) — auto-detected:
node src/index.js

# multi-user instance, or just to be explicit:
OPENGYM_UID=<your-uid> OPENGYM_DATA=/path/to/openGym/data node src/index.js

3. Register with your LLM client

Add the server to your LLM client's MCP config. For Claude Desktop, edit claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "opengym": {
      "command": "node",
      "args": ["/absolute/path/to/openGym/mcp/src/index.js"],
      "env": {
        "OPENGYM_DATA": "/absolute/path/to/openGym/data",
        "OPENGYM_UID": "<your-uid>"   // optional — auto-detected if you have one profile
      }
    }
  }
}

For Cursor and other MCP-compatible clients, see the client's MCP docs — the same command + args + env shape is what every stdio MCP server expects.

Restart the client; you should see the openGym tools appear with "serving profile <name>" on the server's stderr.

Tools

Nine read-only tools in v1:

Tool What it answers
list_routines What routines are saved in my profile? (names + exercise counts)
get_routine What does the Push Day routine prescribe? (sets/reps/weight and rest per exercise)
preview_session What will the app actually put on screen when I start this routine — after the progression policy and my history have overridden the plan?
get_week_plan What's on my plan this week, including today with any date-specific override?
list_workouts Recent sessions — newest first, with dates, sets done/planned, volume, duration, PRs.
get_workout Full set-by-set breakdown of one session, by workout_id or by date. On a day with two sessions the date alone returns both ids to pick from rather than guessing at one.
get_bodyweight Weigh-ins with the latest weight, the goal line, and deltas vs goal.
estimate_1rm All-time best 1RM for an exercise + the trend, or a PR table across all exercises.
muscle_balance Which muscles I've trained this week/month/all-time, ranked + which I've neglected.

get_routine and preview_session answer two different questions, and confusing them is the easiest way for a coach to give wrong advice. get_routine reports what the routine stores. preview_session reports what the athlete will actually see: a routine holding "squat 3×8 @ 60 kg" opens at 75 kg if the policy deloaded from the last logged session, and the rep counts come from history, not the plan. The routine's own numbers are the last fallback the session builder consults, not the first. Ask preview_session before naming a weight.

Each tool returns JSON the LLM can format as it likes; structured fields (sets, dates, levels) are pre-formatted into human-readable labels in src/labels.js so the LLM doesn't need to re-interpret them.

How it reuses the training logic

The MCP server imports the training helpers under frontend/src/lib/ directly as Node ESM and calls the same functions the React UI does (history.js, onerm.js, muscles.js, exercises.js). The numbers it returns match what the Stats screen shows, because they are the same code.

The one lib file that wasn't Node-safe was i18n.js (Vite's import.meta.glob at module top level) — split into i18n-core.js (pure, Node-safe) + i18n.js (Vite/React bits, re-exports from core). exercises.js got a one-line import.meta.env || {} guard. No new dependencies landed in frontend/, no public exports changed.

Design constraints honoured

  • One runtime dependency beyond the MCP SDK: none. No database driver, no HTTP framework.
  • No new container. stdio transport is spawned by the LLM client; nothing to add to docker-compose.yml.
  • No new auth. The filesystem is the boundary — same as docker compose running on the user's box. No passkey material, VAPID keys, or session secrets ever cross it.
  • No telemetry, no network. Reads ./data/*.json and exits when the LLM client disconnects.

Tests

cd mcp && npm test

58 cases seeding state from frontend/src/lib/demoSeed.js (the same deterministic fixture the public demo runs on). Pins JSON shape and the user-facing edge cases: rest-day override, missing routine, zero-workout history, no synced state, superset links, three 1RM formulas. "Today" is pinned via vi.useFakeTimers({ now: ..., toFake: ['Date'] }) so date-dependent tools see consistent values regardless of when the suite runs. The pure lib functions have their own 92 tests in frontend/src/lib/*.test.js.

Roadmap

  • Done (Phase 1): read-only stdio, 8 tools, direct ./data access.
  • Done (Phase 1.5): preview_session — the policy's next prescription, the opening set rows it produces, and which of plan / confirmed weight / history each number came from.
  • Phase 2: read+write over stdio. Requires a long-lived token auth path minted from the admin dashboard (new ./data/tokens.json) and a write-lock against the web UI's read-modify- write of state-<uid>.json. Tools: log_workout, add_bodyweight, edit_routine, assign_weekday, override_day.
  • Phase 3: Streamable HTTP transport, opt-in 4th container in docker-compose.yml. Same tool implementations, second transport — the MCP SDK supports both behind one tool registration.

License

AGPL-3.0-or-later, same as openGym.