Skip to content
Conduit

mcp server

workout-app

MCP-native workout tracker. Your AI agent (Claude, Cursor, …) writes the program; you lift. Open-source, ~1,500 tests, offline-first PWA, Supabase + RLS.

PierreTsia/workout-appcommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

This project does not publish an install method we can turn into a config. Follow the README instead.

README

synced Oct 21, 2026

GymLogic

A mobile-first PWA for tracking strength training — with offline support, progression tracking, workout analytics, and an MCP server that lets your AI agent read your training data, help you adapt or design programs in conversation, and save a program to your account when you are ready.

Built with React 19, TypeScript, Supabase, and Tailwind CSS. Live at gymlogic.me.


Connect your AI agent

GymLogic exposes your training data as an MCP (Model Context Protocol) server. Claude, Cursor, Le Chat, and other MCP-compatible agents can search your exercise catalog, review session history and stats, see what is programmed next (get_upcoming_workouts), and design or adapt a training plan with you — then create_program writes a new multi-day program to your profile (same persistence as the in-app AI program flow: always dry_run first to preview, then dry_run: false to apply and set it active). “Adapt” in practice means: read what you have, agree on a revised day/exercise layout, save it as the new active program. Fine-grained Builder edits (single-day tweaks) stay in the app unless we add more MCP tools later. The in-app experience stays the default; MCP is additive.

Setup guides:

Building an agent / runtime? The skills/gymlogic-mcp/SKILL.md drop-in prompt context covers tool intent mapping, the per-side weight convention for unilateral equipment, and edge cases — load it into your agent's system prompt or skill registry alongside the connector.

Available tools:

ToolWhat it does
search_exercisesSearch 360+ exercises by name (FR/EN), muscle group, equipment, difficulty
resolve_exercisesResolve several exercise names to UUIDs in one call, with weight_convention / measurement_type
get_exercise_detailsFull exercise metadata: instructions, muscles, equipment, media
get_workout_historyPast sessions with sets, weights, and PR flags
get_training_statsVolume by muscle group, personal records, session frequency
get_upcoming_workoutsProgrammed training days and exercises
list_programsAll your programs (active / drafts / archived) with id, day count, active-cycle flag
get_program_detailsOne program's full structure by UUID — days, exercises, sets / reps / weights / rest
create_programCreate or replace your active multi-day program. Defaults to dry_run: true (JSON preview only); dry_run: false commits and sets it active
update_programEdit an existing program in place by program_id — rename, add / remove / reorder days, swap exercises, revise prescriptions. Preserves logged history. dry_run: true by default
create_workout_dayOne ad-hoc session (program_id: null) without touching the active program. dry_run: true by default
render_session_cardShow the latest finished session as a card in an MCP Apps host (Claude Desktop / mobile); a text summary elsewhere

Plus an app-only apply_program_patch (hidden from the model) that a conversation Decision Card calls when the athlete clicks Apply.

