@thenextrace/tnr
v0.25.0
Published
CLI and MCP server for thenextrace.app — plan races and training from your terminal and from AI agents.
Maintainers
Readme
tnr — thenextrace CLI & MCP server
Plan races and training from your terminal — and from AI agents (Claude Code, Cursor, Claude Desktop).
npm install -g @thenextrace/tnr
tnr loginTwo ways to connect
Remote MCP (no install). thenextrace hosts an MCP server at
https://thenextrace.app/mcp. Point any client that supports remote MCP at that
URL and sign in with OAuth — nothing to install, nothing to update.
agent ── HTTP + OAuth ──► https://thenextrace.app/mcpLocal MCP + CLI (this package). tnr mcp runs as a stdio MCP server —
Claude Desktop / Cursor / Claude Code spawn the tnr binary as a child process
and talk to it over stdin/stdout. It runs on your machine and makes
authenticated HTTPS calls to https://www.thenextrace.app/api/v1. This is the
same model GitHub MCP, filesystem MCP, and most other MCP servers use — your
token never leaves your machine, and the agent never sees it. Installing the
package also gives you the tnr CLI.
agent ── stdio ──► tnr (your machine) ── HTTPS + Bearer ──► thenextrace.appUse with AI agents
The MCP server exposes 48 tools. Each carries detailed instructions for the agent (preconditions, enums, friendly aliases like ironman → tri) and read-only / destructive / idempotent annotations so clients can warn before anything permanent:
- Discovery (read-only) —
whoami,list_races,get_race,list_plans,get_plan,list_library_workouts,get_library_workout,list_plan_workouts,get_scheduled_workout,list_activities,get_activity,get_profile,get_weather_forecast,get_fitness_summary,get_recovery,suggest_links,list_workout_comments,list_race_comments,list_incidents,get_incident,list_incident_comments - Races —
create_race,update_race,delete_race - Plans —
create_training_plan,update_plan,delete_plan, andset_plan_garmin_syncto take a plan's workouts off Garmin in bulk or send them again - Library workouts —
create_library_workout,update_library_workout,delete_library_workout; file them withlist_library_foldersandcreate_library_folder(folderIdon list, create and update) - Scheduling (planned workouts inside a plan) —
schedule_workout,update_scheduled_workout,remove_scheduled_workout(link actual activities viaupdate_scheduled_workout'slinked_activity_id, or in bulk withsuggest_links+bulk_link_activities) - Activities —
update_activity - Injuries & illness —
create_incident,update_incident,delete_incident(the athlete's own, or an athlete who shares injuries and illness with you as their coach) - Comments (coach ↔ athlete) —
add_workout_comment,add_race_comment,add_incident_comment - Profile —
update_profile - Coaching —
list_athleteslists the athletes you coach and what each one shares with you. Pass an athlete's id asathleteIdtolist_activities,get_fitness_summary,get_recovery,get_profile,list_plansandlist_incidentsto read their data within what they share, or tocreate_training_planto create a plan for them;get_activityreads a shared athlete's activity by id.copy_plan_weekscopies weeks of one of your plans, such as a template, into athletes' calendars, each in their own plan from you, andset_training_visibilitysets how far ahead an athlete sees the training you plan for them. Fitbit data is never returned to a coach.
It also ships prompts that clients surface as slash-commands: plan_my_week, how_am_i_doing, log_recent_efforts, should_i_train_today, race_week_checklist.
Remote MCP (no install)
For clients that support remote MCP, skip the install entirely and point them at the hosted server — you'll be sent through an OAuth sign-in the first time:
claude mcp add --transport http tnr https://thenextrace.app/mcpThe sections below are for the local tnr mcp server (this npm package).
Claude Desktop
Edit ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"tnr": { "command": "tnr", "args": ["mcp"] }
}
}Restart Claude Desktop. Try: "What races do I have planned in thenextrace?"
Claude Code
claude mcp add tnr -- tnr mcpCursor
Edit ~/.cursor/mcp.json (or the project-local .cursor/mcp.json):
{
"mcpServers": {
"tnr": { "command": "tnr", "args": ["mcp"] }
}
}Sign in
- Generate a token at thenextrace.app — sign in, open the Settings dialog → API Tokens → Generate token. Copy the
tnr_…string immediately; it's shown only once. - Run
tnr loginand paste it (ortnr login --token tnr_…in scripts).
Tokens are stored in your OS config dir (~/Library/Preferences/tnr-nodejs/config.json on macOS, ~/.config/tnr-nodejs/config.json on Linux, %APPDATA%\tnr-nodejs\Config\config.json on Windows), in a file only your user can read (mode 0600 on macOS and Linux). tnr status prints the exact path. Revoke any token at any time from the same Settings page.
CLI
Every command supports --json for machine-readable output to stdout. Status messages go to stderr. Exit codes follow standard conventions.
tnr login [--token <token>] Save your API token
tnr logout Clear stored credentials
tnr whoami Show the current user
tnr status Show auth state, API host, token location, data freshness
tnr open [plan|athlete] [id] Open a plan/athlete (or the app home) in the browser
tnr completion <bash|zsh|fish> Print a shell completion script
tnr race list [--upcoming] List races
tnr race show <id> Show a single race
tnr race create --title <t> --date <YYYY-MM-DD>
[--plan <id>] [--type <type>] [--location <l>] [--description <d>]
tnr race update <id> [--title <t>] [--date <d>] [--plan <id>] [--type <t>]
[--location <l>] [--description <d>]
[--completed | --not-completed]
tnr race delete <id> [-y] Delete a race
tnr incident list [--ongoing] [--athlete <id>]
List injuries and illnesses (yours, or an athlete's)
tnr incident show <id> Show a single injury or illness
tnr plan list List training plans
tnr plan show <id> Show a single training plan
tnr plan create --title <t> Create an empty plan
tnr plan update <id> [--title <t>] [--public | --no-public] [--access view|comment|edit]
[--allow-duplication | --no-allow-duplication]
tnr plan delete <id> [-y] Delete a plan (cascades to its races)
tnr workout list [--sport <s>] List library workouts
tnr workout show <id> Show a single library workout
tnr workout create --title <t> --sport <s>
[--type <type>] [--description <d>]
tnr workout update <id> [--title <t>] [--sport <s>] [--type <t>]
[--description <d>] [--distance <d>] [--duration <d>] [--pace <p>]
tnr workout delete <id> [-y] Delete a library workout (unless locked)
tnr schedule list <planId> [--from <d>] [--to <d>] List planned workouts in a plan
tnr schedule show <planId> <scheduledId> Show a planned workout
tnr schedule add <planId> --title <t> --sport <s>
[--date <YYYY-MM-DD>] [--week <n>] [--day <0-6>]
[--type <t>] [--description <d>]
tnr schedule update <planId> <scheduledId> [--title <t>] [--date <d>]
[--sport <s>] [--type <t>]
[--description <d>]
[--completed | --not-completed]
tnr schedule remove <planId> <scheduledId> [-y] Remove a planned workout
tnr schedule link <planId> <scheduledId> --activity <activityId>
tnr schedule unlink <planId> <scheduledId>
tnr schedule suggest <planId> [--from <d>] [--to <d>] Suggest activity→workout links
tnr schedule auto-link <planId> [--from <d>] [--to <d>] [-y] Apply unambiguous suggestions
tnr schedule comments <planId> <scheduledId> List workout comments
tnr schedule comment <planId> <scheduledId> --body <text> Add a workout comment
tnr activity list [--from <d>] [--to <d>] [--sport <s>] [--source <src>]
[--unlinked] [--limit <n>]
tnr activity show <id> Show a single activity with full detail
tnr activity update <id> [--title <t>] [--description <d>] [--sport <s>] [--type <t>]
tnr fitness summary [--weeks <n>] Weekly load + Δ vs the prior window
tnr weather [--from <d>] [--to <d>] Daily forecast for your home city and other places
tnr profile show Show profile and physiology
tnr profile set [--ftp <w>] [--threshold-pace <mm:ss>] [--max-hr <bpm>]
[--resting-hr <bpm>] [--weight <kg>] [--css <mm:ss>]
[--vo2max <v>]
tnr mcp Run as an MCP server over stdio (local)Planned workouts vs activities. Planned workouts (tnr schedule …) live inside a training plan — they're sessions the athlete is supposed to do. Activities (tnr activity …) are what the athlete actually did — uploaded manually or imported from Strava. Link an activity to a planned workout with tnr schedule link.
Race types are: run, tri, swim, bike, custom. Friendly aliases (marathon, ironman, 70.3, 5k, half_marathon, ultra, olympic, sprint, duathlon, aquathlon, gravel, mtb, pool, open_water, virtual) are accepted and normalized.
Configuration
| Variable | Default | Purpose |
|---|---|---|
| THENEXTRACE_API_URL | https://www.thenextrace.app/api/v1 | Override the API host (e.g. for staging) |
Examples
# What races do I have coming up?
tnr race list --upcoming
# Pipe to jq for scripting
tnr --json race list --upcoming | jq '.[] | .title'
# Quick race entry
tnr race create \
--title "Berlin Marathon" \
--date 2026-09-27 \
--plan 123 \
--type marathon \
--location Berlin
# Update your zones
tnr profile set --ftp 285 --threshold-pace 4:05 --max-hr 188Releasing
Published by hand, from apps/mcp:
npm version patch # or minor / major — writes package.json
npm test # 53 tests
npm run test:parity -w @thenextrace/shared # the two servers + this README
npm publish --access publicBoth test runs are the release gate. The parity suite lives in
packages/shared because it reads the hosted server too, and it is what fails
when a tool or prompt reaches one server and not the other, or when the tool
list above goes stale.
Then add the CHANGELOG entry and commit both.
There is no CI release. This package used to live in its own repository, where
a v* tag triggered a publish workflow; moving it into the monorepo left that
workflow at a path GitHub does not read, so tagging published nothing and the
README went on describing a release that never ran. The workflow is gone rather
than relocated — a manual publish is the honest description of what happens.
npm whoami should say thenextrace2 before you publish; the version number
is permanent once it lands.
License
MIT
