FitCoach MCP
Enables Claude or ChatGPT to act as a personalized fitness coach with persistent goals, conversational workout logging, and weekly generated training plans.
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_weeksupersedes 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 firstlog_workout,plan_my_week, orlog_run. Onboarding andimport_historydeliberately do not start it. Read tools are never locked anddelete_my_accountis 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_ACCESSis 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.tsruns a deterministic red-flag matcher over all 9 user free-text surfaces, enumerated in one place (freeTextSources()insrc/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 |
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。