A hosted Model Context Protocol server that exposes Strava fitness data as ML-powered tools. Any LLM can call them. Built with platform engineering principles: versioned prompt registry, LLM-as-judge eval framework, and tool observability out of the box.
Live demo: Connect Claude Desktop → ask "Analyse my training"
Short_form.mp4
Long_Form.mp4
Every team building on Strava data re-implements the same OAuth flow, API parsing, and feature computation from scratch. Strava's Athlete Intelligence summarises the past but exposes no API — no agent can call it as a tool.
This project builds the platform layer that was missing:
Without this:
Engineer A builds training load → hardcodes Strava OAuth
Engineer B builds race predictor → hardcodes Strava OAuth
Engineer C builds anomaly detector → hardcodes Strava OAuth
With this:
Any LLM or agent calls get_training_load() as a tool
The platform handles OAuth, caching, eval, and observability
Claude Desktop / Any LLM
↓ MCP protocol
FastMCP Server (port 8001)
↓ tools
Strava API → activity data, GPS streams, segments
Anthropic API → LLM reasoning in detect_patterns()
HuggingFace Hub → TS2Vec anomaly detection model
↓ governed by
Prompt Registry → versioned prompts, eval scores, rollback
Eval Framework → LLM-as-judge, A/B testing
Observability → latency, error rate, usage per tool
↓ stored in
Upstash Redis → prompt store, eval results, tool metrics
| Tool | What it does | Key signal |
|---|---|---|
get_athlete_profile() |
Athlete stats, gear, FTP | Foundational context |
get_recent_activities() |
Last N activities filtered by sport | Activity history |
get_training_load() |
ATL, CTL, TSB via Banister model | Fitness numbers |
get_fatigue_state() |
Plain-language fatigue interpretation | Coaching state |
predict_race_time(km) |
Riegel formula + CTL adjustment | Race readiness |
get_segment_performance(id) |
Effort history, trend, days since PR | Segment fitness |
detect_patterns() |
LLM coaching analysis over 60 days | Pattern detection |
Prompts are managed assets — versioned, evaluated, and fetched at call time. No hardcoded strings in tools.
detect_patterns v1.0 eval_score: null (never deployed)
detect_patterns v1.1 eval_score: 0.84 ← active, production
Update a prompt without a code deploy:
# Register new version
POST /registry/prompts/detect_patterns/v2.0/activate
# Roll back if scores drop
POST /registry/prompts/detect_patterns/rollbackEvery detect_patterns() call is scored on three dimensions by a
secondary Claude instance:
| Dimension | What it measures | Weight |
|---|---|---|
| Specificity | Does it cite real dates, paces, HR values? | 40% |
| Actionability | Does it give concrete, measurable next steps? | 40% |
| Safety | Does it respect progressive overload principles? | 20% |
Scores stored in Redis per prompt version — powers A/B testing between prompt versions.
Every tool call logged via @observe decorator:
GET /stats/tools
# → most used, slowest (p50/p95/p99), highest error rate
GET /stats/evals
# → eval scores per tool, trend over timegit clone <repo>
cd strava-athlete-mcp
pip install -r requirements.txtSet up .env:
STRAVA_CLIENT_ID=your_id
STRAVA_CLIENT_SECRET=your_secret
STRAVA_REDIRECT_URI=http://localhost:8000/auth/callback
JWT_SECRET_KEY=your_32_char_secret
REDIS_URL=rediss://default:password@endpoint:6379
ANTHROPIC_API_KEY=your_keyRun both servers:
# Terminal 1 — OAuth + REST API
uvicorn server.app:app --reload --port 8000
# Terminal 2 — MCP tools
python -m server.mcp_serverAuthenticate:
# Open in browser
http://localhost:8000/auth/login
# Authorise with Strava → copy access_token to .env{
"mcpServers": {
"strava-athlete-mcp": {
"command": "path/to/venv/python.exe",
"args": ["-m", "server.mcp_server"],
"cwd": "path/to/strava-athlete-mcp",
"env": {
"PYTHONPATH": "path/to/strava-athlete-mcp"
}
}
}
}Ask Claude:
"Analyse my last 60 days of training and tell me
what patterns you see."
"What would my predicted half marathon time be?"
"Am I ready to build volume again after my last race?"
Claude chains tools automatically — no explicit orchestration needed.
python seed.pyRegisters detect_patterns v1.1 with personalised HR zones.
Claude's pattern analysis uses your actual max HR — not generic
population zones.
# OAuth
GET /auth/login → redirect to Strava
GET /auth/callback → issues JWT
GET /auth/me → verify token
# Platform
GET /stats/tools → tool observability dashboard
GET /stats/evals → eval scores per tool
GET /stats/ab-test/{id} → A/B test results
# Prompt Registry
GET /registry/prompts/{tool} → list versions
POST /registry/prompts/{tool}/{v}/activate → set active version
POST /registry/prompts/{tool}/rollback → roll back one version
# Health
GET /healthTested against real Strava activity history:
Patterns detected from 60 days of running:
⚠️ Volume spike — 95% jump in two weeks
⚠️ Easy runs running at threshold HR (personalised zones)
⚠️ Long runs not spaced with rest beforehand
ℹ️ Cadence slipping as mileage climbs
✅ Strong race-day fitness (TCS 10k, 4:15/km)
Prompt registry eval score: 0.84
specificity: 0.85 (cited real dates, paces, metrics)
actionability: 0.90 (gave specific targets)
safety: 1.00 (no load progression violations)
| Component | Technology |
|---|---|
| MCP protocol | FastMCP 3.3.1 |
| REST API | FastAPI |
| Auth | Strava OAuth2 + JWT |
| LLM | Claude (Anthropic API) |
| Anomaly model | TS2Vec (HuggingFace Hub) |
| Cache + store | Upstash Redis |
Related projects:
- Leaderboard Integrity System — TS2Vec GPS anomaly detection
- DRL Training Load Optimizer — PPO + Transformer coaching agent
- DAG Training Planner — constraint-based plan generation