@etweisberg/garmin-connect-mcp 0.1.20 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/resources.js +390 -0
- package/dist/test.js +2 -2
- package/dist/tools.js +3 -303
- package/package.json +1 -1
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
// ── Workout Templates (MCP Resources) ──────────────────────────────────────
|
|
2
|
+
// Templates adapted from Taxuspt/garmin_mcp (MIT License, Copyright (c) 2025 Alexandre Domingues)
|
|
3
|
+
// https://github.com/Taxuspt/garmin_mcp/blob/main/src/garmin_mcp/workout_templates.py
|
|
4
|
+
const WORKOUT_TEMPLATES = {
|
|
5
|
+
"simple-run": {
|
|
6
|
+
workoutName: "Simple Run",
|
|
7
|
+
sportType: { sportTypeId: 1, sportTypeKey: "running" },
|
|
8
|
+
workoutSegments: [
|
|
9
|
+
{
|
|
10
|
+
segmentOrder: 1,
|
|
11
|
+
sportType: { sportTypeId: 1, sportTypeKey: "running" },
|
|
12
|
+
workoutSteps: [
|
|
13
|
+
{
|
|
14
|
+
type: "ExecutableStepDTO",
|
|
15
|
+
stepOrder: 1,
|
|
16
|
+
stepType: { stepTypeId: 1, stepTypeKey: "warmup" },
|
|
17
|
+
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
18
|
+
endConditionValue: 300.0,
|
|
19
|
+
targetType: {
|
|
20
|
+
workoutTargetTypeId: 1,
|
|
21
|
+
workoutTargetTypeKey: "no.target",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
type: "ExecutableStepDTO",
|
|
26
|
+
stepOrder: 2,
|
|
27
|
+
stepType: { stepTypeId: 3, stepTypeKey: "interval" },
|
|
28
|
+
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
29
|
+
endConditionValue: 1800.0,
|
|
30
|
+
targetType: {
|
|
31
|
+
workoutTargetTypeId: 1,
|
|
32
|
+
workoutTargetTypeKey: "no.target",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
type: "ExecutableStepDTO",
|
|
37
|
+
stepOrder: 3,
|
|
38
|
+
stepType: { stepTypeId: 2, stepTypeKey: "cooldown" },
|
|
39
|
+
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
40
|
+
endConditionValue: 300.0,
|
|
41
|
+
targetType: {
|
|
42
|
+
workoutTargetTypeId: 1,
|
|
43
|
+
workoutTargetTypeKey: "no.target",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
"interval-running": {
|
|
51
|
+
workoutName: "Interval Running",
|
|
52
|
+
sportType: { sportTypeId: 1, sportTypeKey: "running" },
|
|
53
|
+
workoutSegments: [
|
|
54
|
+
{
|
|
55
|
+
segmentOrder: 1,
|
|
56
|
+
sportType: { sportTypeId: 1, sportTypeKey: "running" },
|
|
57
|
+
workoutSteps: [
|
|
58
|
+
{
|
|
59
|
+
type: "ExecutableStepDTO",
|
|
60
|
+
stepOrder: 1,
|
|
61
|
+
stepType: { stepTypeId: 1, stepTypeKey: "warmup" },
|
|
62
|
+
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
63
|
+
endConditionValue: 600.0,
|
|
64
|
+
targetType: {
|
|
65
|
+
workoutTargetTypeId: 1,
|
|
66
|
+
workoutTargetTypeKey: "no.target",
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
type: "RepeatGroupDTO",
|
|
71
|
+
stepOrder: 2,
|
|
72
|
+
numberOfIterations: 6,
|
|
73
|
+
workoutSteps: [
|
|
74
|
+
{
|
|
75
|
+
type: "ExecutableStepDTO",
|
|
76
|
+
stepOrder: 1,
|
|
77
|
+
stepType: { stepTypeId: 3, stepTypeKey: "interval" },
|
|
78
|
+
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
79
|
+
endConditionValue: 60.0,
|
|
80
|
+
targetType: {
|
|
81
|
+
workoutTargetTypeId: 4,
|
|
82
|
+
workoutTargetTypeKey: "heart.rate.zone",
|
|
83
|
+
},
|
|
84
|
+
zoneNumber: 5,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
type: "ExecutableStepDTO",
|
|
88
|
+
stepOrder: 2,
|
|
89
|
+
stepType: { stepTypeId: 4, stepTypeKey: "recovery" },
|
|
90
|
+
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
91
|
+
endConditionValue: 90.0,
|
|
92
|
+
targetType: {
|
|
93
|
+
workoutTargetTypeId: 4,
|
|
94
|
+
workoutTargetTypeKey: "heart.rate.zone",
|
|
95
|
+
},
|
|
96
|
+
zoneNumber: 2,
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
type: "ExecutableStepDTO",
|
|
102
|
+
stepOrder: 3,
|
|
103
|
+
stepType: { stepTypeId: 2, stepTypeKey: "cooldown" },
|
|
104
|
+
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
105
|
+
endConditionValue: 600.0,
|
|
106
|
+
targetType: {
|
|
107
|
+
workoutTargetTypeId: 1,
|
|
108
|
+
workoutTargetTypeKey: "no.target",
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
"tempo-run": {
|
|
116
|
+
workoutName: "Tempo Run",
|
|
117
|
+
sportType: { sportTypeId: 1, sportTypeKey: "running" },
|
|
118
|
+
workoutSegments: [
|
|
119
|
+
{
|
|
120
|
+
segmentOrder: 1,
|
|
121
|
+
sportType: { sportTypeId: 1, sportTypeKey: "running" },
|
|
122
|
+
workoutSteps: [
|
|
123
|
+
{
|
|
124
|
+
type: "ExecutableStepDTO",
|
|
125
|
+
stepOrder: 1,
|
|
126
|
+
stepType: { stepTypeId: 1, stepTypeKey: "warmup" },
|
|
127
|
+
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
128
|
+
endConditionValue: 600.0,
|
|
129
|
+
targetType: {
|
|
130
|
+
workoutTargetTypeId: 4,
|
|
131
|
+
workoutTargetTypeKey: "heart.rate.zone",
|
|
132
|
+
},
|
|
133
|
+
zoneNumber: 2,
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
type: "ExecutableStepDTO",
|
|
137
|
+
stepOrder: 2,
|
|
138
|
+
stepType: { stepTypeId: 3, stepTypeKey: "interval" },
|
|
139
|
+
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
140
|
+
endConditionValue: 1200.0,
|
|
141
|
+
targetType: {
|
|
142
|
+
workoutTargetTypeId: 4,
|
|
143
|
+
workoutTargetTypeKey: "heart.rate.zone",
|
|
144
|
+
},
|
|
145
|
+
zoneNumber: 4,
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
type: "ExecutableStepDTO",
|
|
149
|
+
stepOrder: 3,
|
|
150
|
+
stepType: { stepTypeId: 2, stepTypeKey: "cooldown" },
|
|
151
|
+
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
152
|
+
endConditionValue: 600.0,
|
|
153
|
+
targetType: {
|
|
154
|
+
workoutTargetTypeId: 4,
|
|
155
|
+
workoutTargetTypeKey: "heart.rate.zone",
|
|
156
|
+
},
|
|
157
|
+
zoneNumber: 2,
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
"strength-circuit": {
|
|
164
|
+
workoutName: "Strength Circuit",
|
|
165
|
+
description: "Strength training workout template",
|
|
166
|
+
sportType: { sportTypeId: 5, sportTypeKey: "strength_training" },
|
|
167
|
+
workoutSegments: [
|
|
168
|
+
{
|
|
169
|
+
segmentOrder: 1,
|
|
170
|
+
sportType: { sportTypeId: 5, sportTypeKey: "strength_training" },
|
|
171
|
+
workoutSteps: [
|
|
172
|
+
{
|
|
173
|
+
type: "ExecutableStepDTO",
|
|
174
|
+
stepOrder: 1,
|
|
175
|
+
stepType: { stepTypeId: 1, stepTypeKey: "warmup" },
|
|
176
|
+
description: "Warmup description",
|
|
177
|
+
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
178
|
+
endConditionValue: 600.0,
|
|
179
|
+
targetType: {
|
|
180
|
+
workoutTargetTypeId: 1,
|
|
181
|
+
workoutTargetTypeKey: "no.target",
|
|
182
|
+
},
|
|
183
|
+
category: "WARM_UP",
|
|
184
|
+
exerciseName: "",
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
type: "RepeatGroupDTO",
|
|
188
|
+
stepOrder: 2,
|
|
189
|
+
numberOfIterations: 3,
|
|
190
|
+
skipLastRestStep: true,
|
|
191
|
+
workoutSteps: [
|
|
192
|
+
{
|
|
193
|
+
type: "ExecutableStepDTO",
|
|
194
|
+
stepOrder: 1,
|
|
195
|
+
stepType: { stepTypeId: 3, stepTypeKey: "interval" },
|
|
196
|
+
description: "Bench press 115lb × 10 reps",
|
|
197
|
+
endCondition: { conditionTypeId: 10, conditionTypeKey: "reps" },
|
|
198
|
+
endConditionValue: 10,
|
|
199
|
+
targetType: {
|
|
200
|
+
workoutTargetTypeId: 1,
|
|
201
|
+
workoutTargetTypeKey: "no.target",
|
|
202
|
+
},
|
|
203
|
+
category: "BENCH_PRESS",
|
|
204
|
+
exerciseName: "",
|
|
205
|
+
weightValue: 115.0,
|
|
206
|
+
weightUnit: { unitId: 9, unitKey: "pound", factor: 453.59237 },
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
type: "ExecutableStepDTO",
|
|
210
|
+
stepOrder: 2,
|
|
211
|
+
stepType: { stepTypeId: 4, stepTypeKey: "recovery" },
|
|
212
|
+
description: "Transition to push-ups (60 sec)",
|
|
213
|
+
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
214
|
+
endConditionValue: 60.0,
|
|
215
|
+
targetType: {
|
|
216
|
+
workoutTargetTypeId: 1,
|
|
217
|
+
workoutTargetTypeKey: "no.target",
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
type: "ExecutableStepDTO",
|
|
222
|
+
stepOrder: 3,
|
|
223
|
+
stepType: { stepTypeId: 3, stepTypeKey: "interval" },
|
|
224
|
+
description: "Push-ups × 10 reps (bodyweight)",
|
|
225
|
+
endCondition: { conditionTypeId: 10, conditionTypeKey: "reps" },
|
|
226
|
+
endConditionValue: 10,
|
|
227
|
+
targetType: {
|
|
228
|
+
workoutTargetTypeId: 1,
|
|
229
|
+
workoutTargetTypeKey: "no.target",
|
|
230
|
+
},
|
|
231
|
+
category: "PUSH_UP",
|
|
232
|
+
exerciseName: "PUSH_UP",
|
|
233
|
+
weightValue: null,
|
|
234
|
+
weightUnit: null,
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
type: "ExecutableStepDTO",
|
|
238
|
+
stepOrder: 4,
|
|
239
|
+
stepType: { stepTypeId: 4, stepTypeKey: "recovery" },
|
|
240
|
+
description: "Transition to bicep curls (60 sec)",
|
|
241
|
+
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
242
|
+
endConditionValue: 60.0,
|
|
243
|
+
targetType: {
|
|
244
|
+
workoutTargetTypeId: 1,
|
|
245
|
+
workoutTargetTypeKey: "no.target",
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
type: "ExecutableStepDTO",
|
|
250
|
+
stepOrder: 5,
|
|
251
|
+
stepType: { stepTypeId: 3, stepTypeKey: "interval" },
|
|
252
|
+
description: "Bicep curls 25lb × 12 reps",
|
|
253
|
+
endCondition: { conditionTypeId: 10, conditionTypeKey: "reps" },
|
|
254
|
+
endConditionValue: 12,
|
|
255
|
+
targetType: {
|
|
256
|
+
workoutTargetTypeId: 1,
|
|
257
|
+
workoutTargetTypeKey: "no.target",
|
|
258
|
+
},
|
|
259
|
+
category: "CURL",
|
|
260
|
+
exerciseName: "ALTERNATING_DUMBBELL_BICEPS_CURL",
|
|
261
|
+
weightValue: 25.0,
|
|
262
|
+
weightUnit: { unitId: 9, unitKey: "pound", factor: 453.59237 },
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
type: "ExecutableStepDTO",
|
|
266
|
+
stepOrder: 6,
|
|
267
|
+
stepType: { stepTypeId: 4, stepTypeKey: "recovery" },
|
|
268
|
+
description: "Rest 2 min before next round",
|
|
269
|
+
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
270
|
+
endConditionValue: 120.0,
|
|
271
|
+
targetType: {
|
|
272
|
+
workoutTargetTypeId: 1,
|
|
273
|
+
workoutTargetTypeKey: "no.target",
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
const WORKOUT_STRUCTURE_REFERENCE = `# Garmin Connect Workout JSON Structure Reference
|
|
284
|
+
|
|
285
|
+
## Top-level fields
|
|
286
|
+
- workoutName: string (required)
|
|
287
|
+
- sportType: { sportTypeId: number, sportTypeKey: string } (required)
|
|
288
|
+
- IDs: 1=running, 2=cycling, 3=swimming, 4=walking, 5=strength_training
|
|
289
|
+
- workoutSegments: array of segment objects (required)
|
|
290
|
+
- description: string (optional)
|
|
291
|
+
|
|
292
|
+
## Segment fields
|
|
293
|
+
- segmentOrder: number (1-based, required)
|
|
294
|
+
- sportType: same as top-level (required)
|
|
295
|
+
- workoutSteps: array of step objects (required)
|
|
296
|
+
|
|
297
|
+
## Step types
|
|
298
|
+
|
|
299
|
+
### ExecutableStepDTO (regular steps)
|
|
300
|
+
- type: "ExecutableStepDTO" (required)
|
|
301
|
+
- stepOrder: number (1-based within the containing steps array)
|
|
302
|
+
- stepType: { stepTypeId: number, stepTypeKey: string }
|
|
303
|
+
- 1="warmup", 2="cooldown", 3="interval", 4="recovery", 5="rest"
|
|
304
|
+
- endCondition: { conditionTypeId: number, conditionTypeKey: string }
|
|
305
|
+
- 2="time" (endConditionValue in seconds)
|
|
306
|
+
- 3="distance" (endConditionValue in meters)
|
|
307
|
+
- 7="lap.button" (press lap button; no endConditionValue needed)
|
|
308
|
+
- 10="reps" (endConditionValue = rep count — use for strength exercises)
|
|
309
|
+
- endConditionValue: number (required for time/distance/reps conditions)
|
|
310
|
+
- targetType: { workoutTargetTypeId: number, workoutTargetTypeKey: string }
|
|
311
|
+
- 1="no.target"
|
|
312
|
+
- 2="speed" — use targetValueOne/targetValueTwo (m/s)
|
|
313
|
+
- 4="heart.rate.zone" — use zoneNumber (1-5), NOT targetValueOne/targetValueTwo
|
|
314
|
+
- 6="cadence" — use targetValueOne/targetValueTwo (steps per minute)
|
|
315
|
+
- 11="power.zone" — use zoneNumber
|
|
316
|
+
- zoneNumber: number 1-5 (for heart.rate.zone or power.zone targets only)
|
|
317
|
+
- targetValueOne: number (lower bound for speed/cadence ranges)
|
|
318
|
+
- targetValueTwo: number (upper bound for speed/cadence ranges)
|
|
319
|
+
- description: string (optional, displayed on device)
|
|
320
|
+
|
|
321
|
+
### Strength exercise fields (interval steps in strength_training workouts)
|
|
322
|
+
- category: string — exercise category (e.g. "BENCH_PRESS", "SQUAT", "PUSH_UP", "CURL")
|
|
323
|
+
- exerciseName: string — specific variant (e.g. "PUSH_UP", "ALTERNATING_DUMBBELL_BICEPS_CURL"); use "" if only one variant
|
|
324
|
+
- weightValue: number | null — weight in pounds as float; null/omit for bodyweight
|
|
325
|
+
- weightUnit: { unitId: 9, unitKey: "pound", factor: 453.59237 } | null — omit/null for bodyweight
|
|
326
|
+
- Warmup steps: set category="WARM_UP", exerciseName=""
|
|
327
|
+
- Recovery/transition steps: omit category and exerciseName (or set to null)
|
|
328
|
+
|
|
329
|
+
Exercise names from FIT SDK (not yet fully validated against Connect API):
|
|
330
|
+
| Category | exerciseName variants |
|
|
331
|
+
|----------|-----------------------|
|
|
332
|
+
| BENCH_PRESS | BARBELL_BENCH_PRESS, DUMBBELL_BENCH_PRESS, INCLINE_BARBELL_BENCH_PRESS, INCLINE_DUMBBELL_BENCH_PRESS, DECLINE_BARBELL_BENCH_PRESS, CLOSE_GRIP_BARBELL_BENCH_PRESS |
|
|
333
|
+
| SQUAT | BARBELL_BACK_SQUAT, BARBELL_FRONT_SQUAT, BODYWEIGHT_SQUAT, DUMBBELL_SQUAT, GOBLET_SQUAT, HACK_SQUAT, LEG_PRESS, SUMO_SQUAT, WALL_SIT |
|
|
334
|
+
| DEADLIFT | BARBELL_DEADLIFT, DUMBBELL_DEADLIFT, ROMANIAN_DEADLIFT, SINGLE_LEG_DEADLIFT, STIFF_LEG_DEADLIFT, SUMO_DEADLIFT |
|
|
335
|
+
| LUNGE | BARBELL_LUNGE, BODYWEIGHT_LUNGE, DUMBBELL_LUNGE, FORWARD_LUNGE, LATERAL_LUNGE, REVERSE_LUNGE, WALKING_LUNGE |
|
|
336
|
+
| ROW | CABLE_ROW, DUMBBELL_ROW, INVERTED_ROW, SEATED_CABLE_ROW, SINGLE_ARM_DUMBBELL_ROW, T_BAR_ROW |
|
|
337
|
+
| PULL_UP | PULL_UP, CHIN_UP, WEIGHTED_PULL_UP, CLOSE_GRIP_PULLDOWN, BANDED_PULL_UPS |
|
|
338
|
+
| SHOULDER_PRESS | OVERHEAD_DUMBBELL_PRESS, BARBELL_SHOULDER_PRESS, DUMBBELL_SHOULDER_PRESS, ARNOLD_PRESS, ALTERNATING_DUMBBELL_SHOULDER_PRESS, SEATED_DUMBBELL_SHOULDER_PRESS |
|
|
339
|
+
| CURL | ALTERNATING_DUMBBELL_BICEPS_CURL, BARBELL_BICEP_CURL, CABLE_BICEP_CURL, CONCENTRATION_CURL, DUMBBELL_BICEP_CURL, HAMMER_CURL, INCLINE_DUMBBELL_BICEP_CURL |
|
|
340
|
+
| TRICEP_EXTENSION | BENCH_DIPS, CABLE_OVERHEAD_TRICEP_EXTENSION, DUMBBELL_LYING_TRICEP_EXTENSION, SKULL_CRUSHERS, TRICEP_PUSHDOWN, TRICEP_ROPE_PUSHDOWN |
|
|
341
|
+
| FLYE | CABLE_CROSSOVER, DUMBBELL_FLYE, INCLINE_DUMBBELL_FLYE |
|
|
342
|
+
| PUSH_UP | PUSH_UP, CLOSE_GRIP_PUSH_UP, DECLINE_PUSH_UP, INCLINE_PUSH_UP, PIKE_PUSH_UP, WEIGHTED_PUSH_UP |
|
|
343
|
+
| PLANK | PLANK, SIDE_PLANK, WEIGHTED_PLANK, PLANK_WITH_KNEE_TO_CHEST |
|
|
344
|
+
| CRUNCH | CRUNCH, BICYCLE_CRUNCH, CABLE_CRUNCH, REVERSE_CRUNCH, SITUP, WEIGHTED_CRUNCH |
|
|
345
|
+
| HIP_RAISE | BARBELL_HIP_THRUST, GLUTE_BRIDGE, HIP_RAISE, WEIGHTED_HIP_RAISE |
|
|
346
|
+
| CALF_RAISE | CALF_RAISE, DONKEY_CALF_RAISE, SEATED_CALF_RAISE |
|
|
347
|
+
| STEP_UP | BARBELL_STEP_UPS, BODYWEIGHT_STEP_UP, DUMBBELL_STEP_UPS |
|
|
348
|
+
| LATERAL_RAISE | ALTERNATING_LATERAL_RAISE, DUMBBELL_LATERAL_RAISE |
|
|
349
|
+
| CORE | TURKISH_GET_UP, DEAD_BUG, PALLOF_PRESS |
|
|
350
|
+
| WARM_UP | JUMPING_JACK, MOUNTAIN_CLIMBER |
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
### RepeatGroupDTO (repeat blocks)
|
|
354
|
+
- type: "RepeatGroupDTO" (required)
|
|
355
|
+
- stepOrder: number (1-based within the containing steps array)
|
|
356
|
+
- numberOfIterations: number (how many times to repeat)
|
|
357
|
+
- skipLastRestStep: true — set this on strength circuits to skip the final rest after the last round
|
|
358
|
+
- workoutSteps: array of ExecutableStepDTO (the steps to repeat)
|
|
359
|
+
- stepOrder within this array is 1-based and independent of the parent
|
|
360
|
+
|
|
361
|
+
## Notes
|
|
362
|
+
- NEVER use targetValueOne/targetValueTwo for heart rate zones — use zoneNumber instead.
|
|
363
|
+
Using targetValueOne/targetValueTwo with heart.rate.zone target type causes Garmin to
|
|
364
|
+
misinterpret the values as pace (m/s), resulting in impossible paces like ~11 sec/mile.
|
|
365
|
+
- RepeatGroupDTO cannot be nested inside another RepeatGroupDTO.
|
|
366
|
+
- All stepOrder values within the same array must be sequential starting from 1.
|
|
367
|
+
`;
|
|
368
|
+
export function registerResources(server) {
|
|
369
|
+
for (const [name, template] of Object.entries(WORKOUT_TEMPLATES)) {
|
|
370
|
+
const uri = `workout://templates/${name}`;
|
|
371
|
+
server.resource(name, uri, async (resourceUri) => ({
|
|
372
|
+
contents: [
|
|
373
|
+
{
|
|
374
|
+
uri: resourceUri.href,
|
|
375
|
+
mimeType: "application/json",
|
|
376
|
+
text: JSON.stringify(template, null, 2),
|
|
377
|
+
},
|
|
378
|
+
],
|
|
379
|
+
}));
|
|
380
|
+
}
|
|
381
|
+
server.resource("workout-structure-reference", "workout://reference/structure", async (resourceUri) => ({
|
|
382
|
+
contents: [
|
|
383
|
+
{
|
|
384
|
+
uri: resourceUri.href,
|
|
385
|
+
mimeType: "text/markdown",
|
|
386
|
+
text: WORKOUT_STRUCTURE_REFERENCE,
|
|
387
|
+
},
|
|
388
|
+
],
|
|
389
|
+
}));
|
|
390
|
+
}
|
package/dist/test.js
CHANGED
|
@@ -98,8 +98,8 @@ const resourceTests = [
|
|
|
98
98
|
const data = JSON.parse(content.text);
|
|
99
99
|
if (data.workoutName !== "Strength Circuit")
|
|
100
100
|
throw new Error(`unexpected workoutName: ${data.workoutName}`);
|
|
101
|
-
if (data.sportType.sportTypeKey !== "
|
|
102
|
-
throw new Error("expected
|
|
101
|
+
if (data.sportType.sportTypeKey !== "strength_training")
|
|
102
|
+
throw new Error("expected strength_training sport type");
|
|
103
103
|
// Must have a RepeatGroupDTO
|
|
104
104
|
const steps = data.workoutSegments[0]?.workoutSteps ?? [];
|
|
105
105
|
const hasRepeatGroup = steps.some((s) => s.type === "RepeatGroupDTO");
|
package/dist/tools.js
CHANGED
|
@@ -3,6 +3,7 @@ import { writeFileSync, mkdirSync } from "node:fs";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { inflateRawSync } from "node:zlib";
|
|
5
5
|
import { getSharedClient, resetSharedClient, sessionExists, getSessionFile, } from "./garmin-client.js";
|
|
6
|
+
export { registerResources } from "./resources.js";
|
|
6
7
|
function jsonResult(data) {
|
|
7
8
|
return {
|
|
8
9
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
@@ -454,9 +455,9 @@ IMPORTANT: Step types must use Garmin's DTO format:
|
|
|
454
455
|
IMPORTANT: For heart rate zone targets, use "zoneNumber" (1-5), NOT targetValueOne/targetValueTwo.
|
|
455
456
|
targetValueOne/targetValueTwo are only for absolute value ranges (e.g. pace in m/s, power in watts).
|
|
456
457
|
|
|
457
|
-
Sport type IDs: 1=running, 2=cycling, 3=swimming, 4=walking, 5=
|
|
458
|
+
Sport type IDs: 1=running, 2=cycling, 3=swimming, 4=walking, 5=strength_training, 6=fitness_equipment, 7=hiking.
|
|
458
459
|
Step type IDs: warmup (1), cooldown (2), interval (3), recovery (4), rest (5).
|
|
459
|
-
End condition IDs:
|
|
460
|
+
End condition IDs: time (2, value in seconds), distance (3, value in meters), lap.button (7, no value), reps (10, value = rep count — use for strength exercises).
|
|
460
461
|
Target type IDs: no.target (1), speed (2, m/s range via targetValueOne/targetValueTwo), heart.rate.zone (4, use zoneNumber 1-5), power.zone (11, use zoneNumber).
|
|
461
462
|
|
|
462
463
|
**Available Templates:**
|
|
@@ -628,307 +629,6 @@ Present results as a markdown table: | Tool | Status | Notes |
|
|
|
628
629
|
Count total passed vs failed at the end.`);
|
|
629
630
|
});
|
|
630
631
|
}
|
|
631
|
-
// ── Workout Templates (MCP Resources) ──────────────────────────────────────
|
|
632
|
-
// Templates adapted from Taxuspt/garmin_mcp (MIT License, Copyright (c) 2025 Alexandre Domingues)
|
|
633
|
-
// https://github.com/Taxuspt/garmin_mcp/blob/main/src/garmin_mcp/workout_templates.py
|
|
634
|
-
const WORKOUT_TEMPLATES = {
|
|
635
|
-
"simple-run": {
|
|
636
|
-
workoutName: "Simple Run",
|
|
637
|
-
sportType: { sportTypeId: 1, sportTypeKey: "running" },
|
|
638
|
-
workoutSegments: [
|
|
639
|
-
{
|
|
640
|
-
segmentOrder: 1,
|
|
641
|
-
sportType: { sportTypeId: 1, sportTypeKey: "running" },
|
|
642
|
-
workoutSteps: [
|
|
643
|
-
{
|
|
644
|
-
type: "ExecutableStepDTO",
|
|
645
|
-
stepOrder: 1,
|
|
646
|
-
stepType: { stepTypeId: 1, stepTypeKey: "warmup" },
|
|
647
|
-
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
648
|
-
endConditionValue: 300.0,
|
|
649
|
-
targetType: {
|
|
650
|
-
workoutTargetTypeId: 1,
|
|
651
|
-
workoutTargetTypeKey: "no.target",
|
|
652
|
-
},
|
|
653
|
-
},
|
|
654
|
-
{
|
|
655
|
-
type: "ExecutableStepDTO",
|
|
656
|
-
stepOrder: 2,
|
|
657
|
-
stepType: { stepTypeId: 3, stepTypeKey: "interval" },
|
|
658
|
-
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
659
|
-
endConditionValue: 1800.0,
|
|
660
|
-
targetType: {
|
|
661
|
-
workoutTargetTypeId: 1,
|
|
662
|
-
workoutTargetTypeKey: "no.target",
|
|
663
|
-
},
|
|
664
|
-
},
|
|
665
|
-
{
|
|
666
|
-
type: "ExecutableStepDTO",
|
|
667
|
-
stepOrder: 3,
|
|
668
|
-
stepType: { stepTypeId: 2, stepTypeKey: "cooldown" },
|
|
669
|
-
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
670
|
-
endConditionValue: 300.0,
|
|
671
|
-
targetType: {
|
|
672
|
-
workoutTargetTypeId: 1,
|
|
673
|
-
workoutTargetTypeKey: "no.target",
|
|
674
|
-
},
|
|
675
|
-
},
|
|
676
|
-
],
|
|
677
|
-
},
|
|
678
|
-
],
|
|
679
|
-
},
|
|
680
|
-
"interval-running": {
|
|
681
|
-
workoutName: "Interval Running",
|
|
682
|
-
sportType: { sportTypeId: 1, sportTypeKey: "running" },
|
|
683
|
-
workoutSegments: [
|
|
684
|
-
{
|
|
685
|
-
segmentOrder: 1,
|
|
686
|
-
sportType: { sportTypeId: 1, sportTypeKey: "running" },
|
|
687
|
-
workoutSteps: [
|
|
688
|
-
{
|
|
689
|
-
type: "ExecutableStepDTO",
|
|
690
|
-
stepOrder: 1,
|
|
691
|
-
stepType: { stepTypeId: 1, stepTypeKey: "warmup" },
|
|
692
|
-
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
693
|
-
endConditionValue: 600.0,
|
|
694
|
-
targetType: {
|
|
695
|
-
workoutTargetTypeId: 1,
|
|
696
|
-
workoutTargetTypeKey: "no.target",
|
|
697
|
-
},
|
|
698
|
-
},
|
|
699
|
-
{
|
|
700
|
-
type: "RepeatGroupDTO",
|
|
701
|
-
stepOrder: 2,
|
|
702
|
-
numberOfIterations: 6,
|
|
703
|
-
workoutSteps: [
|
|
704
|
-
{
|
|
705
|
-
type: "ExecutableStepDTO",
|
|
706
|
-
stepOrder: 1,
|
|
707
|
-
stepType: { stepTypeId: 3, stepTypeKey: "interval" },
|
|
708
|
-
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
709
|
-
endConditionValue: 60.0,
|
|
710
|
-
targetType: {
|
|
711
|
-
workoutTargetTypeId: 4,
|
|
712
|
-
workoutTargetTypeKey: "heart.rate.zone",
|
|
713
|
-
},
|
|
714
|
-
zoneNumber: 5,
|
|
715
|
-
},
|
|
716
|
-
{
|
|
717
|
-
type: "ExecutableStepDTO",
|
|
718
|
-
stepOrder: 2,
|
|
719
|
-
stepType: { stepTypeId: 4, stepTypeKey: "recovery" },
|
|
720
|
-
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
721
|
-
endConditionValue: 90.0,
|
|
722
|
-
targetType: {
|
|
723
|
-
workoutTargetTypeId: 4,
|
|
724
|
-
workoutTargetTypeKey: "heart.rate.zone",
|
|
725
|
-
},
|
|
726
|
-
zoneNumber: 2,
|
|
727
|
-
},
|
|
728
|
-
],
|
|
729
|
-
},
|
|
730
|
-
{
|
|
731
|
-
type: "ExecutableStepDTO",
|
|
732
|
-
stepOrder: 3,
|
|
733
|
-
stepType: { stepTypeId: 2, stepTypeKey: "cooldown" },
|
|
734
|
-
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
735
|
-
endConditionValue: 600.0,
|
|
736
|
-
targetType: {
|
|
737
|
-
workoutTargetTypeId: 1,
|
|
738
|
-
workoutTargetTypeKey: "no.target",
|
|
739
|
-
},
|
|
740
|
-
},
|
|
741
|
-
],
|
|
742
|
-
},
|
|
743
|
-
],
|
|
744
|
-
},
|
|
745
|
-
"tempo-run": {
|
|
746
|
-
workoutName: "Tempo Run",
|
|
747
|
-
sportType: { sportTypeId: 1, sportTypeKey: "running" },
|
|
748
|
-
workoutSegments: [
|
|
749
|
-
{
|
|
750
|
-
segmentOrder: 1,
|
|
751
|
-
sportType: { sportTypeId: 1, sportTypeKey: "running" },
|
|
752
|
-
workoutSteps: [
|
|
753
|
-
{
|
|
754
|
-
type: "ExecutableStepDTO",
|
|
755
|
-
stepOrder: 1,
|
|
756
|
-
stepType: { stepTypeId: 1, stepTypeKey: "warmup" },
|
|
757
|
-
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
758
|
-
endConditionValue: 600.0,
|
|
759
|
-
targetType: {
|
|
760
|
-
workoutTargetTypeId: 4,
|
|
761
|
-
workoutTargetTypeKey: "heart.rate.zone",
|
|
762
|
-
},
|
|
763
|
-
zoneNumber: 2,
|
|
764
|
-
},
|
|
765
|
-
{
|
|
766
|
-
type: "ExecutableStepDTO",
|
|
767
|
-
stepOrder: 2,
|
|
768
|
-
stepType: { stepTypeId: 3, stepTypeKey: "interval" },
|
|
769
|
-
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
770
|
-
endConditionValue: 1200.0,
|
|
771
|
-
targetType: {
|
|
772
|
-
workoutTargetTypeId: 4,
|
|
773
|
-
workoutTargetTypeKey: "heart.rate.zone",
|
|
774
|
-
},
|
|
775
|
-
zoneNumber: 4,
|
|
776
|
-
},
|
|
777
|
-
{
|
|
778
|
-
type: "ExecutableStepDTO",
|
|
779
|
-
stepOrder: 3,
|
|
780
|
-
stepType: { stepTypeId: 2, stepTypeKey: "cooldown" },
|
|
781
|
-
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
782
|
-
endConditionValue: 600.0,
|
|
783
|
-
targetType: {
|
|
784
|
-
workoutTargetTypeId: 4,
|
|
785
|
-
workoutTargetTypeKey: "heart.rate.zone",
|
|
786
|
-
},
|
|
787
|
-
zoneNumber: 2,
|
|
788
|
-
},
|
|
789
|
-
],
|
|
790
|
-
},
|
|
791
|
-
],
|
|
792
|
-
},
|
|
793
|
-
"strength-circuit": {
|
|
794
|
-
workoutName: "Strength Circuit",
|
|
795
|
-
sportType: { sportTypeId: 6, sportTypeKey: "fitness_equipment" },
|
|
796
|
-
workoutSegments: [
|
|
797
|
-
{
|
|
798
|
-
segmentOrder: 1,
|
|
799
|
-
sportType: { sportTypeId: 6, sportTypeKey: "fitness_equipment" },
|
|
800
|
-
workoutSteps: [
|
|
801
|
-
{
|
|
802
|
-
type: "ExecutableStepDTO",
|
|
803
|
-
stepOrder: 1,
|
|
804
|
-
stepType: { stepTypeId: 1, stepTypeKey: "warmup" },
|
|
805
|
-
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
806
|
-
endConditionValue: 300.0,
|
|
807
|
-
targetType: {
|
|
808
|
-
workoutTargetTypeId: 1,
|
|
809
|
-
workoutTargetTypeKey: "no.target",
|
|
810
|
-
},
|
|
811
|
-
},
|
|
812
|
-
{
|
|
813
|
-
type: "RepeatGroupDTO",
|
|
814
|
-
stepOrder: 2,
|
|
815
|
-
numberOfIterations: 3,
|
|
816
|
-
workoutSteps: [
|
|
817
|
-
{
|
|
818
|
-
type: "ExecutableStepDTO",
|
|
819
|
-
stepOrder: 1,
|
|
820
|
-
stepType: { stepTypeId: 3, stepTypeKey: "interval" },
|
|
821
|
-
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
822
|
-
endConditionValue: 40.0,
|
|
823
|
-
targetType: {
|
|
824
|
-
workoutTargetTypeId: 1,
|
|
825
|
-
workoutTargetTypeKey: "no.target",
|
|
826
|
-
},
|
|
827
|
-
description: "Exercise (e.g. push-ups, squats, rows)",
|
|
828
|
-
},
|
|
829
|
-
{
|
|
830
|
-
type: "ExecutableStepDTO",
|
|
831
|
-
stepOrder: 2,
|
|
832
|
-
stepType: { stepTypeId: 5, stepTypeKey: "rest" },
|
|
833
|
-
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
834
|
-
endConditionValue: 20.0,
|
|
835
|
-
targetType: {
|
|
836
|
-
workoutTargetTypeId: 1,
|
|
837
|
-
workoutTargetTypeKey: "no.target",
|
|
838
|
-
},
|
|
839
|
-
},
|
|
840
|
-
],
|
|
841
|
-
},
|
|
842
|
-
{
|
|
843
|
-
type: "ExecutableStepDTO",
|
|
844
|
-
stepOrder: 3,
|
|
845
|
-
stepType: { stepTypeId: 2, stepTypeKey: "cooldown" },
|
|
846
|
-
endCondition: { conditionTypeId: 2, conditionTypeKey: "time" },
|
|
847
|
-
endConditionValue: 300.0,
|
|
848
|
-
targetType: {
|
|
849
|
-
workoutTargetTypeId: 1,
|
|
850
|
-
workoutTargetTypeKey: "no.target",
|
|
851
|
-
},
|
|
852
|
-
},
|
|
853
|
-
],
|
|
854
|
-
},
|
|
855
|
-
],
|
|
856
|
-
},
|
|
857
|
-
};
|
|
858
|
-
const WORKOUT_STRUCTURE_REFERENCE = `# Garmin Connect Workout JSON Structure Reference
|
|
859
|
-
|
|
860
|
-
## Top-level fields
|
|
861
|
-
- workoutName: string (required)
|
|
862
|
-
- sportType: { sportTypeId: number, sportTypeKey: string } (required)
|
|
863
|
-
- IDs: 1=running, 2=cycling, 3=swimming, 4=walking, 5=multi, 6=fitness_equipment, 7=hiking
|
|
864
|
-
- workoutSegments: array of segment objects (required)
|
|
865
|
-
- description: string (optional)
|
|
866
|
-
|
|
867
|
-
## Segment fields
|
|
868
|
-
- segmentOrder: number (1-based, required)
|
|
869
|
-
- sportType: same as top-level (required)
|
|
870
|
-
- workoutSteps: array of step objects (required)
|
|
871
|
-
|
|
872
|
-
## Step types
|
|
873
|
-
|
|
874
|
-
### ExecutableStepDTO (regular steps)
|
|
875
|
-
- type: "ExecutableStepDTO" (required)
|
|
876
|
-
- stepOrder: number (1-based within the containing steps array)
|
|
877
|
-
- stepType: { stepTypeId: number, stepTypeKey: string }
|
|
878
|
-
- 1="warmup", 2="cooldown", 3="interval", 4="recovery", 5="rest"
|
|
879
|
-
- endCondition: { conditionTypeId: number, conditionTypeKey: string }
|
|
880
|
-
- 1="distance" (endConditionValue in meters)
|
|
881
|
-
- 2="time" (endConditionValue in seconds)
|
|
882
|
-
- 7="lap.button" (press lap button; no endConditionValue needed)
|
|
883
|
-
- endConditionValue: number (required for distance/time conditions)
|
|
884
|
-
- targetType: { workoutTargetTypeId: number, workoutTargetTypeKey: string }
|
|
885
|
-
- 1="no.target"
|
|
886
|
-
- 2="speed" — use targetValueOne/targetValueTwo (m/s)
|
|
887
|
-
- 4="heart.rate.zone" — use zoneNumber (1-5), NOT targetValueOne/targetValueTwo
|
|
888
|
-
- 6="cadence" — use targetValueOne/targetValueTwo (steps per minute)
|
|
889
|
-
- 11="power.zone" — use zoneNumber
|
|
890
|
-
- zoneNumber: number 1-5 (for heart.rate.zone or power.zone targets only)
|
|
891
|
-
- targetValueOne: number (lower bound for speed/cadence ranges)
|
|
892
|
-
- targetValueTwo: number (upper bound for speed/cadence ranges)
|
|
893
|
-
- description: string (optional, displayed on device)
|
|
894
|
-
|
|
895
|
-
### RepeatGroupDTO (repeat blocks)
|
|
896
|
-
- type: "RepeatGroupDTO" (required)
|
|
897
|
-
- stepOrder: number (1-based within the containing steps array)
|
|
898
|
-
- numberOfIterations: number (how many times to repeat)
|
|
899
|
-
- workoutSteps: array of ExecutableStepDTO (the steps to repeat)
|
|
900
|
-
- stepOrder within this array is 1-based and independent of the parent
|
|
901
|
-
|
|
902
|
-
## Notes
|
|
903
|
-
- NEVER use targetValueOne/targetValueTwo for heart rate zones — use zoneNumber instead.
|
|
904
|
-
Using targetValueOne/targetValueTwo with heart.rate.zone target type causes Garmin to
|
|
905
|
-
misinterpret the values as pace (m/s), resulting in impossible paces like ~11 sec/mile.
|
|
906
|
-
- RepeatGroupDTO cannot be nested inside another RepeatGroupDTO.
|
|
907
|
-
- All stepOrder values within the same array must be sequential starting from 1.
|
|
908
|
-
`;
|
|
909
|
-
export function registerResources(server) {
|
|
910
|
-
for (const [name, template] of Object.entries(WORKOUT_TEMPLATES)) {
|
|
911
|
-
const uri = `workout://templates/${name}`;
|
|
912
|
-
server.resource(name, uri, async (resourceUri) => ({
|
|
913
|
-
contents: [
|
|
914
|
-
{
|
|
915
|
-
uri: resourceUri.href,
|
|
916
|
-
mimeType: "application/json",
|
|
917
|
-
text: JSON.stringify(template, null, 2),
|
|
918
|
-
},
|
|
919
|
-
],
|
|
920
|
-
}));
|
|
921
|
-
}
|
|
922
|
-
server.resource("workout-structure-reference", "workout://reference/structure", async (resourceUri) => ({
|
|
923
|
-
contents: [
|
|
924
|
-
{
|
|
925
|
-
uri: resourceUri.href,
|
|
926
|
-
mimeType: "text/markdown",
|
|
927
|
-
text: WORKOUT_STRUCTURE_REFERENCE,
|
|
928
|
-
},
|
|
929
|
-
],
|
|
930
|
-
}));
|
|
931
|
-
}
|
|
932
632
|
/**
|
|
933
633
|
* Minimal zip extraction — finds the first .fit file using the central
|
|
934
634
|
* directory (which always has correct sizes, unlike local headers that
|