FitCoach MCP

FitCoach MCP

Enables Claude or ChatGPT to act as a personalized fitness coach with persistent goals, conversational workout logging, and weekly generated training plans.

Category
Visit Server

README

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.

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_init … 0015_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

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured