Skip to main content
Glama
routines.ts7.69 kB
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; // Import types from generated client import type { PostRoutinesRequestExercise, PostRoutinesRequestSet, PostRoutinesRequestSetTypeEnum, PutRoutinesRequestExercise, PutRoutinesRequestSet, PutRoutinesRequestSetTypeEnum, Routine, } from "../generated/client/types/index.js"; import { withErrorHandling } from "../utils/error-handler.js"; import { formatRoutine } from "../utils/formatters.js"; import { createEmptyResponse, createJsonResponse, } from "../utils/response-formatter.js"; // Type definitions for the routine operations type HevyClient = ReturnType< typeof import("../utils/hevyClientKubb.js").createClient >; // Define types for the routine operations based on Zod schemas type CreateRoutineParams = { title: string; folderId?: number | null; notes?: string; exercises: Array<{ exerciseTemplateId: string; supersetId?: number | null; restSeconds?: number; notes?: string; sets: Array<{ type: "warmup" | "normal" | "failure" | "dropset"; weightKg?: number; reps?: number; distanceMeters?: number; durationSeconds?: number; customMetric?: number; }>; }>; }; type UpdateRoutineParams = { routineId: string; title: string; notes?: string; exercises: Array<{ exerciseTemplateId: string; supersetId?: number | null; restSeconds?: number; notes?: string; sets: Array<{ type: "warmup" | "normal" | "failure" | "dropset"; weightKg?: number; reps?: number; distanceMeters?: number; durationSeconds?: number; customMetric?: number; }>; }>; }; /** * Register all routine-related tools with the MCP server */ export function registerRoutineTools( server: McpServer, hevyClient: HevyClient, ) { // Get routines server.tool( "get-routines", "Get a paginated list of your workout routines, including custom and default routines. Useful for browsing or searching your available routines.", { page: z.coerce.number().int().gte(1).default(1), pageSize: z.coerce.number().int().gte(1).lte(10).default(5), }, withErrorHandling(async (args) => { const { page, pageSize } = args as { page: number; pageSize: number }; const data = await hevyClient.getRoutines({ page, pageSize, }); // Process routines to extract relevant information const routines = data?.routines?.map((routine: Routine) => formatRoutine(routine)) || []; if (routines.length === 0) { return createEmptyResponse( "No routines found for the specified parameters", ); } return createJsonResponse(routines); }, "get-routines"), ); // Get single routine by ID (new, direct endpoint) server.tool( "get-routine", "Get a routine by its ID using the direct endpoint. Returns all details for the specified routine.", { routineId: z.string().min(1), }, withErrorHandling(async ({ routineId }) => { const data = await hevyClient.getRoutineById(String(routineId)); if (!data || !data.routine) { return createEmptyResponse(`Routine with ID ${routineId} not found`); } const routine = formatRoutine(data.routine); return createJsonResponse(routine); }, "get-routine"), ); // Create new routine server.tool( "create-routine", "Create a new workout routine in your Hevy account. Requires a title and at least one exercise with sets. Optionally assign to a folder. Returns the full routine details including the new routine ID.", { title: z.string().min(1), folderId: z.coerce.number().nullable().optional(), notes: z.string().optional(), exercises: z.array( z.object({ exerciseTemplateId: z.string().min(1), supersetId: z.coerce.number().nullable().optional(), restSeconds: z.coerce.number().int().min(0).optional(), notes: z.string().optional(), sets: z.array( z.object({ type: z .enum(["warmup", "normal", "failure", "dropset"]) .default("normal"), weightKg: z.coerce.number().optional(), reps: z.coerce.number().int().optional(), distanceMeters: z.coerce.number().int().optional(), durationSeconds: z.coerce.number().int().optional(), customMetric: z.coerce.number().optional(), }), ), }), ), }, withErrorHandling(async (args) => { const { title, folderId, notes, exercises } = args as CreateRoutineParams; const data = await hevyClient.createRoutine({ routine: { title, folder_id: folderId ?? null, notes: notes ?? "", exercises: exercises.map( (exercise): PostRoutinesRequestExercise => ({ exercise_template_id: exercise.exerciseTemplateId, superset_id: exercise.supersetId ?? null, rest_seconds: exercise.restSeconds ?? null, notes: exercise.notes ?? null, sets: exercise.sets.map( (set): PostRoutinesRequestSet => ({ type: set.type as PostRoutinesRequestSetTypeEnum, weight_kg: set.weightKg ?? null, reps: set.reps ?? null, distance_meters: set.distanceMeters ?? null, duration_seconds: set.durationSeconds ?? null, custom_metric: set.customMetric ?? null, }), ), }), ), }, }); if (!data) { return createEmptyResponse( "Failed to create routine: Server returned no data", ); } const routine = formatRoutine(data); return createJsonResponse(routine, { pretty: true, indent: 2, }); }, "create-routine"), ); // Update existing routine server.tool( "update-routine", "Update an existing routine by ID. You can modify the title, notes, and exercise configurations. Returns the updated routine with all changes applied.", { routineId: z.string().min(1), title: z.string().min(1), notes: z.string().optional(), exercises: z.array( z.object({ exerciseTemplateId: z.string().min(1), supersetId: z.coerce.number().nullable().optional(), restSeconds: z.coerce.number().int().min(0).optional(), notes: z.string().optional(), sets: z.array( z.object({ type: z .enum(["warmup", "normal", "failure", "dropset"]) .default("normal"), weightKg: z.coerce.number().optional(), reps: z.coerce.number().int().optional(), distanceMeters: z.coerce.number().int().optional(), durationSeconds: z.coerce.number().int().optional(), customMetric: z.coerce.number().optional(), }), ), }), ), }, withErrorHandling(async (args) => { const { routineId, title, notes, exercises } = args as UpdateRoutineParams; const data = await hevyClient.updateRoutine(routineId, { routine: { title, notes: notes ?? null, exercises: exercises.map( (exercise): PutRoutinesRequestExercise => ({ exercise_template_id: exercise.exerciseTemplateId, superset_id: exercise.supersetId ?? null, rest_seconds: exercise.restSeconds ?? null, notes: exercise.notes ?? null, sets: exercise.sets.map( (set): PutRoutinesRequestSet => ({ type: set.type as PutRoutinesRequestSetTypeEnum, weight_kg: set.weightKg ?? null, reps: set.reps ?? null, distance_meters: set.distanceMeters ?? null, duration_seconds: set.durationSeconds ?? null, custom_metric: set.customMetric ?? null, }), ), }), ), }, }); if (!data) { return createEmptyResponse( `Failed to update routine with ID ${routineId}`, ); } const routine = formatRoutine(data); return createJsonResponse(routine, { pretty: true, indent: 2, }); }, "update-routine"), ); }

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/chrisdoc/hevy-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server