TrainHeroic
TrainHeroic MCP Server
TrainHeroic MCP server — connect TrainHeroic to Claude via the Model Context Protocol.
Give Claude (and other AI agents) direct access to your TrainHeroic workout data — history, exercise stats, personal records, and personal calendar — through the Model Context Protocol.
Built from the TrainHeroic mobile API (iOS app v8.25.0, endpoints captured via mitmproxy).
What you can do
Ask Claude things like:
"What workouts did I do this week?"
"What's my working max for back squat and how has it changed?"
"Show me my bench press PRs by rep count."
"Log today's session — I did 4×5 back squat at 225 lbs, RPE 8."
"Create a personal session for tomorrow and add deadlifts and Romanian deadlifts."
"How did I feel after Monday's workout? What was my energy and stress survey?"
"Who's on the leaderboard for Tuesday's workout?"
Related MCP server: Trainzilla MCP
Quick Start
Prerequisites: Python 3.12+ and uv
# 1. Clone and install
git clone https://github.com/cmagorian/trainheroicMcp
cd trainheroicMcp
make install
# 2. Add your credentials
cp .env.example .env
# Edit .env — see "Credentials" section below
# 3. Register with your AI client
make setup-claude # Claude Code
make setup-openclaw # OpenClaw
# See below for Claude Desktop and CursorRestart your AI client and ask: "What did I train this week?"
Credentials
Copy .env.example to .env and choose one of two methods:
Option A — Email + password (recommended)
TRAINHEROIC_EMAIL=you@example.com
TRAINHEROIC_PASSWORD=yourpasswordThe server logs in on first start and caches the session token to ~/.config/trainheroic/session.json. Re-login is automatic when the token expires.
Option B — Session token
If you'd rather not store your password:
Log in at trainheroic.com
Open DevTools (
F12) → Network tabClick any request to
api.trainheroic.comUnder Request Headers, copy the
session-tokenvalue
TRAINHEROIC_SESSION_TOKEN=<your-session-token>Verify your credentials before registering:
make check-env # confirms .env is present and populated
make run # starts the server — look for "Ready — logged in as ..." on stderrRegistering with your AI client
Claude Code
make setup-claudeThis registers the server in .claude/settings.json for this project. Credentials are loaded from .env automatically.
Run /mcp in Claude Code (or restart) to pick up the new server.
Claude Desktop
Open the config file for your OS:
OS | Path |
macOS |
|
Windows |
|
Linux |
|
Add the trainheroic entry — replace the path and credentials:
{
"mcpServers": {
"trainheroic": {
"command": "uv",
"args": [
"run",
"--directory", "/absolute/path/to/trainheroicMcp",
"python", "-m", "trainheroic_mcp.server"
],
"env": {
"TRAINHEROIC_EMAIL": "you@example.com",
"TRAINHEROIC_PASSWORD": "yourpassword"
}
}
}
}Restart Claude Desktop after saving.
Cursor
Open Cursor Settings → MCP → Add new MCP server, or edit ~/.cursor/mcp.json directly:
{
"mcpServers": {
"trainheroic": {
"command": "uv",
"args": [
"run",
"--directory", "/absolute/path/to/trainheroicMcp",
"python", "-m", "trainheroic_mcp.server"
],
"env": {
"TRAINHEROIC_EMAIL": "you@example.com",
"TRAINHEROIC_PASSWORD": "yourpassword"
}
}
}
}Restart Cursor after saving.
OpenClaw
make setup-openclawThis registers the server in ~/.openclaw/openclaw.json. Restart OpenClaw to pick it up.
Example prompts
Checking history
What workouts did I complete last week?
Show me everything I trained in May.
Did I train on Monday?Exercise stats and PRs
What's my current working max for back squat?
Show me my bench press PRs broken down by rep count.
What were my last 3 performances on Romanian deadlifts?Logging a session
Create a personal session for today and add back squat, bench press, and cable rows.
Log my workout — I completed all sets. RPE was 7, rating 8 out of 10.Surveys and recovery
What were my energy and stress scores after Tuesday's workout?
Submit my readiness survey: sleep was good, mood was great, energy was ok.Social
Who's on the leaderboard for this week's main lift?
What comments are on today's workout?Available tools
Core data
Tool | What it does | Key params |
| Name, ID, coach status | — |
| Teams, program IDs, coaches | — |
| Workouts in a date range (flat summary by default) |
|
| Full sets + logged weights for one session |
|
| Last performance, PR, working max |
|
| All PRs by rep count |
|
| Current working max |
|
Tip:
get_workout_historyreturns compact summaries (date, title, rating, RPE, notes) by default. Passinclude_sets=Truefor set-level data on a 1–3 day window, or callget_workout_detailsfor a single session.
Exercise library
Tool | What it does | Key params |
| Full library; filter by name |
|
| Full circuit library | — |
| Recently used exercises | — |
| Recently used circuits | — |
Personal calendar
Tool | What it does | Key params |
| Create a new session |
|
| Add exercises in order |
|
| Save a completed workout |
|
| Delete a session |
|
Surveys & messaging
Tool | What it does | Key params |
| Sleep/mood/energy/soreness/stress data |
|
| Answer a survey question |
|
| Comments on a workout |
|
| Full leaderboard for a workout |
|
Survey reference:
Sleep | Mood | Energy | Soreness | Stress | |
| 8 | 9 | 10 | 11 | 12 |
| 1 | 2 | 3 | 4 | 5 |
Meaning | Awful | Poor | Ok | Good | Excellent |
Deploying online (access from any machine)
Host the server on Railway so any device running Claude Code, Claude Desktop, or OpenClaw can connect to it over HTTPS — no local Python install needed on the client.
Step 1 — Generate an auth token
make generate-token
# prints: MCP_AUTH_TOKEN=e5b7a8e3...Copy the full line — you'll need it in Steps 2 and 3.
Step 2 — Deploy to Railway
Push this repo to GitHub
Go to railway.app → New Project → Deploy from GitHub repo
Select the repository — Railway auto-detects the
DockerfileIn Variables, add:
Variable | Value |
|
|
| your email |
| your password |
| the token from Step 1 |
Click Deploy. Railway assigns a URL like
https://trainheroicmcp-production.up.railway.app
Check the deploy logs for:
INFO [trainheroic-mcp] Ready — logged in as Your Name (team: Your Team)
INFO [trainheroic-mcp] HTTP transport — listening on 0.0.0.0:8000/mcpStep 3 — Connect from any machine
Replace YOUR_URL with your Railway URL and YOUR_TOKEN with the token from Step 1.
Claude Code:
claude mcp add \
--transport http \
--header "Authorization: Bearer YOUR_TOKEN" \
trainheroic \
https://YOUR_URL/mcpClaude Desktop / Cursor (claude_desktop_config.json or ~/.cursor/mcp.json):
{
"mcpServers": {
"trainheroic": {
"type": "http",
"url": "https://YOUR_URL/mcp",
"headers": {
"Authorization": "Bearer YOUR_TOKEN"
}
}
}
}OpenClaw:
openclaw mcp set trainheroic \
'{"type":"http","url":"https://YOUR_URL/mcp","headers":{"Authorization":"Bearer YOUR_TOKEN"}}'Test the connection:
curl -s \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
https://YOUR_URL/mcp
# Should return an MCP protocol response, not 401Run HTTP transport locally (before deploying):
MCP_AUTH_TOKEN=test-token make run-http
# Server starts on http://localhost:8000/mcpTroubleshooting
Symptom | Likely cause | Fix |
|
|
|
| Expired or invalid token | Delete |
| Wrong team resolved | Pass |
Server starts but Claude can't find it | Not registered or client not restarted | Re-run |
Responses seem incomplete | Date range too wide | Use 1–2 week windows; call |
Known limitations
Feature | Reason unavailable |
Lift goals | Requires Athlete Pro subscription |
Nutrition calendar | Requires Athlete Pro subscription |
Program listing | Coach accounts only |
Data export | Backend returns 504 timeout |
Development
Running tests
No credentials needed — all HTTP is intercepted by pytest-httpx.
make test # quiet summary
make test-v # verbose, one line per test
uv run pytest -k "TestLogin" # single class
uv run pytest tests/test_client.py # single fileProject structure
src/trainheroic_mcp/
├── client.py # TrainHeroicClient — auth, token cache, HTTP helpers
└── server.py # FastMCP server — 19 tool definitions, response projectors
tests/
├── conftest.py # fixtures: cache isolation (autouse), th_client, patched_server
├── helpers.py # shared constants + add_init_responses helper
├── test_client.py # init, login, token cache, HTTP helpers
└── test_tools.py # one test class per toolAdding a new tool
Add the function to
server.pywith@mcp.tool().Add a test class to
tests/test_tools.pyusing thepatched_serverfixture.
# server.py
@mcp.tool()
def get_athlete_pro_status() -> dict:
"""Check whether the user has an active Athlete Pro subscription."""
return _get_client()._get("/v5/athletePro/access")# tests/test_tools.py
class TestGetAthleteProStatus:
def test_calls_correct_endpoint(self, patched_server, httpx_mock):
httpx_mock.add_response(
method="GET",
url=f"{BASE}/v5/athletePro/access",
json={"hasAthleteProAccess": False},
)
result = server.get_athlete_pro_status()
assert result["hasAthleteProAccess"] is FalseThis server cannot be installed
Maintenance
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/cmagorian/trainheroicMcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server