Personal training dashboard with FastAPI backend, Vue 3 frontend, and MCP server for Claude integration.
See docs/README.md for planning and decision documents, including the roadmap.
- Backend: FastAPI + SQLite (Python)
- Frontend: Vue 3 + Vite
- MCP Server: Python script that lets Claude push data directly
- Docker Compose: One command to run everything
docker compose up --buildOr use the Makefile:
make up- Frontend: http://localhost:3000
- Backend API: http://localhost:8000
- API Docs: http://localhost:8000/docs
Install the MCP dependency:
cd mcp
pip install -r requirements.txtAdd to your Claude Desktop config file:
Mac: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"training-dashboard": {
"command": "python",
"args": ["/ABSOLUTE/PATH/TO/training-dashboard/mcp/server.py"]
}
}
}Replace /ABSOLUTE/PATH/TO/ with the actual path. Then restart Claude Desktop.
The backend now also exposes the same MCP tools over HTTP at:
http://localhost:8000/mcp
That endpoint is for remote MCP clients such as ChatGPT once you expose it through HTTPS.
Practical setup:
- Run the app locally with Docker.
- Expose
http://localhost:8000through a tunnel such as ngrok or Cloudflare Tunnel. - In ChatGPT developer mode, connect the public
https://.../mcpURL.
Example with ngrok:
make chatgptThat one command:
- starts Docker Compose in the background
- starts an ngrok tunnel to
localhost:8000 - lets you copy the HTTPS tunnel URL for ChatGPT
Then take the HTTPS forwarding URL from ngrok, for example:
https://abc123.ngrok-free.app
and use this MCP endpoint in ChatGPT:
https://abc123.ngrok-free.app/mcp
ngrok supports a fixed dev-domain URL. As of the ngrok blog update on January 8, 2026, every ngrok account gets one automatically assigned free dev domain on ngrok-free.dev, and you can use it with the CLI by passing --url.
Find your dev domain in the ngrok dashboard under Gateway > Domains, then start the tunnel like this:
NGROK_URL=https://your-domain.ngrok-free.dev make chatgptor:
NGROK_URL=https://your-domain.ngrok-free.dev make tunnelThen use:
https://your-domain.ngrok-free.dev/mcp
Notes:
- On free accounts, the dev domain is assigned automatically; you cannot choose the hostname.
- If
NGROK_URLis not set, the Makefile keeps the old behavior and starts a random public URL. - If you want a chosen
*.ngrok.apphostname or your own custom domain, that requires a paid ngrok plan. - The
Makefilenow loads root.env, so you can storeNGROK_URL=https://your-domain.ngrok-free.devthere and just runmake chatgpt.
Other useful commands:
make up-detached
make tunnel
make logs
make down
make test-backendThe backend now has a lightweight smoke test pass that exercises the modularized app assembly and core routes.
Run it with:
make test-backendNotes:
- The test command uses a workspace-local dependency directory at
.tmp_test_deps/. - The test app uses
TRAINING_DB_PATHto point SQLite at a temporary test database instead of/data/training.db. - In normal app usage,
TRAINING_DB_PATHis optional and defaults to/data/training.db.
Quick verification before connecting ChatGPT:
curl http://localhost:8000/mcpExpected shape:
{
"name": "training-dashboard",
"version": "1.1.0",
"endpoint": "/mcp",
"transport": "jsonrpc-http"
}You can also verify initialize manually:
curl -X POST http://localhost:8000/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'Notes:
- The existing
mcp/server.pyis still the localstdiobridge for Claude Desktop. - ChatGPT will need the remote HTTPS endpoint, not the local
stdioscript. - Read tools and write tools are both exposed; ChatGPT can write into the app through MCP once connected.
- The app itself still runs locally on your machine; only the tunnel URL is public.
- With the current setup there is no auth layer on
/mcp, so use a temporary tunnel only while you need it.
There is no app-level, one-click import from Claude chat history into ChatGPT in this project.
The practical approach is:
- Export or copy the important Claude thread.
- Start a new ChatGPT conversation.
- Paste a project summary or upload the exported text/file as context.
- Ask ChatGPT to continue from that history.
For this dashboard, the better long-term source of truth is the app itself:
- activities
- metrics
- coach notes
- weekly plans
Once ChatGPT is connected to /mcp, it can read those directly instead of relying on old chat history.
You can import activities straight from Strava through the backend, without using MCP tokens.
In the Strava API settings, get:
STRAVA_CLIENT_IDSTRAVA_CLIENT_SECRETSTRAVA_REFRESH_TOKEN
The backend uses the refresh token to obtain access tokens automatically.
Create a root .env file from the example:
cp .env.example .envThen fill in:
STRAVA_CLIENT_ID=your_client_id
STRAVA_CLIENT_SECRET=your_client_secret
STRAVA_REFRESH_TOKEN=your_refresh_tokenDocker Compose reads .env automatically. You can still export them in your shell instead if you prefer:
export STRAVA_CLIENT_ID=your_client_id
export STRAVA_CLIENT_SECRET=your_client_secret
export STRAVA_REFRESH_TOKEN=your_refresh_token
docker compose up --buildDate range is optional.
- If you leave dates empty, the backend syncs from the latest stored activity date through today.
- The start date is inclusive, so if you already have one workout on that day and later add another, the next sync still picks it up.
- For the very first import, set a custom start date if you want a historical backfill.
Use the Activities page import form, or call the backend directly:
curl -X POST http://localhost:8000/integrations/strava/import \
-H "Content-Type: application/json" \
-d '{"start_date":"2026-06-01","end_date":"2026-06-22"}'You can also let the backend choose the range automatically:
curl -X POST http://localhost:8000/integrations/strava/import \
-H "Content-Type: application/json" \
-d '{}'Check integration status:
curl http://localhost:8000/integrations/strava/statusImported activities retain a Strava source reference. Rerunning the same range refreshes the canonical activity, including one first created from HealthFit, instead of duplicating it.
HealthFit can be used as the always-available workout source when Strava API access is unavailable. The backend scans HealthFit's .fit backups from a read-only directory mount.
Set the host directory in .env:
HEALTHFIT_EXPORT_DIR="/Users/your-name/Library/Mobile Documents/iCloud~com~altifondo~HealthFit/Documents"Then rebuild/restart the services and open Sync → HealthFit Directory. Always run the preview before applying an import.
Duplicate protection is deliberately conservative:
- during the first initialization only, files strictly before the latest stored activity date are baselined by filename without opening or importing them;
- cutoff-day files are decoded and linked only when one compatible same-day activity exists;
- only unmatched files newer than the cutoff create activities;
- ambiguous matches are skipped for review;
- every processed file receives a durable HealthFit source reference;
- after initialization, every previously unseen file is parsed regardless of workout date, so late iCloud uploads are not hidden by the cutoff;
- a later Strava import attaches its Strava ID to a uniquely compatible HealthFit activity instead of inserting another activity.
The iCloud directory is mounted read-only at /healthfit. If macOS has evicted a recent FIT file from local storage, download it in Finder before scanning again.
| Page | Description |
|---|---|
| Dashboard | 14-day overview, recent runs/rides, Z2 pace trend, coach notes |
| Plan | Weekly workout plans prepared by Claude, shown day by day |
| Calendar | Weekly calendar view with daily activities, hours, distance, and elevation |
| Activities | Full activity log with filters by type |
| Metrics | Personal metrics trends (weight, Z2 pace, FTP, heel pain, streak) |
| Coach Notes | All coaching observations categorized by topic |
Once the MCP server is connected, Claude can:
- Log activities — runs, rides, strength sessions from Strava analysis
- Read dashboard activity history — activities, stats, notes, plans, and calendar summaries directly from your app
- Read compact coaching context — one bundled MCP tool for recent load, latest activities, notes, metrics, streak, weekly mix, and active plan
- Add coach notes — observations about HR, pacing, fatigue, heel pain
- Log metrics — Z2 pace benchmarks, FTP, weight, resting HR, streak count, heel pain level
- Update weekly summaries — total km, elevation, sessions per week
- Save weekly plans — structured day-by-day workout weeks directly into the dashboard
- Adjust weekly plans — patch only the remaining days of the current week while preserving past or already completed sessions
"Log today's run to my dashboard and add a note about Zone 2 progress"
"Update my streak metric to 170 days"
"Add a coach note about the heel recovery progress"
"Look at my completed workouts so far this week and adjust the rest of my weekly plan"
| Zone | Running HR | Cycling HR |
|---|---|---|
| Z1 recovery | <150 | <140 |
| Z2 aerobic | 150–162 | 140–152 |
| Z3 tempo | 163–172 | 153–162 |
| Z4 threshold | 173–182 | 163–172 |
| Z5 max | 183+ | 173+ |
SQLite database stored in ./data/training.db — persists across container restarts.
The sidebar streak is computed automatically from consecutive calendar days with at least one logged activity.
- Any activity type counts toward the streak.
- If your latest activity was
todayoryesterday, the streak is considered active. - If the latest activity is older than yesterday, the current streak shows
0 days.
Backend hot-reloads automatically. For frontend changes:
docker compose upChanges to frontend/src/ reflect immediately.
To reset all data:
rm -rf data/
docker compose up