TrainHeroic
TrainHeroic MCP Server
An MCP server that gives Claude (and other AI agents) direct access to your TrainHeroic workout data, exercise library, and personal calendar.
Built from the mobile API (iOS app v8.25.0, endpoints captured via mitmproxy).
Prerequisites
Requirement | Install |
Python 3.12+ | |
uv |
|
Claude Code or OpenClaw | see below |
Related MCP server: Pelaris
Setup
1. Install dependencies
git clone <repo-url> trainheroicMcp
cd trainheroicMcp
make install2. Configure credentials
cp .env.example .env
# edit .env and fill in your credentialsOption A — email + password (recommended)
TRAINHEROIC_EMAIL=you@example.com
TRAINHEROIC_PASSWORD=yourpasswordThe server authenticates on first start and caches the session token to
~/.config/trainheroic/session.json. Subsequent starts reuse the cached token
and only re-login if it expires.
Option B — session token directly
Log in at trainheroic.com
Open DevTools (
F12) → Network tabClick any request to
api.trainheroic.comUnder Request Headers, find
session-tokenCopy that value into
.env:
TRAINHEROIC_SESSION_TOKEN=<your-session-token>3. Verify credentials
make check-env # confirms .env exists and has credentials
make run # starts the server; look for "Ready — logged in as ..." on stderrPress Ctrl+C to stop. The server waits silently for MCP protocol input — that's
normal for stdio transport.
Register with your AI client
Claude Code
make setup-claudeThis runs:
claude mcp add --scope project trainheroic -- uv run --directory "$PWD" python -m trainheroic_mcp.serverThe server is registered in .claude/settings.json for this project. Credentials
are loaded from the .env file in the repo root automatically.
Restart Claude Code (or run /mcp to reload servers without restarting).
OpenClaw
make setup-openclawThis runs:
openclaw mcp set trainheroic '{"command":"uv","args":["run","python","-m","trainheroic_mcp.server"],"cwd":"<repo path>"}'The server is registered in ~/.openclaw/openclaw.json. Restart OpenClaw to
pick up the change. Because cwd is set to the repo root, the .env file is
found automatically at startup.
Claude Desktop (manual)
Open your Claude Desktop config:
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.
Confirming it works
Once registered, ask Claude:
"What workouts did I do this week?" "What's my working max for back squat?" "Show me my recent exercise history."
If something is wrong, the tools return a clear error message explaining what's missing (e.g. missing credentials, expired token).
Deploying online (use from any machine)
Deploy to Railway to host the server in the cloud. Once deployed, any machine running Claude Code or OpenClaw can connect to it over HTTPS — no local Python install required on the client side.
Step 1 — Generate an auth token
The deployed server is public by default. Protect it with a pre-shared bearer token:
make generate-token
# prints: MCP_AUTH_TOKEN=e5b7a8e39538541...Copy the full line — you'll need it in Steps 2 and 3.
Step 2 — Deploy to Railway
Push this repo to GitHub (it must be a git repo)
Go to railway.app → New Project → Deploy from GitHub repo
Select this repository — Railway auto-detects the
DockerfileIn Variables, add all of these:
Variable | Value |
|
|
| your email |
| your password |
| the token from Step 1 |
Click Deploy. Railway assigns a public URL like
https://trainheroicmcp-production.up.railway.app
Check the deploy logs — you should see:
INFO [trainheroic-mcp] Ready — logged in as Your Name (team: Team Name)
INFO [trainheroic-mcp] HTTP transport — listening on 0.0.0.0:8000/mcpStep 3 — Connect from any machine
Replace YOUR_URL with the 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/mcpOpenClaw:
openclaw mcp set trainheroic \
'{"type":"http","url":"https://YOUR_URL/mcp","headers":{"Authorization":"Bearer YOUR_TOKEN"}}'Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"trainheroic": {
"type": "http",
"url": "https://YOUR_URL/mcp",
"headers": {
"Authorization": "Bearer YOUR_TOKEN"
}
}
}
}Testing the deployed server
curl -s \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
https://YOUR_URL/mcp
# Should return an MCP protocol response (not 401)Running HTTP transport locally (for testing before deploy)
MCP_AUTH_TOKEN=test-token make run-http
# Server starts on http://localhost:8000/mcpAvailable tools
Core data
Tool | What it does | Key params |
| Name, ID, coach status | — |
| Teams, program IDs, coaches | — |
| Workouts in a date range |
|
| Full sets + logged weights for one session |
|
| Last performance, PR, working max |
|
| All PRs by rep count |
|
| Current working max |
|
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 survey |
|
| Answer a survey question |
|
| Comments on a workout |
|
| Full leaderboard for a workout |
|
Survey reference:
Sleep | Mood | Energy | Soreness | Stress | |
question_id | 8 | 9 | 10 | 11 | 12 |
answer_id | 1 | 2 | 3 | 4 | 5 |
meaning | Awful | Poor | Ok | Good | Excellent |
Known limitations
These endpoints are gated or broken on the backend:
Tool | Reason |
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 calls are intercepted by pytest-httpx.
make test # quiet summary
make test-v # verbose, one line per test
uv run pytest -k "TestLogin" # one class
uv run pytest tests/test_client.py # one fileProject structure
src/trainheroic_mcp/
├── client.py # TrainHeroicClient — auth, token cache, HTTP helpers
└── server.py # FastMCP server — 19 tool definitions + startup logging
tests/
├── conftest.py # fixtures: cache isolation (autouse), th_client, patched_server
├── helpers.py # shared constants + add_init_responses helper
├── test_client.py # 33 tests: init, login, token cache, HTTP helpers
└── test_tools.py # 33 tests: one 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
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