Twelve tools, one app-only apply tool, one MCP Resource (exercise_catalog_schema), and two MCP App Views (ui://gymlogic/session-card, ui://gymlogic/program-patch).

The MCP server runs as a single Supabase Edge Function with hand-rolled JSON-RPC 2.0, OAuth 2.1 for in-app client auth or Personal Access Tokens for headless / long-lived auth, and RLS-scoped queries so each user only sees their own data (reads and writes).

Personal Access Tokens (PATs) — glp_… bearer tokens, full-account scope, lifetimes 30 / 90 / 365 days or never, manual revocation. Create and manage them at Account → Security & access → API tokens. PATs are the recommended path for Cursor and any headless agent; OAuth remains the default for Claude Desktop and Le Chat.

See the Epic Brief, the Phase 1 tech plan (as built), the agent → save → gym tech plan, and the Personal Access Tokens tech plan for architecture details.


Features

Workout session

  • Day selector and horizontal exercise strip; per-exercise sets table (reps, weight, done checkbox)
  • RIR (reps in reserve) per set with in-session load suggestions based on previous set performance
  • Full-screen rest timer (countdown, audio, vibration, notifications) and session timer; session summary on completion
  • Last session reference per exercise
  • Exercise detail: structured instructions, YouTube demo, illustration, body-map muscle highlight, and exercise history sheet (past sessions + trend chart)
  • Quick workout: generate a single ad-hoc session with AI (constraints + Edge Function) when you want something off-program

Programs & cycles

  • Create program flow: pick a template, start blank, or build an AI-generated multi-day program (constraints wizard, preview, confirm) via Gemini
  • Template path resolves equipment swaps using the exercise alternatives catalog
  • Sessions linked to training cycles; cycle summary route surfaces completion stats and history
  • Full CRUD for programs (workout days, slot exercises) with drag-and-drop reorder and exercise library picker

Progression & PRs

  • Epley estimated 1RM from logged sets; automatic PR detection with a PR badge when you beat your previous best
  • estimated_1rm, was_pr, and RIR persisted on set_logs for history and suggestions

History & analytics

  • Stats dashboard (sessions, sets, PRs, volume) and reverse-chronological session list with expandable details
  • Per-exercise 1RM-over-time charts
  • Activity heatmap and muscle group breakdown

Achievements

  • Badge system with multiple tracks (consistency, strength, volume, exploration)
  • Achievement detail drawer with progress tracking
  • Title system — unlock display titles based on achievements

Exercise library

  • 360+ exercise catalog with search, muscle group and equipment filters, difficulty badges
  • Rich metadata: FR/EN names, instructions (setup, movement, breathing, common mistakes), YouTube URLs, illustrations, secondary muscles
  • Exercise detail page with body-map, instructions, history, and media

Account & email

  • Account page with avatar upload, display name, weight unit preference
  • Transactional emails via Resend (welcome, lifecycle) with unsubscribe management
  • Custom domain email (admin@gymlogic.me)

Offline-first sync

  • SyncService queues set logs and session finishes in localStorage with fingerprint-based deduplication
  • Queue drains on reconnect and on load when authenticated
  • Sync status chip in the UI

PWA & shell

  • Install prompt, Workbox service worker (app shell + runtime caching), standalone display
  • Route-level error boundaries for failed navigations
  • Bottom nav + drawer App shell for workout, history, builder, library, achievements

Internationalization & theming

  • FR / EN via react-i18next with per-feature namespaces
  • kg / lbs display preference (storage always in kg)
  • Dark / light theme

Auth & admin

  • Google OAuth via Supabase Auth; AuthGuard on protected routes
  • OAuth 2.1 server for MCP client authentication with consent page
  • Personal Access Tokens (glp_…) as long-lived MCP auth: create, list, revoke from /account/api-tokens; HMAC-SHA-256 hashed at rest with a server-side pepper; never re-shown after creation
  • Admin: exercise review, enrichment tools, feedback triage, gated by admin_users

Tech Stack

LayerTechnology
FrameworkReact 19
BuildVite 7
LanguageTypeScript 5.9
StateJotai (atomWithStorage for persistence)
Server stateTanStack React Query 5
BackendSupabase (Postgres + Auth + RLS + Edge Functions)
MCP serverHand-rolled JSON-RPC 2.0 on Supabase Edge Functions
AI generationGoogle Gemini 2.5 Flash (via Edge Functions)
UI componentsshadcn/ui (Radix primitives)
StylingTailwind CSS 3.4 + tailwindcss-animate
ChartsRecharts
Drag & dropdnd-kit
i18nreact-i18next + i18next-browser-languagedetector
EmailResend (transactional)
Iconslucide-react
PWAvite-plugin-pwa (Workbox)
Themingnext-themes
TestingVitest + Testing Library + Playwright
CI/CDGitHub Actions + Vercel — prod deploys on a release, not on every merge (ADR 0025)

Getting Started

Prerequisites

  • Node.js 20+
  • A Supabase project (or the Supabase CLI for local dev)

Setup

git clone https://github.com/PierreTsia/workout-app.git
cd workout-app
npm install

Create a .env file from the example:

cp .env.example .env

Fill in your Supabase credentials (hosted project):

VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key

For local Supabase, put VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY from supabase status in .env.local (see Local Supabase development).

Development

npm run dev

Build

npm run build
npm run preview

Local Supabase development

Use a local stack so day-to-day usage and experiments do not write to production. Requires Docker and the Supabase CLI.

npm scripts

ScriptCommand
npm run supabase:startsupabase start — Postgres, Auth, Studio, Storage, etc.
npm run supabase:stopsupabase stop — stops containers; data stays in Docker volumes
npm run supabase:resetsupabase db reset — re-runs all migrations + seed.sql (wipes local user data)

Typical workflow

  1. npm run supabase:start
  2. Copy API URL and anon key from supabase status into .env.local as VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY (point at http://127.0.0.1:54321, not *.supabase.co).
  3. npm run dev
  4. When you need a clean database: npm run supabase:reset (stack must already be running).

The app reads Supabase URL/anon key from Vite env (import.meta.env). Restart npm run dev after changing env files.

Environment files

FileWho reads itPurpose
.env.localVite (npm run dev, npm run build locally)VITE_* for the browser client — use for local API URL + anon key
.env (project root)Supabase CLI when parsing supabase/config.tomlenv(...) placeholders (e.g. Google OAuth for local Auth). The CLI does not load .env.local.
Vercel / CIProduction buildsSet VITE_* in the dashboard; nothing from .env.local is deployed

Service URLs and ports (default)

PortService
54321REST / Auth / Edge Functions API (VITE_SUPABASE_URL)
54322Postgres (postgresql://postgres:postgres@127.0.0.1:54322/postgres)
54323Supabase Studio — table editor, SQL, Auth users
54324Inbucket — captures auth emails locally

Google Sign-In locally

supabase/config.toml enables Google when the corresponding vars exist in project-root .env. In Google Cloud Console → APIs & Services → Credentials → your Web OAuth client:

  • Authorized redirect URIs must include: http://127.0.0.1:54321/auth/v1/callback
  • Keep your hosted callback too if you use the same client for prod
  • Authorized JavaScript origins: http://localhost:5173 and http://127.0.0.1:5173

After changing config.toml or .env auth vars, run npm run supabase:stop then npm run supabase:start.

Edge Functions

With VITE_SUPABASE_URL pointing at localhost, supabase.functions.invoke hits local functions.

FunctionPurpose
embedded-agentConversational onboarding/program agent (chat turns + program draft via Gemini, Groq fallback)
generate-quick-workoutOne-shot quick workout generation via Gemini (Groq fallback)
mcpMCP server (JSON-RPC 2.0, 6 tools + 1 resource; accepts both OAuth/session JWTs and Personal Access Tokens)
create-patMints a new Personal Access Token (browser-session JWT only — anti-escalation)
send-transactional-emailWelcome and lifecycle emails via Resend
email-unsubscribeEmail preference management
delete-accountAccount deletion with data cleanup

The AI keys must be available to the Edge runtime:

  1. Copy supabase/functions/.env.example → supabase/functions/.env
  2. Set GEMINI_API_KEY= to a key from Google AI Studio
  3. Optional but recommended: set GROQ_API_KEY= to a key from the Groq Console (no-card free tier). This enables the AI Provider Fallback (#405): when Gemini is unavailable (503), in-app AI calls retry once on Groq before erroring. Absent = fallback disabled cleanly; the app stays fully functional on Gemini alone.
  4. Restart: npm run supabase:stop then npm run supabase:start

For hosted Supabase, set secrets via supabase secrets set GEMINI_API_KEY=... (and GROQ_API_KEY=...) or the dashboard.

History seed data

npm run seed:history -- --user-id=<your-auth-uuid>

Inserts ~30+ finished sessions over the last ~90 days. See the script for options (--list-users, --url, etc.).

E2E tests

Playwright tests expect local Supabase. Start the stack before npm run test:e2e.


Project Structure

src/
├── components/
│   ├── ui/            # shadcn primitives
│   ├── workout/       # WorkoutDayCarousel, ExerciseStrip, SetsTable, RestTimerOverlay, SessionSummary
│   ├── history/       # StatsDashboard, SessionList, ActivityTab, Heatmap
│   ├── builder/       # DayList, DayEditor, ExerciseLibraryPicker
│   ├── library/       # ExerciseCatalog, AddExerciseToDaySheet
│   ├── body-map/      # BodyMap muscle highlight visualization
│   ├── achievements/  # BadgeGrid, AchievementDetailDrawer
│   └── AppShell.tsx   # Layout shell with bottom nav
├── hooks/             # useWorkoutDays, useLastSession, useBestPerformance, useWeightUnit, etc.
├── lib/               # supabase client, syncService, epley 1RM, i18n, formatters, supabase-oauth
├── locales/           # en/ and fr/ JSON translation files
├── pages/             # LoginPage, WorkoutPage, HistoryPage, BuilderPage, AchievementsPage, etc.
├── router/            # Routes + AuthGuard
├── store/             # Jotai atoms (session, rest, auth, sync, locale, theme)
├── types/             # TypeScript types (auth, database, achievements, etc.)
└── main.tsx

supabase/
├── functions/
│   ├── _shared/       # Cross-function libs: AI provider fallback (withFallback, providerError, groqClient), catalog, quota
│   ├── embedded-agent/        # Conversational onboarding/program agent (chat + program draft)
│   ├── generate-quick-workout/ # One-shot quick workout generation
│   ├── mcp/           # MCP server (JSON-RPC handler, tools, resources, formatters, PAT auth)
│   ├── create-pat/    # Mints Personal Access Tokens (session-JWT only)
│   ├── send-transactional-email/
│   ├── email-unsubscribe/
│   └── delete-account/
├── migrations/        # Postgres schema migrations
└── seed.sql           # Exercise catalog + seed data

docs/
├── mcp-connect/       # Per-client MCP connection guides
├── Epic_Brief_*       # Epic briefs for current work
├── Tech_Plan_*        # Technical plans
├── T*                 # Implementation tickets
└── done/              # Completed epic/ticket docs

Database

Core tables with row-level security scoped to the authenticated user:

TablePurpose
exercisesExercise catalog (360+ items, FR/EN names, instructions, media)
programsUser training programs
cyclesTraining cycles within a program
workout_daysTraining days (label, sort order)
workout_exercisesExercises assigned to a day (sets, reps, weight, rest)
sessionsCompleted workout sessions
set_logsIndividual set records (reps, weight, 1RM, PR flag, RIR)
achievementsBadge definitions and user progress
admin_usersAdmin access control
exercise_feedbackUser-reported content issues
transactional_email_logEmail delivery tracking
email_preferencesUser email opt-in/out
personal_access_tokensHashed PATs for long-lived MCP auth (HMAC-SHA-256 + server-side pepper; plaintext never stored)

See supabase/migrations/ for the full schema.


License

MIT


Built by Pierre Tsiakkaros

↑↓ navigate openesc close