Skip to main content
Glama
henryhf
by henryhf

FitCoach MCP

A remote MCP server that turns Claude or ChatGPT into a fitness coach with memory: persistent goals, conversational workout logging, per-user parameter fitting, and a weekly "plan my sessions" ritual that produces a versioned, explained training plan.

The division of labor: the LLM captures conversation and narrates; the deterministic engine in src/engine/ makes every programming decision (progression, volume, deloads, exercise substitution, run pacing, autoregulation). Same inputs, same plan, always.

Facts live in one place

docs/CURRENT-STATE.md is the source of truth for counts, constants, deployment identity, and what is and isn't built. This README deliberately restates as little of it as possible, because an August 2026 audit found this file, RUNBOOK, and SUBMISSION-PACK all independently claiming 11 tools when there were 22. If a number here disagrees with CURRENT-STATE, CURRENT-STATE wins and this file is stale. If CURRENT-STATE disagrees with the code, fix CURRENT-STATE first.

Related MCP server: WorkoutGuide MCP

Product mechanics

  • Raw log is append-only. Sessions, sets, feedback, runs, and recovery metrics are never edited — everything intelligent is derived state in user_params (e1RMs, trends, stall detection, recovery score, volume landmarks, run fitness), refit on every planning call.

  • Plans are versioned. Every plan_my_week supersedes the old revision and records a parent pointer + a human-readable rationale — "git, minus git."

  • Trial is a training block, not a clock. TRIAL_DAYS = 35 (a 28-day mesocycle + 7 days grace), starting at the first log_workout, plan_my_week, or log_run. Onboarding and import_history deliberately do not start it. Read tools are never locked and delete_my_account is never gated — your data stays yours.

  • Upgrade happens in the conversation. A blocked write tool returns a warm upgrade message as tool content (not an error) so the model relays it at the moment of intent.

  • EARLY_ACCESS is currently ON, so nothing is gated right now and the trial clock never starts. The gate is fully built and tested; the flag just holds it open. See CURRENT-STATE → Entitlements.

  • Safety screen. src/server/safety.ts runs a deterministic red-flag matcher over all 9 user free-text surfaces, enumerated in one place (freeTextSources() in src/server/tools.ts). An emergency-tier match strips everything else out of the response, including the upgrade footer — and it runs on the entitlement-blocked path too, so a gated-out user reporting chest pain gets the escalation, not a sales pitch.

Quickstart (local)

npm install
npm test                       # full suite; see CURRENT-STATE for the current count
AUTH_MODE=dev npm run dev      # Streamable HTTP MCP server on :3000

AUTH_MODE is required and explicit — the server refuses to start if it is unset or anything other than dev / supabase:

Fatal startup error: Error: Unknown or missing AUTH_MODE null. Set AUTH_MODE=dev or AUTH_MODE=supabase

Nothing in this repo loads a .env file — there is no dotenv dependency and no --env-file flag on the dev script. .env.example documents the variables, but copying it to .env has no effect: pass variables inline (as above), export them, or add --env-file=.env yourself.

Once running there are two probes, and the difference matters: curl localhost:3000/healthz returns {"ok":true} without touching the database (liveness — it is what Fly polls every 30s, so a DB blip must not fail it), and curl localhost:3000/readyz makes a real query and returns {"ok":true,"db":"up","durationMs":N} or a 503 db:down. /readyz is the one external monitoring watches.

In dev mode any bearer token dev-<name> authenticates as user <name>; it is refused outright when NODE_ENV=production.

Connect from Claude (custom connector) or MCP Inspector with URL http://localhost:3000/mcp and header Authorization: Bearer dev-henry, then try: set up a profile, set a goal, log a workout, and plan my week.

Other scripts: npm run build (tsc + copies migrations into dist/), npm start (run the build), npm run test:watch, npm run provision (Supabase), npm run seed-demo, npm run metrics, npm run deploy (see Deploying).

Storage

One function decides the backend: createStorage() in src/storage/select.ts, called once from src/server/index.ts.

DATABASE_URL

Backend

Used for

set

PostgresStorage — Supabase Postgres, ideally the transaction-pooler URL (port 6543)

production

unset, dev/test

PgliteStorage — embedded Postgres, optionally persisted via DATA_DIR

dev and tests

unset, production-like

throws at startup

"Production-like" means NODE_ENV=production or AUTH_MODE=supabase, and the refusal is deliberate. DATA_DIR is not set in fly.toml, so the old PGlite fallback was in-memory: the server booted clean, /healthz stayed green, tools succeeded, and every user got a fresh empty account that emptied again on the next restart. A dropped secret looked exactly like a healthy deploy. It now fails loudly instead (tests/storage-select.test.ts).

Both are complete implementations of the same Storage interface and run the same migrations (src/storage/migrations/, 0001_init0015_rls_v7, with RLS policies in the paired _rls_ files). PGlite is not a stub and Postgres is not future work: PostgresStorage is a complete, shipped implementation and is what the live deployment runs. The one query where the two backends must not drift, the population-tuning aggregate, is shared verbatim from src/storage/tuning-shared.ts.

