create_garmin_workout
Convert natural language workout descriptions into structured Garmin Connect workouts for running, cycling, or swimming with automatic device sync.
Instructions
Create a workout in Garmin Connect from structured workout data. Claude should parse the natural language description and pass structured workout steps.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Name of the workout | |
| sport | Yes | Sport type (defaults to running) | |
| steps | Yes | Array of workout steps |
Implementation Reference
- src/garmin-workout-creator.ts:33-142 (handler)Core handler function that executes the Garmin workout creation: parses workout data, converts to Garmin API payload, makes authenticated POST request to Garmin Connect API, and returns success/error with workout URL.export async function createGarminWorkout( description: string, authToken: string, cookies: string, customName?: string, sport: string = "running", llmParser?: ( description: string, sport: string, customName?: string ) => Promise<WorkoutData> ): Promise<WorkoutResult> { try { // Parse the description into workout data using LLM const workoutData = llmParser ? await llmParser(description, sport, customName) : await parseWorkoutDescriptionWithLLM(description, sport, customName); // Convert to Garmin API format const payload = convertToGarminPayload(workoutData); console.error(`🏃 Creating workout: ${workoutData.name}`); console.error(`📊 Workout has ${workoutData.steps.length} steps:`); workoutData.steps.forEach((step, i) => { console.error( ` ${i + 1}. ${step.name}: ${step.duration} at ${step.target} (${ step.intensity })` ); }); console.error( `📤 Garmin payload has ${payload.workoutSegments[0].workoutSteps.length} steps` ); // Write debug info to file for inspection try { const fs = require("fs"); const debugInfo = { workoutName: workoutData.name, inputSteps: workoutData.steps, outputSteps: payload.workoutSegments[0].workoutSteps, fullPayload: payload, }; fs.writeFileSync( "/tmp/garmin-debug.json", JSON.stringify(debugInfo, null, 2) ); console.error(`📝 Debug info written to /tmp/garmin-debug.json`); } catch (e) { console.error("Failed to write debug file:", e); } // Make API call to Garmin const response = await fetch( "https://connect.garmin.com/workout-service/workout", { method: "POST", headers: { accept: "application/json, text/plain, */*", "accept-language": "en-GB,en-US;q=0.9,en;q=0.8", authorization: authToken, "content-type": "application/json;charset=UTF-8", cookie: cookies, "di-backend": "connectapi.garmin.com", nk: "NT", origin: "https://connect.garmin.com", referer: `https://connect.garmin.com/modern/workout/create/${sport}`, "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36", "x-app-ver": "5.14.1.2", "x-lang": "en-US", }, body: JSON.stringify(payload), } ); if (response.ok) { const result = (await response.json()) as GarminApiResponse; const workoutId = result.workoutId.toString(); return { success: true, workoutId, name: result.workoutName, url: `https://connect.garmin.com/modern/workout/${workoutId}`, }; } else { const errorText = await response.text(); console.error("API Error:", response.status, errorText); if (response.status === 401) { return { success: false, error: "Authentication expired. Please re-authenticate with Garmin Connect.", }; } return { success: false, error: `API error: ${response.status} ${response.statusText}`, }; } } catch (error) { console.error("Workout creation error:", error); return { success: false, error: error instanceof Error ? error.message : String(error), }; } }
- src/mcp-server.ts:115-185 (handler)MCP server request handler for the 'create_garmin_workout' tool call: handles authentication check, constructs workout data from tool arguments, invokes the core createGarminWorkout function, and formats the MCP response.case "create_garmin_workout": { const { name: workoutName, sport = "running", steps } = args as { name: string; sport?: string; steps: WorkoutStep[]; }; try { // Check auth first const authData = await garminAuth.getValidAuth(); if (!authData) { return { content: [ { type: "text", text: "❌ Authentication required. Please run the 'authenticate_garmin' tool to authenticate with Garmin Connect.", }, ], }; } // Create workout data from structured input const workoutData: WorkoutData = { name: workoutName, sport: sport as 'running' | 'cycling' | 'swimming', steps }; // Create LLM parser that just returns the structured data const llmParser = async () => workoutData; // Create workout with structured data const result = await createGarminWorkout( workoutName, // description not needed anymore authData.authToken, authData.cookies, workoutName, sport, llmParser ); if (result.success) { return { content: [ { type: "text", text: `✅ Workout created successfully!\n\n**${result.name}** (ID: ${result.workoutId})\n\n🔗 **View in Garmin Connect:** ${result.url}\n\nThe workout is now available in your Garmin Connect account and ready to sync to your device.`, }, ], }; } else { return { content: [ { type: "text", text: `❌ Failed to create workout: ${result.error}`, }, ], }; } } catch (error) { return { content: [ { type: "text", text: `❌ Error creating workout: ${error instanceof Error ? error.message : String(error)}`, }, ], }; } }
- src/mcp-server.ts:48-89 (schema)JSON schema defining the input parameters for the 'create_garmin_workout' tool: workout name, sport, and array of steps with name, duration, target, and intensity.inputSchema: { type: "object", properties: { name: { type: "string", description: "Name of the workout", }, sport: { type: "string", enum: ["running", "cycling", "swimming"], description: "Sport type (defaults to running)", }, steps: { type: "array", description: "Array of workout steps", items: { type: "object", properties: { name: { type: "string", description: "Step name (e.g., 'Warm-up', 'Sprint 1', 'Recovery 1')", }, duration: { type: "string", description: "Duration in MM:SS format (e.g., '10:00') or distance (e.g., '1.5 km')", }, target: { type: "string", description: "Target zone or intensity. Use 'Zone 1' for recovery/easy, 'Zone 2' for aerobic/base, 'Zone 3' for tempo, 'Zone 4' for threshold, 'Zone 5' for VO2 max/all-out efforts. IMPORTANT: Default warmup and cooldown steps to 'Zone 2' unless user specifically requests otherwise. Use 'Open' only when no specific target is mentioned.", }, intensity: { type: "string", enum: ["warmup", "active", "rest", "cooldown"], description: "Step intensity type", }, }, required: ["name", "duration", "target", "intensity"], }, }, }, required: ["name", "sport", "steps"], },
- src/mcp-server.ts:46-90 (registration)Registration of the 'create_garmin_workout' tool in the MCP server's listTools response, including name, description, and input schema reference.name: "create_garmin_workout", description: "Create a workout in Garmin Connect from structured workout data. Claude should parse the natural language description and pass structured workout steps.", inputSchema: { type: "object", properties: { name: { type: "string", description: "Name of the workout", }, sport: { type: "string", enum: ["running", "cycling", "swimming"], description: "Sport type (defaults to running)", }, steps: { type: "array", description: "Array of workout steps", items: { type: "object", properties: { name: { type: "string", description: "Step name (e.g., 'Warm-up', 'Sprint 1', 'Recovery 1')", }, duration: { type: "string", description: "Duration in MM:SS format (e.g., '10:00') or distance (e.g., '1.5 km')", }, target: { type: "string", description: "Target zone or intensity. Use 'Zone 1' for recovery/easy, 'Zone 2' for aerobic/base, 'Zone 3' for tempo, 'Zone 4' for threshold, 'Zone 5' for VO2 max/all-out efforts. IMPORTANT: Default warmup and cooldown steps to 'Zone 2' unless user specifically requests otherwise. Use 'Open' only when no specific target is mentioned.", }, intensity: { type: "string", enum: ["warmup", "active", "rest", "cooldown"], description: "Step intensity type", }, }, required: ["name", "duration", "target", "intensity"], }, }, }, required: ["name", "sport", "steps"], }, },
- Helper function that converts the parsed workout data into the exact JSON payload format required by Garmin Connect's workout creation API.function convertToGarminPayload(workoutData: WorkoutData) { // Sport type mapping const sportMapping: { [key: string]: { id: number; key: string; order: number }; } = { running: { id: 1, key: "running", order: 1 }, cycling: { id: 2, key: "cycling", order: 2 }, swimming: { id: 5, key: "swimming", order: 5 }, }; const sport = sportMapping[workoutData.sport] || sportMapping["running"]; // Convert workout steps const workoutSteps = workoutData.steps.map((step, index) => { return convertWorkoutStep(step, index + 1); }); return { sportType: { sportTypeId: sport.id, sportTypeKey: sport.key, displayOrder: sport.order, }, subSportType: null, workoutName: workoutData.name, estimatedDistanceUnit: { unitKey: null }, workoutSegments: [ { segmentOrder: 1, sportType: { sportTypeId: sport.id, sportTypeKey: sport.key, displayOrder: sport.order, }, workoutSteps, }, ], avgTrainingSpeed: 3.0727914832080057, estimatedDurationInSecs: 0, estimatedDistanceInMeters: 0, estimateType: null, isWheelchair: false, }; }