Trainheroicmcp — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Trainheroicmcp (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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).
Ask Claude things like:
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?"
Copy .env.example to .env and choose one of two methods:
[email protected]
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.
If you'd rather not store your password:
F12) → Network tabapi.trainheroic.comsession-token valueTRAINHEROIC_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 stderrmake 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.
Open the config file for your OS:
| OS | Path |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
| Linux | ~/.config/Claude/claude_desktop_config.json |
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": "[email protected]",
"TRAINHEROIC_PASSWORD": "yourpassword"
}
}
}
}Restart Claude Desktop after saving.
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": "[email protected]",
"TRAINHEROIC_PASSWORD": "yourpassword"
}
}
}
}Restart Cursor after saving.
make setup-openclawThis registers the server in ~/.openclaw/openclaw.json. Restart OpenClaw to pick it up.
What workouts did I complete last week?
Show me everything I trained in May.
Did I train on Monday?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?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.What were my energy and stress scores after Tuesday's workout?
Submit my readiness survey: sleep was good, mood was great, energy was ok.Who's on the leaderboard for this week's main lift?
What comments are on today's workout?| Tool | What it does | Key params |
|---|---|---|
get_user_profile | Name, ID, coach status | — |
get_team_info | Teams, program IDs, coaches | — |
get_workout_history | Workouts in a date range (flat summary by default) | start_date, end_date, weeks_back, include_sets |
get_workout_details | Full sets + logged weights for one session | program_workout_id, program_id |
get_exercise_stats | Last performance, PR, working max | exercise_id, stat_date |
get_personal_records | All PRs by rep count | exercise_id |
get_working_max | Current working max | exercise_id |
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.
| Tool | What it does | Key params |
|---|---|---|
get_exercise_library | Full library; filter by name | query (optional substring) |
get_circuit_library | Full circuit library | — |
get_recent_exercises | Recently used exercises | — |
get_recent_circuits | Recently used circuits | — |
| Tool | What it does | Key params |
|---|---|---|
create_personal_session | Create a new session | session_date (YYYY-MM-DD) |
add_exercises_to_session | Add exercises in order | workout_id, exercise_ids |
log_workout | Save a completed workout | saved_workout_id, workout_id, date_string, blocks, notes, rpe, workout_rating |
delete_session | Delete a session | program_workout_id |
| Tool | What it does | Key params |
|---|---|---|
get_workout_surveys | Sleep/mood/energy/soreness/stress data | saved_workout_ids |
submit_survey | Answer a survey question | saved_workout_id, question_id, answer_id |
get_workout_messages | Comments on a workout | program_workout_id |
get_workout_leaderboard | Full leaderboard for a workout | program_workout_id |
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 |
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.
make generate-token
# prints: MCP_AUTH_TOKEN=e5b7a8e3...Copy the full line — you'll need it in Steps 2 and 3.
Dockerfile| Variable | Value |
|---|---|
MCP_TRANSPORT | http |
TRAINHEROIC_EMAIL | your email |
TRAINHEROIC_PASSWORD | your password |
MCP_AUTH_TOKEN | the token from Step 1 |
https://trainheroicmcp-production.up.railway.appCheck 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/mcpReplace 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/mcp| Symptom | Likely cause | Fix |
|---|---|---|
No TrainHeroic credentials found | .env missing or empty | cp .env.example .env and add credentials |
401 Unauthorized on startup | Expired or invalid token | Delete ~/.config/trainheroic/session.json and restart; the server re-logs in |
401 from get_workout_details | Wrong team resolved | Pass program_id from the get_workout_history item alongside program_workout_id |
| Server starts but Claude can't find it | Not registered or client not restarted | Re-run make setup-claude and restart Claude |
| Responses seem incomplete | Date range too wide | Use 1–2 week windows; call get_workout_details per session for set data |
| 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 |
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 filesrc/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 toolserver.py with @mcp.tool().tests/test_tools.py using the patched_server fixture.# 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 False~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.