PostgresStorage.init() applies every migration in order at boot. Portable files are additive and idempotent, so re-running against a live database is safe; the _rls_ files reference Supabase's auth.uid() and are applied automatically only when the connected database exposes it — plain Postgres in local dev and CI skips them.

Note that 71 tests skip unless a real DATABASE_URL is present. Run the suite against Postgres before a release, not just PGlite.

Deploying

The Fly app is fitcoach-hs — not the package name. Both fly.toml and scripts/deploy-fly.sh name it, so a bare deploy is correct:

FLY_API_TOKEN=... npm run deploy

(Until 0.7.1 both defaulted to fitcoach-mcp, so a bare deploy would create that app, deploy there, and health-check its URL — a green run against an app nobody uses. If a stray fitcoach-mcp app is still on the Fly account from back then, flyctl apps destroy fitcoach-mcp it; a decoy that answers /healthz is worse than no app.)

fly.toml declares a [[mounts]] volume and that is deliberate — it matches the running machine, which keeps deploys prompt-free. The volume is unused (prod data is in external Postgres) but it pins the app to a single machine, which the in-process rate limiter depends on. Details in docs/DEPLOY-NOTES.md.

Architecture

src/
  types.ts            # binding contracts: domain, Storage, Engine, TOOL_NAMES
  storage/
    migrations/       # 0001..0015; portable DDL + paired Supabase RLS policies
    select.ts         # createStorage(): DATABASE_URL ? Postgres : PGlite
    postgres.ts       # production Storage impl (Supabase Postgres)
    pglite.ts         # dev/test Storage impl (embedded Postgres)
    tuning-shared.ts  # the aggregates-only tuning evidence SQL, shared by both
    seed-exercises.ts # exercise catalog: substitutes, movement pattern, fatigue cost
  engine/             # deterministic; see docs/INTELLIGENCE-DESIGN.md
    e1rm.ts           # Epley + RPE→RIR adjustment
    fitting.ts        # fitParams: e1RM smoothing, trends, stalls, freshness, landmarks
    planner.ts        # planWeek: splits, progression, deloads, hybrid day layout
    running.ts        # run fitness, program-mode arbitration, run-week construction
    adjust.ts         # same-day autoregulation (short on time / beat up)
    alignment.ts      # goal-vs-behaviour drift detection, proactive check-ins
    experiments.ts    # 2-week n-of-1 plateau tests
    recap.ts          # weekly recap + PR detection
    tuning.ts         # bounded population tuning from aggregate evidence
  server/
    index.ts          # express + stateless StreamableHTTP, per-request server factory
    auth.ts           # dev tokens / Supabase JWT (JWKS) + RFC 9728 metadata
    consent.ts        # OAuth 2.1 consent UI (Supabase as authorization server)
    entitlements.ts   # mesocycle trial gate + EARLY_ACCESS
    metering.ts       # idempotent usage events
    safety.ts         # deterministic red-flag screen (emergency / injury)
    temporal.ts       # server-side, timezone-aware natural-language dates
    rate-limit.ts     # in-process burst + sustained limits
    tools.ts, tools-*.ts, tools/*.ts   # the tool surface (see CURRENT-STATE)
    pages/, site.ts, share.ts, ui/     # landing, /connect, /docs, share links
  billing/provider.ts # BillingProvider interface + StubBillingProvider

No payment provider is wired. StubBillingProvider is what runs and CHECKOUT_BASE_URL is unset, so checkout links fall back to the live /#pricing section. Stripe is runbook Phase 4.

Documentation

Doc

What

docs/CURRENT-STATE.md

Source of truth — counts, identity, what is and isn't built

docs/INTELLIGENCE-DESIGN.md

The engine: every algorithm, constant, and guardrail

docs/RUNBOOK.md

Operating manual — env vars, deploys, EARLY_ACCESS, rate limits, auth

docs/INCIDENT-RUNBOOK.md

It's down (or looks down): triage probes and cause playbooks

docs/PRIVACY-CHECKLIST.md

This product stores injury notes and wellbeing text — treat as sensitive

docs/DEPLOY-NOTES.md

Fly specifics

docs/WEARABLES.md

Recovery-metric ingestion

docs/SUBMISSION-PACK.md

Directory submission material

F
license - not found
Not graded
quality - not tested
A
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Exposes Whoop fitness data (recovery, sleep, strain, workouts) to Claude for use as a daily training coach, enabling natural language queries about your health metrics and training readiness.
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables workout tracking and coaching within Claude conversations, managing exercise configs, logs, streaks, and health metrics via an MCP server with PostgreSQL.
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables Claude to analyze training data from spreadsheets and Amazfit watches, providing insights on strength progression, running metrics, recovery status, and readiness, with tools for weekly reviews, exercise progression, and health reporting.
    1
    MIT

View all related MCP servers

Related MCP Connectors

  • Create Hevy routines and analyze your training from chat. Unofficial; BYO Hevy PRO API key.

  • WHOOP recovery, strain, sleep and workouts in Claude via official WHOOP OAuth. Free, open source.

  • Garmin data in Claude: 135 tools — activities, sleep, HRV, training, workouts. Free, open source.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/henryhf/fitcoach-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server