update_routine
Update an existing workout routine by providing its ID and the modified routine details.
Instructions
Update an existing routine in place.
Same payload shape as create_routine.routine, with one important caveat:
Hevy's PUT endpoint does NOT accept folder_id, and there is no public API
endpoint for moving a routine between folders. If folder_id is present in
the payload it is silently stripped and a warning is included in the
response. To 'move' a routine, create a new copy in the target folder and
delete the old one in the Hevy app.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| routine_id | Yes | ||
| routine | Yes |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- src/hevy_mcp/tools/routines.py:72-87 (handler)Core execution function for update_routine: validates routine dict via Pydantic, sanitizes out server-managed fields (folder_id, id, created_at, updated_at, index), makes PUT /routines/{id} call to Hevy API, and surfaces a warning if folder_id was ignored.
async def _do_update_routine( client, routine_id: str, routine: dict[str, Any], ) -> dict[str, Any]: """Module-level body of the update_routine tool — kept testable without FastMCP. Sanitizes the payload (drops folder_id and a few server-managed fields) and surfaces a `warning` field to the caller if folder_id was explicitly set, so users notice the silent no-op rather than thinking the move worked. """ validated = Routine.model_validate(routine).model_dump(exclude_none=True) sanitized, folder_id_was_present = _sanitize_routine_for_put(validated) data = await client.put(f"/routines/{routine_id}", json={"routine": sanitized}) out: dict[str, Any] = {"text": "Routine updated.", "data": data} if folder_id_was_present: out["warning"] = _FOLDER_MOVE_WARNING return out - src/hevy_mcp/tools/routines.py:135-147 (handler)MCP tool registration of update_routine: decorated with @mcp.tool() and @tool_guard, accepts routine_id and routine dict, delegates to _do_update_routine.
@mcp.tool() @tool_guard async def update_routine(routine_id: str, routine: dict[str, Any]) -> dict[str, Any]: """Update an existing routine in place. Same payload shape as `create_routine.routine`, with one important caveat: Hevy's PUT endpoint does NOT accept `folder_id`, and there is no public API endpoint for moving a routine between folders. If `folder_id` is present in the payload it is silently stripped and a `warning` is included in the response. To 'move' a routine, create a new copy in the target folder and delete the old one in the Hevy app. """ return await _do_update_routine(client, routine_id, routine) - src/hevy_mcp/tools/routines.py:135-147 (registration)Tool registration via @mcp.tool() decorator inside the register() function of routines.py. The register() function is called from tools/__init__.py which is called from server.py.
@mcp.tool() @tool_guard async def update_routine(routine_id: str, routine: dict[str, Any]) -> dict[str, Any]: """Update an existing routine in place. Same payload shape as `create_routine.routine`, with one important caveat: Hevy's PUT endpoint does NOT accept `folder_id`, and there is no public API endpoint for moving a routine between folders. If `folder_id` is present in the payload it is silently stripped and a `warning` is included in the response. To 'move' a routine, create a new copy in the target folder and delete the old one in the Hevy app. """ return await _do_update_routine(client, routine_id, routine) - src/hevy_mcp/schemas.py:73-78 (schema)Pydantic model for Routine used by _do_update_routine for validation (Routine.model_validate). Defines the accepted input shape.
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/tools/routines.py:38-47 (helper)Helper that strips fields (id, folder_id, created_at, updated_at, index) from the routine dict before sending to Hevy's PUT endpoint, and reports whether folder_id was present for warning purposes.
def _sanitize_routine_for_put(routine: dict[str, Any]) -> tuple[dict[str, Any], bool]: """Strip fields that PUT /routines/{id} rejects. Returns ``(cleaned, folder_id_was_explicitly_set)``. The boolean lets the caller surface a warning to the user that folder reassignment is not supported by Hevy's API. """ folder_id_was_present = routine.get("folder_id") is not None cleaned = {k: v for k, v in routine.items() if k not in _PUT_ROUTINE_DROP_TOP} return cleaned, folder_id_was_present