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.
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, 2026GymLogic
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:
| Tool | What it does |
|---|---|
search_exercises | Search 360+ exercises by name (FR/EN), muscle group, equipment, difficulty |
resolve_exercises | Resolve several exercise names to UUIDs in one call, with weight_convention / measurement_type |
get_exercise_details | Full exercise metadata: instructions, muscles, equipment, media |
get_workout_history | Past sessions with sets, weights, and PR flags |
get_training_stats | Volume by muscle group, personal records, session frequency |
get_upcoming_workouts | Programmed training days and exercises |
list_programs | All your programs (active / drafts / archived) with id, day count, active-cycle flag |
get_program_details | One program's full structure by UUID — days, exercises, sets / reps / weights / rest |
create_program | Create or replace your active multi-day program. Defaults to dry_run: true (JSON preview only); dry_run: false commits and sets it active |
update_program | Edit 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_day | One ad-hoc session (program_id: null) without touching the active program. dry_run: true by default |
render_session_card | Show 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 onset_logsfor 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
SyncServicequeues 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
| Layer | Technology |
|---|---|
| Framework | React 19 |
| Build | Vite 7 |
| Language | TypeScript 5.9 |
| State | Jotai (atomWithStorage for persistence) |
| Server state | TanStack React Query 5 |
| Backend | Supabase (Postgres + Auth + RLS + Edge Functions) |
| MCP server | Hand-rolled JSON-RPC 2.0 on Supabase Edge Functions |
| AI generation | Google Gemini 2.5 Flash (via Edge Functions) |
| UI components | shadcn/ui (Radix primitives) |
| Styling | Tailwind CSS 3.4 + tailwindcss-animate |
| Charts | Recharts |
| Drag & drop | dnd-kit |
| i18n | react-i18next + i18next-browser-languagedetector |
| Resend (transactional) | |
| Icons | lucide-react |
| PWA | vite-plugin-pwa (Workbox) |
| Theming | next-themes |
| Testing | Vitest + Testing Library + Playwright |
| CI/CD | GitHub 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
| Script | Command |
|---|---|
npm run supabase:start | supabase start — Postgres, Auth, Studio, Storage, etc. |
npm run supabase:stop | supabase stop — stops containers; data stays in Docker volumes |
npm run supabase:reset | supabase db reset — re-runs all migrations + seed.sql (wipes local user data) |
Typical workflow
npm run supabase:start- Copy API URL and anon key from
supabase statusinto.env.localasVITE_SUPABASE_URLandVITE_SUPABASE_ANON_KEY(point athttp://127.0.0.1:54321, not*.supabase.co). npm run dev- 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
| File | Who reads it | Purpose |
|---|---|---|
.env.local | Vite (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.toml | env(...) placeholders (e.g. Google OAuth for local Auth). The CLI does not load .env.local. |
| Vercel / CI | Production builds | Set VITE_* in the dashboard; nothing from .env.local is deployed |
Service URLs and ports (default)
| Port | Service |
|---|---|
| 54321 | REST / Auth / Edge Functions API (VITE_SUPABASE_URL) |
| 54322 | Postgres (postgresql://postgres:postgres@127.0.0.1:54322/postgres) |
| 54323 | Supabase Studio — table editor, SQL, Auth users |
| 54324 | Inbucket — 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:5173andhttp://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.
| Function | Purpose |
|---|---|
embedded-agent | Conversational onboarding/program agent (chat turns + program draft via Gemini, Groq fallback) |
generate-quick-workout | One-shot quick workout generation via Gemini (Groq fallback) |
mcp | MCP server (JSON-RPC 2.0, 6 tools + 1 resource; accepts both OAuth/session JWTs and Personal Access Tokens) |
create-pat | Mints a new Personal Access Token (browser-session JWT only — anti-escalation) |
send-transactional-email | Welcome and lifecycle emails via Resend |
email-unsubscribe | Email preference management |
delete-account | Account deletion with data cleanup |
The AI keys must be available to the Edge runtime:
- Copy
supabase/functions/.env.example→supabase/functions/.env - Set
GEMINI_API_KEY=to a key from Google AI Studio - 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. - Restart:
npm run supabase:stopthennpm 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:
| Table | Purpose |
|---|---|
exercises | Exercise catalog (360+ items, FR/EN names, instructions, media) |
programs | User training programs |
cycles | Training cycles within a program |
workout_days | Training days (label, sort order) |
workout_exercises | Exercises assigned to a day (sets, reps, weight, rest) |
sessions | Completed workout sessions |
set_logs | Individual set records (reps, weight, 1RM, PR flag, RIR) |
achievements | Badge definitions and user progress |
admin_users | Admin access control |
exercise_feedback | User-reported content issues |
transactional_email_log | Email delivery tracking |
email_preferences | User email opt-in/out |
personal_access_tokens | Hashed 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