create_routine
Create a new workout routine by defining exercises, sets, and rest intervals. Optionally assign to a folder and handle duplicates.
Instructions
Create a new routine.
Required routine shape:
{ title, folder_id?, notes?, exercises: [
{ exercise_template_id, rest_seconds?, notes?, sets: [
{ type, weight_kg?, reps?, rpe? }
] }
] }
WORKFLOW for natural-language requests:
Resolve every exercise name to a template id with
search_exercise_templates.(Optional) Create or look up the target folder with the folder tools.
Call this tool. If a routine with the same title already exists in the folder you'll get back a
duplicate_ofpayload — confirm with the user, then re-call withforce=True(or callupdate_routineinstead).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| routine | Yes | ||
| force | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- src/hevy_mcp/tools/routines.py:111-133 (handler)The actual MCP tool handler (with @mcp.tool() and @tool_guard decorators) for create_routine. Parses the routine dict, delegates to _do_create_routine.
@mcp.tool() @tool_guard async def create_routine( routine: dict[str, Any], force: bool = False, ) -> dict[str, Any]: """Create a new routine. Required `routine` shape: { title, folder_id?, notes?, exercises: [ { exercise_template_id, rest_seconds?, notes?, sets: [ { type, weight_kg?, reps?, rpe? } ] } ] } WORKFLOW for natural-language requests: 1. Resolve every exercise name to a template id with `search_exercise_templates`. 2. (Optional) Create or look up the target folder with the folder tools. 3. Call this tool. If a routine with the same title already exists in the folder you'll get back a `duplicate_of` payload — confirm with the user, then re-call with `force=True` (or call `update_routine` instead). """ return await _do_create_routine(client, routine, force) - src/hevy_mcp/tools/routines.py:50-69 (handler)Core business logic: validates via Routine Pydantic schema, checks for duplicate title in folder (unless force=True), then POSTs to /routines.
async def _do_create_routine( client, routine: dict[str, Any], force: bool, ) -> dict[str, Any]: """Module-level body of the create_routine tool — kept testable without FastMCP.""" validated = Routine.model_validate(routine).model_dump(exclude_none=True) title = validated.get("title") folder_id = validated.get("folder_id") if not force and title: dup = await _find_duplicate(client, title, folder_id) if dup is not None: return { "error": f"A routine titled {title!r} already exists in this folder.", "hint": ("Confirm with the user. To overwrite, call `update_routine` " "with the existing id. To create anyway, re-call with force=True."), "duplicate_of": dup, } data = await client.post("/routines", json={"routine": validated}) return {"text": f"Routine '{title}' created.", "data": data} - src/hevy_mcp/schemas.py:73-78 (schema)Pydantic schema used by create_routine to validate the routine payload before POSTing to Hevy API.
class Routine(_Base): id: str | None = None title: str folder_id: int | None = None notes: str | None = None exercises: list[RoutineExercise] = Field(default_factory=list) - src/hevy_mcp/server.py:43-59 (registration)Server entry point calls register_all() which calls routines.register(), attaching create_routine to the MCP server.
def build_server() -> tuple[FastMCP, AppContext]: _configure_logging() client = HevyClient() ctx = AppContext(client=client, template_cache=TTLCache(ttl_seconds=24 * 60 * 60)) mcp = FastMCP( name="hevy-mcp", instructions=( "Tools to read and write a user's data on Hevy (workout-tracking app). " "When the user asks to build or modify a routine from natural language, " "ALWAYS resolve exercise names to template ids via `search_exercise_templates` " "before calling `create_routine` or `update_routine`. Do not invent ids. " "Workout list pages are capped at 10 items by Hevy." ), ) register_all(mcp, ctx) return mcp, ctx - src/hevy_mcp/tools/__init__.py:6-8 (registration)Module import/registration wiring: register_all() calls routines.register() which defines the create_routine tool.
def register_all(mcp, ctx) -> None: workouts.register(mcp, ctx) routines.register(mcp, ctx)